package com.netifera.platform.ui.util; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Shell; /** * Used to to display information control hovers. Tracks mouse events to show * and hide the hover control. */ public abstract class MouseTracker extends ShellAdapter implements MouseTrackListener, MouseMoveListener, DisposeListener , KeyListener { /** * Margin around the original hover event location for computing the hover * area. */ protected final static int EPSILON = 3; /** The area in which the original hover event occurred. */ private Rectangle hoverArea; /** * subjectArea the area relative to the subject control inside which the * presented information is valid */ private Rectangle subjectArea; /** * keepUpArea if the mouse if moved inside the keepUpArea the hover is not * closed */ private Rectangle keepUpArea; private Control subjectControl; /* isComputing the information to be displayed is being computed */ private boolean isComputing; /* ignoreShowEvents if set the hover events are ignored */ private boolean ignoreShowEvents; /* the mouse moved outside the subject area while computing */ private boolean mouseLostWhileComputing; /* the subject control shell was deactivated while computing */ private boolean shellDeactivatedWhileComputing; private MouseEvent lastHoverEvent; private boolean expandAreaToIncludePointer; public MouseTracker(Control subjectControl) { start(subjectControl); } /** * Starts tracking mouse events in the given control * * @param subjectControl * the control where the hover will be active */ private void start(Control subjectControl) { this.subjectControl = subjectControl; if (subjectControl != null && !subjectControl.isDisposed()) { subjectControl.addMouseTrackListener(this); subjectControl.addDisposeListener(this); subjectControl.addKeyListener(this); } ignoreShowEvents = false; isComputing = false; mouseLostWhileComputing = false; shellDeactivatedWhileComputing = false; } public void stop() { if (subjectControl != null && !subjectControl.isDisposed()) { subjectControl.removeMouseTrackListener(this); subjectControl.removeMouseMoveListener(this); subjectControl.getShell().removeShellListener(this); subjectControl = null; } } /** * @param subjectArea * rectangle containing the item for which the information being * shown has relevance */ private void setSubjectArea(Rectangle subjectArea) { this.subjectArea = subjectArea; } /** * @param hoverArea * the rectangle containing the information control */ protected void setHoverArea(Rectangle hoverArea) { this.hoverArea = hoverArea; keepUpArea = new Rectangle(Math.max(hoverArea.x - EPSILON * 3, 0), Math .max(hoverArea.y - EPSILON * 3, 0), hoverArea.width + EPSILON * 3 * 2, hoverArea.height + EPSILON * 3); } /** * @return lastHoverEvent the event object of the last handled hover event */ private MouseEvent getLastHoverEvent() { return lastHoverEvent; } private boolean canMoveOverHoverControl() { return true; } /** * Determines whether the computed information is still useful for * presentation. This is not the case, if the shell of the subject control * has been deactivated, the mouse left the subject control, or the mouse * moved on, so that it is no longer in the subject area. * * @return <code>true</code> if information is still useful for * presentation, <code>false</code> otherwise */ private boolean isComputedInformationStillValid() { if (mouseLostWhileComputing || shellDeactivatedWhileComputing) { return true; } if (subjectControl != null && !subjectControl.isDisposed()) { Point cursorLocation = subjectControl.getDisplay() .getCursorLocation(); cursorLocation = subjectControl.toControl(cursorLocation); if (!subjectArea.contains(cursorLocation) && !hoverArea.contains(cursorLocation)) { return true; } } return false; } /** * Tests whether a given mouse location is within the keep-up area The hover * should not be hidden as long as the mouse stays inside this area. */ private boolean inKeepUpArea(int x, int y) { Rectangle informationControlArea = getInformationControlArea(); if (informationControlArea != null) { setHoverArea(informationControlArea); } Point pointInScreen = subjectControl.toDisplay(x, y); if (keepUpArea != null && keepUpArea.contains(pointInScreen)) { return true; } return false; } /** * hides the information control and disables the mouse tracking */ protected void deactivate() { if (isComputing) { return; } hideInformationControl(); ignoreShowEvents = false; if (subjectControl != null && !subjectControl.isDisposed()) { subjectControl.removeMouseMoveListener(this); subjectControl.getShell().removeShellListener(this); } } /** * the information to be displayed has been computed */ protected void computationCompleted() { isComputing = false; mouseLostWhileComputing = false; shellDeactivatedWhileComputing = false; } /** UI event handlers */ public void mouseEnter(MouseEvent e) { } /** * Executes when the mouse pointer moves over the subject control but * without hovering. * * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events * .MouseEvent) */ public void mouseMove(MouseEvent e) { if (!(subjectArea.contains(e.x, e.y) || inKeepUpArea(e.x, e.y))) { deactivate(); } } public void mouseExit(MouseEvent e) { // TODO } /** * Executes when the mouse pointer hovers (stops moving for an operating * system specific period of time) over the subject control * * @see org.eclipse.swt.events.MouseTrackListener#mouseHover(org.eclipse.swt. * events.MouseEvent) */ public void mouseHover(MouseEvent e) { lastHoverEvent = e; showEventAt(new Point(e.x,e.y)); } protected void showEventAt(final Point eventPointInControl) { if (isComputing || ignoreShowEvents) { return; } if (subjectControl == null || subjectControl.isDisposed()) { return; } Shell activeShell = subjectControl.getShell().getDisplay() .getActiveShell(); if (subjectControl.getShell() != activeShell) { return; } Point eventPointScreen = subjectControl.toDisplay(eventPointInControl); calculateAreas(eventPointInControl); /* * if after the areas are calculated the triggering point is outside the * subjectArea then the hover is not shown */ if (!subjectArea.contains(eventPointInControl)) { return; } isComputing = true; ignoreShowEvents = true; mouseLostWhileComputing = false; shellDeactivatedWhileComputing = false; /* * add listeners to the subject control to know when to hide the hover * control */ subjectControl.addMouseMoveListener(this); activeShell.addShellListener(this); /* ready to show the information control */ Object item = getItemAt(eventPointInControl); if (item != null) { try { showInformationControl(activeShell, eventPointScreen, getInput(), item); } catch (IllegalArgumentException exception) { ignoreShowEvents = false; } } else { ignoreShowEvents = false; } computationCompleted(); } /** * Calculates the hover and subject area * * @param pointInControl * coordinates relative to the subject control where the hover * event happened. */ private void calculateAreas(Point pointInControl) { /* very small default hover area */ Rectangle hoverArea = new Rectangle(pointInControl.x - EPSILON, pointInControl.y - EPSILON, 2 * EPSILON, 2 * EPSILON); hoverArea.y = Math.max(hoverArea.y, 0); hoverArea.x = Math.max(hoverArea.x, 0); Rectangle itemArea = getAreaOfItemAt(pointInControl); if (itemArea == null) { return; } /* * if for some reason the point that triggered the hover is not inside * the rectangle, the rectangle is expanded. */ if (!itemArea.contains(pointInControl) && expandAreaToIncludePointer) { itemArea.add(hoverArea); } setSubjectArea(itemArea); setHoverArea(hoverArea); } /** ShellListener methods */ public void shellIconified(ShellEvent e) { shellDeactivatedWhileComputing = true; deactivate(); } public void shellActivated(ShellEvent e) { if (ignoreShowEvents) { e.doit = false; } } public void shellDeactivated(ShellEvent e) { shellDeactivatedWhileComputing = true; ignoreShowEvents = false; /* we could remove the previous line and call deactivate after checking that * the hover is not the the shell that got focus */ // deactivate(); } public void widgetDisposed(DisposeEvent e) { stop(); } /** KeyListener methods */ public void keyPressed(KeyEvent e) { if (e.character == ' ') { Rectangle itemArea = getAreaOfSelectedItem(); if(itemArea != null) { Point eventPoint = new Point(itemArea.x,itemArea.y); showEventAt(eventPoint); } } else if (e.character == SWT.ESC) { hideInformationControl(); ignoreShowEvents = false; } else { if(!isComputing) { e.doit = !focusInformationControl(); } } } public void keyReleased(KeyEvent e) { } /** the following methods belong to the subject control interface */ /** * @param point * relative to the subject control * @return Object whose data is represented by the item at the specified * point */ protected abstract Object getItemAt(Point point); /** * @param point * relative to the subject control * @return subject area rectangle relative to the subject control */ protected Rectangle getAreaOfItemAt(Point point) { return new Rectangle(point.x - EPSILON, point.y - EPSILON, EPSILON * 2, EPSILON * 2); } /** * @return subject area rectangle relative to the subject control if any * item is selected, null otherwise. */ protected Rectangle getAreaOfSelectedItem() { return null; } /** * @return input Object to which the subject item belongs */ public Object getInput() { return subjectControl.getData(); } /** to show information controls the following methods should be implemented */ protected abstract void showInformationControl(Shell parent, Point location, Object input, Object item); protected abstract void hideInformationControl(); /** * @return true if the control got focus, and false if was unable to */ protected abstract boolean focusInformationControl(); protected abstract Rectangle getInformationControlArea(); }