comparison org.eclipse.jface/src/org/eclipse/jface/window/ToolTip.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children 52184e4b815c
comparison
equal deleted inserted replaced
11:43904fec5dca 12:bc29606a740c
1 /*******************************************************************************
2 * Copyright (c) 2006, 2008 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 * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
10 * bugfix in: 195137, 198089, 225190
11 * Port to the D programming language:
12 * Frank Benoit <benoit@tionex.de>
13 *******************************************************************************/
14
15 module org.eclipse.jface.window.ToolTip;
16
17 import tango.util.log.Trace;
18
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.events.DisposeEvent;
21 import org.eclipse.swt.events.DisposeListener;
22 import org.eclipse.swt.graphics.Point;
23 import org.eclipse.swt.graphics.Rectangle;
24 import org.eclipse.swt.layout.FillLayout;
25 import org.eclipse.swt.widgets.Composite;
26 import org.eclipse.swt.widgets.Control;
27 import org.eclipse.swt.widgets.Display;
28 import org.eclipse.swt.widgets.Event;
29 import org.eclipse.swt.widgets.Listener;
30 import org.eclipse.swt.widgets.Monitor;
31 import org.eclipse.swt.widgets.Shell;
32 // import org.eclipse.jface.viewers.ColumnViewer;
33 // import org.eclipse.jface.viewers.ViewerCell;
34
35 import java.lang.all;
36 import java.util.HashMap;
37 import java.util.Set;
38 import java.lang.JThread;
39 /**
40 * This class gives implementors to provide customized tooltips for any control.
41 *
42 * @since 3.3
43 */
44 public abstract class ToolTip {
45 private Control control;
46
47 private int xShift = 3;
48
49 private int yShift = 0;
50
51 private int popupDelay = 0;
52
53 private int hideDelay = 0;
54
55 private ToolTipOwnerControlListener listener;
56
57 private HashMap data;
58
59 // Ensure that only one tooltip is active in time
60 private static Shell CURRENT_TOOLTIP;
61
62 /**
63 * Recreate the tooltip on every mouse move
64 */
65 public static const int RECREATE = 1;
66
67 /**
68 * Don't recreate the tooltip as long the mouse doesn't leave the area
69 * triggering the tooltip creation
70 */
71 public static const int NO_RECREATE = 1 << 1;
72
73 private TooltipHideListener hideListener;
74
75 private Listener shellListener;
76
77 private bool hideOnMouseDown = true;
78
79 private bool respectDisplayBounds = true;
80
81 private bool respectMonitorBounds = true;
82
83 private int style;
84
85 private Object currentArea;
86
87 private static final bool IS_OSX = SWT.getPlatform().equals("carbon"); //$NON-NLS-1$
88
89 /**
90 * Create new instance which add TooltipSupport to the widget
91 *
92 * @param control
93 * the control on whose action the tooltip is shown
94 */
95 public this(Control control) {
96 this(control, RECREATE, false);
97 }
98
99 /**
100 * @param control
101 * the control to which the tooltip is bound
102 * @param style
103 * style passed to control tooltip behavior
104 *
105 * @param manualActivation
106 * <code>true</code> if the activation is done manually using
107 * {@link #show(Point)}
108 * @see #RECREATE
109 * @see #NO_RECREATE
110 */
111 public this(Control control, int style, bool manualActivation) {
112 this.control = control;
113 this.style = style;
114 this.hideListener = new TooltipHideListener();
115 this.control.addDisposeListener(new class DisposeListener {
116
117 public void widgetDisposed(DisposeEvent e) {
118 this.outer.data = null;
119 this.outer.deactivate();
120 }
121
122 });
123
124 this.listener = new ToolTipOwnerControlListener();
125 this.shellListener = dgListener( (Event event_, Control control_, ToolTip tooltip_) {
126 if( control_ !is null
127 && ! control_.isDisposed() ) {
128 control_.getDisplay().asyncExec( dgRunnable( (Event event__, Control control__, ToolTip tooltip__){
129 // Check if the new active shell is the tooltip
130 // itself
131 if( control__.getDisplay().getActiveShell() !is CURRENT_TOOLTIP) {
132 tooltip__.toolTipHide(CURRENT_TOOLTIP, event__);
133
134 }
135 }, event_, control_, tooltip_));
136 }
137 }, control, this);
138
139 if (!manualActivation) {
140 activate();
141 }
142 }
143
144 /**
145 * Restore arbitrary data under the given key
146 *
147 * @param key
148 * the key
149 * @param value
150 * the value
151 */
152 public void setData(String key, Object value) {
153 if (data is null) {
154 data = new HashMap();
155 }
156 data.put(stringcast(key), value);
157 }
158
159 /**
160 * Get the data restored under the key
161 *
162 * @param key
163 * the key
164 * @return data or <code>null</code> if no entry is restored under the key
165 */
166 public Object getData(String key) {
167 if (data !is null) {
168 return data.get(stringcast(key));
169 }
170 return null;
171 }
172
173 /**
174 * Set the shift (from the mouse position triggered the event) used to
175 * display the tooltip.
176 * <p>
177 * By default the tooltip is shifted 3 pixels to the right.
178 * </p>
179 *
180 * @param p
181 * the new shift
182 */
183 public void setShift(Point p) {
184 xShift = p.x;
185 yShift = p.y;
186 }
187
188 /**
189 * Activate tooltip support for this control
190 */
191 public void activate() {
192 deactivate();
193 control.addListener(SWT.Dispose, listener);
194 control.addListener(SWT.MouseHover, listener);
195 control.addListener(SWT.MouseMove, listener);
196 control.addListener(SWT.MouseExit, listener);
197 control.addListener(SWT.MouseDown, listener);
198 control.addListener(SWT.MouseWheel, listener);
199 }
200
201 /**
202 * Deactivate tooltip support for the underlying control
203 */
204 public void deactivate() {
205 control.removeListener(SWT.Dispose, listener);
206 control.removeListener(SWT.MouseHover, listener);
207 control.removeListener(SWT.MouseMove, listener);
208 control.removeListener(SWT.MouseExit, listener);
209 control.removeListener(SWT.MouseDown, listener);
210 control.removeListener(SWT.MouseWheel, listener);
211 }
212
213 /**
214 * Return whether the tooltip respects bounds of the display.
215 *
216 * @return <code>true</code> if the tooltip respects bounds of the display
217 */
218 public bool isRespectDisplayBounds() {
219 return respectDisplayBounds;
220 }
221
222 /**
223 * Set to <code>false</code> if display bounds should not be respected or
224 * to <code>true</code> if the tooltip is should repositioned to not
225 * overlap the display bounds.
226 * <p>
227 * Default is <code>true</code>
228 * </p>
229 *
230 * @param respectDisplayBounds
231 */
232 public void setRespectDisplayBounds(bool respectDisplayBounds) {
233 this.respectDisplayBounds = respectDisplayBounds;
234 }
235
236 /**
237 * Return whether the tooltip respects bounds of the monitor.
238 *
239 * @return <code>true</code> if tooltip respects the bounds of the monitor
240 */
241 public bool isRespectMonitorBounds() {
242 return respectMonitorBounds;
243 }
244
245 /**
246 * Set to <code>false</code> if monitor bounds should not be respected or
247 * to <code>true</code> if the tooltip is should repositioned to not
248 * overlap the monitors bounds. The monitor the tooltip belongs to is the
249 * same is control's monitor the tooltip is shown for.
250 * <p>
251 * Default is <code>true</code>
252 * </p>
253 *
254 * @param respectMonitorBounds
255 */
256 public void setRespectMonitorBounds(bool respectMonitorBounds) {
257 this.respectMonitorBounds = respectMonitorBounds;
258 }
259
260 /**
261 * Should the tooltip displayed because of the given event.
262 * <p>
263 * <b>Subclasses may overwrite this to get custom behavior</b>
264 * </p>
265 *
266 * @param event
267 * the event
268 * @return <code>true</code> if tooltip should be displayed
269 */
270 protected bool shouldCreateToolTip(Event event) {
271 if ((style & NO_RECREATE) !is 0) {
272 Object tmp = getToolTipArea(event);
273
274 // No new area close the current tooltip
275 if (tmp is null) {
276 hide();
277 return false;
278 }
279
280 bool rv = !tmp.opEquals(currentArea);
281 return rv;
282 }
283
284 return true;
285 }
286
287 /**
288 * This method is called before the tooltip is hidden
289 *
290 * @param event
291 * the event trying to hide the tooltip
292 * @return <code>true</code> if the tooltip should be hidden
293 */
294 private bool shouldHideToolTip(Event event) {
295 if (event !is null && event.type is SWT.MouseMove
296 && (style & NO_RECREATE) !is 0) {
297 Object tmp = getToolTipArea(event);
298
299 // No new area close the current tooltip
300 if (tmp is null) {
301 hide();
302 return false;
303 }
304
305 bool rv = !tmp.opEquals(currentArea);
306 return rv;
307 }
308
309 return true;
310 }
311
312 /**
313 * This method is called to check for which area the tooltip is
314 * created/hidden for. In case of {@link #NO_RECREATE} this is used to
315 * decide if the tooltip is hidden recreated.
316 *
317 * <code>By the default it is the widget the tooltip is created for but could be any object. To decide if
318 * the area changed the {@link Object#equals(Object)} method is used.</code>
319 *
320 * @param event
321 * the event
322 * @return the area responsible for the tooltip creation or
323 * <code>null</code> this could be any object describing the area
324 * (e.g. the {@link Control} onto which the tooltip is bound to, a
325 * part of this area e.g. for {@link ColumnViewer} this could be a
326 * {@link ViewerCell})
327 */
328 protected Object getToolTipArea(Event event) {
329 return control;
330 }
331
332 /**
333 * Start up the tooltip programmatically
334 *
335 * @param location
336 * the location relative to the control the tooltip is shown
337 */
338 public void show(Point location) {
339 Event event = new Event();
340 event.x = location.x;
341 event.y = location.y;
342 event.widget = control;
343 toolTipCreate(event);
344 }
345
346 private Shell toolTipCreate(Event event) {
347 if (shouldCreateToolTip(event)) {
348 Shell shell = new Shell(control.getShell(), SWT.ON_TOP | SWT.TOOL
349 | SWT.NO_FOCUS);
350 shell.setLayout(new FillLayout());
351
352 toolTipOpen(shell, event);
353
354 return shell;
355 }
356
357 return null;
358 }
359
360 private void toolTipShow(Shell tip, Event event) {
361 if (!tip.isDisposed()) {
362 currentArea = getToolTipArea(event);
363 createToolTipContentArea(event, tip);
364 if (isHideOnMouseDown()) {
365 toolTipHookBothRecursively(tip);
366 } else {
367 toolTipHookByTypeRecursively(tip, true, SWT.MouseExit);
368 }
369
370 tip.pack();
371 Point size = tip.getSize();
372 Point location = fixupDisplayBounds(size, getLocation(size, event));
373
374 // Need to adjust a bit more if the mouse cursor.y is tip.y and
375 // the cursor.x is inside the tip
376 Point cursorLocation = tip.getDisplay().getCursorLocation();
377
378 if (cursorLocation.y is location.y && location.x < cursorLocation.x
379 && location.x + size.x > cursorLocation.x) {
380 location.y -= 2;
381 }
382
383 tip.setLocation(location);
384 tip.setVisible(true);
385 }
386 }
387
388 private Point fixupDisplayBounds(Point tipSize, Point location) {
389 if (respectDisplayBounds || respectMonitorBounds) {
390 Rectangle bounds;
391 Point rightBounds = new Point(tipSize.x + location.x, tipSize.y
392 + location.y);
393
394 org.eclipse.swt.widgets.Monitor.Monitor[] ms = control.getDisplay().getMonitors();
395
396 if (respectMonitorBounds && ms.length > 1) {
397 // By default present in the monitor of the control
398 bounds = control.getMonitor().getBounds();
399 Point p = new Point(location.x, location.y);
400
401 // Search on which monitor the event occurred
402 Rectangle tmp;
403 for (int i = 0; i < ms.length; i++) {
404 tmp = ms[i].getBounds();
405 if (tmp.contains(p)) {
406 bounds = tmp;
407 break;
408 }
409 }
410
411 } else {
412 bounds = control.getDisplay().getBounds();
413 }
414
415 if (!(bounds.contains(location) && bounds.contains(rightBounds))) {
416 if (rightBounds.x > bounds.x + bounds.width) {
417 location.x -= rightBounds.x - (bounds.x + bounds.width);
418 }
419
420 if (rightBounds.y > bounds.y + bounds.height) {
421 location.y -= rightBounds.y - (bounds.y + bounds.height);
422 }
423
424 if (location.x < bounds.x) {
425 location.x = bounds.x;
426 }
427
428 if (location.y < bounds.y) {
429 location.y = bounds.y;
430 }
431 }
432 }
433
434 return location;
435 }
436
437 /**
438 * Get the display relative location where the tooltip is displayed.
439 * Subclasses may overwrite to implement custom positioning.
440 *
441 * @param tipSize
442 * the size of the tooltip to be shown
443 * @param event
444 * the event triggered showing the tooltip
445 * @return the absolute position on the display
446 */
447 public Point getLocation(Point tipSize, Event event) {
448 return control.toDisplay(event.x + xShift, event.y + yShift);
449 }
450
451 private void toolTipHide(Shell tip, Event event) {
452 assert(this);
453 if (tip !is null && !tip.isDisposed() && shouldHideToolTip(event)) {
454 assert(this);
455 control.getShell().removeListener(SWT.Deactivate, shellListener);
456 currentArea = null;
457 passOnEvent(tip, event);
458 tip.dispose();
459 CURRENT_TOOLTIP = null;
460 afterHideToolTip(event);
461 }
462 }
463
464 private void passOnEvent(Shell tip, Event event) {
465 if (control !is null && !control.isDisposed() && event !is null
466 && event.widget !is control && event.type is SWT.MouseDown) {
467 Display display = control.getDisplay();
468 Point newPt = display.map(tip, null, new Point(event.x, event.y));
469
470 Event newEvent = new Event();
471 newEvent.button = event.button;
472 newEvent.character = event.character;
473 newEvent.count = event.count;
474 newEvent.data = event.data;
475 newEvent.detail = event.detail;
476 newEvent.display = event.display;
477 newEvent.doit = event.doit;
478 newEvent.end = event.end;
479 newEvent.gc = event.gc;
480 newEvent.height = event.height;
481 newEvent.index = event.index;
482 newEvent.item = event.item;
483 newEvent.keyCode = event.keyCode;
484 newEvent.start = event.start;
485 newEvent.stateMask = event.stateMask;
486 newEvent.text = event.text;
487 newEvent.time = event.time;
488 newEvent.type = event.type;
489 newEvent.widget = event.widget;
490 newEvent.width = event.width;
491 newEvent.x = newPt.x;
492 newEvent.y = newPt.y;
493
494 tip.close();
495 display.asyncExec(dgRunnable( delegate(Display display_, Event newEvent_) {
496 if (IS_OSX) {
497 try {
498 JThread.sleep(300);
499 } catch (InterruptedException e) {
500
501 }
502
503 display_.post(newEvent_);
504 newEvent_.type = SWT.MouseUp;
505 display_.post(newEvent_);
506 } else {
507 display_.post(newEvent_);
508 }
509 }, display,newEvent));
510 }
511 }
512
513 private void toolTipOpen(Shell shell, Event event) {
514 // Ensure that only one Tooltip is shown in time
515 if (CURRENT_TOOLTIP !is null) {
516 toolTipHide(CURRENT_TOOLTIP, null);
517 }
518
519 CURRENT_TOOLTIP = shell;
520
521 control.getShell().addListener(SWT.Deactivate, shellListener);
522
523 if (popupDelay > 0) {
524 control.getDisplay().timerExec(popupDelay, dgRunnable( (Shell shell_,Event event_, ToolTip tooltip_){
525 assert(tooltip_);
526 tooltip_.toolTipShow(shell_, event_);
527 },shell,event, this));
528 } else {
529 toolTipShow(CURRENT_TOOLTIP, event);
530 }
531
532 if (hideDelay > 0) {
533 control.getDisplay().timerExec(popupDelay + hideDelay, dgRunnable( (Shell shell_, ToolTip tooltip_){
534 assert(tooltip_);
535 tooltip_.toolTipHide(shell_, null);
536 }, shell, this ));
537 }
538 }
539
540 private void toolTipHookByTypeRecursively(Control c, bool add, int type) {
541 if (add) {
542 c.addListener(type, hideListener);
543 } else {
544 c.removeListener(type, hideListener);
545 }
546
547 if ( auto c2 = cast(Composite)c ) {
548 Control[] children = c2.getChildren();
549 for (int i = 0; i < children.length; i++) {
550 toolTipHookByTypeRecursively(children[i], add, type);
551 }
552 }
553 }
554
555 private void toolTipHookBothRecursively(Control c) {
556 c.addListener(SWT.MouseDown, hideListener);
557 c.addListener(SWT.MouseExit, hideListener);
558
559 if ( auto comp = cast(Composite) c ) {
560 Control[] children = comp.getChildren();
561 for (int i = 0; i < children.length; i++) {
562 toolTipHookBothRecursively(children[i]);
563 }
564 }
565 }
566
567 /**
568 * Creates the content area of the the tooltip.
569 *
570 * @param event
571 * the event that triggered the activation of the tooltip
572 * @param parent
573 * the parent of the content area
574 * @return the content area created
575 */
576 protected abstract Composite createToolTipContentArea(Event event,
577 Composite parent);
578
579 /**
580 * This method is called after a tooltip is hidden.
581 * <p>
582 * <b>Subclasses may override to clean up requested system resources</b>
583 * </p>
584 *
585 * @param event
586 * event triggered the hiding action (may be <code>null</code>
587 * if event wasn't triggered by user actions directly)
588 */
589 protected void afterHideToolTip(Event event) {
590
591 }
592
593 /**
594 * Set the hide delay.
595 *
596 * @param hideDelay
597 * the delay before the tooltip is hidden. If <code>0</code>
598 * the tooltip is shown until user moves to other item
599 */
600 public void setHideDelay(int hideDelay) {
601 this.hideDelay = hideDelay;
602 }
603
604 /**
605 * Set the popup delay.
606 *
607 * @param popupDelay
608 * the delay before the tooltip is shown to the user. If
609 * <code>0</code> the tooltip is shown immediately
610 */
611 public void setPopupDelay(int popupDelay) {
612 this.popupDelay = popupDelay;
613 }
614
615 /**
616 * Return if hiding on mouse down is set.
617 *
618 * @return <code>true</code> if hiding on mouse down in the tool tip is on
619 */
620 public bool isHideOnMouseDown() {
621 return hideOnMouseDown;
622 }
623
624 /**
625 * If you don't want the tool tip to be hidden when the user clicks inside
626 * the tool tip set this to <code>false</code>. You maybe also need to
627 * hide the tool tip yourself depending on what you do after clicking in the
628 * tooltip (e.g. if you open a new {@link Shell})
629 *
630 * @param hideOnMouseDown
631 * flag to indicate of tooltip is hidden automatically on mouse
632 * down inside the tool tip
633 */
634 public void setHideOnMouseDown(bool hideOnMouseDown) {
635 // Only needed if there's currently a tooltip active
636 if (CURRENT_TOOLTIP !is null && !CURRENT_TOOLTIP.isDisposed()) {
637 // Only change if value really changed
638 if (hideOnMouseDown !is this.hideOnMouseDown) {
639 control.getDisplay().syncExec(new class(hideOnMouseDown) Runnable {
640 bool hideOnMouseDown_;
641 this(bool a){ hideOnMouseDown_=a; }
642 public void run() {
643 if (CURRENT_TOOLTIP !is null
644 && CURRENT_TOOLTIP.isDisposed()) {
645 toolTipHookByTypeRecursively(CURRENT_TOOLTIP,
646 hideOnMouseDown_, SWT.MouseDown);
647 }
648 }
649
650 });
651 }
652 }
653
654 this.hideOnMouseDown = hideOnMouseDown;
655 }
656
657 /**
658 * Hide the currently active tool tip
659 */
660 public void hide() {
661 toolTipHide(CURRENT_TOOLTIP, null);
662 }
663
664 private class ToolTipOwnerControlListener : Listener {
665 public void handleEvent(Event event) {
666 switch (event.type) {
667 case SWT.Dispose:
668 case SWT.KeyDown:
669 case SWT.MouseDown:
670 case SWT.MouseMove:
671 case SWT.MouseWheel:
672 toolTipHide(CURRENT_TOOLTIP, event);
673 break;
674 case SWT.MouseHover:
675 toolTipCreate(event);
676 break;
677 case SWT.MouseExit:
678 /*
679 * Check if the mouse exit happened because we move over the
680 * tooltip
681 */
682 if (CURRENT_TOOLTIP !is null && !CURRENT_TOOLTIP.isDisposed()) {
683 if (CURRENT_TOOLTIP.getBounds().contains(
684 control.toDisplay(event.x, event.y))) {
685 break;
686 }
687 }
688
689 toolTipHide(CURRENT_TOOLTIP, event);
690 break;
691 default:
692 }
693 }
694 }
695
696 private class TooltipHideListener : Listener {
697 public void handleEvent(Event event) {
698 if ( auto c = cast(Control)event.widget ) {
699
700 Shell shell = c.getShell();
701
702 switch (event.type) {
703 case SWT.MouseDown:
704 if (isHideOnMouseDown()) {
705 toolTipHide(shell, event);
706 }
707 break;
708 case SWT.MouseExit:
709 /*
710 * Give some insets to ensure we get exit informations from
711 * a wider area ;-)
712 */
713 Rectangle rect = shell.getBounds();
714 rect.x += 5;
715 rect.y += 5;
716 rect.width -= 10;
717 rect.height -= 10;
718
719 if (!rect.contains(c.getDisplay().getCursorLocation())) {
720 toolTipHide(shell, event);
721 }
722
723 break;
724 default:
725 }
726 }
727 }
728 }
729 }