Greg Sheremeta has uploaded a new change for review.

Change subject: userportal, webadmin: new tooltip infrastructure
......................................................................

userportal, webadmin: new tooltip infrastructure

New tooltip infrastructure that works with Element, Cells, and Widgets.
Uses PatternFly tooltips for rendering.

Change-Id: Ief7f8524a3dd69c983ace95206379df462bc7daf
Bug-Url: Bug-Url: https://bugzilla.redhat.com/1067318
Signed-off-by: Greg Sheremeta <[email protected]>
---
M 
backend/manager/modules/branding/src/main/resources/META-INF/tags/obrand/javascripts.tag
M 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/ElementUtils.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/JqueryUtils.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/AbstractTooltipCell.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/TooltipCell.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractColumn.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/ImageResourceCell.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltip.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltipDetails.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/Tooltip.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipConfig.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipMixin.java
M 
frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/widget/extended/vm/TooltipCell.java
M 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/tab/MainTabClusterView.java
A 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/table/column/CommentColumn2.java
A packaging/branding/ovirt.brand/mousetrack.js
M packaging/branding/ovirt.brand/ovirt.css
17 files changed, 2,022 insertions(+), 3 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/55/38555/1

diff --git 
a/backend/manager/modules/branding/src/main/resources/META-INF/tags/obrand/javascripts.tag
 
b/backend/manager/modules/branding/src/main/resources/META-INF/tags/obrand/javascripts.tag
index 0310ee4..e186e91 100644
--- 
a/backend/manager/modules/branding/src/main/resources/META-INF/tags/obrand/javascripts.tag
+++ 
b/backend/manager/modules/branding/src/main/resources/META-INF/tags/obrand/javascripts.tag
@@ -7,6 +7,7 @@
 <script type="text/javascript" 
src="${pageContext.request.contextPath}${initParam['obrandThemePath']}${baseTheme.path}/patternfly/components/jquery/jquery.min.js"></script>
 <script type="text/javascript" 
src="${pageContext.request.contextPath}${initParam['obrandThemePath']}${baseTheme.path}/patternfly/components/bootstrap/dist/js/bootstrap.min.js"></script>
 <script type="text/javascript" 
src="${pageContext.request.contextPath}${initParam['obrandThemePath']}${baseTheme.path}/patternfly/js/patternfly.min.js"></script>
+<script type="text/javascript" 
src="${pageContext.request.contextPath}${initParam['obrandThemePath']}${baseTheme.path}/mousetrack.js"></script>
 <c:choose>
     <c:when test="${fn:containsIgnoreCase(header['User-Agent'],'MSIE 8.0')}">
         <script type="text/javascript" 
src="${pageContext.request.contextPath}${initParam['obrandThemePath']}${baseTheme.path}/patternfly/components/respond/dest/respond.min.js"></script>
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/ElementUtils.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/ElementUtils.java
index 484014b..2339472 100644
--- 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/ElementUtils.java
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/ElementUtils.java
@@ -36,4 +36,51 @@
         return clientHeightAfter > clientHeightBefore || clientHeightAfter < 
scrollHeightAfter;
     }
 
+    /**
+     * Check up this Element's ancestor tree, and return true if we find a 
null parent.
+     * @return true if a null parent is found, false if this Element has body 
as an ancestor
+     * (meaning it's still attached).
+     */
+    public static boolean hasNullAncestor(Element element) {
+        Element parent = element.getParentElement();
+        while (parent != null) {
+            if (parent.getTagName().equalsIgnoreCase("body")) { //$NON-NLS-1$
+                return false;
+            }
+            parent = parent.getParentElement();
+        }
+        return true;
+    }
+
+    /**
+     * Is an Element an ancestor of another Element?
+     */
+    public static boolean hasAncestor(Element element, Element ancestor) {
+
+        if (ancestor == null || element == null) {
+            return false;
+        }
+
+        Element parent = element.getParentElement();
+        while (parent != null) {
+            if (parent.getInnerHTML().equals(ancestor.getInnerHTML())) {
+                return true;
+            }
+            parent = parent.getParentElement();
+        }
+        return false;
+    }
+
+    /**
+     * Return any element found at point x, y.
+     */
+    public static native Element getElementFromPoint(int clientX, int clientY)
+    /*-{
+        var el = $wnd.document.elementFromPoint(clientX, clientY);
+        if(el != null && el.nodeType == 3) {
+            el = el.parentNode;
+        }
+        return el;
+    }-*/;
+
 }
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/JqueryUtils.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/JqueryUtils.java
new file mode 100644
index 0000000..8f6487b
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/utils/JqueryUtils.java
@@ -0,0 +1,48 @@
+package org.ovirt.engine.ui.common.utils;
+
+
+/**
+ * A collection of native functions for executing jquery code.
+ *
+ * Please have a *really* good reason to use jquery.
+ *
+ */
+public class JqueryUtils {
+
+    /**
+     * Fire a mouseenter event at an element found at x,y. Must use jquery 
because
+     * GWT doesn't support 'mouseenter.'
+     *
+     * @param clientX
+     * @param clientY
+     * @return
+     */
+    public static native void fireMouseEnter(int clientX, int clientY)
+    /*-{
+        var el = $wnd.document.elementFromPoint(clientX, clientY);
+        if(el != null && el.nodeType == 3) {
+            el = el.parentNode;
+            console.log(el.parentNode);
+        }
+        $wnd.jQuery(el).trigger("mouseenter");
+    }-*/;
+
+    /**
+     * Is there any open tooltip visible?
+     *
+     * @return
+     */
+    public static native boolean anyTooltipVisible() /*-{
+        var $tooltip = $wnd.jQuery("div.tooltip:visible");
+        if ($tooltip.length == 0) return false;
+        return true;
+    }-*/;
+
+    /**
+     * Get mouse position
+     */
+    public static native String getMousePosition() /*-{
+        return "" + $wnd.mouseX + "," + $wnd.mouseY;
+    }-*/;
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/AbstractTooltipCell.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/AbstractTooltipCell.java
new file mode 100644
index 0000000..726e343
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/AbstractTooltipCell.java
@@ -0,0 +1,133 @@
+package org.ovirt.engine.ui.common.widget.table.cell;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.ovirt.engine.ui.common.utils.ElementIdUtils;
+import org.ovirt.engine.ui.common.widget.tooltip.TooltipMixin;
+
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.dom.client.BrowserEvents;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.DOM;
+
+/**
+ * <p>
+ * This should be the base class of every oVirt custom Cell that would 
otherwise extend AbstractCell.
+ * </p>
+ * <p>
+ * Cell that displays a tooltip when hovered over. Uses the jQuery-based 
Bootstrap / PatternFly
+ * tooltip widget, ElementTooltip. While this cell doesn't have state, it
+ * relies on state saved in ElementTooltip.
+ * </p>
+ * <p>
+ * Supports rendering Element ids via the oVirt Element-ID framework.
+ * </p>
+ */
+public abstract class AbstractTooltipCell<C> extends AbstractCell<C> 
implements TooltipCell<C>, CellWithElementId<C> {
+
+    private String elementIdPrefix = DOM.createUniqueId(); // default
+    private String columnId;
+
+    /**
+     * Events to sink. By default, we only sink mouse events that tooltips 
need. Override this
+     * (and include addAll(super.getConsumedEvents())'s events!) if your cell 
needs to respond
+     * to additional events.
+     */
+    @Override
+    public Set<String> getConsumedEvents() {
+        Set<String> set = new HashSet<String>();
+        TooltipMixin.addTooltipsEvents(set);
+        return set;
+    }
+
+    /**
+     * Don't call this from a custom Column. This is only used in the case 
this Cell is used by
+     * a CompositeCell. In that case, we give each component Cell of the 
Composite a chance to render its own
+     * tooltip.
+     *
+     * See userportal's composite action button cell as an example 
(SideTabExtendedVirtualMachineView).
+     *
+     * @see 
com.google.gwt.cell.client.AbstractCell#onBrowserEvent(com.google.gwt.cell.client.Cell.Context,
 com.google.gwt.dom.client.Element, java.lang.Object, 
com.google.gwt.dom.client.NativeEvent, com.google.gwt.cell.client.ValueUpdater)
+     */
+    @Override
+    public void onBrowserEvent(Context context, Element parent, C value, 
NativeEvent event, ValueUpdater<C> valueUpdater) {
+        onBrowserEvent(context, parent, value, null, event, valueUpdater);
+    }
+
+    /**
+     * Handle events for this cell.
+     *
+     * @see 
org.ovirt.engine.ui.common.widget.table.cell.TooltipCell#onBrowserEvent(com.google.gwt.cell.client.Cell.Context,
 com.google.gwt.dom.client.Element, java.lang.Object, 
com.google.gwt.safehtml.shared.SafeHtml, com.google.gwt.dom.client.NativeEvent, 
com.google.gwt.cell.client.ValueUpdater)
+     */
+    public void onBrowserEvent(Context context, Element parent, C value,
+            SafeHtml tooltipContent, NativeEvent event, ValueUpdater<C> 
valueUpdater) {
+
+        // if the Column did not provide a tooltip, give the Cell a chance to 
render one using the cell value C
+        if (tooltipContent == null) {
+            tooltipContent = getTooltip(value);
+        }
+
+        if (BrowserEvents.MOUSEOVER.equals(event.getType())) {
+            TooltipMixin.configureTooltip(parent, tooltipContent, event);
+        }
+
+        if (BrowserEvents.MOUSEOUT.equals(event.getType())) {
+            TooltipMixin.reapAllTooltips();
+        }
+
+        if (BrowserEvents.MOUSEDOWN.equals(event.getType())) {
+            TooltipMixin.hideAllTooltips();
+        }
+    }
+
+    /**
+     * Let the Cell render the tooltip using C value. This is only attempted 
if the Column itself
+     * did not provide a tooltip. This is usually only used when there is a 
Composite Column that
+     * contains multiple Cells, but each Cell needs its own tooltip.
+     */
+    public SafeHtml getTooltip(C value) {
+        return null;
+    }
+
+    /**
+     * Override the normal render to pass along an id.
+     *
+     * @see 
com.google.gwt.cell.client.AbstractCell#render(com.google.gwt.cell.client.Cell.Context,
 java.lang.Object, com.google.gwt.safehtml.shared.SafeHtmlBuilder)
+     */
+    public void render(Context context, C value, SafeHtmlBuilder sb) {
+        String id = 
ElementIdUtils.createTableCellElementId(getElementIdPrefix(), getColumnId(), 
context);
+        render(context, value, sb, id);
+    }
+
+    /**
+     * Render the cell. Using the value, the id, and the context, append some 
HTML to the
+     * SafeHtmlBuilder that will show in the cell when it is rendered.
+     *
+     * Override this and use the id in your render.
+     *
+     * @see 
org.ovirt.engine.ui.common.widget.table.cell.TooltipCell#render(com.google.gwt.cell.client.Cell.Context,
 java.lang.Object, com.google.gwt.safehtml.shared.SafeHtmlBuilder, 
java.lang.String)
+     */
+    public abstract void render(Context context, C value, SafeHtmlBuilder sb, 
String id);
+
+    public void setElementIdPrefix(String elementIdPrefix) {
+        this.elementIdPrefix = elementIdPrefix;
+    }
+
+    public void setColumnId(String columnId) {
+        this.columnId = columnId;
+    }
+
+    public String getElementIdPrefix() {
+        return elementIdPrefix;
+    }
+
+    public String getColumnId() {
+        return columnId;
+    }
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/TooltipCell.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/TooltipCell.java
new file mode 100644
index 0000000..85f3fa8
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/cell/TooltipCell.java
@@ -0,0 +1,30 @@
+package org.ovirt.engine.ui.common.widget.table.cell;
+
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
+/**
+ * A Cell with a tooltip. All Cells should implement this by extending ovirt's 
AbstractCell.
+ *
+ * @param <C> cell render type
+ */
+public interface TooltipCell<C> extends Cell<C>, CellWithElementId<C> {
+
+    /**
+     * Called by AbstractColumn when an event occurs in a Cell. The only 
difference from GWT's native
+     * Column is that here we ask the column to provide us a tooltip value in 
addition to the cell's
+     * C value.
+     */
+    public void onBrowserEvent(Context context, final Element parent, C value, 
final SafeHtml tooltipContent,
+            final NativeEvent event, ValueUpdater<C> valueUpdater);
+
+    /**
+     * Called by AbstractColumn to render a cell. Sends the cell id so your 
template can include it
+     * in the render.
+     */
+    public abstract void render(Context context, C value, SafeHtmlBuilder sb, 
String id);
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractColumn.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractColumn.java
new file mode 100644
index 0000000..0279696
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/AbstractColumn.java
@@ -0,0 +1,67 @@
+package org.ovirt.engine.ui.common.widget.table.column;
+
+import org.ovirt.engine.ui.common.widget.table.cell.TooltipCell;
+
+import com.google.gwt.cell.client.Cell.Context;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.cellview.client.Column;
+
+/**
+ * A {@link Column}. Supports tooltips.
+ * TODO: add sorting support. Add element-id support.
+ *
+ * @param <T>
+ *            Table row data type.
+ * @param <C>
+ *            Cell data type.
+ */
+public abstract class AbstractColumn<T, C> extends Column<T, C> implements 
ColumnWithElementId {
+
+    public AbstractColumn(TooltipCell<C> cell) {
+        super(cell);
+    }
+
+    public TooltipCell<C> getCell() {
+        return (TooltipCell<C>) super.getCell();
+    }
+
+    /**
+     * <p>
+     * Implement this to return tooltip content for T object. You'll likely 
use some member(s)
+     * of T to build a tooltip. You could also use a constant if the tooltip 
is always the same
+     * for this column.
+     * </p>
+     * <p>
+     * The tooltip cell will then use this value when rendering the cell.
+     * </p>
+     *
+     * @param object
+     * @return tooltip content
+     */
+    public abstract SafeHtml getTooltip(T object);
+
+    /**
+     * This is copied from GWT's Column, but we also inject the tooltip 
content into the cell.
+     * TODO-GWT: make sure that this method is in sync with 
Column::onBroswerEvent.
+     */
+    public void onBrowserEvent(Context context, Element elem, final T object, 
NativeEvent event) {
+        final int index = context.getIndex();
+        ValueUpdater<C> valueUpdater = (getFieldUpdater() == null) ? null : 
new ValueUpdater<C>() {
+            @Override
+            public void update(C value) {
+                getFieldUpdater().update(index, object, value);
+            }
+        };
+        getCell().onBrowserEvent(context, elem, getValue(object), /***/ 
getTooltip(object) /***/, event, valueUpdater);
+    }
+
+    @Override
+    public void configureElementId(String elementIdPrefix, String columnId) {
+        getCell().setElementIdPrefix(elementIdPrefix);
+        getCell().setColumnId(columnId);
+    }
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/ImageResourceCell.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/ImageResourceCell.java
new file mode 100644
index 0000000..ae7498c
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/table/column/ImageResourceCell.java
@@ -0,0 +1,59 @@
+package org.ovirt.engine.ui.common.widget.table.column;
+
+import org.ovirt.engine.ui.common.widget.table.HasStyleClass;
+import org.ovirt.engine.ui.common.widget.table.cell.AbstractTooltipCell;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+
+/**
+ * Cell that renders and ImageResource. Supports setting a style / class. 
Supports tooltips.
+ * TODO: this will replace StyledImageResourceCell. Delete it and change 
references.
+ */
+public class ImageResourceCell extends AbstractTooltipCell<ImageResource> 
implements HasStyleClass {
+
+    interface CellTemplate extends SafeHtmlTemplates {
+        @Template("<div id=\"{0}\" style=\"{1}\" class=\"{2}\">{3}</div>")
+        SafeHtml imageContainerWithStyleClass(String id, String style, String 
styleClass, SafeHtml imageHtml);
+    }
+
+    private String style = "line-height: 100%; text-align: center; 
vertical-align: middle;"; //$NON-NLS-1$
+    private String styleClass = ""; //$NON-NLS-1$
+
+    private CellTemplate template;
+
+    public ImageResourceCell() {
+        super();
+
+        // Delay cell template creation until the first time it's needed
+        if (template == null) {
+            template = GWT.create(CellTemplate.class);
+        }
+    }
+
+    @Override
+    public void render(Context context, ImageResource value, SafeHtmlBuilder 
sb, String id) {
+        if (value != null) {
+            sb.append(template.imageContainerWithStyleClass(
+                    id,
+                    style,
+                    styleClass,
+                    
SafeHtmlUtils.fromTrustedString(AbstractImagePrototype.create(value).getHTML())));
+        }
+    }
+
+    public void setStyle(String style) {
+        this.style = style;
+    }
+
+    @Override
+    public void setStyleClass(String styleClass) {
+        this.styleClass = styleClass == null ? "" : styleClass; //$NON-NLS-1$
+    }
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltip.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltip.java
new file mode 100644
index 0000000..2a391a0
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltip.java
@@ -0,0 +1,615 @@
+package org.ovirt.engine.ui.common.widget.tooltip;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+
+import org.gwtbootstrap3.client.ui.base.HasHover;
+import org.gwtbootstrap3.client.ui.base.HasId;
+import org.gwtbootstrap3.client.ui.constants.Placement;
+import org.gwtbootstrap3.client.ui.constants.Trigger;
+import org.ovirt.engine.ui.common.utils.ElementUtils;
+import org.ovirt.engine.ui.common.utils.JqueryUtils;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+
+/**
+ * <p>
+ * Implementation of Bootstrap tooltips that are capable of wrapping non-GWT 
elements.
+ * This is designed primarily for use in grids, but could be used in any Cell. 
Since
+ * Cells don't support Widget's event system, we must use a reaper timer to 
check for
+ * when a tooltip's Element has been detached from the document.
+ * </p>
+ * <p>
+ * Bootstrap tooltips use jQuery under the hood. jQuery must be present for 
this widget
+ * to function.
+ * </p>
+ * <p>
+ * <br/>
+ * See also: <br/>
+ * <a href="http://getbootstrap.com/javascript/#tooltips";>Bootstrap 
Documentation</a>
+ * <br/>
+ * See also: <br/>
+ * {@link Tooltip}
+ * </p>
+ * <p>
+ * ** Must call reconfigure() after altering any/all Tooltips!
+ * </p>
+ *
+ * @author Joshua Godi
+ * @author Pontus Enmark
+ * @author Greg Sheremeta
+ */
+public class ElementTooltip implements HasId, HasHover {
+    private static final String TOGGLE = "toggle"; //$NON-NLS-1$
+    private static final String SHOW = "show"; //$NON-NLS-1$
+    private static final String HIDE = "hide"; //$NON-NLS-1$
+    private static final String DESTROY = "destroy"; //$NON-NLS-1$
+
+    private boolean isAnimated = TooltipConfig.IS_ANIMATED;
+    private boolean isHTML = TooltipConfig.IS_HTML;
+    private Placement placement = TooltipConfig.PLACEMENT;
+    private Trigger trigger = TooltipConfig.TRIGGER;
+    private SafeHtml content = null;
+    private String container = TooltipConfig.CONTAINER;
+    private final String selector = null;
+
+    private int hideDelayMs = TooltipConfig.HIDE_DELAY_MS;
+    private int showDelayMs = TooltipConfig.SHOW_DELAY_MS;
+    private int reaperInterval = 200;
+    private Timer reaperTimer = null;
+
+    private Element element;
+    private String id;
+
+    private static final Logger logger = 
Logger.getLogger(ElementTooltip.class.getName());
+
+    /**
+     * A static registry of all tooltips in existence. Keyed by the id of the 
element the
+     * tooltip is bound to.
+     */
+    private static Map<String, ElementTooltipDetails> tooltipRegistry = new 
HashMap<String, ElementTooltipDetails>();
+
+    /**
+     * Creates an empty ElementTooltip
+     */
+    public ElementTooltip() {
+    }
+
+    /**
+     * Creates the tooltip around this element
+     *
+     * @param e Element for the tooltip
+     */
+    public ElementTooltip(final Element e) {
+        setElement(e);
+    }
+
+    /**
+     * Sets the Element that this tooltip hovers over.
+     * @param e Element for the tooltip
+     */
+    public void setElement(final Element e) {
+        element = e;
+        bindJavaScriptEvents(element);
+    }
+
+    /**
+     * Return the Element that this tooltip hovers over.
+     */
+    public Element getElement() {
+        return element;
+    }
+
+    /**
+     * Return the tooltip registry.
+     * @return
+     */
+    public static Map<String, ElementTooltipDetails> getRegistry() {
+        return tooltipRegistry;
+    }
+
+    /**
+     * Return a tooltip.
+     * @return
+     */
+    public static ElementTooltip getTooltip(String id) {
+        if (isTooltipConfigured(id)) {
+            return tooltipRegistry.get(id).getTooltip();
+        }
+        return null;
+    }
+
+    /**
+     * Is a tooltip in the registry for this id?
+     */
+    public static boolean isTooltipConfigured(String id) {
+        logger.finer("checking tooltip registry for " + id); //$NON-NLS-1$
+
+        if (id == null || id.isEmpty()) {
+            return false;
+        }
+        return ElementTooltip.getRegistry().containsKey(id);
+    }
+
+    /**
+     * Return the reaper interval.
+     */
+    public int getReaperInterval() {
+        return reaperInterval;
+    }
+
+    /**
+     * Sets the reaper interval.
+     */
+    public void setReaperInterval(int reaperInterval) {
+        this.reaperInterval = reaperInterval;
+    }
+
+    /**
+     * <p>
+     * Starts a timer that checks for this tooltip to be hanging open.
+     * This can happen when the Element or one of its ancestors is detached 
from the document.
+     * Such detaching happens frequently when Grids are refreshed -- GWT 
replaces an entire
+     * grid row, but doesn't actually delete the row. The row just gets 
removed from its parent
+     * table. To detect this, we search up the ancestor tree for a null 
ancestor.
+     * </p>
+     * <p>
+     * This should only be called for a tooltip when it is shown. It will be 
reaped very quickly,
+     * within 5 seconds. And since only visible tooltips start the timer, the 
timer won't run that
+     * much. In other words, this should not be a performance concern.
+     * </p>
+     */
+    public void startHangingTooltipReaper() {
+        if (reaperTimer != null && reaperTimer.isRunning()) {
+            return;
+        }
+
+        reaperTimer = new Timer() {
+
+            @Override
+            public void run() {
+                logger.finer("reaper timer"); //$NON-NLS-1$
+                if (hasNullAncestor()) {
+                    reap();
+                    cancel(); // cancel this timer since this tooltip is dead
+                }
+            }
+        };
+
+        reaperTimer.scheduleRepeating(getReaperInterval());
+    }
+
+    /**
+     * Check up this Elements ancestor tree, and return true if we find a null 
parent.
+     * @return true if a null parent is found, false if this Element has body 
as an ancestor
+     * (meaning it's still attached).
+     */
+    public boolean hasNullAncestor() {
+        return ElementUtils.hasNullAncestor(element);
+    }
+
+    /**
+     * Walk through the tooltip registry and hide and delete any tooltips who 
are orphaned.
+     */
+    public static void reapAll() {
+        for (Iterator<Entry<String, ElementTooltipDetails>> i = 
tooltipRegistry.entrySet().iterator(); i.hasNext(); ) {
+            Entry<String, ElementTooltipDetails> entry = i.next();
+            ElementTooltip tooltip = entry.getValue().getTooltip();
+            if (tooltip.hasNullAncestor()) {
+                tooltip.hide();
+                i.remove();
+            }
+        }
+    }
+
+    /**
+     * Hide all tooltips.
+     */
+    public static void hideAll() {
+        for (Iterator<Entry<String, ElementTooltipDetails>> i = 
tooltipRegistry.entrySet().iterator(); i.hasNext(); ) {
+            Entry<String, ElementTooltipDetails> entry = i.next();
+            ElementTooltip tooltip = entry.getValue().getTooltip();
+            tooltip.hide();
+        }
+    }
+
+    /**
+     * <p>
+     * Reap this tooltip. That is, hide it and remove it from the registry.
+     * </p>
+     * <p>
+     * Tooltips get reaped primarily when GWT removes their elements from the 
Document
+     * (happens every 5 seconds in grid refreshes, for example). They are 
watching for mouseover
+     * on an element that will never be visible again. So we make sure they 
are hidden and deleted
+     * from the registry.
+     * </p>
+     * <p>
+     * The reap *won't* happen if the mouse cursor is currently over an 
element that is
+     * identical to this tooltip's element. In other words, if I'm hovered 
over an icon in
+     * a grid, and GWT redraws the grid and puts an identical icon where the 
old one was,
+     * leave this open tooltip showing. (It'll end up getting reaped when that 
cell is
+     * moused-out of via reapAll().
+     */
+    public void reap() {
+        for (Iterator<Entry<String, ElementTooltipDetails>> i = 
tooltipRegistry.entrySet().iterator(); i.hasNext(); ) {
+            Entry<String, ElementTooltipDetails> entry = i.next();
+            ElementTooltip tooltip = entry.getValue().getTooltip();
+            if (this.equals(tooltip)) {
+
+                String[] pos = JqueryUtils.getMousePosition().split(","); 
//$NON-NLS-1$
+                int x = Integer.parseInt(pos[0]);
+                int y = Integer.parseInt(pos[1]);
+
+                // can we find a replacement element using mouse coordinates?
+                Element replacement = ElementUtils.getElementFromPoint(x, y);
+                if (replacement == null) {
+                    logger.finer("can't detect potential replacement element. 
reaping"); //$NON-NLS-1$
+                    hide();
+                    i.remove();
+                    return;
+                }
+
+                // we found a potential replacement element. compare the html 
to see if this
+                // element is identical to the old one.
+                String html = entry.getValue().getInnerHTML();
+                if (html.contains(replacement.getInnerHTML())) {
+                    // yep, element was replaced with an identical element. 
DON'T reap.
+                    logger.finer("mouse is hovering over my replacement. not 
reaping"); //$NON-NLS-1$
+                }
+                else {
+                    logger.finer("mouse is hovering over an element, but it's 
not identical to previous " //$NON-NLS-1$
+                            + "element. reaping"); //$NON-NLS-1$
+                    hide();
+                    i.remove();
+                    return;
+                }
+
+                return;
+            }
+        }
+    }
+
+    /**
+     * Called when the tooltip is shown. Starts the hanging tooltip reaper 
timer.
+     *
+     * This method also does two very important checks.
+     *
+     * First, it checks for "orphaned tooltips." Because of the high-refresh 
nature of our application,
+     * tooltipped Elements are often deleted before the tooltip can finish its 
render. So we check for
+     * this condition, and simply cancel the render if it is true. Then we 
fire another mouseover event
+     * at the current mouse coordinates, assuming the mouse is over the 
refreshed Element. No harm done
+     * if the mouse has moved away.
+     *
+     * Second, it checks for "abandoned tooltips." mouseover and mouseout are 
not perfect in any browser.
+     * Occasionally a mouseover will trigger a tooltip render, and then the 
user will quickly move the
+     * mouse away -- but for some reason the mouseout doesn't fire. In this 
case, a tooltip will render
+     * over an Element the mouse is no longer over. So we check for this 
condition, and simply cancel
+     * the render if it is true.
+     *
+     * If any issues with hanging tooltips creep up, start debugging here :)
+     *
+     * @param event Event
+     */
+    protected void onShow(final Event event) {
+
+        // handle the case where the element i'm attached to was just removed 
from the document ("orphaned tooltip")
+        if (hasNullAncestor()) {
+            event.preventDefault();
+            logger.finer("orphaned tooltip. canceling render, re-firing 
mouseover"); //$NON-NLS-1$
+
+            // trigger another mouseover on the new element
+            String[] pos = JqueryUtils.getMousePosition().split(","); 
//$NON-NLS-1$
+            int x = Integer.parseInt(pos[0]);
+            int y = Integer.parseInt(pos[1]);
+            Element replacement = ElementUtils.getElementFromPoint(x, y);
+            if (replacement != null) {
+
+                // TODO-GWT use mouseenter if it ever becomes supported
+                NativeEvent newEvent = Document.get().createMouseOverEvent(0, 
event.getScreenX(),
+                        event.getScreenY(), event.getClientX(), 
event.getClientY(), event.getCtrlKey(),
+                        event.getAltKey(), event.getShiftKey(), 
event.getMetaKey(), event.getButton(), replacement);
+
+                replacement.dispatchEvent(newEvent);
+                return;
+            }
+        }
+        // handle the case where the mouse is not over me anymore ("abandoned 
tooltip")
+        else {
+            String[] pos = JqueryUtils.getMousePosition().split(","); 
//$NON-NLS-1$
+            int x = Integer.parseInt(pos[0]);
+            int y = Integer.parseInt(pos[1]);
+
+            Element currentElement = ElementUtils.getElementFromPoint(x, y);
+            Element target = Element.as(event.getEventTarget());
+
+            if (!ElementUtils.hasAncestor(currentElement, target) && 
!currentElement.equals(target)) {
+                logger.finer("abandoned tooltip. canceling render."); 
//$NON-NLS-1$
+                event.preventDefault();
+            }
+        }
+
+        logger.finer("starting tooltip reaper"); //$NON-NLS-1$
+        this.startHangingTooltipReaper();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setId(final String id) {
+        this.id = id;
+        if (element != null) {
+            element.setId(id);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getId() {
+        return (element == null) ? id : element.getId();
+    }
+
+    @Override
+    public void setIsAnimated(final boolean isAnimated) {
+        this.isAnimated = isAnimated;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAnimated() {
+        return isAnimated;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setIsHtml(final boolean isHTML) {
+        this.isHTML = isHTML;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isHtml() {
+        return isHTML;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setPlacement(final Placement placement) {
+        this.placement = placement;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Placement getPlacement() {
+        return placement;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setTrigger(final Trigger trigger) {
+        this.trigger = trigger;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Trigger getTrigger() {
+        return trigger;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setShowDelayMs(final int showDelayMs) {
+        this.showDelayMs = showDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getShowDelayMs() {
+        return showDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setHideDelayMs(final int hideDelayMs) {
+        this.hideDelayMs = hideDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getHideDelayMs() {
+        return hideDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setContainer(final String container) {
+        this.container = container;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getContainer() {
+        return container;
+    }
+
+    /**
+     * Sets the tooltip's HTML content
+     */
+    public void setContent(final SafeHtml content) {
+        this.isHTML = true;
+        this.content = content;
+    }
+
+    /**
+     * Reconfigures the tooltip, must be called when altering any tooltip 
after it has already been shown
+     */
+    public void reconfigure() {
+        // First destroy the old tooltip
+        destroy();
+
+        // Setup the new tooltip
+        if (container != null && selector != null) {
+            tooltip(element, isAnimated, isHTML, placement.getCssName(), 
selector, content.asString(),
+                    trigger.getCssName(), showDelayMs, hideDelayMs, container);
+        } else if (container != null) {
+            tooltip(element, isAnimated, isHTML, placement.getCssName(), 
content.asString(),
+                    trigger.getCssName(), showDelayMs, hideDelayMs, container);
+        } else if (selector != null) {
+            tooltip(element, isAnimated, isHTML, placement.getCssName(), 
selector, content.asString(),
+                    trigger.getCssName(), showDelayMs, hideDelayMs);
+        } else {
+            tooltip(element, isAnimated, isHTML, placement.getCssName(), 
content.asString(),
+                    trigger.getCssName(), showDelayMs, hideDelayMs);
+        }
+    }
+
+    /**
+     * Toggle the Tooltip to either show/hide
+     */
+    public void toggle() {
+        call(element, TOGGLE);
+    }
+
+    /**
+     * <p>
+     * Force show the Tooltip. If you must, you should probably wrap this call 
in a Timer delay
+     * of getShowDelayMs() ms.
+     * </p>
+     * <p>
+     * This is generally flaky though. Better to fire a mouseover (or 
mouseenter) event at the tooltip's element.
+     * </p>
+     */
+    public void show() {
+        logger.finer("tooltip show on element id " + element.getId()); 
//$NON-NLS-1$
+        call(element, SHOW);
+    }
+
+    /**
+     * Force hide the Tooltip
+     */
+    public void hide() {
+        call(element, HIDE);
+    }
+
+    /**
+     * Force the Tooltip to be destroyed
+     */
+    public void destroy() {
+        call(element, DESTROY);
+    }
+
+    private native void call(final Element e, final String arg) /*-{
+        $wnd.jQuery(e).tooltip(arg);
+    }-*/;
+
+    // @formatter:off
+    private native void bindJavaScriptEvents(final Element e) /*-{
+        var target = this;
+        var $tooltip = $wnd.jQuery(e);
+
+        $tooltip.on('show.bs.tooltip', function (evt) {
+            
[email protected]::onShow(Lcom/google/gwt/user/client/Event;)(evt);
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement, String selector,
+                                String content, String trigger, int showDelay, 
int hideDelay, String container) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            selector: selector,
+            title: content,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            container: container
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement,
+                                String content, String trigger, int showDelay, 
int hideDelay, String container) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            title: content,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            container: container
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement, String selector,
+                                String content, String trigger, int showDelay, 
int hideDelay) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            selector: selector,
+            title: content,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            }
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement,
+                                String content, String trigger, int showDelay, 
int hideDelay) /*-{
+        console.log($wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            title: content,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            }
+        }));
+    }-*/;
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltipDetails.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltipDetails.java
new file mode 100644
index 0000000..6c541c5
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/ElementTooltipDetails.java
@@ -0,0 +1,27 @@
+package org.ovirt.engine.ui.common.widget.tooltip;
+
+/**
+ * Wrapper for storing tooltip + its element's html in a map.
+ */
+public class ElementTooltipDetails {
+
+    private ElementTooltip tooltip;
+    private String innerHTML;
+
+    public ElementTooltip getTooltip() {
+        return tooltip;
+    }
+
+    public void setTooltip(ElementTooltip tooltip) {
+        this.tooltip = tooltip;
+    }
+
+    public String getInnerHTML() {
+        return innerHTML;
+    }
+
+    public void setInnerHTML(String innerHTML) {
+        this.innerHTML = innerHTML;
+    }
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/Tooltip.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/Tooltip.java
new file mode 100644
index 0000000..759c1da
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/Tooltip.java
@@ -0,0 +1,723 @@
+package org.ovirt.engine.ui.common.widget.tooltip;
+
+/*
+ * #%L
+ * GwtBootstrap3
+ * %%
+ * Copyright (C) 2013 GwtBootstrap3
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.gwtbootstrap3.client.shared.event.HiddenEvent;
+import org.gwtbootstrap3.client.shared.event.HiddenHandler;
+import org.gwtbootstrap3.client.shared.event.HideEvent;
+import org.gwtbootstrap3.client.shared.event.HideHandler;
+import org.gwtbootstrap3.client.shared.event.ShowEvent;
+import org.gwtbootstrap3.client.shared.event.ShowHandler;
+import org.gwtbootstrap3.client.shared.event.ShownEvent;
+import org.gwtbootstrap3.client.shared.event.ShownHandler;
+import org.gwtbootstrap3.client.ui.base.HasHover;
+import org.gwtbootstrap3.client.ui.base.HasId;
+import org.gwtbootstrap3.client.ui.constants.Placement;
+import org.gwtbootstrap3.client.ui.constants.Trigger;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.AttachEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HasOneWidget;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.IsWidget;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+
+/**
+ * 
========================================================================================
+ * oVirt customization of gwtbootstrap3 tooltip
+ * 
========================================================================================
+ * TODO-GWT switch back to using native gwtbootstrap3 tooltip when upstream 
patch
+ * xxx is merged and released.
+ * 
========================================================================================
+ * 
========================================================================================
+ *
+ * Basic implementation for the Bootstrap tooltip
+ * <p/>
+ * <a href="http://getbootstrap.com/javascript/#tooltips";>Bootstrap 
Documentation</a>
+ * <p/>
+ * <p/>
+ * <h3>UiBinder example</h3>
+ * <p/>
+ * <pre>
+ * {@code
+ * <t:Tooltip text="...">
+ *    ...
+ * </t:Tooltip>
+ * }
+ * </pre>
+ * <p/>
+ * ** Must call reconfigure() after altering any/all Tooltips!
+ *
+ * @author Joshua Godi
+ * @author Pontus Enmark
+ * @author Greg Sheremeta
+ */
+public class Tooltip implements IsWidget, HasWidgets, HasOneWidget, HasId, 
HasHover {
+    private static final String TOGGLE = "toggle"; //$NON-NLS-1$
+    private static final String SHOW = "show"; //$NON-NLS-1$
+    private static final String HIDE = "hide"; //$NON-NLS-1$
+    private static final String DESTROY = "destroy"; //$NON-NLS-1$
+
+    // Defaults from http://getbootstrap.com/javascript/#tooltips
+    private boolean isAnimated = true;
+    private boolean isHTML = false;
+    private Placement placement = Placement.TOP;
+    private Trigger trigger = Trigger.HOVER;
+    private String title = ""; //$NON-NLS-1$
+    private int hideDelayMs = 0;
+    private int showDelayMs = 0;
+    private String container = null;
+    private final String selector = null;
+
+    private String tooltipClassNames = "tooltip"; //$NON-NLS-1$
+    private String tooltipArrowClassNames = "tooltip-arrow"; //$NON-NLS-1$
+    private String tooltipInnerClassNames = "tooltip-inner"; //$NON-NLS-1$
+
+    private final static String DEFAULT_TEMPLATE = "<div class=\"{0}\"><div 
class=\"{1}\"></div><div class=\"{2}\"></div></div>"; //$NON-NLS-1$
+    private String alternateTemplate = null;
+
+    private Widget widget;
+    private String id;
+
+    /**
+     * Creates the empty Tooltip
+     */
+    public Tooltip() {
+    }
+
+    /**
+     * Creates the tooltip around this widget
+     *
+     * @param w widget for the tooltip
+     */
+    public Tooltip(final Widget w) {
+        setWidget(w);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setWidget(final Widget w) {
+        // Validate
+        if (w == widget) {
+            return;
+        }
+
+        // Detach new child
+        if (w != null) {
+            w.removeFromParent();
+        }
+
+        // Remove old child
+        if (widget != null) {
+            remove(widget);
+        }
+
+        // Logical attach, but don't physical attach; done by jquery.
+        widget = w;
+        if (widget == null) {
+            return;
+        }
+
+        // Bind jquery events
+        bindJavaScriptEvents(widget.getElement());
+
+        // When we attach it, configure the tooltip
+        widget.addAttachHandler(new AttachEvent.Handler() {
+            @Override
+            public void onAttachOrDetach(final AttachEvent event) {
+                reconfigure();
+            }
+        });
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void add(final Widget child) {
+        if (getWidget() != null) {
+            throw new IllegalStateException("Can only contain one child 
widget"); //$NON-NLS-1$
+        }
+        setWidget(child);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setWidget(final IsWidget w) {
+        widget = (w == null) ? null : w.asWidget();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Widget getWidget() {
+        return widget;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setId(final String id) {
+        this.id = id;
+        if (widget != null) {
+            widget.getElement().setId(id);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getId() {
+        return (widget == null) ? id : widget.getElement().getId();
+    }
+
+    @Override
+    public void setIsAnimated(final boolean isAnimated) {
+        this.isAnimated = isAnimated;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAnimated() {
+        return isAnimated;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setIsHtml(final boolean isHTML) {
+        this.isHTML = isHTML;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isHtml() {
+        return isHTML;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setPlacement(final Placement placement) {
+        this.placement = placement;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Placement getPlacement() {
+        return placement;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setTrigger(final Trigger trigger) {
+        this.trigger = trigger;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Trigger getTrigger() {
+        return trigger;
+    }
+
+    @Override
+    public void setShowDelayMs(final int showDelayMs) {
+        this.showDelayMs = showDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getShowDelayMs() {
+        return showDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setHideDelayMs(final int hideDelayMs) {
+        this.hideDelayMs = hideDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getHideDelayMs() {
+        return hideDelayMs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setContainer(final String container) {
+        this.container = container;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getContainer() {
+        return container;
+    }
+
+    /**
+     * Gets the tooltip's display string
+     *
+     * @return String tooltip display string
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * Sets the tooltip's display string
+     *
+     * @param text String display string
+     */
+    public void setText(final String text) {
+        setTitle(text);
+    }
+
+    /**
+     * Sets the tooltip's display string in HTML format
+     *
+     * @param text String display string in HTML format
+     */
+    public void setHtml(final SafeHtml html) {
+        setHTML(true);
+        if (html != null) {
+            setTitle(html.asString());
+        }
+    }
+
+    /**
+     * Sets the tooltip's display string
+     *
+     * @param title String display string
+     */
+    public void setTitle(final String title) {
+        this.title = title;
+    }
+
+    public boolean isHTML() {
+        return isHTML;
+    }
+
+    public void setHTML(boolean isHTML) {
+        this.isHTML = isHTML;
+    }
+
+    public String getSelector() {
+        return selector;
+    }
+
+    public void setAnimated(boolean isAnimated) {
+        this.isAnimated = isAnimated;
+    }
+
+    public String getTooltipClassNames() {
+        return tooltipClassNames;
+    }
+
+    public void setTooltipClassNames(String tooltipClassNames) {
+        this.tooltipClassNames = tooltipClassNames;
+    }
+
+    public void addTooltipClassName(String tooltipClassName) {
+        this.tooltipClassNames += " " + tooltipClassName; //$NON-NLS-1$
+    }
+
+    public String getTooltipArrowClassNames() {
+        return tooltipArrowClassNames;
+    }
+
+    public void setTooltipArrowClassNames(String tooltipArrowClassNames) {
+        this.tooltipArrowClassNames = tooltipArrowClassNames;
+    }
+
+    public void addTooltipArrowClassName(String tooltipArrowClassName) {
+        this.tooltipArrowClassNames += " " + tooltipArrowClassName; 
//$NON-NLS-1$
+    }
+
+    public String getTooltipInnerClassNames() {
+        return tooltipInnerClassNames;
+    }
+
+    public void setTooltipInnerClassNames(String tooltipInnerClassNames) {
+        this.tooltipInnerClassNames = tooltipInnerClassNames;
+    }
+
+    public void addTooltipInnerClassName(String tooltipInnerClassName) {
+        this.tooltipInnerClassNames += " " + tooltipInnerClassName; 
//$NON-NLS-1$
+    }
+
+    public String getTemplate() {
+        return alternateTemplate;
+    }
+
+    public void setTemplate(String alternateTemplate) {
+        this.alternateTemplate = alternateTemplate;
+    }
+
+    /**
+     * Reconfigures the tooltip, must be called when altering any tooltip 
after it has already been shown
+     */
+    public void reconfigure() {
+        // First destroy the old tooltip
+        destroy();
+
+        // prepare template
+        String template = null;
+        if (alternateTemplate == null) {
+            template = DEFAULT_TEMPLATE.replace("{0}", 
getTooltipClassNames()); //$NON-NLS-1$
+            template = template.replace("{1}", getTooltipArrowClassNames()); 
//$NON-NLS-1$
+            template = template.replace("{2}", getTooltipInnerClassNames()); 
//$NON-NLS-1$
+        }
+        else {
+            template = alternateTemplate;
+        }
+
+        // TODO clean this up
+
+        // Setup the new tooltip
+        if (container != null && selector != null) {
+            tooltip(widget.getElement(), isAnimated, isHTML, 
placement.getCssName(), selector, title,
+                    trigger.getCssName(), showDelayMs, hideDelayMs, container, 
template);
+        } else if (container != null) {
+            tooltip(widget.getElement(), isAnimated, isHTML, 
placement.getCssName(), title,
+                    trigger.getCssName(), showDelayMs, hideDelayMs, container, 
template);
+        } else if (selector != null) {
+            tooltip(widget.getElement(), isAnimated, isHTML, 
placement.getCssName(), selector, title,
+                    trigger.getCssName(), showDelayMs, hideDelayMs, template);
+        } else {
+            tooltip(widget.getElement(), isAnimated, isHTML, 
placement.getCssName(), title,
+                    trigger.getCssName(), showDelayMs, hideDelayMs, template);
+        }
+    }
+
+    /**
+     * Toggle the Tooltip to either show/hide
+     */
+    public void toggle() {
+        call(widget.getElement(), TOGGLE);
+    }
+
+    /**
+     * Force show the Tooltip
+     */
+    public void show() {
+        call(widget.getElement(), SHOW);
+    }
+
+    /**
+     * Force hide the Tooltip
+     */
+    public void hide() {
+        call(widget.getElement(), HIDE);
+    }
+
+    /**
+     * Force the Tooltip to be destroyed
+     */
+    public void destroy() {
+        call(widget.getElement(), DESTROY);
+    }
+
+    /**
+     * Can be override by subclasses to handle Tooltip's "show" event however
+     * it's recommended to add an event handler to the tooltip.
+     *
+     * @param evt Event
+     * @see org.gwtbootstrap3.client.shared.event.ShowEvent
+     */
+    protected void onShow(final Event evt) {
+        widget.fireEvent(new ShowEvent(evt));
+    }
+
+    /**
+     * Can be override by subclasses to handle Tooltip's "shown" event however
+     * it's recommended to add an event handler to the tooltip.
+     *
+     * @param evt Event
+     * @see ShownEvent
+     */
+    protected void onShown(final Event evt) {
+        widget.fireEvent(new ShownEvent(evt));
+    }
+
+    /**
+     * Can be override by subclasses to handle Tooltip's "hide" event however
+     * it's recommended to add an event handler to the tooltip.
+     *
+     * @param evt Event
+     * @see org.gwtbootstrap3.client.shared.event.HideEvent
+     */
+    protected void onHide(final Event evt) {
+        widget.fireEvent(new HideEvent(evt));
+    }
+
+    /**
+     * Can be override by subclasses to handle Tooltip's "hidden" event however
+     * it's recommended to add an event handler to the tooltip.
+     *
+     * @param evt Event
+     * @see org.gwtbootstrap3.client.shared.event.HiddenEvent
+     */
+    protected void onHidden(final Event evt) {
+        widget.fireEvent(new HiddenEvent(evt));
+    }
+
+    /**
+     * Adds a show handler to the Tooltip that will be fired when the 
Tooltip's show event is fired
+     *
+     * @param showHandler ShowHandler to handle the show event
+     * @return HandlerRegistration of the handler
+     */
+    public HandlerRegistration addShowHandler(final ShowHandler showHandler) {
+        return widget.addHandler(showHandler, ShowEvent.getType());
+    }
+
+    /**
+     * Adds a shown handler to the Tooltip that will be fired when the 
Tooltip's shown event is fired
+     *
+     * @param shownHandler ShownHandler to handle the shown event
+     * @return HandlerRegistration of the handler
+     */
+    public HandlerRegistration addShownHandler(final ShownHandler 
shownHandler) {
+        return widget.addHandler(shownHandler, ShownEvent.getType());
+    }
+
+    /**
+     * Adds a hide handler to the Tooltip that will be fired when the 
Tooltip's hide event is fired
+     *
+     * @param hideHandler HideHandler to handle the hide event
+     * @return HandlerRegistration of the handler
+     */
+    public HandlerRegistration addHideHandler(final HideHandler hideHandler) {
+        return widget.addHandler(hideHandler, HideEvent.getType());
+    }
+
+    /**
+     * Adds a hidden handler to the Tooltip that will be fired when the 
Tooltip's hidden event is fired
+     *
+     * @param hiddenHandler HiddenHandler to handle the hidden event
+     * @return HandlerRegistration of the handler
+     */
+    public HandlerRegistration addHiddenHandler(final HiddenHandler 
hiddenHandler) {
+        return widget.addHandler(hiddenHandler, HiddenEvent.getType());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        widget = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<Widget> iterator() {
+        // Simple iterator for the widget
+        return new Iterator<Widget>() {
+            boolean hasElement = widget != null;
+            Widget returned = null;
+
+            @Override
+            public boolean hasNext() {
+                return hasElement;
+            }
+
+            @Override
+            public Widget next() {
+                if (!hasElement || (widget == null)) {
+                    throw new NoSuchElementException();
+                }
+                hasElement = false;
+                return (returned = widget);
+            }
+
+            @Override
+            public void remove() {
+                if (returned != null) {
+                    Tooltip.this.remove(returned);
+                }
+            }
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean remove(final Widget w) {
+        // Validate.
+        if (widget != w) {
+            return false;
+        }
+
+        // Logical detach.
+        clear();
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Widget asWidget() {
+        return widget;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return asWidget().toString();
+    }
+
+    // @formatter:off
+    private native void bindJavaScriptEvents(final Element e) /*-{
+        var target = this;
+        var $tooltip = $wnd.jQuery(e);
+
+        $tooltip.on('show.bs.tooltip', function (evt) {
+            
[email protected]::onShow(Lcom/google/gwt/user/client/Event;)(evt);
+        });
+
+        $tooltip.on('shown.bs.tooltip', function (evt) {
+            
[email protected]::onShown(Lcom/google/gwt/user/client/Event;)(evt);
+        });
+
+        $tooltip.on('hide.bs.tooltip', function (evt) {
+            
[email protected]::onHide(Lcom/google/gwt/user/client/Event;)(evt);
+        });
+
+        $tooltip.on('hidden.bs.tooltip', function (evt) {
+            
[email protected]::onHidden(Lcom/google/gwt/user/client/Event;)(evt);
+        });
+    }-*/;
+
+    private native void call(final Element e, final String arg) /*-{
+        $wnd.jQuery(e).tooltip(arg);
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement, String selector,
+                                String title, String trigger, int showDelay, 
int hideDelay, String container, String template) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            selector: selector,
+            title: title,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            container: container,
+            template: template
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement,
+                                String title, String trigger, int showDelay, 
int hideDelay, String container, String template) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            title: title,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            container: container,
+            template: template
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement, String selector,
+                                String title, String trigger, int showDelay, 
int hideDelay, String template) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            selector: selector,
+            title: title,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            template: template
+        });
+    }-*/;
+
+    private native void tooltip(Element e, boolean animation, boolean html, 
String placement,
+                                String title, String trigger, int showDelay, 
int hideDelay, String template) /*-{
+        $wnd.jQuery(e).tooltip({
+            animation: animation,
+            html: html,
+            placement: placement,
+            title: title,
+            trigger: trigger,
+            delay: {
+                show: showDelay,
+                hide: hideDelay
+            },
+            template: template
+        });
+    }-*/;
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipConfig.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipConfig.java
new file mode 100644
index 0000000..f41b614
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipConfig.java
@@ -0,0 +1,37 @@
+package org.ovirt.engine.ui.common.widget.tooltip;
+
+import org.gwtbootstrap3.client.ui.constants.Placement;
+import org.gwtbootstrap3.client.ui.constants.Trigger;
+
+/**
+ * Constant configuration values shared across all tooltips.
+ */
+public class TooltipConfig {
+
+    public enum Width {
+        W220 ("tooltip-w220"), //$NON-NLS-1$
+        W320 ("tooltip-w320"), //$NON-NLS-1$
+        W420 ("tooltip-w420"), //$NON-NLS-1$
+        W520 ("tooltip-w520"), //$NON-NLS-1$
+        W620 ("tooltip-w620"); //$NON-NLS-1$
+
+        private final String widthClass; // in px
+
+        Width(String widthClass) {
+            this.widthClass = widthClass;
+        }
+
+        public String getWidthClass() {
+            return widthClass;
+        }
+    }
+
+    public final static boolean IS_ANIMATED = true;
+    public final static boolean IS_HTML = true;
+    public final static Placement PLACEMENT = Placement.TOP;
+    public final static Trigger TRIGGER = Trigger.HOVER;
+    public final static String CONTAINER = "body"; //$NON-NLS-1$
+    public final static int HIDE_DELAY_MS = 0;
+    public final static int SHOW_DELAY_MS = 500;
+
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipMixin.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipMixin.java
new file mode 100644
index 0000000..b31497d
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/widget/tooltip/TooltipMixin.java
@@ -0,0 +1,140 @@
+package org.ovirt.engine.ui.common.widget.tooltip;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.ovirt.engine.ui.common.utils.JqueryUtils;
+
+import com.google.gwt.dom.client.BrowserEvents;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+
+/**
+ *
+ * Set of static methods used by tooltip-capable Cells to group the tooltip 
logic in one place.
+ *
+ * TODO When we have Java 8 mixins, consider using those in place of this 
class.
+ *
+ * There is currently some GWT or ovirt bug causing duplicate mouseover and 
mouseout events.
+ * It doesn't affect the logic, but be aware of it when working on this code.
+ *
+ */
+public class TooltipMixin {
+
+    private static final Logger logger = 
Logger.getLogger(TooltipMixin.class.getName());
+
+    /**
+     * Add events that tooltips care about (over, down, out) to a cell's Set 
of sunk events.
+     */
+    public static void addTooltipsEvents(Set<String> set) {
+        set.add(BrowserEvents.MOUSEOVER);
+        set.add(BrowserEvents.MOUSEOUT);
+        set.add(BrowserEvents.MOUSEDOWN);
+    }
+
+    /**
+     * Hijack mouseover event and use it to create the tooltip. This is done 
the first time a Cell is moused-over
+     * because that's the only place GWT gives us access to the actual Element.
+     *
+     * Once the tooltip is configured, we need to fire a new mouseenter event 
at the cell so jQuery can pick it
+     * up and show the tooltip.
+     */
+    public static void configureTooltip(final Element parent, SafeHtml 
tooltipContent, final NativeEvent event) {
+        if (tooltipContent == null || 
tooltipContent.asString().trim().isEmpty()) {
+            // there is no tooltip for this render. no-op.
+            logger.finer("null or empty tooltip content"); //$NON-NLS-1$
+        }
+        else if (isTooltipConfigured(parent)) {
+            logger.finer("tooltip already configured"); //$NON-NLS-1$
+
+            // should this bad tooltip be showing and it's not?
+            checkForceShow(event);
+        }
+        else {
+
+            logger.finer("tooltip not configured yet -- adding"); //$NON-NLS-1$
+            addTooltipToElement(tooltipContent, parent);
+
+            logger.finer("firing native event to jquery tooltip"); 
//$NON-NLS-1$
+
+            // kill this event -- we abused it to configure the tooltip
+            event.stopPropagation();
+            event.preventDefault();
+
+            // and fire another event for jquery to handle
+            Node node = parent.getChild(0);
+            if (node instanceof Element) {
+                Element e = (Element) node;
+
+                NativeEvent newEvent = Document.get().createMouseOverEvent(0, 
event.getScreenX(),
+                        event.getScreenY(), event.getClientX(), 
event.getClientY(), event.getCtrlKey(),
+                        event.getAltKey(), event.getShiftKey(), 
event.getMetaKey(), event.getButton(), e);
+                e.dispatchEvent(newEvent);
+            }
+        }
+    }
+
+    public static void reapAllTooltips() {
+        // all tooltips should be reaped
+        ElementTooltip.reapAll();
+    }
+
+    public static void hideAllTooltips() {
+        // all tooltips should be hidden
+        ElementTooltip.hideAll();
+    }
+
+    public static ElementTooltip addTooltipToElement(SafeHtml tooltipContent, 
Element element) {
+        ElementTooltip tooltip = new ElementTooltip(element);
+
+        tooltip.setContent(tooltipContent);
+        tooltip.reconfigure();
+
+        String cellId = element.getId();
+        if (cellId == null || cellId.isEmpty()) {
+            cellId = DOM.createUniqueId();
+            element.setId(cellId);
+        }
+
+        // add tooltip to registry -- key by element-id, save the tooltip and 
the html of the element
+        ElementTooltipDetails details = new ElementTooltipDetails();
+        details.setTooltip(tooltip);
+        details.setInnerHTML(element.getInnerHTML());
+        ElementTooltip.getRegistry().put(cellId, details);
+
+        return tooltip;
+    }
+
+    public static boolean isTooltipConfigured(Element parent) {
+        return ElementTooltip.isTooltipConfigured(parent.getId());
+    }
+
+    /**
+     * mouseover and mouseout aren't perfect
+     * so give tooltip some time (50ms) to show, and then check to see if we 
should force show it
+     * TODO-GWT try using mouseenter and mouseleave, if GWT adds support for 
these.
+     */
+    public static void checkForceShow(final NativeEvent event) {
+        Timer timer = new Timer() {
+            @Override
+            public void run() {
+                String[] pos = JqueryUtils.getMousePosition().split(","); 
//$NON-NLS-1$
+                int x = Integer.parseInt(pos[0]);
+                int y = Integer.parseInt(pos[1]);
+
+                logger.finer("checking for force show. any tooltip visible? " 
+ JqueryUtils.anyTooltipVisible()); //$NON-NLS-1$
+                if (!JqueryUtils.anyTooltipVisible()) {
+                    logger.finer("force showing closed tooltip"); //$NON-NLS-1$
+                    JqueryUtils.fireMouseEnter(x, y);
+                }
+            }
+        };
+        timer.schedule(50);
+    }
+
+}
diff --git 
a/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/widget/extended/vm/TooltipCell.java
 
b/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/widget/extended/vm/TooltipCell.java
index 326d0ce..e8c1ef5 100644
--- 
a/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/widget/extended/vm/TooltipCell.java
+++ 
b/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/widget/extended/vm/TooltipCell.java
@@ -13,11 +13,13 @@
 import com.google.gwt.user.client.DOM;
 
 /**
+ * TODO remove
  * Decorates a cell with a tooltip which is given from a tooltip provider
  *
  * @param <C>
  *            the type that this Cell represents
  */
+@Deprecated
 public class TooltipCell<T> extends CompositeCell<T> {
 
     private final TooltipProvider<T> provider;
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/tab/MainTabClusterView.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/tab/MainTabClusterView.java
index c1b0623..2886b07 100644
--- 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/tab/MainTabClusterView.java
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/section/main/view/tab/MainTabClusterView.java
@@ -24,7 +24,7 @@
 import org.ovirt.engine.ui.webadmin.widget.action.WebAdminButtonDefinition;
 import 
org.ovirt.engine.ui.webadmin.widget.action.WebAdminImageButtonDefinition;
 import 
org.ovirt.engine.ui.webadmin.widget.action.WebAdminMenuBarButtonDefinition;
-import org.ovirt.engine.ui.webadmin.widget.table.column.CommentColumn;
+import org.ovirt.engine.ui.webadmin.widget.table.column.CommentColumn2;
 
 import com.google.gwt.core.client.GWT;
 import com.google.inject.Inject;
@@ -57,8 +57,11 @@
         nameColumn.makeSortable(ClusterConditionFieldAutoCompleter.NAME);
         getTable().addColumn(nameColumn, constants.nameCluster(), "150px"); 
//$NON-NLS-1$
 
-        CommentColumn<VDSGroup> commentColumn = new CommentColumn<VDSGroup>();
-        getTable().addColumnWithHtmlHeader(commentColumn, 
commentColumn.getHeaderHtml(), "30px"); //$NON-NLS-1$
+        CommentColumn2<VDSGroup> commentColumn = new 
CommentColumn2<VDSGroup>();
+        // TODO: add support for tooltips on headers
+        // TODO: don't hardcode "Comment" -- use image
+        // getTable().addColumnWithHtmlHeader(commentColumn, 
commentColumn.getHeaderHtml(), "30px"); //$NON-NLS-1$
+        getTable().addColumn(commentColumn, "Comment", "50px"); //$NON-NLS-1$ 
//$NON-NLS-2$
 
         if (ApplicationModeHelper.getUiMode() != ApplicationMode.GlusterOnly) {
             AbstractTextColumnWithTooltip<VDSGroup> dataCenterColumn = new 
AbstractTextColumnWithTooltip<VDSGroup>() {
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/table/column/CommentColumn2.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/table/column/CommentColumn2.java
new file mode 100644
index 0000000..50eeec6
--- /dev/null
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/widget/table/column/CommentColumn2.java
@@ -0,0 +1,55 @@
+package org.ovirt.engine.ui.webadmin.widget.table.column;
+
+import org.ovirt.engine.core.common.businessentities.Commented;
+import org.ovirt.engine.ui.common.CommonApplicationResources;
+import org.ovirt.engine.ui.common.widget.table.cell.TooltipCell;
+import org.ovirt.engine.ui.common.widget.table.column.AbstractColumn;
+import org.ovirt.engine.ui.common.widget.table.column.ImageResourceCell;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * Column that render a comment image (yellow paper icon) that, when hovered, 
shows the
+ * actual comment in a tooltip.
+ *
+ * @param <T> row type
+ */
+public class CommentColumn2<T extends Commented> extends AbstractColumn<T, 
ImageResource> {
+
+    private static final CommonApplicationResources RESOURCES = 
GWT.create(CommonApplicationResources.class);
+
+    public CommentColumn2() {
+        super(new ImageResourceCell());
+    }
+
+    public CommentColumn2(TooltipCell<ImageResource> cell) {
+        super(cell);
+    }
+
+    /**
+     * Using some row value of type T, build an ImageResource to render in 
this column.
+     *
+     * @see 
com.google.gwt.user.cellview.client.Column#getValue(java.lang.Object)
+     */
+    @Override
+    public ImageResource getValue(T value) {
+        if (value.getComment() != null && !value.getComment().isEmpty()) {
+            return RESOURCES.commentImage();
+        }
+        return null;
+    }
+
+    /**
+     * Using some row value of type T, build a SafeHtml tooltip to render when 
this column is moused over.
+     *
+     * @see 
org.ovirt.engine.ui.common.widget.table.column.AbstractColumn#getTooltip(java.lang.Object)
+     */
+    @Override
+    public SafeHtml getTooltip(T value) {
+        return SafeHtmlUtils.fromString(value.getComment());
+    }
+
+}
diff --git a/packaging/branding/ovirt.brand/mousetrack.js 
b/packaging/branding/ovirt.brand/mousetrack.js
new file mode 100644
index 0000000..7a67cd3
--- /dev/null
+++ b/packaging/branding/ovirt.brand/mousetrack.js
@@ -0,0 +1,7 @@
+jQuery(function() {
+    jQuery(document).mousemove(function(e) {
+        window.mouseX = e.pageX;
+        window.mouseY = e.pageY;
+    });
+});
+
diff --git a/packaging/branding/ovirt.brand/ovirt.css 
b/packaging/branding/ovirt.brand/ovirt.css
index 49dc1fc..3383d5c 100644
--- a/packaging/branding/ovirt.brand/ovirt.css
+++ b/packaging/branding/ovirt.brand/ovirt.css
@@ -22,3 +22,28 @@
 .labelDisabled {
     color: gray;
 }
+
+/***************************************
+Tooltips
+TODO: use SASS, break into tooltips.sass
+****************************************/
+
+.tooltip-w220 .tooltip-inner {
+    max-width: 220px !important;
+}
+
+.tooltip-w320 .tooltip-inner {
+    max-width: 320px !important;
+}
+
+.tooltip-w420 .tooltip-inner {
+    max-width: 420px !important;
+}
+
+.tooltip-w520 .tooltip-inner {
+    max-width: 520px !important;
+}
+
+.tooltip-w620 .tooltip-inner {
+    max-width: 620px !important;
+}


-- 
To view, visit https://gerrit.ovirt.org/38555
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ief7f8524a3dd69c983ace95206379df462bc7daf
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Greg Sheremeta <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to