aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java
diff options
context:
space:
mode:
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.java396
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;
+ }
+}