Mercurial > projects > dwt-addons
comparison dwtx/jface/text/AbstractHoverInformationControlManager.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 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 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.text.AbstractHoverInformationControlManager; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 | |
18 | |
19 | |
20 import dwt.DWT; | |
21 import dwt.events.ControlEvent; | |
22 import dwt.events.ControlListener; | |
23 import dwt.events.KeyEvent; | |
24 import dwt.events.KeyListener; | |
25 import dwt.events.MouseEvent; | |
26 import dwt.events.MouseListener; | |
27 import dwt.events.MouseMoveListener; | |
28 import dwt.events.MouseTrackListener; | |
29 import dwt.events.SelectionEvent; | |
30 import dwt.events.SelectionListener; | |
31 import dwt.events.ShellAdapter; | |
32 import dwt.events.ShellEvent; | |
33 import dwt.graphics.Point; | |
34 import dwt.graphics.Rectangle; | |
35 import dwt.widgets.Control; | |
36 import dwt.widgets.Display; | |
37 import dwt.widgets.Event; | |
38 import dwt.widgets.Listener; | |
39 import dwt.widgets.ScrollBar; | |
40 import dwt.widgets.Scrollable; | |
41 import dwtx.core.runtime.Assert; | |
42 import dwtx.core.runtime.IProgressMonitor; | |
43 import dwtx.core.runtime.IStatus; | |
44 import dwtx.core.runtime.Status; | |
45 import dwtx.core.runtime.jobs.Job; | |
46 import dwtx.jface.internal.text.DelayedInputChangeListener; | |
47 import dwtx.jface.internal.text.InformationControlReplacer; | |
48 import dwtx.jface.internal.text.InternalAccessor; | |
49 import dwtx.jface.text.ITextViewerExtension8.EnrichMode; | |
50 import dwtx.jface.text.source.AnnotationBarHoverManager; | |
51 import dwtx.jface.util.Geometry; | |
52 | |
53 | |
54 /** | |
55 * An information control manager that shows information in response to mouse | |
56 * hover events. The mouse hover events are caught by registering a | |
57 * {@link dwt.events.MouseTrackListener} on the manager's subject | |
58 * control. The manager has by default an information control closer that closes | |
59 * the information control as soon as the mouse pointer leaves the subject area, | |
60 * the user presses a key, or the subject control is resized, moved, or | |
61 * deactivated. | |
62 * <p> | |
63 * When being activated by a mouse hover event, the manager disables itself, | |
64 * until the mouse leaves the subject area. Thus, the manager is usually still | |
65 * disabled, when the information control has already been closed by the closer. | |
66 * | |
67 * @see dwt.events.MouseTrackListener | |
68 * @since 2.0 | |
69 */ | |
70 abstract public class AbstractHoverInformationControlManager : AbstractInformationControlManager { | |
71 | |
72 /** | |
73 * The information control closer for the hover information. Closes the information control as | |
74 * soon as the mouse pointer leaves the subject area (unless "move into hover" is enabled), | |
75 * a mouse button is pressed, the user presses a key, or the subject control is resized, moved, or loses focus. | |
76 */ | |
77 class Closer : IInformationControlCloser, MouseListener, MouseMoveListener, ControlListener, KeyListener, SelectionListener, Listener { | |
78 | |
79 /** The closer's subject control */ | |
80 private Control fSubjectControl; | |
81 /** The subject area */ | |
82 private Rectangle fSubjectArea; | |
83 /** Indicates whether this closer is active */ | |
84 private bool fIsActive= false; | |
85 /** | |
86 * The cached display. | |
87 * @since 3.1 | |
88 */ | |
89 private Display fDisplay; | |
90 | |
91 | |
92 /** | |
93 * Creates a new information control closer. | |
94 */ | |
95 public Closer() { | |
96 } | |
97 | |
98 /* | |
99 * @see IInformationControlCloser#setSubjectControl(Control) | |
100 */ | |
101 public void setSubjectControl(Control control) { | |
102 fSubjectControl= control; | |
103 } | |
104 | |
105 /* | |
106 * @see IInformationControlCloser#setHoverControl(IHoverControl) | |
107 */ | |
108 public void setInformationControl(IInformationControl control) { | |
109 // NOTE: we use getCurrentInformationControl() from the outer class | |
110 } | |
111 | |
112 /* | |
113 * @see IInformationControlCloser#start(Rectangle) | |
114 */ | |
115 public void start(Rectangle subjectArea) { | |
116 | |
117 if (fIsActive) | |
118 return; | |
119 fIsActive= true; | |
120 fWaitForMouseUp= false; | |
121 | |
122 fSubjectArea= subjectArea; | |
123 | |
124 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
125 fSubjectControl.addMouseListener(this); | |
126 fSubjectControl.addMouseMoveListener(this); | |
127 fSubjectControl.addControlListener(this); | |
128 fSubjectControl.addKeyListener(this); | |
129 if (fSubjectControl instanceof Scrollable) { | |
130 Scrollable scrollable= (Scrollable) fSubjectControl; | |
131 ScrollBar vBar= scrollable.getVerticalBar(); | |
132 if (vBar !is null) | |
133 vBar.addSelectionListener(this); | |
134 ScrollBar hBar= scrollable.getHorizontalBar(); | |
135 if (hBar !is null) | |
136 hBar.addSelectionListener(this); | |
137 } | |
138 | |
139 fDisplay= fSubjectControl.getDisplay(); | |
140 if (!fDisplay.isDisposed()) { | |
141 fDisplay.addFilter(DWT.Activate, this); | |
142 fDisplay.addFilter(DWT.MouseWheel, this); | |
143 | |
144 fDisplay.addFilter(DWT.FocusOut, this); | |
145 | |
146 fDisplay.addFilter(DWT.MouseDown, this); | |
147 fDisplay.addFilter(DWT.MouseUp, this); | |
148 | |
149 fDisplay.addFilter(DWT.MouseMove, this); | |
150 fDisplay.addFilter(DWT.MouseEnter, this); | |
151 fDisplay.addFilter(DWT.MouseExit, this); | |
152 } | |
153 } | |
154 } | |
155 | |
156 /* | |
157 * @see IInformationControlCloser#stop() | |
158 */ | |
159 public void stop() { | |
160 if (!fIsActive) | |
161 return; | |
162 | |
163 fIsActive= false; | |
164 | |
165 if (DEBUG) | |
166 System.out.println("AbstractHoverInformationControlManager.Closer stopped"); //$NON-NLS-1$ | |
167 | |
168 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
169 fSubjectControl.removeMouseListener(this); | |
170 fSubjectControl.removeMouseMoveListener(this); | |
171 fSubjectControl.removeControlListener(this); | |
172 fSubjectControl.removeKeyListener(this); | |
173 if (fSubjectControl instanceof Scrollable) { | |
174 Scrollable scrollable= (Scrollable) fSubjectControl; | |
175 ScrollBar vBar= scrollable.getVerticalBar(); | |
176 if (vBar !is null) | |
177 vBar.removeSelectionListener(this); | |
178 ScrollBar hBar= scrollable.getHorizontalBar(); | |
179 if (hBar !is null) | |
180 hBar.removeSelectionListener(this); | |
181 } | |
182 } | |
183 | |
184 if (fDisplay !is null && !fDisplay.isDisposed()) { | |
185 fDisplay.removeFilter(DWT.Activate, this); | |
186 fDisplay.removeFilter(DWT.MouseWheel, this); | |
187 | |
188 fDisplay.removeFilter(DWT.FocusOut, this); | |
189 | |
190 fDisplay.removeFilter(DWT.MouseDown, this); | |
191 fDisplay.removeFilter(DWT.MouseUp, this); | |
192 | |
193 fDisplay.removeFilter(DWT.MouseMove, this); | |
194 fDisplay.removeFilter(DWT.MouseEnter, this); | |
195 fDisplay.removeFilter(DWT.MouseExit, this); | |
196 } | |
197 fDisplay= null; | |
198 } | |
199 | |
200 /* | |
201 * @see dwt.events.MouseMoveListener#mouseMove(dwt.events.MouseEvent) | |
202 */ | |
203 public void mouseMove(MouseEvent event) { | |
204 if (!hasInformationControlReplacer() || !canMoveIntoInformationControl(getCurrentInformationControl())) { | |
205 if (!fSubjectArea.contains(event.x, event.y)) { | |
206 hideInformationControl(); | |
207 } | |
208 | |
209 } else if (getCurrentInformationControl() !is null && !getCurrentInformationControl().isFocusControl()) { | |
210 if (!inKeepUpZone(event.x, event.y, fSubjectControl, fSubjectArea, true)) { | |
211 hideInformationControl(); | |
212 } | |
213 } | |
214 } | |
215 | |
216 /* | |
217 * @see dwt.events.MouseListener#mouseUp(dwt.events.MouseEvent) | |
218 */ | |
219 public void mouseUp(MouseEvent event) { | |
220 } | |
221 | |
222 /* | |
223 * @see MouseListener#mouseDown(MouseEvent) | |
224 */ | |
225 public void mouseDown(MouseEvent event) { | |
226 hideInformationControl(); | |
227 } | |
228 | |
229 /* | |
230 * @see MouseListener#mouseDoubleClick(MouseEvent) | |
231 */ | |
232 public void mouseDoubleClick(MouseEvent event) { | |
233 hideInformationControl(); | |
234 } | |
235 | |
236 /* | |
237 * @see ControlListener#controlResized(ControlEvent) | |
238 */ | |
239 public void controlResized(ControlEvent event) { | |
240 hideInformationControl(); | |
241 } | |
242 | |
243 /* | |
244 * @see ControlListener#controlMoved(ControlEvent) | |
245 */ | |
246 public void controlMoved(ControlEvent event) { | |
247 hideInformationControl(); | |
248 } | |
249 | |
250 /* | |
251 * @see KeyListener#keyReleased(KeyEvent) | |
252 */ | |
253 public void keyReleased(KeyEvent event) { | |
254 } | |
255 | |
256 /* | |
257 * @see KeyListener#keyPressed(KeyEvent) | |
258 */ | |
259 public void keyPressed(KeyEvent event) { | |
260 hideInformationControl(); | |
261 } | |
262 | |
263 /* | |
264 * @see dwt.events.SelectionListener#widgetSelected(dwt.events.SelectionEvent) | |
265 */ | |
266 public void widgetSelected(SelectionEvent e) { | |
267 hideInformationControl(); | |
268 } | |
269 | |
270 /* | |
271 * @see dwt.events.SelectionListener#widgetDefaultSelected(dwt.events.SelectionEvent) | |
272 */ | |
273 public void widgetDefaultSelected(SelectionEvent e) { | |
274 } | |
275 | |
276 /* | |
277 * @see dwt.widgets.Listener#handleEvent(dwt.widgets.Event) | |
278 * @since 3.1 | |
279 */ | |
280 public void handleEvent(Event event) { | |
281 switch (event.type) { | |
282 case DWT.Activate: | |
283 case DWT.MouseWheel: | |
284 if (!hasInformationControlReplacer()) | |
285 hideInformationControl(); | |
286 else if (!isReplaceInProgress()) { | |
287 IInformationControl infoControl= getCurrentInformationControl(); | |
288 // During isReplaceInProgress(), events can come from the replacing information control | |
289 if (event.widget instanceof Control && infoControl instanceof IInformationControlExtension5) { | |
290 Control control= (Control) event.widget; | |
291 IInformationControlExtension5 iControl5= (IInformationControlExtension5) infoControl; | |
292 if (!(iControl5.containsControl(control))) | |
293 hideInformationControl(); | |
294 else if (event.type is DWT.MouseWheel && cancelReplacingDelay()) | |
295 replaceInformationControl(false); | |
296 } else if (infoControl !is null && infoControl.isFocusControl() && cancelReplacingDelay()) { | |
297 replaceInformationControl(true); | |
298 } | |
299 } | |
300 break; | |
301 | |
302 case DWT.MouseUp: | |
303 case DWT.MouseDown: | |
304 if (!hasInformationControlReplacer()) | |
305 hideInformationControl(); | |
306 else if (!isReplaceInProgress()) { | |
307 IInformationControl infoControl= getCurrentInformationControl(); | |
308 if (event.widget instanceof Control && infoControl instanceof IInformationControlExtension5) { | |
309 Control control= (Control) event.widget; | |
310 final IInformationControlExtension5 iControl5= (IInformationControlExtension5) infoControl; | |
311 if (!(iControl5.containsControl(control))) { | |
312 hideInformationControl(); | |
313 } else if (cancelReplacingDelay()) { | |
314 if (event.type is DWT.MouseUp) { | |
315 stop(); // avoid that someone else replaces the info control before the async is exec'd | |
316 if (infoControl instanceof IDelayedInputChangeProvider) { | |
317 final IDelayedInputChangeProvider delayedICP= (IDelayedInputChangeProvider) infoControl; | |
318 final IInputChangedListener inputChangeListener= new DelayedInputChangeListener(delayedICP, getInformationControlReplacer()); | |
319 delayedICP.setDelayedInputChangeListener(inputChangeListener); | |
320 // cancel automatic input updating after a small timeout: | |
321 control.getShell().getDisplay().timerExec(1000, new Runnable() { | |
322 public void run() { | |
323 delayedICP.setDelayedInputChangeListener(null); | |
324 } | |
325 }); | |
326 } | |
327 | |
328 // XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212392 : | |
329 control.getShell().getDisplay().asyncExec(new Runnable() { | |
330 public void run() { | |
331 replaceInformationControl(true); | |
332 } | |
333 }); | |
334 } else { | |
335 fWaitForMouseUp= true; | |
336 } | |
337 } | |
338 } else { | |
339 handleMouseMove(event); | |
340 } | |
341 } | |
342 break; | |
343 | |
344 case DWT.FocusOut: | |
345 IInformationControl iControl= getCurrentInformationControl(); | |
346 if (iControl !is null && ! iControl.isFocusControl()) | |
347 hideInformationControl(); | |
348 break; | |
349 | |
350 case DWT.MouseMove: | |
351 case DWT.MouseEnter: | |
352 case DWT.MouseExit: | |
353 handleMouseMove(event); | |
354 break; | |
355 } | |
356 } | |
357 | |
358 /** | |
359 * Handle mouse movement events. | |
360 * | |
361 * @param event the event | |
362 * @since 3.4 | |
363 */ | |
364 private void handleMouseMove(Event event) { | |
365 // if (DEBUG) | |
366 // System.out.println("AbstractHoverInformationControl.Closer.handleMouseMove():" + event); //$NON-NLS-1$ | |
367 | |
368 if (!(event.widget instanceof Control)) | |
369 return; | |
370 Control eventControl= (Control) event.widget; | |
371 | |
372 //transform coordinates to subject control: | |
373 Point mouseLoc= event.display.map(eventControl, fSubjectControl, event.x, event.y); | |
374 | |
375 if (fSubjectArea.contains(mouseLoc)) | |
376 return; | |
377 | |
378 IInformationControl iControl= getCurrentInformationControl(); | |
379 if (!hasInformationControlReplacer() || !canMoveIntoInformationControl(iControl)) { | |
380 if (AbstractHoverInformationControlManager.this instanceof AnnotationBarHoverManager) { | |
381 if (getInternalAccessor().getAllowMouseExit()) | |
382 return; | |
383 } | |
384 hideInformationControl(); | |
385 return; | |
386 } | |
387 | |
388 IInformationControlExtension3 iControl3= (IInformationControlExtension3) iControl; | |
389 Rectangle controlBounds= iControl3.getBounds(); | |
390 if (controlBounds !is null) { | |
391 Rectangle tooltipBounds= event.display.map(null, eventControl, controlBounds); | |
392 if (tooltipBounds.contains(event.x, event.y)) { | |
393 if (!isReplaceInProgress() && event.type !is DWT.MouseExit) | |
394 startReplaceInformationControl(event.display); | |
395 return; | |
396 } | |
397 cancelReplacingDelay(); | |
398 } | |
399 | |
400 if (!fSubjectControl.getBounds().contains(mouseLoc)) { | |
401 /* | |
402 * Use inKeepUpZone() to make sure it also works when the hover is | |
403 * completely outside of the subject control. | |
404 */ | |
405 if (!inKeepUpZone(mouseLoc.x, mouseLoc.y, fSubjectControl, fSubjectArea, true)) { | |
406 hideInformationControl(); | |
407 return; | |
408 } | |
409 } | |
410 } | |
411 } | |
412 | |
413 /** | |
414 * To be installed on the manager's subject control. Serves two different purposes: | |
415 * <ul> | |
416 * <li> start function: initiates the computation of the information to be presented. This happens on | |
417 * receipt of a mouse hover event and disables the information control manager, | |
418 * <li> restart function: tracks mouse move and shell activation event to determine when the information | |
419 * control manager needs to be reactivated. | |
420 * </ul> | |
421 */ | |
422 class MouseTracker : ShellAdapter , MouseTrackListener, MouseMoveListener { | |
423 | |
424 /** Margin around the original hover event location for computing the hover area. */ | |
425 private final static int EPSILON= 3; | |
426 | |
427 /** The area in which the original hover event occurred. */ | |
428 private Rectangle fHoverArea; | |
429 /** The area for which is computed information is valid. */ | |
430 private Rectangle fSubjectArea; | |
431 /** The tracker's subject control. */ | |
432 private Control fSubjectControl; | |
433 | |
434 /** Indicates whether the tracker is in restart mode ignoring hover events. */ | |
435 private bool fIsInRestartMode= false; | |
436 /** Indicates whether the tracker is computing the information to be presented. */ | |
437 private bool fIsComputing= false; | |
438 /** Indicates whether the mouse has been lost. */ | |
439 private bool fMouseLostWhileComputing= false; | |
440 /** Indicates whether the subject control's shell has been deactivated. */ | |
441 private bool fShellDeactivatedWhileComputing= false; | |
442 | |
443 /** | |
444 * Creates a new mouse tracker. | |
445 */ | |
446 public MouseTracker() { | |
447 } | |
448 | |
449 /** | |
450 * Sets this mouse tracker's subject area, the area to be tracked in order | |
451 * to re-enable the information control manager. | |
452 * | |
453 * @param subjectArea the subject area | |
454 */ | |
455 public void setSubjectArea(Rectangle subjectArea) { | |
456 Assert.isNotNull(subjectArea); | |
457 fSubjectArea= subjectArea; | |
458 } | |
459 | |
460 /** | |
461 * Starts this mouse tracker. The given control becomes this tracker's subject control. | |
462 * Installs itself as mouse track listener on the subject control. | |
463 * | |
464 * @param subjectControl the subject control | |
465 */ | |
466 public void start(Control subjectControl) { | |
467 fSubjectControl= subjectControl; | |
468 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) | |
469 fSubjectControl.addMouseTrackListener(this); | |
470 | |
471 fIsInRestartMode= false; | |
472 fIsComputing= false; | |
473 fMouseLostWhileComputing= false; | |
474 fShellDeactivatedWhileComputing= false; | |
475 } | |
476 | |
477 /** | |
478 * Stops this mouse tracker. Removes itself as mouse track, mouse move, and | |
479 * shell listener from the subject control. | |
480 */ | |
481 public void stop() { | |
482 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
483 fSubjectControl.removeMouseTrackListener(this); | |
484 fSubjectControl.removeMouseMoveListener(this); | |
485 fSubjectControl.getShell().removeShellListener(this); | |
486 } | |
487 } | |
488 | |
489 /** | |
490 * Initiates the computation of the information to be presented. Sets the initial hover area | |
491 * to a small rectangle around the hover event location. Adds mouse move and shell activation listeners | |
492 * to track whether the computed information is, after completion, useful for presentation and to | |
493 * implement the restart function. | |
494 * | |
495 * @param event the mouse hover event | |
496 */ | |
497 public void mouseHover(MouseEvent event) { | |
498 if (fIsComputing || fIsInRestartMode || | |
499 (fSubjectControl !is null && !fSubjectControl.isDisposed() && fSubjectControl.getShell() !is fSubjectControl.getShell().getDisplay().getActiveShell())) { | |
500 if (DEBUG) | |
501 System.out.println("AbstractHoverInformationControlManager...mouseHover: @ " + event.x + "/" + event.y + " : hover cancelled: fIsComputing= " + fIsComputing + ", fIsInRestartMode= " + fIsInRestartMode); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ | |
502 return; | |
503 } | |
504 | |
505 fIsInRestartMode= true; | |
506 fIsComputing= true; | |
507 fMouseLostWhileComputing= false; | |
508 fShellDeactivatedWhileComputing= false; | |
509 | |
510 fHoverEventStateMask= event.stateMask; | |
511 fHoverEvent= event; | |
512 fHoverArea= new Rectangle(event.x - EPSILON, event.y - EPSILON, 2 * EPSILON, 2 * EPSILON ); | |
513 if (fHoverArea.x < 0) | |
514 fHoverArea.x= 0; | |
515 if (fHoverArea.y < 0) | |
516 fHoverArea.y= 0; | |
517 setSubjectArea(fHoverArea); | |
518 | |
519 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
520 fSubjectControl.addMouseMoveListener(this); | |
521 fSubjectControl.getShell().addShellListener(this); | |
522 } | |
523 doShowInformation(); | |
524 } | |
525 | |
526 /** | |
527 * Deactivates this tracker's restart function and enables the information control | |
528 * manager. Does not have any effect if the tracker is still executing the start function (i.e. | |
529 * computing the information to be presented. | |
530 */ | |
531 protected void deactivate() { | |
532 if (fIsComputing) | |
533 return; | |
534 | |
535 fIsInRestartMode= false; | |
536 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
537 fSubjectControl.removeMouseMoveListener(this); | |
538 fSubjectControl.getShell().removeShellListener(this); | |
539 } | |
540 } | |
541 | |
542 /* | |
543 * @see MouseTrackListener#mouseEnter(MouseEvent) | |
544 */ | |
545 public void mouseEnter(MouseEvent e) { | |
546 } | |
547 | |
548 /* | |
549 * @see MouseTrackListener#mouseExit(MouseEvent) | |
550 */ | |
551 public void mouseExit(MouseEvent e) { | |
552 if (!hasInformationControlReplacer() || !canMoveIntoInformationControl(getCurrentInformationControl()) || !inKeepUpZone(e.x, e.y, fSubjectControl, fSubjectArea, false)) { | |
553 fMouseLostWhileComputing= true; | |
554 deactivate(); | |
555 } | |
556 } | |
557 | |
558 /* | |
559 * @see MouseMoveListener#mouseMove(MouseEvent) | |
560 */ | |
561 public void mouseMove(MouseEvent event) { | |
562 if (!hasInformationControlReplacer() || !canMoveIntoInformationControl(getCurrentInformationControl())) { | |
563 if (!fSubjectArea.contains(event.x, event.y)) | |
564 deactivate(); | |
565 } else { | |
566 if (!inKeepUpZone(event.x, event.y, fSubjectControl, fSubjectArea, false)) | |
567 deactivate(); | |
568 } | |
569 } | |
570 | |
571 /* | |
572 * @see ShellListener#shellDeactivated(ShellEvent) | |
573 */ | |
574 public void shellDeactivated(ShellEvent e) { | |
575 fShellDeactivatedWhileComputing= true; | |
576 deactivate(); | |
577 } | |
578 | |
579 /* | |
580 * @see ShellListener#shellIconified(ShellEvent) | |
581 */ | |
582 public void shellIconified(ShellEvent e) { | |
583 fShellDeactivatedWhileComputing= true; | |
584 deactivate(); | |
585 } | |
586 | |
587 /** | |
588 * Tells this tracker that the start function processing has been completed. | |
589 */ | |
590 public void computationCompleted() { | |
591 fIsComputing= false; | |
592 fMouseLostWhileComputing= false; | |
593 fShellDeactivatedWhileComputing= false; | |
594 } | |
595 | |
596 /** | |
597 * Determines whether the computed information is still useful for presentation. | |
598 * This is not the case, if the shell of the subject control has been deactivated, the mouse | |
599 * left the subject control, or the mouse moved on, so that it is no longer in the subject | |
600 * area. | |
601 * | |
602 * @return <code>true</code> if information is still useful for presentation, <code>false</code> otherwise | |
603 */ | |
604 public bool isMouseLost() { | |
605 | |
606 if (fMouseLostWhileComputing || fShellDeactivatedWhileComputing) | |
607 return true; | |
608 | |
609 if (fSubjectControl !is null && !fSubjectControl.isDisposed()) { | |
610 Display display= fSubjectControl.getDisplay(); | |
611 Point p= display.getCursorLocation(); | |
612 p= fSubjectControl.toControl(p); | |
613 if (!fSubjectArea.contains(p) && !fHoverArea.contains(p)) | |
614 return true; | |
615 } | |
616 | |
617 return false; | |
618 } | |
619 } | |
620 | |
621 /** | |
622 * The delay in {@link ITextViewerExtension8.EnrichMode#AFTER_DELAY} mode after which | |
623 * the hover is enriched when the mouse has stopped moving inside the hover. | |
624 * @since 3.4 | |
625 */ | |
626 private static final long HOVER_AUTO_REPLACING_DELAY= 200; | |
627 | |
628 /** The mouse tracker on the subject control */ | |
629 private MouseTracker fMouseTracker= new MouseTracker(); | |
630 /** | |
631 * The remembered hover event. | |
632 * @since 3.0 | |
633 */ | |
634 private MouseEvent fHoverEvent= null; | |
635 /** The remembered hover event state mask of the keyboard modifiers */ | |
636 private int fHoverEventStateMask= 0; | |
637 /** | |
638 * The thread that delays replacing of the hover information control. | |
639 * To be accessed in the UI thread only! | |
640 * | |
641 * @since 3.4 | |
642 */ | |
643 private Job fReplacingDelayJob; | |
644 | |
645 /** | |
646 * The {@link ITextViewerExtension8.EnrichMode}, may be <code>null</code>. | |
647 * @since 3.4 | |
648 */ | |
649 private EnrichMode fEnrichMode; | |
650 | |
651 /** | |
652 * Indicates whether we have received a MouseDown event and are waiting for a MouseUp | |
653 * (and don't replace the information control until that happened). | |
654 * @since 3.4 | |
655 */ | |
656 private bool fWaitForMouseUp= false; | |
657 | |
658 /** | |
659 * Creates a new hover information control manager using the given information control creator. | |
660 * By default a <code>Closer</code> instance is set as this manager's closer. | |
661 * | |
662 * @param creator the information control creator | |
663 */ | |
664 protected AbstractHoverInformationControlManager(IInformationControlCreator creator) { | |
665 super(creator); | |
666 setCloser(new Closer()); | |
667 setHoverEnrichMode(ITextViewerExtension8.EnrichMode.AFTER_DELAY); | |
668 } | |
669 | |
670 /** | |
671 * Tests whether a given mouse location is within the keep-up zone. | |
672 * The hover should not be hidden as long as the mouse stays inside this zone. | |
673 * | |
674 * @param x the x coordinate, relative to the <em>subject control</em> | |
675 * @param y the y coordinate, relative to the <em>subject control</em> | |
676 * @param subjectControl the subject control | |
677 * @param subjectArea the area for which the presented information is valid | |
678 * @param blowUp If <code>true</code>, then calculate for the closer, i.e. blow up the keepUp area. | |
679 * If <code>false</code>, then use tight bounds for hover detection. | |
680 * | |
681 * @return <code>true</code> iff the mouse event occurred in the keep-up zone | |
682 * @since 3.4 | |
683 */ | |
684 private bool inKeepUpZone(int x, int y, Control subjectControl, Rectangle subjectArea, bool blowUp) { | |
685 if (subjectArea.contains(x, y)) | |
686 return true; | |
687 | |
688 IInformationControl iControl= getCurrentInformationControl(); | |
689 if ((iControl instanceof IInformationControlExtension5 && !((IInformationControlExtension5) iControl).isVisible())) { | |
690 iControl= null; | |
691 if (getInformationControlReplacer() !is null) { | |
692 iControl= getInformationControlReplacer().getCurrentInformationControl2(); | |
693 if ((iControl instanceof IInformationControlExtension5 && !((IInformationControlExtension5) iControl).isVisible())) { | |
694 return false; | |
695 } | |
696 } | |
697 } | |
698 if (iControl instanceof IInformationControlExtension3) { | |
699 IInformationControlExtension3 iControl3= (IInformationControlExtension3) iControl; | |
700 | |
701 Rectangle iControlBounds= subjectControl.getDisplay().map(null, subjectControl, iControl3.getBounds()); | |
702 Rectangle totalBounds= Geometry.copy(iControlBounds); | |
703 if (blowUp && isReplaceInProgress()) { | |
704 //Problem: blown up iControl overlaps rest of subjectArea's line | |
705 // solution for now: only blow up for keep up (closer), but not for further hover detection | |
706 int margin= getInformationControlReplacer().getKeepUpMargin(); | |
707 Geometry.expand(totalBounds, margin, margin, margin, margin); | |
708 } | |
709 | |
710 if (!blowUp) { | |
711 if (iControlBounds.contains(x, y)) | |
712 return true; | |
713 | |
714 if (subjectArea.y + subjectArea.height < iControlBounds.y) { | |
715 // special case for hover events: subjectArea totally above iControl: | |
716 // +-----------+ | |
717 // |subjectArea| | |
718 // +-----------+ | |
719 // |also keepUp| | |
720 // ++-----------+-------+ | |
721 // | InformationControl | | |
722 // +--------------------+ | |
723 if (subjectArea.y + subjectArea.height <= y && y <= totalBounds.y) { | |
724 // is vertically between subject area and iControl | |
725 if (subjectArea.x <= x && x <= subjectArea.x + subjectArea.width) { | |
726 // is below subject area (in a vertical projection) | |
727 return true; | |
728 } | |
729 // FIXME: cases when subjectArea extends to left or right of iControl? | |
730 } | |
731 return false; | |
732 | |
733 } else if (iControlBounds.x + iControlBounds.width < subjectArea.x) { | |
734 // special case for hover events (e.g. in overview ruler): iControl totally left of subjectArea | |
735 // +--------------------+-----------+ | |
736 // | | +-----------+ | |
737 // | InformationControl |also keepUp|subjectArea| | |
738 // | | +-----------+ | |
739 // +--------------------+-----------+ | |
740 if (iControlBounds.x + iControlBounds.width <= x && x <= subjectArea.x) { | |
741 // is horizontally between iControl and subject area | |
742 if (iControlBounds.y <= y && y <= iControlBounds.y + iControlBounds.height) { | |
743 // is to the right of iControl (in a horizontal projection) | |
744 return true; | |
745 } | |
746 } | |
747 return false; | |
748 | |
749 } else if (subjectArea.x + subjectArea.width < iControlBounds.x) { | |
750 // special case for hover events (e.g. in annotation ruler): subjectArea totally left of iControl | |
751 // +-----------+--------------------+ | |
752 // +-----------+ | | | |
753 // |subjectArea|also keepUp| InformationControl | | |
754 // +-----------+ | | | |
755 // +-----------+--------------------+ | |
756 if (subjectArea.x + subjectArea.width <= x && x <= iControlBounds.x) { | |
757 // is horizontally between subject area and iControl | |
758 if (iControlBounds.y <= y && y <= iControlBounds.y + iControlBounds.height) { | |
759 // is to the left of iControl (in a horizontal projection) | |
760 return true; | |
761 } | |
762 } | |
763 return false; | |
764 } | |
765 } | |
766 | |
767 // FIXME: should maybe use convex hull, not bounding box | |
768 totalBounds.add(subjectArea); | |
769 if (totalBounds.contains(x, y)) | |
770 return true; | |
771 } | |
772 return false; | |
773 } | |
774 | |
775 /** | |
776 * Tests whether the given information control allows the mouse to be moved | |
777 * into it. | |
778 * | |
779 * @param iControl information control or <code>null</code> if none | |
780 * @return <code>true</code> if information control allows mouse move into | |
781 * control, <code>false</code> otherwise | |
782 */ | |
783 bool canMoveIntoInformationControl(IInformationControl iControl) { | |
784 return fEnrichMode !is null && canReplace(iControl); | |
785 } | |
786 | |
787 /* | |
788 * @see dwtx.jface.text.AbstractInformationControlManager#hideInformationControl() | |
789 */ | |
790 protected void hideInformationControl() { | |
791 cancelReplacingDelay(); | |
792 super.hideInformationControl(); | |
793 } | |
794 | |
795 /** | |
796 * Sets the hover enrich mode. Only applicable when an information | |
797 * control replacer has been set with | |
798 * {@link #setInformationControlReplacer(InformationControlReplacer)} . | |
799 * | |
800 * @param mode the enrich mode | |
801 * @since 3.4 | |
802 * @see ITextViewerExtension8#setHoverEnrichMode(dwtx.jface.text.ITextViewerExtension8.EnrichMode) | |
803 */ | |
804 void setHoverEnrichMode(EnrichMode mode) { | |
805 fEnrichMode= mode; | |
806 } | |
807 | |
808 /* | |
809 * @see dwtx.jface.text.AbstractInformationControlManager#replaceInformationControl(bool) | |
810 */ | |
811 void replaceInformationControl(bool takeFocus) { | |
812 fWaitForMouseUp= false; | |
813 super.replaceInformationControl(takeFocus); | |
814 } | |
815 | |
816 /** | |
817 * Cancels the replacing delay job. | |
818 * @return <code>true</code> iff canceling was successful, <code>false</code> if replacing has already started | |
819 */ | |
820 bool cancelReplacingDelay() { | |
821 fWaitForMouseUp= false; | |
822 if (fReplacingDelayJob !is null && fReplacingDelayJob.getState() !is Job.RUNNING) { | |
823 bool cancelled= fReplacingDelayJob.cancel(); | |
824 fReplacingDelayJob= null; | |
825 // if (DEBUG) | |
826 // System.out.println("AbstractHoverInformationControlManager.cancelReplacingDelay(): cancelled=" + cancelled); //$NON-NLS-1$ | |
827 return cancelled; | |
828 } | |
829 // if (DEBUG) | |
830 // System.out.println("AbstractHoverInformationControlManager.cancelReplacingDelay(): not delayed"); //$NON-NLS-1$ | |
831 return true; | |
832 } | |
833 | |
834 /** | |
835 * Starts replacing the information control, considering the current | |
836 * {@link ITextViewerExtension8.EnrichMode}. | |
837 * If set to {@link ITextViewerExtension8.EnrichMode#AFTER_DELAY}, this | |
838 * method cancels previous requests and restarts the delay timer. | |
839 * | |
840 * @param display the display to be used for the call to | |
841 * {@link #replaceInformationControl(bool)} in the UI thread | |
842 */ | |
843 private void startReplaceInformationControl(final Display display) { | |
844 if (fEnrichMode is EnrichMode.ON_CLICK) | |
845 return; | |
846 | |
847 if (fReplacingDelayJob !is null) { | |
848 if (fReplacingDelayJob.getState() !is Job.RUNNING) { | |
849 if (fReplacingDelayJob.cancel()) { | |
850 if (fEnrichMode is EnrichMode.IMMEDIATELY) { | |
851 fReplacingDelayJob= null; | |
852 if (! fWaitForMouseUp) | |
853 replaceInformationControl(false); | |
854 } else { | |
855 // if (DEBUG) | |
856 // System.out.println("AbstractHoverInformationControlManager.startReplaceInformationControl(): rescheduled"); //$NON-NLS-1$ | |
857 fReplacingDelayJob.schedule(HOVER_AUTO_REPLACING_DELAY); | |
858 } | |
859 } | |
860 } | |
861 return; | |
862 } | |
863 | |
864 fReplacingDelayJob= new Job("AbstractHoverInformationControlManager Replace Delayer") { //$NON-NLS-1$ | |
865 public IStatus run(final IProgressMonitor monitor) { | |
866 if (monitor.isCanceled() || display.isDisposed()) { | |
867 return Status.CANCEL_STATUS; | |
868 } | |
869 display.syncExec(new Runnable() { | |
870 public void run() { | |
871 fReplacingDelayJob= null; | |
872 if (monitor.isCanceled()) | |
873 return; | |
874 if (! fWaitForMouseUp) | |
875 replaceInformationControl(false); | |
876 } | |
877 }); | |
878 return Status.OK_STATUS; | |
879 } | |
880 }; | |
881 fReplacingDelayJob.setSystem(true); | |
882 fReplacingDelayJob.setPriority(Job.INTERACTIVE); | |
883 // if (DEBUG) | |
884 // System.out.println("AbstractHoverInformationControlManager.startReplaceInformationControl(): scheduled"); //$NON-NLS-1$ | |
885 fReplacingDelayJob.schedule(HOVER_AUTO_REPLACING_DELAY); | |
886 } | |
887 | |
888 /* | |
889 * @see dwtx.jface.text.AbstractInformationControlManager#presentInformation() | |
890 */ | |
891 protected void presentInformation() { | |
892 if (fMouseTracker is null) { | |
893 super.presentInformation(); | |
894 return; | |
895 } | |
896 | |
897 Rectangle area= getSubjectArea(); | |
898 if (area !is null) | |
899 fMouseTracker.setSubjectArea(area); | |
900 | |
901 if (fMouseTracker.isMouseLost()) { | |
902 fMouseTracker.computationCompleted(); | |
903 fMouseTracker.deactivate(); | |
904 } else { | |
905 fMouseTracker.computationCompleted(); | |
906 super.presentInformation(); | |
907 } | |
908 } | |
909 | |
910 /** | |
911 * {@inheritDoc} | |
912 * @deprecated visibility will be changed to protected | |
913 */ | |
914 public void setEnabled(bool enabled) { | |
915 | |
916 bool was= isEnabled(); | |
917 super.setEnabled(enabled); | |
918 bool is= isEnabled(); | |
919 | |
920 if (was !is is && fMouseTracker !is null) { | |
921 if (is) | |
922 fMouseTracker.start(getSubjectControl()); | |
923 else | |
924 fMouseTracker.stop(); | |
925 } | |
926 } | |
927 | |
928 /** | |
929 * Disposes this manager's information control. | |
930 */ | |
931 public void dispose() { | |
932 if (fMouseTracker !is null) { | |
933 fMouseTracker.stop(); | |
934 fMouseTracker.fSubjectControl= null; | |
935 fMouseTracker= null; | |
936 } | |
937 super.dispose(); | |
938 } | |
939 | |
940 /** | |
941 * Returns the location at which the most recent mouse hover event | |
942 * has been issued. | |
943 * | |
944 * @return the location of the most recent mouse hover event | |
945 */ | |
946 protected Point getHoverEventLocation() { | |
947 return fHoverEvent !is null ? new Point(fHoverEvent.x, fHoverEvent.y) : new Point(-1, -1); | |
948 } | |
949 | |
950 /** | |
951 * Returns the most recent mouse hover event. | |
952 * | |
953 * @return the most recent mouse hover event or <code>null</code> | |
954 * @since 3.0 | |
955 */ | |
956 protected MouseEvent getHoverEvent() { | |
957 return fHoverEvent; | |
958 } | |
959 | |
960 /** | |
961 * Returns the DWT event state of the most recent mouse hover event. | |
962 * | |
963 * @return the DWT event state of the most recent mouse hover event | |
964 */ | |
965 protected int getHoverEventStateMask() { | |
966 return fHoverEventStateMask; | |
967 } | |
968 | |
969 /** | |
970 * Returns an adapter that gives access to internal methods. | |
971 * <p> | |
972 * <strong>Note:</strong> This method is not intended to be referenced or overridden by clients.</p> | |
973 * | |
974 * @return the replaceable information control accessor | |
975 * @since 3.4 | |
976 * @noreference This method is not intended to be referenced by clients. | |
977 * @nooverride This method is not intended to be re-implemented or extended by clients. | |
978 */ | |
979 public InternalAccessor getInternalAccessor() { | |
980 return new MyInternalAccessor() { | |
981 public void setHoverEnrichMode(EnrichMode mode) { | |
982 AbstractHoverInformationControlManager.this.setHoverEnrichMode(mode); | |
983 } | |
984 }; | |
985 } | |
986 | |
987 } |