Home | History | Annotate | Download | only in gle2
      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.jface.resource.JFaceResources;
     22 import org.eclipse.swt.SWT;
     23 import org.eclipse.swt.custom.CLabel;
     24 import org.eclipse.swt.custom.ScrolledComposite;
     25 import org.eclipse.swt.events.ControlAdapter;
     26 import org.eclipse.swt.events.ControlEvent;
     27 import org.eclipse.swt.events.MouseAdapter;
     28 import org.eclipse.swt.events.MouseEvent;
     29 import org.eclipse.swt.events.MouseTrackListener;
     30 import org.eclipse.swt.graphics.Color;
     31 import org.eclipse.swt.graphics.Font;
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT);
    222             }
    223             label.setFont(labelFont);
    224             label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
    225             setHeader(header, label);
    226             label.addMouseListener(new MouseAdapter() {
    227                 @Override
    228                 public void mouseUp(MouseEvent e) {
    229                     if (e.button == 1 && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
    230                         toggle(label, true, mSingle);
    231                     }
    232                 }
    233             });
    234             label.addMouseTrackListener(new MouseTrackListener() {
    235                 @Override
    236                 public void mouseEnter(MouseEvent e) {
    237                     updateBackground(label, true);
    238                 }
    239 
    240                 @Override
    241                 public void mouseExit(MouseEvent e) {
    242                     updateBackground(label, false);
    243                 }
    244 
    245                 @Override
    246                 public void mouseHover(MouseEvent e) {
    247                 }
    248             });
    249 
    250             // Turn off border?
    251             final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL);
    252             ScrollBar verticalBar = scrolledComposite.getVerticalBar();
    253             verticalBar.setIncrement(20);
    254             verticalBar.setPageIncrement(100);
    255 
    256             // Do we need the scrolled composite or can we just look at the next
    257             // wizard in the hierarchy?
    258 
    259             setContentArea(label, scrolledComposite);
    260             scrolledComposite.setExpandHorizontal(true);
    261             scrolledComposite.setExpandVertical(true);
    262             GridData scrollGridData = new GridData(SWT.FILL,
    263                     greedy ? SWT.FILL : SWT.TOP, false, greedy, 1, 1);
    264             scrollGridData.exclude = true;
    265             scrollGridData.grabExcessHorizontalSpace = wrapChildren;
    266             scrolledComposite.setLayoutData(scrollGridData);
    267 
    268             if (wrapChildren) {
    269                 scrolledComposite.addControlListener(new ControlAdapter() {
    270                     @Override
    271                     public void controlResized(ControlEvent e) {
    272                         Rectangle r = scrolledComposite.getClientArea();
    273                         Control content = scrolledComposite.getContent();
    274                         if (content != null && r != null) {
    275                             Point minSize = content.computeSize(r.width, SWT.DEFAULT);
    276                             scrolledComposite.setMinSize(minSize);
    277                             ScrollBar vBar = scrolledComposite.getVerticalBar();
    278                             vBar.setPageIncrement(r.height);
    279                         }
    280                     }
    281                   });
    282             }
    283 
    284             updateIcon(label);
    285             if (expand != null && expand.contains(label.getText())) {
    286                 // Comparing "label.getText()" rather than "header" because we make some
    287                 // tweaks to the label (replacing & with && etc) and in the getExpandedCategories
    288                 // method we return the label texts
    289                 expandLabels.add(label);
    290             }
    291         }
    292 
    293         // Expand the requested categories
    294         for (CLabel label : expandLabels) {
    295             toggle(label, false, false);
    296         }
    297     }
    298 
    299     /** Updates the background gradient of the given header label */
    300     private void updateBackground(CLabel label, boolean mouseOver) {
    301         Display display = label.getDisplay();
    302         label.setBackground(new Color[] {
    303                 display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW),
    304                 display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND),
    305                 display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW)
    306         }, new int[] {
    307                 mouseOver ? 60 : 40, 100
    308         }, true);
    309     }
    310 
    311     /**
    312      * Updates the icon for a header label to be open/close based on the {@link #isOpen}
    313      * state
    314      */
    315     private void updateIcon(CLabel label) {
    316         label.setImage(isOpen(label) ? mOpen : mClosed);
    317     }
    318 
    319     /** Returns true if the content area for the given label is open/showing */
    320     private boolean isOpen(Control label) {
    321         return !((GridData) getContentArea(label).getLayoutData()).exclude;
    322     }
    323 
    324     /** Toggles the visibility of the children of the given label */
    325     private void toggle(CLabel label, boolean performLayout, boolean autoClose) {
    326         if (autoClose) {
    327             collapseAll(true);
    328         }
    329         ScrolledComposite scrolledComposite = getContentArea(label);
    330 
    331         GridData scrollGridData = (GridData) scrolledComposite.getLayoutData();
    332         boolean close = !scrollGridData.exclude;
    333         scrollGridData.exclude = close;
    334         scrolledComposite.setVisible(!close);
    335         updateIcon(label);
    336 
    337         if (!scrollGridData.exclude && scrolledComposite.getContent() == null) {
    338             Object header = getHeader(label);
    339             Composite composite = createChildContainer(scrolledComposite, header, SWT.NONE);
    340             createChildren(composite, header);
    341             while (composite.getParent() != scrolledComposite) {
    342                 composite = composite.getParent();
    343             }
    344             scrolledComposite.setContent(composite);
    345             scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
    346         }
    347 
    348         if (performLayout) {
    349             layout(true);
    350         }
    351     }
    352 
    353     /** Returns the header object for the given header label */
    354     private Object getHeader(Control label) {
    355         return label.getData(KEY_HEADER);
    356     }
    357 
    358     /** Sets the header object for the given header label */
    359     private void setHeader(Object header, final CLabel label) {
    360         label.setData(KEY_HEADER, header);
    361     }
    362 
    363     /** Returns the content area for the given header label */
    364     private ScrolledComposite getContentArea(Control label) {
    365         return (ScrolledComposite) label.getData(KEY_CONTENT);
    366     }
    367 
    368     /** Sets the content area for the given header label */
    369     private void setContentArea(final CLabel label, ScrolledComposite scrolledComposite) {
    370         label.setData(KEY_CONTENT, scrolledComposite);
    371     }
    372 
    373     @Override
    374     protected void checkSubclass() {
    375         // Disable the check that prevents subclassing of SWT components
    376     }
    377 
    378     /**
    379      * Returns the set of expanded categories in the palette. Note: Header labels will have
    380      * escaped ampersand characters with double ampersands.
    381      *
    382      * @return the set of expanded categories in the palette - never null
    383      */
    384     public Set<String> getExpandedCategories() {
    385         Set<String> expanded = new HashSet<String>();
    386         for (Control c : getChildren()) {
    387             if (c instanceof CLabel) {
    388                 if (isOpen(c)) {
    389                     expanded.add(((CLabel) c).getText());
    390                 }
    391             }
    392         }
    393 
    394         return expanded;
    395     }
    396 }
    397