| /* |
| * 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; |
| } |
| } |