Project: netifera
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 * 30), Math 
    .max(hoverArea.y - EPSILON * 30), 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(); 
}