Mercurial > projects > dwt-addons
diff dwtx/ui/forms/widgets/ExpandableComposite.d @ 75:5d489b9f966c
Fix continue porting
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 24 May 2008 05:11:16 +0200 |
parents | |
children | 56fea7e5f0f9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ExpandableComposite.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,1136 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.ExpandableComposite; + +import dwtx.ui.forms.widgets.ToggleHyperlink; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.SizeCache; +import dwtx.ui.forms.widgets.Twistie; +import dwtx.ui.forms.widgets.TreeNode; +import dwtx.ui.forms.widgets.Hyperlink; + +import dwt.DWT; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.KeyAdapter; +import dwt.events.KeyEvent; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.events.TraverseEvent; +import dwt.events.TraverseListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Layout; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.ListenerList; +import dwtx.ui.forms.events.ExpansionEvent; +import dwtx.ui.forms.events.HyperlinkAdapter; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.forms.events.IExpansionListener; +import dwtx.ui.internal.forms.widgets.FormUtil; +import dwtx.ui.internal.forms.widgets.FormsResources; + +import dwt.dwthelper.utils; + +/** + * 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 layed 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 : Canvas { + /** + * If this style is used, a twistie will be used to render the expansion + * toggle. + */ + public static const 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 const 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 const 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 const 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 acount. + */ + public static const 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 const int EXPANDED = 1 << 6; + + /** + * If this style is used, title bar decoration will be painted behind the + * text. + */ + public static const 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 descrete + * option is needed for the title bar. + * + * @since 3.1 + */ + public static const int SHORT_TITLE_BAR = 1 << 9; + + /** + * If this style is used, title will not be rendered. + */ + public static const 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 const int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13; + + /** + * 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; + package ToggleHyperlink toggle_package(){ + return toggle; + } + package ToggleHyperlink toggle_package(ToggleHyperlink t){ + return (toggle = t); + } + + /** + * The text label for the title. + */ + protected Control textLabel; + package Control textLabel_package(){ + return textLabel; + } + package Control textLabel_package(Control t){ + return (textLabel = t); + } + + /** + * @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. + */ + 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. + */ + protected int GAP = 4; + + static const int IGAP = 4; + static const int IVGAP = 3; + + private static Point NULL_SIZE_; + private static Point NULL_SIZE(){ + if( NULL_SIZE_ is null ){ + synchronized(ExpandableComposite.classinfo ){ + if( NULL_SIZE_ is null ){ + NULL_SIZE_ = new Point(0, 0); + } + } + } + return NULL_SIZE_; + } + + private static const int VSPACE = 3; + + private static const int SEPARATOR_HEIGHT = 2; + + private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED; + + private bool expanded; + + private Control textClient; + + private Control client; + + private ListenerList listeners; + + private Color titleBarForeground; + + private class ExpandableLayout : Layout, ILayoutExtension { + + private SizeCache toggleCache; + + private SizeCache textClientCache; + + private SizeCache textLabelCache; + + private SizeCache descriptionCache; + + private SizeCache clientCache; + + this(){ + toggleCache = new SizeCache(); + textClientCache = new SizeCache(); + textLabelCache = new SizeCache(); + descriptionCache = new SizeCache(); + clientCache = new SizeCache(); + } + private void initCache(bool 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, bool 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 !is null) + tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + int twidth = clientArea.width - marginWidth - marginWidth + - thmargin - thmargin; + if (tsize.x > 0) + twidth -= tsize.x + IGAP; + if (textClient !is null) { + tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + Point size = NULL_SIZE; + if (textLabel !is null) { + if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) { + size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + if (twidth < size.x + IGAP + tcsize.x) { + twidth -= IGAP; + if (null !is cast(Label)textLabel ) + size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x)))); + else + size = textLabelCache.computeSize(cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT); + tcsize = textClientCache.computeSize(twidth-size.x, DWT.DEFAULT); + } + } + else { + if (tcsize.x > 0) + twidth -= tcsize.x + IGAP; + size = textLabelCache.computeSize(twidth, DWT.DEFAULT); + } + } + if (null !is cast(Label)textLabel ) { + Point defSize = textLabelCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + if (defSize.y is size.y) { + // One line - pick the smaller of the two widths + size.x = Math.min(defSize.x, size.x); + } + } + if (toggle !is null) { + GC gc = new GC(this.outer); + gc.setFont(getFont()); + FontMetrics fm = gc.getFontMetrics(); + int textHeight = fm.getHeight(); + gc.dispose(); + if (textClient !is null + && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 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 !is null) { + int ty = y; + if (textClient !is null + && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 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 !is null) { + int tcx; + if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 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() !is 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) !is 0) { + cx = x; + areaWidth -= x; + } + if (client !is null) { + Point dsize = null; + Control desc = getDescriptionControl(); + if (desc !is null) { + dsize = descriptionCache.computeSize(areaWidth, + DWT.DEFAULT); + y += descriptionVerticalSpacing; + descriptionCache.setBounds(cx, y, areaWidth, dsize.y); + y += dsize.y + clientVerticalSpacing; + } else { + y += clientVerticalSpacing; + if (getSeparatorControl() !is 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, + bool changed) { + initCache(changed); + + int width = 0, height = 0; + Point tsize = NULL_SIZE; + int twidth = 0; + if (toggle !is null) { + tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + twidth = tsize.x + IGAP; + } + int thmargin = 0; + int tvmargin = 0; + + if (hasTitleBar()) { + thmargin = titleBarTextMarginWidth; + tvmargin = IVGAP; + } + int innerwHint = wHint; + if (innerwHint !is DWT.DEFAULT) + innerwHint -= twidth + marginWidth + marginWidth + thmargin + + thmargin; + + int innertHint = innerwHint; + + Point tcsize = NULL_SIZE; + if (textClient !is null) { + tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + Point size = NULL_SIZE; + + if (textLabel !is null) { + if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) { + size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + if (innertHint !is DWT.DEFAULT && innertHint < size.x + IGAP + tcsize.x) { + innertHint -= IGAP; + if (null !is cast(Label)textLabel ) + size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x)))); + else + size = textLabelCache.computeSize(cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT); + tcsize = textClientCache.computeSize(innertHint-size.x, DWT.DEFAULT); + } + } else { + if (innertHint !is DWT.DEFAULT && tcsize.x > 0) + innertHint -= IGAP + tcsize.x; + size = textLabelCache.computeSize(innertHint, DWT.DEFAULT); + } + } + if (null !is cast(Label)textLabel ) { + Point defSize = textLabelCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + if (defSize.y is size.y) { + // One line - pick the smaller of the two widths + 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 !is null) + width += twidth; + height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y; + if (getSeparatorControl() !is null) { + height += VSPACE + SEPARATOR_HEIGHT; + if (expanded && client !is null) + height += VSPACE; + } + // if (hasTitleBar()) + // height += VSPACE; + if ((expanded || (expansionStyle & COMPACT) is 0) && client !is null) { + int cwHint = wHint; + int clientIndent = 0; + if ((expansionStyle & CLIENT_INDENT) !is 0) + clientIndent = twidth; + + if (cwHint !is DWT.DEFAULT) { + cwHint -= marginWidth + marginWidth + thmargin + thmargin; + if ((expansionStyle & CLIENT_INDENT) !is 0) + if (tcsize.x > 0) + cwHint -= twidth; + } + Point dsize = null; + Point csize = clientCache.computeSize(FormUtil.getWidthHint( + cwHint, client), DWT.DEFAULT); + if (getDescriptionControl() !is null) { + int dwHint = cwHint; + if (dwHint is DWT.DEFAULT) { + dwHint = csize.x; + if ((expansionStyle & CLIENT_INDENT) !is 0) + dwHint -= twidth; + } + dsize = descriptionCache.computeSize(dwHint, DWT.DEFAULT); + } + if (dsize !is null) { + width = Math.max(width, dsize.x + clientIndent); + if (expanded) + height += descriptionVerticalSpacing + dsize.y + + clientVerticalSpacing; + } else { + height += clientVerticalSpacing; + if (getSeparatorControl() !is null) + height -= VSPACE; + } + width = Math.max(width, csize.x + clientIndent); + if (expanded) + height += csize.y; + } + if (toggle !is 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, bool changed) { + return computeSize(parent, 0, DWT.DEFAULT, changed).x; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.parts.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + } + + /** + * Creates an expandable composite using a TWISTIE toggle. + * + * @param parent + * the parent composite + * @param style + * DWT style bits + */ + public this(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 DWT subclass) + * @param expansionStyle + * the style of the expansion widget (TREE_NODE, TWISTIE, + * CLIENT_INDENT, COMPACT, FOCUS_TITLE, + * LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE) + */ + public this(Composite parent, int style, int expansionStyle) { + listeners = new ListenerList(); + super(parent, style); + this.expansionStyle = expansionStyle; + if ((expansionStyle & TITLE_BAR) !is 0) + setBackgroundMode(DWT.INHERIT_DEFAULT); + super.setLayout(new ExpandableLayout()); + if (hasTitleBar()) { + this.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + onPaint(e); + } + }); + } + if ((expansionStyle & TWISTIE) !is 0) + toggle = new Twistie(this, DWT.NULL); + else if ((expansionStyle & TREE_NODE) !is 0) + toggle = new TreeNode(this, DWT.NULL); + else + expanded = true; + if ((expansionStyle & EXPANDED) !is 0) + expanded = true; + if (toggle !is null) { + toggle.setExpanded(expanded); + toggle.addHyperlinkListener(new class HyperlinkAdapter { + public void linkActivated(HyperlinkEvent e) { + toggleState(); + } + }); + toggle.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + if (null !is cast(Label)textLabel && !isFixedStyle()) + textLabel.setForeground(toggle.hover_package ? toggle + .getHoverDecorationColor() + : getTitleBarForeground()); + } + }); + toggle.addKeyListener(new class KeyAdapter { + public void keyPressed(KeyEvent e) { + if (e.keyCode is DWT.ARROW_UP) { + verticalMove(false); + e.doit = false; + } else if (e.keyCode is DWT.ARROW_DOWN) { + verticalMove(true); + e.doit = false; + } + } + }); + if ((getExpansionStyle()&FOCUS_TITLE) is 0) { + toggle.paintFocus=false; + toggle.addFocusListener(new class FocusListener { + public void focusGained(FocusEvent e) { + textLabel.redraw(); + } + + public void focusLost(FocusEvent e) { + textLabel.redraw(); + } + }); + } + } + if ((expansionStyle & FOCUS_TITLE) !is 0) { + Hyperlink link = new Hyperlink(this, DWT.WRAP); + link.addHyperlinkListener(new class HyperlinkAdapter { + public void linkActivated(HyperlinkEvent e) { + programmaticToggleState(); + } + }); + textLabel = link; + } else if ((expansionStyle & NO_TITLE) is 0) { + final Label label = new Label(this, DWT.WRAP); + if (!isFixedStyle()) { + label.setCursor(FormsResources.getHandCursor()); + Listener listener = dgListener( (Event e, Label label) { + switch (e.type) { + case DWT.MouseDown: + if (toggle !is null) + toggle.setFocus(); + break; + case DWT.MouseUp: + label.setCursor(FormsResources.getBusyCursor()); + programmaticToggleState(); + label.setCursor(FormsResources.getHandCursor()); + break; + case DWT.MouseEnter: + if (toggle !is null) { + label.setForeground(toggle + .getHoverDecorationColor()); + toggle.hover_package = true; + toggle.redraw(); + } + break; + case DWT.MouseExit: + if (toggle !is null) { + label.setForeground(getTitleBarForeground()); + toggle.hover_package = false; + toggle.redraw(); + } + break; + case DWT.Paint: + if (toggle !is null) { + paintTitleFocus(e.gc); + } + break; + } + }, label ); + label.addListener(DWT.MouseDown, listener); + label.addListener(DWT.MouseUp, listener); + label.addListener(DWT.MouseEnter, listener); + label.addListener(DWT.MouseExit, listener); + label.addListener(DWT.Paint, listener); + } + textLabel = label; + } + if (textLabel !is null) { + textLabel.setMenu(getMenu()); + textLabel.addTraverseListener(new class TraverseListener { + public void keyTraversed(TraverseEvent e) { + if (e.detail is DWT.TRAVERSE_MNEMONIC) { + // steal the mnemonic + if (!isVisible() || !isEnabled()) + return; + if (FormUtil.mnemonicMatch(getText(), e.character)) { + e.doit = false; + if (!isFixedStyle()) { + programmaticToggleState(); + } + setFocus(); + } + } + } + }); + } + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#forceFocus() + */ + public bool 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. + */ + + public void setMenu(Menu menu) { + if (textLabel !is null) + textLabel.setMenu(menu); + super.setMenu(menu); + } + + /** + * Prevents assignment of the layout manager - expandable composite uses its + * own layout. + */ + public final void setLayout(Layout layout) { + } + + /** + * Sets the background of all the custom controls in the expandable. + */ + public void setBackground(Color bg) { + super.setBackground(bg); + if ((getExpansionStyle() & TITLE_BAR) is 0) { + if (textLabel !is null) + textLabel.setBackground(bg); + if (toggle !is null) + toggle.setBackground(bg); + } + } + + /** + * Sets the foreground of all the custom controls in the expandable. + */ + public void setForeground(Color fg) { + super.setForeground(fg); + if (textLabel !is null) + textLabel.setForeground(fg); + if (toggle !is null) + toggle.setForeground(fg); + } + + /** + * Sets the color of the toggle control. + * + * @param c + * the color object + */ + public void setToggleColor(Color c) { + if (toggle !is 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 !is null) + toggle.setHoverDecorationColor(c); + } + + /** + * Sets the fonts of all the custom controls in the expandable. + */ + public void setFont(Font font) { + super.setFont(font); + if (textLabel !is null) + textLabel.setFont(font); + if (toggle !is null) + toggle.setFont(font); + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Control#setEnabled(bool) + */ + + public void setEnabled(bool enabled) { + if (textLabel !is null) + textLabel.setEnabled(enabled); + if (toggle !is 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 !is null && client.getParent().opEquals(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 (null !is cast(Label)textLabel ) + (cast(Label) textLabel).setText(title); + else if (null !is cast(Hyperlink)textLabel ) + (cast(Hyperlink) textLabel).setText(title); + } + + /** + * Returns the title string. + * + * @return the title string + * @see #setText(String) + */ + public String getText() { + if (null !is cast(Label)textLabel ) + return (cast(Label) textLabel).getText(); + else if (null !is cast(Hyperlink)textLabel ) + return (cast(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 bool 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(bool expanded) { + internalSetExpanded(expanded); + if (toggle !is null) + toggle.setExpanded(expanded); + } + + /** + * Performs the expansion state change for the expandable control. + * + * @param expanded + * the expansion state + */ + protected void internalSetExpanded(bool expanded) { + if (this.expanded !is expanded) { + this.expanded = expanded; + if (getDescriptionControl() !is null) + getDescriptionControl().setVisible(expanded); + if (client !is null) + client.setVisible(expanded); + layout(); + } + } + + /** + * 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(cast(Object)listener); + } + + /** + * Removes the expansion listener. + * + * @param listener + * the listner to remove + */ + public void removeExpansionListener(IExpansionListener listener) { + listeners.remove(cast(Object)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 dwt.widgets.Composite#computeSize + */ + public Point computeSize(int wHint, int hHint, bool changed) { + checkWidget(); + Point size; + ExpandableLayout layout = cast(ExpandableLayout) getLayout(); + if (wHint is DWT.DEFAULT || hHint is DWT.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 bool isFixedStyle() { + return (expansionStyle & TWISTIE) is 0 + && (expansionStyle & TREE_NODE) is 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 !is 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 is null || textLabel is null) + return 0; + int theight = textLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT).y; + int tcheight = textClient.computeSize(DWT.DEFAULT, DWT.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 bool hasTitleBar() { + return (getExpansionStyle() & TITLE_BAR) !is 0 + || (getExpansionStyle() & SHORT_TITLE_BAR) !is 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; + 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() { + bool newState = !isExpanded(); + fireExpanding(newState, true); + internalSetExpanded(newState); + fireExpanding(newState, false); + if (newState) + FormUtil.ensureVisible(this); + } + + private void fireExpanding(bool state, bool before) { + int size = listeners.size(); + if (size is 0) + return; + ExpansionEvent e = new ExpansionEvent(this, state); + Object [] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IExpansionListener listener = cast(IExpansionListener) listenerList[i]; + if (before) + listener.expansionStateChanging(e); + else + listener.expansionStateChanged(e); + } + } + + private void verticalMove(bool down) { + Composite parent = getParent(); + Control[] children = parent.getChildren(); + for (int i = 0; i < children.length; i++) { + Control child = children[i]; + if (child is this) { + ExpandableComposite sibling = getSibling(children, i, down); + if (sibling !is null && sibling.toggle !is null) { + sibling.setFocus(); + } + break; + } + } + } + + private ExpandableComposite getSibling(Control[] children, int index, + bool down) { + int loc = down ? index + 1 : index - 1; + while (loc >= 0 && loc < children.length) { + Control c = children[loc]; + if (null !is cast(ExpandableComposite)c && c.isVisible()) + return cast(ExpandableComposite) c; + loc = down ? loc + 1 : loc - 1; + } + return null; + } + + private void programmaticToggleState() { + if (toggle !is 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); + } +}