| /******************************************************************************* |
| * Copyright (c) 2000, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Kai Nacke - Fix for Bug 202382 |
| * Bryan Hunt - Fix for Bug 245457 |
| * Didier Villevalois - Fix for Bug 178534 |
| * Robin Stocker - Fix for Bug 193034 (tool tip also on text) |
| * Alena Laskavaia - Bug 481604, Bug 482024 |
| * Ralf Petter <ralf.petter@gmail.com> - Bug 183675 |
| *******************************************************************************/ |
| package org.eclipse.ui.forms.widgets; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Menu; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.forms.events.ExpansionEvent; |
| import org.eclipse.ui.forms.events.HyperlinkAdapter; |
| import org.eclipse.ui.forms.events.HyperlinkEvent; |
| import org.eclipse.ui.forms.events.IExpansionListener; |
| import org.eclipse.ui.internal.forms.widgets.FormUtil; |
| import org.eclipse.ui.internal.forms.widgets.FormsResources; |
| |
| /** |
| * This composite is capable of expanding or collapsing a single client that is |
| * its direct child. The composite renders an expansion toggle affordance |
| * (according to the chosen style), and a title that also acts as a hyperlink |
| * (can be selected and is traversable). The client is laid out below the title |
| * when expanded, or hidden when collapsed. |
| * <p> |
| * The widget can be instantiated as-is, or subclassed to modify some aspects of |
| * it. * |
| * <p> |
| * Since 3.1, left/right arrow keys can be used to control the expansion state. |
| * If several expandable composites are created in the same parent, up/down |
| * arrow keys can be used to traverse between them. Expandable text accepts |
| * mnemonics and mnemonic activation will toggle the expansion state. |
| * |
| * <p> |
| * While expandable composite recognize that different styles can be used to |
| * render the title bar, and even defines the constants for these styles |
| * (<code>TITLE_BAR</code> and <code>SHORT_TITLE_BAR</code> the actual painting |
| * is done in the subclasses. |
| * |
| * @see Section |
| * @since 3.0 |
| */ |
| public class ExpandableComposite extends Canvas { |
| /** |
| * If this style is used, a twistie will be used to render the expansion |
| * toggle. |
| */ |
| public static final int TWISTIE = 1 << 1; |
| |
| /** |
| * If this style is used, a tree node with either + or - signs will be used |
| * to render the expansion toggle. |
| */ |
| public static final int TREE_NODE = 1 << 2; |
| |
| /** |
| * If this style is used, the title text will be rendered as a hyperlink |
| * that can individually accept focus. Otherwise, it will still act like a |
| * hyperlink, but only the toggle control will accept focus. |
| */ |
| public static final int FOCUS_TITLE = 1 << 3; |
| |
| /** |
| * If this style is used, the client origin will be vertically aligned with |
| * the title text. Otherwise, it will start at x = 0. |
| */ |
| public static final int CLIENT_INDENT = 1 << 4; |
| |
| /** |
| * If this style is used, computed size of the composite will take the |
| * client width into consideration only in the expanded state. Otherwise, |
| * client width will always be taken into account. |
| */ |
| public static final int COMPACT = 1 << 5; |
| |
| /** |
| * If this style is used, the control will be created in the expanded state. |
| * This state can later be changed programmatically or by the user if |
| * TWISTIE or TREE_NODE style is used. |
| */ |
| public static final int EXPANDED = 1 << 6; |
| |
| /** |
| * If this style is used, title bar decoration will be painted behind the |
| * text. |
| */ |
| public static final int TITLE_BAR = 1 << 8; |
| |
| /** |
| * If this style is used, a short version of the title bar decoration will |
| * be painted behind the text. This style is useful when a more discrete |
| * option is needed for the title bar. |
| * |
| * @since 3.1 |
| */ |
| public static final int SHORT_TITLE_BAR = 1 << 9; |
| |
| /** |
| * If this style is used, title will not be rendered. |
| */ |
| public static final int NO_TITLE = 1 << 12; |
| |
| /** |
| * By default, text client is right-aligned. If this style is used, it will |
| * be positioned after the text control and vertically centered with it. |
| */ |
| public static final int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13; |
| |
| /** |
| * By default, a focus box is painted around the title when it receives focus. |
| * If this style is used, the focus box will not be painted. This style does |
| * not apply when FOCUS_TITLE is used. |
| * @since 3.5 |
| */ |
| public static final int NO_TITLE_FOCUS_BOX = 1 << 14; |
| |
| /** |
| * Width of the margin that will be added around the control (default is 0). |
| */ |
| public int marginWidth = 0; |
| |
| /** |
| * Height of the margin that will be added around the control (default is |
| * 0). |
| */ |
| public int marginHeight = 0; |
| |
| /** |
| * Vertical spacing between the title area and the composite client control |
| * (default is 3). |
| */ |
| public int clientVerticalSpacing = 3; |
| |
| /** |
| * Vertical spacing between the title area and the description control |
| * (default is 0). The description control is normally placed at the new |
| * line as defined in the font used to render it. This value will be added |
| * to it. |
| * |
| * @since 3.3 |
| */ |
| public int descriptionVerticalSpacing = 0; |
| |
| /** |
| * Horizontal margin around the inside of the title bar area when TITLE_BAR |
| * or SHORT_TITLE_BAR style is used. This variable is not used otherwise. |
| * |
| * @since 3.3 |
| */ |
| public int titleBarTextMarginWidth = 6; |
| |
| /** |
| * The toggle widget used to expand the composite. |
| */ |
| protected ToggleHyperlink toggle; |
| |
| /** |
| * The text label for the title. |
| */ |
| protected Control textLabel; |
| |
| /** |
| * @deprecated this variable was left as protected by mistake. It will be |
| * turned into static and hidden in the future versions. Do not |
| * use them and do not change its value. |
| */ |
| @Deprecated |
| protected int VGAP = 3; |
| /** |
| * @deprecated this variable was left as protected by mistake. It will be |
| * turned into static and hidden in the future versions. Do not |
| * use it and do not change its value. |
| */ |
| @Deprecated |
| protected int GAP = 4; |
| |
| static final int IGAP = 4; |
| static final int IVGAP = 3; |
| |
| private static final Point NULL_SIZE = new Point(0, 0); |
| |
| private static final int VSPACE = 3; |
| |
| private static final int SEPARATOR_HEIGHT = 2; |
| |
| private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED; |
| |
| private boolean expanded; |
| |
| private Control textClient; |
| |
| private Control client; |
| |
| private ListenerList<IExpansionListener> listeners = new ListenerList<>(); |
| |
| private Color titleBarForeground; |
| |
| private class ExpandableLayout extends Layout implements ILayoutExtension { |
| |
| private static final int MIN_WIDTH = -2; |
| |
| private SizeCache toggleCache = new SizeCache(); |
| |
| private SizeCache textClientCache = new SizeCache(); |
| |
| private SizeCache textLabelCache = new SizeCache(); |
| |
| private SizeCache descriptionCache = new SizeCache(); |
| |
| private SizeCache clientCache = new SizeCache(); |
| |
| private void initCache(boolean shouldFlush) { |
| toggleCache.setControl(toggle); |
| textClientCache.setControl(textClient); |
| textLabelCache.setControl(textLabel); |
| descriptionCache.setControl(getDescriptionControl()); |
| clientCache.setControl(client); |
| |
| if (shouldFlush) { |
| toggleCache.flush(); |
| textClientCache.flush(); |
| textLabelCache.flush(); |
| descriptionCache.flush(); |
| clientCache.flush(); |
| } |
| } |
| |
| @Override |
| protected void layout(Composite parent, boolean changed) { |
| initCache(changed); |
| |
| Rectangle clientArea = parent.getClientArea(); |
| int thmargin = 0; |
| int tvmargin = 0; |
| |
| if (hasTitleBar()) { |
| thmargin = titleBarTextMarginWidth; |
| tvmargin = IVGAP; |
| } |
| int x = marginWidth + thmargin; |
| int y = marginHeight + tvmargin; |
| // toggle |
| Point toggleSize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| |
| int width = clientArea.width - marginWidth - marginWidth - thmargin - thmargin; |
| if (toggleSize.x > 0) |
| width -= toggleSize.x + IGAP; |
| |
| // TODO: This code is common between computeSize and layout |
| int gapBetweenTcAndLabel = (textClient != null && textLabel != null) ? IGAP : 0; |
| |
| int widthForTcAndLabel = Math.max(0, width - gapBetweenTcAndLabel); |
| |
| Point tcDefault = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| Point labelDefault = this.textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| |
| int tcWidthBeforeSplit = Math.min(width, tcDefault.x); |
| int labelWidthBeforeSplit = Math.min(width, labelDefault.x); |
| |
| int tcWidthAfterSplit = tcWidthBeforeSplit; |
| int labelWidthAfterSplit = labelWidthBeforeSplit; |
| |
| int expectedWidthForTcAndLabel = tcWidthBeforeSplit + labelWidthBeforeSplit; |
| |
| if (expectedWidthForTcAndLabel > widthForTcAndLabel) { |
| // this is heuristic since we don't have a reliable way to find |
| // out if control can wrap. It checks if width of each label or |
| // textClient is less then half |
| // and gives them what they asked in this case |
| if (labelWidthBeforeSplit < widthForTcAndLabel / 2) { |
| labelWidthAfterSplit = labelWidthBeforeSplit; |
| } else { |
| labelWidthAfterSplit = widthForTcAndLabel * labelWidthBeforeSplit |
| / expectedWidthForTcAndLabel; |
| } |
| |
| if (tcWidthBeforeSplit < widthForTcAndLabel / 2) { |
| tcWidthAfterSplit = tcWidthBeforeSplit; |
| labelWidthAfterSplit = widthForTcAndLabel - tcWidthAfterSplit; |
| } else { |
| tcWidthAfterSplit = widthForTcAndLabel - labelWidthAfterSplit; |
| } |
| } |
| |
| // TODO: Add support for fill alignment of textControl |
| |
| Point tcsize = textClientCache.computeSize(tcWidthAfterSplit, SWT.DEFAULT); |
| Point size = textLabelCache.computeSize(labelWidthAfterSplit, SWT.DEFAULT); |
| |
| int height = Math.max(tcsize.y, size.y); // max of label/text client |
| height = Math.max(height, toggleSize.y); // or max of toggle |
| |
| boolean leftAlignment = textClient != null && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0; |
| if (toggle != null) { |
| // if label control is absent we vertically center the toggle, |
| // because the text client is usually a lot thicker |
| int ty = (height - toggleSize.y) / 2 + 1; |
| ty = Math.max(ty, 0); |
| ty += marginHeight + tvmargin; |
| toggle.setLocation(x, ty); |
| toggle.setSize(toggleSize); |
| x += toggleSize.x + IGAP; |
| } |
| if (textLabel != null) { |
| int ty = y; |
| if (leftAlignment) { |
| if (size.y < tcsize.y) |
| ty = tcsize.y / 2 - size.y / 2 + marginHeight |
| + tvmargin; |
| } |
| textLabelCache.setBounds(x, ty, size.x, size.y); |
| } |
| |
| if (textClient != null) { |
| int tcwidth = clientArea.width - marginWidth - marginWidth - thmargin - thmargin; |
| if (toggleSize.x > 0) |
| tcwidth -= toggleSize.x + IGAP; |
| if (size.x > 0) |
| tcwidth -= size.x + IGAP; |
| tcwidth = Math.min(tcsize.x, tcwidth); |
| if (tcwidth < 0) |
| tcwidth = 0; |
| int tcx; |
| if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) { |
| tcx = x + ((size.x > 0) ? size.x + IGAP : 0); |
| } else { |
| tcx = clientArea.width - tcwidth - marginWidth - thmargin; |
| } |
| textClientCache.setBounds(tcx, y, tcwidth, height); |
| } |
| |
| y += height; |
| if (hasTitleBar()) |
| y += tvmargin; |
| Control separatorControl = getSeparatorControl(); |
| if (separatorControl != null) { |
| y += VSPACE; |
| separatorControl.setBounds(marginWidth, y, |
| clientArea.width - marginWidth - marginWidth, |
| SEPARATOR_HEIGHT); |
| y += SEPARATOR_HEIGHT; |
| } |
| if (expanded && client != null) { |
| int areaWidth = clientArea.width - marginWidth - thmargin; |
| int cx = marginWidth + thmargin; |
| if ((expansionStyle & CLIENT_INDENT) != 0) { |
| cx = x; |
| } |
| areaWidth -= cx; |
| Control desc = getDescriptionControl(); |
| if (desc != null) { |
| if (separatorControl != null) { |
| y += VSPACE; |
| } |
| Point dsize = descriptionCache.computeSize(areaWidth, SWT.DEFAULT); |
| y += descriptionVerticalSpacing; |
| descriptionCache.setBounds(cx, y, areaWidth, dsize.y); |
| y += dsize.y; |
| } |
| y += clientVerticalSpacing; |
| int cwidth = areaWidth; |
| int cheight = clientArea.height - marginHeight - marginHeight - y; |
| clientCache.setBounds(cx, y, cwidth, cheight); |
| } |
| } |
| |
| @Override |
| protected Point computeSize(Composite parent, int wHint, int hHint, |
| boolean changed) { |
| initCache(changed); |
| |
| Point toggleSize = NULL_SIZE; |
| int toggleWidthPlusGap = 0; |
| if (toggle != null) { |
| toggleSize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| toggleWidthPlusGap = toggleSize.x + IGAP; |
| } |
| int thmargin = 0; |
| int tvmargin = 0; |
| |
| if (hasTitleBar()) { |
| thmargin = titleBarTextMarginWidth; |
| tvmargin = IVGAP; |
| } |
| |
| // TODO: This code is common between computeSize and layout |
| int gapBetweenTcAndLabel = (textClient != null && textLabel != null) ? IGAP : 0; |
| |
| Point tcDefault = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| Point labelDefault = this.textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| |
| int width = 0; |
| if (wHint == SWT.DEFAULT || wHint == MIN_WIDTH) { |
| width += toggleWidthPlusGap; |
| width += labelDefault.x; |
| width += gapBetweenTcAndLabel; |
| width += tcDefault.x; |
| } else { |
| width = wHint - marginWidth - marginWidth - thmargin - thmargin; |
| } |
| |
| width = Math.max(0, width); |
| |
| int widthForTcAndLabel = Math.max(0, width - gapBetweenTcAndLabel - toggleWidthPlusGap); |
| |
| int tcWidthBeforeSplit = Math.min(width, tcDefault.x); |
| int labelWidthBeforeSplit = Math.min(width, labelDefault.x); |
| |
| int tcWidthAfterSplit = tcWidthBeforeSplit; |
| int labelWidthAfterSplit = labelWidthBeforeSplit; |
| |
| int expectedWidthForTcAndLabel = tcWidthBeforeSplit + labelWidthBeforeSplit; |
| |
| if (expectedWidthForTcAndLabel > widthForTcAndLabel) { |
| labelWidthAfterSplit = widthForTcAndLabel * labelWidthBeforeSplit / expectedWidthForTcAndLabel; |
| tcWidthAfterSplit = widthForTcAndLabel - labelWidthAfterSplit; |
| } |
| |
| // TODO: Add support for fill alignment of textControl |
| |
| Point tcsize = textClientCache.computeSize(tcWidthAfterSplit, SWT.DEFAULT); |
| Point size = textLabelCache.computeSize(labelWidthAfterSplit, SWT.DEFAULT); |
| |
| int height = Math.max(tcsize.y, size.y); // max of label/text client |
| height = Math.max(height, toggleSize.y); // or max of toggle |
| |
| if (getSeparatorControl() != null) { |
| height += VSPACE + SEPARATOR_HEIGHT; |
| } |
| // if (hasTitleBar()) |
| // height += VSPACE; |
| if ((expanded || (expansionStyle & COMPACT) == 0) && client != null) { |
| int cwHint = wHint; |
| int clientIndent = 0; |
| if ((expansionStyle & CLIENT_INDENT) != 0) |
| clientIndent = toggleWidthPlusGap; |
| |
| if (cwHint != SWT.DEFAULT && cwHint != MIN_WIDTH) { |
| cwHint -= marginWidth + marginWidth + thmargin + thmargin; |
| if ((expansionStyle & CLIENT_INDENT) != 0) |
| if (tcsize.x > 0) |
| cwHint -= toggleWidthPlusGap; |
| } |
| Point dsize = null; |
| Point csize; |
| if (cwHint == MIN_WIDTH) { |
| int minWidth = clientCache.computeMinimumWidth(); |
| csize = clientCache.computeSize(minWidth, SWT.DEFAULT); |
| } else { |
| csize = clientCache.computeSize(cwHint, SWT.DEFAULT); |
| } |
| if (getDescriptionControl() != null) { |
| int dwHint = cwHint; |
| if (dwHint == SWT.DEFAULT || dwHint == MIN_WIDTH) { |
| dwHint = csize.x; |
| if ((expansionStyle & CLIENT_INDENT) != 0) |
| dwHint -= toggleWidthPlusGap; |
| } |
| dsize = descriptionCache.computeSize(dwHint, SWT.DEFAULT); |
| width = Math.max(width, dsize.x + clientIndent); |
| if (expanded) { |
| if (getSeparatorControl() != null) { |
| height += VSPACE; |
| } |
| height += descriptionVerticalSpacing + dsize.y; |
| } |
| } |
| width = Math.max(width, csize.x + clientIndent); |
| if (expanded) { |
| height += clientVerticalSpacing; |
| height += csize.y; |
| } |
| } |
| |
| int resultWidth = width + marginWidth + marginWidth + thmargin + thmargin; |
| |
| if (wHint != SWT.DEFAULT && wHint != MIN_WIDTH) { |
| resultWidth = wHint; |
| } |
| |
| int resultHeight = height + marginHeight + marginHeight + tvmargin + tvmargin; |
| |
| if (hHint != SWT.DEFAULT) { |
| resultHeight = hHint; |
| } |
| |
| Point result = new Point(resultWidth, resultHeight); |
| return result; |
| } |
| |
| @Override |
| public int computeMinimumWidth(Composite parent, boolean changed) { |
| return computeSize(parent, MIN_WIDTH, SWT.DEFAULT, changed).x; |
| } |
| |
| @Override |
| public int computeMaximumWidth(Composite parent, boolean changed) { |
| return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x; |
| } |
| } |
| |
| /** |
| * Creates an expandable composite using a TWISTIE toggle. |
| * |
| * @param parent |
| * the parent composite |
| * @param style |
| * SWT style bits |
| */ |
| public ExpandableComposite(Composite parent, int style) { |
| this(parent, style, TWISTIE); |
| } |
| |
| /** |
| * Creates the expandable composite in the provided parent. |
| * |
| * @param parent |
| * the parent |
| * @param style |
| * the control style (as expected by SWT subclass) |
| * @param expansionStyle |
| * the style of the expansion widget (TREE_NODE, TWISTIE, |
| * CLIENT_INDENT, COMPACT, FOCUS_TITLE, |
| * LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE) |
| */ |
| public ExpandableComposite(Composite parent, int style, int expansionStyle) { |
| super(parent, style); |
| this.expansionStyle = expansionStyle; |
| if ((expansionStyle & TITLE_BAR) != 0) |
| setBackgroundMode(SWT.INHERIT_DEFAULT); |
| super.setLayout(new ExpandableLayout()); |
| if (hasTitleBar()) { |
| this.addPaintListener(e -> onPaint(e)); |
| } |
| if ((expansionStyle & TWISTIE) != 0) |
| toggle = new Twistie(this, SWT.NULL); |
| else if ((expansionStyle & TREE_NODE) != 0) |
| toggle = new TreeNode(this, SWT.NULL); |
| else |
| expanded = true; |
| if ((expansionStyle & EXPANDED) != 0) |
| expanded = true; |
| if (toggle != null) { |
| toggle.setExpanded(expanded); |
| toggle.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| toggleState(); |
| } |
| }); |
| toggle.addPaintListener(e -> { |
| if (textLabel instanceof Label && !isFixedStyle()) |
| textLabel.setForeground(toggle.hover ? toggle.getHoverDecorationColor() : getTitleBarForeground()); |
| }); |
| toggle.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.keyCode == SWT.ARROW_UP) { |
| verticalMove(false); |
| e.doit = false; |
| } else if (e.keyCode == SWT.ARROW_DOWN) { |
| verticalMove(true); |
| e.doit = false; |
| } |
| } |
| }); |
| if ((getExpansionStyle()&FOCUS_TITLE)==0) { |
| toggle.paintFocus=false; |
| toggle.addFocusListener(new FocusListener() { |
| @Override |
| public void focusGained(FocusEvent e) { |
| if (textLabel != null) { |
| textLabel.redraw(); |
| } |
| } |
| |
| @Override |
| public void focusLost(FocusEvent e) { |
| if (textLabel != null) { |
| textLabel.redraw(); |
| } |
| } |
| }); |
| } |
| } |
| if ((expansionStyle & FOCUS_TITLE) != 0) { |
| Hyperlink link = new Hyperlink(this, SWT.WRAP); |
| link.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(HyperlinkEvent e) { |
| programmaticToggleState(); |
| } |
| }); |
| textLabel = link; |
| } else if ((expansionStyle & NO_TITLE) == 0) { |
| final Label label = new Label(this, SWT.WRAP); |
| if (!isFixedStyle()) { |
| label.setCursor(FormsResources.getHandCursor()); |
| Listener listener = e -> { |
| switch (e.type) { |
| case SWT.MouseDown: |
| if (toggle != null) |
| toggle.setFocus(); |
| break; |
| case SWT.MouseUp: |
| label.setCursor(FormsResources.getBusyCursor()); |
| programmaticToggleState(); |
| label.setCursor(FormsResources.getHandCursor()); |
| break; |
| case SWT.MouseEnter: |
| if (toggle != null) { |
| label.setForeground(toggle.getHoverDecorationColor()); |
| toggle.hover = true; |
| toggle.redraw(); |
| } |
| break; |
| case SWT.MouseExit: |
| if (toggle != null) { |
| label.setForeground(getTitleBarForeground()); |
| toggle.hover = false; |
| toggle.redraw(); |
| } |
| break; |
| case SWT.Paint: |
| if (toggle != null && (getExpansionStyle() & NO_TITLE_FOCUS_BOX) == 0) { |
| paintTitleFocus(e.gc); |
| } |
| break; |
| } |
| }; |
| label.addListener(SWT.MouseDown, listener); |
| label.addListener(SWT.MouseUp, listener); |
| label.addListener(SWT.MouseEnter, listener); |
| label.addListener(SWT.MouseExit, listener); |
| label.addListener(SWT.Paint, listener); |
| } |
| textLabel = label; |
| } |
| if (textLabel != null) { |
| textLabel.setMenu(getMenu()); |
| textLabel.addTraverseListener(e -> { |
| if (e.detail == SWT.TRAVERSE_MNEMONIC) { |
| // steal the mnemonic |
| if (!isVisible() || !isEnabled()) |
| return; |
| if (FormUtil.mnemonicMatch(getText(), e.character)) { |
| e.doit = false; |
| if (!isFixedStyle()) { |
| programmaticToggleState(); |
| } |
| setFocus(); |
| } |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public boolean forceFocus() { |
| return false; |
| } |
| |
| /** |
| * Overrides 'super' to pass the menu to the text label. |
| * |
| * @param menu |
| * the menu from the parent to attach to this control. |
| */ |
| |
| @Override |
| public void setMenu(Menu menu) { |
| if (textLabel != null) |
| textLabel.setMenu(menu); |
| super.setMenu(menu); |
| } |
| |
| /** |
| * Prevents assignment of the layout manager - expandable composite uses its |
| * own layout. |
| */ |
| @Override |
| public final void setLayout(Layout layout) { |
| } |
| |
| /** |
| * Sets the background of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setBackground(Color bg) { |
| super.setBackground(bg); |
| if ((getExpansionStyle() & TITLE_BAR) == 0) { |
| if (textLabel != null) |
| textLabel.setBackground(bg); |
| if (toggle != null) |
| toggle.setBackground(bg); |
| } |
| } |
| |
| /** |
| * Sets the foreground of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setForeground(Color fg) { |
| super.setForeground(fg); |
| if (textLabel != null) |
| textLabel.setForeground(fg); |
| if (toggle != null) |
| toggle.setForeground(fg); |
| } |
| |
| /** |
| * Sets the color of the toggle control. |
| * |
| * @param c |
| * the color object |
| */ |
| public void setToggleColor(Color c) { |
| if (toggle != null) |
| toggle.setDecorationColor(c); |
| } |
| |
| /** |
| * Sets the active color of the toggle control (when the mouse enters the |
| * toggle area). |
| * |
| * @param c |
| * the active color object |
| */ |
| public void setActiveToggleColor(Color c) { |
| if (toggle != null) |
| toggle.setHoverDecorationColor(c); |
| } |
| |
| /** |
| * Sets the fonts of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setFont(Font font) { |
| super.setFont(font); |
| if (textLabel != null) |
| textLabel.setFont(font); |
| if (toggle != null) |
| toggle.setFont(font); |
| } |
| |
| @Override |
| public void setEnabled(boolean enabled) { |
| if (textLabel != null) |
| textLabel.setEnabled(enabled); |
| if (toggle != null) |
| toggle.setEnabled(enabled); |
| super.setEnabled(enabled); |
| } |
| |
| /** |
| * Sets the client of this expandable composite. The client must not be |
| * <samp>null </samp> and must be a direct child of this container. |
| * |
| * @param client |
| * the client that will be expanded or collapsed |
| */ |
| public void setClient(Control client) { |
| Assert.isTrue(client != null && client.getParent().equals(this)); |
| this.client = client; |
| } |
| |
| /** |
| * Returns the current expandable client. |
| * |
| * @return the client control |
| */ |
| public Control getClient() { |
| return client; |
| } |
| |
| /** |
| * Sets the title of the expandable composite. The title will act as a |
| * hyperlink and activating it will toggle the client between expanded and |
| * collapsed state. |
| * |
| * @param title |
| * the new title string |
| * @see #getText() |
| */ |
| public void setText(String title) { |
| if (textLabel instanceof Label) { |
| ((Label) textLabel).setText(title); |
| } else if (textLabel instanceof Hyperlink) { |
| ((Hyperlink) textLabel).setText(title); |
| } else { |
| return; |
| } |
| layout(); |
| } |
| |
| @Override |
| public void setToolTipText(String string) { |
| super.setToolTipText(string); |
| // Also set on label, otherwise it's just on the background without text. |
| if (textLabel instanceof Label) { |
| ((Label) textLabel).setToolTipText(string); |
| } else if (textLabel instanceof Hyperlink) { |
| ((Hyperlink) textLabel).setToolTipText(string); |
| } |
| } |
| |
| /** |
| * Returns the title string. |
| * |
| * @return the title string |
| * @see #setText(String) |
| */ |
| public String getText() { |
| if (textLabel instanceof Label) |
| return ((Label) textLabel).getText(); |
| else if (textLabel instanceof Hyperlink) |
| return ((Hyperlink) textLabel).getText(); |
| else |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Tests the expanded state of the composite. |
| * |
| * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed. |
| */ |
| public boolean isExpanded() { |
| return expanded; |
| } |
| |
| /** |
| * Returns the bitwise-ORed style bits for the expansion control. |
| * |
| * @return the bitwise-ORed style bits for the expansion control |
| */ |
| public int getExpansionStyle() { |
| return expansionStyle; |
| } |
| |
| /** |
| * Programmatically changes expanded state. |
| * |
| * @param expanded |
| * the new expanded state |
| */ |
| public void setExpanded(boolean expanded) { |
| internalSetExpanded(expanded); |
| if (toggle != null) |
| toggle.setExpanded(expanded); |
| } |
| |
| /** |
| * Performs the expansion state change for the expandable control. |
| * |
| * @param expanded |
| * the expansion state |
| */ |
| protected void internalSetExpanded(boolean expanded) { |
| if (this.expanded != expanded) { |
| this.expanded = expanded; |
| if (getDescriptionControl() != null) |
| getDescriptionControl().setVisible(expanded); |
| if (client != null) |
| client.setVisible(expanded); |
| reflow(); |
| } |
| } |
| |
| /** |
| * Adds the listener that will be notified when the expansion state changes. |
| * |
| * @param listener |
| * the listener to add |
| */ |
| public void addExpansionListener(IExpansionListener listener) { |
| listeners.add(listener); |
| } |
| |
| /** |
| * Removes the expansion listener. |
| * |
| * @param listener |
| * the listener to remove |
| */ |
| public void removeExpansionListener(IExpansionListener listener) { |
| listeners.remove(listener); |
| } |
| |
| /** |
| * If TITLE_BAR or SHORT_TITLE_BAR style is used, title bar decoration will |
| * be painted behind the text in this method. The default implementation |
| * does nothing - subclasses are responsible for rendering the title area. |
| * |
| * @param e |
| * the paint event |
| */ |
| protected void onPaint(PaintEvent e) { |
| } |
| |
| /** |
| * Returns description control that will be placed under the title if |
| * present. |
| * |
| * @return the description control or <samp>null </samp> if not used. |
| */ |
| protected Control getDescriptionControl() { |
| return null; |
| } |
| |
| /** |
| * Returns the separator control that will be placed between the title and |
| * the description if present. |
| * |
| * @return the separator control or <samp>null </samp> if not used. |
| */ |
| protected Control getSeparatorControl() { |
| return null; |
| } |
| |
| /** |
| * Computes the size of the expandable composite. |
| * |
| * @see org.eclipse.swt.widgets.Composite#computeSize |
| */ |
| @Override |
| public Point computeSize(int wHint, int hHint, boolean changed) { |
| checkWidget(); |
| Point size; |
| ExpandableLayout layout = (ExpandableLayout) getLayout(); |
| if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) { |
| size = layout.computeSize(this, wHint, hHint, changed); |
| } else { |
| size = new Point(wHint, hHint); |
| } |
| Rectangle trim = computeTrim(0, 0, size.x, size.y); |
| return new Point(trim.width, trim.height); |
| } |
| |
| /** |
| * Returns <samp>true </samp> if the composite is fixed i.e. cannot be |
| * expanded or collapsed. Fixed control will still contain the title, |
| * separator and description (if present) as well as the client, but will be |
| * in the permanent expanded state and the toggle affordance will not be |
| * shown. |
| * |
| * @return <samp>true </samp> if the control is fixed in the expanded state, |
| * <samp>false </samp> if it can be collapsed. |
| */ |
| protected boolean isFixedStyle() { |
| return (expansionStyle & TWISTIE) == 0 |
| && (expansionStyle & TREE_NODE) == 0; |
| } |
| |
| /** |
| * Returns the text client control. |
| * |
| * @return Returns the text client control if specified, or |
| * <code>null</code> if not. |
| */ |
| public Control getTextClient() { |
| return textClient; |
| } |
| |
| /** |
| * Sets the text client control. Text client is a control that is a child of |
| * the expandable composite and is placed to the right of the text. It can |
| * be used to place small image hyperlinks. If more than one control is |
| * needed, use Composite to hold them. Care should be taken that the height |
| * of the control is comparable to the height of the text. |
| * |
| * @param textClient |
| * the textClient to set or <code>null</code> if not needed any |
| * more. |
| */ |
| public void setTextClient(Control textClient) { |
| if (this.textClient != null) |
| this.textClient.dispose(); |
| this.textClient = textClient; |
| } |
| |
| /** |
| * Returns the difference in height between the text and the text client (if |
| * set). This difference can cause vertical alignment problems when two |
| * expandable composites are placed side by side, one with and one without |
| * the text client. Use this method obtain the value to add to either |
| * <code>descriptionVerticalSpacing</code> (if you have description) or |
| * <code>clientVerticalSpacing</code> to correct the alignment of the |
| * expandable without the text client. |
| * |
| * @return the difference in height between the text and the text client or |
| * 0 if no corrective action is needed. |
| * @since 3.3 |
| */ |
| public int getTextClientHeightDifference() { |
| if (textClient == null || textLabel == null) |
| return 0; |
| int theight = textLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; |
| int tcheight = textClient.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; |
| return Math.max(tcheight - theight, 0); |
| } |
| |
| /** |
| * Tests if this expandable composite renders a title bar around the text. |
| * |
| * @return <code>true</code> for <code>TITLE_BAR</code> or |
| * <code>SHORT_TITLE_BAR</code> styles, <code>false</code> |
| * otherwise. |
| */ |
| protected boolean hasTitleBar() { |
| return (getExpansionStyle() & TITLE_BAR) != 0 |
| || (getExpansionStyle() & SHORT_TITLE_BAR) != 0; |
| } |
| |
| /** |
| * Sets the color of the title bar foreground when TITLE_BAR style is used. |
| * |
| * @param color |
| * the title bar foreground |
| */ |
| public void setTitleBarForeground(Color color) { |
| titleBarForeground = color; |
| if (textLabel != null) |
| textLabel.setForeground(color); |
| } |
| |
| /** |
| * Returns the title bar foreground when TITLE_BAR style is used. |
| * |
| * @return the title bar foreground |
| */ |
| public Color getTitleBarForeground() { |
| return titleBarForeground; |
| } |
| |
| // end of APIs |
| |
| private void toggleState() { |
| boolean newState = !isExpanded(); |
| fireExpanding(newState, true); |
| internalSetExpanded(newState); |
| fireExpanding(newState, false); |
| if (newState) |
| FormUtil.ensureVisible(this); |
| } |
| |
| private void fireExpanding(boolean state, boolean before) { |
| int size = listeners.size(); |
| if (size == 0) |
| return; |
| ExpansionEvent e = new ExpansionEvent(this, state); |
| for (IExpansionListener listener : listeners) { |
| if (before) |
| listener.expansionStateChanging(e); |
| else |
| listener.expansionStateChanged(e); |
| } |
| } |
| |
| private void verticalMove(boolean down) { |
| Composite parent = getParent(); |
| Control[] children = parent.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| Control child = children[i]; |
| if (child == this) { |
| ExpandableComposite sibling = getSibling(children, i, down); |
| if (sibling != null && sibling.toggle != null) { |
| sibling.setFocus(); |
| } |
| break; |
| } |
| } |
| } |
| |
| private ExpandableComposite getSibling(Control[] children, int index, |
| boolean down) { |
| int loc = down ? index + 1 : index - 1; |
| while (loc >= 0 && loc < children.length) { |
| Control c = children[loc]; |
| if (c instanceof ExpandableComposite && c.isVisible()) |
| return (ExpandableComposite) c; |
| loc = down ? loc + 1 : loc - 1; |
| } |
| return null; |
| } |
| |
| private void programmaticToggleState() { |
| if (toggle != null) |
| toggle.setExpanded(!toggle.isExpanded()); |
| toggleState(); |
| } |
| |
| private void paintTitleFocus(GC gc) { |
| Point size = textLabel.getSize(); |
| gc.setBackground(textLabel.getBackground()); |
| gc.setForeground(textLabel.getForeground()); |
| if (toggle.isFocusControl()) |
| gc.drawFocus(0, 0, size.x, size.y); |
| } |
| |
| void reflow() { |
| Composite c = this; |
| while (c != null) { |
| c.setRedraw(false); |
| c = c.getParent(); |
| if (c instanceof SharedScrolledComposite || c instanceof Shell) { |
| break; |
| } |
| } |
| c = this; |
| while (c != null) { |
| c.requestLayout(); |
| c = c.getParent(); |
| if (c instanceof SharedScrolledComposite) { |
| ((SharedScrolledComposite) c).reflow(true); |
| break; |
| } |
| } |
| c = this; |
| while (c != null) { |
| c.setRedraw(true); |
| c = c.getParent(); |
| if (c instanceof SharedScrolledComposite || c instanceof Shell) { |
| break; |
| } |
| } |
| } |
| } |