During row mouseout events, only consider it as row unhovering if the
event coordinates are outside the hovering row. Previously, if there's a popup
dialog floating on top of a table row, moving into this element will cause a row unhover event. This is particularly a problem if the element is shown/dismissed based on row hovering (causing a flickring effect).

Also include the original browser event in the RowHoverEvent in case the user needs more information about the original event.

Review at http://gwt-code-reviews.appspot.com/1546803

Review by: jlabanca@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10660 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
index b17b737..f51f92a 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
@@ -44,6 +44,7 @@
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
 import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
 import com.google.gwt.user.client.ui.Widget;
@@ -1777,13 +1778,26 @@
       if ("mouseover".equals(eventType)) {
         // Unstyle the old row if it is still part of the table.
         if (hoveringRow != null && getTableBodyElement().isOrHasChild(hoveringRow)) {
-          setRowHover(hoveringRow, false);
+          setRowHover(hoveringRow, event, false);
         }
         hoveringRow = targetTableRow;
-        setRowHover(hoveringRow, true);
+        setRowHover(hoveringRow, event, true);
       } else if ("mouseout".equals(eventType) && hoveringRow != null) {
-        setRowHover(hoveringRow, false);
-        hoveringRow = null;
+        // Ignore events happening directly over the hovering row. If there are floating element
+        // on top of the row, mouseout event should not be triggered. This is to avoid the flickring
+        // effect if the floating element is shown/hide based on hover event.
+        int clientX = event.getClientX() + Window.getScrollLeft();
+        int clientY = event.getClientY() + Window.getScrollTop();
+        int rowLeft = hoveringRow.getAbsoluteLeft();
+        int rowTop = hoveringRow.getAbsoluteTop();
+        int rowWidth = hoveringRow.getOffsetWidth();
+        int rowHeight = hoveringRow.getOffsetHeight();
+        int rowBottom = rowTop + rowHeight;
+        int rowRight = rowLeft + rowWidth;
+        if (clientX < rowLeft || clientX > rowRight || clientY < rowTop || clientY > rowBottom) {
+          setRowHover(hoveringRow, event, false);
+          hoveringRow = null;
+        }
       }
 
       // If the event causes us to page, then the physical index will be out
@@ -2410,11 +2424,12 @@
    * Set a row's hovering style and fire a {@link RowHoverEvent}
    * 
    * @param tr the row element
+   * @param event the original event
    * @param isHovering false if this is an unhover event
    */
-  private void setRowHover(TableRowElement tr, boolean isHovering) {
+  private void setRowHover(TableRowElement tr, Event event, boolean isHovering) {
     setRowStyleName(tr, style.hoveredRow(), style.hoveredRowCell(), isHovering);
-    RowHoverEvent.fire(this, tr, !isHovering);
+    RowHoverEvent.fire(this, tr, event, !isHovering);
   }
   
   /**
diff --git a/user/src/com/google/gwt/user/cellview/client/RowHoverEvent.java b/user/src/com/google/gwt/user/cellview/client/RowHoverEvent.java
index 77a8af9..43d1ea8 100644
--- a/user/src/com/google/gwt/user/cellview/client/RowHoverEvent.java
+++ b/user/src/com/google/gwt/user/cellview/client/RowHoverEvent.java
@@ -19,6 +19,7 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
 import com.google.gwt.event.shared.HasHandlers;
+import com.google.gwt.user.client.Event;
 
 /**
  * Represents a row hover event.
@@ -55,7 +56,23 @@
    */
   public static RowHoverEvent fire(HasHandlers source, TableRowElement hoveringRow,
       boolean isUnHover) {
-    RowHoverEvent event = new RowHoverEvent(hoveringRow, isUnHover);
+    return fire(source, hoveringRow, null, isUnHover);
+  }
+  
+  /**
+   * Fires a row hover event on all registered handlers in the handler
+   * manager. If no such handlers exist, this implementation will do nothing.
+   * 
+   * @param source the source of the event
+   * @param hoveringRow the currently hovering {@link TableRowElement}. If isUnHover is false, this
+   *          should be the previouly hovering {@link TableRowElement}
+   * @param browserEvent the original browser event
+   * @param isUnHover false if this is an unhover event
+   * @return the {@link RowHoverEvent} that was fired
+   */
+  public static RowHoverEvent fire(HasHandlers source, TableRowElement hoveringRow,
+      Event browserEvent, boolean isUnHover) {
+    RowHoverEvent event = new RowHoverEvent(hoveringRow, browserEvent, isUnHover);
     if (TYPE != null) {
       source.fireEvent(event);
     }
@@ -73,6 +90,8 @@
     }
     return TYPE;
   }
+
+  private Event browserEvent;
   
   private TableRowElement hoveringRow;
   
@@ -86,7 +105,20 @@
    * @param isUnHover false if this is an unhover event
    */
   protected RowHoverEvent(TableRowElement hoveringRow, boolean isUnHover) {
+    this(hoveringRow, null, isUnHover);
+  }
+  
+  /**
+   * Construct a new {@link RowHoverEvent}.
+   * 
+   * @param hoveringRow the currently hovering {@link TableRowElement}. If isUnHover is false, this
+   *                    should be the previouly hovering {@link TableRowElement}
+   * @param browserEvent the original browser event
+   * @param isUnHover false if this is an unhover event
+   */
+  protected RowHoverEvent(TableRowElement hoveringRow, Event browserEvent, boolean isUnHover) {
     this.hoveringRow = hoveringRow;
+    this.browserEvent = browserEvent;
     this.isUnHover = isUnHover;
   }
   
@@ -96,6 +128,14 @@
   }
   
   /**
+   * Return the original browser {@link Event}. The browser event could be null if the event is
+   * fired without one (e.g., by calling {@link #fire(HasHandler, TableRowElement, isUnHover)})
+   */
+  public Event getBrowserEvent() {
+    return browserEvent;
+  }
+  
+  /**
    * Return the {@link TableRowElement} that the user just hovered or unhovered.
    */
   public TableRowElement getHoveringRow() {