comparison 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
comparison
equal deleted inserted replaced
28:50b0163e18f8 29:f12d40e7da8f
1 /*******************************************************************************
2 * Copyright (c) 2006, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.jface.fieldassist.ControlDecoration;
14
15
16 import dwt.DWT;
17 import dwt.events.DisposeEvent;
18 import dwt.events.DisposeListener;
19 import dwt.events.FocusEvent;
20 import dwt.events.FocusListener;
21 import dwt.events.MenuDetectEvent;
22 import dwt.events.MenuDetectListener;
23 import dwt.events.MouseAdapter;
24 import dwt.events.MouseEvent;
25 import dwt.events.MouseMoveListener;
26 import dwt.events.MouseTrackListener;
27 import dwt.events.PaintEvent;
28 import dwt.events.PaintListener;
29 import dwt.events.SelectionEvent;
30 import dwt.events.SelectionListener;
31 import dwt.graphics.GC;
32 import dwt.graphics.Image;
33 import dwt.graphics.Point;
34 import dwt.graphics.Rectangle;
35 import dwt.graphics.Region;
36 import dwt.widgets.Composite;
37 import dwt.widgets.Control;
38 import dwt.widgets.Display;
39 import dwt.widgets.Event;
40 import dwt.widgets.Listener;
41 import dwt.widgets.Shell;
42 import dwt.widgets.Widget;
43 import dwtx.core.runtime.ListenerList;
44
45 import dwt.dwthelper.utils;
46 import tango.io.Stdout;
47
48 /**
49 * ControlDecoration renders an image decoration near a control. It allows
50 * clients to specify an image and a position for the image relative to the
51 * control. A ControlDecoration may be assigned description text, which can
52 * optionally be shown when the user hovers over the image. Clients can decorate
53 * any kind of control.
54 * <p>
55 * Decoration images always appear on the left or right side of the field, never
56 * above or below it. Decorations can be positioned at the top, center, or
57 * bottom of either side of the control. Future implementations may provide
58 * additional positioning options for decorations.
59 * <p>
60 * ControlDecoration renders the image adjacent to the specified (already
61 * created) control, with no guarantee that it won't be clipped or otherwise
62 * obscured or overlapped by adjacent controls, including another
63 * ControlDecoration placed in the same location. Clients should ensure that
64 * there is adequate space adjacent to the control to show the decoration
65 * properly.
66 * <p>
67 * Clients using ControlDecoration should typically ensure that enough margin
68 * space is reserved for a decoration by altering the layout data margins,
69 * although this is not assumed or required by the ControlDecoration
70 * implementation.
71 * <p>
72 * This class is intended to be instantiated and used by clients. It is not
73 * intended to be subclassed by clients.
74 *
75 * @since 3.3
76 *
77 * @see FieldDecoration
78 * @see FieldDecorationRegistry
79 */
80 public class ControlDecoration {
81 /**
82 * Debug flag for tracing
83 */
84 private static bool DEBUG = false;
85
86 /**
87 * Cached platform flags for dealing with platform-specific issues.
88 */
89 private static bool CARBON = "carbon".equals(DWT.getPlatform()); //$NON-NLS-1$
90
91 /**
92 * The associated control
93 */
94 private Control control;
95
96 /**
97 * The composite on which to render the decoration and hook mouse events, or
98 * null if we are hooking all parent composites.
99 */
100 private Composite composite;
101
102 /**
103 * The associated image.
104 */
105 private Image image;
106
107 /**
108 * The associated description text.
109 */
110 private String descriptionText;
111 /**
112 * The position of the decoration.
113 */
114 private int position;
115
116 /**
117 * The decoration's visibility flag
118 */
119 private bool visible = true;
120
121 /**
122 * bool indicating whether the decoration should only be shown when the
123 * control has focus
124 */
125 private bool showOnlyOnFocus = false;
126
127 /**
128 * bool indicating whether the decoration should show its description
129 * text in a hover when the user hovers over the decoration.
130 */
131 private bool showHover = true;
132
133 /**
134 * Margin width used between the decorator and the control.
135 */
136 private int marginWidth = 0;
137
138 /**
139 * Registered selection listeners.
140 */
141 private ListenerList selectionListeners;
142
143 /**
144 * Registered menu detect listeners.
145 */
146 private ListenerList menuDetectListeners;
147
148 /**
149 * The focus listener
150 */
151 private FocusListener focusListener;
152
153 /**
154 * The dispose listener
155 */
156 private DisposeListener disposeListener;
157
158 /**
159 * The paint listener installed for drawing the decoration
160 */
161 private PaintListener paintListener;
162
163 /**
164 * The mouse listener installed for tracking the hover
165 */
166 private MouseTrackListener mouseTrackListener;
167
168 /**
169 * The mouse move listener installed for tracking the hover
170 */
171 private MouseMoveListener mouseMoveListener;
172
173 /**
174 * The untyped listener installed for notifying external listeners
175 */
176 private Listener compositeListener;
177
178 /**
179 * Control that we last installed a move listener on. We only want one at a
180 * time.
181 */
182 private Control moveListeningTarget = null;
183
184 /**
185 * Debug counter used to match add and remove listeners
186 */
187 private int listenerInstalls = 0;
188
189 /**
190 * The current rectangle used for tracking mouse moves
191 */
192 private Rectangle decorationRectangle;
193
194 /**
195 * An internal flag tracking whether we have focus. We use this rather than
196 * isFocusControl() so that we can set the flag as soon as we get the focus
197 * callback, rather than having to do an asyncExec in the middle of a focus
198 * callback to ensure that isFocusControl() represents the outcome of the
199 * event.
200 */
201 private bool hasFocus = false;
202
203 /**
204 * The hover used for showing description text
205 */
206 private Hover hover;
207
208 /**
209 * The hover used to show a decoration image's description.
210 */
211 class Hover {
212 private static const String EMPTY = ""; //$NON-NLS-1$
213
214 /**
215 * Offset of info hover arrow from the left or right side.
216 */
217 private int hao = 10;
218
219 /**
220 * Width of info hover arrow.
221 */
222 private int haw = 8;
223
224 /**
225 * Height of info hover arrow.
226 */
227 private int hah = 10;
228
229 /**
230 * Margin around info hover text.
231 */
232 private int hm = 2;
233
234 /**
235 * This info hover's shell.
236 */
237 Shell hoverShell;
238
239 /**
240 * The info hover text.
241 */
242 String text = EMPTY;
243
244 /**
245 * The region used to manage the shell shape
246 */
247 Region region;
248
249 /**
250 * bool indicating whether the last computed polygon location had an
251 * arrow on left. (true if left, false if right).
252 */
253 bool arrowOnLeft = true;
254
255 /*
256 * Create a hover parented by the specified shell.
257 */
258 this(Shell parent) {
259 Display display = parent.getDisplay();
260 hoverShell = new Shell(parent, DWT.NO_TRIM | DWT.ON_TOP
261 | DWT.NO_FOCUS);
262 hoverShell.setBackground(display
263 .getSystemColor(DWT.COLOR_INFO_BACKGROUND));
264 hoverShell.setForeground(display
265 .getSystemColor(DWT.COLOR_INFO_FOREGROUND));
266 hoverShell.addPaintListener(new class PaintListener {
267 public void paintControl(PaintEvent pe) {
268 pe.gc.drawText(text, hm, hm);
269 if (!CARBON) {
270 pe.gc.drawPolygon(getPolygon(true));
271 }
272 }
273 });
274 hoverShell.addMouseListener(new class MouseAdapter {
275 public void mouseDown(MouseEvent e) {
276 hideHover();
277 }
278 });
279 }
280
281 /*
282 * Compute a polygon that represents a hover with an arrow pointer. If
283 * border is true, compute the polygon inset by 1-pixel border. Consult
284 * the arrowOnLeft flag to determine which side the arrow is on.
285 */
286 int[] getPolygon(bool border) {
287 Point e = getExtent();
288 int b = border ? 1 : 0;
289 if (arrowOnLeft) {
290 return [ 0, 0, e.x - b, 0, e.x - b, e.y - b,
291 hao + haw, e.y - b, hao + haw / 2, e.y + hah - b, hao,
292 e.y - b, 0, e.y - b, 0, 0 ];
293 }
294 return [ 0, 0, e.x - b, 0, e.x - b, e.y - b,
295 e.x - hao - b, e.y - b, e.x - hao - haw / 2, e.y + hah - b,
296 e.x - hao - haw, e.y - b, 0, e.y - b, 0, 0 ];
297 }
298
299 /*
300 * Dispose the hover, it is no longer needed. Dispose any resources
301 * allocated by the hover.
302 */
303 void dispose() {
304 if (!hoverShell.isDisposed()) {
305 hoverShell.dispose();
306 }
307 if (region !is null) {
308 region.dispose();
309 }
310 }
311
312 /*
313 * Set the visibility of the hover.
314 */
315 void setVisible(bool visible) {
316 if (visible) {
317 if (!hoverShell.isVisible()) {
318 hoverShell.setVisible(true);
319 }
320 } else {
321 if (hoverShell.isVisible()) {
322 hoverShell.setVisible(false);
323 }
324 }
325 }
326
327 /*
328 * Set the text of the hover to the specified text. Recompute the size
329 * and location of the hover to hover near the decoration rectangle,
330 * pointing the arrow toward the target control.
331 */
332 void setText(String t, Rectangle decorationRectangle,
333 Control targetControl) {
334 if (t is null) {
335 t = EMPTY;
336 }
337 if (!t.equals(text)) {
338 Point oldSize = getExtent();
339 text = t;
340 hoverShell.redraw();
341 Point newSize = getExtent();
342 if (!oldSize.opEquals(newSize)) {
343 // set a flag that indicates the direction of arrow
344 arrowOnLeft = decorationRectangle.x <= targetControl
345 .getLocation().x;
346 setNewShape();
347 }
348 }
349
350 Point extent = getExtent();
351 int y = -extent.y - hah + 1;
352 int x = arrowOnLeft ? -hao + haw / 2 : -extent.x + hao + haw / 2;
353
354 hoverShell.setLocation(control.getParent().toDisplay(
355 decorationRectangle.x + x, decorationRectangle.y + y));
356 }
357
358 /*
359 * Return whether or not the hover (shell) is visible.
360 */
361 bool isVisible() {
362 return hoverShell.isVisible();
363 }
364
365 /*
366 * Compute the extent of the hover for the current text.
367 */
368 Point getExtent() {
369 GC gc = new GC(hoverShell);
370 Point e = gc.textExtent(text);
371 gc.dispose();
372 e.x += hm * 2;
373 e.y += hm * 2;
374 return e;
375 }
376
377 /*
378 * Compute a new shape for the hover shell.
379 */
380 void setNewShape() {
381 Region oldRegion = region;
382 region = new Region();
383 region.add(getPolygon(false));
384 hoverShell.setRegion(region);
385 if (oldRegion !is null) {
386 oldRegion.dispose();
387 }
388
389 }
390 }
391
392 /**
393 * Construct a ControlDecoration for decorating the specified control at the
394 * specified position relative to the control. Render the decoration on top
395 * of any Control that happens to appear at the specified location.
396 * <p>
397 * DWT constants are used to specify the position of the decoration relative
398 * to the control. The position should include style bits describing both
399 * the vertical and horizontal orientation. <code>DWT.LEFT</code> and
400 * <code>DWT.RIGHT</code> describe the horizontal placement of the
401 * decoration relative to the control, and the constants
402 * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and
403 * <code>DWT.BOTTOM</code> describe the vertical alignment of the
404 * decoration relative to the control. Decorations always appear on either
405 * the left or right side of the control, never above or below it. For
406 * example, a decoration appearing on the left side of the field, at the
407 * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
408 * specified, the control decoration will be positioned to the left and
409 * center of the control (<code>DWT.LEFT | DWT.CENTER</code>).
410 * </p>
411 *
412 * @param control
413 * the control to be decorated
414 * @param position
415 * bit-wise or of position constants (<code>DWT.TOP</code>,
416 * <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>,
417 * <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>).
418 */
419 public this(Control control, int position) {
420 this(control, position, null);
421
422 }
423
424 /**
425 * Construct a ControlDecoration for decorating the specified control at the
426 * specified position relative to the control. Render the decoration only on
427 * the specified Composite or its children. The decoration will be clipped
428 * if it does not appear within the visible bounds of the composite or its
429 * child composites.
430 * <p>
431 * DWT constants are used to specify the position of the decoration relative
432 * to the control. The position should include style bits describing both
433 * the vertical and horizontal orientation. <code>DWT.LEFT</code> and
434 * <code>DWT.RIGHT</code> describe the horizontal placement of the
435 * decoration relative to the control, and the constants
436 * <code>DWT.TOP</code>, <code>DWT.CENTER</code>, and
437 * <code>DWT.BOTTOM</code> describe the vertical alignment of the
438 * decoration relative to the control. Decorations always appear on either
439 * the left or right side of the control, never above or below it. For
440 * example, a decoration appearing on the left side of the field, at the
441 * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
442 * specified, the control decoration will be positioned to the left and
443 * center of the control (<code>DWT.LEFT | DWT.CENTER</code>).
444 * </p>
445 *
446 * @param control
447 * the control to be decorated
448 * @param position
449 * bit-wise or of position constants (<code>DWT.TOP</code>,
450 * <code>DWT.BOTTOM</code>, <code>DWT.LEFT</code>,
451 * <code>DWT.RIGHT</code>, and <code>DWT.CENTER</code>).
452 * @param composite
453 * The DWT composite within which the decoration should be
454 * rendered. The decoration will be clipped to this composite,
455 * but it may be rendered on a child of the composite. The
456 * decoration will not be visible if the specified composite or
457 * its child composites are not visible in the space relative to
458 * the control, where the decoration is to be rendered. If this
459 * value is <code>null</code>, then the decoration will be
460 * rendered on whichever composite (or composites) are located in
461 * the specified position.
462 */
463 public this(Control control, int position, Composite composite) {
464 selectionListeners = new ListenerList();
465 menuDetectListeners = new ListenerList();
466 this.position = position;
467 this.control = control;
468 this.composite = composite;
469
470 addControlListeners();
471
472 }
473
474 /**
475 * Adds the listener to the collection of listeners who will be notified
476 * when the platform-specific context menu trigger has occurred, by sending
477 * it one of the messages defined in the <code>MenuDetectListener</code>
478 * interface.
479 * <p>
480 * The <code>widget</code> field in the SelectionEvent will contain the
481 * Composite on which the decoration is rendered that received the click.
482 * The <code>x</code> and <code>y</code> fields will be in coordinates
483 * relative to the display. The <code>data</code> field will contain the
484 * decoration that received the event.
485 * </p>
486 *
487 * @param listener
488 * the listener which should be notified
489 *
490 * @see dwt.events.MenuDetectListener
491 * @see dwt.events.MenuDetectEvent
492 * @see #removeMenuDetectListener
493 */
494 public void addMenuDetectListener(MenuDetectListener listener) {
495 menuDetectListeners.add(cast(Object)listener);
496 }
497
498 /**
499 * Removes the listener from the collection of listeners who will be
500 * notified when the platform-specific context menu trigger has occurred.
501 *
502 * @param listener
503 * the listener which should no longer be notified. This message
504 * has no effect if the listener was not previously added to the
505 * receiver.
506 *
507 * @see dwt.events.MenuDetectListener
508 * @see #addMenuDetectListener
509 */
510 public void removeMenuDetectListener(MenuDetectListener listener) {
511 menuDetectListeners.remove(cast(Object)listener);
512 }
513
514 /**
515 * Adds the listener to the collection of listeners who will be notified
516 * when the decoration is selected, by sending it one of the messages
517 * defined in the <code>SelectionListener</code> interface.
518 * <p>
519 * <code>widgetSelected</code> is called when the decoration is selected
520 * (by mouse click). <code>widgetDefaultSelected</code> is called when the
521 * decoration is double-clicked.
522 * </p>
523 * <p>
524 * The <code>widget</code> field in the SelectionEvent will contain the
525 * Composite on which the decoration is rendered that received the click.
526 * The <code>x</code> and <code>y</code> fields will be in coordinates
527 * relative to that widget. The <code>data</code> field will contain the
528 * decoration that received the event.
529 * </p>
530 *
531 * @param listener
532 * the listener which should be notified
533 *
534 * @see dwt.events.SelectionListener
535 * @see dwt.events.SelectionEvent
536 * @see #removeSelectionListener
537 */
538 public void addSelectionListener(SelectionListener listener) {
539 selectionListeners.add(cast(Object)listener);
540 }
541
542 /**
543 * Removes the listener from the collection of listeners who will be
544 * notified when the decoration is selected.
545 *
546 * @param listener
547 * the listener which should no longer be notified. This message
548 * has no effect if the listener was not previously added to the
549 * receiver.
550 *
551 * @see dwt.events.SelectionListener
552 * @see #addSelectionListener
553 */
554 public void removeSelectionListener(SelectionListener listener) {
555 selectionListeners.remove(cast(Object)listener);
556 }
557
558 /**
559 * Dispose this ControlDecoration. Unhook any listeners that have been
560 * installed on the target control. This method has no effect if the
561 * receiver is already disposed.
562 */
563 public void dispose() {
564 if (control is null) {
565 return;
566 }
567 if (hover !is null) {
568 hover.dispose();
569 hover = null;
570 }
571 removeControlListeners();
572 control = null;
573 }
574
575 /**
576 * Get the control that is decorated by the receiver.
577 *
578 * @return the Control decorated by the receiver. May be <code>null</code>
579 * if the control has been uninstalled.
580 */
581 public Control getControl() {
582 return control;
583 }
584
585 /**
586 * Add any listeners needed on the target control and on the composite where
587 * the decoration is to be rendered.
588 */
589 private void addControlListeners() {
590 disposeListener = new class DisposeListener {
591 public void widgetDisposed(DisposeEvent event) {
592 dispose();
593 }
594 };
595 printAddListener(control, "DISPOSE"); //$NON-NLS-1$
596 control.addDisposeListener(disposeListener);
597
598 focusListener = new class FocusListener {
599 public void focusGained(FocusEvent event) {
600 hasFocus = true;
601 if (showOnlyOnFocus) {
602 update();
603 }
604 }
605
606 public void focusLost(FocusEvent event) {
607 hasFocus = false;
608 if (showOnlyOnFocus) {
609 update();
610 }
611 }
612 };
613 printAddListener(control, "FOCUS"); //$NON-NLS-1$
614 control.addFocusListener(focusListener);
615
616 // Listener for painting the decoration
617 paintListener = new class PaintListener {
618 public void paintControl(PaintEvent event) {
619 Control control = cast(Control) event.widget;
620 Rectangle rect = getDecorationRectangle(control);
621 if (shouldShowDecoration()) {
622 event.gc.drawImage(getImage(), rect.x, rect.y);
623 }
624 }
625 };
626
627 // Listener for tracking the end of a hover. Only installed
628 // after a hover begins.
629 mouseMoveListener = new class MouseMoveListener {
630 public void mouseMove(MouseEvent event) {
631 if (showHover) {
632 if (!decorationRectangle.contains(event.x, event.y)) {
633 hideHover();
634 // No need to listen any longer
635 printRemoveListener(event.widget, "MOUSEMOVE"); //$NON-NLS-1$
636 (cast(Control) event.widget)
637 .removeMouseMoveListener(mouseMoveListener);
638 moveListeningTarget = null;
639 }
640 }
641 }
642 };
643
644 // Listener for tracking the beginning of a hover. Always installed.
645 mouseTrackListener = new class MouseTrackListener {
646 public void mouseExit(MouseEvent event) {
647 // Just in case we didn't catch it before.
648 Control target = cast(Control) event.widget;
649 if (target is moveListeningTarget) {
650 printRemoveListener(target, "MOUSEMOVE"); //$NON-NLS-1$
651 target.removeMouseMoveListener(mouseMoveListener);
652 moveListeningTarget = null;
653 }
654 hideHover();
655 }
656
657 public void mouseHover(MouseEvent event) {
658 if (showHover) {
659 decorationRectangle = getDecorationRectangle(cast(Control) event.widget);
660 if (decorationRectangle.contains(event.x, event.y)) {
661 showHoverText(getDescriptionText());
662 Control target = cast(Control) event.widget;
663 if (moveListeningTarget is null) {
664 printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
665 target.addMouseMoveListener(mouseMoveListener);
666 moveListeningTarget = target;
667 } else if (target !is moveListeningTarget) {
668 printRemoveListener(moveListeningTarget,
669 "MOUSEMOVE"); //$NON-NLS-1$
670 moveListeningTarget
671 .removeMouseMoveListener(mouseMoveListener);
672 printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
673 target.addMouseMoveListener(mouseMoveListener);
674 moveListeningTarget = target;
675 } else {
676 // It is already installed on this control.
677 }
678 }
679 }
680 }
681
682 public void mouseEnter(MouseEvent event) {
683 // Nothing to do until a hover occurs.
684 }
685 };
686
687 compositeListener = new class Listener {
688 public void handleEvent(Event event) {
689 // Don't forward events if decoration is not showing
690 if (!visible) {
691 return;
692 }
693 // Notify listeners if any are registered.
694 switch (event.type) {
695 case DWT.MouseDown:
696 if (!selectionListeners.isEmpty())
697 notifySelectionListeners(event);
698 break;
699 case DWT.MouseDoubleClick:
700 if (!selectionListeners.isEmpty())
701 notifySelectionListeners(event);
702 break;
703 case DWT.MenuDetect:
704 if (!menuDetectListeners.isEmpty())
705 notifyMenuDetectListeners(event);
706 break;
707 }
708 }
709 };
710
711 // We do not know which parent in the control hierarchy
712 // is providing the decoration space, so hook all the way up, until
713 // the shell or the specified parent composite is reached.
714 Composite c = control.getParent();
715 while (c !is null) {
716 installCompositeListeners(c);
717 if (composite !is null && composite is c) {
718 // We just installed on the specified composite, so stop.
719 c = null;
720 } else if (cast(Shell)c ) {
721 // We just installed on a shell, so don't go further
722 c = null;
723 } else {
724 c = c.getParent();
725 }
726 }
727 // force a redraw of the decoration area so our paint listener
728 // is notified.
729 update();
730 }
731
732 /*
733 * Install the listeners used to paint and track mouse events on the
734 * composite.
735 */
736 private void installCompositeListeners(Composite c) {
737 if (!c.isDisposed()) {
738 printAddListener(c, "PAINT"); //$NON-NLS-1$
739 c.addPaintListener(paintListener);
740 printAddListener(c, "MOUSETRACK"); //$NON-NLS-1$
741 c.addMouseTrackListener(mouseTrackListener);
742 printAddListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
743 c.addListener(DWT.MenuDetect, compositeListener);
744 printAddListener(c, "DWT.MouseDown"); //$NON-NLS-1$
745 c.addListener(DWT.MouseDown, compositeListener);
746 printAddListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
747 c.addListener(DWT.MouseDoubleClick, compositeListener);
748 }
749 }
750
751 /*
752 * Remove the listeners used to paint and track mouse events on the
753 * composite.
754 */
755 private void removeCompositeListeners(Composite c) {
756 if (!c.isDisposed()) {
757 printRemoveListener(c, "PAINT"); //$NON-NLS-1$
758 c.removePaintListener(paintListener);
759 printRemoveListener(c, "MOUSETRACK"); //$NON-NLS-1$
760 c.removeMouseTrackListener(mouseTrackListener);
761 printRemoveListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
762 c.removeListener(DWT.MenuDetect, compositeListener);
763 printRemoveListener(c, "DWT.MouseDown"); //$NON-NLS-1$
764 c.removeListener(DWT.MouseDown, compositeListener);
765 printRemoveListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
766 c.removeListener(DWT.MouseDoubleClick, compositeListener);
767 }
768 }
769
770 private void notifySelectionListeners(Event event) {
771 if (!(cast(Control)event.widget )) {
772 return;
773 }
774 if (getDecorationRectangle(cast(Control) event.widget).contains(event.x,
775 event.y)) {
776 SelectionEvent clientEvent = new SelectionEvent(event);
777 clientEvent.data = this;
778 if (getImage() !is null) {
779 clientEvent.height = getImage().getBounds().height;
780 clientEvent.width = getImage().getBounds().width;
781 }
782 Object[] listeners;
783 switch (event.type) {
784 case DWT.MouseDoubleClick:
785 if (event.button is 1) {
786 listeners = selectionListeners.getListeners();
787 for (int i = 0; i < listeners.length; i++) {
788 (cast(SelectionListener) listeners[i])
789 .widgetDefaultSelected(clientEvent);
790 }
791 }
792 break;
793 case DWT.MouseDown:
794 if (event.button is 1) {
795 listeners = selectionListeners.getListeners();
796 for (int i = 0; i < listeners.length; i++) {
797 (cast(SelectionListener) listeners[i])
798 .widgetSelected(clientEvent);
799 }
800 }
801 break;
802 }
803 }
804 }
805
806 private void notifyMenuDetectListeners(Event event) {
807 if (getDecorationRectangle(null).contains(event.x, event.y)) {
808 MenuDetectEvent clientEvent = new MenuDetectEvent(event);
809 clientEvent.data = this;
810 Object[] listeners = menuDetectListeners.getListeners();
811 for (int i = 0; i < listeners.length; i++) {
812 (cast(MenuDetectListener) listeners[i]).menuDetected(clientEvent);
813
814 }
815 }
816 }
817
818 /**
819 * Show the specified text using the same hover dialog as is used to show
820 * decorator descriptions. When {@link #setShowHover(bool)} has been set
821 * to <code>true</code>, a decoration's description text will be shown in
822 * an info hover over the field's control whenever the mouse hovers over the
823 * decoration. This method can be used to show a decoration's description
824 * text at other times (such as when the control receives focus), or to show
825 * other text associated with the field.
826 *
827 * @param text
828 * the text to be shown in the info hover, or <code>null</code>
829 * if no text should be shown.
830 */
831 public void showHoverText(String text) {
832 if (control is null) {
833 return;
834 }
835 showHoverText(text, control);
836 }
837
838 /**
839 * Hide any hover popups that are currently showing on the control. When
840 * {@link #setShowHover(bool)} has been set to <code>true</code>, a
841 * decoration's description text will be shown in an info hover over the
842 * field's control as long as the mouse hovers over the decoration, and will
843 * be hidden when the mouse exits the decoration. This method can be used to
844 * hide a hover, whether it was shown explicitly using
845 * {@link #showHoverText(String)}, or was showing because the user was
846 * hovering in the decoration.
847 * <p>
848 * This message has no effect if there is no current hover.
849 *
850 */
851 public void hideHover() {
852 if (hover !is null) {
853 hover.setVisible(false);
854 }
855 }
856
857 /**
858 * Show the control decoration. This message has no effect if the decoration
859 * is already showing. If {@link #setShowOnlyOnFocus(bool)} is set to
860 * <code>true</code>, the decoration will only be shown if the control
861 * has focus.
862 */
863 public void show() {
864 if (!visible) {
865 visible = true;
866 update();
867 }
868 }
869
870 /**
871 * Hide the control decoration. This message has no effect if the decoration
872 * is already hidden.
873 */
874 public void hide() {
875 if (visible) {
876 visible = false;
877 update();
878 }
879 }
880
881 /**
882 * Get the description text that may be shown in a hover for this
883 * decoration.
884 *
885 * @return the text to be shown as a description for the decoration, or
886 * <code>null</code> if none has been set.
887 */
888 public String getDescriptionText() {
889 return descriptionText;
890 }
891
892 /**
893 * Set the image shown in this control decoration. Update the rendered
894 * decoration.
895 *
896 * @param text
897 * the text to be shown as a description for the decoration, or
898 * <code>null</code> if none has been set.
899 */
900 public void setDescriptionText(String text) {
901 this.descriptionText = text;
902 update();
903 }
904
905 /**
906 * Get the image shown in this control decoration.
907 *
908 * @return the image to be shown adjacent to the control, or
909 * <code>null</code> if one has not been set.
910 */
911 public Image getImage() {
912 return image;
913 }
914
915 /**
916 * Set the image shown in this control decoration. Update the rendered
917 * decoration.
918 *
919 * @param image
920 * the image to be shown adjacent to the control
921 */
922 public void setImage(Image image) {
923 this.image = image;
924 update();
925 }
926
927 /**
928 * Get the bool that controls whether the decoration is shown only when
929 * the control has focus. The default value of this setting is
930 * <code>false</code>.
931 *
932 * @return <code>true</code> if the decoration should only be shown when
933 * the control has focus, and <code>false</code> if it should
934 * always be shown. Note that if the control is not capable of
935 * receiving focus (<code>DWT.NO_FOCUS</code>), then the
936 * decoration will never show when this value is <code>true</code>.
937 */
938 public bool getShowOnlyOnFocus() {
939 return showOnlyOnFocus;
940 }
941
942 /**
943 * Set the bool that controls whether the decoration is shown only when
944 * the control has focus. The default value of this setting is
945 * <code>false</code>.
946 *
947 * @param showOnlyOnFocus
948 * <code>true</code> if the decoration should only be shown
949 * when the control has focus, and <code>false</code> if it
950 * should always be shown. Note that if the control is not
951 * capable of receiving focus (<code>DWT.NO_FOCUS</code>),
952 * then the decoration will never show when this value is
953 * <code>true</code>.
954 */
955 public void setShowOnlyOnFocus(bool showOnlyOnFocus) {
956 this.showOnlyOnFocus = showOnlyOnFocus;
957 update();
958 }
959
960 /**
961 * Get the bool that controls whether the decoration's description text
962 * should be shown in a hover when the user hovers over the decoration. The
963 * default value of this setting is <code>true</code>.
964 *
965 * @return <code>true</code> if a hover popup containing the decoration's
966 * description text should be shown when the user hovers over the
967 * decoration, and <code>false</code> if a hover should not be
968 * shown.
969 */
970 public bool getShowHover() {
971 return showHover;
972 }
973
974 /**
975 * Set the bool that controls whether the decoration's description text
976 * should be shown in a hover when the user hovers over the decoration. The
977 * default value of this setting is <code>true</code>.
978 *
979 * @param showHover
980 * <code>true</code> if a hover popup containing the
981 * decoration's description text should be shown when the user
982 * hovers over the decoration, and <code>false</code> if a
983 * hover should not be shown.
984 */
985 public void setShowHover(bool showHover) {
986 this.showHover = showHover;
987 update();
988 }
989
990 /**
991 * Get the margin width in pixels that should be used between the decorator
992 * and the horizontal edge of the control. The default value of this setting
993 * is <code>0</code>.
994 *
995 * @return the number of pixels that should be reserved between the
996 * horizontal edge of the control and the adjacent edge of the
997 * decoration.
998 */
999 public int getMarginWidth() {
1000 return marginWidth;
1001 }
1002
1003 /**
1004 * Set the margin width in pixels that should be used between the decorator
1005 * and the horizontal edge of the control. The default value of this setting
1006 * is <code>0</code>.
1007 *
1008 * @param marginWidth
1009 * the number of pixels that should be reserved between the
1010 * horizontal edge of the control and the adjacent edge of the
1011 * decoration.
1012 */
1013 public void setMarginWidth(int marginWidth) {
1014 this.marginWidth = marginWidth;
1015 update();
1016 }
1017
1018 /**
1019 * Something has changed, requiring redraw. Redraw the decoration and update
1020 * the hover text if appropriate.
1021 */
1022 protected void update() {
1023 if (control is null || control.isDisposed()) {
1024 return;
1025 }
1026 Rectangle rect = getDecorationRectangle(control.getShell());
1027 // Redraw this rectangle in all children
1028 control.getShell()
1029 .redraw(rect.x, rect.y, rect.width, rect.height, true);
1030 control.getShell().update();
1031 if (hover !is null && getDescriptionText() !is null) {
1032 hover.setText(getDescriptionText(), getDecorationRectangle(control
1033 .getParent()), control);
1034 }
1035 }
1036
1037 /*
1038 * Show the specified text in the hover, positioning the hover near the
1039 * specified control.
1040 */
1041 private void showHoverText(String text, Control hoverNear) {
1042 // If we aren't to show a hover, don't do anything.
1043 if (!showHover) {
1044 return;
1045 }
1046 // If there is no text, don't do anything.
1047 if (text is null) {
1048 hideHover();
1049 return;
1050 }
1051
1052 // If there is no control, nothing to do
1053 if (control is null) {
1054 return;
1055 }
1056 // Create the hover if it's not showing
1057 if (hover is null) {
1058 hover = new Hover(hoverNear.getShell());
1059 }
1060 hover.setText(text, getDecorationRectangle(control.getParent()),
1061 control);
1062 hover.setVisible(true);
1063 }
1064
1065 /*
1066 * Remove any listeners installed on the controls.
1067 */
1068 private void removeControlListeners() {
1069 if (control is null) {
1070 return;
1071 }
1072 printRemoveListener(control, "FOCUS"); //$NON-NLS-1$
1073 control.removeFocusListener(focusListener);
1074 focusListener = null;
1075
1076 printRemoveListener(control, "DISPOSE"); //$NON-NLS-1$
1077 control.removeDisposeListener(disposeListener);
1078 disposeListener = null;
1079
1080 Composite c = control.getParent();
1081 while (c !is null) {
1082 removeCompositeListeners(c);
1083 if (composite !is null && composite is c) {
1084 // We previously installed listeners only to the specified
1085 // composite, so stop.
1086 c = null;
1087 } else if (cast(Shell)c ) {
1088 // We previously installed listeners only up to the first Shell
1089 // encountered, so stop.
1090 c = null;
1091 } else {
1092 c = c.getParent();
1093 }
1094 }
1095 paintListener = null;
1096 mouseTrackListener = null;
1097 compositeListener = null;
1098
1099 // We may have a remaining mouse move listener installed
1100 if (moveListeningTarget !is null) {
1101 printRemoveListener(moveListeningTarget, "MOUSEMOVE"); //$NON-NLS-1$
1102 moveListeningTarget.removeMouseMoveListener(mouseMoveListener);
1103 moveListeningTarget = null;
1104 mouseMoveListener = null;
1105 }
1106 if (DEBUG) {
1107 if (listenerInstalls > 0) {
1108 Stdout.formatln("LISTENER LEAK>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
1109 } else if (listenerInstalls < 0) {
1110 Stdout.formatln("REMOVED UNREGISTERED LISTENERS>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
1111 } else {
1112 Stdout.formatln("ALL INSTALLED LISTENERS WERE REMOVED."); //$NON-NLS-1$
1113 }
1114 }
1115 }
1116
1117 /**
1118 * Return the rectangle in which the decoration should be rendered, in
1119 * coordinates relative to the specified control. If the specified control
1120 * is null, return the rectangle in display coordinates.
1121 *
1122 * @param targetControl
1123 * the control whose coordinates should be used
1124 * @return the rectangle in which the decoration should be rendered
1125 */
1126 protected Rectangle getDecorationRectangle(Control targetControl) {
1127 if (getImage() is null || control is null) {
1128 return new Rectangle(0, 0, 0, 0);
1129 }
1130 // Compute the bounds first relative to the control's parent.
1131 Rectangle imageBounds = getImage().getBounds();
1132 Rectangle controlBounds = control.getBounds();
1133 int x, y;
1134 // Compute x
1135 if ((position & DWT.RIGHT) is DWT.RIGHT) {
1136 x = controlBounds.x + controlBounds.width + marginWidth;
1137 } else {
1138 // default is left
1139 x = controlBounds.x - imageBounds.width - marginWidth;
1140 }
1141 // Compute y
1142 if ((position & DWT.TOP) is DWT.TOP) {
1143 y = controlBounds.y;
1144 } else if ((position & DWT.BOTTOM) is DWT.BOTTOM) {
1145 y = controlBounds.y + control.getBounds().height
1146 - imageBounds.height;
1147 } else {
1148 // default is center
1149 y = controlBounds.y
1150 + (control.getBounds().height - imageBounds.height) / 2;
1151 }
1152
1153 // Now convert to coordinates relative to the target control.
1154 Point globalPoint = control.getParent().toDisplay(x, y);
1155 Point targetPoint;
1156 if (targetControl is null) {
1157 targetPoint = globalPoint;
1158 } else {
1159 targetPoint = targetControl.toControl(globalPoint);
1160 }
1161 return new Rectangle(targetPoint.x, targetPoint.y, imageBounds.width,
1162 imageBounds.height);
1163 }
1164
1165 /*
1166 * Return true if the decoration should be shown, false if it should not.
1167 */
1168 private bool shouldShowDecoration() {
1169 if (!visible) {
1170 return false;
1171 }
1172 if (control is null || control.isDisposed() || getImage() is null) {
1173 return false;
1174 }
1175
1176 if (!control.isVisible()) {
1177 return false;
1178 }
1179 if (showOnlyOnFocus) {
1180 return hasFocus;
1181 }
1182 return true;
1183 }
1184
1185 /*
1186 * If in debug mode, print info about adding the specified listener.
1187 */
1188 private void printAddListener(Widget widget, String listenerType) {
1189 listenerInstalls++;
1190 if (DEBUG) {
1191 Stdout.formatln("Added listener>>>{} to>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
1192 }
1193 }
1194
1195 /*
1196 * If in debug mode, print info about adding the specified listener.
1197 */
1198 private void printRemoveListener(Widget widget, String listenerType) {
1199 listenerInstalls--;
1200 if (DEBUG) {
1201 Stdout.formatln("Removed listener>>>{} from>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
1202 }
1203 }
1204 }