blob: 33666a068b4a6330ccc5a6c7791848dbd2c473ca [file] [log] [blame]
/*=============================================================================#
# 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);
// }
}
}