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.events.PaintListener;
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.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.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.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;
public class ExpandableComposite extends Canvas {
public static final int TWISTIE = 1 << 1;
public static final int TREE_NODE = 1 << 2;
public static final int FOCUS_TITLE = 1 << 3;
public static final int CLIENT_INDENT = 1 << 4;
public static final int COMPACT = 1 << 5;
public static final int EXPANDED = 1 << 6;
public static final int TITLE_BAR = 1 << 8;
public static final int SHORT_TITLE_BAR = 1 << 9;
public static final int NO_TITLE = 1 << 12;
public static final int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13;
public int marginWidth = 0;
public int marginHeight = 0;
public int clientVerticalSpacing = 3;
public int descriptionVerticalSpacing = 0;
public int titleBarTextMarginWidth = 6;
protected ToggleHyperlink toggle;
protected Control textLabel;
protected int VGAP = 3;
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 listeners = new ListenerList();
private Color titleBarForeground;
private class ExpandableLayout extends Layout implements ILayoutExtension {
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();
}
}
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;
Point tsize = NULL_SIZE;
Point tcsize = NULL_SIZE;
if (toggle != null)
tsize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
int twidth = clientArea.width - marginWidth - marginWidth
- thmargin - thmargin;
if (tsize.x > 0)
twidth -= tsize.x + IGAP;
if (textClient != null) {
tcsize = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
}
Point size = NULL_SIZE;
if (textLabel != null) {
if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
size = textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
if (twidth < size.x + IGAP + tcsize.x) {
twidth -= IGAP;
if (textLabel instanceof Label) {
GC gc = new GC(textLabel);
size = FormUtil.computeWrapSize(gc, ((Label)textLabel).getText(), Math.round(twidth*(size.x/(float)(size.x+tcsize.x))));
gc.dispose();
} else
size = textLabelCache.computeSize(Math.round(twidth*(size.x/(float)(size.x+tcsize.x))), SWT.DEFAULT);
tcsize = textClientCache.computeSize(twidth-size.x, SWT.DEFAULT);
}
}
else {
if (tcsize.x > 0)
twidth -= tcsize.x + IGAP;
size = textLabelCache.computeSize(twidth, SWT.DEFAULT);
}
}
if (textLabel instanceof Label) {
Point defSize = textLabelCache.computeSize(SWT.DEFAULT,
SWT.DEFAULT);
if (defSize.y == size.y) {
size.x = Math.min(defSize.x, size.x);
}
}
if (toggle != null) {
GC gc = new GC(ExpandableComposite.this);
gc.setFont(getFont());
FontMetrics fm = gc.getFontMetrics();
int textHeight = fm.getHeight();
gc.dispose();
if (textClient != null
&& (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) {
textHeight = Math.max(textHeight, tcsize.y);
}
int ty = textHeight / 2 - tsize.y / 2 + 1;
ty = Math.max(ty, 0);
ty += marginHeight + tvmargin;
toggle.setLocation(x, ty);
toggle.setSize(tsize);
x += tsize.x + IGAP;
}
if (textLabel != null) {
int ty = y;
if (textClient != null
&& (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) {
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 tcx;
if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) {
tcx = x + size.x + GAP;
} else {
tcx = clientArea.width - tcsize.x - marginWidth - thmargin;
}
textClientCache.setBounds(tcx, y, tcsize.x, tcsize.y);
}
int tbarHeight = 0;
if (size.y > 0)
tbarHeight = size.y;
if (tcsize.y > 0)
tbarHeight = Math.max(tbarHeight, tcsize.y);
y += tbarHeight;
if (hasTitleBar())
y += tvmargin;
if (getSeparatorControl() != null) {
y += VSPACE;
getSeparatorControl().setBounds(marginWidth, y,
clientArea.width - marginWidth - marginWidth,
SEPARATOR_HEIGHT);
y += SEPARATOR_HEIGHT;
if (expanded)
y += VSPACE;
}
if (expanded) {
int areaWidth = clientArea.width - marginWidth - marginWidth
- thmargin - thmargin;
int cx = marginWidth + thmargin;
if ((expansionStyle & CLIENT_INDENT) != 0) {
cx = x;
areaWidth -= x;
}
if (client != null) {
Point dsize = null;
Control desc = getDescriptionControl();
if (desc != null) {
dsize = descriptionCache.computeSize(areaWidth,
SWT.DEFAULT);
y += descriptionVerticalSpacing;
descriptionCache.setBounds(cx, y, areaWidth, dsize.y);
y += dsize.y + clientVerticalSpacing;
} else {
y += clientVerticalSpacing;
if (getSeparatorControl() != null)
y -= VSPACE;
}
int cwidth = areaWidth;
int cheight = clientArea.height - marginHeight
- marginHeight - y;
clientCache.setBounds(cx, y, cwidth, cheight);
}
}
}
protected Point computeSize(Composite parent, int wHint, int hHint,
boolean changed) {
initCache(changed);
int width = 0, height = 0;
Point tsize = NULL_SIZE;
int twidth = 0;
if (toggle != null) {
tsize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
twidth = tsize.x + IGAP;
}
int thmargin = 0;
int tvmargin = 0;
if (hasTitleBar()) {
thmargin = titleBarTextMarginWidth;
tvmargin = IVGAP;
}
int innerwHint = wHint;
if (innerwHint != SWT.DEFAULT)
innerwHint -= twidth + marginWidth + marginWidth + thmargin
+ thmargin;
int innertHint = innerwHint;
Point tcsize = NULL_SIZE;
if (textClient != null) {
tcsize = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
}
Point size = NULL_SIZE;
if (textLabel != null) {
if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
size = textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
if (innertHint != SWT.DEFAULT && innertHint < size.x + IGAP + tcsize.x) {
innertHint -= IGAP;
if (textLabel instanceof Label) {
GC gc = new GC(textLabel);
size = FormUtil.computeWrapSize(gc, ((Label)textLabel).getText(), Math.round(innertHint*(size.x/(float)(size.x+tcsize.x))));
gc.dispose();
} else
size = textLabelCache.computeSize(Math.round(innertHint*(size.x/(float)(size.x+tcsize.x))), SWT.DEFAULT);
tcsize = textClientCache.computeSize(innertHint-size.x, SWT.DEFAULT);
}
} else {
if (innertHint != SWT.DEFAULT && tcsize.x > 0)
innertHint -= IGAP + tcsize.x;
size = textLabelCache.computeSize(innertHint, SWT.DEFAULT);
}
}
if (textLabel instanceof Label) {
Point defSize = textLabelCache.computeSize(SWT.DEFAULT,
SWT.DEFAULT);
if (defSize.y == size.y) {
size.x = Math.min(defSize.x, size.x);
}
}
if (size.x > 0)
width = size.x;
if (tcsize.x > 0)
width += IGAP + tcsize.x;
if (toggle != null)
width += twidth;
height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y;
if (getSeparatorControl() != null) {
height += VSPACE + SEPARATOR_HEIGHT;
if (expanded && client != null)
height += VSPACE;
}
if ((expanded || (expansionStyle & COMPACT) == 0) && client != null) {
int cwHint = wHint;
int clientIndent = 0;
if ((expansionStyle & CLIENT_INDENT) != 0)
clientIndent = twidth;
if (cwHint != SWT.DEFAULT) {
cwHint -= marginWidth + marginWidth + thmargin + thmargin;
if ((expansionStyle & CLIENT_INDENT) != 0)
if (tcsize.x > 0)
cwHint -= twidth;
}
Point dsize = null;
Point csize = clientCache.computeSize(FormUtil.getWidthHint(
cwHint, client), SWT.DEFAULT);
if (getDescriptionControl() != null) {
int dwHint = cwHint;
if (dwHint == SWT.DEFAULT) {
dwHint = csize.x;
if ((expansionStyle & CLIENT_INDENT) != 0)
dwHint -= twidth;
}
dsize = descriptionCache.computeSize(dwHint, SWT.DEFAULT);
}
if (dsize != null) {
width = Math.max(width, dsize.x + clientIndent);
if (expanded)
height += descriptionVerticalSpacing + dsize.y
+ clientVerticalSpacing;
} else {
height += clientVerticalSpacing;
if (getSeparatorControl() != null)
height -= VSPACE;
}
width = Math.max(width, csize.x + clientIndent);
if (expanded)
height += csize.y;
}
if (toggle != null)
height = height - size.y + Math.max(size.y, tsize.y);
Point result = new Point(width + marginWidth + marginWidth
+ thmargin + thmargin, height + marginHeight + marginHeight
+ tvmargin + tvmargin);
return result;
}
public int computeMinimumWidth(Composite parent, boolean changed) {
return computeSize(parent, 0, SWT.DEFAULT, changed).x;
}
public int computeMaximumWidth(Composite parent, boolean changed) {
return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x;
}
}
public ExpandableComposite(Composite parent, int style) {
this(parent, style, TWISTIE);
}
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(new PaintListener() {
public void paintControl(PaintEvent 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() {
public void linkActivated(HyperlinkEvent e) {
toggleState();
}
});
toggle.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
if (textLabel instanceof Label && !isFixedStyle())
textLabel.setForeground(toggle.hover ? toggle
.getHoverDecorationColor()
: getTitleBarForeground());
}
});
toggle.addKeyListener(new KeyAdapter() {
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() {
public void focusGained(FocusEvent e) {
textLabel.redraw();
}
public void focusLost(FocusEvent e) {
textLabel.redraw();
}
});
}
}
if ((expansionStyle & FOCUS_TITLE) != 0) {
Hyperlink link = new Hyperlink(this, SWT.WRAP);
link.addHyperlinkListener(new HyperlinkAdapter() {
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 = new Listener() {
public void handleEvent(Event 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) {
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(new TraverseListener() {
public void keyTraversed(TraverseEvent e) {
if (e.detail == SWT.TRAVERSE_MNEMONIC) {
if (!isVisible() || !isEnabled())
return;
if (FormUtil.mnemonicMatch(getText(), e.character)) {
e.doit = false;
if (!isFixedStyle()) {
programmaticToggleState();
}
setFocus();
}
}
}
});
}
}
public boolean forceFocus() {
return false;
}
public void setMenu(Menu menu) {
if (textLabel != null)
textLabel.setMenu(menu);
super.setMenu(menu);
}
public final void setLayout(Layout layout) {
}
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);
}
}
public void setForeground(Color fg) {
super.setForeground(fg);
if (textLabel != null)
textLabel.setForeground(fg);
if (toggle != null)
toggle.setForeground(fg);
}
public void setToggleColor(Color c) {
if (toggle != null)
toggle.setDecorationColor(c);
}
public void setActiveToggleColor(Color c) {
if (toggle != null)
toggle.setHoverDecorationColor(c);
}
public void setFont(Font font) {
super.setFont(font);
if (textLabel != null)
textLabel.setFont(font);
if (toggle != null)
toggle.setFont(font);
}
public void setEnabled(boolean enabled) {
if (textLabel != null)
textLabel.setEnabled(enabled);
if (toggle != null)
toggle.setEnabled(enabled);
super.setEnabled(enabled);
}
public void setClient(Control client) {
Assert.isTrue(client != null && client.getParent().equals(this));
this.client = client;
}
public Control getClient() {
return client;
}
public void setText(String title) {
if (textLabel instanceof Label)
((Label) textLabel).setText(title);
else if (textLabel instanceof Hyperlink)
((Hyperlink) textLabel).setText(title);
}
public String getText() {
if (textLabel instanceof Label)
return ((Label) textLabel).getText();
else if (textLabel instanceof Hyperlink)
return ((Hyperlink) textLabel).getText();
else
return ""; }
public boolean isExpanded() {
return expanded;
}
public int getExpansionStyle() {
return expansionStyle;
}
public void setExpanded(boolean expanded) {
internalSetExpanded(expanded);
if (toggle != null)
toggle.setExpanded(expanded);
}
protected void internalSetExpanded(boolean expanded) {
if (this.expanded != expanded) {
this.expanded = expanded;
if (getDescriptionControl() != null)
getDescriptionControl().setVisible(expanded);
if (client != null)
client.setVisible(expanded);
layout();
}
}
public void addExpansionListener(IExpansionListener listener) {
listeners.add(listener);
}
public void removeExpansionListener(IExpansionListener listener) {
listeners.remove(listener);
}
protected void onPaint(PaintEvent e) {
}
protected Control getDescriptionControl() {
return null;
}
protected Control getSeparatorControl() {
return null;
}
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);
}
protected boolean isFixedStyle() {
return (expansionStyle & TWISTIE) == 0
&& (expansionStyle & TREE_NODE) == 0;
}
public Control getTextClient() {
return textClient;
}
public void setTextClient(Control textClient) {
if (this.textClient != null)
this.textClient.dispose();
this.textClient = textClient;
}
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);
}
protected boolean hasTitleBar() {
return (getExpansionStyle() & TITLE_BAR) != 0
|| (getExpansionStyle() & SHORT_TITLE_BAR) != 0;
}
public void setTitleBarForeground(Color color) {
if (hasTitleBar())
titleBarForeground = color;
if (textLabel != null)
textLabel.setForeground(color);
}
public Color getTitleBarForeground() {
return titleBarForeground;
}
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);
Object [] listenerList = listeners.getListeners();
for (int i = 0; i < size; i++) {
IExpansionListener listener = (IExpansionListener) listenerList[i];
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);
}
}