comparison dwtx/jface/window/ToolTip.d @ 7:8a302fdb4140

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