1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.editors.ui; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 21 22 import org.eclipse.jface.text.DefaultInformationControl; 23 import org.eclipse.swt.events.DisposeEvent; 24 import org.eclipse.swt.events.DisposeListener; 25 import org.eclipse.swt.events.MouseEvent; 26 import org.eclipse.swt.events.MouseTrackListener; 27 import org.eclipse.swt.graphics.Point; 28 import org.eclipse.swt.layout.GridLayout; 29 import org.eclipse.swt.widgets.Button; 30 import org.eclipse.swt.widgets.Composite; 31 import org.eclipse.swt.widgets.Control; 32 import org.eclipse.swt.widgets.Label; 33 import org.eclipse.swt.widgets.Text; 34 import org.eclipse.ui.forms.SectionPart; 35 import org.eclipse.ui.forms.widgets.FormText; 36 import org.eclipse.ui.forms.widgets.FormToolkit; 37 import org.eclipse.ui.forms.widgets.Section; 38 import org.eclipse.ui.forms.widgets.TableWrapData; 39 import org.eclipse.ui.forms.widgets.TableWrapLayout; 40 41 import java.lang.reflect.Method; 42 43 /** 44 * Helper for the AndroidManifest form editor. 45 * 46 * Helps create a new SectionPart with sensible default parameters, 47 * create default layout or add typical widgets. 48 * 49 * IMPORTANT: This is NOT a generic class. It makes a lot of assumptions on the 50 * UI as used by the form editor for the AndroidManifest. 51 * 52 * TODO: Consider moving to a common package. 53 */ 54 public final class SectionHelper { 55 56 /** 57 * Utility class that derives from SectionPart, constructs the Section with 58 * sensible defaults (with a title and a description) and provide some shorthand 59 * methods for creating typically UI (label and text, form text.) 60 */ 61 static public class ManifestSectionPart extends SectionPart { 62 63 /** 64 * Construct a SectionPart that uses a title bar and a description. 65 * It's up to the caller to call setText() and setDescription(). 66 * <p/> 67 * The section style includes a description and a title bar by default. 68 * 69 * @param body The parent (e.g. FormPage body) 70 * @param toolkit Form Toolkit 71 */ 72 public ManifestSectionPart(Composite body, FormToolkit toolkit) { 73 this(body, toolkit, 0, false); 74 } 75 76 /** 77 * Construct a SectionPart that uses a title bar and a description. 78 * It's up to the caller to call setText() and setDescription(). 79 * <p/> 80 * The section style includes a description and a title bar by default. 81 * You can add extra styles, like Section.TWISTIE. 82 * 83 * @param body The parent (e.g. FormPage body). 84 * @param toolkit Form Toolkit. 85 * @param extra_style Extra styles (on top of description and title bar). 86 * @param use_description True if the Section.DESCRIPTION style should be added. 87 */ 88 public ManifestSectionPart(Composite body, FormToolkit toolkit, 89 int extra_style, boolean use_description) { 90 super(body, toolkit, extra_style | 91 Section.TITLE_BAR | 92 (use_description ? Section.DESCRIPTION : 0)); 93 } 94 95 // Create non-static methods of helpers just for convenience 96 97 /** 98 * Creates a new composite with a TableWrapLayout set with a given number of columns. 99 * 100 * If the parent composite is a Section, the new composite is set as a client. 101 * 102 * @param toolkit Form Toolkit 103 * @param numColumns Desired number of columns. 104 * @return The new composite. 105 */ 106 public Composite createTableLayout(FormToolkit toolkit, int numColumns) { 107 return SectionHelper.createTableLayout(getSection(), toolkit, numColumns); 108 } 109 110 /** 111 * Creates a label widget. 112 * If the parent layout if a TableWrapLayout, maximize it to span over all columns. 113 * 114 * @param parent The parent (e.g. composite from CreateTableLayout()) 115 * @param toolkit Form Toolkit 116 * @param label The string for the label. 117 * @param tooltip An optional tooltip for the label and text. Can be null. 118 * @return The new created label 119 */ 120 public Label createLabel(Composite parent, FormToolkit toolkit, String label, 121 String tooltip) { 122 return SectionHelper.createLabel(parent, toolkit, label, tooltip); 123 } 124 125 /** 126 * Creates two widgets: a label and a text field. 127 * 128 * This expects the parent composite to have a TableWrapLayout with 2 columns. 129 * 130 * @param parent The parent (e.g. composite from CreateTableLayout()) 131 * @param toolkit Form Toolkit 132 * @param label The string for the label. 133 * @param value The initial value of the text field. Can be null. 134 * @param tooltip An optional tooltip for the label and text. Can be null. 135 * @return The new created Text field (the label is not returned) 136 */ 137 public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label, 138 String value, String tooltip) { 139 return SectionHelper.createLabelAndText(parent, toolkit, label, value, tooltip); 140 } 141 142 /** 143 * Creates a FormText widget. 144 * 145 * This expects the parent composite to have a TableWrapLayout with 2 columns. 146 * 147 * @param parent The parent (e.g. composite from CreateTableLayout()) 148 * @param toolkit Form Toolkit 149 * @param isHtml True if the form text will contain HTML that must be interpreted as 150 * rich text (i.e. parse tags & expand URLs). 151 * @param label The string for the label. 152 * @param setupLayoutData indicates whether the created form text receives a TableWrapData 153 * through the setLayoutData method. In some case, creating it will make the table parent 154 * huge, which we don't want. 155 * @return The new created FormText. 156 */ 157 public FormText createFormText(Composite parent, FormToolkit toolkit, boolean isHtml, 158 String label, boolean setupLayoutData) { 159 return SectionHelper.createFormText(parent, toolkit, isHtml, label, setupLayoutData); 160 } 161 162 /** 163 * Forces the section to recompute its layout and redraw. 164 * This is needed after the content of the section has been changed at runtime. 165 */ 166 public void layoutChanged() { 167 Section section = getSection(); 168 169 // Calls getSection().reflow(), which is the same that Section calls 170 // when the expandable state is changed and the height changes. 171 // Since this is protected, some reflection is needed to invoke it. 172 try { 173 Method reflow; 174 reflow = section.getClass().getDeclaredMethod("reflow", (Class<?>[])null); 175 reflow.setAccessible(true); 176 reflow.invoke(section); 177 } catch (Exception e) { 178 AdtPlugin.log(e, "Error when invoking Section.reflow"); 179 } 180 181 section.layout(true /* changed */, true /* all */); 182 } 183 } 184 185 /** 186 * Creates a new composite with a TableWrapLayout set with a given number of columns. 187 * 188 * If the parent composite is a Section, the new composite is set as a client. 189 * 190 * @param composite The parent (e.g. a Section or SectionPart) 191 * @param toolkit Form Toolkit 192 * @param numColumns Desired number of columns. 193 * @return The new composite. 194 */ 195 static public Composite createTableLayout(Composite composite, FormToolkit toolkit, 196 int numColumns) { 197 Composite table = toolkit.createComposite(composite); 198 TableWrapLayout layout = new TableWrapLayout(); 199 layout.numColumns = numColumns; 200 table.setLayout(layout); 201 toolkit.paintBordersFor(table); 202 if (composite instanceof Section) { 203 ((Section) composite).setClient(table); 204 } 205 return table; 206 } 207 208 /** 209 * Creates a new composite with a GridLayout set with a given number of columns. 210 * 211 * If the parent composite is a Section, the new composite is set as a client. 212 * 213 * @param composite The parent (e.g. a Section or SectionPart) 214 * @param toolkit Form Toolkit 215 * @param numColumns Desired number of columns. 216 * @return The new composite. 217 */ 218 static public Composite createGridLayout(Composite composite, FormToolkit toolkit, 219 int numColumns) { 220 Composite grid = toolkit.createComposite(composite); 221 GridLayout layout = new GridLayout(); 222 layout.numColumns = numColumns; 223 grid.setLayout(layout); 224 toolkit.paintBordersFor(grid); 225 if (composite instanceof Section) { 226 ((Section) composite).setClient(grid); 227 } 228 return grid; 229 } 230 231 /** 232 * Creates two widgets: a label and a text field. 233 * 234 * This expects the parent composite to have a TableWrapLayout with 2 columns. 235 * 236 * @param parent The parent (e.g. composite from CreateTableLayout()) 237 * @param toolkit Form Toolkit 238 * @param label_text The string for the label. 239 * @param value The initial value of the text field. Can be null. 240 * @param tooltip An optional tooltip for the label and text. Can be null. 241 * @return The new created Text field (the label is not returned) 242 */ 243 static public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label_text, 244 String value, String tooltip) { 245 Label label = toolkit.createLabel(parent, label_text); 246 label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); 247 Text text = toolkit.createText(parent, value); 248 text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); 249 250 addControlTooltip(label, tooltip); 251 return text; 252 } 253 254 /** 255 * Creates a label widget. 256 * If the parent layout if a TableWrapLayout, maximize it to span over all columns. 257 * 258 * @param parent The parent (e.g. composite from CreateTableLayout()) 259 * @param toolkit Form Toolkit 260 * @param label_text The string for the label. 261 * @param tooltip An optional tooltip for the label and text. Can be null. 262 * @return The new created label 263 */ 264 static public Label createLabel(Composite parent, FormToolkit toolkit, String label_text, 265 String tooltip) { 266 Label label = toolkit.createLabel(parent, label_text); 267 268 TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); 269 if (parent.getLayout() instanceof TableWrapLayout) { 270 twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; 271 } 272 label.setLayoutData(twd); 273 274 addControlTooltip(label, tooltip); 275 return label; 276 } 277 278 /** 279 * Associates a tooltip with a control. 280 * 281 * This mirrors the behavior from org.eclipse.pde.internal.ui.editor.text.PDETextHover 282 * 283 * @param control The control to which associate the tooltip. 284 * @param tooltip The tooltip string. Can use \n for multi-lines. Will not display if null. 285 */ 286 static public void addControlTooltip(final Control control, String tooltip) { 287 if (control == null || tooltip == null || tooltip.length() == 0) { 288 return; 289 } 290 291 // Some kinds of controls already properly implement tooltip display. 292 if (control instanceof Button) { 293 control.setToolTipText(tooltip); 294 return; 295 } 296 297 control.setToolTipText(null); 298 299 final DefaultInformationControl ic = new DefaultInformationControl(control.getShell()); 300 ic.setInformation(tooltip); 301 Point sz = ic.computeSizeHint(); 302 ic.setSize(sz.x, sz.y); 303 ic.setVisible(false); // initially hidden 304 305 control.addMouseTrackListener(new MouseTrackListener() { 306 @Override 307 public void mouseEnter(MouseEvent e) { 308 } 309 310 @Override 311 public void mouseExit(MouseEvent e) { 312 ic.setVisible(false); 313 } 314 315 @Override 316 public void mouseHover(MouseEvent e) { 317 ic.setLocation(control.toDisplay(10, 25)); // same offset as in PDETextHover 318 ic.setVisible(true); 319 } 320 }); 321 control.addDisposeListener(new DisposeListener() { 322 @Override 323 public void widgetDisposed(DisposeEvent e) { 324 ic.dispose(); 325 } 326 }); 327 } 328 329 /** 330 * Creates a FormText widget. 331 * 332 * This expects the parent composite to have a TableWrapLayout with 2 columns. 333 * 334 * @param parent The parent (e.g. composite from CreateTableLayout()) 335 * @param toolkit Form Toolkit 336 * @param isHtml True if the form text will contain HTML that must be interpreted as 337 * rich text (i.e. parse tags & expand URLs). 338 * @param label The string for the label. 339 * @param setupLayoutData indicates whether the created form text receives a TableWrapData 340 * through the setLayoutData method. In some case, creating it will make the table parent 341 * huge, which we don't want. 342 * @return The new created FormText. 343 */ 344 static public FormText createFormText(Composite parent, FormToolkit toolkit, 345 boolean isHtml, String label, boolean setupLayoutData) { 346 FormText text = toolkit.createFormText(parent, true /* track focus */); 347 if (setupLayoutData) { 348 TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); 349 twd.maxWidth = AndroidXmlEditor.TEXT_WIDTH_HINT; 350 if (parent.getLayout() instanceof TableWrapLayout) { 351 twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; 352 } 353 text.setLayoutData(twd); 354 } 355 text.setWhitespaceNormalized(true); 356 if (isHtml && !label.startsWith("<form>")) { //$NON-NLS-1$ 357 // This assertion is violated, for example by the Class attribute for an activity 358 //assert label.startsWith("<form>") : "HTML for FormText must be wrapped in <form>...</form>"; //$NON-NLS-1$ 359 label = "<form>" + label + "</form>"; //$NON-NLS-1$ //$NON-NLS-2$ 360 } 361 text.setText(label, isHtml /* parseTags */, isHtml /* expandURLs */); 362 return text; 363 } 364 } 365