• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.layout.gle2;
18 
19 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
20 
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.custom.CLabel;
23 import org.eclipse.swt.custom.ScrolledComposite;
24 import org.eclipse.swt.events.ControlAdapter;
25 import org.eclipse.swt.events.ControlEvent;
26 import org.eclipse.swt.events.MouseAdapter;
27 import org.eclipse.swt.events.MouseEvent;
28 import org.eclipse.swt.events.MouseTrackListener;
29 import org.eclipse.swt.graphics.Color;
30 import org.eclipse.swt.graphics.Font;
31 import org.eclipse.swt.graphics.FontData;
32 import org.eclipse.swt.graphics.Image;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.Rectangle;
35 import org.eclipse.swt.layout.GridData;
36 import org.eclipse.swt.layout.GridLayout;
37 import org.eclipse.swt.layout.RowLayout;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Control;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.ScrollBar;
42 
43 import java.util.ArrayList;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Set;
47 
48 /**
49  * The accordion control allows a series of labels with associated content that can be
50  * shown. For more details on accordions, see http://en.wikipedia.org/wiki/Accordion_(GUI)
51  * <p>
52  * This control allows the children to be created lazily. You can also customize the
53  * composite which is created to hold the children items, to for example allow multiple
54  * columns of items rather than just the default vertical stack.
55  * <p>
56  * The visual appearance of the headers is built in; it uses a mild gradient, with a
57  * heavier gradient during mouse-overs. It also uses a bold label along with the eclipse
58  * folder icons.
59  * <p>
60  * The control can be configured to enforce a single category open at any time (the
61  * default), or allowing multiple categories open (where they share the available space).
62  * The control can also be configured to fill the available vertical space for the open
63  * category/categories.
64  */
65 public abstract class AccordionControl extends Composite {
66     /** Pixel spacing between header items */
67     private static final int HEADER_SPACING = 0;
68 
69     /** Pixel spacing between items in the content area */
70     private static final int ITEM_SPACING = 0;
71 
72     private static final String KEY_CONTENT = "content"; //$NON-NLS-1$
73     private static final String KEY_HEADER = "header"; //$NON-NLS-1$
74 
75     private Image mClosed;
76     private Image mOpen;
77     private boolean mSingle = true;
78     private boolean mWrap;
79 
80     /**
81      * Creates the container which will hold the items in a category; this can be
82      * overridden to lay out the children with a different layout than the default
83      * vertical RowLayout
84      */
createChildContainer(Composite parent, Object header, int style)85     protected Composite createChildContainer(Composite parent, Object header, int style) {
86         Composite composite = new Composite(parent, style);
87         if (mWrap) {
88             RowLayout layout = new RowLayout(SWT.HORIZONTAL);
89             layout.center = true;
90             composite.setLayout(layout);
91         } else {
92             RowLayout layout = new RowLayout(SWT.VERTICAL);
93             layout.spacing = ITEM_SPACING;
94             layout.marginHeight = 0;
95             layout.marginWidth = 0;
96             layout.marginLeft = 0;
97             layout.marginTop = 0;
98             layout.marginRight = 0;
99             layout.marginBottom = 0;
100             composite.setLayout(layout);
101         }
102 
103         // TODO - maybe do multi-column arrangement for simple nodes
104         return composite;
105     }
106 
107     /**
108      * Creates the children under a particular header
109      *
110      * @param parent the parent composite to add the SWT items to
111      * @param header the header object that is being opened for the first time
112      */
createChildren(Composite parent, Object header)113     protected abstract void createChildren(Composite parent, Object header);
114 
115     /**
116      * Set whether a single category should be enforced or not (default=true)
117      *
118      * @param single if true, enforce a single category open at a time
119      */
setAutoClose(boolean single)120     public void setAutoClose(boolean single) {
121         mSingle = single;
122     }
123 
124     /**
125      * Returns whether a single category should be enforced or not (default=true)
126      *
127      * @return true if only a single category can be open at a time
128      */
isAutoClose()129     public boolean isAutoClose() {
130         return mSingle;
131     }
132 
133     /**
134      * Returns the labels used as header categories
135      *
136      * @return list of header labels
137      */
getHeaderLabels()138     public List<CLabel> getHeaderLabels() {
139         List<CLabel> headers = new ArrayList<CLabel>();
140         for (Control c : getChildren()) {
141             if (c instanceof CLabel) {
142                 headers.add((CLabel) c);
143             }
144         }
145 
146         return headers;
147     }
148 
149     /**
150      * Show all categories
151      *
152      * @param performLayout if true, call {@link #layout} and {@link #pack} when done
153      */
expandAll(boolean performLayout)154     public void expandAll(boolean performLayout) {
155         for (Control c : getChildren()) {
156             if (c instanceof CLabel) {
157                 if (!isOpen(c)) {
158                     toggle((CLabel) c, false, false);
159                 }
160             }
161         }
162         if (performLayout) {
163             pack();
164             layout();
165         }
166     }
167 
168     /**
169      * Hide all categories
170      *
171      * @param performLayout if true, call {@link #layout} and {@link #pack} when done
172      */
collapseAll(boolean performLayout)173     public void collapseAll(boolean performLayout) {
174         for (Control c : getChildren()) {
175             if (c instanceof CLabel) {
176                 if (isOpen(c)) {
177                     toggle((CLabel) c, false, false);
178                 }
179             }
180         }
181         if (performLayout) {
182             layout();
183         }
184     }
185 
186     /**
187      * Create the composite.
188      *
189      * @param parent the parent widget to add the accordion to
190      * @param style the SWT style mask to use
191      * @param headers a list of headers, whose {@link Object#toString} method should
192      *            produce the heading label
193      * @param greedy if true, grow vertically as much as possible
194      * @param wrapChildren if true, configure the child area to be horizontally laid out
195      *            with wrapping
196      * @param expand Set of headers to expand initially
197      */
AccordionControl(Composite parent, int style, List<?> headers, boolean greedy, boolean wrapChildren, Set<String> expand)198     public AccordionControl(Composite parent, int style, List<?> headers,
199             boolean greedy, boolean wrapChildren, Set<String> expand) {
200         super(parent, style);
201         mWrap = wrapChildren;
202 
203         GridLayout gridLayout = new GridLayout(1, false);
204         gridLayout.verticalSpacing = HEADER_SPACING;
205         gridLayout.horizontalSpacing = 0;
206         gridLayout.marginWidth = 0;
207         gridLayout.marginHeight = 0;
208         setLayout(gridLayout);
209 
210         Font labelFont = null;
211 
212         mOpen = IconFactory.getInstance().getIcon("open-folder");     //$NON-NLS-1$
213         mClosed = IconFactory.getInstance().getIcon("closed-folder"); //$NON-NLS-1$
214         List<CLabel> expandLabels = new ArrayList<CLabel>();
215 
216         for (Object header : headers) {
217             final CLabel label = new CLabel(this, SWT.SHADOW_OUT);
218             label.setText(header.toString().replace("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$
219             updateBackground(label, false);
220             if (labelFont == null) {
221                 labelFont = label.getFont();
222                 FontData normal = labelFont.getFontData()[0];
223                 FontData bold = new FontData(normal.getName(), normal.getHeight(), SWT.BOLD);
224                 labelFont = new Font(null, bold);
225             }
226             label.setFont(labelFont);
227             label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
228             setHeader(header, label);
229             label.addMouseListener(new MouseAdapter() {
230                 @Override
231                 public void mouseUp(MouseEvent e) {
232                     if (e.button == 1 && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
233                         toggle(label, true, mSingle);
234                     }
235                 }
236             });
237             label.addMouseTrackListener(new MouseTrackListener() {
238                 @Override
239                 public void mouseEnter(MouseEvent e) {
240                     updateBackground(label, true);
241                 }
242 
243                 @Override
244                 public void mouseExit(MouseEvent e) {
245                     updateBackground(label, false);
246                 }
247 
248                 @Override
249                 public void mouseHover(MouseEvent e) {
250                 }
251             });
252 
253             // Turn off border?
254             final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL);
255             ScrollBar verticalBar = scrolledComposite.getVerticalBar();
256             verticalBar.setIncrement(20);
257             verticalBar.setPageIncrement(100);
258 
259             // Do we need the scrolled composite or can we just look at the next
260             // wizard in the hierarchy?
261 
262             setContentArea(label, scrolledComposite);
263             scrolledComposite.setExpandHorizontal(true);
264             scrolledComposite.setExpandVertical(true);
265             GridData scrollGridData = new GridData(SWT.FILL,
266                     greedy ? SWT.FILL : SWT.TOP, false, greedy, 1, 1);
267             scrollGridData.exclude = true;
268             scrollGridData.grabExcessHorizontalSpace = wrapChildren;
269             scrolledComposite.setLayoutData(scrollGridData);
270 
271             if (wrapChildren) {
272                 scrolledComposite.addControlListener(new ControlAdapter() {
273                     @Override
274                     public void controlResized(ControlEvent e) {
275                         Rectangle r = scrolledComposite.getClientArea();
276                         Control content = scrolledComposite.getContent();
277                         if (content != null && r != null) {
278                             Point minSize = content.computeSize(r.width, SWT.DEFAULT);
279                             scrolledComposite.setMinSize(minSize);
280                             ScrollBar vBar = scrolledComposite.getVerticalBar();
281                             vBar.setPageIncrement(r.height);
282                         }
283                     }
284                   });
285             }
286 
287             updateIcon(label);
288             if (expand != null && expand.contains(label.getText())) {
289                 // Comparing "label.getText()" rather than "header" because we make some
290                 // tweaks to the label (replacing & with && etc) and in the getExpandedCategories
291                 // method we return the label texts
292                 expandLabels.add(label);
293             }
294         }
295 
296         // Expand the requested categories
297         for (CLabel label : expandLabels) {
298             toggle(label, false, false);
299         }
300     }
301 
302     /** Updates the background gradient of the given header label */
updateBackground(CLabel label, boolean mouseOver)303     private void updateBackground(CLabel label, boolean mouseOver) {
304         Display display = label.getDisplay();
305         label.setBackground(new Color[] {
306                 display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW),
307                 display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND),
308                 display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)
309         }, new int[] {
310                 mouseOver ? 60 : 40, 100
311         }, true);
312     }
313 
314     /**
315      * Updates the icon for a header label to be open/close based on the {@link #isOpen}
316      * state
317      */
updateIcon(CLabel label)318     private void updateIcon(CLabel label) {
319         label.setImage(isOpen(label) ? mOpen : mClosed);
320     }
321 
322     /** Returns true if the content area for the given label is open/showing */
isOpen(Control label)323     private boolean isOpen(Control label) {
324         return !((GridData) getContentArea(label).getLayoutData()).exclude;
325     }
326 
327     /** Toggles the visibility of the children of the given label */
toggle(CLabel label, boolean performLayout, boolean autoClose)328     private void toggle(CLabel label, boolean performLayout, boolean autoClose) {
329         if (autoClose) {
330             collapseAll(true);
331         }
332         ScrolledComposite scrolledComposite = getContentArea(label);
333 
334         GridData scrollGridData = (GridData) scrolledComposite.getLayoutData();
335         boolean close = !scrollGridData.exclude;
336         scrollGridData.exclude = close;
337         scrolledComposite.setVisible(!close);
338         updateIcon(label);
339 
340         if (!scrollGridData.exclude && scrolledComposite.getContent() == null) {
341             Object header = getHeader(label);
342             Composite composite = createChildContainer(scrolledComposite, header, SWT.NONE);
343             createChildren(composite, header);
344             while (composite.getParent() != scrolledComposite) {
345                 composite = composite.getParent();
346             }
347             scrolledComposite.setContent(composite);
348             scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
349         }
350 
351         if (performLayout) {
352             layout(true);
353         }
354     }
355 
356     /** Returns the header object for the given header label */
getHeader(Control label)357     private Object getHeader(Control label) {
358         return label.getData(KEY_HEADER);
359     }
360 
361     /** Sets the header object for the given header label */
setHeader(Object header, final CLabel label)362     private void setHeader(Object header, final CLabel label) {
363         label.setData(KEY_HEADER, header);
364     }
365 
366     /** Returns the content area for the given header label */
getContentArea(Control label)367     private ScrolledComposite getContentArea(Control label) {
368         return (ScrolledComposite) label.getData(KEY_CONTENT);
369     }
370 
371     /** Sets the content area for the given header label */
setContentArea(final CLabel label, ScrolledComposite scrolledComposite)372     private void setContentArea(final CLabel label, ScrolledComposite scrolledComposite) {
373         label.setData(KEY_CONTENT, scrolledComposite);
374     }
375 
376     @Override
checkSubclass()377     protected void checkSubclass() {
378         // Disable the check that prevents subclassing of SWT components
379     }
380 
381     /**
382      * Returns the set of expanded categories in the palette. Note: Header labels will have
383      * escaped ampersand characters with double ampersands.
384      *
385      * @return the set of expanded categories in the palette - never null
386      */
getExpandedCategories()387     public Set<String> getExpandedCategories() {
388         Set<String> expanded = new HashSet<String>();
389         for (Control c : getChildren()) {
390             if (c instanceof CLabel) {
391                 if (isOpen(c)) {
392                     expanded.add(((CLabel) c).getText());
393                 }
394             }
395         }
396 
397         return expanded;
398     }
399 }
400