001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text;
011:
012: import org.eclipse.swt.SWT;
013: import org.eclipse.swt.events.ControlEvent;
014: import org.eclipse.swt.events.ControlListener;
015: import org.eclipse.swt.events.KeyEvent;
016: import org.eclipse.swt.events.KeyListener;
017: import org.eclipse.swt.events.MouseEvent;
018: import org.eclipse.swt.events.MouseListener;
019: import org.eclipse.swt.events.MouseMoveListener;
020: import org.eclipse.swt.events.MouseTrackAdapter;
021: import org.eclipse.swt.events.MouseTrackListener;
022: import org.eclipse.swt.events.ShellAdapter;
023: import org.eclipse.swt.events.ShellEvent;
024: import org.eclipse.swt.events.ShellListener;
025: import org.eclipse.swt.graphics.Point;
026: import org.eclipse.swt.graphics.Rectangle;
027: import org.eclipse.swt.widgets.Control;
028: import org.eclipse.swt.widgets.Display;
029: import org.eclipse.swt.widgets.Event;
030: import org.eclipse.swt.widgets.Listener;
031:
032: import org.eclipse.core.runtime.Assert;
033:
034: /**
035: * An information control manager that shows information in response to mouse
036: * hover events. The mouse hover events are caught by registering a
037: * {@link org.eclipse.swt.events.MouseTrackListener} on the manager's subject
038: * control. The manager has by default an information control closer that closes
039: * the information control as soon as the mouse pointer leaves the subject area,
040: * the user presses a key, or the subject control is resized, moved, or
041: * deactivated.
042: * <p>
043: * When being activated by a mouse hover event, the manager disables itself,
044: * until the mouse leaves the subject area. Thus, the manager is usually still
045: * disabled, when the information control has already been closed by the closer.
046: *
047: * @see org.eclipse.swt.events.MouseTrackListener
048: * @since 2.0
049: */
050: abstract public class AbstractHoverInformationControlManager extends
051: AbstractInformationControlManager {
052:
053: /**
054: * The information control closer for the hover information. Closes the information control as
055: * soon as the mouse pointer leaves the subject area, a mouse button is pressed, the user presses a key,
056: * or the subject control is resized or moved.
057: */
058: class Closer extends MouseTrackAdapter implements
059: IInformationControlCloser, MouseListener,
060: MouseMoveListener, ControlListener, KeyListener,
061: ShellListener, Listener {
062:
063: /** The closer's subject control */
064: private Control fSubjectControl;
065: /** The subject area */
066: private Rectangle fSubjectArea;
067: /** Indicates whether this closer is active */
068: private boolean fIsActive = false;
069: /**
070: * The cached display.
071: * @since 3.1
072: */
073: private Display fDisplay;
074:
075: /**
076: * Creates a new information control closer.
077: */
078: public Closer() {
079: }
080:
081: /*
082: * @see IInformationControlCloser#setSubjectControl(Control)
083: */
084: public void setSubjectControl(Control control) {
085: fSubjectControl = control;
086: }
087:
088: /*
089: * @see IInformationControlCloser#setHoverControl(IHoverControl)
090: */
091: public void setInformationControl(IInformationControl control) {
092: }
093:
094: /*
095: * @see IInformationControlCloser#start(Rectangle)
096: */
097: public void start(Rectangle subjectArea) {
098:
099: if (fIsActive)
100: return;
101: fIsActive = true;
102:
103: fSubjectArea = subjectArea;
104:
105: if (fSubjectControl != null
106: && !fSubjectControl.isDisposed()) {
107: fSubjectControl.addMouseListener(this );
108: fSubjectControl.addMouseMoveListener(this );
109: fSubjectControl.addMouseTrackListener(this );
110: fSubjectControl.addControlListener(this );
111: fSubjectControl.addKeyListener(this );
112: fSubjectControl.getShell().addShellListener(this );
113:
114: fDisplay = fSubjectControl.getDisplay();
115: if (!fDisplay.isDisposed()) {
116: fDisplay.addFilter(SWT.Show, this );
117: fDisplay.addFilter(SWT.Activate, this );
118: fDisplay.addFilter(SWT.MouseWheel, this );
119: }
120: }
121: }
122:
123: /*
124: * @see IInformationControlCloser#stop()
125: */
126: public void stop() {
127: stop(false);
128: }
129:
130: /**
131: * Stops the information control and if <code>delayRestart</code> is set
132: * allows restart only after a certain delay.
133: *
134: * @param delayRestart <code>true</code> if restart should be delayed
135: */
136: protected void stop(boolean delayRestart) {
137:
138: if (!fIsActive)
139: return;
140:
141: fIsActive = false;
142:
143: hideInformationControl();
144:
145: if (fSubjectControl != null
146: && !fSubjectControl.isDisposed()) {
147: fSubjectControl.removeMouseListener(this );
148: fSubjectControl.removeMouseMoveListener(this );
149: fSubjectControl.removeMouseTrackListener(this );
150: fSubjectControl.removeControlListener(this );
151: fSubjectControl.removeKeyListener(this );
152: fSubjectControl.getShell().removeShellListener(this );
153: }
154:
155: if (fDisplay != null && !fDisplay.isDisposed()) {
156: fDisplay.removeFilter(SWT.Show, this );
157: fDisplay.removeFilter(SWT.Activate, this );
158: fDisplay.removeFilter(SWT.MouseWheel, this );
159: }
160: fDisplay = null;
161: }
162:
163: /*
164: * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent)
165: */
166: public void mouseMove(MouseEvent event) {
167: if (!fSubjectArea.contains(event.x, event.y))
168: stop();
169: }
170:
171: /*
172: * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent)
173: */
174: public void mouseUp(MouseEvent event) {
175: }
176:
177: /*
178: * @see MouseListener#mouseDown(MouseEvent)
179: */
180: public void mouseDown(MouseEvent event) {
181: stop();
182: }
183:
184: /*
185: * @see MouseListener#mouseDoubleClick(MouseEvent)
186: */
187: public void mouseDoubleClick(MouseEvent event) {
188: stop();
189: }
190:
191: /*
192: * @see MouseTrackAdapter#mouseExit(MouseEvent)
193: */
194: public void mouseExit(MouseEvent event) {
195: stop();
196: }
197:
198: /*
199: * @see ControlListener#controlResized(ControlEvent)
200: */
201: public void controlResized(ControlEvent event) {
202: stop();
203: }
204:
205: /*
206: * @see ControlListener#controlMoved(ControlEvent)
207: */
208: public void controlMoved(ControlEvent event) {
209: stop();
210: }
211:
212: /*
213: * @see KeyListener#keyReleased(KeyEvent)
214: */
215: public void keyReleased(KeyEvent event) {
216: }
217:
218: /*
219: * @see KeyListener#keyPressed(KeyEvent)
220: */
221: public void keyPressed(KeyEvent event) {
222: stop(true);
223: }
224:
225: /*
226: * @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent)
227: * @since 3.1
228: */
229: public void shellActivated(ShellEvent e) {
230: }
231:
232: /*
233: * @see org.eclipse.swt.events.ShellListener#shellClosed(org.eclipse.swt.events.ShellEvent)
234: * @since 3.1
235: */
236: public void shellClosed(ShellEvent e) {
237: }
238:
239: /*
240: * @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent)
241: * @since 3.1
242: */
243: public void shellDeactivated(ShellEvent e) {
244: stop();
245: }
246:
247: /*
248: * @see org.eclipse.swt.events.ShellListener#shellDeiconified(org.eclipse.swt.events.ShellEvent)
249: * @since 3.1
250: */
251: public void shellDeiconified(ShellEvent e) {
252: }
253:
254: /*
255: * @see org.eclipse.swt.events.ShellListener#shellIconified(org.eclipse.swt.events.ShellEvent)
256: * @since 3.1
257: */
258: public void shellIconified(ShellEvent e) {
259: }
260:
261: /*
262: * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
263: * @since 3.1
264: */
265: public void handleEvent(Event event) {
266: if (event.type == SWT.Activate || event.type == SWT.Show
267: || event.type == SWT.MouseWheel)
268: stop();
269: }
270: }
271:
272: /**
273: * To be installed on the manager's subject control. Serves two different purposes:
274: * <ul>
275: * <li> start function: initiates the computation of the information to be presented. This happens on
276: * receipt of a mouse hover event and disables the information control manager,
277: * <li> restart function: tracks mouse move and shell activation event to determine when the information
278: * control manager needs to be reactivated.
279: * </ul>
280: */
281: class MouseTracker extends ShellAdapter implements
282: MouseTrackListener, MouseMoveListener {
283:
284: /** Margin around the original hover event location for computing the hover area. */
285: private final static int EPSILON = 3;
286:
287: /** The area in which the original hover event occurred. */
288: private Rectangle fHoverArea;
289: /** The area for which is computed information is valid. */
290: private Rectangle fSubjectArea;
291: /** The tracker's subject control. */
292: private Control fSubjectControl;
293:
294: /** Indicates whether the tracker is in restart mode ignoring hover events. */
295: private boolean fIsInRestartMode = false;
296: /** Indicates whether the tracker is computing the information to be presented. */
297: private boolean fIsComputing = false;
298: /** Indicates whether the mouse has been lost. */
299: private boolean fMouseLostWhileComputing = false;
300: /** Indicates whether the subject control's shell has been deactivated. */
301: private boolean fShellDeactivatedWhileComputing = false;
302:
303: /**
304: * Creates a new mouse tracker.
305: */
306: public MouseTracker() {
307: }
308:
309: /**
310: * Sets this mouse tracker's subject area, the area to be tracked in order
311: * to re-enable the information control manager.
312: *
313: * @param subjectArea the subject area
314: */
315: public void setSubjectArea(Rectangle subjectArea) {
316: Assert.isNotNull(subjectArea);
317: fSubjectArea = subjectArea;
318: }
319:
320: /**
321: * Starts this mouse tracker. The given control becomes this tracker's subject control.
322: * Installs itself as mouse track listener on the subject control.
323: *
324: * @param subjectControl the subject control
325: */
326: public void start(Control subjectControl) {
327: fSubjectControl = subjectControl;
328: if (fSubjectControl != null
329: && !fSubjectControl.isDisposed())
330: fSubjectControl.addMouseTrackListener(this );
331:
332: fIsInRestartMode = false;
333: fIsComputing = false;
334: fMouseLostWhileComputing = false;
335: fShellDeactivatedWhileComputing = false;
336: }
337:
338: /**
339: * Stops this mouse tracker. Removes itself as mouse track, mouse move, and
340: * shell listener from the subject control.
341: */
342: public void stop() {
343: if (fSubjectControl != null
344: && !fSubjectControl.isDisposed()) {
345: fSubjectControl.removeMouseTrackListener(this );
346: fSubjectControl.removeMouseMoveListener(this );
347: fSubjectControl.getShell().removeShellListener(this );
348: }
349: }
350:
351: /**
352: * Initiates the computation of the information to be presented. Sets the initial hover area
353: * to a small rectangle around the hover event location. Adds mouse move and shell activation listeners
354: * to track whether the computed information is, after completion, useful for presentation and to
355: * implement the restart function.
356: *
357: * @param event the mouse hover event
358: */
359: public void mouseHover(MouseEvent event) {
360:
361: if (fIsComputing || fIsInRestartMode)
362: return;
363:
364: fIsInRestartMode = true;
365: fIsComputing = true;
366: fMouseLostWhileComputing = false;
367: fShellDeactivatedWhileComputing = false;
368:
369: fHoverEventStateMask = event.stateMask;
370: fHoverEvent = event;
371: fHoverArea = new Rectangle(event.x - EPSILON, event.y
372: - EPSILON, 2 * EPSILON, 2 * EPSILON);
373: if (fHoverArea.x < 0)
374: fHoverArea.x = 0;
375: if (fHoverArea.y < 0)
376: fHoverArea.y = 0;
377: setSubjectArea(fHoverArea);
378:
379: if (fSubjectControl != null
380: && !fSubjectControl.isDisposed()) {
381: fSubjectControl.addMouseMoveListener(this );
382: fSubjectControl.getShell().addShellListener(this );
383: }
384: doShowInformation();
385: }
386:
387: /**
388: * Deactivates this tracker's restart function and enables the information control
389: * manager. Does not have any effect if the tracker is still executing the start function (i.e.
390: * computing the information to be presented.
391: */
392: protected void deactivate() {
393: if (fIsComputing)
394: return;
395: fIsInRestartMode = false;
396: if (fSubjectControl != null
397: && !fSubjectControl.isDisposed()) {
398: fSubjectControl.removeMouseMoveListener(this );
399: fSubjectControl.getShell().removeShellListener(this );
400: }
401: }
402:
403: /*
404: * @see MouseTrackListener#mouseEnter(MouseEvent)
405: */
406: public void mouseEnter(MouseEvent e) {
407: }
408:
409: /*
410: * @see MouseTrackListener#mouseExit(MouseEvent)
411: */
412: public void mouseExit(MouseEvent e) {
413: fMouseLostWhileComputing = true;
414: deactivate();
415: }
416:
417: /*
418: * @see MouseMoveListener#mouseMove(MouseEvent)
419: */
420: public void mouseMove(MouseEvent event) {
421: if (!fSubjectArea.contains(event.x, event.y))
422: deactivate();
423: }
424:
425: /*
426: * @see ShellListener#shellDeactivated(ShellEvent)
427: */
428: public void shellDeactivated(ShellEvent e) {
429: fShellDeactivatedWhileComputing = true;
430: deactivate();
431: }
432:
433: /*
434: * @see ShellListener#shellIconified(ShellEvent)
435: */
436: public void shellIconified(ShellEvent e) {
437: fShellDeactivatedWhileComputing = true;
438: deactivate();
439: }
440:
441: /**
442: * Tells this tracker that the start function processing has been completed.
443: */
444: public void computationCompleted() {
445: fIsComputing = false;
446: fMouseLostWhileComputing = false;
447: fShellDeactivatedWhileComputing = false;
448: }
449:
450: /**
451: * Determines whether the computed information is still useful for presentation.
452: * This is not the case, if the shell of the subject control has been deactivated, the mouse
453: * left the subject control, or the mouse moved on, so that it is no longer in the subject
454: * area.
455: *
456: * @return <code>true</code> if information is still useful for presentation, <code>false</code> otherwise
457: */
458: public boolean isMouseLost() {
459:
460: if (fMouseLostWhileComputing
461: || fShellDeactivatedWhileComputing)
462: return true;
463:
464: if (fSubjectControl != null
465: && !fSubjectControl.isDisposed()) {
466: Display display = fSubjectControl.getDisplay();
467: Point p = display.getCursorLocation();
468: p = fSubjectControl.toControl(p);
469: if (!fSubjectArea.contains(p)
470: && !fHoverArea.contains(p))
471: return true;
472: }
473:
474: return false;
475: }
476: }
477:
478: /** The mouse tracker on the subject control */
479: private MouseTracker fMouseTracker = new MouseTracker();
480: /**
481: * The remembered hover event.
482: * @since 3.0
483: */
484: private MouseEvent fHoverEvent = null;
485: /** The remembered hover event sate mask of the keyboard modifiers */
486: private int fHoverEventStateMask = 0;
487:
488: /**
489: * Creates a new hover information control manager using the given information control creator.
490: * By default a <code>Closer</code> instance is set as this manager's closer.
491: *
492: * @param creator the information control creator
493: */
494: protected AbstractHoverInformationControlManager(
495: IInformationControlCreator creator) {
496: super (creator);
497: setCloser(new Closer());
498: }
499:
500: /*
501: * @see org.eclipse.jface.text.AbstractInformationControlManager#presentInformation()
502: */
503: protected void presentInformation() {
504: if (fMouseTracker == null) {
505: super .presentInformation();
506: return;
507: }
508:
509: Rectangle area = getSubjectArea();
510: if (area != null)
511: fMouseTracker.setSubjectArea(area);
512:
513: if (fMouseTracker.isMouseLost()) {
514: fMouseTracker.computationCompleted();
515: fMouseTracker.deactivate();
516: } else {
517: fMouseTracker.computationCompleted();
518: super .presentInformation();
519: }
520: }
521:
522: /**
523: * {@inheritDoc}
524: * @deprecated visibility will be changed to protected
525: */
526: public void setEnabled(boolean enabled) {
527:
528: boolean was = isEnabled();
529: super .setEnabled(enabled);
530: boolean is = isEnabled();
531:
532: if (was != is && fMouseTracker != null) {
533: if (is)
534: fMouseTracker.start(getSubjectControl());
535: else
536: fMouseTracker.stop();
537: }
538: }
539:
540: /**
541: * Disposes this manager's information control.
542: */
543: public void dispose() {
544: if (fMouseTracker != null) {
545: fMouseTracker.stop();
546: fMouseTracker.fSubjectControl = null;
547: fMouseTracker = null;
548: }
549: super .dispose();
550: }
551:
552: /**
553: * Returns the location at which the most recent mouse hover event
554: * has been issued.
555: *
556: * @return the location of the most recent mouse hover event
557: */
558: protected Point getHoverEventLocation() {
559: return fHoverEvent != null ? new Point(fHoverEvent.x,
560: fHoverEvent.y) : new Point(-1, -1);
561: }
562:
563: /**
564: * Returns the most recent mouse hover event.
565: *
566: * @return the most recent mouse hover event or <code>null</code>
567: * @since 3.0
568: */
569: protected MouseEvent getHoverEvent() {
570: return fHoverEvent;
571: }
572:
573: /**
574: * Returns the SWT event state of the most recent mouse hover event.
575: *
576: * @return the SWT event state of the most recent mouse hover event
577: */
578: protected int getHoverEventStateMask() {
579: return fHoverEventStateMask;
580: }
581:
582: }
|