Mercurial > projects > dwt-addons
diff dwtx/jface/fieldassist/ControlDecoration.d @ 29:f12d40e7da8f
fieldassist
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Thu, 03 Apr 2008 18:56:20 +0200 |
parents | |
children | b3c8e32d406f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/fieldassist/ControlDecoration.d Thu Apr 03 18:56:20 2008 +0200 @@ -0,0 +1,1204 @@ +/******************************************************************************* + * Copyright (c) 2006, 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.fieldassist.ControlDecoration; + + +import dwt.DWT; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.MenuDetectEvent; +import dwt.events.MenuDetectListener; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.MouseMoveListener; +import dwt.events.MouseTrackListener; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.events.SelectionEvent; +import dwt.events.SelectionListener; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.graphics.Region; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.Shell; +import dwt.widgets.Widget; +import dwtx.core.runtime.ListenerList; + +import dwt.dwthelper.utils; +import tango.io.Stdout; + +/** + * ControlDecoration renders an image decoration near a control. It allows + * clients to specify an image and a position for the image relative to the + * control. A ControlDecoration may be assigned description text, which can + * optionally be shown when the user hovers over the image. Clients can decorate + * any kind of control. + * <p> + * Decoration images always appear on the left or right side of the field, never + * above or below it. Decorations can be positioned at the top, center, or + * bottom of either side of the control. Future implementations may provide + * additional positioning options for decorations. + * <p> + * ControlDecoration renders the image adjacent to the specified (already + * created) control, with no guarantee that it won't be clipped or otherwise + * obscured or overlapped by adjacent controls, including another + * ControlDecoration placed in the same location. Clients should ensure that + * there is adequate space adjacent to the control to show the decoration + * properly. + * <p> + * Clients using ControlDecoration should typically ensure that enough margin + * space is reserved for a decoration by altering the layout data margins, + * although this is not assumed or required by the ControlDecoration + * implementation. + * <p> + * This class is intended to be instantiated and used by clients. It is not + * intended to be subclassed by clients. + * + * @since 3.3 + * + * @see FieldDecoration + * @see FieldDecorationRegistry + */ +public class ControlDecoration { + /** + * Debug flag for tracing + */ + private static bool DEBUG = false; + + /** + * Cached platform flags for dealing with platform-specific issues. + */ + private static bool CARBON = "carbon".equals(DWT.getPlatform()); //$NON-NLS-1$ + + /** + * The associated control + */ + private Control control; + + /** + * The composite on which to render the decoration and hook mouse events, or + * null if we are hooking all parent composites. + */ + private Composite composite; + + /** + * The associated image. + */ + private Image image; + + /** + * The associated description text. + */ + private String descriptionText; + /** + * The position of the decoration. + */ + private int position; + + /** + * The decoration's visibility flag + */ + private bool visible = true; + + /** + * bool indicating whether the decoration should only be shown when the + * control has focus + */ + private bool showOnlyOnFocus = false; + + /** + * bool indicating whether the decoration should show its description + * text in a hover when the user hovers over the decoration. + */ + private bool showHover = true; + + /** + * Margin width used between the decorator and the control. + */ + private int marginWidth = 0; + + /** + * Registered selection listeners. + */ + private ListenerList selectionListeners; + + /** + * Registered menu detect listeners. + */ + private ListenerList menuDetectListeners; + + /** + * The focus listener + */ + private FocusListener focusListener; + + /** + * The dispose listener + */ + private DisposeListener disposeListener; + + /** + * The paint listener installed for drawing the decoration + */ + private PaintListener paintListener; + + /** + * The mouse listener installed for tracking the hover + */ + private MouseTrackListener mouseTrackListener; + + /** + * The mouse move listener installed for tracking the hover + */ + private MouseMoveListener mouseMoveListener; + + /** + * The untyped listener installed for notifying external listeners + */ + private Listener compositeListener; + + /** + * Control that we last installed a move listener on. We only want one at a + * time. + */ + private Control moveListeningTarget = null; + + /** + * Debug counter used to match add and remove listeners + */ + private int listenerInstalls = 0; + + /** + * The current rectangle used for tracking mouse moves + */ + private Rectangle decorationRectangle; + + /** + * An internal flag tracking whether we have focus. We use this rather than + * isFocusControl() so that we can set the flag as soon as we get the focus + * callback, rather than having to do an asyncExec in the middle of a focus + * callback to ensure that isFocusControl() represents the outcome of the + * event. + */ + private bool hasFocus = false; + + /** + * The hover used for showing description text + */ + private Hover hover; + + /** + * The hover used to show a decoration image's description. + */ + class Hover { + private static const String EMPTY = ""; //$NON-NLS-1$ + + /** + * Offset of info hover arrow from the left or right side. + */ + private int hao = 10; + + /** + * Width of info hover arrow. + */ + private int haw = 8; + + /** + * Height of info hover arrow. + */ + private int hah = 10; + + /** + * Margin around info hover text. + */ + private int hm = 2; + + /** + * This info hover's shell. + */ + Shell hoverShell; + + /** + * The info hover text. + */ + String text = EMPTY; + + /** + * The region used to manage the shell shape + */ + Region region; + + /** + * bool indicating whether the last computed polygon location had an + * arrow on left. (true if left, false if right). + */ + bool arrowOnLeft = true; + + /* + * Create a hover parented by the specified shell. + */ + this(Shell parent) { + Display display = parent.getDisplay(); + hoverShell = new Shell(parent, DWT.NO_TRIM | DWT.ON_TOP + | DWT.NO_FOCUS); + hoverShell.setBackground(display + .getSystemColor(DWT.COLOR_INFO_BACKGROUND)); + hoverShell.setForeground(display + .getSystemColor(DWT.COLOR_INFO_FOREGROUND)); + hoverShell.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent pe) { + pe.gc.drawText(text, hm, hm); + if (!CARBON) { + pe.gc.drawPolygon(getPolygon(true)); + } + } + }); + hoverShell.addMouseListener(new class MouseAdapter { + public void mouseDown(MouseEvent e) { + hideHover(); + } + }); + } + + /* + * Compute a polygon that represents a hover with an arrow pointer. If + * border is true, compute the polygon inset by 1-pixel border. Consult + * the arrowOnLeft flag to determine which side the arrow is on. + */ + int[] getPolygon(bool border) { + Point e = getExtent(); + int b = border ? 1 : 0; + if (arrowOnLeft) { + return [ 0, 0, e.x - b, 0, e.x - b, e.y - b, + hao + haw, e.y - b, hao + haw / 2, e.y + hah - b, hao, + e.y - b, 0, e.y - b, 0, 0 ]; + } + return [ 0, 0, e.x - b, 0, e.x - b, e.y - b, + e.x - hao - b, e.y - b, e.x - hao - haw / 2, e.y + hah - b, + e.x - hao - haw, e.y - b, 0, e.y - b, 0, 0 ]; + } + + /* + * Dispose the hover, it is no longer needed. Dispose any resources + * allocated by the hover. + */ + void dispose() { + if (!hoverShell.isDisposed()) { + hoverShell.dispose(); + } + if (region !is null) { + region.dispose(); + } + } + + /* + * Set the visibility of the hover. + */ + void setVisible(bool visible) { + if (visible) { + if (!hoverShell.isVisible()) { + hoverShell.setVisible(true); + } + } else { + if (hoverShell.isVisible()) { + hoverShell.setVisible(false); + } + } + } + + /* + * Set the text of the hover to the specified text. Recompute the size + * and location of the hover to hover near the decoration rectangle, + * pointing the arrow toward the target control. + */ + void setText(String t, Rectangle decorationRectangle, + Control targetControl) { + if (t is null) { + t = EMPTY; + } + if (!t.equals(text)) { + Point oldSize = getExtent(); + text = t; + hoverShell.redraw(); + Point newSize = getExtent(); + if (!oldSize.opEquals(newSize)) { + // set a flag that indicates the direction of arrow + arrowOnLeft = decorationRectangle.x <= targetControl + .getLocation().x; + setNewShape(); + } + } + + Point extent = getExtent(); + int y = -extent.y - hah + 1; + int x = arrowOnLeft ? -hao + haw / 2 : -extent.x + hao + haw / 2; + + hoverShell.setLocation(control.getParent().toDisplay( + decorationRectangle.x + x, decorationRectangle.y + y)); + } + + /* + * Return whether or not the hover (shell) is visible. + */ + bool isVisible() { + return hoverShell.isVisible(); + } + + /* + * Compute the extent of the hover for the current text. + */ + Point getExtent() { + GC gc = new GC(hoverShell); + Point e = gc.textExtent(text); + gc.dispose(); + e.x += hm * 2; + e.y += hm * 2; + return e; + } + + /* + * Compute a new shape for the hover shell. + */ + void setNewShape() { + Region oldRegion = region; + region = new Region(); + region.add(getPolygon(false)); + hoverShell.setRegion(region); + if (oldRegion !is null) { + oldRegion.dispose(); + } + + } + } + + /** + * Construct a ControlDecoration for decorating the specified control at the + * specified position relative to the control. Render the decoration on top + * of any Control that happens to appear at the specified location. + * <p> + * DWT constants are used to specify the position of the decoration relative + * to the control. The position should include style bits describing both + * the vertical and horizontal orientation. <code>DWT.LEFT</code> and + * <code>DWT.RIGHT</code> describe the horizontal placement of the + * decoration relative to the control, and the constants + * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and + * <code>DWT.BOTTOM</code> describe the vertical alignment of the + * decoration relative to the control. Decorations always appear on either + * the left or right side of the control, never above or below it. For + * example, a decoration appearing on the left side of the field, at the + * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are + * specified, the control decoration will be positioned to the left and + * center of the control (<code>DWT.LEFT | DWT.CENTER</code>). + * </p> + * + * @param control + * the control to be decorated + * @param position + * bit-wise or of position constants (<code>DWT.TOP</code>, + * <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>, + * <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>). + */ + public this(Control control, int position) { + this(control, position, null); + + } + + /** + * Construct a ControlDecoration for decorating the specified control at the + * specified position relative to the control. Render the decoration only on + * the specified Composite or its children. The decoration will be clipped + * if it does not appear within the visible bounds of the composite or its + * child composites. + * <p> + * DWT constants are used to specify the position of the decoration relative + * to the control. The position should include style bits describing both + * the vertical and horizontal orientation. <code>DWT.LEFT</code> and + * <code>DWT.RIGHT</code> describe the horizontal placement of the + * decoration relative to the control, and the constants + * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and + * <code>DWT.BOTTOM</code> describe the vertical alignment of the + * decoration relative to the control. Decorations always appear on either + * the left or right side of the control, never above or below it. For + * example, a decoration appearing on the left side of the field, at the + * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are + * specified, the control decoration will be positioned to the left and + * center of the control (<code>DWT.LEFT | DWT.CENTER</code>). + * </p> + * + * @param control + * the control to be decorated + * @param position + * bit-wise or of position constants (<code>DWT.TOP</code>, + * <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>, + * <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>). + * @param composite + * The DWT composite within which the decoration should be + * rendered. The decoration will be clipped to this composite, + * but it may be rendered on a child of the composite. The + * decoration will not be visible if the specified composite or + * its child composites are not visible in the space relative to + * the control, where the decoration is to be rendered. If this + * value is <code>null</code>, then the decoration will be + * rendered on whichever composite (or composites) are located in + * the specified position. + */ + public this(Control control, int position, Composite composite) { + selectionListeners = new ListenerList(); + menuDetectListeners = new ListenerList(); + this.position = position; + this.control = control; + this.composite = composite; + + addControlListeners(); + + } + + /** + * Adds the listener to the collection of listeners who will be notified + * when the platform-specific context menu trigger has occurred, by sending + * it one of the messages defined in the <code>MenuDetectListener</code> + * interface. + * <p> + * The <code>widget</code> field in the SelectionEvent will contain the + * Composite on which the decoration is rendered that received the click. + * The <code>x</code> and <code>y</code> fields will be in coordinates + * relative to the display. The <code>data</code> field will contain the + * decoration that received the event. + * </p> + * + * @param listener + * the listener which should be notified + * + * @see dwt.events.MenuDetectListener + * @see dwt.events.MenuDetectEvent + * @see #removeMenuDetectListener + */ + public void addMenuDetectListener(MenuDetectListener listener) { + menuDetectListeners.add(cast(Object)listener); + } + + /** + * Removes the listener from the collection of listeners who will be + * notified when the platform-specific context menu trigger has occurred. + * + * @param listener + * the listener which should no longer be notified. This message + * has no effect if the listener was not previously added to the + * receiver. + * + * @see dwt.events.MenuDetectListener + * @see #addMenuDetectListener + */ + public void removeMenuDetectListener(MenuDetectListener listener) { + menuDetectListeners.remove(cast(Object)listener); + } + + /** + * Adds the listener to the collection of listeners who will be notified + * when the decoration is selected, by sending it one of the messages + * defined in the <code>SelectionListener</code> interface. + * <p> + * <code>widgetSelected</code> is called when the decoration is selected + * (by mouse click). <code>widgetDefaultSelected</code> is called when the + * decoration is double-clicked. + * </p> + * <p> + * The <code>widget</code> field in the SelectionEvent will contain the + * Composite on which the decoration is rendered that received the click. + * The <code>x</code> and <code>y</code> fields will be in coordinates + * relative to that widget. The <code>data</code> field will contain the + * decoration that received the event. + * </p> + * + * @param listener + * the listener which should be notified + * + * @see dwt.events.SelectionListener + * @see dwt.events.SelectionEvent + * @see #removeSelectionListener + */ + public void addSelectionListener(SelectionListener listener) { + selectionListeners.add(cast(Object)listener); + } + + /** + * Removes the listener from the collection of listeners who will be + * notified when the decoration is selected. + * + * @param listener + * the listener which should no longer be notified. This message + * has no effect if the listener was not previously added to the + * receiver. + * + * @see dwt.events.SelectionListener + * @see #addSelectionListener + */ + public void removeSelectionListener(SelectionListener listener) { + selectionListeners.remove(cast(Object)listener); + } + + /** + * Dispose this ControlDecoration. Unhook any listeners that have been + * installed on the target control. This method has no effect if the + * receiver is already disposed. + */ + public void dispose() { + if (control is null) { + return; + } + if (hover !is null) { + hover.dispose(); + hover = null; + } + removeControlListeners(); + control = null; + } + + /** + * Get the control that is decorated by the receiver. + * + * @return the Control decorated by the receiver. May be <code>null</code> + * if the control has been uninstalled. + */ + public Control getControl() { + return control; + } + + /** + * Add any listeners needed on the target control and on the composite where + * the decoration is to be rendered. + */ + private void addControlListeners() { + disposeListener = new class DisposeListener { + public void widgetDisposed(DisposeEvent event) { + dispose(); + } + }; + printAddListener(control, "DISPOSE"); //$NON-NLS-1$ + control.addDisposeListener(disposeListener); + + focusListener = new class FocusListener { + public void focusGained(FocusEvent event) { + hasFocus = true; + if (showOnlyOnFocus) { + update(); + } + } + + public void focusLost(FocusEvent event) { + hasFocus = false; + if (showOnlyOnFocus) { + update(); + } + } + }; + printAddListener(control, "FOCUS"); //$NON-NLS-1$ + control.addFocusListener(focusListener); + + // Listener for painting the decoration + paintListener = new class PaintListener { + public void paintControl(PaintEvent event) { + Control control = cast(Control) event.widget; + Rectangle rect = getDecorationRectangle(control); + if (shouldShowDecoration()) { + event.gc.drawImage(getImage(), rect.x, rect.y); + } + } + }; + + // Listener for tracking the end of a hover. Only installed + // after a hover begins. + mouseMoveListener = new class MouseMoveListener { + public void mouseMove(MouseEvent event) { + if (showHover) { + if (!decorationRectangle.contains(event.x, event.y)) { + hideHover(); + // No need to listen any longer + printRemoveListener(event.widget, "MOUSEMOVE"); //$NON-NLS-1$ + (cast(Control) event.widget) + .removeMouseMoveListener(mouseMoveListener); + moveListeningTarget = null; + } + } + } + }; + + // Listener for tracking the beginning of a hover. Always installed. + mouseTrackListener = new class MouseTrackListener { + public void mouseExit(MouseEvent event) { + // Just in case we didn't catch it before. + Control target = cast(Control) event.widget; + if (target is moveListeningTarget) { + printRemoveListener(target, "MOUSEMOVE"); //$NON-NLS-1$ + target.removeMouseMoveListener(mouseMoveListener); + moveListeningTarget = null; + } + hideHover(); + } + + public void mouseHover(MouseEvent event) { + if (showHover) { + decorationRectangle = getDecorationRectangle(cast(Control) event.widget); + if (decorationRectangle.contains(event.x, event.y)) { + showHoverText(getDescriptionText()); + Control target = cast(Control) event.widget; + if (moveListeningTarget is null) { + printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$ + target.addMouseMoveListener(mouseMoveListener); + moveListeningTarget = target; + } else if (target !is moveListeningTarget) { + printRemoveListener(moveListeningTarget, + "MOUSEMOVE"); //$NON-NLS-1$ + moveListeningTarget + .removeMouseMoveListener(mouseMoveListener); + printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$ + target.addMouseMoveListener(mouseMoveListener); + moveListeningTarget = target; + } else { + // It is already installed on this control. + } + } + } + } + + public void mouseEnter(MouseEvent event) { + // Nothing to do until a hover occurs. + } + }; + + compositeListener = new class Listener { + public void handleEvent(Event event) { + // Don't forward events if decoration is not showing + if (!visible) { + return; + } + // Notify listeners if any are registered. + switch (event.type) { + case DWT.MouseDown: + if (!selectionListeners.isEmpty()) + notifySelectionListeners(event); + break; + case DWT.MouseDoubleClick: + if (!selectionListeners.isEmpty()) + notifySelectionListeners(event); + break; + case DWT.MenuDetect: + if (!menuDetectListeners.isEmpty()) + notifyMenuDetectListeners(event); + break; + } + } + }; + + // We do not know which parent in the control hierarchy + // is providing the decoration space, so hook all the way up, until + // the shell or the specified parent composite is reached. + Composite c = control.getParent(); + while (c !is null) { + installCompositeListeners(c); + if (composite !is null && composite is c) { + // We just installed on the specified composite, so stop. + c = null; + } else if (cast(Shell)c ) { + // We just installed on a shell, so don't go further + c = null; + } else { + c = c.getParent(); + } + } + // force a redraw of the decoration area so our paint listener + // is notified. + update(); + } + + /* + * Install the listeners used to paint and track mouse events on the + * composite. + */ + private void installCompositeListeners(Composite c) { + if (!c.isDisposed()) { + printAddListener(c, "PAINT"); //$NON-NLS-1$ + c.addPaintListener(paintListener); + printAddListener(c, "MOUSETRACK"); //$NON-NLS-1$ + c.addMouseTrackListener(mouseTrackListener); + printAddListener(c, "DWT.MenuDetect"); //$NON-NLS-1$ + c.addListener(DWT.MenuDetect, compositeListener); + printAddListener(c, "DWT.MouseDown"); //$NON-NLS-1$ + c.addListener(DWT.MouseDown, compositeListener); + printAddListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$ + c.addListener(DWT.MouseDoubleClick, compositeListener); + } + } + + /* + * Remove the listeners used to paint and track mouse events on the + * composite. + */ + private void removeCompositeListeners(Composite c) { + if (!c.isDisposed()) { + printRemoveListener(c, "PAINT"); //$NON-NLS-1$ + c.removePaintListener(paintListener); + printRemoveListener(c, "MOUSETRACK"); //$NON-NLS-1$ + c.removeMouseTrackListener(mouseTrackListener); + printRemoveListener(c, "DWT.MenuDetect"); //$NON-NLS-1$ + c.removeListener(DWT.MenuDetect, compositeListener); + printRemoveListener(c, "DWT.MouseDown"); //$NON-NLS-1$ + c.removeListener(DWT.MouseDown, compositeListener); + printRemoveListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$ + c.removeListener(DWT.MouseDoubleClick, compositeListener); + } + } + + private void notifySelectionListeners(Event event) { + if (!(cast(Control)event.widget )) { + return; + } + if (getDecorationRectangle(cast(Control) event.widget).contains(event.x, + event.y)) { + SelectionEvent clientEvent = new SelectionEvent(event); + clientEvent.data = this; + if (getImage() !is null) { + clientEvent.height = getImage().getBounds().height; + clientEvent.width = getImage().getBounds().width; + } + Object[] listeners; + switch (event.type) { + case DWT.MouseDoubleClick: + if (event.button is 1) { + listeners = selectionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + (cast(SelectionListener) listeners[i]) + .widgetDefaultSelected(clientEvent); + } + } + break; + case DWT.MouseDown: + if (event.button is 1) { + listeners = selectionListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + (cast(SelectionListener) listeners[i]) + .widgetSelected(clientEvent); + } + } + break; + } + } + } + + private void notifyMenuDetectListeners(Event event) { + if (getDecorationRectangle(null).contains(event.x, event.y)) { + MenuDetectEvent clientEvent = new MenuDetectEvent(event); + clientEvent.data = this; + Object[] listeners = menuDetectListeners.getListeners(); + for (int i = 0; i < listeners.length; i++) { + (cast(MenuDetectListener) listeners[i]).menuDetected(clientEvent); + + } + } + } + + /** + * Show the specified text using the same hover dialog as is used to show + * decorator descriptions. When {@link #setShowHover(bool)} has been set + * to <code>true</code>, a decoration's description text will be shown in + * an info hover over the field's control whenever the mouse hovers over the + * decoration. This method can be used to show a decoration's description + * text at other times (such as when the control receives focus), or to show + * other text associated with the field. + * + * @param text + * the text to be shown in the info hover, or <code>null</code> + * if no text should be shown. + */ + public void showHoverText(String text) { + if (control is null) { + return; + } + showHoverText(text, control); + } + + /** + * Hide any hover popups that are currently showing on the control. When + * {@link #setShowHover(bool)} has been set to <code>true</code>, a + * decoration's description text will be shown in an info hover over the + * field's control as long as the mouse hovers over the decoration, and will + * be hidden when the mouse exits the decoration. This method can be used to + * hide a hover, whether it was shown explicitly using + * {@link #showHoverText(String)}, or was showing because the user was + * hovering in the decoration. + * <p> + * This message has no effect if there is no current hover. + * + */ + public void hideHover() { + if (hover !is null) { + hover.setVisible(false); + } + } + + /** + * Show the control decoration. This message has no effect if the decoration + * is already showing. If {@link #setShowOnlyOnFocus(bool)} is set to + * <code>true</code>, the decoration will only be shown if the control + * has focus. + */ + public void show() { + if (!visible) { + visible = true; + update(); + } + } + + /** + * Hide the control decoration. This message has no effect if the decoration + * is already hidden. + */ + public void hide() { + if (visible) { + visible = false; + update(); + } + } + + /** + * Get the description text that may be shown in a hover for this + * decoration. + * + * @return the text to be shown as a description for the decoration, or + * <code>null</code> if none has been set. + */ + public String getDescriptionText() { + return descriptionText; + } + + /** + * Set the image shown in this control decoration. Update the rendered + * decoration. + * + * @param text + * the text to be shown as a description for the decoration, or + * <code>null</code> if none has been set. + */ + public void setDescriptionText(String text) { + this.descriptionText = text; + update(); + } + + /** + * Get the image shown in this control decoration. + * + * @return the image to be shown adjacent to the control, or + * <code>null</code> if one has not been set. + */ + public Image getImage() { + return image; + } + + /** + * Set the image shown in this control decoration. Update the rendered + * decoration. + * + * @param image + * the image to be shown adjacent to the control + */ + public void setImage(Image image) { + this.image = image; + update(); + } + + /** + * Get the bool that controls whether the decoration is shown only when + * the control has focus. The default value of this setting is + * <code>false</code>. + * + * @return <code>true</code> if the decoration should only be shown when + * the control has focus, and <code>false</code> if it should + * always be shown. Note that if the control is not capable of + * receiving focus (<code>DWT.NO_FOCUS</code>), then the + * decoration will never show when this value is <code>true</code>. + */ + public bool getShowOnlyOnFocus() { + return showOnlyOnFocus; + } + + /** + * Set the bool that controls whether the decoration is shown only when + * the control has focus. The default value of this setting is + * <code>false</code>. + * + * @param showOnlyOnFocus + * <code>true</code> if the decoration should only be shown + * when the control has focus, and <code>false</code> if it + * should always be shown. Note that if the control is not + * capable of receiving focus (<code>DWT.NO_FOCUS</code>), + * then the decoration will never show when this value is + * <code>true</code>. + */ + public void setShowOnlyOnFocus(bool showOnlyOnFocus) { + this.showOnlyOnFocus = showOnlyOnFocus; + update(); + } + + /** + * Get the bool that controls whether the decoration's description text + * should be shown in a hover when the user hovers over the decoration. The + * default value of this setting is <code>true</code>. + * + * @return <code>true</code> if a hover popup containing the decoration's + * description text should be shown when the user hovers over the + * decoration, and <code>false</code> if a hover should not be + * shown. + */ + public bool getShowHover() { + return showHover; + } + + /** + * Set the bool that controls whether the decoration's description text + * should be shown in a hover when the user hovers over the decoration. The + * default value of this setting is <code>true</code>. + * + * @param showHover + * <code>true</code> if a hover popup containing the + * decoration's description text should be shown when the user + * hovers over the decoration, and <code>false</code> if a + * hover should not be shown. + */ + public void setShowHover(bool showHover) { + this.showHover = showHover; + update(); + } + + /** + * Get the margin width in pixels that should be used between the decorator + * and the horizontal edge of the control. The default value of this setting + * is <code>0</code>. + * + * @return the number of pixels that should be reserved between the + * horizontal edge of the control and the adjacent edge of the + * decoration. + */ + public int getMarginWidth() { + return marginWidth; + } + + /** + * Set the margin width in pixels that should be used between the decorator + * and the horizontal edge of the control. The default value of this setting + * is <code>0</code>. + * + * @param marginWidth + * the number of pixels that should be reserved between the + * horizontal edge of the control and the adjacent edge of the + * decoration. + */ + public void setMarginWidth(int marginWidth) { + this.marginWidth = marginWidth; + update(); + } + + /** + * Something has changed, requiring redraw. Redraw the decoration and update + * the hover text if appropriate. + */ + protected void update() { + if (control is null || control.isDisposed()) { + return; + } + Rectangle rect = getDecorationRectangle(control.getShell()); + // Redraw this rectangle in all children + control.getShell() + .redraw(rect.x, rect.y, rect.width, rect.height, true); + control.getShell().update(); + if (hover !is null && getDescriptionText() !is null) { + hover.setText(getDescriptionText(), getDecorationRectangle(control + .getParent()), control); + } + } + + /* + * Show the specified text in the hover, positioning the hover near the + * specified control. + */ + private void showHoverText(String text, Control hoverNear) { + // If we aren't to show a hover, don't do anything. + if (!showHover) { + return; + } + // If there is no text, don't do anything. + if (text is null) { + hideHover(); + return; + } + + // If there is no control, nothing to do + if (control is null) { + return; + } + // Create the hover if it's not showing + if (hover is null) { + hover = new Hover(hoverNear.getShell()); + } + hover.setText(text, getDecorationRectangle(control.getParent()), + control); + hover.setVisible(true); + } + + /* + * Remove any listeners installed on the controls. + */ + private void removeControlListeners() { + if (control is null) { + return; + } + printRemoveListener(control, "FOCUS"); //$NON-NLS-1$ + control.removeFocusListener(focusListener); + focusListener = null; + + printRemoveListener(control, "DISPOSE"); //$NON-NLS-1$ + control.removeDisposeListener(disposeListener); + disposeListener = null; + + Composite c = control.getParent(); + while (c !is null) { + removeCompositeListeners(c); + if (composite !is null && composite is c) { + // We previously installed listeners only to the specified + // composite, so stop. + c = null; + } else if (cast(Shell)c ) { + // We previously installed listeners only up to the first Shell + // encountered, so stop. + c = null; + } else { + c = c.getParent(); + } + } + paintListener = null; + mouseTrackListener = null; + compositeListener = null; + + // We may have a remaining mouse move listener installed + if (moveListeningTarget !is null) { + printRemoveListener(moveListeningTarget, "MOUSEMOVE"); //$NON-NLS-1$ + moveListeningTarget.removeMouseMoveListener(mouseMoveListener); + moveListeningTarget = null; + mouseMoveListener = null; + } + if (DEBUG) { + if (listenerInstalls > 0) { + Stdout.formatln("LISTENER LEAK>>>CHECK TRACE ABOVE"); //$NON-NLS-1$ + } else if (listenerInstalls < 0) { + Stdout.formatln("REMOVED UNREGISTERED LISTENERS>>>CHECK TRACE ABOVE"); //$NON-NLS-1$ + } else { + Stdout.formatln("ALL INSTALLED LISTENERS WERE REMOVED."); //$NON-NLS-1$ + } + } + } + + /** + * Return the rectangle in which the decoration should be rendered, in + * coordinates relative to the specified control. If the specified control + * is null, return the rectangle in display coordinates. + * + * @param targetControl + * the control whose coordinates should be used + * @return the rectangle in which the decoration should be rendered + */ + protected Rectangle getDecorationRectangle(Control targetControl) { + if (getImage() is null || control is null) { + return new Rectangle(0, 0, 0, 0); + } + // Compute the bounds first relative to the control's parent. + Rectangle imageBounds = getImage().getBounds(); + Rectangle controlBounds = control.getBounds(); + int x, y; + // Compute x + if ((position & DWT.RIGHT) is DWT.RIGHT) { + x = controlBounds.x + controlBounds.width + marginWidth; + } else { + // default is left + x = controlBounds.x - imageBounds.width - marginWidth; + } + // Compute y + if ((position & DWT.TOP) is DWT.TOP) { + y = controlBounds.y; + } else if ((position & DWT.BOTTOM) is DWT.BOTTOM) { + y = controlBounds.y + control.getBounds().height + - imageBounds.height; + } else { + // default is center + y = controlBounds.y + + (control.getBounds().height - imageBounds.height) / 2; + } + + // Now convert to coordinates relative to the target control. + Point globalPoint = control.getParent().toDisplay(x, y); + Point targetPoint; + if (targetControl is null) { + targetPoint = globalPoint; + } else { + targetPoint = targetControl.toControl(globalPoint); + } + return new Rectangle(targetPoint.x, targetPoint.y, imageBounds.width, + imageBounds.height); + } + + /* + * Return true if the decoration should be shown, false if it should not. + */ + private bool shouldShowDecoration() { + if (!visible) { + return false; + } + if (control is null || control.isDisposed() || getImage() is null) { + return false; + } + + if (!control.isVisible()) { + return false; + } + if (showOnlyOnFocus) { + return hasFocus; + } + return true; + } + + /* + * If in debug mode, print info about adding the specified listener. + */ + private void printAddListener(Widget widget, String listenerType) { + listenerInstalls++; + if (DEBUG) { + Stdout.formatln("Added listener>>>{} to>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$ + } + } + + /* + * If in debug mode, print info about adding the specified listener. + */ + private void printRemoveListener(Widget widget, String listenerType) { + listenerInstalls--; + if (DEBUG) { + Stdout.formatln("Removed listener>>>{} from>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$ + } + } +}