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          */
ManifestSectionPart(Composite body, FormToolkit toolkit)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          */
ManifestSectionPart(Composite body, FormToolkit toolkit, int extra_style, boolean use_description)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          */
createTableLayout(FormToolkit toolkit, int numColumns)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          */
createLabel(Composite parent, FormToolkit toolkit, String label, String tooltip)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          */
createLabelAndText(Composite parent, FormToolkit toolkit, String label, String value, String tooltip)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          */
createFormText(Composite parent, FormToolkit toolkit, boolean isHtml, String label, boolean setupLayoutData)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          */
layoutChanged()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      */
createTableLayout(Composite composite, FormToolkit toolkit, int numColumns)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      */
createGridLayout(Composite composite, FormToolkit toolkit, int numColumns)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      */
createLabelAndText(Composite parent, FormToolkit toolkit, String label_text, String value, String tooltip)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      */
createLabel(Composite parent, FormToolkit toolkit, String label_text, String tooltip)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      */
addControlTooltip(final Control control, String tooltip)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      */
createFormText(Composite parent, FormToolkit toolkit, boolean isHtml, String label, boolean setupLayoutData)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