| /*=============================================================================# |
| # Copyright (c) 2000, 2018 IBM Corporation and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 |
| # |
| # Contributors: |
| # IBM Corporation - org.eclipse.ui.forms: initial API and implementation |
| # Kai Nacke - org.eclipse.ui.forms: Bug 202382 |
| # Bryan Hunt - org.eclipse.ui.forms: Bug 245457 |
| # Didier Villevalois - org.eclipse.ui.forms: Bug 178534 |
| # Alena Laskavaia - org.eclipse.ui.forms: Bug 481604 |
| # Ralf Petter <ralf.petter@gmail.com> - org.eclipse.ui.forms: Bug 183675 |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| // org.eclipse.ui.forms.widgets.ExpandableComposite |
| // a2318aea7aeb731a6b1cadf0e85dd1e1287f4dd1 |
| // removed textClient |
| // without changed layout for wrapping controls (2016-2017) |
| |
| package org.eclipse.statet.internal.r.ui.datafilterview; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.action.LegacyActionTools; |
| import org.eclipse.osgi.service.environment.Constants; |
| 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.events.TraverseEvent; |
| import org.eclipse.swt.events.TraverseListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontMetrics; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.RGB; |
| 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.Display; |
| import org.eclipse.swt.widgets.Event; |
| 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.forms.widgets.FormToolkit; |
| import org.eclipse.ui.forms.widgets.Hyperlink; |
| import org.eclipse.ui.forms.widgets.ILayoutExtension; |
| import org.eclipse.ui.forms.widgets.Section; |
| import org.eclipse.ui.forms.widgets.SharedScrolledComposite; |
| import org.eclipse.ui.forms.widgets.SizeCache; |
| import org.eclipse.ui.forms.widgets.Twistie; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.collections.ImIdentityList; |
| |
| import org.eclipse.statet.ecommons.ui.SharedUIResources; |
| |
| |
| /** |
| * 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 |
| */ |
| public class ExpandableRowComposite extends Canvas { |
| |
| |
| private static class Toggle extends Twistie { |
| |
| public Toggle(final Composite parent, final int style) { |
| super(parent, style); |
| } |
| |
| public void setHover(final boolean on) { |
| this.hover= on; |
| } |
| |
| public boolean getHover() { |
| return this.hover; |
| } |
| |
| @Override |
| protected void paint(final PaintEvent e) { |
| // don't paint focus |
| final GC gc= e.gc; |
| final Rectangle clientArea= getClientArea(); |
| if (clientArea.width == 0 || clientArea.height == 0) { |
| return; |
| } |
| paintHyperlink(gc); |
| } |
| |
| } |
| |
| |
| /** |
| * 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, 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; |
| |
| /** |
| * 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; |
| |
| public static final int IMAGE= 1 << 30; |
| |
| /** |
| * 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 Toggle toggle; |
| |
| /** |
| * The text label for the title. |
| */ |
| protected Control textLabel; |
| |
| private static final int IGAP= 4; |
| private 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 Label imageLabel; |
| |
| private Control client; |
| |
| private final CopyOnWriteIdentityListSet<IExpansionListener> listeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private Color titleBarForeground; |
| |
| private final Rectangle titleHeaderRegion= new Rectangle(0, 0, 0, 0); |
| |
| private class ExpandableLayout extends Layout implements ILayoutExtension { |
| |
| private final SizeCache toggleCache= new SizeCache(); |
| |
| private final SizeCache textClientCache= new SizeCache(); |
| |
| private final SizeCache textLabelCache= new SizeCache(); |
| |
| private final SizeCache descriptionCache= new SizeCache(); |
| |
| private final SizeCache clientCache= new SizeCache(); |
| |
| private void initCache(final boolean shouldFlush) { |
| this.toggleCache.setControl(ExpandableRowComposite.this.toggle); |
| this.textLabelCache.setControl(ExpandableRowComposite.this.textLabel); |
| this.descriptionCache.setControl(getDescriptionControl()); |
| this.clientCache.setControl(ExpandableRowComposite.this.client); |
| |
| if (shouldFlush) { |
| this.toggleCache.flush(); |
| this.textClientCache.flush(); |
| this.textLabelCache.flush(); |
| this.descriptionCache.flush(); |
| this.clientCache.flush(); |
| } |
| } |
| |
| @Override |
| protected void layout(final Composite parent, final boolean changed) { |
| initCache(changed); |
| |
| final Rectangle clientArea= parent.getClientArea(); |
| int thmargin= 0; |
| int tvmargin= 0; |
| |
| if (hasTitleBar()) { |
| thmargin= ExpandableRowComposite.this.titleBarTextMarginWidth; |
| tvmargin= IVGAP; |
| } |
| int x= ExpandableRowComposite.this.marginWidth + thmargin; |
| int y= ExpandableRowComposite.this.marginHeight + tvmargin; |
| Point toggleSize= NULL_SIZE; |
| if (ExpandableRowComposite.this.toggle != null) { |
| toggleSize= this.toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| } |
| Point imageSize= NULL_SIZE; |
| if (ExpandableRowComposite.this.imageLabel != null) { |
| imageSize= ExpandableRowComposite.this.imageLabel.computeSize(16, 16); |
| } |
| int toggleWidth= clientArea.width - ExpandableRowComposite.this.marginWidth - ExpandableRowComposite.this.marginWidth - thmargin - thmargin; |
| if (toggleSize.x > 0) { |
| toggleWidth-= toggleSize.x + IGAP; |
| } |
| if (imageSize.x > 0) { |
| toggleWidth-= imageSize.x; |
| } |
| |
| Point size= NULL_SIZE; |
| if (ExpandableRowComposite.this.textLabel != null) { |
| size= this.textLabelCache.computeSize(toggleWidth, SWT.DEFAULT); |
| |
| if (ExpandableRowComposite.this.textLabel instanceof Label) { |
| final Point defSize= this.textLabelCache.computeSize(SWT.DEFAULT, |
| SWT.DEFAULT); |
| if (defSize.y == size.y) { |
| // One line - pick the smaller of the two widths |
| size.x= Math.min(defSize.x, size.x); |
| } |
| } |
| } |
| int theaderHeight; |
| { final GC gc= new GC(ExpandableRowComposite.this); |
| gc.setFont(getFont()); |
| final FontMetrics fm= gc.getFontMetrics(); |
| theaderHeight= fm.getHeight(); |
| gc.dispose(); |
| |
| if (imageSize.y > theaderHeight) { |
| theaderHeight= imageSize.y; |
| } |
| } |
| if (ExpandableRowComposite.this.toggle != null) { |
| int ty= theaderHeight / 2 - toggleSize.y / 2 + 1; |
| ty= Math.max(ty, 0); |
| ty+= ExpandableRowComposite.this.marginHeight + tvmargin; |
| ExpandableRowComposite.this.toggle.setLocation(x, ty); |
| ExpandableRowComposite.this.toggle.setSize(toggleSize); |
| x+= toggleSize.x + IGAP; |
| } |
| if (ExpandableRowComposite.this.imageLabel != null) { |
| final int iy= theaderHeight / 2 - imageSize.y / 2; |
| ExpandableRowComposite.this.imageLabel.setBounds(x, iy, imageSize.x, imageSize.y); |
| } |
| if (ExpandableRowComposite.this.textLabel != null) { |
| int ty= theaderHeight / 2 - size.y / 2; |
| int tx= x; |
| if (ExpandableRowComposite.this.imageLabel != null) { |
| tx+= imageSize.x + IGAP; |
| } |
| if (Constants.WS_GTK.equals(Platform.getWS())) { |
| size.x+= 1; // See Bug 342610 |
| } |
| |
| this.textLabelCache.setBounds(tx, ty, size.x, size.y); |
| } |
| int tbarHeight= theaderHeight; |
| if (size.y > tbarHeight) { |
| tbarHeight= size.y; |
| } |
| y+= tbarHeight; |
| if (hasTitleBar()) { |
| y+= tvmargin; |
| } |
| final Control separatorControl= getSeparatorControl(); |
| if (separatorControl != null) { |
| y+= VSPACE; |
| separatorControl.setBounds(ExpandableRowComposite.this.marginWidth, y, |
| clientArea.width - ExpandableRowComposite.this.marginWidth - ExpandableRowComposite.this.marginWidth, |
| SEPARATOR_HEIGHT); |
| y+= SEPARATOR_HEIGHT; |
| } |
| if (ExpandableRowComposite.this.expanded |
| && ExpandableRowComposite.this.client != null) { |
| int areaWidth= clientArea.width - ExpandableRowComposite.this.marginWidth - thmargin; |
| int cx= ExpandableRowComposite.this.marginWidth + thmargin; |
| if ((ExpandableRowComposite.this.expansionStyle & CLIENT_INDENT) != 0) { |
| cx= x; |
| } |
| areaWidth-= cx; |
| if (getDescriptionControl() != null) { |
| if (ExpandableRowComposite.this.expanded) { |
| y+= VSPACE; |
| } |
| final Point dsize= this.descriptionCache.computeSize(areaWidth, SWT.DEFAULT); |
| y+= ExpandableRowComposite.this.descriptionVerticalSpacing; |
| this.descriptionCache.setBounds(cx, y, areaWidth, dsize.y); |
| y+= dsize.y + ExpandableRowComposite.this.clientVerticalSpacing; |
| } |
| y+= ExpandableRowComposite.this.clientVerticalSpacing; |
| final int cwidth= areaWidth; |
| final int cheight= clientArea.height - ExpandableRowComposite.this.marginHeight - |
| ExpandableRowComposite.this.marginHeight - y; |
| this.clientCache.setBounds(cx, y, cwidth, cheight); |
| } |
| |
| ExpandableRowComposite.this.titleHeaderRegion.x= ExpandableRowComposite.this.marginWidth; |
| ExpandableRowComposite.this.titleHeaderRegion.y= ExpandableRowComposite.this.marginHeight; |
| ExpandableRowComposite.this.titleHeaderRegion.width= clientArea.width - ExpandableRowComposite.this.marginWidth - ExpandableRowComposite.this.marginWidth; |
| ExpandableRowComposite.this.titleHeaderRegion.height= theaderHeight; |
| } |
| |
| @Override |
| protected Point computeSize(final Composite parent, final int wHint, final int hHint, |
| final boolean changed) { |
| initCache(changed); |
| |
| int width= 0, height= 0; |
| Point toggleSize= NULL_SIZE; |
| int toggleWidth= 0; |
| if (ExpandableRowComposite.this.toggle != null) { |
| toggleSize= this.toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| toggleWidth= toggleSize.x + IGAP; |
| } |
| int thmargin= 0; |
| int tvmargin= 0; |
| |
| Point imageSize= NULL_SIZE; |
| if (ExpandableRowComposite.this.imageLabel != null) { |
| imageSize= ExpandableRowComposite.this.imageLabel.computeSize(16, 16); |
| } |
| |
| if (hasTitleBar()) { |
| thmargin= ExpandableRowComposite.this.titleBarTextMarginWidth; |
| tvmargin= IVGAP; |
| } |
| int innerwHint= wHint; |
| if (innerwHint != SWT.DEFAULT) { |
| innerwHint-= toggleWidth + ExpandableRowComposite.this.marginWidth + ExpandableRowComposite.this.marginWidth + thmargin + thmargin; |
| if (imageSize.x > 0) { |
| innerwHint-= imageSize.x + IGAP; |
| } |
| } |
| |
| int innertHint= innerwHint; |
| |
| Point size= NULL_SIZE; |
| if (ExpandableRowComposite.this.textLabel != null) { |
| size= this.textLabelCache.computeSize(innertHint, SWT.DEFAULT); |
| |
| if (ExpandableRowComposite.this.textLabel instanceof Label) { |
| final Point defSize= this.textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| if (defSize.y == size.y) { |
| // One line - pick the smaller of the two widths |
| size.x= Math.min(defSize.x, size.x); |
| } |
| } |
| } |
| |
| if (toggleWidth > 0) { |
| width+= toggleWidth; |
| } |
| if (imageSize.x > 0) { |
| width+= imageSize.x + IGAP; |
| } |
| if (size.x > 0) { |
| width+= size.x; |
| } |
| if (toggleSize.y > height) { |
| height= toggleSize.y; |
| } |
| if (imageSize.y > height) { |
| height= imageSize.y; |
| } |
| if (size.y > height) { |
| height= size.y; |
| } |
| final Control separatorControl= getSeparatorControl(); |
| if (separatorControl != null) { |
| height+= VSPACE + SEPARATOR_HEIGHT; |
| } |
| |
| if (wHint < width && size.x > 50) { |
| width-= size.x - 50; |
| } |
| |
| // if (hasTitleBar()) |
| // height+= VSPACE; |
| if ((ExpandableRowComposite.this.expanded || (ExpandableRowComposite.this.expansionStyle & COMPACT) == 0) |
| && ExpandableRowComposite.this.client != null) { |
| int cwHint= wHint; |
| int clientIndent= 0; |
| if ((ExpandableRowComposite.this.expansionStyle & CLIENT_INDENT) != 0) { |
| clientIndent= toggleWidth; |
| } |
| |
| if (cwHint != SWT.DEFAULT) { |
| cwHint-= ExpandableRowComposite.this.marginWidth + ExpandableRowComposite.this.marginWidth + thmargin + thmargin; |
| } |
| final Point csize= this.clientCache.computeSize(cwHint, SWT.DEFAULT); |
| if (getDescriptionControl() != null) { |
| int dwHint= cwHint; |
| if (dwHint == SWT.DEFAULT) { |
| dwHint= csize.x; |
| if ((ExpandableRowComposite.this.expansionStyle & CLIENT_INDENT) != 0) { |
| dwHint-= toggleWidth; |
| } |
| } |
| final Point dsize= this.descriptionCache.computeSize(dwHint, SWT.DEFAULT); |
| width= Math.max(width, dsize.x + clientIndent); |
| if (ExpandableRowComposite.this.expanded) { |
| if (separatorControl != null) { |
| height+= VSPACE; |
| } |
| height+= ExpandableRowComposite.this.descriptionVerticalSpacing + dsize.y; |
| } |
| } |
| width= Math.max(width, csize.x + clientIndent); |
| if (ExpandableRowComposite.this.expanded) { |
| height+= ExpandableRowComposite.this.clientVerticalSpacing; |
| height+= csize.y; |
| } |
| } |
| |
| final Point result= new Point(width + ExpandableRowComposite.this.marginWidth + ExpandableRowComposite.this.marginWidth |
| + thmargin + thmargin, height + ExpandableRowComposite.this.marginHeight + ExpandableRowComposite.this.marginHeight |
| + tvmargin + tvmargin); |
| return result; |
| } |
| |
| @Override |
| public int computeMinimumWidth(final Composite parent, final boolean changed) { |
| return computeSize(parent, 0, SWT.DEFAULT, changed).x; |
| } |
| |
| @Override |
| public int computeMaximumWidth(final Composite parent, final 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 ExpandableRowComposite(final Composite parent, final 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 ExpandableRowComposite(final Composite parent, final int style, final int expansionStyle) { |
| super(parent, style); |
| this.expansionStyle= expansionStyle; |
| if ((expansionStyle & TITLE_BAR) != 0) { |
| setBackgroundMode(SWT.INHERIT_DEFAULT); |
| } |
| super.setLayout(new ExpandableLayout()); |
| if (hasTitleBar()) { |
| addPaintListener(this::onPaint); |
| } |
| if ((expansionStyle & TWISTIE) != 0) { |
| this.toggle= new Toggle(this, SWT.NULL); |
| } |
| else { |
| this.expanded= true; |
| } |
| if ((expansionStyle & EXPANDED) != 0) { |
| this.expanded= true; |
| } |
| if (this.toggle != null) { |
| this.toggle.setExpanded(this.expanded); |
| this.toggle.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(final HyperlinkEvent e) { |
| toggleState(); |
| } |
| }); |
| this.toggle.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(final 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) { |
| this.toggle.addFocusListener(new FocusListener() { |
| @Override |
| public void focusGained(final FocusEvent e) { |
| if (ExpandableRowComposite.this.textLabel != null) { |
| ExpandableRowComposite.this.textLabel.redraw(); |
| } |
| } |
| |
| @Override |
| public void focusLost(final FocusEvent e) { |
| if (ExpandableRowComposite.this.textLabel != null) { |
| ExpandableRowComposite.this.textLabel.redraw(); |
| } |
| } |
| }); |
| } |
| } |
| if ((expansionStyle & IMAGE) != 0) { |
| this.imageLabel= new Label(this, SWT.NONE); |
| } |
| if ((expansionStyle & FOCUS_TITLE) != 0) { |
| final Hyperlink link= new Hyperlink(this, SWT.NONE); |
| link.addHyperlinkListener(new HyperlinkAdapter() { |
| @Override |
| public void linkActivated(final HyperlinkEvent e) { |
| programmaticToggleState(); |
| } |
| }); |
| this.textLabel= link; |
| } else { |
| final Label label= new Label(this, SWT.NONE); |
| if (!isFixedStyle()) { |
| final Listener listener= new Listener() { |
| @Override |
| public void handleEvent(final Event e) { |
| switch (e.type) { |
| case SWT.Paint: |
| if (ExpandableRowComposite.this.toggle != null && (getExpansionStyle() & NO_TITLE_FOCUS_BOX) == 0) { |
| paintTitleFocus(e.gc); |
| } |
| break; |
| case SWT.Resize: |
| updateLabelTrim(); |
| break; |
| } |
| } |
| }; |
| label.addListener(SWT.Paint, listener); |
| label.addListener(SWT.Resize, listener); |
| } |
| this.textLabel= label; |
| } |
| if (this.textLabel != null) { |
| this.textLabel.setMenu(getMenu()); |
| this.textLabel.addTraverseListener(new TraverseListener() { |
| @Override |
| public void keyTraversed(final TraverseEvent e) { |
| if (e.detail == SWT.TRAVERSE_MNEMONIC) { |
| // steal the mnemonic |
| if (!isVisible() || !isEnabled()) { |
| return; |
| } |
| if (e.character != 0 |
| && e.character == LegacyActionTools.extractMnemonic(getText())) { |
| e.doit= false; |
| if (!isFixedStyle()) { |
| programmaticToggleState(); |
| } |
| setFocus(); |
| } |
| } |
| } |
| }); |
| } |
| { // Complete Title header |
| final Listener listener= |
| (final Event event) -> { |
| int x= event.x; |
| int y= event.y; |
| if (event.widget != ExpandableRowComposite.this) { |
| final Point p= ((Control) event.widget).getLocation(); |
| x+= p.x; |
| y+= p.y; |
| } |
| switch (event.type) { |
| case SWT.MouseDown: |
| if (ExpandableRowComposite.this.titleHeaderRegion.contains(x, y)) { |
| ExpandableRowComposite.this.toggle.setFocus(); |
| } |
| break; |
| case SWT.MouseUp: |
| if (ExpandableRowComposite.this.titleHeaderRegion.contains(x, y) && event.button == 1) { |
| // textLabel.setCursor(FormsResources.getBusyCursor()); |
| programmaticToggleState(); |
| // textLabel.setCursor(null); |
| } |
| break; |
| case SWT.MouseEnter: |
| case SWT.MouseMove: |
| case SWT.MouseExit: |
| if (ExpandableRowComposite.this.toggle.getHover()) { |
| if (!ExpandableRowComposite.this.titleHeaderRegion.contains(x, y)) { |
| ExpandableRowComposite.this.toggle.setHover(false); |
| ExpandableRowComposite.this.toggle.redraw(); |
| } |
| } |
| else { // !toggle.getHover() |
| if (ExpandableRowComposite.this.titleHeaderRegion.contains(x, y)) { |
| ExpandableRowComposite.this.toggle.setHover(true); |
| ExpandableRowComposite.this.toggle.redraw(); |
| } |
| } |
| break; |
| // case SWT.Paint: |
| // if (toggle != null && (getExpansionStyle() & NO_TITLE_FOCUS_BOX) == 0) { |
| // paintTitleFocus(event.gc); |
| // } |
| // break; |
| } |
| }; |
| addListener(SWT.MouseDown, listener); |
| addListener(SWT.MouseUp, listener); |
| addListener(SWT.MouseEnter, listener); |
| addListener(SWT.MouseMove, listener); |
| addListener(SWT.MouseExit, listener); |
| if (this.imageLabel != null) { |
| this.imageLabel.addListener(SWT.MouseDown, listener); |
| this.imageLabel.addListener(SWT.MouseUp, listener); |
| this.imageLabel.addListener(SWT.MouseEnter, listener); |
| this.imageLabel.addListener(SWT.MouseExit, listener); |
| } |
| this.textLabel.addListener(SWT.MouseDown, listener); |
| this.textLabel.addListener(SWT.MouseUp, listener); |
| this.textLabel.addListener(SWT.MouseEnter, listener); |
| this.textLabel.addListener(SWT.MouseExit, listener); |
| } |
| } |
| |
| @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(final Menu menu) { |
| if (this.toggle != null) { |
| this.toggle.setMenu(menu); |
| } |
| if (this.imageLabel != null) { |
| this.imageLabel.setMenu(menu); |
| } |
| if (this.textLabel != null) { |
| this.textLabel.setMenu(menu); |
| } |
| super.setMenu(menu); |
| } |
| |
| /** |
| * Prevents assignment of the layout manager - expandable composite uses its |
| * own layout. |
| */ |
| @Override |
| public final void setLayout(final Layout layout) { |
| } |
| |
| /** |
| * Sets the background of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setBackground(final Color bg) { |
| super.setBackground(bg); |
| if ((getExpansionStyle() & TITLE_BAR) == 0) { |
| if (this.toggle != null) { |
| this.toggle.setBackground(bg); |
| this.toggle.setHoverDecorationColor(computeActiveToggleColor()); |
| } |
| if (this.imageLabel != null) { |
| this.imageLabel.setBackground(bg); |
| } |
| if (this.textLabel != null) { |
| this.textLabel.setBackground(bg); |
| } |
| } |
| } |
| |
| protected Color computeActiveToggleColor() { |
| final Display display= getDisplay(); |
| final RGB selection= display.getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB(); |
| final RGB background= getBackground().getRGB(); |
| return SharedUIResources.getColors().getColor(new RGB( |
| (63 + 3 * selection.red + 2 * background.red) / 6, |
| (63 + 3 * selection.green + 2 *background.green) / 6, |
| (63 + 3 * selection.blue + 2 * background.blue) / 6 )); |
| } |
| |
| /** |
| * Sets the foreground of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setForeground(final Color fg) { |
| super.setForeground(fg); |
| if (this.textLabel != null) { |
| this.textLabel.setForeground(fg); |
| } |
| if (this.toggle != null) { |
| this.toggle.setForeground(fg); |
| } |
| } |
| |
| /** |
| * Sets the color of the toggle control. |
| * |
| * @param c |
| * the color object |
| */ |
| public void setToggleColor(final Color c) { |
| if (this.toggle != null) { |
| this.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(final Color c) { |
| if (this.toggle != null) { |
| this.toggle.setHoverDecorationColor(c); |
| } |
| } |
| |
| /** |
| * Sets the fonts of all the custom controls in the expandable. |
| */ |
| @Override |
| public void setFont(final Font font) { |
| super.setFont(font); |
| if (this.textLabel != null) { |
| this.textLabel.setFont(font); |
| } |
| if (this.toggle != null) { |
| this.toggle.setFont(font); |
| } |
| } |
| |
| @Override |
| public void setEnabled(final boolean enabled) { |
| if (this.textLabel != null) { |
| this.textLabel.setEnabled(enabled); |
| } |
| if (this.toggle != null) { |
| this.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(final 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 this.client; |
| } |
| |
| public void setImage(final Image image) { |
| if (this.imageLabel != null) { |
| this.imageLabel.setImage(image); |
| } |
| } |
| |
| public Image getImage() { |
| if (this.imageLabel != null) { |
| return this.imageLabel.getImage(); |
| } |
| return null; |
| } |
| |
| /** |
| * 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(final String title) { |
| if (this.textLabel instanceof Label) { |
| ((Label) this.textLabel).setText(title); |
| updateLabelTrim(); |
| } else if (this.textLabel instanceof Hyperlink) { |
| ((Hyperlink) this.textLabel).setText(title); |
| } else { |
| return; |
| } |
| layout(); |
| } |
| |
| /** |
| * Returns the title string. |
| * |
| * @return the title string |
| * @see #setText(String) |
| */ |
| public String getText() { |
| if (this.textLabel instanceof Label) { |
| return ((Label) this.textLabel).getText(); |
| } |
| else if (this.textLabel instanceof Hyperlink) { |
| return ((Hyperlink) this.textLabel).getText(); |
| } |
| else { |
| return ""; //$NON-NLS-1$ |
| } |
| } |
| |
| @Override |
| public void setToolTipText(final String string) { |
| super.setToolTipText(string); |
| // Also set on label, otherwise it's just on the background without text. |
| if (this.toggle != null) { |
| this.toggle.setToolTipText(string); |
| } |
| if (this.imageLabel != null) { |
| this.imageLabel.setToolTipText(string); |
| } |
| if (this.textLabel != null) { |
| this.textLabel.setToolTipText(string); |
| } |
| } |
| |
| /** |
| * Tests the expanded state of the composite. |
| * |
| * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed. |
| */ |
| public boolean isExpanded() { |
| return this.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 this.expansionStyle; |
| } |
| |
| /** |
| * Programmatically changes expanded state. |
| * |
| * @param expanded |
| * the new expanded state |
| */ |
| public void setExpanded(final boolean expanded) { |
| internalSetExpanded(expanded); |
| if (this.toggle != null) { |
| this.toggle.setExpanded(expanded); |
| } |
| } |
| |
| /** |
| * Performs the expansion state change for the expandable control. |
| * |
| * @param expanded |
| * the expansion state |
| */ |
| protected void internalSetExpanded(final boolean expanded) { |
| if (this.expanded != expanded) { |
| this.expanded= expanded; |
| if (getDescriptionControl() != null) { |
| getDescriptionControl().setVisible(expanded); |
| } |
| if (this.client != null) { |
| this.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(final IExpansionListener listener) { |
| this.listeners.add(listener); |
| } |
| |
| /** |
| * Removes the expansion listener. |
| * |
| * @param listener |
| * the listener to remove |
| */ |
| public void removeExpansionListener(final IExpansionListener listener) { |
| this.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(final 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(final int wHint, final int hHint, final boolean changed) { |
| checkWidget(); |
| Point size; |
| final ExpandableLayout layout= (ExpandableLayout) getLayout(); |
| if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) { |
| size= layout.computeSize(this, wHint, hHint, changed); |
| } else { |
| size= new Point(wHint, hHint); |
| } |
| final 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 (this.expansionStyle & TWISTIE) == 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(final Color color) { |
| this.titleBarForeground= color; |
| if (this.textLabel != null) { |
| this.textLabel.setForeground(color); |
| } |
| } |
| |
| /** |
| * Returns the title bar foreground when TITLE_BAR style is used. |
| * |
| * @return the title bar foreground |
| */ |
| public Color getTitleBarForeground() { |
| return this.titleBarForeground; |
| } |
| |
| // end of APIs |
| |
| private void toggleState() { |
| final boolean newState= !isExpanded(); |
| fireExpanding(newState, true); |
| internalSetExpanded(newState); |
| fireExpanding(newState, false); |
| if (newState) { |
| FormToolkit.ensureVisible(this); |
| } |
| } |
| |
| private void fireExpanding(final boolean state, final boolean before) { |
| final ImIdentityList<IExpansionListener> listenerList= this.listeners.toList(); |
| if (listenerList.isEmpty()) { |
| return; |
| } |
| final ExpansionEvent e= new ExpansionEvent(this, state); |
| for (final IExpansionListener listener : listenerList) { |
| if (before) { |
| listener.expansionStateChanging(e); |
| } |
| else { |
| listener.expansionStateChanged(e); |
| } |
| } |
| } |
| |
| private void verticalMove(final boolean down) { |
| final Composite parent= getParent(); |
| final Control[] children= parent.getChildren(); |
| for (int i= 0; i < children.length; i++) { |
| final Control child= children[i]; |
| if (child == this) { |
| final ExpandableRowComposite sibling= getSibling(children, i, down); |
| if (sibling != null && sibling.toggle != null) { |
| sibling.setFocus(); |
| } |
| break; |
| } |
| } |
| } |
| |
| private ExpandableRowComposite getSibling(final Control[] children, final int index, |
| final boolean down) { |
| int loc= down ? index + 1 : index - 1; |
| while (loc >= 0 && loc < children.length) { |
| final Control c= children[loc]; |
| if (c instanceof ExpandableRowComposite && c.isVisible()) { |
| return (ExpandableRowComposite) c; |
| } |
| loc= down ? loc + 1 : loc - 1; |
| } |
| return null; |
| } |
| |
| private void programmaticToggleState() { |
| if (this.toggle != null) { |
| this.toggle.setExpanded(!this.toggle.isExpanded()); |
| } |
| toggleState(); |
| } |
| |
| private void paintTitleFocus(final GC gc) { |
| final Point size= this.textLabel.getSize(); |
| gc.setBackground(this.textLabel.getBackground()); |
| gc.setForeground(this.textLabel.getForeground()); |
| if (this.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; |
| } |
| } |
| try { |
| c = this; |
| while (c != null) { |
| c.requestLayout(); |
| c = c.getParent(); |
| if (c instanceof SharedScrolledComposite) { |
| ((SharedScrolledComposite) c).reflow(true); |
| break; |
| } |
| if (c instanceof Shell) { |
| break; |
| } |
| } |
| } |
| finally { |
| c = this; |
| while (c != null) { |
| c.setRedraw(true); |
| c = c.getParent(); |
| if (c instanceof SharedScrolledComposite || c instanceof Shell) { |
| break; |
| } |
| } |
| } |
| } |
| |
| private void updateLabelTrim() { |
| // if (textLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > textLabel.getSize().x) { |
| // textLabel.setToolTipText(((Label) textLabel).getText()); |
| // } |
| // else { |
| // textLabel.setToolTipText(null); |
| // } |
| } |
| |
| } |