diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java new file mode 100644 index 000000000..b3dce0756 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.ide.eclipse.adt.internal.editors.IconFactory; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.ScrollBar; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The accordion control allows a series of labels with associated content that can be + * shown. For more details on accordions, see http://en.wikipedia.org/wiki/Accordion_(GUI) + * <p> + * This control allows the children to be created lazily. You can also customize the + * composite which is created to hold the children items, to for example allow multiple + * columns of items rather than just the default vertical stack. + * <p> + * The visual appearance of the headers is built in; it uses a mild gradient, with a + * heavier gradient during mouse-overs. It also uses a bold label along with the eclipse + * folder icons. + * <p> + * The control can be configured to enforce a single category open at any time (the + * default), or allowing multiple categories open (where they share the available space). + * The control can also be configured to fill the available vertical space for the open + * category/categories. + */ +public abstract class AccordionControl extends Composite { + /** Pixel spacing between header items */ + private static final int HEADER_SPACING = 0; + + /** Pixel spacing between items in the content area */ + private static final int ITEM_SPACING = 0; + + private static final String KEY_CONTENT = "content"; //$NON-NLS-1$ + private static final String KEY_HEADER = "header"; //$NON-NLS-1$ + + private Image mClosed; + private Image mOpen; + private boolean mSingle = true; + private boolean mWrap; + + /** + * Creates the container which will hold the items in a category; this can be + * overridden to lay out the children with a different layout than the default + * vertical RowLayout + */ + protected Composite createChildContainer(Composite parent, Object header, int style) { + Composite composite = new Composite(parent, style); + if (mWrap) { + RowLayout layout = new RowLayout(SWT.HORIZONTAL); + layout.center = true; + composite.setLayout(layout); + } else { + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.spacing = ITEM_SPACING; + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.marginLeft = 0; + layout.marginTop = 0; + layout.marginRight = 0; + layout.marginBottom = 0; + composite.setLayout(layout); + } + + // TODO - maybe do multi-column arrangement for simple nodes + return composite; + } + + /** + * Creates the children under a particular header + * + * @param parent the parent composite to add the SWT items to + * @param header the header object that is being opened for the first time + */ + protected abstract void createChildren(Composite parent, Object header); + + /** + * Set whether a single category should be enforced or not (default=true) + * + * @param single if true, enforce a single category open at a time + */ + public void setAutoClose(boolean single) { + mSingle = single; + } + + /** + * Returns whether a single category should be enforced or not (default=true) + * + * @return true if only a single category can be open at a time + */ + public boolean isAutoClose() { + return mSingle; + } + + /** + * Returns the labels used as header categories + * + * @return list of header labels + */ + public List<CLabel> getHeaderLabels() { + List<CLabel> headers = new ArrayList<CLabel>(); + for (Control c : getChildren()) { + if (c instanceof CLabel) { + headers.add((CLabel) c); + } + } + + return headers; + } + + /** + * Show all categories + * + * @param performLayout if true, call {@link #layout} and {@link #pack} when done + */ + public void expandAll(boolean performLayout) { + for (Control c : getChildren()) { + if (c instanceof CLabel) { + if (!isOpen(c)) { + toggle((CLabel) c, false, false); + } + } + } + if (performLayout) { + pack(); + layout(); + } + } + + /** + * Hide all categories + * + * @param performLayout if true, call {@link #layout} and {@link #pack} when done + */ + public void collapseAll(boolean performLayout) { + for (Control c : getChildren()) { + if (c instanceof CLabel) { + if (isOpen(c)) { + toggle((CLabel) c, false, false); + } + } + } + if (performLayout) { + layout(); + } + } + + /** + * Create the composite. + * + * @param parent the parent widget to add the accordion to + * @param style the SWT style mask to use + * @param headers a list of headers, whose {@link Object#toString} method should + * produce the heading label + * @param greedy if true, grow vertically as much as possible + * @param wrapChildren if true, configure the child area to be horizontally laid out + * with wrapping + * @param expand Set of headers to expand initially + */ + public AccordionControl(Composite parent, int style, List<?> headers, + boolean greedy, boolean wrapChildren, Set<String> expand) { + super(parent, style); + mWrap = wrapChildren; + + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.verticalSpacing = HEADER_SPACING; + gridLayout.horizontalSpacing = 0; + gridLayout.marginWidth = 0; + gridLayout.marginHeight = 0; + setLayout(gridLayout); + + Font labelFont = null; + + mOpen = IconFactory.getInstance().getIcon("open-folder"); //$NON-NLS-1$ + mClosed = IconFactory.getInstance().getIcon("closed-folder"); //$NON-NLS-1$ + List<CLabel> expandLabels = new ArrayList<CLabel>(); + + for (Object header : headers) { + final CLabel label = new CLabel(this, SWT.SHADOW_OUT); + label.setText(header.toString().replace("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$ + updateBackground(label, false); + if (labelFont == null) { + labelFont = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT); + } + label.setFont(labelFont); + label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + setHeader(header, label); + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + if (e.button == 1 && (e.stateMask & SWT.MODIFIER_MASK) == 0) { + toggle(label, true, mSingle); + } + } + }); + label.addMouseTrackListener(new MouseTrackListener() { + @Override + public void mouseEnter(MouseEvent e) { + updateBackground(label, true); + } + + @Override + public void mouseExit(MouseEvent e) { + updateBackground(label, false); + } + + @Override + public void mouseHover(MouseEvent e) { + } + }); + + // Turn off border? + final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL); + ScrollBar verticalBar = scrolledComposite.getVerticalBar(); + verticalBar.setIncrement(20); + verticalBar.setPageIncrement(100); + + // Do we need the scrolled composite or can we just look at the next + // wizard in the hierarchy? + + setContentArea(label, scrolledComposite); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.setExpandVertical(true); + GridData scrollGridData = new GridData(SWT.FILL, + greedy ? SWT.FILL : SWT.TOP, false, greedy, 1, 1); + scrollGridData.exclude = true; + scrollGridData.grabExcessHorizontalSpace = wrapChildren; + scrolledComposite.setLayoutData(scrollGridData); + + if (wrapChildren) { + scrolledComposite.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = scrolledComposite.getClientArea(); + Control content = scrolledComposite.getContent(); + if (content != null && r != null) { + Point minSize = content.computeSize(r.width, SWT.DEFAULT); + scrolledComposite.setMinSize(minSize); + ScrollBar vBar = scrolledComposite.getVerticalBar(); + vBar.setPageIncrement(r.height); + } + } + }); + } + + updateIcon(label); + if (expand != null && expand.contains(label.getText())) { + // Comparing "label.getText()" rather than "header" because we make some + // tweaks to the label (replacing & with && etc) and in the getExpandedCategories + // method we return the label texts + expandLabels.add(label); + } + } + + // Expand the requested categories + for (CLabel label : expandLabels) { + toggle(label, false, false); + } + } + + /** Updates the background gradient of the given header label */ + private void updateBackground(CLabel label, boolean mouseOver) { + Display display = label.getDisplay(); + label.setBackground(new Color[] { + display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW), + display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND), + display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW) + }, new int[] { + mouseOver ? 60 : 40, 100 + }, true); + } + + /** + * Updates the icon for a header label to be open/close based on the {@link #isOpen} + * state + */ + private void updateIcon(CLabel label) { + label.setImage(isOpen(label) ? mOpen : mClosed); + } + + /** Returns true if the content area for the given label is open/showing */ + private boolean isOpen(Control label) { + return !((GridData) getContentArea(label).getLayoutData()).exclude; + } + + /** Toggles the visibility of the children of the given label */ + private void toggle(CLabel label, boolean performLayout, boolean autoClose) { + if (autoClose) { + collapseAll(true); + } + ScrolledComposite scrolledComposite = getContentArea(label); + + GridData scrollGridData = (GridData) scrolledComposite.getLayoutData(); + boolean close = !scrollGridData.exclude; + scrollGridData.exclude = close; + scrolledComposite.setVisible(!close); + updateIcon(label); + + if (!scrollGridData.exclude && scrolledComposite.getContent() == null) { + Object header = getHeader(label); + Composite composite = createChildContainer(scrolledComposite, header, SWT.NONE); + createChildren(composite, header); + while (composite.getParent() != scrolledComposite) { + composite = composite.getParent(); + } + scrolledComposite.setContent(composite); + scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + } + + if (performLayout) { + layout(true); + } + } + + /** Returns the header object for the given header label */ + private Object getHeader(Control label) { + return label.getData(KEY_HEADER); + } + + /** Sets the header object for the given header label */ + private void setHeader(Object header, final CLabel label) { + label.setData(KEY_HEADER, header); + } + + /** Returns the content area for the given header label */ + private ScrolledComposite getContentArea(Control label) { + return (ScrolledComposite) label.getData(KEY_CONTENT); + } + + /** Sets the content area for the given header label */ + private void setContentArea(final CLabel label, ScrolledComposite scrolledComposite) { + label.setData(KEY_CONTENT, scrolledComposite); + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + /** + * Returns the set of expanded categories in the palette. Note: Header labels will have + * escaped ampersand characters with double ampersands. + * + * @return the set of expanded categories in the palette - never null + */ + public Set<String> getExpandedCategories() { + Set<String> expanded = new HashSet<String>(); + for (Control c : getChildren()) { + if (c instanceof CLabel) { + if (isOpen(c)) { + expanded.add(((CLabel) c).getText()); + } + } + } + + return expanded; + } +} |