| /******************************************************************************* |
| * 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 |
| *******************************************************************************/ |
| package org.eclipse.jface.fieldassist; |
| |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.MenuDetectEvent; |
| import org.eclipse.swt.events.MenuDetectListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.events.MouseTrackListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.graphics.Region; |
| 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.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Widget; |
| |
| /** |
| * 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 boolean DEBUG = false; |
| |
| /** |
| * Cached platform flags for dealing with platform-specific issues. |
| */ |
| private static boolean CARBON = "carbon".equals(SWT.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 boolean visible = true; |
| |
| /** |
| * Boolean indicating whether the decoration should only be shown when the |
| * control has focus |
| */ |
| private boolean showOnlyOnFocus = false; |
| |
| /** |
| * Boolean indicating whether the decoration should show its description |
| * text in a hover when the user hovers over the decoration. |
| */ |
| private boolean showHover = true; |
| |
| /** |
| * Margin width used between the decorator and the control. |
| */ |
| private int marginWidth = 0; |
| |
| /** |
| * Registered selection listeners. |
| */ |
| private ListenerList selectionListeners = new ListenerList(); |
| |
| /** |
| * Registered menu detect listeners. |
| */ |
| private ListenerList menuDetectListeners = new ListenerList(); |
| |
| /** |
| * 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 boolean 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 final 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; |
| |
| /** |
| * Boolean indicating whether the last computed polygon location had an |
| * arrow on left. (true if left, false if right). |
| */ |
| boolean arrowOnLeft = true; |
| |
| /* |
| * Create a hover parented by the specified shell. |
| */ |
| Hover(Shell parent) { |
| final Display display = parent.getDisplay(); |
| hoverShell = new Shell(parent, SWT.NO_TRIM | SWT.ON_TOP |
| | SWT.NO_FOCUS); |
| hoverShell.setBackground(display |
| .getSystemColor(SWT.COLOR_INFO_BACKGROUND)); |
| hoverShell.setForeground(display |
| .getSystemColor(SWT.COLOR_INFO_FOREGROUND)); |
| hoverShell.addPaintListener(new PaintListener() { |
| public void paintControl(PaintEvent pe) { |
| pe.gc.drawText(text, hm, hm); |
| if (!CARBON) { |
| pe.gc.drawPolygon(getPolygon(true)); |
| } |
| } |
| }); |
| hoverShell.addMouseListener(new 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(boolean border) { |
| Point e = getExtent(); |
| int b = border ? 1 : 0; |
| if (arrowOnLeft) { |
| return new int[] { 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 new int[] { 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 != null) { |
| region.dispose(); |
| } |
| } |
| |
| /* |
| * Set the visibility of the hover. |
| */ |
| void setVisible(boolean 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 == null) { |
| t = EMPTY; |
| } |
| if (!t.equals(text)) { |
| Point oldSize = getExtent(); |
| text = t; |
| hoverShell.redraw(); |
| Point newSize = getExtent(); |
| if (!oldSize.equals(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. |
| */ |
| boolean 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 != 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> |
| * SWT 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>SWT.LEFT</code> and |
| * <code>SWT.RIGHT</code> describe the horizontal placement of the |
| * decoration relative to the control, and the constants |
| * <code>SWT.TOP</code>, <code>SWT.CENTER</code>, and |
| * <code>SWT.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 SWT.LEFT | SWT.TOP. If no position style bits are |
| * specified, the control decoration will be positioned to the left and |
| * center of the control (<code>SWT.LEFT | SWT.CENTER</code>). |
| * </p> |
| * |
| * @param control |
| * the control to be decorated |
| * @param position |
| * bit-wise or of position constants (<code>SWT.TOP</code>, |
| * <code>SWT.BOTTOM</code>, <code>SWT.LEFT</code>, |
| * <code>SWT.RIGHT</code>, and <code>SWT.CENTER</code>). |
| */ |
| public ControlDecoration(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> |
| * SWT 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>SWT.LEFT</code> and |
| * <code>SWT.RIGHT</code> describe the horizontal placement of the |
| * decoration relative to the control, and the constants |
| * <code>SWT.TOP</code>, <code>SWT.CENTER</code>, and |
| * <code>SWT.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 SWT.LEFT | SWT.TOP. If no position style bits are |
| * specified, the control decoration will be positioned to the left and |
| * center of the control (<code>SWT.LEFT | SWT.CENTER</code>). |
| * </p> |
| * |
| * @param control |
| * the control to be decorated |
| * @param position |
| * bit-wise or of position constants (<code>SWT.TOP</code>, |
| * <code>SWT.BOTTOM</code>, <code>SWT.LEFT</code>, |
| * <code>SWT.RIGHT</code>, and <code>SWT.CENTER</code>). |
| * @param composite |
| * The SWT 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 ControlDecoration(Control control, int position, Composite composite) { |
| 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 org.eclipse.swt.events.MenuDetectListener |
| * @see org.eclipse.swt.events.MenuDetectEvent |
| * @see #removeMenuDetectListener |
| */ |
| public void addMenuDetectListener(MenuDetectListener listener) { |
| menuDetectListeners.add(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 org.eclipse.swt.events.MenuDetectListener |
| * @see #addMenuDetectListener |
| */ |
| public void removeMenuDetectListener(MenuDetectListener listener) { |
| menuDetectListeners.remove(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 org.eclipse.swt.events.SelectionListener |
| * @see org.eclipse.swt.events.SelectionEvent |
| * @see #removeSelectionListener |
| */ |
| public void addSelectionListener(SelectionListener listener) { |
| selectionListeners.add(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 org.eclipse.swt.events.SelectionListener |
| * @see #addSelectionListener |
| */ |
| public void removeSelectionListener(SelectionListener listener) { |
| selectionListeners.remove(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 == null) { |
| return; |
| } |
| if (hover != 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 DisposeListener() { |
| public void widgetDisposed(DisposeEvent event) { |
| dispose(); |
| } |
| }; |
| printAddListener(control, "DISPOSE"); //$NON-NLS-1$ |
| control.addDisposeListener(disposeListener); |
| |
| focusListener = new 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 PaintListener() { |
| public void paintControl(PaintEvent event) { |
| Control control = (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 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$ |
| ((Control) event.widget) |
| .removeMouseMoveListener(mouseMoveListener); |
| moveListeningTarget = null; |
| } |
| } |
| } |
| }; |
| |
| // Listener for tracking the beginning of a hover. Always installed. |
| mouseTrackListener = new MouseTrackListener() { |
| public void mouseExit(MouseEvent event) { |
| // Just in case we didn't catch it before. |
| Control target = (Control) event.widget; |
| if (target == moveListeningTarget) { |
| printRemoveListener(target, "MOUSEMOVE"); //$NON-NLS-1$ |
| target.removeMouseMoveListener(mouseMoveListener); |
| moveListeningTarget = null; |
| } |
| hideHover(); |
| } |
| |
| public void mouseHover(MouseEvent event) { |
| if (showHover) { |
| decorationRectangle = getDecorationRectangle((Control) event.widget); |
| if (decorationRectangle.contains(event.x, event.y)) { |
| showHoverText(getDescriptionText()); |
| Control target = (Control) event.widget; |
| if (moveListeningTarget == null) { |
| printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$ |
| target.addMouseMoveListener(mouseMoveListener); |
| moveListeningTarget = target; |
| } else if (target != 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 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 SWT.MouseDown: |
| if (!selectionListeners.isEmpty()) |
| notifySelectionListeners(event); |
| break; |
| case SWT.MouseDoubleClick: |
| if (!selectionListeners.isEmpty()) |
| notifySelectionListeners(event); |
| break; |
| case SWT.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 != null) { |
| installCompositeListeners(c); |
| if (composite != null && composite == c) { |
| // We just installed on the specified composite, so stop. |
| c = null; |
| } else if (c instanceof Shell) { |
| // 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, "SWT.MenuDetect"); //$NON-NLS-1$ |
| c.addListener(SWT.MenuDetect, compositeListener); |
| printAddListener(c, "SWT.MouseDown"); //$NON-NLS-1$ |
| c.addListener(SWT.MouseDown, compositeListener); |
| printAddListener(c, "SWT.MouseDoubleClick"); //$NON-NLS-1$ |
| c.addListener(SWT.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, "SWT.MenuDetect"); //$NON-NLS-1$ |
| c.removeListener(SWT.MenuDetect, compositeListener); |
| printRemoveListener(c, "SWT.MouseDown"); //$NON-NLS-1$ |
| c.removeListener(SWT.MouseDown, compositeListener); |
| printRemoveListener(c, "SWT.MouseDoubleClick"); //$NON-NLS-1$ |
| c.removeListener(SWT.MouseDoubleClick, compositeListener); |
| } |
| } |
| |
| private void notifySelectionListeners(Event event) { |
| if (!(event.widget instanceof Control)) { |
| return; |
| } |
| if (getDecorationRectangle((Control) event.widget).contains(event.x, |
| event.y)) { |
| SelectionEvent clientEvent = new SelectionEvent(event); |
| clientEvent.data = this; |
| if (getImage() != null) { |
| clientEvent.height = getImage().getBounds().height; |
| clientEvent.width = getImage().getBounds().width; |
| } |
| Object[] listeners; |
| switch (event.type) { |
| case SWT.MouseDoubleClick: |
| if (event.button == 1) { |
| listeners = selectionListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| ((SelectionListener) listeners[i]) |
| .widgetDefaultSelected(clientEvent); |
| } |
| } |
| break; |
| case SWT.MouseDown: |
| if (event.button == 1) { |
| listeners = selectionListeners.getListeners(); |
| for (int i = 0; i < listeners.length; i++) { |
| ((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++) { |
| ((MenuDetectListener) listeners[i]).menuDetected(clientEvent); |
| |
| } |
| } |
| } |
| |
| /** |
| * Show the specified text using the same hover dialog as is used to show |
| * decorator descriptions. When {@link #setShowHover(boolean)} 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 == null) { |
| return; |
| } |
| showHoverText(text, control); |
| } |
| |
| /** |
| * Hide any hover popups that are currently showing on the control. When |
| * {@link #setShowHover(boolean)} 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 != null) { |
| hover.setVisible(false); |
| } |
| } |
| |
| /** |
| * Show the control decoration. This message has no effect if the decoration |
| * is already showing. If {@link #setShowOnlyOnFocus(boolean)} 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 boolean 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>SWT.NO_FOCUS</code>), then the |
| * decoration will never show when this value is <code>true</code>. |
| */ |
| public boolean getShowOnlyOnFocus() { |
| return showOnlyOnFocus; |
| } |
| |
| /** |
| * Set the boolean 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>SWT.NO_FOCUS</code>), |
| * then the decoration will never show when this value is |
| * <code>true</code>. |
| */ |
| public void setShowOnlyOnFocus(boolean showOnlyOnFocus) { |
| this.showOnlyOnFocus = showOnlyOnFocus; |
| update(); |
| } |
| |
| /** |
| * Get the boolean 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 boolean getShowHover() { |
| return showHover; |
| } |
| |
| /** |
| * Set the boolean 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(boolean 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 == 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 != null && getDescriptionText() != 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 == null) { |
| hideHover(); |
| return; |
| } |
| |
| // If there is no control, nothing to do |
| if (control == null) { |
| return; |
| } |
| // Create the hover if it's not showing |
| if (hover == 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 == 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 != null) { |
| removeCompositeListeners(c); |
| if (composite != null && composite == c) { |
| // We previously installed listeners only to the specified |
| // composite, so stop. |
| c = null; |
| } else if (c instanceof Shell) { |
| // 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 != null) { |
| printRemoveListener(moveListeningTarget, "MOUSEMOVE"); //$NON-NLS-1$ |
| moveListeningTarget.removeMouseMoveListener(mouseMoveListener); |
| moveListeningTarget = null; |
| mouseMoveListener = null; |
| } |
| if (DEBUG) { |
| if (listenerInstalls > 0) { |
| System.out.println("LISTENER LEAK>>>CHECK TRACE ABOVE"); //$NON-NLS-1$ |
| } else if (listenerInstalls < 0) { |
| System.out |
| .println("REMOVED UNREGISTERED LISTENERS>>>CHECK TRACE ABOVE"); //$NON-NLS-1$ |
| } else { |
| System.out.println("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() == null || control == 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 & SWT.RIGHT) == SWT.RIGHT) { |
| x = controlBounds.x + controlBounds.width + marginWidth; |
| } else { |
| // default is left |
| x = controlBounds.x - imageBounds.width - marginWidth; |
| } |
| // Compute y |
| if ((position & SWT.TOP) == SWT.TOP) { |
| y = controlBounds.y; |
| } else if ((position & SWT.BOTTOM) == SWT.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 == 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 boolean shouldShowDecoration() { |
| if (!visible) { |
| return false; |
| } |
| if (control == null || control.isDisposed() || getImage() == 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) { |
| System.out |
| .println("Added listener>>>" + listenerType + " to>>>" + 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) { |
| System.out |
| .println("Removed listener>>>" + listenerType + " from>>>" + widget); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| } |
| } |