package org.ovirt.engine.ui.common.widget.table.header; import java.util.HashSet; import java.util.Set; import org.ovirt.engine.ui.common.widget.table.ColumnResizeHandler; import org.ovirt.engine.ui.common.widget.table.HasResizableColumns; import org.ovirt.engine.ui.common.widget.table.cell.SafeHtmlCell; import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.core.shared.GWT; 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.dom.client.Style.Cursor; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.cellview.client.Column; /** * A Header that allows the user to resize the associated column by dragging its right-hand * border using mouse. Renders SafeHtml. Supports tooltips. * * @param <T> * Table row data type. */ public class ResizableHeader<T> extends SafeHtmlHeader { /** * The template interface that defines the wrapper around the content. This is so we can * add/remove style sheet classes to/from the content. */ public interface HeaderTemplate extends SafeHtmlTemplates { /** * Apply the template to the passed in value and style sheet classes. If more than one class is used * you must have them space separated. * @param cssClassNames The names of the css classes, space separated. * @param value The value of the content. * @return The content wrapped in the template. */ @Template("<div class=\"{0}\">{1}</div>") SafeHtml templatedContent(String cssClassNames, SafeHtml value); } /** * Style sheet interface. */ public interface ResizableHeaderCss extends CssResource { /** * Get the style sheet class name for the cell table header content. * @return The style sheet class name as a {@code String} */ String cellTableHeaderContent(); } /** * Resize-able Header resources interface. */ public interface ResizableHeaderResources extends ClientBundle { /** * Get the Resource containing the resize-able header style sheet classes. * @return The resource as a {@code ResizableHeaderCss} object. */ @Source("org/ovirt/engine/ui/common/css/ResizableHeader.css") ResizableHeaderCss resizableHeaderCss(); } /** * Singleton instance of the {@code HeaderTemplate}. */ private static final HeaderTemplate TEMPLATE = GWT.create(HeaderTemplate.class); /** * Singleton instance of the {@code ResizableHeaderResource}. */ private static final ResizableHeaderResources RESOURCES = GWT.create(ResizableHeaderResources.class); /** * Width of the column header resize bar area, in pixels. */ public static final int RESIZE_BAR_WIDTH = 7; /** * The resize-able column. */ private final Column<T, ?> column; /** * The table that contains the header and columns. */ private final HasResizableColumns<T> table; /** * The styles for the header. */ private final ResizableHeaderCss style; private final boolean applyStyle; private boolean resizeEnabled = true; /** * Constructor. * @param safeHtmlHeader The header. * @param column The column associated with the header. * @param table The table containing the header/column. * @param applyStyle Whether to apply default styling. */ public ResizableHeader(SafeHtmlHeader safeHtmlHeader, Column<T, ?> column, HasResizableColumns<T> table, boolean applyStyle) { super(createSafeHtmlCell()); // ignore the header's cell -- we need to specify our own set of events style = RESOURCES.resizableHeaderCss(); style.ensureInjected(); setValue(safeHtmlHeader.getValue()); setTooltip(safeHtmlHeader.getTooltip()); this.column = column; this.table = table; this.applyStyle = applyStyle; } public static SafeHtmlCell createSafeHtmlCell() { return new SafeHtmlCell() { @Override public Set<String> getConsumedEvents() { Set<String> set = new HashSet<>(super.getConsumedEvents()); set.add(BrowserEvents.CLICK); // for sorting set.add(BrowserEvents.CONTEXTMENU); // for column context menu set.add(BrowserEvents.MOUSEMOVE); // for changing mouse cursor set.add(BrowserEvents.CHANGE); // for checkbox toggle return set; } }; } @Override public SafeHtml getValue() { return applyStyle ? TEMPLATE.templatedContent(style.cellTableHeaderContent(), super.getValue()) : super.getValue(); } @Override public void onBrowserEvent(Context context, Element target, NativeEvent event) { super.onBrowserEvent(context, target, event); if (!resizeEnabled) { return; } int clientX = event.getClientX(); int absoluteLeft = target.getAbsoluteLeft(); int offsetWidth = target.getOffsetWidth(); boolean mouseOverResizeBarArea = clientX > absoluteLeft + offsetWidth - RESIZE_BAR_WIDTH; // Resolve th element (header cell for given column) Element headerElement = findThElement(target); assert headerElement != null; // Update mouse cursor for the header element if (mouseOverResizeBarArea) { headerElement.getStyle().setCursor(Cursor.COL_RESIZE); } else if (column.isSortable()) { headerElement.getStyle().setCursor(Cursor.POINTER); } else { headerElement.getStyle().setCursor(Cursor.DEFAULT); } // On mouse down event, which initiates the column resize operation, // register a column resize handler that listens to mouse move events if (BrowserEvents.MOUSEDOWN.equals(event.getType())) { if (mouseOverResizeBarArea) { new ColumnResizeHandler<>(headerElement, column, table); } event.preventDefault(); event.stopPropagation(); } } /** * Find the 'TH' DOM element for the passed in {@code Element}. * @param elm The element to start the search at. * @return The 'TH' DOM element if found or null. */ Element findThElement(Element elm) { if (elm == null) { return null; } else if (TableCellElement.TAG_TH.equalsIgnoreCase(elm.getTagName())) { return elm; } return findThElement(elm.getParentElement()); } public void setResizeEnabled(boolean resizeEnabled) { this.resizeEnabled = resizeEnabled; } }