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
17package com.android.ide.eclipse.adt.internal.editors.ui;
18
19import com.android.ide.eclipse.adt.AdtPlugin;
20import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
21
22import org.eclipse.jface.text.DefaultInformationControl;
23import org.eclipse.swt.events.DisposeEvent;
24import org.eclipse.swt.events.DisposeListener;
25import org.eclipse.swt.events.MouseEvent;
26import org.eclipse.swt.events.MouseTrackListener;
27import org.eclipse.swt.graphics.Point;
28import org.eclipse.swt.layout.GridLayout;
29import org.eclipse.swt.widgets.Button;
30import org.eclipse.swt.widgets.Composite;
31import org.eclipse.swt.widgets.Control;
32import org.eclipse.swt.widgets.Label;
33import org.eclipse.swt.widgets.Text;
34import org.eclipse.ui.forms.SectionPart;
35import org.eclipse.ui.forms.widgets.FormText;
36import org.eclipse.ui.forms.widgets.FormToolkit;
37import org.eclipse.ui.forms.widgets.Section;
38import org.eclipse.ui.forms.widgets.TableWrapData;
39import org.eclipse.ui.forms.widgets.TableWrapLayout;
40
41import 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 */
54public 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