import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.AbstractConnector;
import com.vaadin.client.ui.VContextMenu;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VNotification.HideEvent;
import com.vaadin.client.ui.dd.VDragAndDropManager;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.client.ui.notification.VNotification.HideEvent;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.shared.ApplicationConstants;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.csslayout.VCssLayout;
-import com.vaadin.client.ui.gridlayout.VGridLayout;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout;
-import com.vaadin.client.ui.tabsheet.VTabsheetPanel;
-import com.vaadin.client.ui.ui.VUI;
-import com.vaadin.client.ui.window.VWindow;
+import com.vaadin.client.ui.VCssLayout;
+import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VOrderedLayout;
+import com.vaadin.client.ui.VTabsheetPanel;
+import com.vaadin.client.ui.VUI;
+import com.vaadin.client.ui.VWindow;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.shared.ComponentState;
import com.vaadin.shared.Connector;
import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.client.ui.layout.LayoutDependencyTree;
-import com.vaadin.client.ui.notification.VNotification;
public class LayoutManager {
private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
import com.google.gwt.storage.client.Storage;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.client.ui.notification.VNotification.EventListener;
-import com.vaadin.client.ui.notification.VNotification.HideEvent;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VNotification.EventListener;
+import com.vaadin.client.ui.VNotification.HideEvent;
/**
* Class that enables SuperDevMode using a ?superdevmode parameter in the url.
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.VLazyExecutor;
+import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.notification.VNotification;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.shared.Version;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.UnknownComponentConnector;
-import com.vaadin.client.ui.window.VWindow;
+import com.vaadin.client.ui.VWindow;
/**
* TODO Rename to something more Vaadin7-ish?
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
-import com.vaadin.client.ui.richtextarea.VRichTextArea;
import com.vaadin.shared.Connector;
import com.vaadin.shared.ui.ShortCutConstants;
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.VCaption;
+
+public class VAbsoluteLayout extends ComplexPanel {
+
+ /** Tag name for widget creation */
+ public static final String TAGNAME = "absolutelayout";
+
+ /** Class name, prefix in styling */
+ public static final String CLASSNAME = "v-absolutelayout";
+
+ private DivElement marginElement;
+
+ protected final Element canvas = DOM.createDiv();
+
+ /**
+ * Default constructor
+ */
+ public VAbsoluteLayout() {
+ setElement(Document.get().createDivElement());
+ marginElement = Document.get().createDivElement();
+ canvas.getStyle().setProperty("position", "relative");
+ canvas.getStyle().setProperty("overflow", "hidden");
+ marginElement.appendChild(canvas);
+ getElement().appendChild(marginElement);
+ setStyleName(CLASSNAME);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
+ * .Widget)
+ */
+ @Override
+ public void add(Widget child) {
+ AbsoluteWrapper wrapper = new AbsoluteWrapper(child);
+ wrapper.updateStyleNames();
+ super.add(wrapper, canvas);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
+ * .client.ui.Widget)
+ */
+ @Override
+ public boolean remove(Widget w) {
+ AbsoluteWrapper wrapper = getChildWrapper(w);
+ if (wrapper != null) {
+ wrapper.destroy();
+ return super.remove(wrapper);
+ }
+ return super.remove(w);
+ }
+
+ /**
+ * Does this layout contain a widget
+ *
+ * @param widget
+ * The widget to check
+ * @return Returns true if the widget is in this layout, false if not
+ */
+ public boolean contains(Widget widget) {
+ return getChildWrapper(widget) != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.ComplexPanel#getWidget(int)
+ */
+ @Override
+ public Widget getWidget(int index) {
+ for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
+ Widget w = getWidget(i);
+ if (w instanceof AbsoluteWrapper) {
+ if (j == index) {
+ return w;
+ } else {
+ j++;
+ }
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.ComplexPanel#getWidgetCount()
+ */
+ @Override
+ public int getWidgetCount() {
+ int counter = 0;
+ for (int i = 0; i < super.getWidgetCount(); i++) {
+ if (getWidget(i) instanceof AbsoluteWrapper) {
+ counter++;
+ }
+ }
+ return counter;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.ComplexPanel#getWidgetIndex(com.google.
+ * gwt.user.client.ui.Widget)
+ */
+ @Override
+ public int getWidgetIndex(Widget child) {
+ for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
+ Widget w = getWidget(i);
+ if (w instanceof AbsoluteWrapper) {
+ if (child == w) {
+ return j;
+ } else {
+ j++;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Sets a caption for a contained widget
+ *
+ * @param child
+ * The child widget to set the caption for
+ * @param caption
+ * The caption of the widget
+ */
+ public void setWidgetCaption(Widget child, VCaption caption) {
+ AbsoluteWrapper wrapper = getChildWrapper(child);
+ if (wrapper != null) {
+ if (caption != null) {
+ if (!getChildren().contains(caption)) {
+ super.add(caption, canvas);
+ }
+ wrapper.setCaption(caption);
+ caption.updateCaption();
+ wrapper.updateCaptionPosition();
+ } else if (wrapper.getCaption() != null) {
+ wrapper.setCaption(null);
+ }
+ }
+ }
+
+ /**
+ * Set the position of the widget in the layout. The position is a CSS
+ * property string using properties such as top,left,right,top
+ *
+ * @param child
+ * The child widget to set the position for
+ * @param position
+ * The position string
+ */
+ public void setWidgetPosition(Widget child, String position) {
+ AbsoluteWrapper wrapper = getChildWrapper(child);
+ if (wrapper != null) {
+ wrapper.setPosition(position);
+ }
+ }
+
+ /**
+ * Get the caption for a widget
+ *
+ * @param child
+ * The child widget to get the caption of
+ */
+ public VCaption getWidgetCaption(Widget child) {
+ AbsoluteWrapper wrapper = getChildWrapper(child);
+ if (wrapper != null) {
+ return wrapper.getCaption();
+ }
+ return null;
+ }
+
+ /**
+ * Get the pixel width of an slot in the layout
+ *
+ * @param child
+ * The widget in the layout.
+ * @return Returns the size in pixels, or 0 if child is not in the layout
+ */
+ public int getWidgetSlotWidth(Widget child) {
+ AbsoluteWrapper wrapper = getChildWrapper(child);
+ if (wrapper != null) {
+ return wrapper.getOffsetWidth();
+ }
+ return 0;
+ }
+
+ /**
+ * Get the pixel height of an slot in the layout
+ *
+ * @param child
+ * The widget in the layout
+ * @return Returns the size in pixels, or 0 if the child is not in the
+ * layout
+ */
+ public int getWidgetSlotHeight(Widget child) {
+ AbsoluteWrapper wrapper = getChildWrapper(child);
+ if (wrapper != null) {
+ return wrapper.getOffsetHeight();
+ }
+ return 0;
+ }
+
+ /**
+ * Get the wrapper for a widget
+ *
+ * @param child
+ * The child to get the wrapper for
+ * @return
+ */
+ protected AbsoluteWrapper getChildWrapper(Widget child) {
+ for (Widget w : getChildren()) {
+ if (w instanceof AbsoluteWrapper) {
+ AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
+ if (wrapper.getWidget() == child) {
+ return wrapper;
+ }
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang.
+ * String)
+ */
+ @Override
+ public void setStylePrimaryName(String style) {
+ updateStylenames(style);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
+ */
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStylenames(style);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ }
+
+ /**
+ * Updates all style names contained in the layout
+ *
+ * @param primaryStyleName
+ * The style name to use as primary
+ */
+ protected void updateStylenames(String primaryStyleName) {
+ super.setStylePrimaryName(primaryStyleName);
+ canvas.setClassName(getStylePrimaryName() + "-canvas");
+ canvas.setClassName(getStylePrimaryName() + "-margin");
+ for (Widget w : getChildren()) {
+ if (w instanceof AbsoluteWrapper) {
+ AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
+ wrapper.updateStyleNames();
+ }
+ }
+ }
+
+ /**
+ * Performs a vertical layout of the layout. Should be called when a widget
+ * is added or removed
+ */
+ public void layoutVertically() {
+ for (Widget widget : getChildren()) {
+ if (widget instanceof AbsoluteWrapper) {
+ AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
+
+ /*
+ * Cleanup old wrappers which have been left empty by other
+ * inner layouts moving the widget from the wrapper into their
+ * own hierarchy. This usually happens when a call to
+ * setWidget(widget) is done in an inner layout which
+ * automatically detaches the widget from the parent, in this
+ * case the wrapper, and re-attaches it somewhere else. This has
+ * to be done in the layout phase since the order of the
+ * hierarchy events are not defined.
+ */
+ if (wrapper.getWidget() == null) {
+ wrapper.destroy();
+ super.remove(wrapper);
+ continue;
+ }
+
+ Style wrapperStyle = wrapper.getElement().getStyle();
+ Style widgetStyle = wrapper.getWidget().getElement().getStyle();
+ if (widgetStyle.getHeight() != null
+ && widgetStyle.getHeight().endsWith("%")) {
+ int h;
+ if (wrapper.top != null && wrapper.bottom != null) {
+ h = wrapper.getOffsetHeight();
+ } else if (wrapper.bottom != null) {
+ // top not defined, available space 0... bottom of
+ // wrapper
+ h = wrapper.getElement().getOffsetTop()
+ + wrapper.getOffsetHeight();
+ } else {
+ // top defined or both undefined, available space ==
+ // canvas - top
+ h = canvas.getOffsetHeight()
+ - wrapper.getElement().getOffsetTop();
+ }
+ wrapperStyle.setHeight(h, Unit.PX);
+ } else {
+ wrapperStyle.clearHeight();
+ }
+
+ wrapper.updateCaptionPosition();
+ }
+ }
+ }
+
+ /**
+ * Performs an horizontal layout. Should be called when a widget is add or
+ * removed
+ */
+ public void layoutHorizontally() {
+ for (Widget widget : getChildren()) {
+ if (widget instanceof AbsoluteWrapper) {
+ AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
+
+ /*
+ * Cleanup old wrappers which have been left empty by other
+ * inner layouts moving the widget from the wrapper into their
+ * own hierarchy. This usually happens when a call to
+ * setWidget(widget) is done in an inner layout which
+ * automatically detaches the widget from the parent, in this
+ * case the wrapper, and re-attaches it somewhere else. This has
+ * to be done in the layout phase since the order of the
+ * hierarchy events are not defined.
+ */
+ if (wrapper.getWidget() == null) {
+ wrapper.destroy();
+ super.remove(wrapper);
+ continue;
+ }
+
+ Style wrapperStyle = wrapper.getElement().getStyle();
+ Style widgetStyle = wrapper.getWidget().getElement().getStyle();
+
+ if (widgetStyle.getWidth() != null
+ && widgetStyle.getWidth().endsWith("%")) {
+ int w;
+ if (wrapper.left != null && wrapper.right != null) {
+ w = wrapper.getOffsetWidth();
+ } else if (wrapper.right != null) {
+ // left == null
+ // available width == right edge == offsetleft + width
+ w = wrapper.getOffsetWidth()
+ + wrapper.getElement().getOffsetLeft();
+ } else {
+ // left != null && right == null || left == null &&
+ // right == null
+ // available width == canvas width - offset left
+ w = canvas.getOffsetWidth()
+ - wrapper.getElement().getOffsetLeft();
+ }
+ wrapperStyle.setWidth(w, Unit.PX);
+ } else {
+ wrapperStyle.clearWidth();
+ }
+
+ wrapper.updateCaptionPosition();
+ }
+ }
+ }
+
+ /**
+ * Sets style names for the wrapper wrapping the widget in the layout. The
+ * style names will be prefixed with v-absolutelayout-wrapper.
+ *
+ * @param widget
+ * The widget which wrapper we want to add the stylenames to
+ * @param stylenames
+ * The style names that should be added to the wrapper
+ */
+ public void setWidgetWrapperStyleNames(Widget widget, String... stylenames) {
+ AbsoluteWrapper wrapper = getChildWrapper(widget);
+ if (wrapper == null) {
+ throw new IllegalArgumentException(
+ "No wrapper for widget found, has the widget been added to the layout?");
+ }
+ wrapper.setWrapperStyleNames(stylenames);
+ }
+
+ /**
+ * Internal wrapper for wrapping widgets in the Absolute layout
+ */
+ protected class AbsoluteWrapper extends SimplePanel {
+ private String css;
+ private String left;
+ private String top;
+ private String right;
+ private String bottom;
+ private String zIndex;
+
+ private VCaption caption;
+ private String[] extraStyleNames;
+
+ /**
+ * Constructor
+ *
+ * @param child
+ * The child to wrap
+ */
+ public AbsoluteWrapper(Widget child) {
+ setWidget(child);
+ }
+
+ /**
+ * Get the caption of the wrapper
+ */
+ public VCaption getCaption() {
+ return caption;
+ }
+
+ /**
+ * Set the caption for the wrapper
+ *
+ * @param caption
+ * The caption for the wrapper
+ */
+ public void setCaption(VCaption caption) {
+ if (caption != null) {
+ this.caption = caption;
+ } else if (this.caption != null) {
+ this.caption.removeFromParent();
+ this.caption = caption;
+ }
+ }
+
+ /**
+ * Removes the wrapper caption and itself from the layout
+ */
+ public void destroy() {
+ if (caption != null) {
+ caption.removeFromParent();
+ }
+ removeFromParent();
+ }
+
+ /**
+ * Set the position for the wrapper in the layout
+ *
+ * @param position
+ * The position string
+ */
+ public void setPosition(String position) {
+ if (css == null || !css.equals(position)) {
+ css = position;
+ top = right = bottom = left = zIndex = null;
+ if (!css.equals("")) {
+ String[] properties = css.split(";");
+ for (int i = 0; i < properties.length; i++) {
+ String[] keyValue = properties[i].split(":");
+ if (keyValue[0].equals("left")) {
+ left = keyValue[1];
+ } else if (keyValue[0].equals("top")) {
+ top = keyValue[1];
+ } else if (keyValue[0].equals("right")) {
+ right = keyValue[1];
+ } else if (keyValue[0].equals("bottom")) {
+ bottom = keyValue[1];
+ } else if (keyValue[0].equals("z-index")) {
+ zIndex = keyValue[1];
+ }
+ }
+ }
+ // ensure ne values
+ Style style = getElement().getStyle();
+ /*
+ * IE8 dies when nulling zIndex, even in IE7 mode. All other css
+ * properties (and even in older IE's) accept null values just
+ * fine. Assign empty string instead of null.
+ */
+ if (zIndex != null) {
+ style.setProperty("zIndex", zIndex);
+ } else {
+ style.setProperty("zIndex", "");
+ }
+ style.setProperty("top", top);
+ style.setProperty("left", left);
+ style.setProperty("right", right);
+ style.setProperty("bottom", bottom);
+
+ }
+ updateCaptionPosition();
+ }
+
+ /**
+ * Updates the caption position by using element offset left and top
+ */
+ private void updateCaptionPosition() {
+ if (caption != null) {
+ Style style = caption.getElement().getStyle();
+ style.setProperty("position", "absolute");
+ style.setPropertyPx("left", getElement().getOffsetLeft());
+ style.setPropertyPx("top", getElement().getOffsetTop()
+ - caption.getHeight());
+ }
+ }
+
+ /**
+ * Sets the style names of the wrapper. Will be prefixed with the
+ * v-absolutelayout-wrapper prefix
+ *
+ * @param stylenames
+ * The wrapper style names
+ */
+ public void setWrapperStyleNames(String... stylenames) {
+ extraStyleNames = stylenames;
+ updateStyleNames();
+ }
+
+ /**
+ * Updates the style names using the primary style name as prefix
+ */
+ protected void updateStyleNames() {
+ setStyleName(VAbsoluteLayout.this.getStylePrimaryName()
+ + "-wrapper");
+ if (extraStyleNames != null) {
+ for (String stylename : extraStyleNames) {
+ addStyleDependentName(stylename);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.TouchCancelEvent;
+import com.google.gwt.event.dom.client.TouchCancelHandler;
+import com.google.gwt.event.dom.client.TouchEndEvent;
+import com.google.gwt.event.dom.client.TouchEndHandler;
+import com.google.gwt.event.dom.client.TouchMoveEvent;
+import com.google.gwt.event.dom.client.TouchMoveHandler;
+import com.google.gwt.event.dom.client.TouchStartEvent;
+import com.google.gwt.event.dom.client.TouchStartHandler;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
+import com.vaadin.shared.ui.Orientation;
+
+public class VAbstractSplitPanel extends ComplexPanel {
+
+ private boolean enabled = false;
+
+ public static final String CLASSNAME = "v-splitpanel";
+
+ private static final int MIN_SIZE = 30;
+
+ private Orientation orientation = Orientation.HORIZONTAL;
+
+ Widget firstChild;
+
+ Widget secondChild;
+
+ private final Element wrapper = DOM.createDiv();
+
+ private final Element firstContainer = DOM.createDiv();
+
+ private final Element secondContainer = DOM.createDiv();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element splitter = DOM.createDiv();
+
+ private boolean resizing;
+
+ private boolean resized = false;
+
+ private int origX;
+
+ private int origY;
+
+ private int origMouseX;
+
+ private int origMouseY;
+
+ private boolean locked = false;
+
+ private boolean positionReversed = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public List<String> componentStyleNames = Collections.emptyList();
+
+ private Element draggingCurtain;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /**
+ * The current position of the split handle in either percentages or pixels
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String position;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String maximumPosition;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String minimumPosition;
+
+ private TouchScrollHandler touchScrollHandler;
+
+ protected Element scrolledContainer;
+
+ protected int origScrollTop;
+
+ public VAbstractSplitPanel() {
+ this(Orientation.HORIZONTAL);
+ }
+
+ public VAbstractSplitPanel(Orientation orientation) {
+ setElement(DOM.createDiv());
+ setStyleName(StyleConstants.UI_LAYOUT);
+ switch (orientation) {
+ case HORIZONTAL:
+ addStyleName(CLASSNAME + "-horizontal");
+ break;
+ case VERTICAL:
+ default:
+ addStyleName(CLASSNAME + "-vertical");
+ break;
+ }
+ // size below will be overridden in update from uidl, initial size
+ // needed to keep IE alive
+ setWidth(MIN_SIZE + "px");
+ setHeight(MIN_SIZE + "px");
+ constructDom();
+ setOrientation(orientation);
+ sinkEvents(Event.MOUSEEVENTS);
+
+ makeScrollable();
+
+ addDomHandler(new TouchCancelHandler() {
+ @Override
+ public void onTouchCancel(TouchCancelEvent event) {
+ // TODO When does this actually happen??
+ VConsole.log("TOUCH CANCEL");
+ }
+ }, TouchCancelEvent.getType());
+ addDomHandler(new TouchStartHandler() {
+ @Override
+ public void onTouchStart(TouchStartEvent event) {
+ Node target = event.getTouches().get(0).getTarget().cast();
+ if (splitter.isOrHasChild(target)) {
+ onMouseDown(Event.as(event.getNativeEvent()));
+ }
+ }
+ }, TouchStartEvent.getType());
+ addDomHandler(new TouchMoveHandler() {
+ @Override
+ public void onTouchMove(TouchMoveEvent event) {
+ if (resizing) {
+ onMouseMove(Event.as(event.getNativeEvent()));
+ }
+ }
+ }, TouchMoveEvent.getType());
+ addDomHandler(new TouchEndHandler() {
+ @Override
+ public void onTouchEnd(TouchEndEvent event) {
+ if (resizing) {
+ onMouseUp(Event.as(event.getNativeEvent()));
+ }
+ }
+ }, TouchEndEvent.getType());
+
+ }
+
+ protected void constructDom() {
+ DOM.appendChild(splitter, DOM.createDiv()); // for styling
+ DOM.appendChild(getElement(), wrapper);
+ DOM.setStyleAttribute(wrapper, "position", "relative");
+ DOM.setStyleAttribute(wrapper, "width", "100%");
+ DOM.setStyleAttribute(wrapper, "height", "100%");
+
+ DOM.appendChild(wrapper, secondContainer);
+ DOM.appendChild(wrapper, firstContainer);
+ DOM.appendChild(wrapper, splitter);
+
+ DOM.setStyleAttribute(splitter, "position", "absolute");
+ DOM.setStyleAttribute(secondContainer, "position", "absolute");
+
+ setStylenames();
+ }
+
+ private void setOrientation(Orientation orientation) {
+ this.orientation = orientation;
+ if (orientation == Orientation.HORIZONTAL) {
+ DOM.setStyleAttribute(splitter, "height", "100%");
+ DOM.setStyleAttribute(splitter, "top", "0");
+ DOM.setStyleAttribute(firstContainer, "height", "100%");
+ DOM.setStyleAttribute(secondContainer, "height", "100%");
+ } else {
+ DOM.setStyleAttribute(splitter, "width", "100%");
+ DOM.setStyleAttribute(splitter, "left", "0");
+ DOM.setStyleAttribute(firstContainer, "width", "100%");
+ DOM.setStyleAttribute(secondContainer, "width", "100%");
+ }
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ boolean removed = super.remove(w);
+ if (removed) {
+ if (firstChild == w) {
+ firstChild = null;
+ } else {
+ secondChild = null;
+ }
+ }
+ return removed;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setLocked(boolean newValue) {
+ if (locked != newValue) {
+ locked = newValue;
+ splitterSize = -1;
+ setStylenames();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setPositionReversed(boolean reversed) {
+ if (positionReversed != reversed) {
+ if (orientation == Orientation.HORIZONTAL) {
+ DOM.setStyleAttribute(splitter, "right", "");
+ DOM.setStyleAttribute(splitter, "left", "");
+ } else if (orientation == Orientation.VERTICAL) {
+ DOM.setStyleAttribute(splitter, "top", "");
+ DOM.setStyleAttribute(splitter, "bottom", "");
+ }
+
+ positionReversed = reversed;
+ }
+ }
+
+ /**
+ * Converts given split position string (in pixels or percentage) to a
+ * floating point pixel value.
+ *
+ * @param pos
+ * @return
+ */
+ private float convertToPixels(String pos) {
+ float posAsFloat;
+ if (pos.indexOf("%") > 0) {
+ posAsFloat = Math.round(Float.parseFloat(pos.substring(0,
+ pos.length() - 1))
+ / 100
+ * (orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+ : getOffsetHeight()));
+ } else {
+ posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2));
+ }
+ return posAsFloat;
+ }
+
+ /**
+ * Converts given split position string (in pixels or percentage) to a float
+ * percentage value.
+ *
+ * @param pos
+ * @return
+ */
+ private float convertToPercentage(String pos) {
+ if (pos.endsWith("px")) {
+ float pixelPosition = Float.parseFloat(pos.substring(0,
+ pos.length() - 2));
+ int offsetLength = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+ : getOffsetHeight();
+
+ // Take splitter size into account at the edge
+ if (pixelPosition + getSplitterSize() >= offsetLength) {
+ return 100;
+ }
+
+ return pixelPosition / offsetLength * 100;
+ } else {
+ assert pos.endsWith("%");
+ return Float.parseFloat(pos.substring(0, pos.length() - 1));
+ }
+ }
+
+ /**
+ * Returns the given position clamped to the range between current minimum
+ * and maximum positions.
+ *
+ * TODO Should this be in the connector?
+ *
+ * @param pos
+ * Position of the splitter as a CSS string, either pixels or a
+ * percentage.
+ * @return minimumPosition if pos is less than minimumPosition;
+ * maximumPosition if pos is greater than maximumPosition; pos
+ * otherwise.
+ */
+ private String checkSplitPositionLimits(String pos) {
+ float positionAsFloat = convertToPixels(pos);
+
+ if (maximumPosition != null
+ && convertToPixels(maximumPosition) < positionAsFloat) {
+ pos = maximumPosition;
+ } else if (minimumPosition != null
+ && convertToPixels(minimumPosition) > positionAsFloat) {
+ pos = minimumPosition;
+ }
+ return pos;
+ }
+
+ /**
+ * Converts given string to the same units as the split position is.
+ *
+ * @param pos
+ * position to be converted
+ * @return converted position string
+ */
+ private String convertToPositionUnits(String pos) {
+ if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) {
+ // position is in percentage, pos in pixels
+ pos = convertToPercentage(pos) + "%";
+ } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) {
+ // position is in pixels and pos in percentage
+ pos = convertToPixels(pos) + "px";
+ }
+
+ return pos;
+ }
+
+ public void setSplitPosition(String pos) {
+ if (pos == null) {
+ return;
+ }
+
+ pos = checkSplitPositionLimits(pos);
+ if (!pos.equals(position)) {
+ position = convertToPositionUnits(pos);
+ }
+
+ // Convert percentage values to pixels
+ if (pos.indexOf("%") > 0) {
+ int size = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+ : getOffsetHeight();
+ float percentage = Float.parseFloat(pos.substring(0,
+ pos.length() - 1));
+ pos = percentage / 100 * size + "px";
+ }
+
+ String attributeName;
+ if (orientation == Orientation.HORIZONTAL) {
+ if (positionReversed) {
+ attributeName = "right";
+ } else {
+ attributeName = "left";
+ }
+ } else {
+ if (positionReversed) {
+ attributeName = "bottom";
+ } else {
+ attributeName = "top";
+ }
+ }
+
+ Style style = splitter.getStyle();
+ if (!pos.equals(style.getProperty(attributeName))) {
+ style.setProperty(attributeName, pos);
+ updateSizes();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateSizes() {
+ if (!isAttached()) {
+ return;
+ }
+
+ int wholeSize;
+ int pixelPosition;
+
+ switch (orientation) {
+ case HORIZONTAL:
+ wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
+ pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
+
+ // reposition splitter in case it is out of box
+ if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
+ || (positionReversed && pixelPosition < 0)) {
+ pixelPosition = wholeSize - getSplitterSize();
+ if (pixelPosition < 0) {
+ pixelPosition = 0;
+ }
+ setSplitPosition(pixelPosition + "px");
+ return;
+ }
+
+ DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px");
+ int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
+ if (secondContainerWidth < 0) {
+ secondContainerWidth = 0;
+ }
+ DOM.setStyleAttribute(secondContainer, "width",
+ secondContainerWidth + "px");
+ DOM.setStyleAttribute(secondContainer, "left",
+ (pixelPosition + getSplitterSize()) + "px");
+
+ LayoutManager layoutManager = LayoutManager.get(client);
+ ConnectorMap connectorMap = ConnectorMap.get(client);
+ if (firstChild != null) {
+ ComponentConnector connector = connectorMap
+ .getConnector(firstChild);
+ if (connector.isRelativeWidth()) {
+ layoutManager.reportWidthAssignedToRelative(connector,
+ pixelPosition);
+ } else {
+ layoutManager.setNeedsMeasure(connector);
+ }
+ }
+ if (secondChild != null) {
+ ComponentConnector connector = connectorMap
+ .getConnector(secondChild);
+ if (connector.isRelativeWidth()) {
+ layoutManager.reportWidthAssignedToRelative(connector,
+ secondContainerWidth);
+ } else {
+ layoutManager.setNeedsMeasure(connector);
+ }
+ }
+ break;
+ case VERTICAL:
+ wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
+ pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
+
+ // reposition splitter in case it is out of box
+ if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
+ || (positionReversed && pixelPosition < 0)) {
+ pixelPosition = wholeSize - getSplitterSize();
+ if (pixelPosition < 0) {
+ pixelPosition = 0;
+ }
+ setSplitPosition(pixelPosition + "px");
+ return;
+ }
+
+ DOM.setStyleAttribute(firstContainer, "height", pixelPosition
+ + "px");
+ int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
+ if (secondContainerHeight < 0) {
+ secondContainerHeight = 0;
+ }
+ DOM.setStyleAttribute(secondContainer, "height",
+ secondContainerHeight + "px");
+ DOM.setStyleAttribute(secondContainer, "top",
+ (pixelPosition + getSplitterSize()) + "px");
+
+ layoutManager = LayoutManager.get(client);
+ connectorMap = ConnectorMap.get(client);
+ if (firstChild != null) {
+ ComponentConnector connector = connectorMap
+ .getConnector(firstChild);
+ if (connector.isRelativeHeight()) {
+ layoutManager.reportHeightAssignedToRelative(connector,
+ pixelPosition);
+ } else {
+ layoutManager.setNeedsMeasure(connector);
+ }
+ }
+ if (secondChild != null) {
+ ComponentConnector connector = connectorMap
+ .getConnector(secondChild);
+ if (connector.isRelativeHeight()) {
+ layoutManager.reportHeightAssignedToRelative(connector,
+ secondContainerHeight);
+ } else {
+ layoutManager.setNeedsMeasure(connector);
+ }
+ }
+ break;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setFirstWidget(Widget w) {
+ if (firstChild != null) {
+ firstChild.removeFromParent();
+ }
+ if (w != null) {
+ super.add(w, firstContainer);
+ }
+ firstChild = w;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setSecondWidget(Widget w) {
+ if (secondChild != null) {
+ secondChild.removeFromParent();
+ }
+ if (w != null) {
+ super.add(w, secondContainer);
+ }
+ secondChild = w;
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEMOVE:
+ // case Event.ONTOUCHMOVE:
+ if (resizing) {
+ onMouseMove(event);
+ }
+ break;
+ case Event.ONMOUSEDOWN:
+ // case Event.ONTOUCHSTART:
+ onMouseDown(event);
+ break;
+ case Event.ONMOUSEOUT:
+ // Dragging curtain interferes with click events if added in
+ // mousedown so we add it only when needed i.e., if the mouse moves
+ // outside the splitter.
+ if (resizing) {
+ showDraggingCurtain();
+ }
+ break;
+ case Event.ONMOUSEUP:
+ // case Event.ONTOUCHEND:
+ if (resizing) {
+ onMouseUp(event);
+ }
+ break;
+ case Event.ONCLICK:
+ resizing = false;
+ break;
+ }
+ // Only fire click event listeners if the splitter isn't moved
+ if (Util.isTouchEvent(event) || !resized) {
+ super.onBrowserEvent(event);
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ // Reset the resized flag after a mouseup has occured so the next
+ // mousedown/mouseup can be interpreted as a click.
+ resized = false;
+ }
+ }
+
+ public void onMouseDown(Event event) {
+ if (locked || !isEnabled()) {
+ return;
+ }
+ final Element trg = event.getEventTarget().cast();
+ if (trg == splitter || trg == DOM.getChild(splitter, 0)) {
+ resizing = true;
+ DOM.setCapture(getElement());
+ origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
+ origY = DOM.getElementPropertyInt(splitter, "offsetTop");
+ origMouseX = Util.getTouchOrMouseClientX(event);
+ origMouseY = Util.getTouchOrMouseClientY(event);
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+
+ public void onMouseMove(Event event) {
+ switch (orientation) {
+ case HORIZONTAL:
+ final int x = Util.getTouchOrMouseClientX(event);
+ onHorizontalMouseMove(x);
+ break;
+ case VERTICAL:
+ default:
+ final int y = Util.getTouchOrMouseClientY(event);
+ onVerticalMouseMove(y);
+ break;
+ }
+
+ }
+
+ private void onHorizontalMouseMove(int x) {
+ int newX = origX + x - origMouseX;
+ if (newX < 0) {
+ newX = 0;
+ }
+ if (newX + getSplitterSize() > getOffsetWidth()) {
+ newX = getOffsetWidth() - getSplitterSize();
+ }
+
+ if (position.indexOf("%") > 0) {
+ position = convertToPositionUnits(newX + "px");
+ } else {
+ // Reversed position
+ if (positionReversed) {
+ position = (getOffsetWidth() - newX - getSplitterSize()) + "px";
+ } else {
+ position = newX + "px";
+ }
+ }
+
+ if (origX != newX) {
+ resized = true;
+ }
+
+ // Reversed position
+ if (positionReversed) {
+ newX = getOffsetWidth() - newX - getSplitterSize();
+ }
+
+ setSplitPosition(newX + "px");
+ }
+
+ private void onVerticalMouseMove(int y) {
+ int newY = origY + y - origMouseY;
+ if (newY < 0) {
+ newY = 0;
+ }
+
+ if (newY + getSplitterSize() > getOffsetHeight()) {
+ newY = getOffsetHeight() - getSplitterSize();
+ }
+
+ if (position.indexOf("%") > 0) {
+ position = convertToPositionUnits(newY + "px");
+ } else {
+ // Reversed position
+ if (positionReversed) {
+ position = (getOffsetHeight() - newY - getSplitterSize())
+ + "px";
+ } else {
+ position = newY + "px";
+ }
+ }
+
+ if (origY != newY) {
+ resized = true;
+ }
+
+ // Reversed position
+ if (positionReversed) {
+ newY = getOffsetHeight() - newY - getSplitterSize();
+ }
+
+ setSplitPosition(newY + "px");
+ }
+
+ public void onMouseUp(Event event) {
+ DOM.releaseCapture(getElement());
+ hideDraggingCurtain();
+ resizing = false;
+ if (!Util.isTouchEvent(event)) {
+ onMouseMove(event);
+ }
+ fireEvent(new SplitterMoveEvent(this));
+ }
+
+ public interface SplitterMoveHandler extends EventHandler {
+ public void splitterMoved(SplitterMoveEvent event);
+
+ public static class SplitterMoveEvent extends
+ GwtEvent<SplitterMoveHandler> {
+
+ public static final Type<SplitterMoveHandler> TYPE = new Type<SplitterMoveHandler>();
+
+ private Widget splitPanel;
+
+ public SplitterMoveEvent(Widget splitPanel) {
+ this.splitPanel = splitPanel;
+ }
+
+ @Override
+ public com.google.gwt.event.shared.GwtEvent.Type<SplitterMoveHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(SplitterMoveHandler handler) {
+ handler.splitterMoved(this);
+ }
+
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String getSplitterPosition() {
+ return position;
+ }
+
+ /**
+ * Used in FF to avoid losing mouse capture when pointer is moved on an
+ * iframe.
+ */
+ private void showDraggingCurtain() {
+ if (!isDraggingCurtainRequired()) {
+ return;
+ }
+ if (draggingCurtain == null) {
+ draggingCurtain = DOM.createDiv();
+ DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
+ DOM.setStyleAttribute(draggingCurtain, "top", "0px");
+ DOM.setStyleAttribute(draggingCurtain, "left", "0px");
+ DOM.setStyleAttribute(draggingCurtain, "width", "100%");
+ DOM.setStyleAttribute(draggingCurtain, "height", "100%");
+ DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
+ + VOverlay.Z_INDEX);
+
+ DOM.appendChild(wrapper, draggingCurtain);
+ }
+ }
+
+ /**
+ * A dragging curtain is required in Gecko and Webkit.
+ *
+ * @return true if the browser requires a dragging curtain
+ */
+ private boolean isDraggingCurtainRequired() {
+ return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit());
+ }
+
+ /**
+ * Hides dragging curtain
+ */
+ private void hideDraggingCurtain() {
+ if (draggingCurtain != null) {
+ DOM.removeChild(wrapper, draggingCurtain);
+ draggingCurtain = null;
+ }
+ }
+
+ private int splitterSize = -1;
+
+ private int getSplitterSize() {
+ if (splitterSize < 0) {
+ if (isAttached()) {
+ switch (orientation) {
+ case HORIZONTAL:
+ splitterSize = DOM.getElementPropertyInt(splitter,
+ "offsetWidth");
+ break;
+
+ default:
+ splitterSize = DOM.getElementPropertyInt(splitter,
+ "offsetHeight");
+ break;
+ }
+ }
+ }
+ return splitterSize;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setStylenames() {
+ final String splitterClass = CLASSNAME
+ + (orientation == Orientation.HORIZONTAL ? "-hsplitter"
+ : "-vsplitter");
+ final String firstContainerClass = CLASSNAME + "-first-container";
+ final String secondContainerClass = CLASSNAME + "-second-container";
+ final String lockedSuffix = locked ? "-locked" : "";
+
+ splitter.setClassName(splitterClass + lockedSuffix);
+ firstContainer.setClassName(firstContainerClass);
+ secondContainer.setClassName(secondContainerClass);
+
+ for (String styleName : componentStyleNames) {
+ splitter.addClassName(splitterClass + "-" + styleName
+ + lockedSuffix);
+ firstContainer.addClassName(firstContainerClass + "-" + styleName);
+ secondContainer
+ .addClassName(secondContainerClass + "-" + styleName);
+ }
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Ensures the panels are scrollable eg. after style name changes
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void makeScrollable() {
+ if (touchScrollHandler == null) {
+ touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+ }
+ touchScrollHandler.addElement(firstContainer);
+ touchScrollHandler.addElement(secondContainer);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
+
+public class VAccordion extends VTabsheetBase {
+
+ public static final String CLASSNAME = "v-accordion";
+
+ private Set<Widget> widgets = new HashSet<Widget>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public StackItem openTab = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int selectedUIDLItemIndex = -1;
+
+ private final TouchScrollHandler touchScrollHandler;
+
+ public VAccordion() {
+ super(CLASSNAME);
+ touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+ }
+
+ @Override
+ public void renderTab(UIDL tabUidl, int index, boolean selected,
+ boolean hidden) {
+ StackItem item;
+ int itemIndex;
+ if (getWidgetCount() <= index) {
+ // Create stackItem and render caption
+ item = new StackItem(tabUidl);
+ if (getWidgetCount() == 0) {
+ item.addStyleDependentName("first");
+ }
+ itemIndex = getWidgetCount();
+ add(item, getElement());
+ } else {
+ item = getStackItem(index);
+ item = moveStackItemIfNeeded(item, index, tabUidl);
+ itemIndex = index;
+ }
+ item.updateCaption(tabUidl);
+
+ item.setVisible(!hidden);
+
+ if (selected) {
+ selectedUIDLItemIndex = itemIndex;
+ }
+
+ if (tabUidl.getChildCount() > 0) {
+ lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
+ }
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ updateStyleNames(style);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames(style);
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ for (Widget w : getChildren()) {
+ if (w instanceof StackItem) {
+ StackItem item = (StackItem) w;
+ item.updateStyleNames(primaryStyleName);
+ }
+ }
+ }
+
+ /**
+ * This method tries to find out if a tab has been rendered with a different
+ * index previously. If this is the case it re-orders the children so the
+ * same StackItem is used for rendering this time. E.g. if the first tab has
+ * been removed all tabs which contain cached content must be moved 1 step
+ * up to preserve the cached content.
+ *
+ * @param item
+ * @param newIndex
+ * @param tabUidl
+ * @return
+ */
+ private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
+ UIDL tabUidl) {
+ UIDL tabContentUIDL = null;
+ ComponentConnector tabContent = null;
+ if (tabUidl.getChildCount() > 0) {
+ tabContentUIDL = tabUidl.getChildUIDL(0);
+ tabContent = client.getPaintable(tabContentUIDL);
+ }
+
+ Widget itemWidget = item.getComponent();
+ if (tabContent != null) {
+ if (tabContent.getWidget() != itemWidget) {
+ /*
+ * This is not the same widget as before, find out if it has
+ * been moved
+ */
+ int oldIndex = -1;
+ StackItem oldItem = null;
+ for (int i = 0; i < getWidgetCount(); i++) {
+ Widget w = getWidget(i);
+ oldItem = (StackItem) w;
+ if (tabContent == oldItem.getComponent()) {
+ oldIndex = i;
+ break;
+ }
+ }
+
+ if (oldIndex != -1 && oldIndex > newIndex) {
+ /*
+ * The tab has previously been rendered in another position
+ * so we must move the cached content to correct position.
+ * We move only items with oldIndex > newIndex to prevent
+ * moving items already rendered in this update. If for
+ * instance tabs 1,2,3 are removed and added as 3,2,1 we
+ * cannot re-use "1" when we get to the third tab.
+ */
+ insert(oldItem, getElement(), newIndex, true);
+ return oldItem;
+ }
+ }
+ } else {
+ // Tab which has never been loaded. Must assure we use an empty
+ // StackItem
+ Widget oldWidget = item.getComponent();
+ if (oldWidget != null) {
+ oldWidget.removeFromParent();
+ }
+ }
+ return item;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void open(int itemIndex) {
+ StackItem item = (StackItem) getWidget(itemIndex);
+ boolean alreadyOpen = false;
+ if (openTab != null) {
+ if (openTab.isOpen()) {
+ if (openTab == item) {
+ alreadyOpen = true;
+ } else {
+ openTab.close();
+ }
+ }
+ }
+ if (!alreadyOpen) {
+ item.open();
+ activeTabIndex = itemIndex;
+ openTab = item;
+ }
+
+ // Update the size for the open tab
+ updateOpenTabSize();
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void close(StackItem item) {
+ if (!item.isOpen()) {
+ return;
+ }
+
+ item.close();
+ activeTabIndex = -1;
+ openTab = null;
+
+ }
+
+ @Override
+ protected void selectTab(final int index, final UIDL contentUidl) {
+ StackItem item = getStackItem(index);
+ if (index != activeTabIndex) {
+ open(index);
+ iLayout();
+ // TODO Check if this is needed
+ client.runDescendentsLayout(this);
+
+ }
+ item.setContent(contentUidl);
+ }
+
+ public void onSelectTab(StackItem item) {
+ final int index = getWidgetIndex(item);
+ if (index != activeTabIndex && !disabled && !readonly
+ && !disabledTabKeys.contains(tabKeys.get(index))) {
+ addStyleDependentName("loading");
+ client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
+ }
+ }
+
+ /**
+ * Sets the size of the open tab.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void updateOpenTabSize() {
+ if (openTab == null) {
+ return;
+ }
+
+ // WIDTH
+ if (!isDynamicWidth()) {
+ openTab.setWidth("100%");
+ } else {
+ openTab.setWidth(null);
+ }
+
+ // HEIGHT
+ if (!isDynamicHeight()) {
+ int usedPixels = 0;
+ for (Widget w : getChildren()) {
+ StackItem item = (StackItem) w;
+ if (item == openTab) {
+ usedPixels += item.getCaptionHeight();
+ } else {
+ // This includes the captionNode borders
+ usedPixels += item.getHeight();
+ }
+ }
+
+ int offsetHeight = getOffsetHeight();
+
+ int spaceForOpenItem = offsetHeight - usedPixels;
+
+ if (spaceForOpenItem < 0) {
+ spaceForOpenItem = 0;
+ }
+
+ openTab.setHeight(spaceForOpenItem);
+ } else {
+ openTab.setHeightFromWidget();
+
+ }
+
+ }
+
+ public void iLayout() {
+ if (openTab == null) {
+ return;
+ }
+
+ if (isDynamicWidth()) {
+ int maxWidth = 40;
+ for (Widget w : getChildren()) {
+ StackItem si = (StackItem) w;
+ int captionWidth = si.getCaptionWidth();
+ if (captionWidth > maxWidth) {
+ maxWidth = captionWidth;
+ }
+ }
+ int widgetWidth = openTab.getWidgetWidth();
+ if (widgetWidth > maxWidth) {
+ maxWidth = widgetWidth;
+ }
+ super.setWidth(maxWidth + "px");
+ openTab.setWidth(maxWidth);
+ }
+ }
+
+ /**
+ * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
+ * the actual child widget.
+ */
+ public class StackItem extends ComplexPanel implements ClickHandler {
+
+ public void setHeight(int height) {
+ if (height == -1) {
+ super.setHeight("");
+ DOM.setStyleAttribute(content, "height", "0px");
+ } else {
+ super.setHeight((height + getCaptionHeight()) + "px");
+ DOM.setStyleAttribute(content, "height", height + "px");
+ DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
+
+ }
+ }
+
+ public Widget getComponent() {
+ if (getWidgetCount() < 2) {
+ return null;
+ }
+ return getWidget(1);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ }
+
+ public void setHeightFromWidget() {
+ Widget widget = getChildWidget();
+ if (widget == null) {
+ return;
+ }
+
+ int paintableHeight = widget.getElement().getOffsetHeight();
+ setHeight(paintableHeight);
+
+ }
+
+ /**
+ * Returns caption width including padding
+ *
+ * @return
+ */
+ public int getCaptionWidth() {
+ if (caption == null) {
+ return 0;
+ }
+
+ int captionWidth = caption.getRequiredWidth();
+ int padding = Util.measureHorizontalPaddingAndBorder(
+ caption.getElement(), 18);
+ return captionWidth + padding;
+ }
+
+ public void setWidth(int width) {
+ if (width == -1) {
+ super.setWidth("");
+ } else {
+ super.setWidth(width + "px");
+ }
+ }
+
+ public int getHeight() {
+ return getOffsetHeight();
+ }
+
+ public int getCaptionHeight() {
+ return captionNode.getOffsetHeight();
+ }
+
+ private VCaption caption;
+ private boolean open = false;
+ private Element content = DOM.createDiv();
+ private Element captionNode = DOM.createDiv();
+
+ public StackItem(UIDL tabUidl) {
+ setElement(DOM.createDiv());
+ caption = new VCaption(client);
+ caption.addClickHandler(this);
+ super.add(caption, captionNode);
+ DOM.appendChild(captionNode, caption.getElement());
+ DOM.appendChild(getElement(), captionNode);
+ DOM.appendChild(getElement(), content);
+
+ updateStyleNames(VAccordion.this.getStylePrimaryName());
+
+ touchScrollHandler.addElement(getContainerElement());
+
+ close();
+ }
+
+ private void updateStyleNames(String primaryStyleName) {
+ content.removeClassName(getStylePrimaryName() + "-content");
+ captionNode.removeClassName(getStylePrimaryName() + "-caption");
+
+ setStylePrimaryName(primaryStyleName + "-item");
+
+ captionNode.addClassName(getStylePrimaryName() + "-caption");
+ content.addClassName(getStylePrimaryName() + "-content");
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ onSelectTab(this);
+ }
+
+ public Element getContainerElement() {
+ return content;
+ }
+
+ public Widget getChildWidget() {
+ if (getWidgetCount() > 1) {
+ return getWidget(1);
+ } else {
+ return null;
+ }
+ }
+
+ public void replaceWidget(Widget newWidget) {
+ if (getWidgetCount() > 1) {
+ Widget oldWidget = getWidget(1);
+ ComponentConnector oldPaintable = ConnectorMap.get(client)
+ .getConnector(oldWidget);
+ ConnectorMap.get(client).unregisterConnector(oldPaintable);
+ widgets.remove(oldWidget);
+ remove(1);
+ }
+ add(newWidget, content);
+ widgets.add(newWidget);
+ }
+
+ public void open() {
+ open = true;
+ DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
+ DOM.setStyleAttribute(content, "left", "0px");
+ DOM.setStyleAttribute(content, "visibility", "");
+ addStyleDependentName("open");
+ }
+
+ public void hide() {
+ DOM.setStyleAttribute(content, "visibility", "hidden");
+ }
+
+ public void close() {
+ DOM.setStyleAttribute(content, "visibility", "hidden");
+ DOM.setStyleAttribute(content, "top", "-100000px");
+ DOM.setStyleAttribute(content, "left", "-100000px");
+ removeStyleDependentName("open");
+ setHeight(-1);
+ setWidth("");
+ open = false;
+ }
+
+ public boolean isOpen() {
+ return open;
+ }
+
+ public void setContent(UIDL contentUidl) {
+ final ComponentConnector newPntbl = client
+ .getPaintable(contentUidl);
+ Widget newWidget = newPntbl.getWidget();
+ if (getChildWidget() == null) {
+ add(newWidget, content);
+ widgets.add(newWidget);
+ } else if (getChildWidget() != newWidget) {
+ replaceWidget(newWidget);
+ }
+ if (contentUidl.getBooleanAttribute("cached")) {
+ /*
+ * The size of a cached, relative sized component must be
+ * updated to report correct size.
+ */
+ client.handleComponentRelativeSize(newPntbl.getWidget());
+ }
+ if (isOpen() && isDynamicHeight()) {
+ setHeightFromWidget();
+ }
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ onSelectTab(this);
+ }
+
+ public void updateCaption(UIDL uidl) {
+ // TODO need to call this because the caption does not have an owner
+ caption.updateCaptionWithoutOwner(
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
+ }
+
+ public int getWidgetWidth() {
+ return DOM.getFirstChild(content).getOffsetWidth();
+ }
+
+ public boolean contains(ComponentConnector p) {
+ return (getChildWidget() == p.getWidget());
+ }
+
+ public boolean isCaptionVisible() {
+ return caption.isVisible();
+ }
+
+ }
+
+ @Override
+ protected void clearPaintables() {
+ clear();
+ }
+
+ boolean isDynamicWidth() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ return paintable.isUndefinedWidth();
+ }
+
+ boolean isDynamicHeight() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ return paintable.isUndefinedHeight();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Iterator<Widget> getWidgetIterator() {
+ return widgets.iterator();
+ }
+
+ @Override
+ public int getTabCount() {
+ return getWidgetCount();
+ }
+
+ @Override
+ public void removeTab(int index) {
+ StackItem item = getStackItem(index);
+ remove(item);
+ touchScrollHandler.removeElement(item.getContainerElement());
+ }
+
+ @Override
+ public ComponentConnector getTab(int index) {
+ if (index < getWidgetCount()) {
+ StackItem stackItem = getStackItem(index);
+ if (stackItem == null) {
+ return null;
+ }
+ Widget w = stackItem.getChildWidget();
+ if (w != null) {
+ return ConnectorMap.get(client).getConnector(w);
+ }
+ }
+
+ return null;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public StackItem getStackItem(int index) {
+ return (StackItem) getWidget(index);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.AudioElement;
+import com.google.gwt.dom.client.Document;
+
+public class VAudio extends VMediaBase {
+ private static String CLASSNAME = "v-audio";
+
+ private AudioElement audio;
+
+ public VAudio() {
+ audio = Document.get().createAudioElement();
+ setMediaElement(audio);
+ setStyleName(CLASSNAME);
+ }
+
+}
--- /dev/null
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.IFrameElement;
+import com.google.gwt.user.client.ui.Widget;
+
+public class VBrowserFrame extends Widget {
+
+ protected IFrameElement iframe;
+ protected Element altElement;
+ protected String altText;
+
+ public static final String CLASSNAME = "v-browserframe";
+
+ public VBrowserFrame() {
+ Element root = Document.get().createDivElement();
+ setElement(root);
+
+ setStyleName(CLASSNAME);
+
+ createAltTextElement();
+ }
+
+ /**
+ * Always creates new iframe inside widget. Will replace previous iframe.
+ *
+ * @return
+ */
+ protected IFrameElement createIFrameElement(String src) {
+ String name = null;
+
+ // Remove alt text
+ if (altElement != null) {
+ getElement().removeChild(altElement);
+ altElement = null;
+ }
+
+ // Remove old iframe
+ if (iframe != null) {
+ name = iframe.getAttribute("name");
+ getElement().removeChild(iframe);
+ iframe = null;
+ }
+
+ iframe = Document.get().createIFrameElement();
+ iframe.setSrc(src);
+ iframe.setFrameBorder(0);
+ iframe.setAttribute("width", "100%");
+ iframe.setAttribute("height", "100%");
+ iframe.setAttribute("allowTransparency", "true");
+
+ getElement().appendChild(iframe);
+
+ // Reset old attributes (except src)
+ if (name != null) {
+ iframe.setName(name);
+ }
+
+ return iframe;
+ }
+
+ protected void createAltTextElement() {
+ if (iframe != null) {
+ return;
+ }
+
+ if (altElement == null) {
+ altElement = Document.get().createSpanElement();
+ getElement().appendChild(altElement);
+ }
+
+ if (altText != null) {
+ altElement.setInnerText(altText);
+ } else {
+ altElement.setInnerText("");
+ }
+ }
+
+ public void setAlternateText(String altText) {
+ if (this.altText != altText) {
+ this.altText = altText;
+ if (altElement != null) {
+ if (altText != null) {
+ altElement.setInnerText(altText);
+ } else {
+ altElement.setInnerText("");
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the source (the "src" attribute) of iframe. Will replace old iframe
+ * with new.
+ *
+ * @param source
+ * Source of iframe.
+ */
+ public void setSource(String source) {
+
+ if (source == null) {
+ if (iframe != null) {
+ getElement().removeChild(iframe);
+ iframe = null;
+ }
+ createAltTextElement();
+ setAlternateText(altText);
+ return;
+ }
+
+ if (iframe == null || iframe.getSrc() != source) {
+ createIFrameElement(source);
+ }
+ }
+
+ public void setName(String name) {
+ if (iframe != null) {
+ iframe.setName(name);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+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.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Accessibility;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+
+public class VButton extends FocusWidget implements ClickHandler {
+
+ public static final String CLASSNAME = "v-button";
+ private static final String CLASSNAME_PRESSED = "v-pressed";
+
+ // mouse movement is checked before synthesizing click event on mouseout
+ protected static int MOVE_THRESHOLD = 3;
+ protected int mousedownX = 0;
+ protected int mousedownY = 0;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element wrapper = DOM.createSpan();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element errorIndicatorElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element captionElement = DOM.createSpan();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Icon icon;
+
+ /**
+ * Helper flag to handle special-case where the button is moved from under
+ * mouse while clicking it. In this case mouse leaves the button without
+ * moving.
+ */
+ protected boolean clickPending;
+
+ private boolean enabled = true;
+
+ private int tabIndex = 0;
+
+ /*
+ * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton
+ */
+
+ /**
+ * If <code>true</code>, this widget is capturing with the mouse held down.
+ */
+ private boolean isCapturing;
+
+ /**
+ * If <code>true</code>, this widget has focus with the space bar down. This
+ * means that we will get events when the button is released, but we should
+ * trigger the button only if the button is still focused at that point.
+ */
+ private boolean isFocusing;
+
+ /**
+ * Used to decide whether to allow clicks to propagate up to the superclass
+ * or container elements.
+ */
+ private boolean disallowNextClick = false;
+ private boolean isHovering;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int clickShortcut = 0;
+
+ private HandlerRegistration focusHandlerRegistration;
+ private HandlerRegistration blurHandlerRegistration;
+
+ public VButton() {
+ super(DOM.createDiv());
+ setTabIndex(0);
+ sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS
+ | Event.KEYEVENTS);
+
+ // Add a11y role "button"
+ Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
+
+ getElement().appendChild(wrapper);
+ wrapper.appendChild(captionElement);
+
+ setStyleName(CLASSNAME);
+
+ addClickHandler(this);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ wrapper.setClassName(getStylePrimaryName() + "-wrap");
+ captionElement.setClassName(getStylePrimaryName() + "-caption");
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ wrapper.setClassName(getStylePrimaryName() + "-wrap");
+ captionElement.setClassName(getStylePrimaryName() + "-caption");
+ }
+
+ public void setText(String text) {
+ captionElement.setInnerText(text);
+ }
+
+ public void setHtml(String html) {
+ captionElement.setInnerHTML(html);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ /*
+ * Copy-pasted from GWT CustomButton, some minor modifications done:
+ *
+ * -for IE/Opera added CLASSNAME_PRESSED
+ *
+ * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply
+ * :active styles if it is present)
+ *
+ * -Tooltip event handling added
+ *
+ * -onload event handler added (for icon handling)
+ */
+ public void onBrowserEvent(Event event) {
+ if (DOM.eventGetType(event) == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+ }
+ // Should not act on button if disabled.
+ if (!isEnabled()) {
+ // This can happen when events are bubbled up from non-disabled
+ // children
+ return;
+ }
+
+ int type = DOM.eventGetType(event);
+ switch (type) {
+ case Event.ONCLICK:
+ // If clicks are currently disallowed, keep it from bubbling or
+ // being passed to the superclass.
+ if (disallowNextClick) {
+ event.stopPropagation();
+ disallowNextClick = false;
+ return;
+ }
+ break;
+ case Event.ONMOUSEDOWN:
+ if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+ // This was moved from mouseover, which iOS sometimes skips.
+ // We're certainly hovering at this point, and we don't actually
+ // need that information before this point.
+ setHovering(true);
+ }
+ if (event.getButton() == Event.BUTTON_LEFT) {
+ // save mouse position to detect movement before synthesizing
+ // event later
+ mousedownX = event.getClientX();
+ mousedownY = event.getClientY();
+
+ disallowNextClick = true;
+ clickPending = true;
+ setFocus(true);
+ DOM.setCapture(getElement());
+ isCapturing = true;
+ // Prevent dragging (on some browsers);
+ // DOM.eventPreventDefault(event);
+ if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+ addStyleName(CLASSNAME_PRESSED);
+ }
+ }
+ break;
+ case Event.ONMOUSEUP:
+ if (isCapturing) {
+ isCapturing = false;
+ DOM.releaseCapture(getElement());
+ if (isHovering() && event.getButton() == Event.BUTTON_LEFT) {
+ // Click ok
+ disallowNextClick = false;
+ }
+ if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+ removeStyleName(CLASSNAME_PRESSED);
+ }
+ // Explicitly prevent IE 8 from propagating mouseup events
+ // upward (fixes #6753)
+ if (BrowserInfo.get().isIE8()) {
+ event.stopPropagation();
+ }
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ clickPending = false;
+ if (isCapturing) {
+ // Prevent dragging (on other browsers);
+ DOM.eventPreventDefault(event);
+ }
+ break;
+ case Event.ONMOUSEOUT:
+ Element to = event.getRelatedTarget();
+ if (getElement().isOrHasChild(DOM.eventGetTarget(event))
+ && (to == null || !getElement().isOrHasChild(to))) {
+ if (clickPending
+ && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD
+ && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) {
+ onClick();
+ break;
+ }
+ clickPending = false;
+ if (isCapturing) {
+ }
+ setHovering(false);
+ if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+ removeStyleName(CLASSNAME_PRESSED);
+ }
+ }
+ break;
+ case Event.ONBLUR:
+ if (isFocusing) {
+ isFocusing = false;
+ }
+ break;
+ case Event.ONLOSECAPTURE:
+ if (isCapturing) {
+ isCapturing = false;
+ }
+ break;
+ }
+
+ super.onBrowserEvent(event);
+
+ // Synthesize clicks based on keyboard events AFTER the normal key
+ // handling.
+ if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
+ switch (type) {
+ case Event.ONKEYDOWN:
+ // Stop propagation when the user starts pressing a button that
+ // we are handling to prevent actions from getting triggered
+ if (event.getKeyCode() == 32 /* space */) {
+ isFocusing = true;
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
+ event.stopPropagation();
+ }
+ break;
+ case Event.ONKEYUP:
+ if (isFocusing && event.getKeyCode() == 32 /* space */) {
+ isFocusing = false;
+ onClick();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ break;
+ case Event.ONKEYPRESS:
+ if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
+ onClick();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+ }
+
+ final void setHovering(boolean hovering) {
+ if (hovering != isHovering()) {
+ isHovering = hovering;
+ }
+ }
+
+ final boolean isHovering() {
+ return isHovering;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+ * .dom.client.ClickEvent)
+ */
+ @Override
+ public void onClick(ClickEvent event) {
+ if (BrowserInfo.get().isSafari()) {
+ VButton.this.setFocus(true);
+ }
+
+ clickPending = false;
+ }
+
+ /*
+ * ALL BELOW COPY-PASTED FROM GWT CustomButton
+ */
+
+ /**
+ * Called internally when the user finishes clicking on this button. The
+ * default behavior is to fire the click event to listeners. Subclasses that
+ * override {@link #onClickStart()} should override this method to restore
+ * the normal widget display.
+ * <p>
+ * To add custom code for a click event, override
+ * {@link #onClick(ClickEvent)} instead of this.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void onClick() {
+ // Allow the click we're about to synthesize to pass through to the
+ // superclass and containing elements. Element.dispatchEvent() is
+ // synchronous, so we simply set and clear the flag within this method.
+
+ disallowNextClick = false;
+
+ // Mouse coordinates are not always available (e.g., when the click is
+ // caused by a keyboard event).
+ NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
+ false, false, false);
+ getElement().dispatchEvent(evt);
+ }
+
+ /**
+ * Sets whether this button is enabled.
+ *
+ * @param enabled
+ * <code>true</code> to enable the button, <code>false</code> to
+ * disable it
+ */
+
+ @Override
+ public final void setEnabled(boolean enabled) {
+ if (isEnabled() != enabled) {
+ this.enabled = enabled;
+ if (!enabled) {
+ cleanupCaptureState();
+ Accessibility.removeState(getElement(),
+ Accessibility.STATE_PRESSED);
+ super.setTabIndex(-1);
+ } else {
+ Accessibility.setState(getElement(),
+ Accessibility.STATE_PRESSED, "false");
+ super.setTabIndex(tabIndex);
+ }
+ }
+ }
+
+ @Override
+ public final boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public final void setTabIndex(int index) {
+ super.setTabIndex(index);
+ tabIndex = index;
+ }
+
+ /**
+ * Resets internal state if this button can no longer service events. This
+ * can occur when the widget becomes detached or disabled.
+ */
+ private void cleanupCaptureState() {
+ if (isCapturing || isFocusing) {
+ DOM.releaseCapture(getElement());
+ isCapturing = false;
+ isFocusing = false;
+ }
+ }
+
+ private static native int getHorizontalBorderAndPaddingWidth(Element elem)
+ /*-{
+ // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS
+
+ var convertToPixel = function(elem, value) {
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // Remember the original values
+ var left = elem.style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ elem.style.left = value || 0;
+ var ret = elem.style.pixelLeft;
+
+ // Revert the changed values
+ elem.style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+
+ return ret;
+ }
+
+ var ret = 0;
+
+ var sides = ["Right","Left"];
+ for(var i=0; i<2; i++) {
+ var side = sides[i];
+ var value;
+ // Border -------------------------------------------------------
+ if(elem.currentStyle["border"+side+"Style"] != "none") {
+ value = elem.currentStyle["border"+side+"Width"];
+ if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
+ ret += convertToPixel(elem, value);
+ } else if(value.length > 2) {
+ ret += parseInt(value.substr(0, value.length-2));
+ }
+ }
+
+ // Padding -------------------------------------------------------
+ value = elem.currentStyle["padding"+side];
+ if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
+ ret += convertToPixel(elem, value);
+ } else if(value.length > 2) {
+ ret += parseInt(value.substr(0, value.length-2));
+ }
+ }
+
+ return ret;
+ }-*/;
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.InlineHTML;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+@SuppressWarnings("deprecation")
+public class VCalendarPanel extends FocusableFlexTable implements
+ KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler,
+ MouseUpHandler, BlurHandler, FocusHandler, SubPartAware {
+
+ public interface SubmitListener {
+
+ /**
+ * Called when calendar user triggers a submitting operation in calendar
+ * panel. Eg. clicking on day or hitting enter.
+ */
+ void onSubmit();
+
+ /**
+ * On eg. ESC key.
+ */
+ void onCancel();
+ }
+
+ /**
+ * Blur listener that listens to blur event from the panel
+ */
+ public interface FocusOutListener {
+ /**
+ * @return true if the calendar panel is not used after focus moves out
+ */
+ boolean onFocusOut(DomEvent<?> event);
+ }
+
+ /**
+ * FocusChangeListener is notified when the panel changes its _focused_
+ * value.
+ */
+ public interface FocusChangeListener {
+ void focusChanged(Date focusedDate);
+ }
+
+ /**
+ * Dispatches an event when the panel when time is changed
+ */
+ public interface TimeChangeListener {
+
+ void changed(int hour, int min, int sec, int msec);
+ }
+
+ /**
+ * Represents a Date button in the calendar
+ */
+ private class VEventButton extends Button {
+ public VEventButton() {
+ addMouseDownHandler(VCalendarPanel.this);
+ addMouseOutHandler(VCalendarPanel.this);
+ addMouseUpHandler(VCalendarPanel.this);
+ }
+ }
+
+ private static final String CN_FOCUSED = "focused";
+
+ private static final String CN_TODAY = "today";
+
+ private static final String CN_SELECTED = "selected";
+
+ private static final String CN_OFFMONTH = "offmonth";
+
+ /**
+ * Represents a click handler for when a user selects a value by using the
+ * mouse
+ */
+ private ClickHandler dayClickHandler = new ClickHandler() {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
+ * .event.dom.client.ClickEvent)
+ */
+ @Override
+ public void onClick(ClickEvent event) {
+ Date newDate = ((Day) event.getSource()).getDate();
+ if (newDate.getMonth() != displayedMonth.getMonth()
+ || newDate.getYear() != displayedMonth.getYear()) {
+ // If an off-month date was clicked, we must change the
+ // displayed month and re-render the calendar (#8931)
+ displayedMonth.setMonth(newDate.getMonth());
+ displayedMonth.setYear(newDate.getYear());
+ renderCalendar();
+ }
+ focusDay(newDate);
+ selectFocused();
+ onSubmit();
+ }
+ };
+
+ private VEventButton prevYear;
+
+ private VEventButton nextYear;
+
+ private VEventButton prevMonth;
+
+ private VEventButton nextMonth;
+
+ private VTime time;
+
+ private FlexTable days = new FlexTable();
+
+ private Resolution resolution = Resolution.YEAR;
+
+ private int focusedRow;
+
+ private Timer mouseTimer;
+
+ private Date value;
+
+ private boolean enabled = true;
+
+ private boolean readonly = false;
+
+ private DateTimeService dateTimeService;
+
+ private boolean showISOWeekNumbers;
+
+ private Date displayedMonth;
+
+ private Date focusedDate;
+
+ private Day selectedDay;
+
+ private Day focusedDay;
+
+ private FocusOutListener focusOutListener;
+
+ private SubmitListener submitListener;
+
+ private FocusChangeListener focusChangeListener;
+
+ private TimeChangeListener timeChangeListener;
+
+ private boolean hasFocus = false;
+
+ private VDateField parent;
+
+ private boolean initialRenderDone = false;
+
+ public VCalendarPanel() {
+
+ setStyleName(VDateField.CLASSNAME + "-calendarpanel");
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+ addFocusHandler(this);
+ addBlurHandler(this);
+ }
+
+ public void setParentField(VDateField parent) {
+ this.parent = parent;
+ }
+
+ /**
+ * Sets the focus to given date in the current view. Used when moving in the
+ * calendar with the keyboard.
+ *
+ * @param date
+ * A Date representing the day of month to be focused. Must be
+ * one of the days currently visible.
+ */
+ private void focusDay(Date date) {
+ // Only used when calender body is present
+ if (resolution.getCalendarField() > Resolution.MONTH.getCalendarField()) {
+ if (focusedDay != null) {
+ focusedDay.removeStyleDependentName(CN_FOCUSED);
+ }
+
+ if (date != null && focusedDate != null) {
+ focusedDate.setTime(date.getTime());
+ int rowCount = days.getRowCount();
+ for (int i = 0; i < rowCount; i++) {
+ int cellCount = days.getCellCount(i);
+ for (int j = 0; j < cellCount; j++) {
+ Widget widget = days.getWidget(i, j);
+ if (widget != null && widget instanceof Day) {
+ Day curday = (Day) widget;
+ if (curday.getDate().equals(date)) {
+ curday.addStyleDependentName(CN_FOCUSED);
+ focusedDay = curday;
+ focusedRow = i;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the selection highlight to a given day in the current view
+ *
+ * @param date
+ * A Date representing the day of month to be selected. Must be
+ * one of the days currently visible.
+ *
+ */
+ private void selectDate(Date date) {
+ if (selectedDay != null) {
+ selectedDay.removeStyleDependentName(CN_SELECTED);
+ }
+
+ int rowCount = days.getRowCount();
+ for (int i = 0; i < rowCount; i++) {
+ int cellCount = days.getCellCount(i);
+ for (int j = 0; j < cellCount; j++) {
+ Widget widget = days.getWidget(i, j);
+ if (widget != null && widget instanceof Day) {
+ Day curday = (Day) widget;
+ if (curday.getDate().equals(date)) {
+ curday.addStyleDependentName(CN_SELECTED);
+ selectedDay = curday;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates year, month, day from focusedDate to value
+ */
+ private void selectFocused() {
+ if (focusedDate != null) {
+ if (value == null) {
+ // No previously selected value (set to null on server side).
+ // Create a new date using current date and time
+ value = new Date();
+ }
+ /*
+ * #5594 set Date (day) to 1 in order to prevent any kind of
+ * wrapping of months when later setting the month. (e.g. 31 ->
+ * month with 30 days -> wraps to the 1st of the following month,
+ * e.g. 31st of May -> 31st of April = 1st of May)
+ */
+ value.setDate(1);
+ if (value.getYear() != focusedDate.getYear()) {
+ value.setYear(focusedDate.getYear());
+ }
+ if (value.getMonth() != focusedDate.getMonth()) {
+ value.setMonth(focusedDate.getMonth());
+ }
+ if (value.getDate() != focusedDate.getDate()) {
+ }
+ // We always need to set the date, even if it hasn't changed, since
+ // it was forced to 1 above.
+ value.setDate(focusedDate.getDate());
+
+ selectDate(focusedDate);
+ } else {
+ VConsole.log("Trying to select a the focused date which is NULL!");
+ }
+ }
+
+ protected boolean onValueChange() {
+ return false;
+ }
+
+ public Resolution getResolution() {
+ return resolution;
+ }
+
+ public void setResolution(Resolution resolution) {
+ this.resolution = resolution;
+ if (time != null) {
+ time.removeFromParent();
+ time = null;
+ }
+ }
+
+ private boolean isReadonly() {
+ return readonly;
+ }
+
+ private boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ if (initialRenderDone) {
+ // Dynamic updates to the stylename needs to render the calendar to
+ // update the inner element stylenames
+ renderCalendar();
+ }
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ if (initialRenderDone) {
+ // Dynamic updates to the stylename needs to render the calendar to
+ // update the inner element stylenames
+ renderCalendar();
+ }
+ }
+
+ private void clearCalendarBody(boolean remove) {
+ if (!remove) {
+ // Leave the cells in place but clear their contents
+
+ // This has the side effect of ensuring that the calendar always
+ // contain 7 rows.
+ for (int row = 1; row < 7; row++) {
+ for (int col = 0; col < 8; col++) {
+ days.setHTML(row, col, " ");
+ }
+ }
+ } else if (getRowCount() > 1) {
+ removeRow(1);
+ days.clear();
+ }
+ }
+
+ /**
+ * Builds the top buttons and current month and year header.
+ *
+ * @param needsMonth
+ * Should the month buttons be visible?
+ */
+ private void buildCalendarHeader(boolean needsMonth) {
+
+ getRowFormatter().addStyleName(0,
+ parent.getStylePrimaryName() + "-calendarpanel-header");
+
+ if (prevMonth == null && needsMonth) {
+ prevMonth = new VEventButton();
+ prevMonth.setHTML("‹");
+ prevMonth.setStyleName("v-button-prevmonth");
+ prevMonth.setTabIndex(-1);
+ nextMonth = new VEventButton();
+ nextMonth.setHTML("›");
+ nextMonth.setStyleName("v-button-nextmonth");
+ nextMonth.setTabIndex(-1);
+
+ setWidget(0, 3, nextMonth);
+ setWidget(0, 1, prevMonth);
+ } else if (prevMonth != null && !needsMonth) {
+ // Remove month traverse buttons
+ remove(prevMonth);
+ remove(nextMonth);
+ prevMonth = null;
+ nextMonth = null;
+ }
+
+ if (prevYear == null) {
+ prevYear = new VEventButton();
+ prevYear.setHTML("«");
+ prevYear.setStyleName("v-button-prevyear");
+ prevYear.setTabIndex(-1);
+ nextYear = new VEventButton();
+ nextYear.setHTML("»");
+ nextYear.setStyleName("v-button-nextyear");
+ nextYear.setTabIndex(-1);
+ setWidget(0, 0, prevYear);
+ setWidget(0, 4, nextYear);
+ }
+
+ final String monthName = needsMonth ? getDateTimeService().getMonth(
+ displayedMonth.getMonth()) : "";
+ final int year = displayedMonth.getYear() + 1900;
+
+ getFlexCellFormatter().setStyleName(0, 2,
+ parent.getStylePrimaryName() + "-calendarpanel-month");
+ getFlexCellFormatter().setStyleName(0, 0,
+ parent.getStylePrimaryName() + "-calendarpanel-prevyear");
+ getFlexCellFormatter().setStyleName(0, 4,
+ parent.getStylePrimaryName() + "-calendarpanel-nextyear");
+ getFlexCellFormatter().setStyleName(0, 3,
+ parent.getStylePrimaryName() + "-calendarpanel-nextmonth");
+ getFlexCellFormatter().setStyleName(0, 1,
+ parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
+
+ setHTML(0, 2, "<span class=\"" + parent.getStylePrimaryName()
+ + "-calendarpanel-month\">" + monthName + " " + year
+ + "</span>");
+ }
+
+ private DateTimeService getDateTimeService() {
+ return dateTimeService;
+ }
+
+ public void setDateTimeService(DateTimeService dateTimeService) {
+ this.dateTimeService = dateTimeService;
+ }
+
+ /**
+ * Returns whether ISO 8601 week numbers should be shown in the value
+ * selector or not. ISO 8601 defines that a week always starts with a Monday
+ * so the week numbers are only shown if this is the case.
+ *
+ * @return true if week number should be shown, false otherwise
+ */
+ public boolean isShowISOWeekNumbers() {
+ return showISOWeekNumbers;
+ }
+
+ public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
+ this.showISOWeekNumbers = showISOWeekNumbers;
+ }
+
+ /**
+ * Builds the day and time selectors of the calendar.
+ */
+ private void buildCalendarBody() {
+
+ final int weekColumn = 0;
+ final int firstWeekdayColumn = 1;
+ final int headerRow = 0;
+
+ setWidget(1, 0, days);
+ setCellPadding(0);
+ setCellSpacing(0);
+ getFlexCellFormatter().setColSpan(1, 0, 5);
+ getFlexCellFormatter().setStyleName(1, 0,
+ parent.getStylePrimaryName() + "-calendarpanel-body");
+
+ days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
+ "v-week");
+ days.setHTML(headerRow, weekColumn, "<strong></strong>");
+ // Hide the week column if week numbers are not to be displayed.
+ days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
+ isShowISOWeekNumbers());
+
+ days.getRowFormatter().setStyleName(headerRow,
+ parent.getStylePrimaryName() + "-calendarpanel-weekdays");
+
+ if (isShowISOWeekNumbers()) {
+ days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
+ "v-first");
+ days.getFlexCellFormatter().setStyleName(headerRow,
+ firstWeekdayColumn, "");
+ days.getRowFormatter()
+ .addStyleName(
+ headerRow,
+ parent.getStylePrimaryName()
+ + "-calendarpanel-weeknumbers");
+ } else {
+ days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
+ days.getFlexCellFormatter().setStyleName(headerRow,
+ firstWeekdayColumn, "v-first");
+ }
+
+ days.getFlexCellFormatter().setStyleName(headerRow,
+ firstWeekdayColumn + 6, "v-last");
+
+ // Print weekday names
+ final int firstDay = getDateTimeService().getFirstDayOfWeek();
+ for (int i = 0; i < 7; i++) {
+ int day = i + firstDay;
+ if (day > 6) {
+ day = 0;
+ }
+ if (getResolution().getCalendarField() > Resolution.MONTH
+ .getCalendarField()) {
+ days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
+ + getDateTimeService().getShortDay(day) + "</strong>");
+ } else {
+ days.setHTML(headerRow, firstWeekdayColumn + i, "");
+ }
+ }
+
+ // Zero out hours, minutes, seconds, and milliseconds to compare dates
+ // without time part
+ final Date tmp = new Date();
+ final Date today = new Date(tmp.getYear(), tmp.getMonth(),
+ tmp.getDate());
+
+ final Date selectedDate = value == null ? null : new Date(
+ value.getYear(), value.getMonth(), value.getDate());
+
+ final int startWeekDay = getDateTimeService().getStartWeekDay(
+ displayedMonth);
+ final Date curr = (Date) displayedMonth.clone();
+ // Start from the first day of the week that at least partially belongs
+ // to the current month
+ curr.setDate(1 - startWeekDay);
+
+ // No month has more than 6 weeks so 6 is a safe maximum for rows.
+ for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
+ for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
+
+ // Actually write the day of month
+ Day day = new Day((Date) curr.clone());
+ day.setStyleName(parent.getStylePrimaryName()
+ + "-calendarpanel-day");
+
+ if (curr.equals(selectedDate)) {
+ day.addStyleDependentName(CN_SELECTED);
+ selectedDay = day;
+ }
+ if (curr.equals(today)) {
+ day.addStyleDependentName(CN_TODAY);
+ }
+ if (curr.equals(focusedDate)) {
+ focusedDay = day;
+ focusedRow = weekOfMonth;
+ if (hasFocus) {
+ day.addStyleDependentName(CN_FOCUSED);
+ }
+ }
+ if (curr.getMonth() != displayedMonth.getMonth()) {
+ day.addStyleDependentName(CN_OFFMONTH);
+ }
+
+ days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
+
+ // ISO week numbers if requested
+ days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
+ isShowISOWeekNumbers());
+ if (isShowISOWeekNumbers()) {
+ final String baseCssClass = parent.getStylePrimaryName()
+ + "-calendarpanel-weeknumber";
+ String weekCssClass = baseCssClass;
+
+ int weekNumber = DateTimeService.getISOWeekNumber(curr);
+
+ days.setHTML(weekOfMonth, 0, "<span class=\""
+ + weekCssClass + "\"" + ">" + weekNumber
+ + "</span>");
+ }
+ curr.setDate(curr.getDate() + 1);
+ }
+ }
+ }
+
+ /**
+ * Do we need the time selector
+ *
+ * @return True if it is required
+ */
+ private boolean isTimeSelectorNeeded() {
+ return getResolution().getCalendarField() > Resolution.DAY
+ .getCalendarField();
+ }
+
+ /**
+ * Updates the calendar and text field with the selected dates.
+ */
+ public void renderCalendar() {
+
+ super.setStylePrimaryName(parent.getStylePrimaryName()
+ + "-calendarpanel");
+
+ if (focusedDate == null) {
+ Date now = new Date();
+ // focusedDate must have zero hours, mins, secs, millisecs
+ focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
+ displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
+ }
+
+ if (getResolution().getCalendarField() <= Resolution.MONTH
+ .getCalendarField() && focusChangeListener != null) {
+ focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
+ }
+
+ final boolean needsMonth = getResolution().getCalendarField() > Resolution.YEAR
+ .getCalendarField();
+ boolean needsBody = getResolution().getCalendarField() >= Resolution.DAY
+ .getCalendarField();
+ buildCalendarHeader(needsMonth);
+ clearCalendarBody(!needsBody);
+ if (needsBody) {
+ buildCalendarBody();
+ }
+
+ if (isTimeSelectorNeeded() && time == null) {
+ time = new VTime();
+ setWidget(2, 0, time);
+ getFlexCellFormatter().setColSpan(2, 0, 5);
+ getFlexCellFormatter().setStyleName(2, 0,
+ parent.getStylePrimaryName() + "-calendarpanel-time");
+ } else if (isTimeSelectorNeeded()) {
+ time.updateTimes();
+ } else if (time != null) {
+ remove(time);
+ }
+
+ initialRenderDone = true;
+ }
+
+ /**
+ * Moves the focus forward the given number of days.
+ */
+ private void focusNextDay(int days) {
+ int oldMonth = focusedDate.getMonth();
+ int oldYear = focusedDate.getYear();
+ focusedDate.setDate(focusedDate.getDate() + days);
+
+ if (focusedDate.getMonth() == oldMonth
+ && focusedDate.getYear() == oldYear) {
+ // Month did not change, only move the selection
+ focusDay(focusedDate);
+ } else {
+ // If the month changed we need to re-render the calendar
+ displayedMonth.setMonth(focusedDate.getMonth());
+ displayedMonth.setYear(focusedDate.getYear());
+ renderCalendar();
+ }
+ }
+
+ /**
+ * Moves the focus backward the given number of days.
+ */
+ private void focusPreviousDay(int days) {
+ focusNextDay(-days);
+ }
+
+ /**
+ * Selects the next month
+ */
+ private void focusNextMonth() {
+
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setMonth(currentMonth + 1);
+ int requestedMonth = (currentMonth + 1) % 12;
+
+ /*
+ * If the selected value was e.g. 31.3 the new value would be 31.4 but
+ * this value is invalid so the new value will be 1.5. This is taken
+ * care of by decreasing the value until we have the correct month.
+ */
+ while (focusedDate.getMonth() != requestedMonth) {
+ focusedDate.setDate(focusedDate.getDate() - 1);
+ }
+ displayedMonth.setMonth(displayedMonth.getMonth() + 1);
+
+ renderCalendar();
+ }
+
+ /**
+ * Selects the previous month
+ */
+ private void focusPreviousMonth() {
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setMonth(currentMonth - 1);
+
+ /*
+ * If the selected value was e.g. 31.12 the new value would be 31.11 but
+ * this value is invalid so the new value will be 1.12. This is taken
+ * care of by decreasing the value until we have the correct month.
+ */
+ while (focusedDate.getMonth() == currentMonth) {
+ focusedDate.setDate(focusedDate.getDate() - 1);
+ }
+ displayedMonth.setMonth(displayedMonth.getMonth() - 1);
+
+ renderCalendar();
+ }
+
+ /**
+ * Selects the previous year
+ */
+ private void focusPreviousYear(int years) {
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setYear(focusedDate.getYear() - years);
+ displayedMonth.setYear(displayedMonth.getYear() - years);
+ /*
+ * If the focused date was a leap day (Feb 29), the new date becomes Mar
+ * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
+ */
+ if (focusedDate.getMonth() != currentMonth) {
+ focusedDate.setDate(0);
+ }
+ renderCalendar();
+ }
+
+ /**
+ * Selects the next year
+ */
+ private void focusNextYear(int years) {
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setYear(focusedDate.getYear() + years);
+ displayedMonth.setYear(displayedMonth.getYear() + years);
+ /*
+ * If the focused date was a leap day (Feb 29), the new date becomes Mar
+ * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
+ */
+ if (focusedDate.getMonth() != currentMonth) {
+ focusedDate.setDate(0);
+ }
+ renderCalendar();
+ }
+
+ /**
+ * Handles a user click on the component
+ *
+ * @param sender
+ * The component that was clicked
+ * @param updateVariable
+ * Should the value field be updated
+ *
+ */
+ private void processClickEvent(Widget sender) {
+ if (!isEnabled() || isReadonly()) {
+ return;
+ }
+ if (sender == prevYear) {
+ focusPreviousYear(1);
+ } else if (sender == nextYear) {
+ focusNextYear(1);
+ } else if (sender == prevMonth) {
+ focusPreviousMonth();
+ } else if (sender == nextMonth) {
+ focusNextMonth();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ handleKeyPress(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ handleKeyPress(event);
+ }
+
+ /**
+ * Handles the keypress from both the onKeyPress event and the onKeyDown
+ * event
+ *
+ * @param event
+ * The keydown/keypress event
+ */
+ private void handleKeyPress(DomEvent<?> event) {
+ if (time != null
+ && time.getElement().isOrHasChild(
+ (Node) event.getNativeEvent().getEventTarget().cast())) {
+ int nativeKeyCode = event.getNativeEvent().getKeyCode();
+ if (nativeKeyCode == getSelectKey()) {
+ onSubmit(); // submit happens if enter key hit down on listboxes
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ return;
+ }
+
+ // Check tabs
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) {
+ if (onTabOut(event)) {
+ return;
+ }
+ }
+
+ // Handle the navigation
+ if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey()
+ || event.getNativeEvent().getMetaKey(), event.getNativeEvent()
+ .getShiftKey())) {
+ event.preventDefault();
+ }
+
+ }
+
+ /**
+ * Notifies submit-listeners of a submit event
+ */
+ private void onSubmit() {
+ if (getSubmitListener() != null) {
+ getSubmitListener().onSubmit();
+ }
+ }
+
+ /**
+ * Notifies submit-listeners of a cancel event
+ */
+ private void onCancel() {
+ if (getSubmitListener() != null) {
+ getSubmitListener().onCancel();
+ }
+ }
+
+ /**
+ * Handles the keyboard navigation when the resolution is set to years.
+ *
+ * @param keycode
+ * The keycode to process
+ * @param ctrl
+ * Is ctrl pressed?
+ * @param shift
+ * is shift pressed
+ * @return Returns true if the keycode was processed, else false
+ */
+ protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Ctrl and Shift selection not supported
+ if (ctrl || shift) {
+ return false;
+ }
+
+ else if (keycode == getPreviousKey()) {
+ focusNextYear(10); // Add 10 years
+ return true;
+ }
+
+ else if (keycode == getForwardKey()) {
+ focusNextYear(1); // Add 1 year
+ return true;
+ }
+
+ else if (keycode == getNextKey()) {
+ focusPreviousYear(10); // Subtract 10 years
+ return true;
+ }
+
+ else if (keycode == getBackwardKey()) {
+ focusPreviousYear(1); // Subtract 1 year
+ return true;
+
+ } else if (keycode == getSelectKey()) {
+ value = (Date) focusedDate.clone();
+ onSubmit();
+ return true;
+
+ } else if (keycode == getResetKey()) {
+ // Restore showing value the selected value
+ focusedDate.setTime(value.getTime());
+ renderCalendar();
+ return true;
+
+ } else if (keycode == getCloseKey()) {
+ // TODO fire listener, on users responsibility??
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the keyboard navigation when the resolution is set to MONTH
+ *
+ * @param keycode
+ * The keycode to handle
+ * @param ctrl
+ * Was the ctrl key pressed?
+ * @param shift
+ * Was the shift key pressed?
+ * @return
+ */
+ protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Ctrl selection not supported
+ if (ctrl) {
+ return false;
+
+ } else if (keycode == getPreviousKey()) {
+ focusNextYear(1); // Add 1 year
+ return true;
+
+ } else if (keycode == getForwardKey()) {
+ focusNextMonth(); // Add 1 month
+ return true;
+
+ } else if (keycode == getNextKey()) {
+ focusPreviousYear(1); // Subtract 1 year
+ return true;
+
+ } else if (keycode == getBackwardKey()) {
+ focusPreviousMonth(); // Subtract 1 month
+ return true;
+
+ } else if (keycode == getSelectKey()) {
+ value = (Date) focusedDate.clone();
+ onSubmit();
+ return true;
+
+ } else if (keycode == getResetKey()) {
+ // Restore showing value the selected value
+ focusedDate.setTime(value.getTime());
+ renderCalendar();
+ return true;
+
+ } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
+
+ // TODO fire close event
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handle keyboard navigation what the resolution is set to DAY
+ *
+ * @param keycode
+ * The keycode to handle
+ * @param ctrl
+ * Was the ctrl key pressed?
+ * @param shift
+ * Was the shift key pressed?
+ * @return Return true if the key press was handled by the method, else
+ * return false.
+ */
+ protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Ctrl key is not in use
+ if (ctrl) {
+ return false;
+ }
+
+ /*
+ * Jumps to the next day.
+ */
+ if (keycode == getForwardKey() && !shift) {
+ focusNextDay(1);
+ return true;
+
+ /*
+ * Jumps to the previous day
+ */
+ } else if (keycode == getBackwardKey() && !shift) {
+ focusPreviousDay(1);
+ return true;
+
+ /*
+ * Jumps one week forward in the calendar
+ */
+ } else if (keycode == getNextKey() && !shift) {
+ focusNextDay(7);
+ return true;
+
+ /*
+ * Jumps one week back in the calendar
+ */
+ } else if (keycode == getPreviousKey() && !shift) {
+ focusPreviousDay(7);
+ return true;
+
+ /*
+ * Selects the value that is chosen
+ */
+ } else if (keycode == getSelectKey() && !shift) {
+ selectFocused();
+ onSubmit(); // submit
+ return true;
+
+ } else if (keycode == getCloseKey()) {
+ onCancel();
+ // TODO close event
+
+ return true;
+
+ /*
+ * Jumps to the next month
+ */
+ } else if (shift && keycode == getForwardKey()) {
+ focusNextMonth();
+ return true;
+
+ /*
+ * Jumps to the previous month
+ */
+ } else if (shift && keycode == getBackwardKey()) {
+ focusPreviousMonth();
+ return true;
+
+ /*
+ * Jumps to the next year
+ */
+ } else if (shift && keycode == getPreviousKey()) {
+ focusNextYear(1);
+ return true;
+
+ /*
+ * Jumps to the previous year
+ */
+ } else if (shift && keycode == getNextKey()) {
+ focusPreviousYear(1);
+ return true;
+
+ /*
+ * Resets the selection
+ */
+ } else if (keycode == getResetKey() && !shift) {
+ // Restore showing value the selected value
+ focusedDate = new Date(value.getYear(), value.getMonth(),
+ value.getDate());
+ displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ renderCalendar();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handles the keyboard navigation
+ *
+ * @param keycode
+ * The key code that was pressed
+ * @param ctrl
+ * Was the ctrl key pressed
+ * @param shift
+ * Was the shift key pressed
+ * @return Return true if key press was handled by the component, else
+ * return false
+ */
+ protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (!isEnabled() || isReadonly()) {
+ return false;
+ }
+
+ else if (resolution == Resolution.YEAR) {
+ return handleNavigationYearMode(keycode, ctrl, shift);
+ }
+
+ else if (resolution == Resolution.MONTH) {
+ return handleNavigationMonthMode(keycode, ctrl, shift);
+ }
+
+ else if (resolution == Resolution.DAY) {
+ return handleNavigationDayMode(keycode, ctrl, shift);
+ }
+
+ else {
+ return handleNavigationDayMode(keycode, ctrl, shift);
+ }
+
+ }
+
+ /**
+ * Returns the reset key which will reset the calendar to the previous
+ * selection. By default this is backspace but it can be overriden to change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getResetKey() {
+ return KeyCodes.KEY_BACKSPACE;
+ }
+
+ /**
+ * Returns the select key which selects the value. By default this is the
+ * enter key but it can be changed to whatever you like by overriding this
+ * method.
+ *
+ * @return
+ */
+ protected int getSelectKey() {
+ return KeyCodes.KEY_ENTER;
+ }
+
+ /**
+ * Returns the key that closes the popup window if this is a VPopopCalendar.
+ * Else this does nothing. By default this is the Escape key but you can
+ * change the key to whatever you want by overriding this method.
+ *
+ * @return
+ */
+ protected int getCloseKey() {
+ return KeyCodes.KEY_ESCAPE;
+ }
+
+ /**
+ * The key that selects the next day in the calendar. By default this is the
+ * right arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getForwardKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * The key that selects the previous day in the calendar. By default this is
+ * the left arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getBackwardKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * The key that selects the next week in the calendar. By default this is
+ * the down arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getNextKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * The key that selects the previous week in the calendar. By default this
+ * is the up arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getPreviousKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
+ * .gwt.event.dom.client.MouseOutEvent)
+ */
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ if (mouseTimer != null) {
+ mouseTimer.cancel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
+ * .gwt.event.dom.client.MouseDownEvent)
+ */
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ // Allow user to click-n-hold for fast-forward or fast-rewind.
+ // Timer is first used for a 500ms delay after mousedown. After that has
+ // elapsed, another timer is triggered to go off every 150ms. Both
+ // timers are cancelled on mouseup or mouseout.
+ if (event.getSource() instanceof VEventButton) {
+ final VEventButton sender = (VEventButton) event.getSource();
+ processClickEvent(sender);
+ mouseTimer = new Timer() {
+ @Override
+ public void run() {
+ mouseTimer = new Timer() {
+ @Override
+ public void run() {
+ processClickEvent(sender);
+ }
+ };
+ mouseTimer.scheduleRepeating(150);
+ }
+ };
+ mouseTimer.schedule(500);
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
+ * .event.dom.client.MouseUpEvent)
+ */
+ @Override
+ public void onMouseUp(MouseUpEvent event) {
+ if (mouseTimer != null) {
+ mouseTimer.cancel();
+ }
+ }
+
+ /**
+ * Sets the data of the Panel.
+ *
+ * @param currentDate
+ * The date to set
+ */
+ public void setDate(Date currentDate) {
+
+ // Check that we are not re-rendering an already active date
+ if (currentDate == value && currentDate != null) {
+ return;
+ }
+
+ Date oldDisplayedMonth = displayedMonth;
+ value = currentDate;
+
+ if (value == null) {
+ focusedDate = displayedMonth = null;
+ } else {
+ focusedDate = new Date(value.getYear(), value.getMonth(),
+ value.getDate());
+ displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ }
+
+ // Re-render calendar if the displayed month is changed,
+ // or if a time selector is needed but does not exist.
+ if ((isTimeSelectorNeeded() && time == null)
+ || oldDisplayedMonth == null || value == null
+ || oldDisplayedMonth.getYear() != value.getYear()
+ || oldDisplayedMonth.getMonth() != value.getMonth()) {
+ renderCalendar();
+ } else {
+ focusDay(focusedDate);
+ selectFocused();
+ if (isTimeSelectorNeeded()) {
+ time.updateTimes();
+ }
+ }
+
+ if (!hasFocus) {
+ focusDay(null);
+ }
+ }
+
+ /**
+ * TimeSelector is a widget consisting of list boxes that modifie the Date
+ * object that is given for.
+ *
+ */
+ public class VTime extends FlowPanel implements ChangeHandler {
+
+ private ListBox hours;
+
+ private ListBox mins;
+
+ private ListBox sec;
+
+ private ListBox ampm;
+
+ /**
+ * Constructor
+ */
+ public VTime() {
+ super();
+ setStyleName(VDateField.CLASSNAME + "-time");
+ buildTime();
+ }
+
+ private ListBox createListBox() {
+ ListBox lb = new ListBox();
+ lb.setStyleName(VNativeSelect.CLASSNAME);
+ lb.addChangeHandler(this);
+ lb.addBlurHandler(VCalendarPanel.this);
+ lb.addFocusHandler(VCalendarPanel.this);
+ return lb;
+ }
+
+ /**
+ * Constructs the ListBoxes and updates their value
+ *
+ * @param redraw
+ * Should new instances of the listboxes be created
+ */
+ private void buildTime() {
+ clear();
+
+ hours = createListBox();
+ if (getDateTimeService().isTwelveHourClock()) {
+ hours.addItem("12");
+ for (int i = 1; i < 12; i++) {
+ hours.addItem((i < 10) ? "0" + i : "" + i);
+ }
+ } else {
+ for (int i = 0; i < 24; i++) {
+ hours.addItem((i < 10) ? "0" + i : "" + i);
+ }
+ }
+
+ hours.addChangeHandler(this);
+ if (getDateTimeService().isTwelveHourClock()) {
+ ampm = createListBox();
+ final String[] ampmText = getDateTimeService().getAmPmStrings();
+ ampm.addItem(ampmText[0]);
+ ampm.addItem(ampmText[1]);
+ ampm.addChangeHandler(this);
+ }
+
+ if (getResolution().getCalendarField() >= Resolution.MINUTE
+ .getCalendarField()) {
+ mins = createListBox();
+ for (int i = 0; i < 60; i++) {
+ mins.addItem((i < 10) ? "0" + i : "" + i);
+ }
+ mins.addChangeHandler(this);
+ }
+ if (getResolution().getCalendarField() >= Resolution.SECOND
+ .getCalendarField()) {
+ sec = createListBox();
+ for (int i = 0; i < 60; i++) {
+ sec.addItem((i < 10) ? "0" + i : "" + i);
+ }
+ sec.addChangeHandler(this);
+ }
+
+ final String delimiter = getDateTimeService().getClockDelimeter();
+ if (isReadonly()) {
+ int h = 0;
+ if (value != null) {
+ h = value.getHours();
+ }
+ if (getDateTimeService().isTwelveHourClock()) {
+ h -= h < 12 ? 0 : 12;
+ }
+ add(new VLabel(h < 10 ? "0" + h : "" + h));
+ } else {
+ add(hours);
+ }
+
+ if (getResolution().getCalendarField() >= Resolution.MINUTE
+ .getCalendarField()) {
+ add(new VLabel(delimiter));
+ if (isReadonly()) {
+ final int m = mins.getSelectedIndex();
+ add(new VLabel(m < 10 ? "0" + m : "" + m));
+ } else {
+ add(mins);
+ }
+ }
+ if (getResolution().getCalendarField() >= Resolution.SECOND
+ .getCalendarField()) {
+ add(new VLabel(delimiter));
+ if (isReadonly()) {
+ final int s = sec.getSelectedIndex();
+ add(new VLabel(s < 10 ? "0" + s : "" + s));
+ } else {
+ add(sec);
+ }
+ }
+ if (getResolution() == Resolution.HOUR) {
+ add(new VLabel(delimiter + "00")); // o'clock
+ }
+ if (getDateTimeService().isTwelveHourClock()) {
+ add(new VLabel(" "));
+ if (isReadonly()) {
+ int i = 0;
+ if (value != null) {
+ i = (value.getHours() < 12) ? 0 : 1;
+ }
+ add(new VLabel(ampm.getItemText(i)));
+ } else {
+ add(ampm);
+ }
+ }
+
+ if (isReadonly()) {
+ return;
+ }
+
+ // Update times
+ updateTimes();
+
+ ListBox lastDropDown = getLastDropDown();
+ lastDropDown.addKeyDownHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ boolean shiftKey = event.getNativeEvent().getShiftKey();
+ if (shiftKey) {
+ return;
+ } else {
+ int nativeKeyCode = event.getNativeKeyCode();
+ if (nativeKeyCode == KeyCodes.KEY_TAB) {
+ onTabOut(event);
+ }
+ }
+ }
+ });
+
+ }
+
+ private ListBox getLastDropDown() {
+ int i = getWidgetCount() - 1;
+ while (i >= 0) {
+ Widget widget = getWidget(i);
+ if (widget instanceof ListBox) {
+ return (ListBox) widget;
+ }
+ i--;
+ }
+ return null;
+ }
+
+ /**
+ * Updates the valus to correspond to the values in value
+ */
+ public void updateTimes() {
+ boolean selected = true;
+ if (value == null) {
+ value = new Date();
+ selected = false;
+ }
+ if (getDateTimeService().isTwelveHourClock()) {
+ int h = value.getHours();
+ ampm.setSelectedIndex(h < 12 ? 0 : 1);
+ h -= ampm.getSelectedIndex() * 12;
+ hours.setSelectedIndex(h);
+ } else {
+ hours.setSelectedIndex(value.getHours());
+ }
+ if (getResolution().getCalendarField() >= Resolution.MINUTE
+ .getCalendarField()) {
+ mins.setSelectedIndex(value.getMinutes());
+ }
+ if (getResolution().getCalendarField() >= Resolution.SECOND
+ .getCalendarField()) {
+ sec.setSelectedIndex(value.getSeconds());
+ }
+ if (getDateTimeService().isTwelveHourClock()) {
+ ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1);
+ }
+
+ hours.setEnabled(isEnabled());
+ if (mins != null) {
+ mins.setEnabled(isEnabled());
+ }
+ if (sec != null) {
+ sec.setEnabled(isEnabled());
+ }
+ if (ampm != null) {
+ ampm.setEnabled(isEnabled());
+ }
+
+ }
+
+ private int getMilliseconds() {
+ return DateTimeService.getMilliseconds(value);
+ }
+
+ private DateTimeService getDateTimeService() {
+ if (dateTimeService == null) {
+ dateTimeService = new DateTimeService();
+ }
+ return dateTimeService;
+ }
+
+ /*
+ * (non-Javadoc) VT
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
+ * .event.dom.client.ChangeEvent)
+ */
+ @Override
+ public void onChange(ChangeEvent event) {
+ /*
+ * Value from dropdowns gets always set for the value. Like year and
+ * month when resolution is month or year.
+ */
+ if (event.getSource() == hours) {
+ int h = hours.getSelectedIndex();
+ if (getDateTimeService().isTwelveHourClock()) {
+ h = h + ampm.getSelectedIndex() * 12;
+ }
+ value.setHours(h);
+ if (timeChangeListener != null) {
+ timeChangeListener.changed(h, value.getMinutes(),
+ value.getSeconds(),
+ DateTimeService.getMilliseconds(value));
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (event.getSource() == mins) {
+ final int m = mins.getSelectedIndex();
+ value.setMinutes(m);
+ if (timeChangeListener != null) {
+ timeChangeListener.changed(value.getHours(), m,
+ value.getSeconds(),
+ DateTimeService.getMilliseconds(value));
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (event.getSource() == sec) {
+ final int s = sec.getSelectedIndex();
+ value.setSeconds(s);
+ if (timeChangeListener != null) {
+ timeChangeListener.changed(value.getHours(),
+ value.getMinutes(), s,
+ DateTimeService.getMilliseconds(value));
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (event.getSource() == ampm) {
+ final int h = hours.getSelectedIndex()
+ + (ampm.getSelectedIndex() * 12);
+ value.setHours(h);
+ if (timeChangeListener != null) {
+ timeChangeListener.changed(h, value.getMinutes(),
+ value.getSeconds(),
+ DateTimeService.getMilliseconds(value));
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ }
+
+ /**
+ * A widget representing a single day in the calendar panel.
+ */
+ private class Day extends InlineHTML {
+ private final Date date;
+
+ Day(Date date) {
+ super("" + date.getDate());
+ this.date = date;
+ addClickHandler(dayClickHandler);
+ }
+
+ public Date getDate() {
+ return date;
+ }
+ }
+
+ public Date getDate() {
+ return value;
+ }
+
+ /**
+ * If true should be returned if the panel will not be used after this
+ * event.
+ *
+ * @param event
+ * @return
+ */
+ protected boolean onTabOut(DomEvent<?> event) {
+ if (focusOutListener != null) {
+ return focusOutListener.onFocusOut(event);
+ }
+ return false;
+ }
+
+ /**
+ * A focus out listener is triggered when the panel loosed focus. This can
+ * happen either after a user clicks outside the panel or tabs out.
+ *
+ * @param listener
+ * The listener to trigger
+ */
+ public void setFocusOutListener(FocusOutListener listener) {
+ focusOutListener = listener;
+ }
+
+ /**
+ * The submit listener is called when the user selects a value from the
+ * calender either by clicking the day or selects it by keyboard.
+ *
+ * @param submitListener
+ * The listener to trigger
+ */
+ public void setSubmitListener(SubmitListener submitListener) {
+ this.submitListener = submitListener;
+ }
+
+ /**
+ * The given FocusChangeListener is notified when the focused date changes
+ * by user either clicking on a new date or by using the keyboard.
+ *
+ * @param listener
+ * The FocusChangeListener to be notified
+ */
+ public void setFocusChangeListener(FocusChangeListener listener) {
+ focusChangeListener = listener;
+ }
+
+ /**
+ * The time change listener is triggered when the user changes the time.
+ *
+ * @param listener
+ */
+ public void setTimeChangeListener(TimeChangeListener listener) {
+ timeChangeListener = listener;
+ }
+
+ /**
+ * Returns the submit listener that listens to selection made from the panel
+ *
+ * @return The listener or NULL if no listener has been set
+ */
+ public SubmitListener getSubmitListener() {
+ return submitListener;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+ @Override
+ public void onBlur(final BlurEvent event) {
+ if (event.getSource() instanceof VCalendarPanel) {
+ hasFocus = false;
+ focusDay(null);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (event.getSource() instanceof VCalendarPanel) {
+ hasFocus = true;
+
+ // Focuses the current day if the calendar shows the days
+ if (focusedDay != null) {
+ focusDay(focusedDate);
+ }
+ }
+ }
+
+ private static final String SUBPART_NEXT_MONTH = "nextmon";
+ private static final String SUBPART_PREV_MONTH = "prevmon";
+
+ private static final String SUBPART_NEXT_YEAR = "nexty";
+ private static final String SUBPART_PREV_YEAR = "prevy";
+ private static final String SUBPART_HOUR_SELECT = "h";
+ private static final String SUBPART_MINUTE_SELECT = "m";
+ private static final String SUBPART_SECS_SELECT = "s";
+ private static final String SUBPART_MSECS_SELECT = "ms";
+ private static final String SUBPART_AMPM_SELECT = "ampm";
+ private static final String SUBPART_DAY = "day";
+ private static final String SUBPART_MONTH_YEAR_HEADER = "header";
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (contains(nextMonth, subElement)) {
+ return SUBPART_NEXT_MONTH;
+ } else if (contains(prevMonth, subElement)) {
+ return SUBPART_PREV_MONTH;
+ } else if (contains(nextYear, subElement)) {
+ return SUBPART_NEXT_YEAR;
+ } else if (contains(prevYear, subElement)) {
+ return SUBPART_PREV_YEAR;
+ } else if (contains(days, subElement)) {
+ // Day, find out which dayOfMonth and use that as the identifier
+ Day day = Util.findWidget(subElement, Day.class);
+ if (day != null) {
+ Date date = day.getDate();
+ int id = date.getDate();
+ // Zero or negative ids map to days of the preceding month,
+ // past-the-end-of-month ids to days of the following month
+ if (date.getMonth() < displayedMonth.getMonth()) {
+ id -= DateTimeService.getNumberOfDaysInMonth(date);
+ } else if (date.getMonth() > displayedMonth.getMonth()) {
+ id += DateTimeService
+ .getNumberOfDaysInMonth(displayedMonth);
+ }
+ return SUBPART_DAY + id;
+ }
+ } else if (time != null) {
+ if (contains(time.hours, subElement)) {
+ return SUBPART_HOUR_SELECT;
+ } else if (contains(time.mins, subElement)) {
+ return SUBPART_MINUTE_SELECT;
+ } else if (contains(time.sec, subElement)) {
+ return SUBPART_SECS_SELECT;
+ } else if (contains(time.ampm, subElement)) {
+ return SUBPART_AMPM_SELECT;
+
+ }
+ } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) {
+ return SUBPART_MONTH_YEAR_HEADER;
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if subElement is inside the widget DOM hierarchy.
+ *
+ * @param w
+ * @param subElement
+ * @return true if {@code w} is a parent of subElement, false otherwise.
+ */
+ private boolean contains(Widget w, Element subElement) {
+ if (w == null || w.getElement() == null) {
+ return false;
+ }
+
+ return w.getElement().isOrHasChild(subElement);
+ }
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if (SUBPART_NEXT_MONTH.equals(subPart)) {
+ return nextMonth.getElement();
+ }
+ if (SUBPART_PREV_MONTH.equals(subPart)) {
+ return prevMonth.getElement();
+ }
+ if (SUBPART_NEXT_YEAR.equals(subPart)) {
+ return nextYear.getElement();
+ }
+ if (SUBPART_PREV_YEAR.equals(subPart)) {
+ return prevYear.getElement();
+ }
+ if (SUBPART_HOUR_SELECT.equals(subPart)) {
+ return time.hours.getElement();
+ }
+ if (SUBPART_MINUTE_SELECT.equals(subPart)) {
+ return time.mins.getElement();
+ }
+ if (SUBPART_SECS_SELECT.equals(subPart)) {
+ return time.sec.getElement();
+ }
+ if (SUBPART_AMPM_SELECT.equals(subPart)) {
+ return time.ampm.getElement();
+ }
+ if (subPart.startsWith(SUBPART_DAY)) {
+ // Zero or negative ids map to days in the preceding month,
+ // past-the-end-of-month ids to days in the following month
+ int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY
+ .length()));
+ Date date = new Date(displayedMonth.getYear(),
+ displayedMonth.getMonth(), dayOfMonth);
+ Iterator<Widget> iter = days.iterator();
+ while (iter.hasNext()) {
+ Widget w = iter.next();
+ if (w instanceof Day) {
+ Day day = (Day) w;
+ if (day.getDate().equals(date)) {
+ return day.getElement();
+ }
+ }
+ }
+ }
+
+ if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
+ return (Element) getCellFormatter().getElement(0, 2).getChild(0);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ if (mouseTimer != null) {
+ mouseTimer.cancel();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Util;
+import com.vaadin.client.VTooltip;
+
+public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
+ Field {
+
+ public static final String CLASSNAME = "v-checkbox";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element errorIndicatorElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Icon icon;
+
+ public VCheckBox() {
+ setStyleName(CLASSNAME);
+
+ Element el = DOM.getFirstChild(getElement());
+ while (el != null) {
+ DOM.sinkEvents(el,
+ (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS));
+ el = DOM.getNextSibling(el);
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (icon != null && (event.getTypeInt() == Event.ONCLICK)
+ && (DOM.eventGetTarget(event) == icon.getElement())) {
+ // Click on icon should do nothing if widget is disabled
+ if (isEnabled()) {
+ setValue(!getValue());
+ }
+ }
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.StyleConstants;
+
+/**
+ * VCCSlayout is a layout which supports configuring it's children with CSS
+ * selectors
+ */
+public class VCssLayout extends FlowPanel {
+
+ public static final String CLASSNAME = "v-csslayout";
+
+ /**
+ * Default constructor
+ */
+ public VCssLayout() {
+ super();
+ setStyleName(CLASSNAME);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void addOrMove(Widget child, int index) {
+ if (child.getParent() == this) {
+ int currentIndex = getWidgetIndex(child);
+ if (index == currentIndex) {
+ return;
+ }
+ }
+ insert(child, index);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class VCustomComponent extends SimplePanel {
+
+ private static final String CLASSNAME = "v-customcomponent";
+
+ public VCustomComponent() {
+ super();
+ setStyleName(CLASSNAME);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.VCaptionWrapper;
+
+/**
+ * Custom Layout implements complex layout defined with HTML template.
+ *
+ * @author Vaadin Ltd
+ *
+ */
+public class VCustomLayout extends ComplexPanel {
+
+ public static final String CLASSNAME = "v-customlayout";
+
+ /** Location-name to containing element in DOM map */
+ private final HashMap<String, Element> locationToElement = new HashMap<String, Element>();
+
+ /** Location-name to contained widget map */
+ final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>();
+
+ /** Widget to captionwrapper map */
+ private final HashMap<Widget, VCaptionWrapper> childWidgetToCaptionWrapper = new HashMap<Widget, VCaptionWrapper>();
+
+ /**
+ * Unexecuted scripts loaded from the template.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String scripts = "";
+
+ /**
+ * Paintable ID of this paintable.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String pid;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ private boolean htmlInitialized = false;
+
+ private Element elementWithNativeResizeFunction;
+
+ private String height = "";
+
+ private String width = "";
+
+ public VCustomLayout() {
+ setElement(DOM.createDiv());
+ // Clear any unwanted styling
+ Style style = getElement().getStyle();
+ style.setBorderStyle(BorderStyle.NONE);
+ style.setMargin(0, Unit.PX);
+ style.setPadding(0, Unit.PX);
+
+ if (BrowserInfo.get().isIE()) {
+ style.setPosition(Position.RELATIVE);
+ }
+
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ }
+
+ /**
+ * Sets widget to given location.
+ *
+ * If location already contains a widget it will be removed.
+ *
+ * @param widget
+ * Widget to be set into location.
+ * @param location
+ * location name where widget will be added
+ *
+ * @throws IllegalArgumentException
+ * if no such location is found in the layout.
+ */
+ public void setWidget(Widget widget, String location) {
+
+ if (widget == null) {
+ return;
+ }
+
+ // If no given location is found in the layout, and exception is throws
+ Element elem = locationToElement.get(location);
+ if (elem == null && hasTemplate()) {
+ throw new IllegalArgumentException("No location " + location
+ + " found");
+ }
+
+ // Get previous widget
+ final Widget previous = locationToWidget.get(location);
+ // NOP if given widget already exists in this location
+ if (previous == widget) {
+ return;
+ }
+
+ if (previous != null) {
+ remove(previous);
+ }
+
+ // if template is missing add element in order
+ if (!hasTemplate()) {
+ elem = getElement();
+ }
+
+ // Add widget to location
+ super.add(widget, elem);
+ locationToWidget.put(location, widget);
+ }
+
+ /** Initialize HTML-layout. */
+ public void initializeHTML(String template, String themeUri) {
+
+ // Connect body of the template to DOM
+ template = extractBodyAndScriptsFromTemplate(template);
+
+ // TODO prefix img src:s here with a regeps, cannot work further with IE
+
+ String relImgPrefix = themeUri + "/layouts/";
+
+ // prefix all relative image elements to point to theme dir with a
+ // regexp search
+ template = template.replaceAll(
+ "<((?:img)|(?:IMG))\\s([^>]*)src=\"((?![a-z]+:)[^/][^\"]+)\"",
+ "<$1 $2src=\"" + relImgPrefix + "$3\"");
+ // also support src attributes without quotes
+ template = template
+ .replaceAll(
+ "<((?:img)|(?:IMG))\\s([^>]*)src=[^\"]((?![a-z]+:)[^/][^ />]+)[ />]",
+ "<$1 $2src=\"" + relImgPrefix + "$3\"");
+ // also prefix relative style="...url(...)..."
+ template = template
+ .replaceAll(
+ "(<[^>]+style=\"[^\"]*url\\()((?![a-z]+:)[^/][^\"]+)(\\)[^>]*>)",
+ "$1 " + relImgPrefix + "$2 $3");
+
+ getElement().setInnerHTML(template);
+
+ // Remap locations to elements
+ locationToElement.clear();
+ scanForLocations(getElement());
+
+ initImgElements();
+
+ elementWithNativeResizeFunction = DOM.getFirstChild(getElement());
+ if (elementWithNativeResizeFunction == null) {
+ elementWithNativeResizeFunction = getElement();
+ }
+ publishResizedFunction(elementWithNativeResizeFunction);
+
+ htmlInitialized = true;
+ }
+
+ private native boolean uriEndsWithSlash()
+ /*-{
+ var path = $wnd.location.pathname;
+ if(path.charAt(path.length - 1) == "/")
+ return true;
+ return false;
+ }-*/;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean hasTemplate() {
+ return htmlInitialized;
+ }
+
+ /** Collect locations from template */
+ private void scanForLocations(Element elem) {
+
+ final String location = elem.getAttribute("location");
+ if (!"".equals(location)) {
+ locationToElement.put(location, elem);
+ elem.setInnerHTML("");
+
+ } else {
+ final int len = DOM.getChildCount(elem);
+ for (int i = 0; i < len; i++) {
+ scanForLocations(DOM.getChild(elem, i));
+ }
+ }
+ }
+
+ /**
+ * Evaluate given script in browser document.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public static native void eval(String script)
+ /*-{
+ try {
+ if (script != null)
+ eval("{ var document = $doc; var window = $wnd; "+ script + "}");
+ } catch (e) {
+ }
+ }-*/;
+
+ /**
+ * Img elements needs some special handling in custom layout. Img elements
+ * will get their onload events sunk. This way custom layout can notify
+ * parent about possible size change.
+ */
+ private void initImgElements() {
+ NodeList<com.google.gwt.dom.client.Element> nodeList = getElement()
+ .getElementsByTagName("IMG");
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList
+ .getItem(i);
+ DOM.sinkEvents((Element) img.cast(), Event.ONLOAD);
+ }
+ }
+
+ /**
+ * Extract body part and script tags from raw html-template.
+ *
+ * Saves contents of all script-tags to private property: scripts. Returns
+ * contents of the body part for the html without script-tags. Also replaces
+ * all _UID_ tags with an unique id-string.
+ *
+ * @param html
+ * Original HTML-template received from server
+ * @return html that is used to create the HTMLPanel.
+ */
+ private String extractBodyAndScriptsFromTemplate(String html) {
+
+ // Replace UID:s
+ html = html.replaceAll("_UID_", pid + "__");
+
+ // Exctract script-tags
+ scripts = "";
+ int endOfPrevScript = 0;
+ int nextPosToCheck = 0;
+ String lc = html.toLowerCase();
+ String res = "";
+ int scriptStart = lc.indexOf("<script", nextPosToCheck);
+ while (scriptStart > 0) {
+ res += html.substring(endOfPrevScript, scriptStart);
+ scriptStart = lc.indexOf(">", scriptStart);
+ final int j = lc.indexOf("</script>", scriptStart);
+ scripts += html.substring(scriptStart + 1, j) + ";";
+ nextPosToCheck = endOfPrevScript = j + "</script>".length();
+ scriptStart = lc.indexOf("<script", nextPosToCheck);
+ }
+ res += html.substring(endOfPrevScript);
+
+ // Extract body
+ html = res;
+ lc = html.toLowerCase();
+ int startOfBody = lc.indexOf("<body");
+ if (startOfBody < 0) {
+ res = html;
+ } else {
+ res = "";
+ startOfBody = lc.indexOf(">", startOfBody) + 1;
+ final int endOfBody = lc.indexOf("</body>", startOfBody);
+ if (endOfBody > startOfBody) {
+ res = html.substring(startOfBody, endOfBody);
+ } else {
+ res = html.substring(startOfBody);
+ }
+ }
+
+ return res;
+ }
+
+ /** Update caption for given widget */
+ public void updateCaption(ComponentConnector paintable) {
+ Widget widget = paintable.getWidget();
+ VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget);
+ if (VCaption.isNeeded(paintable.getState())) {
+ if (wrapper == null) {
+ // Add a wrapper between the layout and the child widget
+ final String loc = getLocation(widget);
+ super.remove(widget);
+ wrapper = new VCaptionWrapper(paintable, client);
+ super.add(wrapper, locationToElement.get(loc));
+ childWidgetToCaptionWrapper.put(widget, wrapper);
+ }
+ wrapper.updateCaption();
+ } else {
+ if (wrapper != null) {
+ // Remove the wrapper and add the widget directly to the layout
+ final String loc = getLocation(widget);
+ super.remove(wrapper);
+ super.add(widget, locationToElement.get(loc));
+ childWidgetToCaptionWrapper.remove(widget);
+ }
+ }
+ }
+
+ /** Get the location of an widget */
+ public String getLocation(Widget w) {
+ for (final Iterator<String> i = locationToWidget.keySet().iterator(); i
+ .hasNext();) {
+ final String location = i.next();
+ if (locationToWidget.get(location) == w) {
+ return location;
+ }
+ }
+ return null;
+ }
+
+ /** Removes given widget from the layout */
+ @Override
+ public boolean remove(Widget w) {
+ final String location = getLocation(w);
+ if (location != null) {
+ locationToWidget.remove(location);
+ }
+ final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w);
+ if (cw != null) {
+ childWidgetToCaptionWrapper.remove(w);
+ return super.remove(cw);
+ } else if (w != null) {
+ return super.remove(w);
+ }
+ return false;
+ }
+
+ /** Adding widget without specifying location is not supported */
+ @Override
+ public void add(Widget w) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** Clear all widgets from the layout */
+ @Override
+ public void clear() {
+ super.clear();
+ locationToWidget.clear();
+ childWidgetToCaptionWrapper.clear();
+ }
+
+ /**
+ * This method is published to JS side with the same name into first DOM
+ * node of custom layout. This way if one implements some resizeable
+ * containers in custom layout he/she can notify children after resize.
+ */
+ public void notifyChildrenOfSizeChange() {
+ client.runDescendentsLayout(this);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ if (elementWithNativeResizeFunction != null) {
+ detachResizedFunction(elementWithNativeResizeFunction);
+ }
+ }
+
+ private native void detachResizedFunction(Element element)
+ /*-{
+ element.notifyChildrenOfSizeChange = null;
+ }-*/;
+
+ private native void publishResizedFunction(Element element)
+ /*-{
+ var self = this;
+ element.notifyChildrenOfSizeChange = $entry(function() {
+ self.@com.vaadin.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()();
+ });
+ }-*/;
+
+ /**
+ * In custom layout one may want to run layout functions made with
+ * JavaScript. This function tests if one exists (with name "iLayoutJS" in
+ * layouts first DOM node) and runs et. Return value is used to determine if
+ * children needs to be notified of size changes.
+ * <p>
+ * Note! When implementing a JS layout function you most likely want to call
+ * notifyChildrenOfSizeChange() function on your custom layouts main
+ * element. That method is used to control whether child components layout
+ * functions are to be run.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param el
+ * @return true if layout function exists and was run successfully, else
+ * false.
+ */
+ public native boolean iLayoutJS(Element el)
+ /*-{
+ if(el && el.iLayoutJS) {
+ try {
+ el.iLayoutJS();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }-*/;
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+ event.cancelBubble(true);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+public class VDateField extends FlowPanel implements Field {
+
+ public static final String CLASSNAME = "v-datefield";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ @Deprecated
+ public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
+ @Deprecated
+ public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
+ @Deprecated
+ public static final Resolution RESOLUTION_DAY = Resolution.DAY;
+ @Deprecated
+ public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
+ @Deprecated
+ public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
+ @Deprecated
+ public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public static String resolutionToString(Resolution res) {
+ if (res.getCalendarField() > Resolution.DAY.getCalendarField()) {
+ return "full";
+ }
+ if (res == Resolution.DAY) {
+ return "day";
+ }
+ if (res == Resolution.MONTH) {
+ return "month";
+ }
+ return "year";
+ }
+
+ protected Resolution currentResolution = Resolution.YEAR;
+
+ protected String currentLocale;
+
+ protected boolean readonly;
+
+ protected boolean enabled;
+
+ /**
+ * The date that is selected in the date field. Null if an invalid date is
+ * specified.
+ */
+ private Date date = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public DateTimeService dts;
+
+ protected boolean showISOWeekNumbers = false;
+
+ public VDateField() {
+ setStyleName(CLASSNAME);
+ dts = new DateTimeService();
+ }
+
+ /**
+ * We need this redundant native function because Java's Date object doesn't
+ * have a setMilliseconds method.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public static native double getTime(int y, int m, int d, int h, int mi,
+ int s, int ms)
+ /*-{
+ try {
+ var date = new Date(2000,1,1,1); // don't use current date here
+ if(y && y >= 0) date.setFullYear(y);
+ if(m && m >= 1) date.setMonth(m-1);
+ if(d && d >= 0) date.setDate(d);
+ if(h >= 0) date.setHours(h);
+ if(mi >= 0) date.setMinutes(mi);
+ if(s >= 0) date.setSeconds(s);
+ if(ms >= 0) date.setMilliseconds(ms);
+ return date.getTime();
+ } catch (e) {
+ // TODO print some error message on the console
+ //console.log(e);
+ return (new Date()).getTime();
+ }
+ }-*/;
+
+ public int getMilliseconds() {
+ return DateTimeService.getMilliseconds(date);
+ }
+
+ public void setMilliseconds(int ms) {
+ DateTimeService.setMilliseconds(date, ms);
+ }
+
+ public Resolution getCurrentResolution() {
+ return currentResolution;
+ }
+
+ public void setCurrentResolution(Resolution currentResolution) {
+ this.currentResolution = currentResolution;
+ }
+
+ public String getCurrentLocale() {
+ return currentLocale;
+ }
+
+ public void setCurrentLocale(String currentLocale) {
+ this.currentLocale = currentLocale;
+ }
+
+ public Date getCurrentDate() {
+ return date;
+ }
+
+ public void setCurrentDate(Date date) {
+ this.date = date;
+ }
+
+ public boolean isImmediate() {
+ return immediate;
+ }
+
+ public void setImmediate(boolean immediate) {
+ this.immediate = immediate;
+ }
+
+ public boolean isReadonly() {
+ return readonly;
+ }
+
+ public void setReadonly(boolean readonly) {
+ this.readonly = readonly;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public DateTimeService getDateTimeService() {
+ return dts;
+ }
+
+ public String getId() {
+ return paintableId;
+ }
+
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ /**
+ * Returns whether ISO 8601 week numbers should be shown in the date
+ * selector or not. ISO 8601 defines that a week always starts with a Monday
+ * so the week numbers are only shown if this is the case.
+ *
+ * @return true if week number should be shown, false otherwise
+ */
+ public boolean isShowISOWeekNumbers() {
+ return showISOWeekNumbers;
+ }
+
+ public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
+ this.showISOWeekNumbers = showISOWeekNumbers;
+ }
+
+ /**
+ * Returns a copy of the current date. Modifying the returned date will not
+ * modify the value of this VDateField. Use {@link #setDate(Date)} to change
+ * the current date.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @return A copy of the current date
+ */
+ public Date getDate() {
+ Date current = getCurrentDate();
+ if (current == null) {
+ return null;
+ } else {
+ return (Date) getCurrentDate().clone();
+ }
+ }
+
+ /**
+ * Sets the current date for this VDateField.
+ *
+ * @param date
+ * The new date to use
+ */
+ protected void setDate(Date date) {
+ this.date = date;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.DomEvent;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
+import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+/**
+ * A client side implementation for InlineDateField
+ */
+public class VDateFieldCalendar extends VDateField {
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final VCalendarPanel calendarPanel;
+
+ public VDateFieldCalendar() {
+ super();
+ calendarPanel = new VCalendarPanel();
+ calendarPanel.setParentField(this);
+ add(calendarPanel);
+ calendarPanel.setSubmitListener(new SubmitListener() {
+ @Override
+ public void onSubmit() {
+ updateValueFromPanel();
+ }
+
+ @Override
+ public void onCancel() {
+ // TODO Auto-generated method stub
+
+ }
+ });
+ calendarPanel.setFocusOutListener(new FocusOutListener() {
+ @Override
+ public boolean onFocusOut(DomEvent<?> event) {
+ updateValueFromPanel();
+ return false;
+ }
+ });
+ }
+
+ /**
+ * TODO refactor: almost same method as in VPopupCalendar.updateValue
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+
+ @SuppressWarnings("deprecation")
+ public void updateValueFromPanel() {
+
+ // If field is invisible at the beginning, client can still be null when
+ // this function is called.
+ if (getClient() == null) {
+ return;
+ }
+
+ Date date2 = calendarPanel.getDate();
+ Date currentDate = getCurrentDate();
+ if (currentDate == null || date2.getTime() != currentDate.getTime()) {
+ setCurrentDate((Date) date2.clone());
+ getClient().updateVariable(getId(), "year", date2.getYear() + 1900,
+ false);
+ if (getCurrentResolution().getCalendarField() > Resolution.YEAR
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "month",
+ date2.getMonth() + 1, false);
+ if (getCurrentResolution().getCalendarField() > Resolution.MONTH
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "day", date2.getDate(),
+ false);
+ if (getCurrentResolution().getCalendarField() > Resolution.DAY
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "hour",
+ date2.getHours(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.HOUR
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "min",
+ date2.getMinutes(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "sec",
+ date2.getSeconds(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.SECOND
+ .getCalendarField()) {
+ getClient().updateVariable(
+ getId(),
+ "msec",
+ DateTimeService
+ .getMilliseconds(date2),
+ false);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (isImmediate()) {
+ getClient().sendPendingVariableChanges();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.TouchStartEvent;
+import com.google.gwt.event.dom.client.TouchStartHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.xhr.client.ReadyStateChangeHandler;
+import com.google.gwt.xhr.client.XMLHttpRequest;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.ui.dd.DDUtil;
+import com.vaadin.client.ui.dd.VAbstractDropHandler;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragAndDropManager;
+import com.vaadin.client.ui.dd.VDragEvent;
+import com.vaadin.client.ui.dd.VDropHandler;
+import com.vaadin.client.ui.dd.VHasDropHandler;
+import com.vaadin.client.ui.dd.VHtml5DragEvent;
+import com.vaadin.client.ui.dd.VHtml5File;
+import com.vaadin.client.ui.dd.VTransferable;
+import com.vaadin.shared.ui.dd.HorizontalDropLocation;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+
+/**
+ *
+ * Must have features pending:
+ *
+ * drop details: locations + sizes in document hierarchy up to wrapper
+ *
+ */
+public class VDragAndDropWrapper extends VCustomComponent implements
+ VHasDropHandler {
+
+ private static final String CLASSNAME = "v-ddwrapper";
+ protected static final String DRAGGABLE = "draggable";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean hasTooltip = false;
+
+ public VDragAndDropWrapper() {
+ super();
+
+ hookHtml5Events(getElement());
+ setStyleName(CLASSNAME);
+ addDomHandler(new MouseDownHandler() {
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ if (startDrag(event.getNativeEvent())) {
+ event.preventDefault(); // prevent text selection
+ }
+ }
+ }, MouseDownEvent.getType());
+
+ addDomHandler(new TouchStartHandler() {
+
+ @Override
+ public void onTouchStart(TouchStartEvent event) {
+ if (startDrag(event.getNativeEvent())) {
+ /*
+ * Dont let eg. panel start scrolling.
+ */
+ event.stopPropagation();
+ }
+ }
+ }, TouchStartEvent.getType());
+
+ sinkEvents(Event.TOUCHEVENTS);
+ }
+
+ /**
+ * Starts a drag and drop operation from mousedown or touchstart event if
+ * required conditions are met.
+ *
+ * @param event
+ * @return true if the event was handled as a drag start event
+ */
+ private boolean startDrag(NativeEvent event) {
+ if (dragStartMode == WRAPPER || dragStartMode == COMPONENT) {
+ VTransferable transferable = new VTransferable();
+ transferable.setDragSource(ConnectorMap.get(client).getConnector(
+ VDragAndDropWrapper.this));
+
+ ComponentConnector paintable = Util.findPaintable(client,
+ (Element) event.getEventTarget().cast());
+ Widget widget = paintable.getWidget();
+ transferable.setData("component", paintable);
+ VDragEvent dragEvent = VDragAndDropManager.get().startDrag(
+ transferable, event, true);
+
+ transferable.setData("mouseDown", MouseEventDetailsBuilder
+ .buildMouseEventDetails(event).serialize());
+
+ if (dragStartMode == WRAPPER) {
+ dragEvent.createDragImage(getElement(), true);
+ } else {
+ dragEvent.createDragImage(widget.getElement(), true);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ protected final static int NONE = 0;
+ protected final static int COMPONENT = 1;
+ protected final static int WRAPPER = 2;
+ protected final static int HTML5 = 3;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int dragStartMode;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VAbstractDropHandler dropHandler;
+
+ private VDragEvent vaadinDragEvent;
+
+ int filecounter = 0;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Map<String, String> fileIdToReceiver;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ValueMap html5DataFlavors;
+
+ private Element dragStartElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void initDragStartMode() {
+ Element div = getElement();
+ if (dragStartMode == HTML5) {
+ if (dragStartElement == null) {
+ dragStartElement = getDragStartElement();
+ dragStartElement.setPropertyBoolean(DRAGGABLE, true);
+ VConsole.log("draggable = "
+ + dragStartElement.getPropertyBoolean(DRAGGABLE));
+ hookHtml5DragStart(dragStartElement);
+ VConsole.log("drag start listeners hooked.");
+ }
+ } else {
+ dragStartElement = null;
+ if (div.hasAttribute(DRAGGABLE)) {
+ div.removeAttribute(DRAGGABLE);
+ }
+ }
+ }
+
+ protected Element getDragStartElement() {
+ return getElement();
+ }
+
+ private boolean uploading;
+
+ private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
+
+ @Override
+ public void onReadyStateChange(XMLHttpRequest xhr) {
+ if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+ // visit server for possible
+ // variable changes
+ client.sendPendingVariableChanges();
+ uploading = false;
+ startNextUpload();
+ xhr.clearOnReadyStateChange();
+ }
+ }
+ };
+ private Timer dragleavetimer;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void startNextUpload() {
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ if (!uploading) {
+ if (fileIds.size() > 0) {
+
+ uploading = true;
+ final Integer fileId = fileIds.remove(0);
+ VHtml5File file = files.remove(0);
+ final String receiverUrl = client
+ .translateVaadinUri(fileIdToReceiver
+ .remove(fileId.toString()));
+ ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
+ .create();
+ extendedXHR
+ .setOnReadyStateChange(readyStateChangeHandler);
+ extendedXHR.open("POST", receiverUrl);
+ extendedXHR.postFile(file);
+ }
+ }
+
+ }
+ });
+
+ }
+
+ public boolean html5DragStart(VHtml5DragEvent event) {
+ if (dragStartMode == HTML5) {
+ /*
+ * Populate html5 payload with dataflavors from the serverside
+ */
+ JsArrayString flavors = html5DataFlavors.getKeyArray();
+ for (int i = 0; i < flavors.length(); i++) {
+ String flavor = flavors.get(i);
+ event.setHtml5DataFlavor(flavor,
+ html5DataFlavors.getString(flavor));
+ }
+ event.setEffectAllowed("copy");
+ return true;
+ }
+ return false;
+ }
+
+ public boolean html5DragEnter(VHtml5DragEvent event) {
+ if (dropHandler == null) {
+ return true;
+ }
+ try {
+ if (dragleavetimer != null) {
+ // returned quickly back to wrapper
+ dragleavetimer.cancel();
+ dragleavetimer = null;
+ }
+ if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) {
+ VTransferable transferable = new VTransferable();
+ transferable.setDragSource(ConnectorMap.get(client)
+ .getConnector(this));
+
+ vaadinDragEvent = VDragAndDropManager.get().startDrag(
+ transferable, event, false);
+ VDragAndDropManager.get().setCurrentDropHandler(
+ getDropHandler());
+ }
+ try {
+ event.preventDefault();
+ event.stopPropagation();
+ } catch (Exception e) {
+ // VConsole.log("IE9 fails");
+ }
+ return false;
+ } catch (Exception e) {
+ GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+ return true;
+ }
+ }
+
+ public boolean html5DragLeave(VHtml5DragEvent event) {
+ if (dropHandler == null) {
+ return true;
+ }
+
+ try {
+ dragleavetimer = new Timer() {
+
+ @Override
+ public void run() {
+ // Yes, dragleave happens before drop. Makes no sense to me.
+ // IMO shouldn't fire leave at all if drop happens (I guess
+ // this
+ // is what IE does).
+ // In Vaadin we fire it only if drop did not happen.
+ if (vaadinDragEvent != null
+ && VDragAndDropManager.get()
+ .getCurrentDropHandler() == getDropHandler()) {
+ VDragAndDropManager.get().interruptDrag();
+ }
+ }
+ };
+ dragleavetimer.schedule(350);
+ try {
+ event.preventDefault();
+ event.stopPropagation();
+ } catch (Exception e) {
+ // VConsole.log("IE9 fails");
+ }
+ return false;
+ } catch (Exception e) {
+ GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+ return true;
+ }
+ }
+
+ public boolean html5DragOver(VHtml5DragEvent event) {
+ if (dropHandler == null) {
+ return true;
+ }
+
+ if (dragleavetimer != null) {
+ // returned quickly back to wrapper
+ dragleavetimer.cancel();
+ dragleavetimer = null;
+ }
+
+ vaadinDragEvent.setCurrentGwtEvent(event);
+ getDropHandler().dragOver(vaadinDragEvent);
+
+ String s = event.getEffectAllowed();
+ if ("all".equals(s) || s.contains("opy")) {
+ event.setDropEffect("copy");
+ } else {
+ event.setDropEffect(s);
+ }
+
+ try {
+ event.preventDefault();
+ event.stopPropagation();
+ } catch (Exception e) {
+ // VConsole.log("IE9 fails");
+ }
+ return false;
+ }
+
+ public boolean html5DragDrop(VHtml5DragEvent event) {
+ if (dropHandler == null || !currentlyValid) {
+ return true;
+ }
+ try {
+
+ VTransferable transferable = vaadinDragEvent.getTransferable();
+
+ JsArrayString types = event.getTypes();
+ for (int i = 0; i < types.length(); i++) {
+ String type = types.get(i);
+ if (isAcceptedType(type)) {
+ String data = event.getDataAsText(type);
+ if (data != null) {
+ transferable.setData(type, data);
+ }
+ }
+ }
+
+ int fileCount = event.getFileCount();
+ if (fileCount > 0) {
+ transferable.setData("filecount", fileCount);
+ for (int i = 0; i < fileCount; i++) {
+ final int fileId = filecounter++;
+ final VHtml5File file = event.getFile(i);
+ transferable.setData("fi" + i, "" + fileId);
+ transferable.setData("fn" + i, file.getName());
+ transferable.setData("ft" + i, file.getType());
+ transferable.setData("fs" + i, file.getSize());
+ queueFilePost(fileId, file);
+ }
+
+ }
+
+ VDragAndDropManager.get().endDrag();
+ vaadinDragEvent = null;
+ try {
+ event.preventDefault();
+ event.stopPropagation();
+ } catch (Exception e) {
+ // VConsole.log("IE9 fails");
+ }
+ return false;
+ } catch (Exception e) {
+ GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+ return true;
+ }
+
+ }
+
+ protected String[] acceptedTypes = new String[] { "Text", "Url",
+ "text/html", "text/plain", "text/rtf" };
+
+ private boolean isAcceptedType(String type) {
+ for (String t : acceptedTypes) {
+ if (t.equals(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static class ExtendedXHR extends XMLHttpRequest {
+
+ protected ExtendedXHR() {
+ }
+
+ public final native void postFile(VHtml5File file)
+ /*-{
+
+ this.setRequestHeader('Content-Type', 'multipart/form-data');
+ this.send(file);
+ }-*/;
+
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public List<Integer> fileIds = new ArrayList<Integer>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public List<VHtml5File> files = new ArrayList<VHtml5File>();
+
+ private void queueFilePost(final int fileId, final VHtml5File file) {
+ fileIds.add(fileId);
+ files.add(file);
+ }
+
+ @Override
+ public VDropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ protected VerticalDropLocation verticalDropLocation;
+ protected HorizontalDropLocation horizontalDropLocation;
+ private VerticalDropLocation emphasizedVDrop;
+ private HorizontalDropLocation emphasizedHDrop;
+
+ /**
+ * Flag used by html5 dd
+ */
+ private boolean currentlyValid;
+
+ private static final String OVER_STYLE = "v-ddwrapper-over";
+
+ public class CustomDropHandler extends VAbstractDropHandler {
+
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ updateDropDetails(drag);
+ currentlyValid = false;
+ super.dragEnter(drag);
+ }
+
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis(true);
+ dragleavetimer = null;
+ }
+
+ @Override
+ public void dragOver(final VDragEvent drag) {
+ boolean detailsChanged = updateDropDetails(drag);
+ if (detailsChanged) {
+ currentlyValid = false;
+ validate(new VAcceptCallback() {
+
+ @Override
+ public void accepted(VDragEvent event) {
+ dragAccepted(drag);
+ }
+ }, drag);
+ }
+ }
+
+ @Override
+ public boolean drop(VDragEvent drag) {
+ deEmphasis(true);
+
+ Map<String, Object> dd = drag.getDropDetails();
+
+ // this is absolute layout based, and we may want to set
+ // component
+ // relatively to where the drag ended.
+ // need to add current location of the drop area
+
+ int absoluteLeft = getAbsoluteLeft();
+ int absoluteTop = getAbsoluteTop();
+
+ dd.put("absoluteLeft", absoluteLeft);
+ dd.put("absoluteTop", absoluteTop);
+
+ if (verticalDropLocation != null) {
+ dd.put("verticalLocation", verticalDropLocation.toString());
+ dd.put("horizontalLocation", horizontalDropLocation.toString());
+ }
+
+ return super.drop(drag);
+ }
+
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ currentlyValid = true;
+ emphasis(drag);
+ }
+
+ @Override
+ public ComponentConnector getConnector() {
+ return ConnectorMap.get(client).getConnector(
+ VDragAndDropWrapper.this);
+ }
+
+ @Override
+ public ApplicationConnection getApplicationConnection() {
+ return client;
+ }
+
+ }
+
+ protected native void hookHtml5DragStart(Element el)
+ /*-{
+ var me = this;
+ el.addEventListener("dragstart", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }), false);
+ }-*/;
+
+ /**
+ * Prototype code, memory leak risk.
+ *
+ * @param el
+ */
+ protected native void hookHtml5Events(Element el)
+ /*-{
+ var me = this;
+
+ el.addEventListener("dragenter", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }), false);
+
+ el.addEventListener("dragleave", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }), false);
+
+ el.addEventListener("dragover", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }), false);
+
+ el.addEventListener("drop", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }), false);
+ }-*/;
+
+ public boolean updateDropDetails(VDragEvent drag) {
+ VerticalDropLocation oldVL = verticalDropLocation;
+ verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(),
+ drag.getCurrentGwtEvent(), 0.2);
+ drag.getDropDetails().put("verticalLocation",
+ verticalDropLocation.toString());
+ HorizontalDropLocation oldHL = horizontalDropLocation;
+ horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(),
+ drag.getCurrentGwtEvent(), 0.2);
+ drag.getDropDetails().put("horizontalLocation",
+ horizontalDropLocation.toString());
+ if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected void deEmphasis(boolean doLayout) {
+ if (emphasizedVDrop != null) {
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false);
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
+ + emphasizedVDrop.toString().toLowerCase(), false);
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
+ + emphasizedHDrop.toString().toLowerCase(), false);
+ }
+ if (doLayout) {
+ notifySizePotentiallyChanged();
+ }
+ }
+
+ private void notifySizePotentiallyChanged() {
+ LayoutManager.get(client).setNeedsMeasure(
+ ConnectorMap.get(client).getConnector(getElement()));
+ }
+
+ protected void emphasis(VDragEvent drag) {
+ deEmphasis(false);
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true);
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
+ + verticalDropLocation.toString().toLowerCase(), true);
+ VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
+ + horizontalDropLocation.toString().toLowerCase(), true);
+ emphasizedVDrop = verticalDropLocation;
+ emphasizedHDrop = horizontalDropLocation;
+
+ // TODO build (to be an example) an emphasis mode where drag image
+ // is fitted before or after the content
+ notifySizePotentiallyChanged();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.VConsole;
+
+public class VDragAndDropWrapperIE extends VDragAndDropWrapper {
+ private AnchorElement anchor = null;
+
+ @Override
+ protected Element getDragStartElement() {
+ VConsole.log("IE get drag start element...");
+ Element div = getElement();
+ if (dragStartMode == HTML5) {
+ if (anchor == null) {
+ anchor = Document.get().createAnchorElement();
+ anchor.setHref("#");
+ anchor.setClassName("drag-start");
+ div.appendChild(anchor);
+ }
+ VConsole.log("IE get drag start element...");
+ return (Element) anchor.cast();
+ } else {
+ if (anchor != null) {
+ div.removeChild(anchor);
+ anchor = null;
+ }
+ return div;
+ }
+ }
+
+ @Override
+ protected native void hookHtml5DragStart(Element el)
+ /*-{
+ var me = this;
+
+ el.attachEvent("ondragstart", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }));
+ }-*/;
+
+ @Override
+ protected native void hookHtml5Events(Element el)
+ /*-{
+ var me = this;
+
+ el.attachEvent("ondragenter", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }));
+
+ el.attachEvent("ondragleave", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }));
+
+ el.attachEvent("ondragover", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }));
+
+ el.attachEvent("ondrop", $entry(function(ev) {
+ return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+ }));
+ }-*/;
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import com.google.gwt.user.client.ui.TextBox;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.menubar.MenuBar;
+import com.vaadin.client.ui.menubar.MenuItem;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.ui.combobox.FilteringMode;
+
+/**
+ * Client side implementation of the Select component.
+ *
+ * TODO needs major refactoring (to be extensible etc)
+ */
+@SuppressWarnings("deprecation")
+public class VFilterSelect extends Composite implements Field, KeyDownHandler,
+ KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
+ SubPartAware {
+
+ /**
+ * Represents a suggestion in the suggestion popup box
+ */
+ public class FilterSelectSuggestion implements Suggestion, Command {
+
+ private final String key;
+ private final String caption;
+ private String iconUri;
+
+ /**
+ * Constructor
+ *
+ * @param uidl
+ * The UIDL recieved from the server
+ */
+ public FilterSelectSuggestion(UIDL uidl) {
+ key = uidl.getStringAttribute("key");
+ caption = uidl.getStringAttribute("caption");
+ if (uidl.hasAttribute("icon")) {
+ iconUri = client.translateVaadinUri(uidl
+ .getStringAttribute("icon"));
+ }
+ }
+
+ /**
+ * Gets the visible row in the popup as a HTML string. The string
+ * contains an image tag with the rows icon (if an icon has been
+ * specified) and the caption of the item
+ */
+
+ @Override
+ public String getDisplayString() {
+ final StringBuffer sb = new StringBuffer();
+ if (iconUri != null) {
+ sb.append("<img src=\"");
+ sb.append(Util.escapeAttribute(iconUri));
+ sb.append("\" alt=\"\" class=\"v-icon\" />");
+ }
+ String content;
+ if ("".equals(caption)) {
+ // Ensure that empty options use the same height as other
+ // options and are not collapsed (#7506)
+ content = " ";
+ } else {
+ content = Util.escapeHTML(caption);
+ }
+ sb.append("<span>" + content + "</span>");
+ return sb.toString();
+ }
+
+ /**
+ * Get a string that represents this item. This is used in the text box.
+ */
+
+ @Override
+ public String getReplacementString() {
+ return caption;
+ }
+
+ /**
+ * Get the option key which represents the item on the server side.
+ *
+ * @return The key of the item
+ */
+ public int getOptionKey() {
+ return Integer.parseInt(key);
+ }
+
+ /**
+ * Get the URI of the icon. Used when constructing the displayed option.
+ *
+ * @return
+ */
+ public String getIconUri() {
+ return iconUri;
+ }
+
+ /**
+ * Executes a selection of this item.
+ */
+
+ @Override
+ public void execute() {
+ onSuggestionSelected(this);
+ }
+ }
+
+ /**
+ * Represents the popup box with the selection options. Wraps a suggestion
+ * menu.
+ */
+ public class SuggestionPopup extends VOverlay implements PositionCallback,
+ CloseHandler<PopupPanel> {
+
+ private static final int Z_INDEX = 30000;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final SuggestionMenu menu;
+
+ private final Element up = DOM.createDiv();
+ private final Element down = DOM.createDiv();
+ private final Element status = DOM.createDiv();
+
+ private boolean isPagingEnabled = true;
+
+ private long lastAutoClosed;
+
+ private int popupOuterPadding = -1;
+
+ private int topPosition;
+
+ /**
+ * Default constructor
+ */
+ SuggestionPopup() {
+ super(true, false, true);
+ setOwner(VFilterSelect.this);
+ menu = new SuggestionMenu();
+ setWidget(menu);
+
+ getElement().getStyle().setZIndex(Z_INDEX);
+
+ final Element root = getContainerElement();
+
+ up.setInnerHTML("<span>Prev</span>");
+ DOM.sinkEvents(up, Event.ONCLICK);
+
+ down.setInnerHTML("<span>Next</span>");
+ DOM.sinkEvents(down, Event.ONCLICK);
+
+ root.insertFirst(up);
+ root.appendChild(down);
+ root.appendChild(status);
+
+ DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL);
+ addCloseHandler(this);
+ }
+
+ /**
+ * Shows the popup where the user can see the filtered options
+ *
+ * @param currentSuggestions
+ * The filtered suggestions
+ * @param currentPage
+ * The current page number
+ * @param totalSuggestions
+ * The total amount of suggestions
+ */
+ public void showSuggestions(
+ final Collection<FilterSelectSuggestion> currentSuggestions,
+ final int currentPage, final int totalSuggestions) {
+
+ /*
+ * We need to defer the opening of the popup so that the parent DOM
+ * has stabilized so we can calculate an absolute top and left
+ * correctly. This issue manifests when a Combobox is placed in
+ * another popupView which also needs to calculate the absoluteTop()
+ * to position itself. #9768
+ */
+ final SuggestionPopup popup = this;
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ // Add TT anchor point
+ getElement().setId("VAADIN_COMBOBOX_OPTIONLIST");
+
+ menu.setSuggestions(currentSuggestions);
+ final int x = VFilterSelect.this.getAbsoluteLeft();
+
+ topPosition = tb.getAbsoluteTop();
+ topPosition += tb.getOffsetHeight();
+
+ setPopupPosition(x, topPosition);
+
+ int nullOffset = (nullSelectionAllowed
+ && "".equals(lastFilter) ? 1 : 0);
+ boolean firstPage = (currentPage == 0);
+ final int first = currentPage * pageLength + 1
+ - (firstPage ? 0 : nullOffset);
+ final int last = first
+ + currentSuggestions.size()
+ - 1
+ - (firstPage && "".equals(lastFilter) ? nullOffset
+ : 0);
+ final int matches = totalSuggestions - nullOffset;
+ if (last > 0) {
+ // nullsel not counted, as requested by user
+ status.setInnerText((matches == 0 ? 0 : first) + "-"
+ + last + "/" + matches);
+ } else {
+ status.setInnerText("");
+ }
+ // We don't need to show arrows or statusbar if there is
+ // only one
+ // page
+ if (totalSuggestions <= pageLength || pageLength == 0) {
+ setPagingEnabled(false);
+ } else {
+ setPagingEnabled(true);
+ }
+ setPrevButtonActive(first > 1);
+ setNextButtonActive(last < matches);
+
+ // clear previously fixed width
+ menu.setWidth("");
+ menu.getElement().getFirstChildElement().getStyle()
+ .clearWidth();
+
+ setPopupPositionAndShow(popup);
+ }
+ });
+ }
+
+ /**
+ * Should the next page button be visible to the user?
+ *
+ * @param active
+ */
+ private void setNextButtonActive(boolean active) {
+ if (active) {
+ DOM.sinkEvents(down, Event.ONCLICK);
+ down.setClassName(VFilterSelect.this.getStylePrimaryName()
+ + "-nextpage");
+ } else {
+ DOM.sinkEvents(down, 0);
+ down.setClassName(VFilterSelect.this.getStylePrimaryName()
+ + "-nextpage-off");
+ }
+ }
+
+ /**
+ * Should the previous page button be visible to the user
+ *
+ * @param active
+ */
+ private void setPrevButtonActive(boolean active) {
+ if (active) {
+ DOM.sinkEvents(up, Event.ONCLICK);
+ up.setClassName(VFilterSelect.this.getStylePrimaryName()
+ + "-prevpage");
+ } else {
+ DOM.sinkEvents(up, 0);
+ up.setClassName(VFilterSelect.this.getStylePrimaryName()
+ + "-prevpage-off");
+ }
+
+ }
+
+ /**
+ * Selects the next item in the filtered selections
+ */
+ public void selectNextItem() {
+ final MenuItem cur = menu.getSelectedItem();
+ final int index = 1 + menu.getItems().indexOf(cur);
+ if (menu.getItems().size() > index) {
+ final MenuItem newSelectedItem = menu.getItems().get(index);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length() - lastFilter.length());
+
+ } else if (hasNextPage()) {
+ selectPopupItemWhenResponseIsReceived = Select.FIRST;
+ filterOptions(currentPage + 1, lastFilter);
+ }
+ }
+
+ /**
+ * Selects the previous item in the filtered selections
+ */
+ public void selectPrevItem() {
+ final MenuItem cur = menu.getSelectedItem();
+ final int index = -1 + menu.getItems().indexOf(cur);
+ if (index > -1) {
+ final MenuItem newSelectedItem = menu.getItems().get(index);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length() - lastFilter.length());
+ } else if (index == -1) {
+ if (currentPage > 0) {
+ selectPopupItemWhenResponseIsReceived = Select.LAST;
+ filterOptions(currentPage - 1, lastFilter);
+ }
+ } else {
+ final MenuItem newSelectedItem = menu.getItems().get(
+ menu.getItems().size() - 1);
+ menu.selectItem(newSelectedItem);
+ tb.setText(newSelectedItem.getText());
+ tb.setSelectionRange(lastFilter.length(), newSelectedItem
+ .getText().length() - lastFilter.length());
+ }
+ }
+
+ /*
+ * Using a timer to scroll up or down the pages so when we receive lots
+ * of consecutive mouse wheel events the pages does not flicker.
+ */
+ private LazyPageScroller lazyPageScroller = new LazyPageScroller();
+
+ private class LazyPageScroller extends Timer {
+ private int pagesToScroll = 0;
+
+ @Override
+ public void run() {
+ if (pagesToScroll != 0) {
+ if (!waitingForFilteringResponse) {
+ /*
+ * Avoid scrolling while we are waiting for a response
+ * because otherwise the waiting flag will be reset in
+ * the first response and the second response will be
+ * ignored, causing an empty popup...
+ *
+ * As long as the scrolling delay is suitable
+ * double/triple clicks will work by scrolling two or
+ * three pages at a time and this should not be a
+ * problem.
+ */
+ filterOptions(currentPage + pagesToScroll, lastFilter);
+ }
+ pagesToScroll = 0;
+ }
+ }
+
+ public void scrollUp() {
+ if (currentPage + pagesToScroll > 0) {
+ pagesToScroll--;
+ cancel();
+ schedule(200);
+ }
+ }
+
+ public void scrollDown() {
+ if (totalMatches > (currentPage + pagesToScroll + 1)
+ * pageLength) {
+ pagesToScroll++;
+ cancel();
+ schedule(200);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ final Element target = DOM.eventGetTarget(event);
+ if (target == up || target == DOM.getChild(up, 0)) {
+ lazyPageScroller.scrollUp();
+ } else if (target == down || target == DOM.getChild(down, 0)) {
+ lazyPageScroller.scrollDown();
+ }
+ } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) {
+ int velocity = event.getMouseWheelVelocityY();
+ if (velocity > 0) {
+ lazyPageScroller.scrollDown();
+ } else {
+ lazyPageScroller.scrollUp();
+ }
+ }
+
+ /*
+ * Prevent the keyboard focus from leaving the textfield by
+ * preventing the default behaviour of the browser. Fixes #4285.
+ */
+ handleMouseDownEvent(event);
+ }
+
+ /**
+ * Should paging be enabled. If paging is enabled then only a certain
+ * amount of items are visible at a time and a scrollbar or buttons are
+ * visible to change page. If paging is turned of then all options are
+ * rendered into the popup menu.
+ *
+ * @param paging
+ * Should the paging be turned on?
+ */
+ public void setPagingEnabled(boolean paging) {
+ if (isPagingEnabled == paging) {
+ return;
+ }
+ if (paging) {
+ down.getStyle().clearDisplay();
+ up.getStyle().clearDisplay();
+ status.getStyle().clearDisplay();
+ } else {
+ down.getStyle().setDisplay(Display.NONE);
+ up.getStyle().setDisplay(Display.NONE);
+ status.getStyle().setDisplay(Display.NONE);
+ }
+ isPagingEnabled = paging;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition
+ * (int, int)
+ */
+
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight) {
+
+ int top = -1;
+ int left = -1;
+
+ // reset menu size and retrieve its "natural" size
+ menu.setHeight("");
+ if (currentPage > 0) {
+ // fix height to avoid height change when getting to last page
+ menu.fixHeightTo(pageLength);
+ }
+ offsetHeight = getOffsetHeight();
+
+ final int desiredWidth = getMainWidth();
+ Element menuFirstChild = menu.getElement().getFirstChildElement()
+ .cast();
+ int naturalMenuWidth = menuFirstChild.getOffsetWidth();
+
+ if (popupOuterPadding == -1) {
+ popupOuterPadding = Util.measureHorizontalPaddingAndBorder(
+ getElement(), 2);
+ }
+
+ if (naturalMenuWidth < desiredWidth) {
+ menu.setWidth((desiredWidth - popupOuterPadding) + "px");
+ menuFirstChild.getStyle().setWidth(100, Unit.PCT);
+ naturalMenuWidth = desiredWidth;
+ }
+
+ if (BrowserInfo.get().isIE()) {
+ /*
+ * IE requires us to specify the width for the container
+ * element. Otherwise it will be 100% wide
+ */
+ int rootWidth = naturalMenuWidth - popupOuterPadding;
+ getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
+ }
+
+ if (offsetHeight + getPopupTop() > Window.getClientHeight()
+ + Window.getScrollTop()) {
+ // popup on top of input instead
+ top = getPopupTop() - offsetHeight
+ - VFilterSelect.this.getOffsetHeight();
+ if (top < 0) {
+ top = 0;
+ }
+ } else {
+ top = getPopupTop();
+ /*
+ * Take popup top margin into account. getPopupTop() returns the
+ * top value including the margin but the value we give must not
+ * include the margin.
+ */
+ int topMargin = (top - topPosition);
+ top -= topMargin;
+ }
+
+ // fetch real width (mac FF bugs here due GWT popups overflow:auto )
+ offsetWidth = menuFirstChild.getOffsetWidth();
+ if (offsetWidth + getPopupLeft() > Window.getClientWidth()
+ + Window.getScrollLeft()) {
+ left = VFilterSelect.this.getAbsoluteLeft()
+ + VFilterSelect.this.getOffsetWidth()
+ + Window.getScrollLeft() - offsetWidth;
+ if (left < 0) {
+ left = 0;
+ }
+ } else {
+ left = getPopupLeft();
+ }
+ setPopupPosition(left, top);
+ }
+
+ /**
+ * Was the popup just closed?
+ *
+ * @return true if popup was just closed
+ */
+ public boolean isJustClosed() {
+ final long now = (new Date()).getTime();
+ return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google
+ * .gwt.event.logical.shared.CloseEvent)
+ */
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (event.isAutoClosed()) {
+ lastAutoClosed = (new Date()).getTime();
+ }
+ }
+
+ /**
+ * Updates style names in suggestion popup to help theme building.
+ *
+ * @param uidl
+ * UIDL for the whole combo box
+ * @param componentState
+ * shared state of the combo box
+ */
+ public void updateStyleNames(UIDL uidl, ComponentState componentState) {
+ setStyleName(VFilterSelect.this.getStylePrimaryName()
+ + "-suggestpopup");
+ menu.setStyleName(VFilterSelect.this.getStylePrimaryName()
+ + "-suggestmenu");
+ status.setClassName(VFilterSelect.this.getStylePrimaryName()
+ + "-status");
+ if (ComponentStateUtil.hasStyles(componentState)) {
+ for (String style : componentState.styles) {
+ if (!"".equals(style)) {
+ addStyleDependentName(style);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * The menu where the suggestions are rendered
+ */
+ public class SuggestionMenu extends MenuBar implements SubPartAware,
+ LoadHandler {
+
+ /**
+ * Tracks the item that is currently selected using the keyboard. This
+ * is need only because mouseover changes the selection and we do not
+ * want to use that selection when pressing enter to select the item.
+ */
+ private MenuItem keyboardSelectedItem;
+
+ private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(
+ 100, new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ if (suggestionPopup.isVisible()
+ && suggestionPopup.isAttached()) {
+ setWidth("");
+ getElement().getFirstChildElement().getStyle()
+ .clearWidth();
+ suggestionPopup
+ .setPopupPositionAndShow(suggestionPopup);
+ }
+
+ }
+ });
+
+ /**
+ * Default constructor
+ */
+ SuggestionMenu() {
+ super(true);
+ addDomHandler(this, LoadEvent.getType());
+ }
+
+ /**
+ * Fixes menus height to use same space as full page would use. Needed
+ * to avoid height changes when quickly "scrolling" to last page
+ */
+ public void fixHeightTo(int pagelenth) {
+ if (currentSuggestions.size() > 0) {
+ final int pixels = pagelenth * (getOffsetHeight() - 2)
+ / currentSuggestions.size();
+ setHeight((pixels + 2) + "px");
+ }
+ }
+
+ /**
+ * Sets the suggestions rendered in the menu
+ *
+ * @param suggestions
+ * The suggestions to be rendered in the menu
+ */
+ public void setSuggestions(
+ Collection<FilterSelectSuggestion> suggestions) {
+ // Reset keyboard selection when contents is updated to avoid
+ // reusing old, invalid data
+ setKeyboardSelectedItem(null);
+
+ clearItems();
+ final Iterator<FilterSelectSuggestion> it = suggestions.iterator();
+ while (it.hasNext()) {
+ final FilterSelectSuggestion s = it.next();
+ final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+
+ Util.sinkOnloadForImages(mi.getElement());
+
+ this.addItem(mi);
+ if (s == currentSuggestion) {
+ selectItem(mi);
+ }
+ }
+ }
+
+ /**
+ * Send the current selection to the server. Triggered when a selection
+ * is made or on a blur event.
+ */
+ public void doSelectedItemAction() {
+ // do not send a value change event if null was and stays selected
+ final String enteredItemValue = tb.getText();
+ if (nullSelectionAllowed && "".equals(enteredItemValue)
+ && selectedOptionKey != null
+ && !"".equals(selectedOptionKey)) {
+ if (nullSelectItem) {
+ reset();
+ return;
+ }
+ // null is not visible on pages != 0, and not visible when
+ // filtering: handle separately
+ client.updateVariable(paintableId, "filter", "", false);
+ client.updateVariable(paintableId, "page", 0, false);
+ client.updateVariable(paintableId, "selected", new String[] {},
+ immediate);
+ suggestionPopup.hide();
+ return;
+ }
+
+ updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
+ if (!waitingForFilteringResponse) {
+ doPostFilterSelectedItemAction();
+ }
+ }
+
+ /**
+ * Triggered after a selection has been made
+ */
+ public void doPostFilterSelectedItemAction() {
+ final MenuItem item = getSelectedItem();
+ final String enteredItemValue = tb.getText();
+
+ updateSelectionWhenReponseIsReceived = false;
+
+ // check for exact match in menu
+ int p = getItems().size();
+ if (p > 0) {
+ for (int i = 0; i < p; i++) {
+ final MenuItem potentialExactMatch = getItems().get(i);
+ if (potentialExactMatch.getText().equals(enteredItemValue)) {
+ selectItem(potentialExactMatch);
+ // do not send a value change event if null was and
+ // stays selected
+ if (!"".equals(enteredItemValue)
+ || (selectedOptionKey != null && !""
+ .equals(selectedOptionKey))) {
+ doItemAction(potentialExactMatch, true);
+ }
+ suggestionPopup.hide();
+ return;
+ }
+ }
+ }
+ if (allowNewItem) {
+
+ if (!prompting && !enteredItemValue.equals(lastNewItemString)) {
+ /*
+ * Store last sent new item string to avoid double sends
+ */
+ lastNewItemString = enteredItemValue;
+ client.updateVariable(paintableId, "newitem",
+ enteredItemValue, immediate);
+ }
+ } else if (item != null
+ && !"".equals(lastFilter)
+ && (filteringmode == FilteringMode.CONTAINS ? item
+ .getText().toLowerCase()
+ .contains(lastFilter.toLowerCase()) : item
+ .getText().toLowerCase()
+ .startsWith(lastFilter.toLowerCase()))) {
+ doItemAction(item, true);
+ } else {
+ // currentSuggestion has key="" for nullselection
+ if (currentSuggestion != null
+ && !currentSuggestion.key.equals("")) {
+ // An item (not null) selected
+ String text = currentSuggestion.getReplacementString();
+ tb.setText(text);
+ selectedOptionKey = currentSuggestion.key;
+ } else {
+ // Null selected
+ tb.setText("");
+ selectedOptionKey = null;
+ }
+ }
+ suggestionPopup.hide();
+ }
+
+ private static final String SUBPART_PREFIX = "item";
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX
+ .length()));
+
+ MenuItem item = getItems().get(index);
+
+ return item.getElement();
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (!getElement().isOrHasChild(subElement)) {
+ return null;
+ }
+
+ Element menuItemRoot = subElement;
+ while (menuItemRoot != null
+ && !menuItemRoot.getTagName().equalsIgnoreCase("td")) {
+ menuItemRoot = menuItemRoot.getParentElement().cast();
+ }
+ // "menuItemRoot" is now the root of the menu item
+
+ final int itemCount = getItems().size();
+ for (int i = 0; i < itemCount; i++) {
+ if (getItems().get(i).getElement() == menuItemRoot) {
+ String name = SUBPART_PREFIX + i;
+ return name;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoad(LoadEvent event) {
+ // Handle icon onload events to ensure shadow is resized
+ // correctly
+ delayedImageLoadExecutioner.trigger();
+
+ }
+
+ public void selectFirstItem() {
+ MenuItem firstItem = getItems().get(0);
+ selectItem(firstItem);
+ }
+
+ private MenuItem getKeyboardSelectedItem() {
+ return keyboardSelectedItem;
+ }
+
+ public void setKeyboardSelectedItem(MenuItem firstItem) {
+ keyboardSelectedItem = firstItem;
+ }
+
+ public void selectLastItem() {
+ List<MenuItem> items = getItems();
+ MenuItem lastItem = items.get(items.size() - 1);
+ selectItem(lastItem);
+ }
+ }
+
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
+
+ public static final String CLASSNAME = "v-filterselect";
+ private static final String STYLE_NO_INPUT = "no-input";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int pageLength = 10;
+
+ private boolean enableDebug = false;
+
+ private final FlowPanel panel = new FlowPanel();
+
+ /**
+ * The text box where the filter is written
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public final TextBox tb = new TextBox() {
+
+ // Overridden to avoid selecting text when text input is disabled
+ @Override
+ public void setSelectionRange(int pos, int length) {
+ if (textInputEnabled) {
+ super.setSelectionRange(pos, length);
+ } else {
+ super.setSelectionRange(getValue().length(), 0);
+ }
+ };
+ };
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final SuggestionPopup suggestionPopup = new SuggestionPopup();
+
+ /**
+ * Used when measuring the width of the popup
+ */
+ private final HTML popupOpener = new HTML("") {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ /*
+ * Prevent the keyboard focus from leaving the textfield by
+ * preventing the default behaviour of the browser. Fixes #4285.
+ */
+ handleMouseDownEvent(event);
+ }
+ };
+
+ private final Image selectedItemIcon = new Image();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int currentPage;
+
+ /**
+ * A collection of available suggestions (options) as received from the
+ * server.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String selectedOptionKey;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean waitingForFilteringResponse = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean updateSelectionWhenReponseIsReceived = false;
+
+ private boolean tabPressedWhenPopupOpen = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean initDone = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String lastFilter = "";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public enum Select {
+ NONE, FIRST, LAST
+ };
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Select selectPopupItemWhenResponseIsReceived = Select.NONE;
+
+ /**
+ * The current suggestion selected from the dropdown. This is one of the
+ * values in currentSuggestions except when filtering, in this case
+ * currentSuggestion might not be in currentSuggestions.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public FilterSelectSuggestion currentSuggestion;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean allowNewItem;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int totalMatches;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean nullSelectionAllowed;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean nullSelectItem;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean enabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean readonly;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public FilteringMode filteringmode = FilteringMode.OFF;
+
+ // shown in unfocused empty field, disappears on focus (e.g "Search here")
+ private static final String CLASSNAME_PROMPT = "prompt";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String inputPrompt = "";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean prompting = false;
+
+ /**
+ * Set true when popupopened has been clicked. Cleared on each UIDL-update.
+ * This handles the special case where are not filtering yet and the
+ * selected value has changed on the server-side. See #2119
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean popupOpenerClicked;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int suggestionPopupMinWidth = 0;
+
+ private int popupWidth = -1;
+ /**
+ * Stores the last new item string to avoid double submissions. Cleared on
+ * uidl updates.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String lastNewItemString;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean focused = false;
+
+ /**
+ * If set to false, the component should not allow entering text to the
+ * field even for filtering.
+ */
+ private boolean textInputEnabled = true;
+
+ /**
+ * Default constructor
+ */
+ public VFilterSelect() {
+ selectedItemIcon.setStyleName("v-icon");
+ selectedItemIcon.addLoadHandler(new LoadHandler() {
+
+ @Override
+ public void onLoad(LoadEvent event) {
+ if (BrowserInfo.get().isIE8()) {
+ // IE8 needs some help to discover it should reposition the
+ // text field
+ forceReflow();
+ }
+ updateRootWidth();
+ updateSelectedIconPosition();
+ }
+ });
+
+ popupOpener.sinkEvents(Event.ONMOUSEDOWN);
+ panel.add(tb);
+ panel.add(popupOpener);
+ initWidget(panel);
+ tb.addKeyDownHandler(this);
+ tb.addKeyUpHandler(this);
+
+ tb.addFocusHandler(this);
+ tb.addBlurHandler(this);
+ tb.addClickHandler(this);
+
+ popupOpener.addClickHandler(this);
+
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ updateStyleNames();
+ }
+
+ protected void updateStyleNames() {
+ tb.setStyleName(getStylePrimaryName() + "-input");
+ popupOpener.setStyleName(getStylePrimaryName() + "-button");
+ suggestionPopup.setStyleName(getStylePrimaryName() + "-suggestpopup");
+ }
+
+ /**
+ * Does the Select have more pages?
+ *
+ * @return true if a next page exists, else false if the current page is the
+ * last page
+ */
+ public boolean hasNextPage() {
+ if (totalMatches > (currentPage + 1) * pageLength) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Filters the options at a certain page. Uses the text box input as a
+ * filter
+ *
+ * @param page
+ * The page which items are to be filtered
+ */
+ public void filterOptions(int page) {
+ filterOptions(page, tb.getText());
+ }
+
+ /**
+ * Filters the options at certain page using the given filter
+ *
+ * @param page
+ * The page to filter
+ * @param filter
+ * The filter to apply to the components
+ */
+ public void filterOptions(int page, String filter) {
+ filterOptions(page, filter, true);
+ }
+
+ /**
+ * Filters the options at certain page using the given filter
+ *
+ * @param page
+ * The page to filter
+ * @param filter
+ * The filter to apply to the options
+ * @param immediate
+ * Whether to send the options request immediately
+ */
+ private void filterOptions(int page, String filter, boolean immediate) {
+ if (filter.equals(lastFilter) && currentPage == page) {
+ if (!suggestionPopup.isAttached()) {
+ suggestionPopup.showSuggestions(currentSuggestions,
+ currentPage, totalMatches);
+ }
+ return;
+ }
+ if (!filter.equals(lastFilter)) {
+ // we are on subsequent page and text has changed -> reset page
+ if ("".equals(filter)) {
+ // let server decide
+ page = -1;
+ } else {
+ page = 0;
+ }
+ }
+
+ waitingForFilteringResponse = true;
+ client.updateVariable(paintableId, "filter", filter, false);
+ client.updateVariable(paintableId, "page", page, immediate);
+ lastFilter = filter;
+ currentPage = page;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateReadOnly() {
+ tb.setReadOnly(readonly || !textInputEnabled);
+ }
+
+ public void setTextInputEnabled(boolean textInputEnabled) {
+ // Always update styles as they might have been overwritten
+ if (textInputEnabled) {
+ removeStyleDependentName(STYLE_NO_INPUT);
+ } else {
+ addStyleDependentName(STYLE_NO_INPUT);
+ }
+
+ if (this.textInputEnabled == textInputEnabled) {
+ return;
+ }
+
+ this.textInputEnabled = textInputEnabled;
+ updateReadOnly();
+ }
+
+ /**
+ * Sets the text in the text box.
+ *
+ * @param text
+ * the text to set in the text box
+ */
+ public void setTextboxText(final String text) {
+ tb.setText(text);
+ }
+
+ /**
+ * Turns prompting on. When prompting is turned on a command prompt is shown
+ * in the text box if nothing has been entered.
+ */
+ public void setPromptingOn() {
+ if (!prompting) {
+ prompting = true;
+ addStyleDependentName(CLASSNAME_PROMPT);
+ }
+ setTextboxText(inputPrompt);
+ }
+
+ /**
+ * Turns prompting off. When prompting is turned on a command prompt is
+ * shown in the text box if nothing has been entered.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param text
+ * The text the text box should contain.
+ */
+ public void setPromptingOff(String text) {
+ setTextboxText(text);
+ if (prompting) {
+ prompting = false;
+ removeStyleDependentName(CLASSNAME_PROMPT);
+ }
+ }
+
+ /**
+ * Triggered when a suggestion is selected
+ *
+ * @param suggestion
+ * The suggestion that just got selected.
+ */
+ public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
+ updateSelectionWhenReponseIsReceived = false;
+
+ currentSuggestion = suggestion;
+ String newKey;
+ if (suggestion.key.equals("")) {
+ // "nullselection"
+ newKey = "";
+ } else {
+ // normal selection
+ newKey = String.valueOf(suggestion.getOptionKey());
+ }
+
+ String text = suggestion.getReplacementString();
+ if ("".equals(newKey) && !focused) {
+ setPromptingOn();
+ } else {
+ setPromptingOff(text);
+ }
+ setSelectedItemIcon(suggestion.getIconUri());
+ if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) {
+ selectedOptionKey = newKey;
+ client.updateVariable(paintableId, "selected",
+ new String[] { selectedOptionKey }, immediate);
+ // currentPage = -1; // forget the page
+ }
+ suggestionPopup.hide();
+ }
+
+ /**
+ * Sets the icon URI of the selected item. The icon is shown on the left
+ * side of the item caption text. Set the URI to null to remove the icon.
+ *
+ * @param iconUri
+ * The URI of the icon
+ */
+ public void setSelectedItemIcon(String iconUri) {
+ if (iconUri == null || iconUri.length() == 0) {
+ if (selectedItemIcon.isAttached()) {
+ panel.remove(selectedItemIcon);
+ if (BrowserInfo.get().isIE8()) {
+ // IE8 needs some help to discover it should reposition the
+ // text field
+ forceReflow();
+ }
+ updateRootWidth();
+ }
+ } else {
+ panel.insert(selectedItemIcon, 0);
+ selectedItemIcon.setUrl(iconUri);
+ updateRootWidth();
+ updateSelectedIconPosition();
+ }
+ }
+
+ private void forceReflow() {
+ Util.setStyleTemporarily(tb.getElement(), "zoom", "1");
+ }
+
+ /**
+ * Positions the icon vertically in the middle. Should be called after the
+ * icon has loaded
+ */
+ private void updateSelectedIconPosition() {
+ // Position icon vertically to middle
+ int availableHeight = 0;
+ availableHeight = getOffsetHeight();
+
+ int iconHeight = Util.getRequiredHeight(selectedItemIcon);
+ int marginTop = (availableHeight - iconHeight) / 2;
+ DOM.setStyleAttribute(selectedItemIcon.getElement(), "marginTop",
+ marginTop + "px");
+ }
+
+ private static Set<Integer> navigationKeyCodes = new HashSet<Integer>();
+ static {
+ navigationKeyCodes.add(KeyCodes.KEY_DOWN);
+ navigationKeyCodes.add(KeyCodes.KEY_UP);
+ navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN);
+ navigationKeyCodes.add(KeyCodes.KEY_PAGEUP);
+ navigationKeyCodes.add(KeyCodes.KEY_ENTER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (enabled && !readonly) {
+ int keyCode = event.getNativeKeyCode();
+
+ debug("key down: " + keyCode);
+ if (waitingForFilteringResponse
+ && navigationKeyCodes.contains(keyCode)) {
+ /*
+ * Keyboard navigation events should not be handled while we are
+ * waiting for a response. This avoids flickering, disappearing
+ * items, wrongly interpreted responses and more.
+ */
+ debug("Ignoring " + keyCode
+ + " because we are waiting for a filtering response");
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ event.stopPropagation();
+ return;
+ }
+
+ if (suggestionPopup.isAttached()) {
+ debug("Keycode " + keyCode + " target is popup");
+ popupKeyDown(event);
+ } else {
+ debug("Keycode " + keyCode + " target is text field");
+ inputFieldKeyDown(event);
+ }
+ }
+ }
+
+ private void debug(String string) {
+ if (enableDebug) {
+ VConsole.error(string);
+ }
+ }
+
+ /**
+ * Triggered when a key is pressed in the text box
+ *
+ * @param event
+ * The KeyDownEvent
+ */
+ private void inputFieldKeyDown(KeyDownEvent event) {
+ switch (event.getNativeKeyCode()) {
+ case KeyCodes.KEY_DOWN:
+ case KeyCodes.KEY_UP:
+ case KeyCodes.KEY_PAGEDOWN:
+ case KeyCodes.KEY_PAGEUP:
+ // open popup as from gadget
+ filterOptions(-1, "");
+ lastFilter = "";
+ tb.selectAll();
+ break;
+ case KeyCodes.KEY_ENTER:
+ /*
+ * This only handles the case when new items is allowed, a text is
+ * entered, the popup opener button is clicked to close the popup
+ * and enter is then pressed (see #7560).
+ */
+ if (!allowNewItem) {
+ return;
+ }
+
+ if (currentSuggestion != null
+ && tb.getText().equals(
+ currentSuggestion.getReplacementString())) {
+ // Retain behavior from #6686 by returning without stopping
+ // propagation if there's nothing to do
+ return;
+ }
+ suggestionPopup.menu.doSelectedItemAction();
+
+ event.stopPropagation();
+ break;
+ }
+
+ }
+
+ /**
+ * Triggered when a key was pressed in the suggestion popup.
+ *
+ * @param event
+ * The KeyDownEvent of the key
+ */
+ private void popupKeyDown(KeyDownEvent event) {
+ // Propagation of handled events is stopped so other handlers such as
+ // shortcut key handlers do not also handle the same events.
+ switch (event.getNativeKeyCode()) {
+ case KeyCodes.KEY_DOWN:
+ suggestionPopup.selectNextItem();
+ suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
+ .getSelectedItem());
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ event.stopPropagation();
+ break;
+ case KeyCodes.KEY_UP:
+ suggestionPopup.selectPrevItem();
+ suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
+ .getSelectedItem());
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ event.stopPropagation();
+ break;
+ case KeyCodes.KEY_PAGEDOWN:
+ if (hasNextPage()) {
+ filterOptions(currentPage + 1, lastFilter);
+ }
+ event.stopPropagation();
+ break;
+ case KeyCodes.KEY_PAGEUP:
+ if (currentPage > 0) {
+ filterOptions(currentPage - 1, lastFilter);
+ }
+ event.stopPropagation();
+ break;
+ case KeyCodes.KEY_TAB:
+ tabPressedWhenPopupOpen = true;
+ filterOptions(currentPage);
+ // onBlur() takes care of the rest
+ break;
+ case KeyCodes.KEY_ESCAPE:
+ reset();
+ event.stopPropagation();
+ break;
+ case KeyCodes.KEY_ENTER:
+ if (suggestionPopup.menu.getKeyboardSelectedItem() == null) {
+ /*
+ * Nothing selected using up/down. Happens e.g. when entering a
+ * text (causes popup to open) and then pressing enter.
+ */
+ if (!allowNewItem) {
+ /*
+ * New items are not allowed: If there is only one
+ * suggestion, select that. Otherwise do nothing.
+ */
+ if (currentSuggestions.size() == 1) {
+ onSuggestionSelected(currentSuggestions.get(0));
+ }
+ } else {
+ // Handle addition of new items.
+ suggestionPopup.menu.doSelectedItemAction();
+ }
+ } else {
+ /*
+ * Get the suggestion that was navigated to using up/down.
+ */
+ currentSuggestion = ((FilterSelectSuggestion) suggestionPopup.menu
+ .getKeyboardSelectedItem().getCommand());
+ onSuggestionSelected(currentSuggestion);
+ }
+
+ event.stopPropagation();
+ break;
+ }
+
+ }
+
+ /**
+ * Triggered when a key was depressed
+ *
+ * @param event
+ * The KeyUpEvent of the key depressed
+ */
+
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ if (enabled && !readonly) {
+ switch (event.getNativeKeyCode()) {
+ case KeyCodes.KEY_ENTER:
+ case KeyCodes.KEY_TAB:
+ case KeyCodes.KEY_SHIFT:
+ case KeyCodes.KEY_CTRL:
+ case KeyCodes.KEY_ALT:
+ case KeyCodes.KEY_DOWN:
+ case KeyCodes.KEY_UP:
+ case KeyCodes.KEY_PAGEDOWN:
+ case KeyCodes.KEY_PAGEUP:
+ case KeyCodes.KEY_ESCAPE:
+ ; // NOP
+ break;
+ default:
+ if (textInputEnabled) {
+ filterOptions(currentPage);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Resets the Select to its initial state
+ */
+ private void reset() {
+ if (currentSuggestion != null) {
+ String text = currentSuggestion.getReplacementString();
+ setPromptingOff(text);
+ selectedOptionKey = currentSuggestion.key;
+ } else {
+ if (focused) {
+ setPromptingOff("");
+ } else {
+ setPromptingOn();
+ }
+ selectedOptionKey = null;
+ }
+ lastFilter = "";
+ suggestionPopup.hide();
+ }
+
+ /**
+ * Listener for popupopener
+ */
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (textInputEnabled
+ && event.getNativeEvent().getEventTarget().cast() == tb
+ .getElement()) {
+ // Don't process clicks on the text field if text input is enabled
+ return;
+ }
+ if (enabled && !readonly) {
+ // ask suggestionPopup if it was just closed, we are using GWT
+ // Popup's auto close feature
+ if (!suggestionPopup.isJustClosed()) {
+ // If a focus event is not going to be sent, send the options
+ // request immediately; otherwise queue in the same burst as the
+ // focus event. Fixes #8321.
+ boolean immediate = focused
+ || !client.hasEventListeners(this, EventId.FOCUS);
+ filterOptions(-1, "", immediate);
+ popupOpenerClicked = true;
+ lastFilter = "";
+ }
+ DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+ focus();
+ tb.selectAll();
+ }
+ }
+
+ /**
+ * Calculate minimum width for FilterSelect textarea.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public native int minWidth(String captions)
+ /*-{
+ if(!captions || captions.length <= 0)
+ return 0;
+ captions = captions.split("|");
+ var d = $wnd.document.createElement("div");
+ var html = "";
+ for(var i=0; i < captions.length; i++) {
+ html += "<div>" + captions[i] + "</div>";
+ // TODO apply same CSS classname as in suggestionmenu
+ }
+ d.style.position = "absolute";
+ d.style.top = "0";
+ d.style.left = "0";
+ d.style.visibility = "hidden";
+ d.innerHTML = html;
+ $wnd.document.body.appendChild(d);
+ var w = d.offsetWidth;
+ $wnd.document.body.removeChild(d);
+ return w;
+ }-*/;
+
+ /**
+ * A flag which prevents a focus event from taking place
+ */
+ boolean iePreventNextFocus = false;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+
+ @Override
+ public void onFocus(FocusEvent event) {
+
+ /*
+ * When we disable a blur event in ie we need to refocus the textfield.
+ * This will cause a focus event we do not want to process, so in that
+ * case we just ignore it.
+ */
+ if (BrowserInfo.get().isIE() && iePreventNextFocus) {
+ iePreventNextFocus = false;
+ return;
+ }
+
+ focused = true;
+ if (prompting && !readonly) {
+ setPromptingOff("");
+ }
+ addStyleDependentName("focus");
+
+ if (client.hasEventListeners(this, EventId.FOCUS)) {
+ client.updateVariable(paintableId, EventId.FOCUS, "", true);
+ }
+ }
+
+ /**
+ * A flag which cancels the blur event and sets the focus back to the
+ * textfield if the Browser is IE
+ */
+ boolean preventNextBlurEventInIE = false;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+
+ @Override
+ public void onBlur(BlurEvent event) {
+
+ if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) {
+ /*
+ * Clicking in the suggestion popup or on the popup button in IE
+ * causes a blur event to be sent for the field. In other browsers
+ * this is prevented by canceling/preventing default behavior for
+ * the focus event, in IE we handle it here by refocusing the text
+ * field and ignoring the resulting focus event for the textfield
+ * (in onFocus).
+ */
+ preventNextBlurEventInIE = false;
+
+ Element focusedElement = Util.getIEFocusedElement();
+ if (getElement().isOrHasChild(focusedElement)
+ || suggestionPopup.getElement()
+ .isOrHasChild(focusedElement)) {
+
+ // IF the suggestion popup or another part of the VFilterSelect
+ // was focused, move the focus back to the textfield and prevent
+ // the triggered focus event (in onFocus).
+ iePreventNextFocus = true;
+ tb.setFocus(true);
+ return;
+ }
+ }
+
+ focused = false;
+ if (!readonly) {
+ // much of the TAB handling takes place here
+ if (tabPressedWhenPopupOpen) {
+ tabPressedWhenPopupOpen = false;
+ suggestionPopup.menu.doSelectedItemAction();
+ suggestionPopup.hide();
+ } else if (!suggestionPopup.isAttached()
+ || suggestionPopup.isJustClosed()) {
+ suggestionPopup.menu.doSelectedItemAction();
+ }
+ if (selectedOptionKey == null) {
+ setPromptingOn();
+ } else if (currentSuggestion != null) {
+ setPromptingOff(currentSuggestion.caption);
+ }
+ }
+ removeStyleDependentName("focus");
+
+ if (client.hasEventListeners(this, EventId.BLUR)) {
+ client.updateVariable(paintableId, EventId.BLUR, "", true);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.Focusable#focus()
+ */
+
+ @Override
+ public void focus() {
+ focused = true;
+ if (prompting && !readonly) {
+ setPromptingOff("");
+ }
+ tb.setFocus(true);
+ }
+
+ /**
+ * Calculates the width of the select if the select has undefined width.
+ * Should be called when the width changes or when the icon changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void updateRootWidth() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ if (paintable.isUndefinedWidth()) {
+
+ /*
+ * When the select has a undefined with we need to check that we are
+ * only setting the text box width relative to the first page width
+ * of the items. If this is not done the text box width will change
+ * when the popup is used to view longer items than the text box is
+ * wide.
+ */
+ int w = Util.getRequiredWidth(this);
+ if ((!initDone || currentPage + 1 < 0)
+ && suggestionPopupMinWidth > w) {
+ /*
+ * We want to compensate for the paddings just to preserve the
+ * exact size as in Vaadin 6.x, but we get here before
+ * MeasuredSize has been initialized.
+ * Util.measureHorizontalPaddingAndBorder does not work with
+ * border-box, so we must do this the hard way.
+ */
+ Style style = getElement().getStyle();
+ String originalPadding = style.getPadding();
+ String originalBorder = style.getBorderWidth();
+ style.setPaddingLeft(0, Unit.PX);
+ style.setBorderWidth(0, Unit.PX);
+ int offset = w - Util.getRequiredWidth(this);
+ style.setProperty("padding", originalPadding);
+ style.setProperty("borderWidth", originalBorder);
+
+ setWidth(suggestionPopupMinWidth + offset + "px");
+ }
+
+ /*
+ * Lock the textbox width to its current value if it's not already
+ * locked
+ */
+ if (!tb.getElement().getStyle().getWidth().endsWith("px")) {
+ tb.setWidth((tb.getOffsetWidth() - selectedItemIcon
+ .getOffsetWidth()) + "px");
+ }
+ }
+ }
+
+ /**
+ * Get the width of the select in pixels where the text area and icon has
+ * been included.
+ *
+ * @return The width in pixels
+ */
+ private int getMainWidth() {
+ return getOffsetWidth();
+ }
+
+ @Override
+ public void setWidth(String width) {
+ super.setWidth(width);
+ if (width.length() != 0) {
+ tb.setWidth("100%");
+ }
+ }
+
+ /**
+ * Handles special behavior of the mouse down event
+ *
+ * @param event
+ */
+ private void handleMouseDownEvent(Event event) {
+ /*
+ * Prevent the keyboard focus from leaving the textfield by preventing
+ * the default behaviour of the browser. Fixes #4285.
+ */
+ if (event.getTypeInt() == Event.ONMOUSEDOWN) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ /*
+ * In IE the above wont work, the blur event will still trigger. So,
+ * we set a flag here to prevent the next blur event from happening.
+ * This is not needed if do not already have focus, in that case
+ * there will not be any blur event and we should not cancel the
+ * next blur.
+ */
+ if (BrowserInfo.get().isIE() && focused) {
+ preventNextBlurEventInIE = true;
+ }
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ suggestionPopup.hide();
+ }
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if ("textbox".equals(subPart)) {
+ return tb.getElement();
+ } else if ("button".equals(subPart)) {
+ return popupOpener.getElement();
+ }
+ return null;
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (tb.getElement().isOrHasChild(subElement)) {
+ return "textbox";
+ } else if (popupOpener.getElement().isOrHasChild(subElement)) {
+ return "button";
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.gridlayout.GridLayoutConnector;
+import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot;
+import com.vaadin.client.ui.layout.VLayoutSlot;
+import com.vaadin.shared.ui.AlignmentInfo;
+import com.vaadin.shared.ui.MarginInfo;
+
+public class VGridLayout extends ComplexPanel {
+
+ public static final String CLASSNAME = "v-gridlayout";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int[] columnWidths;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int[] rowHeights;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int[] colExpandRatioArray;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int[] rowExpandRatioArray;
+
+ int[] minColumnWidths;
+
+ private int[] minRowHeights;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public DivElement spacingMeasureElement;
+
+ public VGridLayout() {
+ super();
+ setElement(Document.get().createDivElement());
+
+ spacingMeasureElement = Document.get().createDivElement();
+ Style spacingStyle = spacingMeasureElement.getStyle();
+ spacingStyle.setPosition(Position.ABSOLUTE);
+ getElement().appendChild(spacingMeasureElement);
+
+ setStyleName(CLASSNAME);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ }
+
+ private GridLayoutConnector getConnector() {
+ return (GridLayoutConnector) ConnectorMap.get(client)
+ .getConnector(this);
+ }
+
+ /**
+ * Returns the column widths measured in pixels
+ *
+ * @return
+ */
+ protected int[] getColumnWidths() {
+ return columnWidths;
+ }
+
+ /**
+ * Returns the row heights measured in pixels
+ *
+ * @return
+ */
+ protected int[] getRowHeights() {
+ return rowHeights;
+ }
+
+ /**
+ * Returns the spacing between the cells horizontally in pixels
+ *
+ * @return
+ */
+ protected int getHorizontalSpacing() {
+ return LayoutManager.get(client).getOuterWidth(spacingMeasureElement);
+ }
+
+ /**
+ * Returns the spacing between the cells vertically in pixels
+ *
+ * @return
+ */
+ protected int getVerticalSpacing() {
+ return LayoutManager.get(client).getOuterHeight(spacingMeasureElement);
+ }
+
+ static int[] cloneArray(int[] toBeCloned) {
+ int[] clone = new int[toBeCloned.length];
+ for (int i = 0; i < clone.length; i++) {
+ clone[i] = toBeCloned[i] * 1;
+ }
+ return clone;
+ }
+
+ void expandRows() {
+ if (!isUndefinedHeight()) {
+ int usedSpace = minRowHeights[0];
+ int verticalSpacing = getVerticalSpacing();
+ for (int i = 1; i < minRowHeights.length; i++) {
+ usedSpace += verticalSpacing + minRowHeights[i];
+ }
+ int availableSpace = LayoutManager.get(client).getInnerHeight(
+ getElement());
+ int excessSpace = availableSpace - usedSpace;
+ int distributed = 0;
+ if (excessSpace > 0) {
+ for (int i = 0; i < rowHeights.length; i++) {
+ int ew = excessSpace * rowExpandRatioArray[i] / 1000;
+ rowHeights[i] = minRowHeights[i] + ew;
+ distributed += ew;
+ }
+ excessSpace -= distributed;
+ int c = 0;
+ while (excessSpace > 0) {
+ rowHeights[c % rowHeights.length]++;
+ excessSpace--;
+ c++;
+ }
+ }
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateHeight() {
+ // Detect minimum heights & calculate spans
+ detectRowHeights();
+
+ // Expand
+ expandRows();
+
+ // Position
+ layoutCellsVertically();
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateWidth() {
+ // Detect widths & calculate spans
+ detectColWidths();
+ // Expand
+ expandColumns();
+ // Position
+ layoutCellsHorizontally();
+
+ }
+
+ void expandColumns() {
+ if (!isUndefinedWidth()) {
+ int usedSpace = minColumnWidths[0];
+ int horizontalSpacing = getHorizontalSpacing();
+ for (int i = 1; i < minColumnWidths.length; i++) {
+ usedSpace += horizontalSpacing + minColumnWidths[i];
+ }
+
+ int availableSpace = LayoutManager.get(client).getInnerWidth(
+ getElement());
+ int excessSpace = availableSpace - usedSpace;
+ int distributed = 0;
+ if (excessSpace > 0) {
+ for (int i = 0; i < columnWidths.length; i++) {
+ int ew = excessSpace * colExpandRatioArray[i] / 1000;
+ columnWidths[i] = minColumnWidths[i] + ew;
+ distributed += ew;
+ }
+ excessSpace -= distributed;
+ int c = 0;
+ while (excessSpace > 0) {
+ columnWidths[c % columnWidths.length]++;
+ excessSpace--;
+ c++;
+ }
+ }
+ }
+ }
+
+ void layoutCellsVertically() {
+ int verticalSpacing = getVerticalSpacing();
+ LayoutManager layoutManager = LayoutManager.get(client);
+ Element element = getElement();
+ int paddingTop = layoutManager.getPaddingTop(element);
+ int paddingBottom = layoutManager.getPaddingBottom(element);
+ int y = paddingTop;
+
+ for (int i = 0; i < cells.length; i++) {
+ y = paddingTop;
+ for (int j = 0; j < cells[i].length; j++) {
+ Cell cell = cells[i][j];
+ if (cell != null) {
+ int reservedMargin;
+ if (cell.rowspan + j >= cells[i].length) {
+ // Make room for layout padding for cells reaching the
+ // bottom of the layout
+ reservedMargin = paddingBottom;
+ } else {
+ reservedMargin = 0;
+ }
+ cell.layoutVertically(y, reservedMargin);
+ }
+ y += rowHeights[j] + verticalSpacing;
+ }
+ }
+
+ if (isUndefinedHeight()) {
+ int outerHeight = y - verticalSpacing
+ + layoutManager.getPaddingBottom(element)
+ + layoutManager.getBorderHeight(element);
+ element.getStyle().setHeight(outerHeight, Unit.PX);
+ getConnector().getLayoutManager().reportOuterHeight(getConnector(),
+ outerHeight);
+ }
+ }
+
+ void layoutCellsHorizontally() {
+ LayoutManager layoutManager = LayoutManager.get(client);
+ Element element = getElement();
+ int x = layoutManager.getPaddingLeft(element);
+ int paddingRight = layoutManager.getPaddingRight(element);
+ int horizontalSpacing = getHorizontalSpacing();
+ for (int i = 0; i < cells.length; i++) {
+ for (int j = 0; j < cells[i].length; j++) {
+ Cell cell = cells[i][j];
+ if (cell != null) {
+ int reservedMargin;
+ // Make room for layout padding for cells reaching the
+ // right edge of the layout
+ if (i + cell.colspan >= cells.length) {
+ reservedMargin = paddingRight;
+ } else {
+ reservedMargin = 0;
+ }
+ cell.layoutHorizontally(x, reservedMargin);
+ }
+ }
+ x += columnWidths[i] + horizontalSpacing;
+ }
+
+ if (isUndefinedWidth()) {
+ int outerWidth = x - horizontalSpacing
+ + layoutManager.getPaddingRight(element)
+ + layoutManager.getBorderWidth(element);
+ element.getStyle().setWidth(outerWidth, Unit.PX);
+ getConnector().getLayoutManager().reportOuterWidth(getConnector(),
+ outerWidth);
+ }
+ }
+
+ private boolean isUndefinedHeight() {
+ return getConnector().isUndefinedHeight();
+ }
+
+ private boolean isUndefinedWidth() {
+ return getConnector().isUndefinedWidth();
+ }
+
+ private void detectRowHeights() {
+ for (int i = 0; i < rowHeights.length; i++) {
+ rowHeights[i] = 0;
+ }
+
+ // collect min rowheight from non-rowspanned cells
+ for (int i = 0; i < cells.length; i++) {
+ for (int j = 0; j < cells[i].length; j++) {
+ Cell cell = cells[i][j];
+ if (cell != null) {
+ if (cell.rowspan == 1) {
+ if (!cell.hasRelativeHeight()
+ && rowHeights[j] < cell.getHeight()) {
+ rowHeights[j] = cell.getHeight();
+ }
+ } else {
+ storeRowSpannedCell(cell);
+ }
+ }
+ }
+ }
+
+ distributeRowSpanHeights();
+
+ minRowHeights = cloneArray(rowHeights);
+ }
+
+ private void detectColWidths() {
+ // collect min colwidths from non-colspanned cells
+ for (int i = 0; i < columnWidths.length; i++) {
+ columnWidths[i] = 0;
+ }
+
+ for (int i = 0; i < cells.length; i++) {
+ for (int j = 0; j < cells[i].length; j++) {
+ Cell cell = cells[i][j];
+ if (cell != null) {
+ if (cell.colspan == 1) {
+ if (!cell.hasRelativeWidth()
+ && columnWidths[i] < cell.getWidth()) {
+ columnWidths[i] = cell.getWidth();
+ }
+ } else {
+ storeColSpannedCell(cell);
+ }
+ }
+ }
+ }
+
+ distributeColSpanWidths();
+
+ minColumnWidths = cloneArray(columnWidths);
+ }
+
+ private void storeRowSpannedCell(Cell cell) {
+ SpanList l = null;
+ for (SpanList list : rowSpans) {
+ if (list.span < cell.rowspan) {
+ continue;
+ } else {
+ // insert before this
+ l = list;
+ break;
+ }
+ }
+ if (l == null) {
+ l = new SpanList(cell.rowspan);
+ rowSpans.add(l);
+ } else if (l.span != cell.rowspan) {
+ SpanList newL = new SpanList(cell.rowspan);
+ rowSpans.add(rowSpans.indexOf(l), newL);
+ l = newL;
+ }
+ l.cells.add(cell);
+ }
+
+ /**
+ * Iterates colspanned cells, ensures cols have enough space to accommodate
+ * them
+ */
+ void distributeColSpanWidths() {
+ for (SpanList list : colSpans) {
+ for (Cell cell : list.cells) {
+ // cells with relative content may return non 0 here if on
+ // subsequent renders
+ int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
+ distributeSpanSize(columnWidths, cell.col, cell.colspan,
+ getHorizontalSpacing(), width, colExpandRatioArray);
+ }
+ }
+ }
+
+ /**
+ * Iterates rowspanned cells, ensures rows have enough space to accommodate
+ * them
+ */
+ private void distributeRowSpanHeights() {
+ for (SpanList list : rowSpans) {
+ for (Cell cell : list.cells) {
+ // cells with relative content may return non 0 here if on
+ // subsequent renders
+ int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
+ distributeSpanSize(rowHeights, cell.row, cell.rowspan,
+ getVerticalSpacing(), height, rowExpandRatioArray);
+ }
+ }
+ }
+
+ private static void distributeSpanSize(int[] dimensions,
+ int spanStartIndex, int spanSize, int spacingSize, int size,
+ int[] expansionRatios) {
+ int allocated = dimensions[spanStartIndex];
+ for (int i = 1; i < spanSize; i++) {
+ allocated += spacingSize + dimensions[spanStartIndex + i];
+ }
+ if (allocated < size) {
+ // dimensions needs to be expanded due spanned cell
+ int neededExtraSpace = size - allocated;
+ int allocatedExtraSpace = 0;
+
+ // Divide space according to expansion ratios if any span has a
+ // ratio
+ int totalExpansion = 0;
+ for (int i = 0; i < spanSize; i++) {
+ int itemIndex = spanStartIndex + i;
+ totalExpansion += expansionRatios[itemIndex];
+ }
+
+ for (int i = 0; i < spanSize; i++) {
+ int itemIndex = spanStartIndex + i;
+ int expansion;
+ if (totalExpansion == 0) {
+ // Divide equally among all cells if there are no
+ // expansion ratios
+ expansion = neededExtraSpace / spanSize;
+ } else {
+ expansion = neededExtraSpace * expansionRatios[itemIndex]
+ / totalExpansion;
+ }
+ dimensions[itemIndex] += expansion;
+ allocatedExtraSpace += expansion;
+ }
+
+ // We might still miss a couple of pixels because of
+ // rounding errors...
+ if (neededExtraSpace > allocatedExtraSpace) {
+ for (int i = 0; i < spanSize; i++) {
+ // Add one pixel to every cell until we have
+ // compensated for any rounding error
+ int itemIndex = spanStartIndex + i;
+ dimensions[itemIndex] += 1;
+ allocatedExtraSpace += 1;
+ if (neededExtraSpace == allocatedExtraSpace) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
+ private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
+
+ private class SpanList {
+ final int span;
+ List<Cell> cells = new LinkedList<Cell>();
+
+ public SpanList(int span) {
+ this.span = span;
+ }
+ }
+
+ void storeColSpannedCell(Cell cell) {
+ SpanList l = null;
+ for (SpanList list : colSpans) {
+ if (list.span < cell.colspan) {
+ continue;
+ } else {
+ // insert before this
+ l = list;
+ break;
+ }
+ }
+ if (l == null) {
+ l = new SpanList(cell.colspan);
+ colSpans.add(l);
+ } else if (l.span != cell.colspan) {
+
+ SpanList newL = new SpanList(cell.colspan);
+ colSpans.add(colSpans.indexOf(l), newL);
+ l = newL;
+ }
+ l.cells.add(cell);
+ }
+
+ Cell[][] cells;
+
+ /**
+ * Private helper class.
+ */
+ /** For internal use only. May be removed or replaced in the future. */
+ public class Cell {
+ public Cell(int row, int col) {
+ this.row = row;
+ this.col = col;
+ }
+
+ public boolean hasContent() {
+ return hasContent;
+ }
+
+ public boolean hasRelativeHeight() {
+ if (slot != null) {
+ return slot.getChild().isRelativeHeight();
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * @return total of spanned cols
+ */
+ private int getAvailableWidth() {
+ int width = columnWidths[col];
+ for (int i = 1; i < colspan; i++) {
+ width += getHorizontalSpacing() + columnWidths[col + i];
+ }
+ return width;
+ }
+
+ /**
+ * @return total of spanned rows
+ */
+ private int getAvailableHeight() {
+ int height = rowHeights[row];
+ for (int i = 1; i < rowspan; i++) {
+ height += getVerticalSpacing() + rowHeights[row + i];
+ }
+ return height;
+ }
+
+ public void layoutHorizontally(int x, int marginRight) {
+ if (slot != null) {
+ slot.positionHorizontally(x, getAvailableWidth(), marginRight);
+ }
+ }
+
+ public void layoutVertically(int y, int marginBottom) {
+ if (slot != null) {
+ slot.positionVertically(y, getAvailableHeight(), marginBottom);
+ }
+ }
+
+ public int getWidth() {
+ if (slot != null) {
+ return slot.getUsedWidth();
+ } else {
+ return 0;
+ }
+ }
+
+ public int getHeight() {
+ if (slot != null) {
+ return slot.getUsedHeight();
+ } else {
+ return 0;
+ }
+ }
+
+ protected boolean hasRelativeWidth() {
+ if (slot != null) {
+ return slot.getChild().isRelativeWidth();
+ } else {
+ return true;
+ }
+ }
+
+ final int row;
+ final int col;
+ int colspan = 1;
+ int rowspan = 1;
+
+ private boolean hasContent;
+
+ private AlignmentInfo alignment;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ComponentConnectorLayoutSlot slot;
+
+ public void updateFromUidl(UIDL cellUidl) {
+ // Set cell width
+ colspan = cellUidl.hasAttribute("w") ? cellUidl
+ .getIntAttribute("w") : 1;
+ // Set cell height
+ rowspan = cellUidl.hasAttribute("h") ? cellUidl
+ .getIntAttribute("h") : 1;
+ // ensure we will lose reference to old cells, now overlapped by
+ // this cell
+ for (int i = 0; i < colspan; i++) {
+ for (int j = 0; j < rowspan; j++) {
+ if (i > 0 || j > 0) {
+ cells[col + i][row + j] = null;
+ }
+ }
+ }
+
+ UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested
+ // about childUidl
+ hasContent = childUidl != null;
+ if (hasContent) {
+ ComponentConnector childConnector = client
+ .getPaintable(childUidl);
+
+ if (slot == null || slot.getChild() != childConnector) {
+ slot = new ComponentConnectorLayoutSlot(CLASSNAME,
+ childConnector, getConnector());
+ if (childConnector.isRelativeWidth()) {
+ slot.getWrapperElement().getStyle()
+ .setWidth(100, Unit.PCT);
+ }
+ Element slotWrapper = slot.getWrapperElement();
+ getElement().appendChild(slotWrapper);
+
+ Widget widget = childConnector.getWidget();
+ insert(widget, slotWrapper, getWidgetCount(), false);
+ Cell oldCell = widgetToCell.put(widget, this);
+ if (oldCell != null) {
+ oldCell.slot.getWrapperElement().removeFromParent();
+ oldCell.slot = null;
+ }
+ }
+
+ }
+ }
+
+ public void setAlignment(AlignmentInfo alignmentInfo) {
+ slot.setAlignment(alignmentInfo);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Cell getCell(int row, int col) {
+ return cells[col][row];
+ }
+
+ /**
+ * Creates a new Cell with the given coordinates. If an existing cell was
+ * found, returns that one.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param row
+ * @param col
+ * @return
+ */
+ public Cell createCell(int row, int col) {
+ Cell cell = getCell(row, col);
+ if (cell == null) {
+ cell = new Cell(row, col);
+ cells[col][row] = cell;
+ }
+ return cell;
+ }
+
+ /**
+ * Returns the deepest nested child component which contains "element". The
+ * child component is also returned if "element" is part of its caption.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param element
+ * An element that is a nested sub element of the root element in
+ * this layout
+ * @return The Paintable which the element is a part of. Null if the element
+ * belongs to the layout and not to a child.
+ */
+ public ComponentConnector getComponent(Element element) {
+ return Util.getConnectorForElement(client, this, element);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setCaption(Widget widget, VCaption caption) {
+ VLayoutSlot slot = widgetToCell.get(widget).slot;
+
+ if (caption != null) {
+ // Logical attach.
+ getChildren().add(caption);
+ }
+
+ // Physical attach if not null, also removes old caption
+ slot.setCaption(caption);
+
+ if (caption != null) {
+ // Adopt.
+ adopt(caption);
+ }
+ }
+
+ private void togglePrefixedStyleName(String name, boolean enabled) {
+ if (enabled) {
+ addStyleDependentName(name);
+ } else {
+ removeStyleDependentName(name);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateMarginStyleNames(MarginInfo marginInfo) {
+ togglePrefixedStyleName("margin-top", marginInfo.hasTop());
+ togglePrefixedStyleName("margin-right", marginInfo.hasRight());
+ togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom());
+ togglePrefixedStyleName("margin-left", marginInfo.hasLeft());
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateSpacingStyleName(boolean spacingEnabled) {
+ String styleName = getStylePrimaryName();
+ if (spacingEnabled) {
+ spacingMeasureElement.addClassName(styleName + "-spacing-on");
+ spacingMeasureElement.removeClassName(styleName + "-spacing-off");
+ } else {
+ spacingMeasureElement.removeClassName(styleName + "-spacing-on");
+ spacingMeasureElement.addClassName(styleName + "-spacing-off");
+ }
+ }
+
+ public void setSize(int rows, int cols) {
+ if (cells == null) {
+ cells = new Cell[cols][rows];
+ } else if (cells.length != cols || cells[0].length != rows) {
+ Cell[][] newCells = new Cell[cols][rows];
+ for (int i = 0; i < cells.length; i++) {
+ for (int j = 0; j < cells[i].length; j++) {
+ if (i < cols && j < rows) {
+ newCells[i][j] = cells[i][j];
+ }
+ }
+ }
+ cells = newCells;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import com.vaadin.client.StyleConstants;
+
+/**
+ * Represents a layout where the children is ordered vertically
+ */
+public class VHorizontalLayout extends VOrderedLayout {
+
+ public static final String CLASSNAME = "v-horizontallayout";
+
+ /**
+ * Default constructor
+ */
+ public VHorizontalLayout() {
+ super(false);
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ addStyleName("v-horizontal");
+ }
+}
--- /dev/null
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.Image;
+
+public class VImage extends Image {
+
+ public static final String CLASSNAME = "v-image";
+
+ public VImage() {
+ setStylePrimaryName(CLASSNAME);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+import com.vaadin.client.VTooltip;
+
+public class VLabel extends HTML {
+
+ public static final String CLASSNAME = "v-label";
+ private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w";
+
+ private ApplicationConnection connection;
+
+ public VLabel() {
+ super();
+ setStyleName(CLASSNAME);
+ sinkEvents(VTooltip.TOOLTIP_EVENTS);
+ }
+
+ public VLabel(String text) {
+ super(text);
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+ event.stopPropagation();
+ return;
+ }
+ }
+
+ @Override
+ public void setWidth(String width) {
+ super.setWidth(width);
+ if (width == null || width.equals("")) {
+ setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true);
+ getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
+ } else {
+ setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false);
+ getElement().getStyle().clearDisplay();
+ }
+ }
+
+ @Override
+ public void setText(String text) {
+ if (BrowserInfo.get().isIE8()) {
+ // #3983 - IE8 incorrectly replaces \n with <br> so we do the
+ // escaping manually and set as HTML
+ super.setHTML(Util.escapeHTML(text));
+ } else {
+ super.setText(text);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setConnection(ApplicationConnection client) {
+ connection = client;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.BorderStyle;
+
+public class VLink extends HTML implements ClickHandler {
+
+ public static final String CLASSNAME = "v-link";
+
+ @Deprecated
+ protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT;
+
+ @Deprecated
+ protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL;
+
+ @Deprecated
+ protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String src;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String target;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public BorderStyle borderStyle = BorderStyle.DEFAULT;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean enabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int targetWidth;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int targetHeight;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element errorIndicatorElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element anchor = DOM.createAnchor();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element captionElement = DOM.createSpan();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Icon icon;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ public VLink() {
+ super();
+ getElement().appendChild(anchor);
+ anchor.appendChild(captionElement);
+ addClickHandler(this);
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (enabled) {
+ if (target == null) {
+ target = "_self";
+ }
+ String features;
+ switch (borderStyle) {
+ case NONE:
+ features = "menubar=no,location=no,status=no";
+ break;
+ case MINIMAL:
+ features = "menubar=yes,location=no,status=no";
+ break;
+ default:
+ features = "";
+ break;
+ }
+
+ if (targetWidth > 0) {
+ features += (features.length() > 0 ? "," : "") + "width="
+ + targetWidth;
+ }
+ if (targetHeight > 0) {
+ features += (features.length() > 0 ? "," : "") + "height="
+ + targetHeight;
+ }
+
+ if (features.length() > 0) {
+ // if 'special features' are set, use window.open(), unless
+ // a modifier key is held (ctrl to open in new tab etc)
+ Event e = DOM.eventGetCurrentEvent();
+ if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey()
+ && !e.getMetaKey()) {
+ Window.open(src, target, features);
+ e.preventDefault();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ final Element target = DOM.eventGetTarget(event);
+ if (event.getTypeInt() == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+ }
+ if (target == captionElement || target == anchor
+ || (icon != null && target == icon.getElement())) {
+ super.onBrowserEvent(event);
+ }
+ if (!enabled) {
+ event.preventDefault();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.client.UIDL;
+
+public class VListSelect extends VOptionGroupBase {
+
+ public static final String CLASSNAME = "v-select";
+
+ private static final int VISIBLE_COUNT = 10;
+
+ protected ListBox select;
+
+ private int lastSelectedIndex = -1;
+
+ public VListSelect() {
+ super(new ListBox(true), CLASSNAME);
+ select = getOptionsContainer();
+ select.addChangeHandler(this);
+ select.addClickHandler(this);
+ select.setVisibleItemCount(VISIBLE_COUNT);
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ updateStyleNames();
+ }
+
+ protected void updateStyleNames() {
+ container.setStyleName(getStylePrimaryName());
+ select.setStyleName(getStylePrimaryName() + "-select");
+ }
+
+ protected ListBox getOptionsContainer() {
+ return (ListBox) optionsContainer;
+ }
+
+ @Override
+ public void buildOptions(UIDL uidl) {
+ select.setMultipleSelect(isMultiselect());
+ select.setEnabled(!isDisabled() && !isReadonly());
+ select.clear();
+ if (!isMultiselect() && isNullSelectionAllowed()
+ && !isNullSelectionItemAvailable()) {
+ // can't unselect last item in singleselect mode
+ select.addItem("", (String) null);
+ }
+ for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ select.addItem(optionUidl.getStringAttribute("caption"),
+ optionUidl.getStringAttribute("key"));
+ if (optionUidl.hasAttribute("selected")) {
+ int itemIndex = select.getItemCount() - 1;
+ select.setItemSelected(itemIndex, true);
+ lastSelectedIndex = itemIndex;
+ }
+ }
+ if (getRows() > 0) {
+ select.setVisibleItemCount(getRows());
+ }
+ }
+
+ @Override
+ protected String[] getSelectedItems() {
+ final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+ for (int i = 0; i < select.getItemCount(); i++) {
+ if (select.isItemSelected(i)) {
+ selectedItemKeys.add(select.getValue(i));
+ }
+ }
+ return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+ }
+
+ @Override
+ public void onChange(ChangeEvent event) {
+ final int si = select.getSelectedIndex();
+ if (si == -1 && !isNullSelectionAllowed()) {
+ select.setSelectedIndex(lastSelectedIndex);
+ } else {
+ lastSelectedIndex = si;
+ if (isMultiselect()) {
+ client.updateVariable(paintableId, "selected",
+ getSelectedItems(), isImmediate());
+ } else {
+ client.updateVariable(paintableId, "selected",
+ new String[] { "" + getSelectedItem() }, isImmediate());
+ }
+ }
+ }
+
+ @Override
+ public void setHeight(String height) {
+ select.setHeight(height);
+ super.setHeight(height);
+ }
+
+ @Override
+ public void setWidth(String width) {
+ select.setWidth(width);
+ super.setWidth(width);
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getOptionsContainer().setTabIndex(tabIndex);
+ }
+
+ @Override
+ public void focus() {
+ select.setFocus(true);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.menubar.MenuBarConstants;
+
+public class VMenuBar extends SimpleFocusablePanel implements
+ CloseHandler<PopupPanel>, KeyPressHandler, KeyDownHandler,
+ FocusHandler, SubPartAware {
+
+ // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable,
+ // used for the root menu but also used for the sub menus.
+
+ /** Set the CSS class name to allow styling. */
+ public static final String CLASSNAME = "v-menubar";
+ public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu";
+
+ /**
+ * For server connections.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String uidlId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final VMenuBar hostReference = this;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public CustomMenuItem moreItem = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VMenuBar collapsedRootItems;
+
+ /**
+ * An empty command to be used when the item has no command associated
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public static final Command emptyCommand = null;
+
+ /** Widget fields **/
+ protected boolean subMenu;
+ protected ArrayList<CustomMenuItem> items;
+ protected Element containerElement;
+ protected VOverlay popup;
+ protected VMenuBar visibleChildMenu;
+ protected boolean menuVisible = false;
+ protected VMenuBar parentMenu;
+ protected CustomMenuItem selected;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean enabled = true;
+
+ private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100,
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ iLayout(true);
+ }
+ });
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean openRootOnHover;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean htmlContentAllowed;
+
+ public VMenuBar() {
+ // Create an empty horizontal menubar
+ this(false, null);
+
+ // Navigation is only handled by the root bar
+ addFocusHandler(this);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+ }
+
+ public VMenuBar(boolean subMenu, VMenuBar parentMenu) {
+
+ items = new ArrayList<CustomMenuItem>();
+ popup = null;
+ visibleChildMenu = null;
+ this.subMenu = subMenu;
+
+ containerElement = getElement();
+
+ sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
+ | Event.ONLOAD);
+
+ if (parentMenu == null) {
+ // Root menu
+ setStyleName(CLASSNAME);
+ } else {
+ // Child menus inherits style name
+ setStyleName(parentMenu.getStyleName());
+ }
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ super.setStylePrimaryName(style);
+ updateStyleNames();
+ }
+
+ protected void updateStyleNames() {
+ String primaryStyleName = getParentMenu() != null ? getParentMenu()
+ .getStylePrimaryName() : getStylePrimaryName();
+
+ // Reset the style name for all the items
+ for (CustomMenuItem item : items) {
+ item.setStyleName(primaryStyleName + "-menuitem");
+ }
+
+ if (subMenu
+ && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) {
+ /*
+ * Sub-menus should get the sub-menu prefix
+ */
+ super.setStylePrimaryName(primaryStyleName
+ + SUBMENU_CLASSNAME_PREFIX);
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ if (!subMenu) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+ }
+ }
+
+ void updateSize() {
+ // Take from setWidth
+ if (!subMenu) {
+ // Only needed for root level menu
+ hideChildren();
+ setSelected(null);
+ menuVisible = false;
+ }
+ }
+
+ /**
+ * Build the HTML content for a menu item.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String buildItemHTML(UIDL item) {
+ // Construct html from the text and the optional icon
+ StringBuffer itemHTML = new StringBuffer();
+ if (item.hasAttribute("separator")) {
+ itemHTML.append("<span>---</span>");
+ } else {
+ // Add submenu indicator
+ if (item.getChildCount() > 0) {
+ String bgStyle = "";
+ itemHTML.append("<span class=\"" + getStylePrimaryName()
+ + "-submenu-indicator\"" + bgStyle + ">►</span>");
+ }
+
+ itemHTML.append("<span class=\"" + getStylePrimaryName()
+ + "-menuitem-caption\">");
+ if (item.hasAttribute("icon")) {
+ itemHTML.append("<img src=\""
+ + Util.escapeAttribute(client.translateVaadinUri(item
+ .getStringAttribute("icon"))) + "\" class=\""
+ + Icon.CLASSNAME + "\" alt=\"\" />");
+ }
+ String itemText = item.getStringAttribute("text");
+ if (!htmlContentAllowed) {
+ itemText = Util.escapeHTML(itemText);
+ }
+ itemHTML.append(itemText);
+ itemHTML.append("</span>");
+ }
+ return itemHTML.toString();
+ }
+
+ /**
+ * This is called by the items in the menu and it communicates the
+ * information to the server
+ *
+ * @param clickedItemId
+ * id of the item that was clicked
+ */
+ public void onMenuClick(int clickedItemId) {
+ // Updating the state to the server can not be done before
+ // the server connection is known, i.e., before updateFromUIDL()
+ // has been called.
+ if (uidlId != null && client != null) {
+ // Communicate the user interaction parameters to server. This call
+ // will initiate an AJAX request to the server.
+ client.updateVariable(uidlId, "clickedId", clickedItemId, true);
+ }
+ }
+
+ /** Widget methods **/
+
+ /**
+ * Returns a list of items in this menu
+ */
+ public List<CustomMenuItem> getItems() {
+ return items;
+ }
+
+ /**
+ * Remove all the items in this menu
+ */
+ public void clearItems() {
+ Element e = getContainerElement();
+ while (DOM.getChildCount(e) > 0) {
+ DOM.removeChild(e, DOM.getChild(e, 0));
+ }
+ items.clear();
+ }
+
+ /**
+ * Returns the containing element of the menu
+ *
+ * @return
+ */
+ @Override
+ public Element getContainerElement() {
+ return containerElement;
+ }
+
+ /**
+ * Add a new item to this menu
+ *
+ * @param html
+ * items text
+ * @param cmd
+ * items command
+ * @return the item created
+ */
+ public CustomMenuItem addItem(String html, Command cmd) {
+ CustomMenuItem item = GWT.create(CustomMenuItem.class);
+ item.setHTML(html);
+ item.setCommand(cmd);
+ addItem(item);
+ return item;
+ }
+
+ /**
+ * Add a new item to this menu
+ *
+ * @param item
+ */
+ public void addItem(CustomMenuItem item) {
+ if (items.contains(item)) {
+ return;
+ }
+ DOM.appendChild(getContainerElement(), item.getElement());
+ item.setParentMenu(this);
+ item.setSelected(false);
+ items.add(item);
+ }
+
+ public void addItem(CustomMenuItem item, int index) {
+ if (items.contains(item)) {
+ return;
+ }
+ DOM.insertChild(getContainerElement(), item.getElement(), index);
+ item.setParentMenu(this);
+ item.setSelected(false);
+ items.add(index, item);
+ }
+
+ /**
+ * Remove the given item from this menu
+ *
+ * @param item
+ */
+ public void removeItem(CustomMenuItem item) {
+ if (items.contains(item)) {
+ int index = items.indexOf(item);
+
+ DOM.removeChild(getContainerElement(),
+ DOM.getChild(getContainerElement(), index));
+ items.remove(index);
+ }
+ }
+
+ /*
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+ * .client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event e) {
+ super.onBrowserEvent(e);
+
+ // Handle onload events (icon loaded, size changes)
+ if (DOM.eventGetType(e) == Event.ONLOAD) {
+ VMenuBar parent = getParentMenu();
+ if (parent != null) {
+ // The onload event for an image in a popup should be sent to
+ // the parent, which owns the popup
+ parent.iconLoaded();
+ } else {
+ // Onload events for images in the root menu are handled by the
+ // root menu itself
+ iconLoaded();
+ }
+ return;
+ }
+
+ Element targetElement = DOM.eventGetTarget(e);
+ CustomMenuItem targetItem = null;
+ for (int i = 0; i < items.size(); i++) {
+ CustomMenuItem item = items.get(i);
+ if (DOM.isOrHasChild(item.getElement(), targetElement)) {
+ targetItem = item;
+ }
+ }
+
+ if (targetItem != null) {
+ switch (DOM.eventGetType(e)) {
+
+ case Event.ONCLICK:
+ if (isEnabled() && targetItem.isEnabled()) {
+ itemClick(targetItem);
+ }
+ if (subMenu) {
+ // Prevent moving keyboard focus to child menus
+ VMenuBar parent = parentMenu;
+ while (parent.getParentMenu() != null) {
+ parent = parent.getParentMenu();
+ }
+ parent.setFocus(true);
+ }
+
+ break;
+
+ case Event.ONMOUSEOVER:
+ LazyCloser.cancelClosing();
+
+ if (isEnabled() && targetItem.isEnabled()) {
+ itemOver(targetItem);
+ }
+ break;
+
+ case Event.ONMOUSEOUT:
+ itemOut(targetItem);
+ LazyCloser.schedule();
+ break;
+ }
+ } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) {
+ // Prevent moving keyboard focus to child menus
+ VMenuBar parent = parentMenu;
+ while (parent.getParentMenu() != null) {
+ parent = parent.getParentMenu();
+ }
+ parent.setFocus(true);
+ }
+ }
+
+ private boolean isEnabled() {
+ return enabled;
+ }
+
+ private void iconLoaded() {
+ iconLoadedExecutioner.trigger();
+ }
+
+ /**
+ * When an item is clicked
+ *
+ * @param item
+ */
+ public void itemClick(CustomMenuItem item) {
+ if (item.getCommand() != null) {
+ setSelected(null);
+
+ if (visibleChildMenu != null) {
+ visibleChildMenu.hideChildren();
+ }
+
+ hideParents(true);
+ menuVisible = false;
+ Scheduler.get().scheduleDeferred(item.getCommand());
+
+ } else {
+ if (item.getSubMenu() != null
+ && item.getSubMenu() != visibleChildMenu) {
+ setSelected(item);
+ showChildMenu(item);
+ menuVisible = true;
+ } else if (!subMenu) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+ }
+ }
+ }
+
+ /**
+ * When the user hovers the mouse over the item
+ *
+ * @param item
+ */
+ public void itemOver(CustomMenuItem item) {
+ if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) {
+ setSelected(item);
+ if (!subMenu && openRootOnHover && !menuVisible) {
+ menuVisible = true; // start opening menus
+ LazyCloser.prepare(this);
+ }
+ }
+
+ if (menuVisible && visibleChildMenu != item.getSubMenu()
+ && popup != null) {
+ popup.hide();
+ }
+
+ if (menuVisible && item.getSubMenu() != null
+ && visibleChildMenu != item.getSubMenu()) {
+ showChildMenu(item);
+ }
+ }
+
+ /**
+ * When the mouse is moved away from an item
+ *
+ * @param item
+ */
+ public void itemOut(CustomMenuItem item) {
+ if (visibleChildMenu != item.getSubMenu()) {
+ hideChildMenu(item);
+ setSelected(null);
+ } else if (visibleChildMenu == null) {
+ setSelected(null);
+ }
+ }
+
+ /**
+ * Used to autoclose submenus when they the menu is in a mode which opens
+ * root menus on mouse hover.
+ */
+ private static class LazyCloser extends Timer {
+ static LazyCloser INSTANCE;
+ private VMenuBar activeRoot;
+
+ @Override
+ public void run() {
+ activeRoot.hideChildren();
+ activeRoot.setSelected(null);
+ activeRoot.menuVisible = false;
+ activeRoot = null;
+ }
+
+ public static void cancelClosing() {
+ if (INSTANCE != null) {
+ INSTANCE.cancel();
+ }
+ }
+
+ public static void prepare(VMenuBar vMenuBar) {
+ if (INSTANCE == null) {
+ INSTANCE = new LazyCloser();
+ }
+ if (INSTANCE.activeRoot == vMenuBar) {
+ INSTANCE.cancel();
+ } else if (INSTANCE.activeRoot != null) {
+ INSTANCE.cancel();
+ INSTANCE.run();
+ }
+ INSTANCE.activeRoot = vMenuBar;
+ }
+
+ public static void schedule() {
+ if (INSTANCE != null && INSTANCE.activeRoot != null) {
+ INSTANCE.schedule(750);
+ }
+ }
+
+ }
+
+ /**
+ * Shows the child menu of an item. The caller must ensure that the item has
+ * a submenu.
+ *
+ * @param item
+ */
+ public void showChildMenu(CustomMenuItem item) {
+
+ int left = 0;
+ int top = 0;
+ if (subMenu) {
+ left = item.getParentMenu().getAbsoluteLeft()
+ + item.getParentMenu().getOffsetWidth();
+ top = item.getAbsoluteTop();
+ } else {
+ left = item.getAbsoluteLeft();
+ top = item.getParentMenu().getAbsoluteTop()
+ + item.getParentMenu().getOffsetHeight();
+ }
+ showChildMenuAt(item, top, left);
+ }
+
+ protected void showChildMenuAt(CustomMenuItem item, int top, int left) {
+ final int shadowSpace = 10;
+
+ popup = new VOverlay(true, false, true);
+ popup.setOwner(this);
+
+ /*
+ * Use parents primary style name if possible and remove the submenu
+ * prefix if needed
+ */
+ String primaryStyleName = parentMenu != null ? parentMenu
+ .getStylePrimaryName() : getStylePrimaryName();
+ if (subMenu) {
+ primaryStyleName = primaryStyleName.replace(
+ SUBMENU_CLASSNAME_PREFIX, "");
+ }
+ popup.setStyleName(primaryStyleName + "-popup");
+
+ // Setting owner and handlers to support tooltips. Needed for tooltip
+ // handling of overlay widgets (will direct queries to parent menu)
+ if (parentMenu == null) {
+ popup.setOwner(this);
+ } else {
+ VMenuBar parent = parentMenu;
+ while (parent.getParentMenu() != null) {
+ parent = parent.getParentMenu();
+ }
+ popup.setOwner(parent);
+ }
+ if (client != null) {
+ client.getVTooltip().connectHandlersToWidget(popup);
+ }
+
+ popup.setWidget(item.getSubMenu());
+ popup.addCloseHandler(this);
+ popup.addAutoHidePartner(item.getElement());
+
+ // at 0,0 because otherwise IE7 add extra scrollbars (#5547)
+ popup.setPopupPosition(0, 0);
+
+ item.getSubMenu().onShow();
+ visibleChildMenu = item.getSubMenu();
+ item.getSubMenu().setParentMenu(this);
+
+ popup.show();
+
+ if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
+ .getOffsetWidth() - shadowSpace) {
+ if (subMenu) {
+ left = item.getParentMenu().getAbsoluteLeft()
+ - popup.getOffsetWidth() - shadowSpace;
+ } else {
+ left = RootPanel.getBodyElement().getOffsetWidth()
+ - popup.getOffsetWidth() - shadowSpace;
+ }
+ // Accommodate space for shadow
+ if (left < shadowSpace) {
+ left = shadowSpace;
+ }
+ }
+
+ top = adjustPopupHeight(top, shadowSpace);
+
+ popup.setPopupPosition(left, top);
+
+ }
+
+ private int adjustPopupHeight(int top, final int shadowSpace) {
+ // Check that the popup will fit the screen
+ int availableHeight = RootPanel.getBodyElement().getOffsetHeight()
+ - top - shadowSpace;
+ int missingHeight = popup.getOffsetHeight() - availableHeight;
+ if (missingHeight > 0) {
+ // First move the top of the popup to get more space
+ // Don't move above top of screen, don't move more than needed
+ int moveUpBy = Math.min(top - shadowSpace, missingHeight);
+
+ // Update state
+ top -= moveUpBy;
+ missingHeight -= moveUpBy;
+ availableHeight += moveUpBy;
+
+ if (missingHeight > 0) {
+ int contentWidth = visibleChildMenu.getOffsetWidth();
+
+ // If there's still not enough room, limit height to fit and add
+ // a scroll bar
+ Style style = popup.getElement().getStyle();
+ style.setHeight(availableHeight, Unit.PX);
+ style.setOverflowY(Overflow.SCROLL);
+
+ // Make room for the scroll bar by adjusting the width of the
+ // popup
+ style.setWidth(contentWidth + Util.getNativeScrollbarSize(),
+ Unit.PX);
+ popup.positionOrSizeUpdated();
+ }
+ }
+ return top;
+ }
+
+ /**
+ * Hides the submenu of an item
+ *
+ * @param item
+ */
+ public void hideChildMenu(CustomMenuItem item) {
+ if (visibleChildMenu != null
+ && !(visibleChildMenu == item.getSubMenu())) {
+ popup.hide();
+ }
+ }
+
+ /**
+ * When the menu is shown.
+ */
+ public void onShow() {
+ // remove possible previous selection
+ if (selected != null) {
+ selected.setSelected(false);
+ selected = null;
+ }
+ menuVisible = true;
+ }
+
+ /**
+ * Listener method, fired when this menu is closed
+ */
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ hideChildren();
+ if (event.isAutoClosed()) {
+ hideParents(true);
+ menuVisible = false;
+ }
+ visibleChildMenu = null;
+ popup = null;
+ }
+
+ /**
+ * Recursively hide all child menus
+ */
+ public void hideChildren() {
+ if (visibleChildMenu != null) {
+ visibleChildMenu.hideChildren();
+ popup.hide();
+ }
+ }
+
+ /**
+ * Recursively hide all parent menus
+ */
+ public void hideParents(boolean autoClosed) {
+ if (visibleChildMenu != null) {
+ popup.hide();
+ setSelected(null);
+ menuVisible = !autoClosed;
+ }
+
+ if (getParentMenu() != null) {
+ getParentMenu().hideParents(autoClosed);
+ }
+ }
+
+ /**
+ * Returns the parent menu of this menu, or null if this is the top-level
+ * menu
+ *
+ * @return
+ */
+ public VMenuBar getParentMenu() {
+ return parentMenu;
+ }
+
+ /**
+ * Set the parent menu of this menu
+ *
+ * @param parent
+ */
+ public void setParentMenu(VMenuBar parent) {
+ parentMenu = parent;
+ }
+
+ /**
+ * Returns the currently selected item of this menu, or null if nothing is
+ * selected
+ *
+ * @return
+ */
+ public CustomMenuItem getSelected() {
+ return selected;
+ }
+
+ /**
+ * Set the currently selected item of this menu
+ *
+ * @param item
+ */
+ public void setSelected(CustomMenuItem item) {
+ // If we had something selected, unselect
+ if (item != selected && selected != null) {
+ selected.setSelected(false);
+ }
+ // If we have a valid selection, select it
+ if (item != null) {
+ item.setSelected(true);
+ }
+
+ selected = item;
+ }
+
+ /**
+ *
+ * A class to hold information on menu items
+ *
+ */
+ public static class CustomMenuItem extends Widget implements HasHTML {
+
+ protected String html = null;
+ protected Command command = null;
+ protected VMenuBar subMenu = null;
+ protected VMenuBar parentMenu = null;
+ protected boolean enabled = true;
+ protected boolean isSeparator = false;
+ protected boolean checkable = false;
+ protected boolean checked = false;
+ protected boolean selected = false;
+ protected String description = null;
+
+ private String styleName;
+
+ /**
+ * Default menu item {@link Widget} constructor for GWT.create().
+ *
+ * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after
+ * constructing a menu item.
+ */
+ public CustomMenuItem() {
+ this("", null);
+ }
+
+ /**
+ * Creates a menu item {@link Widget}.
+ *
+ * @param html
+ * @param cmd
+ * @deprecated use the default constructor and {@link #setHTML(String)}
+ * and {@link #setCommand(Command)} instead
+ */
+ @Deprecated
+ public CustomMenuItem(String html, Command cmd) {
+ // We need spans to allow inline-block in IE
+ setElement(DOM.createSpan());
+
+ setHTML(html);
+ setCommand(cmd);
+ setSelected(false);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+
+ // Pass stylename down to submenus
+ if (getSubMenu() != null) {
+ getSubMenu().setStyleName(style);
+ }
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ updateStyleNames();
+ }
+
+ public void setChecked(boolean checked) {
+ if (checkable && !isSeparator) {
+ this.checked = checked;
+ } else {
+ this.checked = false;
+ }
+ updateStyleNames();
+ }
+
+ public boolean isChecked() {
+ return checked;
+ }
+
+ public void setCheckable(boolean checkable) {
+ if (checkable && !isSeparator) {
+ this.checkable = true;
+ } else {
+ setChecked(false);
+ this.checkable = false;
+ }
+ }
+
+ public boolean isCheckable() {
+ return checkable;
+ }
+
+ /*
+ * setters and getters for the fields
+ */
+
+ public void setSubMenu(VMenuBar subMenu) {
+ this.subMenu = subMenu;
+ }
+
+ public VMenuBar getSubMenu() {
+ return subMenu;
+ }
+
+ public void setParentMenu(VMenuBar parentMenu) {
+ this.parentMenu = parentMenu;
+ updateStyleNames();
+ }
+
+ protected void updateStyleNames() {
+ if (parentMenu == null) {
+ // Style names depend on the parent menu's primary style name so
+ // don't do updates until the item has a parent
+ return;
+ }
+
+ String primaryStyleName = parentMenu.getStylePrimaryName();
+ if (parentMenu.subMenu) {
+ primaryStyleName = primaryStyleName.replace(
+ SUBMENU_CLASSNAME_PREFIX, "");
+ }
+
+ if (isSeparator) {
+ super.setStyleName(primaryStyleName + "-separator");
+ } else {
+ super.setStyleName(primaryStyleName + "-menuitem");
+ }
+
+ if (styleName != null) {
+ addStyleDependentName(styleName);
+ }
+
+ if (enabled) {
+ removeStyleDependentName("disabled");
+ } else {
+ addStyleDependentName("disabled");
+ }
+
+ if (selected && isSelectable()) {
+ addStyleDependentName("selected");
+ // needed for IE6 to have a single style name to match for an
+ // element
+ // TODO Can be optimized now that IE6 is not supported any more
+ if (checkable) {
+ if (checked) {
+ removeStyleDependentName("selected-unchecked");
+ addStyleDependentName("selected-checked");
+ } else {
+ removeStyleDependentName("selected-checked");
+ addStyleDependentName("selected-unchecked");
+ }
+ }
+ } else {
+ removeStyleDependentName("selected");
+ // needed for IE6 to have a single style name to match for an
+ // element
+ removeStyleDependentName("selected-checked");
+ removeStyleDependentName("selected-unchecked");
+ }
+
+ if (checkable && !isSeparator) {
+ if (checked) {
+ addStyleDependentName("checked");
+ removeStyleDependentName("unchecked");
+ } else {
+ addStyleDependentName("unchecked");
+ removeStyleDependentName("checked");
+ }
+ }
+ }
+
+ public VMenuBar getParentMenu() {
+ return parentMenu;
+ }
+
+ public void setCommand(Command command) {
+ this.command = command;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+
+ @Override
+ public String getHTML() {
+ return html;
+ }
+
+ @Override
+ public void setHTML(String html) {
+ this.html = html;
+ DOM.setInnerHTML(getElement(), html);
+
+ // Sink the onload event for any icons. The onload
+ // events are handled by the parent VMenuBar.
+ Util.sinkOnloadForImages(getElement());
+ }
+
+ @Override
+ public String getText() {
+ return html;
+ }
+
+ @Override
+ public void setText(String text) {
+ setHTML(Util.escapeHTML(text));
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ updateStyleNames();
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ private void setSeparator(boolean separator) {
+ isSeparator = separator;
+ updateStyleNames();
+ if (!separator) {
+ setEnabled(enabled);
+ }
+ }
+
+ public boolean isSeparator() {
+ return isSeparator;
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ setSeparator(uidl.hasAttribute("separator"));
+ setEnabled(!uidl
+ .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED));
+
+ if (!isSeparator()
+ && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) {
+ // if the selected attribute is present (either true or false),
+ // the item is selectable
+ setCheckable(true);
+ setChecked(uidl
+ .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED));
+ } else {
+ setCheckable(false);
+ }
+
+ if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) {
+ styleName = uidl
+ .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE);
+ }
+
+ if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) {
+ description = uidl
+ .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION);
+ }
+
+ updateStyleNames();
+ }
+
+ public TooltipInfo getTooltip() {
+ if (description == null) {
+ return null;
+ }
+
+ return new TooltipInfo(description);
+ }
+
+ /**
+ * Checks if the item can be selected.
+ *
+ * @return true if it is possible to select this item, false otherwise
+ */
+ public boolean isSelectable() {
+ return !isSeparator() && isEnabled();
+ }
+
+ }
+
+ /**
+ * @author Jouni Koivuviita / Vaadin Ltd.
+ */
+ public void iLayout() {
+ iLayout(false);
+ updateSize();
+ }
+
+ public void iLayout(boolean iconLoadEvent) {
+ // Only collapse if there is more than one item in the root menu and the
+ // menu has an explicit size
+ if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems
+ .getItems().size() > 0))
+ && getElement().getStyle().getProperty("width") != null
+ && moreItem != null) {
+
+ // Measure the width of the "more" item
+ final boolean morePresent = getItems().contains(moreItem);
+ addItem(moreItem);
+ final int moreItemWidth = moreItem.getOffsetWidth();
+ if (!morePresent) {
+ removeItem(moreItem);
+ }
+
+ int availableWidth = LayoutManager.get(client).getInnerWidth(
+ getElement());
+
+ // Used width includes the "more" item if present
+ int usedWidth = getConsumedWidth();
+ int diff = availableWidth - usedWidth;
+ removeItem(moreItem);
+
+ if (diff < 0) {
+ // Too many items: collapse last items from root menu
+ int widthNeeded = usedWidth - availableWidth;
+ if (!morePresent) {
+ widthNeeded += moreItemWidth;
+ }
+ int widthReduced = 0;
+
+ while (widthReduced < widthNeeded && getItems().size() > 0) {
+ // Move last root menu item to collapsed menu
+ CustomMenuItem collapse = getItems().get(
+ getItems().size() - 1);
+ widthReduced += collapse.getOffsetWidth();
+ removeItem(collapse);
+ collapsedRootItems.addItem(collapse, 0);
+ }
+ } else if (collapsedRootItems.getItems().size() > 0) {
+ // Space available for items: expand first items from collapsed
+ // menu
+ int widthAvailable = diff + moreItemWidth;
+ int widthGrowth = 0;
+
+ while (widthAvailable > widthGrowth
+ && collapsedRootItems.getItems().size() > 0) {
+ // Move first item from collapsed menu to the root menu
+ CustomMenuItem expand = collapsedRootItems.getItems()
+ .get(0);
+ collapsedRootItems.removeItem(expand);
+ addItem(expand);
+ widthGrowth += expand.getOffsetWidth();
+ if (collapsedRootItems.getItems().size() > 0) {
+ widthAvailable -= moreItemWidth;
+ }
+ if (widthGrowth > widthAvailable) {
+ removeItem(expand);
+ collapsedRootItems.addItem(expand, 0);
+ } else {
+ widthAvailable = diff + moreItemWidth;
+ }
+ }
+ }
+ if (collapsedRootItems.getItems().size() > 0) {
+ addItem(moreItem);
+ }
+ }
+
+ // If a popup is open we might need to adjust the shadow as well if an
+ // icon shown in that popup was loaded
+ if (popup != null) {
+ // Forces a recalculation of the shadow size
+ popup.show();
+ }
+ if (iconLoadEvent) {
+ // Size have changed if the width is undefined
+ Util.notifyParentOfSizeChange(this, false);
+ }
+ }
+
+ private int getConsumedWidth() {
+ int w = 0;
+ for (CustomMenuItem item : getItems()) {
+ if (!collapsedRootItems.getItems().contains(item)) {
+ w += item.getOffsetWidth();
+ }
+ }
+ return w;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(),
+ event.isControlKeyDown() || event.isMetaKeyDown(),
+ event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(),
+ event.isControlKeyDown() || event.isMetaKeyDown(),
+ event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Get the key that moves the selection upwards. By default it is the up
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection downwards. By default it is the down
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that moves the selection left. By default it is the left
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that moves the selection right. By default it is the right
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects a menu item. By default it is the Enter key but
+ * by overriding this you can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return KeyCodes.KEY_ENTER;
+ }
+
+ /**
+ * Get the key that closes the menu. By default it is the escape key but by
+ * overriding this yoy can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getCloseMenuKey() {
+ return KeyCodes.KEY_ESCAPE;
+ }
+
+ /**
+ * Handles the keyboard events handled by the MenuBar
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+
+ // If tab or shift+tab close menus
+ if (keycode == KeyCodes.KEY_TAB) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+ return false;
+ }
+
+ if (ctrl || shift || !isEnabled()) {
+ // Do not handle tab key, nor ctrl keys
+ return false;
+ }
+
+ if (keycode == getNavigationLeftKey()) {
+ if (getSelected() == null) {
+ // If nothing is selected then select the last item
+ setSelected(items.get(items.size() - 1));
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu then move to the left
+ int idx = items.indexOf(getSelected());
+ if (idx > 0) {
+ setSelected(items.get(idx - 1));
+ } else {
+ setSelected(items.get(items.size() - 1));
+ }
+
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+
+ } else if (getParentMenu().getParentMenu() == null) {
+ // Inside a sub menu, whose parent is a root menu item
+ VMenuBar root = getParentMenu();
+
+ root.getSelected().getSubMenu().setSelected(null);
+ root.hideChildren();
+
+ // Get the root menus items and select the previous one
+ int idx = root.getItems().indexOf(root.getSelected());
+ idx = idx > 0 ? idx : root.getItems().size();
+ CustomMenuItem selected = root.getItems().get(--idx);
+
+ while (selected.isSeparator() || !selected.isEnabled()) {
+ idx = idx > 0 ? idx : root.getItems().size();
+ selected = root.getItems().get(--idx);
+ }
+
+ root.setSelected(selected);
+ openMenuAndFocusFirstIfPossible(selected);
+ } else {
+ getParentMenu().getSelected().getSubMenu().setSelected(null);
+ getParentMenu().hideChildren();
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationRightKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ setSelected(items.get(0));
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu then move to the right
+ int idx = items.indexOf(getSelected());
+
+ if (idx < items.size() - 1) {
+ setSelected(items.get(idx + 1));
+ } else {
+ setSelected(items.get(0));
+ }
+
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu == null
+ && getSelected().getSubMenu() != null) {
+ // If the item has a submenu then show it and move the selection
+ // there
+ showChildMenu(getSelected());
+ menuVisible = true;
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else if (visibleChildMenu == null) {
+
+ // Get the root menu
+ VMenuBar root = getParentMenu();
+ while (root.getParentMenu() != null) {
+ root = root.getParentMenu();
+ }
+
+ // Hide the submenu
+ root.hideChildren();
+
+ // Get the root menus items and select the next one
+ int idx = root.getItems().indexOf(root.getSelected());
+ idx = idx < root.getItems().size() - 1 ? idx : -1;
+ CustomMenuItem selected = root.getItems().get(++idx);
+
+ while (selected.isSeparator() || !selected.isEnabled()) {
+ idx = idx < root.getItems().size() - 1 ? idx : -1;
+ selected = root.getItems().get(++idx);
+ }
+
+ root.setSelected(selected);
+ openMenuAndFocusFirstIfPossible(selected);
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationUpKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the last item
+ setSelected(items.get(items.size() - 1));
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else {
+ // Select the previous item if possible or loop to the last item
+ int idx = items.indexOf(getSelected());
+ if (idx > 0) {
+ setSelected(items.get(idx - 1));
+ } else {
+ setSelected(items.get(items.size() - 1));
+ }
+
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ }
+
+ return true;
+
+ } else if (keycode == getNavigationDownKey()) {
+
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ selectFirstItem();
+ } else if (visibleChildMenu == null && getParentMenu() == null) {
+ // If this is the root menu the show the child menu with arrow
+ // down, if there is a child menu
+ openMenuAndFocusFirstIfPossible(getSelected());
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ } else {
+ // Select the next item if possible or loop to the first item
+ int idx = items.indexOf(getSelected());
+ if (idx < items.size() - 1) {
+ setSelected(items.get(idx + 1));
+ } else {
+ setSelected(items.get(0));
+ }
+
+ if (!getSelected().isSelectable()) {
+ handleNavigation(keycode, ctrl, shift);
+ }
+ }
+ return true;
+
+ } else if (keycode == getCloseMenuKey()) {
+ setSelected(null);
+ hideChildren();
+ menuVisible = false;
+
+ } else if (keycode == getNavigationSelectKey()) {
+ if (getSelected() == null) {
+ // If nothing is selected then select the first item
+ selectFirstItem();
+ } else if (visibleChildMenu != null) {
+ // Redirect all navigation to the submenu
+ visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+ menuVisible = false;
+ } else if (visibleChildMenu == null
+ && getSelected().getSubMenu() != null) {
+ // If the item has a sub menu then show it and move the
+ // selection there
+ openMenuAndFocusFirstIfPossible(getSelected());
+ } else {
+ Command command = getSelected().getCommand();
+ if (command != null) {
+ command.execute();
+ }
+
+ setSelected(null);
+ hideParents(true);
+ }
+ }
+
+ return false;
+ }
+
+ private void selectFirstItem() {
+ for (int i = 0; i < items.size(); i++) {
+ CustomMenuItem item = items.get(i);
+ if (item.isSelectable()) {
+ setSelected(item);
+ break;
+ }
+ }
+ }
+
+ private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) {
+ VMenuBar subMenu = menuItem.getSubMenu();
+ if (subMenu == null) {
+ // No child menu? Nothing to do
+ return;
+ }
+
+ VMenuBar parentMenu = menuItem.getParentMenu();
+ parentMenu.showChildMenu(menuItem);
+
+ menuVisible = true;
+ // Select the first item in the newly open submenu
+ subMenu.selectFirstItem();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ @Override
+ public void onFocus(FocusEvent event) {
+
+ }
+
+ private final String SUBPART_PREFIX = "item";
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ int index = Integer
+ .parseInt(subPart.substring(SUBPART_PREFIX.length()));
+ CustomMenuItem item = getItems().get(index);
+
+ return item.getElement();
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (!getElement().isOrHasChild(subElement)) {
+ return null;
+ }
+
+ Element menuItemRoot = subElement;
+ while (menuItemRoot != null && menuItemRoot.getParentElement() != null
+ && menuItemRoot.getParentElement() != getElement()) {
+ menuItemRoot = menuItemRoot.getParentElement().cast();
+ }
+ // "menuItemRoot" is now the root of the menu item
+
+ final int itemCount = getItems().size();
+ for (int i = 0; i < itemCount; i++) {
+ if (getItems().get(i).getElement() == menuItemRoot) {
+ String name = SUBPART_PREFIX + i;
+ return name;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get menu item with given DOM element
+ *
+ * @param element
+ * Element used in search
+ * @return Menu item or null if not found
+ */
+ public CustomMenuItem getMenuItemWithElement(Element element) {
+ for (int i = 0; i < items.size(); i++) {
+ CustomMenuItem item = items.get(i);
+ if (DOM.isOrHasChild(item.getElement(), element)) {
+ return item;
+ }
+
+ if (item.getSubMenu() != null) {
+ item = item.getSubMenu().getMenuItemWithElement(element);
+ if (item != null) {
+ return item;
+ }
+ }
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Button;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.Util;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.button.ButtonServerRpc;
+
+public class VNativeButton extends Button implements ClickHandler {
+
+ public static final String CLASSNAME = "v-nativebutton";
+
+ protected String width = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ButtonServerRpc buttonRpcProxy;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element errorIndicatorElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element captionElement = DOM.createSpan();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Icon icon;
+
+ /**
+ * Helper flag to handle special-case where the button is moved from under
+ * mouse while clicking it. In this case mouse leaves the button without
+ * moving.
+ */
+ private boolean clickPending;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean disableOnClick = false;
+
+ public VNativeButton() {
+ setStyleName(CLASSNAME);
+
+ getElement().appendChild(captionElement);
+ captionElement.setClassName(getStyleName() + "-caption");
+
+ addClickHandler(this);
+
+ sinkEvents(Event.ONMOUSEDOWN);
+ sinkEvents(Event.ONMOUSEUP);
+ }
+
+ @Override
+ public void setText(String text) {
+ captionElement.setInnerText(text);
+ }
+
+ @Override
+ public void setHTML(String html) {
+ captionElement.setInnerHTML(html);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ if (DOM.eventGetType(event) == Event.ONLOAD) {
+ Util.notifyParentOfSizeChange(this, true);
+
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN
+ && event.getButton() == Event.BUTTON_LEFT) {
+ clickPending = true;
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) {
+ clickPending = false;
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) {
+ if (clickPending) {
+ click();
+ }
+ clickPending = false;
+ }
+ }
+
+ @Override
+ public void setWidth(String width) {
+ this.width = width;
+ super.setWidth(width);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+ * .dom.client.ClickEvent)
+ */
+ @Override
+ public void onClick(ClickEvent event) {
+ if (paintableId == null || client == null) {
+ return;
+ }
+
+ if (BrowserInfo.get().isSafari()) {
+ VNativeButton.this.setFocus(true);
+ }
+ if (disableOnClick) {
+ setEnabled(false);
+ // FIXME: This should be moved to NativeButtonConnector along with
+ // buttonRpcProxy
+ addStyleName(ApplicationConnection.DISABLED_CLASSNAME);
+ buttonRpcProxy.disableOnClick();
+ }
+
+ // Add mouse details
+ MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(event.getNativeEvent(), getElement());
+ buttonRpcProxy.click(details);
+
+ clickPending = false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.client.UIDL;
+
+public class VNativeSelect extends VOptionGroupBase implements Field {
+
+ public static final String CLASSNAME = "v-select";
+
+ protected ListBox select;
+
+ private boolean firstValueIsTemporaryNullItem = false;
+
+ public VNativeSelect() {
+ super(new ListBox(false), CLASSNAME);
+ select = getOptionsContainer();
+ select.setVisibleItemCount(1);
+ select.addChangeHandler(this);
+ select.setStyleName(CLASSNAME + "-select");
+
+ }
+
+ protected ListBox getOptionsContainer() {
+ return (ListBox) optionsContainer;
+ }
+
+ @Override
+ public void buildOptions(UIDL uidl) {
+ select.setEnabled(!isDisabled() && !isReadonly());
+ select.clear();
+ firstValueIsTemporaryNullItem = false;
+
+ if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) {
+ // can't unselect last item in singleselect mode
+ select.addItem("", (String) null);
+ }
+ boolean selected = false;
+ for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ select.addItem(optionUidl.getStringAttribute("caption"),
+ optionUidl.getStringAttribute("key"));
+ if (optionUidl.hasAttribute("selected")) {
+ select.setItemSelected(select.getItemCount() - 1, true);
+ selected = true;
+ }
+ }
+ if (!selected && !isNullSelectionAllowed()) {
+ // null-select not allowed, but value not selected yet; add null and
+ // remove when something is selected
+ select.insertItem("", (String) null, 0);
+ select.setItemSelected(0, true);
+ firstValueIsTemporaryNullItem = true;
+ }
+ }
+
+ @Override
+ protected String[] getSelectedItems() {
+ final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+ for (int i = 0; i < select.getItemCount(); i++) {
+ if (select.isItemSelected(i)) {
+ selectedItemKeys.add(select.getValue(i));
+ }
+ }
+ return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+ }
+
+ @Override
+ public void onChange(ChangeEvent event) {
+
+ if (select.isMultipleSelect()) {
+ client.updateVariable(paintableId, "selected", getSelectedItems(),
+ isImmediate());
+ } else {
+ client.updateVariable(paintableId, "selected", new String[] { ""
+ + getSelectedItem() }, isImmediate());
+ }
+ if (firstValueIsTemporaryNullItem) {
+ // remove temporary empty item
+ select.removeItem(0);
+ firstValueIsTemporaryNullItem = false;
+ }
+ }
+
+ @Override
+ public void setHeight(String height) {
+ select.setHeight(height);
+ super.setHeight(height);
+ }
+
+ @Override
+ public void setWidth(String width) {
+ select.setWidth(width);
+ super.setWidth(width);
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ getOptionsContainer().setTabIndex(tabIndex);
+ }
+
+ @Override
+ public void focus() {
+ select.setFocus(true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.Position;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+public class VNotification extends VOverlay {
+
+ public static final Position CENTERED = Position.MIDDLE_CENTER;
+ public static final Position CENTERED_TOP = Position.TOP_CENTER;
+ public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
+ public static final Position TOP_LEFT = Position.TOP_LEFT;
+ public static final Position TOP_RIGHT = Position.TOP_RIGHT;
+ public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
+ public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
+
+ public static final int DELAY_FOREVER = -1;
+ public static final int DELAY_NONE = 0;
+
+ private static final String STYLENAME = "v-Notification";
+ private static final int mouseMoveThreshold = 7;
+ private static final int Z_INDEX_BASE = 20000;
+ public static final String STYLE_SYSTEM = "system";
+ private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
+
+ private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
+
+ private int startOpacity = 90;
+ private int fadeMsec = 400;
+ private int delayMsec = 1000;
+
+ private Timer fader;
+ private Timer delay;
+
+ private int x = -1;
+ private int y = -1;
+
+ private String temporaryStyle;
+
+ private ArrayList<EventListener> listeners;
+ private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
+
+ /**
+ * Default constructor. You should use GWT.create instead.
+ */
+ public VNotification() {
+ setStyleName(STYLENAME);
+ sinkEvents(Event.ONCLICK);
+ DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
+ }
+
+ /**
+ * @deprecated Use static {@link #createNotification(int)} instead to enable
+ * GWT deferred binding.
+ *
+ * @param delayMsec
+ */
+ @Deprecated
+ public VNotification(int delayMsec) {
+ this();
+ this.delayMsec = delayMsec;
+ if (BrowserInfo.get().isTouchDevice()) {
+ new Timer() {
+ @Override
+ public void run() {
+ if (isAttached()) {
+ fade();
+ }
+ }
+ }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
+ }
+ }
+
+ /**
+ * @deprecated Use static {@link #createNotification(int, int, int)} instead
+ * to enable GWT deferred binding.
+ *
+ * @param delayMsec
+ * @param fadeMsec
+ * @param startOpacity
+ */
+ @Deprecated
+ public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
+ this(delayMsec);
+ this.fadeMsec = fadeMsec;
+ this.startOpacity = startOpacity;
+ }
+
+ public void startDelay() {
+ DOM.removeEventPreview(this);
+ if (delayMsec > 0) {
+ if (delay == null) {
+ delay = new Timer() {
+ @Override
+ public void run() {
+ fade();
+ }
+ };
+ delay.schedule(delayMsec);
+ }
+ } else if (delayMsec == 0) {
+ fade();
+ }
+ }
+
+ @Override
+ public void show() {
+ show(CENTERED);
+ }
+
+ public void show(String style) {
+ show(CENTERED, style);
+ }
+
+ public void show(com.vaadin.shared.Position position) {
+ show(position, null);
+ }
+
+ public void show(Widget widget, Position position, String style) {
+ setWidget(widget);
+ show(position, style);
+ }
+
+ public void show(String html, Position position, String style) {
+ setWidget(new HTML(html));
+ show(position, style);
+ }
+
+ public void show(Position position, String style) {
+ setOpacity(getElement(), startOpacity);
+ if (style != null) {
+ temporaryStyle = style;
+ addStyleName(style);
+ addStyleDependentName(style);
+ }
+ super.show();
+ notifications.add(this);
+ setPosition(position);
+ positionOrSizeUpdated();
+ /**
+ * Android 4 fails to render notifications correctly without a little
+ * nudge (#8551)
+ */
+ if (BrowserInfo.get().isAndroid()) {
+ Util.setStyleTemporarily(getElement(), "display", "none");
+ }
+ }
+
+ @Override
+ public void hide() {
+ DOM.removeEventPreview(this);
+ cancelDelay();
+ cancelFade();
+ if (temporaryStyle != null) {
+ removeStyleName(temporaryStyle);
+ removeStyleDependentName(temporaryStyle);
+ temporaryStyle = null;
+ }
+ super.hide();
+ notifications.remove(this);
+ fireEvent(new HideEvent(this));
+ }
+
+ public void fade() {
+ DOM.removeEventPreview(this);
+ cancelDelay();
+ if (fader == null) {
+ fader = new Timer() {
+ private final long start = new Date().getTime();
+
+ @Override
+ public void run() {
+ /*
+ * To make animation smooth, don't count that event happens
+ * on time. Reduce opacity according to the actual time
+ * spent instead of fixed decrement.
+ */
+ long now = new Date().getTime();
+ long timeEplaced = now - start;
+ float remainingFraction = 1 - timeEplaced
+ / (float) fadeMsec;
+ int opacity = (int) (startOpacity * remainingFraction);
+ if (opacity <= 0) {
+ cancel();
+ hide();
+ if (BrowserInfo.get().isOpera()) {
+ // tray notification on opera needs to explicitly
+ // define
+ // size, reset it
+ DOM.setStyleAttribute(getElement(), "width", "");
+ DOM.setStyleAttribute(getElement(), "height", "");
+ }
+ } else {
+ setOpacity(getElement(), opacity);
+ }
+ }
+ };
+ fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
+ }
+ }
+
+ public void setPosition(com.vaadin.shared.Position position) {
+ final Element el = getElement();
+ DOM.setStyleAttribute(el, "top", "");
+ DOM.setStyleAttribute(el, "left", "");
+ DOM.setStyleAttribute(el, "bottom", "");
+ DOM.setStyleAttribute(el, "right", "");
+ switch (position) {
+ case TOP_LEFT:
+ DOM.setStyleAttribute(el, "top", "0px");
+ DOM.setStyleAttribute(el, "left", "0px");
+ break;
+ case TOP_RIGHT:
+ DOM.setStyleAttribute(el, "top", "0px");
+ DOM.setStyleAttribute(el, "right", "0px");
+ break;
+ case BOTTOM_RIGHT:
+ DOM.setStyleAttribute(el, "position", "absolute");
+ if (BrowserInfo.get().isOpera()) {
+ // tray notification on opera needs explicitly defined size
+ DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
+ DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
+ }
+ DOM.setStyleAttribute(el, "bottom", "0px");
+ DOM.setStyleAttribute(el, "right", "0px");
+ break;
+ case BOTTOM_LEFT:
+ DOM.setStyleAttribute(el, "bottom", "0px");
+ DOM.setStyleAttribute(el, "left", "0px");
+ break;
+ case TOP_CENTER:
+ center();
+ DOM.setStyleAttribute(el, "top", "0px");
+ break;
+ case BOTTOM_CENTER:
+ center();
+ DOM.setStyleAttribute(el, "top", "");
+ DOM.setStyleAttribute(el, "bottom", "0px");
+ break;
+ default:
+ case MIDDLE_CENTER:
+ center();
+ break;
+ }
+ }
+
+ private void cancelFade() {
+ if (fader != null) {
+ fader.cancel();
+ fader = null;
+ }
+ }
+
+ private void cancelDelay() {
+ if (delay != null) {
+ delay.cancel();
+ delay = null;
+ }
+ }
+
+ private void setOpacity(Element el, int opacity) {
+ DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
+ if (BrowserInfo.get().isIE()) {
+ DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
+ + ")");
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ DOM.removeEventPreview(this);
+ if (fader == null) {
+ fade();
+ }
+ }
+
+ @Override
+ public boolean onEventPreview(Event event) {
+ int type = DOM.eventGetType(event);
+ // "modal"
+ if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
+ if (type == Event.ONCLICK) {
+ if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+ fade();
+ return false;
+ }
+ } else if (type == Event.ONKEYDOWN
+ && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ fade();
+ return false;
+ }
+ if (temporaryStyle == STYLE_SYSTEM) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // default
+ switch (type) {
+ case Event.ONMOUSEMOVE:
+
+ if (x < 0) {
+ x = DOM.eventGetClientX(event);
+ y = DOM.eventGetClientY(event);
+ } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
+ || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
+ startDelay();
+ }
+ break;
+ case Event.ONMOUSEDOWN:
+ case Event.ONMOUSEWHEEL:
+ case Event.ONSCROLL:
+ startDelay();
+ break;
+ case Event.ONKEYDOWN:
+ if (event.getRepeat()) {
+ return true;
+ }
+ startDelay();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public void addEventListener(EventListener listener) {
+ if (listeners == null) {
+ listeners = new ArrayList<EventListener>();
+ }
+ listeners.add(listener);
+ }
+
+ public void removeEventListener(EventListener listener) {
+ if (listeners == null) {
+ return;
+ }
+ listeners.remove(listener);
+ }
+
+ private void fireEvent(HideEvent event) {
+ if (listeners != null) {
+ for (Iterator<EventListener> it = listeners.iterator(); it
+ .hasNext();) {
+ EventListener l = it.next();
+ l.notificationHidden(event);
+ }
+ }
+ }
+
+ public static void showNotification(ApplicationConnection client,
+ final UIDL notification) {
+ boolean onlyPlainText = notification
+ .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
+ String html = "";
+ if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
+ final String parsedUri = client
+ .translateVaadinUri(notification
+ .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON));
+ html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
+ }
+ if (notification
+ .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
+ String caption = notification
+ .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
+ if (onlyPlainText) {
+ caption = Util.escapeHTML(caption);
+ caption = caption.replaceAll("\\n", "<br />");
+ }
+ html += "<h1>" + caption + "</h1>";
+ }
+ if (notification
+ .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
+ String message = notification
+ .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
+ if (onlyPlainText) {
+ message = Util.escapeHTML(message);
+ message = message.replaceAll("\\n", "<br />");
+ }
+ html += "<p>" + message + "</p>";
+ }
+
+ final String style = notification
+ .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
+ .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
+ : null;
+
+ final int pos = notification
+ .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
+ Position position = Position.values()[pos];
+
+ final int delay = notification
+ .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
+ createNotification(delay, client.getUIConnector().getWidget()).show(
+ html, position, style);
+ }
+
+ public static VNotification createNotification(int delayMsec, Widget owner) {
+ final VNotification notification = GWT.create(VNotification.class);
+ notification.delayMsec = delayMsec;
+ if (BrowserInfo.get().isTouchDevice()) {
+ new Timer() {
+ @Override
+ public void run() {
+ if (notification.isAttached()) {
+ notification.fade();
+ }
+ }
+ }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
+ }
+ notification.setOwner(owner);
+ return notification;
+ }
+
+ public class HideEvent extends EventObject {
+
+ public HideEvent(Object source) {
+ super(source);
+ }
+ }
+
+ public interface EventListener extends java.util.EventListener {
+ public void notificationHidden(HideEvent event);
+ }
+
+ /**
+ * Moves currently visible notifications to the top of the event preview
+ * stack. Can be called when opening other overlays such as subwindows to
+ * ensure the notifications receive the events they need and don't linger
+ * indefinitely. See #7136.
+ *
+ * TODO Should this be a generic Overlay feature instead?
+ */
+ public static void bringNotificationsToFront() {
+ for (VNotification notification : notifications) {
+ DOM.removeEventPreview(notification);
+ DOM.addEventPreview(notification);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
+
+public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
+ BlurHandler {
+
+ public static final String CLASSNAME = "v-select-optiongroup";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Panel panel;
+
+ private final Map<CheckBox, String> optionsToKeys;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean sendFocusEvents = false;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean sendBlurEvents = false;
+ /** For internal use only. May be removed or replaced in the future. */
+ public List<HandlerRegistration> focusHandlers = null;
+ /** For internal use only. May be removed or replaced in the future. */
+ public List<HandlerRegistration> blurHandlers = null;
+
+ private final LoadHandler iconLoadHandler = new LoadHandler() {
+ @Override
+ public void onLoad(LoadEvent event) {
+ Util.notifyParentOfSizeChange(VOptionGroup.this, true);
+ }
+ };
+
+ /**
+ * used to check whether a blur really was a blur of the complete
+ * optiongroup: if a control inside this optiongroup gains focus right after
+ * blur of another control inside this optiongroup (meaning: if onFocus
+ * fires after onBlur has fired), the blur and focus won't be sent to the
+ * server side as only a focus change inside this optiongroup occured
+ */
+ private boolean blurOccured = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean htmlContentAllowed = false;
+
+ public VOptionGroup() {
+ super(CLASSNAME);
+ panel = (Panel) optionsContainer;
+ optionsToKeys = new HashMap<CheckBox, String>();
+ }
+
+ /*
+ * Return true if no elements were changed, false otherwise.
+ */
+ @Override
+ public void buildOptions(UIDL uidl) {
+ panel.clear();
+ for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
+ final UIDL opUidl = (UIDL) it.next();
+ CheckBox op;
+
+ String itemHtml = opUidl.getStringAttribute("caption");
+ if (!htmlContentAllowed) {
+ itemHtml = Util.escapeHTML(itemHtml);
+ }
+
+ String icon = opUidl.getStringAttribute("icon");
+ if (icon != null && icon.length() != 0) {
+ String iconUrl = client.translateVaadinUri(icon);
+ itemHtml = "<img src=\"" + iconUrl + "\" class=\""
+ + Icon.CLASSNAME + "\" alt=\"\" />" + itemHtml;
+ }
+
+ if (isMultiselect()) {
+ op = new VCheckBox();
+ op.setHTML(itemHtml);
+ } else {
+ op = new RadioButton(paintableId, itemHtml, true);
+ op.setStyleName("v-radiobutton");
+ }
+
+ if (icon != null && icon.length() != 0) {
+ Util.sinkOnloadForImages(op.getElement());
+ op.addHandler(iconLoadHandler, LoadEvent.getType());
+ }
+
+ op.addStyleName(CLASSNAME_OPTION);
+ op.setValue(opUidl.getBooleanAttribute("selected"));
+ boolean enabled = !opUidl
+ .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED)
+ && !isReadonly() && !isDisabled();
+ op.setEnabled(enabled);
+ setStyleName(op.getElement(),
+ ApplicationConnection.DISABLED_CLASSNAME, !enabled);
+ op.addClickHandler(this);
+ optionsToKeys.put(op, opUidl.getStringAttribute("key"));
+ panel.add(op);
+ }
+ }
+
+ @Override
+ protected String[] getSelectedItems() {
+ return selectedKeys.toArray(new String[selectedKeys.size()]);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ super.onClick(event);
+ if (event.getSource() instanceof CheckBox) {
+ final boolean selected = ((CheckBox) event.getSource()).getValue();
+ final String key = optionsToKeys.get(event.getSource());
+ if (!isMultiselect()) {
+ selectedKeys.clear();
+ }
+ if (selected) {
+ selectedKeys.add(key);
+ } else {
+ selectedKeys.remove(key);
+ }
+ client.updateVariable(paintableId, "selected", getSelectedItems(),
+ isImmediate());
+ }
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ for (Iterator<Widget> iterator = panel.iterator(); iterator.hasNext();) {
+ FocusWidget widget = (FocusWidget) iterator.next();
+ widget.setTabIndex(tabIndex);
+ }
+ }
+
+ @Override
+ public void focus() {
+ Iterator<Widget> iterator = panel.iterator();
+ if (iterator.hasNext()) {
+ ((Focusable) iterator.next()).setFocus(true);
+ }
+ }
+
+ @Override
+ public void onFocus(FocusEvent arg0) {
+ if (!blurOccured) {
+ // no blur occured before this focus event
+ // panel was blurred => fire the event to the server side if
+ // requested by server side
+ if (sendFocusEvents) {
+ client.updateVariable(paintableId, EventId.FOCUS, "", true);
+ }
+ } else {
+ // blur occured before this focus event
+ // another control inside the panel (checkbox / radio box) was
+ // blurred => do not fire the focus and set blurOccured to false, so
+ // blur will not be fired, too
+ blurOccured = false;
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent arg0) {
+ blurOccured = true;
+ if (sendBlurEvents) {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ // check whether blurOccured still is true and then send the
+ // event out to the server
+ if (blurOccured) {
+ client.updateVariable(paintableId, EventId.BLUR, "",
+ true);
+ blurOccured = false;
+ }
+ }
+ });
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.UIDL;
+
+public abstract class VOptionGroupBase extends Composite implements Field,
+ ClickHandler, ChangeHandler, KeyPressHandler, Focusable {
+
+ public static final String CLASSNAME_OPTION = "v-select-option";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Set<String> selectedKeys;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean multiselect;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean disabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean readonly;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int cols = 0;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int rows = 0;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean nullSelectionAllowed = true;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean nullSelectionItemAvailable = false;
+
+ /**
+ * Widget holding the different options (e.g. ListBox or Panel for radio
+ * buttons) (optional, fallbacks to container Panel)
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public Widget optionsContainer;
+
+ /**
+ * Panel containing the component.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public final Panel container;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VTextField newItemField;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VNativeButton newItemButton;
+
+ public VOptionGroupBase(String classname) {
+ container = new FlowPanel();
+ initWidget(container);
+ optionsContainer = container;
+ container.setStyleName(classname);
+ immediate = false;
+ multiselect = false;
+ }
+
+ /*
+ * Call this if you wish to specify your own container for the option
+ * elements (e.g. SELECT)
+ */
+ public VOptionGroupBase(Widget w, String classname) {
+ this(classname);
+ optionsContainer = w;
+ container.add(optionsContainer);
+ }
+
+ protected boolean isImmediate() {
+ return immediate;
+ }
+
+ protected boolean isMultiselect() {
+ return multiselect;
+ }
+
+ protected boolean isDisabled() {
+ return disabled;
+ }
+
+ protected boolean isReadonly() {
+ return readonly;
+ }
+
+ protected boolean isNullSelectionAllowed() {
+ return nullSelectionAllowed;
+ }
+
+ protected boolean isNullSelectionItemAvailable() {
+ return nullSelectionItemAvailable;
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @return "cols" specified in uidl, 0 if not specified
+ */
+ public int getColumns() {
+ return cols;
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @return "rows" specified in uidl, 0 if not specified
+ */
+ public int getRows() {
+ return rows;
+ }
+
+ public abstract void setTabIndex(int tabIndex);
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getSource() == newItemButton
+ && !newItemField.getText().equals("")) {
+ client.updateVariable(paintableId, "newitem",
+ newItemField.getText(), true);
+ newItemField.setText("");
+ }
+ }
+
+ @Override
+ public void onChange(ChangeEvent event) {
+ if (multiselect) {
+ client.updateVariable(paintableId, "selected", getSelectedItems(),
+ immediate);
+ } else {
+ client.updateVariable(paintableId, "selected", new String[] { ""
+ + getSelectedItem() }, immediate);
+ }
+ }
+
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (event.getSource() == newItemField
+ && event.getCharCode() == KeyCodes.KEY_ENTER) {
+ newItemButton.click();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public abstract void buildOptions(UIDL uidl);
+
+ protected abstract String[] getSelectedItems();
+
+ protected String getSelectedItem() {
+ final String[] sel = getSelectedItems();
+ if (sel.length > 0) {
+ return sel[0];
+ } else {
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.layout.ElementResizeListener;
+import com.vaadin.shared.ui.AlignmentInfo;
+import com.vaadin.shared.ui.MarginInfo;
+
+/**
+ * Base class for ordered layouts
+ */
+public class VOrderedLayout extends FlowPanel {
+
+ private static final String ALIGN_CLASS_PREFIX = "v-align-";
+
+ protected boolean spacing = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean vertical = true;
+
+ protected boolean definedHeight = false;
+
+ private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
+
+ private Element expandWrapper;
+
+ private LayoutManager layoutManager;
+
+ public VOrderedLayout(boolean vertical) {
+ this.vertical = vertical;
+ }
+
+ /**
+ * Add or move a slot to another index.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ * <p>
+ * You should note that the index does not refer to the DOM index if
+ * spacings are used. If spacings are used then the index will be adjusted
+ * to include the spacings when inserted.
+ * <p>
+ * For instance when using spacing the index converts to DOM index in the
+ * following way:
+ *
+ * <pre>
+ * index : 0 -> DOM index: 0
+ * index : 1 -> DOM index: 1
+ * index : 2 -> DOM index: 3
+ * index : 3 -> DOM index: 5
+ * index : 4 -> DOM index: 7
+ * </pre>
+ *
+ * When using this method never account for spacings.
+ * </p>
+ *
+ * @param slot
+ * The slot to move or add
+ * @param index
+ * The index where the slot should be placed.
+ */
+ public void addOrMoveSlot(Slot slot, int index) {
+ if (slot.getParent() == this) {
+ int currentIndex = getWidgetIndex(slot);
+ if (index == currentIndex) {
+ return;
+ }
+ }
+
+ insert(slot, index);
+
+ /*
+ * We need to confirm spacings are correctly applied after each insert.
+ */
+ setSpacing(spacing);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void insert(Widget child, Element container, int beforeIndex,
+ boolean domInsert) {
+ // Validate index; adjust if the widget is already a child of this
+ // panel.
+ beforeIndex = adjustIndex(child, beforeIndex);
+
+ // Detach new child.
+ child.removeFromParent();
+
+ // Logical attach.
+ getChildren().insert(child, beforeIndex);
+
+ // Physical attach.
+ container = expandWrapper != null ? expandWrapper : getElement();
+ if (domInsert) {
+ if (spacing) {
+ if (beforeIndex != 0) {
+ /*
+ * Since the spacing elements are located at the same DOM
+ * level as the slots we need to take them into account when
+ * calculating the slot position.
+ *
+ * The spacing elements are always located before the actual
+ * slot except for the first slot which do not have a
+ * spacing element like this
+ *
+ * |<slot1><spacing2><slot2><spacing3><slot3>...|
+ */
+ beforeIndex = beforeIndex * 2 - 1;
+ }
+ }
+ DOM.insertChild(container, child.getElement(), beforeIndex);
+ } else {
+ DOM.appendChild(container, child.getElement());
+ }
+
+ // Adopt.
+ adopt(child);
+ }
+
+ /**
+ * Remove a slot from the layout
+ *
+ * @param widget
+ * @return
+ */
+ public void removeWidget(Widget widget) {
+ Slot slot = widgetToSlot.get(widget);
+ remove(slot);
+ widgetToSlot.remove(widget);
+ }
+
+ /**
+ * Get the containing slot for a widget. If no slot is found a new slot is
+ * created and returned.
+ *
+ * @param widget
+ * The widget whose slot you want to get
+ *
+ * @return
+ */
+ public Slot getSlot(Widget widget) {
+ Slot slot = widgetToSlot.get(widget);
+ if (slot == null) {
+ slot = new Slot(widget);
+ widgetToSlot.put(widget, slot);
+ }
+ return slot;
+ }
+
+ /**
+ * Gets a slot based on the widget element. If no slot is found then null is
+ * returned.
+ *
+ * @param widgetElement
+ * The element of the widget ( Same as getWidget().getElement() )
+ * @return
+ */
+ public Slot getSlot(Element widgetElement) {
+ for (Map.Entry<Widget, Slot> entry : widgetToSlot.entrySet()) {
+ if (entry.getKey().getElement() == widgetElement) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Defines where the caption should be placed
+ */
+ public enum CaptionPosition {
+ TOP, RIGHT, BOTTOM, LEFT
+ }
+
+ /**
+ * Represents a slot which contains the actual widget in the layout.
+ */
+ public final class Slot extends SimplePanel {
+
+ public static final String SLOT_CLASSNAME = "v-slot";
+
+ private Element spacer;
+ private Element captionWrap;
+ private Element caption;
+ private Element captionText;
+ private Icon icon;
+ private Element errorIcon;
+ private Element requiredIcon;
+
+ private ElementResizeListener captionResizeListener;
+
+ private ElementResizeListener widgetResizeListener;
+
+ private ElementResizeListener spacingResizeListener;
+
+ // Caption is placed after component unless there is some part which
+ // moves it above.
+ private CaptionPosition captionPosition = CaptionPosition.RIGHT;
+
+ private AlignmentInfo alignment;
+
+ private double expandRatio = -1;
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * The widget to put in the slot
+ *
+ * @param layoutManager
+ * The layout manager used by the layout
+ */
+ private Slot(Widget widget) {
+ setStyleName(SLOT_CLASSNAME);
+ setWidget(widget);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user
+ * .client.ui.Widget)
+ */
+ @Override
+ public boolean remove(Widget w) {
+ detachListeners();
+ return super.remove(w);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt
+ * .user.client.ui.Widget)
+ */
+ @Override
+ public void setWidget(Widget w) {
+ detachListeners();
+ super.setWidget(w);
+ attachListeners();
+ }
+
+ /**
+ * Attached resize listeners to the widget, caption and spacing elements
+ */
+ private void attachListeners() {
+ if (getWidget() != null && getLayoutManager() != null) {
+ LayoutManager lm = getLayoutManager();
+ if (getCaptionElement() != null
+ && captionResizeListener != null) {
+ lm.addElementResizeListener(getCaptionElement(),
+ captionResizeListener);
+ }
+ if (widgetResizeListener != null) {
+ lm.addElementResizeListener(getWidget().getElement(),
+ widgetResizeListener);
+ }
+ if (getSpacingElement() != null
+ && spacingResizeListener != null) {
+ lm.addElementResizeListener(getSpacingElement(),
+ spacingResizeListener);
+ }
+ }
+ }
+
+ /**
+ * Detaches resize listeners from the widget, caption and spacing
+ * elements
+ */
+ private void detachListeners() {
+ if (getWidget() != null && getLayoutManager() != null) {
+ LayoutManager lm = getLayoutManager();
+ if (getCaptionElement() != null
+ && captionResizeListener != null) {
+ lm.removeElementResizeListener(getCaptionElement(),
+ captionResizeListener);
+ }
+ if (widgetResizeListener != null) {
+ lm.removeElementResizeListener(getWidget().getElement(),
+ widgetResizeListener);
+ }
+ if (getSpacingElement() != null
+ && spacingResizeListener != null) {
+ lm.removeElementResizeListener(getSpacingElement(),
+ spacingResizeListener);
+ }
+ }
+ }
+
+ public ElementResizeListener getCaptionResizeListener() {
+ return captionResizeListener;
+ }
+
+ public void setCaptionResizeListener(
+ ElementResizeListener captionResizeListener) {
+ detachListeners();
+ this.captionResizeListener = captionResizeListener;
+ attachListeners();
+ }
+
+ public ElementResizeListener getWidgetResizeListener() {
+ return widgetResizeListener;
+ }
+
+ public void setWidgetResizeListener(
+ ElementResizeListener widgetResizeListener) {
+ detachListeners();
+ this.widgetResizeListener = widgetResizeListener;
+ attachListeners();
+ }
+
+ public ElementResizeListener getSpacingResizeListener() {
+ return spacingResizeListener;
+ }
+
+ public void setSpacingResizeListener(
+ ElementResizeListener spacingResizeListener) {
+ detachListeners();
+ this.spacingResizeListener = spacingResizeListener;
+ attachListeners();
+ }
+
+ /**
+ * Returns the alignment for the slot
+ *
+ */
+ public AlignmentInfo getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Sets the style names for the slot containing the widget
+ *
+ * @param stylenames
+ * The style names for the slot
+ */
+ protected void setStyleNames(String... stylenames) {
+ setStyleName(SLOT_CLASSNAME);
+ if (stylenames != null) {
+ for (String stylename : stylenames) {
+ addStyleDependentName(stylename);
+ }
+ }
+
+ // Ensure alignment style names are correct
+ setAlignment(alignment);
+ }
+
+ /**
+ * Sets how the widget is aligned inside the slot
+ *
+ * @param alignment
+ * The alignment inside the slot
+ */
+ public void setAlignment(AlignmentInfo alignment) {
+ this.alignment = alignment;
+
+ if (alignment != null && alignment.isHorizontalCenter()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "center");
+ removeStyleName(ALIGN_CLASS_PREFIX + "right");
+ } else if (alignment != null && alignment.isRight()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "right");
+ removeStyleName(ALIGN_CLASS_PREFIX + "center");
+ } else {
+ removeStyleName(ALIGN_CLASS_PREFIX + "right");
+ removeStyleName(ALIGN_CLASS_PREFIX + "center");
+ }
+
+ if (alignment != null && alignment.isVerticalCenter()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "middle");
+ removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ } else if (alignment != null && alignment.isBottom()) {
+ addStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+ } else {
+ removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+ removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+ }
+ }
+
+ /**
+ * Set how the slot should be expanded relative to the other slots
+ *
+ * @param expandRatio
+ * The ratio of the space the slot should occupy
+ *
+ */
+ public void setExpandRatio(double expandRatio) {
+ this.expandRatio = expandRatio;
+ }
+
+ /**
+ * Get the expand ratio for the slot. The expand ratio describes how the
+ * slot should be resized compared to other slots in the layout
+ *
+ * @return
+ */
+ public double getExpandRatio() {
+ return expandRatio;
+ }
+
+ /**
+ * Set the spacing for the slot. The spacing determines if there should
+ * be empty space around the slot when the slot.
+ *
+ * @param spacing
+ * Should spacing be enabled
+ */
+ public void setSpacing(boolean spacing) {
+ if (spacing && spacer == null) {
+ spacer = DOM.createDiv();
+ spacer.addClassName("v-spacing");
+
+ /*
+ * This has to be done here for the initial render. In other
+ * cases where the spacer already exists onAttach will handle
+ * it.
+ */
+ getElement().getParentElement().insertBefore(spacer,
+ getElement());
+ } else if (!spacing && spacer != null) {
+ spacer.removeFromParent();
+ spacer = null;
+ }
+ }
+
+ /**
+ * Get the element which is added to make the spacing
+ *
+ * @return
+ */
+ public Element getSpacingElement() {
+ return spacer;
+ }
+
+ /**
+ * Does the slot have spacing
+ */
+ public boolean hasSpacing() {
+ return getSpacingElement() != null;
+ }
+
+ /**
+ * Get the vertical amount in pixels of the spacing
+ */
+ protected int getVerticalSpacing() {
+ if (spacer == null) {
+ return 0;
+ } else if (getLayoutManager() != null) {
+ return getLayoutManager().getOuterHeight(spacer);
+ }
+ return spacer.getOffsetHeight();
+ }
+
+ /**
+ * Get the horizontal amount of pixels of the spacing
+ *
+ * @return
+ */
+ protected int getHorizontalSpacing() {
+ if (spacer == null) {
+ return 0;
+ } else if (getLayoutManager() != null) {
+ return getLayoutManager().getOuterWidth(spacer);
+ }
+ return spacer.getOffsetWidth();
+ }
+
+ /**
+ * Set the position of the caption relative to the slot
+ *
+ * @param captionPosition
+ * The position of the caption
+ */
+ public void setCaptionPosition(CaptionPosition captionPosition) {
+ if (caption == null) {
+ return;
+ }
+ captionWrap.removeClassName("v-caption-on-"
+ + this.captionPosition.name().toLowerCase());
+
+ this.captionPosition = captionPosition;
+ if (captionPosition == CaptionPosition.BOTTOM
+ || captionPosition == CaptionPosition.RIGHT) {
+ captionWrap.appendChild(caption);
+ } else {
+ captionWrap.insertFirst(caption);
+ }
+
+ captionWrap.addClassName("v-caption-on-"
+ + captionPosition.name().toLowerCase());
+ }
+
+ /**
+ * Get the position of the caption relative to the slot
+ */
+ public CaptionPosition getCaptionPosition() {
+ return captionPosition;
+ }
+
+ /**
+ * Set the caption of the slot
+ *
+ * @param captionText
+ * The text of the caption
+ * @param iconUrl
+ * The icon URL
+ * @param styles
+ * The style names
+ * @param error
+ * The error message
+ * @param showError
+ * Should the error message be shown
+ * @param required
+ * Is the (field) required
+ * @param enabled
+ * Is the component enabled
+ */
+ public void setCaption(String captionText, String iconUrl,
+ List<String> styles, String error, boolean showError,
+ boolean required, boolean enabled) {
+
+ // TODO place for optimization: check if any of these have changed
+ // since last time, and only run those changes
+
+ // Caption wrappers
+ if (captionText != null || iconUrl != null || error != null
+ || required) {
+ if (caption == null) {
+ caption = DOM.createDiv();
+ captionWrap = DOM.createDiv();
+ captionWrap.addClassName(StyleConstants.UI_WIDGET);
+ captionWrap.addClassName("v-has-caption");
+ getElement().appendChild(captionWrap);
+ captionWrap.appendChild(getWidget().getElement());
+ }
+ } else if (caption != null) {
+ getElement().appendChild(getWidget().getElement());
+ captionWrap.removeFromParent();
+ caption = null;
+ captionWrap = null;
+ }
+
+ // Caption text
+ if (captionText != null) {
+ if (this.captionText == null) {
+ this.captionText = DOM.createSpan();
+ this.captionText.addClassName("v-captiontext");
+ caption.appendChild(this.captionText);
+ }
+ if (captionText.trim().equals("")) {
+ this.captionText.setInnerHTML(" ");
+ } else {
+ this.captionText.setInnerText(captionText);
+ }
+ } else if (this.captionText != null) {
+ this.captionText.removeFromParent();
+ this.captionText = null;
+ }
+
+ // Icon
+ if (iconUrl != null) {
+ if (icon == null) {
+ icon = new Icon();
+ caption.insertFirst(icon.getElement());
+ }
+ icon.setUri(iconUrl);
+ } else if (icon != null) {
+ icon.getElement().removeFromParent();
+ icon = null;
+ }
+
+ // Required
+ if (required) {
+ if (requiredIcon == null) {
+ requiredIcon = DOM.createSpan();
+ // TODO decide something better (e.g. use CSS to insert the
+ // character)
+ requiredIcon.setInnerHTML("*");
+ requiredIcon.setClassName("v-required-field-indicator");
+ }
+ caption.appendChild(requiredIcon);
+ } else if (requiredIcon != null) {
+ requiredIcon.removeFromParent();
+ requiredIcon = null;
+ }
+
+ // Error
+ if (error != null && showError) {
+ if (errorIcon == null) {
+ errorIcon = DOM.createSpan();
+ errorIcon.setClassName("v-errorindicator");
+ }
+ caption.appendChild(errorIcon);
+ } else if (errorIcon != null) {
+ errorIcon.removeFromParent();
+ errorIcon = null;
+ }
+
+ if (caption != null) {
+ // Styles
+ caption.setClassName("v-caption");
+
+ if (styles != null) {
+ for (String style : styles) {
+ caption.addClassName("v-caption-" + style);
+ }
+ }
+
+ if (enabled) {
+ caption.removeClassName("v-disabled");
+ } else {
+ caption.addClassName("v-disabled");
+ }
+
+ // Caption position
+ if (captionText != null || iconUrl != null) {
+ setCaptionPosition(CaptionPosition.TOP);
+ } else {
+ setCaptionPosition(CaptionPosition.RIGHT);
+ }
+ }
+ }
+
+ /**
+ * Does the slot have a caption
+ */
+ public boolean hasCaption() {
+ return caption != null;
+ }
+
+ /**
+ * Get the slots caption element
+ */
+ public Element getCaptionElement() {
+ return caption;
+ }
+
+ /**
+ * Set if the slot has a relative width
+ *
+ * @param relativeWidth
+ * True if slot uses relative width, false if the slot has a
+ * static width
+ */
+ private boolean relativeWidth = false;
+
+ public void setRelativeWidth(boolean relativeWidth) {
+ this.relativeWidth = relativeWidth;
+ updateRelativeSize(relativeWidth, "width");
+ }
+
+ /**
+ * Set if the slot has a relative height
+ *
+ * @param relativeHeight
+ * Trie if the slot uses a relative height, false if the slot
+ * has a static height
+ */
+ private boolean relativeHeight = false;
+
+ public void setRelativeHeight(boolean relativeHeight) {
+ this.relativeHeight = relativeHeight;
+ updateRelativeSize(relativeHeight, "height");
+ }
+
+ /**
+ * Updates the captions size if the slot is relative
+ *
+ * @param isRelativeSize
+ * Is the slot relatived sized
+ * @param direction
+ * The directorion of the caption
+ */
+ private void updateRelativeSize(boolean isRelativeSize, String direction) {
+ if (isRelativeSize && hasCaption()) {
+ captionWrap.getStyle().setProperty(
+ direction,
+ getWidget().getElement().getStyle()
+ .getProperty(direction));
+ captionWrap.addClassName("v-has-" + direction);
+ } else if (hasCaption()) {
+ if (direction.equals("height")) {
+ captionWrap.getStyle().clearHeight();
+ } else {
+ captionWrap.getStyle().clearWidth();
+ }
+ captionWrap.removeClassName("v-has-" + direction);
+ captionWrap.getStyle().clearPaddingTop();
+ captionWrap.getStyle().clearPaddingRight();
+ captionWrap.getStyle().clearPaddingBottom();
+ captionWrap.getStyle().clearPaddingLeft();
+ caption.getStyle().clearMarginTop();
+ caption.getStyle().clearMarginRight();
+ caption.getStyle().clearMarginBottom();
+ caption.getStyle().clearMarginLeft();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (DOM.eventGetType(event) == Event.ONLOAD
+ && icon.getElement() == DOM.eventGetTarget(event)) {
+ if (getLayoutManager() != null) {
+ getLayoutManager().layoutLater();
+ } else {
+ updateCaptionOffset(caption);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement()
+ */
+ @Override
+ protected Element getContainerElement() {
+ if (captionWrap == null) {
+ return getElement();
+ } else {
+ return captionWrap;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onDetach()
+ */
+ @Override
+ protected void onDetach() {
+ if (spacer != null) {
+ spacer.removeFromParent();
+ }
+ super.onDetach();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onAttach()
+ */
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (spacer != null) {
+ getElement().getParentElement().insertBefore(spacer,
+ getElement());
+ }
+ }
+ }
+
+ /**
+ * The icon for each widget. Located in the caption of the slot.
+ */
+ public static class Icon extends UIObject {
+
+ public static final String CLASSNAME = "v-icon";
+
+ private String myUrl;
+
+ /**
+ * Constructor
+ */
+ public Icon() {
+ setElement(DOM.createImg());
+ DOM.setElementProperty(getElement(), "alt", "");
+ setStyleName(CLASSNAME);
+ }
+
+ /**
+ * Set the URL where the icon is located
+ *
+ * @param url
+ * A fully qualified URL
+ */
+ public void setUri(String url) {
+ if (!url.equals(myUrl)) {
+ /*
+ * Start sinking onload events, widgets responsibility to react.
+ * We must do this BEFORE we set src as IE fires the event
+ * immediately if the image is found in cache (#2592).
+ */
+ sinkEvents(Event.ONLOAD);
+
+ DOM.setElementProperty(getElement(), "src", url);
+ myUrl = url;
+ }
+ }
+ }
+
+ /**
+ * Set the layout manager for the layout
+ *
+ * @param manager
+ * The layout manager to use
+ */
+ public void setLayoutManager(LayoutManager manager) {
+ layoutManager = manager;
+ }
+
+ /**
+ * Get the layout manager used by this layout
+ *
+ */
+ public LayoutManager getLayoutManager() {
+ return layoutManager;
+ }
+
+ /**
+ * Deducts the caption position by examining the wrapping element.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param captionWrap
+ * The wrapping element
+ *
+ * @return The caption position
+ */
+ public CaptionPosition getCaptionPositionFromElement(Element captionWrap) {
+ RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)");
+
+ // Get caption position from the classname
+ MatchResult matcher = captionPositionRegexp.exec(captionWrap
+ .getClassName());
+ if (matcher == null || matcher.getGroupCount() < 2) {
+ return CaptionPosition.TOP;
+ }
+ String captionClass = matcher.getGroup(1);
+ CaptionPosition captionPosition = CaptionPosition.valueOf(
+ CaptionPosition.class, captionClass.toUpperCase());
+ return captionPosition;
+ }
+
+ /**
+ * Update the offset off the caption relative to the slot
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param caption
+ * The caption element
+ */
+ public void updateCaptionOffset(Element caption) {
+
+ Element captionWrap = caption.getParentElement().cast();
+
+ Style captionWrapStyle = captionWrap.getStyle();
+ captionWrapStyle.clearPaddingTop();
+ captionWrapStyle.clearPaddingRight();
+ captionWrapStyle.clearPaddingBottom();
+ captionWrapStyle.clearPaddingLeft();
+
+ Style captionStyle = caption.getStyle();
+ captionStyle.clearMarginTop();
+ captionStyle.clearMarginRight();
+ captionStyle.clearMarginBottom();
+ captionStyle.clearMarginLeft();
+
+ // Get caption position from the classname
+ CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
+
+ if (captionPosition == CaptionPosition.LEFT
+ || captionPosition == CaptionPosition.RIGHT) {
+ int captionWidth;
+ if (layoutManager != null) {
+ captionWidth = layoutManager.getOuterWidth(caption)
+ - layoutManager.getMarginWidth(caption);
+ } else {
+ captionWidth = caption.getOffsetWidth();
+ }
+ if (captionWidth > 0) {
+ if (captionPosition == CaptionPosition.LEFT) {
+ captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX);
+ captionStyle.setMarginLeft(-captionWidth, Unit.PX);
+ } else {
+ captionWrapStyle.setPaddingRight(captionWidth, Unit.PX);
+ captionStyle.setMarginRight(-captionWidth, Unit.PX);
+ }
+ }
+ }
+ if (captionPosition == CaptionPosition.TOP
+ || captionPosition == CaptionPosition.BOTTOM) {
+ int captionHeight;
+ if (layoutManager != null) {
+ captionHeight = layoutManager.getOuterHeight(caption)
+ - layoutManager.getMarginHeight(caption);
+ } else {
+ captionHeight = caption.getOffsetHeight();
+ }
+ if (captionHeight > 0) {
+ if (captionPosition == CaptionPosition.TOP) {
+ captionWrapStyle.setPaddingTop(captionHeight, Unit.PX);
+ captionStyle.setMarginTop(-captionHeight, Unit.PX);
+ } else {
+ captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX);
+ captionStyle.setMarginBottom(-captionHeight, Unit.PX);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the margin of the layout
+ *
+ * @param marginInfo
+ * The margin information
+ */
+ public void setMargin(MarginInfo marginInfo) {
+ if (marginInfo != null) {
+ setStyleName("v-margin-top", marginInfo.hasTop());
+ setStyleName("v-margin-right", marginInfo.hasRight());
+ setStyleName("v-margin-bottom", marginInfo.hasBottom());
+ setStyleName("v-margin-left", marginInfo.hasLeft());
+ }
+ }
+
+ /**
+ * Turn on or off spacing in the layout
+ *
+ * @param spacing
+ * True if spacing should be used, false if not
+ */
+ public void setSpacing(boolean spacing) {
+ this.spacing = spacing;
+ for (Slot slot : widgetToSlot.values()) {
+ if (getWidgetIndex(slot) > 0) {
+ slot.setSpacing(spacing);
+ } else {
+ slot.setSpacing(false);
+ }
+ }
+ }
+
+ /**
+ * Triggers a recalculation of the expand width and heights
+ */
+ private void recalculateExpands() {
+ double total = 0;
+ for (Slot slot : widgetToSlot.values()) {
+ if (slot.getExpandRatio() > -1) {
+ total += slot.getExpandRatio();
+ } else {
+ if (vertical) {
+ slot.getElement().getStyle().clearHeight();
+ } else {
+ slot.getElement().getStyle().clearWidth();
+ }
+ }
+ }
+ for (Slot slot : widgetToSlot.values()) {
+ if (slot.getExpandRatio() > -1) {
+ if (vertical) {
+ slot.setHeight((100 * (slot.getExpandRatio() / total))
+ + "%");
+ if (slot.relativeHeight) {
+ Util.notifyParentOfSizeChange(this, true);
+ }
+ } else {
+ slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%");
+ if (slot.relativeWidth) {
+ Util.notifyParentOfSizeChange(this, true);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes elements used to expand a slot.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void clearExpand() {
+ if (expandWrapper != null) {
+ for (; expandWrapper.getChildCount() > 0;) {
+ Element el = expandWrapper.getChild(0).cast();
+ getElement().appendChild(el);
+ if (vertical) {
+ el.getStyle().clearHeight();
+ el.getStyle().clearMarginTop();
+ } else {
+ el.getStyle().clearWidth();
+ el.getStyle().clearMarginLeft();
+ }
+ }
+ expandWrapper.removeFromParent();
+ expandWrapper = null;
+ }
+ }
+
+ /**
+ * Adds elements used to expand a slot
+ */
+ public void updateExpand() {
+ boolean isExpanding = false;
+ for (Widget slot : getChildren()) {
+ if (((Slot) slot).getExpandRatio() > -1) {
+ isExpanding = true;
+ } else {
+ if (vertical) {
+ slot.getElement().getStyle().clearHeight();
+ } else {
+ slot.getElement().getStyle().clearWidth();
+ }
+ }
+ slot.getElement().getStyle().clearMarginLeft();
+ slot.getElement().getStyle().clearMarginTop();
+ }
+
+ if (isExpanding) {
+ if (expandWrapper == null) {
+ expandWrapper = DOM.createDiv();
+ expandWrapper.setClassName("v-expand");
+ for (; getElement().getChildCount() > 0;) {
+ Node el = getElement().getChild(0);
+ expandWrapper.appendChild(el);
+ }
+ getElement().appendChild(expandWrapper);
+ }
+
+ int totalSize = 0;
+ for (Widget w : getChildren()) {
+ Slot slot = (Slot) w;
+ if (slot.getExpandRatio() == -1) {
+
+ if (layoutManager != null) {
+ // TODO check caption position
+ if (vertical) {
+ int size = layoutManager.getOuterHeight(slot
+ .getWidget().getElement())
+ - layoutManager.getMarginHeight(slot
+ .getWidget().getElement());
+ if (slot.hasCaption()) {
+ size += layoutManager.getOuterHeight(slot
+ .getCaptionElement())
+ - layoutManager.getMarginHeight(slot
+ .getCaptionElement());
+ }
+ if (size > 0) {
+ totalSize += size;
+ }
+ } else {
+ int max = -1;
+ max = layoutManager.getOuterWidth(slot.getWidget()
+ .getElement())
+ - layoutManager.getMarginWidth(slot
+ .getWidget().getElement());
+ if (slot.hasCaption()) {
+ int max2 = layoutManager.getOuterWidth(slot
+ .getCaptionElement())
+ - layoutManager.getMarginWidth(slot
+ .getCaptionElement());
+ max = Math.max(max, max2);
+ }
+ if (max > 0) {
+ totalSize += max;
+ }
+ }
+ } else {
+ totalSize += vertical ? slot.getOffsetHeight() : slot
+ .getOffsetWidth();
+ }
+ }
+ // TODO fails in Opera, always returns 0
+ int spacingSize = vertical ? slot.getVerticalSpacing() : slot
+ .getHorizontalSpacing();
+ if (spacingSize > 0) {
+ totalSize += spacingSize;
+ }
+ }
+
+ // When we set the margin to the first child, we don't need
+ // overflow:hidden in the layout root element, since the wrapper
+ // would otherwise be placed outside of the layout root element
+ // and block events on elements below it.
+ if (vertical) {
+ expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX);
+ expandWrapper.getFirstChildElement().getStyle()
+ .setMarginTop(-totalSize, Unit.PX);
+ } else {
+ expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX);
+ expandWrapper.getFirstChildElement().getStyle()
+ .setMarginLeft(-totalSize, Unit.PX);
+ }
+
+ recalculateExpands();
+ }
+ }
+
+ /**
+ * Perform a recalculation of the layout height
+ */
+ public void recalculateLayoutHeight() {
+ // Only needed if a horizontal layout is undefined high, and contains
+ // relative height children or vertical alignments
+ if (vertical || definedHeight) {
+ return;
+ }
+
+ boolean hasRelativeHeightChildren = false;
+ boolean hasVAlign = false;
+
+ for (Widget slot : getChildren()) {
+ Widget widget = ((Slot) slot).getWidget();
+ String h = widget.getElement().getStyle().getHeight();
+ if (h != null && h.indexOf("%") > -1) {
+ hasRelativeHeightChildren = true;
+ }
+ AlignmentInfo a = ((Slot) slot).getAlignment();
+ if (a != null && (a.isVerticalCenter() || a.isBottom())) {
+ hasVAlign = true;
+ }
+ }
+
+ if (hasRelativeHeightChildren || hasVAlign) {
+ int newHeight;
+ if (layoutManager != null) {
+ newHeight = layoutManager.getOuterHeight(getElement())
+ - layoutManager.getMarginHeight(getElement());
+ } else {
+ newHeight = getElement().getOffsetHeight();
+ }
+ VOrderedLayout.this.getElement().getStyle()
+ .setHeight(newHeight, Unit.PX);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setHeight(String height) {
+ super.setHeight(height);
+ definedHeight = (height != null && !"".equals(height));
+ }
+
+ /**
+ * Sets the slots style names. The style names will be prefixed with the
+ * v-slot prefix.
+ *
+ * @param stylenames
+ * The style names of the slot.
+ */
+ public void setSlotStyleNames(Widget widget, String... stylenames) {
+ Slot slot = getSlot(widget);
+ if (slot == null) {
+ throw new IllegalArgumentException(
+ "A slot for the widget could not be found. Has the widget been added to the layout?");
+ }
+ slot.setStyleNames(stylenames);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+
+public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner,
+ Focusable {
+
+ public static final String CLASSNAME = "v-panel";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element captionNode = DOM.createDiv();
+
+ private final Element captionText = DOM.createSpan();
+
+ private Icon icon;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element bottomDecoration = DOM.createDiv();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element contentNode = DOM.createDiv();
+
+ private Element errorIndicatorElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ShortcutActionHandler shortcutHandler;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int scrollTop;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int scrollLeft;
+
+ private TouchScrollHandler touchScrollHandler;
+
+ public VPanel() {
+ super();
+ DivElement captionWrap = Document.get().createDivElement();
+ captionWrap.appendChild(captionNode);
+ captionNode.appendChild(captionText);
+
+ captionWrap.setClassName(CLASSNAME + "-captionwrap");
+ captionNode.setClassName(CLASSNAME + "-caption");
+ contentNode.setClassName(CLASSNAME + "-content");
+ bottomDecoration.setClassName(CLASSNAME + "-deco");
+
+ getElement().appendChild(captionWrap);
+
+ /*
+ * Make contentNode focusable only by using the setFocus() method. This
+ * behaviour can be changed by invoking setTabIndex() in the serverside
+ * implementation
+ */
+ contentNode.setTabIndex(-1);
+
+ getElement().appendChild(contentNode);
+
+ getElement().appendChild(bottomDecoration);
+ setStyleName(CLASSNAME);
+ DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
+ DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS);
+
+ contentNode.getStyle().setProperty("position", "relative");
+ getElement().getStyle().setProperty("overflow", "hidden");
+
+ makeScrollable();
+ }
+
+ /**
+ * Sets the keyboard focus on the Panel
+ *
+ * @param focus
+ * Should the panel have focus or not.
+ */
+ public void setFocus(boolean focus) {
+ if (focus) {
+ getContainerElement().focus();
+ } else {
+ getContainerElement().blur();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.Focusable#focus()
+ */
+
+ @Override
+ public void focus() {
+ setFocus(true);
+
+ }
+
+ @Override
+ protected Element getContainerElement() {
+ return contentNode;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setCaption(String text) {
+ DOM.setInnerHTML(captionText, text);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setErrorIndicatorVisible(boolean showError) {
+ if (showError) {
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createSpan();
+ DOM.setElementProperty(errorIndicatorElement, "className",
+ "v-errorindicator");
+ DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
+ sinkEvents(Event.MOUSEEVENTS);
+ }
+ DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(captionNode, errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setIconUri(String iconUri, ApplicationConnection client) {
+ if (iconUri == null) {
+ if (icon != null) {
+ DOM.removeChild(captionNode, icon.getElement());
+ icon = null;
+ }
+ } else {
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertChild(captionNode, icon.getElement(), 0);
+ }
+ icon.setUri(iconUri);
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ final Element target = DOM.eventGetTarget(event);
+ final int type = DOM.eventGetType(event);
+ if (type == Event.ONKEYDOWN && shortcutHandler != null) {
+ shortcutHandler.handleKeyboardEvent(event);
+ return;
+ }
+ if (type == Event.ONSCROLL) {
+ int newscrollTop = DOM.getElementPropertyInt(contentNode,
+ "scrollTop");
+ int newscrollLeft = DOM.getElementPropertyInt(contentNode,
+ "scrollLeft");
+ if (client != null
+ && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
+ scrollLeft = newscrollLeft;
+ scrollTop = newscrollTop;
+ client.updateVariable(id, "scrollTop", scrollTop, false);
+ client.updateVariable(id, "scrollLeft", scrollLeft, false);
+ }
+ }
+ }
+
+ @Override
+ public ShortcutActionHandler getShortcutActionHandler() {
+ return shortcutHandler;
+ }
+
+ /**
+ * Ensures the panel is scrollable eg. after style name changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void makeScrollable() {
+ if (touchScrollHandler == null) {
+ touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+ }
+ touchScrollHandler.addElement(contentNode);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+
+/**
+ * This class represents a password field.
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+public class VPasswordField extends VTextField {
+
+ public VPasswordField() {
+ super(DOM.createInputPassword());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
+import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+/**
+ * Represents a date selection component with a text field and a popup date
+ * selector.
+ *
+ * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
+ * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass
+ * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code>
+ * method.
+ *
+ */
+public class VPopupCalendar extends VTextualDate implements Field,
+ ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Button calendarToggle = new Button();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VCalendarPanel calendar;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final VOverlay popup;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean parsable = true;
+
+ private boolean open = false;
+
+ public VPopupCalendar() {
+ super();
+
+ calendarToggle.setText("");
+ calendarToggle.addClickHandler(this);
+ // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
+ calendarToggle.getElement().setTabIndex(-2);
+ add(calendarToggle);
+
+ calendar = GWT.create(VCalendarPanel.class);
+ calendar.setParentField(this);
+ calendar.setFocusOutListener(new FocusOutListener() {
+ @Override
+ public boolean onFocusOut(DomEvent<?> event) {
+ event.preventDefault();
+ closeCalendarPanel();
+ return true;
+ }
+ });
+
+ calendar.setSubmitListener(new SubmitListener() {
+ @Override
+ public void onSubmit() {
+ // Update internal value and send valuechange event if immediate
+ updateValue(calendar.getDate());
+
+ // Update text field (a must when not immediate).
+ buildDate(true);
+
+ closeCalendarPanel();
+ }
+
+ @Override
+ public void onCancel() {
+ closeCalendarPanel();
+ }
+ });
+
+ popup = new VOverlay(true, true, true);
+ popup.setOwner(this);
+
+ popup.setWidget(calendar);
+ popup.addCloseHandler(this);
+
+ DOM.setElementProperty(calendar.getElement(), "id",
+ "PID_VAADIN_POPUPCAL");
+
+ sinkEvents(Event.ONKEYDOWN);
+
+ updateStyleNames();
+ }
+
+ @SuppressWarnings("deprecation")
+ public void updateValue(Date newDate) {
+ Date currentDate = getCurrentDate();
+ if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
+ setCurrentDate((Date) newDate.clone());
+ getClient().updateVariable(getId(), "year",
+ newDate.getYear() + 1900, false);
+ if (getCurrentResolution().getCalendarField() > Resolution.YEAR
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "month",
+ newDate.getMonth() + 1, false);
+ if (getCurrentResolution().getCalendarField() > Resolution.MONTH
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "day",
+ newDate.getDate(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.DAY
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "hour",
+ newDate.getHours(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.HOUR
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "min",
+ newDate.getMinutes(), false);
+ if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "sec",
+ newDate.getSeconds(), false);
+ }
+ }
+ }
+ }
+ }
+ if (isImmediate()) {
+ getClient().sendPendingVariableChanges();
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
+ */
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ removeStyleName(getStylePrimaryName() + "-popupcalendar");
+ super.setStylePrimaryName(style);
+ updateStyleNames();
+ }
+
+ @Override
+ protected void updateStyleNames() {
+ super.updateStyleNames();
+ if (getStylePrimaryName() != null && calendarToggle != null) {
+ addStyleName(getStylePrimaryName() + "-popupcalendar");
+ calendarToggle.setStyleName(getStylePrimaryName() + "-button");
+ popup.setStyleName(getStylePrimaryName() + "-popup");
+ calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
+ }
+ }
+
+ /**
+ * Opens the calendar panel popup
+ */
+ public void openCalendarPanel() {
+
+ if (!open && !readonly) {
+ open = true;
+
+ if (getCurrentDate() != null) {
+ calendar.setDate((Date) getCurrentDate().clone());
+ } else {
+ calendar.setDate(new Date());
+ }
+
+ // clear previous values
+ popup.setWidth("");
+ popup.setHeight("");
+ popup.setPopupPositionAndShow(new PositionCallback() {
+ @Override
+ public void setPosition(int offsetWidth, int offsetHeight) {
+ final int w = offsetWidth;
+ final int h = offsetHeight;
+ final int browserWindowWidth = Window.getClientWidth()
+ + Window.getScrollLeft();
+ final int browserWindowHeight = Window.getClientHeight()
+ + Window.getScrollTop();
+ int t = calendarToggle.getAbsoluteTop();
+ int l = calendarToggle.getAbsoluteLeft();
+
+ // Add a little extra space to the right to avoid
+ // problems with IE7 scrollbars and to make it look
+ // nicer.
+ int extraSpace = 30;
+
+ boolean overflowRight = false;
+ if (l + +w + extraSpace > browserWindowWidth) {
+ overflowRight = true;
+ // Part of the popup is outside the browser window
+ // (to the right)
+ l = browserWindowWidth - w - extraSpace;
+ }
+
+ if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
+ // Part of the popup is outside the browser window
+ // (below)
+ t = browserWindowHeight - h
+ - calendarToggle.getOffsetHeight() - 30;
+ if (!overflowRight) {
+ // Show to the right of the popup button unless we
+ // are in the lower right corner of the screen
+ l += calendarToggle.getOffsetWidth();
+ }
+ }
+
+ // fix size
+ popup.setWidth(w + "px");
+ popup.setHeight(h + "px");
+
+ popup.setPopupPosition(l,
+ t + calendarToggle.getOffsetHeight() + 2);
+
+ /*
+ * We have to wait a while before focusing since the popup
+ * needs to be opened before we can focus
+ */
+ Timer focusTimer = new Timer() {
+ @Override
+ public void run() {
+ setFocus(true);
+ }
+ };
+
+ focusTimer.schedule(100);
+ }
+ });
+ } else {
+ VConsole.error("Cannot reopen popup, it is already open!");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+ * .dom.client.ClickEvent)
+ */
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getSource() == calendarToggle && isEnabled()) {
+ openCalendarPanel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
+ * .event.logical.shared.CloseEvent)
+ */
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ if (event.getSource() == popup) {
+ buildDate();
+ if (!BrowserInfo.get().isTouchDevice()) {
+ /*
+ * Move focus to textbox, unless on touch device (avoids opening
+ * virtual keyboard).
+ */
+ focus();
+ }
+
+ // TODO resolve what the "Sigh." is all about and document it here
+ // Sigh.
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ open = false;
+ }
+ };
+ t.schedule(100);
+ }
+ }
+
+ /**
+ * Sets focus to Calendar panel.
+ *
+ * @param focus
+ */
+ public void setFocus(boolean focus) {
+ calendar.setFocus(focus);
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @see com.vaadin.client.ui.VTextualDate#buildDate()
+ */
+ @Override
+ public void buildDate() {
+ // Save previous value
+ String previousValue = getText();
+ super.buildDate();
+
+ // Restore previous value if the input could not be parsed
+ if (!parsable) {
+ setText(previousValue);
+ }
+ }
+
+ /**
+ * Update the text field contents from the date. See {@link #buildDate()}.
+ *
+ * @param forceValid
+ * true to force the text field to be updated, false to only
+ * update if the parsable flag is true.
+ */
+ protected void buildDate(boolean forceValid) {
+ if (forceValid) {
+ parsable = true;
+ }
+ buildDate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google
+ * .gwt.user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(com.google.gwt.user.client.Event event) {
+ super.onBrowserEvent(event);
+ if (DOM.eventGetType(event) == Event.ONKEYDOWN
+ && event.getKeyCode() == getOpenCalenderPanelKey()) {
+ openCalendarPanel();
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Get the key code that opens the calendar panel. By default it is the down
+ * key but you can override this to be whatever you like
+ *
+ * @return
+ */
+ protected int getOpenCalenderPanelKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Closes the open popup panel
+ */
+ public void closeCalendarPanel() {
+ if (open) {
+ popup.hide(true);
+ }
+ }
+
+ private final String CALENDAR_TOGGLE_ID = "popupButton";
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if (subPart.equals(CALENDAR_TOGGLE_ID)) {
+ return calendarToggle.getElement();
+ }
+
+ return super.getSubPartElement(subPart);
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (calendarToggle.getElement().isOrHasChild(subElement)) {
+ return CALENDAR_TOGGLE_ID;
+ }
+
+ return super.getSubPartName(subElement);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.VCaptionWrapper;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.popupview.VisibilityChangeEvent;
+import com.vaadin.client.ui.popupview.VisibilityChangeHandler;
+
+public class VPopupView extends HTML {
+
+ public static final String CLASSNAME = "v-popupview";
+
+ /**
+ * For server-client communication.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String uidlId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /**
+ * Helps to communicate popup visibility to the server.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean hostPopupVisible;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final CustomPopup popup;
+ private final Label loading = new Label();
+
+ /**
+ * loading constructor
+ */
+ public VPopupView() {
+ super();
+ popup = new CustomPopup();
+
+ setStyleName(CLASSNAME);
+ popup.setStyleName(CLASSNAME + "-popup");
+ loading.setStyleName(CLASSNAME + "-loading");
+
+ setHTML("");
+ popup.setWidget(loading);
+
+ // When we click to open the popup...
+ addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ fireEvent(new VisibilityChangeEvent(true));
+ }
+ });
+
+ // ..and when we close it
+ popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ fireEvent(new VisibilityChangeEvent(false));
+ }
+ });
+
+ popup.setAnimationEnabled(true);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void preparePopup(final CustomPopup popup) {
+ popup.setVisible(false);
+ popup.show();
+ }
+
+ /**
+ * Determines the correct position for a popup and displays the popup at
+ * that position.
+ *
+ * By default, the popup is shown centered relative to its host component,
+ * ensuring it is visible on the screen if possible.
+ *
+ * Can be overridden to customize the popup position.
+ *
+ * @param popup
+ */
+ public void showPopup(final CustomPopup popup) {
+ popup.setPopupPosition(0, 0);
+
+ popup.setVisible(true);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void center() {
+ int windowTop = RootPanel.get().getAbsoluteTop();
+ int windowLeft = RootPanel.get().getAbsoluteLeft();
+ int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
+ int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
+
+ int offsetWidth = popup.getOffsetWidth();
+ int offsetHeight = popup.getOffsetHeight();
+
+ int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
+ + VPopupView.this.getOffsetWidth() / 2;
+ int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
+ + VPopupView.this.getOffsetHeight() / 2;
+
+ int left = hostHorizontalCenter - offsetWidth / 2;
+ int top = hostVerticalCenter - offsetHeight / 2;
+
+ // Don't show the popup outside the screen.
+ if ((left + offsetWidth) > windowRight) {
+ left -= (left + offsetWidth) - windowRight;
+ }
+
+ if ((top + offsetHeight) > windowBottom) {
+ top -= (top + offsetHeight) - windowBottom;
+ }
+
+ if (left < 0) {
+ left = 0;
+ }
+
+ if (top < 0) {
+ top = 0;
+ }
+
+ popup.setPopupPosition(left, top);
+ }
+
+ /**
+ * Make sure that we remove the popup when the main widget is removed.
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onUnload()
+ */
+ @Override
+ protected void onDetach() {
+ popup.hide();
+ super.onDetach();
+ }
+
+ private static native void nativeBlur(Element e)
+ /*-{
+ if(e && e.blur) {
+ e.blur();
+ }
+ }-*/;
+
+ /**
+ * This class is only public to enable overriding showPopup, and is
+ * currently not intended to be extended or otherwise used directly. Its API
+ * (other than it being a VOverlay) is to be considered private and
+ * potentially subject to change.
+ */
+ public class CustomPopup extends VOverlay {
+
+ private ComponentConnector popupComponentConnector = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Widget popupComponentWidget = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VCaptionWrapper captionWrapper = null;
+
+ private boolean hasHadMouseOver = false;
+ private boolean hideOnMouseOut = true;
+ private final Set<Element> activeChildren = new HashSet<Element>();
+ private boolean hiding = false;
+
+ private ShortcutActionHandler shortcutActionHandler;
+
+ public CustomPopup() {
+ super(true, false, true); // autoHide, not modal, dropshadow
+ setOwner(VPopupView.this);
+ // Delegate popup keyboard events to the relevant handler. The
+ // events do not propagate automatically because the popup is
+ // directly attached to the RootPanel.
+ addDomHandler(new KeyDownHandler() {
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (shortcutActionHandler != null) {
+ shortcutActionHandler.handleKeyboardEvent(Event
+ .as(event.getNativeEvent()));
+ }
+ }
+ }, KeyDownEvent.getType());
+ }
+
+ // For some reason ONMOUSEOUT events are not always received, so we have
+ // to use ONMOUSEMOVE that doesn't target the popup
+ @Override
+ public boolean onEventPreview(Event event) {
+ Element target = DOM.eventGetTarget(event);
+ boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
+ int type = DOM.eventGetType(event);
+
+ // Catch children that use keyboard, so we can unfocus them when
+ // hiding
+ if (eventTargetsPopup && type == Event.ONKEYPRESS) {
+ activeChildren.add(target);
+ }
+
+ if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
+ hasHadMouseOver = true;
+ }
+
+ if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
+ if (hasHadMouseOver && hideOnMouseOut) {
+ hide();
+ return true;
+ }
+ }
+
+ // Was the TAB key released outside of our popup?
+ if (!eventTargetsPopup && type == Event.ONKEYUP
+ && event.getKeyCode() == KeyCodes.KEY_TAB) {
+ // Should we hide on focus out (mouse out)?
+ if (hideOnMouseOut) {
+ hide();
+ return true;
+ }
+ }
+
+ return super.onEventPreview(event);
+ }
+
+ @Override
+ public void hide(boolean autoClosed) {
+ VConsole.log("Hiding popupview");
+ hiding = true;
+ syncChildren();
+ if (popupComponentWidget != null && popupComponentWidget != loading) {
+ remove(popupComponentWidget);
+ }
+ hasHadMouseOver = false;
+ shortcutActionHandler = null;
+ super.hide(autoClosed);
+ }
+
+ @Override
+ public void show() {
+ hiding = false;
+
+ // Find the shortcut action handler that should handle keyboard
+ // events from the popup. The events do not propagate automatically
+ // because the popup is directly attached to the RootPanel.
+ Widget widget = VPopupView.this;
+ while (shortcutActionHandler == null && widget != null) {
+ if (widget instanceof ShortcutActionHandlerOwner) {
+ shortcutActionHandler = ((ShortcutActionHandlerOwner) widget)
+ .getShortcutActionHandler();
+ }
+ widget = widget.getParent();
+ }
+
+ super.show();
+ }
+
+ /**
+ * Try to sync all known active child widgets to server
+ */
+ public void syncChildren() {
+ // Notify children with focus
+ if ((popupComponentWidget instanceof Focusable)) {
+ ((Focusable) popupComponentWidget).setFocus(false);
+ } else {
+
+ checkForRTE(popupComponentWidget);
+ }
+
+ // Notify children that have used the keyboard
+ for (Element e : activeChildren) {
+ try {
+ nativeBlur(e);
+ } catch (Exception ignored) {
+ }
+ }
+ activeChildren.clear();
+ }
+
+ private void checkForRTE(Widget popupComponentWidget2) {
+ if (popupComponentWidget2 instanceof VRichTextArea) {
+ ((VRichTextArea) popupComponentWidget2)
+ .synchronizeContentToServer();
+ } else if (popupComponentWidget2 instanceof HasWidgets) {
+ HasWidgets hw = (HasWidgets) popupComponentWidget2;
+ Iterator<Widget> iterator = hw.iterator();
+ while (iterator.hasNext()) {
+ checkForRTE(iterator.next());
+ }
+ }
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+
+ popupComponentConnector = null;
+ popupComponentWidget = null;
+ captionWrapper = null;
+
+ return super.remove(w);
+ }
+
+ public void setPopupConnector(ComponentConnector newPopupComponent) {
+
+ if (newPopupComponent != popupComponentConnector) {
+ Widget newWidget = newPopupComponent.getWidget();
+ setWidget(newWidget);
+ popupComponentWidget = newWidget;
+ popupComponentConnector = newPopupComponent;
+ }
+
+ }
+
+ public void setHideOnMouseOut(boolean hideOnMouseOut) {
+ this.hideOnMouseOut = hideOnMouseOut;
+ }
+
+ /*
+ *
+ * We need a hack make popup act as a child of VPopupView in Vaadin's
+ * component tree, but work in default GWT manner when closing or
+ * opening.
+ *
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#getParent()
+ */
+ @Override
+ public Widget getParent() {
+ if (!isAttached() || hiding) {
+ return super.getParent();
+ } else {
+ return VPopupView.this;
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ hiding = false;
+ }
+
+ @Override
+ public Element getContainerElement() {
+ return super.getContainerElement();
+ }
+
+ }// class CustomPopup
+
+ public HandlerRegistration addVisibilityChangeHandler(
+ final VisibilityChangeHandler visibilityChangeHandler) {
+ return addHandler(visibilityChangeHandler,
+ VisibilityChangeEvent.getType());
+ }
+
+}// class VPopupView
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.HasEnabled;
+import com.google.gwt.user.client.ui.Widget;
+
+public class VProgressIndicator extends Widget implements HasEnabled {
+
+ public static final String CLASSNAME = "v-progressindicator";
+ Element wrapper = DOM.createDiv();
+ Element indicator = DOM.createDiv();
+
+ protected boolean indeterminate = false;
+ protected float state = 0.0f;
+ private boolean enabled;
+
+ public VProgressIndicator() {
+ setElement(DOM.createDiv());
+ getElement().appendChild(wrapper);
+ setStyleName(CLASSNAME);
+ wrapper.appendChild(indicator);
+ indicator.setClassName(CLASSNAME + "-indicator");
+ wrapper.setClassName(CLASSNAME + "-wrapper");
+ }
+
+ public void setIndeterminate(boolean indeterminate) {
+ this.indeterminate = indeterminate;
+ setStyleName(CLASSNAME + "-indeterminate", indeterminate);
+ }
+
+ public void setState(float state) {
+ final int size = Math.round(100 * state);
+ indicator.getStyle().setWidth(size, Unit.PCT);
+ }
+
+ public boolean isIndeterminate() {
+ return indeterminate;
+ }
+
+ public float getState() {
+ return state;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ setStyleName("v-disabled", !enabled);
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+
+/**
+ * This class implements a basic client side rich text editor component.
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+public class VRichTextArea extends Composite implements Field, ChangeHandler,
+ BlurHandler, KeyPressHandler, KeyDownHandler, Focusable {
+
+ /**
+ * The input node CSS classname.
+ */
+ public static final String CLASSNAME = "v-richtextarea";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public RichTextArea rta;
+
+ private VRichTextToolbar formatter;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public HTML html = new HTML();
+
+ private final FlowPanel fp = new FlowPanel();
+
+ private boolean enabled = true;
+
+ private int extraHorizontalPixels = -1;
+ private int extraVerticalPixels = -1;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int maxLength = -1;
+
+ private int toolbarNaturalWidth = 500;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public HandlerRegistration keyPressHandler;
+
+ private ShortcutActionHandlerOwner hasShortcutActionHandler;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String currentValue = "";
+
+ private boolean readOnly = false;
+
+ public VRichTextArea() {
+ createRTAComponents();
+ fp.add(formatter);
+ fp.add(rta);
+
+ initWidget(fp);
+ setStyleName(CLASSNAME);
+
+ TouchScrollDelegate.enableTouchScrolling(html, html.getElement());
+ }
+
+ private void createRTAComponents() {
+ rta = new RichTextArea();
+ rta.setWidth("100%");
+ rta.addBlurHandler(this);
+ rta.addKeyDownHandler(this);
+ formatter = new VRichTextToolbar(rta);
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (this.enabled != enabled) {
+ // rta.setEnabled(enabled);
+ swapEditableArea();
+ this.enabled = enabled;
+ }
+ }
+
+ /**
+ * Swaps html to rta and visa versa.
+ */
+ private void swapEditableArea() {
+ if (html.isAttached()) {
+ fp.remove(html);
+ if (BrowserInfo.get().isWebkit()) {
+ fp.remove(formatter);
+ createRTAComponents(); // recreate new RTA to bypass #5379
+ fp.add(formatter);
+ }
+ rta.setHTML(currentValue);
+ fp.add(rta);
+ } else {
+ html.setHTML(currentValue);
+ fp.remove(rta);
+ fp.add(html);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void selectAll() {
+ /*
+ * There is a timing issue if trying to select all immediately on first
+ * render. Simple deferred command is not enough. Using Timer with
+ * moderated timeout. If this appears to fail on many (most likely slow)
+ * environments, consider increasing the timeout.
+ *
+ * FF seems to require the most time to stabilize its RTA. On Vaadin
+ * tiergarden test machines, 200ms was not enough always (about 50%
+ * success rate) - 300 ms was 100% successful. This however was not
+ * enough on a sluggish old non-virtualized XP test machine. A bullet
+ * proof solution would be nice, GWT 2.1 might however solve these. At
+ * least setFocus has a workaround for this kind of issue.
+ */
+ new Timer() {
+ @Override
+ public void run() {
+ rta.getFormatter().selectAll();
+ }
+ }.schedule(320);
+ }
+
+ public void setReadOnly(boolean b) {
+ if (isReadOnly() != b) {
+ swapEditableArea();
+ readOnly = b;
+ }
+ // reset visibility in case enabled state changed and the formatter was
+ // recreated
+ formatter.setVisible(!readOnly);
+ }
+
+ private boolean isReadOnly() {
+ return readOnly;
+ }
+
+ // TODO is this really used, or does everything go via onBlur() only?
+ @Override
+ public void onChange(ChangeEvent event) {
+ synchronizeContentToServer();
+ }
+
+ /**
+ * Method is public to let popupview force synchronization on close.
+ */
+ public void synchronizeContentToServer() {
+ if (client != null && id != null) {
+ final String html = rta.getHTML();
+ if (!html.equals(currentValue)) {
+ client.updateVariable(id, "text", html, immediate);
+ currentValue = html;
+ }
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ synchronizeContentToServer();
+ // TODO notify possible server side blur/focus listeners
+ }
+
+ /**
+ * @return space used by components paddings and borders
+ */
+ private int getExtraHorizontalPixels() {
+ if (extraHorizontalPixels < 0) {
+ detectExtraSizes();
+ }
+ return extraHorizontalPixels;
+ }
+
+ /**
+ * @return space used by components paddings and borders
+ */
+ private int getExtraVerticalPixels() {
+ if (extraVerticalPixels < 0) {
+ detectExtraSizes();
+ }
+ return extraVerticalPixels;
+ }
+
+ /**
+ * Detects space used by components paddings and borders.
+ */
+ private void detectExtraSizes() {
+ Element clone = Util.cloneNode(getElement(), false);
+ DOM.setElementAttribute(clone, "id", "");
+ DOM.setStyleAttribute(clone, "visibility", "hidden");
+ DOM.setStyleAttribute(clone, "position", "absolute");
+ // due FF3 bug set size to 10px and later subtract it from extra pixels
+ DOM.setStyleAttribute(clone, "width", "10px");
+ DOM.setStyleAttribute(clone, "height", "10px");
+ DOM.appendChild(DOM.getParent(getElement()), clone);
+ extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10;
+ extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10;
+
+ DOM.removeChild(DOM.getParent(getElement()), clone);
+ }
+
+ @Override
+ public void setHeight(String height) {
+ if (height.endsWith("px")) {
+ int h = Integer.parseInt(height.substring(0, height.length() - 2));
+ h -= getExtraVerticalPixels();
+ if (h < 0) {
+ h = 0;
+ }
+
+ super.setHeight(h + "px");
+ } else {
+ super.setHeight(height);
+ }
+
+ if (height == null || height.equals("")) {
+ rta.setHeight("");
+ } else {
+ /*
+ * The formatter height will be initially calculated wrong so we
+ * delay the height setting so the DOM has had time to stabilize.
+ */
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ int editorHeight = getOffsetHeight()
+ - getExtraVerticalPixels()
+ - formatter.getOffsetHeight();
+ if (editorHeight < 0) {
+ editorHeight = 0;
+ }
+ rta.setHeight(editorHeight + "px");
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setWidth(String width) {
+ if (width.endsWith("px")) {
+ int w = Integer.parseInt(width.substring(0, width.length() - 2));
+ w -= getExtraHorizontalPixels();
+ if (w < 0) {
+ w = 0;
+ }
+
+ super.setWidth(w + "px");
+ } else if (width.equals("")) {
+ /*
+ * IE cannot calculate the width of the 100% iframe correctly if
+ * there is no width specified for the parent. In this case we would
+ * use the toolbar but IE cannot calculate the width of that one
+ * correctly either in all cases. So we end up using a default width
+ * for a RichTextArea with no width definition in all browsers (for
+ * compatibility).
+ */
+
+ super.setWidth(toolbarNaturalWidth + "px");
+ } else {
+ super.setWidth(width);
+ }
+ }
+
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ if (maxLength >= 0) {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ if (rta.getHTML().length() > maxLength) {
+ rta.setHTML(rta.getHTML().substring(0, maxLength));
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ // delegate to closest shortcut action handler
+ // throw event from the iframe forward to the shortcuthandler
+ ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner()
+ .getShortcutActionHandler();
+ if (shortcutHandler != null) {
+ shortcutHandler
+ .handleKeyboardEvent(com.google.gwt.user.client.Event
+ .as(event.getNativeEvent()),
+ ConnectorMap.get(client).getConnector(this));
+ }
+ }
+
+ private ShortcutActionHandlerOwner getShortcutHandlerOwner() {
+ if (hasShortcutActionHandler == null) {
+ Widget parent = getParent();
+ while (parent != null) {
+ if (parent instanceof ShortcutActionHandlerOwner) {
+ break;
+ }
+ parent = parent.getParent();
+ }
+ hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent;
+ }
+ return hasShortcutActionHandler;
+ }
+
+ @Override
+ public int getTabIndex() {
+ return rta.getTabIndex();
+ }
+
+ @Override
+ public void setAccessKey(char key) {
+ rta.setAccessKey(key);
+ }
+
+ @Override
+ public void setFocus(boolean focused) {
+ /*
+ * Similar issue as with selectAll. Focusing must happen before possible
+ * selectall, so keep the timeout here lower.
+ */
+ new Timer() {
+
+ @Override
+ public void run() {
+ rta.setFocus(true);
+ }
+ }.schedule(300);
+ }
+
+ @Override
+ public void setTabIndex(int index) {
+ rta.setTabIndex(index);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.ToggleButton;
+
+/**
+ * A modified version of sample toolbar for use with {@link RichTextArea}. It
+ * provides a simple UI for all rich text formatting, dynamically displayed only
+ * for the available functionality.
+ */
+public class VRichTextToolbar extends Composite {
+
+ /**
+ * This {@link ClientBundle} is used for all the button icons. Using a
+ * bundle allows all of these images to be packed into a single image, which
+ * saves a lot of HTTP requests, drastically improving startup time.
+ */
+ public interface Images extends ClientBundle {
+
+ ImageResource bold();
+
+ ImageResource createLink();
+
+ ImageResource hr();
+
+ ImageResource indent();
+
+ ImageResource insertImage();
+
+ ImageResource italic();
+
+ ImageResource justifyCenter();
+
+ ImageResource justifyLeft();
+
+ ImageResource justifyRight();
+
+ ImageResource ol();
+
+ ImageResource outdent();
+
+ ImageResource removeFormat();
+
+ ImageResource removeLink();
+
+ ImageResource strikeThrough();
+
+ ImageResource subscript();
+
+ ImageResource superscript();
+
+ ImageResource ul();
+
+ ImageResource underline();
+ }
+
+ /**
+ * This {@link Constants} interface is used to make the toolbar's strings
+ * internationalizable.
+ */
+ public interface Strings extends Constants {
+
+ String black();
+
+ String blue();
+
+ String bold();
+
+ String color();
+
+ String createLink();
+
+ String font();
+
+ String green();
+
+ String hr();
+
+ String indent();
+
+ String insertImage();
+
+ String italic();
+
+ String justifyCenter();
+
+ String justifyLeft();
+
+ String justifyRight();
+
+ String large();
+
+ String medium();
+
+ String normal();
+
+ String ol();
+
+ String outdent();
+
+ String red();
+
+ String removeFormat();
+
+ String removeLink();
+
+ String size();
+
+ String small();
+
+ String strikeThrough();
+
+ String subscript();
+
+ String superscript();
+
+ String ul();
+
+ String underline();
+
+ String white();
+
+ String xlarge();
+
+ String xsmall();
+
+ String xxlarge();
+
+ String xxsmall();
+
+ String yellow();
+ }
+
+ /**
+ * We use an inner EventHandler class to avoid exposing event methods on the
+ * RichTextToolbar itself.
+ */
+ private class EventHandler implements ClickHandler, ChangeHandler,
+ KeyUpHandler {
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onChange(ChangeEvent event) {
+ Object sender = event.getSource();
+ if (sender == backColors) {
+ basic.setBackColor(backColors.getValue(backColors
+ .getSelectedIndex()));
+ backColors.setSelectedIndex(0);
+ } else if (sender == foreColors) {
+ basic.setForeColor(foreColors.getValue(foreColors
+ .getSelectedIndex()));
+ foreColors.setSelectedIndex(0);
+ } else if (sender == fonts) {
+ basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
+ fonts.setSelectedIndex(0);
+ } else if (sender == fontSizes) {
+ basic.setFontSize(fontSizesConstants[fontSizes
+ .getSelectedIndex() - 1]);
+ fontSizes.setSelectedIndex(0);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onClick(ClickEvent event) {
+ Object sender = event.getSource();
+ if (sender == bold) {
+ basic.toggleBold();
+ } else if (sender == italic) {
+ basic.toggleItalic();
+ } else if (sender == underline) {
+ basic.toggleUnderline();
+ } else if (sender == subscript) {
+ basic.toggleSubscript();
+ } else if (sender == superscript) {
+ basic.toggleSuperscript();
+ } else if (sender == strikethrough) {
+ extended.toggleStrikethrough();
+ } else if (sender == indent) {
+ extended.rightIndent();
+ } else if (sender == outdent) {
+ extended.leftIndent();
+ } else if (sender == justifyLeft) {
+ basic.setJustification(RichTextArea.Justification.LEFT);
+ } else if (sender == justifyCenter) {
+ basic.setJustification(RichTextArea.Justification.CENTER);
+ } else if (sender == justifyRight) {
+ basic.setJustification(RichTextArea.Justification.RIGHT);
+ } else if (sender == insertImage) {
+ final String url = Window.prompt("Enter an image URL:",
+ "http://");
+ if (url != null) {
+ extended.insertImage(url);
+ }
+ } else if (sender == createLink) {
+ final String url = Window
+ .prompt("Enter a link URL:", "http://");
+ if (url != null) {
+ extended.createLink(url);
+ }
+ } else if (sender == removeLink) {
+ extended.removeLink();
+ } else if (sender == hr) {
+ extended.insertHorizontalRule();
+ } else if (sender == ol) {
+ extended.insertOrderedList();
+ } else if (sender == ul) {
+ extended.insertUnorderedList();
+ } else if (sender == removeFormat) {
+ extended.removeFormat();
+ } else if (sender == richText) {
+ // We use the RichTextArea's onKeyUp event to update the toolbar
+ // status. This will catch any cases where the user moves the
+ // cursur using the keyboard, or uses one of the browser's
+ // built-in keyboard shortcuts.
+ updateStatus();
+ }
+ }
+
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ if (event.getSource() == richText) {
+ // We use the RichTextArea's onKeyUp event to update the toolbar
+ // status. This will catch any cases where the user moves the
+ // cursor using the keyboard, or uses one of the browser's
+ // built-in keyboard shortcuts.
+ updateStatus();
+ }
+ }
+ }
+
+ private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
+ RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
+ RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
+ RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
+ RichTextArea.FontSize.XX_LARGE };
+
+ private final Images images = (Images) GWT.create(Images.class);
+ private final Strings strings = (Strings) GWT.create(Strings.class);
+ private final EventHandler handler = new EventHandler();
+
+ private final RichTextArea richText;
+ @SuppressWarnings("deprecation")
+ private final RichTextArea.BasicFormatter basic;
+ @SuppressWarnings("deprecation")
+ private final RichTextArea.ExtendedFormatter extended;
+
+ private final FlowPanel outer = new FlowPanel();
+ private final FlowPanel topPanel = new FlowPanel();
+ private final FlowPanel bottomPanel = new FlowPanel();
+ private ToggleButton bold;
+ private ToggleButton italic;
+ private ToggleButton underline;
+ private ToggleButton subscript;
+ private ToggleButton superscript;
+ private ToggleButton strikethrough;
+ private PushButton indent;
+ private PushButton outdent;
+ private PushButton justifyLeft;
+ private PushButton justifyCenter;
+ private PushButton justifyRight;
+ private PushButton hr;
+ private PushButton ol;
+ private PushButton ul;
+ private PushButton insertImage;
+ private PushButton createLink;
+ private PushButton removeLink;
+ private PushButton removeFormat;
+
+ private ListBox backColors;
+ private ListBox foreColors;
+ private ListBox fonts;
+ private ListBox fontSizes;
+
+ /**
+ * Creates a new toolbar that drives the given rich text area.
+ *
+ * @param richText
+ * the rich text area to be controlled
+ */
+ @SuppressWarnings("deprecation")
+ public VRichTextToolbar(RichTextArea richText) {
+ this.richText = richText;
+ basic = richText.getBasicFormatter();
+ extended = richText.getExtendedFormatter();
+
+ outer.add(topPanel);
+ outer.add(bottomPanel);
+ topPanel.setStyleName("gwt-RichTextToolbar-top");
+ bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
+
+ initWidget(outer);
+ setStyleName("gwt-RichTextToolbar");
+
+ if (basic != null) {
+ topPanel.add(bold = createToggleButton(images.bold(),
+ strings.bold()));
+ topPanel.add(italic = createToggleButton(images.italic(),
+ strings.italic()));
+ topPanel.add(underline = createToggleButton(images.underline(),
+ strings.underline()));
+ topPanel.add(subscript = createToggleButton(images.subscript(),
+ strings.subscript()));
+ topPanel.add(superscript = createToggleButton(images.superscript(),
+ strings.superscript()));
+ topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
+ strings.justifyLeft()));
+ topPanel.add(justifyCenter = createPushButton(
+ images.justifyCenter(), strings.justifyCenter()));
+ topPanel.add(justifyRight = createPushButton(images.justifyRight(),
+ strings.justifyRight()));
+ }
+
+ if (extended != null) {
+ topPanel.add(strikethrough = createToggleButton(
+ images.strikeThrough(), strings.strikeThrough()));
+ topPanel.add(indent = createPushButton(images.indent(),
+ strings.indent()));
+ topPanel.add(outdent = createPushButton(images.outdent(),
+ strings.outdent()));
+ topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
+ topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
+ topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
+ topPanel.add(insertImage = createPushButton(images.insertImage(),
+ strings.insertImage()));
+ topPanel.add(createLink = createPushButton(images.createLink(),
+ strings.createLink()));
+ topPanel.add(removeLink = createPushButton(images.removeLink(),
+ strings.removeLink()));
+ topPanel.add(removeFormat = createPushButton(images.removeFormat(),
+ strings.removeFormat()));
+ }
+
+ if (basic != null) {
+ bottomPanel.add(backColors = createColorList("Background"));
+ bottomPanel.add(foreColors = createColorList("Foreground"));
+ bottomPanel.add(fonts = createFontList());
+ bottomPanel.add(fontSizes = createFontSizes());
+
+ // We only use these handlers for updating status, so don't hook
+ // them up unless at least basic editing is supported.
+ richText.addKeyUpHandler(handler);
+ richText.addClickHandler(handler);
+ }
+ }
+
+ private ListBox createColorList(String caption) {
+ final ListBox lb = new ListBox();
+ lb.addChangeHandler(handler);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(caption);
+ lb.addItem(strings.white(), "white");
+ lb.addItem(strings.black(), "black");
+ lb.addItem(strings.red(), "red");
+ lb.addItem(strings.green(), "green");
+ lb.addItem(strings.yellow(), "yellow");
+ lb.addItem(strings.blue(), "blue");
+ lb.setTabIndex(-1);
+ return lb;
+ }
+
+ private ListBox createFontList() {
+ final ListBox lb = new ListBox();
+ lb.addChangeHandler(handler);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(strings.font(), "");
+ lb.addItem(strings.normal(), "inherit");
+ lb.addItem("Times New Roman", "Times New Roman");
+ lb.addItem("Arial", "Arial");
+ lb.addItem("Courier New", "Courier New");
+ lb.addItem("Georgia", "Georgia");
+ lb.addItem("Trebuchet", "Trebuchet");
+ lb.addItem("Verdana", "Verdana");
+ lb.setTabIndex(-1);
+ return lb;
+ }
+
+ private ListBox createFontSizes() {
+ final ListBox lb = new ListBox();
+ lb.addChangeHandler(handler);
+ lb.setVisibleItemCount(1);
+
+ lb.addItem(strings.size());
+ lb.addItem(strings.xxsmall());
+ lb.addItem(strings.xsmall());
+ lb.addItem(strings.small());
+ lb.addItem(strings.medium());
+ lb.addItem(strings.large());
+ lb.addItem(strings.xlarge());
+ lb.addItem(strings.xxlarge());
+ lb.setTabIndex(-1);
+ return lb;
+ }
+
+ private PushButton createPushButton(ImageResource img, String tip) {
+ final PushButton pb = new PushButton(new Image(img));
+ pb.addClickHandler(handler);
+ pb.setTitle(tip);
+ pb.setTabIndex(-1);
+ return pb;
+ }
+
+ private ToggleButton createToggleButton(ImageResource img, String tip) {
+ final ToggleButton tb = new ToggleButton(new Image(img));
+ tb.addClickHandler(handler);
+ tb.setTitle(tip);
+ tb.setTabIndex(-1);
+ return tb;
+ }
+
+ /**
+ * Updates the status of all the stateful buttons.
+ */
+ @SuppressWarnings("deprecation")
+ private void updateStatus() {
+ if (basic != null) {
+ bold.setDown(basic.isBold());
+ italic.setDown(basic.isItalic());
+ underline.setDown(basic.isUnderlined());
+ subscript.setDown(basic.isSubscript());
+ superscript.setDown(basic.isSuperscript());
+ }
+
+ if (extended != null) {
+ strikethrough.setDown(extended.isStrikethrough());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.dom.client.Touch;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.VTooltip;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.client.ui.dd.DDUtil;
+import com.vaadin.client.ui.dd.VAbstractDropHandler;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragAndDropManager;
+import com.vaadin.client.ui.dd.VDragEvent;
+import com.vaadin.client.ui.dd.VHasDropHandler;
+import com.vaadin.client.ui.dd.VTransferable;
+import com.vaadin.client.ui.embedded.VEmbedded;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.table.TableConstants;
+
+/**
+ * VScrollTable
+ *
+ * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
+ * ScrollPanel
+ *
+ * TableHead contains table's header and widgets + logic for resizing,
+ * reordering and hiding columns.
+ *
+ * ScrollPanel contains VScrollTableBody object which handles content. To save
+ * some bandwidth and to improve clients responsiveness with loads of data, in
+ * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
+ * VScrollTableBody to use the exact same space as non-rendered rows would use.
+ * This way we can use seamlessly traditional scrollbars and scrolling to fetch
+ * more rows instead of "paging".
+ *
+ * In VScrollTable we listen to scroll events. On horizontal scrolling we also
+ * update TableHeads scroll position which has its scrollbars hidden. On
+ * vertical scroll events we will check if we are reaching the end of area where
+ * we have rows rendered and
+ *
+ * TODO implement unregistering for child components in Cells
+ */
+public class VScrollTable extends FlowPanel implements HasWidgets,
+ ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
+ ActionOwner {
+
+ public static final String STYLENAME = "v-table";
+
+ public enum SelectMode {
+ NONE(0), SINGLE(1), MULTI(2);
+ private int id;
+
+ private SelectMode(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+ }
+
+ private static final String ROW_HEADER_COLUMN_KEY = "0";
+
+ private static final double CACHE_RATE_DEFAULT = 2;
+
+ /**
+ * The default multi select mode where simple left clicks only selects one
+ * item, CTRL+left click selects multiple items and SHIFT-left click selects
+ * a range of items.
+ */
+ private static final int MULTISELECT_MODE_DEFAULT = 0;
+
+ /**
+ * The simple multiselect mode is what the table used to have before
+ * ctrl/shift selections were added. That is that when this is set clicking
+ * on an item selects/deselects the item and no ctrl/shift selections are
+ * available.
+ */
+ private static final int MULTISELECT_MODE_SIMPLE = 1;
+
+ /**
+ * multiple of pagelength which component will cache when requesting more
+ * rows
+ */
+ private double cache_rate = CACHE_RATE_DEFAULT;
+ /**
+ * fraction of pageLenght which can be scrolled without making new request
+ */
+ private double cache_react_rate = 0.75 * cache_rate;
+
+ public static final char ALIGN_CENTER = 'c';
+ public static final char ALIGN_LEFT = 'b';
+ public static final char ALIGN_RIGHT = 'e';
+ private static final int CHARCODE_SPACE = 32;
+ private int firstRowInViewPort = 0;
+ private int pageLength = 15;
+ private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean showRowHeaders = false;
+
+ private String[] columnOrder;
+
+ protected ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ private boolean nullSelectionAllowed = true;
+
+ private SelectMode selectMode = SelectMode.NONE;
+
+ private final HashSet<String> selectedRowKeys = new HashSet<String>();
+
+ /*
+ * When scrolling and selecting at the same time, the selections are not in
+ * sync with the server while retrieving new rows (until key is released).
+ */
+ private HashSet<Object> unSyncedselectionsBeforeRowFetch;
+
+ /*
+ * These are used when jumping between pages when pressing Home and End
+ */
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean selectLastItemInNextRender = false;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean selectFirstItemInNextRender = false;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean focusFirstItemInNextRender = false;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean focusLastItemInNextRender = false;
+
+ /**
+ * The currently focused row.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public VScrollTableRow focusedRow;
+
+ /**
+ * Helper to store selection range start in when using the keyboard
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public VScrollTableRow selectionRangeStart;
+
+ /**
+ * Flag for notifying when the selection has changed and should be sent to
+ * the server
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean selectionChanged = false;
+
+ /*
+ * The speed (in pixels) which the scrolling scrolls vertically/horizontally
+ */
+ private int scrollingVelocity = 10;
+
+ private Timer scrollingVelocityTimer = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String[] bodyActionKeys;
+
+ private boolean enableDebug = false;
+
+ private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
+ .isTouchDevice()
+ && !BrowserInfo.get().requiresTouchScrollDelegate();
+
+ private Set<String> noncollapsibleColumns;
+
+ /**
+ * The last known row height used to preserve the height of a table with
+ * custom row heights and a fixed page length after removing the last row
+ * from the table.
+ *
+ * A new VScrollTableBody instance is created every time the number of rows
+ * changes causing {@link VScrollTableBody#rowHeight} to be discarded and
+ * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)}
+ * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
+ * round(3 * 19.8) / 3 = 19.66.
+ */
+ private double lastKnownRowHeight = Double.NaN;
+
+ /**
+ * Represents a select range of rows
+ */
+ private class SelectionRange {
+ private VScrollTableRow startRow;
+ private final int length;
+
+ /**
+ * Constuctor.
+ */
+ public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
+ VScrollTableRow endRow;
+ if (row2.isBefore(row1)) {
+ startRow = row2;
+ endRow = row1;
+ } else {
+ startRow = row1;
+ endRow = row2;
+ }
+ length = endRow.getIndex() - startRow.getIndex() + 1;
+ }
+
+ public SelectionRange(VScrollTableRow row, int length) {
+ startRow = row;
+ this.length = length;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return startRow.getKey() + "-" + length;
+ }
+
+ private boolean inRange(VScrollTableRow row) {
+ return row.getIndex() >= startRow.getIndex()
+ && row.getIndex() < startRow.getIndex() + length;
+ }
+
+ public Collection<SelectionRange> split(VScrollTableRow row) {
+ assert row.isAttached();
+ ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
+
+ int endOfFirstRange = row.getIndex() - 1;
+ if (!(endOfFirstRange - startRow.getIndex() < 0)) {
+ // create range of first part unless its length is < 1
+ ranges.add(new SelectionRange(startRow, endOfFirstRange
+ - startRow.getIndex() + 1));
+ }
+ int startOfSecondRange = row.getIndex() + 1;
+ if (!(getEndIndex() - startOfSecondRange < 0)) {
+ // create range of second part unless its length is < 1
+ VScrollTableRow startOfRange = scrollBody
+ .getRowByRowIndex(startOfSecondRange);
+ ranges.add(new SelectionRange(startOfRange, getEndIndex()
+ - startOfSecondRange + 1));
+ }
+ return ranges;
+ }
+
+ private int getEndIndex() {
+ return startRow.getIndex() + length - 1;
+ }
+
+ };
+
+ private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean initializedAndAttached = false;
+
+ /**
+ * Flag to indicate if a column width recalculation is needed due update.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean headerChangedDuringUpdate = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final TableHead tHead = new TableHead();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final TableFooter tFoot = new TableFooter();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(
+ true);
+
+ private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
+
+ @Override
+ public void onKeyPress(KeyPressEvent keyPressEvent) {
+ // This is used for Firefox only, since Firefox auto-repeat
+ // works correctly only if we use a key press handler, other
+ // browsers handle it correctly when using a key down handler
+ if (!BrowserInfo.get().isGecko()) {
+ return;
+ }
+
+ NativeEvent event = keyPressEvent.getNativeEvent();
+ if (!enabled) {
+ // Cancel default keyboard events on a disabled Table
+ // (prevents scrolling)
+ event.preventDefault();
+ } else if (hasFocus) {
+ // Key code in Firefox/onKeyPress is present only for
+ // special keys, otherwise 0 is returned
+ int keyCode = event.getKeyCode();
+ if (keyCode == 0 && event.getCharCode() == ' ') {
+ // Provide a keyCode for space to be compatible with
+ // FireFox keypress event
+ keyCode = CHARCODE_SPACE;
+ }
+
+ if (handleNavigation(keyCode,
+ event.getCtrlKey() || event.getMetaKey(),
+ event.getShiftKey())) {
+ event.preventDefault();
+ }
+
+ startScrollingVelocityTimer();
+ }
+ }
+
+ };
+
+ private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
+
+ @Override
+ public void onKeyUp(KeyUpEvent keyUpEvent) {
+ NativeEvent event = keyUpEvent.getNativeEvent();
+ int keyCode = event.getKeyCode();
+
+ if (!isFocusable()) {
+ cancelScrollingVelocityTimer();
+ } else if (isNavigationKey(keyCode)) {
+ if (keyCode == getNavigationDownKey()
+ || keyCode == getNavigationUpKey()) {
+ /*
+ * in multiselect mode the server may still have value from
+ * previous page. Clear it unless doing multiselection or
+ * just moving focus.
+ */
+ if (!event.getShiftKey() && !event.getCtrlKey()) {
+ instructServerToForgetPreviousSelections();
+ }
+ sendSelectedRows();
+ }
+ cancelScrollingVelocityTimer();
+ navKeyDown = false;
+ }
+ }
+ };
+
+ private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
+
+ @Override
+ public void onKeyDown(KeyDownEvent keyDownEvent) {
+ NativeEvent event = keyDownEvent.getNativeEvent();
+ // This is not used for Firefox
+ if (BrowserInfo.get().isGecko()) {
+ return;
+ }
+
+ if (!enabled) {
+ // Cancel default keyboard events on a disabled Table
+ // (prevents scrolling)
+ event.preventDefault();
+ } else if (hasFocus) {
+ if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
+ || event.getMetaKey(), event.getShiftKey())) {
+ navKeyDown = true;
+ event.preventDefault();
+ }
+
+ startScrollingVelocityTimer();
+ }
+ }
+ };
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int totalRows;
+
+ private Set<String> collapsedColumns;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final RowRequestHandler rowRequestHandler;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VScrollTableBody scrollBody;
+
+ private int firstvisible = 0;
+ private boolean sortAscending;
+ private String sortColumn;
+ private String oldSortColumn;
+ private boolean columnReordering;
+
+ /**
+ * This map contains captions and icon urls for actions like: * "33_c" ->
+ * "Edit" * "33_i" -> "http://dom.com/edit.png"
+ */
+ private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
+ private String[] visibleColOrder;
+ private boolean initialContentReceived = false;
+ private Element scrollPositionElement;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean enabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean showColHeaders;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean showColFooters;
+
+ /** flag to indicate that table body has changed */
+ private boolean isNewBody = true;
+
+ /**
+ * Read from the "recalcWidths" -attribute. When it is true, the table will
+ * recalculate the widths for columns - desirable in some cases. For #1983,
+ * marked experimental.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean recalcWidths = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean rendering = false;
+
+ private boolean hasFocus = false;
+ private int dragmode;
+
+ private int multiselectmode;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int tabIndex;
+
+ private TouchScrollDelegate touchScrollDelegate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int lastRenderedHeight;
+
+ /**
+ * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
+ * rows (indexes) are in the server side cache (page buffer). -1 means
+ * unknown. The server side cache row MUST MATCH the client side cache rows.
+ *
+ * If the client side cache contains additional rows with e.g. buttons, it
+ * will cause out of sync when such a button is pressed.
+ *
+ * If the server side cache contains additional rows with e.g. buttons,
+ * scrolling in the client will cause empty buttons to be rendered
+ * (cached=true request for non-existing components)
+ *
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public int serverCacheFirst = -1;
+ public int serverCacheLast = -1;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean sizeNeedsInit = true;
+
+ /**
+ * Used to recall the position of an open context menu if we need to close
+ * and reopen it during a row update.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public class ContextMenuDetails {
+ public String rowKey;
+ public int left;
+ public int top;
+
+ public ContextMenuDetails(String rowKey, int left, int top) {
+ this.rowKey = rowKey;
+ this.left = left;
+ this.top = top;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ContextMenuDetails contextMenu = null;
+
+ public VScrollTable() {
+ setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
+
+ scrollBodyPanel.addFocusHandler(this);
+ scrollBodyPanel.addBlurHandler(this);
+
+ scrollBodyPanel.addScrollHandler(this);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
+ } else {
+ scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
+ }
+ scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
+
+ scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
+
+ scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU);
+ scrollBodyPanel.addDomHandler(new ContextMenuHandler() {
+
+ @Override
+ public void onContextMenu(ContextMenuEvent event) {
+ handleBodyContextMenu(event);
+ }
+ }, ContextMenuEvent.getType());
+
+ setStyleName(STYLENAME);
+
+ add(tHead);
+ add(scrollBodyPanel);
+ add(tFoot);
+
+ rowRequestHandler = new RowRequestHandler();
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ updateStyleNames(style, false);
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ updateStyleNames(style, true);
+ }
+
+ private void updateStyleNames(String newStyle, boolean isPrimary) {
+ scrollBodyPanel
+ .removeStyleName(getStylePrimaryName() + "-body-wrapper");
+ scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
+
+ if (scrollBody != null) {
+ scrollBody.removeStyleName(getStylePrimaryName()
+ + "-body-noselection");
+ }
+
+ if (isPrimary) {
+ super.setStylePrimaryName(newStyle);
+ } else {
+ super.setStyleName(newStyle);
+ }
+
+ scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
+ scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
+
+ tHead.updateStyleNames(getStylePrimaryName());
+ tFoot.updateStyleNames(getStylePrimaryName());
+
+ if (scrollBody != null) {
+ scrollBody.updateStyleNames(getStylePrimaryName());
+ }
+ }
+
+ public void init(ApplicationConnection client) {
+ this.client = client;
+ // Add a handler to clear saved context menu details when the menu
+ // closes. See #8526.
+ client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() {
+
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ contextMenu = null;
+ }
+ });
+ }
+
+ private void handleBodyContextMenu(ContextMenuEvent event) {
+ if (enabled && bodyActionKeys != null) {
+ int left = Util.getTouchOrMouseClientX(event.getNativeEvent());
+ int top = Util.getTouchOrMouseClientY(event.getNativeEvent());
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ client.getContextMenu().showAt(this, left, top);
+
+ // Only prevent browser context menu if there are action handlers
+ // registered
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Fires a column resize event which sends the resize information to the
+ * server.
+ *
+ * @param columnId
+ * The columnId of the column which was resized
+ * @param originalWidth
+ * The width in pixels of the column before the resize event
+ * @param newWidth
+ * The width in pixels of the column after the resize event
+ */
+ private void fireColumnResizeEvent(String columnId, int originalWidth,
+ int newWidth) {
+ client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
+ false);
+ client.updateVariable(paintableId, "columnResizeEventPrev",
+ originalWidth, false);
+ client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
+ immediate);
+
+ }
+
+ /**
+ * Non-immediate variable update of column widths for a collection of
+ * columns.
+ *
+ * @param columns
+ * the columns to trigger the events for.
+ */
+ private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
+ String[] newSizes = new String[columns.size()];
+ int ix = 0;
+ for (HeaderCell cell : columns) {
+ newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
+ }
+ client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
+ false);
+ }
+
+ /**
+ * Moves the focus one step down
+ *
+ * @return Returns true if succeeded
+ */
+ private boolean moveFocusDown() {
+ return moveFocusDown(0);
+ }
+
+ /**
+ * Moves the focus down by 1+offset rows
+ *
+ * @return Returns true if succeeded, else false if the selection could not
+ * be move downwards
+ */
+ private boolean moveFocusDown(int offset) {
+ if (isSelectable()) {
+ if (focusedRow == null && scrollBody.iterator().hasNext()) {
+ // FIXME should focus first visible from top, not first rendered
+ // ??
+ return setRowFocus((VScrollTableRow) scrollBody.iterator()
+ .next());
+ } else {
+ VScrollTableRow next = getNextRow(focusedRow, offset);
+ if (next != null) {
+ return setRowFocus(next);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Moves the selection one step up
+ *
+ * @return Returns true if succeeded
+ */
+ private boolean moveFocusUp() {
+ return moveFocusUp(0);
+ }
+
+ /**
+ * Moves the focus row upwards
+ *
+ * @return Returns true if succeeded, else false if the selection could not
+ * be move upwards
+ *
+ */
+ private boolean moveFocusUp(int offset) {
+ if (isSelectable()) {
+ if (focusedRow == null && scrollBody.iterator().hasNext()) {
+ // FIXME logic is exactly the same as in moveFocusDown, should
+ // be the opposite??
+ return setRowFocus((VScrollTableRow) scrollBody.iterator()
+ .next());
+ } else {
+ VScrollTableRow prev = getPreviousRow(focusedRow, offset);
+ if (prev != null) {
+ return setRowFocus(prev);
+ } else {
+ VConsole.log("no previous available");
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Selects a row where the current selection head is
+ *
+ * @param ctrlSelect
+ * Is the selection a ctrl+selection
+ * @param shiftSelect
+ * Is the selection a shift+selection
+ * @return Returns truw
+ */
+ private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
+ if (focusedRow != null) {
+ // Arrows moves the selection and clears previous selections
+ if (isSelectable() && !ctrlSelect && !shiftSelect) {
+ deselectAll();
+ focusedRow.toggleSelection();
+ selectionRangeStart = focusedRow;
+ } else if (isSelectable() && ctrlSelect && !shiftSelect) {
+ // Ctrl+arrows moves selection head
+ selectionRangeStart = focusedRow;
+ // No selection, only selection head is moved
+ } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
+ // Shift+arrows selection selects a range
+ focusedRow.toggleShiftSelection(shiftSelect);
+ }
+ }
+ }
+
+ /**
+ * Sends the selection to the server if changed since the last update/visit.
+ */
+ protected void sendSelectedRows() {
+ sendSelectedRows(immediate);
+ }
+
+ /**
+ * Sends the selection to the server if it has been changed since the last
+ * update/visit.
+ *
+ * @param immediately
+ * set to true to immediately send the rows
+ */
+ protected void sendSelectedRows(boolean immediately) {
+ // Don't send anything if selection has not changed
+ if (!selectionChanged) {
+ return;
+ }
+
+ // Reset selection changed flag
+ selectionChanged = false;
+
+ // Note: changing the immediateness of this might require changes to
+ // "clickEvent" immediateness also.
+ if (isMultiSelectModeDefault()) {
+ // Convert ranges to a set of strings
+ Set<String> ranges = new HashSet<String>();
+ for (SelectionRange range : selectedRowRanges) {
+ ranges.add(range.toString());
+ }
+
+ // Send the selected row ranges
+ client.updateVariable(paintableId, "selectedRanges",
+ ranges.toArray(new String[selectedRowRanges.size()]), false);
+
+ // clean selectedRowKeys so that they don't contain excess values
+ for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
+ .hasNext();) {
+ String key = iterator.next();
+ VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
+ if (renderedRowByKey != null) {
+ for (SelectionRange range : selectedRowRanges) {
+ if (range.inRange(renderedRowByKey)) {
+ iterator.remove();
+ }
+ }
+ } else {
+ // orphaned selected key, must be in a range, ignore
+ iterator.remove();
+ }
+
+ }
+ }
+
+ // Send the selected rows
+ client.updateVariable(paintableId, "selected",
+ selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
+ immediately);
+
+ }
+
+ /**
+ * Get the key that moves the selection head upwards. By default it is the
+ * up arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection head downwards. By default it is the
+ * down arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that scrolls to the left in the table. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that scroll to the right on the table. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the space
+ * bar key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return CHARCODE_SPACE;
+ }
+
+ /**
+ * Get the key the moves the selection one page up in the table. By default
+ * this is the Page Up key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageUpKey() {
+ return KeyCodes.KEY_PAGEUP;
+ }
+
+ /**
+ * Get the key the moves the selection one page down in the table. By
+ * default this is the Page Down key but by overriding this you can change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageDownKey() {
+ return KeyCodes.KEY_PAGEDOWN;
+ }
+
+ /**
+ * Get the key the moves the selection to the beginning of the table. By
+ * default this is the Home key but by overriding this you can change the
+ * key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationStartKey() {
+ return KeyCodes.KEY_HOME;
+ }
+
+ /**
+ * Get the key the moves the selection to the end of the table. By default
+ * this is the End key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationEndKey() {
+ return KeyCodes.KEY_END;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void initializeRows(UIDL uidl, UIDL rowData) {
+ if (scrollBody != null) {
+ scrollBody.removeFromParent();
+ }
+ scrollBody = createScrollBody();
+
+ scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
+ uidl.getIntAttribute("rows"));
+ scrollBodyPanel.add(scrollBody);
+
+ // New body starts scrolled to the left, make sure the header and footer
+ // are also scrolled to the left
+ tHead.setHorizontalScrollPosition(0);
+ tFoot.setHorizontalScrollPosition(0);
+
+ initialContentReceived = true;
+ sizeNeedsInit = true;
+ scrollBody.restoreRowVisibility();
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateColumnProperties(UIDL uidl) {
+ updateColumnOrder(uidl);
+
+ updateCollapsedColumns(uidl);
+
+ UIDL vc = uidl.getChildByTagName("visiblecolumns");
+ if (vc != null) {
+ tHead.updateCellsFromUIDL(vc);
+ tFoot.updateCellsFromUIDL(vc);
+ }
+
+ updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+ updateFooter(uidl.getStringArrayAttribute("vcolorder"));
+ if (uidl.hasVariable("noncollapsiblecolumns")) {
+ noncollapsibleColumns = uidl
+ .getStringArrayVariableAsSet("noncollapsiblecolumns");
+ }
+ }
+
+ private void updateCollapsedColumns(UIDL uidl) {
+ if (uidl.hasVariable("collapsedcolumns")) {
+ tHead.setColumnCollapsingAllowed(true);
+ collapsedColumns = uidl
+ .getStringArrayVariableAsSet("collapsedcolumns");
+ } else {
+ tHead.setColumnCollapsingAllowed(false);
+ }
+ }
+
+ private void updateColumnOrder(UIDL uidl) {
+ if (uidl.hasVariable("columnorder")) {
+ columnReordering = true;
+ columnOrder = uidl.getStringArrayVariable("columnorder");
+ } else {
+ columnReordering = false;
+ columnOrder = null;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean selectSelectedRows(UIDL uidl) {
+ boolean keyboardSelectionOverRowFetchInProgress = false;
+
+ if (uidl.hasVariable("selected")) {
+ final Set<String> selectedKeys = uidl
+ .getStringArrayVariableAsSet("selected");
+ if (scrollBody != null) {
+ Iterator<Widget> iterator = scrollBody.iterator();
+ while (iterator.hasNext()) {
+ /*
+ * Make the focus reflect to the server side state unless we
+ * are currently selecting multiple rows with keyboard.
+ */
+ VScrollTableRow row = (VScrollTableRow) iterator.next();
+ boolean selected = selectedKeys.contains(row.getKey());
+ if (!selected
+ && unSyncedselectionsBeforeRowFetch != null
+ && unSyncedselectionsBeforeRowFetch.contains(row
+ .getKey())) {
+ selected = true;
+ keyboardSelectionOverRowFetchInProgress = true;
+ }
+ if (selected != row.isSelected()) {
+ row.toggleSelection();
+ if (!isSingleSelectMode() && !selected) {
+ // Update selection range in case a row is
+ // unselected from the middle of a range - #8076
+ removeRowFromUnsentSelectionRanges(row);
+ }
+ }
+ }
+ }
+ }
+ unSyncedselectionsBeforeRowFetch = null;
+ return keyboardSelectionOverRowFetchInProgress;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateSortingProperties(UIDL uidl) {
+ oldSortColumn = sortColumn;
+ if (uidl.hasVariable("sortascending")) {
+ sortAscending = uidl.getBooleanVariable("sortascending");
+ sortColumn = uidl.getStringVariable("sortcolumn");
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void resizeSortedColumnForSortIndicator() {
+ // Force recalculation of the captionContainer element inside the header
+ // cell to accomodate for the size of the sort arrow.
+ HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
+ if (sortedHeader != null) {
+ tHead.resizeCaptionContainer(sortedHeader);
+ }
+ // Also recalculate the width of the captionContainer element in the
+ // previously sorted header, since this now has more room.
+ HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
+ if (oldSortedHeader != null) {
+ tHead.resizeCaptionContainer(oldSortedHeader);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
+ firstvisible = uidl.hasVariable("firstvisible") ? uidl
+ .getIntVariable("firstvisible") : 0;
+ if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
+ // received 'surprising' firstvisible from server: scroll there
+ firstRowInViewPort = firstvisible;
+ scrollBodyPanel
+ .setScrollPosition(measureRowHeightOffset(firstvisible));
+ }
+ }
+
+ protected int measureRowHeightOffset(int rowIx) {
+ return (int) (rowIx * scrollBody.getRowHeight());
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updatePageLength(UIDL uidl) {
+ int oldPageLength = pageLength;
+ if (uidl.hasAttribute("pagelength")) {
+ pageLength = uidl.getIntAttribute("pagelength");
+ } else {
+ // pagelenght is "0" meaning scrolling is turned off
+ pageLength = totalRows;
+ }
+
+ if (oldPageLength != pageLength && initializedAndAttached) {
+ // page length changed, need to update size
+ sizeNeedsInit = true;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateSelectionProperties(UIDL uidl, ComponentState state,
+ boolean readOnly) {
+ setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl
+ .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT);
+
+ nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
+ .getBooleanAttribute("nsa") : true;
+
+ if (uidl.hasAttribute("selectmode")) {
+ if (readOnly) {
+ selectMode = SelectMode.NONE;
+ } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
+ selectMode = SelectMode.MULTI;
+ } else if (uidl.getStringAttribute("selectmode").equals("single")) {
+ selectMode = SelectMode.SINGLE;
+ } else {
+ selectMode = SelectMode.NONE;
+ }
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateDragMode(UIDL uidl) {
+ dragmode = uidl.hasAttribute("dragmode") ? uidl
+ .getIntAttribute("dragmode") : 0;
+ if (BrowserInfo.get().isIE()) {
+ if (dragmode > 0) {
+ getElement().setPropertyJSO("onselectstart",
+ getPreventTextSelectionIEHack());
+ } else {
+ getElement().setPropertyJSO("onselectstart", null);
+ }
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateTotalRows(UIDL uidl) {
+ int newTotalRows = uidl.getIntAttribute("totalrows");
+ if (newTotalRows != getTotalRows()) {
+ if (scrollBody != null) {
+ if (getTotalRows() == 0) {
+ tHead.clear();
+ tFoot.clear();
+ }
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ setTotalRows(newTotalRows);
+ }
+ }
+
+ protected void setTotalRows(int newTotalRows) {
+ totalRows = newTotalRows;
+ }
+
+ public int getTotalRows() {
+ return totalRows;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void focusRowFromBody() {
+ if (selectedRowKeys.size() == 1) {
+ // try to focus a row currently selected and in viewport
+ String selectedRowKey = selectedRowKeys.iterator().next();
+ if (selectedRowKey != null) {
+ VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
+ if (renderedRow == null || !renderedRow.isInViewPort()) {
+ setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+ } else {
+ setRowFocus(renderedRow);
+ }
+ }
+ } else {
+ // multiselect mode
+ setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+ }
+ }
+
+ protected VScrollTableBody createScrollBody() {
+ return new VScrollTableBody();
+ }
+
+ /**
+ * Selects the last row visible in the table
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param focusOnly
+ * Should the focus only be moved to the last row
+ */
+ public void selectLastRenderedRowInViewPort(boolean focusOnly) {
+ int index = firstRowInViewPort + getFullyVisibleRowCount();
+ VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
+ if (lastRowInViewport == null) {
+ // this should not happen in normal situations (white space at the
+ // end of viewport). Select the last rendered as a fallback.
+ lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody
+ .getLastRendered());
+ if (lastRowInViewport == null) {
+ return; // empty table
+ }
+ }
+ setRowFocus(lastRowInViewport);
+ if (!focusOnly) {
+ selectFocusedRow(false, multiselectPending);
+ sendSelectedRows();
+ }
+ }
+
+ /**
+ * Selects the first row visible in the table
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param focusOnly
+ * Should the focus only be moved to the first row
+ */
+ public void selectFirstRenderedRowInViewPort(boolean focusOnly) {
+ int index = firstRowInViewPort;
+ VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
+ if (firstInViewport == null) {
+ // this should not happen in normal situations
+ return;
+ }
+ setRowFocus(firstInViewport);
+ if (!focusOnly) {
+ selectFocusedRow(false, multiselectPending);
+ sendSelectedRows();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setCacheRateFromUIDL(UIDL uidl) {
+ setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
+ : CACHE_RATE_DEFAULT);
+ }
+
+ private void setCacheRate(double d) {
+ if (cache_rate != d) {
+ cache_rate = d;
+ cache_react_rate = 0.75 * d;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateActionMap(UIDL mainUidl) {
+ UIDL actionsUidl = mainUidl.getChildByTagName("actions");
+ if (actionsUidl == null) {
+ return;
+ }
+
+ final Iterator<?> it = actionsUidl.getChildIterator();
+ while (it.hasNext()) {
+ final UIDL action = (UIDL) it.next();
+ final String key = action.getStringAttribute("key");
+ final String caption = action.getStringAttribute("caption");
+ actionMap.put(key + "_c", caption);
+ if (action.hasAttribute("icon")) {
+ // TODO need some uri handling ??
+ actionMap.put(key + "_i", client.translateVaadinUri(action
+ .getStringAttribute("icon")));
+ } else {
+ actionMap.remove(key + "_i");
+ }
+ }
+
+ }
+
+ public String getActionCaption(String actionKey) {
+ return actionMap.get(actionKey + "_c");
+ }
+
+ public String getActionIcon(String actionKey) {
+ return actionMap.get(actionKey + "_i");
+ }
+
+ private void updateHeader(String[] strings) {
+ if (strings == null) {
+ return;
+ }
+
+ int visibleCols = strings.length;
+ int colIndex = 0;
+ if (showRowHeaders) {
+ tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
+ visibleCols++;
+ visibleColOrder = new String[visibleCols];
+ visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
+ colIndex++;
+ } else {
+ visibleColOrder = new String[visibleCols];
+ tHead.removeCell(ROW_HEADER_COLUMN_KEY);
+ }
+
+ int i;
+ for (i = 0; i < strings.length; i++) {
+ final String cid = strings[i];
+ visibleColOrder[colIndex] = cid;
+ tHead.enableColumn(cid, colIndex);
+ colIndex++;
+ }
+
+ tHead.setVisible(showColHeaders);
+ setContainerHeight();
+
+ }
+
+ /**
+ * Updates footers.
+ * <p>
+ * Update headers whould be called before this method is called!
+ * </p>
+ *
+ * @param strings
+ */
+ private void updateFooter(String[] strings) {
+ if (strings == null) {
+ return;
+ }
+
+ // Add dummy column if row headers are present
+ int colIndex = 0;
+ if (showRowHeaders) {
+ tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
+ colIndex++;
+ } else {
+ tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
+ }
+
+ int i;
+ for (i = 0; i < strings.length; i++) {
+ final String cid = strings[i];
+ tFoot.enableColumn(cid, colIndex);
+ colIndex++;
+ }
+
+ tFoot.setVisible(showColFooters);
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param uidl
+ * which contains row data
+ * @param firstRow
+ * first row in data set
+ * @param reqRows
+ * amount of rows in data set
+ */
+ public void updateBody(UIDL uidl, int firstRow, int reqRows) {
+ if (uidl == null || reqRows < 1) {
+ // container is empty, remove possibly existing rows
+ if (firstRow <= 0) {
+ while (scrollBody.getLastRendered() > scrollBody.firstRendered) {
+ scrollBody.unlinkRow(false);
+ }
+ scrollBody.unlinkRow(false);
+ }
+ return;
+ }
+
+ scrollBody.renderRows(uidl, firstRow, reqRows);
+
+ discardRowsOutsideCacheWindow();
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateRowsInBody(UIDL partialRowUpdates) {
+ if (partialRowUpdates == null) {
+ return;
+ }
+ int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
+ int count = partialRowUpdates.getIntAttribute("numurows");
+ scrollBody.unlinkRows(firstRowIx, count);
+ scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
+ }
+
+ /**
+ * Updates the internal cache by unlinking rows that fall outside of the
+ * caching window.
+ */
+ protected void discardRowsOutsideCacheWindow() {
+ int firstRowToKeep = (int) (firstRowInViewPort - pageLength
+ * cache_rate);
+ int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
+ * cache_rate);
+ debug("Client side calculated cache rows to keep: " + firstRowToKeep
+ + "-" + lastRowToKeep);
+
+ if (serverCacheFirst != -1) {
+ firstRowToKeep = serverCacheFirst;
+ lastRowToKeep = serverCacheLast;
+ debug("Server cache rows that override: " + serverCacheFirst + "-"
+ + serverCacheLast);
+ if (firstRowToKeep < scrollBody.getFirstRendered()
+ || lastRowToKeep > scrollBody.getLastRendered()) {
+ debug("*** Server wants us to keep " + serverCacheFirst + "-"
+ + serverCacheLast + " but we only have rows "
+ + scrollBody.getFirstRendered() + "-"
+ + scrollBody.getLastRendered() + " rendered!");
+ }
+ }
+ discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
+
+ scrollBody.fixSpacers();
+
+ scrollBody.restoreRowVisibility();
+ }
+
+ private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
+ /*
+ * firstDiscarded and lastDiscarded are only calculated for debug
+ * purposes
+ */
+ int firstDiscarded = -1, lastDiscarded = -1;
+ boolean cont = true;
+ while (cont && scrollBody.getLastRendered() > optimalFirstRow
+ && scrollBody.getFirstRendered() < optimalFirstRow) {
+ if (firstDiscarded == -1) {
+ firstDiscarded = scrollBody.getFirstRendered();
+ }
+
+ // removing row from start
+ cont = scrollBody.unlinkRow(true);
+ }
+ if (firstDiscarded != -1) {
+ lastDiscarded = scrollBody.getFirstRendered() - 1;
+ debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+ }
+ firstDiscarded = lastDiscarded = -1;
+
+ cont = true;
+ while (cont && scrollBody.getLastRendered() > optimalLastRow) {
+ if (lastDiscarded == -1) {
+ lastDiscarded = scrollBody.getLastRendered();
+ }
+
+ // removing row from the end
+ cont = scrollBody.unlinkRow(false);
+ }
+ if (lastDiscarded != -1) {
+ firstDiscarded = scrollBody.getLastRendered() + 1;
+ debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+ }
+
+ debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
+ + scrollBody.getLastRendered());
+ }
+
+ /**
+ * Inserts rows in the table body or removes them from the table body based
+ * on the commands in the UIDL.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param partialRowAdditions
+ * the UIDL containing row updates.
+ */
+ public void addAndRemoveRows(UIDL partialRowAdditions) {
+ if (partialRowAdditions == null) {
+ return;
+ }
+ if (partialRowAdditions.hasAttribute("hide")) {
+ scrollBody.unlinkAndReindexRows(
+ partialRowAdditions.getIntAttribute("firstprowix"),
+ partialRowAdditions.getIntAttribute("numprows"));
+ scrollBody.ensureCacheFilled();
+ } else {
+ if (partialRowAdditions.hasAttribute("delbelow")) {
+ scrollBody.insertRowsDeleteBelow(partialRowAdditions,
+ partialRowAdditions.getIntAttribute("firstprowix"),
+ partialRowAdditions.getIntAttribute("numprows"));
+ } else {
+ scrollBody.insertAndReindexRows(partialRowAdditions,
+ partialRowAdditions.getIntAttribute("firstprowix"),
+ partialRowAdditions.getIntAttribute("numprows"));
+ }
+ }
+
+ discardRowsOutsideCacheWindow();
+ }
+
+ /**
+ * Gives correct column index for given column key ("cid" in UIDL).
+ *
+ * @param colKey
+ * @return column index of visible columns, -1 if column not visible
+ */
+ private int getColIndexByKey(String colKey) {
+ // return 0 if asked for rowHeaders
+ if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
+ return 0;
+ }
+ for (int i = 0; i < visibleColOrder.length; i++) {
+ if (visibleColOrder[i].equals(colKey)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private boolean isMultiSelectModeSimple() {
+ return selectMode == SelectMode.MULTI
+ && multiselectmode == MULTISELECT_MODE_SIMPLE;
+ }
+
+ private boolean isSingleSelectMode() {
+ return selectMode == SelectMode.SINGLE;
+ }
+
+ private boolean isMultiSelectModeAny() {
+ return selectMode == SelectMode.MULTI;
+ }
+
+ private boolean isMultiSelectModeDefault() {
+ return selectMode == SelectMode.MULTI
+ && multiselectmode == MULTISELECT_MODE_DEFAULT;
+ }
+
+ private void setMultiSelectMode(int multiselectmode) {
+ if (BrowserInfo.get().isTouchDevice()) {
+ // Always use the simple mode for touch devices that do not have
+ // shift/ctrl keys
+ this.multiselectmode = MULTISELECT_MODE_SIMPLE;
+ } else {
+ this.multiselectmode = multiselectmode;
+ }
+
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean isSelectable() {
+ return selectMode.getId() > SelectMode.NONE.getId();
+ }
+
+ private boolean isCollapsedColumn(String colKey) {
+ if (collapsedColumns == null) {
+ return false;
+ }
+ if (collapsedColumns.contains(colKey)) {
+ return true;
+ }
+ return false;
+ }
+
+ private String getColKeyByIndex(int index) {
+ return tHead.getHeaderCell(index).getColKey();
+ }
+
+ private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
+ final HeaderCell hcell = tHead.getHeaderCell(colIndex);
+
+ // Make sure that the column grows to accommodate the sort indicator if
+ // necessary.
+ if (w < hcell.getMinWidth()) {
+ w = hcell.getMinWidth();
+ }
+
+ // Set header column width
+ hcell.setWidth(w, isDefinedWidth);
+
+ // Ensure indicators have been taken into account
+ tHead.resizeCaptionContainer(hcell);
+
+ // Set body column width
+ scrollBody.setColWidth(colIndex, w);
+
+ // Set footer column width
+ FooterCell fcell = tFoot.getFooterCell(colIndex);
+ fcell.setWidth(w, isDefinedWidth);
+ }
+
+ private int getColWidth(String colKey) {
+ return tHead.getHeaderCell(colKey).getWidth();
+ }
+
+ /**
+ * Get a rendered row by its key
+ *
+ * @param key
+ * The key to search with
+ * @return
+ */
+ public VScrollTableRow getRenderedRowByKey(String key) {
+ if (scrollBody != null) {
+ final Iterator<Widget> it = scrollBody.iterator();
+ VScrollTableRow r = null;
+ while (it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ if (r.getKey().equals(key)) {
+ return r;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the next row to the given row
+ *
+ * @param row
+ * The row to calculate from
+ *
+ * @return The next row or null if no row exists
+ */
+ private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
+ final Iterator<Widget> it = scrollBody.iterator();
+ VScrollTableRow r = null;
+ while (it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ if (r == row) {
+ r = null;
+ while (offset >= 0 && it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ offset--;
+ }
+ return r;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the previous row from the given row
+ *
+ * @param row
+ * The row to calculate from
+ * @return The previous row or null if no row exists
+ */
+ private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
+ final Iterator<Widget> it = scrollBody.iterator();
+ final Iterator<Widget> offsetIt = scrollBody.iterator();
+ VScrollTableRow r = null;
+ VScrollTableRow prev = null;
+ while (it.hasNext()) {
+ r = (VScrollTableRow) it.next();
+ if (offset < 0) {
+ prev = (VScrollTableRow) offsetIt.next();
+ }
+ if (r == row) {
+ return prev;
+ }
+ offset--;
+ }
+
+ return null;
+ }
+
+ protected void reOrderColumn(String columnKey, int newIndex) {
+
+ final int oldIndex = getColIndexByKey(columnKey);
+
+ // Change header order
+ tHead.moveCell(oldIndex, newIndex);
+
+ // Change body order
+ scrollBody.moveCol(oldIndex, newIndex);
+
+ // Change footer order
+ tFoot.moveCell(oldIndex, newIndex);
+
+ /*
+ * Build new columnOrder and update it to server Note that columnOrder
+ * also contains collapsed columns so we cannot directly build it from
+ * cells vector Loop the old columnOrder and append in order to new
+ * array unless on moved columnKey. On new index also put the moved key
+ * i == index on columnOrder, j == index on newOrder
+ */
+ final String oldKeyOnNewIndex = visibleColOrder[newIndex];
+ if (showRowHeaders) {
+ newIndex--; // columnOrder don't have rowHeader
+ }
+ // add back hidden rows,
+ for (int i = 0; i < columnOrder.length; i++) {
+ if (columnOrder[i].equals(oldKeyOnNewIndex)) {
+ break; // break loop at target
+ }
+ if (isCollapsedColumn(columnOrder[i])) {
+ newIndex++;
+ }
+ }
+ // finally we can build the new columnOrder for server
+ final String[] newOrder = new String[columnOrder.length];
+ for (int i = 0, j = 0; j < newOrder.length; i++) {
+ if (j == newIndex) {
+ newOrder[j] = columnKey;
+ j++;
+ }
+ if (i == columnOrder.length) {
+ break;
+ }
+ if (columnOrder[i].equals(columnKey)) {
+ continue;
+ }
+ newOrder[j] = columnOrder[i];
+ j++;
+ }
+ columnOrder = newOrder;
+ // also update visibleColumnOrder
+ int i = showRowHeaders ? 1 : 0;
+ for (int j = 0; j < newOrder.length; j++) {
+ final String cid = newOrder[j];
+ if (!isCollapsedColumn(cid)) {
+ visibleColOrder[i++] = cid;
+ }
+ }
+ client.updateVariable(paintableId, "columnorder", columnOrder, false);
+ if (client.hasEventListeners(this,
+ TableConstants.COLUMN_REORDER_EVENT_ID)) {
+ client.sendPendingVariableChanges();
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ rowRequestHandler.cancel();
+ super.onDetach();
+ // ensure that scrollPosElement will be detached
+ if (scrollPositionElement != null) {
+ final Element parent = DOM.getParent(scrollPositionElement);
+ if (parent != null) {
+ DOM.removeChild(parent, scrollPositionElement);
+ }
+ }
+ }
+
+ /**
+ * Run only once when component is attached and received its initial
+ * content. This function:
+ *
+ * * Syncs headers and bodys "natural widths and saves the values.
+ *
+ * * Sets proper width and height
+ *
+ * * Makes deferred request to get some cache rows
+ *
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void sizeInit() {
+ sizeNeedsInit = false;
+
+ scrollBody.setContainerHeight();
+
+ /*
+ * We will use browsers table rendering algorithm to find proper column
+ * widths. If content and header take less space than available, we will
+ * divide extra space relatively to each column which has not width set.
+ *
+ * Overflow pixels are added to last column.
+ */
+
+ Iterator<Widget> headCells = tHead.iterator();
+ Iterator<Widget> footCells = tFoot.iterator();
+ int i = 0;
+ int totalExplicitColumnsWidths = 0;
+ int total = 0;
+ float expandRatioDivider = 0;
+
+ final int[] widths = new int[tHead.visibleCells.size()];
+
+ tHead.enableBrowserIntelligence();
+ tFoot.enableBrowserIntelligence();
+
+ // first loop: collect natural widths
+ while (headCells.hasNext()) {
+ final HeaderCell hCell = (HeaderCell) headCells.next();
+ final FooterCell fCell = (FooterCell) footCells.next();
+ int w = hCell.getWidth();
+ if (hCell.isDefinedWidth()) {
+ // server has defined column width explicitly
+ totalExplicitColumnsWidths += w;
+ } else {
+ if (hCell.getExpandRatio() > 0) {
+ expandRatioDivider += hCell.getExpandRatio();
+ w = 0;
+ } else {
+ // get and store greater of header width and column width,
+ // and
+ // store it as a minimumn natural col width
+ int headerWidth = hCell.getNaturalColumnWidth(i);
+ int footerWidth = fCell.getNaturalColumnWidth(i);
+ w = headerWidth > footerWidth ? headerWidth : footerWidth;
+ }
+ hCell.setNaturalMinimumColumnWidth(w);
+ fCell.setNaturalMinimumColumnWidth(w);
+ }
+ widths[i] = w;
+ total += w;
+ i++;
+ }
+
+ tHead.disableBrowserIntelligence();
+ tFoot.disableBrowserIntelligence();
+
+ boolean willHaveScrollbarz = willHaveScrollbars();
+
+ // fix "natural" width if width not set
+ if (isDynamicWidth()) {
+ int w = total;
+ w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
+ if (willHaveScrollbarz) {
+ w += Util.getNativeScrollbarSize();
+ }
+ setContentWidth(w);
+ }
+
+ int availW = scrollBody.getAvailableWidth();
+ if (BrowserInfo.get().isIE()) {
+ // Hey IE, are you really sure about this?
+ availW = scrollBody.getAvailableWidth();
+ }
+ availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
+
+ if (willHaveScrollbarz) {
+ availW -= Util.getNativeScrollbarSize();
+ }
+
+ // TODO refactor this code to be the same as in resize timer
+ boolean needsReLayout = false;
+
+ if (availW > total) {
+ // natural size is smaller than available space
+ final int extraSpace = availW - total;
+ final int totalWidthR = total - totalExplicitColumnsWidths;
+ int checksum = 0;
+ needsReLayout = true;
+
+ if (extraSpace == 1) {
+ // We cannot divide one single pixel so we give it the first
+ // undefined column
+ headCells = tHead.iterator();
+ i = 0;
+ checksum = availW;
+ while (headCells.hasNext()) {
+ HeaderCell hc = (HeaderCell) headCells.next();
+ if (!hc.isDefinedWidth()) {
+ widths[i]++;
+ break;
+ }
+ i++;
+ }
+
+ } else if (expandRatioDivider > 0) {
+ // visible columns have some active expand ratios, excess
+ // space is divided according to them
+ headCells = tHead.iterator();
+ i = 0;
+ while (headCells.hasNext()) {
+ HeaderCell hCell = (HeaderCell) headCells.next();
+ if (hCell.getExpandRatio() > 0) {
+ int w = widths[i];
+ final int newSpace = Math.round((extraSpace * (hCell
+ .getExpandRatio() / expandRatioDivider)));
+ w += newSpace;
+ widths[i] = w;
+ }
+ checksum += widths[i];
+ i++;
+ }
+ } else if (totalWidthR > 0) {
+ // no expand ratios defined, we will share extra space
+ // relatively to "natural widths" among those without
+ // explicit width
+ headCells = tHead.iterator();
+ i = 0;
+ while (headCells.hasNext()) {
+ HeaderCell hCell = (HeaderCell) headCells.next();
+ if (!hCell.isDefinedWidth()) {
+ int w = widths[i];
+ final int newSpace = Math.round((float) extraSpace
+ * (float) w / totalWidthR);
+ w += newSpace;
+ widths[i] = w;
+ }
+ checksum += widths[i];
+ i++;
+ }
+ }
+
+ if (extraSpace > 0 && checksum != availW) {
+ /*
+ * There might be in some cases a rounding error of 1px when
+ * extra space is divided so if there is one then we give the
+ * first undefined column 1 more pixel
+ */
+ headCells = tHead.iterator();
+ i = 0;
+ while (headCells.hasNext()) {
+ HeaderCell hc = (HeaderCell) headCells.next();
+ if (!hc.isDefinedWidth()) {
+ widths[i] += availW - checksum;
+ break;
+ }
+ i++;
+ }
+ }
+
+ } else {
+ // bodys size will be more than available and scrollbar will appear
+ }
+
+ // last loop: set possibly modified values or reset if new tBody
+ i = 0;
+ headCells = tHead.iterator();
+ while (headCells.hasNext()) {
+ final HeaderCell hCell = (HeaderCell) headCells.next();
+ if (isNewBody || hCell.getWidth() == -1) {
+ final int w = widths[i];
+ setColWidth(i, w, false);
+ }
+ i++;
+ }
+
+ initializedAndAttached = true;
+
+ if (needsReLayout) {
+ scrollBody.reLayoutComponents();
+ }
+
+ updatePageLength();
+
+ /*
+ * Fix "natural" height if height is not set. This must be after width
+ * fixing so the components' widths have been adjusted.
+ */
+ if (isDynamicHeight()) {
+ /*
+ * We must force an update of the row height as this point as it
+ * might have been (incorrectly) calculated earlier
+ */
+
+ int bodyHeight;
+ if (pageLength == totalRows) {
+ /*
+ * A hack to support variable height rows when paging is off.
+ * Generally this is not supported by scrolltable. We want to
+ * show all rows so the bodyHeight should be equal to the table
+ * height.
+ */
+ // int bodyHeight = scrollBody.getOffsetHeight();
+ bodyHeight = scrollBody.getRequiredHeight();
+ } else {
+ bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
+ * pageLength);
+ }
+ boolean needsSpaceForHorizontalSrollbar = (total > availW);
+ if (needsSpaceForHorizontalSrollbar) {
+ bodyHeight += Util.getNativeScrollbarSize();
+ }
+ scrollBodyPanel.setHeight(bodyHeight + "px");
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+ }
+
+ isNewBody = false;
+
+ if (firstvisible > 0) {
+ // Deferred due to some Firefox oddities
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ scrollBodyPanel
+ .setScrollPosition(measureRowHeightOffset(firstvisible));
+ firstRowInViewPort = firstvisible;
+ }
+ });
+ }
+
+ if (enabled) {
+ // Do we need cache rows
+ if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
+ + pageLength + (int) cache_react_rate * pageLength) {
+ if (totalRows - 1 > scrollBody.getLastRendered()) {
+ // fetch cache rows
+ int firstInNewSet = scrollBody.getLastRendered() + 1;
+ rowRequestHandler.setReqFirstRow(firstInNewSet);
+ int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
+ * pageLength);
+ if (lastInNewSet > totalRows - 1) {
+ lastInNewSet = totalRows - 1;
+ }
+ rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet
+ + 1);
+ rowRequestHandler.deferRowFetch(1);
+ }
+ }
+ }
+
+ /*
+ * Ensures the column alignments are correct at initial loading. <br/>
+ * (child components widths are correct)
+ */
+ scrollBody.reLayoutComponents();
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+ }
+ });
+ }
+
+ /**
+ * Note, this method is not official api although declared as protected.
+ * Extend at you own risk.
+ *
+ * @return true if content area will have scrollbars visible.
+ */
+ protected boolean willHaveScrollbars() {
+ if (isDynamicHeight()) {
+ if (pageLength < totalRows) {
+ return true;
+ }
+ } else {
+ int fakeheight = (int) Math.round(scrollBody.getRowHeight()
+ * totalRows);
+ int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
+ "clientHeight");
+ if (fakeheight > availableHeight) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void announceScrollPosition() {
+ if (scrollPositionElement == null) {
+ scrollPositionElement = DOM.createDiv();
+ scrollPositionElement.setClassName(getStylePrimaryName()
+ + "-scrollposition");
+ scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
+ scrollPositionElement.getStyle().setDisplay(Display.NONE);
+ getElement().appendChild(scrollPositionElement);
+ }
+
+ Style style = scrollPositionElement.getStyle();
+ style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
+ style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
+
+ // indexes go from 1-totalRows, as rowheaders in index-mode indicate
+ int last = (firstRowInViewPort + pageLength);
+ if (last > totalRows) {
+ last = totalRows;
+ }
+ scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
+ + " – " + (last) + "..." + "</span>");
+ style.setDisplay(Display.BLOCK);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void hideScrollPositionAnnotation() {
+ if (scrollPositionElement != null) {
+ DOM.setStyleAttribute(scrollPositionElement, "display", "none");
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean isScrollPositionVisible() {
+ return scrollPositionElement != null
+ && !scrollPositionElement.getStyle().getDisplay()
+ .equals(Display.NONE.toString());
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public class RowRequestHandler extends Timer {
+
+ private int reqFirstRow = 0;
+ private int reqRows = 0;
+ private boolean isRunning = false;
+
+ public void deferRowFetch() {
+ deferRowFetch(250);
+ }
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ public void deferRowFetch(int msec) {
+ isRunning = true;
+ if (reqRows > 0 && reqFirstRow < totalRows) {
+ schedule(msec);
+
+ // tell scroll position to user if currently "visible" rows are
+ // not rendered
+ if (totalRows > pageLength
+ && ((firstRowInViewPort + pageLength > scrollBody
+ .getLastRendered()) || (firstRowInViewPort < scrollBody
+ .getFirstRendered()))) {
+ announceScrollPosition();
+ } else {
+ hideScrollPositionAnnotation();
+ }
+ }
+ }
+
+ public void setReqFirstRow(int reqFirstRow) {
+ if (reqFirstRow < 0) {
+ reqFirstRow = 0;
+ } else if (reqFirstRow >= totalRows) {
+ reqFirstRow = totalRows - 1;
+ }
+ this.reqFirstRow = reqFirstRow;
+ }
+
+ public void setReqRows(int reqRows) {
+ this.reqRows = reqRows;
+ }
+
+ @Override
+ public void run() {
+ if (client.hasActiveRequest() || navKeyDown) {
+ // if client connection is busy, don't bother loading it more
+ VConsole.log("Postponed rowfetch");
+ schedule(250);
+ } else {
+
+ int firstToBeRendered = scrollBody.firstRendered;
+ if (reqFirstRow < firstToBeRendered) {
+ firstToBeRendered = reqFirstRow;
+ } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
+ firstToBeRendered = firstRowInViewPort
+ - (int) (cache_rate * pageLength);
+ if (firstToBeRendered < 0) {
+ firstToBeRendered = 0;
+ }
+ }
+
+ int lastToBeRendered = scrollBody.lastRendered;
+
+ if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
+ lastToBeRendered = reqFirstRow + reqRows - 1;
+ } else if (firstRowInViewPort + pageLength + pageLength
+ * cache_rate < lastToBeRendered) {
+ lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
+ if (lastToBeRendered >= totalRows) {
+ lastToBeRendered = totalRows - 1;
+ }
+ // due Safari 3.1 bug (see #2607), verify reqrows, original
+ // problem unknown, but this should catch the issue
+ if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
+ reqRows = lastToBeRendered - reqFirstRow;
+ }
+ }
+
+ client.updateVariable(paintableId, "firstToBeRendered",
+ firstToBeRendered, false);
+
+ client.updateVariable(paintableId, "lastToBeRendered",
+ lastToBeRendered, false);
+ // remember which firstvisible we requested, in case the server
+ // has
+ // a differing opinion
+ lastRequestedFirstvisible = firstRowInViewPort;
+ client.updateVariable(paintableId, "firstvisible",
+ firstRowInViewPort, false);
+ client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
+ false);
+ client.updateVariable(paintableId, "reqrows", reqRows, true);
+
+ if (selectionChanged) {
+ unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
+ selectedRowKeys);
+ }
+ isRunning = false;
+ }
+ }
+
+ public int getReqFirstRow() {
+ return reqFirstRow;
+ }
+
+ /**
+ * Sends request to refresh content at this position.
+ */
+ public void refreshContent() {
+ isRunning = true;
+ int first = (int) (firstRowInViewPort - pageLength * cache_rate);
+ int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
+ if (first < 0) {
+ reqRows = reqRows + first;
+ first = 0;
+ }
+ setReqFirstRow(first);
+ setReqRows(reqRows);
+ run();
+ }
+ }
+
+ public class HeaderCell extends Widget {
+
+ Element td = DOM.createTD();
+
+ Element captionContainer = DOM.createDiv();
+
+ Element sortIndicator = DOM.createDiv();
+
+ Element colResizeWidget = DOM.createDiv();
+
+ Element floatingCopyOfHeaderCell;
+
+ private boolean sortable = false;
+ private final String cid;
+ private boolean dragging;
+
+ private int dragStartX;
+ private int colIndex;
+ private int originalWidth;
+
+ private boolean isResizing;
+
+ private int headerX;
+
+ private boolean moved;
+
+ private int closestSlot;
+
+ private int width = -1;
+
+ private int naturalWidth = -1;
+
+ private char align = ALIGN_LEFT;
+
+ boolean definedWidth = false;
+
+ private float expandRatio = 0;
+
+ private boolean sorted;
+
+ public void setSortable(boolean b) {
+ sortable = b;
+ }
+
+ /**
+ * Makes room for the sorting indicator in case the column that the
+ * header cell belongs to is sorted. This is done by resizing the width
+ * of the caption container element by the correct amount
+ */
+ public void resizeCaptionContainer(int rightSpacing) {
+ int captionContainerWidth = width
+ - colResizeWidget.getOffsetWidth() - rightSpacing;
+
+ if (td.getClassName().contains("-asc")
+ || td.getClassName().contains("-desc")) {
+ // Leave room for the sort indicator
+ captionContainerWidth -= sortIndicator.getOffsetWidth();
+ }
+
+ if (captionContainerWidth < 0) {
+ rightSpacing += captionContainerWidth;
+ captionContainerWidth = 0;
+ }
+
+ captionContainer.getStyle().setPropertyPx("width",
+ captionContainerWidth);
+
+ // Apply/Remove spacing if defined
+ if (rightSpacing > 0) {
+ colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
+ } else {
+ colResizeWidget.getStyle().clearMarginLeft();
+ }
+ }
+
+ public void setNaturalMinimumColumnWidth(int w) {
+ naturalWidth = w;
+ }
+
+ public HeaderCell(String colId, String headerText) {
+ cid = colId;
+
+ setText(headerText);
+
+ td.appendChild(colResizeWidget);
+
+ // ensure no clipping initially (problem on column additions)
+ captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
+
+ td.appendChild(sortIndicator);
+ td.appendChild(captionContainer);
+
+ DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
+ | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
+
+ setElement(td);
+
+ setAlign(ALIGN_LEFT);
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ colResizeWidget.setClassName(primaryStyleName + "-resizer");
+ sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
+ captionContainer.setClassName(primaryStyleName
+ + "-caption-container");
+ if (sorted) {
+ if (sortAscending) {
+ setStyleName(primaryStyleName + "-header-cell-asc");
+ } else {
+ setStyleName(primaryStyleName + "-header-cell-desc");
+ }
+ } else {
+ setStyleName(primaryStyleName + "-header-cell");
+ }
+
+ final String ALIGN_PREFIX = primaryStyleName
+ + "-caption-container-align-";
+
+ switch (align) {
+ case ALIGN_CENTER:
+ captionContainer.addClassName(ALIGN_PREFIX + "center");
+ break;
+ case ALIGN_RIGHT:
+ captionContainer.addClassName(ALIGN_PREFIX + "right");
+ break;
+ default:
+ captionContainer.addClassName(ALIGN_PREFIX + "left");
+ break;
+ }
+
+ }
+
+ public void disableAutoWidthCalculation() {
+ definedWidth = true;
+ expandRatio = 0;
+ }
+
+ public void setWidth(int w, boolean ensureDefinedWidth) {
+ if (ensureDefinedWidth) {
+ definedWidth = true;
+ // on column resize expand ratio becomes zero
+ expandRatio = 0;
+ }
+ if (width == -1) {
+ // go to default mode, clip content if necessary
+ DOM.setStyleAttribute(captionContainer, "overflow", "");
+ }
+ width = w;
+ if (w == -1) {
+ DOM.setStyleAttribute(captionContainer, "width", "");
+ setWidth("");
+ } else {
+ tHead.resizeCaptionContainer(this);
+
+ /*
+ * if we already have tBody, set the header width properly, if
+ * not defer it. IE will fail with complex float in table header
+ * unless TD width is not explicitly set.
+ */
+ if (scrollBody != null) {
+ int tdWidth = width + scrollBody.getCellExtraWidth();
+ setWidth(tdWidth + "px");
+ } else {
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ int tdWidth = width
+ + scrollBody.getCellExtraWidth();
+ setWidth(tdWidth + "px");
+ }
+ });
+ }
+ }
+ }
+
+ public void setUndefinedWidth() {
+ definedWidth = false;
+ setWidth(-1, false);
+ }
+
+ /**
+ * Detects if width is fixed by developer on server side or resized to
+ * current width by user.
+ *
+ * @return true if defined, false if "natural" width
+ */
+ public boolean isDefinedWidth() {
+ return definedWidth && width >= 0;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setText(String headerText) {
+ DOM.setInnerHTML(captionContainer, headerText);
+ }
+
+ public String getColKey() {
+ return cid;
+ }
+
+ private void setSorted(boolean sorted) {
+ this.sorted = sorted;
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ /**
+ * Handle column reordering.
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (enabled && event != null) {
+ if (isResizing
+ || event.getEventTarget().cast() == colResizeWidget) {
+ if (dragging
+ && (event.getTypeInt() == Event.ONMOUSEUP || event
+ .getTypeInt() == Event.ONTOUCHEND)) {
+ // Handle releasing column header on spacer #5318
+ handleCaptionEvent(event);
+ } else {
+ onResizeEvent(event);
+ }
+ } else {
+ /*
+ * Ensure focus before handling caption event. Otherwise
+ * variables changed from caption event may be before
+ * variables from other components that fire variables when
+ * they lose focus.
+ */
+ if (event.getTypeInt() == Event.ONMOUSEDOWN
+ || event.getTypeInt() == Event.ONTOUCHSTART) {
+ scrollBodyPanel.setFocus(true);
+ }
+ handleCaptionEvent(event);
+ boolean stopPropagation = true;
+ if (event.getTypeInt() == Event.ONCONTEXTMENU
+ && !client.hasEventListeners(VScrollTable.this,
+ TableConstants.HEADER_CLICK_EVENT_ID)) {
+ // Prevent showing the browser's context menu only when
+ // there is a header click listener.
+ stopPropagation = false;
+ }
+ if (stopPropagation) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+ }
+ }
+
+ private void createFloatingCopy() {
+ floatingCopyOfHeaderCell = DOM.createDiv();
+ DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
+ floatingCopyOfHeaderCell = DOM
+ .getChild(floatingCopyOfHeaderCell, 2);
+ DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
+ VScrollTable.this.getStylePrimaryName() + "-header-drag");
+ // otherwise might wrap or be cut if narrow column
+ DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto");
+ updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
+ DOM.getAbsoluteTop(td));
+ DOM.appendChild(RootPanel.get().getElement(),
+ floatingCopyOfHeaderCell);
+ }
+
+ private void updateFloatingCopysPosition(int x, int y) {
+ x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
+ "offsetWidth") / 2;
+ DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
+ if (y > 0) {
+ DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
+ + "px");
+ }
+ }
+
+ private void hideFloatingCopy() {
+ DOM.removeChild(RootPanel.get().getElement(),
+ floatingCopyOfHeaderCell);
+ floatingCopyOfHeaderCell = null;
+ }
+
+ /**
+ * Fires a header click event after the user has clicked a column header
+ * cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireHeaderClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ TableConstants.HEADER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(event);
+ client.updateVariable(paintableId, "headerClickEvent",
+ details.toString(), false);
+ client.updateVariable(paintableId, "headerClickCID", cid, true);
+ }
+ }
+
+ protected void handleCaptionEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONTOUCHSTART:
+ case Event.ONMOUSEDOWN:
+ if (columnReordering
+ && Util.isTouchEventOrLeftMouseButton(event)) {
+ if (event.getTypeInt() == Event.ONTOUCHSTART) {
+ /*
+ * prevent using this event in e.g. scrolling
+ */
+ event.stopPropagation();
+ }
+ dragging = true;
+ moved = false;
+ colIndex = getColIndexByKey(cid);
+ DOM.setCapture(getElement());
+ headerX = tHead.getAbsoluteLeft();
+ event.preventDefault(); // prevent selecting text &&
+ // generated touch events
+ }
+ break;
+ case Event.ONMOUSEUP:
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ if (columnReordering
+ && Util.isTouchEventOrLeftMouseButton(event)) {
+ dragging = false;
+ DOM.releaseCapture(getElement());
+ if (moved) {
+ hideFloatingCopy();
+ tHead.removeSlotFocus();
+ if (closestSlot != colIndex
+ && closestSlot != (colIndex + 1)) {
+ if (closestSlot > colIndex) {
+ reOrderColumn(cid, closestSlot - 1);
+ } else {
+ reOrderColumn(cid, closestSlot);
+ }
+ }
+ }
+ if (Util.isTouchEvent(event)) {
+ /*
+ * Prevent using in e.g. scrolling and prevent generated
+ * events.
+ */
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if (!moved) {
+ // mouse event was a click to header -> sort column
+ if (sortable && Util.isTouchEventOrLeftMouseButton(event)) {
+ if (sortColumn.equals(cid)) {
+ // just toggle order
+ client.updateVariable(paintableId, "sortascending",
+ !sortAscending, false);
+ } else {
+ // set table sorted by this column
+ client.updateVariable(paintableId, "sortcolumn",
+ cid, false);
+ }
+ // get also cache columns at the same request
+ scrollBodyPanel.setScrollPosition(0);
+ firstvisible = 0;
+ rowRequestHandler.setReqFirstRow(0);
+ rowRequestHandler.setReqRows((int) (2 * pageLength
+ * cache_rate + pageLength));
+ rowRequestHandler.deferRowFetch(); // some validation +
+ // defer 250ms
+ rowRequestHandler.cancel(); // instead of waiting
+ rowRequestHandler.run(); // run immediately
+ }
+ fireHeaderClickedEvent(event);
+ if (Util.isTouchEvent(event)) {
+ /*
+ * Prevent using in e.g. scrolling and prevent generated
+ * events.
+ */
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+ break;
+ case Event.ONDBLCLICK:
+ fireHeaderClickedEvent(event);
+ break;
+ case Event.ONTOUCHMOVE:
+ case Event.ONMOUSEMOVE:
+ if (dragging && Util.isTouchEventOrLeftMouseButton(event)) {
+ if (event.getTypeInt() == Event.ONTOUCHMOVE) {
+ /*
+ * prevent using this event in e.g. scrolling
+ */
+ event.stopPropagation();
+ }
+ if (!moved) {
+ createFloatingCopy();
+ moved = true;
+ }
+
+ final int clientX = Util.getTouchOrMouseClientX(event);
+ final int x = clientX + tHead.hTableWrapper.getScrollLeft();
+ int slotX = headerX;
+ closestSlot = colIndex;
+ int closestDistance = -1;
+ int start = 0;
+ if (showRowHeaders) {
+ start++;
+ }
+ final int visibleCellCount = tHead.getVisibleCellCount();
+ for (int i = start; i <= visibleCellCount; i++) {
+ if (i > 0) {
+ final String colKey = getColKeyByIndex(i - 1);
+ slotX += getColWidth(colKey);
+ }
+ final int dist = Math.abs(x - slotX);
+ if (closestDistance == -1 || dist < closestDistance) {
+ closestDistance = dist;
+ closestSlot = i;
+ }
+ }
+ tHead.focusSlot(closestSlot);
+
+ updateFloatingCopysPosition(clientX, -1);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void onResizeEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ if (!Util.isTouchEventOrLeftMouseButton(event)) {
+ return;
+ }
+ isResizing = true;
+ DOM.setCapture(getElement());
+ dragStartX = DOM.eventGetClientX(event);
+ colIndex = getColIndexByKey(cid);
+ originalWidth = getWidth();
+ DOM.eventPreventDefault(event);
+ break;
+ case Event.ONMOUSEUP:
+ if (!Util.isTouchEventOrLeftMouseButton(event)) {
+ return;
+ }
+ isResizing = false;
+ DOM.releaseCapture(getElement());
+ tHead.disableAutoColumnWidthCalculation(this);
+
+ // Ensure last header cell is taking into account possible
+ // column selector
+ HeaderCell lastCell = tHead.getHeaderCell(tHead
+ .getVisibleCellCount() - 1);
+ tHead.resizeCaptionContainer(lastCell);
+ triggerLazyColumnAdjustment(true);
+
+ fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
+ break;
+ case Event.ONMOUSEMOVE:
+ if (!Util.isTouchEventOrLeftMouseButton(event)) {
+ return;
+ }
+ if (isResizing) {
+ final int deltaX = DOM.eventGetClientX(event) - dragStartX;
+ if (deltaX == 0) {
+ return;
+ }
+ tHead.disableAutoColumnWidthCalculation(this);
+
+ int newWidth = originalWidth + deltaX;
+ if (newWidth < getMinWidth()) {
+ newWidth = getMinWidth();
+ }
+ setColWidth(colIndex, newWidth, true);
+ triggerLazyColumnAdjustment(false);
+ forceRealignColumnHeaders();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public int getMinWidth() {
+ int cellExtraWidth = 0;
+ if (scrollBody != null) {
+ cellExtraWidth += scrollBody.getCellExtraWidth();
+ }
+ return cellExtraWidth + sortIndicator.getOffsetWidth();
+ }
+
+ public String getCaption() {
+ return DOM.getInnerText(captionContainer);
+ }
+
+ public boolean isEnabled() {
+ return getParent() != null;
+ }
+
+ public void setAlign(char c) {
+ align = c;
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ public char getAlign() {
+ return align;
+ }
+
+ /**
+ * Detects the natural minimum width for the column of this header cell.
+ * If column is resized by user or the width is defined by server the
+ * actual width is returned. Else the natural min width is returned.
+ *
+ * @param columnIndex
+ * column index hint, if -1 (unknown) it will be detected
+ *
+ * @return
+ */
+ public int getNaturalColumnWidth(int columnIndex) {
+ if (isDefinedWidth()) {
+ return width;
+ } else {
+ if (naturalWidth < 0) {
+ // This is recently revealed column. Try to detect a proper
+ // value (greater of header and data
+ // cols)
+
+ int hw = captionContainer.getOffsetWidth()
+ + scrollBody.getCellExtraWidth();
+ if (BrowserInfo.get().isGecko()) {
+ hw += sortIndicator.getOffsetWidth();
+ }
+ if (columnIndex < 0) {
+ columnIndex = 0;
+ for (Iterator<Widget> it = tHead.iterator(); it
+ .hasNext(); columnIndex++) {
+ if (it.next() == this) {
+ break;
+ }
+ }
+ }
+ final int cw = scrollBody.getColWidth(columnIndex);
+ naturalWidth = (hw > cw ? hw : cw);
+ }
+ return naturalWidth;
+ }
+ }
+
+ public void setExpandRatio(float floatAttribute) {
+ if (floatAttribute != expandRatio) {
+ triggerLazyColumnAdjustment(false);
+ }
+ expandRatio = floatAttribute;
+ }
+
+ public float getExpandRatio() {
+ return expandRatio;
+ }
+
+ public boolean isSorted() {
+ return sorted;
+ }
+ }
+
+ /**
+ * HeaderCell that is header cell for row headers.
+ *
+ * Reordering disabled and clicking on it resets sorting.
+ */
+ public class RowHeadersHeaderCell extends HeaderCell {
+
+ RowHeadersHeaderCell() {
+ super(ROW_HEADER_COLUMN_KEY, "");
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ @Override
+ protected void updateStyleNames(String primaryStyleName) {
+ super.updateStyleNames(primaryStyleName);
+ setStyleName(primaryStyleName + "-header-cell-rowheader");
+ }
+
+ @Override
+ protected void handleCaptionEvent(Event event) {
+ // NOP: RowHeaders cannot be reordered
+ // TODO It'd be nice to reset sorting here
+ }
+ }
+
+ public class TableHead extends Panel implements ActionOwner {
+
+ private static final int WRAPPER_WIDTH = 900000;
+
+ ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+
+ HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
+
+ Element div = DOM.createDiv();
+ Element hTableWrapper = DOM.createDiv();
+ Element hTableContainer = DOM.createDiv();
+ Element table = DOM.createTable();
+ Element headerTableBody = DOM.createTBody();
+ Element tr = DOM.createTR();
+
+ private final Element columnSelector = DOM.createDiv();
+
+ private int focusedSlot = -1;
+
+ public TableHead() {
+ if (BrowserInfo.get().isIE()) {
+ table.setPropertyInt("cellSpacing", 0);
+ }
+
+ DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+ DOM.setStyleAttribute(columnSelector, "display", "none");
+
+ DOM.appendChild(table, headerTableBody);
+ DOM.appendChild(headerTableBody, tr);
+ DOM.appendChild(hTableContainer, table);
+ DOM.appendChild(hTableWrapper, hTableContainer);
+ DOM.appendChild(div, hTableWrapper);
+ DOM.appendChild(div, columnSelector);
+ setElement(div);
+
+ DOM.sinkEvents(columnSelector, Event.ONCLICK);
+
+ availableCells.put(ROW_HEADER_COLUMN_KEY,
+ new RowHeadersHeaderCell());
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ hTableWrapper.setClassName(primaryStyleName + "-header");
+ columnSelector.setClassName(primaryStyleName + "-column-selector");
+ setStyleName(primaryStyleName + "-header-wrap");
+ for (HeaderCell c : availableCells.values()) {
+ c.updateStyleNames(primaryStyleName);
+ }
+ }
+
+ public void resizeCaptionContainer(HeaderCell cell) {
+ HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
+
+ // Measure column widths
+ int columnTotalWidth = 0;
+ for (Widget w : visibleCells) {
+ columnTotalWidth += w.getOffsetWidth();
+ }
+
+ if (cell == lastcell
+ && columnSelector.getOffsetWidth() > 0
+ && columnTotalWidth >= div.getOffsetWidth()
+ - columnSelector.getOffsetWidth()
+ && !hasVerticalScrollbar()) {
+ // Ensure column caption is visible when placed under the column
+ // selector widget by shifting and resizing the caption.
+ int offset = 0;
+ int diff = div.getOffsetWidth() - columnTotalWidth;
+ if (diff < columnSelector.getOffsetWidth() && diff > 0) {
+ // If the difference is less than the column selectors width
+ // then just offset by the
+ // difference
+ offset = columnSelector.getOffsetWidth() - diff;
+ } else {
+ // Else offset by the whole column selector
+ offset = columnSelector.getOffsetWidth();
+ }
+ lastcell.resizeCaptionContainer(offset);
+ } else {
+ cell.resizeCaptionContainer(0);
+ }
+ }
+
+ @Override
+ public void clear() {
+ for (String cid : availableCells.keySet()) {
+ removeCell(cid);
+ }
+ availableCells.clear();
+ availableCells.put(ROW_HEADER_COLUMN_KEY,
+ new RowHeadersHeaderCell());
+ }
+
+ public void updateCellsFromUIDL(UIDL uidl) {
+ Iterator<?> it = uidl.getChildIterator();
+ HashSet<String> updated = new HashSet<String>();
+ boolean refreshContentWidths = false;
+ while (it.hasNext()) {
+ final UIDL col = (UIDL) it.next();
+ final String cid = col.getStringAttribute("cid");
+ updated.add(cid);
+
+ String caption = buildCaptionHtmlSnippet(col);
+ HeaderCell c = getHeaderCell(cid);
+ if (c == null) {
+ c = new HeaderCell(cid, caption);
+ availableCells.put(cid, c);
+ if (initializedAndAttached) {
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ } else {
+ c.setText(caption);
+ }
+
+ if (col.hasAttribute("sortable")) {
+ c.setSortable(true);
+ if (cid.equals(sortColumn)) {
+ c.setSorted(true);
+ } else {
+ c.setSorted(false);
+ }
+ } else {
+ c.setSortable(false);
+ }
+
+ if (col.hasAttribute("align")) {
+ c.setAlign(col.getStringAttribute("align").charAt(0));
+ } else {
+ c.setAlign(ALIGN_LEFT);
+
+ }
+ if (col.hasAttribute("width")) {
+ final String widthStr = col.getStringAttribute("width");
+ // Make sure to accomodate for the sort indicator if
+ // necessary.
+ int width = Integer.parseInt(widthStr);
+ if (width < c.getMinWidth()) {
+ width = c.getMinWidth();
+ }
+ if (width != c.getWidth() && scrollBody != null) {
+ // Do a more thorough update if a column is resized from
+ // the server *after* the header has been properly
+ // initialized
+ final int colIx = getColIndexByKey(c.cid);
+ final int newWidth = width;
+ Scheduler.get().scheduleDeferred(
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ setColWidth(colIx, newWidth, true);
+ }
+ });
+ refreshContentWidths = true;
+ } else {
+ c.setWidth(width, true);
+ }
+ } else if (recalcWidths) {
+ c.setUndefinedWidth();
+ }
+ if (col.hasAttribute("er")) {
+ c.setExpandRatio(col.getFloatAttribute("er"));
+ }
+ if (col.hasAttribute("collapsed")) {
+ // ensure header is properly removed from parent (case when
+ // collapsing happens via servers side api)
+ if (c.isAttached()) {
+ c.removeFromParent();
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ if (refreshContentWidths) {
+ // Recalculate the column sizings if any column has changed
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ triggerLazyColumnAdjustment(true);
+ }
+ });
+ }
+
+ // check for orphaned header cells
+ for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+ .hasNext();) {
+ String cid = cit.next();
+ if (!updated.contains(cid)) {
+ removeCell(cid);
+ cit.remove();
+ // we will need a column width recalculation, since columns
+ // with expand ratios should expand to fill the void.
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ }
+ }
+
+ public void enableColumn(String cid, int index) {
+ final HeaderCell c = getHeaderCell(cid);
+ if (!c.isEnabled() || getHeaderCell(index) != c) {
+ setHeaderCell(index, c);
+ if (initializedAndAttached) {
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ public int getVisibleCellCount() {
+ return visibleCells.size();
+ }
+
+ public void setHorizontalScrollPosition(int scrollLeft) {
+ hTableWrapper.setScrollLeft(scrollLeft);
+ }
+
+ public void setColumnCollapsingAllowed(boolean cc) {
+ if (cc) {
+ columnSelector.getStyle().setDisplay(Display.BLOCK);
+ } else {
+ columnSelector.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ public void disableBrowserIntelligence() {
+ hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
+ }
+
+ public void enableBrowserIntelligence() {
+ hTableContainer.getStyle().clearWidth();
+ }
+
+ public void setHeaderCell(int index, HeaderCell cell) {
+ if (cell.isEnabled()) {
+ // we're moving the cell
+ DOM.removeChild(tr, cell.getElement());
+ orphan(cell);
+ visibleCells.remove(cell);
+ }
+ if (index < visibleCells.size()) {
+ // insert to right slot
+ DOM.insertChild(tr, cell.getElement(), index);
+ adopt(cell);
+ visibleCells.add(index, cell);
+ } else if (index == visibleCells.size()) {
+ // simply append
+ DOM.appendChild(tr, cell.getElement());
+ adopt(cell);
+ visibleCells.add(cell);
+ } else {
+ throw new RuntimeException(
+ "Header cells must be appended in order");
+ }
+ }
+
+ public HeaderCell getHeaderCell(int index) {
+ if (index >= 0 && index < visibleCells.size()) {
+ return (HeaderCell) visibleCells.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get's HeaderCell by it's column Key.
+ *
+ * Note that this returns HeaderCell even if it is currently collapsed.
+ *
+ * @param cid
+ * Column key of accessed HeaderCell
+ * @return HeaderCell
+ */
+ public HeaderCell getHeaderCell(String cid) {
+ return availableCells.get(cid);
+ }
+
+ public void moveCell(int oldIndex, int newIndex) {
+ final HeaderCell hCell = getHeaderCell(oldIndex);
+ final Element cell = hCell.getElement();
+
+ visibleCells.remove(oldIndex);
+ DOM.removeChild(tr, cell);
+
+ DOM.insertChild(tr, cell, newIndex);
+ visibleCells.add(newIndex, hCell);
+ }
+
+ @Override
+ public Iterator<Widget> iterator() {
+ return visibleCells.iterator();
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ if (visibleCells.contains(w)) {
+ visibleCells.remove(w);
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+ return true;
+ }
+ return false;
+ }
+
+ public void removeCell(String colKey) {
+ final HeaderCell c = getHeaderCell(colKey);
+ remove(c);
+ }
+
+ private void focusSlot(int index) {
+ removeSlotFocus();
+ if (index > 0) {
+ Element child = tr.getChild(index - 1).getFirstChild().cast();
+ child.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-resizer");
+ child.addClassName(VScrollTable.this.getStylePrimaryName()
+ + "-focus-slot-right");
+ } else {
+ Element child = tr.getChild(index).getFirstChild().cast();
+ child.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-resizer");
+ child.addClassName(VScrollTable.this.getStylePrimaryName()
+ + "-focus-slot-left");
+ }
+ focusedSlot = index;
+ }
+
+ private void removeSlotFocus() {
+ if (focusedSlot < 0) {
+ return;
+ }
+ if (focusedSlot == 0) {
+ Element child = tr.getChild(focusedSlot).getFirstChild().cast();
+ child.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-resizer");
+ } else if (focusedSlot > 0) {
+ Element child = tr.getChild(focusedSlot - 1).getFirstChild()
+ .cast();
+ child.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-resizer");
+ }
+ focusedSlot = -1;
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (enabled) {
+ if (event.getEventTarget().cast() == columnSelector) {
+ final int left = DOM.getAbsoluteLeft(columnSelector);
+ final int top = DOM.getAbsoluteTop(columnSelector)
+ + DOM.getElementPropertyInt(columnSelector,
+ "offsetHeight");
+ client.getContextMenu().showAt(this, left, top);
+ }
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ if (client != null) {
+ client.getContextMenu().ensureHidden(this);
+ }
+ }
+
+ class VisibleColumnAction extends Action {
+
+ String colKey;
+ private boolean collapsed;
+ private boolean noncollapsible = false;
+ private VScrollTableRow currentlyFocusedRow;
+
+ public VisibleColumnAction(String colKey) {
+ super(VScrollTable.TableHead.this);
+ this.colKey = colKey;
+ caption = tHead.getHeaderCell(colKey).getCaption();
+ currentlyFocusedRow = focusedRow;
+ }
+
+ @Override
+ public void execute() {
+ if (noncollapsible) {
+ return;
+ }
+ client.getContextMenu().hide();
+ // toggle selected column
+ if (collapsedColumns.contains(colKey)) {
+ collapsedColumns.remove(colKey);
+ } else {
+ tHead.removeCell(colKey);
+ collapsedColumns.add(colKey);
+ triggerLazyColumnAdjustment(true);
+ }
+
+ // update variable to server
+ client.updateVariable(paintableId, "collapsedcolumns",
+ collapsedColumns.toArray(new String[collapsedColumns
+ .size()]), false);
+ // let rowRequestHandler determine proper rows
+ rowRequestHandler.refreshContent();
+ lazyRevertFocusToRow(currentlyFocusedRow);
+ }
+
+ public void setCollapsed(boolean b) {
+ collapsed = b;
+ }
+
+ public void setNoncollapsible(boolean b) {
+ noncollapsible = b;
+ }
+
+ /**
+ * Override default method to distinguish on/off columns
+ */
+
+ @Override
+ public String getHTML() {
+ final StringBuffer buf = new StringBuffer();
+ buf.append("<span class=\"");
+ if (collapsed) {
+ buf.append("v-off");
+ } else {
+ buf.append("v-on");
+ }
+ if (noncollapsible) {
+ buf.append(" v-disabled");
+ }
+ buf.append("\">");
+
+ buf.append(super.getHTML());
+ buf.append("</span>");
+
+ return buf.toString();
+ }
+
+ }
+
+ /*
+ * Returns columns as Action array for column select popup
+ */
+
+ @Override
+ public Action[] getActions() {
+ Object[] cols;
+ if (columnReordering && columnOrder != null) {
+ cols = columnOrder;
+ } else {
+ // if columnReordering is disabled, we need different way to get
+ // all available columns
+ cols = visibleColOrder;
+ cols = new Object[visibleColOrder.length
+ + collapsedColumns.size()];
+ int i;
+ for (i = 0; i < visibleColOrder.length; i++) {
+ cols[i] = visibleColOrder[i];
+ }
+ for (final Iterator<String> it = collapsedColumns.iterator(); it
+ .hasNext();) {
+ cols[i++] = it.next();
+ }
+ }
+ final Action[] actions = new Action[cols.length];
+
+ for (int i = 0; i < cols.length; i++) {
+ final String cid = (String) cols[i];
+ final HeaderCell c = getHeaderCell(cid);
+ final VisibleColumnAction a = new VisibleColumnAction(
+ c.getColKey());
+ a.setCaption(c.getCaption());
+ if (!c.isEnabled()) {
+ a.setCollapsed(true);
+ }
+ if (noncollapsibleColumns.contains(cid)) {
+ a.setNoncollapsible(true);
+ }
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ @Override
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ /**
+ * Returns column alignments for visible columns
+ */
+ public char[] getColumnAlignments() {
+ final Iterator<Widget> it = visibleCells.iterator();
+ final char[] aligns = new char[visibleCells.size()];
+ int colIndex = 0;
+ while (it.hasNext()) {
+ aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
+ }
+ return aligns;
+ }
+
+ /**
+ * Disables the automatic calculation of all column widths by forcing
+ * the widths to be "defined" thus turning off expand ratios and such.
+ */
+ public void disableAutoColumnWidthCalculation(HeaderCell source) {
+ for (HeaderCell cell : availableCells.values()) {
+ cell.disableAutoWidthCalculation();
+ }
+ // fire column resize events for all columns but the source of the
+ // resize action, since an event will fire separately for this.
+ ArrayList<HeaderCell> columns = new ArrayList<HeaderCell>(
+ availableCells.values());
+ columns.remove(source);
+ sendColumnWidthUpdates(columns);
+ forceRealignColumnHeaders();
+ }
+ }
+
+ /**
+ * A cell in the footer
+ */
+ public class FooterCell extends Widget {
+ private final Element td = DOM.createTD();
+ private final Element captionContainer = DOM.createDiv();
+ private char align = ALIGN_LEFT;
+ private int width = -1;
+ private float expandRatio = 0;
+ private final String cid;
+ boolean definedWidth = false;
+ private int naturalWidth = -1;
+
+ public FooterCell(String colId, String headerText) {
+ cid = colId;
+
+ setText(headerText);
+
+ // ensure no clipping initially (problem on column additions)
+ DOM.setStyleAttribute(captionContainer, "overflow", "visible");
+
+ DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
+
+ DOM.appendChild(td, captionContainer);
+
+ DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
+ | Event.ONCONTEXTMENU);
+
+ setElement(td);
+
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ captionContainer.setClassName(primaryStyleName
+ + "-footer-container");
+ }
+
+ /**
+ * Sets the text of the footer
+ *
+ * @param footerText
+ * The text in the footer
+ */
+ public void setText(String footerText) {
+ if (footerText == null || footerText.equals("")) {
+ footerText = " ";
+ }
+
+ DOM.setInnerHTML(captionContainer, footerText);
+ }
+
+ /**
+ * Set alignment of the text in the cell
+ *
+ * @param c
+ * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
+ * ALIGN_RIGHT
+ */
+ public void setAlign(char c) {
+ if (align != c) {
+ switch (c) {
+ case ALIGN_CENTER:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "center");
+ break;
+ case ALIGN_RIGHT:
+ DOM.setStyleAttribute(captionContainer, "textAlign",
+ "right");
+ break;
+ default:
+ DOM.setStyleAttribute(captionContainer, "textAlign", "");
+ break;
+ }
+ }
+ align = c;
+ }
+
+ /**
+ * Get the alignment of the text int the cell
+ *
+ * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
+ */
+ public char getAlign() {
+ return align;
+ }
+
+ /**
+ * Sets the width of the cell
+ *
+ * @param w
+ * The width of the cell
+ * @param ensureDefinedWidth
+ * Ensures the the given width is not recalculated
+ */
+ public void setWidth(int w, boolean ensureDefinedWidth) {
+
+ if (ensureDefinedWidth) {
+ definedWidth = true;
+ // on column resize expand ratio becomes zero
+ expandRatio = 0;
+ }
+ if (width == w) {
+ return;
+ }
+ if (width == -1) {
+ // go to default mode, clip content if necessary
+ DOM.setStyleAttribute(captionContainer, "overflow", "");
+ }
+ width = w;
+ if (w == -1) {
+ DOM.setStyleAttribute(captionContainer, "width", "");
+ setWidth("");
+ } else {
+ /*
+ * Reduce width with one pixel for the right border since the
+ * footers does not have any spacers between them.
+ */
+ final int borderWidths = 1;
+
+ // Set the container width (check for negative value)
+ captionContainer.getStyle().setPropertyPx("width",
+ Math.max(w - borderWidths, 0));
+
+ /*
+ * if we already have tBody, set the header width properly, if
+ * not defer it. IE will fail with complex float in table header
+ * unless TD width is not explicitly set.
+ */
+ if (scrollBody != null) {
+ int tdWidth = width + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(Math.max(tdWidth, 0) + "px");
+ } else {
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ int tdWidth = width
+ + scrollBody.getCellExtraWidth()
+ - borderWidths;
+ setWidth(Math.max(tdWidth, 0) + "px");
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Sets the width to undefined
+ */
+ public void setUndefinedWidth() {
+ setWidth(-1, false);
+ }
+
+ /**
+ * Detects if width is fixed by developer on server side or resized to
+ * current width by user.
+ *
+ * @return true if defined, false if "natural" width
+ */
+ public boolean isDefinedWidth() {
+ return definedWidth && width >= 0;
+ }
+
+ /**
+ * Returns the pixels width of the footer cell
+ *
+ * @return The width in pixels
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Sets the expand ratio of the cell
+ *
+ * @param floatAttribute
+ * The expand ratio
+ */
+ public void setExpandRatio(float floatAttribute) {
+ expandRatio = floatAttribute;
+ }
+
+ /**
+ * Returns the expand ration of the cell
+ *
+ * @return The expand ratio
+ */
+ public float getExpandRatio() {
+ return expandRatio;
+ }
+
+ /**
+ * Is the cell enabled?
+ *
+ * @return True if enabled else False
+ */
+ public boolean isEnabled() {
+ return getParent() != null;
+ }
+
+ /**
+ * Handle column clicking
+ */
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (enabled && event != null) {
+ handleCaptionEvent(event);
+
+ if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+ scrollBodyPanel.setFocus(true);
+ }
+ boolean stopPropagation = true;
+ if (event.getTypeInt() == Event.ONCONTEXTMENU
+ && !client.hasEventListeners(VScrollTable.this,
+ TableConstants.FOOTER_CLICK_EVENT_ID)) {
+ // Show browser context menu if a footer click listener is
+ // not present
+ stopPropagation = false;
+ }
+ if (stopPropagation) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+ }
+
+ /**
+ * Handles a event on the captions
+ *
+ * @param event
+ * The event to handle
+ */
+ protected void handleCaptionEvent(Event event) {
+ if (event.getTypeInt() == Event.ONMOUSEUP
+ || event.getTypeInt() == Event.ONDBLCLICK) {
+ fireFooterClickedEvent(event);
+ }
+ }
+
+ /**
+ * Fires a footer click event after the user has clicked a column footer
+ * cell
+ *
+ * @param event
+ * The click event
+ */
+ private void fireFooterClickedEvent(Event event) {
+ if (client.hasEventListeners(VScrollTable.this,
+ TableConstants.FOOTER_CLICK_EVENT_ID)) {
+ MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(event);
+ client.updateVariable(paintableId, "footerClickEvent",
+ details.toString(), false);
+ client.updateVariable(paintableId, "footerClickCID", cid, true);
+ }
+ }
+
+ /**
+ * Returns the column key of the column
+ *
+ * @return The column key
+ */
+ public String getColKey() {
+ return cid;
+ }
+
+ /**
+ * Detects the natural minimum width for the column of this header cell.
+ * If column is resized by user or the width is defined by server the
+ * actual width is returned. Else the natural min width is returned.
+ *
+ * @param columnIndex
+ * column index hint, if -1 (unknown) it will be detected
+ *
+ * @return
+ */
+ public int getNaturalColumnWidth(int columnIndex) {
+ if (isDefinedWidth()) {
+ return width;
+ } else {
+ if (naturalWidth < 0) {
+ // This is recently revealed column. Try to detect a proper
+ // value (greater of header and data
+ // cols)
+
+ final int hw = ((Element) getElement().getLastChild())
+ .getOffsetWidth() + scrollBody.getCellExtraWidth();
+ if (columnIndex < 0) {
+ columnIndex = 0;
+ for (Iterator<Widget> it = tHead.iterator(); it
+ .hasNext(); columnIndex++) {
+ if (it.next() == this) {
+ break;
+ }
+ }
+ }
+ final int cw = scrollBody.getColWidth(columnIndex);
+ naturalWidth = (hw > cw ? hw : cw);
+ }
+ return naturalWidth;
+ }
+ }
+
+ public void setNaturalMinimumColumnWidth(int w) {
+ naturalWidth = w;
+ }
+ }
+
+ /**
+ * HeaderCell that is header cell for row headers.
+ *
+ * Reordering disabled and clicking on it resets sorting.
+ */
+ public class RowHeadersFooterCell extends FooterCell {
+
+ RowHeadersFooterCell() {
+ super(ROW_HEADER_COLUMN_KEY, "");
+ }
+
+ @Override
+ protected void handleCaptionEvent(Event event) {
+ // NOP: RowHeaders cannot be reordered
+ // TODO It'd be nice to reset sorting here
+ }
+ }
+
+ /**
+ * The footer of the table which can be seen in the bottom of the Table.
+ */
+ public class TableFooter extends Panel {
+
+ private static final int WRAPPER_WIDTH = 900000;
+
+ ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+ HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
+
+ Element div = DOM.createDiv();
+ Element hTableWrapper = DOM.createDiv();
+ Element hTableContainer = DOM.createDiv();
+ Element table = DOM.createTable();
+ Element headerTableBody = DOM.createTBody();
+ Element tr = DOM.createTR();
+
+ public TableFooter() {
+
+ DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+
+ DOM.appendChild(table, headerTableBody);
+ DOM.appendChild(headerTableBody, tr);
+ DOM.appendChild(hTableContainer, table);
+ DOM.appendChild(hTableWrapper, hTableContainer);
+ DOM.appendChild(div, hTableWrapper);
+ setElement(div);
+
+ availableCells.put(ROW_HEADER_COLUMN_KEY,
+ new RowHeadersFooterCell());
+
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ hTableWrapper.setClassName(primaryStyleName + "-footer");
+ setStyleName(primaryStyleName + "-footer-wrap");
+ for (FooterCell c : availableCells.values()) {
+ c.updateStyleNames(primaryStyleName);
+ }
+ }
+
+ @Override
+ public void clear() {
+ for (String cid : availableCells.keySet()) {
+ removeCell(cid);
+ }
+ availableCells.clear();
+ availableCells.put(ROW_HEADER_COLUMN_KEY,
+ new RowHeadersFooterCell());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
+ * .ui.Widget)
+ */
+
+ @Override
+ public boolean remove(Widget w) {
+ if (visibleCells.contains(w)) {
+ visibleCells.remove(w);
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
+ */
+
+ @Override
+ public Iterator<Widget> iterator() {
+ return visibleCells.iterator();
+ }
+
+ /**
+ * Gets a footer cell which represents the given columnId
+ *
+ * @param cid
+ * The columnId
+ *
+ * @return The cell
+ */
+ public FooterCell getFooterCell(String cid) {
+ return availableCells.get(cid);
+ }
+
+ /**
+ * Gets a footer cell by using a column index
+ *
+ * @param index
+ * The index of the column
+ * @return The Cell
+ */
+ public FooterCell getFooterCell(int index) {
+ if (index < visibleCells.size()) {
+ return (FooterCell) visibleCells.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Updates the cells contents when updateUIDL request is received
+ *
+ * @param uidl
+ * The UIDL
+ */
+ public void updateCellsFromUIDL(UIDL uidl) {
+ Iterator<?> columnIterator = uidl.getChildIterator();
+ HashSet<String> updated = new HashSet<String>();
+ while (columnIterator.hasNext()) {
+ final UIDL col = (UIDL) columnIterator.next();
+ final String cid = col.getStringAttribute("cid");
+ updated.add(cid);
+
+ String caption = col.hasAttribute("fcaption") ? col
+ .getStringAttribute("fcaption") : "";
+ FooterCell c = getFooterCell(cid);
+ if (c == null) {
+ c = new FooterCell(cid, caption);
+ availableCells.put(cid, c);
+ if (initializedAndAttached) {
+ // we will need a column width recalculation
+ initializedAndAttached = false;
+ initialContentReceived = false;
+ isNewBody = true;
+ }
+ } else {
+ c.setText(caption);
+ }
+
+ if (col.hasAttribute("align")) {
+ c.setAlign(col.getStringAttribute("align").charAt(0));
+ } else {
+ c.setAlign(ALIGN_LEFT);
+
+ }
+ if (col.hasAttribute("width")) {
+ if (scrollBody == null) {
+ // Already updated by setColWidth called from
+ // TableHeads.updateCellsFromUIDL in case of a server
+ // side resize
+ final String width = col.getStringAttribute("width");
+ c.setWidth(Integer.parseInt(width), true);
+ }
+ } else if (recalcWidths) {
+ c.setUndefinedWidth();
+ }
+ if (col.hasAttribute("er")) {
+ c.setExpandRatio(col.getFloatAttribute("er"));
+ }
+ if (col.hasAttribute("collapsed")) {
+ // ensure header is properly removed from parent (case when
+ // collapsing happens via servers side api)
+ if (c.isAttached()) {
+ c.removeFromParent();
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ // check for orphaned header cells
+ for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+ .hasNext();) {
+ String cid = cit.next();
+ if (!updated.contains(cid)) {
+ removeCell(cid);
+ cit.remove();
+ }
+ }
+ }
+
+ /**
+ * Set a footer cell for a specified column index
+ *
+ * @param index
+ * The index
+ * @param cell
+ * The footer cell
+ */
+ public void setFooterCell(int index, FooterCell cell) {
+ if (cell.isEnabled()) {
+ // we're moving the cell
+ DOM.removeChild(tr, cell.getElement());
+ orphan(cell);
+ visibleCells.remove(cell);
+ }
+ if (index < visibleCells.size()) {
+ // insert to right slot
+ DOM.insertChild(tr, cell.getElement(), index);
+ adopt(cell);
+ visibleCells.add(index, cell);
+ } else if (index == visibleCells.size()) {
+ // simply append
+ DOM.appendChild(tr, cell.getElement());
+ adopt(cell);
+ visibleCells.add(cell);
+ } else {
+ throw new RuntimeException(
+ "Header cells must be appended in order");
+ }
+ }
+
+ /**
+ * Remove a cell by using the columnId
+ *
+ * @param colKey
+ * The columnId to remove
+ */
+ public void removeCell(String colKey) {
+ final FooterCell c = getFooterCell(colKey);
+ remove(c);
+ }
+
+ /**
+ * Enable a column (Sets the footer cell)
+ *
+ * @param cid
+ * The columnId
+ * @param index
+ * The index of the column
+ */
+ public void enableColumn(String cid, int index) {
+ final FooterCell c = getFooterCell(cid);
+ if (!c.isEnabled() || getFooterCell(index) != c) {
+ setFooterCell(index, c);
+ if (initializedAndAttached) {
+ headerChangedDuringUpdate = true;
+ }
+ }
+ }
+
+ /**
+ * Disable browser measurement of the table width
+ */
+ public void disableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
+ + "px");
+ }
+
+ /**
+ * Enable browser measurement of the table width
+ */
+ public void enableBrowserIntelligence() {
+ DOM.setStyleAttribute(hTableContainer, "width", "");
+ }
+
+ /**
+ * Set the horizontal position in the cell in the footer. This is done
+ * when a horizontal scrollbar is present.
+ *
+ * @param scrollLeft
+ * The value of the leftScroll
+ */
+ public void setHorizontalScrollPosition(int scrollLeft) {
+ hTableWrapper.setScrollLeft(scrollLeft);
+ }
+
+ /**
+ * Swap cells when the column are dragged
+ *
+ * @param oldIndex
+ * The old index of the cell
+ * @param newIndex
+ * The new index of the cell
+ */
+ public void moveCell(int oldIndex, int newIndex) {
+ final FooterCell hCell = getFooterCell(oldIndex);
+ final Element cell = hCell.getElement();
+
+ visibleCells.remove(oldIndex);
+ DOM.removeChild(tr, cell);
+
+ DOM.insertChild(tr, cell, newIndex);
+ visibleCells.add(newIndex, hCell);
+ }
+ }
+
+ /**
+ * This Panel can only contain VScrollTableRow type of widgets. This
+ * "simulates" very large table, keeping spacers which take room of
+ * unrendered rows.
+ *
+ */
+ public class VScrollTableBody extends Panel {
+
+ public static final int DEFAULT_ROW_HEIGHT = 24;
+
+ private double rowHeight = -1;
+
+ private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
+
+ /**
+ * Due some optimizations row height measuring is deferred and initial
+ * set of rows is rendered detached. Flag set on when table body has
+ * been attached in dom and rowheight has been measured.
+ */
+ private boolean tBodyMeasurementsDone = false;
+
+ Element preSpacer = DOM.createDiv();
+ Element postSpacer = DOM.createDiv();
+
+ Element container = DOM.createDiv();
+
+ TableSectionElement tBodyElement = Document.get().createTBodyElement();
+ Element table = DOM.createTable();
+
+ private int firstRendered;
+ private int lastRendered;
+
+ private char[] aligns;
+
+ protected VScrollTableBody() {
+ constructDOM();
+ setElement(container);
+ }
+
+ public VScrollTableRow getRowByRowIndex(int indexInTable) {
+ int internalIndex = indexInTable - firstRendered;
+ if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
+ return (VScrollTableRow) renderedRows.get(internalIndex);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return the height of scrollable body, subpixels ceiled.
+ */
+ public int getRequiredHeight() {
+ return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
+ + Util.getRequiredHeight(table);
+ }
+
+ private void constructDOM() {
+ if (BrowserInfo.get().isIE()) {
+ table.setPropertyInt("cellSpacing", 0);
+ }
+
+ table.appendChild(tBodyElement);
+ DOM.appendChild(container, preSpacer);
+ DOM.appendChild(container, table);
+ DOM.appendChild(container, postSpacer);
+ if (BrowserInfo.get().requiresTouchScrollDelegate()) {
+ NodeList<Node> childNodes = container.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Element item = (Element) childNodes.getItem(i);
+ item.getStyle().setProperty("webkitTransform",
+ "translate3d(0,0,0)");
+ }
+ }
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+ table.setClassName(primaryStyleName + "-table");
+ preSpacer.setClassName(primaryStyleName + "-row-spacer");
+ postSpacer.setClassName(primaryStyleName + "-row-spacer");
+ for (Widget w : renderedRows) {
+ VScrollTableRow row = (VScrollTableRow) w;
+ row.updateStyleNames(primaryStyleName);
+ }
+ }
+
+ public int getAvailableWidth() {
+ int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
+ return availW;
+ }
+
+ public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
+ firstRendered = firstIndex;
+ lastRendered = firstIndex + rows - 1;
+ final Iterator<?> it = rowData.getChildIterator();
+ aligns = tHead.getColumnAlignments();
+ while (it.hasNext()) {
+ final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
+ addRow(row);
+ }
+ if (isAttached()) {
+ fixSpacers();
+ }
+ }
+
+ public void renderRows(UIDL rowData, int firstIndex, int rows) {
+ // FIXME REVIEW
+ aligns = tHead.getColumnAlignments();
+ final Iterator<?> it = rowData.getChildIterator();
+ if (firstIndex == lastRendered + 1) {
+ while (it.hasNext()) {
+ final VScrollTableRow row = prepareRow((UIDL) it.next());
+ addRow(row);
+ lastRendered++;
+ }
+ fixSpacers();
+ } else if (firstIndex + rows == firstRendered) {
+ final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
+ int i = rows;
+ while (it.hasNext()) {
+ i--;
+ rowArray[i] = prepareRow((UIDL) it.next());
+ }
+ for (i = 0; i < rows; i++) {
+ addRowBeforeFirstRendered(rowArray[i]);
+ firstRendered--;
+ }
+ } else {
+ // completely new set of rows
+ while (lastRendered + 1 > firstRendered) {
+ unlinkRow(false);
+ }
+ final VScrollTableRow row = prepareRow((UIDL) it.next());
+ firstRendered = firstIndex;
+ lastRendered = firstIndex - 1;
+ addRow(row);
+ lastRendered++;
+ setContainerHeight();
+ fixSpacers();
+ while (it.hasNext()) {
+ addRow(prepareRow((UIDL) it.next()));
+ lastRendered++;
+ }
+ fixSpacers();
+ }
+
+ // this may be a new set of rows due content change,
+ // ensure we have proper cache rows
+ ensureCacheFilled();
+ }
+
+ /**
+ * Ensure we have the correct set of rows on client side, e.g. if the
+ * content on the server side has changed, or the client scroll position
+ * has changed since the last request.
+ */
+ protected void ensureCacheFilled() {
+ int reactFirstRow = (int) (firstRowInViewPort - pageLength
+ * cache_react_rate);
+ int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
+ * cache_react_rate);
+ if (reactFirstRow < 0) {
+ reactFirstRow = 0;
+ }
+ if (reactLastRow >= totalRows) {
+ reactLastRow = totalRows - 1;
+ }
+ if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
+ /*
+ * #8040 - scroll position is completely changed since the
+ * latest request, so request a new set of rows.
+ *
+ * TODO: We should probably check whether the fetched rows match
+ * the current scroll position right when they arrive, so as to
+ * not waste time rendering a set of rows that will never be
+ * visible...
+ */
+ rowRequestHandler.setReqFirstRow(reactFirstRow);
+ rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1);
+ rowRequestHandler.deferRowFetch(1);
+ } else if (lastRendered < reactLastRow) {
+ // get some cache rows below visible area
+ rowRequestHandler.setReqFirstRow(lastRendered + 1);
+ rowRequestHandler.setReqRows(reactLastRow - lastRendered);
+ rowRequestHandler.deferRowFetch(1);
+ } else if (firstRendered > reactFirstRow) {
+ /*
+ * Branch for fetching cache above visible area.
+ *
+ * If cache needed for both before and after visible area, this
+ * will be rendered after-cache is received and rendered. So in
+ * some rare situations the table may make two cache visits to
+ * server.
+ */
+ rowRequestHandler.setReqFirstRow(reactFirstRow);
+ rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
+ rowRequestHandler.deferRowFetch(1);
+ }
+ }
+
+ /**
+ * Inserts rows as provided in the rowData starting at firstIndex.
+ *
+ * @param rowData
+ * @param firstIndex
+ * @param rows
+ * the number of rows
+ * @return a list of the rows added.
+ */
+ protected List<VScrollTableRow> insertRows(UIDL rowData,
+ int firstIndex, int rows) {
+ aligns = tHead.getColumnAlignments();
+ final Iterator<?> it = rowData.getChildIterator();
+ List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
+
+ if (firstIndex == lastRendered + 1) {
+ while (it.hasNext()) {
+ final VScrollTableRow row = prepareRow((UIDL) it.next());
+ addRow(row);
+ insertedRows.add(row);
+ lastRendered++;
+ }
+ fixSpacers();
+ } else if (firstIndex + rows == firstRendered) {
+ final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
+ int i = rows;
+ while (it.hasNext()) {
+ i--;
+ rowArray[i] = prepareRow((UIDL) it.next());
+ }
+ for (i = 0; i < rows; i++) {
+ addRowBeforeFirstRendered(rowArray[i]);
+ insertedRows.add(rowArray[i]);
+ firstRendered--;
+ }
+ } else {
+ // insert in the middle
+ int ix = firstIndex;
+ while (it.hasNext()) {
+ VScrollTableRow row = prepareRow((UIDL) it.next());
+ insertRowAt(row, ix);
+ insertedRows.add(row);
+ lastRendered++;
+ ix++;
+ }
+ fixSpacers();
+ }
+ return insertedRows;
+ }
+
+ protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
+ int firstIndex, int rows) {
+ List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
+ rows);
+ int actualIxOfFirstRowAfterInserted = firstIndex + rows
+ - firstRendered;
+ for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
+ .size(); ix++) {
+ VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+ r.setIndex(r.getIndex() + rows);
+ }
+ setContainerHeight();
+ return inserted;
+ }
+
+ protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
+ int rows) {
+ unlinkAllRowsStartingAt(firstIndex);
+ insertRows(rowData, firstIndex, rows);
+ setContainerHeight();
+ }
+
+ /**
+ * This method is used to instantiate new rows for this table. It
+ * automatically sets correct widths to rows cells and assigns correct
+ * client reference for child widgets.
+ *
+ * This method can be called only after table has been initialized
+ *
+ * @param uidl
+ */
+ private VScrollTableRow prepareRow(UIDL uidl) {
+ final VScrollTableRow row = createRow(uidl, aligns);
+ row.initCellWidths();
+ return row;
+ }
+
+ protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
+ if (uidl.hasAttribute("gen_html")) {
+ // This is a generated row.
+ return new VScrollTableGeneratedRow(uidl, aligns2);
+ }
+ return new VScrollTableRow(uidl, aligns2);
+ }
+
+ private void addRowBeforeFirstRendered(VScrollTableRow row) {
+ row.setIndex(firstRendered - 1);
+ if (row.isSelected()) {
+ row.addStyleName("v-selected");
+ }
+ tBodyElement.insertBefore(row.getElement(),
+ tBodyElement.getFirstChild());
+ adopt(row);
+ renderedRows.add(0, row);
+ }
+
+ private void addRow(VScrollTableRow row) {
+ row.setIndex(firstRendered + renderedRows.size());
+ if (row.isSelected()) {
+ row.addStyleName("v-selected");
+ }
+ tBodyElement.appendChild(row.getElement());
+ // Add to renderedRows before adopt so iterator() will return also
+ // this row if called in an attach handler (#9264)
+ renderedRows.add(row);
+ adopt(row);
+ }
+
+ private void insertRowAt(VScrollTableRow row, int index) {
+ row.setIndex(index);
+ if (row.isSelected()) {
+ row.addStyleName("v-selected");
+ }
+ if (index > 0) {
+ VScrollTableRow sibling = getRowByRowIndex(index - 1);
+ tBodyElement
+ .insertAfter(row.getElement(), sibling.getElement());
+ } else {
+ VScrollTableRow sibling = getRowByRowIndex(index);
+ tBodyElement.insertBefore(row.getElement(),
+ sibling.getElement());
+ }
+ adopt(row);
+ int actualIx = index - firstRendered;
+ renderedRows.add(actualIx, row);
+ }
+
+ @Override
+ public Iterator<Widget> iterator() {
+ return renderedRows.iterator();
+ }
+
+ /**
+ * @return false if couldn't remove row
+ */
+ protected boolean unlinkRow(boolean fromBeginning) {
+ if (lastRendered - firstRendered < 0) {
+ return false;
+ }
+ int actualIx;
+ if (fromBeginning) {
+ actualIx = 0;
+ firstRendered++;
+ } else {
+ actualIx = renderedRows.size() - 1;
+ lastRendered--;
+ }
+ if (actualIx >= 0) {
+ unlinkRowAtActualIndex(actualIx);
+ fixSpacers();
+ return true;
+ }
+ return false;
+ }
+
+ protected void unlinkRows(int firstIndex, int count) {
+ if (count < 1) {
+ return;
+ }
+ if (firstRendered > firstIndex
+ && firstRendered < firstIndex + count) {
+ firstIndex = firstRendered;
+ }
+ int lastIndex = firstIndex + count - 1;
+ if (lastRendered < lastIndex) {
+ lastIndex = lastRendered;
+ }
+ for (int ix = lastIndex; ix >= firstIndex; ix--) {
+ unlinkRowAtActualIndex(actualIndex(ix));
+ lastRendered--;
+ }
+ fixSpacers();
+ }
+
+ protected void unlinkAndReindexRows(int firstIndex, int count) {
+ unlinkRows(firstIndex, count);
+ int actualFirstIx = firstIndex - firstRendered;
+ for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
+ VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+ r.setIndex(r.getIndex() - count);
+ }
+ setContainerHeight();
+ }
+
+ protected void unlinkAllRowsStartingAt(int index) {
+ if (firstRendered > index) {
+ index = firstRendered;
+ }
+ for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
+ unlinkRowAtActualIndex(actualIndex(ix));
+ lastRendered--;
+ }
+ fixSpacers();
+ }
+
+ private int actualIndex(int index) {
+ return index - firstRendered;
+ }
+
+ private void unlinkRowAtActualIndex(int index) {
+ final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
+ .get(index);
+ tBodyElement.removeChild(toBeRemoved.getElement());
+ orphan(toBeRemoved);
+ renderedRows.remove(index);
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Fix container blocks height according to totalRows to avoid
+ * "bouncing" when scrolling
+ */
+ private void setContainerHeight() {
+ fixSpacers();
+ DOM.setStyleAttribute(container, "height",
+ measureRowHeightOffset(totalRows) + "px");
+ }
+
+ private void fixSpacers() {
+ int prepx = measureRowHeightOffset(firstRendered);
+ if (prepx < 0) {
+ prepx = 0;
+ }
+ preSpacer.getStyle().setPropertyPx("height", prepx);
+ int postpx = measureRowHeightOffset(totalRows - 1)
+ - measureRowHeightOffset(lastRendered);
+ if (postpx < 0) {
+ postpx = 0;
+ }
+ postSpacer.getStyle().setPropertyPx("height", postpx);
+ }
+
+ public double getRowHeight() {
+ return getRowHeight(false);
+ }
+
+ public double getRowHeight(boolean forceUpdate) {
+ if (tBodyMeasurementsDone && !forceUpdate) {
+ return rowHeight;
+ } else {
+ if (tBodyElement.getRows().getLength() > 0) {
+ int tableHeight = getTableHeight();
+ int rowCount = tBodyElement.getRows().getLength();
+ rowHeight = tableHeight / (double) rowCount;
+ } else {
+ // Special cases if we can't just measure the current rows
+ if (!Double.isNaN(lastKnownRowHeight)) {
+ // Use previous value if available
+ if (BrowserInfo.get().isIE()) {
+ /*
+ * IE needs to reflow the table element at this
+ * point to work correctly (e.g.
+ * com.vaadin.tests.components.table.
+ * ContainerSizeChange) - the other code paths
+ * already trigger reflows, but here it must be done
+ * explicitly.
+ */
+ getTableHeight();
+ }
+ rowHeight = lastKnownRowHeight;
+ } else if (isAttached()) {
+ // measure row height by adding a dummy row
+ VScrollTableRow scrollTableRow = new VScrollTableRow();
+ tBodyElement.appendChild(scrollTableRow.getElement());
+ getRowHeight(forceUpdate);
+ tBodyElement.removeChild(scrollTableRow.getElement());
+ } else {
+ // TODO investigate if this can never happen anymore
+ return DEFAULT_ROW_HEIGHT;
+ }
+ }
+ lastKnownRowHeight = rowHeight;
+ tBodyMeasurementsDone = true;
+ return rowHeight;
+ }
+ }
+
+ public int getTableHeight() {
+ return table.getOffsetHeight();
+ }
+
+ /**
+ * Returns the width available for column content.
+ *
+ * @param columnIndex
+ * @return
+ */
+ public int getColWidth(int columnIndex) {
+ if (tBodyMeasurementsDone) {
+ if (renderedRows.isEmpty()) {
+ // no rows yet rendered
+ return 0;
+ }
+ for (Widget row : renderedRows) {
+ if (!(row instanceof VScrollTableGeneratedRow)) {
+ TableRowElement tr = row.getElement().cast();
+ Element wrapperdiv = tr.getCells().getItem(columnIndex)
+ .getFirstChildElement().cast();
+ return wrapperdiv.getOffsetWidth();
+ }
+ }
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the content width of a column.
+ *
+ * Due IE limitation, we must set the width to a wrapper elements inside
+ * table cells (with overflow hidden, which does not work on td
+ * elements).
+ *
+ * To get this work properly crossplatform, we will also set the width
+ * of td.
+ *
+ * @param colIndex
+ * @param w
+ */
+ public void setColWidth(int colIndex, int w) {
+ for (Widget row : renderedRows) {
+ ((VScrollTableRow) row).setCellWidth(colIndex, w);
+ }
+ }
+
+ private int cellExtraWidth = -1;
+
+ /**
+ * Method to return the space used for cell paddings + border.
+ */
+ private int getCellExtraWidth() {
+ if (cellExtraWidth < 0) {
+ detectExtrawidth();
+ }
+ return cellExtraWidth;
+ }
+
+ private void detectExtrawidth() {
+ NodeList<TableRowElement> rows = tBodyElement.getRows();
+ if (rows.getLength() == 0) {
+ /* need to temporary add empty row and detect */
+ VScrollTableRow scrollTableRow = new VScrollTableRow();
+ scrollTableRow.updateStyleNames(VScrollTable.this
+ .getStylePrimaryName());
+ tBodyElement.appendChild(scrollTableRow.getElement());
+ detectExtrawidth();
+ tBodyElement.removeChild(scrollTableRow.getElement());
+ } else {
+ boolean noCells = false;
+ TableRowElement item = rows.getItem(0);
+ TableCellElement firstTD = item.getCells().getItem(0);
+ if (firstTD == null) {
+ // content is currently empty, we need to add a fake cell
+ // for measuring
+ noCells = true;
+ VScrollTableRow next = (VScrollTableRow) iterator().next();
+ boolean sorted = tHead.getHeaderCell(0) != null ? tHead
+ .getHeaderCell(0).isSorted() : false;
+ next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
+ firstTD = item.getCells().getItem(0);
+ }
+ com.google.gwt.dom.client.Element wrapper = firstTD
+ .getFirstChildElement();
+ cellExtraWidth = firstTD.getOffsetWidth()
+ - wrapper.getOffsetWidth();
+ if (noCells) {
+ firstTD.getParentElement().removeChild(firstTD);
+ }
+ }
+ }
+
+ private void reLayoutComponents() {
+ for (Widget w : this) {
+ VScrollTableRow r = (VScrollTableRow) w;
+ for (Widget widget : r) {
+ client.handleComponentRelativeSize(widget);
+ }
+ }
+ }
+
+ public int getLastRendered() {
+ return lastRendered;
+ }
+
+ public int getFirstRendered() {
+ return firstRendered;
+ }
+
+ public void moveCol(int oldIndex, int newIndex) {
+
+ // loop all rows and move given index to its new place
+ final Iterator<?> rows = iterator();
+ while (rows.hasNext()) {
+ final VScrollTableRow row = (VScrollTableRow) rows.next();
+
+ final Element td = DOM.getChild(row.getElement(), oldIndex);
+ if (td != null) {
+ DOM.removeChild(row.getElement(), td);
+
+ DOM.insertChild(row.getElement(), td, newIndex);
+ }
+ }
+
+ }
+
+ /**
+ * Restore row visibility which is set to "none" when the row is
+ * rendered (due a performance optimization).
+ */
+ private void restoreRowVisibility() {
+ for (Widget row : renderedRows) {
+ row.getElement().getStyle().setProperty("visibility", "");
+ }
+ }
+
+ public class VScrollTableRow extends Panel implements ActionOwner {
+
+ private static final int TOUCHSCROLL_TIMEOUT = 100;
+ private static final int DRAGMODE_MULTIROW = 2;
+ protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
+ private boolean selected = false;
+ protected final int rowKey;
+
+ private String[] actionKeys = null;
+ private final TableRowElement rowElement;
+ private int index;
+ private Event touchStart;
+
+ private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
+ private Timer contextTouchTimeout;
+ private Timer dragTouchTimeout;
+ private int touchStartY;
+ private int touchStartX;
+ private TooltipInfo tooltipInfo = null;
+ private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
+ private boolean isDragging = false;
+ private String rowStyle = null;
+
+ private VScrollTableRow(int rowKey) {
+ this.rowKey = rowKey;
+ rowElement = Document.get().createTRElement();
+ setElement(rowElement);
+ DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
+ | Event.TOUCHEVENTS | Event.ONDBLCLICK
+ | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS);
+ }
+
+ public VScrollTableRow(UIDL uidl, char[] aligns) {
+ this(uidl.getIntAttribute("key"));
+
+ /*
+ * Rendering the rows as hidden improves Firefox and Safari
+ * performance drastically.
+ */
+ getElement().getStyle().setProperty("visibility", "hidden");
+
+ rowStyle = uidl.getStringAttribute("rowstyle");
+ updateStyleNames(VScrollTable.this.getStylePrimaryName());
+
+ String rowDescription = uidl.getStringAttribute("rowdescr");
+ if (rowDescription != null && !rowDescription.equals("")) {
+ tooltipInfo = new TooltipInfo(rowDescription);
+ } else {
+ tooltipInfo = null;
+ }
+
+ tHead.getColumnAlignments();
+ int col = 0;
+ int visibleColumnIndex = -1;
+
+ // row header
+ if (showRowHeaders) {
+ boolean sorted = tHead.getHeaderCell(col).isSorted();
+ addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
+ "rowheader", true, sorted);
+ visibleColumnIndex++;
+ }
+
+ if (uidl.hasAttribute("al")) {
+ actionKeys = uidl.getStringArrayAttribute("al");
+ }
+
+ addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
+
+ if (uidl.hasAttribute("selected") && !isSelected()) {
+ toggleSelection();
+ }
+ }
+
+ protected void updateStyleNames(String primaryStyleName) {
+
+ if (getStylePrimaryName().contains("odd")) {
+ setStyleName(primaryStyleName + "-row-odd");
+ } else {
+ setStyleName(primaryStyleName + "-row");
+ }
+
+ if (rowStyle != null) {
+ addStyleName(primaryStyleName + "-row-" + rowStyle);
+ }
+
+ for (int i = 0; i < rowElement.getChildCount(); i++) {
+ TableCellElement cell = (TableCellElement) rowElement
+ .getChild(i);
+ updateCellStyleNames(cell, primaryStyleName);
+ }
+ }
+
+ public TooltipInfo getTooltipInfo() {
+ return tooltipInfo;
+ }
+
+ /**
+ * Add a dummy row, used for measurements if Table is empty.
+ */
+ public VScrollTableRow() {
+ this(0);
+ addCell(null, "_", 'b', "", true, false);
+ }
+
+ protected void initCellWidths() {
+ final int cells = tHead.getVisibleCellCount();
+ for (int i = 0; i < cells; i++) {
+ int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
+ if (w < 0) {
+ w = 0;
+ }
+ setCellWidth(i, w);
+ }
+ }
+
+ protected void setCellWidth(int cellIx, int width) {
+ final Element cell = DOM.getChild(getElement(), cellIx);
+ cell.getFirstChildElement().getStyle()
+ .setPropertyPx("width", width);
+ cell.getStyle().setPropertyPx("width", width);
+ }
+
+ protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+ int visibleColumnIndex) {
+ final Iterator<?> cells = uidl.getChildIterator();
+ while (cells.hasNext()) {
+ final Object cell = cells.next();
+ visibleColumnIndex++;
+
+ String columnId = visibleColOrder[visibleColumnIndex];
+
+ String style = "";
+ if (uidl.hasAttribute("style-" + columnId)) {
+ style = uidl.getStringAttribute("style-" + columnId);
+ }
+
+ String description = null;
+ if (uidl.hasAttribute("descr-" + columnId)) {
+ description = uidl.getStringAttribute("descr-"
+ + columnId);
+ }
+
+ boolean sorted = tHead.getHeaderCell(col).isSorted();
+ if (cell instanceof String) {
+ addCell(uidl, cell.toString(), aligns[col++], style,
+ isRenderHtmlInCells(), sorted, description);
+ } else {
+ final ComponentConnector cellContent = client
+ .getPaintable((UIDL) cell);
+
+ addCell(uidl, cellContent.getWidget(), aligns[col++],
+ style, sorted);
+ }
+ }
+ }
+
+ /**
+ * Overriding this and returning true causes all text cells to be
+ * rendered as HTML.
+ *
+ * @return always returns false in the default implementation
+ */
+ protected boolean isRenderHtmlInCells() {
+ return false;
+ }
+
+ /**
+ * Detects whether row is visible in tables viewport.
+ *
+ * @return
+ */
+ public boolean isInViewPort() {
+ int absoluteTop = getAbsoluteTop();
+ int scrollPosition = scrollBodyPanel.getScrollPosition();
+ if (absoluteTop < scrollPosition) {
+ return false;
+ }
+ int maxVisible = scrollPosition
+ + scrollBodyPanel.getOffsetHeight() - getOffsetHeight();
+ if (absoluteTop > maxVisible) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Makes a check based on indexes whether the row is before the
+ * compared row.
+ *
+ * @param row1
+ * @return true if this rows index is smaller than in the row1
+ */
+ public boolean isBefore(VScrollTableRow row1) {
+ return getIndex() < row1.getIndex();
+ }
+
+ /**
+ * Sets the index of the row in the whole table. Currently used just
+ * to set even/odd classname
+ *
+ * @param indexInWholeTable
+ */
+ private void setIndex(int indexInWholeTable) {
+ index = indexInWholeTable;
+ boolean isOdd = indexInWholeTable % 2 == 0;
+ // Inverted logic to be backwards compatible with earlier 6.4.
+ // It is very strange because rows 1,3,5 are considered "even"
+ // and 2,4,6 "odd".
+ //
+ // First remove any old styles so that both styles aren't
+ // applied when indexes are updated.
+ String primaryStyleName = getStylePrimaryName();
+ if (primaryStyleName != null && !primaryStyleName.equals("")) {
+ removeStyleName(getStylePrimaryName());
+ }
+ if (!isOdd) {
+ addStyleName(VScrollTable.this.getStylePrimaryName()
+ + "-row-odd");
+ } else {
+ addStyleName(VScrollTable.this.getStylePrimaryName()
+ + "-row");
+ }
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ client.getContextMenu().ensureHidden(this);
+ }
+
+ public String getKey() {
+ return String.valueOf(rowKey);
+ }
+
+ public void addCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML, boolean sorted) {
+ addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
+ }
+
+ public void addCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML, boolean sorted,
+ String description) {
+ // String only content is optimized by not using Label widget
+ final TableCellElement td = DOM.createTD().cast();
+ initCellWithText(text, align, style, textIsHTML, sorted,
+ description, td);
+ }
+
+ protected void initCellWithText(String text, char align,
+ String style, boolean textIsHTML, boolean sorted,
+ String description, final TableCellElement td) {
+ final Element container = DOM.createDiv();
+ container.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-cell-wrapper");
+
+ td.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-cell-content");
+
+ if (style != null && !style.equals("")) {
+ td.addClassName(VScrollTable.this.getStylePrimaryName()
+ + "-cell-content-" + style);
+ }
+
+ if (sorted) {
+ td.addClassName(VScrollTable.this.getStylePrimaryName()
+ + "-cell-content-sorted");
+ }
+
+ if (textIsHTML) {
+ container.setInnerHTML(text);
+ } else {
+ container.setInnerText(text);
+ }
+ if (align != ALIGN_LEFT) {
+ switch (align) {
+ case ALIGN_CENTER:
+ container.getStyle().setProperty("textAlign", "center");
+ break;
+ case ALIGN_RIGHT:
+ default:
+ container.getStyle().setProperty("textAlign", "right");
+ break;
+ }
+ }
+
+ if (description != null && !description.equals("")) {
+ TooltipInfo info = new TooltipInfo(description);
+ cellToolTips.put(td, info);
+ } else {
+ cellToolTips.remove(td);
+ }
+
+ td.appendChild(container);
+ getElement().appendChild(td);
+ }
+
+ protected void updateCellStyleNames(TableCellElement td,
+ String primaryStyleName) {
+ Element container = td.getFirstChild().cast();
+ container.setClassName(primaryStyleName + "-cell-wrapper");
+
+ /*
+ * Replace old primary style name with new one
+ */
+ String className = td.getClassName();
+ String oldPrimaryName = className.split("-cell-content")[0];
+ td.setClassName(className.replaceAll(oldPrimaryName,
+ primaryStyleName));
+ }
+
+ public void addCell(UIDL rowUidl, Widget w, char align,
+ String style, boolean sorted) {
+ final TableCellElement td = DOM.createTD().cast();
+ initCellWithWidget(w, align, style, sorted, td);
+ }
+
+ protected void initCellWithWidget(Widget w, char align,
+ String style, boolean sorted, final TableCellElement td) {
+ final Element container = DOM.createDiv();
+ String className = VScrollTable.this.getStylePrimaryName()
+ + "-cell-content";
+ if (style != null && !style.equals("")) {
+ className += " " + VScrollTable.this.getStylePrimaryName()
+ + "-cell-content-" + style;
+ }
+ if (sorted) {
+ className += " " + VScrollTable.this.getStylePrimaryName()
+ + "-cell-content-sorted";
+ }
+ td.setClassName(className);
+ container.setClassName(VScrollTable.this.getStylePrimaryName()
+ + "-cell-wrapper");
+ // TODO most components work with this, but not all (e.g.
+ // Select)
+ // Old comment: make widget cells respect align.
+ // text-align:center for IE, margin: auto for others
+ if (align != ALIGN_LEFT) {
+ switch (align) {
+ case ALIGN_CENTER:
+ container.getStyle().setProperty("textAlign", "center");
+ break;
+ case ALIGN_RIGHT:
+ default:
+ container.getStyle().setProperty("textAlign", "right");
+ break;
+ }
+ }
+ td.appendChild(container);
+ getElement().appendChild(td);
+ // ensure widget not attached to another element (possible tBody
+ // change)
+ w.removeFromParent();
+ container.appendChild(w.getElement());
+ adopt(w);
+ childWidgets.add(w);
+ }
+
+ @Override
+ public Iterator<Widget> iterator() {
+ return childWidgets.iterator();
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ if (childWidgets.contains(w)) {
+ orphan(w);
+ DOM.removeChild(DOM.getParent(w.getElement()),
+ w.getElement());
+ childWidgets.remove(w);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * If there are registered click listeners, sends a click event and
+ * returns true. Otherwise, does nothing and returns false.
+ *
+ * @param event
+ * @param targetTdOrTr
+ * @param immediate
+ * Whether the event is sent immediately
+ * @return Whether a click event was sent
+ */
+ private boolean handleClickEvent(Event event, Element targetTdOrTr,
+ boolean immediate) {
+ if (!client.hasEventListeners(VScrollTable.this,
+ TableConstants.ITEM_CLICK_EVENT_ID)) {
+ // Don't send an event if nobody is listening
+ return false;
+ }
+
+ // This row was clicked
+ client.updateVariable(paintableId, "clickedKey", "" + rowKey,
+ false);
+
+ if (getElement() == targetTdOrTr.getParentElement()) {
+ // A specific column was clicked
+ int childIndex = DOM.getChildIndex(getElement(),
+ targetTdOrTr);
+ String colKey = null;
+ colKey = tHead.getHeaderCell(childIndex).getColKey();
+ client.updateVariable(paintableId, "clickedColKey", colKey,
+ false);
+ }
+
+ MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(event);
+
+ client.updateVariable(paintableId, "clickEvent",
+ details.toString(), immediate);
+
+ return true;
+ }
+
+ public TooltipInfo getTooltip(
+ com.google.gwt.dom.client.Element target) {
+
+ TooltipInfo info = null;
+
+ if (target.hasTagName("TD")) {
+
+ TableCellElement td = (TableCellElement) target.cast();
+ info = cellToolTips.get(td);
+ }
+
+ if (info == null) {
+ info = tooltipInfo;
+ }
+
+ return info;
+ }
+
+ /**
+ * Special handler for touch devices that support native scrolling
+ *
+ * @return Whether the event was handled by this method.
+ */
+ private boolean handleTouchEvent(final Event event) {
+
+ boolean touchEventHandled = false;
+
+ if (enabled && hasNativeTouchScrolling) {
+ final Element targetTdOrTr = getEventTargetTdOrTr(event);
+ final int type = event.getTypeInt();
+
+ switch (type) {
+ case Event.ONTOUCHSTART:
+ touchEventHandled = true;
+ touchStart = event;
+ isDragging = false;
+ Touch touch = event.getChangedTouches().get(0);
+ // save position to fields, touches in events are same
+ // instance during the operation.
+ touchStartX = touch.getClientX();
+ touchStartY = touch.getClientY();
+
+ if (dragmode != 0) {
+ if (dragTouchTimeout == null) {
+ dragTouchTimeout = new Timer() {
+
+ @Override
+ public void run() {
+ if (touchStart != null) {
+ // Start a drag if a finger is held
+ // in place long enough, then moved
+ isDragging = true;
+ }
+ }
+ };
+ }
+ dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
+ }
+
+ if (actionKeys != null) {
+ if (contextTouchTimeout == null) {
+ contextTouchTimeout = new Timer() {
+
+ @Override
+ public void run() {
+ if (touchStart != null) {
+ // Open the context menu if finger
+ // is held in place long enough.
+ showContextMenu(touchStart);
+ event.preventDefault();
+ touchStart = null;
+ }
+ }
+ };
+ }
+ contextTouchTimeout
+ .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
+ }
+ break;
+ case Event.ONTOUCHMOVE:
+ touchEventHandled = true;
+ if (isSignificantMove(event)) {
+ if (contextTouchTimeout != null) {
+ // Moved finger before the context menu timer
+ // expired, so let the browser handle this as a
+ // scroll.
+ contextTouchTimeout.cancel();
+ contextTouchTimeout = null;
+ }
+ if (!isDragging && dragTouchTimeout != null) {
+ // Moved finger before the drag timer expired,
+ // so let the browser handle this as a scroll.
+ dragTouchTimeout.cancel();
+ dragTouchTimeout = null;
+ }
+
+ if (dragmode != 0 && touchStart != null
+ && isDragging) {
+ event.preventDefault();
+ event.stopPropagation();
+ startRowDrag(touchStart, type, targetTdOrTr);
+ }
+ touchStart = null;
+ }
+ break;
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ touchEventHandled = true;
+ if (contextTouchTimeout != null) {
+ contextTouchTimeout.cancel();
+ }
+ if (dragTouchTimeout != null) {
+ dragTouchTimeout.cancel();
+ }
+ if (touchStart != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ if (!BrowserInfo.get().isAndroid()) {
+ Util.simulateClickFromTouchEvent(touchStart,
+ this);
+ }
+ touchStart = null;
+ }
+ isDragging = false;
+ break;
+ }
+ }
+ return touchEventHandled;
+ }
+
+ /*
+ * React on click that occur on content cells only
+ */
+
+ @Override
+ public void onBrowserEvent(final Event event) {
+
+ final boolean touchEventHandled = handleTouchEvent(event);
+
+ if (enabled && !touchEventHandled) {
+ final int type = event.getTypeInt();
+ final Element targetTdOrTr = getEventTargetTdOrTr(event);
+ if (type == Event.ONCONTEXTMENU) {
+ showContextMenu(event);
+ if (enabled
+ && (actionKeys != null || client
+ .hasEventListeners(
+ VScrollTable.this,
+ TableConstants.ITEM_CLICK_EVENT_ID))) {
+ /*
+ * Prevent browser context menu only if there are
+ * action handlers or item click listeners
+ * registered
+ */
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ return;
+ }
+
+ boolean targetCellOrRowFound = targetTdOrTr != null;
+
+ switch (type) {
+ case Event.ONDBLCLICK:
+ if (targetCellOrRowFound) {
+ handleClickEvent(event, targetTdOrTr, true);
+ }
+ break;
+ case Event.ONMOUSEUP:
+ if (targetCellOrRowFound) {
+ /*
+ * Queue here, send at the same time as the
+ * corresponding value change event - see #7127
+ */
+ boolean clickEventSent = handleClickEvent(event,
+ targetTdOrTr, false);
+
+ if (event.getButton() == Event.BUTTON_LEFT
+ && isSelectable()) {
+
+ // Ctrl+Shift click
+ if ((event.getCtrlKey() || event.getMetaKey())
+ && event.getShiftKey()
+ && isMultiSelectModeDefault()) {
+ toggleShiftSelection(false);
+ setRowFocus(this);
+
+ // Ctrl click
+ } else if ((event.getCtrlKey() || event
+ .getMetaKey())
+ && isMultiSelectModeDefault()) {
+ boolean wasSelected = isSelected();
+ toggleSelection();
+ setRowFocus(this);
+ /*
+ * next possible range select must start on
+ * this row
+ */
+ selectionRangeStart = this;
+ if (wasSelected) {
+ removeRowFromUnsentSelectionRanges(this);
+ }
+
+ } else if ((event.getCtrlKey() || event
+ .getMetaKey()) && isSingleSelectMode()) {
+ // Ctrl (or meta) click (Single selection)
+ if (!isSelected()
+ || (isSelected() && nullSelectionAllowed)) {
+
+ if (!isSelected()) {
+ deselectAll();
+ }
+
+ toggleSelection();
+ setRowFocus(this);
+ }
+
+ } else if (event.getShiftKey()
+ && isMultiSelectModeDefault()) {
+ // Shift click
+ toggleShiftSelection(true);
+
+ } else {
+ // click
+ boolean currentlyJustThisRowSelected = selectedRowKeys
+ .size() == 1
+ && selectedRowKeys
+ .contains(getKey());
+
+ if (!currentlyJustThisRowSelected) {
+ if (isSingleSelectMode()
+ || isMultiSelectModeDefault()) {
+ /*
+ * For default multi select mode
+ * (ctrl/shift) and for single
+ * select mode we need to clear the
+ * previous selection before
+ * selecting a new one when the user
+ * clicks on a row. Only in
+ * multiselect/simple mode the old
+ * selection should remain after a
+ * normal click.
+ */
+ deselectAll();
+ }
+ toggleSelection();
+ } else if ((isSingleSelectMode() || isMultiSelectModeSimple())
+ && nullSelectionAllowed) {
+ toggleSelection();
+ }/*
+ * else NOP to avoid excessive server
+ * visits (selection is removed with
+ * CTRL/META click)
+ */
+
+ selectionRangeStart = this;
+ setRowFocus(this);
+ }
+
+ // Remove IE text selection hack
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO("onselectstart",
+ null);
+ }
+ // Queue value change
+ sendSelectedRows(false);
+ }
+ /*
+ * Send queued click and value change events if any
+ * If a click event is sent, send value change with
+ * it regardless of the immediate flag, see #7127
+ */
+ if (immediate || clickEventSent) {
+ client.sendPendingVariableChanges();
+ }
+ }
+ break;
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ if (touchStart != null) {
+ /*
+ * Touch has not been handled as neither context or
+ * drag start, handle it as a click.
+ */
+ Util.simulateClickFromTouchEvent(touchStart, this);
+ touchStart = null;
+ }
+ if (contextTouchTimeout != null) {
+ contextTouchTimeout.cancel();
+ }
+ break;
+ case Event.ONTOUCHMOVE:
+ if (isSignificantMove(event)) {
+ /*
+ * TODO figure out scroll delegate don't eat events
+ * if row is selected. Null check for active
+ * delegate is as a workaround.
+ */
+ if (dragmode != 0
+ && touchStart != null
+ && (TouchScrollDelegate
+ .getActiveScrollDelegate() == null)) {
+ startRowDrag(touchStart, type, targetTdOrTr);
+ }
+ if (contextTouchTimeout != null) {
+ contextTouchTimeout.cancel();
+ }
+ /*
+ * Avoid clicks and drags by clearing touch start
+ * flag.
+ */
+ touchStart = null;
+ }
+
+ break;
+ case Event.ONTOUCHSTART:
+ touchStart = event;
+ Touch touch = event.getChangedTouches().get(0);
+ // save position to fields, touches in events are same
+ // isntance during the operation.
+ touchStartX = touch.getClientX();
+ touchStartY = touch.getClientY();
+ /*
+ * Prevent simulated mouse events.
+ */
+ touchStart.preventDefault();
+ if (dragmode != 0 || actionKeys != null) {
+ new Timer() {
+
+ @Override
+ public void run() {
+ TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
+ .getActiveScrollDelegate();
+ /*
+ * If there's a scroll delegate, check if
+ * we're actually scrolling and handle it.
+ * If no delegate, do nothing here and let
+ * the row handle potential drag'n'drop or
+ * context menu.
+ */
+ if (activeScrollDelegate != null) {
+ if (activeScrollDelegate.isMoved()) {
+ /*
+ * Prevent the row from handling
+ * touch move/end events (the
+ * delegate handles those) and from
+ * doing drag'n'drop or opening a
+ * context menu.
+ */
+ touchStart = null;
+ } else {
+ /*
+ * Scrolling hasn't started, so
+ * cancel delegate and let the row
+ * handle potential drag'n'drop or
+ * context menu.
+ */
+ activeScrollDelegate
+ .stopScrolling();
+ }
+ }
+ }
+ }.schedule(TOUCHSCROLL_TIMEOUT);
+
+ if (contextTouchTimeout == null
+ && actionKeys != null) {
+ contextTouchTimeout = new Timer() {
+
+ @Override
+ public void run() {
+ if (touchStart != null) {
+ showContextMenu(touchStart);
+ touchStart = null;
+ }
+ }
+ };
+ }
+ if (contextTouchTimeout != null) {
+ contextTouchTimeout.cancel();
+ contextTouchTimeout
+ .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
+ }
+ }
+ break;
+ case Event.ONMOUSEDOWN:
+ if (targetCellOrRowFound) {
+ setRowFocus(this);
+ ensureFocus();
+ if (dragmode != 0
+ && (event.getButton() == NativeEvent.BUTTON_LEFT)) {
+ startRowDrag(event, type, targetTdOrTr);
+
+ } else if (event.getCtrlKey()
+ || event.getShiftKey()
+ || event.getMetaKey()
+ && isMultiSelectModeDefault()) {
+
+ // Prevent default text selection in Firefox
+ event.preventDefault();
+
+ // Prevent default text selection in IE
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast())
+ .setPropertyJSO(
+ "onselectstart",
+ getPreventTextSelectionIEHack());
+ }
+
+ event.stopPropagation();
+ }
+ }
+ break;
+ case Event.ONMOUSEOUT:
+ break;
+ default:
+ break;
+ }
+ }
+ super.onBrowserEvent(event);
+ }
+
+ private boolean isSignificantMove(Event event) {
+ if (touchStart == null) {
+ // no touch start
+ return false;
+ }
+ /*
+ * TODO calculate based on real distance instead of separate
+ * axis checks
+ */
+ Touch touch = event.getChangedTouches().get(0);
+ if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
+ return true;
+ }
+ if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the row represented by the row key has been selected
+ *
+ * @param key
+ * The generated row key
+ */
+ private boolean rowKeyIsSelected(int rowKey) {
+ // Check single selections
+ if (selectedRowKeys.contains("" + rowKey)) {
+ return true;
+ }
+
+ // Check range selections
+ for (SelectionRange r : selectedRowRanges) {
+ if (r.inRange(getRenderedRowByKey("" + rowKey))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected void startRowDrag(Event event, final int type,
+ Element targetTdOrTr) {
+ VTransferable transferable = new VTransferable();
+ transferable.setDragSource(ConnectorMap.get(client)
+ .getConnector(VScrollTable.this));
+ transferable.setData("itemId", "" + rowKey);
+ NodeList<TableCellElement> cells = rowElement.getCells();
+ for (int i = 0; i < cells.getLength(); i++) {
+ if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
+ HeaderCell headerCell = tHead.getHeaderCell(i);
+ transferable.setData("propertyId", headerCell.cid);
+ break;
+ }
+ }
+
+ VDragEvent ev = VDragAndDropManager.get().startDrag(
+ transferable, event, true);
+ if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
+ && rowKeyIsSelected(rowKey)) {
+
+ // Create a drag image of ALL rows
+ ev.createDragImage(
+ (Element) scrollBody.tBodyElement.cast(), true);
+
+ // Hide rows which are not selected
+ Element dragImage = ev.getDragImage();
+ int i = 0;
+ for (Iterator<Widget> iterator = scrollBody.iterator(); iterator
+ .hasNext();) {
+ VScrollTableRow next = (VScrollTableRow) iterator
+ .next();
+
+ Element child = (Element) dragImage.getChild(i++);
+
+ if (!rowKeyIsSelected(next.rowKey)) {
+ child.getStyle().setVisibility(Visibility.HIDDEN);
+ }
+ }
+ } else {
+ ev.createDragImage(getElement(), true);
+ }
+ if (type == Event.ONMOUSEDOWN) {
+ event.preventDefault();
+ }
+ event.stopPropagation();
+ }
+
+ /**
+ * Finds the TD that the event interacts with. Returns null if the
+ * target of the event should not be handled. If the event target is
+ * the row directly this method returns the TR element instead of
+ * the TD.
+ *
+ * @param event
+ * @return TD or TR element that the event targets (the actual event
+ * target is this element or a child of it)
+ */
+ private Element getEventTargetTdOrTr(Event event) {
+ final Element eventTarget = event.getEventTarget().cast();
+ Widget widget = Util.findWidget(eventTarget, null);
+ final Element thisTrElement = getElement();
+
+ if (widget != this) {
+ /*
+ * This is a workaround to make Labels, read only TextFields
+ * and Embedded in a Table clickable (see #2688). It is
+ * really not a fix as it does not work with a custom read
+ * only components (not extending VLabel/VEmbedded).
+ */
+ while (widget != null && widget.getParent() != this) {
+ widget = widget.getParent();
+ }
+
+ if (!(widget instanceof VLabel)
+ && !(widget instanceof VEmbedded)
+ && !(widget instanceof VTextField && ((VTextField) widget)
+ .isReadOnly())) {
+ return null;
+ }
+ }
+ if (eventTarget == thisTrElement) {
+ // This was a click on the TR element
+ return thisTrElement;
+ }
+
+ // Iterate upwards until we find the TR element
+ Element element = eventTarget;
+ while (element != null
+ && element.getParentElement().cast() != thisTrElement) {
+ element = element.getParentElement().cast();
+ }
+ return element;
+ }
+
+ public void showContextMenu(Event event) {
+ if (enabled && actionKeys != null) {
+ // Show context menu if there are registered action handlers
+ int left = Util.getTouchOrMouseClientX(event);
+ int top = Util.getTouchOrMouseClientY(event);
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ contextMenu = new ContextMenuDetails(getKey(), left, top);
+ client.getContextMenu().showAt(this, left, top);
+ }
+ }
+
+ /**
+ * Has the row been selected?
+ *
+ * @return Returns true if selected, else false
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * Toggle the selection of the row
+ */
+ public void toggleSelection() {
+ selected = !selected;
+ selectionChanged = true;
+ if (selected) {
+ selectedRowKeys.add(String.valueOf(rowKey));
+ addStyleName("v-selected");
+ } else {
+ removeStyleName("v-selected");
+ selectedRowKeys.remove(String.valueOf(rowKey));
+ }
+ }
+
+ /**
+ * Is called when a user clicks an item when holding SHIFT key down.
+ * This will select a new range from the last focused row
+ *
+ * @param deselectPrevious
+ * Should the previous selected range be deselected
+ */
+ private void toggleShiftSelection(boolean deselectPrevious) {
+
+ /*
+ * Ensures that we are in multiselect mode and that we have a
+ * previous selection which was not a deselection
+ */
+ if (isSingleSelectMode()) {
+ // No previous selection found
+ deselectAll();
+ toggleSelection();
+ return;
+ }
+
+ // Set the selectable range
+ VScrollTableRow endRow = this;
+ VScrollTableRow startRow = selectionRangeStart;
+ if (startRow == null) {
+ startRow = focusedRow;
+ // If start row is null then we have a multipage selection
+ // from
+ // above
+ if (startRow == null) {
+ startRow = (VScrollTableRow) scrollBody.iterator()
+ .next();
+ setRowFocus(endRow);
+ }
+ }
+ // Deselect previous items if so desired
+ if (deselectPrevious) {
+ deselectAll();
+ }
+
+ // we'll ensure GUI state from top down even though selection
+ // was the opposite way
+ if (!startRow.isBefore(endRow)) {
+ VScrollTableRow tmp = startRow;
+ startRow = endRow;
+ endRow = tmp;
+ }
+ SelectionRange range = new SelectionRange(startRow, endRow);
+
+ for (Widget w : scrollBody) {
+ VScrollTableRow row = (VScrollTableRow) w;
+ if (range.inRange(row)) {
+ if (!row.isSelected()) {
+ row.toggleSelection();
+ }
+ selectedRowKeys.add(row.getKey());
+ }
+ }
+
+ // Add range
+ if (startRow != endRow) {
+ selectedRowRanges.add(range);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.ui.IActionOwner#getActions ()
+ */
+
+ @Override
+ public Action[] getActions() {
+ if (actionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[actionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = actionKeys[i];
+ final TreeAction a = new TreeAction(this,
+ String.valueOf(rowKey), actionKey) {
+
+ @Override
+ public void execute() {
+ super.execute();
+ lazyRevertFocusToRow(VScrollTableRow.this);
+ }
+ };
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ @Override
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ private int getColIndexOf(Widget child) {
+ com.google.gwt.dom.client.Element widgetCell = child
+ .getElement().getParentElement().getParentElement();
+ NodeList<TableCellElement> cells = rowElement.getCells();
+ for (int i = 0; i < cells.getLength(); i++) {
+ if (cells.getItem(i) == widgetCell) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public Widget getWidgetForPaintable() {
+ return this;
+ }
+ }
+
+ protected class VScrollTableGeneratedRow extends VScrollTableRow {
+
+ private boolean spanColumns;
+ private boolean htmlContentAllowed;
+
+ public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
+ super(uidl, aligns);
+ addStyleName("v-table-generated-row");
+ }
+
+ public boolean isSpanColumns() {
+ return spanColumns;
+ }
+
+ @Override
+ protected void initCellWidths() {
+ if (spanColumns) {
+ setSpannedColumnWidthAfterDOMFullyInited();
+ } else {
+ super.initCellWidths();
+ }
+ }
+
+ private void setSpannedColumnWidthAfterDOMFullyInited() {
+ // Defer setting width on spanned columns to make sure that
+ // they are added to the DOM before trying to calculate
+ // widths.
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ if (showRowHeaders) {
+ setCellWidth(0, tHead.getHeaderCell(0).getWidth());
+ calcAndSetSpanWidthOnCell(1);
+ } else {
+ calcAndSetSpanWidthOnCell(0);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected boolean isRenderHtmlInCells() {
+ return htmlContentAllowed;
+ }
+
+ @Override
+ protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+ int visibleColumnIndex) {
+ htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
+ spanColumns = uidl.getBooleanAttribute("gen_span");
+
+ final Iterator<?> cells = uidl.getChildIterator();
+ if (spanColumns) {
+ int colCount = uidl.getChildCount();
+ if (cells.hasNext()) {
+ final Object cell = cells.next();
+ if (cell instanceof String) {
+ addSpannedCell(uidl, cell.toString(), aligns[0],
+ "", htmlContentAllowed, false, null,
+ colCount);
+ } else {
+ addSpannedCell(uidl, (Widget) cell, aligns[0], "",
+ false, colCount);
+ }
+ }
+ } else {
+ super.addCellsFromUIDL(uidl, aligns, col,
+ visibleColumnIndex);
+ }
+ }
+
+ private void addSpannedCell(UIDL rowUidl, Widget w, char align,
+ String style, boolean sorted, int colCount) {
+ TableCellElement td = DOM.createTD().cast();
+ td.setColSpan(colCount);
+ initCellWithWidget(w, align, style, sorted, td);
+ }
+
+ private void addSpannedCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML, boolean sorted,
+ String description, int colCount) {
+ // String only content is optimized by not using Label widget
+ final TableCellElement td = DOM.createTD().cast();
+ td.setColSpan(colCount);
+ initCellWithText(text, align, style, textIsHTML, sorted,
+ description, td);
+ }
+
+ @Override
+ protected void setCellWidth(int cellIx, int width) {
+ if (isSpanColumns()) {
+ if (showRowHeaders) {
+ if (cellIx == 0) {
+ super.setCellWidth(0, width);
+ } else {
+ // We need to recalculate the spanning TDs width for
+ // every cellIx in order to support column resizing.
+ calcAndSetSpanWidthOnCell(1);
+ }
+ } else {
+ // Same as above.
+ calcAndSetSpanWidthOnCell(0);
+ }
+ } else {
+ super.setCellWidth(cellIx, width);
+ }
+ }
+
+ private void calcAndSetSpanWidthOnCell(final int cellIx) {
+ int spanWidth = 0;
+ for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
+ .getVisibleCellCount(); ix++) {
+ spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
+ }
+ Util.setWidthExcludingPaddingAndBorder((Element) getElement()
+ .getChild(cellIx), spanWidth, 13, false);
+ }
+ }
+
+ /**
+ * Ensure the component has a focus.
+ *
+ * TODO the current implementation simply always calls focus for the
+ * component. In case the Table at some point implements focus/blur
+ * listeners, this method needs to be evolved to conditionally call
+ * focus only if not currently focused.
+ */
+ protected void ensureFocus() {
+ if (!hasFocus) {
+ scrollBodyPanel.setFocus(true);
+ }
+
+ }
+
+ }
+
+ /**
+ * Deselects all items
+ */
+ public void deselectAll() {
+ for (Widget w : scrollBody) {
+ VScrollTableRow row = (VScrollTableRow) w;
+ if (row.isSelected()) {
+ row.toggleSelection();
+ }
+ }
+ // still ensure all selects are removed from (not necessary rendered)
+ selectedRowKeys.clear();
+ selectedRowRanges.clear();
+ // also notify server that it clears all previous selections (the client
+ // side does not know about the invisible ones)
+ instructServerToForgetPreviousSelections();
+ }
+
+ /**
+ * Used in multiselect mode when the client side knows that all selections
+ * are in the next request.
+ */
+ private void instructServerToForgetPreviousSelections() {
+ client.updateVariable(paintableId, "clearSelections", true, false);
+ }
+
+ /**
+ * Determines the pagelength when the table height is fixed.
+ */
+ public void updatePageLength() {
+ // Only update if visible and enabled
+ if (!isVisible() || !enabled) {
+ return;
+ }
+
+ if (scrollBody == null) {
+ return;
+ }
+
+ if (isDynamicHeight()) {
+ return;
+ }
+
+ int rowHeight = (int) Math.round(scrollBody.getRowHeight());
+ int bodyH = scrollBodyPanel.getOffsetHeight();
+ int rowsAtOnce = bodyH / rowHeight;
+ boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
+ if (anotherPartlyVisible) {
+ rowsAtOnce++;
+ }
+ if (pageLength != rowsAtOnce) {
+ pageLength = rowsAtOnce;
+ client.updateVariable(paintableId, "pagelength", pageLength, false);
+
+ if (!rendering) {
+ int currentlyVisible = scrollBody.lastRendered
+ - scrollBody.firstRendered;
+ if (currentlyVisible < pageLength
+ && currentlyVisible < totalRows) {
+ // shake scrollpanel to fill empty space
+ scrollBodyPanel.setScrollPosition(scrollTop + 1);
+ scrollBodyPanel.setScrollPosition(scrollTop - 1);
+ }
+
+ sizeNeedsInit = true;
+ }
+ }
+
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateWidth() {
+ if (!isVisible()) {
+ /*
+ * Do not update size when the table is hidden as all column widths
+ * will be set to zero and they won't be recalculated when the table
+ * is set visible again (until the size changes again)
+ */
+ return;
+ }
+
+ if (!isDynamicWidth()) {
+ int innerPixels = getOffsetWidth() - getBorderWidth();
+ if (innerPixels < 0) {
+ innerPixels = 0;
+ }
+ setContentWidth(innerPixels);
+
+ // readjust undefined width columns
+ triggerLazyColumnAdjustment(false);
+
+ } else {
+
+ sizeNeedsInit = true;
+
+ // readjust undefined width columns
+ triggerLazyColumnAdjustment(false);
+ }
+
+ /*
+ * setting width may affect wheter the component has scrollbars -> needs
+ * scrolling or not
+ */
+ setProperTabIndex();
+ }
+
+ private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
+
+ private final Timer lazyAdjustColumnWidths = new Timer() {
+ /**
+ * Check for column widths, and available width, to see if we can fix
+ * column widths "optimally". Doing this lazily to avoid expensive
+ * calculation when resizing is not yet finished.
+ */
+
+ @Override
+ public void run() {
+ if (scrollBody == null) {
+ // Try again later if we get here before scrollBody has been
+ // initalized
+ triggerLazyColumnAdjustment(false);
+ return;
+ }
+
+ Iterator<Widget> headCells = tHead.iterator();
+ int usedMinimumWidth = 0;
+ int totalExplicitColumnsWidths = 0;
+ float expandRatioDivider = 0;
+ int colIndex = 0;
+ while (headCells.hasNext()) {
+ final HeaderCell hCell = (HeaderCell) headCells.next();
+ if (hCell.isDefinedWidth()) {
+ totalExplicitColumnsWidths += hCell.getWidth();
+ usedMinimumWidth += hCell.getWidth();
+ } else {
+ usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
+ expandRatioDivider += hCell.getExpandRatio();
+ }
+ colIndex++;
+ }
+
+ int availW = scrollBody.getAvailableWidth();
+ // Hey IE, are you really sure about this?
+ availW = scrollBody.getAvailableWidth();
+ int visibleCellCount = tHead.getVisibleCellCount();
+ int totalExtraWidth = scrollBody.getCellExtraWidth()
+ * visibleCellCount;
+ if (willHaveScrollbars()) {
+ totalExtraWidth += Util.getNativeScrollbarSize();
+ }
+ availW -= totalExtraWidth;
+ int forceScrollBodyWidth = -1;
+
+ int extraSpace = availW - usedMinimumWidth;
+ if (extraSpace < 0) {
+ if (getTotalRows() == 0) {
+ /*
+ * Too wide header combined with no rows in the table.
+ *
+ * No horizontal scrollbars would be displayed because
+ * there's no rows that grows too wide causing the
+ * scrollBody container div to overflow. Must explicitely
+ * force a width to a scrollbar. (see #9187)
+ */
+ forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
+ }
+ extraSpace = 0;
+ }
+
+ if (forceScrollBodyWidth > 0) {
+ scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
+ Unit.PX);
+ } else {
+ // Clear width that might have been set to force horizontal
+ // scrolling if there are no rows
+ scrollBody.container.getStyle().clearWidth();
+ }
+
+ int totalUndefinedNaturalWidths = usedMinimumWidth
+ - totalExplicitColumnsWidths;
+
+ // we have some space that can be divided optimally
+ HeaderCell hCell;
+ colIndex = 0;
+ headCells = tHead.iterator();
+ int checksum = 0;
+ while (headCells.hasNext()) {
+ hCell = (HeaderCell) headCells.next();
+ if (!hCell.isDefinedWidth()) {
+ int w = hCell.getNaturalColumnWidth(colIndex);
+ int newSpace;
+ if (expandRatioDivider > 0) {
+ // divide excess space by expand ratios
+ newSpace = Math.round((w + extraSpace
+ * hCell.getExpandRatio() / expandRatioDivider));
+ } else {
+ if (totalUndefinedNaturalWidths != 0) {
+ // divide relatively to natural column widths
+ newSpace = Math.round(w + (float) extraSpace
+ * (float) w / totalUndefinedNaturalWidths);
+ } else {
+ newSpace = w;
+ }
+ }
+ checksum += newSpace;
+ setColWidth(colIndex, newSpace, false);
+ } else {
+ checksum += hCell.getWidth();
+ }
+ colIndex++;
+ }
+
+ if (extraSpace > 0 && checksum != availW) {
+ /*
+ * There might be in some cases a rounding error of 1px when
+ * extra space is divided so if there is one then we give the
+ * first undefined column 1 more pixel
+ */
+ headCells = tHead.iterator();
+ colIndex = 0;
+ while (headCells.hasNext()) {
+ HeaderCell hc = (HeaderCell) headCells.next();
+ if (!hc.isDefinedWidth()) {
+ setColWidth(colIndex,
+ hc.getWidth() + availW - checksum, false);
+ break;
+ }
+ colIndex++;
+ }
+ }
+
+ if (isDynamicHeight() && totalRows == pageLength) {
+ // fix body height (may vary if lazy loading is offhorizontal
+ // scrollbar appears/disappears)
+ int bodyHeight = scrollBody.getRequiredHeight();
+ boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
+ if (needsSpaceForHorizontalScrollbar) {
+ bodyHeight += Util.getNativeScrollbarSize();
+ }
+ int heightBefore = getOffsetHeight();
+ scrollBodyPanel.setHeight(bodyHeight + "px");
+ if (heightBefore != getOffsetHeight()) {
+ Util.notifyParentOfSizeChange(VScrollTable.this, false);
+ }
+ }
+ scrollBody.reLayoutComponents();
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+ }
+ });
+
+ forceRealignColumnHeaders();
+ }
+
+ };
+
+ private void forceRealignColumnHeaders() {
+ if (BrowserInfo.get().isIE()) {
+ /*
+ * IE does not fire onscroll event if scroll position is reverted to
+ * 0 due to the content element size growth. Ensure headers are in
+ * sync with content manually. Safe to use null event as we don't
+ * actually use the event object in listener.
+ */
+ onScroll(null);
+ }
+ }
+
+ /**
+ * helper to set pixel size of head and body part
+ *
+ * @param pixels
+ */
+ private void setContentWidth(int pixels) {
+ tHead.setWidth(pixels + "px");
+ scrollBodyPanel.setWidth(pixels + "px");
+ tFoot.setWidth(pixels + "px");
+ }
+
+ private int borderWidth = -1;
+
+ /**
+ * @return border left + border right
+ */
+ private int getBorderWidth() {
+ if (borderWidth < 0) {
+ borderWidth = Util.measureHorizontalPaddingAndBorder(
+ scrollBodyPanel.getElement(), 2);
+ if (borderWidth < 0) {
+ borderWidth = 0;
+ }
+ }
+ return borderWidth;
+ }
+
+ /**
+ * Ensures scrollable area is properly sized. This method is used when fixed
+ * size is used.
+ */
+ private int containerHeight;
+
+ private void setContainerHeight() {
+ if (!isDynamicHeight()) {
+ containerHeight = getOffsetHeight();
+ containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
+ containerHeight -= tFoot.getOffsetHeight();
+ containerHeight -= getContentAreaBorderHeight();
+ if (containerHeight < 0) {
+ containerHeight = 0;
+ }
+ scrollBodyPanel.setHeight(containerHeight + "px");
+ }
+ }
+
+ private int contentAreaBorderHeight = -1;
+ private int scrollLeft;
+ private int scrollTop;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VScrollTableDropHandler dropHandler;
+
+ private boolean navKeyDown;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean multiselectPending;
+
+ /**
+ * @return border top + border bottom of the scrollable area of table
+ */
+ private int getContentAreaBorderHeight() {
+ if (contentAreaBorderHeight < 0) {
+
+ DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
+ "hidden");
+ int oh = scrollBodyPanel.getOffsetHeight();
+ int ch = scrollBodyPanel.getElement()
+ .getPropertyInt("clientHeight");
+ contentAreaBorderHeight = oh - ch;
+ DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
+ "auto");
+ }
+ return contentAreaBorderHeight;
+ }
+
+ @Override
+ public void setHeight(String height) {
+ if (height.length() == 0
+ && getElement().getStyle().getHeight().length() != 0) {
+ /*
+ * Changing from defined to undefined size -> should do a size init
+ * to take page length into account again
+ */
+ sizeNeedsInit = true;
+ }
+ super.setHeight(height);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateHeight() {
+ setContainerHeight();
+
+ if (initializedAndAttached) {
+ updatePageLength();
+ }
+ if (!rendering) {
+ // Webkit may sometimes get an odd rendering bug (white space
+ // between header and body), see bug #3875. Running
+ // overflow hack here to shake body element a bit.
+ // We must run the fix as a deferred command to prevent it from
+ // overwriting the scroll position with an outdated value, see
+ // #7607.
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+ }
+ });
+ }
+
+ triggerLazyColumnAdjustment(false);
+
+ /*
+ * setting height may affect wheter the component has scrollbars ->
+ * needs scrolling or not
+ */
+ setProperTabIndex();
+
+ }
+
+ /*
+ * Overridden due Table might not survive of visibility change (scroll pos
+ * lost). Example ITabPanel just set contained components invisible and back
+ * when changing tabs.
+ */
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (isVisible() != visible) {
+ super.setVisible(visible);
+ if (initializedAndAttached) {
+ if (visible) {
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ scrollBodyPanel
+ .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function to build html snippet for column or row headers
+ *
+ * @param uidl
+ * possibly with values caption and icon
+ * @return html snippet containing possibly an icon + caption text
+ */
+ protected String buildCaptionHtmlSnippet(UIDL uidl) {
+ String s = uidl.hasAttribute("caption") ? uidl
+ .getStringAttribute("caption") : "";
+ if (uidl.hasAttribute("icon")) {
+ s = "<img src=\""
+ + Util.escapeAttribute(client.translateVaadinUri(uidl
+ .getStringAttribute("icon")))
+ + "\" alt=\"icon\" class=\"v-icon\">" + s;
+ }
+ return s;
+ }
+
+ /**
+ * This method has logic which rows needs to be requested from server when
+ * user scrolls
+ */
+
+ @Override
+ public void onScroll(ScrollEvent event) {
+ scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
+ scrollTop = scrollBodyPanel.getScrollPosition();
+ /*
+ * #6970 - IE sometimes fires scroll events for a detached table.
+ *
+ * FIXME initializedAndAttached should probably be renamed - its name
+ * doesn't seem to reflect its semantics. onDetach() doesn't set it to
+ * false, and changing that might break something else, so we need to
+ * check isAttached() separately.
+ */
+ if (!initializedAndAttached || !isAttached()) {
+ return;
+ }
+ if (!enabled) {
+ scrollBodyPanel
+ .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
+ return;
+ }
+
+ rowRequestHandler.cancel();
+
+ if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
+ // due to the webkitoverflowworkaround, top may sometimes report 0
+ // for webkit, although it really is not. Expecting to have the
+ // correct
+ // value available soon.
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ onScroll(null);
+ }
+ });
+ return;
+ }
+
+ // fix headers horizontal scrolling
+ tHead.setHorizontalScrollPosition(scrollLeft);
+
+ // fix footers horizontal scrolling
+ tFoot.setHorizontalScrollPosition(scrollLeft);
+
+ firstRowInViewPort = calcFirstRowInViewPort();
+ if (firstRowInViewPort > totalRows - pageLength) {
+ firstRowInViewPort = totalRows - pageLength;
+ }
+
+ int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
+ * cache_react_rate);
+ if (postLimit > totalRows - 1) {
+ postLimit = totalRows - 1;
+ }
+ int preLimit = (int) (firstRowInViewPort - pageLength
+ * cache_react_rate);
+ if (preLimit < 0) {
+ preLimit = 0;
+ }
+ final int lastRendered = scrollBody.getLastRendered();
+ final int firstRendered = scrollBody.getFirstRendered();
+
+ if (postLimit <= lastRendered && preLimit >= firstRendered) {
+ // we're within no-react area, no need to request more rows
+ // remember which firstvisible we requested, in case the server has
+ // a differing opinion
+ lastRequestedFirstvisible = firstRowInViewPort;
+ client.updateVariable(paintableId, "firstvisible",
+ firstRowInViewPort, false);
+ return;
+ }
+
+ if (firstRowInViewPort - pageLength * cache_rate > lastRendered
+ || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
+ // need a totally new set of rows
+ rowRequestHandler
+ .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
+ int last = firstRowInViewPort + (int) (cache_rate * pageLength)
+ + pageLength - 1;
+ if (last >= totalRows) {
+ last = totalRows - 1;
+ }
+ rowRequestHandler.setReqRows(last
+ - rowRequestHandler.getReqFirstRow() + 1);
+ rowRequestHandler.deferRowFetch();
+ return;
+ }
+ if (preLimit < firstRendered) {
+ // need some rows to the beginning of the rendered area
+ rowRequestHandler
+ .setReqFirstRow((int) (firstRowInViewPort - pageLength
+ * cache_rate));
+ rowRequestHandler.setReqRows(firstRendered
+ - rowRequestHandler.getReqFirstRow());
+ rowRequestHandler.deferRowFetch();
+
+ return;
+ }
+ if (postLimit > lastRendered) {
+ // need some rows to the end of the rendered area
+ rowRequestHandler.setReqFirstRow(lastRendered + 1);
+ rowRequestHandler.setReqRows((int) ((firstRowInViewPort
+ + pageLength + pageLength * cache_rate) - lastRendered));
+ rowRequestHandler.deferRowFetch();
+ }
+ }
+
+ protected int calcFirstRowInViewPort() {
+ return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
+ }
+
+ @Override
+ public VScrollTableDropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ private static class TableDDDetails {
+ int overkey = -1;
+ VerticalDropLocation dropLocation;
+ String colkey;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof TableDDDetails) {
+ TableDDDetails other = (TableDDDetails) obj;
+ return dropLocation == other.dropLocation
+ && overkey == other.overkey
+ && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
+ }
+ return false;
+ }
+
+ //
+ // public int hashCode() {
+ // return overkey;
+ // }
+ }
+
+ public class VScrollTableDropHandler extends VAbstractDropHandler {
+
+ private static final String ROWSTYLEBASE = "v-table-row-drag-";
+ private TableDDDetails dropDetails;
+ private TableDDDetails lastEmphasized;
+
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ updateDropDetails(drag);
+ super.dragEnter(drag);
+ }
+
+ private void updateDropDetails(VDragEvent drag) {
+ dropDetails = new TableDDDetails();
+ Element elementOver = drag.getElementOver();
+
+ VScrollTableRow row = Util.findWidget(elementOver, getRowClass());
+ if (row != null) {
+ dropDetails.overkey = row.rowKey;
+ Element tr = row.getElement();
+ Element element = elementOver;
+ while (element != null && element.getParentElement() != tr) {
+ element = (Element) element.getParentElement();
+ }
+ int childIndex = DOM.getChildIndex(tr, element);
+ dropDetails.colkey = tHead.getHeaderCell(childIndex)
+ .getColKey();
+ dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
+ row.getElement(), drag.getCurrentGwtEvent(), 0.2);
+ }
+
+ drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
+ drag.getDropDetails().put(
+ "detail",
+ dropDetails.dropLocation != null ? dropDetails.dropLocation
+ .toString() : null);
+
+ }
+
+ private Class<? extends Widget> getRowClass() {
+ // get the row type this way to make dd work in derived
+ // implementations
+ return scrollBody.iterator().next().getClass();
+ }
+
+ @Override
+ public void dragOver(VDragEvent drag) {
+ TableDDDetails oldDetails = dropDetails;
+ updateDropDetails(drag);
+ if (!oldDetails.equals(dropDetails)) {
+ deEmphasis();
+ final TableDDDetails newDetails = dropDetails;
+ VAcceptCallback cb = new VAcceptCallback() {
+
+ @Override
+ public void accepted(VDragEvent event) {
+ if (newDetails.equals(dropDetails)) {
+ dragAccepted(event);
+ }
+ /*
+ * Else new target slot already defined, ignore
+ */
+ }
+ };
+ validate(cb, drag);
+ }
+ }
+
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis();
+ super.dragLeave(drag);
+ }
+
+ @Override
+ public boolean drop(VDragEvent drag) {
+ deEmphasis();
+ return super.drop(drag);
+ }
+
+ private void deEmphasis() {
+ UIObject.setStyleName(getElement(),
+ VScrollTable.this.getStylePrimaryName() + "-drag", false);
+ if (lastEmphasized == null) {
+ return;
+ }
+ for (Widget w : scrollBody.renderedRows) {
+ VScrollTableRow row = (VScrollTableRow) w;
+ if (lastEmphasized != null
+ && row.rowKey == lastEmphasized.overkey) {
+ String stylename = ROWSTYLEBASE
+ + lastEmphasized.dropLocation.toString()
+ .toLowerCase();
+ VScrollTableRow.setStyleName(row.getElement(), stylename,
+ false);
+ lastEmphasized = null;
+ return;
+ }
+ }
+ }
+
+ /**
+ * TODO needs different drop modes ?? (on cells, on rows), now only
+ * supports rows
+ */
+ private void emphasis(TableDDDetails details) {
+ deEmphasis();
+ UIObject.setStyleName(getElement(),
+ VScrollTable.this.getStylePrimaryName() + "-drag", true);
+ // iterate old and new emphasized row
+ for (Widget w : scrollBody.renderedRows) {
+ VScrollTableRow row = (VScrollTableRow) w;
+ if (details != null && details.overkey == row.rowKey) {
+ String stylename = ROWSTYLEBASE
+ + details.dropLocation.toString().toLowerCase();
+ VScrollTableRow.setStyleName(row.getElement(), stylename,
+ true);
+ lastEmphasized = details;
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ emphasis(dropDetails);
+ }
+
+ @Override
+ public ComponentConnector getConnector() {
+ return ConnectorMap.get(client).getConnector(VScrollTable.this);
+ }
+
+ @Override
+ public ApplicationConnection getApplicationConnection() {
+ return client;
+ }
+
+ }
+
+ protected VScrollTableRow getFocusedRow() {
+ return focusedRow;
+ }
+
+ /**
+ * Moves the selection head to a specific row
+ *
+ * @param row
+ * The row to where the selection head should move
+ * @return Returns true if focus was moved successfully, else false
+ */
+ public boolean setRowFocus(VScrollTableRow row) {
+
+ if (!isSelectable()) {
+ return false;
+ }
+
+ // Remove previous selection
+ if (focusedRow != null && focusedRow != row) {
+ focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
+ }
+
+ if (row != null) {
+
+ // Apply focus style to new selection
+ row.addStyleName(getStylePrimaryName() + "-focus");
+
+ /*
+ * Trying to set focus on already focused row
+ */
+ if (row == focusedRow) {
+ return false;
+ }
+
+ // Set new focused row
+ focusedRow = row;
+
+ ensureRowIsVisible(row);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Ensures that the row is visible
+ *
+ * @param row
+ * The row to ensure is visible
+ */
+ private void ensureRowIsVisible(VScrollTableRow row) {
+ if (BrowserInfo.get().isTouchDevice()) {
+ // Skip due to android devices that have broken scrolltop will may
+ // get odd scrolling here.
+ return;
+ }
+ Util.scrollIntoViewVertically(row.getElement());
+ }
+
+ /**
+ * Handles the keyboard events handled by the table
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
+ // Do not handle tab key
+ return false;
+ }
+
+ // Down navigation
+ if (!isSelectable() && keycode == getNavigationDownKey()) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition() + scrollingVelocity);
+ return true;
+ } else if (keycode == getNavigationDownKey()) {
+ if (isMultiSelectModeAny() && moveFocusDown()) {
+ selectFocusedRow(ctrl, shift);
+
+ } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
+ selectFocusedRow(ctrl, shift);
+ }
+ return true;
+ }
+
+ // Up navigation
+ if (!isSelectable() && keycode == getNavigationUpKey()) {
+ scrollBodyPanel.setScrollPosition(scrollBodyPanel
+ .getScrollPosition() - scrollingVelocity);
+ return true;
+ } else if (keycode == getNavigationUpKey()) {
+ if (isMultiSelectModeAny() && moveFocusUp()) {
+ selectFocusedRow(ctrl, shift);
+ } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
+ selectFocusedRow(ctrl, shift);
+ }
+ return true;
+ }
+
+ if (keycode == getNavigationLeftKey()) {
+ // Left navigation
+ scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+ .getHorizontalScrollPosition() - scrollingVelocity);
+ return true;
+
+ } else if (keycode == getNavigationRightKey()) {
+ // Right navigation
+ scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+ .getHorizontalScrollPosition() + scrollingVelocity);
+ return true;
+ }
+
+ // Select navigation
+ if (isSelectable() && keycode == getNavigationSelectKey()) {
+ if (isSingleSelectMode()) {
+ boolean wasSelected = focusedRow.isSelected();
+ deselectAll();
+ if (!wasSelected || !nullSelectionAllowed) {
+ focusedRow.toggleSelection();
+ }
+ } else {
+ focusedRow.toggleSelection();
+ removeRowFromUnsentSelectionRanges(focusedRow);
+ }
+
+ sendSelectedRows();
+ return true;
+ }
+
+ // Page Down navigation
+ if (keycode == getNavigationPageDownKey()) {
+ if (isSelectable()) {
+ /*
+ * If selectable we plagiate MSW behaviour: first scroll to the
+ * end of current view. If at the end, scroll down one page
+ * length and keep the selected row in the bottom part of
+ * visible area.
+ */
+ if (!isFocusAtTheEndOfTable()) {
+ VScrollTableRow lastVisibleRowInViewPort = scrollBody
+ .getRowByRowIndex(firstRowInViewPort
+ + getFullyVisibleRowCount() - 1);
+ if (lastVisibleRowInViewPort != null
+ && lastVisibleRowInViewPort != focusedRow) {
+ // focused row is not at the end of the table, move
+ // focus and select the last visible row
+ setRowFocus(lastVisibleRowInViewPort);
+ selectFocusedRow(ctrl, shift);
+ sendSelectedRows();
+ } else {
+ int indexOfToBeFocused = focusedRow.getIndex()
+ + getFullyVisibleRowCount();
+ if (indexOfToBeFocused >= totalRows) {
+ indexOfToBeFocused = totalRows - 1;
+ }
+ VScrollTableRow toBeFocusedRow = scrollBody
+ .getRowByRowIndex(indexOfToBeFocused);
+
+ if (toBeFocusedRow != null) {
+ /*
+ * if the next focused row is rendered
+ */
+ setRowFocus(toBeFocusedRow);
+ selectFocusedRow(ctrl, shift);
+ // TODO needs scrollintoview ?
+ sendSelectedRows();
+ } else {
+ // scroll down by pixels and return, to wait for
+ // new rows, then select the last item in the
+ // viewport
+ selectLastItemInNextRender = true;
+ multiselectPending = shift;
+ scrollByPagelenght(1);
+ }
+ }
+ }
+ } else {
+ /* No selections, go page down by scrolling */
+ scrollByPagelenght(1);
+ }
+ return true;
+ }
+
+ // Page Up navigation
+ if (keycode == getNavigationPageUpKey()) {
+ if (isSelectable()) {
+ /*
+ * If selectable we plagiate MSW behaviour: first scroll to the
+ * end of current view. If at the end, scroll down one page
+ * length and keep the selected row in the bottom part of
+ * visible area.
+ */
+ if (!isFocusAtTheBeginningOfTable()) {
+ VScrollTableRow firstVisibleRowInViewPort = scrollBody
+ .getRowByRowIndex(firstRowInViewPort);
+ if (firstVisibleRowInViewPort != null
+ && firstVisibleRowInViewPort != focusedRow) {
+ // focus is not at the beginning of the table, move
+ // focus and select the first visible row
+ setRowFocus(firstVisibleRowInViewPort);
+ selectFocusedRow(ctrl, shift);
+ sendSelectedRows();
+ } else {
+ int indexOfToBeFocused = focusedRow.getIndex()
+ - getFullyVisibleRowCount();
+ if (indexOfToBeFocused < 0) {
+ indexOfToBeFocused = 0;
+ }
+ VScrollTableRow toBeFocusedRow = scrollBody
+ .getRowByRowIndex(indexOfToBeFocused);
+
+ if (toBeFocusedRow != null) { // if the next focused row
+ // is rendered
+ setRowFocus(toBeFocusedRow);
+ selectFocusedRow(ctrl, shift);
+ // TODO needs scrollintoview ?
+ sendSelectedRows();
+ } else {
+ // unless waiting for the next rowset already
+ // scroll down by pixels and return, to wait for
+ // new rows, then select the last item in the
+ // viewport
+ selectFirstItemInNextRender = true;
+ multiselectPending = shift;
+ scrollByPagelenght(-1);
+ }
+ }
+ }
+ } else {
+ /* No selections, go page up by scrolling */
+ scrollByPagelenght(-1);
+ }
+
+ return true;
+ }
+
+ // Goto start navigation
+ if (keycode == getNavigationStartKey()) {
+ scrollBodyPanel.setScrollPosition(0);
+ if (isSelectable()) {
+ if (focusedRow != null && focusedRow.getIndex() == 0) {
+ return false;
+ } else {
+ VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
+ .iterator().next();
+ if (rowByRowIndex.getIndex() == 0) {
+ setRowFocus(rowByRowIndex);
+ selectFocusedRow(ctrl, shift);
+ sendSelectedRows();
+ } else {
+ // first row of table will come in next row fetch
+ if (ctrl) {
+ focusFirstItemInNextRender = true;
+ } else {
+ selectFirstItemInNextRender = true;
+ multiselectPending = shift;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ // Goto end navigation
+ if (keycode == getNavigationEndKey()) {
+ scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
+ if (isSelectable()) {
+ final int lastRendered = scrollBody.getLastRendered();
+ if (lastRendered + 1 == totalRows) {
+ VScrollTableRow rowByRowIndex = scrollBody
+ .getRowByRowIndex(lastRendered);
+ if (focusedRow != rowByRowIndex) {
+ setRowFocus(rowByRowIndex);
+ selectFocusedRow(ctrl, shift);
+ sendSelectedRows();
+ }
+ } else {
+ if (ctrl) {
+ focusLastItemInNextRender = true;
+ } else {
+ selectLastItemInNextRender = true;
+ multiselectPending = shift;
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isFocusAtTheBeginningOfTable() {
+ return focusedRow.getIndex() == 0;
+ }
+
+ private boolean isFocusAtTheEndOfTable() {
+ return focusedRow.getIndex() + 1 >= totalRows;
+ }
+
+ private int getFullyVisibleRowCount() {
+ return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody
+ .getRowHeight());
+ }
+
+ private void scrollByPagelenght(int i) {
+ int pixels = i * scrollBodyPanel.getOffsetHeight();
+ int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
+ if (newPixels < 0) {
+ newPixels = 0;
+ } // else if too high, NOP (all know browsers accept illegally big
+ // values here)
+ scrollBodyPanel.setScrollPosition(newPixels);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (isFocusable()) {
+ hasFocus = true;
+
+ // Focus a row if no row is in focus
+ if (focusedRow == null) {
+ focusRowFromBody();
+ } else {
+ setRowFocus(focusedRow);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ hasFocus = false;
+ navKeyDown = false;
+
+ if (BrowserInfo.get().isIE()) {
+ // IE sometimes moves focus to a clicked table cell...
+ Element focusedElement = Util.getIEFocusedElement();
+ if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) {
+ // ..in that case, steal the focus back to the focus handler
+ // but not if focus is in a child component instead (#7965)
+ focus();
+ return;
+ }
+ }
+
+ if (isFocusable()) {
+ // Unfocus any row
+ setRowFocus(null);
+ }
+ }
+
+ /**
+ * Removes a key from a range if the key is found in a selected range
+ *
+ * @param key
+ * The key to remove
+ */
+ private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
+ Collection<SelectionRange> newRanges = null;
+ for (Iterator<SelectionRange> iterator = selectedRowRanges.iterator(); iterator
+ .hasNext();) {
+ SelectionRange range = iterator.next();
+ if (range.inRange(row)) {
+ // Split the range if given row is in range
+ Collection<SelectionRange> splitranges = range.split(row);
+ if (newRanges == null) {
+ newRanges = new ArrayList<SelectionRange>();
+ }
+ newRanges.addAll(splitranges);
+ iterator.remove();
+ }
+ }
+ if (newRanges != null) {
+ selectedRowRanges.addAll(newRanges);
+ }
+ }
+
+ /**
+ * Can the Table be focused?
+ *
+ * @return True if the table can be focused, else false
+ */
+ public boolean isFocusable() {
+ if (scrollBody != null && enabled) {
+ return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable());
+ }
+ return false;
+ }
+
+ private boolean hasHorizontalScrollbar() {
+ return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
+ }
+
+ private boolean hasVerticalScrollbar() {
+ return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.Focusable#focus()
+ */
+
+ @Override
+ public void focus() {
+ if (isFocusable()) {
+ scrollBodyPanel.focus();
+ }
+ }
+
+ /**
+ * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the
+ * component).
+ * <p>
+ * If the component has no explicit tabIndex a zero is given (default
+ * tabbing order based on dom hierarchy) or -1 if the component does not
+ * need to gain focus. The component needs no focus if it has no scrollabars
+ * (not scrollable) and not selectable. Note that in the future shortcut
+ * actions may need focus.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void setProperTabIndex() {
+ int storedScrollTop = 0;
+ int storedScrollLeft = 0;
+
+ if (BrowserInfo.get().getOperaVersion() >= 11) {
+ // Workaround for Opera scroll bug when changing tabIndex (#6222)
+ storedScrollTop = scrollBodyPanel.getScrollPosition();
+ storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
+ }
+
+ if (tabIndex == 0 && !isFocusable()) {
+ scrollBodyPanel.setTabIndex(-1);
+ } else {
+ scrollBodyPanel.setTabIndex(tabIndex);
+ }
+
+ if (BrowserInfo.get().getOperaVersion() >= 11) {
+ // Workaround for Opera scroll bug when changing tabIndex (#6222)
+ scrollBodyPanel.setScrollPosition(storedScrollTop);
+ scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
+ }
+ }
+
+ public void startScrollingVelocityTimer() {
+ if (scrollingVelocityTimer == null) {
+ scrollingVelocityTimer = new Timer() {
+
+ @Override
+ public void run() {
+ scrollingVelocity++;
+ }
+ };
+ scrollingVelocityTimer.scheduleRepeating(100);
+ }
+ }
+
+ public void cancelScrollingVelocityTimer() {
+ if (scrollingVelocityTimer != null) {
+ // Remove velocityTimer if it exists and the Table is disabled
+ scrollingVelocityTimer.cancel();
+ scrollingVelocityTimer = null;
+ scrollingVelocity = 10;
+ }
+ }
+
+ /**
+ *
+ * @param keyCode
+ * @return true if the given keyCode is used by the table for navigation
+ */
+ private boolean isNavigationKey(int keyCode) {
+ return keyCode == getNavigationUpKey()
+ || keyCode == getNavigationLeftKey()
+ || keyCode == getNavigationRightKey()
+ || keyCode == getNavigationDownKey()
+ || keyCode == getNavigationPageUpKey()
+ || keyCode == getNavigationPageDownKey()
+ || keyCode == getNavigationEndKey()
+ || keyCode == getNavigationStartKey();
+ }
+
+ public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
+ Scheduler.get().scheduleFinally(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ if (currentlyFocusedRow != null) {
+ setRowFocus(currentlyFocusedRow);
+ } else {
+ VConsole.log("no row?");
+ focusRowFromBody();
+ }
+ scrollBody.ensureFocus();
+ }
+ });
+ }
+
+ @Override
+ public Action[] getActions() {
+ if (bodyActionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[bodyActionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = bodyActionKeys[i];
+ Action bodyAction = new TreeAction(this, null, actionKey);
+ bodyAction.setCaption(getActionCaption(actionKey));
+ bodyAction.setIconUrl(getActionIcon(actionKey));
+ actions[i] = bodyAction;
+ }
+ return actions;
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ @Override
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ /**
+ * Add this to the element mouse down event by using element.setPropertyJSO
+ * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
+ * when the mouse is depressed in the mouse up event.
+ *
+ * @return Returns the JSO preventing text selection
+ */
+ private static native JavaScriptObject getPreventTextSelectionIEHack()
+ /*-{
+ return function(){ return false; };
+ }-*/;
+
+ public void triggerLazyColumnAdjustment(boolean now) {
+ lazyAdjustColumnWidths.cancel();
+ if (now) {
+ lazyAdjustColumnWidths.run();
+ } else {
+ lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
+ }
+ }
+
+ private boolean isDynamicWidth() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ return paintable.isUndefinedWidth();
+ }
+
+ private boolean isDynamicHeight() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ if (paintable == null) {
+ // This should be refactored. As isDynamicHeight can be called from
+ // a timer it is possible that the connector has been unregistered
+ // when this method is called, causing getConnector to return null.
+ return false;
+ }
+ return paintable.isUndefinedHeight();
+ }
+
+ private void debug(String msg) {
+ if (enableDebug) {
+ VConsole.error(msg);
+ }
+ }
+
+ public Widget getWidgetForPaintable() {
+ return this;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+//
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasValue;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ContainerResizedListener;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.ui.slider.SliderOrientation;
+
+public class VSlider extends SimpleFocusablePanel implements Field,
+ ContainerResizedListener, HasValue<Double> {
+
+ public static final String CLASSNAME = "v-slider";
+
+ /**
+ * Minimum size (width or height, depending on orientation) of the slider
+ * base.
+ */
+ private static final int MIN_SIZE = 50;
+
+ protected ApplicationConnection client;
+
+ protected String id;
+
+ protected boolean immediate;
+ protected boolean disabled;
+ protected boolean readonly;
+
+ private int acceleration = 1;
+ protected double min;
+ protected double max;
+ protected int resolution;
+ protected Double value;
+ protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
+
+ private final HTML feedback = new HTML("", false);
+ private final VOverlay feedbackPopup = new VOverlay(true, false, true) {
+ {
+ setOwner(VSlider.this);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ updateFeedbackPosition();
+ }
+ };
+
+ /* DOM element for slider's base */
+ private final Element base;
+ private final int BASE_BORDER_WIDTH = 1;
+
+ /* DOM element for slider's handle */
+ private final Element handle;
+
+ /* DOM element for decrement arrow */
+ private final Element smaller;
+
+ /* DOM element for increment arrow */
+ private final Element bigger;
+
+ /* Temporary dragging/animation variables */
+ private boolean dragging = false;
+
+ private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100,
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ fireValueChanged();
+ acceleration = 1;
+ }
+ });
+
+ public VSlider() {
+ super();
+
+ base = DOM.createDiv();
+ handle = DOM.createDiv();
+ smaller = DOM.createDiv();
+ bigger = DOM.createDiv();
+
+ setStyleName(CLASSNAME);
+
+ getElement().appendChild(bigger);
+ getElement().appendChild(smaller);
+ getElement().appendChild(base);
+ base.appendChild(handle);
+
+ // Hide initially
+ smaller.getStyle().setDisplay(Display.NONE);
+ bigger.getStyle().setDisplay(Display.NONE);
+ handle.getStyle().setVisibility(Visibility.HIDDEN);
+
+ sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
+ | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
+
+ feedbackPopup.setWidget(feedback);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ updateStyleNames(style, false);
+ }
+
+ @Override
+ public void setStylePrimaryName(String style) {
+ updateStyleNames(style, true);
+ }
+
+ protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) {
+
+ feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback");
+ removeStyleName(getStylePrimaryName() + "-vertical");
+
+ if (isPrimaryStyleName) {
+ super.setStylePrimaryName(styleName);
+ } else {
+ super.setStyleName(styleName);
+ }
+
+ feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback");
+ base.setClassName(getStylePrimaryName() + "-base");
+ handle.setClassName(getStylePrimaryName() + "-handle");
+ smaller.setClassName(getStylePrimaryName() + "-smaller");
+ bigger.setClassName(getStylePrimaryName() + "-bigger");
+
+ if (isVertical()) {
+ addStyleName(getStylePrimaryName() + "-vertical");
+ }
+ }
+
+ public void setFeedbackValue(double value) {
+ String currentValue = "" + value;
+ if (resolution == 0) {
+ currentValue = "" + new Double(value).intValue();
+ }
+ feedback.setText(currentValue);
+ }
+
+ private void updateFeedbackPosition() {
+ if (isVertical()) {
+ feedbackPopup.setPopupPosition(
+ handle.getAbsoluteLeft() + handle.getOffsetWidth(),
+ handle.getAbsoluteTop() + handle.getOffsetHeight() / 2
+ - feedbackPopup.getOffsetHeight() / 2);
+ } else {
+ feedbackPopup.setPopupPosition(
+ handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2
+ - feedbackPopup.getOffsetWidth() / 2,
+ handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight());
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void buildBase() {
+ final String styleAttribute = isVertical() ? "height" : "width";
+ final String oppositeStyleAttribute = isVertical() ? "width" : "height";
+ final String domProperty = isVertical() ? "offsetHeight"
+ : "offsetWidth";
+
+ // clear unnecessary opposite style attribute
+ base.getStyle().clearProperty(oppositeStyleAttribute);
+
+ final Element p = getElement().getParentElement().cast();
+ if (p.getPropertyInt(domProperty) > 50) {
+ if (isVertical()) {
+ setHeight();
+ } else {
+ base.getStyle().clearProperty(styleAttribute);
+ }
+ } else {
+ // Set minimum size and adjust after all components have
+ // (supposedly) been drawn completely.
+ base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ final Element p = getElement().getParentElement().cast();
+ if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5)) {
+ if (isVertical()) {
+ setHeight();
+ } else {
+ base.getStyle().clearProperty(styleAttribute);
+ }
+ // Ensure correct position
+ setValue(value, false);
+ }
+ }
+ });
+ }
+
+ if (!isVertical()) {
+ // Draw handle with a delay to allow base to gain maximum width
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ buildHandle();
+ setValue(value, false);
+ }
+ });
+ } else {
+ buildHandle();
+ setValue(value, false);
+ }
+
+ // TODO attach listeners for focusing and arrow keys
+ }
+
+ void buildHandle() {
+ final String handleAttribute = isVertical() ? "marginTop"
+ : "marginLeft";
+ final String oppositeHandleAttribute = isVertical() ? "marginLeft"
+ : "marginTop";
+
+ handle.getStyle().setProperty(handleAttribute, "0");
+
+ // clear unnecessary opposite handle attribute
+ handle.getStyle().clearProperty(oppositeHandleAttribute);
+
+ // Restore visibility
+ handle.getStyle().setVisibility(Visibility.VISIBLE);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (disabled || readonly) {
+ return;
+ }
+ final Element targ = DOM.eventGetTarget(event);
+
+ if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
+ processMouseWheelEvent(event);
+ } else if (dragging || targ == handle) {
+ processHandleEvent(event);
+ } else if (targ == smaller) {
+ decreaseValue(true);
+ } else if (targ == bigger) {
+ increaseValue(true);
+ } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
+ processBaseEvent(event);
+ } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
+ || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
+
+ if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
+ event.getShiftKey())) {
+
+ feedbackPopup.show();
+
+ delayedValueUpdater.trigger();
+
+ DOM.eventPreventDefault(event);
+ DOM.eventCancelBubble(event, true);
+ }
+ } else if (targ.equals(getElement())
+ && DOM.eventGetType(event) == Event.ONFOCUS) {
+ feedbackPopup.show();
+ } else if (targ.equals(getElement())
+ && DOM.eventGetType(event) == Event.ONBLUR) {
+ feedbackPopup.hide();
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
+ feedbackPopup.show();
+ }
+ if (Util.isTouchEvent(event)) {
+ event.preventDefault(); // avoid simulated events
+ event.stopPropagation();
+ }
+ }
+
+ private void processMouseWheelEvent(final Event event) {
+ final int dir = DOM.eventGetMouseWheelVelocityY(event);
+
+ if (dir < 0) {
+ increaseValue(false);
+ } else {
+ decreaseValue(false);
+ }
+
+ delayedValueUpdater.trigger();
+
+ DOM.eventPreventDefault(event);
+ DOM.eventCancelBubble(event, true);
+ }
+
+ private void processHandleEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ case Event.ONTOUCHSTART:
+ if (!disabled && !readonly) {
+ focus();
+ feedbackPopup.show();
+ dragging = true;
+ handle.setClassName(getStylePrimaryName() + "-handle");
+ handle.addClassName(getStylePrimaryName() + "-handle-active");
+
+ DOM.setCapture(getElement());
+ DOM.eventPreventDefault(event); // prevent selecting text
+ DOM.eventCancelBubble(event, true);
+ event.stopPropagation();
+ VConsole.log("Slider move start");
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ if (dragging) {
+ VConsole.log("Slider move");
+ setValueByEvent(event, false);
+ updateFeedbackPosition();
+ event.stopPropagation();
+ }
+ break;
+ case Event.ONTOUCHEND:
+ feedbackPopup.hide();
+ case Event.ONMOUSEUP:
+ // feedbackPopup.hide();
+ VConsole.log("Slider move end");
+ dragging = false;
+ handle.setClassName(getStylePrimaryName() + "-handle");
+ DOM.releaseCapture(getElement());
+ setValueByEvent(event, true);
+ event.stopPropagation();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void processBaseEvent(Event event) {
+ if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
+ if (!disabled && !readonly && !dragging) {
+ setValueByEvent(event, true);
+ DOM.eventCancelBubble(event, true);
+ }
+ }
+ }
+
+ private void decreaseValue(boolean updateToServer) {
+ setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
+ updateToServer);
+ }
+
+ private void increaseValue(boolean updateToServer) {
+ setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
+ updateToServer);
+ }
+
+ private void setValueByEvent(Event event, boolean updateToServer) {
+ double v = min; // Fallback to min
+
+ final int coord = getEventPosition(event);
+
+ final int handleSize, baseSize, baseOffset;
+ if (isVertical()) {
+ handleSize = handle.getOffsetHeight();
+ baseSize = base.getOffsetHeight();
+ baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
+ - handleSize / 2;
+ } else {
+ handleSize = handle.getOffsetWidth();
+ baseSize = base.getOffsetWidth();
+ baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
+ + handleSize / 2;
+ }
+
+ if (isVertical()) {
+ v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize))
+ * (max - min) + min;
+ } else {
+ v = ((coord - baseOffset) / (double) (baseSize - handleSize))
+ * (max - min) + min;
+ }
+
+ if (v < min) {
+ v = min;
+ } else if (v > max) {
+ v = max;
+ }
+
+ setValue(v, updateToServer);
+ }
+
+ /**
+ * TODO consider extracting touches support to an impl class specific for
+ * webkit (only browser that really supports touches).
+ *
+ * @param event
+ * @return
+ */
+ protected int getEventPosition(Event event) {
+ if (isVertical()) {
+ return Util.getTouchOrMouseClientY(event);
+ } else {
+ return Util.getTouchOrMouseClientX(event);
+ }
+ }
+
+ @Override
+ public void iLayout() {
+ if (isVertical()) {
+ setHeight();
+ }
+ // Update handle position
+ setValue(value, false);
+ }
+
+ private void setHeight() {
+ // Calculate decoration size
+ base.getStyle().setHeight(0, Unit.PX);
+ base.getStyle().setOverflow(Overflow.HIDDEN);
+ int h = getElement().getOffsetHeight();
+ if (h < MIN_SIZE) {
+ h = MIN_SIZE;
+ }
+ base.getStyle().setHeight(h, Unit.PX);
+ base.getStyle().clearOverflow();
+ }
+
+ private void fireValueChanged() {
+ ValueChangeEvent.fire(VSlider.this, value);
+ }
+
+ /**
+ * Handles the keyboard events handled by the Slider
+ *
+ * @param event
+ * The keyboard event received
+ * @return true iff the navigation event was handled
+ */
+ public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+
+ // No support for ctrl moving
+ if (ctrl) {
+ return false;
+ }
+
+ if ((keycode == getNavigationUpKey() && isVertical())
+ || (keycode == getNavigationRightKey() && !isVertical())) {
+ if (shift) {
+ for (int a = 0; a < acceleration; a++) {
+ increaseValue(false);
+ }
+ acceleration++;
+ } else {
+ increaseValue(false);
+ }
+ return true;
+ } else if (keycode == getNavigationDownKey() && isVertical()
+ || (keycode == getNavigationLeftKey() && !isVertical())) {
+ if (shift) {
+ for (int a = 0; a < acceleration; a++) {
+ decreaseValue(false);
+ }
+ acceleration++;
+ } else {
+ decreaseValue(false);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the key that increases the vertical slider. By default it is the up
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that decreases the vertical slider. By default it is the down
+ * arrow key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that decreases the horizontal slider. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that increases the horizontal slider. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ public void setConnection(ApplicationConnection client) {
+ this.client = client;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setImmediate(boolean immediate) {
+ this.immediate = immediate;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public void setReadOnly(boolean readonly) {
+ this.readonly = readonly;
+ }
+
+ private boolean isVertical() {
+ return orientation == SliderOrientation.VERTICAL;
+ }
+
+ public void setOrientation(SliderOrientation orientation) {
+ if (this.orientation != orientation) {
+ this.orientation = orientation;
+ updateStyleNames(getStylePrimaryName(), true);
+ }
+ }
+
+ public void setMinValue(double value) {
+ min = value;
+ }
+
+ public void setMaxValue(double value) {
+ max = value;
+ }
+
+ public void setResolution(int resolution) {
+ this.resolution = resolution;
+ }
+
+ @Override
+ public HandlerRegistration addValueChangeHandler(
+ ValueChangeHandler<Double> handler) {
+ return addHandler(handler, ValueChangeEvent.getType());
+ }
+
+ @Override
+ public Double getValue() {
+ return value;
+ }
+
+ @Override
+ public void setValue(Double value) {
+ if (value < min) {
+ value = min;
+ } else if (value > max) {
+ value = max;
+ }
+
+ // Update handle position
+ final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
+ final String domProperty = isVertical() ? "offsetHeight"
+ : "offsetWidth";
+ final int handleSize = handle.getPropertyInt(domProperty);
+ final int baseSize = base.getPropertyInt(domProperty)
+ - (2 * BASE_BORDER_WIDTH);
+
+ final int range = baseSize - handleSize;
+ double v = value.doubleValue();
+
+ // Round value to resolution
+ if (resolution > 0) {
+ v = Math.round(v * Math.pow(10, resolution));
+ v = v / Math.pow(10, resolution);
+ } else {
+ v = Math.round(v);
+ }
+ final double valueRange = max - min;
+ double p = 0;
+ if (valueRange > 0) {
+ p = range * ((v - min) / valueRange);
+ }
+ if (p < 0) {
+ p = 0;
+ }
+ if (isVertical()) {
+ p = range - p;
+ }
+ final double pos = p;
+
+ handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
+
+ // Update value
+ this.value = new Double(v);
+ setFeedbackValue(v);
+ }
+
+ @Override
+ public void setValue(Double value, boolean fireEvents) {
+ if (value == null) {
+ return;
+ }
+
+ setValue(value);
+
+ if (fireEvents) {
+ fireValueChanged();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.vaadin.shared.ui.Orientation;
+
+public class VSplitPanelHorizontal extends VAbstractSplitPanel {
+
+ public VSplitPanelHorizontal() {
+ super(Orientation.HORIZONTAL);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.vaadin.shared.ui.Orientation;
+
+public class VSplitPanelVertical extends VAbstractSplitPanel {
+
+ public VSplitPanelVertical() {
+ super(Orientation.VERTICAL);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
+import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
+
+public class VTabsheet extends VTabsheetBase implements Focusable,
+ FocusHandler, BlurHandler, KeyDownHandler {
+
+ private static class VCloseEvent {
+ private Tab tab;
+
+ VCloseEvent(Tab tab) {
+ this.tab = tab;
+ }
+
+ public Tab getTab() {
+ return tab;
+ }
+
+ }
+
+ private interface VCloseHandler {
+ public void onClose(VCloseEvent event);
+ }
+
+ /**
+ * Representation of a single "tab" shown in the TabBar
+ *
+ */
+ private static class Tab extends SimplePanel implements HasFocusHandlers,
+ HasBlurHandlers, HasKeyDownHandlers {
+ private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
+ private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
+ + "-first";
+ private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
+ + "-selected";
+ private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
+ + "-first";
+ private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
+ + "-disabled";
+
+ private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
+ private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
+ + "-selected";
+
+ private TabCaption tabCaption;
+ Element td = getElement();
+ private VCloseHandler closeHandler;
+
+ private boolean enabledOnServer = true;
+ private Element div;
+ private TabBar tabBar;
+ private boolean hiddenOnServer = false;
+
+ private String styleName;
+
+ private Tab(TabBar tabBar) {
+ super(DOM.createTD());
+ this.tabBar = tabBar;
+ setStyleName(td, TD_CLASSNAME);
+
+ div = DOM.createDiv();
+ focusImpl.setTabIndex(td, -1);
+ setStyleName(div, DIV_CLASSNAME);
+
+ DOM.appendChild(td, div);
+
+ tabCaption = new TabCaption(this, getTabsheet()
+ .getApplicationConnection());
+ add(tabCaption);
+
+ addFocusHandler(getTabsheet());
+ addBlurHandler(getTabsheet());
+ addKeyDownHandler(getTabsheet());
+ }
+
+ public boolean isHiddenOnServer() {
+ return hiddenOnServer;
+ }
+
+ public void setHiddenOnServer(boolean hiddenOnServer) {
+ this.hiddenOnServer = hiddenOnServer;
+ }
+
+ @Override
+ protected Element getContainerElement() {
+ // Attach caption element to div, not td
+ return div;
+ }
+
+ public boolean isEnabledOnServer() {
+ return enabledOnServer;
+ }
+
+ public void setEnabledOnServer(boolean enabled) {
+ enabledOnServer = enabled;
+ setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
+ if (!enabled) {
+ focusImpl.setTabIndex(td, -1);
+ }
+ }
+
+ public void addClickHandler(ClickHandler handler) {
+ tabCaption.addClickHandler(handler);
+ }
+
+ public void setCloseHandler(VCloseHandler closeHandler) {
+ this.closeHandler = closeHandler;
+ }
+
+ /**
+ * Toggles the style names for the Tab
+ *
+ * @param selected
+ * true if the Tab is selected
+ * @param first
+ * true if the Tab is the first visible Tab
+ */
+ public void setStyleNames(boolean selected, boolean first) {
+ setStyleName(td, TD_FIRST_CLASSNAME, first);
+ setStyleName(td, TD_SELECTED_CLASSNAME, selected);
+ setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
+ setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
+ }
+
+ public void setTabulatorIndex(int tabIndex) {
+ focusImpl.setTabIndex(td, tabIndex);
+ }
+
+ public boolean isClosable() {
+ return tabCaption.isClosable();
+ }
+
+ public void onClose() {
+ closeHandler.onClose(new VCloseEvent(this));
+ }
+
+ public VTabsheet getTabsheet() {
+ return tabBar.getTabsheet();
+ }
+
+ public void updateFromUIDL(UIDL tabUidl) {
+ tabCaption.updateCaption(tabUidl);
+
+ // Apply the styleName set for the tab
+ String newStyleName = tabUidl
+ .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
+ // Find the nth td element
+ if (newStyleName != null && newStyleName.length() != 0) {
+ if (!newStyleName.equals(styleName)) {
+ // If we have a new style name
+ if (styleName != null && styleName.length() != 0) {
+ // Remove old style name if present
+ td.removeClassName(TD_CLASSNAME + "-" + styleName);
+ }
+ // Set new style name
+ td.addClassName(TD_CLASSNAME + "-" + newStyleName);
+ styleName = newStyleName;
+ }
+ } else if (styleName != null) {
+ // Remove the set stylename if no stylename is present in the
+ // uidl
+ td.removeClassName(TD_CLASSNAME + "-" + styleName);
+ styleName = null;
+ }
+ }
+
+ public void recalculateCaptionWidth() {
+ tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
+ }
+
+ @Override
+ public HandlerRegistration addFocusHandler(FocusHandler handler) {
+ return addDomHandler(handler, FocusEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addBlurHandler(BlurHandler handler) {
+ return addDomHandler(handler, BlurEvent.getType());
+ }
+
+ @Override
+ public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+ return addDomHandler(handler, KeyDownEvent.getType());
+ }
+
+ public void focus() {
+ focusImpl.focus(td);
+ }
+
+ public void blur() {
+ focusImpl.blur(td);
+ }
+ }
+
+ public static class TabCaption extends VCaption {
+
+ private boolean closable = false;
+ private Element closeButton;
+ private Tab tab;
+ private ApplicationConnection client;
+
+ TabCaption(Tab tab, ApplicationConnection client) {
+ super(client);
+ this.client = client;
+ this.tab = tab;
+ }
+
+ public boolean updateCaption(UIDL uidl) {
+ if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION)) {
+ setTooltipInfo(new TooltipInfo(
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)));
+ } else {
+ setTooltipInfo(null);
+ }
+
+ // TODO need to call this instead of super because the caption does
+ // not have an owner
+ boolean ret = updateCaptionWithoutOwner(
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+ uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
+ uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
+
+ setClosable(uidl.hasAttribute("closable"));
+
+ return ret;
+ }
+
+ private VTabsheet getTabsheet() {
+ return tab.getTabsheet();
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (closable && event.getTypeInt() == Event.ONCLICK
+ && event.getEventTarget().cast() == closeButton) {
+ tab.onClose();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ super.onBrowserEvent(event);
+
+ if (event.getTypeInt() == Event.ONLOAD) {
+ getTabsheet().tabSizeMightHaveChanged(getTab());
+ }
+ }
+
+ public Tab getTab() {
+ return tab;
+ }
+
+ public void setClosable(boolean closable) {
+ this.closable = closable;
+ if (closable && closeButton == null) {
+ closeButton = DOM.createSpan();
+ closeButton.setInnerHTML("×");
+ closeButton
+ .setClassName(VTabsheet.CLASSNAME + "-caption-close");
+ getElement().insertBefore(closeButton,
+ getElement().getLastChild());
+ } else if (!closable && closeButton != null) {
+ getElement().removeChild(closeButton);
+ closeButton = null;
+ }
+ if (closable) {
+ addStyleDependentName("closable");
+ } else {
+ removeStyleDependentName("closable");
+ }
+ }
+
+ public boolean isClosable() {
+ return closable;
+ }
+
+ @Override
+ public int getRequiredWidth() {
+ int width = super.getRequiredWidth();
+ if (closeButton != null) {
+ width += Util.getRequiredWidth(closeButton);
+ }
+ return width;
+ }
+
+ public Element getCloseButton() {
+ return closeButton;
+ }
+
+ }
+
+ static class TabBar extends ComplexPanel implements ClickHandler,
+ VCloseHandler {
+
+ private final Element tr = DOM.createTR();
+
+ private final Element spacerTd = DOM.createTD();
+
+ private Tab selected;
+
+ private VTabsheet tabsheet;
+
+ TabBar(VTabsheet tabsheet) {
+ this.tabsheet = tabsheet;
+
+ Element el = DOM.createTable();
+ Element tbody = DOM.createTBody();
+ DOM.appendChild(el, tbody);
+ DOM.appendChild(tbody, tr);
+ setStyleName(spacerTd, CLASSNAME + "-spacertd");
+ DOM.appendChild(tr, spacerTd);
+ DOM.appendChild(spacerTd, DOM.createDiv());
+ setElement(el);
+ }
+
+ @Override
+ public void onClose(VCloseEvent event) {
+ Tab tab = event.getTab();
+ if (!tab.isEnabledOnServer()) {
+ return;
+ }
+ int tabIndex = getWidgetIndex(tab);
+ getTabsheet().sendTabClosedEvent(tabIndex);
+ }
+
+ protected Element getContainerElement() {
+ return tr;
+ }
+
+ public int getTabCount() {
+ return getWidgetCount();
+ }
+
+ public Tab addTab() {
+ Tab t = new Tab(this);
+ int tabIndex = getTabCount();
+
+ // Logical attach
+ insert(t, tr, tabIndex, true);
+
+ if (tabIndex == 0) {
+ // Set the "first" style
+ t.setStyleNames(false, true);
+ }
+
+ t.addClickHandler(this);
+ t.setCloseHandler(this);
+
+ return t;
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ TabCaption caption = (TabCaption) event.getSource();
+ Element targetElement = event.getNativeEvent().getEventTarget()
+ .cast();
+ // the tab should not be focused if the close button was clicked
+ if (targetElement == caption.getCloseButton()) {
+ return;
+ }
+
+ int index = getWidgetIndex(caption.getParent());
+ // IE needs explicit focus()
+ if (BrowserInfo.get().isIE()) {
+ getTabsheet().focus();
+ }
+ getTabsheet().onTabSelected(index);
+ }
+
+ public VTabsheet getTabsheet() {
+ return tabsheet;
+ }
+
+ public Tab getTab(int index) {
+ if (index < 0 || index >= getTabCount()) {
+ return null;
+ }
+ return (Tab) super.getWidget(index);
+ }
+
+ public void selectTab(int index) {
+ final Tab newSelected = getTab(index);
+ final Tab oldSelected = selected;
+
+ newSelected.setStyleNames(true, isFirstVisibleTab(index));
+ newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
+
+ if (oldSelected != null && oldSelected != newSelected) {
+ oldSelected.setStyleNames(false,
+ isFirstVisibleTab(getWidgetIndex(oldSelected)));
+ oldSelected.setTabulatorIndex(-1);
+ }
+
+ // Update the field holding the currently selected tab
+ selected = newSelected;
+
+ // The selected tab might need more (or less) space
+ newSelected.recalculateCaptionWidth();
+ getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
+ }
+
+ public void removeTab(int i) {
+ Tab tab = getTab(i);
+ if (tab == null) {
+ return;
+ }
+
+ remove(tab);
+
+ /*
+ * If this widget was selected we need to unmark it as the last
+ * selected
+ */
+ if (tab == selected) {
+ selected = null;
+ }
+
+ // FIXME: Shouldn't something be selected instead?
+ }
+
+ private boolean isFirstVisibleTab(int index) {
+ return getFirstVisibleTab() == index;
+ }
+
+ /**
+ * Returns the index of the first visible tab
+ *
+ * @return
+ */
+ private int getFirstVisibleTab() {
+ return getNextVisibleTab(-1);
+ }
+
+ /**
+ * Find the next visible tab. Returns -1 if none is found.
+ *
+ * @param i
+ * @return
+ */
+ private int getNextVisibleTab(int i) {
+ int tabs = getTabCount();
+ do {
+ i++;
+ } while (i < tabs && getTab(i).isHiddenOnServer());
+
+ if (i == tabs) {
+ return -1;
+ } else {
+ return i;
+ }
+ }
+
+ /**
+ * Find the previous visible tab. Returns -1 if none is found.
+ *
+ * @param i
+ * @return
+ */
+ private int getPreviousVisibleTab(int i) {
+ do {
+ i--;
+ } while (i >= 0 && getTab(i).isHiddenOnServer());
+
+ return i;
+
+ }
+
+ public int scrollLeft(int currentFirstVisible) {
+ int prevVisible = getPreviousVisibleTab(currentFirstVisible);
+ if (prevVisible == -1) {
+ return -1;
+ }
+
+ Tab newFirst = getTab(prevVisible);
+ newFirst.setVisible(true);
+ newFirst.recalculateCaptionWidth();
+
+ return prevVisible;
+ }
+
+ public int scrollRight(int currentFirstVisible) {
+ int nextVisible = getNextVisibleTab(currentFirstVisible);
+ if (nextVisible == -1) {
+ return -1;
+ }
+ Tab currentFirst = getTab(currentFirstVisible);
+ currentFirst.setVisible(false);
+ currentFirst.recalculateCaptionWidth();
+ return nextVisible;
+ }
+ }
+
+ public static final String CLASSNAME = "v-tabsheet";
+
+ public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
+ public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element tabs; // tabbar and 'scroller' container
+ Tab focusedTab;
+ /**
+ * The tabindex property (position in the browser's focus cycle.) Named like
+ * this to avoid confusion with activeTabIndex.
+ */
+ int tabulatorIndex = 0;
+
+ private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
+
+ private final Element scroller; // tab-scroller element
+ private final Element scrollerNext; // tab-scroller next button element
+ private final Element scrollerPrev; // tab-scroller prev button element
+
+ /**
+ * The index of the first visible tab (when scrolled)
+ */
+ private int scrollerIndex = 0;
+
+ final TabBar tb = new TabBar(this);
+ /** For internal use only. May be removed or replaced in the future. */
+ public final VTabsheetPanel tp = new VTabsheetPanel();
+ /** For internal use only. May be removed or replaced in the future. */
+ public final Element contentNode;
+
+ private final Element deco;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean waitingForResponse;
+
+ private String currentStyle;
+
+ /**
+ * @return Whether the tab could be selected or not.
+ */
+ private boolean onTabSelected(final int tabIndex) {
+ Tab tab = tb.getTab(tabIndex);
+ if (client == null || disabled || waitingForResponse) {
+ return false;
+ }
+ if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
+ return false;
+ }
+ if (activeTabIndex != tabIndex) {
+ tb.selectTab(tabIndex);
+
+ // If this TabSheet already has focus, set the new selected tab
+ // as focused.
+ if (focusedTab != null) {
+ focusedTab = tab;
+ }
+
+ addStyleDependentName("loading");
+ // Hide the current contents so a loading indicator can be shown
+ // instead
+ Widget currentlyDisplayedWidget = tp.getWidget(tp
+ .getVisibleWidget());
+ currentlyDisplayedWidget.getElement().getParentElement().getStyle()
+ .setVisibility(Visibility.HIDDEN);
+ client.updateVariable(id, "selected", tabKeys.get(tabIndex)
+ .toString(), true);
+ waitingForResponse = true;
+ }
+ // Note that we return true when tabIndex == activeTabIndex; the active
+ // tab could be selected, it's just a no-op.
+ return true;
+ }
+
+ public ApplicationConnection getApplicationConnection() {
+ return client;
+ }
+
+ public void tabSizeMightHaveChanged(Tab tab) {
+ // icon onloads may change total width of tabsheet
+ if (isDynamicWidth()) {
+ updateDynamicWidth();
+ }
+ updateTabScroller();
+
+ }
+
+ void sendTabClosedEvent(int tabIndex) {
+ client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
+ }
+
+ boolean isDynamicWidth() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ return paintable.isUndefinedWidth();
+ }
+
+ boolean isDynamicHeight() {
+ ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+ this);
+ return paintable.isUndefinedHeight();
+ }
+
+ public VTabsheet() {
+ super(CLASSNAME);
+
+ addHandler(this, FocusEvent.getType());
+ addHandler(this, BlurEvent.getType());
+
+ // Tab scrolling
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ tabs = DOM.createDiv();
+ DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+ scroller = DOM.createDiv();
+
+ DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
+ scrollerPrev = DOM.createButton();
+ DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ + "Prev");
+ DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
+ scrollerNext = DOM.createButton();
+ DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+ + "Next");
+ DOM.sinkEvents(scrollerNext, Event.ONCLICK);
+ DOM.appendChild(getElement(), tabs);
+
+ // Tabs
+ tp.setStyleName(CLASSNAME + "-tabsheetpanel");
+ contentNode = DOM.createDiv();
+
+ deco = DOM.createDiv();
+
+ addStyleDependentName("loading"); // Indicate initial progress
+ tb.setStyleName(CLASSNAME + "-tabs");
+ DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
+ DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+
+ add(tb, tabs);
+ DOM.appendChild(scroller, scrollerPrev);
+ DOM.appendChild(scroller, scrollerNext);
+
+ DOM.appendChild(getElement(), contentNode);
+ add(tp, contentNode);
+ DOM.appendChild(getElement(), deco);
+
+ DOM.appendChild(tabs, scroller);
+
+ // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
+ // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
+ // .getFirstChild(tb.getElement()))), "display", "none");
+
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ // Tab scrolling
+ if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
+ int newFirstIndex = tb.scrollLeft(scrollerIndex);
+ if (newFirstIndex != -1) {
+ scrollerIndex = newFirstIndex;
+ updateTabScroller();
+ }
+ event.stopPropagation();
+ return;
+ } else if (isClippedTabs()
+ && DOM.eventGetTarget(event) == scrollerNext) {
+ int newFirstIndex = tb.scrollRight(scrollerIndex);
+
+ if (newFirstIndex != -1) {
+ scrollerIndex = newFirstIndex;
+ updateTabScroller();
+ }
+ event.stopPropagation();
+ return;
+ }
+ }
+ super.onBrowserEvent(event);
+ }
+
+ /**
+ * Checks if the tab with the selected index has been scrolled out of the
+ * view (on the left side).
+ *
+ * @param index
+ * @return
+ */
+ private boolean scrolledOutOfView(int index) {
+ return scrollerIndex > index;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void handleStyleNames(UIDL uidl, ComponentState state) {
+ // Add proper stylenames for all elements (easier to prevent unwanted
+ // style inheritance)
+ if (ComponentStateUtil.hasStyles(state)) {
+ final List<String> styles = state.styles;
+ if (!currentStyle.equals(styles.toString())) {
+ currentStyle = styles.toString();
+ final String tabsBaseClass = TABS_CLASSNAME;
+ String tabsClass = tabsBaseClass;
+ final String contentBaseClass = CLASSNAME + "-content";
+ String contentClass = contentBaseClass;
+ final String decoBaseClass = CLASSNAME + "-deco";
+ String decoClass = decoBaseClass;
+ for (String style : styles) {
+ tb.addStyleDependentName(style);
+ tabsClass += " " + tabsBaseClass + "-" + style;
+ contentClass += " " + contentBaseClass + "-" + style;
+ decoClass += " " + decoBaseClass + "-" + style;
+ }
+ DOM.setElementProperty(tabs, "className", tabsClass);
+ DOM.setElementProperty(contentNode, "className", contentClass);
+ DOM.setElementProperty(deco, "className", decoClass);
+ borderW = -1;
+ }
+ } else {
+ tb.setStyleName(CLASSNAME + "-tabs");
+ DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+ DOM.setElementProperty(contentNode, "className", CLASSNAME
+ + "-content");
+ DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+ }
+
+ if (uidl.hasAttribute("hidetabs")) {
+ tb.setVisible(false);
+ addStyleName(CLASSNAME + "-hidetabs");
+ } else {
+ tb.setVisible(true);
+ removeStyleName(CLASSNAME + "-hidetabs");
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateDynamicWidth() {
+ // Find width consumed by tabs
+ TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
+ .getRows().getItem(0).getCells().getItem(tb.getTabCount());
+
+ int spacerWidth = spacerCell.getOffsetWidth();
+ DivElement div = (DivElement) spacerCell.getFirstChildElement();
+
+ int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
+
+ int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
+
+ // Find content width
+ Style style = tp.getElement().getStyle();
+ String overflow = style.getProperty("overflow");
+ style.setProperty("overflow", "hidden");
+ style.setPropertyPx("width", tabsWidth);
+
+ boolean hasTabs = tp.getWidgetCount() > 0;
+
+ Style wrapperstyle = null;
+ if (hasTabs) {
+ wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
+ .getParentElement().getStyle();
+ wrapperstyle.setPropertyPx("width", tabsWidth);
+ }
+ // Get content width from actual widget
+
+ int contentWidth = 0;
+ if (hasTabs) {
+ contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
+ }
+ style.setProperty("overflow", overflow);
+
+ // Set widths to max(tabs,content)
+ if (tabsWidth < contentWidth) {
+ tabsWidth = contentWidth;
+ }
+
+ int outerWidth = tabsWidth + getContentAreaBorderWidth();
+
+ tabs.getStyle().setPropertyPx("width", outerWidth);
+ style.setPropertyPx("width", tabsWidth);
+ if (hasTabs) {
+ wrapperstyle.setPropertyPx("width", tabsWidth);
+ }
+
+ contentNode.getStyle().setPropertyPx("width", tabsWidth);
+ super.setWidth(outerWidth + "px");
+ updateOpenTabSize();
+ }
+
+ @Override
+ public void renderTab(final UIDL tabUidl, int index, boolean selected,
+ boolean hidden) {
+ Tab tab = tb.getTab(index);
+ if (tab == null) {
+ tab = tb.addTab();
+ }
+ tab.updateFromUIDL(tabUidl);
+ tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
+ tab.setHiddenOnServer(hidden);
+
+ if (scrolledOutOfView(index)) {
+ // Should not set tabs visible if they are scrolled out of view
+ hidden = true;
+ }
+ // Set the current visibility of the tab (in the browser)
+ tab.setVisible(!hidden);
+
+ /*
+ * Force the width of the caption container so the content will not wrap
+ * and tabs won't be too narrow in certain browsers
+ */
+ tab.recalculateCaptionWidth();
+
+ UIDL tabContentUIDL = null;
+ ComponentConnector tabContentPaintable = null;
+ Widget tabContentWidget = null;
+ if (tabUidl.getChildCount() > 0) {
+ tabContentUIDL = tabUidl.getChildUIDL(0);
+ tabContentPaintable = client.getPaintable(tabContentUIDL);
+ tabContentWidget = tabContentPaintable.getWidget();
+ }
+
+ if (tabContentPaintable != null) {
+ /* This is a tab with content information */
+
+ int oldIndex = tp.getWidgetIndex(tabContentWidget);
+ if (oldIndex != -1 && oldIndex != index) {
+ /*
+ * The tab has previously been rendered in another position so
+ * we must move the cached content to correct position
+ */
+ tp.insert(tabContentWidget, index);
+ }
+ } else {
+ /* A tab whose content has not yet been loaded */
+
+ /*
+ * Make sure there is a corresponding empty tab in tp. The same
+ * operation as the moving above but for not-loaded tabs.
+ */
+ if (index < tp.getWidgetCount()) {
+ Widget oldWidget = tp.getWidget(index);
+ if (!(oldWidget instanceof PlaceHolder)) {
+ tp.insert(new PlaceHolder(), index);
+ }
+ }
+
+ }
+
+ if (selected) {
+ renderContent(tabContentUIDL);
+ tb.selectTab(index);
+ } else {
+ if (tabContentUIDL != null) {
+ // updating a drawn child on hidden tab
+ if (tp.getWidgetIndex(tabContentWidget) < 0) {
+ tp.insert(tabContentWidget, index);
+ }
+ } else if (tp.getWidgetCount() <= index) {
+ tp.add(new PlaceHolder());
+ }
+ }
+ }
+
+ public class PlaceHolder extends VLabel {
+ public PlaceHolder() {
+ super("");
+ }
+ }
+
+ @Override
+ protected void selectTab(int index, final UIDL contentUidl) {
+ if (index != activeTabIndex) {
+ activeTabIndex = index;
+ tb.selectTab(activeTabIndex);
+ }
+ renderContent(contentUidl);
+ }
+
+ private void renderContent(final UIDL contentUIDL) {
+ final ComponentConnector content = client.getPaintable(contentUIDL);
+ Widget newWidget = content.getWidget();
+ if (tp.getWidgetCount() > activeTabIndex) {
+ Widget old = tp.getWidget(activeTabIndex);
+ if (old != newWidget) {
+ tp.remove(activeTabIndex);
+ ConnectorMap paintableMap = ConnectorMap.get(client);
+ if (paintableMap.isConnector(old)) {
+ paintableMap.unregisterConnector(paintableMap
+ .getConnector(old));
+ }
+ tp.insert(content.getWidget(), activeTabIndex);
+ }
+ } else {
+ tp.add(content.getWidget());
+ }
+
+ tp.showWidget(activeTabIndex);
+
+ VTabsheet.this.iLayout();
+ /*
+ * The size of a cached, relative sized component must be updated to
+ * report correct size to updateOpenTabSize().
+ */
+ if (contentUIDL.getBooleanAttribute("cached")) {
+ client.handleComponentRelativeSize(content.getWidget());
+ }
+ updateOpenTabSize();
+ VTabsheet.this.removeStyleDependentName("loading");
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateContentNodeHeight() {
+ if (!isDynamicHeight()) {
+ int contentHeight = getOffsetHeight();
+ contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
+ contentHeight -= tb.getOffsetHeight();
+ if (contentHeight < 0) {
+ contentHeight = 0;
+ }
+
+ // Set proper values for content element
+ DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
+ } else {
+ DOM.setStyleAttribute(contentNode, "height", "");
+ }
+ }
+
+ public void iLayout() {
+ updateTabScroller();
+ }
+
+ /**
+ * Sets the size of the visible tab (component). As the tab is set to
+ * position: absolute (to work around a firefox flickering bug) we must keep
+ * this up-to-date by hand.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void updateOpenTabSize() {
+ /*
+ * The overflow=auto element must have a height specified, otherwise it
+ * will be just as high as the contents and no scrollbars will appear
+ */
+ int height = -1;
+ int width = -1;
+ int minWidth = 0;
+
+ if (!isDynamicHeight()) {
+ height = contentNode.getOffsetHeight();
+ }
+ if (!isDynamicWidth()) {
+ width = contentNode.getOffsetWidth() - getContentAreaBorderWidth();
+ } else {
+ /*
+ * If the tabbar is wider than the content we need to use the tabbar
+ * width as minimum width so scrollbars get placed correctly (at the
+ * right edge).
+ */
+ minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
+ }
+ tp.fixVisibleTabSize(width, height, minWidth);
+
+ }
+
+ /**
+ * Layouts the tab-scroller elements, and applies styles.
+ */
+ private void updateTabScroller() {
+ if (!isDynamicWidth()) {
+ ComponentConnector paintable = ConnectorMap.get(client)
+ .getConnector(this);
+ DOM.setStyleAttribute(tabs, "width", paintable.getState().width);
+ }
+
+ // Make sure scrollerIndex is valid
+ if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
+ scrollerIndex = tb.getFirstVisibleTab();
+ } else if (tb.getTabCount() > 0
+ && tb.getTab(scrollerIndex).isHiddenOnServer()) {
+ scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
+ }
+
+ boolean scrolled = isScrolledTabs();
+ boolean clipped = isClippedTabs();
+ if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
+ DOM.setStyleAttribute(scroller, "display", "");
+ DOM.setElementProperty(scrollerPrev, "className",
+ SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
+ DOM.setElementProperty(scrollerNext, "className",
+ SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
+ } else {
+ DOM.setStyleAttribute(scroller, "display", "none");
+ }
+
+ if (BrowserInfo.get().isSafari()) {
+ // fix tab height for safari, bugs sometimes if tabs contain icons
+ String property = tabs.getStyle().getProperty("height");
+ if (property == null || property.equals("")) {
+ tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
+ }
+ /*
+ * another hack for webkits. tabscroller sometimes drops without
+ * "shaking it" reproducable in
+ * com.vaadin.tests.components.tabsheet.TabSheetIcons
+ */
+ final Style style = scroller.getStyle();
+ style.setProperty("whiteSpace", "normal");
+ Scheduler.get().scheduleDeferred(new Command() {
+
+ @Override
+ public void execute() {
+ style.setProperty("whiteSpace", "");
+ }
+ });
+ }
+
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void showAllTabs() {
+ scrollerIndex = tb.getFirstVisibleTab();
+ for (int i = 0; i < tb.getTabCount(); i++) {
+ Tab t = tb.getTab(i);
+ if (!t.isHiddenOnServer()) {
+ t.setVisible(true);
+ }
+ }
+ }
+
+ private boolean isScrolledTabs() {
+ return scrollerIndex > tb.getFirstVisibleTab();
+ }
+
+ private boolean isClippedTabs() {
+ return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
+ .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
+ - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
+ }
+
+ private boolean isClipped(Tab tab) {
+ return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft()
+ + getOffsetWidth() - scroller.getOffsetWidth();
+ }
+
+ @Override
+ protected void clearPaintables() {
+
+ int i = tb.getTabCount();
+ while (i > 0) {
+ tb.removeTab(--i);
+ }
+ tp.clear();
+
+ }
+
+ @Override
+ public Iterator<Widget> getWidgetIterator() {
+ return tp.iterator();
+ }
+
+ private int borderW = -1;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int getContentAreaBorderWidth() {
+ if (borderW < 0) {
+ borderW = Util.measureHorizontalBorder(contentNode);
+ }
+ return borderW;
+ }
+
+ @Override
+ public int getTabCount() {
+ return tb.getTabCount();
+ }
+
+ @Override
+ public ComponentConnector getTab(int index) {
+ if (tp.getWidgetCount() > index) {
+ Widget widget = tp.getWidget(index);
+ return ConnectorMap.get(client).getConnector(widget);
+ }
+ return null;
+ }
+
+ @Override
+ public void removeTab(int index) {
+ tb.removeTab(index);
+ /*
+ * This must be checked because renderTab automatically removes the
+ * active tab content when it changes
+ */
+ if (tp.getWidgetCount() > index) {
+ tp.remove(index);
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ if (focusedTab != null && event.getSource() instanceof Tab) {
+ focusedTab = null;
+ if (client.hasEventListeners(this, EventId.BLUR)) {
+ client.updateVariable(id, EventId.BLUR, "", true);
+ }
+ }
+ }
+
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (focusedTab == null && event.getSource() instanceof Tab) {
+ focusedTab = (Tab) event.getSource();
+ if (client.hasEventListeners(this, EventId.FOCUS)) {
+ client.updateVariable(id, EventId.FOCUS, "", true);
+ }
+ }
+ }
+
+ @Override
+ public void focus() {
+ tb.getTab(activeTabIndex).focus();
+ }
+
+ public void blur() {
+ tb.getTab(activeTabIndex).blur();
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getSource() instanceof Tab) {
+ int keycode = event.getNativeEvent().getKeyCode();
+
+ if (keycode == getPreviousTabKey()) {
+ selectPreviousTab();
+ } else if (keycode == getNextTabKey()) {
+ selectNextTab();
+ } else if (keycode == getCloseTabKey()) {
+ Tab tab = tb.getTab(activeTabIndex);
+ if (tab.isClosable()) {
+ tab.onClose();
+ }
+ }
+ }
+ }
+
+ /**
+ * @return The key code of the keyboard shortcut that selects the previous
+ * tab in a focused tabsheet.
+ */
+ protected int getPreviousTabKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * @return The key code of the keyboard shortcut that selects the next tab
+ * in a focused tabsheet.
+ */
+ protected int getNextTabKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * @return The key code of the keyboard shortcut that closes the currently
+ * selected tab in a focused tabsheet.
+ */
+ protected int getCloseTabKey() {
+ return KeyCodes.KEY_DELETE;
+ }
+
+ private void selectPreviousTab() {
+ int newTabIndex = activeTabIndex;
+ // Find the previous visible and enabled tab if any.
+ do {
+ newTabIndex--;
+ } while (newTabIndex >= 0 && !onTabSelected(newTabIndex));
+
+ if (newTabIndex >= 0) {
+ activeTabIndex = newTabIndex;
+ if (isScrolledTabs()) {
+ // Scroll until the new active tab is visible
+ int newScrollerIndex = scrollerIndex;
+ while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
+ && newScrollerIndex != -1) {
+ newScrollerIndex = tb.scrollLeft(newScrollerIndex);
+ }
+ scrollerIndex = newScrollerIndex;
+ updateTabScroller();
+ }
+ }
+ }
+
+ private void selectNextTab() {
+ int newTabIndex = activeTabIndex;
+ // Find the next visible and enabled tab if any.
+ do {
+ newTabIndex++;
+ } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex));
+
+ if (newTabIndex < getTabCount()) {
+ activeTabIndex = newTabIndex;
+ if (isClippedTabs()) {
+ // Scroll until the new active tab is completely visible
+ int newScrollerIndex = scrollerIndex;
+ while (isClipped(tb.getTab(activeTabIndex))
+ && newScrollerIndex != -1) {
+ newScrollerIndex = tb.scrollRight(newScrollerIndex);
+ }
+ scrollerIndex = newScrollerIndex;
+ updateTabScroller();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.UIDL;
+
+public abstract class VTabsheetBase extends ComplexPanel {
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final ArrayList<String> tabKeys = new ArrayList<String>();
+ /** For internal use only. May be removed or replaced in the future. */
+ public Set<String> disabledTabKeys = new HashSet<String>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int activeTabIndex = 0;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean disabled;
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean readonly;
+
+ public VTabsheetBase(String classname) {
+ setElement(DOM.createDiv());
+ setStyleName(classname);
+ }
+
+ /**
+ * @return a list of currently shown Widgets
+ */
+ public abstract Iterator<Widget> getWidgetIterator();
+
+ /**
+ * Clears current tabs and contents
+ */
+ abstract protected void clearPaintables();
+
+ /**
+ * Implement in extending classes. This method should render needed elements
+ * and set the visibility of the tab according to the 'selected' parameter.
+ */
+ public abstract void renderTab(final UIDL tabUidl, int index,
+ boolean selected, boolean hidden);
+
+ /**
+ * Implement in extending classes. This method should render any previously
+ * non-cached content and set the activeTabIndex property to the specified
+ * index.
+ */
+ protected abstract void selectTab(int index, final UIDL contentUidl);
+
+ /**
+ * Implement in extending classes. This method should return the number of
+ * tabs currently rendered.
+ */
+ public abstract int getTabCount();
+
+ /**
+ * Implement in extending classes. This method should return the Paintable
+ * corresponding to the given index.
+ */
+ public abstract ComponentConnector getTab(int index);
+
+ /**
+ * Implement in extending classes. This method should remove the rendered
+ * tab with the specified index.
+ */
+ public abstract void removeTab(int index);
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+
+/**
+ * A panel that displays all of its child widgets in a 'deck', where only one
+ * can be visible at a time. It is used by
+ * {@link com.vaadin.client.ui.VTabsheet}.
+ *
+ * This class has the same basic functionality as the GWT DeckPanel
+ * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it
+ * doesn't manipulate the child widgets' width and height attributes.
+ */
+public class VTabsheetPanel extends ComplexPanel {
+
+ private Widget visibleWidget;
+
+ private final TouchScrollHandler touchScrollHandler;
+
+ /**
+ * Creates an empty tabsheet panel.
+ */
+ public VTabsheetPanel() {
+ setElement(DOM.createDiv());
+ touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+ }
+
+ /**
+ * Adds the specified widget to the deck.
+ *
+ * @param w
+ * the widget to be added
+ */
+ @Override
+ public void add(Widget w) {
+ Element el = createContainerElement();
+ DOM.appendChild(getElement(), el);
+ super.add(w, el);
+ }
+
+ private Element createContainerElement() {
+ Element el = DOM.createDiv();
+ DOM.setStyleAttribute(el, "position", "absolute");
+ hide(el);
+ touchScrollHandler.addElement(el);
+ return el;
+ }
+
+ /**
+ * Gets the index of the currently-visible widget.
+ *
+ * @return the visible widget's index
+ */
+ public int getVisibleWidget() {
+ return getWidgetIndex(visibleWidget);
+ }
+
+ /**
+ * Inserts a widget before the specified index.
+ *
+ * @param w
+ * the widget to be inserted
+ * @param beforeIndex
+ * the index before which it will be inserted
+ * @throws IndexOutOfBoundsException
+ * if <code>beforeIndex</code> is out of range
+ */
+ public void insert(Widget w, int beforeIndex) {
+ Element el = createContainerElement();
+ DOM.insertChild(getElement(), el, beforeIndex);
+ super.insert(w, el, beforeIndex, false);
+ }
+
+ @Override
+ public boolean remove(Widget w) {
+ Element child = w.getElement();
+ Element parent = null;
+ if (child != null) {
+ parent = DOM.getParent(child);
+ }
+ final boolean removed = super.remove(w);
+ if (removed) {
+ if (visibleWidget == w) {
+ visibleWidget = null;
+ }
+ if (parent != null) {
+ DOM.removeChild(getElement(), parent);
+ }
+ touchScrollHandler.removeElement(parent);
+ }
+ return removed;
+ }
+
+ /**
+ * Shows the widget at the specified index. This causes the currently-
+ * visible widget to be hidden.
+ *
+ * @param index
+ * the index of the widget to be shown
+ */
+ public void showWidget(int index) {
+ checkIndexBoundsForAccess(index);
+ Widget newVisible = getWidget(index);
+ if (visibleWidget != newVisible) {
+ if (visibleWidget != null) {
+ hide(DOM.getParent(visibleWidget.getElement()));
+ }
+ visibleWidget = newVisible;
+ touchScrollHandler.setElements(visibleWidget.getElement()
+ .getParentElement());
+ }
+ // Always ensure the selected tab is visible. If server prevents a tab
+ // change we might end up here with visibleWidget == newVisible but its
+ // parent is still hidden.
+ unHide(DOM.getParent(visibleWidget.getElement()));
+ }
+
+ private void hide(Element e) {
+ DOM.setStyleAttribute(e, "visibility", "hidden");
+ DOM.setStyleAttribute(e, "top", "-100000px");
+ DOM.setStyleAttribute(e, "left", "-100000px");
+ }
+
+ private void unHide(Element e) {
+ DOM.setStyleAttribute(e, "top", "0px");
+ DOM.setStyleAttribute(e, "left", "0px");
+ DOM.setStyleAttribute(e, "visibility", "");
+ }
+
+ public void fixVisibleTabSize(int width, int height, int minWidth) {
+ if (visibleWidget == null) {
+ return;
+ }
+
+ boolean dynamicHeight = false;
+
+ if (height < 0) {
+ height = visibleWidget.getOffsetHeight();
+ dynamicHeight = true;
+ }
+ if (width < 0) {
+ width = visibleWidget.getOffsetWidth();
+ }
+ if (width < minWidth) {
+ width = minWidth;
+ }
+
+ Element wrapperDiv = (Element) visibleWidget.getElement()
+ .getParentElement();
+
+ // width first
+ getElement().getStyle().setPropertyPx("width", width);
+ wrapperDiv.getStyle().setPropertyPx("width", width);
+
+ if (dynamicHeight) {
+ // height of widget might have changed due wrapping
+ height = visibleWidget.getOffsetHeight();
+ }
+ // v-tabsheet-tabsheetpanel height
+ getElement().getStyle().setPropertyPx("height", height);
+
+ // widget wrapper height
+ if (dynamicHeight) {
+ wrapperDiv.getStyle().clearHeight();
+ } else {
+ // widget wrapper height
+ wrapperDiv.getStyle().setPropertyPx("height", height);
+ }
+ }
+
+ public void replaceComponent(Widget oldComponent, Widget newComponent) {
+ boolean isVisible = (visibleWidget == oldComponent);
+ int widgetIndex = getWidgetIndex(oldComponent);
+ remove(oldComponent);
+ insert(newComponent, widgetIndex);
+ if (isVisible) {
+ showWidget(widgetIndex);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.TextAreaElement;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+
+/**
+ * This class represents a multiline textfield (textarea).
+ *
+ * TODO consider replacing this with a RichTextArea based implementation. IE
+ * does not support CSS height for textareas in Strict mode :-(
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+public class VTextArea extends VTextField {
+ public static final String CLASSNAME = "v-textarea";
+ private boolean wordwrap = true;
+ private MaxLengthHandler maxLengthHandler = new MaxLengthHandler();
+ private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute();
+
+ public VTextArea() {
+ super(DOM.createTextArea());
+ setStyleName(CLASSNAME);
+ if (!browserSupportsMaxLengthAttribute) {
+ addKeyUpHandler(maxLengthHandler);
+ addChangeHandler(maxLengthHandler);
+ sinkEvents(Event.ONPASTE);
+ }
+ }
+
+ public TextAreaElement getTextAreaElement() {
+ return super.getElement().cast();
+ }
+
+ public void setRows(int rows) {
+ getTextAreaElement().setRows(rows);
+ }
+
+ private class MaxLengthHandler implements KeyUpHandler, ChangeHandler {
+
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ enforceMaxLength();
+ }
+
+ public void onPaste(Event event) {
+ enforceMaxLength();
+ }
+
+ @Override
+ public void onChange(ChangeEvent event) {
+ // Opera does not support paste events so this enforces max length
+ // for Opera.
+ enforceMaxLength();
+ }
+
+ }
+
+ protected void enforceMaxLength() {
+ if (getMaxLength() >= 0) {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ if (getText().length() > getMaxLength()) {
+ setText(getText().substring(0, getMaxLength()));
+ }
+ }
+ });
+ }
+ }
+
+ protected boolean browserSupportsMaxLengthAttribute() {
+ BrowserInfo info = BrowserInfo.get();
+ if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) {
+ return true;
+ }
+ if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) {
+ return true;
+ }
+ if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) {
+ return true;
+ }
+ if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void updateMaxLength(int maxLength) {
+ if (browserSupportsMaxLengthAttribute) {
+ super.updateMaxLength(maxLength);
+ } else {
+ // Events handled by MaxLengthHandler. This call enforces max length
+ // when the max length value has changed
+ enforceMaxLength();
+ }
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONPASTE) {
+ maxLengthHandler.onPaste(event);
+ }
+ }
+
+ @Override
+ public int getCursorPos() {
+ // This is needed so that TextBoxImplIE6 is used to return the correct
+ // position for old Internet Explorer versions where it has to be
+ // detected in a different way.
+ return getImpl().getTextAreaCursorPos(getElement());
+ }
+
+ @Override
+ protected void setMaxLengthToElement(int newMaxLength) {
+ // There is no maxlength property for textarea. The maximum length is
+ // enforced by the KEYUP handler
+
+ }
+
+ public void setWordwrap(boolean wordwrap) {
+ if (wordwrap == this.wordwrap) {
+ return; // No change
+ }
+
+ if (wordwrap) {
+ getElement().removeAttribute("wrap");
+ getElement().getStyle().clearOverflow();
+ } else {
+ getElement().setAttribute("wrap", "off");
+ getElement().getStyle().setOverflow(Overflow.AUTO);
+ }
+ if (BrowserInfo.get().isOpera()) {
+ // Opera fails to dynamically update the wrap attribute so we detach
+ // and reattach the whole TextArea.
+ Util.detachAttach(getElement());
+ }
+ this.wordwrap = wordwrap;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.TextBoxBase;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.textfield.TextFieldConstants;
+
+/**
+ * This class represents a basic text input field with one row.
+ *
+ * @author Vaadin Ltd.
+ *
+ */
+public class VTextField extends TextBoxBase implements Field, ChangeHandler,
+ FocusHandler, BlurHandler, KeyDownHandler {
+
+ /**
+ * The input node CSS classname.
+ */
+ public static final String CLASSNAME = "v-textfield";
+ /**
+ * This CSS classname is added to the input node on hover.
+ */
+ public static final String CLASSNAME_FOCUS = "focus";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String valueBeforeEdit = null;
+
+ /**
+ * Set to false if a text change event has been sent since the last value
+ * change event. This means that {@link #valueBeforeEdit} should not be
+ * trusted when determining whether a text change even should be sent.
+ */
+ private boolean valueBeforeEditIsSynced = true;
+
+ private boolean immediate = false;
+ private int maxLength = -1;
+
+ private static final String CLASSNAME_PROMPT = "prompt";
+ private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT";
+
+ private String inputPrompt = null;
+ private boolean prompting = false;
+ private int lastCursorPos = -1;
+
+ public VTextField() {
+ this(DOM.createInputText());
+ }
+
+ protected VTextField(Element node) {
+ super(node);
+ setStyleName(CLASSNAME);
+ addChangeHandler(this);
+ if (BrowserInfo.get().isIE()) {
+ // IE does not send change events when pressing enter in a text
+ // input so we handle it using a key listener instead
+ addKeyDownHandler(this);
+ }
+ addFocusHandler(this);
+ addBlurHandler(this);
+ }
+
+ /**
+ * For internal use only. May be removed or replaced in the future.
+ * <p>
+ * TODO When GWT adds ONCUT, add it there and remove workaround. See
+ * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030
+ * <p>
+ * Also note that the cut/paste are not totally crossbrowsers compatible.
+ * E.g. in Opera mac works via context menu, but on via File->Paste/Cut.
+ * Opera might need the polling method for 100% working textchanceevents.
+ * Eager polling for a change is bit dum and heavy operation, so I guess we
+ * should first try to survive without.
+ */
+ public static final int TEXTCHANGE_EVENTS = Event.ONPASTE | Event.KEYEVENTS
+ | Event.ONMOUSEUP;
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ if (listenTextChangeEvents
+ && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event
+ .getTypeInt()) {
+ deferTextChangeEvent();
+ }
+
+ }
+
+ /*
+ * TODO optimize this so that only changes are sent + make the value change
+ * event just a flag that moves the current text to value
+ */
+ private String lastTextChangeString = null;
+
+ private String getLastCommunicatedString() {
+ return lastTextChangeString;
+ }
+
+ private void communicateTextValueToServer() {
+ String text = getText();
+ if (prompting) {
+ // Input prompt visible, text is actually ""
+ text = "";
+ }
+ if (!text.equals(getLastCommunicatedString())) {
+ if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) {
+ /*
+ * Value change for the current text has been enqueued since the
+ * last text change event was sent, but we can't know that it
+ * has been sent to the server. Ensure that all pending changes
+ * are sent now. Sending a value change without a text change
+ * will simulate a TextChangeEvent on the server.
+ */
+ client.sendPendingVariableChanges();
+ } else {
+ // Default case - just send an immediate text change message
+ client.updateVariable(paintableId,
+ TextFieldConstants.VAR_CUR_TEXT, text, true);
+
+ // Shouldn't investigate valueBeforeEdit to avoid duplicate text
+ // change events as the states are not in sync any more
+ valueBeforeEditIsSynced = false;
+ }
+ lastTextChangeString = text;
+ }
+ }
+
+ private Timer textChangeEventTrigger = new Timer() {
+
+ @Override
+ public void run() {
+ if (isAttached()) {
+ updateCursorPosition();
+ communicateTextValueToServer();
+ scheduled = false;
+ }
+ }
+ };
+
+ private boolean scheduled = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean listenTextChangeEvents;
+ /** For internal use only. May be removed or replaced in the future. */
+ public String textChangeEventMode;
+ public int textChangeEventTimeout;
+
+ private void deferTextChangeEvent() {
+ if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) {
+ return;
+ } else {
+ textChangeEventTrigger.cancel();
+ }
+ textChangeEventTrigger.schedule(getTextChangeEventTimeout());
+ scheduled = true;
+ }
+
+ private int getTextChangeEventTimeout() {
+ return textChangeEventTimeout;
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ boolean wasReadOnly = isReadOnly();
+
+ if (readOnly) {
+ setTabIndex(-1);
+ } else if (wasReadOnly && !readOnly && getTabIndex() == -1) {
+ /*
+ * Need to manually set tab index to 0 since server will not send
+ * the tab index if it is 0.
+ */
+ setTabIndex(0);
+ }
+
+ super.setReadOnly(readOnly);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateFieldContent(final String text) {
+ setPrompting(inputPrompt != null && focusedTextField != this
+ && (text.equals("")));
+
+ String fieldValue;
+ if (prompting) {
+ fieldValue = isReadOnly() ? "" : inputPrompt;
+ addStyleDependentName(CLASSNAME_PROMPT);
+ } else {
+ fieldValue = text;
+ removeStyleDependentName(CLASSNAME_PROMPT);
+ }
+ setText(fieldValue);
+
+ lastTextChangeString = valueBeforeEdit = text;
+ valueBeforeEditIsSynced = true;
+ }
+
+ protected void onCut() {
+ if (listenTextChangeEvents) {
+ deferTextChangeEvent();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public native void attachCutEventListener(Element el)
+ /*-{
+ var me = this;
+ el.oncut = $entry(function() {
+ me.@com.vaadin.client.ui.textfield.VTextField::onCut()();
+ });
+ }-*/;
+
+ protected native void detachCutEventListener(Element el)
+ /*-{
+ el.oncut = null;
+ }-*/;
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ detachCutEventListener(getElement());
+ if (focusedTextField == this) {
+ focusedTextField = null;
+ }
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (listenTextChangeEvents) {
+ detachCutEventListener(getElement());
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setMaxLength(int newMaxLength) {
+ if (newMaxLength >= 0 && newMaxLength != maxLength) {
+ maxLength = newMaxLength;
+ updateMaxLength(maxLength);
+ } else if (maxLength != -1) {
+ maxLength = -1;
+ updateMaxLength(maxLength);
+ }
+
+ }
+
+ /**
+ * This method is reponsible for updating the DOM or otherwise ensuring that
+ * the given max length is enforced. Called when the max length for the
+ * field has changed.
+ *
+ * @param maxLength
+ * The new max length
+ */
+ protected void updateMaxLength(int maxLength) {
+ if (maxLength >= 0) {
+ getElement().setPropertyInt("maxLength", maxLength);
+ } else {
+ getElement().removeAttribute("maxLength");
+
+ }
+ setMaxLengthToElement(maxLength);
+ }
+
+ protected void setMaxLengthToElement(int newMaxLength) {
+ if (newMaxLength >= 0) {
+ getElement().setPropertyInt("maxLength", newMaxLength);
+ } else {
+ getElement().removeAttribute("maxLength");
+ }
+ }
+
+ public int getMaxLength() {
+ return maxLength;
+ }
+
+ @Override
+ public void onChange(ChangeEvent event) {
+ valueChange(false);
+ }
+
+ /**
+ * Called when the field value might have changed and/or the field was
+ * blurred. These are combined so the blur event is sent in the same batch
+ * as a possible value change event (these are often connected).
+ *
+ * @param blurred
+ * true if the field was blurred
+ */
+ public void valueChange(boolean blurred) {
+ if (client != null && paintableId != null) {
+ boolean sendBlurEvent = false;
+ boolean sendValueChange = false;
+
+ if (blurred && client.hasEventListeners(this, EventId.BLUR)) {
+ sendBlurEvent = true;
+ client.updateVariable(paintableId, EventId.BLUR, "", false);
+ }
+
+ String newText = getText();
+ if (!prompting && newText != null
+ && !newText.equals(valueBeforeEdit)) {
+ sendValueChange = immediate;
+ client.updateVariable(paintableId, "text", newText, false);
+ valueBeforeEdit = newText;
+ valueBeforeEditIsSynced = true;
+ }
+
+ /*
+ * also send cursor position, no public api yet but for easier
+ * extension
+ */
+ updateCursorPosition();
+
+ if (sendBlurEvent || sendValueChange) {
+ /*
+ * Avoid sending text change event as we will simulate it on the
+ * server side before value change events.
+ */
+ textChangeEventTrigger.cancel();
+ scheduled = false;
+ client.sendPendingVariableChanges();
+ }
+ }
+ }
+
+ /**
+ * Updates the cursor position variable if it has changed since the last
+ * update.
+ *
+ * @return true iff the value was updated
+ */
+ protected boolean updateCursorPosition() {
+ if (Util.isAttachedAndDisplayed(this)) {
+ int cursorPos = getCursorPos();
+ if (lastCursorPos != cursorPos) {
+ client.updateVariable(paintableId,
+ TextFieldConstants.VAR_CURSOR, cursorPos, false);
+ lastCursorPos = cursorPos;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static VTextField focusedTextField;
+
+ public static void flushChangesFromFocusedTextField() {
+ if (focusedTextField != null) {
+ focusedTextField.onChange(null);
+ }
+ }
+
+ @Override
+ public void onFocus(FocusEvent event) {
+ addStyleDependentName(CLASSNAME_FOCUS);
+ if (prompting) {
+ setText("");
+ removeStyleDependentName(CLASSNAME_PROMPT);
+ setPrompting(false);
+ }
+ focusedTextField = this;
+ if (client.hasEventListeners(this, EventId.FOCUS)) {
+ client.updateVariable(paintableId, EventId.FOCUS, "", true);
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ // this is called twice on Chrome when e.g. changing tab while prompting
+ // field focused - do not change settings on the second time
+ if (focusedTextField != this) {
+ return;
+ }
+ removeStyleDependentName(CLASSNAME_FOCUS);
+ focusedTextField = null;
+ String text = getText();
+ setPrompting(inputPrompt != null && (text == null || "".equals(text)));
+ if (prompting) {
+ setText(isReadOnly() ? "" : inputPrompt);
+ addStyleDependentName(CLASSNAME_PROMPT);
+ }
+
+ valueChange(true);
+ }
+
+ private void setPrompting(boolean prompting) {
+ this.prompting = prompting;
+ }
+
+ public void setColumns(int columns) {
+ if (columns <= 0) {
+ return;
+ }
+
+ setWidth(columns + "em");
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
+ valueChange(false);
+ }
+ }
+
+ public void setImmediate(boolean immediate) {
+ this.immediate = immediate;
+ }
+
+ public void setInputPrompt(String inputPrompt) {
+ this.inputPrompt = inputPrompt;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.TextBox;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LocaleNotLoadedException;
+import com.vaadin.client.LocaleService;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+public class VTextualDate extends VDateField implements Field, ChangeHandler,
+ Focusable, SubPartAware {
+
+ private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final TextBox text;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String formatStr;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean lenient;
+
+ private static final String CLASSNAME_PROMPT = "prompt";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public static final String ATTR_INPUTPROMPT = "prompt";
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String inputPrompt = "";
+
+ private boolean prompting = false;
+
+ public VTextualDate() {
+ super();
+ text = new TextBox();
+ text.addChangeHandler(this);
+ text.addFocusHandler(new FocusHandler() {
+ @Override
+ public void onFocus(FocusEvent event) {
+ text.addStyleName(VTextField.CLASSNAME + "-"
+ + VTextField.CLASSNAME_FOCUS);
+ if (prompting) {
+ text.setText("");
+ setPrompting(false);
+ }
+ if (getClient() != null
+ && getClient().hasEventListeners(VTextualDate.this,
+ EventId.FOCUS)) {
+ getClient()
+ .updateVariable(getId(), EventId.FOCUS, "", true);
+ }
+ }
+ });
+ text.addBlurHandler(new BlurHandler() {
+ @Override
+ public void onBlur(BlurEvent event) {
+ text.removeStyleName(VTextField.CLASSNAME + "-"
+ + VTextField.CLASSNAME_FOCUS);
+ String value = getText();
+ setPrompting(inputPrompt != null
+ && (value == null || "".equals(value)));
+ if (prompting) {
+ text.setText(readonly ? "" : inputPrompt);
+ }
+ if (getClient() != null
+ && getClient().hasEventListeners(VTextualDate.this,
+ EventId.BLUR)) {
+ getClient().updateVariable(getId(), EventId.BLUR, "", true);
+ }
+ }
+ });
+ add(text);
+ }
+
+ protected void updateStyleNames() {
+ if (text != null) {
+ text.setStyleName(VTextField.CLASSNAME);
+ text.addStyleName(getStylePrimaryName() + "-textfield");
+ }
+ }
+
+ protected String getFormatString() {
+ if (formatStr == null) {
+ if (currentResolution == Resolution.YEAR) {
+ formatStr = "yyyy"; // force full year
+ } else {
+
+ try {
+ String frmString = LocaleService
+ .getDateFormat(currentLocale);
+ frmString = cleanFormat(frmString);
+ // String delim = LocaleService
+ // .getClockDelimiter(currentLocale);
+ if (currentResolution.getCalendarField() >= Resolution.HOUR
+ .getCalendarField()) {
+ if (dts.isTwelveHourClock()) {
+ frmString += " hh";
+ } else {
+ frmString += " HH";
+ }
+ if (currentResolution.getCalendarField() >= Resolution.MINUTE
+ .getCalendarField()) {
+ frmString += ":mm";
+ if (currentResolution.getCalendarField() >= Resolution.SECOND
+ .getCalendarField()) {
+ frmString += ":ss";
+ }
+ }
+ if (dts.isTwelveHourClock()) {
+ frmString += " aaa";
+ }
+
+ }
+
+ formatStr = frmString;
+ } catch (LocaleNotLoadedException e) {
+ // TODO should die instead? Can the component survive
+ // without format string?
+ VConsole.error(e);
+ }
+ }
+ }
+ return formatStr;
+ }
+
+ /**
+ * Updates the text field according to the current date (provided by
+ * {@link #getDate()}). Takes care of updating text, enabling and disabling
+ * the field, setting/removing readonly status and updating readonly styles.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ * <p>
+ * TODO: Split part of this into a method that only updates the text as this
+ * is what usually is needed except for updateFromUIDL.
+ */
+ public void buildDate() {
+ removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+ // Create the initial text for the textfield
+ String dateText;
+ Date currentDate = getDate();
+ if (currentDate != null) {
+ dateText = getDateTimeService().formatDate(currentDate,
+ getFormatString());
+ } else {
+ dateText = "";
+ }
+
+ setText(dateText);
+ text.setEnabled(enabled);
+ text.setReadOnly(readonly);
+
+ if (readonly) {
+ text.addStyleName("v-readonly");
+ } else {
+ text.removeStyleName("v-readonly");
+ }
+
+ }
+
+ protected void setPrompting(boolean prompting) {
+ this.prompting = prompting;
+ if (prompting) {
+ addStyleDependentName(CLASSNAME_PROMPT);
+ } else {
+ removeStyleDependentName(CLASSNAME_PROMPT);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onChange(ChangeEvent event) {
+ if (!text.getText().equals("")) {
+ try {
+ String enteredDate = text.getText();
+
+ setDate(getDateTimeService().parseDate(enteredDate,
+ getFormatString(), lenient));
+
+ if (lenient) {
+ // If date value was leniently parsed, normalize text
+ // presentation.
+ // FIXME: Add a description/example here of when this is
+ // needed
+ text.setValue(
+ getDateTimeService().formatDate(getDate(),
+ getFormatString()), false);
+ }
+
+ // remove possibly added invalid value indication
+ removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+ } catch (final Exception e) {
+ VConsole.log(e);
+
+ addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+ // this is a hack that may eventually be removed
+ getClient().updateVariable(getId(), "lastInvalidDateString",
+ text.getText(), false);
+ setDate(null);
+ }
+ } else {
+ setDate(null);
+ // remove possibly added invalid value indication
+ removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+ }
+ // always send the date string
+ getClient()
+ .updateVariable(getId(), "dateString", text.getText(), false);
+
+ // Update variables
+ // (only the smallest defining resolution needs to be
+ // immediate)
+ Date currentDate = getDate();
+ getClient().updateVariable(getId(), "year",
+ currentDate != null ? currentDate.getYear() + 1900 : -1,
+ currentResolution == Resolution.YEAR && immediate);
+ if (currentResolution.getCalendarField() >= Resolution.MONTH
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "month",
+ currentDate != null ? currentDate.getMonth() + 1 : -1,
+ currentResolution == Resolution.MONTH && immediate);
+ }
+ if (currentResolution.getCalendarField() >= Resolution.DAY
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "day",
+ currentDate != null ? currentDate.getDate() : -1,
+ currentResolution == Resolution.DAY && immediate);
+ }
+ if (currentResolution.getCalendarField() >= Resolution.HOUR
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "hour",
+ currentDate != null ? currentDate.getHours() : -1,
+ currentResolution == Resolution.HOUR && immediate);
+ }
+ if (currentResolution.getCalendarField() >= Resolution.MINUTE
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "min",
+ currentDate != null ? currentDate.getMinutes() : -1,
+ currentResolution == Resolution.MINUTE && immediate);
+ }
+ if (currentResolution.getCalendarField() >= Resolution.SECOND
+ .getCalendarField()) {
+ getClient().updateVariable(getId(), "sec",
+ currentDate != null ? currentDate.getSeconds() : -1,
+ currentResolution == Resolution.SECOND && immediate);
+ }
+
+ }
+
+ private String cleanFormat(String format) {
+ // Remove unnecessary d & M if resolution is too low
+ if (currentResolution.getCalendarField() < Resolution.DAY
+ .getCalendarField()) {
+ format = format.replaceAll("d", "");
+ }
+ if (currentResolution.getCalendarField() < Resolution.MONTH
+ .getCalendarField()) {
+ format = format.replaceAll("M", "");
+ }
+
+ // Remove unsupported patterns
+ // TODO support for 'G', era designator (used at least in Japan)
+ format = format.replaceAll("[GzZwWkK]", "");
+
+ // Remove extra delimiters ('/' and '.')
+ while (format.startsWith("/") || format.startsWith(".")
+ || format.startsWith("-")) {
+ format = format.substring(1);
+ }
+ while (format.endsWith("/") || format.endsWith(".")
+ || format.endsWith("-")) {
+ format = format.substring(0, format.length() - 1);
+ }
+
+ // Remove duplicate delimiters
+ format = format.replaceAll("//", "/");
+ format = format.replaceAll("\\.\\.", ".");
+ format = format.replaceAll("--", "-");
+
+ return format.trim();
+ }
+
+ @Override
+ public void focus() {
+ text.setFocus(true);
+ }
+
+ protected String getText() {
+ if (prompting) {
+ return "";
+ }
+ return text.getText();
+ }
+
+ protected void setText(String text) {
+ if (inputPrompt != null && (text == null || "".equals(text))) {
+ text = readonly ? "" : inputPrompt;
+ setPrompting(true);
+ } else {
+ setPrompting(false);
+ }
+
+ this.text.setText(text);
+ }
+
+ private final String TEXTFIELD_ID = "field";
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if (subPart.equals(TEXTFIELD_ID)) {
+ return text.getElement();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (text.getElement().isOrHasChild(subElement)) {
+ return TEXTFIELD_ID;
+ }
+
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.dd.DDUtil;
+import com.vaadin.client.ui.dd.VAbstractDropHandler;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragAndDropManager;
+import com.vaadin.client.ui.dd.VDragEvent;
+import com.vaadin.client.ui.dd.VDropHandler;
+import com.vaadin.client.ui.dd.VHasDropHandler;
+import com.vaadin.client.ui.dd.VTransferable;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.MouseEventDetails.MouseButton;
+import com.vaadin.shared.ui.MultiSelectMode;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.tree.TreeConstants;
+
+/**
+ *
+ */
+public class VTree extends FocusElementPanel implements VHasDropHandler,
+ FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler,
+ SubPartAware, ActionOwner {
+
+ public static final String CLASSNAME = "v-tree";
+
+ /**
+ * @deprecated from 7.0, use {@link MultiSelectMode#DEFAULT} instead.
+ */
+ @Deprecated
+ public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT;
+
+ /**
+ * @deprecated from 7.0, use {@link MultiSelectMode#SIMPLE} instead.
+ */
+ @Deprecated
+ public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE;
+
+ private static final int CHARCODE_SPACE = 32;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final FlowPanel body = new FlowPanel();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Set<String> selectedIds = new HashSet<String>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean selectable;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean isMultiselect;
+
+ private String currentMouseOverKey;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public TreeNode lastSelection;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public TreeNode focusedNode;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
+
+ /**
+ * This map contains captions and icon urls for actions like: * "33_c" ->
+ * "Edit" * "33_i" -> "http://dom.com/edit.png"
+ */
+ private final HashMap<String, String> actionMap = new HashMap<String, String>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean isNullSelectionAllowed = true;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean disabled = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean readonly;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean rendering;
+
+ private VAbstractDropHandler dropHandler;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int dragMode;
+
+ private boolean selectionHasChanged = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String[] bodyActionKeys;
+
+ public VLazyExecutor iconLoaded = new VLazyExecutor(50,
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ Util.notifyParentOfSizeChange(VTree.this, true);
+ }
+
+ });
+
+ public VTree() {
+ super();
+ setStyleName(CLASSNAME);
+ add(body);
+
+ addFocusHandler(this);
+ addBlurHandler(this);
+
+ /*
+ * Listen to context menu events on the empty space in the tree
+ */
+ sinkEvents(Event.ONCONTEXTMENU);
+ addDomHandler(new ContextMenuHandler() {
+ @Override
+ public void onContextMenu(ContextMenuEvent event) {
+ handleBodyContextMenu(event);
+ }
+ }, ContextMenuEvent.getType());
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+
+ /*
+ * We need to use the sinkEvents method to catch the keyUp events so we
+ * can cache a single shift. KeyUpHandler cannot do this. At the same
+ * time we catch the mouse down and up events so we can apply the text
+ * selection patch in IE
+ */
+ sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
+
+ /*
+ * Re-set the tab index to make sure that the FocusElementPanel's
+ * (super) focus element gets the tab index and not the element
+ * containing the tree.
+ */
+ setTabIndex(0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+ * .client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONMOUSEDOWN) {
+ // Prevent default text selection in IE
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast()).setPropertyJSO(
+ "onselectstart", applyDisableTextSelectionIEHack());
+ }
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ // Remove IE text selection hack
+ if (BrowserInfo.get().isIE()) {
+ ((Element) event.getEventTarget().cast()).setPropertyJSO(
+ "onselectstart", null);
+ }
+ } else if (event.getTypeInt() == Event.ONKEYUP) {
+ if (selectionHasChanged) {
+ if (event.getKeyCode() == getNavigationDownKey()
+ && !event.getShiftKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == getNavigationUpKey()
+ && !event.getShiftKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
+ sendSelectionToServer();
+ event.preventDefault();
+ } else if (event.getKeyCode() == getNavigationSelectKey()) {
+ sendSelectionToServer();
+ event.preventDefault();
+ }
+ }
+ }
+ }
+
+ public String getActionCaption(String actionKey) {
+ return actionMap.get(actionKey + "_c");
+ }
+
+ public String getActionIcon(String actionKey) {
+ return actionMap.get(actionKey + "_i");
+ }
+
+ /**
+ * Returns the first root node of the tree or null if there are no root
+ * nodes.
+ *
+ * @return The first root {@link TreeNode}
+ */
+ protected TreeNode getFirstRootNode() {
+ if (body.getWidgetCount() == 0) {
+ return null;
+ }
+ return (TreeNode) body.getWidget(0);
+ }
+
+ /**
+ * Returns the last root node of the tree or null if there are no root
+ * nodes.
+ *
+ * @return The last root {@link TreeNode}
+ */
+ protected TreeNode getLastRootNode() {
+ if (body.getWidgetCount() == 0) {
+ return null;
+ }
+ return (TreeNode) body.getWidget(body.getWidgetCount() - 1);
+ }
+
+ /**
+ * Returns a list of all root nodes in the Tree in the order they appear in
+ * the tree.
+ *
+ * @return A list of all root {@link TreeNode}s.
+ */
+ protected List<TreeNode> getRootNodes() {
+ ArrayList<TreeNode> rootNodes = new ArrayList<TreeNode>();
+ for (int i = 0; i < body.getWidgetCount(); i++) {
+ rootNodes.add((TreeNode) body.getWidget(i));
+ }
+ return rootNodes;
+ }
+
+ private void updateTreeRelatedDragData(VDragEvent drag) {
+
+ currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver());
+
+ drag.getDropDetails().put("itemIdOver", currentMouseOverKey);
+ if (currentMouseOverKey != null) {
+ TreeNode treeNode = getNodeByKey(currentMouseOverKey);
+ VerticalDropLocation detail = treeNode.getDropDetail(drag
+ .getCurrentGwtEvent());
+ Boolean overTreeNode = null;
+ if (treeNode != null && !treeNode.isLeaf()
+ && detail == VerticalDropLocation.MIDDLE) {
+ overTreeNode = true;
+ }
+ drag.getDropDetails().put("itemIdOverIsNode", overTreeNode);
+ drag.getDropDetails().put("detail", detail);
+ } else {
+ drag.getDropDetails().put("itemIdOverIsNode", null);
+ drag.getDropDetails().put("detail", null);
+ }
+
+ }
+
+ private String findCurrentMouseOverKey(Element elementOver) {
+ TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class);
+ return treeNode == null ? null : treeNode.key;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateDropHandler(UIDL childUidl) {
+ if (dropHandler == null) {
+ dropHandler = new VAbstractDropHandler() {
+
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ }
+
+ @Override
+ protected void dragAccepted(final VDragEvent drag) {
+
+ }
+
+ @Override
+ public void dragOver(final VDragEvent currentDrag) {
+ final Object oldIdOver = currentDrag.getDropDetails().get(
+ "itemIdOver");
+ final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag
+ .getDropDetails().get("detail");
+
+ updateTreeRelatedDragData(currentDrag);
+ final VerticalDropLocation detail = (VerticalDropLocation) currentDrag
+ .getDropDetails().get("detail");
+ boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver)
+ || (currentMouseOverKey == null && oldIdOver != null);
+ boolean detailHasChanded = (detail != null && detail != oldDetail)
+ || (detail == null && oldDetail != null);
+
+ if (nodeHasChanged || detailHasChanded) {
+ final String newKey = currentMouseOverKey;
+ TreeNode treeNode = keyToNode.get(oldIdOver);
+ if (treeNode != null) {
+ // clear old styles
+ treeNode.emphasis(null);
+ }
+ if (newKey != null) {
+ validate(new VAcceptCallback() {
+ @Override
+ public void accepted(VDragEvent event) {
+ VerticalDropLocation curDetail = (VerticalDropLocation) event
+ .getDropDetails().get("detail");
+ if (curDetail == detail
+ && newKey.equals(currentMouseOverKey)) {
+ getNodeByKey(newKey).emphasis(detail);
+ }
+ /*
+ * Else drag is already on a different
+ * node-detail pair, new criteria check is
+ * going on
+ */
+ }
+ }, currentDrag);
+
+ }
+ }
+
+ }
+
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ cleanUp();
+ }
+
+ private void cleanUp() {
+ if (currentMouseOverKey != null) {
+ getNodeByKey(currentMouseOverKey).emphasis(null);
+ currentMouseOverKey = null;
+ }
+ }
+
+ @Override
+ public boolean drop(VDragEvent drag) {
+ cleanUp();
+ return super.drop(drag);
+ }
+
+ @Override
+ public ComponentConnector getConnector() {
+ return ConnectorMap.get(client).getConnector(VTree.this);
+ }
+
+ @Override
+ public ApplicationConnection getApplicationConnection() {
+ return client;
+ }
+
+ };
+ }
+ dropHandler.updateAcceptRules(childUidl);
+ }
+
+ public void setSelected(TreeNode treeNode, boolean selected) {
+ if (selected) {
+ if (!isMultiselect) {
+ while (selectedIds.size() > 0) {
+ final String id = selectedIds.iterator().next();
+ final TreeNode oldSelection = getNodeByKey(id);
+ if (oldSelection != null) {
+ // can be null if the node is not visible (parent
+ // collapsed)
+ oldSelection.setSelected(false);
+ }
+ selectedIds.remove(id);
+ }
+ }
+ treeNode.setSelected(true);
+ selectedIds.add(treeNode.key);
+ } else {
+ if (!isNullSelectionAllowed) {
+ if (!isMultiselect || selectedIds.size() == 1) {
+ return;
+ }
+ }
+ selectedIds.remove(treeNode.key);
+ treeNode.setSelected(false);
+ }
+
+ sendSelectionToServer();
+ }
+
+ /**
+ * Sends the selection to the server
+ */
+ private void sendSelectionToServer() {
+ Command command = new Command() {
+ @Override
+ public void execute() {
+ client.updateVariable(paintableId, "selected",
+ selectedIds.toArray(new String[selectedIds.size()]),
+ immediate);
+ selectionHasChanged = false;
+ }
+ };
+
+ /*
+ * Delaying the sending of the selection in webkit to ensure the
+ * selection is always sent when the tree has focus and after click
+ * events have been processed. This is due to the focusing
+ * implementation in FocusImplSafari which uses timeouts when focusing
+ * and blurring.
+ */
+ if (BrowserInfo.get().isWebkit()) {
+ Scheduler.get().scheduleDeferred(command);
+ } else {
+ command.execute();
+ }
+ }
+
+ /**
+ * Is a node selected in the tree
+ *
+ * @param treeNode
+ * The node to check
+ * @return
+ */
+ public boolean isSelected(TreeNode treeNode) {
+ return selectedIds.contains(treeNode.key);
+ }
+
+ public class TreeNode extends SimplePanel implements ActionOwner {
+
+ public static final String CLASSNAME = "v-tree-node";
+ public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
+
+ public String key;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String[] actionKeys = null;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean childrenLoaded;
+
+ Element nodeCaptionDiv;
+
+ protected Element nodeCaptionSpan;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public FlowPanel childNodeContainer;
+
+ private boolean open;
+
+ private Icon icon;
+
+ private Event mouseDownEvent;
+
+ private int cachedHeight = -1;
+
+ private boolean focused = false;
+
+ public TreeNode() {
+ constructDom();
+ sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
+ | Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
+ }
+
+ public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) {
+ if (cachedHeight < 0) {
+ /*
+ * Height is cached to avoid flickering (drop hints may change
+ * the reported offsetheight -> would change the drop detail)
+ */
+ cachedHeight = nodeCaptionDiv.getOffsetHeight();
+ }
+ VerticalDropLocation verticalDropLocation = DDUtil
+ .getVerticalDropLocation(nodeCaptionDiv, cachedHeight,
+ currentGwtEvent, 0.15);
+ return verticalDropLocation;
+ }
+
+ protected void emphasis(VerticalDropLocation detail) {
+ String base = "v-tree-node-drag-";
+ UIObject.setStyleName(getElement(), base + "top",
+ VerticalDropLocation.TOP == detail);
+ UIObject.setStyleName(getElement(), base + "bottom",
+ VerticalDropLocation.BOTTOM == detail);
+ UIObject.setStyleName(getElement(), base + "center",
+ VerticalDropLocation.MIDDLE == detail);
+ base = "v-tree-node-caption-drag-";
+ UIObject.setStyleName(nodeCaptionDiv, base + "top",
+ VerticalDropLocation.TOP == detail);
+ UIObject.setStyleName(nodeCaptionDiv, base + "bottom",
+ VerticalDropLocation.BOTTOM == detail);
+ UIObject.setStyleName(nodeCaptionDiv, base + "center",
+ VerticalDropLocation.MIDDLE == detail);
+
+ // also add classname to "folder node" into which the drag is
+ // targeted
+
+ TreeNode folder = null;
+ /* Possible parent of this TreeNode will be stored here */
+ TreeNode parentFolder = getParentNode();
+
+ // TODO fix my bugs
+ if (isLeaf()) {
+ folder = parentFolder;
+ // note, parent folder may be null if this is root node => no
+ // folder target exists
+ } else {
+ if (detail == VerticalDropLocation.TOP) {
+ folder = parentFolder;
+ } else {
+ folder = this;
+ }
+ // ensure we remove the dragfolder classname from the previous
+ // folder node
+ setDragFolderStyleName(this, false);
+ setDragFolderStyleName(parentFolder, false);
+ }
+ if (folder != null) {
+ setDragFolderStyleName(folder, detail != null);
+ }
+
+ }
+
+ private TreeNode getParentNode() {
+ Widget parent2 = getParent().getParent();
+ if (parent2 instanceof TreeNode) {
+ return (TreeNode) parent2;
+ }
+ return null;
+ }
+
+ private void setDragFolderStyleName(TreeNode folder, boolean add) {
+ if (folder != null) {
+ UIObject.setStyleName(folder.getElement(),
+ "v-tree-node-dragfolder", add);
+ UIObject.setStyleName(folder.nodeCaptionDiv,
+ "v-tree-node-caption-dragfolder", add);
+ }
+ }
+
+ /**
+ * Handles mouse selection
+ *
+ * @param ctrl
+ * Was the ctrl-key pressed
+ * @param shift
+ * Was the shift-key pressed
+ * @return Returns true if event was handled, else false
+ */
+ private boolean handleClickSelection(final boolean ctrl,
+ final boolean shift) {
+
+ // always when clicking an item, focus it
+ setFocusedNode(this, false);
+
+ if (!BrowserInfo.get().isOpera()) {
+ /*
+ * Ensure that the tree's focus element also gains focus
+ * (TreeNodes focus is faked using FocusElementPanel in browsers
+ * other than Opera).
+ */
+ focus();
+ }
+
+ executeEventCommand(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+
+ if (multiSelectMode == MultiSelectMode.SIMPLE
+ || !isMultiselect) {
+ toggleSelection();
+ lastSelection = TreeNode.this;
+ } else if (multiSelectMode == MultiSelectMode.DEFAULT) {
+ // Handle ctrl+click
+ if (isMultiselect && ctrl && !shift) {
+ toggleSelection();
+ lastSelection = TreeNode.this;
+
+ // Handle shift+click
+ } else if (isMultiselect && !ctrl && shift) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, key);
+ sendSelectionToServer();
+
+ // Handle ctrl+shift click
+ } else if (isMultiselect && ctrl && shift) {
+ selectNodeRange(lastSelection.key, key);
+
+ // Handle click
+ } else {
+ // TODO should happen only if this alone not yet
+ // selected,
+ // now sending excess server calls
+ deselectAll();
+ toggleSelection();
+ lastSelection = TreeNode.this;
+ }
+ }
+ }
+ });
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ final int type = DOM.eventGetType(event);
+ final Element target = DOM.eventGetTarget(event);
+
+ if (type == Event.ONLOAD && target == icon.getElement()) {
+ iconLoaded.trigger();
+ }
+
+ if (disabled) {
+ return;
+ }
+
+ final boolean inCaption = isCaptionElement(target);
+ if (inCaption
+ && client.hasEventListeners(VTree.this,
+ TreeConstants.ITEM_CLICK_EVENT_ID)
+
+ && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
+ fireClick(event);
+ }
+ if (type == Event.ONCLICK) {
+ if (getElement() == target) {
+ // state change
+ toggleState();
+ } else if (!readonly && inCaption) {
+ if (selectable) {
+ // caption click = selection change && possible click
+ // event
+ if (handleClickSelection(
+ event.getCtrlKey() || event.getMetaKey(),
+ event.getShiftKey())) {
+ event.preventDefault();
+ }
+ } else {
+ // Not selectable, only focus the node.
+ setFocusedNode(this);
+ }
+ }
+ event.stopPropagation();
+ } else if (type == Event.ONCONTEXTMENU) {
+ showContextMenu(event);
+ }
+
+ if (dragMode != 0 || dropHandler != null) {
+ if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
+ if (nodeCaptionDiv.isOrHasChild((Node) event
+ .getEventTarget().cast())) {
+ if (dragMode > 0
+ && (type == Event.ONTOUCHSTART || event
+ .getButton() == NativeEvent.BUTTON_LEFT)) {
+ mouseDownEvent = event; // save event for possible
+ // dd operation
+ if (type == Event.ONMOUSEDOWN) {
+ event.preventDefault(); // prevent text
+ // selection
+ } else {
+ /*
+ * FIXME We prevent touch start event to be used
+ * as a scroll start event. Note that we cannot
+ * easily distinguish whether the user wants to
+ * drag or scroll. The same issue is in table
+ * that has scrollable area and has drag and
+ * drop enable. Some kind of timer might be used
+ * to resolve the issue.
+ */
+ event.stopPropagation();
+ }
+ }
+ }
+ } else if (type == Event.ONMOUSEMOVE
+ || type == Event.ONMOUSEOUT
+ || type == Event.ONTOUCHMOVE) {
+
+ if (mouseDownEvent != null) {
+ // start actual drag on slight move when mouse is down
+ VTransferable t = new VTransferable();
+ t.setDragSource(ConnectorMap.get(client).getConnector(
+ VTree.this));
+ t.setData("itemId", key);
+ VDragEvent drag = VDragAndDropManager.get().startDrag(
+ t, mouseDownEvent, true);
+
+ drag.createDragImage(nodeCaptionDiv, true);
+ event.stopPropagation();
+
+ mouseDownEvent = null;
+ }
+ } else if (type == Event.ONMOUSEUP) {
+ mouseDownEvent = null;
+ }
+ if (type == Event.ONMOUSEOVER) {
+ mouseDownEvent = null;
+ currentMouseOverKey = key;
+ event.stopPropagation();
+ }
+
+ } else if (type == Event.ONMOUSEDOWN
+ && event.getButton() == NativeEvent.BUTTON_LEFT) {
+ event.preventDefault(); // text selection
+ }
+ }
+
+ /**
+ * Checks if the given element is the caption or the icon.
+ *
+ * @param target
+ * The element to check
+ * @return true if the element is the caption or the icon
+ */
+ public boolean isCaptionElement(com.google.gwt.dom.client.Element target) {
+ return (target == nodeCaptionSpan || (icon != null && target == icon
+ .getElement()));
+ }
+
+ private void fireClick(final Event evt) {
+ /*
+ * Ensure we have focus in tree before sending variables. Otherwise
+ * previously modified field may contain dirty variables.
+ */
+ if (!treeHasFocus) {
+ if (BrowserInfo.get().isOpera()) {
+ if (focusedNode == null) {
+ getNodeByKey(key).setFocused(true);
+ } else {
+ focusedNode.setFocused(true);
+ }
+ } else {
+ focus();
+ }
+ }
+
+ final MouseEventDetails details = MouseEventDetailsBuilder
+ .buildMouseEventDetails(evt);
+
+ executeEventCommand(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ // Determine if we should send the event immediately to the
+ // server. We do not want to send the event if there is a
+ // selection event happening after this. In all other cases
+ // we want to send it immediately.
+ boolean sendClickEventNow = true;
+
+ if (details.getButton() == MouseButton.LEFT && immediate
+ && selectable) {
+ // Probably a selection that will cause a value change
+ // event to be sent
+ sendClickEventNow = false;
+
+ // The exception is that user clicked on the
+ // currently selected row and null selection is not
+ // allowed == no selection event
+ if (isSelected() && selectedIds.size() == 1
+ && !isNullSelectionAllowed) {
+ sendClickEventNow = true;
+ }
+ }
+
+ client.updateVariable(paintableId, "clickedKey", key, false);
+ client.updateVariable(paintableId, "clickEvent",
+ details.toString(), sendClickEventNow);
+ }
+ });
+ }
+
+ /*
+ * Must wait for Safari to focus before sending click and value change
+ * events (see #6373, #6374)
+ */
+ private void executeEventCommand(ScheduledCommand command) {
+ if (BrowserInfo.get().isWebkit() && !treeHasFocus) {
+ Scheduler.get().scheduleDeferred(command);
+ } else {
+ command.execute();
+ }
+ }
+
+ private void toggleSelection() {
+ if (selectable) {
+ VTree.this.setSelected(this, !isSelected());
+ }
+ }
+
+ private void toggleState() {
+ setState(!getState(), true);
+ }
+
+ protected void constructDom() {
+ addStyleName(CLASSNAME);
+
+ nodeCaptionDiv = DOM.createDiv();
+ DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
+ + "-caption");
+ Element wrapper = DOM.createDiv();
+ nodeCaptionSpan = DOM.createSpan();
+ DOM.appendChild(getElement(), nodeCaptionDiv);
+ DOM.appendChild(nodeCaptionDiv, wrapper);
+ DOM.appendChild(wrapper, nodeCaptionSpan);
+
+ if (BrowserInfo.get().isOpera()) {
+ /*
+ * Focus the caption div of the node to get keyboard navigation
+ * to work without scrolling up or down when focusing a node.
+ */
+ nodeCaptionDiv.setTabIndex(-1);
+ }
+
+ childNodeContainer = new FlowPanel();
+ childNodeContainer.setStyleName(CLASSNAME + "-children");
+ setWidget(childNodeContainer);
+ }
+
+ public boolean isLeaf() {
+ String[] styleNames = getStyleName().split(" ");
+ for (String styleName : styleNames) {
+ if (styleName.equals(CLASSNAME + "-leaf")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setState(boolean state, boolean notifyServer) {
+ if (open == state) {
+ return;
+ }
+ if (state) {
+ if (!childrenLoaded && notifyServer) {
+ client.updateVariable(paintableId, "requestChildTree",
+ true, false);
+ }
+ if (notifyServer) {
+ client.updateVariable(paintableId, "expand",
+ new String[] { key }, true);
+ }
+ addStyleName(CLASSNAME + "-expanded");
+ childNodeContainer.setVisible(true);
+
+ } else {
+ removeStyleName(CLASSNAME + "-expanded");
+ childNodeContainer.setVisible(false);
+ if (notifyServer) {
+ client.updateVariable(paintableId, "collapse",
+ new String[] { key }, true);
+ }
+ }
+ open = state;
+
+ if (!rendering) {
+ Util.notifyParentOfSizeChange(VTree.this, false);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean getState() {
+ return open;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setText(String text) {
+ DOM.setInnerText(nodeCaptionSpan, text);
+ }
+
+ public boolean isChildrenLoaded() {
+ return childrenLoaded;
+ }
+
+ /**
+ * Returns the children of the node
+ *
+ * @return A set of tree nodes
+ */
+ public List<TreeNode> getChildren() {
+ List<TreeNode> nodes = new LinkedList<TreeNode>();
+
+ if (!isLeaf() && isChildrenLoaded()) {
+ Iterator<Widget> iter = childNodeContainer.iterator();
+ while (iter.hasNext()) {
+ TreeNode node = (TreeNode) iter.next();
+ nodes.add(node);
+ }
+ }
+ return nodes;
+ }
+
+ @Override
+ public Action[] getActions() {
+ if (actionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[actionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = actionKeys[i];
+ final TreeAction a = new TreeAction(this, String.valueOf(key),
+ actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ @Override
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ /**
+ * Adds/removes Vaadin specific style name.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ *
+ * @param selected
+ */
+ public void setSelected(boolean selected) {
+ // add style name to caption dom structure only, not to subtree
+ setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected);
+ }
+
+ protected boolean isSelected() {
+ return VTree.this.isSelected(this);
+ }
+
+ /**
+ * Travels up the hierarchy looking for this node
+ *
+ * @param child
+ * The child which grandparent this is or is not
+ * @return True if this is a grandparent of the child node
+ */
+ public boolean isGrandParentOf(TreeNode child) {
+ TreeNode currentNode = child;
+ boolean isGrandParent = false;
+ while (currentNode != null) {
+ currentNode = currentNode.getParentNode();
+ if (currentNode == this) {
+ isGrandParent = true;
+ break;
+ }
+ }
+ return isGrandParent;
+ }
+
+ public boolean isSibling(TreeNode node) {
+ return node.getParentNode() == getParentNode();
+ }
+
+ public void showContextMenu(Event event) {
+ if (!readonly && !disabled) {
+ if (actionKeys != null) {
+ int left = event.getClientX();
+ int top = event.getClientY();
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ client.getContextMenu().showAt(this, left, top);
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.Widget#onDetach()
+ */
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ client.getContextMenu().ensureHidden(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.UIObject#toString()
+ */
+ @Override
+ public String toString() {
+ return nodeCaptionSpan.getInnerText();
+ }
+
+ /**
+ * Is the node focused?
+ *
+ * @param focused
+ * True if focused, false if not
+ */
+ public void setFocused(boolean focused) {
+ if (!this.focused && focused) {
+ nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
+
+ this.focused = focused;
+ if (BrowserInfo.get().isOpera()) {
+ nodeCaptionDiv.focus();
+ }
+ treeHasFocus = true;
+ } else if (this.focused && !focused) {
+ nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
+ this.focused = focused;
+ treeHasFocus = false;
+ }
+ }
+
+ /**
+ * Scrolls the caption into view
+ */
+ public void scrollIntoView() {
+ Util.scrollIntoViewVertically(nodeCaptionDiv);
+ }
+
+ public void setIcon(String iconUrl) {
+ if (iconUrl != null) {
+ // Add icon if not present
+ if (icon == null) {
+ icon = new Icon(client);
+ DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
+ icon.getElement(), nodeCaptionSpan);
+ }
+ icon.setUri(iconUrl);
+ } else {
+ // Remove icon if present
+ if (icon != null) {
+ DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv),
+ icon.getElement());
+ icon = null;
+ }
+ }
+ }
+
+ public void setNodeStyleName(String styleName) {
+ addStyleName(TreeNode.CLASSNAME + "-" + styleName);
+ setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-"
+ + styleName, true);
+ childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-"
+ + styleName);
+
+ }
+
+ }
+
+ @Override
+ public VDropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ public TreeNode getNodeByKey(String key) {
+ return keyToNode.get(key);
+ }
+
+ /**
+ * Deselects all items in the tree
+ */
+ public void deselectAll() {
+ for (String key : selectedIds) {
+ TreeNode node = keyToNode.get(key);
+ if (node != null) {
+ node.setSelected(false);
+ }
+ }
+ selectedIds.clear();
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects a range of nodes
+ *
+ * @param startNodeKey
+ * The start node key
+ * @param endNodeKey
+ * The end node key
+ */
+ private void selectNodeRange(String startNodeKey, String endNodeKey) {
+
+ TreeNode startNode = keyToNode.get(startNodeKey);
+ TreeNode endNode = keyToNode.get(endNodeKey);
+
+ // The nodes have the same parent
+ if (startNode.getParent() == endNode.getParent()) {
+ doSiblingSelection(startNode, endNode);
+
+ // The start node is a grandparent of the end node
+ } else if (startNode.isGrandParentOf(endNode)) {
+ doRelationSelection(startNode, endNode);
+
+ // The end node is a grandparent of the start node
+ } else if (endNode.isGrandParentOf(startNode)) {
+ doRelationSelection(endNode, startNode);
+
+ } else {
+ doNoRelationSelection(startNode, endNode);
+ }
+ }
+
+ /**
+ * Selects a node and deselect all other nodes
+ *
+ * @param node
+ * The node to select
+ */
+ private void selectNode(TreeNode node, boolean deselectPrevious) {
+ if (deselectPrevious) {
+ deselectAll();
+ }
+
+ if (node != null) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ lastSelection = node;
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Deselects a node
+ *
+ * @param node
+ * The node to deselect
+ */
+ private void deselectNode(TreeNode node) {
+ node.setSelected(false);
+ selectedIds.remove(node.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects all the open children to a node
+ *
+ * @param node
+ * The parent node
+ */
+ private void selectAllChildren(TreeNode node, boolean includeRootNode) {
+ if (includeRootNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ }
+
+ for (TreeNode child : node.getChildren()) {
+ if (!child.isLeaf() && child.getState()) {
+ selectAllChildren(child, true);
+ } else {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects all children until a stop child is reached
+ *
+ * @param root
+ * The root not to start from
+ * @param stopNode
+ * The node to finish with
+ * @param includeRootNode
+ * Should the root node be selected
+ * @param includeStopNode
+ * Should the stop node be selected
+ *
+ * @return Returns false if the stop child was found, else true if all
+ * children was selected
+ */
+ private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
+ boolean includeRootNode, boolean includeStopNode) {
+ if (includeRootNode) {
+ root.setSelected(true);
+ selectedIds.add(root.key);
+ }
+ if (root.getState() && root != stopNode) {
+ for (TreeNode child : root.getChildren()) {
+ if (!child.isLeaf() && child.getState() && child != stopNode) {
+ if (!selectAllChildrenUntil(child, stopNode, true,
+ includeStopNode)) {
+ return false;
+ }
+ } else if (child == stopNode) {
+ if (includeStopNode) {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ return false;
+ } else {
+ child.setSelected(true);
+ selectedIds.add(child.key);
+ }
+ }
+ }
+ selectionHasChanged = true;
+
+ return true;
+ }
+
+ /**
+ * Select a range between two nodes which have no relation to each other
+ *
+ * @param startNode
+ * The start node to start the selection from
+ * @param endNode
+ * The end node to end the selection to
+ */
+ private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
+
+ TreeNode commonParent = getCommonGrandParent(startNode, endNode);
+ TreeNode startBranch = null, endBranch = null;
+
+ // Find the children of the common parent
+ List<TreeNode> children;
+ if (commonParent != null) {
+ children = commonParent.getChildren();
+ } else {
+ children = getRootNodes();
+ }
+
+ // Find the start and end branches
+ for (TreeNode node : children) {
+ if (nodeIsInBranch(startNode, node)) {
+ startBranch = node;
+ }
+ if (nodeIsInBranch(endNode, node)) {
+ endBranch = node;
+ }
+ }
+
+ // Swap nodes if necessary
+ if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
+ TreeNode temp = startBranch;
+ startBranch = endBranch;
+ endBranch = temp;
+
+ temp = startNode;
+ startNode = endNode;
+ endNode = temp;
+ }
+
+ // Select all children under the start node
+ selectAllChildren(startNode, true);
+ TreeNode startParent = startNode.getParentNode();
+ TreeNode currentNode = startNode;
+ while (startParent != null && startParent != commonParent) {
+ List<TreeNode> startChildren = startParent.getChildren();
+ for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
+ .size(); i++) {
+ selectAllChildren(startChildren.get(i), true);
+ }
+
+ currentNode = startParent;
+ startParent = startParent.getParentNode();
+ }
+
+ // Select nodes until the end node is reached
+ for (int i = children.indexOf(startBranch) + 1; i <= children
+ .indexOf(endBranch); i++) {
+ selectAllChildrenUntil(children.get(i), endNode, true, true);
+ }
+
+ // Ensure end node was selected
+ endNode.setSelected(true);
+ selectedIds.add(endNode.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Examines the children of the branch node and returns true if a node is in
+ * that branch
+ *
+ * @param node
+ * The node to search for
+ * @param branch
+ * The branch to search in
+ * @return True if found, false if not found
+ */
+ private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
+ if (node == branch) {
+ return true;
+ }
+ for (TreeNode child : branch.getChildren()) {
+ if (child == node) {
+ return true;
+ }
+ if (!child.isLeaf() && child.getState()) {
+ if (nodeIsInBranch(node, child)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Selects a range of items which are in direct relation with each other.<br/>
+ * NOTE: The start node <b>MUST</b> be before the end node!
+ *
+ * @param startNode
+ *
+ * @param endNode
+ */
+ private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
+ TreeNode currentNode = endNode;
+ while (currentNode != startNode) {
+ currentNode.setSelected(true);
+ selectedIds.add(currentNode.key);
+
+ // Traverse children above the selection
+ List<TreeNode> subChildren = currentNode.getParentNode()
+ .getChildren();
+ if (subChildren.size() > 1) {
+ selectNodeRange(subChildren.iterator().next().key,
+ currentNode.key);
+ } else if (subChildren.size() == 1) {
+ TreeNode n = subChildren.get(0);
+ n.setSelected(true);
+ selectedIds.add(n.key);
+ }
+
+ currentNode = currentNode.getParentNode();
+ }
+ startNode.setSelected(true);
+ selectedIds.add(startNode.key);
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Selects a range of items which have the same parent.
+ *
+ * @param startNode
+ * The start node
+ * @param endNode
+ * The end node
+ */
+ private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
+ TreeNode parent = startNode.getParentNode();
+
+ List<TreeNode> children;
+ if (parent == null) {
+ // Topmost parent
+ children = getRootNodes();
+ } else {
+ children = parent.getChildren();
+ }
+
+ // Swap start and end point if needed
+ if (children.indexOf(startNode) > children.indexOf(endNode)) {
+ TreeNode temp = startNode;
+ startNode = endNode;
+ endNode = temp;
+ }
+
+ Iterator<TreeNode> childIter = children.iterator();
+ boolean startFound = false;
+ while (childIter.hasNext()) {
+ TreeNode node = childIter.next();
+ if (node == startNode) {
+ startFound = true;
+ }
+
+ if (startFound && node != endNode && node.getState()) {
+ selectAllChildren(node, true);
+ } else if (startFound && node != endNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ }
+
+ if (node == endNode) {
+ node.setSelected(true);
+ selectedIds.add(node.key);
+ break;
+ }
+ }
+ selectionHasChanged = true;
+ }
+
+ /**
+ * Returns the first common parent of two nodes
+ *
+ * @param node1
+ * The first node
+ * @param node2
+ * The second node
+ * @return The common parent or null
+ */
+ public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
+ // If either one does not have a grand parent then return null
+ if (node1.getParentNode() == null || node2.getParentNode() == null) {
+ return null;
+ }
+
+ // If the nodes are parents of each other then return null
+ if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
+ return null;
+ }
+
+ // Get parents of node1
+ List<TreeNode> parents1 = new ArrayList<TreeNode>();
+ TreeNode parent1 = node1.getParentNode();
+ while (parent1 != null) {
+ parents1.add(parent1);
+ parent1 = parent1.getParentNode();
+ }
+
+ // Get parents of node2
+ List<TreeNode> parents2 = new ArrayList<TreeNode>();
+ TreeNode parent2 = node2.getParentNode();
+ while (parent2 != null) {
+ parents2.add(parent2);
+ parent2 = parent2.getParentNode();
+ }
+
+ // Search the parents for the first common parent
+ for (int i = 0; i < parents1.size(); i++) {
+ parent1 = parents1.get(i);
+ for (int j = 0; j < parents2.size(); j++) {
+ parent2 = parents2.get(j);
+ if (parent1 == parent2) {
+ return parent1;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the node currently in focus
+ *
+ * @param node
+ * The node to focus or null to remove the focus completely
+ * @param scrollIntoView
+ * Scroll the node into view
+ */
+ public void setFocusedNode(TreeNode node, boolean scrollIntoView) {
+ // Unfocus previously focused node
+ if (focusedNode != null) {
+ focusedNode.setFocused(false);
+ }
+
+ if (node != null) {
+ node.setFocused(true);
+ }
+
+ focusedNode = node;
+
+ if (node != null && scrollIntoView) {
+ /*
+ * Delay scrolling the focused node into view if we are still
+ * rendering. #5396
+ */
+ if (!rendering) {
+ node.scrollIntoView();
+ } else {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ focusedNode.scrollIntoView();
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Focuses a node and scrolls it into view
+ *
+ * @param node
+ * The node to focus
+ */
+ public void setFocusedNode(TreeNode node) {
+ setFocusedNode(node, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ @Override
+ public void onFocus(FocusEvent event) {
+ treeHasFocus = true;
+ // If no node has focus, focus the first item in the tree
+ if (focusedNode == null && lastSelection == null && selectable) {
+ setFocusedNode(getFirstRootNode(), false);
+ } else if (focusedNode != null && selectable) {
+ setFocusedNode(focusedNode, false);
+ } else if (lastSelection != null && selectable) {
+ setFocusedNode(lastSelection, false);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+ * .dom.client.BlurEvent)
+ */
+ @Override
+ public void onBlur(BlurEvent event) {
+ treeHasFocus = false;
+ if (focusedNode != null) {
+ focusedNode.setFocused(false);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ @Override
+ public void onKeyPress(KeyPressEvent event) {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ int keyCode = nativeEvent.getKeyCode();
+ if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
+ // Provide a keyCode for space to be compatible with FireFox
+ // keypress event
+ keyCode = CHARCODE_SPACE;
+ }
+ if (handleKeyNavigation(keyCode,
+ event.isControlKeyDown() || event.isMetaKeyDown(),
+ event.isShiftKeyDown())) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (handleKeyNavigation(event.getNativeEvent().getKeyCode(),
+ event.isControlKeyDown() || event.isMetaKeyDown(),
+ event.isShiftKeyDown())) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ /**
+ * Handles the keyboard navigation
+ *
+ * @param keycode
+ * The keycode of the pressed key
+ * @param ctrl
+ * Was ctrl pressed
+ * @param shift
+ * Was shift pressed
+ * @return Returns true if the key was handled, else false
+ */
+ protected boolean handleKeyNavigation(int keycode, boolean ctrl,
+ boolean shift) {
+ // Navigate down
+ if (keycode == getNavigationDownKey()) {
+ TreeNode node = null;
+ // If node is open and has children then move in to the children
+ if (!focusedNode.isLeaf() && focusedNode.getState()
+ && focusedNode.getChildren().size() > 0) {
+ node = focusedNode.getChildren().get(0);
+ }
+
+ // Else move down to the next sibling
+ else {
+ node = getNextSibling(focusedNode);
+ if (node == null) {
+ // Else jump to the parent and try to select the next
+ // sibling there
+ TreeNode current = focusedNode;
+ while (node == null && current.getParentNode() != null) {
+ node = getNextSibling(current.getParentNode());
+ current = current.getParentNode();
+ }
+ }
+ }
+
+ if (node != null) {
+ setFocusedNode(node);
+ if (selectable) {
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (shift && isMultiselect) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, node.key);
+ } else if (shift) {
+ selectNode(node, true);
+ }
+ }
+ }
+ return true;
+ }
+
+ // Navigate up
+ if (keycode == getNavigationUpKey()) {
+ TreeNode prev = getPreviousSibling(focusedNode);
+ TreeNode node = null;
+ if (prev != null) {
+ node = getLastVisibleChildInTree(prev);
+ } else if (focusedNode.getParentNode() != null) {
+ node = focusedNode.getParentNode();
+ }
+ if (node != null) {
+ setFocusedNode(node);
+ if (selectable) {
+ if (!ctrl && !shift) {
+ selectNode(node, true);
+ } else if (shift && isMultiselect) {
+ deselectAll();
+ selectNodeRange(lastSelection.key, node.key);
+ } else if (shift) {
+ selectNode(node, true);
+ }
+ }
+ }
+ return true;
+ }
+
+ // Navigate left (close branch)
+ if (keycode == getNavigationLeftKey()) {
+ if (!focusedNode.isLeaf() && focusedNode.getState()) {
+ focusedNode.setState(false, true);
+ } else if (focusedNode.getParentNode() != null
+ && (focusedNode.isLeaf() || !focusedNode.getState())) {
+
+ if (ctrl || !selectable) {
+ setFocusedNode(focusedNode.getParentNode());
+ } else if (shift) {
+ doRelationSelection(focusedNode.getParentNode(),
+ focusedNode);
+ setFocusedNode(focusedNode.getParentNode());
+ } else {
+ focusAndSelectNode(focusedNode.getParentNode());
+ }
+ }
+ return true;
+ }
+
+ // Navigate right (open branch)
+ if (keycode == getNavigationRightKey()) {
+ if (!focusedNode.isLeaf() && !focusedNode.getState()) {
+ focusedNode.setState(true, true);
+ } else if (!focusedNode.isLeaf()) {
+ if (ctrl || !selectable) {
+ setFocusedNode(focusedNode.getChildren().get(0));
+ } else if (shift) {
+ setSelected(focusedNode, true);
+ setFocusedNode(focusedNode.getChildren().get(0));
+ setSelected(focusedNode, true);
+ } else {
+ focusAndSelectNode(focusedNode.getChildren().get(0));
+ }
+ }
+ return true;
+ }
+
+ // Selection
+ if (keycode == getNavigationSelectKey()) {
+ if (!focusedNode.isSelected()) {
+ selectNode(
+ focusedNode,
+ (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE)
+ && selectable);
+ } else {
+ deselectNode(focusedNode);
+ }
+ return true;
+ }
+
+ // Home selection
+ if (keycode == getNavigationStartKey()) {
+ TreeNode node = getFirstRootNode();
+ if (ctrl || !selectable) {
+ setFocusedNode(node);
+ } else if (shift) {
+ deselectAll();
+ selectNodeRange(focusedNode.key, node.key);
+ } else {
+ selectNode(node, true);
+ }
+ sendSelectionToServer();
+ return true;
+ }
+
+ // End selection
+ if (keycode == getNavigationEndKey()) {
+ TreeNode lastNode = getLastRootNode();
+ TreeNode node = getLastVisibleChildInTree(lastNode);
+ if (ctrl || !selectable) {
+ setFocusedNode(node);
+ } else if (shift) {
+ deselectAll();
+ selectNodeRange(focusedNode.key, node.key);
+ } else {
+ selectNode(node, true);
+ }
+ sendSelectionToServer();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void focusAndSelectNode(TreeNode node) {
+ /*
+ * Keyboard navigation doesn't work reliably if the tree is in
+ * multiselect mode as well as isNullSelectionAllowed = false. It first
+ * tries to deselect the old focused node, which fails since there must
+ * be at least one selection. After this the newly focused node is
+ * selected and we've ended up with two selected nodes even though we
+ * only navigated with the arrow keys.
+ *
+ * Because of this, we first select the next node and later de-select
+ * the old one.
+ */
+ TreeNode oldFocusedNode = focusedNode;
+ setFocusedNode(node);
+ setSelected(focusedNode, true);
+ setSelected(oldFocusedNode, false);
+ }
+
+ /**
+ * Traverses the tree to the bottom most child
+ *
+ * @param root
+ * The root of the tree
+ * @return The bottom most child
+ */
+ private TreeNode getLastVisibleChildInTree(TreeNode root) {
+ if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
+ return root;
+ }
+ List<TreeNode> children = root.getChildren();
+ return getLastVisibleChildInTree(children.get(children.size() - 1));
+ }
+
+ /**
+ * Gets the next sibling in the tree
+ *
+ * @param node
+ * The node to get the sibling for
+ * @return The sibling node or null if the node is the last sibling
+ */
+ private TreeNode getNextSibling(TreeNode node) {
+ TreeNode parent = node.getParentNode();
+ List<TreeNode> children;
+ if (parent == null) {
+ children = getRootNodes();
+ } else {
+ children = parent.getChildren();
+ }
+
+ int idx = children.indexOf(node);
+ if (idx < children.size() - 1) {
+ return children.get(idx + 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the previous sibling in the tree
+ *
+ * @param node
+ * The node to get the sibling for
+ * @return The sibling node or null if the node is the first sibling
+ */
+ private TreeNode getPreviousSibling(TreeNode node) {
+ TreeNode parent = node.getParentNode();
+ List<TreeNode> children;
+ if (parent == null) {
+ children = getRootNodes();
+ } else {
+ children = parent.getChildren();
+ }
+
+ int idx = children.indexOf(node);
+ if (idx > 0) {
+ return children.get(idx - 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Add this to the element mouse down event by using element.setPropertyJSO
+ * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
+ * when the mouse is depressed in the mouse up event.
+ *
+ * @return Returns the JSO preventing text selection
+ */
+ private native JavaScriptObject applyDisableTextSelectionIEHack()
+ /*-{
+ return function(){ return false; };
+ }-*/;
+
+ /**
+ * Get the key that moves the selection head upwards. By default it is the
+ * up arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationUpKey() {
+ return KeyCodes.KEY_UP;
+ }
+
+ /**
+ * Get the key that moves the selection head downwards. By default it is the
+ * down arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationDownKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Get the key that scrolls to the left in the table. By default it is the
+ * left arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationLeftKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * Get the key that scroll to the right on the table. By default it is the
+ * right arrow key but by overriding this you can change the key to whatever
+ * you want.
+ *
+ * @return The keycode of the key
+ */
+ protected int getNavigationRightKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the space
+ * bar key but by overriding this you can change the key to whatever you
+ * want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return CHARCODE_SPACE;
+ }
+
+ /**
+ * Get the key the moves the selection one page up in the table. By default
+ * this is the Page Up key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageUpKey() {
+ return KeyCodes.KEY_PAGEUP;
+ }
+
+ /**
+ * Get the key the moves the selection one page down in the table. By
+ * default this is the Page Down key but by overriding this you can change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationPageDownKey() {
+ return KeyCodes.KEY_PAGEDOWN;
+ }
+
+ /**
+ * Get the key the moves the selection to the beginning of the table. By
+ * default this is the Home key but by overriding this you can change the
+ * key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationStartKey() {
+ return KeyCodes.KEY_HOME;
+ }
+
+ /**
+ * Get the key the moves the selection to the end of the table. By default
+ * this is the End key but by overriding this you can change the key to
+ * whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationEndKey() {
+ return KeyCodes.KEY_END;
+ }
+
+ private final String SUBPART_NODE_PREFIX = "n";
+ private final String EXPAND_IDENTIFIER = "expand";
+
+ /*
+ * In webkit, focus may have been requested for this component but not yet
+ * gained. Use this to trac if tree has gained the focus on webkit. See
+ * FocusImplSafari and #6373
+ */
+ private boolean treeHasFocus;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java
+ * .lang.String)
+ */
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if ("fe".equals(subPart)) {
+ if (BrowserInfo.get().isOpera() && focusedNode != null) {
+ return focusedNode.getElement();
+ }
+ return getFocusElement();
+ }
+
+ if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) {
+ boolean expandCollapse = false;
+
+ // Node
+ String[] nodes = subPart.split("/");
+ TreeNode treeNode = null;
+ try {
+ for (String node : nodes) {
+ if (node.startsWith(SUBPART_NODE_PREFIX)) {
+
+ // skip SUBPART_NODE_PREFIX"["
+ node = node.substring(SUBPART_NODE_PREFIX.length() + 1);
+ // skip "]"
+ node = node.substring(0, node.length() - 1);
+ int position = Integer.parseInt(node);
+ if (treeNode == null) {
+ treeNode = getRootNodes().get(position);
+ } else {
+ treeNode = treeNode.getChildren().get(position);
+ }
+ } else if (node.startsWith(EXPAND_IDENTIFIER)) {
+ expandCollapse = true;
+ }
+ }
+
+ if (expandCollapse) {
+ return treeNode.getElement();
+ } else {
+ return treeNode.nodeCaptionSpan;
+ }
+ } catch (Exception e) {
+ // Invalid locator string or node could not be found
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google
+ * .gwt.user.client.Element)
+ */
+ @Override
+ public String getSubPartName(Element subElement) {
+ // Supported identifiers:
+ //
+ // n[index]/n[index]/n[index]{/expand}
+ //
+ // Ends with "/expand" if the target is expand/collapse indicator,
+ // otherwise ends with the node
+
+ boolean isExpandCollapse = false;
+
+ if (!getElement().isOrHasChild(subElement)) {
+ return null;
+ }
+
+ if (subElement == getFocusElement()) {
+ return "fe";
+ }
+
+ TreeNode treeNode = Util.findWidget(subElement, TreeNode.class);
+ if (treeNode == null) {
+ // Did not click on a node, let somebody else take care of the
+ // locator string
+ return null;
+ }
+
+ if (subElement == treeNode.getElement()) {
+ // Targets expand/collapse arrow
+ isExpandCollapse = true;
+ }
+
+ ArrayList<Integer> positions = new ArrayList<Integer>();
+ while (treeNode.getParentNode() != null) {
+ positions.add(0,
+ treeNode.getParentNode().getChildren().indexOf(treeNode));
+ treeNode = treeNode.getParentNode();
+ }
+ positions.add(0, getRootNodes().indexOf(treeNode));
+
+ String locator = "";
+ for (Integer i : positions) {
+ locator += SUBPART_NODE_PREFIX + "[" + i + "]/";
+ }
+
+ locator = locator.substring(0, locator.length() - 1);
+ if (isExpandCollapse) {
+ locator += "/" + EXPAND_IDENTIFIER;
+ }
+ return locator;
+ }
+
+ @Override
+ public Action[] getActions() {
+ if (bodyActionKeys == null) {
+ return new Action[] {};
+ }
+ final Action[] actions = new Action[bodyActionKeys.length];
+ for (int i = 0; i < actions.length; i++) {
+ final String actionKey = bodyActionKeys[i];
+ final TreeAction a = new TreeAction(this, null, actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ actions[i] = a;
+ }
+ return actions;
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return client;
+ }
+
+ @Override
+ public String getPaintableId() {
+ return paintableId;
+ }
+
+ private void handleBodyContextMenu(ContextMenuEvent event) {
+ if (!readonly && !disabled) {
+ if (bodyActionKeys != null) {
+ int left = event.getNativeEvent().getClientX();
+ int top = event.getNativeEvent().getClientY();
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ client.getContextMenu().showAt(this, left, top);
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ }
+
+ public void registerAction(String key, String caption, String iconUrl) {
+ actionMap.put(key + "_c", caption);
+ if (iconUrl != null) {
+ actionMap.put(key + "_i", iconUrl);
+ } else {
+ actionMap.remove(key + "_i");
+ }
+
+ }
+
+ public void registerNode(TreeNode treeNode) {
+ keyToNode.put(treeNode.key, treeNode);
+ }
+
+ public void clearNodeToKeyMap() {
+ keyToNode.clear();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComputedStyle;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow;
+
+public class VTreeTable extends VScrollTable {
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public static class PendingNavigationEvent {
+ public final int keycode;
+ public final boolean ctrl;
+ public final boolean shift;
+
+ public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) {
+ this.keycode = keycode;
+ this.ctrl = ctrl;
+ this.shift = shift;
+ }
+
+ @Override
+ public String toString() {
+ String string = "Keyboard event: " + keycode;
+ if (ctrl) {
+ string += " + ctrl";
+ }
+ if (shift) {
+ string += " + shift";
+ }
+ return string;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean collapseRequest;
+
+ private boolean selectionPending;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int colIndexOfHierarchy;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String collapsedRowKey;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public VTreeTableScrollBody scrollBody;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean animationsEnabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean focusParentResponsePending;
+
+ @Override
+ protected VScrollTableBody createScrollBody() {
+ scrollBody = new VTreeTableScrollBody();
+ return scrollBody;
+ }
+
+ /*
+ * Overridden to allow animation of expands and collapses of nodes.
+ */
+ @Override
+ public void addAndRemoveRows(UIDL partialRowAdditions) {
+ if (partialRowAdditions == null) {
+ return;
+ }
+
+ if (animationsEnabled) {
+ if (partialRowAdditions.hasAttribute("hide")) {
+ scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+ partialRowAdditions.getIntAttribute("firstprowix"),
+ partialRowAdditions.getIntAttribute("numprows"));
+ } else {
+ scrollBody.insertRowsAnimated(partialRowAdditions,
+ partialRowAdditions.getIntAttribute("firstprowix"),
+ partialRowAdditions.getIntAttribute("numprows"));
+ discardRowsOutsideCacheWindow();
+ }
+ } else {
+ super.addAndRemoveRows(partialRowAdditions);
+ }
+ }
+
+ class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
+ private int indentWidth = -1;
+
+ VTreeTableScrollBody() {
+ super();
+ }
+
+ @Override
+ protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
+ if (uidl.hasAttribute("gen_html")) {
+ // This is a generated row.
+ return new VTreeTableGeneratedRow(uidl, aligns2);
+ }
+ return new VTreeTableRow(uidl, aligns2);
+ }
+
+ class VTreeTableRow extends
+ VScrollTable.VScrollTableBody.VScrollTableRow {
+
+ private boolean isTreeCellAdded = false;
+ private SpanElement treeSpacer;
+ private boolean open;
+ private int depth;
+ private boolean canHaveChildren;
+ protected Widget widgetInHierarchyColumn;
+
+ public VTreeTableRow(UIDL uidl, char[] aligns2) {
+ super(uidl, aligns2);
+ }
+
+ @Override
+ public void addCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML, boolean isSorted,
+ String description) {
+ super.addCell(rowUidl, text, align, style, textIsHTML,
+ isSorted, description);
+
+ addTreeSpacer(rowUidl);
+ }
+
+ protected boolean addTreeSpacer(UIDL rowUidl) {
+ if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) {
+ Element container = (Element) getElement().getLastChild()
+ .getFirstChild();
+
+ if (rowUidl.hasAttribute("icon")) {
+ // icons are in first content cell in TreeTable
+ ImageElement icon = Document.get().createImageElement();
+ icon.setClassName("v-icon");
+ icon.setAlt("icon");
+ icon.setSrc(client.translateVaadinUri(rowUidl
+ .getStringAttribute("icon")));
+ container.insertFirst(icon);
+ }
+
+ String classname = "v-treetable-treespacer";
+ if (rowUidl.getBooleanAttribute("ca")) {
+ canHaveChildren = true;
+ open = rowUidl.getBooleanAttribute("open");
+ classname += open ? " v-treetable-node-open"
+ : " v-treetable-node-closed";
+ }
+
+ treeSpacer = Document.get().createSpanElement();
+
+ treeSpacer.setClassName(classname);
+ container.insertFirst(treeSpacer);
+ depth = rowUidl.hasAttribute("depth") ? rowUidl
+ .getIntAttribute("depth") : 0;
+ setIndent();
+ isTreeCellAdded = true;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean cellShowsTreeHierarchy(int curColIndex) {
+ if (isTreeCellAdded) {
+ return false;
+ }
+ return curColIndex == getHierarchyColumnIndex();
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ if (event.getEventTarget().cast() == treeSpacer
+ && treeSpacer.getClassName().contains("node")) {
+ if (event.getTypeInt() == Event.ONMOUSEUP) {
+ sendToggleCollapsedUpdate(getKey());
+ }
+ return;
+ }
+ super.onBrowserEvent(event);
+ }
+
+ @Override
+ public void addCell(UIDL rowUidl, Widget w, char align,
+ String style, boolean isSorted) {
+ super.addCell(rowUidl, w, align, style, isSorted);
+ if (addTreeSpacer(rowUidl)) {
+ widgetInHierarchyColumn = w;
+ }
+
+ }
+
+ private void setIndent() {
+ if (getIndentWidth() > 0) {
+ treeSpacer.getParentElement().getStyle()
+ .setPaddingLeft(getIndent(), Unit.PX);
+ treeSpacer.getStyle().setWidth(getIndent(), Unit.PX);
+ }
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (getIndentWidth() < 0) {
+ detectIndent(this);
+ // If we detect indent here then the size of the hierarchy
+ // column is still wrong as it has been set when the indent
+ // was not known.
+ int w = getCellWidthFromDom(getHierarchyColumnIndex());
+ if (w >= 0) {
+ setColWidth(getHierarchyColumnIndex(), w);
+ }
+ }
+ }
+
+ private int getCellWidthFromDom(int cellIndex) {
+ final Element cell = DOM.getChild(getElement(), cellIndex);
+ String w = cell.getStyle().getProperty("width");
+ if (w == null || "".equals(w) || !w.endsWith("px")) {
+ return -1;
+ } else {
+ return Integer.parseInt(w.substring(0, w.length() - 2));
+ }
+ }
+
+ private int getHierarchyAndIconWidth() {
+ int consumedSpace = treeSpacer.getOffsetWidth();
+ if (treeSpacer.getParentElement().getChildCount() > 2) {
+ // icon next to tree spacer
+ consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer
+ .getNextSibling()).getOffsetWidth();
+ }
+ return consumedSpace;
+ }
+
+ @Override
+ protected void setCellWidth(int cellIx, int width) {
+ if (cellIx == getHierarchyColumnIndex()) {
+ // take indentation padding into account if this is the
+ // hierarchy column
+ int indent = getIndent();
+ if (indent != -1) {
+ width = Math.max(width - getIndent(), 0);
+ }
+ }
+ super.setCellWidth(cellIx, width);
+ }
+
+ private int getHierarchyColumnIndex() {
+ return colIndexOfHierarchy + (showRowHeaders ? 1 : 0);
+ }
+
+ private int getIndent() {
+ return (depth + 1) * getIndentWidth();
+ }
+ }
+
+ protected class VTreeTableGeneratedRow extends VTreeTableRow {
+ private boolean spanColumns;
+ private boolean htmlContentAllowed;
+
+ public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) {
+ super(uidl, aligns);
+ addStyleName("v-table-generated-row");
+ }
+
+ public boolean isSpanColumns() {
+ return spanColumns;
+ }
+
+ @Override
+ protected void initCellWidths() {
+ if (spanColumns) {
+ setSpannedColumnWidthAfterDOMFullyInited();
+ } else {
+ super.initCellWidths();
+ }
+ }
+
+ private void setSpannedColumnWidthAfterDOMFullyInited() {
+ // Defer setting width on spanned columns to make sure that
+ // they are added to the DOM before trying to calculate
+ // widths.
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ if (showRowHeaders) {
+ setCellWidth(0, tHead.getHeaderCell(0).getWidth());
+ calcAndSetSpanWidthOnCell(1);
+ } else {
+ calcAndSetSpanWidthOnCell(0);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected boolean isRenderHtmlInCells() {
+ return htmlContentAllowed;
+ }
+
+ @Override
+ protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+ int visibleColumnIndex) {
+ htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
+ spanColumns = uidl.getBooleanAttribute("gen_span");
+
+ final Iterator<?> cells = uidl.getChildIterator();
+ if (spanColumns) {
+ int colCount = uidl.getChildCount();
+ if (cells.hasNext()) {
+ final Object cell = cells.next();
+ if (cell instanceof String) {
+ addSpannedCell(uidl, cell.toString(), aligns[0],
+ "", htmlContentAllowed, false, null,
+ colCount);
+ } else {
+ addSpannedCell(uidl, (Widget) cell, aligns[0], "",
+ false, colCount);
+ }
+ }
+ } else {
+ super.addCellsFromUIDL(uidl, aligns, col,
+ visibleColumnIndex);
+ }
+ }
+
+ private void addSpannedCell(UIDL rowUidl, Widget w, char align,
+ String style, boolean sorted, int colCount) {
+ TableCellElement td = DOM.createTD().cast();
+ td.setColSpan(colCount);
+ initCellWithWidget(w, align, style, sorted, td);
+ td.getStyle().setHeight(getRowHeight(), Unit.PX);
+ if (addTreeSpacer(rowUidl)) {
+ widgetInHierarchyColumn = w;
+ }
+ }
+
+ private void addSpannedCell(UIDL rowUidl, String text, char align,
+ String style, boolean textIsHTML, boolean sorted,
+ String description, int colCount) {
+ // String only content is optimized by not using Label widget
+ final TableCellElement td = DOM.createTD().cast();
+ td.setColSpan(colCount);
+ initCellWithText(text, align, style, textIsHTML, sorted,
+ description, td);
+ td.getStyle().setHeight(getRowHeight(), Unit.PX);
+ addTreeSpacer(rowUidl);
+ }
+
+ @Override
+ protected void setCellWidth(int cellIx, int width) {
+ if (isSpanColumns()) {
+ if (showRowHeaders) {
+ if (cellIx == 0) {
+ super.setCellWidth(0, width);
+ } else {
+ // We need to recalculate the spanning TDs width for
+ // every cellIx in order to support column resizing.
+ calcAndSetSpanWidthOnCell(1);
+ }
+ } else {
+ // Same as above.
+ calcAndSetSpanWidthOnCell(0);
+ }
+ } else {
+ super.setCellWidth(cellIx, width);
+ }
+ }
+
+ private void calcAndSetSpanWidthOnCell(final int cellIx) {
+ int spanWidth = 0;
+ for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
+ .getVisibleCellCount(); ix++) {
+ spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
+ }
+ Util.setWidthExcludingPaddingAndBorder((Element) getElement()
+ .getChild(cellIx), spanWidth, 13, false);
+ }
+ }
+
+ private int getIndentWidth() {
+ return indentWidth;
+ }
+
+ private void detectIndent(VTreeTableRow vTreeTableRow) {
+ indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth();
+ if (indentWidth == 0) {
+ indentWidth = -1;
+ return;
+ }
+ Iterator<Widget> iterator = iterator();
+ while (iterator.hasNext()) {
+ VTreeTableRow next = (VTreeTableRow) iterator.next();
+ next.setIndent();
+ }
+ }
+
+ protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+ final int firstIndex, final int rows) {
+ List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
+ for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
+ VScrollTableRow row = getRowByRowIndex(ix);
+ if (row != null) {
+ rowsToDelete.add(row);
+ }
+ }
+ if (!rowsToDelete.isEmpty()) {
+ // #8810 Only animate if there's something to animate
+ RowCollapseAnimation anim = new RowCollapseAnimation(
+ rowsToDelete) {
+ @Override
+ protected void onComplete() {
+ super.onComplete();
+ // Actually unlink the rows and update the cache after
+ // the
+ // animation is done.
+ unlinkAndReindexRows(firstIndex, rows);
+ discardRowsOutsideCacheWindow();
+ ensureCacheFilled();
+ }
+ };
+ anim.run(150);
+ }
+ }
+
+ protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
+ int firstIndex, int rows) {
+ List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData,
+ firstIndex, rows);
+ if (!insertedRows.isEmpty()) {
+ // Only animate if there's something to animate (#8810)
+ RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
+ anim.run(150);
+ }
+ return insertedRows;
+ }
+
+ /**
+ * Prepares the table for animation by copying the background colors of
+ * all TR elements to their respective TD elements if the TD element is
+ * transparent. This is needed, since if TDs have transparent
+ * backgrounds, the rows sliding behind them are visible.
+ */
+ private class AnimationPreparator {
+ private final int lastItemIx;
+
+ public AnimationPreparator(int lastItemIx) {
+ this.lastItemIx = lastItemIx;
+ }
+
+ public void prepareTableForAnimation() {
+ int ix = lastItemIx;
+ VScrollTableRow row = null;
+ while ((row = getRowByRowIndex(ix)) != null) {
+ copyTRBackgroundsToTDs(row);
+ --ix;
+ }
+ }
+
+ private void copyTRBackgroundsToTDs(VScrollTableRow row) {
+ Element tr = row.getElement();
+ ComputedStyle cs = new ComputedStyle(tr);
+ String backgroundAttachment = cs
+ .getProperty("backgroundAttachment");
+ String backgroundClip = cs.getProperty("backgroundClip");
+ String backgroundColor = cs.getProperty("backgroundColor");
+ String backgroundImage = cs.getProperty("backgroundImage");
+ String backgroundOrigin = cs.getProperty("backgroundOrigin");
+ for (int ix = 0; ix < tr.getChildCount(); ix++) {
+ Element td = tr.getChild(ix).cast();
+ if (!elementHasBackground(td)) {
+ td.getStyle().setProperty("backgroundAttachment",
+ backgroundAttachment);
+ td.getStyle().setProperty("backgroundClip",
+ backgroundClip);
+ td.getStyle().setProperty("backgroundColor",
+ backgroundColor);
+ td.getStyle().setProperty("backgroundImage",
+ backgroundImage);
+ td.getStyle().setProperty("backgroundOrigin",
+ backgroundOrigin);
+ }
+ }
+ }
+
+ private boolean elementHasBackground(Element element) {
+ ComputedStyle cs = new ComputedStyle(element);
+ String clr = cs.getProperty("backgroundColor");
+ String img = cs.getProperty("backgroundImage");
+ return !("rgba(0, 0, 0, 0)".equals(clr.trim())
+ || "transparent".equals(clr.trim()) || img == null);
+ }
+
+ public void restoreTableAfterAnimation() {
+ int ix = lastItemIx;
+ VScrollTableRow row = null;
+ while ((row = getRowByRowIndex(ix)) != null) {
+ restoreStyleForTDsInRow(row);
+
+ --ix;
+ }
+ }
+
+ private void restoreStyleForTDsInRow(VScrollTableRow row) {
+ Element tr = row.getElement();
+ for (int ix = 0; ix < tr.getChildCount(); ix++) {
+ Element td = tr.getChild(ix).cast();
+ td.getStyle().clearProperty("backgroundAttachment");
+ td.getStyle().clearProperty("backgroundClip");
+ td.getStyle().clearProperty("backgroundColor");
+ td.getStyle().clearProperty("backgroundImage");
+ td.getStyle().clearProperty("backgroundOrigin");
+ }
+ }
+ }
+
+ /**
+ * Animates row expansion using the GWT animation framework.
+ *
+ * The idea is as follows:
+ *
+ * 1. Insert all rows normally
+ *
+ * 2. Insert a newly created DIV containing a new TABLE element below
+ * the DIV containing the actual scroll table body.
+ *
+ * 3. Clone the rows that were inserted in step 1 and attach the clones
+ * to the new TABLE element created in step 2.
+ *
+ * 4. The new DIV from step 2 is absolutely positioned so that the last
+ * inserted row is just behind the row that was expanded.
+ *
+ * 5. Hide the contents of the originally inserted rows by setting the
+ * DIV.v-table-cell-wrapper to display:none;.
+ *
+ * 6. Set the height of the originally inserted rows to 0.
+ *
+ * 7. The animation loop slides the DIV from step 2 downwards, while at
+ * the same pace growing the height of each of the inserted rows from 0
+ * to full height. The first inserted row grows from 0 to full and after
+ * this the second row grows from 0 to full, etc until all rows are full
+ * height.
+ *
+ * 8. Remove the DIV from step 2
+ *
+ * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
+ *
+ * 10. DONE
+ */
+ private class RowExpandAnimation extends Animation {
+
+ private final List<VScrollTableRow> rows;
+ private Element cloneDiv;
+ private Element cloneTable;
+ private AnimationPreparator preparator;
+
+ /**
+ * @param rows
+ * List of rows to animate. Must not be empty.
+ */
+ public RowExpandAnimation(List<VScrollTableRow> rows) {
+ this.rows = rows;
+ buildAndInsertAnimatingDiv();
+ preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
+ preparator.prepareTableForAnimation();
+ for (VScrollTableRow row : rows) {
+ cloneAndAppendRow(row);
+ row.addStyleName("v-table-row-animating");
+ setCellWrapperDivsToDisplayNone(row);
+ row.setHeight(getInitialHeight());
+ }
+ }
+
+ protected String getInitialHeight() {
+ return "0px";
+ }
+
+ private void cloneAndAppendRow(VScrollTableRow row) {
+ Element clonedTR = null;
+ clonedTR = row.getElement().cloneNode(true).cast();
+ clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
+ cloneTable.appendChild(clonedTR);
+ }
+
+ protected double getBaseOffset() {
+ return rows.get(0).getAbsoluteTop()
+ - rows.get(0).getParent().getAbsoluteTop()
+ - rows.size() * getRowHeight();
+ }
+
+ private void buildAndInsertAnimatingDiv() {
+ cloneDiv = DOM.createDiv();
+ cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
+ cloneTable = DOM.createTable();
+ cloneTable.addClassName("v-treetable-animation-clone");
+ cloneDiv.appendChild(cloneTable);
+ insertAnimatingDiv();
+ }
+
+ private void insertAnimatingDiv() {
+ Element tableBody = getElement().cast();
+ Element tableBodyParent = tableBody.getParentElement().cast();
+ tableBodyParent.insertAfter(cloneDiv, tableBody);
+ }
+
+ @Override
+ protected void onUpdate(double progress) {
+ animateDiv(progress);
+ animateRowHeights(progress);
+ }
+
+ private void animateDiv(double progress) {
+ double offset = calculateDivOffset(progress, getRowHeight());
+
+ cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
+ }
+
+ private void animateRowHeights(double progress) {
+ double rh = getRowHeight();
+ double vlh = calculateHeightOfAllVisibleLines(progress, rh);
+ int ix = 0;
+
+ while (ix < rows.size()) {
+ double height = vlh < rh ? vlh : rh;
+ rows.get(ix).setHeight(height + "px");
+ vlh -= height;
+ ix++;
+ }
+ }
+
+ protected double calculateHeightOfAllVisibleLines(double progress,
+ double rh) {
+ return rows.size() * rh * progress;
+ }
+
+ protected double calculateDivOffset(double progress, double rh) {
+ return progress * rows.size() * rh;
+ }
+
+ @Override
+ protected void onComplete() {
+ preparator.restoreTableAfterAnimation();
+ for (VScrollTableRow row : rows) {
+ resetCellWrapperDivsDisplayProperty(row);
+ row.removeStyleName("v-table-row-animating");
+ }
+ Element tableBodyParent = (Element) getElement()
+ .getParentElement();
+ tableBodyParent.removeChild(cloneDiv);
+ }
+
+ private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
+ Element tr = row.getElement();
+ for (int ix = 0; ix < tr.getChildCount(); ix++) {
+ getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ private Element getWrapperDiv(Element tr, int tdIx) {
+ Element td = tr.getChild(tdIx).cast();
+ return td.getChild(0).cast();
+ }
+
+ private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
+ Element tr = row.getElement();
+ for (int ix = 0; ix < tr.getChildCount(); ix++) {
+ getWrapperDiv(tr, ix).getStyle().clearProperty("display");
+ }
+ }
+
+ }
+
+ /**
+ * This is the inverse of the RowExpandAnimation and is implemented by
+ * extending it and overriding the calculation of offsets and heights.
+ */
+ private class RowCollapseAnimation extends RowExpandAnimation {
+
+ private final List<VScrollTableRow> rows;
+
+ /**
+ * @param rows
+ * List of rows to animate. Must not be empty.
+ */
+ public RowCollapseAnimation(List<VScrollTableRow> rows) {
+ super(rows);
+ this.rows = rows;
+ }
+
+ @Override
+ protected String getInitialHeight() {
+ return getRowHeight() + "px";
+ }
+
+ @Override
+ protected double getBaseOffset() {
+ return getRowHeight();
+ }
+
+ @Override
+ protected double calculateHeightOfAllVisibleLines(double progress,
+ double rh) {
+ return rows.size() * rh * (1 - progress);
+ }
+
+ @Override
+ protected double calculateDivOffset(double progress, double rh) {
+ return -super.calculateDivOffset(progress, rh);
+ }
+ }
+ }
+
+ /**
+ * Icons rendered into first actual column in TreeTable, not to row header
+ * cell
+ */
+ @Override
+ protected String buildCaptionHtmlSnippet(UIDL uidl) {
+ if (uidl.getTag().equals("column")) {
+ return super.buildCaptionHtmlSnippet(uidl);
+ } else {
+ String s = uidl.getStringAttribute("caption");
+ return s;
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ @Override
+ public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (collapseRequest || focusParentResponsePending) {
+ // Enqueue the event if there might be pending content changes from
+ // the server
+ if (pendingNavigationEvents.size() < 10) {
+ // Only keep 10 keyboard events in the queue
+ PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent(
+ keycode, ctrl, shift);
+ pendingNavigationEvents.add(pendingNavigationEvent);
+ }
+ return true;
+ }
+
+ VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow();
+ if (focusedRow != null) {
+ if (focusedRow.canHaveChildren
+ && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) {
+ if (!ctrl) {
+ client.updateVariable(paintableId, "selectCollapsed", true,
+ false);
+ }
+ sendSelectedRows(false);
+ sendToggleCollapsedUpdate(focusedRow.getKey());
+ return true;
+ } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) {
+ // already expanded, move selection down if next is on a deeper
+ // level (is-a-child)
+ VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow
+ .getParent();
+ Iterator<Widget> iterator = body.iterator();
+ VTreeTableRow next = null;
+ while (iterator.hasNext()) {
+ next = (VTreeTableRow) iterator.next();
+ if (next == focusedRow) {
+ next = (VTreeTableRow) iterator.next();
+ break;
+ }
+ }
+ if (next != null) {
+ if (next.depth > focusedRow.depth) {
+ selectionPending = true;
+ return super.handleNavigation(getNavigationDownKey(),
+ ctrl, shift);
+ }
+ } else {
+ // Note, a minor change here for a bit false behavior if
+ // cache rows is disabled + last visible row + no childs for
+ // the node
+ selectionPending = true;
+ return super.handleNavigation(getNavigationDownKey(), ctrl,
+ shift);
+ }
+ } else if (keycode == KeyCodes.KEY_LEFT) {
+ // already collapsed move selection up to parent node
+ // do on the server side as the parent is not necessary
+ // rendered on the client, could check if parent is visible if
+ // a performance issue arises
+
+ client.updateVariable(paintableId, "focusParent",
+ focusedRow.getKey(), true);
+
+ // Set flag that we should enqueue navigation events until we
+ // get a response to this request
+ focusParentResponsePending = true;
+
+ return true;
+ }
+ }
+ return super.handleNavigation(keycode, ctrl, shift);
+ }
+
+ private void sendToggleCollapsedUpdate(String rowKey) {
+ collapsedRowKey = rowKey;
+ collapseRequest = true;
+ client.updateVariable(paintableId, "toggleCollapsed", rowKey, true);
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONKEYUP && selectionPending) {
+ sendSelectedRows();
+ }
+ }
+
+ @Override
+ protected void sendSelectedRows(boolean immediately) {
+ super.sendSelectedRows(immediately);
+ selectionPending = false;
+ }
+
+ @Override
+ protected void reOrderColumn(String columnKey, int newIndex) {
+ super.reOrderColumn(columnKey, newIndex);
+ // current impl not intelligent enough to survive without visiting the
+ // server to redraw content
+ client.sendPendingVariableChanges();
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style + " v-treetable");
+ }
+
+ @Override
+ public void updateTotalRows(UIDL uidl) {
+ // Make sure that initializedAndAttached & al are not reset when the
+ // totalrows are updated on expand/collapse requests.
+ int newTotalRows = uidl.getIntAttribute("totalrows");
+ setTotalRows(newTotalRows);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Panel;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants;
+
+public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler,
+ MouseDownHandler, DoubleClickHandler, SubPartAware {
+
+ public static final String CLASSNAME = "v-select-twincol";
+
+ private static final int VISIBLE_COUNT = 10;
+
+ private static final int DEFAULT_COLUMN_COUNT = 10;
+
+ private final DoubleClickListBox options;
+
+ private final DoubleClickListBox selections;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public FlowPanel captionWrapper;
+
+ private HTML optionsCaption = null;
+
+ private HTML selectionsCaption = null;
+
+ private final VButton add;
+
+ private final VButton remove;
+
+ private final FlowPanel buttons;
+
+ private final Panel panel;
+
+ /**
+ * A ListBox which catches double clicks
+ *
+ */
+ public class DoubleClickListBox extends ListBox implements
+ HasDoubleClickHandlers {
+ public DoubleClickListBox(boolean isMultipleSelect) {
+ super(isMultipleSelect);
+ }
+
+ public DoubleClickListBox() {
+ super();
+ }
+
+ @Override
+ public HandlerRegistration addDoubleClickHandler(
+ DoubleClickHandler handler) {
+ return addDomHandler(handler, DoubleClickEvent.getType());
+ }
+ }
+
+ public VTwinColSelect() {
+ super(CLASSNAME);
+
+ captionWrapper = new FlowPanel();
+
+ options = new DoubleClickListBox();
+ options.addClickHandler(this);
+ options.addDoubleClickHandler(this);
+ options.setVisibleItemCount(VISIBLE_COUNT);
+ options.setStyleName(CLASSNAME + "-options");
+
+ selections = new DoubleClickListBox();
+ selections.addClickHandler(this);
+ selections.addDoubleClickHandler(this);
+ selections.setVisibleItemCount(VISIBLE_COUNT);
+ selections.setStyleName(CLASSNAME + "-selections");
+
+ buttons = new FlowPanel();
+ buttons.setStyleName(CLASSNAME + "-buttons");
+ add = new VButton();
+ add.setText(">>");
+ add.addClickHandler(this);
+ remove = new VButton();
+ remove.setText("<<");
+ remove.addClickHandler(this);
+
+ panel = ((Panel) optionsContainer);
+
+ panel.add(captionWrapper);
+ captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
+ // Hide until there actually is a caption to prevent IE from rendering
+ // extra empty space
+ captionWrapper.setVisible(false);
+
+ panel.add(options);
+ buttons.add(add);
+ final HTML br = new HTML("<span/>");
+ br.setStyleName(CLASSNAME + "-deco");
+ buttons.add(br);
+ buttons.add(remove);
+ panel.add(buttons);
+ panel.add(selections);
+
+ options.addKeyDownHandler(this);
+ options.addMouseDownHandler(this);
+
+ selections.addMouseDownHandler(this);
+ selections.addKeyDownHandler(this);
+ }
+
+ public HTML getOptionsCaption() {
+ if (optionsCaption == null) {
+ optionsCaption = new HTML();
+ optionsCaption.setStyleName(CLASSNAME + "-caption-left");
+ optionsCaption.getElement().getStyle()
+ .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
+ captionWrapper.add(optionsCaption);
+ }
+
+ return optionsCaption;
+ }
+
+ public HTML getSelectionsCaption() {
+ if (selectionsCaption == null) {
+ selectionsCaption = new HTML();
+ selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
+ selectionsCaption.getElement().getStyle()
+ .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
+ captionWrapper.add(selectionsCaption);
+ }
+
+ return selectionsCaption;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void updateCaptions(UIDL uidl) {
+ String leftCaption = (uidl
+ .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl
+ .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
+ : null);
+ String rightCaption = (uidl
+ .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl
+ .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
+ : null);
+
+ boolean hasCaptions = (leftCaption != null || rightCaption != null);
+
+ if (leftCaption == null) {
+ removeOptionsCaption();
+ } else {
+ getOptionsCaption().setText(leftCaption);
+
+ }
+
+ if (rightCaption == null) {
+ removeSelectionsCaption();
+ } else {
+ getSelectionsCaption().setText(rightCaption);
+ }
+
+ captionWrapper.setVisible(hasCaptions);
+ }
+
+ private void removeOptionsCaption() {
+ if (optionsCaption == null) {
+ return;
+ }
+
+ if (optionsCaption.getParent() != null) {
+ captionWrapper.remove(optionsCaption);
+ }
+
+ optionsCaption = null;
+ }
+
+ private void removeSelectionsCaption() {
+ if (selectionsCaption == null) {
+ return;
+ }
+
+ if (selectionsCaption.getParent() != null) {
+ captionWrapper.remove(selectionsCaption);
+ }
+
+ selectionsCaption = null;
+ }
+
+ @Override
+ public void buildOptions(UIDL uidl) {
+ final boolean enabled = !isDisabled() && !isReadonly();
+ options.setMultipleSelect(isMultiselect());
+ selections.setMultipleSelect(isMultiselect());
+ options.setEnabled(enabled);
+ selections.setEnabled(enabled);
+ add.setEnabled(enabled && !readonly);
+ remove.setEnabled(enabled && !readonly);
+ options.clear();
+ selections.clear();
+ for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+ final UIDL optionUidl = (UIDL) i.next();
+ if (optionUidl.hasAttribute("selected")) {
+ selections.addItem(optionUidl.getStringAttribute("caption"),
+ optionUidl.getStringAttribute("key"));
+ } else {
+ options.addItem(optionUidl.getStringAttribute("caption"),
+ optionUidl.getStringAttribute("key"));
+ }
+ }
+
+ if (getRows() > 0) {
+ options.setVisibleItemCount(getRows());
+ selections.setVisibleItemCount(getRows());
+
+ }
+
+ add.setStyleName("v-disabled", readonly);
+ remove.setStyleName("v-disabled", readonly);
+ }
+
+ @Override
+ protected String[] getSelectedItems() {
+ final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+ for (int i = 0; i < selections.getItemCount(); i++) {
+ selectedItemKeys.add(selections.getValue(i));
+ }
+ return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+ }
+
+ private boolean[] getSelectionBitmap(ListBox listBox) {
+ final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
+ for (int i = 0; i < listBox.getItemCount(); i++) {
+ if (listBox.isItemSelected(i)) {
+ selectedIndexes[i] = true;
+ } else {
+ selectedIndexes[i] = false;
+ }
+ }
+ return selectedIndexes;
+ }
+
+ private void addItem() {
+ Set<String> movedItems = moveSelectedItems(options, selections);
+ selectedKeys.addAll(movedItems);
+
+ client.updateVariable(paintableId, "selected",
+ selectedKeys.toArray(new String[selectedKeys.size()]),
+ isImmediate());
+ }
+
+ private void removeItem() {
+ Set<String> movedItems = moveSelectedItems(selections, options);
+ selectedKeys.removeAll(movedItems);
+
+ client.updateVariable(paintableId, "selected",
+ selectedKeys.toArray(new String[selectedKeys.size()]),
+ isImmediate());
+ }
+
+ private Set<String> moveSelectedItems(ListBox source, ListBox target) {
+ final boolean[] sel = getSelectionBitmap(source);
+ final Set<String> movedItems = new HashSet<String>();
+ int lastSelected = 0;
+ for (int i = 0; i < sel.length; i++) {
+ if (sel[i]) {
+ final int optionIndex = i
+ - (sel.length - source.getItemCount());
+ movedItems.add(source.getValue(optionIndex));
+
+ // Move selection to another column
+ final String text = source.getItemText(optionIndex);
+ final String value = source.getValue(optionIndex);
+ target.addItem(text, value);
+ target.setItemSelected(target.getItemCount() - 1, true);
+ source.removeItem(optionIndex);
+
+ if (source.getItemCount() > 0) {
+ lastSelected = optionIndex > 0 ? optionIndex - 1 : 0;
+ }
+ }
+ }
+
+ if (source.getItemCount() > 0) {
+ source.setSelectedIndex(lastSelected);
+ }
+
+ // If no items are left move the focus to the selections
+ if (source.getItemCount() == 0) {
+ target.setFocus(true);
+ } else {
+ source.setFocus(true);
+ }
+
+ return movedItems;
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ super.onClick(event);
+ if (event.getSource() == add) {
+ addItem();
+
+ } else if (event.getSource() == remove) {
+ removeItem();
+
+ } else if (event.getSource() == options) {
+ // unselect all in other list, to avoid mistakes (i.e wrong button)
+ final int c = selections.getItemCount();
+ for (int i = 0; i < c; i++) {
+ selections.setItemSelected(i, false);
+ }
+ } else if (event.getSource() == selections) {
+ // unselect all in other list, to avoid mistakes (i.e wrong button)
+ final int c = options.getItemCount();
+ for (int i = 0; i < c; i++) {
+ options.setItemSelected(i, false);
+ }
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void clearInternalHeights() {
+ selections.setHeight("");
+ options.setHeight("");
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setInternalHeights() {
+ int captionHeight = Util.getRequiredHeight(captionWrapper);
+ int totalHeight = getOffsetHeight();
+
+ String selectHeight = (totalHeight - captionHeight) + "px";
+
+ selections.setHeight(selectHeight);
+ options.setHeight(selectHeight);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void clearInternalWidths() {
+ int cols = -1;
+ if (getColumns() > 0) {
+ cols = getColumns();
+ } else {
+ cols = DEFAULT_COLUMN_COUNT;
+ }
+
+ if (cols >= 0) {
+ String colWidth = cols + "em";
+ String containerWidth = (2 * cols + 4) + "em";
+ // Caption wrapper width == optionsSelect + buttons +
+ // selectionsSelect
+ String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em";
+
+ options.setWidth(colWidth);
+ if (optionsCaption != null) {
+ optionsCaption.setWidth(colWidth);
+ }
+ selections.setWidth(colWidth);
+ if (selectionsCaption != null) {
+ selectionsCaption.setWidth(colWidth);
+ }
+ buttons.setWidth("3.5em");
+ optionsContainer.setWidth(containerWidth);
+ captionWrapper.setWidth(captionWrapperWidth);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setInternalWidths() {
+ DOM.setStyleAttribute(getElement(), "position", "relative");
+ int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder(
+ buttons.getElement(), 0);
+
+ int buttonWidth = Util.getRequiredWidth(buttons);
+ int totalWidth = getOffsetWidth();
+
+ int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2;
+
+ options.setWidth(spaceForSelect + "px");
+ if (optionsCaption != null) {
+ optionsCaption.setWidth(spaceForSelect + "px");
+ }
+
+ selections.setWidth(spaceForSelect + "px");
+ if (selectionsCaption != null) {
+ selectionsCaption.setWidth(spaceForSelect + "px");
+ }
+ captionWrapper.setWidth("100%");
+ }
+
+ @Override
+ public void setTabIndex(int tabIndex) {
+ options.setTabIndex(tabIndex);
+ selections.setTabIndex(tabIndex);
+ add.setTabIndex(tabIndex);
+ remove.setTabIndex(tabIndex);
+ }
+
+ @Override
+ public void focus() {
+ options.setFocus(true);
+ }
+
+ /**
+ * Get the key that selects an item in the table. By default it is the Enter
+ * key but by overriding this you can change the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getNavigationSelectKey() {
+ return KeyCodes.KEY_ENTER;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeKeyCode();
+
+ // Catch tab and move between select:s
+ if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Remove current selections
+ for (int i = 0; i < options.getItemCount(); i++) {
+ options.setItemSelected(i, false);
+ }
+
+ // Focus selections
+ selections.setFocus(true);
+ }
+
+ if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
+ && event.getSource() == selections) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Remove current selections
+ for (int i = 0; i < selections.getItemCount(); i++) {
+ selections.setItemSelected(i, false);
+ }
+
+ // Focus options
+ options.setFocus(true);
+ }
+
+ if (keycode == getNavigationSelectKey()) {
+ // Prevent default behavior
+ event.preventDefault();
+
+ // Decide which select the selection was made in
+ if (event.getSource() == options) {
+ // Prevents the selection to become a single selection when
+ // using Enter key
+ // as the selection key (default)
+ options.setFocus(false);
+
+ addItem();
+
+ } else if (event.getSource() == selections) {
+ // Prevents the selection to become a single selection when
+ // using Enter key
+ // as the selection key (default)
+ selections.setFocus(false);
+
+ removeItem();
+ }
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
+ * .gwt.event.dom.client.MouseDownEvent)
+ */
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ // Ensure that items are deselected when selecting
+ // from a different source. See #3699 for details.
+ if (event.getSource() == options) {
+ for (int i = 0; i < selections.getItemCount(); i++) {
+ selections.setItemSelected(i, false);
+ }
+ } else if (event.getSource() == selections) {
+ for (int i = 0; i < options.getItemCount(); i++) {
+ options.setItemSelected(i, false);
+ }
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.
+ * google.gwt.event.dom.client.DoubleClickEvent)
+ */
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ if (event.getSource() == options) {
+ addItem();
+ options.setSelectedIndex(-1);
+ options.setFocus(false);
+ } else if (event.getSource() == selections) {
+ removeItem();
+ selections.setSelectedIndex(-1);
+ selections.setFocus(false);
+ }
+
+ }
+
+ private static final String SUBPART_OPTION_SELECT = "leftSelect";
+ private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
+ + "-item";
+ private static final String SUBPART_SELECTION_SELECT = "rightSelect";
+ private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
+ + "-item";
+ private static final String SUBPART_LEFT_CAPTION = "leftCaption";
+ private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
+ private static final String SUBPART_ADD_BUTTON = "add";
+ private static final String SUBPART_REMOVE_BUTTON = "remove";
+
+ @Override
+ public Element getSubPartElement(String subPart) {
+ if (SUBPART_OPTION_SELECT.equals(subPart)) {
+ return options.getElement();
+ } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
+ String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
+ return (Element) options.getElement().getChild(
+ Integer.parseInt(idx));
+ } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
+ return selections.getElement();
+ } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
+ String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM
+ .length());
+ return (Element) selections.getElement().getChild(
+ Integer.parseInt(idx));
+ } else if (optionsCaption != null
+ && SUBPART_LEFT_CAPTION.equals(subPart)) {
+ return optionsCaption.getElement();
+ } else if (selectionsCaption != null
+ && SUBPART_RIGHT_CAPTION.equals(subPart)) {
+ return selectionsCaption.getElement();
+ } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
+ return add.getElement();
+ } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
+ return remove.getElement();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getSubPartName(Element subElement) {
+ if (optionsCaption != null
+ && optionsCaption.getElement().isOrHasChild(subElement)) {
+ return SUBPART_LEFT_CAPTION;
+ } else if (selectionsCaption != null
+ && selectionsCaption.getElement().isOrHasChild(subElement)) {
+ return SUBPART_RIGHT_CAPTION;
+ } else if (options.getElement().isOrHasChild(subElement)) {
+ if (options.getElement() == subElement) {
+ return SUBPART_OPTION_SELECT;
+ } else {
+ int idx = Util.getChildElementIndex(subElement);
+ return SUBPART_OPTION_SELECT_ITEM + idx;
+ }
+ } else if (selections.getElement().isOrHasChild(subElement)) {
+ if (selections.getElement() == subElement) {
+ return SUBPART_SELECTION_SELECT;
+ } else {
+ int idx = Util.getChildElementIndex(subElement);
+ return SUBPART_SELECTION_SELECT_ITEM + idx;
+ }
+ } else if (add.getElement().isOrHasChild(subElement)) {
+ return SUBPART_ADD_BUTTON;
+ } else if (remove.getElement().isOrHasChild(subElement)) {
+ return SUBPART_REMOVE_BUTTON;
+ }
+
+ return null;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+/**
+ *
+ */
+public class VUI extends SimplePanel implements ResizeHandler,
+ Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable,
+ HasResizeHandlers {
+
+ private static final String CLASSNAME = "v-view";
+
+ private static int MONITOR_PARENT_TIMER_INTERVAL = 1000;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String theme;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ShortcutActionHandler actionHandler;
+
+ /*
+ * Last known window size used to detect whether VView should be layouted
+ * again. Detection must check window size, because the VView size might be
+ * fixed and thus not automatically adapt to changed window sizes.
+ */
+ private int windowWidth;
+ private int windowHeight;
+
+ /*
+ * Last know view size used to detect whether new dimensions should be sent
+ * to the server.
+ */
+ private int viewWidth;
+ private int viewHeight;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection connection;
+
+ /**
+ * Keep track of possible parent size changes when an embedded application.
+ *
+ * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to
+ * keep track of when there is a real change.
+ */
+ private Timer resizeTimer;
+
+ /** stored width of parent for embedded application auto-resize */
+ private int parentWidth;
+
+ /** stored height of parent for embedded application auto-resize */
+ private int parentHeight;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int scrollTop;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int scrollLeft;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean rendering;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean scrollable;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean resizeLazy = false;
+
+ private HandlerRegistration historyHandlerRegistration;
+
+ private TouchScrollHandler touchScrollHandler;
+
+ /**
+ * The current URI fragment, used to avoid sending updates if nothing has
+ * changed.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public String currentFragment;
+
+ /**
+ * Listener for URI fragment changes. Notifies the server of the new value
+ * whenever the value changes.
+ */
+ private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
+
+ @Override
+ public void onValueChange(ValueChangeEvent<String> event) {
+ String newFragment = event.getValue();
+
+ // Send the location to the server if the fragment has changed
+ if (!newFragment.equals(currentFragment) && connection != null) {
+ currentFragment = newFragment;
+ connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
+ Window.Location.getHref(), true);
+ }
+ }
+ };
+
+ private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ performSizeCheck();
+ }
+
+ });
+
+ public VUI() {
+ super();
+ setStyleName(CLASSNAME);
+
+ // Allow focusing the view by using the focus() method, the view
+ // should not be in the document focus flow
+ getElement().setTabIndex(-1);
+ makeScrollable();
+ }
+
+ /**
+ * Start to periodically monitor for parent element resizes if embedded
+ * application (e.g. portlet).
+ */
+ @Override
+ protected void onLoad() {
+ super.onLoad();
+ if (isMonitoringParentSize()) {
+ resizeTimer = new Timer() {
+
+ @Override
+ public void run() {
+ // trigger check to see if parent size has changed,
+ // recalculate layouts
+ performSizeCheck();
+ resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
+ }
+ };
+ resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
+ }
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ historyHandlerRegistration = History
+ .addValueChangeHandler(historyChangeHandler);
+ currentFragment = History.getToken();
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ historyHandlerRegistration.removeHandler();
+ historyHandlerRegistration = null;
+ }
+
+ /**
+ * Stop monitoring for parent element resizes.
+ */
+
+ @Override
+ protected void onUnload() {
+ if (resizeTimer != null) {
+ resizeTimer.cancel();
+ resizeTimer = null;
+ }
+ super.onUnload();
+ }
+
+ /**
+ * Called when the window or parent div might have been resized.
+ *
+ * This immediately checks the sizes of the window and the parent div (if
+ * monitoring it) and triggers layout recalculation if they have changed.
+ */
+ protected void performSizeCheck() {
+ windowSizeMaybeChanged(Window.getClientWidth(),
+ Window.getClientHeight());
+ }
+
+ /**
+ * Called when the window or parent div might have been resized.
+ *
+ * This immediately checks the sizes of the window and the parent div (if
+ * monitoring it) and triggers layout recalculation if they have changed.
+ *
+ * @param newWindowWidth
+ * The new width of the window
+ * @param newWindowHeight
+ * The new height of the window
+ *
+ * @deprecated use {@link #performSizeCheck()}
+ */
+ @Deprecated
+ protected void windowSizeMaybeChanged(int newWindowWidth,
+ int newWindowHeight) {
+ boolean changed = false;
+ ComponentConnector connector = ConnectorMap.get(connection)
+ .getConnector(this);
+ if (windowWidth != newWindowWidth) {
+ windowWidth = newWindowWidth;
+ changed = true;
+ connector.getLayoutManager().reportOuterWidth(connector,
+ newWindowWidth);
+ VConsole.log("New window width: " + windowWidth);
+ }
+ if (windowHeight != newWindowHeight) {
+ windowHeight = newWindowHeight;
+ changed = true;
+ connector.getLayoutManager().reportOuterHeight(connector,
+ newWindowHeight);
+ VConsole.log("New window height: " + windowHeight);
+ }
+ Element parentElement = getElement().getParentElement();
+ if (isMonitoringParentSize() && parentElement != null) {
+ // check also for parent size changes
+ int newParentWidth = parentElement.getClientWidth();
+ int newParentHeight = parentElement.getClientHeight();
+ if (parentWidth != newParentWidth) {
+ parentWidth = newParentWidth;
+ changed = true;
+ VConsole.log("New parent width: " + parentWidth);
+ }
+ if (parentHeight != newParentHeight) {
+ parentHeight = newParentHeight;
+ changed = true;
+ VConsole.log("New parent height: " + parentHeight);
+ }
+ }
+ if (changed) {
+ /*
+ * If the window size has changed, layout the VView again and send
+ * new size to the server if the size changed. (Just checking VView
+ * size would cause us to ignore cases when a relatively sized VView
+ * should shrink as the content's size is fixed and would thus not
+ * automatically shrink.)
+ */
+ VConsole.log("Running layout functions due to window or parent resize");
+
+ // update size to avoid (most) redundant re-layout passes
+ // there can still be an extra layout recalculation if webkit
+ // overflow fix updates the size in a deferred block
+ if (isMonitoringParentSize() && parentElement != null) {
+ parentWidth = parentElement.getClientWidth();
+ parentHeight = parentElement.getClientHeight();
+ }
+
+ sendClientResized();
+
+ LayoutManager layoutManager = connector.getLayoutManager();
+ if (layoutManager.isLayoutRunning()) {
+ layoutManager.layoutLater();
+ } else {
+ layoutManager.layoutNow();
+ }
+ }
+ }
+
+ public String getTheme() {
+ return theme;
+ }
+
+ /**
+ * Used to reload host page on theme changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public static native void reloadHostPage()
+ /*-{
+ $wnd.location.reload();
+ }-*/;
+
+ /**
+ * Returns true if the body is NOT generated, i.e if someone else has made
+ * the page that we're running in. Otherwise we're in charge of the whole
+ * page.
+ *
+ * @return true if we're running embedded
+ */
+ public boolean isEmbedded() {
+ return !getElement().getOwnerDocument().getBody().getClassName()
+ .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME);
+ }
+
+ /**
+ * Returns true if the size of the parent should be checked periodically and
+ * the application should react to its changes.
+ *
+ * @return true if size of parent should be tracked
+ */
+ protected boolean isMonitoringParentSize() {
+ // could also perform a more specific check (Liferay portlet)
+ return isEmbedded();
+ }
+
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ int type = DOM.eventGetType(event);
+ if (type == Event.ONKEYDOWN && actionHandler != null) {
+ actionHandler.handleKeyboardEvent(event);
+ return;
+ } else if (scrollable && type == Event.ONSCROLL) {
+ updateScrollPosition();
+ }
+ }
+
+ /**
+ * Updates scroll position from DOM and saves variables to server.
+ */
+ private void updateScrollPosition() {
+ int oldTop = scrollTop;
+ int oldLeft = scrollLeft;
+ scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
+ scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
+ if (connection != null && !rendering) {
+ if (oldTop != scrollTop) {
+ connection.updateVariable(id, "scrollTop", scrollTop, false);
+ }
+ if (oldLeft != scrollLeft) {
+ connection.updateVariable(id, "scrollLeft", scrollLeft, false);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
+ * .gwt.event.logical.shared.ResizeEvent)
+ */
+
+ @Override
+ public void onResize(ResizeEvent event) {
+ triggerSizeChangeCheck();
+ }
+
+ /**
+ * Called when a resize event is received.
+ *
+ * This may trigger a lazy refresh or perform the size check immediately
+ * depending on the browser used and whether the server side requests
+ * resizes to be lazy.
+ */
+ private void triggerSizeChangeCheck() {
+ /*
+ * IE (pre IE9 at least) will give us some false resize events due to
+ * problems with scrollbars. Firefox 3 might also produce some extra
+ * events. We postpone both the re-layouting and the server side event
+ * for a while to deal with these issues.
+ *
+ * We may also postpone these events to avoid slowness when resizing the
+ * browser window. Constantly recalculating the layout causes the resize
+ * operation to be really slow with complex layouts.
+ */
+ boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
+
+ if (lazy) {
+ delayedResizeExecutor.trigger();
+ } else {
+ performSizeCheck();
+ }
+ }
+
+ /**
+ * Send new dimensions to the server.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void sendClientResized() {
+ Element parentElement = getElement().getParentElement();
+ int viewHeight = parentElement.getClientHeight();
+ int viewWidth = parentElement.getClientWidth();
+
+ ResizeEvent.fire(this, viewWidth, viewHeight);
+ }
+
+ public native static void goTo(String url)
+ /*-{
+ $wnd.location = url;
+ }-*/;
+
+ @Override
+ public void onWindowClosing(Window.ClosingEvent event) {
+ // Change focus on this window in order to ensure that all state is
+ // collected from textfields
+ // TODO this is a naive hack, that only works with text fields and may
+ // cause some odd issues. Should be replaced with a decent solution, see
+ // also related BeforeShortcutActionListener interface. Same interface
+ // might be usable here.
+ VTextField.flushChangesFromFocusedTextField();
+ }
+
+ private native static void loadAppIdListFromDOM(ArrayList<String> list)
+ /*-{
+ var j;
+ for(j in $wnd.vaadin.vaadinConfigurations) {
+ // $entry not needed as function is not exported
+ list.@java.util.Collection::add(Ljava/lang/Object;)(j);
+ }
+ }-*/;
+
+ @Override
+ public ShortcutActionHandler getShortcutActionHandler() {
+ return actionHandler;
+ }
+
+ @Override
+ public void focus() {
+ getElement().focus();
+ }
+
+ /**
+ * Ensures the root is scrollable eg. after style name changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public void makeScrollable() {
+ if (touchScrollHandler == null) {
+ touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+ }
+ touchScrollHandler.addElement(getElement());
+ }
+
+ @Override
+ public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) {
+ return addHandler(resizeHandler, ResizeEvent.getType());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Hidden;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy;
+
+/**
+ *
+ * Note, we are not using GWT FormPanel as we want to listen submitcomplete
+ * events even though the upload component is already detached.
+ *
+ */
+public class VUpload extends SimplePanel {
+
+ private final class MyFileUpload extends FileUpload {
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONCHANGE) {
+ if (immediate && fu.getFilename() != null
+ && !"".equals(fu.getFilename())) {
+ submit();
+ }
+ } else if (BrowserInfo.get().isIE()
+ && event.getTypeInt() == Event.ONFOCUS) {
+ // IE and user has clicked on hidden textarea part of upload
+ // field. Manually open file selector, other browsers do it by
+ // default.
+ fireNativeClick(fu.getElement());
+ // also remove focus to enable hack if user presses cancel
+ // button
+ fireNativeBlur(fu.getElement());
+ }
+ }
+ }
+
+ public static final String CLASSNAME = "v-upload";
+
+ /**
+ * FileUpload component that opens native OS dialog to select file.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public FileUpload fu = new MyFileUpload();
+
+ Panel panel = new FlowPanel();
+
+ UploadIFrameOnloadStrategy onloadstrategy = GWT
+ .create(UploadIFrameOnloadStrategy.class);
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /**
+ * Button that initiates uploading.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public final VButton submitButton;
+
+ /**
+ * When expecting big files, programmer may initiate some UI changes when
+ * uploading the file starts. Bit after submitting file we'll visit the
+ * server to check possible changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public Timer t;
+
+ /**
+ * some browsers tries to send form twice if submit is called in button
+ * click handler, some don't submit at all without it, so we need to track
+ * if form is already being submitted
+ */
+ private boolean submitted = false;
+
+ private boolean enabled = true;
+
+ private boolean immediate;
+
+ private Hidden maxfilesize = new Hidden();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public FormElement element;
+
+ private com.google.gwt.dom.client.Element synthesizedFrame;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int nextUploadId;
+
+ public VUpload() {
+ super(com.google.gwt.dom.client.Document.get().createFormElement());
+
+ element = getElement().cast();
+ setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
+ element.setMethod(FormPanel.METHOD_POST);
+
+ setWidget(panel);
+ panel.add(maxfilesize);
+ panel.add(fu);
+ submitButton = new VButton();
+ submitButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (immediate) {
+ // fire click on upload (eg. focused button and hit space)
+ fireNativeClick(fu.getElement());
+ } else {
+ submit();
+ }
+ }
+ });
+ panel.add(submitButton);
+
+ setStyleName(CLASSNAME);
+ }
+
+ private static native void setEncoding(Element form, String encoding)
+ /*-{
+ form.enctype = encoding;
+ }-*/;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setImmediate(boolean booleanAttribute) {
+ if (immediate != booleanAttribute) {
+ immediate = booleanAttribute;
+ if (immediate) {
+ fu.sinkEvents(Event.ONCHANGE);
+ fu.sinkEvents(Event.ONFOCUS);
+ }
+ }
+ setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
+ }
+
+ private static native void fireNativeClick(Element element)
+ /*-{
+ element.click();
+ }-*/;
+
+ private static native void fireNativeBlur(Element element)
+ /*-{
+ element.blur();
+ }-*/;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void disableUpload() {
+ submitButton.setEnabled(false);
+ if (!submitted) {
+ // Cannot disable the fileupload while submitting or the file won't
+ // be submitted at all
+ fu.getElement().setPropertyBoolean("disabled", true);
+ }
+ enabled = false;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void enableUpload() {
+ submitButton.setEnabled(true);
+ fu.getElement().setPropertyBoolean("disabled", false);
+ enabled = true;
+ if (submitted) {
+ /*
+ * An old request is still in progress (most likely cancelled),
+ * ditching that target frame to make it possible to send a new
+ * file. A new target frame is created later."
+ */
+ cleanTargetFrame();
+ submitted = false;
+ }
+ }
+
+ /**
+ * Re-creates file input field and populates panel. This is needed as we
+ * want to clear existing values from our current file input field.
+ */
+ private void rebuildPanel() {
+ panel.remove(submitButton);
+ panel.remove(fu);
+ fu = new MyFileUpload();
+ fu.setName(paintableId + "_file");
+ fu.getElement().setPropertyBoolean("disabled", !enabled);
+ panel.add(fu);
+ panel.add(submitButton);
+ if (immediate) {
+ fu.sinkEvents(Event.ONCHANGE);
+ }
+ }
+
+ /**
+ * Called by JSNI (hooked via {@link #onloadstrategy})
+ */
+ private void onSubmitComplete() {
+ /* Needs to be run dereferred to avoid various browser issues. */
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ if (submitted) {
+ if (client != null) {
+ if (t != null) {
+ t.cancel();
+ }
+ VConsole.log("VUpload:Submit complete");
+ client.sendPendingVariableChanges();
+ }
+
+ rebuildPanel();
+
+ submitted = false;
+ enableUpload();
+ if (!isAttached()) {
+ /*
+ * Upload is complete when upload is already abandoned.
+ */
+ cleanTargetFrame();
+ }
+ }
+ }
+ });
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void submit() {
+ if (fu.getFilename().length() == 0 || submitted || !enabled) {
+ VConsole.log("Submit cancelled (disabled, no file or already submitted)");
+ return;
+ }
+ // flush possibly pending variable changes, so they will be handled
+ // before upload
+ client.sendPendingVariableChanges();
+
+ element.submit();
+ submitted = true;
+ VConsole.log("Submitted form");
+
+ disableUpload();
+
+ /*
+ * Visit server a moment after upload has started to see possible
+ * changes from UploadStarted event. Will be cleared on complete.
+ */
+ t = new Timer() {
+ @Override
+ public void run() {
+ VConsole.log("Visiting server to see if upload started event changed UI.");
+ client.updateVariable(paintableId, "pollForStart",
+ nextUploadId, true);
+ }
+ };
+ t.schedule(800);
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (client != null) {
+ ensureTargetFrame();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void ensureTargetFrame() {
+ if (synthesizedFrame == null) {
+ // Attach a hidden IFrame to the form. This is the target iframe to
+ // which the form will be submitted. We have to create the iframe
+ // using innerHTML, because setting an iframe's 'name' property
+ // dynamically doesn't work on most browsers.
+ DivElement dummy = Document.get().createDivElement();
+ dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
+ + getFrameName()
+ + "' style='position:absolute;width:0;height:0;border:0'>");
+ synthesizedFrame = dummy.getFirstChildElement();
+ Document.get().getBody().appendChild(synthesizedFrame);
+ element.setTarget(getFrameName());
+ onloadstrategy.hookEvents(synthesizedFrame, this);
+ }
+ }
+
+ private String getFrameName() {
+ return paintableId + "_TGT_FRAME";
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ if (!submitted) {
+ cleanTargetFrame();
+ }
+ }
+
+ private void cleanTargetFrame() {
+ if (synthesizedFrame != null) {
+ Document.get().getBody().removeChild(synthesizedFrame);
+ onloadstrategy.unHookEvents(synthesizedFrame);
+ synthesizedFrame = null;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.ui;
+
+import com.vaadin.client.StyleConstants;
+
+/**
+ * Represents a layout where the children is ordered vertically
+ */
+public class VVerticalLayout extends VOrderedLayout {
+
+ public static final String CLASSNAME = "v-verticallayout";
+
+ /**
+ * Default constructor
+ */
+ public VVerticalLayout() {
+ super(true);
+ setStyleName(CLASSNAME);
+ }
+
+ @Override
+ public void setStyleName(String style) {
+ super.setStyleName(style);
+ addStyleName(StyleConstants.UI_LAYOUT);
+ addStyleName("v-vertical");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.VideoElement;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+
+public class VVideo extends VMediaBase {
+
+ public static String CLASSNAME = "v-video";
+
+ private VideoElement video;
+
+ public VVideo() {
+ video = Document.get().createVideoElement();
+ setMediaElement(video);
+ setStyleName(CLASSNAME);
+
+ updateDimensionsWhenMetadataLoaded(getElement());
+ }
+
+ /**
+ * Registers a listener that updates the dimensions of the widget when the
+ * video metadata has been loaded.
+ *
+ * @param el
+ */
+ private native void updateDimensionsWhenMetadataLoaded(Element el)
+ /*-{
+ var self = this;
+ el.addEventListener('loadedmetadata', $entry(function(e) {
+ self.@com.vaadin.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight);
+ }), false);
+
+ }-*/;
+
+ /**
+ * Updates the dimensions of the widget.
+ *
+ * @param w
+ * @param h
+ */
+ private void updateElementDynamicSize(int w, int h) {
+ video.getStyle().setWidth(w, Unit.PX);
+ video.getStyle().setHeight(h, Unit.PX);
+ Util.notifyParentOfSizeChange(this, true);
+ }
+
+ public void setPoster(String poster) {
+ video.setPoster(poster);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2011 Vaadin Ltd.
+ *
+ * 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.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Console;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.shared.EventId;
+
+/**
+ * "Sub window" component.
+ *
+ * @author Vaadin Ltd
+ */
+public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
+ ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
+
+ /**
+ * Minimum allowed height of a window. This refers to the content area, not
+ * the outer borders.
+ */
+ private static final int MIN_CONTENT_AREA_HEIGHT = 100;
+
+ /**
+ * Minimum allowed width of a window. This refers to the content area, not
+ * the outer borders.
+ */
+ private static final int MIN_CONTENT_AREA_WIDTH = 150;
+
+ private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
+
+ private static boolean orderingDefered;
+
+ public static final String CLASSNAME = "v-window";
+
+ private static final int STACKING_OFFSET_PIXELS = 15;
+
+ public static final int Z_INDEX = 10000;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ComponentConnector layout;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element contents;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element header;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element footer;
+
+ private Element resizeBox;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
+
+ private boolean dragging;
+
+ private int startX;
+
+ private int startY;
+
+ private int origX;
+
+ private int origY;
+
+ private boolean resizing;
+
+ private int origW;
+
+ private int origH;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public Element closeBox;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String id;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ShortcutActionHandler shortcutHandler;
+
+ /** Last known positionx read from UIDL or updated to application connection */
+ private int uidlPositionX = -1;
+
+ /** Last known positiony read from UIDL or updated to application connection */
+ private int uidlPositionY = -1;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean vaadinModality = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean resizable = true;
+
+ private boolean draggable = true;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean resizeLazy = false;
+
+ private Element modalityCurtain;
+ private Element draggingCurtain;
+ private Element resizingCurtain;
+
+ private Element headerText;
+
+ private boolean closable = true;
+
+ /**
+ * If centered (via UIDL), the window should stay in the centered -mode
+ * until a position is received from the server, or the user moves or
+ * resizes the window.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public boolean centered = false;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean immediate;
+
+ private Element wrapper;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public boolean visibilityChangesDisabled;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int bringToFrontSequence = -1;
+
+ private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
+ new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ updateContentsSize();
+ }
+ });
+
+ public VWindow() {
+ super(false, false, true); // no autohide, not modal, shadow
+ // Different style of shadow for windows
+ setShadowStyle("window");
+
+ constructDOM();
+ contentPanel.addScrollHandler(this);
+ contentPanel.addKeyDownHandler(this);
+ contentPanel.addFocusHandler(this);
+ contentPanel.addBlurHandler(this);
+ }
+
+ public void bringToFront() {
+ int curIndex = windowOrder.indexOf(this);
+ if (curIndex + 1 < windowOrder.size()) {
+ windowOrder.remove(this);
+ windowOrder.add(this);
+ for (; curIndex < windowOrder.size(); curIndex++) {
+ windowOrder.get(curIndex).setWindowOrder(curIndex);
+ }
+ }
+ }
+
+ /**
+ * Returns true if this window is the topmost VWindow
+ *
+ * @return
+ */
+ private boolean isActive() {
+ return equals(getTopmostWindow());
+ }
+
+ private static VWindow getTopmostWindow() {
+ return windowOrder.get(windowOrder.size() - 1);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setWindowOrderAndPosition() {
+ // This cannot be done in the constructor as the widgets are created in
+ // a different order than on they should appear on screen
+ if (windowOrder.contains(this)) {
+ // Already set
+ return;
+ }
+ final int order = windowOrder.size();
+ setWindowOrder(order);
+ windowOrder.add(this);
+ setPopupPosition(order * STACKING_OFFSET_PIXELS, order
+ * STACKING_OFFSET_PIXELS);
+
+ }
+
+ private void setWindowOrder(int order) {
+ setZIndex(order + Z_INDEX);
+ }
+
+ @Override
+ protected void setZIndex(int zIndex) {
+ super.setZIndex(zIndex);
+ if (vaadinModality) {
+ DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
+ }
+ }
+
+ protected Element getModalityCurtain() {
+ if (modalityCurtain == null) {
+ modalityCurtain = DOM.createDiv();
+ modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
+ }
+ return modalityCurtain;
+ }
+
+ protected void constructDOM() {
+ setStyleName(CLASSNAME);
+
+ header = DOM.createDiv();
+ DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
+ headerText = DOM.createDiv();
+ DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
+ contents = DOM.createDiv();
+ DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
+ footer = DOM.createDiv();
+ DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
+ resizeBox = DOM.createDiv();
+ DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
+ closeBox = DOM.createDiv();
+ DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
+ DOM.appendChild(footer, resizeBox);
+
+ wrapper = DOM.createDiv();
+ DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
+
+ DOM.appendChild(wrapper, header);
+ DOM.appendChild(wrapper, closeBox);
+ DOM.appendChild(header, headerText);
+ DOM.appendChild(wrapper, contents);
+ DOM.appendChild(wrapper, footer);
+ DOM.appendChild(super.getContainerElement(), wrapper);
+
+ sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
+ | Event.ONLOSECAPTURE);
+
+ setWidget(contentPanel);
+
+ }
+
+ /**
+ * Calling this method will defer ordering algorithm, to order windows based
+ * on servers bringToFront and modality instructions. Non changed windows
+ * will be left intact.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public static void deferOrdering() {
+ if (!orderingDefered) {
+ orderingDefered = true;
+ Scheduler.get().scheduleFinally(new Command() {
+
+ @Override
+ public void execute() {
+ doServerSideOrdering();
+ VNotification.bringNotificationsToFront();
+ }
+ });
+ }
+ }
+
+ private static void doServerSideOrdering() {
+ orderingDefered = false;
+ VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
+ Arrays.sort(array, new Comparator<VWindow>() {
+
+ @Override
+ public int compare(VWindow o1, VWindow o2) {
+ /*
+ * Order by modality, then by bringtofront sequence.
+ */
+
+ if (o1.vaadinModality && !o2.vaadinModality) {
+ return 1;
+ } else if (!o1.vaadinModality && o2.vaadinModality) {
+ return -1;
+ } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
+ return 1;
+ } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ });
+ for (int i = 0; i < array.length; i++) {
+ VWindow w = array[i];
+ if (w.bringToFrontSequence != -1 || w.vaadinModality) {
+ w.bringToFront();
+ w.bringToFrontSequence = -1;
+ }
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ /*
+ * Visibility with VWindow works differently than with other Paintables
+ * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
+ * used to avoid visibility call from
+ * ApplicationConnection.updateComponent();
+ */
+ if (!visibilityChangesDisabled) {
+ super.setVisible(visible);
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setDraggable(boolean draggable) {
+ if (this.draggable == draggable) {
+ return;
+ }
+
+ this.draggable = draggable;
+
+ setCursorProperties();
+ }
+
+ private void setCursorProperties() {
+ if (!draggable) {
+ header.getStyle().setProperty("cursor", "default");
+ footer.getStyle().setProperty("cursor", "default");
+ } else {
+ header.getStyle().setProperty("cursor", "");
+ footer.getStyle().setProperty("cursor", "");
+ }
+ }
+
+ /**
+ * Sets the closable state of the window. Additionally hides/shows the close
+ * button according to the new state.
+ *
+ * @param closable
+ * true if the window can be closed by the user
+ */
+ public void setClosable(boolean closable) {
+ if (this.closable == closable) {
+ return;
+ }
+
+ this.closable = closable;
+ if (closable) {
+ DOM.setStyleAttribute(closeBox, "display", "");
+ } else {
+ DOM.setStyleAttribute(closeBox, "display", "none");
+ }
+
+ }
+
+ /**
+ * Returns the closable state of the sub window. If the sub window is
+ * closable a decoration (typically an X) is shown to the user. By clicking
+ * on the X the user can close the window.
+ *
+ * @return true if the sub window is closable
+ */
+ protected boolean isClosable() {
+ return closable;
+ }
+
+ @Override
+ public void show() {
+ if (!windowOrder.contains(this)) {
+ // This is needed if the window is hidden and then shown again.
+ // Otherwise this VWindow is added to windowOrder in the
+ // constructor.
+ windowOrder.add(this);
+ }
+
+ if (vaadinModality) {
+ showModalityCurtain();
+ }
+ super.show();
+ }
+
+ @Override
+ public void hide() {
+ if (vaadinModality) {
+ hideModalityCurtain();
+ }
+ super.hide();
+
+ // Remove window from windowOrder to avoid references being left
+ // hanging.
+ windowOrder.remove(this);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setVaadinModality(boolean modality) {
+ vaadinModality = modality;
+ if (vaadinModality) {
+ if (isAttached()) {
+ showModalityCurtain();
+ }
+ deferOrdering();
+ } else {
+ if (modalityCurtain != null) {
+ if (isAttached()) {
+ hideModalityCurtain();
+ }
+ modalityCurtain = null;
+ }
+ }
+ }
+
+ private void showModalityCurtain() {
+ DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
+ "" + (windowOrder.indexOf(this) + Z_INDEX));
+
+ if (isShowing()) {
+ getOverlayContainer().insertBefore(getModalityCurtain(),
+ getElement());
+ } else {
+ getOverlayContainer().appendChild(getModalityCurtain());
+ }
+
+ }
+
+ private void hideModalityCurtain() {
+ modalityCurtain.removeFromParent();
+ }
+
+ /*
+ * Shows an empty div on top of all other content; used when moving, so that
+ * iframes (etc) do not steal event.
+ */
+ private void showDraggingCurtain() {
+ getElement().getParentElement().insertBefore(getDraggingCurtain(),
+ getElement());
+ }
+
+ private void hideDraggingCurtain() {
+ if (draggingCurtain != null) {
+ draggingCurtain.removeFromParent();
+ }
+ }
+
+ /*
+ * Shows an empty div on top of all other content; used when resizing, so
+ * that iframes (etc) do not steal event.
+ */
+ private void showResizingCurtain() {
+ getElement().getParentElement().insertBefore(getResizingCurtain(),
+ getElement());
+ }
+
+ private void hideResizingCurtain() {
+ if (resizingCurtain != null) {
+ resizingCurtain.removeFromParent();
+ }
+ }
+
+ private Element getDraggingCurtain() {
+ if (draggingCurtain == null) {
+ draggingCurtain = createCurtain();
+ draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
+ }
+
+ return draggingCurtain;
+ }
+
+ private Element getResizingCurtain() {
+ if (resizingCurtain == null) {
+ resizingCurtain = createCurtain();
+ resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
+ }
+
+ return resizingCurtain;
+ }
+
+ private Element createCurtain() {
+ Element curtain = DOM.createDiv();
+
+ DOM.setStyleAttribute(curtain, "position", "absolute");
+ DOM.setStyleAttribute(curtain, "top", "0px");
+ DOM.setStyleAttribute(curtain, "left", "0px");
+ DOM.setStyleAttribute(curtain, "width", "100%");
+ DOM.setStyleAttribute(curtain, "height", "100%");
+ DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX);
+
+ return curtain;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setResizable(boolean resizability) {
+ resizable = resizability;
+ if (resizability) {
+ DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
+ DOM.setElementProperty(resizeBox, "className", CLASSNAME
+ + "-resizebox");
+ } else {
+ DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
+ + CLASSNAME + "-footer-noresize");
+ DOM.setElementProperty(resizeBox, "className", CLASSNAME
+ + "-resizebox " + CLASSNAME + "-resizebox-disabled");
+ }
+ }
+
+ @Override
+ public void setPopupPosition(int left, int top) {
+ if (top < 0) {
+ // ensure window is not moved out of browser window from top of the
+ // screen
+ top = 0;
+ }
+ super.setPopupPosition(left, top);
+ if (left != uidlPositionX && client != null) {
+ client.updateVariable(id, "positionx", left, false);
+ uidlPositionX = left;
+ }
+ if (top != uidlPositionY && client != null) {
+ client.updateVariable(id, "positiony", top, false);
+ uidlPositionY = top;
+ }
+ }
+
+ public void setCaption(String c) {
+ setCaption(c, null);
+ }
+
+ public void setCaption(String c, String icon) {
+ String html = Util.escapeHTML(c);
+ if (icon != null) {
+ icon = client.translateVaadinUri(icon);
+ html = "<img src=\"" + Util.escapeAttribute(icon)
+ + "\" class=\"v-icon\" />" + html;
+ }
+ DOM.setInnerHTML(headerText, html);
+ }
+
+ @Override
+ protected Element getContainerElement() {
+ // in GWT 1.5 this method is used in PopupPanel constructor
+ if (contents == null) {
+ return super.getContainerElement();
+ }
+ return contents;
+ }
+
+ @Override
+ public void onBrowserEvent(final Event event) {
+ boolean bubble = true;
+
+ final int type = event.getTypeInt();
+
+ final Element target = DOM.eventGetTarget(event);
+
+ if (resizing || resizeBox == target) {
+ onResizeEvent(event);
+ bubble = false;
+ } else if (isClosable() && target == closeBox) {
+ if (type == Event.ONCLICK) {
+ onCloseClick();
+ }
+ bubble = false;
+ } else if (dragging || !contents.isOrHasChild(target)) {
+ onDragEvent(event);
+ bubble = false;
+ } else if (type == Event.ONCLICK) {
+ // clicked inside window, ensure to be on top
+ if (!isActive()) {
+ bringToFront();
+ }
+ }
+
+ /*
+ * If clicking on other than the content, move focus to the window.
+ * After that this windows e.g. gets all keyboard shortcuts.
+ */
+ if (type == Event.ONMOUSEDOWN
+ && !contentPanel.getElement().isOrHasChild(target)
+ && target != closeBox) {
+ contentPanel.focus();
+ }
+
+ if (!bubble) {
+ event.stopPropagation();
+ } else {
+ // Super.onBrowserEvent takes care of Handlers added by the
+ // ClickEventHandler
+ super.onBrowserEvent(event);
+ }
+ }
+
+ private void onCloseClick() {
+ client.updateVariable(id, "close", true, true);
+ }
+
+ private void onResizeEvent(Event event) {
+ if (resizable && Util.isTouchEventOrLeftMouseButton(event)) {
+ switch (event.getTypeInt()) {
+ case Event.ONMOUSEDOWN:
+ case Event.ONTOUCHSTART:
+ if (!isActive()) {
+ bringToFront();
+ }
+ showResizingCurtain();
+ if (BrowserInfo.get().isIE()) {
+ DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
+ }
+ resizing = true;
+ startX = Util.getTouchOrMouseClientX(event);
+ startY = Util.getTouchOrMouseClientY(event);
+ origW = getElement().getOffsetWidth();
+ origH = getElement().getOffsetHeight();
+ DOM.setCapture(getElement());
+ event.preventDefault();
+ break;
+ case Event.ONMOUSEUP:
+ case Event.ONTOUCHEND:
+ setSize(event, true);
+ case Event.ONTOUCHCANCEL:
+ DOM.releaseCapture(getElement());
+ case Event.ONLOSECAPTURE:
+ hideResizingCurtain();
+ if (BrowserInfo.get().isIE()) {
+ DOM.setStyleAttribute(resizeBox, "visibility", "");
+ }
+ resizing = false;
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ if (resizing) {
+ centered = false;
+ setSize(event, false);
+ event.preventDefault();
+ }
+ break;
+ default:
+ event.preventDefault();
+ break;
+ }
+ }
+ }
+
+ /**
+ * TODO check if we need to support this with touch based devices.
+ *
+ * Checks if the cursor was inside the browser content area when the event
+ * happened.
+ *
+ * @param event
+ * The event to be checked
+ * @return true, if the cursor is inside the browser content area
+ *
+ * false, otherwise
+ */
+ private boolean cursorInsideBrowserContentArea(Event event) {
+ if (event.getClientX() < 0 || event.getClientY() < 0) {
+ // Outside to the left or above
+ return false;
+ }
+
+ if (event.getClientX() > Window.getClientWidth()
+ || event.getClientY() > Window.getClientHeight()) {
+ // Outside to the right or below
+ return false;
+ }
+
+ return true;
+ }
+
+ private void setSize(Event event, boolean updateVariables) {
+ if (!cursorInsideBrowserContentArea(event)) {
+ // Only drag while cursor is inside the browser client area
+ return;
+ }
+
+ int w = Util.getTouchOrMouseClientX(event) - startX + origW;
+ int minWidth = getMinWidth();
+ if (w < minWidth) {
+ w = minWidth;
+ }
+
+ int h = Util.getTouchOrMouseClientY(event) - startY + origH;
+ int minHeight = getMinHeight();
+ if (h < minHeight) {
+ h = minHeight;
+ }
+
+ setWidth(w + "px");
+ setHeight(h + "px");
+
+ if (updateVariables) {
+ // sending width back always as pixels, no need for unit
+ client.updateVariable(id, "width", w, false);
+ client.updateVariable(id, "height", h, immediate);
+ }
+
+ if (updateVariables || !resizeLazy) {
+ // Resize has finished or is not lazy
+ updateContentsSize();
+ } else {
+ // Lazy resize - wait for a while before re-rendering contents
+ delayedContentsSizeUpdater.trigger();
+ }
+ }
+
+ private void updateContentsSize() {
+ // Update child widget dimensions
+ if (client != null) {
+ client.handleComponentRelativeSize(layout.getWidget());
+ client.runDescendentsLayout((HasWidgets) layout.getWidget());
+ }
+
+ LayoutManager layoutManager = LayoutManager.get(client);
+ layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector(
+ this));
+ layoutManager.layoutNow();
+ }
+
+ @Override
+ public void setWidth(String width) {
+ // Override PopupPanel which sets the width to the contents
+ getElement().getStyle().setProperty("width", width);
+ // Update v-has-width in case undefined window is resized
+ setStyleName("v-has-width", width != null && width.length() > 0);
+ }
+
+ @Override
+ public void setHeight(String height) {
+ // Override PopupPanel which sets the height to the contents
+ getElement().getStyle().setProperty("height", height);
+ // Update v-has-height in case undefined window is resized
+ setStyleName("v-has-height", height != null && height.length() > 0);
+ }
+
+ private void onDragEvent(Event event) {
+ if (!Util.isTouchEventOrLeftMouseButton(event)) {
+ return;
+ }
+
+ switch (DOM.eventGetType(event)) {
+ case Event.ONTOUCHSTART:
+ if (event.getTouches().length() > 1) {
+ return;
+ }
+ case Event.ONMOUSEDOWN:
+ if (!isActive()) {
+ bringToFront();
+ }
+ beginMovingWindow(event);
+ break;
+ case Event.ONMOUSEUP:
+ case Event.ONTOUCHEND:
+ case Event.ONTOUCHCANCEL:
+ case Event.ONLOSECAPTURE:
+ stopMovingWindow();
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ moveWindow(event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void moveWindow(Event event) {
+ if (dragging) {
+ centered = false;
+ if (cursorInsideBrowserContentArea(event)) {
+ // Only drag while cursor is inside the browser client area
+ final int x = Util.getTouchOrMouseClientX(event) - startX
+ + origX;
+ final int y = Util.getTouchOrMouseClientY(event) - startY
+ + origY;
+ setPopupPosition(x, y);
+ }
+ DOM.eventPreventDefault(event);
+ }
+ }
+
+ private void beginMovingWindow(Event event) {
+ if (draggable) {
+ showDraggingCurtain();
+ dragging = true;
+ startX = Util.getTouchOrMouseClientX(event);
+ startY = Util.getTouchOrMouseClientY(event);
+ origX = DOM.getAbsoluteLeft(getElement());
+ origY = DOM.getAbsoluteTop(getElement());
+ DOM.setCapture(getElement());
+ DOM.eventPreventDefault(event);
+ }
+ }
+
+ private void stopMovingWindow() {
+ dragging = false;
+ hideDraggingCurtain();
+ DOM.releaseCapture(getElement());
+ }
+
+ @Override
+ public boolean onEventPreview(Event event) {
+ if (dragging) {
+ onDragEvent(event);
+ return false;
+ } else if (resizing) {
+ onResizeEvent(event);
+ return false;
+ }
+
+ // TODO This is probably completely unnecessary as the modality curtain
+ // prevents events from reaching other windows and any security check
+ // must be done on the server side and not here.
+ // The code here is also run many times as each VWindow has an event
+ // preview but we cannot check only the current VWindow here (e.g.
+ // if(isTopMost) {...}) because PopupPanel will cause all events that
+ // are not cancelled here and target this window to be consume():d
+ // meaning the event won't be sent to the rest of the preview handlers.
+
+ if (getTopmostWindow().vaadinModality) {
+ // Topmost window is modal. Cancel the event if it targets something
+ // outside that window (except debug console...)
+ if (DOM.getCaptureElement() != null) {
+ // Allow events when capture is set
+ return true;
+ }
+
+ final Element target = event.getEventTarget().cast();
+ if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) {
+ // not within the modal window, but let's see if it's in the
+ // debug window
+ Widget w = Util.findWidget(target, null);
+ while (w != null) {
+ if (w instanceof Console) {
+ return true; // allow debug-window clicks
+ } else if (ConnectorMap.get(client).isConnector(w)) {
+ return false;
+ }
+ w = w.getParent();
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void addStyleDependentName(String styleSuffix) {
+ // VWindow's getStyleElement() does not return the same element as
+ // getElement(), so we need to override this.
+ setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
+ true);
+ }
+
+ @Override
+ public ShortcutActionHandler getShortcutActionHandler() {
+ return shortcutHandler;
+ }
+
+ @Override
+ public void onScroll(ScrollEvent event) {
+ client.updateVariable(id, "scrollTop",
+ contentPanel.getScrollPosition(), false);
+ client.updateVariable(id, "scrollLeft",
+ contentPanel.getHorizontalScrollPosition(), false);
+
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (shortcutHandler != null) {
+ shortcutHandler
+ .handleKeyboardEvent(Event.as(event.getNativeEvent()));
+ return;
+ }
+ }
+
+ @Override
+ public void onBlur(BlurEvent event) {
+ if (client.hasEventListeners(this, EventId.BLUR)) {
+ client.updateVariable(id, EventId.BLUR, "", true);
+ }
+ }
+
+ @Override
+ public void onFocus(FocusEvent event) {
+ if (client.hasEventListeners(this, EventId.FOCUS)) {
+ client.updateVariable(id, EventId.FOCUS, "", true);
+ }
+ }
+
+ @Override
+ public void focus() {
+ contentPanel.focus();
+ }
+
+ public int getMinHeight() {
+ return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight();
+ }
+
+ private int getDecorationHeight() {
+ LayoutManager lm = layout.getLayoutManager();
+ int headerHeight = lm.getOuterHeight(header);
+ int footerHeight = lm.getOuterHeight(footer);
+ return headerHeight + footerHeight;
+ }
+
+ public int getMinWidth() {
+ return MIN_CONTENT_AREA_WIDTH + getDecorationWidth();
+ }
+
+ private int getDecorationWidth() {
+ LayoutManager layoutManager = layout.getLayoutManager();
+ return layoutManager.getOuterWidth(getElement())
+ - contentPanel.getElement().getOffsetWidth();
+ }
+
+}
import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VAbsoluteLayout;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.LayoutClickRpc;
import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutServerRpc;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.absolutelayout;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.VCaption;
-
-public class VAbsoluteLayout extends ComplexPanel {
-
- /** Tag name for widget creation */
- public static final String TAGNAME = "absolutelayout";
-
- /** Class name, prefix in styling */
- public static final String CLASSNAME = "v-absolutelayout";
-
- private DivElement marginElement;
-
- protected final Element canvas = DOM.createDiv();
-
- /**
- * Default constructor
- */
- public VAbsoluteLayout() {
- setElement(Document.get().createDivElement());
- marginElement = Document.get().createDivElement();
- canvas.getStyle().setProperty("position", "relative");
- canvas.getStyle().setProperty("overflow", "hidden");
- marginElement.appendChild(canvas);
- getElement().appendChild(marginElement);
- setStyleName(CLASSNAME);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
- * .Widget)
- */
- @Override
- public void add(Widget child) {
- AbsoluteWrapper wrapper = new AbsoluteWrapper(child);
- wrapper.updateStyleNames();
- super.add(wrapper, canvas);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
- * .client.ui.Widget)
- */
- @Override
- public boolean remove(Widget w) {
- AbsoluteWrapper wrapper = getChildWrapper(w);
- if (wrapper != null) {
- wrapper.destroy();
- return super.remove(wrapper);
- }
- return super.remove(w);
- }
-
- /**
- * Does this layout contain a widget
- *
- * @param widget
- * The widget to check
- * @return Returns true if the widget is in this layout, false if not
- */
- public boolean contains(Widget widget) {
- return getChildWrapper(widget) != null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.ComplexPanel#getWidget(int)
- */
- @Override
- public Widget getWidget(int index) {
- for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
- Widget w = getWidget(i);
- if (w instanceof AbsoluteWrapper) {
- if (j == index) {
- return w;
- } else {
- j++;
- }
- }
- }
- return null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.ComplexPanel#getWidgetCount()
- */
- @Override
- public int getWidgetCount() {
- int counter = 0;
- for (int i = 0; i < super.getWidgetCount(); i++) {
- if (getWidget(i) instanceof AbsoluteWrapper) {
- counter++;
- }
- }
- return counter;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.ComplexPanel#getWidgetIndex(com.google.
- * gwt.user.client.ui.Widget)
- */
- @Override
- public int getWidgetIndex(Widget child) {
- for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
- Widget w = getWidget(i);
- if (w instanceof AbsoluteWrapper) {
- if (child == w) {
- return j;
- } else {
- j++;
- }
- }
- }
- return -1;
- }
-
- /**
- * Sets a caption for a contained widget
- *
- * @param child
- * The child widget to set the caption for
- * @param caption
- * The caption of the widget
- */
- public void setWidgetCaption(Widget child, VCaption caption) {
- AbsoluteWrapper wrapper = getChildWrapper(child);
- if (wrapper != null) {
- if (caption != null) {
- if (!getChildren().contains(caption)) {
- super.add(caption, canvas);
- }
- wrapper.setCaption(caption);
- caption.updateCaption();
- wrapper.updateCaptionPosition();
- } else if (wrapper.getCaption() != null) {
- wrapper.setCaption(null);
- }
- }
- }
-
- /**
- * Set the position of the widget in the layout. The position is a CSS
- * property string using properties such as top,left,right,top
- *
- * @param child
- * The child widget to set the position for
- * @param position
- * The position string
- */
- public void setWidgetPosition(Widget child, String position) {
- AbsoluteWrapper wrapper = getChildWrapper(child);
- if (wrapper != null) {
- wrapper.setPosition(position);
- }
- }
-
- /**
- * Get the caption for a widget
- *
- * @param child
- * The child widget to get the caption of
- */
- public VCaption getWidgetCaption(Widget child) {
- AbsoluteWrapper wrapper = getChildWrapper(child);
- if (wrapper != null) {
- return wrapper.getCaption();
- }
- return null;
- }
-
- /**
- * Get the pixel width of an slot in the layout
- *
- * @param child
- * The widget in the layout.
- * @return Returns the size in pixels, or 0 if child is not in the layout
- */
- public int getWidgetSlotWidth(Widget child) {
- AbsoluteWrapper wrapper = getChildWrapper(child);
- if (wrapper != null) {
- return wrapper.getOffsetWidth();
- }
- return 0;
- }
-
- /**
- * Get the pixel height of an slot in the layout
- *
- * @param child
- * The widget in the layout
- * @return Returns the size in pixels, or 0 if the child is not in the
- * layout
- */
- public int getWidgetSlotHeight(Widget child) {
- AbsoluteWrapper wrapper = getChildWrapper(child);
- if (wrapper != null) {
- return wrapper.getOffsetHeight();
- }
- return 0;
- }
-
- /**
- * Get the wrapper for a widget
- *
- * @param child
- * The child to get the wrapper for
- * @return
- */
- protected AbsoluteWrapper getChildWrapper(Widget child) {
- for (Widget w : getChildren()) {
- if (w instanceof AbsoluteWrapper) {
- AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
- if (wrapper.getWidget() == child) {
- return wrapper;
- }
- }
- }
- return null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang.
- * String)
- */
- @Override
- public void setStylePrimaryName(String style) {
- updateStylenames(style);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
- */
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStylenames(style);
- addStyleName(StyleConstants.UI_LAYOUT);
- }
-
- /**
- * Updates all style names contained in the layout
- *
- * @param primaryStyleName
- * The style name to use as primary
- */
- protected void updateStylenames(String primaryStyleName) {
- super.setStylePrimaryName(primaryStyleName);
- canvas.setClassName(getStylePrimaryName() + "-canvas");
- canvas.setClassName(getStylePrimaryName() + "-margin");
- for (Widget w : getChildren()) {
- if (w instanceof AbsoluteWrapper) {
- AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
- wrapper.updateStyleNames();
- }
- }
- }
-
- /**
- * Performs a vertical layout of the layout. Should be called when a widget
- * is added or removed
- */
- public void layoutVertically() {
- for (Widget widget : getChildren()) {
- if (widget instanceof AbsoluteWrapper) {
- AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
- /*
- * Cleanup old wrappers which have been left empty by other
- * inner layouts moving the widget from the wrapper into their
- * own hierarchy. This usually happens when a call to
- * setWidget(widget) is done in an inner layout which
- * automatically detaches the widget from the parent, in this
- * case the wrapper, and re-attaches it somewhere else. This has
- * to be done in the layout phase since the order of the
- * hierarchy events are not defined.
- */
- if (wrapper.getWidget() == null) {
- wrapper.destroy();
- super.remove(wrapper);
- continue;
- }
-
- Style wrapperStyle = wrapper.getElement().getStyle();
- Style widgetStyle = wrapper.getWidget().getElement().getStyle();
- if (widgetStyle.getHeight() != null
- && widgetStyle.getHeight().endsWith("%")) {
- int h;
- if (wrapper.top != null && wrapper.bottom != null) {
- h = wrapper.getOffsetHeight();
- } else if (wrapper.bottom != null) {
- // top not defined, available space 0... bottom of
- // wrapper
- h = wrapper.getElement().getOffsetTop()
- + wrapper.getOffsetHeight();
- } else {
- // top defined or both undefined, available space ==
- // canvas - top
- h = canvas.getOffsetHeight()
- - wrapper.getElement().getOffsetTop();
- }
- wrapperStyle.setHeight(h, Unit.PX);
- } else {
- wrapperStyle.clearHeight();
- }
-
- wrapper.updateCaptionPosition();
- }
- }
- }
-
- /**
- * Performs an horizontal layout. Should be called when a widget is add or
- * removed
- */
- public void layoutHorizontally() {
- for (Widget widget : getChildren()) {
- if (widget instanceof AbsoluteWrapper) {
- AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
- /*
- * Cleanup old wrappers which have been left empty by other
- * inner layouts moving the widget from the wrapper into their
- * own hierarchy. This usually happens when a call to
- * setWidget(widget) is done in an inner layout which
- * automatically detaches the widget from the parent, in this
- * case the wrapper, and re-attaches it somewhere else. This has
- * to be done in the layout phase since the order of the
- * hierarchy events are not defined.
- */
- if (wrapper.getWidget() == null) {
- wrapper.destroy();
- super.remove(wrapper);
- continue;
- }
-
- Style wrapperStyle = wrapper.getElement().getStyle();
- Style widgetStyle = wrapper.getWidget().getElement().getStyle();
-
- if (widgetStyle.getWidth() != null
- && widgetStyle.getWidth().endsWith("%")) {
- int w;
- if (wrapper.left != null && wrapper.right != null) {
- w = wrapper.getOffsetWidth();
- } else if (wrapper.right != null) {
- // left == null
- // available width == right edge == offsetleft + width
- w = wrapper.getOffsetWidth()
- + wrapper.getElement().getOffsetLeft();
- } else {
- // left != null && right == null || left == null &&
- // right == null
- // available width == canvas width - offset left
- w = canvas.getOffsetWidth()
- - wrapper.getElement().getOffsetLeft();
- }
- wrapperStyle.setWidth(w, Unit.PX);
- } else {
- wrapperStyle.clearWidth();
- }
-
- wrapper.updateCaptionPosition();
- }
- }
- }
-
- /**
- * Sets style names for the wrapper wrapping the widget in the layout. The
- * style names will be prefixed with v-absolutelayout-wrapper.
- *
- * @param widget
- * The widget which wrapper we want to add the stylenames to
- * @param stylenames
- * The style names that should be added to the wrapper
- */
- public void setWidgetWrapperStyleNames(Widget widget, String... stylenames) {
- AbsoluteWrapper wrapper = getChildWrapper(widget);
- if (wrapper == null) {
- throw new IllegalArgumentException(
- "No wrapper for widget found, has the widget been added to the layout?");
- }
- wrapper.setWrapperStyleNames(stylenames);
- }
-
- /**
- * Internal wrapper for wrapping widgets in the Absolute layout
- */
- protected class AbsoluteWrapper extends SimplePanel {
- private String css;
- private String left;
- private String top;
- private String right;
- private String bottom;
- private String zIndex;
-
- private VCaption caption;
- private String[] extraStyleNames;
-
- /**
- * Constructor
- *
- * @param child
- * The child to wrap
- */
- public AbsoluteWrapper(Widget child) {
- setWidget(child);
- }
-
- /**
- * Get the caption of the wrapper
- */
- public VCaption getCaption() {
- return caption;
- }
-
- /**
- * Set the caption for the wrapper
- *
- * @param caption
- * The caption for the wrapper
- */
- public void setCaption(VCaption caption) {
- if (caption != null) {
- this.caption = caption;
- } else if (this.caption != null) {
- this.caption.removeFromParent();
- this.caption = caption;
- }
- }
-
- /**
- * Removes the wrapper caption and itself from the layout
- */
- public void destroy() {
- if (caption != null) {
- caption.removeFromParent();
- }
- removeFromParent();
- }
-
- /**
- * Set the position for the wrapper in the layout
- *
- * @param position
- * The position string
- */
- public void setPosition(String position) {
- if (css == null || !css.equals(position)) {
- css = position;
- top = right = bottom = left = zIndex = null;
- if (!css.equals("")) {
- String[] properties = css.split(";");
- for (int i = 0; i < properties.length; i++) {
- String[] keyValue = properties[i].split(":");
- if (keyValue[0].equals("left")) {
- left = keyValue[1];
- } else if (keyValue[0].equals("top")) {
- top = keyValue[1];
- } else if (keyValue[0].equals("right")) {
- right = keyValue[1];
- } else if (keyValue[0].equals("bottom")) {
- bottom = keyValue[1];
- } else if (keyValue[0].equals("z-index")) {
- zIndex = keyValue[1];
- }
- }
- }
- // ensure ne values
- Style style = getElement().getStyle();
- /*
- * IE8 dies when nulling zIndex, even in IE7 mode. All other css
- * properties (and even in older IE's) accept null values just
- * fine. Assign empty string instead of null.
- */
- if (zIndex != null) {
- style.setProperty("zIndex", zIndex);
- } else {
- style.setProperty("zIndex", "");
- }
- style.setProperty("top", top);
- style.setProperty("left", left);
- style.setProperty("right", right);
- style.setProperty("bottom", bottom);
-
- }
- updateCaptionPosition();
- }
-
- /**
- * Updates the caption position by using element offset left and top
- */
- private void updateCaptionPosition() {
- if (caption != null) {
- Style style = caption.getElement().getStyle();
- style.setProperty("position", "absolute");
- style.setPropertyPx("left", getElement().getOffsetLeft());
- style.setPropertyPx("top", getElement().getOffsetTop()
- - caption.getHeight());
- }
- }
-
- /**
- * Sets the style names of the wrapper. Will be prefixed with the
- * v-absolutelayout-wrapper prefix
- *
- * @param stylenames
- * The wrapper style names
- */
- public void setWrapperStyleNames(String... stylenames) {
- extraStyleNames = stylenames;
- updateStyleNames();
- }
-
- /**
- * Updates the style names using the primary style name as prefix
- */
- protected void updateStyleNames() {
- setStyleName(VAbsoluteLayout.this.getStylePrimaryName()
- + "-wrapper");
- if (extraStyleNames != null) {
- for (String stylename : extraStyleNames) {
- addStyleDependentName(stylename);
- }
- }
- }
- }
-}
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.accordion.VAccordion.StackItem;
+import com.vaadin.client.ui.VAccordion;
+import com.vaadin.client.ui.VAccordion.StackItem;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.client.ui.tabsheet.TabsheetBaseConnector;
import com.vaadin.shared.ui.Connect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.accordion;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.tabsheet.VTabsheetBase;
-import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
-
-public class VAccordion extends VTabsheetBase {
-
- public static final String CLASSNAME = "v-accordion";
-
- private Set<Widget> widgets = new HashSet<Widget>();
-
- HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
-
- StackItem openTab = null;
-
- int selectedUIDLItemIndex = -1;
-
- private final TouchScrollHandler touchScrollHandler;
-
- public VAccordion() {
- super(CLASSNAME);
- touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
- }
-
- @Override
- protected void renderTab(UIDL tabUidl, int index, boolean selected,
- boolean hidden) {
- StackItem item;
- int itemIndex;
- if (getWidgetCount() <= index) {
- // Create stackItem and render caption
- item = new StackItem(tabUidl);
- if (getWidgetCount() == 0) {
- item.addStyleDependentName("first");
- }
- itemIndex = getWidgetCount();
- add(item, getElement());
- } else {
- item = getStackItem(index);
- item = moveStackItemIfNeeded(item, index, tabUidl);
- itemIndex = index;
- }
- item.updateCaption(tabUidl);
-
- item.setVisible(!hidden);
-
- if (selected) {
- selectedUIDLItemIndex = itemIndex;
- }
-
- if (tabUidl.getChildCount() > 0) {
- lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
- }
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- updateStyleNames(style);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames(style);
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- for (Widget w : getChildren()) {
- if (w instanceof StackItem) {
- StackItem item = (StackItem) w;
- item.updateStyleNames(primaryStyleName);
- }
- }
- }
-
- /**
- * This method tries to find out if a tab has been rendered with a different
- * index previously. If this is the case it re-orders the children so the
- * same StackItem is used for rendering this time. E.g. if the first tab has
- * been removed all tabs which contain cached content must be moved 1 step
- * up to preserve the cached content.
- *
- * @param item
- * @param newIndex
- * @param tabUidl
- * @return
- */
- private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
- UIDL tabUidl) {
- UIDL tabContentUIDL = null;
- ComponentConnector tabContent = null;
- if (tabUidl.getChildCount() > 0) {
- tabContentUIDL = tabUidl.getChildUIDL(0);
- tabContent = client.getPaintable(tabContentUIDL);
- }
-
- Widget itemWidget = item.getComponent();
- if (tabContent != null) {
- if (tabContent.getWidget() != itemWidget) {
- /*
- * This is not the same widget as before, find out if it has
- * been moved
- */
- int oldIndex = -1;
- StackItem oldItem = null;
- for (int i = 0; i < getWidgetCount(); i++) {
- Widget w = getWidget(i);
- oldItem = (StackItem) w;
- if (tabContent == oldItem.getComponent()) {
- oldIndex = i;
- break;
- }
- }
-
- if (oldIndex != -1 && oldIndex > newIndex) {
- /*
- * The tab has previously been rendered in another position
- * so we must move the cached content to correct position.
- * We move only items with oldIndex > newIndex to prevent
- * moving items already rendered in this update. If for
- * instance tabs 1,2,3 are removed and added as 3,2,1 we
- * cannot re-use "1" when we get to the third tab.
- */
- insert(oldItem, getElement(), newIndex, true);
- return oldItem;
- }
- }
- } else {
- // Tab which has never been loaded. Must assure we use an empty
- // StackItem
- Widget oldWidget = item.getComponent();
- if (oldWidget != null) {
- oldWidget.removeFromParent();
- }
- }
- return item;
- }
-
- void open(int itemIndex) {
- StackItem item = (StackItem) getWidget(itemIndex);
- boolean alreadyOpen = false;
- if (openTab != null) {
- if (openTab.isOpen()) {
- if (openTab == item) {
- alreadyOpen = true;
- } else {
- openTab.close();
- }
- }
- }
- if (!alreadyOpen) {
- item.open();
- activeTabIndex = itemIndex;
- openTab = item;
- }
-
- // Update the size for the open tab
- updateOpenTabSize();
- }
-
- void close(StackItem item) {
- if (!item.isOpen()) {
- return;
- }
-
- item.close();
- activeTabIndex = -1;
- openTab = null;
-
- }
-
- @Override
- protected void selectTab(final int index, final UIDL contentUidl) {
- StackItem item = getStackItem(index);
- if (index != activeTabIndex) {
- open(index);
- iLayout();
- // TODO Check if this is needed
- client.runDescendentsLayout(this);
-
- }
- item.setContent(contentUidl);
- }
-
- public void onSelectTab(StackItem item) {
- final int index = getWidgetIndex(item);
- if (index != activeTabIndex && !disabled && !readonly
- && !disabledTabKeys.contains(tabKeys.get(index))) {
- addStyleDependentName("loading");
- client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
- }
- }
-
- /**
- * Sets the size of the open tab
- */
- void updateOpenTabSize() {
- if (openTab == null) {
- return;
- }
-
- // WIDTH
- if (!isDynamicWidth()) {
- openTab.setWidth("100%");
- } else {
- openTab.setWidth(null);
- }
-
- // HEIGHT
- if (!isDynamicHeight()) {
- int usedPixels = 0;
- for (Widget w : getChildren()) {
- StackItem item = (StackItem) w;
- if (item == openTab) {
- usedPixels += item.getCaptionHeight();
- } else {
- // This includes the captionNode borders
- usedPixels += item.getHeight();
- }
- }
-
- int offsetHeight = getOffsetHeight();
-
- int spaceForOpenItem = offsetHeight - usedPixels;
-
- if (spaceForOpenItem < 0) {
- spaceForOpenItem = 0;
- }
-
- openTab.setHeight(spaceForOpenItem);
- } else {
- openTab.setHeightFromWidget();
-
- }
-
- }
-
- public void iLayout() {
- if (openTab == null) {
- return;
- }
-
- if (isDynamicWidth()) {
- int maxWidth = 40;
- for (Widget w : getChildren()) {
- StackItem si = (StackItem) w;
- int captionWidth = si.getCaptionWidth();
- if (captionWidth > maxWidth) {
- maxWidth = captionWidth;
- }
- }
- int widgetWidth = openTab.getWidgetWidth();
- if (widgetWidth > maxWidth) {
- maxWidth = widgetWidth;
- }
- super.setWidth(maxWidth + "px");
- openTab.setWidth(maxWidth);
- }
- }
-
- /**
- * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
- * the actual child widget.
- */
- protected class StackItem extends ComplexPanel implements ClickHandler {
-
- public void setHeight(int height) {
- if (height == -1) {
- super.setHeight("");
- DOM.setStyleAttribute(content, "height", "0px");
- } else {
- super.setHeight((height + getCaptionHeight()) + "px");
- DOM.setStyleAttribute(content, "height", height + "px");
- DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
-
- }
- }
-
- public Widget getComponent() {
- if (getWidgetCount() < 2) {
- return null;
- }
- return getWidget(1);
- }
-
- @Override
- public void setVisible(boolean visible) {
- super.setVisible(visible);
- }
-
- public void setHeightFromWidget() {
- Widget widget = getChildWidget();
- if (widget == null) {
- return;
- }
-
- int paintableHeight = widget.getElement().getOffsetHeight();
- setHeight(paintableHeight);
-
- }
-
- /**
- * Returns caption width including padding
- *
- * @return
- */
- public int getCaptionWidth() {
- if (caption == null) {
- return 0;
- }
-
- int captionWidth = caption.getRequiredWidth();
- int padding = Util.measureHorizontalPaddingAndBorder(
- caption.getElement(), 18);
- return captionWidth + padding;
- }
-
- public void setWidth(int width) {
- if (width == -1) {
- super.setWidth("");
- } else {
- super.setWidth(width + "px");
- }
- }
-
- public int getHeight() {
- return getOffsetHeight();
- }
-
- public int getCaptionHeight() {
- return captionNode.getOffsetHeight();
- }
-
- private VCaption caption;
- private boolean open = false;
- private Element content = DOM.createDiv();
- private Element captionNode = DOM.createDiv();
-
- public StackItem(UIDL tabUidl) {
- setElement(DOM.createDiv());
- caption = new VCaption(client);
- caption.addClickHandler(this);
- super.add(caption, captionNode);
- DOM.appendChild(captionNode, caption.getElement());
- DOM.appendChild(getElement(), captionNode);
- DOM.appendChild(getElement(), content);
-
- updateStyleNames(VAccordion.this.getStylePrimaryName());
-
- touchScrollHandler.addElement(getContainerElement());
-
- close();
- }
-
- private void updateStyleNames(String primaryStyleName) {
- content.removeClassName(getStylePrimaryName() + "-content");
- captionNode.removeClassName(getStylePrimaryName() + "-caption");
-
- setStylePrimaryName(primaryStyleName + "-item");
-
- captionNode.addClassName(getStylePrimaryName() + "-caption");
- content.addClassName(getStylePrimaryName() + "-content");
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- onSelectTab(this);
- }
-
- public Element getContainerElement() {
- return content;
- }
-
- public Widget getChildWidget() {
- if (getWidgetCount() > 1) {
- return getWidget(1);
- } else {
- return null;
- }
- }
-
- public void replaceWidget(Widget newWidget) {
- if (getWidgetCount() > 1) {
- Widget oldWidget = getWidget(1);
- ComponentConnector oldPaintable = ConnectorMap.get(client)
- .getConnector(oldWidget);
- ConnectorMap.get(client).unregisterConnector(oldPaintable);
- widgets.remove(oldWidget);
- remove(1);
- }
- add(newWidget, content);
- widgets.add(newWidget);
- }
-
- public void open() {
- open = true;
- DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
- DOM.setStyleAttribute(content, "left", "0px");
- DOM.setStyleAttribute(content, "visibility", "");
- addStyleDependentName("open");
- }
-
- public void hide() {
- DOM.setStyleAttribute(content, "visibility", "hidden");
- }
-
- public void close() {
- DOM.setStyleAttribute(content, "visibility", "hidden");
- DOM.setStyleAttribute(content, "top", "-100000px");
- DOM.setStyleAttribute(content, "left", "-100000px");
- removeStyleDependentName("open");
- setHeight(-1);
- setWidth("");
- open = false;
- }
-
- public boolean isOpen() {
- return open;
- }
-
- public void setContent(UIDL contentUidl) {
- final ComponentConnector newPntbl = client
- .getPaintable(contentUidl);
- Widget newWidget = newPntbl.getWidget();
- if (getChildWidget() == null) {
- add(newWidget, content);
- widgets.add(newWidget);
- } else if (getChildWidget() != newWidget) {
- replaceWidget(newWidget);
- }
- if (contentUidl.getBooleanAttribute("cached")) {
- /*
- * The size of a cached, relative sized component must be
- * updated to report correct size.
- */
- client.handleComponentRelativeSize(newPntbl.getWidget());
- }
- if (isOpen() && isDynamicHeight()) {
- setHeightFromWidget();
- }
- }
-
- @Override
- public void onClick(ClickEvent event) {
- onSelectTab(this);
- }
-
- public void updateCaption(UIDL uidl) {
- // TODO need to call this because the caption does not have an owner
- caption.updateCaptionWithoutOwner(
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
- }
-
- public int getWidgetWidth() {
- return DOM.getFirstChild(content).getOffsetWidth();
- }
-
- public boolean contains(ComponentConnector p) {
- return (getChildWidget() == p.getWidget());
- }
-
- public boolean isCaptionVisible() {
- return caption.isVisible();
- }
-
- }
-
- @Override
- protected void clearPaintables() {
- clear();
- }
-
- boolean isDynamicWidth() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- return paintable.isUndefinedWidth();
- }
-
- boolean isDynamicHeight() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- return paintable.isUndefinedHeight();
- }
-
- @Override
- @SuppressWarnings("unchecked")
- protected Iterator<Widget> getWidgetIterator() {
- return widgets.iterator();
- }
-
- @Override
- protected int getTabCount() {
- return getWidgetCount();
- }
-
- @Override
- protected void removeTab(int index) {
- StackItem item = getStackItem(index);
- remove(item);
- touchScrollHandler.removeElement(item.getContainerElement());
- }
-
- @Override
- protected ComponentConnector getTab(int index) {
- if (index < getWidgetCount()) {
- StackItem stackItem = getStackItem(index);
- if (stackItem == null) {
- return null;
- }
- Widget w = stackItem.getChildWidget();
- if (w != null) {
- return ConnectorMap.get(client).getConnector(w);
- }
- }
-
- return null;
- }
-
- StackItem getStackItem(int index) {
- return (StackItem) getWidget(index);
- }
-
-}
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.MediaBaseConnector;
+import com.vaadin.client.ui.VAudio;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.Audio;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.audio;
-
-import com.google.gwt.dom.client.AudioElement;
-import com.google.gwt.dom.client.Document;
-import com.vaadin.client.ui.VMediaBase;
-
-public class VAudio extends VMediaBase {
- private static String CLASSNAME = "v-audio";
-
- private AudioElement audio;
-
- public VAudio() {
- audio = Document.get().createAudioElement();
- setMediaElement(audio);
- setStyleName(CLASSNAME);
- }
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VBrowserFrame;
import com.vaadin.shared.ui.AbstractEmbeddedState;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.browserframe.BrowserFrameState;
+++ /dev/null
-package com.vaadin.client.ui.browserframe;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.IFrameElement;
-import com.google.gwt.user.client.ui.Widget;
-
-public class VBrowserFrame extends Widget {
-
- protected IFrameElement iframe;
- protected Element altElement;
- protected String altText;
-
- public static final String CLASSNAME = "v-browserframe";
-
- public VBrowserFrame() {
- Element root = Document.get().createDivElement();
- setElement(root);
-
- setStyleName(CLASSNAME);
-
- createAltTextElement();
- }
-
- /**
- * Always creates new iframe inside widget. Will replace previous iframe.
- *
- * @return
- */
- protected IFrameElement createIFrameElement(String src) {
- String name = null;
-
- // Remove alt text
- if (altElement != null) {
- getElement().removeChild(altElement);
- altElement = null;
- }
-
- // Remove old iframe
- if (iframe != null) {
- name = iframe.getAttribute("name");
- getElement().removeChild(iframe);
- iframe = null;
- }
-
- iframe = Document.get().createIFrameElement();
- iframe.setSrc(src);
- iframe.setFrameBorder(0);
- iframe.setAttribute("width", "100%");
- iframe.setAttribute("height", "100%");
- iframe.setAttribute("allowTransparency", "true");
-
- getElement().appendChild(iframe);
-
- // Reset old attributes (except src)
- if (name != null) {
- iframe.setName(name);
- }
-
- return iframe;
- }
-
- protected void createAltTextElement() {
- if (iframe != null) {
- return;
- }
-
- if (altElement == null) {
- altElement = Document.get().createSpanElement();
- getElement().appendChild(altElement);
- }
-
- if (altText != null) {
- altElement.setInnerText(altText);
- } else {
- altElement.setInnerText("");
- }
- }
-
- public void setAlternateText(String altText) {
- if (this.altText != altText) {
- this.altText = altText;
- if (altElement != null) {
- if (altText != null) {
- altElement.setInnerText(altText);
- } else {
- altElement.setInnerText("");
- }
- }
- }
- }
-
- /**
- * Set the source (the "src" attribute) of iframe. Will replace old iframe
- * with new.
- *
- * @param source
- * Source of iframe.
- */
- public void setSource(String source) {
-
- if (source == null) {
- if (iframe != null) {
- getElement().removeChild(iframe);
- iframe = null;
- }
- createAltTextElement();
- setAlternateText(altText);
- return;
- }
-
- if (iframe == null || iframe.getSrc() != source) {
- createIFrameElement(source);
- }
- }
-
- public void setName(String name) {
- if (iframe != null) {
- iframe.setName(name);
- }
- }
-}
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.ShortcutAction;
import com.vaadin.client.ui.ShortcutActionTarget;
+import com.vaadin.client.ui.VButton;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.button;
-
-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.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Accessibility;
-import com.google.gwt.user.client.ui.FocusWidget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-
-public class VButton extends FocusWidget implements ClickHandler {
-
- public static final String CLASSNAME = "v-button";
- private static final String CLASSNAME_PRESSED = "v-pressed";
-
- // mouse movement is checked before synthesizing click event on mouseout
- protected static int MOVE_THRESHOLD = 3;
- protected int mousedownX = 0;
- protected int mousedownY = 0;
-
- protected ApplicationConnection client;
-
- protected final Element wrapper = DOM.createSpan();
-
- protected Element errorIndicatorElement;
-
- protected final Element captionElement = DOM.createSpan();
-
- protected Icon icon;
-
- /**
- * Helper flag to handle special-case where the button is moved from under
- * mouse while clicking it. In this case mouse leaves the button without
- * moving.
- */
- protected boolean clickPending;
-
- private boolean enabled = true;
-
- private int tabIndex = 0;
-
- /*
- * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton
- */
-
- /**
- * If <code>true</code>, this widget is capturing with the mouse held down.
- */
- private boolean isCapturing;
-
- /**
- * If <code>true</code>, this widget has focus with the space bar down. This
- * means that we will get events when the button is released, but we should
- * trigger the button only if the button is still focused at that point.
- */
- private boolean isFocusing;
-
- /**
- * Used to decide whether to allow clicks to propagate up to the superclass
- * or container elements.
- */
- private boolean disallowNextClick = false;
- private boolean isHovering;
-
- protected int clickShortcut = 0;
-
- private HandlerRegistration focusHandlerRegistration;
- private HandlerRegistration blurHandlerRegistration;
-
- public VButton() {
- super(DOM.createDiv());
- setTabIndex(0);
- sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS
- | Event.KEYEVENTS);
-
- // Add a11y role "button"
- Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
-
- getElement().appendChild(wrapper);
- wrapper.appendChild(captionElement);
-
- setStyleName(CLASSNAME);
-
- addClickHandler(this);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- wrapper.setClassName(getStylePrimaryName() + "-wrap");
- captionElement.setClassName(getStylePrimaryName() + "-caption");
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- wrapper.setClassName(getStylePrimaryName() + "-wrap");
- captionElement.setClassName(getStylePrimaryName() + "-caption");
- }
-
- public void setText(String text) {
- captionElement.setInnerText(text);
- }
-
- public void setHtml(String html) {
- captionElement.setInnerHTML(html);
- }
-
- @SuppressWarnings("deprecation")
- @Override
- /*
- * Copy-pasted from GWT CustomButton, some minor modifications done:
- *
- * -for IE/Opera added CLASSNAME_PRESSED
- *
- * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply
- * :active styles if it is present)
- *
- * -Tooltip event handling added
- *
- * -onload event handler added (for icon handling)
- */
- public void onBrowserEvent(Event event) {
- if (DOM.eventGetType(event) == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
- }
- // Should not act on button if disabled.
- if (!isEnabled()) {
- // This can happen when events are bubbled up from non-disabled
- // children
- return;
- }
-
- int type = DOM.eventGetType(event);
- switch (type) {
- case Event.ONCLICK:
- // If clicks are currently disallowed, keep it from bubbling or
- // being passed to the superclass.
- if (disallowNextClick) {
- event.stopPropagation();
- disallowNextClick = false;
- return;
- }
- break;
- case Event.ONMOUSEDOWN:
- if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
- // This was moved from mouseover, which iOS sometimes skips.
- // We're certainly hovering at this point, and we don't actually
- // need that information before this point.
- setHovering(true);
- }
- if (event.getButton() == Event.BUTTON_LEFT) {
- // save mouse position to detect movement before synthesizing
- // event later
- mousedownX = event.getClientX();
- mousedownY = event.getClientY();
-
- disallowNextClick = true;
- clickPending = true;
- setFocus(true);
- DOM.setCapture(getElement());
- isCapturing = true;
- // Prevent dragging (on some browsers);
- // DOM.eventPreventDefault(event);
- if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
- addStyleName(CLASSNAME_PRESSED);
- }
- }
- break;
- case Event.ONMOUSEUP:
- if (isCapturing) {
- isCapturing = false;
- DOM.releaseCapture(getElement());
- if (isHovering() && event.getButton() == Event.BUTTON_LEFT) {
- // Click ok
- disallowNextClick = false;
- }
- if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
- removeStyleName(CLASSNAME_PRESSED);
- }
- // Explicitly prevent IE 8 from propagating mouseup events
- // upward (fixes #6753)
- if (BrowserInfo.get().isIE8()) {
- event.stopPropagation();
- }
- }
- break;
- case Event.ONMOUSEMOVE:
- clickPending = false;
- if (isCapturing) {
- // Prevent dragging (on other browsers);
- DOM.eventPreventDefault(event);
- }
- break;
- case Event.ONMOUSEOUT:
- Element to = event.getRelatedTarget();
- if (getElement().isOrHasChild(DOM.eventGetTarget(event))
- && (to == null || !getElement().isOrHasChild(to))) {
- if (clickPending
- && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD
- && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) {
- onClick();
- break;
- }
- clickPending = false;
- if (isCapturing) {
- }
- setHovering(false);
- if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
- removeStyleName(CLASSNAME_PRESSED);
- }
- }
- break;
- case Event.ONBLUR:
- if (isFocusing) {
- isFocusing = false;
- }
- break;
- case Event.ONLOSECAPTURE:
- if (isCapturing) {
- isCapturing = false;
- }
- break;
- }
-
- super.onBrowserEvent(event);
-
- // Synthesize clicks based on keyboard events AFTER the normal key
- // handling.
- if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
- switch (type) {
- case Event.ONKEYDOWN:
- // Stop propagation when the user starts pressing a button that
- // we are handling to prevent actions from getting triggered
- if (event.getKeyCode() == 32 /* space */) {
- isFocusing = true;
- event.preventDefault();
- event.stopPropagation();
- } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
- event.stopPropagation();
- }
- break;
- case Event.ONKEYUP:
- if (isFocusing && event.getKeyCode() == 32 /* space */) {
- isFocusing = false;
- onClick();
- event.stopPropagation();
- event.preventDefault();
- }
- break;
- case Event.ONKEYPRESS:
- if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
- onClick();
- event.stopPropagation();
- event.preventDefault();
- }
- break;
- }
- }
- }
-
- final void setHovering(boolean hovering) {
- if (hovering != isHovering()) {
- isHovering = hovering;
- }
- }
-
- final boolean isHovering() {
- return isHovering;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
- * .dom.client.ClickEvent)
- */
- @Override
- public void onClick(ClickEvent event) {
- if (BrowserInfo.get().isSafari()) {
- VButton.this.setFocus(true);
- }
-
- clickPending = false;
- }
-
- /*
- * ALL BELOW COPY-PASTED FROM GWT CustomButton
- */
-
- /**
- * Called internally when the user finishes clicking on this button. The
- * default behavior is to fire the click event to listeners. Subclasses that
- * override {@link #onClickStart()} should override this method to restore
- * the normal widget display.
- * <p>
- * To add custom code for a click event, override
- * {@link #onClick(ClickEvent)} instead of this.
- */
- protected void onClick() {
- // Allow the click we're about to synthesize to pass through to the
- // superclass and containing elements. Element.dispatchEvent() is
- // synchronous, so we simply set and clear the flag within this method.
-
- disallowNextClick = false;
-
- // Mouse coordinates are not always available (e.g., when the click is
- // caused by a keyboard event).
- NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
- false, false, false);
- getElement().dispatchEvent(evt);
- }
-
- /**
- * Sets whether this button is enabled.
- *
- * @param enabled
- * <code>true</code> to enable the button, <code>false</code> to
- * disable it
- */
-
- @Override
- public final void setEnabled(boolean enabled) {
- if (isEnabled() != enabled) {
- this.enabled = enabled;
- if (!enabled) {
- cleanupCaptureState();
- Accessibility.removeState(getElement(),
- Accessibility.STATE_PRESSED);
- super.setTabIndex(-1);
- } else {
- Accessibility.setState(getElement(),
- Accessibility.STATE_PRESSED, "false");
- super.setTabIndex(tabIndex);
- }
- }
- }
-
- @Override
- public final boolean isEnabled() {
- return enabled;
- }
-
- @Override
- public final void setTabIndex(int index) {
- super.setTabIndex(index);
- tabIndex = index;
- }
-
- /**
- * Resets internal state if this button can no longer service events. This
- * can occur when the widget becomes detached or disabled.
- */
- private void cleanupCaptureState() {
- if (isCapturing || isFocusing) {
- DOM.releaseCapture(getElement());
- isCapturing = false;
- isFocusing = false;
- }
- }
-
- private static native int getHorizontalBorderAndPaddingWidth(Element elem)
- /*-{
- // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS
-
- var convertToPixel = function(elem, value) {
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // Remember the original values
- var left = elem.style.left, rsLeft = elem.runtimeStyle.left;
-
- // Put in the new values to get a computed value out
- elem.runtimeStyle.left = elem.currentStyle.left;
- elem.style.left = value || 0;
- var ret = elem.style.pixelLeft;
-
- // Revert the changed values
- elem.style.left = left;
- elem.runtimeStyle.left = rsLeft;
-
- return ret;
- }
-
- var ret = 0;
-
- var sides = ["Right","Left"];
- for(var i=0; i<2; i++) {
- var side = sides[i];
- var value;
- // Border -------------------------------------------------------
- if(elem.currentStyle["border"+side+"Style"] != "none") {
- value = elem.currentStyle["border"+side+"Width"];
- if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
- ret += convertToPixel(elem, value);
- } else if(value.length > 2) {
- ret += parseInt(value.substr(0, value.length-2));
- }
- }
-
- // Padding -------------------------------------------------------
- value = elem.currentStyle["padding"+side];
- if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
- ret += convertToPixel(elem, value);
- } else if(value.length > 2) {
- ret += parseInt(value.substr(0, value.length-2));
- }
- }
-
- return ret;
- }-*/;
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VCheckBox;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.checkbox;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Util;
-import com.vaadin.client.VTooltip;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.Icon;
-
-public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
- Field {
-
- public static final String CLASSNAME = "v-checkbox";
-
- String id;
-
- boolean immediate;
-
- ApplicationConnection client;
-
- Element errorIndicatorElement;
-
- Icon icon;
-
- public VCheckBox() {
- setStyleName(CLASSNAME);
-
- Element el = DOM.getFirstChild(getElement());
- while (el != null) {
- DOM.sinkEvents(el,
- (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS));
- el = DOM.getNextSibling(el);
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (icon != null && (event.getTypeInt() == Event.ONCLICK)
- && (DOM.eventGetTarget(event) == icon.getElement())) {
- // Click on icon should do nothing if widget is disabled
- if (isEnabled()) {
- setValue(!getValue());
- }
- }
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
- }
- }
-
-}
import com.vaadin.client.Util;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.combobox.VFilterSelect.FilterSelectSuggestion;
+import com.vaadin.client.ui.VFilterSelect;
+import com.vaadin.client.ui.VFilterSelect.FilterSelectSuggestion;
import com.vaadin.client.ui.menubar.MenuItem;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.combobox.ComboBoxConstants;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.combobox;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.event.dom.client.LoadEvent;
-import com.google.gwt.event.dom.client.LoadHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
-import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwt.user.client.ui.TextBox;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.menubar.MenuBar;
-import com.vaadin.client.ui.menubar.MenuItem;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.ComponentStateUtil;
-import com.vaadin.shared.ui.combobox.FilteringMode;
-
-/**
- * Client side implementation of the Select component.
- *
- * TODO needs major refactoring (to be extensible etc)
- */
-@SuppressWarnings("deprecation")
-public class VFilterSelect extends Composite implements Field, KeyDownHandler,
- KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
- SubPartAware {
-
- /**
- * Represents a suggestion in the suggestion popup box
- */
- public class FilterSelectSuggestion implements Suggestion, Command {
-
- private final String key;
- private final String caption;
- private String iconUri;
-
- /**
- * Constructor
- *
- * @param uidl
- * The UIDL recieved from the server
- */
- public FilterSelectSuggestion(UIDL uidl) {
- key = uidl.getStringAttribute("key");
- caption = uidl.getStringAttribute("caption");
- if (uidl.hasAttribute("icon")) {
- iconUri = client.translateVaadinUri(uidl
- .getStringAttribute("icon"));
- }
- }
-
- /**
- * Gets the visible row in the popup as a HTML string. The string
- * contains an image tag with the rows icon (if an icon has been
- * specified) and the caption of the item
- */
-
- @Override
- public String getDisplayString() {
- final StringBuffer sb = new StringBuffer();
- if (iconUri != null) {
- sb.append("<img src=\"");
- sb.append(Util.escapeAttribute(iconUri));
- sb.append("\" alt=\"\" class=\"v-icon\" />");
- }
- String content;
- if ("".equals(caption)) {
- // Ensure that empty options use the same height as other
- // options and are not collapsed (#7506)
- content = " ";
- } else {
- content = Util.escapeHTML(caption);
- }
- sb.append("<span>" + content + "</span>");
- return sb.toString();
- }
-
- /**
- * Get a string that represents this item. This is used in the text box.
- */
-
- @Override
- public String getReplacementString() {
- return caption;
- }
-
- /**
- * Get the option key which represents the item on the server side.
- *
- * @return The key of the item
- */
- public int getOptionKey() {
- return Integer.parseInt(key);
- }
-
- /**
- * Get the URI of the icon. Used when constructing the displayed option.
- *
- * @return
- */
- public String getIconUri() {
- return iconUri;
- }
-
- /**
- * Executes a selection of this item.
- */
-
- @Override
- public void execute() {
- onSuggestionSelected(this);
- }
- }
-
- /**
- * Represents the popup box with the selection options. Wraps a suggestion
- * menu.
- */
- public class SuggestionPopup extends VOverlay implements PositionCallback,
- CloseHandler<PopupPanel> {
-
- private static final int Z_INDEX = 30000;
-
- protected final SuggestionMenu menu;
-
- private final Element up = DOM.createDiv();
- private final Element down = DOM.createDiv();
- private final Element status = DOM.createDiv();
-
- private boolean isPagingEnabled = true;
-
- private long lastAutoClosed;
-
- private int popupOuterPadding = -1;
-
- private int topPosition;
-
- /**
- * Default constructor
- */
- SuggestionPopup() {
- super(true, false, true);
- setOwner(VFilterSelect.this);
- menu = new SuggestionMenu();
- setWidget(menu);
-
- getElement().getStyle().setZIndex(Z_INDEX);
-
- final Element root = getContainerElement();
-
- up.setInnerHTML("<span>Prev</span>");
- DOM.sinkEvents(up, Event.ONCLICK);
-
- down.setInnerHTML("<span>Next</span>");
- DOM.sinkEvents(down, Event.ONCLICK);
-
- root.insertFirst(up);
- root.appendChild(down);
- root.appendChild(status);
-
- DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL);
- addCloseHandler(this);
- }
-
- /**
- * Shows the popup where the user can see the filtered options
- *
- * @param currentSuggestions
- * The filtered suggestions
- * @param currentPage
- * The current page number
- * @param totalSuggestions
- * The total amount of suggestions
- */
- public void showSuggestions(
- final Collection<FilterSelectSuggestion> currentSuggestions,
- final int currentPage, final int totalSuggestions) {
-
- /*
- * We need to defer the opening of the popup so that the parent DOM
- * has stabilized so we can calculate an absolute top and left
- * correctly. This issue manifests when a Combobox is placed in
- * another popupView which also needs to calculate the absoluteTop()
- * to position itself. #9768
- */
- final SuggestionPopup popup = this;
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
- @Override
- public void execute() {
- // Add TT anchor point
- getElement().setId("VAADIN_COMBOBOX_OPTIONLIST");
-
- menu.setSuggestions(currentSuggestions);
- final int x = VFilterSelect.this.getAbsoluteLeft();
-
- topPosition = tb.getAbsoluteTop();
- topPosition += tb.getOffsetHeight();
-
- setPopupPosition(x, topPosition);
-
- int nullOffset = (nullSelectionAllowed
- && "".equals(lastFilter) ? 1 : 0);
- boolean firstPage = (currentPage == 0);
- final int first = currentPage * pageLength + 1
- - (firstPage ? 0 : nullOffset);
- final int last = first
- + currentSuggestions.size()
- - 1
- - (firstPage && "".equals(lastFilter) ? nullOffset
- : 0);
- final int matches = totalSuggestions - nullOffset;
- if (last > 0) {
- // nullsel not counted, as requested by user
- status.setInnerText((matches == 0 ? 0 : first) + "-"
- + last + "/" + matches);
- } else {
- status.setInnerText("");
- }
- // We don't need to show arrows or statusbar if there is
- // only one
- // page
- if (totalSuggestions <= pageLength || pageLength == 0) {
- setPagingEnabled(false);
- } else {
- setPagingEnabled(true);
- }
- setPrevButtonActive(first > 1);
- setNextButtonActive(last < matches);
-
- // clear previously fixed width
- menu.setWidth("");
- menu.getElement().getFirstChildElement().getStyle()
- .clearWidth();
-
- setPopupPositionAndShow(popup);
- }
- });
- }
-
- /**
- * Should the next page button be visible to the user?
- *
- * @param active
- */
- private void setNextButtonActive(boolean active) {
- if (active) {
- DOM.sinkEvents(down, Event.ONCLICK);
- down.setClassName(VFilterSelect.this.getStylePrimaryName()
- + "-nextpage");
- } else {
- DOM.sinkEvents(down, 0);
- down.setClassName(VFilterSelect.this.getStylePrimaryName()
- + "-nextpage-off");
- }
- }
-
- /**
- * Should the previous page button be visible to the user
- *
- * @param active
- */
- private void setPrevButtonActive(boolean active) {
- if (active) {
- DOM.sinkEvents(up, Event.ONCLICK);
- up.setClassName(VFilterSelect.this.getStylePrimaryName()
- + "-prevpage");
- } else {
- DOM.sinkEvents(up, 0);
- up.setClassName(VFilterSelect.this.getStylePrimaryName()
- + "-prevpage-off");
- }
-
- }
-
- /**
- * Selects the next item in the filtered selections
- */
- public void selectNextItem() {
- final MenuItem cur = menu.getSelectedItem();
- final int index = 1 + menu.getItems().indexOf(cur);
- if (menu.getItems().size() > index) {
- final MenuItem newSelectedItem = menu.getItems().get(index);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
-
- } else if (hasNextPage()) {
- selectPopupItemWhenResponseIsReceived = Select.FIRST;
- filterOptions(currentPage + 1, lastFilter);
- }
- }
-
- /**
- * Selects the previous item in the filtered selections
- */
- public void selectPrevItem() {
- final MenuItem cur = menu.getSelectedItem();
- final int index = -1 + menu.getItems().indexOf(cur);
- if (index > -1) {
- final MenuItem newSelectedItem = menu.getItems().get(index);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
- } else if (index == -1) {
- if (currentPage > 0) {
- selectPopupItemWhenResponseIsReceived = Select.LAST;
- filterOptions(currentPage - 1, lastFilter);
- }
- } else {
- final MenuItem newSelectedItem = menu.getItems().get(
- menu.getItems().size() - 1);
- menu.selectItem(newSelectedItem);
- tb.setText(newSelectedItem.getText());
- tb.setSelectionRange(lastFilter.length(), newSelectedItem
- .getText().length() - lastFilter.length());
- }
- }
-
- /*
- * Using a timer to scroll up or down the pages so when we receive lots
- * of consecutive mouse wheel events the pages does not flicker.
- */
- private LazyPageScroller lazyPageScroller = new LazyPageScroller();
-
- private class LazyPageScroller extends Timer {
- private int pagesToScroll = 0;
-
- @Override
- public void run() {
- if (pagesToScroll != 0) {
- if (!waitingForFilteringResponse) {
- /*
- * Avoid scrolling while we are waiting for a response
- * because otherwise the waiting flag will be reset in
- * the first response and the second response will be
- * ignored, causing an empty popup...
- *
- * As long as the scrolling delay is suitable
- * double/triple clicks will work by scrolling two or
- * three pages at a time and this should not be a
- * problem.
- */
- filterOptions(currentPage + pagesToScroll, lastFilter);
- }
- pagesToScroll = 0;
- }
- }
-
- public void scrollUp() {
- if (currentPage + pagesToScroll > 0) {
- pagesToScroll--;
- cancel();
- schedule(200);
- }
- }
-
- public void scrollDown() {
- if (totalMatches > (currentPage + pagesToScroll + 1)
- * pageLength) {
- pagesToScroll++;
- cancel();
- schedule(200);
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
- * .user.client.Event)
- */
-
- @Override
- public void onBrowserEvent(Event event) {
- if (event.getTypeInt() == Event.ONCLICK) {
- final Element target = DOM.eventGetTarget(event);
- if (target == up || target == DOM.getChild(up, 0)) {
- lazyPageScroller.scrollUp();
- } else if (target == down || target == DOM.getChild(down, 0)) {
- lazyPageScroller.scrollDown();
- }
- } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) {
- int velocity = event.getMouseWheelVelocityY();
- if (velocity > 0) {
- lazyPageScroller.scrollDown();
- } else {
- lazyPageScroller.scrollUp();
- }
- }
-
- /*
- * Prevent the keyboard focus from leaving the textfield by
- * preventing the default behaviour of the browser. Fixes #4285.
- */
- handleMouseDownEvent(event);
- }
-
- /**
- * Should paging be enabled. If paging is enabled then only a certain
- * amount of items are visible at a time and a scrollbar or buttons are
- * visible to change page. If paging is turned of then all options are
- * rendered into the popup menu.
- *
- * @param paging
- * Should the paging be turned on?
- */
- public void setPagingEnabled(boolean paging) {
- if (isPagingEnabled == paging) {
- return;
- }
- if (paging) {
- down.getStyle().clearDisplay();
- up.getStyle().clearDisplay();
- status.getStyle().clearDisplay();
- } else {
- down.getStyle().setDisplay(Display.NONE);
- up.getStyle().setDisplay(Display.NONE);
- status.getStyle().setDisplay(Display.NONE);
- }
- isPagingEnabled = paging;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition
- * (int, int)
- */
-
- @Override
- public void setPosition(int offsetWidth, int offsetHeight) {
-
- int top = -1;
- int left = -1;
-
- // reset menu size and retrieve its "natural" size
- menu.setHeight("");
- if (currentPage > 0) {
- // fix height to avoid height change when getting to last page
- menu.fixHeightTo(pageLength);
- }
- offsetHeight = getOffsetHeight();
-
- final int desiredWidth = getMainWidth();
- Element menuFirstChild = menu.getElement().getFirstChildElement()
- .cast();
- int naturalMenuWidth = menuFirstChild.getOffsetWidth();
-
- if (popupOuterPadding == -1) {
- popupOuterPadding = Util.measureHorizontalPaddingAndBorder(
- getElement(), 2);
- }
-
- if (naturalMenuWidth < desiredWidth) {
- menu.setWidth((desiredWidth - popupOuterPadding) + "px");
- menuFirstChild.getStyle().setWidth(100, Unit.PCT);
- naturalMenuWidth = desiredWidth;
- }
-
- if (BrowserInfo.get().isIE()) {
- /*
- * IE requires us to specify the width for the container
- * element. Otherwise it will be 100% wide
- */
- int rootWidth = naturalMenuWidth - popupOuterPadding;
- getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
- }
-
- if (offsetHeight + getPopupTop() > Window.getClientHeight()
- + Window.getScrollTop()) {
- // popup on top of input instead
- top = getPopupTop() - offsetHeight
- - VFilterSelect.this.getOffsetHeight();
- if (top < 0) {
- top = 0;
- }
- } else {
- top = getPopupTop();
- /*
- * Take popup top margin into account. getPopupTop() returns the
- * top value including the margin but the value we give must not
- * include the margin.
- */
- int topMargin = (top - topPosition);
- top -= topMargin;
- }
-
- // fetch real width (mac FF bugs here due GWT popups overflow:auto )
- offsetWidth = menuFirstChild.getOffsetWidth();
- if (offsetWidth + getPopupLeft() > Window.getClientWidth()
- + Window.getScrollLeft()) {
- left = VFilterSelect.this.getAbsoluteLeft()
- + VFilterSelect.this.getOffsetWidth()
- + Window.getScrollLeft() - offsetWidth;
- if (left < 0) {
- left = 0;
- }
- } else {
- left = getPopupLeft();
- }
- setPopupPosition(left, top);
- }
-
- /**
- * Was the popup just closed?
- *
- * @return true if popup was just closed
- */
- public boolean isJustClosed() {
- final long now = (new Date()).getTime();
- return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google
- * .gwt.event.logical.shared.CloseEvent)
- */
-
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- if (event.isAutoClosed()) {
- lastAutoClosed = (new Date()).getTime();
- }
- }
-
- /**
- * Updates style names in suggestion popup to help theme building.
- *
- * @param uidl
- * UIDL for the whole combo box
- * @param componentState
- * shared state of the combo box
- */
- public void updateStyleNames(UIDL uidl, ComponentState componentState) {
- setStyleName(VFilterSelect.this.getStylePrimaryName()
- + "-suggestpopup");
- menu.setStyleName(VFilterSelect.this.getStylePrimaryName()
- + "-suggestmenu");
- status.setClassName(VFilterSelect.this.getStylePrimaryName()
- + "-status");
- if (ComponentStateUtil.hasStyles(componentState)) {
- for (String style : componentState.styles) {
- if (!"".equals(style)) {
- addStyleDependentName(style);
- }
- }
- }
- }
-
- }
-
- /**
- * The menu where the suggestions are rendered
- */
- public class SuggestionMenu extends MenuBar implements SubPartAware,
- LoadHandler {
-
- /**
- * Tracks the item that is currently selected using the keyboard. This
- * is need only because mouseover changes the selection and we do not
- * want to use that selection when pressing enter to select the item.
- */
- private MenuItem keyboardSelectedItem;
-
- private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(
- 100, new ScheduledCommand() {
-
- @Override
- public void execute() {
- if (suggestionPopup.isVisible()
- && suggestionPopup.isAttached()) {
- setWidth("");
- getElement().getFirstChildElement().getStyle()
- .clearWidth();
- suggestionPopup
- .setPopupPositionAndShow(suggestionPopup);
- }
-
- }
- });
-
- /**
- * Default constructor
- */
- SuggestionMenu() {
- super(true);
- addDomHandler(this, LoadEvent.getType());
- }
-
- /**
- * Fixes menus height to use same space as full page would use. Needed
- * to avoid height changes when quickly "scrolling" to last page
- */
- public void fixHeightTo(int pagelenth) {
- if (currentSuggestions.size() > 0) {
- final int pixels = pagelenth * (getOffsetHeight() - 2)
- / currentSuggestions.size();
- setHeight((pixels + 2) + "px");
- }
- }
-
- /**
- * Sets the suggestions rendered in the menu
- *
- * @param suggestions
- * The suggestions to be rendered in the menu
- */
- public void setSuggestions(
- Collection<FilterSelectSuggestion> suggestions) {
- // Reset keyboard selection when contents is updated to avoid
- // reusing old, invalid data
- setKeyboardSelectedItem(null);
-
- clearItems();
- final Iterator<FilterSelectSuggestion> it = suggestions.iterator();
- while (it.hasNext()) {
- final FilterSelectSuggestion s = it.next();
- final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
-
- Util.sinkOnloadForImages(mi.getElement());
-
- this.addItem(mi);
- if (s == currentSuggestion) {
- selectItem(mi);
- }
- }
- }
-
- /**
- * Send the current selection to the server. Triggered when a selection
- * is made or on a blur event.
- */
- public void doSelectedItemAction() {
- // do not send a value change event if null was and stays selected
- final String enteredItemValue = tb.getText();
- if (nullSelectionAllowed && "".equals(enteredItemValue)
- && selectedOptionKey != null
- && !"".equals(selectedOptionKey)) {
- if (nullSelectItem) {
- reset();
- return;
- }
- // null is not visible on pages != 0, and not visible when
- // filtering: handle separately
- client.updateVariable(paintableId, "filter", "", false);
- client.updateVariable(paintableId, "page", 0, false);
- client.updateVariable(paintableId, "selected", new String[] {},
- immediate);
- suggestionPopup.hide();
- return;
- }
-
- updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
- if (!waitingForFilteringResponse) {
- doPostFilterSelectedItemAction();
- }
- }
-
- /**
- * Triggered after a selection has been made
- */
- public void doPostFilterSelectedItemAction() {
- final MenuItem item = getSelectedItem();
- final String enteredItemValue = tb.getText();
-
- updateSelectionWhenReponseIsReceived = false;
-
- // check for exact match in menu
- int p = getItems().size();
- if (p > 0) {
- for (int i = 0; i < p; i++) {
- final MenuItem potentialExactMatch = getItems().get(i);
- if (potentialExactMatch.getText().equals(enteredItemValue)) {
- selectItem(potentialExactMatch);
- // do not send a value change event if null was and
- // stays selected
- if (!"".equals(enteredItemValue)
- || (selectedOptionKey != null && !""
- .equals(selectedOptionKey))) {
- doItemAction(potentialExactMatch, true);
- }
- suggestionPopup.hide();
- return;
- }
- }
- }
- if (allowNewItem) {
-
- if (!prompting && !enteredItemValue.equals(lastNewItemString)) {
- /*
- * Store last sent new item string to avoid double sends
- */
- lastNewItemString = enteredItemValue;
- client.updateVariable(paintableId, "newitem",
- enteredItemValue, immediate);
- }
- } else if (item != null
- && !"".equals(lastFilter)
- && (filteringmode == FilteringMode.CONTAINS ? item
- .getText().toLowerCase()
- .contains(lastFilter.toLowerCase()) : item
- .getText().toLowerCase()
- .startsWith(lastFilter.toLowerCase()))) {
- doItemAction(item, true);
- } else {
- // currentSuggestion has key="" for nullselection
- if (currentSuggestion != null
- && !currentSuggestion.key.equals("")) {
- // An item (not null) selected
- String text = currentSuggestion.getReplacementString();
- tb.setText(text);
- selectedOptionKey = currentSuggestion.key;
- } else {
- // Null selected
- tb.setText("");
- selectedOptionKey = null;
- }
- }
- suggestionPopup.hide();
- }
-
- private static final String SUBPART_PREFIX = "item";
-
- @Override
- public Element getSubPartElement(String subPart) {
- int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX
- .length()));
-
- MenuItem item = getItems().get(index);
-
- return item.getElement();
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (!getElement().isOrHasChild(subElement)) {
- return null;
- }
-
- Element menuItemRoot = subElement;
- while (menuItemRoot != null
- && !menuItemRoot.getTagName().equalsIgnoreCase("td")) {
- menuItemRoot = menuItemRoot.getParentElement().cast();
- }
- // "menuItemRoot" is now the root of the menu item
-
- final int itemCount = getItems().size();
- for (int i = 0; i < itemCount; i++) {
- if (getItems().get(i).getElement() == menuItemRoot) {
- String name = SUBPART_PREFIX + i;
- return name;
- }
- }
- return null;
- }
-
- @Override
- public void onLoad(LoadEvent event) {
- // Handle icon onload events to ensure shadow is resized
- // correctly
- delayedImageLoadExecutioner.trigger();
-
- }
-
- public void selectFirstItem() {
- MenuItem firstItem = getItems().get(0);
- selectItem(firstItem);
- }
-
- private MenuItem getKeyboardSelectedItem() {
- return keyboardSelectedItem;
- }
-
- protected void setKeyboardSelectedItem(MenuItem firstItem) {
- keyboardSelectedItem = firstItem;
- }
-
- public void selectLastItem() {
- List<MenuItem> items = getItems();
- MenuItem lastItem = items.get(items.size() - 1);
- selectItem(lastItem);
- }
- }
-
- @Deprecated
- public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
- @Deprecated
- public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
- @Deprecated
- public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
-
- public static final String CLASSNAME = "v-filterselect";
- private static final String STYLE_NO_INPUT = "no-input";
-
- protected int pageLength = 10;
-
- private boolean enableDebug = false;
-
- private final FlowPanel panel = new FlowPanel();
-
- /**
- * The text box where the filter is written
- */
- protected final TextBox tb = new TextBox() {
-
- // Overridden to avoid selecting text when text input is disabled
- @Override
- public void setSelectionRange(int pos, int length) {
- if (textInputEnabled) {
- super.setSelectionRange(pos, length);
- } else {
- super.setSelectionRange(getValue().length(), 0);
- }
- };
- };
-
- protected final SuggestionPopup suggestionPopup = new SuggestionPopup();
-
- /**
- * Used when measuring the width of the popup
- */
- private final HTML popupOpener = new HTML("") {
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
- * .user.client.Event)
- */
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- /*
- * Prevent the keyboard focus from leaving the textfield by
- * preventing the default behaviour of the browser. Fixes #4285.
- */
- handleMouseDownEvent(event);
- }
- };
-
- private final Image selectedItemIcon = new Image();
-
- protected ApplicationConnection client;
-
- protected String paintableId;
-
- protected int currentPage;
-
- /**
- * A collection of available suggestions (options) as received from the
- * server.
- */
- protected final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>();
-
- protected boolean immediate;
-
- protected String selectedOptionKey;
-
- protected boolean waitingForFilteringResponse = false;
- protected boolean updateSelectionWhenReponseIsReceived = false;
- private boolean tabPressedWhenPopupOpen = false;
- protected boolean initDone = false;
-
- protected String lastFilter = "";
-
- protected enum Select {
- NONE, FIRST, LAST
- };
-
- protected Select selectPopupItemWhenResponseIsReceived = Select.NONE;
-
- /**
- * The current suggestion selected from the dropdown. This is one of the
- * values in currentSuggestions except when filtering, in this case
- * currentSuggestion might not be in currentSuggestions.
- */
- protected FilterSelectSuggestion currentSuggestion;
-
- protected int totalMatches;
- protected boolean allowNewItem;
- protected boolean nullSelectionAllowed;
- protected boolean nullSelectItem;
- protected boolean enabled;
- protected boolean readonly;
-
- protected FilteringMode filteringmode = FilteringMode.OFF;
-
- // shown in unfocused empty field, disappears on focus (e.g "Search here")
- private static final String CLASSNAME_PROMPT = "prompt";
- protected String inputPrompt = "";
- protected boolean prompting = false;
-
- // Set true when popupopened has been clicked. Cleared on each UIDL-update.
- // This handles the special case where are not filtering yet and the
- // selected value has changed on the server-side. See #2119
- protected boolean popupOpenerClicked;
- protected int suggestionPopupMinWidth = 0;
- private int popupWidth = -1;
- /*
- * Stores the last new item string to avoid double submissions. Cleared on
- * uidl updates
- */
- protected String lastNewItemString;
- protected boolean focused = false;
-
- /**
- * If set to false, the component should not allow entering text to the
- * field even for filtering.
- */
- private boolean textInputEnabled = true;
-
- /**
- * Default constructor
- */
- public VFilterSelect() {
- selectedItemIcon.setStyleName("v-icon");
- selectedItemIcon.addLoadHandler(new LoadHandler() {
-
- @Override
- public void onLoad(LoadEvent event) {
- if (BrowserInfo.get().isIE8()) {
- // IE8 needs some help to discover it should reposition the
- // text field
- forceReflow();
- }
- updateRootWidth();
- updateSelectedIconPosition();
- }
- });
-
- popupOpener.sinkEvents(Event.ONMOUSEDOWN);
- panel.add(tb);
- panel.add(popupOpener);
- initWidget(panel);
- tb.addKeyDownHandler(this);
- tb.addKeyUpHandler(this);
-
- tb.addFocusHandler(this);
- tb.addBlurHandler(this);
- tb.addClickHandler(this);
-
- popupOpener.addClickHandler(this);
-
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames();
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- updateStyleNames();
- }
-
- protected void updateStyleNames() {
- tb.setStyleName(getStylePrimaryName() + "-input");
- popupOpener.setStyleName(getStylePrimaryName() + "-button");
- suggestionPopup.setStyleName(getStylePrimaryName() + "-suggestpopup");
- }
-
- /**
- * Does the Select have more pages?
- *
- * @return true if a next page exists, else false if the current page is the
- * last page
- */
- public boolean hasNextPage() {
- if (totalMatches > (currentPage + 1) * pageLength) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Filters the options at a certain page. Uses the text box input as a
- * filter
- *
- * @param page
- * The page which items are to be filtered
- */
- public void filterOptions(int page) {
- filterOptions(page, tb.getText());
- }
-
- /**
- * Filters the options at certain page using the given filter
- *
- * @param page
- * The page to filter
- * @param filter
- * The filter to apply to the components
- */
- public void filterOptions(int page, String filter) {
- filterOptions(page, filter, true);
- }
-
- /**
- * Filters the options at certain page using the given filter
- *
- * @param page
- * The page to filter
- * @param filter
- * The filter to apply to the options
- * @param immediate
- * Whether to send the options request immediately
- */
- private void filterOptions(int page, String filter, boolean immediate) {
- if (filter.equals(lastFilter) && currentPage == page) {
- if (!suggestionPopup.isAttached()) {
- suggestionPopup.showSuggestions(currentSuggestions,
- currentPage, totalMatches);
- }
- return;
- }
- if (!filter.equals(lastFilter)) {
- // we are on subsequent page and text has changed -> reset page
- if ("".equals(filter)) {
- // let server decide
- page = -1;
- } else {
- page = 0;
- }
- }
-
- waitingForFilteringResponse = true;
- client.updateVariable(paintableId, "filter", filter, false);
- client.updateVariable(paintableId, "page", page, immediate);
- lastFilter = filter;
- currentPage = page;
- }
-
- protected void updateReadOnly() {
- tb.setReadOnly(readonly || !textInputEnabled);
- }
-
- protected void setTextInputEnabled(boolean textInputEnabled) {
- // Always update styles as they might have been overwritten
- if (textInputEnabled) {
- removeStyleDependentName(STYLE_NO_INPUT);
- } else {
- addStyleDependentName(STYLE_NO_INPUT);
- }
-
- if (this.textInputEnabled == textInputEnabled) {
- return;
- }
-
- this.textInputEnabled = textInputEnabled;
- updateReadOnly();
- }
-
- /**
- * Sets the text in the text box.
- *
- * @param text
- * the text to set in the text box
- */
- protected void setTextboxText(final String text) {
- tb.setText(text);
- }
-
- /**
- * Turns prompting on. When prompting is turned on a command prompt is shown
- * in the text box if nothing has been entered.
- */
- protected void setPromptingOn() {
- if (!prompting) {
- prompting = true;
- addStyleDependentName(CLASSNAME_PROMPT);
- }
- setTextboxText(inputPrompt);
- }
-
- /**
- * Turns prompting off. When prompting is turned on a command prompt is
- * shown in the text box if nothing has been entered.
- *
- * @param text
- * The text the text box should contain.
- */
- protected void setPromptingOff(String text) {
- setTextboxText(text);
- if (prompting) {
- prompting = false;
- removeStyleDependentName(CLASSNAME_PROMPT);
- }
- }
-
- /**
- * Triggered when a suggestion is selected
- *
- * @param suggestion
- * The suggestion that just got selected.
- */
- public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
- updateSelectionWhenReponseIsReceived = false;
-
- currentSuggestion = suggestion;
- String newKey;
- if (suggestion.key.equals("")) {
- // "nullselection"
- newKey = "";
- } else {
- // normal selection
- newKey = String.valueOf(suggestion.getOptionKey());
- }
-
- String text = suggestion.getReplacementString();
- if ("".equals(newKey) && !focused) {
- setPromptingOn();
- } else {
- setPromptingOff(text);
- }
- setSelectedItemIcon(suggestion.getIconUri());
- if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) {
- selectedOptionKey = newKey;
- client.updateVariable(paintableId, "selected",
- new String[] { selectedOptionKey }, immediate);
- // currentPage = -1; // forget the page
- }
- suggestionPopup.hide();
- }
-
- /**
- * Sets the icon URI of the selected item. The icon is shown on the left
- * side of the item caption text. Set the URI to null to remove the icon.
- *
- * @param iconUri
- * The URI of the icon
- */
- protected void setSelectedItemIcon(String iconUri) {
- if (iconUri == null || iconUri.length() == 0) {
- if (selectedItemIcon.isAttached()) {
- panel.remove(selectedItemIcon);
- if (BrowserInfo.get().isIE8()) {
- // IE8 needs some help to discover it should reposition the
- // text field
- forceReflow();
- }
- updateRootWidth();
- }
- } else {
- panel.insert(selectedItemIcon, 0);
- selectedItemIcon.setUrl(iconUri);
- updateRootWidth();
- updateSelectedIconPosition();
- }
- }
-
- private void forceReflow() {
- Util.setStyleTemporarily(tb.getElement(), "zoom", "1");
- }
-
- /**
- * Positions the icon vertically in the middle. Should be called after the
- * icon has loaded
- */
- private void updateSelectedIconPosition() {
- // Position icon vertically to middle
- int availableHeight = 0;
- availableHeight = getOffsetHeight();
-
- int iconHeight = Util.getRequiredHeight(selectedItemIcon);
- int marginTop = (availableHeight - iconHeight) / 2;
- DOM.setStyleAttribute(selectedItemIcon.getElement(), "marginTop",
- marginTop + "px");
- }
-
- private static Set<Integer> navigationKeyCodes = new HashSet<Integer>();
- static {
- navigationKeyCodes.add(KeyCodes.KEY_DOWN);
- navigationKeyCodes.add(KeyCodes.KEY_UP);
- navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN);
- navigationKeyCodes.add(KeyCodes.KEY_PAGEUP);
- navigationKeyCodes.add(KeyCodes.KEY_ENTER);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
-
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (enabled && !readonly) {
- int keyCode = event.getNativeKeyCode();
-
- debug("key down: " + keyCode);
- if (waitingForFilteringResponse
- && navigationKeyCodes.contains(keyCode)) {
- /*
- * Keyboard navigation events should not be handled while we are
- * waiting for a response. This avoids flickering, disappearing
- * items, wrongly interpreted responses and more.
- */
- debug("Ignoring " + keyCode
- + " because we are waiting for a filtering response");
- DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
- event.stopPropagation();
- return;
- }
-
- if (suggestionPopup.isAttached()) {
- debug("Keycode " + keyCode + " target is popup");
- popupKeyDown(event);
- } else {
- debug("Keycode " + keyCode + " target is text field");
- inputFieldKeyDown(event);
- }
- }
- }
-
- private void debug(String string) {
- if (enableDebug) {
- VConsole.error(string);
- }
- }
-
- /**
- * Triggered when a key is pressed in the text box
- *
- * @param event
- * The KeyDownEvent
- */
- private void inputFieldKeyDown(KeyDownEvent event) {
- switch (event.getNativeKeyCode()) {
- case KeyCodes.KEY_DOWN:
- case KeyCodes.KEY_UP:
- case KeyCodes.KEY_PAGEDOWN:
- case KeyCodes.KEY_PAGEUP:
- // open popup as from gadget
- filterOptions(-1, "");
- lastFilter = "";
- tb.selectAll();
- break;
- case KeyCodes.KEY_ENTER:
- /*
- * This only handles the case when new items is allowed, a text is
- * entered, the popup opener button is clicked to close the popup
- * and enter is then pressed (see #7560).
- */
- if (!allowNewItem) {
- return;
- }
-
- if (currentSuggestion != null
- && tb.getText().equals(
- currentSuggestion.getReplacementString())) {
- // Retain behavior from #6686 by returning without stopping
- // propagation if there's nothing to do
- return;
- }
- suggestionPopup.menu.doSelectedItemAction();
-
- event.stopPropagation();
- break;
- }
-
- }
-
- /**
- * Triggered when a key was pressed in the suggestion popup.
- *
- * @param event
- * The KeyDownEvent of the key
- */
- private void popupKeyDown(KeyDownEvent event) {
- // Propagation of handled events is stopped so other handlers such as
- // shortcut key handlers do not also handle the same events.
- switch (event.getNativeKeyCode()) {
- case KeyCodes.KEY_DOWN:
- suggestionPopup.selectNextItem();
- suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
- .getSelectedItem());
- DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
- event.stopPropagation();
- break;
- case KeyCodes.KEY_UP:
- suggestionPopup.selectPrevItem();
- suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
- .getSelectedItem());
- DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
- event.stopPropagation();
- break;
- case KeyCodes.KEY_PAGEDOWN:
- if (hasNextPage()) {
- filterOptions(currentPage + 1, lastFilter);
- }
- event.stopPropagation();
- break;
- case KeyCodes.KEY_PAGEUP:
- if (currentPage > 0) {
- filterOptions(currentPage - 1, lastFilter);
- }
- event.stopPropagation();
- break;
- case KeyCodes.KEY_TAB:
- tabPressedWhenPopupOpen = true;
- filterOptions(currentPage);
- // onBlur() takes care of the rest
- break;
- case KeyCodes.KEY_ESCAPE:
- reset();
- event.stopPropagation();
- break;
- case KeyCodes.KEY_ENTER:
- if (suggestionPopup.menu.getKeyboardSelectedItem() == null) {
- /*
- * Nothing selected using up/down. Happens e.g. when entering a
- * text (causes popup to open) and then pressing enter.
- */
- if (!allowNewItem) {
- /*
- * New items are not allowed: If there is only one
- * suggestion, select that. Otherwise do nothing.
- */
- if (currentSuggestions.size() == 1) {
- onSuggestionSelected(currentSuggestions.get(0));
- }
- } else {
- // Handle addition of new items.
- suggestionPopup.menu.doSelectedItemAction();
- }
- } else {
- /*
- * Get the suggestion that was navigated to using up/down.
- */
- currentSuggestion = ((FilterSelectSuggestion) suggestionPopup.menu
- .getKeyboardSelectedItem().getCommand());
- onSuggestionSelected(currentSuggestion);
- }
-
- event.stopPropagation();
- break;
- }
-
- }
-
- /**
- * Triggered when a key was depressed
- *
- * @param event
- * The KeyUpEvent of the key depressed
- */
-
- @Override
- public void onKeyUp(KeyUpEvent event) {
- if (enabled && !readonly) {
- switch (event.getNativeKeyCode()) {
- case KeyCodes.KEY_ENTER:
- case KeyCodes.KEY_TAB:
- case KeyCodes.KEY_SHIFT:
- case KeyCodes.KEY_CTRL:
- case KeyCodes.KEY_ALT:
- case KeyCodes.KEY_DOWN:
- case KeyCodes.KEY_UP:
- case KeyCodes.KEY_PAGEDOWN:
- case KeyCodes.KEY_PAGEUP:
- case KeyCodes.KEY_ESCAPE:
- ; // NOP
- break;
- default:
- if (textInputEnabled) {
- filterOptions(currentPage);
- }
- break;
- }
- }
- }
-
- /**
- * Resets the Select to its initial state
- */
- private void reset() {
- if (currentSuggestion != null) {
- String text = currentSuggestion.getReplacementString();
- setPromptingOff(text);
- selectedOptionKey = currentSuggestion.key;
- } else {
- if (focused) {
- setPromptingOff("");
- } else {
- setPromptingOn();
- }
- selectedOptionKey = null;
- }
- lastFilter = "";
- suggestionPopup.hide();
- }
-
- /**
- * Listener for popupopener
- */
-
- @Override
- public void onClick(ClickEvent event) {
- if (textInputEnabled
- && event.getNativeEvent().getEventTarget().cast() == tb
- .getElement()) {
- // Don't process clicks on the text field if text input is enabled
- return;
- }
- if (enabled && !readonly) {
- // ask suggestionPopup if it was just closed, we are using GWT
- // Popup's auto close feature
- if (!suggestionPopup.isJustClosed()) {
- // If a focus event is not going to be sent, send the options
- // request immediately; otherwise queue in the same burst as the
- // focus event. Fixes #8321.
- boolean immediate = focused
- || !client.hasEventListeners(this, EventId.FOCUS);
- filterOptions(-1, "", immediate);
- popupOpenerClicked = true;
- lastFilter = "";
- }
- DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
- focus();
- tb.selectAll();
- }
- }
-
- /**
- * Calculate minimum width for FilterSelect textarea
- */
- protected native int minWidth(String captions)
- /*-{
- if(!captions || captions.length <= 0)
- return 0;
- captions = captions.split("|");
- var d = $wnd.document.createElement("div");
- var html = "";
- for(var i=0; i < captions.length; i++) {
- html += "<div>" + captions[i] + "</div>";
- // TODO apply same CSS classname as in suggestionmenu
- }
- d.style.position = "absolute";
- d.style.top = "0";
- d.style.left = "0";
- d.style.visibility = "hidden";
- d.innerHTML = html;
- $wnd.document.body.appendChild(d);
- var w = d.offsetWidth;
- $wnd.document.body.removeChild(d);
- return w;
- }-*/;
-
- /**
- * A flag which prevents a focus event from taking place
- */
- boolean iePreventNextFocus = false;
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
- * .dom.client.FocusEvent)
- */
-
- @Override
- public void onFocus(FocusEvent event) {
-
- /*
- * When we disable a blur event in ie we need to refocus the textfield.
- * This will cause a focus event we do not want to process, so in that
- * case we just ignore it.
- */
- if (BrowserInfo.get().isIE() && iePreventNextFocus) {
- iePreventNextFocus = false;
- return;
- }
-
- focused = true;
- if (prompting && !readonly) {
- setPromptingOff("");
- }
- addStyleDependentName("focus");
-
- if (client.hasEventListeners(this, EventId.FOCUS)) {
- client.updateVariable(paintableId, EventId.FOCUS, "", true);
- }
- }
-
- /**
- * A flag which cancels the blur event and sets the focus back to the
- * textfield if the Browser is IE
- */
- boolean preventNextBlurEventInIE = false;
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
- * .dom.client.BlurEvent)
- */
-
- @Override
- public void onBlur(BlurEvent event) {
-
- if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) {
- /*
- * Clicking in the suggestion popup or on the popup button in IE
- * causes a blur event to be sent for the field. In other browsers
- * this is prevented by canceling/preventing default behavior for
- * the focus event, in IE we handle it here by refocusing the text
- * field and ignoring the resulting focus event for the textfield
- * (in onFocus).
- */
- preventNextBlurEventInIE = false;
-
- Element focusedElement = Util.getIEFocusedElement();
- if (getElement().isOrHasChild(focusedElement)
- || suggestionPopup.getElement()
- .isOrHasChild(focusedElement)) {
-
- // IF the suggestion popup or another part of the VFilterSelect
- // was focused, move the focus back to the textfield and prevent
- // the triggered focus event (in onFocus).
- iePreventNextFocus = true;
- tb.setFocus(true);
- return;
- }
- }
-
- focused = false;
- if (!readonly) {
- // much of the TAB handling takes place here
- if (tabPressedWhenPopupOpen) {
- tabPressedWhenPopupOpen = false;
- suggestionPopup.menu.doSelectedItemAction();
- suggestionPopup.hide();
- } else if (!suggestionPopup.isAttached()
- || suggestionPopup.isJustClosed()) {
- suggestionPopup.menu.doSelectedItemAction();
- }
- if (selectedOptionKey == null) {
- setPromptingOn();
- } else if (currentSuggestion != null) {
- setPromptingOff(currentSuggestion.caption);
- }
- }
- removeStyleDependentName("focus");
-
- if (client.hasEventListeners(this, EventId.BLUR)) {
- client.updateVariable(paintableId, EventId.BLUR, "", true);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Focusable#focus()
- */
-
- @Override
- public void focus() {
- focused = true;
- if (prompting && !readonly) {
- setPromptingOff("");
- }
- tb.setFocus(true);
- }
-
- /**
- * Calculates the width of the select if the select has undefined width.
- * Should be called when the width changes or when the icon changes.
- */
- protected void updateRootWidth() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- if (paintable.isUndefinedWidth()) {
-
- /*
- * When the select has a undefined with we need to check that we are
- * only setting the text box width relative to the first page width
- * of the items. If this is not done the text box width will change
- * when the popup is used to view longer items than the text box is
- * wide.
- */
- int w = Util.getRequiredWidth(this);
- if ((!initDone || currentPage + 1 < 0)
- && suggestionPopupMinWidth > w) {
- /*
- * We want to compensate for the paddings just to preserve the
- * exact size as in Vaadin 6.x, but we get here before
- * MeasuredSize has been initialized.
- * Util.measureHorizontalPaddingAndBorder does not work with
- * border-box, so we must do this the hard way.
- */
- Style style = getElement().getStyle();
- String originalPadding = style.getPadding();
- String originalBorder = style.getBorderWidth();
- style.setPaddingLeft(0, Unit.PX);
- style.setBorderWidth(0, Unit.PX);
- int offset = w - Util.getRequiredWidth(this);
- style.setProperty("padding", originalPadding);
- style.setProperty("borderWidth", originalBorder);
-
- setWidth(suggestionPopupMinWidth + offset + "px");
- }
-
- /*
- * Lock the textbox width to its current value if it's not already
- * locked
- */
- if (!tb.getElement().getStyle().getWidth().endsWith("px")) {
- tb.setWidth((tb.getOffsetWidth() - selectedItemIcon
- .getOffsetWidth()) + "px");
- }
- }
- }
-
- /**
- * Get the width of the select in pixels where the text area and icon has
- * been included.
- *
- * @return The width in pixels
- */
- private int getMainWidth() {
- return getOffsetWidth();
- }
-
- @Override
- public void setWidth(String width) {
- super.setWidth(width);
- if (width.length() != 0) {
- tb.setWidth("100%");
- }
- }
-
- /**
- * Handles special behavior of the mouse down event
- *
- * @param event
- */
- private void handleMouseDownEvent(Event event) {
- /*
- * Prevent the keyboard focus from leaving the textfield by preventing
- * the default behaviour of the browser. Fixes #4285.
- */
- if (event.getTypeInt() == Event.ONMOUSEDOWN) {
- event.preventDefault();
- event.stopPropagation();
-
- /*
- * In IE the above wont work, the blur event will still trigger. So,
- * we set a flag here to prevent the next blur event from happening.
- * This is not needed if do not already have focus, in that case
- * there will not be any blur event and we should not cancel the
- * next blur.
- */
- if (BrowserInfo.get().isIE() && focused) {
- preventNextBlurEventInIE = true;
- }
- }
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- suggestionPopup.hide();
- }
-
- @Override
- public Element getSubPartElement(String subPart) {
- if ("textbox".equals(subPart)) {
- return tb.getElement();
- } else if ("button".equals(subPart)) {
- return popupOpener.getElement();
- }
- return null;
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (tb.getElement().isOrHasChild(subElement)) {
- return "textbox";
- } else if (popupOpener.getElement().isOrHasChild(subElement)) {
- return "button";
- }
- return null;
- }
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractLayoutConnector;
import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VCssLayout;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.LayoutClickRpc;
import com.vaadin.shared.ui.csslayout.CssLayoutServerRpc;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.csslayout;
-
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.StyleConstants;
-
-/**
- * VCCSlayout is a layout which supports configuring it's children with CSS
- * selectors
- */
-public class VCssLayout extends FlowPanel {
-
- public static final String CLASSNAME = "v-csslayout";
-
- /**
- * Default constructor
- */
- public VCssLayout() {
- super();
- setStyleName(CLASSNAME);
- addStyleName(StyleConstants.UI_LAYOUT);
- }
-
- /**
- * Add or move a child in the
- *
- * @param child
- * @param index
- */
- void addOrMove(Widget child, int index) {
- if (child.getParent() == this) {
- int currentIndex = getWidgetIndex(child);
- if (index == currentIndex) {
- return;
- }
- }
- insert(child, index);
- }
-}
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
+import com.vaadin.client.ui.VCustomComponent;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.ui.CustomComponent;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.customcomponent;
-
-import com.google.gwt.user.client.ui.SimplePanel;
-
-public class VCustomComponent extends SimplePanel {
-
- private static final String CLASSNAME = "v-customcomponent";
-
- public VCustomComponent() {
- super();
- setStyleName(CLASSNAME);
- }
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractLayoutConnector;
import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VCustomLayout;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.customlayout.CustomLayoutState;
import com.vaadin.ui.CustomLayout;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.customlayout;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-import com.google.gwt.dom.client.ImageElement;
-import com.google.gwt.dom.client.NodeList;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.BorderStyle;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.VCaptionWrapper;
-
-/**
- * Custom Layout implements complex layout defined with HTML template.
- *
- * @author Vaadin Ltd
- *
- */
-public class VCustomLayout extends ComplexPanel {
-
- public static final String CLASSNAME = "v-customlayout";
-
- /** Location-name to containing element in DOM map */
- private final HashMap<String, Element> locationToElement = new HashMap<String, Element>();
-
- /** Location-name to contained widget map */
- final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>();
-
- /** Widget to captionwrapper map */
- private final HashMap<Widget, VCaptionWrapper> childWidgetToCaptionWrapper = new HashMap<Widget, VCaptionWrapper>();
-
- /** Name of the currently rendered style */
- String currentTemplateName;
-
- /** Unexecuted scripts loaded from the template */
- String scripts = "";
-
- /** Paintable ID of this paintable */
- String pid;
-
- ApplicationConnection client;
-
- private boolean htmlInitialized = false;
-
- private Element elementWithNativeResizeFunction;
-
- private String height = "";
-
- private String width = "";
-
- public VCustomLayout() {
- setElement(DOM.createDiv());
- // Clear any unwanted styling
- Style style = getElement().getStyle();
- style.setBorderStyle(BorderStyle.NONE);
- style.setMargin(0, Unit.PX);
- style.setPadding(0, Unit.PX);
-
- if (BrowserInfo.get().isIE()) {
- style.setPosition(Position.RELATIVE);
- }
-
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- addStyleName(StyleConstants.UI_LAYOUT);
- }
-
- /**
- * Sets widget to given location.
- *
- * If location already contains a widget it will be removed.
- *
- * @param widget
- * Widget to be set into location.
- * @param location
- * location name where widget will be added
- *
- * @throws IllegalArgumentException
- * if no such location is found in the layout.
- */
- public void setWidget(Widget widget, String location) {
-
- if (widget == null) {
- return;
- }
-
- // If no given location is found in the layout, and exception is throws
- Element elem = locationToElement.get(location);
- if (elem == null && hasTemplate()) {
- throw new IllegalArgumentException("No location " + location
- + " found");
- }
-
- // Get previous widget
- final Widget previous = locationToWidget.get(location);
- // NOP if given widget already exists in this location
- if (previous == widget) {
- return;
- }
-
- if (previous != null) {
- remove(previous);
- }
-
- // if template is missing add element in order
- if (!hasTemplate()) {
- elem = getElement();
- }
-
- // Add widget to location
- super.add(widget, elem);
- locationToWidget.put(location, widget);
- }
-
- /** Initialize HTML-layout. */
- public void initializeHTML(String template, String themeUri) {
-
- // Connect body of the template to DOM
- template = extractBodyAndScriptsFromTemplate(template);
-
- // TODO prefix img src:s here with a regeps, cannot work further with IE
-
- String relImgPrefix = themeUri + "/layouts/";
-
- // prefix all relative image elements to point to theme dir with a
- // regexp search
- template = template.replaceAll(
- "<((?:img)|(?:IMG))\\s([^>]*)src=\"((?![a-z]+:)[^/][^\"]+)\"",
- "<$1 $2src=\"" + relImgPrefix + "$3\"");
- // also support src attributes without quotes
- template = template
- .replaceAll(
- "<((?:img)|(?:IMG))\\s([^>]*)src=[^\"]((?![a-z]+:)[^/][^ />]+)[ />]",
- "<$1 $2src=\"" + relImgPrefix + "$3\"");
- // also prefix relative style="...url(...)..."
- template = template
- .replaceAll(
- "(<[^>]+style=\"[^\"]*url\\()((?![a-z]+:)[^/][^\"]+)(\\)[^>]*>)",
- "$1 " + relImgPrefix + "$2 $3");
-
- getElement().setInnerHTML(template);
-
- // Remap locations to elements
- locationToElement.clear();
- scanForLocations(getElement());
-
- initImgElements();
-
- elementWithNativeResizeFunction = DOM.getFirstChild(getElement());
- if (elementWithNativeResizeFunction == null) {
- elementWithNativeResizeFunction = getElement();
- }
- publishResizedFunction(elementWithNativeResizeFunction);
-
- htmlInitialized = true;
- }
-
- private native boolean uriEndsWithSlash()
- /*-{
- var path = $wnd.location.pathname;
- if(path.charAt(path.length - 1) == "/")
- return true;
- return false;
- }-*/;
-
- boolean hasTemplate() {
- return htmlInitialized;
- }
-
- /** Collect locations from template */
- private void scanForLocations(Element elem) {
-
- final String location = elem.getAttribute("location");
- if (!"".equals(location)) {
- locationToElement.put(location, elem);
- elem.setInnerHTML("");
-
- } else {
- final int len = DOM.getChildCount(elem);
- for (int i = 0; i < len; i++) {
- scanForLocations(DOM.getChild(elem, i));
- }
- }
- }
-
- /** Evaluate given script in browser document */
- static native void eval(String script)
- /*-{
- try {
- if (script != null)
- eval("{ var document = $doc; var window = $wnd; "+ script + "}");
- } catch (e) {
- }
- }-*/;
-
- /**
- * Img elements needs some special handling in custom layout. Img elements
- * will get their onload events sunk. This way custom layout can notify
- * parent about possible size change.
- */
- private void initImgElements() {
- NodeList<com.google.gwt.dom.client.Element> nodeList = getElement()
- .getElementsByTagName("IMG");
- for (int i = 0; i < nodeList.getLength(); i++) {
- com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList
- .getItem(i);
- DOM.sinkEvents((Element) img.cast(), Event.ONLOAD);
- }
- }
-
- /**
- * Extract body part and script tags from raw html-template.
- *
- * Saves contents of all script-tags to private property: scripts. Returns
- * contents of the body part for the html without script-tags. Also replaces
- * all _UID_ tags with an unique id-string.
- *
- * @param html
- * Original HTML-template received from server
- * @return html that is used to create the HTMLPanel.
- */
- private String extractBodyAndScriptsFromTemplate(String html) {
-
- // Replace UID:s
- html = html.replaceAll("_UID_", pid + "__");
-
- // Exctract script-tags
- scripts = "";
- int endOfPrevScript = 0;
- int nextPosToCheck = 0;
- String lc = html.toLowerCase();
- String res = "";
- int scriptStart = lc.indexOf("<script", nextPosToCheck);
- while (scriptStart > 0) {
- res += html.substring(endOfPrevScript, scriptStart);
- scriptStart = lc.indexOf(">", scriptStart);
- final int j = lc.indexOf("</script>", scriptStart);
- scripts += html.substring(scriptStart + 1, j) + ";";
- nextPosToCheck = endOfPrevScript = j + "</script>".length();
- scriptStart = lc.indexOf("<script", nextPosToCheck);
- }
- res += html.substring(endOfPrevScript);
-
- // Extract body
- html = res;
- lc = html.toLowerCase();
- int startOfBody = lc.indexOf("<body");
- if (startOfBody < 0) {
- res = html;
- } else {
- res = "";
- startOfBody = lc.indexOf(">", startOfBody) + 1;
- final int endOfBody = lc.indexOf("</body>", startOfBody);
- if (endOfBody > startOfBody) {
- res = html.substring(startOfBody, endOfBody);
- } else {
- res = html.substring(startOfBody);
- }
- }
-
- return res;
- }
-
- /** Update caption for given widget */
- public void updateCaption(ComponentConnector paintable) {
- Widget widget = paintable.getWidget();
- VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget);
- if (VCaption.isNeeded(paintable.getState())) {
- if (wrapper == null) {
- // Add a wrapper between the layout and the child widget
- final String loc = getLocation(widget);
- super.remove(widget);
- wrapper = new VCaptionWrapper(paintable, client);
- super.add(wrapper, locationToElement.get(loc));
- childWidgetToCaptionWrapper.put(widget, wrapper);
- }
- wrapper.updateCaption();
- } else {
- if (wrapper != null) {
- // Remove the wrapper and add the widget directly to the layout
- final String loc = getLocation(widget);
- super.remove(wrapper);
- super.add(widget, locationToElement.get(loc));
- childWidgetToCaptionWrapper.remove(widget);
- }
- }
- }
-
- /** Get the location of an widget */
- public String getLocation(Widget w) {
- for (final Iterator<String> i = locationToWidget.keySet().iterator(); i
- .hasNext();) {
- final String location = i.next();
- if (locationToWidget.get(location) == w) {
- return location;
- }
- }
- return null;
- }
-
- /** Removes given widget from the layout */
- @Override
- public boolean remove(Widget w) {
- final String location = getLocation(w);
- if (location != null) {
- locationToWidget.remove(location);
- }
- final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w);
- if (cw != null) {
- childWidgetToCaptionWrapper.remove(w);
- return super.remove(cw);
- } else if (w != null) {
- return super.remove(w);
- }
- return false;
- }
-
- /** Adding widget without specifying location is not supported */
- @Override
- public void add(Widget w) {
- throw new UnsupportedOperationException();
- }
-
- /** Clear all widgets from the layout */
- @Override
- public void clear() {
- super.clear();
- locationToWidget.clear();
- childWidgetToCaptionWrapper.clear();
- }
-
- /**
- * This method is published to JS side with the same name into first DOM
- * node of custom layout. This way if one implements some resizeable
- * containers in custom layout he/she can notify children after resize.
- */
- public void notifyChildrenOfSizeChange() {
- client.runDescendentsLayout(this);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (elementWithNativeResizeFunction != null) {
- detachResizedFunction(elementWithNativeResizeFunction);
- }
- }
-
- private native void detachResizedFunction(Element element)
- /*-{
- element.notifyChildrenOfSizeChange = null;
- }-*/;
-
- private native void publishResizedFunction(Element element)
- /*-{
- var self = this;
- element.notifyChildrenOfSizeChange = $entry(function() {
- self.@com.vaadin.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()();
- });
- }-*/;
-
- /**
- * In custom layout one may want to run layout functions made with
- * JavaScript. This function tests if one exists (with name "iLayoutJS" in
- * layouts first DOM node) and runs et. Return value is used to determine if
- * children needs to be notified of size changes.
- *
- * Note! When implementing a JS layout function you most likely want to call
- * notifyChildrenOfSizeChange() function on your custom layouts main
- * element. That method is used to control whether child components layout
- * functions are to be run.
- *
- * @param el
- * @return true if layout function exists and was run successfully, else
- * false.
- */
- native boolean iLayoutJS(Element el)
- /*-{
- if(el && el.iLayoutJS) {
- try {
- el.iLayoutJS();
- return true;
- } catch (e) {
- return false;
- }
- } else {
- return false;
- }
- }-*/;
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
- event.cancelBubble(true);
- }
- }
-
-}
import com.vaadin.client.UIDL;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VDateField;
import com.vaadin.shared.ui.datefield.DateFieldConstants;
import com.vaadin.shared.ui.datefield.Resolution;
getWidget().paintableId = uidl.getId();
getWidget().immediate = getState().immediate;
- getWidget().readonly = isReadOnly();
- getWidget().enabled = isEnabled();
+ getWidget().setReadonly(isReadOnly());
+ getWidget().setEnabled(isEnabled());
if (uidl.hasAttribute("locale")) {
final String locale = uidl.getStringAttribute("locale");
try {
getWidget().dts.setLocale(locale);
- getWidget().currentLocale = locale;
+ getWidget().setCurrentLocale(locale);
} catch (final LocaleNotLoadedException e) {
- getWidget().currentLocale = getWidget().dts.getLocale();
+ getWidget().setCurrentLocale(getWidget().dts.getLocale());
VConsole.error("Tried to use an unloaded locale \"" + locale
+ "\". Using default locale ("
- + getWidget().currentLocale + ").");
+ + getWidget().getCurrentLocale() + ").");
VConsole.error(e);
}
}
// We show week numbers only if the week starts with Monday, as ISO 8601
// specifies
- getWidget().showISOWeekNumbers = uidl
- .getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS)
- && getWidget().dts.getFirstDayOfWeek() == 1;
+ getWidget().setShowISOWeekNumbers(
+ uidl.getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS)
+ && getWidget().dts.getFirstDayOfWeek() == 1);
Resolution newResolution;
if (uidl.hasVariable("sec")) {
setWidgetStyleName(
getWidget().getStylePrimaryName()
+ "-"
- + VDateField
- .resolutionToString(getWidget().currentResolution),
- false);
+ + VDateField.resolutionToString(getWidget()
+ .getCurrentResolution()), false);
- getWidget().currentResolution = newResolution;
+ getWidget().setCurrentResolution(newResolution);
// Add stylename that indicates current resolution
setWidgetStyleName(
getWidget().getStylePrimaryName()
+ "-"
- + VDateField
- .resolutionToString(getWidget().currentResolution),
- true);
+ + VDateField.resolutionToString(getWidget()
+ .getCurrentResolution()), true);
+ final Resolution resolution = getWidget().getCurrentResolution();
final int year = uidl.getIntVariable("year");
- final int month = (getWidget().currentResolution.getCalendarField() >= Resolution.MONTH
+ final int month = (resolution.getCalendarField() >= Resolution.MONTH
.getCalendarField()) ? uidl.getIntVariable("month") : -1;
- final int day = (getWidget().currentResolution.getCalendarField() >= Resolution.DAY
+ final int day = (resolution.getCalendarField() >= Resolution.DAY
.getCalendarField()) ? uidl.getIntVariable("day") : -1;
- final int hour = (getWidget().currentResolution.getCalendarField() >= Resolution.HOUR
+ final int hour = (resolution.getCalendarField() >= Resolution.HOUR
.getCalendarField()) ? uidl.getIntVariable("hour") : 0;
- final int min = (getWidget().currentResolution.getCalendarField() >= Resolution.MINUTE
+ final int min = (resolution.getCalendarField() >= Resolution.MINUTE
.getCalendarField()) ? uidl.getIntVariable("min") : 0;
- final int sec = (getWidget().currentResolution.getCalendarField() >= Resolution.SECOND
+ final int sec = (resolution.getCalendarField() >= Resolution.SECOND
.getCalendarField()) ? uidl.getIntVariable("sec") : 0;
// Construct new date for this datefield (only if not null)
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.DateTimeService;
import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusChangeListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VDateFieldCalendar;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.datefield.InlineDateFieldState;
import com.vaadin.shared.ui.datefield.Resolution;
getWidget().calendarPanel.setDate(null);
}
- if (getWidget().currentResolution.getCalendarField() > Resolution.DAY
+ if (getWidget().getCurrentResolution().getCalendarField() > Resolution.DAY
.getCalendarField()) {
getWidget().calendarPanel
.setTimeChangeListener(new TimeChangeListener() {
});
}
- if (getWidget().currentResolution.getCalendarField() <= Resolution.MONTH
+ if (getWidget().getCurrentResolution().getCalendarField() <= Resolution.MONTH
.getCalendarField()) {
getWidget().calendarPanel
.setFocusChangeListener(new FocusChangeListener() {
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.DateTimeService;
import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusChangeListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VPopupCalendar;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.datefield.PopupDateFieldState;
import com.vaadin.shared.ui.datefield.Resolution;
@Override
@SuppressWarnings("deprecation")
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
- boolean lastReadOnlyState = getWidget().readonly;
+ boolean lastReadOnlyState = getWidget().isReadonly();
boolean lastEnabledState = getWidget().isEnabled();
getWidget().parsable = uidl.getBooleanAttribute("parsable");
.getDateTimeService());
getWidget().calendar.setShowISOWeekNumbers(getWidget()
.isShowISOWeekNumbers());
- if (getWidget().calendar.getResolution() != getWidget().currentResolution) {
- getWidget().calendar.setResolution(getWidget().currentResolution);
+ if (getWidget().calendar.getResolution() != getWidget()
+ .getCurrentResolution()) {
+ getWidget().calendar.setResolution(getWidget()
+ .getCurrentResolution());
if (getWidget().calendar.getDate() != null) {
getWidget().calendar.setDate((Date) getWidget()
.getCurrentDate().clone());
getWidget().calendar.renderCalendar();
}
}
- getWidget().calendarToggle.setEnabled(getWidget().enabled);
+ getWidget().calendarToggle.setEnabled(getWidget().isEnabled());
- if (getWidget().currentResolution.getCalendarField() <= Resolution.MONTH
+ if (getWidget().getCurrentResolution().getCalendarField() <= Resolution.MONTH
.getCalendarField()) {
getWidget().calendar
.setFocusChangeListener(new FocusChangeListener() {
getWidget().calendar.setFocusChangeListener(null);
}
- if (getWidget().currentResolution.getCalendarField() > Resolution.DAY
+ if (getWidget().getCurrentResolution().getCalendarField() > Resolution.DAY
.getCalendarField()) {
getWidget().calendar
.setTimeChangeListener(new TimeChangeListener() {
});
}
- if (getWidget().readonly) {
+ if (getWidget().isReadonly()) {
getWidget().calendarToggle.addStyleName(VPopupCalendar.CLASSNAME
+ "-button-readonly");
} else {
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VTextualDate;
import com.vaadin.shared.ui.datefield.Resolution;
import com.vaadin.shared.ui.datefield.TextualDateFieldState;
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
- Resolution origRes = getWidget().currentResolution;
- String oldLocale = getWidget().currentLocale;
+ Resolution origRes = getWidget().getCurrentResolution();
+ String oldLocale = getWidget().getCurrentLocale();
super.updateFromUIDL(uidl, client);
- if (origRes != getWidget().currentResolution
- || oldLocale != getWidget().currentLocale) {
+ if (origRes != getWidget().getCurrentResolution()
+ || oldLocale != getWidget().getCurrentLocale()) {
// force recreating format string
getWidget().formatStr = null;
}
getWidget().text.setTabIndex(uidl.getIntAttribute("tabindex"));
}
- if (getWidget().readonly) {
+ if (getWidget().isReadonly()) {
getWidget().text.addStyleDependentName("readonly");
} else {
getWidget().text.removeStyleDependentName("readonly");
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-import java.util.Iterator;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.dom.client.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.dom.client.MouseUpEvent;
-import com.google.gwt.event.dom.client.MouseUpHandler;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.FlexTable;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.InlineHTML;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.FocusableFlexTable;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.client.ui.nativeselect.VNativeSelect;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-@SuppressWarnings("deprecation")
-public class VCalendarPanel extends FocusableFlexTable implements
- KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler,
- MouseUpHandler, BlurHandler, FocusHandler, SubPartAware {
-
- public interface SubmitListener {
-
- /**
- * Called when calendar user triggers a submitting operation in calendar
- * panel. Eg. clicking on day or hitting enter.
- */
- void onSubmit();
-
- /**
- * On eg. ESC key.
- */
- void onCancel();
- }
-
- /**
- * Blur listener that listens to blur event from the panel
- */
- public interface FocusOutListener {
- /**
- * @return true if the calendar panel is not used after focus moves out
- */
- boolean onFocusOut(DomEvent<?> event);
- }
-
- /**
- * FocusChangeListener is notified when the panel changes its _focused_
- * value.
- */
- public interface FocusChangeListener {
- void focusChanged(Date focusedDate);
- }
-
- /**
- * Dispatches an event when the panel when time is changed
- */
- public interface TimeChangeListener {
-
- void changed(int hour, int min, int sec, int msec);
- }
-
- /**
- * Represents a Date button in the calendar
- */
- private class VEventButton extends Button {
- public VEventButton() {
- addMouseDownHandler(VCalendarPanel.this);
- addMouseOutHandler(VCalendarPanel.this);
- addMouseUpHandler(VCalendarPanel.this);
- }
- }
-
- private static final String CN_FOCUSED = "focused";
-
- private static final String CN_TODAY = "today";
-
- private static final String CN_SELECTED = "selected";
-
- private static final String CN_OFFMONTH = "offmonth";
-
- /**
- * Represents a click handler for when a user selects a value by using the
- * mouse
- */
- private ClickHandler dayClickHandler = new ClickHandler() {
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
- * .event.dom.client.ClickEvent)
- */
- @Override
- public void onClick(ClickEvent event) {
- Date newDate = ((Day) event.getSource()).getDate();
- if (newDate.getMonth() != displayedMonth.getMonth()
- || newDate.getYear() != displayedMonth.getYear()) {
- // If an off-month date was clicked, we must change the
- // displayed month and re-render the calendar (#8931)
- displayedMonth.setMonth(newDate.getMonth());
- displayedMonth.setYear(newDate.getYear());
- renderCalendar();
- }
- focusDay(newDate);
- selectFocused();
- onSubmit();
- }
- };
-
- private VEventButton prevYear;
-
- private VEventButton nextYear;
-
- private VEventButton prevMonth;
-
- private VEventButton nextMonth;
-
- private VTime time;
-
- private FlexTable days = new FlexTable();
-
- private Resolution resolution = Resolution.YEAR;
-
- private int focusedRow;
-
- private Timer mouseTimer;
-
- private Date value;
-
- private boolean enabled = true;
-
- private boolean readonly = false;
-
- private DateTimeService dateTimeService;
-
- private boolean showISOWeekNumbers;
-
- private Date displayedMonth;
-
- private Date focusedDate;
-
- private Day selectedDay;
-
- private Day focusedDay;
-
- private FocusOutListener focusOutListener;
-
- private SubmitListener submitListener;
-
- private FocusChangeListener focusChangeListener;
-
- private TimeChangeListener timeChangeListener;
-
- private boolean hasFocus = false;
-
- private VDateField parent;
-
- private boolean initialRenderDone = false;
-
- public VCalendarPanel() {
-
- setStyleName(VDateField.CLASSNAME + "-calendarpanel");
-
- /*
- * Firefox auto-repeat works correctly only if we use a key press
- * handler, other browsers handle it correctly when using a key down
- * handler
- */
- if (BrowserInfo.get().isGecko()) {
- addKeyPressHandler(this);
- } else {
- addKeyDownHandler(this);
- }
- addFocusHandler(this);
- addBlurHandler(this);
- }
-
- public void setParentField(VDateField parent) {
- this.parent = parent;
- }
-
- /**
- * Sets the focus to given date in the current view. Used when moving in the
- * calendar with the keyboard.
- *
- * @param date
- * A Date representing the day of month to be focused. Must be
- * one of the days currently visible.
- */
- private void focusDay(Date date) {
- // Only used when calender body is present
- if (resolution.getCalendarField() > Resolution.MONTH.getCalendarField()) {
- if (focusedDay != null) {
- focusedDay.removeStyleDependentName(CN_FOCUSED);
- }
-
- if (date != null && focusedDate != null) {
- focusedDate.setTime(date.getTime());
- int rowCount = days.getRowCount();
- for (int i = 0; i < rowCount; i++) {
- int cellCount = days.getCellCount(i);
- for (int j = 0; j < cellCount; j++) {
- Widget widget = days.getWidget(i, j);
- if (widget != null && widget instanceof Day) {
- Day curday = (Day) widget;
- if (curday.getDate().equals(date)) {
- curday.addStyleDependentName(CN_FOCUSED);
- focusedDay = curday;
- focusedRow = i;
- return;
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Sets the selection highlight to a given day in the current view
- *
- * @param date
- * A Date representing the day of month to be selected. Must be
- * one of the days currently visible.
- *
- */
- private void selectDate(Date date) {
- if (selectedDay != null) {
- selectedDay.removeStyleDependentName(CN_SELECTED);
- }
-
- int rowCount = days.getRowCount();
- for (int i = 0; i < rowCount; i++) {
- int cellCount = days.getCellCount(i);
- for (int j = 0; j < cellCount; j++) {
- Widget widget = days.getWidget(i, j);
- if (widget != null && widget instanceof Day) {
- Day curday = (Day) widget;
- if (curday.getDate().equals(date)) {
- curday.addStyleDependentName(CN_SELECTED);
- selectedDay = curday;
- return;
- }
- }
- }
- }
- }
-
- /**
- * Updates year, month, day from focusedDate to value
- */
- private void selectFocused() {
- if (focusedDate != null) {
- if (value == null) {
- // No previously selected value (set to null on server side).
- // Create a new date using current date and time
- value = new Date();
- }
- /*
- * #5594 set Date (day) to 1 in order to prevent any kind of
- * wrapping of months when later setting the month. (e.g. 31 ->
- * month with 30 days -> wraps to the 1st of the following month,
- * e.g. 31st of May -> 31st of April = 1st of May)
- */
- value.setDate(1);
- if (value.getYear() != focusedDate.getYear()) {
- value.setYear(focusedDate.getYear());
- }
- if (value.getMonth() != focusedDate.getMonth()) {
- value.setMonth(focusedDate.getMonth());
- }
- if (value.getDate() != focusedDate.getDate()) {
- }
- // We always need to set the date, even if it hasn't changed, since
- // it was forced to 1 above.
- value.setDate(focusedDate.getDate());
-
- selectDate(focusedDate);
- } else {
- VConsole.log("Trying to select a the focused date which is NULL!");
- }
- }
-
- protected boolean onValueChange() {
- return false;
- }
-
- public Resolution getResolution() {
- return resolution;
- }
-
- public void setResolution(Resolution resolution) {
- this.resolution = resolution;
- if (time != null) {
- time.removeFromParent();
- time = null;
- }
- }
-
- private boolean isReadonly() {
- return readonly;
- }
-
- private boolean isEnabled() {
- return enabled;
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- if (initialRenderDone) {
- // Dynamic updates to the stylename needs to render the calendar to
- // update the inner element stylenames
- renderCalendar();
- }
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- if (initialRenderDone) {
- // Dynamic updates to the stylename needs to render the calendar to
- // update the inner element stylenames
- renderCalendar();
- }
- }
-
- private void clearCalendarBody(boolean remove) {
- if (!remove) {
- // Leave the cells in place but clear their contents
-
- // This has the side effect of ensuring that the calendar always
- // contain 7 rows.
- for (int row = 1; row < 7; row++) {
- for (int col = 0; col < 8; col++) {
- days.setHTML(row, col, " ");
- }
- }
- } else if (getRowCount() > 1) {
- removeRow(1);
- days.clear();
- }
- }
-
- /**
- * Builds the top buttons and current month and year header.
- *
- * @param needsMonth
- * Should the month buttons be visible?
- */
- private void buildCalendarHeader(boolean needsMonth) {
-
- getRowFormatter().addStyleName(0,
- parent.getStylePrimaryName() + "-calendarpanel-header");
-
- if (prevMonth == null && needsMonth) {
- prevMonth = new VEventButton();
- prevMonth.setHTML("‹");
- prevMonth.setStyleName("v-button-prevmonth");
- prevMonth.setTabIndex(-1);
- nextMonth = new VEventButton();
- nextMonth.setHTML("›");
- nextMonth.setStyleName("v-button-nextmonth");
- nextMonth.setTabIndex(-1);
-
- setWidget(0, 3, nextMonth);
- setWidget(0, 1, prevMonth);
- } else if (prevMonth != null && !needsMonth) {
- // Remove month traverse buttons
- remove(prevMonth);
- remove(nextMonth);
- prevMonth = null;
- nextMonth = null;
- }
-
- if (prevYear == null) {
- prevYear = new VEventButton();
- prevYear.setHTML("«");
- prevYear.setStyleName("v-button-prevyear");
- prevYear.setTabIndex(-1);
- nextYear = new VEventButton();
- nextYear.setHTML("»");
- nextYear.setStyleName("v-button-nextyear");
- nextYear.setTabIndex(-1);
- setWidget(0, 0, prevYear);
- setWidget(0, 4, nextYear);
- }
-
- final String monthName = needsMonth ? getDateTimeService().getMonth(
- displayedMonth.getMonth()) : "";
- final int year = displayedMonth.getYear() + 1900;
-
- getFlexCellFormatter().setStyleName(0, 2,
- parent.getStylePrimaryName() + "-calendarpanel-month");
- getFlexCellFormatter().setStyleName(0, 0,
- parent.getStylePrimaryName() + "-calendarpanel-prevyear");
- getFlexCellFormatter().setStyleName(0, 4,
- parent.getStylePrimaryName() + "-calendarpanel-nextyear");
- getFlexCellFormatter().setStyleName(0, 3,
- parent.getStylePrimaryName() + "-calendarpanel-nextmonth");
- getFlexCellFormatter().setStyleName(0, 1,
- parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
-
- setHTML(0, 2, "<span class=\"" + parent.getStylePrimaryName()
- + "-calendarpanel-month\">" + monthName + " " + year
- + "</span>");
- }
-
- private DateTimeService getDateTimeService() {
- return dateTimeService;
- }
-
- public void setDateTimeService(DateTimeService dateTimeService) {
- this.dateTimeService = dateTimeService;
- }
-
- /**
- * Returns whether ISO 8601 week numbers should be shown in the value
- * selector or not. ISO 8601 defines that a week always starts with a Monday
- * so the week numbers are only shown if this is the case.
- *
- * @return true if week number should be shown, false otherwise
- */
- public boolean isShowISOWeekNumbers() {
- return showISOWeekNumbers;
- }
-
- public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
- this.showISOWeekNumbers = showISOWeekNumbers;
- }
-
- /**
- * Builds the day and time selectors of the calendar.
- */
- private void buildCalendarBody() {
-
- final int weekColumn = 0;
- final int firstWeekdayColumn = 1;
- final int headerRow = 0;
-
- setWidget(1, 0, days);
- setCellPadding(0);
- setCellSpacing(0);
- getFlexCellFormatter().setColSpan(1, 0, 5);
- getFlexCellFormatter().setStyleName(1, 0,
- parent.getStylePrimaryName() + "-calendarpanel-body");
-
- days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
- "v-week");
- days.setHTML(headerRow, weekColumn, "<strong></strong>");
- // Hide the week column if week numbers are not to be displayed.
- days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
- isShowISOWeekNumbers());
-
- days.getRowFormatter().setStyleName(headerRow,
- parent.getStylePrimaryName() + "-calendarpanel-weekdays");
-
- if (isShowISOWeekNumbers()) {
- days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
- "v-first");
- days.getFlexCellFormatter().setStyleName(headerRow,
- firstWeekdayColumn, "");
- days.getRowFormatter()
- .addStyleName(
- headerRow,
- parent.getStylePrimaryName()
- + "-calendarpanel-weeknumbers");
- } else {
- days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
- days.getFlexCellFormatter().setStyleName(headerRow,
- firstWeekdayColumn, "v-first");
- }
-
- days.getFlexCellFormatter().setStyleName(headerRow,
- firstWeekdayColumn + 6, "v-last");
-
- // Print weekday names
- final int firstDay = getDateTimeService().getFirstDayOfWeek();
- for (int i = 0; i < 7; i++) {
- int day = i + firstDay;
- if (day > 6) {
- day = 0;
- }
- if (getResolution().getCalendarField() > Resolution.MONTH
- .getCalendarField()) {
- days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
- + getDateTimeService().getShortDay(day) + "</strong>");
- } else {
- days.setHTML(headerRow, firstWeekdayColumn + i, "");
- }
- }
-
- // Zero out hours, minutes, seconds, and milliseconds to compare dates
- // without time part
- final Date tmp = new Date();
- final Date today = new Date(tmp.getYear(), tmp.getMonth(),
- tmp.getDate());
-
- final Date selectedDate = value == null ? null : new Date(
- value.getYear(), value.getMonth(), value.getDate());
-
- final int startWeekDay = getDateTimeService().getStartWeekDay(
- displayedMonth);
- final Date curr = (Date) displayedMonth.clone();
- // Start from the first day of the week that at least partially belongs
- // to the current month
- curr.setDate(1 - startWeekDay);
-
- // No month has more than 6 weeks so 6 is a safe maximum for rows.
- for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
- for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
-
- // Actually write the day of month
- Day day = new Day((Date) curr.clone());
- day.setStyleName(parent.getStylePrimaryName()
- + "-calendarpanel-day");
-
- if (curr.equals(selectedDate)) {
- day.addStyleDependentName(CN_SELECTED);
- selectedDay = day;
- }
- if (curr.equals(today)) {
- day.addStyleDependentName(CN_TODAY);
- }
- if (curr.equals(focusedDate)) {
- focusedDay = day;
- focusedRow = weekOfMonth;
- if (hasFocus) {
- day.addStyleDependentName(CN_FOCUSED);
- }
- }
- if (curr.getMonth() != displayedMonth.getMonth()) {
- day.addStyleDependentName(CN_OFFMONTH);
- }
-
- days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
-
- // ISO week numbers if requested
- days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
- isShowISOWeekNumbers());
- if (isShowISOWeekNumbers()) {
- final String baseCssClass = parent.getStylePrimaryName()
- + "-calendarpanel-weeknumber";
- String weekCssClass = baseCssClass;
-
- int weekNumber = DateTimeService.getISOWeekNumber(curr);
-
- days.setHTML(weekOfMonth, 0, "<span class=\""
- + weekCssClass + "\"" + ">" + weekNumber
- + "</span>");
- }
- curr.setDate(curr.getDate() + 1);
- }
- }
- }
-
- /**
- * Do we need the time selector
- *
- * @return True if it is required
- */
- private boolean isTimeSelectorNeeded() {
- return getResolution().getCalendarField() > Resolution.DAY
- .getCalendarField();
- }
-
- /**
- * Updates the calendar and text field with the selected dates.
- */
- public void renderCalendar() {
-
- super.setStylePrimaryName(parent.getStylePrimaryName()
- + "-calendarpanel");
-
- if (focusedDate == null) {
- Date now = new Date();
- // focusedDate must have zero hours, mins, secs, millisecs
- focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
- displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
- }
-
- if (getResolution().getCalendarField() <= Resolution.MONTH
- .getCalendarField() && focusChangeListener != null) {
- focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
- }
-
- final boolean needsMonth = getResolution().getCalendarField() > Resolution.YEAR
- .getCalendarField();
- boolean needsBody = getResolution().getCalendarField() >= Resolution.DAY
- .getCalendarField();
- buildCalendarHeader(needsMonth);
- clearCalendarBody(!needsBody);
- if (needsBody) {
- buildCalendarBody();
- }
-
- if (isTimeSelectorNeeded() && time == null) {
- time = new VTime();
- setWidget(2, 0, time);
- getFlexCellFormatter().setColSpan(2, 0, 5);
- getFlexCellFormatter().setStyleName(2, 0,
- parent.getStylePrimaryName() + "-calendarpanel-time");
- } else if (isTimeSelectorNeeded()) {
- time.updateTimes();
- } else if (time != null) {
- remove(time);
- }
-
- initialRenderDone = true;
- }
-
- /**
- * Moves the focus forward the given number of days.
- */
- private void focusNextDay(int days) {
- int oldMonth = focusedDate.getMonth();
- int oldYear = focusedDate.getYear();
- focusedDate.setDate(focusedDate.getDate() + days);
-
- if (focusedDate.getMonth() == oldMonth
- && focusedDate.getYear() == oldYear) {
- // Month did not change, only move the selection
- focusDay(focusedDate);
- } else {
- // If the month changed we need to re-render the calendar
- displayedMonth.setMonth(focusedDate.getMonth());
- displayedMonth.setYear(focusedDate.getYear());
- renderCalendar();
- }
- }
-
- /**
- * Moves the focus backward the given number of days.
- */
- private void focusPreviousDay(int days) {
- focusNextDay(-days);
- }
-
- /**
- * Selects the next month
- */
- private void focusNextMonth() {
-
- int currentMonth = focusedDate.getMonth();
- focusedDate.setMonth(currentMonth + 1);
- int requestedMonth = (currentMonth + 1) % 12;
-
- /*
- * If the selected value was e.g. 31.3 the new value would be 31.4 but
- * this value is invalid so the new value will be 1.5. This is taken
- * care of by decreasing the value until we have the correct month.
- */
- while (focusedDate.getMonth() != requestedMonth) {
- focusedDate.setDate(focusedDate.getDate() - 1);
- }
- displayedMonth.setMonth(displayedMonth.getMonth() + 1);
-
- renderCalendar();
- }
-
- /**
- * Selects the previous month
- */
- private void focusPreviousMonth() {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setMonth(currentMonth - 1);
-
- /*
- * If the selected value was e.g. 31.12 the new value would be 31.11 but
- * this value is invalid so the new value will be 1.12. This is taken
- * care of by decreasing the value until we have the correct month.
- */
- while (focusedDate.getMonth() == currentMonth) {
- focusedDate.setDate(focusedDate.getDate() - 1);
- }
- displayedMonth.setMonth(displayedMonth.getMonth() - 1);
-
- renderCalendar();
- }
-
- /**
- * Selects the previous year
- */
- private void focusPreviousYear(int years) {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setYear(focusedDate.getYear() - years);
- displayedMonth.setYear(displayedMonth.getYear() - years);
- /*
- * If the focused date was a leap day (Feb 29), the new date becomes Mar
- * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
- */
- if (focusedDate.getMonth() != currentMonth) {
- focusedDate.setDate(0);
- }
- renderCalendar();
- }
-
- /**
- * Selects the next year
- */
- private void focusNextYear(int years) {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setYear(focusedDate.getYear() + years);
- displayedMonth.setYear(displayedMonth.getYear() + years);
- /*
- * If the focused date was a leap day (Feb 29), the new date becomes Mar
- * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
- */
- if (focusedDate.getMonth() != currentMonth) {
- focusedDate.setDate(0);
- }
- renderCalendar();
- }
-
- /**
- * Handles a user click on the component
- *
- * @param sender
- * The component that was clicked
- * @param updateVariable
- * Should the value field be updated
- *
- */
- private void processClickEvent(Widget sender) {
- if (!isEnabled() || isReadonly()) {
- return;
- }
- if (sender == prevYear) {
- focusPreviousYear(1);
- } else if (sender == nextYear) {
- focusNextYear(1);
- } else if (sender == prevMonth) {
- focusPreviousMonth();
- } else if (sender == nextMonth) {
- focusNextMonth();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
- @Override
- public void onKeyDown(KeyDownEvent event) {
- handleKeyPress(event);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
- * .gwt.event.dom.client.KeyPressEvent)
- */
- @Override
- public void onKeyPress(KeyPressEvent event) {
- handleKeyPress(event);
- }
-
- /**
- * Handles the keypress from both the onKeyPress event and the onKeyDown
- * event
- *
- * @param event
- * The keydown/keypress event
- */
- private void handleKeyPress(DomEvent<?> event) {
- if (time != null
- && time.getElement().isOrHasChild(
- (Node) event.getNativeEvent().getEventTarget().cast())) {
- int nativeKeyCode = event.getNativeEvent().getKeyCode();
- if (nativeKeyCode == getSelectKey()) {
- onSubmit(); // submit happens if enter key hit down on listboxes
- event.preventDefault();
- event.stopPropagation();
- }
- return;
- }
-
- // Check tabs
- int keycode = event.getNativeEvent().getKeyCode();
- if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) {
- if (onTabOut(event)) {
- return;
- }
- }
-
- // Handle the navigation
- if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey()
- || event.getNativeEvent().getMetaKey(), event.getNativeEvent()
- .getShiftKey())) {
- event.preventDefault();
- }
-
- }
-
- /**
- * Notifies submit-listeners of a submit event
- */
- private void onSubmit() {
- if (getSubmitListener() != null) {
- getSubmitListener().onSubmit();
- }
- }
-
- /**
- * Notifies submit-listeners of a cancel event
- */
- private void onCancel() {
- if (getSubmitListener() != null) {
- getSubmitListener().onCancel();
- }
- }
-
- /**
- * Handles the keyboard navigation when the resolution is set to years.
- *
- * @param keycode
- * The keycode to process
- * @param ctrl
- * Is ctrl pressed?
- * @param shift
- * is shift pressed
- * @return Returns true if the keycode was processed, else false
- */
- protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
- boolean shift) {
-
- // Ctrl and Shift selection not supported
- if (ctrl || shift) {
- return false;
- }
-
- else if (keycode == getPreviousKey()) {
- focusNextYear(10); // Add 10 years
- return true;
- }
-
- else if (keycode == getForwardKey()) {
- focusNextYear(1); // Add 1 year
- return true;
- }
-
- else if (keycode == getNextKey()) {
- focusPreviousYear(10); // Subtract 10 years
- return true;
- }
-
- else if (keycode == getBackwardKey()) {
- focusPreviousYear(1); // Subtract 1 year
- return true;
-
- } else if (keycode == getSelectKey()) {
- value = (Date) focusedDate.clone();
- onSubmit();
- return true;
-
- } else if (keycode == getResetKey()) {
- // Restore showing value the selected value
- focusedDate.setTime(value.getTime());
- renderCalendar();
- return true;
-
- } else if (keycode == getCloseKey()) {
- // TODO fire listener, on users responsibility??
-
- return true;
- }
- return false;
- }
-
- /**
- * Handle the keyboard navigation when the resolution is set to MONTH
- *
- * @param keycode
- * The keycode to handle
- * @param ctrl
- * Was the ctrl key pressed?
- * @param shift
- * Was the shift key pressed?
- * @return
- */
- protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
- boolean shift) {
-
- // Ctrl selection not supported
- if (ctrl) {
- return false;
-
- } else if (keycode == getPreviousKey()) {
- focusNextYear(1); // Add 1 year
- return true;
-
- } else if (keycode == getForwardKey()) {
- focusNextMonth(); // Add 1 month
- return true;
-
- } else if (keycode == getNextKey()) {
- focusPreviousYear(1); // Subtract 1 year
- return true;
-
- } else if (keycode == getBackwardKey()) {
- focusPreviousMonth(); // Subtract 1 month
- return true;
-
- } else if (keycode == getSelectKey()) {
- value = (Date) focusedDate.clone();
- onSubmit();
- return true;
-
- } else if (keycode == getResetKey()) {
- // Restore showing value the selected value
- focusedDate.setTime(value.getTime());
- renderCalendar();
- return true;
-
- } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
-
- // TODO fire close event
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Handle keyboard navigation what the resolution is set to DAY
- *
- * @param keycode
- * The keycode to handle
- * @param ctrl
- * Was the ctrl key pressed?
- * @param shift
- * Was the shift key pressed?
- * @return Return true if the key press was handled by the method, else
- * return false.
- */
- protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
- boolean shift) {
-
- // Ctrl key is not in use
- if (ctrl) {
- return false;
- }
-
- /*
- * Jumps to the next day.
- */
- if (keycode == getForwardKey() && !shift) {
- focusNextDay(1);
- return true;
-
- /*
- * Jumps to the previous day
- */
- } else if (keycode == getBackwardKey() && !shift) {
- focusPreviousDay(1);
- return true;
-
- /*
- * Jumps one week forward in the calendar
- */
- } else if (keycode == getNextKey() && !shift) {
- focusNextDay(7);
- return true;
-
- /*
- * Jumps one week back in the calendar
- */
- } else if (keycode == getPreviousKey() && !shift) {
- focusPreviousDay(7);
- return true;
-
- /*
- * Selects the value that is chosen
- */
- } else if (keycode == getSelectKey() && !shift) {
- selectFocused();
- onSubmit(); // submit
- return true;
-
- } else if (keycode == getCloseKey()) {
- onCancel();
- // TODO close event
-
- return true;
-
- /*
- * Jumps to the next month
- */
- } else if (shift && keycode == getForwardKey()) {
- focusNextMonth();
- return true;
-
- /*
- * Jumps to the previous month
- */
- } else if (shift && keycode == getBackwardKey()) {
- focusPreviousMonth();
- return true;
-
- /*
- * Jumps to the next year
- */
- } else if (shift && keycode == getPreviousKey()) {
- focusNextYear(1);
- return true;
-
- /*
- * Jumps to the previous year
- */
- } else if (shift && keycode == getNextKey()) {
- focusPreviousYear(1);
- return true;
-
- /*
- * Resets the selection
- */
- } else if (keycode == getResetKey() && !shift) {
- // Restore showing value the selected value
- focusedDate = new Date(value.getYear(), value.getMonth(),
- value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
- renderCalendar();
- return true;
- }
-
- return false;
- }
-
- /**
- * Handles the keyboard navigation
- *
- * @param keycode
- * The key code that was pressed
- * @param ctrl
- * Was the ctrl key pressed
- * @param shift
- * Was the shift key pressed
- * @return Return true if key press was handled by the component, else
- * return false
- */
- protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
- if (!isEnabled() || isReadonly()) {
- return false;
- }
-
- else if (resolution == Resolution.YEAR) {
- return handleNavigationYearMode(keycode, ctrl, shift);
- }
-
- else if (resolution == Resolution.MONTH) {
- return handleNavigationMonthMode(keycode, ctrl, shift);
- }
-
- else if (resolution == Resolution.DAY) {
- return handleNavigationDayMode(keycode, ctrl, shift);
- }
-
- else {
- return handleNavigationDayMode(keycode, ctrl, shift);
- }
-
- }
-
- /**
- * Returns the reset key which will reset the calendar to the previous
- * selection. By default this is backspace but it can be overriden to change
- * the key to whatever you want.
- *
- * @return
- */
- protected int getResetKey() {
- return KeyCodes.KEY_BACKSPACE;
- }
-
- /**
- * Returns the select key which selects the value. By default this is the
- * enter key but it can be changed to whatever you like by overriding this
- * method.
- *
- * @return
- */
- protected int getSelectKey() {
- return KeyCodes.KEY_ENTER;
- }
-
- /**
- * Returns the key that closes the popup window if this is a VPopopCalendar.
- * Else this does nothing. By default this is the Escape key but you can
- * change the key to whatever you want by overriding this method.
- *
- * @return
- */
- protected int getCloseKey() {
- return KeyCodes.KEY_ESCAPE;
- }
-
- /**
- * The key that selects the next day in the calendar. By default this is the
- * right arrow key but by overriding this method it can be changed to
- * whatever you like.
- *
- * @return
- */
- protected int getForwardKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- /**
- * The key that selects the previous day in the calendar. By default this is
- * the left arrow key but by overriding this method it can be changed to
- * whatever you like.
- *
- * @return
- */
- protected int getBackwardKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * The key that selects the next week in the calendar. By default this is
- * the down arrow key but by overriding this method it can be changed to
- * whatever you like.
- *
- * @return
- */
- protected int getNextKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * The key that selects the previous week in the calendar. By default this
- * is the up arrow key but by overriding this method it can be changed to
- * whatever you like.
- *
- * @return
- */
- protected int getPreviousKey() {
- return KeyCodes.KEY_UP;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
- * .gwt.event.dom.client.MouseOutEvent)
- */
- @Override
- public void onMouseOut(MouseOutEvent event) {
- if (mouseTimer != null) {
- mouseTimer.cancel();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
- * .gwt.event.dom.client.MouseDownEvent)
- */
- @Override
- public void onMouseDown(MouseDownEvent event) {
- // Allow user to click-n-hold for fast-forward or fast-rewind.
- // Timer is first used for a 500ms delay after mousedown. After that has
- // elapsed, another timer is triggered to go off every 150ms. Both
- // timers are cancelled on mouseup or mouseout.
- if (event.getSource() instanceof VEventButton) {
- final VEventButton sender = (VEventButton) event.getSource();
- processClickEvent(sender);
- mouseTimer = new Timer() {
- @Override
- public void run() {
- mouseTimer = new Timer() {
- @Override
- public void run() {
- processClickEvent(sender);
- }
- };
- mouseTimer.scheduleRepeating(150);
- }
- };
- mouseTimer.schedule(500);
- }
-
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
- * .event.dom.client.MouseUpEvent)
- */
- @Override
- public void onMouseUp(MouseUpEvent event) {
- if (mouseTimer != null) {
- mouseTimer.cancel();
- }
- }
-
- /**
- * Sets the data of the Panel.
- *
- * @param currentDate
- * The date to set
- */
- public void setDate(Date currentDate) {
-
- // Check that we are not re-rendering an already active date
- if (currentDate == value && currentDate != null) {
- return;
- }
-
- Date oldDisplayedMonth = displayedMonth;
- value = currentDate;
-
- if (value == null) {
- focusedDate = displayedMonth = null;
- } else {
- focusedDate = new Date(value.getYear(), value.getMonth(),
- value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
- }
-
- // Re-render calendar if the displayed month is changed,
- // or if a time selector is needed but does not exist.
- if ((isTimeSelectorNeeded() && time == null)
- || oldDisplayedMonth == null || value == null
- || oldDisplayedMonth.getYear() != value.getYear()
- || oldDisplayedMonth.getMonth() != value.getMonth()) {
- renderCalendar();
- } else {
- focusDay(focusedDate);
- selectFocused();
- if (isTimeSelectorNeeded()) {
- time.updateTimes();
- }
- }
-
- if (!hasFocus) {
- focusDay(null);
- }
- }
-
- /**
- * TimeSelector is a widget consisting of list boxes that modifie the Date
- * object that is given for.
- *
- */
- public class VTime extends FlowPanel implements ChangeHandler {
-
- private ListBox hours;
-
- private ListBox mins;
-
- private ListBox sec;
-
- private ListBox ampm;
-
- /**
- * Constructor
- */
- public VTime() {
- super();
- setStyleName(VDateField.CLASSNAME + "-time");
- buildTime();
- }
-
- private ListBox createListBox() {
- ListBox lb = new ListBox();
- lb.setStyleName(VNativeSelect.CLASSNAME);
- lb.addChangeHandler(this);
- lb.addBlurHandler(VCalendarPanel.this);
- lb.addFocusHandler(VCalendarPanel.this);
- return lb;
- }
-
- /**
- * Constructs the ListBoxes and updates their value
- *
- * @param redraw
- * Should new instances of the listboxes be created
- */
- private void buildTime() {
- clear();
-
- hours = createListBox();
- if (getDateTimeService().isTwelveHourClock()) {
- hours.addItem("12");
- for (int i = 1; i < 12; i++) {
- hours.addItem((i < 10) ? "0" + i : "" + i);
- }
- } else {
- for (int i = 0; i < 24; i++) {
- hours.addItem((i < 10) ? "0" + i : "" + i);
- }
- }
-
- hours.addChangeHandler(this);
- if (getDateTimeService().isTwelveHourClock()) {
- ampm = createListBox();
- final String[] ampmText = getDateTimeService().getAmPmStrings();
- ampm.addItem(ampmText[0]);
- ampm.addItem(ampmText[1]);
- ampm.addChangeHandler(this);
- }
-
- if (getResolution().getCalendarField() >= Resolution.MINUTE
- .getCalendarField()) {
- mins = createListBox();
- for (int i = 0; i < 60; i++) {
- mins.addItem((i < 10) ? "0" + i : "" + i);
- }
- mins.addChangeHandler(this);
- }
- if (getResolution().getCalendarField() >= Resolution.SECOND
- .getCalendarField()) {
- sec = createListBox();
- for (int i = 0; i < 60; i++) {
- sec.addItem((i < 10) ? "0" + i : "" + i);
- }
- sec.addChangeHandler(this);
- }
-
- final String delimiter = getDateTimeService().getClockDelimeter();
- if (isReadonly()) {
- int h = 0;
- if (value != null) {
- h = value.getHours();
- }
- if (getDateTimeService().isTwelveHourClock()) {
- h -= h < 12 ? 0 : 12;
- }
- add(new VLabel(h < 10 ? "0" + h : "" + h));
- } else {
- add(hours);
- }
-
- if (getResolution().getCalendarField() >= Resolution.MINUTE
- .getCalendarField()) {
- add(new VLabel(delimiter));
- if (isReadonly()) {
- final int m = mins.getSelectedIndex();
- add(new VLabel(m < 10 ? "0" + m : "" + m));
- } else {
- add(mins);
- }
- }
- if (getResolution().getCalendarField() >= Resolution.SECOND
- .getCalendarField()) {
- add(new VLabel(delimiter));
- if (isReadonly()) {
- final int s = sec.getSelectedIndex();
- add(new VLabel(s < 10 ? "0" + s : "" + s));
- } else {
- add(sec);
- }
- }
- if (getResolution() == Resolution.HOUR) {
- add(new VLabel(delimiter + "00")); // o'clock
- }
- if (getDateTimeService().isTwelveHourClock()) {
- add(new VLabel(" "));
- if (isReadonly()) {
- int i = 0;
- if (value != null) {
- i = (value.getHours() < 12) ? 0 : 1;
- }
- add(new VLabel(ampm.getItemText(i)));
- } else {
- add(ampm);
- }
- }
-
- if (isReadonly()) {
- return;
- }
-
- // Update times
- updateTimes();
-
- ListBox lastDropDown = getLastDropDown();
- lastDropDown.addKeyDownHandler(new KeyDownHandler() {
- @Override
- public void onKeyDown(KeyDownEvent event) {
- boolean shiftKey = event.getNativeEvent().getShiftKey();
- if (shiftKey) {
- return;
- } else {
- int nativeKeyCode = event.getNativeKeyCode();
- if (nativeKeyCode == KeyCodes.KEY_TAB) {
- onTabOut(event);
- }
- }
- }
- });
-
- }
-
- private ListBox getLastDropDown() {
- int i = getWidgetCount() - 1;
- while (i >= 0) {
- Widget widget = getWidget(i);
- if (widget instanceof ListBox) {
- return (ListBox) widget;
- }
- i--;
- }
- return null;
- }
-
- /**
- * Updates the valus to correspond to the values in value
- */
- public void updateTimes() {
- boolean selected = true;
- if (value == null) {
- value = new Date();
- selected = false;
- }
- if (getDateTimeService().isTwelveHourClock()) {
- int h = value.getHours();
- ampm.setSelectedIndex(h < 12 ? 0 : 1);
- h -= ampm.getSelectedIndex() * 12;
- hours.setSelectedIndex(h);
- } else {
- hours.setSelectedIndex(value.getHours());
- }
- if (getResolution().getCalendarField() >= Resolution.MINUTE
- .getCalendarField()) {
- mins.setSelectedIndex(value.getMinutes());
- }
- if (getResolution().getCalendarField() >= Resolution.SECOND
- .getCalendarField()) {
- sec.setSelectedIndex(value.getSeconds());
- }
- if (getDateTimeService().isTwelveHourClock()) {
- ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1);
- }
-
- hours.setEnabled(isEnabled());
- if (mins != null) {
- mins.setEnabled(isEnabled());
- }
- if (sec != null) {
- sec.setEnabled(isEnabled());
- }
- if (ampm != null) {
- ampm.setEnabled(isEnabled());
- }
-
- }
-
- private int getMilliseconds() {
- return DateTimeService.getMilliseconds(value);
- }
-
- private DateTimeService getDateTimeService() {
- if (dateTimeService == null) {
- dateTimeService = new DateTimeService();
- }
- return dateTimeService;
- }
-
- /*
- * (non-Javadoc) VT
- *
- * @see
- * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
- * .event.dom.client.ChangeEvent)
- */
- @Override
- public void onChange(ChangeEvent event) {
- /*
- * Value from dropdowns gets always set for the value. Like year and
- * month when resolution is month or year.
- */
- if (event.getSource() == hours) {
- int h = hours.getSelectedIndex();
- if (getDateTimeService().isTwelveHourClock()) {
- h = h + ampm.getSelectedIndex() * 12;
- }
- value.setHours(h);
- if (timeChangeListener != null) {
- timeChangeListener.changed(h, value.getMinutes(),
- value.getSeconds(),
- DateTimeService.getMilliseconds(value));
- }
- event.preventDefault();
- event.stopPropagation();
- } else if (event.getSource() == mins) {
- final int m = mins.getSelectedIndex();
- value.setMinutes(m);
- if (timeChangeListener != null) {
- timeChangeListener.changed(value.getHours(), m,
- value.getSeconds(),
- DateTimeService.getMilliseconds(value));
- }
- event.preventDefault();
- event.stopPropagation();
- } else if (event.getSource() == sec) {
- final int s = sec.getSelectedIndex();
- value.setSeconds(s);
- if (timeChangeListener != null) {
- timeChangeListener.changed(value.getHours(),
- value.getMinutes(), s,
- DateTimeService.getMilliseconds(value));
- }
- event.preventDefault();
- event.stopPropagation();
- } else if (event.getSource() == ampm) {
- final int h = hours.getSelectedIndex()
- + (ampm.getSelectedIndex() * 12);
- value.setHours(h);
- if (timeChangeListener != null) {
- timeChangeListener.changed(h, value.getMinutes(),
- value.getSeconds(),
- DateTimeService.getMilliseconds(value));
- }
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- }
-
- /**
- * A widget representing a single day in the calendar panel.
- */
- private class Day extends InlineHTML {
- private final Date date;
-
- Day(Date date) {
- super("" + date.getDate());
- this.date = date;
- addClickHandler(dayClickHandler);
- }
-
- public Date getDate() {
- return date;
- }
- }
-
- public Date getDate() {
- return value;
- }
-
- /**
- * If true should be returned if the panel will not be used after this
- * event.
- *
- * @param event
- * @return
- */
- protected boolean onTabOut(DomEvent<?> event) {
- if (focusOutListener != null) {
- return focusOutListener.onFocusOut(event);
- }
- return false;
- }
-
- /**
- * A focus out listener is triggered when the panel loosed focus. This can
- * happen either after a user clicks outside the panel or tabs out.
- *
- * @param listener
- * The listener to trigger
- */
- public void setFocusOutListener(FocusOutListener listener) {
- focusOutListener = listener;
- }
-
- /**
- * The submit listener is called when the user selects a value from the
- * calender either by clicking the day or selects it by keyboard.
- *
- * @param submitListener
- * The listener to trigger
- */
- public void setSubmitListener(SubmitListener submitListener) {
- this.submitListener = submitListener;
- }
-
- /**
- * The given FocusChangeListener is notified when the focused date changes
- * by user either clicking on a new date or by using the keyboard.
- *
- * @param listener
- * The FocusChangeListener to be notified
- */
- public void setFocusChangeListener(FocusChangeListener listener) {
- focusChangeListener = listener;
- }
-
- /**
- * The time change listener is triggered when the user changes the time.
- *
- * @param listener
- */
- public void setTimeChangeListener(TimeChangeListener listener) {
- timeChangeListener = listener;
- }
-
- /**
- * Returns the submit listener that listens to selection made from the panel
- *
- * @return The listener or NULL if no listener has been set
- */
- public SubmitListener getSubmitListener() {
- return submitListener;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
- * .dom.client.BlurEvent)
- */
- @Override
- public void onBlur(final BlurEvent event) {
- if (event.getSource() instanceof VCalendarPanel) {
- hasFocus = false;
- focusDay(null);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
- * .dom.client.FocusEvent)
- */
- @Override
- public void onFocus(FocusEvent event) {
- if (event.getSource() instanceof VCalendarPanel) {
- hasFocus = true;
-
- // Focuses the current day if the calendar shows the days
- if (focusedDay != null) {
- focusDay(focusedDate);
- }
- }
- }
-
- private static final String SUBPART_NEXT_MONTH = "nextmon";
- private static final String SUBPART_PREV_MONTH = "prevmon";
-
- private static final String SUBPART_NEXT_YEAR = "nexty";
- private static final String SUBPART_PREV_YEAR = "prevy";
- private static final String SUBPART_HOUR_SELECT = "h";
- private static final String SUBPART_MINUTE_SELECT = "m";
- private static final String SUBPART_SECS_SELECT = "s";
- private static final String SUBPART_MSECS_SELECT = "ms";
- private static final String SUBPART_AMPM_SELECT = "ampm";
- private static final String SUBPART_DAY = "day";
- private static final String SUBPART_MONTH_YEAR_HEADER = "header";
-
- @Override
- public String getSubPartName(Element subElement) {
- if (contains(nextMonth, subElement)) {
- return SUBPART_NEXT_MONTH;
- } else if (contains(prevMonth, subElement)) {
- return SUBPART_PREV_MONTH;
- } else if (contains(nextYear, subElement)) {
- return SUBPART_NEXT_YEAR;
- } else if (contains(prevYear, subElement)) {
- return SUBPART_PREV_YEAR;
- } else if (contains(days, subElement)) {
- // Day, find out which dayOfMonth and use that as the identifier
- Day day = Util.findWidget(subElement, Day.class);
- if (day != null) {
- Date date = day.getDate();
- int id = date.getDate();
- // Zero or negative ids map to days of the preceding month,
- // past-the-end-of-month ids to days of the following month
- if (date.getMonth() < displayedMonth.getMonth()) {
- id -= DateTimeService.getNumberOfDaysInMonth(date);
- } else if (date.getMonth() > displayedMonth.getMonth()) {
- id += DateTimeService
- .getNumberOfDaysInMonth(displayedMonth);
- }
- return SUBPART_DAY + id;
- }
- } else if (time != null) {
- if (contains(time.hours, subElement)) {
- return SUBPART_HOUR_SELECT;
- } else if (contains(time.mins, subElement)) {
- return SUBPART_MINUTE_SELECT;
- } else if (contains(time.sec, subElement)) {
- return SUBPART_SECS_SELECT;
- } else if (contains(time.ampm, subElement)) {
- return SUBPART_AMPM_SELECT;
-
- }
- } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) {
- return SUBPART_MONTH_YEAR_HEADER;
- }
-
- return null;
- }
-
- /**
- * Checks if subElement is inside the widget DOM hierarchy.
- *
- * @param w
- * @param subElement
- * @return true if {@code w} is a parent of subElement, false otherwise.
- */
- private boolean contains(Widget w, Element subElement) {
- if (w == null || w.getElement() == null) {
- return false;
- }
-
- return w.getElement().isOrHasChild(subElement);
- }
-
- @Override
- public Element getSubPartElement(String subPart) {
- if (SUBPART_NEXT_MONTH.equals(subPart)) {
- return nextMonth.getElement();
- }
- if (SUBPART_PREV_MONTH.equals(subPart)) {
- return prevMonth.getElement();
- }
- if (SUBPART_NEXT_YEAR.equals(subPart)) {
- return nextYear.getElement();
- }
- if (SUBPART_PREV_YEAR.equals(subPart)) {
- return prevYear.getElement();
- }
- if (SUBPART_HOUR_SELECT.equals(subPart)) {
- return time.hours.getElement();
- }
- if (SUBPART_MINUTE_SELECT.equals(subPart)) {
- return time.mins.getElement();
- }
- if (SUBPART_SECS_SELECT.equals(subPart)) {
- return time.sec.getElement();
- }
- if (SUBPART_AMPM_SELECT.equals(subPart)) {
- return time.ampm.getElement();
- }
- if (subPart.startsWith(SUBPART_DAY)) {
- // Zero or negative ids map to days in the preceding month,
- // past-the-end-of-month ids to days in the following month
- int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY
- .length()));
- Date date = new Date(displayedMonth.getYear(),
- displayedMonth.getMonth(), dayOfMonth);
- Iterator<Widget> iter = days.iterator();
- while (iter.hasNext()) {
- Widget w = iter.next();
- if (w instanceof Day) {
- Day day = (Day) w;
- if (day.getDate().equals(date)) {
- return day.getElement();
- }
- }
- }
- }
-
- if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
- return (Element) getCellFormatter().getElement(0, 2).getChild(0);
- }
- return null;
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- if (mouseTimer != null) {
- mouseTimer.cancel();
- }
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.ui.Field;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-public class VDateField extends FlowPanel implements Field {
-
- public static final String CLASSNAME = "v-datefield";
-
- protected String paintableId;
-
- protected ApplicationConnection client;
-
- protected boolean immediate;
-
- @Deprecated
- public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
- @Deprecated
- public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
- @Deprecated
- public static final Resolution RESOLUTION_DAY = Resolution.DAY;
- @Deprecated
- public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
- @Deprecated
- public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
- @Deprecated
- public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
-
- static String resolutionToString(Resolution res) {
- if (res.getCalendarField() > Resolution.DAY.getCalendarField()) {
- return "full";
- }
- if (res == Resolution.DAY) {
- return "day";
- }
- if (res == Resolution.MONTH) {
- return "month";
- }
- return "year";
- }
-
- protected Resolution currentResolution = Resolution.YEAR;
-
- protected String currentLocale;
-
- protected boolean readonly;
-
- protected boolean enabled;
-
- /**
- * The date that is selected in the date field. Null if an invalid date is
- * specified.
- */
- private Date date = null;
-
- protected DateTimeService dts;
-
- protected boolean showISOWeekNumbers = false;
-
- public VDateField() {
- setStyleName(CLASSNAME);
- dts = new DateTimeService();
- }
-
- /*
- * We need this redundant native function because Java's Date object doesn't
- * have a setMilliseconds method.
- */
- protected static native double getTime(int y, int m, int d, int h, int mi,
- int s, int ms)
- /*-{
- try {
- var date = new Date(2000,1,1,1); // don't use current date here
- if(y && y >= 0) date.setFullYear(y);
- if(m && m >= 1) date.setMonth(m-1);
- if(d && d >= 0) date.setDate(d);
- if(h >= 0) date.setHours(h);
- if(mi >= 0) date.setMinutes(mi);
- if(s >= 0) date.setSeconds(s);
- if(ms >= 0) date.setMilliseconds(ms);
- return date.getTime();
- } catch (e) {
- // TODO print some error message on the console
- //console.log(e);
- return (new Date()).getTime();
- }
- }-*/;
-
- public int getMilliseconds() {
- return DateTimeService.getMilliseconds(date);
- }
-
- public void setMilliseconds(int ms) {
- DateTimeService.setMilliseconds(date, ms);
- }
-
- public Resolution getCurrentResolution() {
- return currentResolution;
- }
-
- public void setCurrentResolution(Resolution currentResolution) {
- this.currentResolution = currentResolution;
- }
-
- public String getCurrentLocale() {
- return currentLocale;
- }
-
- public void setCurrentLocale(String currentLocale) {
- this.currentLocale = currentLocale;
- }
-
- public Date getCurrentDate() {
- return date;
- }
-
- public void setCurrentDate(Date date) {
- this.date = date;
- }
-
- public boolean isImmediate() {
- return immediate;
- }
-
- public boolean isReadonly() {
- return readonly;
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public DateTimeService getDateTimeService() {
- return dts;
- }
-
- public String getId() {
- return paintableId;
- }
-
- public ApplicationConnection getClient() {
- return client;
- }
-
- /**
- * Returns whether ISO 8601 week numbers should be shown in the date
- * selector or not. ISO 8601 defines that a week always starts with a Monday
- * so the week numbers are only shown if this is the case.
- *
- * @return true if week number should be shown, false otherwise
- */
- public boolean isShowISOWeekNumbers() {
- return showISOWeekNumbers;
- }
-
- /**
- * Returns a copy of the current date. Modifying the returned date will not
- * modify the value of this VDateField. Use {@link #setDate(Date)} to change
- * the current date.
- *
- * @return A copy of the current date
- */
- protected Date getDate() {
- Date current = getCurrentDate();
- if (current == null) {
- return null;
- } else {
- return (Date) getCurrentDate().clone();
- }
- }
-
- /**
- * Sets the current date for this VDateField.
- *
- * @param date
- * The new date to use
- */
- protected void setDate(Date date) {
- this.date = date;
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.event.dom.client.DomEvent;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusOutListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.SubmitListener;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-/**
- * A client side implementation for InlineDateField
- */
-public class VDateFieldCalendar extends VDateField {
-
- protected final VCalendarPanel calendarPanel;
-
- public VDateFieldCalendar() {
- super();
- calendarPanel = new VCalendarPanel();
- calendarPanel.setParentField(this);
- add(calendarPanel);
- calendarPanel.setSubmitListener(new SubmitListener() {
- @Override
- public void onSubmit() {
- updateValueFromPanel();
- }
-
- @Override
- public void onCancel() {
- // TODO Auto-generated method stub
-
- }
- });
- calendarPanel.setFocusOutListener(new FocusOutListener() {
- @Override
- public boolean onFocusOut(DomEvent<?> event) {
- updateValueFromPanel();
- return false;
- }
- });
- }
-
- /**
- * TODO refactor: almost same method as in VPopupCalendar.updateValue
- */
- @SuppressWarnings("deprecation")
- protected void updateValueFromPanel() {
-
- // If field is invisible at the beginning, client can still be null when
- // this function is called.
- if (getClient() == null) {
- return;
- }
-
- Date date2 = calendarPanel.getDate();
- Date currentDate = getCurrentDate();
- if (currentDate == null || date2.getTime() != currentDate.getTime()) {
- setCurrentDate((Date) date2.clone());
- getClient().updateVariable(getId(), "year", date2.getYear() + 1900,
- false);
- if (getCurrentResolution().getCalendarField() > Resolution.YEAR
- .getCalendarField()) {
- getClient().updateVariable(getId(), "month",
- date2.getMonth() + 1, false);
- if (getCurrentResolution().getCalendarField() > Resolution.MONTH
- .getCalendarField()) {
- getClient().updateVariable(getId(), "day", date2.getDate(),
- false);
- if (getCurrentResolution().getCalendarField() > Resolution.DAY
- .getCalendarField()) {
- getClient().updateVariable(getId(), "hour",
- date2.getHours(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.HOUR
- .getCalendarField()) {
- getClient().updateVariable(getId(), "min",
- date2.getMinutes(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
- .getCalendarField()) {
- getClient().updateVariable(getId(), "sec",
- date2.getSeconds(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.SECOND
- .getCalendarField()) {
- getClient().updateVariable(
- getId(),
- "msec",
- DateTimeService
- .getMilliseconds(date2),
- false);
- }
- }
- }
- }
- }
- }
- if (isImmediate()) {
- getClient().sendPendingVariableChanges();
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusOutListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.SubmitListener;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-/**
- * Represents a date selection component with a text field and a popup date
- * selector.
- *
- * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
- * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass
- * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code>
- * method.
- *
- */
-public class VPopupCalendar extends VTextualDate implements Field,
- ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
-
- protected final Button calendarToggle = new Button();
-
- protected VCalendarPanel calendar;
-
- protected final VOverlay popup;
- private boolean open = false;
- protected boolean parsable = true;
-
- public VPopupCalendar() {
- super();
-
- calendarToggle.setText("");
- calendarToggle.addClickHandler(this);
- // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
- calendarToggle.getElement().setTabIndex(-2);
- add(calendarToggle);
-
- calendar = GWT.create(VCalendarPanel.class);
- calendar.setParentField(this);
- calendar.setFocusOutListener(new FocusOutListener() {
- @Override
- public boolean onFocusOut(DomEvent<?> event) {
- event.preventDefault();
- closeCalendarPanel();
- return true;
- }
- });
-
- calendar.setSubmitListener(new SubmitListener() {
- @Override
- public void onSubmit() {
- // Update internal value and send valuechange event if immediate
- updateValue(calendar.getDate());
-
- // Update text field (a must when not immediate).
- buildDate(true);
-
- closeCalendarPanel();
- }
-
- @Override
- public void onCancel() {
- closeCalendarPanel();
- }
- });
-
- popup = new VOverlay(true, true, true);
- popup.setOwner(this);
-
- popup.setWidget(calendar);
- popup.addCloseHandler(this);
-
- DOM.setElementProperty(calendar.getElement(), "id",
- "PID_VAADIN_POPUPCAL");
-
- sinkEvents(Event.ONKEYDOWN);
-
- updateStyleNames();
- }
-
- @SuppressWarnings("deprecation")
- protected void updateValue(Date newDate) {
- Date currentDate = getCurrentDate();
- if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
- setCurrentDate((Date) newDate.clone());
- getClient().updateVariable(getId(), "year",
- newDate.getYear() + 1900, false);
- if (getCurrentResolution().getCalendarField() > Resolution.YEAR
- .getCalendarField()) {
- getClient().updateVariable(getId(), "month",
- newDate.getMonth() + 1, false);
- if (getCurrentResolution().getCalendarField() > Resolution.MONTH
- .getCalendarField()) {
- getClient().updateVariable(getId(), "day",
- newDate.getDate(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.DAY
- .getCalendarField()) {
- getClient().updateVariable(getId(), "hour",
- newDate.getHours(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.HOUR
- .getCalendarField()) {
- getClient().updateVariable(getId(), "min",
- newDate.getMinutes(), false);
- if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
- .getCalendarField()) {
- getClient().updateVariable(getId(), "sec",
- newDate.getSeconds(), false);
- }
- }
- }
- }
- }
- if (isImmediate()) {
- getClient().sendPendingVariableChanges();
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
- */
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames();
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- removeStyleName(getStylePrimaryName() + "-popupcalendar");
- super.setStylePrimaryName(style);
- updateStyleNames();
- }
-
- @Override
- protected void updateStyleNames() {
- super.updateStyleNames();
- if (getStylePrimaryName() != null && calendarToggle != null) {
- addStyleName(getStylePrimaryName() + "-popupcalendar");
- calendarToggle.setStyleName(getStylePrimaryName() + "-button");
- popup.setStyleName(getStylePrimaryName() + "-popup");
- calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
- }
- }
-
- /**
- * Opens the calendar panel popup
- */
- public void openCalendarPanel() {
-
- if (!open && !readonly) {
- open = true;
-
- if (getCurrentDate() != null) {
- calendar.setDate((Date) getCurrentDate().clone());
- } else {
- calendar.setDate(new Date());
- }
-
- // clear previous values
- popup.setWidth("");
- popup.setHeight("");
- popup.setPopupPositionAndShow(new PositionCallback() {
- @Override
- public void setPosition(int offsetWidth, int offsetHeight) {
- final int w = offsetWidth;
- final int h = offsetHeight;
- final int browserWindowWidth = Window.getClientWidth()
- + Window.getScrollLeft();
- final int browserWindowHeight = Window.getClientHeight()
- + Window.getScrollTop();
- int t = calendarToggle.getAbsoluteTop();
- int l = calendarToggle.getAbsoluteLeft();
-
- // Add a little extra space to the right to avoid
- // problems with IE7 scrollbars and to make it look
- // nicer.
- int extraSpace = 30;
-
- boolean overflowRight = false;
- if (l + +w + extraSpace > browserWindowWidth) {
- overflowRight = true;
- // Part of the popup is outside the browser window
- // (to the right)
- l = browserWindowWidth - w - extraSpace;
- }
-
- if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
- // Part of the popup is outside the browser window
- // (below)
- t = browserWindowHeight - h
- - calendarToggle.getOffsetHeight() - 30;
- if (!overflowRight) {
- // Show to the right of the popup button unless we
- // are in the lower right corner of the screen
- l += calendarToggle.getOffsetWidth();
- }
- }
-
- // fix size
- popup.setWidth(w + "px");
- popup.setHeight(h + "px");
-
- popup.setPopupPosition(l,
- t + calendarToggle.getOffsetHeight() + 2);
-
- /*
- * We have to wait a while before focusing since the popup
- * needs to be opened before we can focus
- */
- Timer focusTimer = new Timer() {
- @Override
- public void run() {
- setFocus(true);
- }
- };
-
- focusTimer.schedule(100);
- }
- });
- } else {
- VConsole.error("Cannot reopen popup, it is already open!");
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
- * .dom.client.ClickEvent)
- */
- @Override
- public void onClick(ClickEvent event) {
- if (event.getSource() == calendarToggle && isEnabled()) {
- openCalendarPanel();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
- * .event.logical.shared.CloseEvent)
- */
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- if (event.getSource() == popup) {
- buildDate();
- if (!BrowserInfo.get().isTouchDevice()) {
- /*
- * Move focus to textbox, unless on touch device (avoids opening
- * virtual keyboard).
- */
- focus();
- }
-
- // TODO resolve what the "Sigh." is all about and document it here
- // Sigh.
- Timer t = new Timer() {
- @Override
- public void run() {
- open = false;
- }
- };
- t.schedule(100);
- }
- }
-
- /**
- * Sets focus to Calendar panel.
- *
- * @param focus
- */
- public void setFocus(boolean focus) {
- calendar.setFocus(focus);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.ui.VTextualDate#buildDate()
- */
- @Override
- protected void buildDate() {
- // Save previous value
- String previousValue = getText();
- super.buildDate();
-
- // Restore previous value if the input could not be parsed
- if (!parsable) {
- setText(previousValue);
- }
- }
-
- /**
- * Update the text field contents from the date. See {@link #buildDate()}.
- *
- * @param forceValid
- * true to force the text field to be updated, false to only
- * update if the parsable flag is true.
- */
- protected void buildDate(boolean forceValid) {
- if (forceValid) {
- parsable = true;
- }
- buildDate();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google
- * .gwt.user.client.Event)
- */
- @Override
- public void onBrowserEvent(com.google.gwt.user.client.Event event) {
- super.onBrowserEvent(event);
- if (DOM.eventGetType(event) == Event.ONKEYDOWN
- && event.getKeyCode() == getOpenCalenderPanelKey()) {
- openCalendarPanel();
- event.preventDefault();
- }
- }
-
- /**
- * Get the key code that opens the calendar panel. By default it is the down
- * key but you can override this to be whatever you like
- *
- * @return
- */
- protected int getOpenCalenderPanelKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * Closes the open popup panel
- */
- public void closeCalendarPanel() {
- if (open) {
- popup.hide(true);
- }
- }
-
- private final String CALENDAR_TOGGLE_ID = "popupButton";
-
- @Override
- public Element getSubPartElement(String subPart) {
- if (subPart.equals(CALENDAR_TOGGLE_ID)) {
- return calendarToggle.getElement();
- }
-
- return super.getSubPartElement(subPart);
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (calendarToggle.getElement().isOrHasChild(subElement)) {
- return CALENDAR_TOGGLE_ID;
- }
-
- return super.getSubPartName(subElement);
- }
-
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.TextBox;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LocaleNotLoadedException;
-import com.vaadin.client.LocaleService;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-public class VTextualDate extends VDateField implements Field, ChangeHandler,
- Focusable, SubPartAware {
-
- private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
-
- protected final TextBox text;
-
- protected String formatStr;
-
- protected boolean lenient;
-
- private static final String CLASSNAME_PROMPT = "prompt";
- protected static final String ATTR_INPUTPROMPT = "prompt";
- protected String inputPrompt = "";
- private boolean prompting = false;
-
- public VTextualDate() {
- super();
- text = new TextBox();
- text.addChangeHandler(this);
- text.addFocusHandler(new FocusHandler() {
- @Override
- public void onFocus(FocusEvent event) {
- text.addStyleName(VTextField.CLASSNAME + "-"
- + VTextField.CLASSNAME_FOCUS);
- if (prompting) {
- text.setText("");
- setPrompting(false);
- }
- if (getClient() != null
- && getClient().hasEventListeners(VTextualDate.this,
- EventId.FOCUS)) {
- getClient()
- .updateVariable(getId(), EventId.FOCUS, "", true);
- }
- }
- });
- text.addBlurHandler(new BlurHandler() {
- @Override
- public void onBlur(BlurEvent event) {
- text.removeStyleName(VTextField.CLASSNAME + "-"
- + VTextField.CLASSNAME_FOCUS);
- String value = getText();
- setPrompting(inputPrompt != null
- && (value == null || "".equals(value)));
- if (prompting) {
- text.setText(readonly ? "" : inputPrompt);
- }
- if (getClient() != null
- && getClient().hasEventListeners(VTextualDate.this,
- EventId.BLUR)) {
- getClient().updateVariable(getId(), EventId.BLUR, "", true);
- }
- }
- });
- add(text);
- }
-
- protected void updateStyleNames() {
- if (text != null) {
- text.setStyleName(VTextField.CLASSNAME);
- text.addStyleName(getStylePrimaryName() + "-textfield");
- }
- }
-
- protected String getFormatString() {
- if (formatStr == null) {
- if (currentResolution == Resolution.YEAR) {
- formatStr = "yyyy"; // force full year
- } else {
-
- try {
- String frmString = LocaleService
- .getDateFormat(currentLocale);
- frmString = cleanFormat(frmString);
- // String delim = LocaleService
- // .getClockDelimiter(currentLocale);
- if (currentResolution.getCalendarField() >= Resolution.HOUR
- .getCalendarField()) {
- if (dts.isTwelveHourClock()) {
- frmString += " hh";
- } else {
- frmString += " HH";
- }
- if (currentResolution.getCalendarField() >= Resolution.MINUTE
- .getCalendarField()) {
- frmString += ":mm";
- if (currentResolution.getCalendarField() >= Resolution.SECOND
- .getCalendarField()) {
- frmString += ":ss";
- }
- }
- if (dts.isTwelveHourClock()) {
- frmString += " aaa";
- }
-
- }
-
- formatStr = frmString;
- } catch (LocaleNotLoadedException e) {
- // TODO should die instead? Can the component survive
- // without format string?
- VConsole.error(e);
- }
- }
- }
- return formatStr;
- }
-
- /**
- * Updates the text field according to the current date (provided by
- * {@link #getDate()}). Takes care of updating text, enabling and disabling
- * the field, setting/removing readonly status and updating readonly styles.
- *
- * TODO: Split part of this into a method that only updates the text as this
- * is what usually is needed except for updateFromUIDL.
- */
- protected void buildDate() {
- removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
- // Create the initial text for the textfield
- String dateText;
- Date currentDate = getDate();
- if (currentDate != null) {
- dateText = getDateTimeService().formatDate(currentDate,
- getFormatString());
- } else {
- dateText = "";
- }
-
- setText(dateText);
- text.setEnabled(enabled);
- text.setReadOnly(readonly);
-
- if (readonly) {
- text.addStyleName("v-readonly");
- } else {
- text.removeStyleName("v-readonly");
- }
-
- }
-
- protected void setPrompting(boolean prompting) {
- this.prompting = prompting;
- if (prompting) {
- addStyleDependentName(CLASSNAME_PROMPT);
- } else {
- removeStyleDependentName(CLASSNAME_PROMPT);
- }
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public void onChange(ChangeEvent event) {
- if (!text.getText().equals("")) {
- try {
- String enteredDate = text.getText();
-
- setDate(getDateTimeService().parseDate(enteredDate,
- getFormatString(), lenient));
-
- if (lenient) {
- // If date value was leniently parsed, normalize text
- // presentation.
- // FIXME: Add a description/example here of when this is
- // needed
- text.setValue(
- getDateTimeService().formatDate(getDate(),
- getFormatString()), false);
- }
-
- // remove possibly added invalid value indication
- removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
- } catch (final Exception e) {
- VConsole.log(e);
-
- addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
- // this is a hack that may eventually be removed
- getClient().updateVariable(getId(), "lastInvalidDateString",
- text.getText(), false);
- setDate(null);
- }
- } else {
- setDate(null);
- // remove possibly added invalid value indication
- removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
- }
- // always send the date string
- getClient()
- .updateVariable(getId(), "dateString", text.getText(), false);
-
- // Update variables
- // (only the smallest defining resolution needs to be
- // immediate)
- Date currentDate = getDate();
- getClient().updateVariable(getId(), "year",
- currentDate != null ? currentDate.getYear() + 1900 : -1,
- currentResolution == Resolution.YEAR && immediate);
- if (currentResolution.getCalendarField() >= Resolution.MONTH
- .getCalendarField()) {
- getClient().updateVariable(getId(), "month",
- currentDate != null ? currentDate.getMonth() + 1 : -1,
- currentResolution == Resolution.MONTH && immediate);
- }
- if (currentResolution.getCalendarField() >= Resolution.DAY
- .getCalendarField()) {
- getClient().updateVariable(getId(), "day",
- currentDate != null ? currentDate.getDate() : -1,
- currentResolution == Resolution.DAY && immediate);
- }
- if (currentResolution.getCalendarField() >= Resolution.HOUR
- .getCalendarField()) {
- getClient().updateVariable(getId(), "hour",
- currentDate != null ? currentDate.getHours() : -1,
- currentResolution == Resolution.HOUR && immediate);
- }
- if (currentResolution.getCalendarField() >= Resolution.MINUTE
- .getCalendarField()) {
- getClient().updateVariable(getId(), "min",
- currentDate != null ? currentDate.getMinutes() : -1,
- currentResolution == Resolution.MINUTE && immediate);
- }
- if (currentResolution.getCalendarField() >= Resolution.SECOND
- .getCalendarField()) {
- getClient().updateVariable(getId(), "sec",
- currentDate != null ? currentDate.getSeconds() : -1,
- currentResolution == Resolution.SECOND && immediate);
- }
-
- }
-
- private String cleanFormat(String format) {
- // Remove unnecessary d & M if resolution is too low
- if (currentResolution.getCalendarField() < Resolution.DAY
- .getCalendarField()) {
- format = format.replaceAll("d", "");
- }
- if (currentResolution.getCalendarField() < Resolution.MONTH
- .getCalendarField()) {
- format = format.replaceAll("M", "");
- }
-
- // Remove unsupported patterns
- // TODO support for 'G', era designator (used at least in Japan)
- format = format.replaceAll("[GzZwWkK]", "");
-
- // Remove extra delimiters ('/' and '.')
- while (format.startsWith("/") || format.startsWith(".")
- || format.startsWith("-")) {
- format = format.substring(1);
- }
- while (format.endsWith("/") || format.endsWith(".")
- || format.endsWith("-")) {
- format = format.substring(0, format.length() - 1);
- }
-
- // Remove duplicate delimiters
- format = format.replaceAll("//", "/");
- format = format.replaceAll("\\.\\.", ".");
- format = format.replaceAll("--", "-");
-
- return format.trim();
- }
-
- @Override
- public void focus() {
- text.setFocus(true);
- }
-
- protected String getText() {
- if (prompting) {
- return "";
- }
- return text.getText();
- }
-
- protected void setText(String text) {
- if (inputPrompt != null && (text == null || "".equals(text))) {
- text = readonly ? "" : inputPrompt;
- setPrompting(true);
- } else {
- setPrompting(false);
- }
-
- this.text.setText(text);
- }
-
- private final String TEXTFIELD_ID = "field";
-
- @Override
- public Element getSubPartElement(String subPart) {
- if (subPart.equals(TEXTFIELD_ID)) {
- return text.getElement();
- }
-
- return null;
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (text.getElement().isOrHasChild(subElement)) {
- return TEXTFIELD_ID;
- }
-
- return null;
- }
-
-}
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.tree.VTree;
-import com.vaadin.client.ui.tree.VTree.TreeNode;
+import com.vaadin.client.ui.VTree;
+import com.vaadin.client.ui.VTree.TreeNode;
import com.vaadin.shared.ui.dd.AcceptCriterion;
import com.vaadin.ui.Tree;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VDragAndDropWrapper;
import com.vaadin.client.ui.customcomponent.CustomComponentConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperConstants;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.draganddropwrapper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.dom.client.TouchStartEvent;
-import com.google.gwt.event.dom.client.TouchStartHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.xhr.client.ReadyStateChangeHandler;
-import com.google.gwt.xhr.client.XMLHttpRequest;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ValueMap;
-import com.vaadin.client.ui.customcomponent.VCustomComponent;
-import com.vaadin.client.ui.dd.DDUtil;
-import com.vaadin.client.ui.dd.VAbstractDropHandler;
-import com.vaadin.client.ui.dd.VAcceptCallback;
-import com.vaadin.client.ui.dd.VDragAndDropManager;
-import com.vaadin.client.ui.dd.VDragEvent;
-import com.vaadin.client.ui.dd.VDropHandler;
-import com.vaadin.client.ui.dd.VHasDropHandler;
-import com.vaadin.client.ui.dd.VHtml5DragEvent;
-import com.vaadin.client.ui.dd.VHtml5File;
-import com.vaadin.client.ui.dd.VTransferable;
-import com.vaadin.shared.ui.dd.HorizontalDropLocation;
-import com.vaadin.shared.ui.dd.VerticalDropLocation;
-
-/**
- *
- * Must have features pending:
- *
- * drop details: locations + sizes in document hierarchy up to wrapper
- *
- */
-public class VDragAndDropWrapper extends VCustomComponent implements
- VHasDropHandler {
-
- private static final String CLASSNAME = "v-ddwrapper";
- protected static final String DRAGGABLE = "draggable";
-
- boolean hasTooltip = false;
-
- public VDragAndDropWrapper() {
- super();
-
- hookHtml5Events(getElement());
- setStyleName(CLASSNAME);
- addDomHandler(new MouseDownHandler() {
-
- @Override
- public void onMouseDown(MouseDownEvent event) {
- if (startDrag(event.getNativeEvent())) {
- event.preventDefault(); // prevent text selection
- }
- }
- }, MouseDownEvent.getType());
-
- addDomHandler(new TouchStartHandler() {
-
- @Override
- public void onTouchStart(TouchStartEvent event) {
- if (startDrag(event.getNativeEvent())) {
- /*
- * Dont let eg. panel start scrolling.
- */
- event.stopPropagation();
- }
- }
- }, TouchStartEvent.getType());
-
- sinkEvents(Event.TOUCHEVENTS);
- }
-
- /**
- * Starts a drag and drop operation from mousedown or touchstart event if
- * required conditions are met.
- *
- * @param event
- * @return true if the event was handled as a drag start event
- */
- private boolean startDrag(NativeEvent event) {
- if (dragStartMode == WRAPPER || dragStartMode == COMPONENT) {
- VTransferable transferable = new VTransferable();
- transferable.setDragSource(ConnectorMap.get(client).getConnector(
- VDragAndDropWrapper.this));
-
- ComponentConnector paintable = Util.findPaintable(client,
- (Element) event.getEventTarget().cast());
- Widget widget = paintable.getWidget();
- transferable.setData("component", paintable);
- VDragEvent dragEvent = VDragAndDropManager.get().startDrag(
- transferable, event, true);
-
- transferable.setData("mouseDown", MouseEventDetailsBuilder
- .buildMouseEventDetails(event).serialize());
-
- if (dragStartMode == WRAPPER) {
- dragEvent.createDragImage(getElement(), true);
- } else {
- dragEvent.createDragImage(widget.getElement(), true);
- }
- return true;
- }
- return false;
- }
-
- protected final static int NONE = 0;
- protected final static int COMPONENT = 1;
- protected final static int WRAPPER = 2;
- protected final static int HTML5 = 3;
-
- protected int dragStartMode;
-
- ApplicationConnection client;
- VAbstractDropHandler dropHandler;
- private VDragEvent vaadinDragEvent;
-
- int filecounter = 0;
- Map<String, String> fileIdToReceiver;
- ValueMap html5DataFlavors;
- private Element dragStartElement;
-
- protected void initDragStartMode() {
- Element div = getElement();
- if (dragStartMode == HTML5) {
- if (dragStartElement == null) {
- dragStartElement = getDragStartElement();
- dragStartElement.setPropertyBoolean(DRAGGABLE, true);
- VConsole.log("draggable = "
- + dragStartElement.getPropertyBoolean(DRAGGABLE));
- hookHtml5DragStart(dragStartElement);
- VConsole.log("drag start listeners hooked.");
- }
- } else {
- dragStartElement = null;
- if (div.hasAttribute(DRAGGABLE)) {
- div.removeAttribute(DRAGGABLE);
- }
- }
- }
-
- protected Element getDragStartElement() {
- return getElement();
- }
-
- private boolean uploading;
-
- private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
-
- @Override
- public void onReadyStateChange(XMLHttpRequest xhr) {
- if (xhr.getReadyState() == XMLHttpRequest.DONE) {
- // visit server for possible
- // variable changes
- client.sendPendingVariableChanges();
- uploading = false;
- startNextUpload();
- xhr.clearOnReadyStateChange();
- }
- }
- };
- private Timer dragleavetimer;
-
- void startNextUpload() {
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- if (!uploading) {
- if (fileIds.size() > 0) {
-
- uploading = true;
- final Integer fileId = fileIds.remove(0);
- VHtml5File file = files.remove(0);
- final String receiverUrl = client
- .translateVaadinUri(fileIdToReceiver
- .remove(fileId.toString()));
- ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
- .create();
- extendedXHR
- .setOnReadyStateChange(readyStateChangeHandler);
- extendedXHR.open("POST", receiverUrl);
- extendedXHR.postFile(file);
- }
- }
-
- }
- });
-
- }
-
- public boolean html5DragStart(VHtml5DragEvent event) {
- if (dragStartMode == HTML5) {
- /*
- * Populate html5 payload with dataflavors from the serverside
- */
- JsArrayString flavors = html5DataFlavors.getKeyArray();
- for (int i = 0; i < flavors.length(); i++) {
- String flavor = flavors.get(i);
- event.setHtml5DataFlavor(flavor,
- html5DataFlavors.getString(flavor));
- }
- event.setEffectAllowed("copy");
- return true;
- }
- return false;
- }
-
- public boolean html5DragEnter(VHtml5DragEvent event) {
- if (dropHandler == null) {
- return true;
- }
- try {
- if (dragleavetimer != null) {
- // returned quickly back to wrapper
- dragleavetimer.cancel();
- dragleavetimer = null;
- }
- if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) {
- VTransferable transferable = new VTransferable();
- transferable.setDragSource(ConnectorMap.get(client)
- .getConnector(this));
-
- vaadinDragEvent = VDragAndDropManager.get().startDrag(
- transferable, event, false);
- VDragAndDropManager.get().setCurrentDropHandler(
- getDropHandler());
- }
- try {
- event.preventDefault();
- event.stopPropagation();
- } catch (Exception e) {
- // VConsole.log("IE9 fails");
- }
- return false;
- } catch (Exception e) {
- GWT.getUncaughtExceptionHandler().onUncaughtException(e);
- return true;
- }
- }
-
- public boolean html5DragLeave(VHtml5DragEvent event) {
- if (dropHandler == null) {
- return true;
- }
-
- try {
- dragleavetimer = new Timer() {
-
- @Override
- public void run() {
- // Yes, dragleave happens before drop. Makes no sense to me.
- // IMO shouldn't fire leave at all if drop happens (I guess
- // this
- // is what IE does).
- // In Vaadin we fire it only if drop did not happen.
- if (vaadinDragEvent != null
- && VDragAndDropManager.get()
- .getCurrentDropHandler() == getDropHandler()) {
- VDragAndDropManager.get().interruptDrag();
- }
- }
- };
- dragleavetimer.schedule(350);
- try {
- event.preventDefault();
- event.stopPropagation();
- } catch (Exception e) {
- // VConsole.log("IE9 fails");
- }
- return false;
- } catch (Exception e) {
- GWT.getUncaughtExceptionHandler().onUncaughtException(e);
- return true;
- }
- }
-
- public boolean html5DragOver(VHtml5DragEvent event) {
- if (dropHandler == null) {
- return true;
- }
-
- if (dragleavetimer != null) {
- // returned quickly back to wrapper
- dragleavetimer.cancel();
- dragleavetimer = null;
- }
-
- vaadinDragEvent.setCurrentGwtEvent(event);
- getDropHandler().dragOver(vaadinDragEvent);
-
- String s = event.getEffectAllowed();
- if ("all".equals(s) || s.contains("opy")) {
- event.setDropEffect("copy");
- } else {
- event.setDropEffect(s);
- }
-
- try {
- event.preventDefault();
- event.stopPropagation();
- } catch (Exception e) {
- // VConsole.log("IE9 fails");
- }
- return false;
- }
-
- public boolean html5DragDrop(VHtml5DragEvent event) {
- if (dropHandler == null || !currentlyValid) {
- return true;
- }
- try {
-
- VTransferable transferable = vaadinDragEvent.getTransferable();
-
- JsArrayString types = event.getTypes();
- for (int i = 0; i < types.length(); i++) {
- String type = types.get(i);
- if (isAcceptedType(type)) {
- String data = event.getDataAsText(type);
- if (data != null) {
- transferable.setData(type, data);
- }
- }
- }
-
- int fileCount = event.getFileCount();
- if (fileCount > 0) {
- transferable.setData("filecount", fileCount);
- for (int i = 0; i < fileCount; i++) {
- final int fileId = filecounter++;
- final VHtml5File file = event.getFile(i);
- transferable.setData("fi" + i, "" + fileId);
- transferable.setData("fn" + i, file.getName());
- transferable.setData("ft" + i, file.getType());
- transferable.setData("fs" + i, file.getSize());
- queueFilePost(fileId, file);
- }
-
- }
-
- VDragAndDropManager.get().endDrag();
- vaadinDragEvent = null;
- try {
- event.preventDefault();
- event.stopPropagation();
- } catch (Exception e) {
- // VConsole.log("IE9 fails");
- }
- return false;
- } catch (Exception e) {
- GWT.getUncaughtExceptionHandler().onUncaughtException(e);
- return true;
- }
-
- }
-
- protected String[] acceptedTypes = new String[] { "Text", "Url",
- "text/html", "text/plain", "text/rtf" };
-
- private boolean isAcceptedType(String type) {
- for (String t : acceptedTypes) {
- if (t.equals(type)) {
- return true;
- }
- }
- return false;
- }
-
- static class ExtendedXHR extends XMLHttpRequest {
-
- protected ExtendedXHR() {
- }
-
- public final native void postFile(VHtml5File file)
- /*-{
-
- this.setRequestHeader('Content-Type', 'multipart/form-data');
- this.send(file);
- }-*/;
-
- }
-
- /**
- * Currently supports only FF36 as no other browser supports natively File
- * api.
- *
- * @param fileId
- * @param data
- */
- List<Integer> fileIds = new ArrayList<Integer>();
- List<VHtml5File> files = new ArrayList<VHtml5File>();
-
- private void queueFilePost(final int fileId, final VHtml5File file) {
- fileIds.add(fileId);
- files.add(file);
- }
-
- @Override
- public VDropHandler getDropHandler() {
- return dropHandler;
- }
-
- protected VerticalDropLocation verticalDropLocation;
- protected HorizontalDropLocation horizontalDropLocation;
- private VerticalDropLocation emphasizedVDrop;
- private HorizontalDropLocation emphasizedHDrop;
-
- /**
- * Flag used by html5 dd
- */
- private boolean currentlyValid;
-
- private static final String OVER_STYLE = "v-ddwrapper-over";
-
- public class CustomDropHandler extends VAbstractDropHandler {
-
- @Override
- public void dragEnter(VDragEvent drag) {
- updateDropDetails(drag);
- currentlyValid = false;
- super.dragEnter(drag);
- }
-
- @Override
- public void dragLeave(VDragEvent drag) {
- deEmphasis(true);
- dragleavetimer = null;
- }
-
- @Override
- public void dragOver(final VDragEvent drag) {
- boolean detailsChanged = updateDropDetails(drag);
- if (detailsChanged) {
- currentlyValid = false;
- validate(new VAcceptCallback() {
-
- @Override
- public void accepted(VDragEvent event) {
- dragAccepted(drag);
- }
- }, drag);
- }
- }
-
- @Override
- public boolean drop(VDragEvent drag) {
- deEmphasis(true);
-
- Map<String, Object> dd = drag.getDropDetails();
-
- // this is absolute layout based, and we may want to set
- // component
- // relatively to where the drag ended.
- // need to add current location of the drop area
-
- int absoluteLeft = getAbsoluteLeft();
- int absoluteTop = getAbsoluteTop();
-
- dd.put("absoluteLeft", absoluteLeft);
- dd.put("absoluteTop", absoluteTop);
-
- if (verticalDropLocation != null) {
- dd.put("verticalLocation", verticalDropLocation.toString());
- dd.put("horizontalLocation", horizontalDropLocation.toString());
- }
-
- return super.drop(drag);
- }
-
- @Override
- protected void dragAccepted(VDragEvent drag) {
- currentlyValid = true;
- emphasis(drag);
- }
-
- @Override
- public ComponentConnector getConnector() {
- return ConnectorMap.get(client).getConnector(
- VDragAndDropWrapper.this);
- }
-
- @Override
- public ApplicationConnection getApplicationConnection() {
- return client;
- }
-
- }
-
- protected native void hookHtml5DragStart(Element el)
- /*-{
- var me = this;
- el.addEventListener("dragstart", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }), false);
- }-*/;
-
- /**
- * Prototype code, memory leak risk.
- *
- * @param el
- */
- protected native void hookHtml5Events(Element el)
- /*-{
- var me = this;
-
- el.addEventListener("dragenter", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }), false);
-
- el.addEventListener("dragleave", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }), false);
-
- el.addEventListener("dragover", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }), false);
-
- el.addEventListener("drop", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }), false);
- }-*/;
-
- public boolean updateDropDetails(VDragEvent drag) {
- VerticalDropLocation oldVL = verticalDropLocation;
- verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(),
- drag.getCurrentGwtEvent(), 0.2);
- drag.getDropDetails().put("verticalLocation",
- verticalDropLocation.toString());
- HorizontalDropLocation oldHL = horizontalDropLocation;
- horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(),
- drag.getCurrentGwtEvent(), 0.2);
- drag.getDropDetails().put("horizontalLocation",
- horizontalDropLocation.toString());
- if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) {
- return true;
- } else {
- return false;
- }
- }
-
- protected void deEmphasis(boolean doLayout) {
- if (emphasizedVDrop != null) {
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false);
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
- + emphasizedVDrop.toString().toLowerCase(), false);
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
- + emphasizedHDrop.toString().toLowerCase(), false);
- }
- if (doLayout) {
- notifySizePotentiallyChanged();
- }
- }
-
- private void notifySizePotentiallyChanged() {
- LayoutManager.get(client).setNeedsMeasure(
- ConnectorMap.get(client).getConnector(getElement()));
- }
-
- protected void emphasis(VDragEvent drag) {
- deEmphasis(false);
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true);
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
- + verticalDropLocation.toString().toLowerCase(), true);
- VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
- + horizontalDropLocation.toString().toLowerCase(), true);
- emphasizedVDrop = verticalDropLocation;
- emphasizedHDrop = horizontalDropLocation;
-
- // TODO build (to be an example) an emphasis mode where drag image
- // is fitted before or after the content
- notifySizePotentiallyChanged();
- }
-
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.draganddropwrapper;
-
-import com.google.gwt.dom.client.AnchorElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.Element;
-import com.vaadin.client.VConsole;
-
-public class VDragAndDropWrapperIE extends VDragAndDropWrapper {
- private AnchorElement anchor = null;
-
- @Override
- protected Element getDragStartElement() {
- VConsole.log("IE get drag start element...");
- Element div = getElement();
- if (dragStartMode == HTML5) {
- if (anchor == null) {
- anchor = Document.get().createAnchorElement();
- anchor.setHref("#");
- anchor.setClassName("drag-start");
- div.appendChild(anchor);
- }
- VConsole.log("IE get drag start element...");
- return (Element) anchor.cast();
- } else {
- if (anchor != null) {
- div.removeChild(anchor);
- anchor = null;
- }
- return div;
- }
- }
-
- @Override
- protected native void hookHtml5DragStart(Element el)
- /*-{
- var me = this;
-
- el.attachEvent("ondragstart", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }));
- }-*/;
-
- @Override
- protected native void hookHtml5Events(Element el)
- /*-{
- var me = this;
-
- el.attachEvent("ondragenter", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }));
-
- el.attachEvent("ondragleave", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }));
-
- el.attachEvent("ondragover", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }));
-
- el.attachEvent("ondrop", $entry(function(ev) {
- return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
- }));
- }-*/;
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.LayoutClickEventHandler;
-import com.vaadin.client.ui.gridlayout.VGridLayout.Cell;
+import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VGridLayout.Cell;
import com.vaadin.client.ui.layout.VLayoutSlot;
import com.vaadin.shared.ui.AlignmentInfo;
import com.vaadin.shared.ui.Connect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.gridlayout;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot;
-import com.vaadin.client.ui.layout.VLayoutSlot;
-import com.vaadin.shared.ui.AlignmentInfo;
-import com.vaadin.shared.ui.MarginInfo;
-
-public class VGridLayout extends ComplexPanel {
-
- public static final String CLASSNAME = "v-gridlayout";
-
- ApplicationConnection client;
-
- HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>();
-
- int[] columnWidths;
- int[] rowHeights;
-
- int[] colExpandRatioArray;
-
- int[] rowExpandRatioArray;
-
- int[] minColumnWidths;
-
- private int[] minRowHeights;
-
- DivElement spacingMeasureElement;
-
- public VGridLayout() {
- super();
- setElement(Document.get().createDivElement());
-
- spacingMeasureElement = Document.get().createDivElement();
- Style spacingStyle = spacingMeasureElement.getStyle();
- spacingStyle.setPosition(Position.ABSOLUTE);
- getElement().appendChild(spacingMeasureElement);
-
- setStyleName(CLASSNAME);
- addStyleName(StyleConstants.UI_LAYOUT);
- }
-
- private GridLayoutConnector getConnector() {
- return (GridLayoutConnector) ConnectorMap.get(client)
- .getConnector(this);
- }
-
- /**
- * Returns the column widths measured in pixels
- *
- * @return
- */
- protected int[] getColumnWidths() {
- return columnWidths;
- }
-
- /**
- * Returns the row heights measured in pixels
- *
- * @return
- */
- protected int[] getRowHeights() {
- return rowHeights;
- }
-
- /**
- * Returns the spacing between the cells horizontally in pixels
- *
- * @return
- */
- protected int getHorizontalSpacing() {
- return LayoutManager.get(client).getOuterWidth(spacingMeasureElement);
- }
-
- /**
- * Returns the spacing between the cells vertically in pixels
- *
- * @return
- */
- protected int getVerticalSpacing() {
- return LayoutManager.get(client).getOuterHeight(spacingMeasureElement);
- }
-
- static int[] cloneArray(int[] toBeCloned) {
- int[] clone = new int[toBeCloned.length];
- for (int i = 0; i < clone.length; i++) {
- clone[i] = toBeCloned[i] * 1;
- }
- return clone;
- }
-
- void expandRows() {
- if (!isUndefinedHeight()) {
- int usedSpace = minRowHeights[0];
- int verticalSpacing = getVerticalSpacing();
- for (int i = 1; i < minRowHeights.length; i++) {
- usedSpace += verticalSpacing + minRowHeights[i];
- }
- int availableSpace = LayoutManager.get(client).getInnerHeight(
- getElement());
- int excessSpace = availableSpace - usedSpace;
- int distributed = 0;
- if (excessSpace > 0) {
- for (int i = 0; i < rowHeights.length; i++) {
- int ew = excessSpace * rowExpandRatioArray[i] / 1000;
- rowHeights[i] = minRowHeights[i] + ew;
- distributed += ew;
- }
- excessSpace -= distributed;
- int c = 0;
- while (excessSpace > 0) {
- rowHeights[c % rowHeights.length]++;
- excessSpace--;
- c++;
- }
- }
- }
- }
-
- void updateHeight() {
- // Detect minimum heights & calculate spans
- detectRowHeights();
-
- // Expand
- expandRows();
-
- // Position
- layoutCellsVertically();
- }
-
- void updateWidth() {
- // Detect widths & calculate spans
- detectColWidths();
- // Expand
- expandColumns();
- // Position
- layoutCellsHorizontally();
-
- }
-
- void expandColumns() {
- if (!isUndefinedWidth()) {
- int usedSpace = minColumnWidths[0];
- int horizontalSpacing = getHorizontalSpacing();
- for (int i = 1; i < minColumnWidths.length; i++) {
- usedSpace += horizontalSpacing + minColumnWidths[i];
- }
-
- int availableSpace = LayoutManager.get(client).getInnerWidth(
- getElement());
- int excessSpace = availableSpace - usedSpace;
- int distributed = 0;
- if (excessSpace > 0) {
- for (int i = 0; i < columnWidths.length; i++) {
- int ew = excessSpace * colExpandRatioArray[i] / 1000;
- columnWidths[i] = minColumnWidths[i] + ew;
- distributed += ew;
- }
- excessSpace -= distributed;
- int c = 0;
- while (excessSpace > 0) {
- columnWidths[c % columnWidths.length]++;
- excessSpace--;
- c++;
- }
- }
- }
- }
-
- void layoutCellsVertically() {
- int verticalSpacing = getVerticalSpacing();
- LayoutManager layoutManager = LayoutManager.get(client);
- Element element = getElement();
- int paddingTop = layoutManager.getPaddingTop(element);
- int paddingBottom = layoutManager.getPaddingBottom(element);
- int y = paddingTop;
-
- for (int i = 0; i < cells.length; i++) {
- y = paddingTop;
- for (int j = 0; j < cells[i].length; j++) {
- Cell cell = cells[i][j];
- if (cell != null) {
- int reservedMargin;
- if (cell.rowspan + j >= cells[i].length) {
- // Make room for layout padding for cells reaching the
- // bottom of the layout
- reservedMargin = paddingBottom;
- } else {
- reservedMargin = 0;
- }
- cell.layoutVertically(y, reservedMargin);
- }
- y += rowHeights[j] + verticalSpacing;
- }
- }
-
- if (isUndefinedHeight()) {
- int outerHeight = y - verticalSpacing
- + layoutManager.getPaddingBottom(element)
- + layoutManager.getBorderHeight(element);
- element.getStyle().setHeight(outerHeight, Unit.PX);
- getConnector().getLayoutManager().reportOuterHeight(getConnector(),
- outerHeight);
- }
- }
-
- void layoutCellsHorizontally() {
- LayoutManager layoutManager = LayoutManager.get(client);
- Element element = getElement();
- int x = layoutManager.getPaddingLeft(element);
- int paddingRight = layoutManager.getPaddingRight(element);
- int horizontalSpacing = getHorizontalSpacing();
- for (int i = 0; i < cells.length; i++) {
- for (int j = 0; j < cells[i].length; j++) {
- Cell cell = cells[i][j];
- if (cell != null) {
- int reservedMargin;
- // Make room for layout padding for cells reaching the
- // right edge of the layout
- if (i + cell.colspan >= cells.length) {
- reservedMargin = paddingRight;
- } else {
- reservedMargin = 0;
- }
- cell.layoutHorizontally(x, reservedMargin);
- }
- }
- x += columnWidths[i] + horizontalSpacing;
- }
-
- if (isUndefinedWidth()) {
- int outerWidth = x - horizontalSpacing
- + layoutManager.getPaddingRight(element)
- + layoutManager.getBorderWidth(element);
- element.getStyle().setWidth(outerWidth, Unit.PX);
- getConnector().getLayoutManager().reportOuterWidth(getConnector(),
- outerWidth);
- }
- }
-
- private boolean isUndefinedHeight() {
- return getConnector().isUndefinedHeight();
- }
-
- private boolean isUndefinedWidth() {
- return getConnector().isUndefinedWidth();
- }
-
- private void detectRowHeights() {
- for (int i = 0; i < rowHeights.length; i++) {
- rowHeights[i] = 0;
- }
-
- // collect min rowheight from non-rowspanned cells
- for (int i = 0; i < cells.length; i++) {
- for (int j = 0; j < cells[i].length; j++) {
- Cell cell = cells[i][j];
- if (cell != null) {
- if (cell.rowspan == 1) {
- if (!cell.hasRelativeHeight()
- && rowHeights[j] < cell.getHeight()) {
- rowHeights[j] = cell.getHeight();
- }
- } else {
- storeRowSpannedCell(cell);
- }
- }
- }
- }
-
- distributeRowSpanHeights();
-
- minRowHeights = cloneArray(rowHeights);
- }
-
- private void detectColWidths() {
- // collect min colwidths from non-colspanned cells
- for (int i = 0; i < columnWidths.length; i++) {
- columnWidths[i] = 0;
- }
-
- for (int i = 0; i < cells.length; i++) {
- for (int j = 0; j < cells[i].length; j++) {
- Cell cell = cells[i][j];
- if (cell != null) {
- if (cell.colspan == 1) {
- if (!cell.hasRelativeWidth()
- && columnWidths[i] < cell.getWidth()) {
- columnWidths[i] = cell.getWidth();
- }
- } else {
- storeColSpannedCell(cell);
- }
- }
- }
- }
-
- distributeColSpanWidths();
-
- minColumnWidths = cloneArray(columnWidths);
- }
-
- private void storeRowSpannedCell(Cell cell) {
- SpanList l = null;
- for (SpanList list : rowSpans) {
- if (list.span < cell.rowspan) {
- continue;
- } else {
- // insert before this
- l = list;
- break;
- }
- }
- if (l == null) {
- l = new SpanList(cell.rowspan);
- rowSpans.add(l);
- } else if (l.span != cell.rowspan) {
- SpanList newL = new SpanList(cell.rowspan);
- rowSpans.add(rowSpans.indexOf(l), newL);
- l = newL;
- }
- l.cells.add(cell);
- }
-
- /**
- * Iterates colspanned cells, ensures cols have enough space to accommodate
- * them
- */
- void distributeColSpanWidths() {
- for (SpanList list : colSpans) {
- for (Cell cell : list.cells) {
- // cells with relative content may return non 0 here if on
- // subsequent renders
- int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
- distributeSpanSize(columnWidths, cell.col, cell.colspan,
- getHorizontalSpacing(), width, colExpandRatioArray);
- }
- }
- }
-
- /**
- * Iterates rowspanned cells, ensures rows have enough space to accommodate
- * them
- */
- private void distributeRowSpanHeights() {
- for (SpanList list : rowSpans) {
- for (Cell cell : list.cells) {
- // cells with relative content may return non 0 here if on
- // subsequent renders
- int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
- distributeSpanSize(rowHeights, cell.row, cell.rowspan,
- getVerticalSpacing(), height, rowExpandRatioArray);
- }
- }
- }
-
- private static void distributeSpanSize(int[] dimensions,
- int spanStartIndex, int spanSize, int spacingSize, int size,
- int[] expansionRatios) {
- int allocated = dimensions[spanStartIndex];
- for (int i = 1; i < spanSize; i++) {
- allocated += spacingSize + dimensions[spanStartIndex + i];
- }
- if (allocated < size) {
- // dimensions needs to be expanded due spanned cell
- int neededExtraSpace = size - allocated;
- int allocatedExtraSpace = 0;
-
- // Divide space according to expansion ratios if any span has a
- // ratio
- int totalExpansion = 0;
- for (int i = 0; i < spanSize; i++) {
- int itemIndex = spanStartIndex + i;
- totalExpansion += expansionRatios[itemIndex];
- }
-
- for (int i = 0; i < spanSize; i++) {
- int itemIndex = spanStartIndex + i;
- int expansion;
- if (totalExpansion == 0) {
- // Divide equally among all cells if there are no
- // expansion ratios
- expansion = neededExtraSpace / spanSize;
- } else {
- expansion = neededExtraSpace * expansionRatios[itemIndex]
- / totalExpansion;
- }
- dimensions[itemIndex] += expansion;
- allocatedExtraSpace += expansion;
- }
-
- // We might still miss a couple of pixels because of
- // rounding errors...
- if (neededExtraSpace > allocatedExtraSpace) {
- for (int i = 0; i < spanSize; i++) {
- // Add one pixel to every cell until we have
- // compensated for any rounding error
- int itemIndex = spanStartIndex + i;
- dimensions[itemIndex] += 1;
- allocatedExtraSpace += 1;
- if (neededExtraSpace == allocatedExtraSpace) {
- break;
- }
- }
- }
- }
- }
-
- private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
- private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
-
- private class SpanList {
- final int span;
- List<Cell> cells = new LinkedList<Cell>();
-
- public SpanList(int span) {
- this.span = span;
- }
- }
-
- void storeColSpannedCell(Cell cell) {
- SpanList l = null;
- for (SpanList list : colSpans) {
- if (list.span < cell.colspan) {
- continue;
- } else {
- // insert before this
- l = list;
- break;
- }
- }
- if (l == null) {
- l = new SpanList(cell.colspan);
- colSpans.add(l);
- } else if (l.span != cell.colspan) {
-
- SpanList newL = new SpanList(cell.colspan);
- colSpans.add(colSpans.indexOf(l), newL);
- l = newL;
- }
- l.cells.add(cell);
- }
-
- Cell[][] cells;
-
- /**
- * Private helper class.
- */
- class Cell {
- public Cell(int row, int col) {
- this.row = row;
- this.col = col;
- }
-
- public boolean hasContent() {
- return hasContent;
- }
-
- public boolean hasRelativeHeight() {
- if (slot != null) {
- return slot.getChild().isRelativeHeight();
- } else {
- return true;
- }
- }
-
- /**
- * @return total of spanned cols
- */
- private int getAvailableWidth() {
- int width = columnWidths[col];
- for (int i = 1; i < colspan; i++) {
- width += getHorizontalSpacing() + columnWidths[col + i];
- }
- return width;
- }
-
- /**
- * @return total of spanned rows
- */
- private int getAvailableHeight() {
- int height = rowHeights[row];
- for (int i = 1; i < rowspan; i++) {
- height += getVerticalSpacing() + rowHeights[row + i];
- }
- return height;
- }
-
- public void layoutHorizontally(int x, int marginRight) {
- if (slot != null) {
- slot.positionHorizontally(x, getAvailableWidth(), marginRight);
- }
- }
-
- public void layoutVertically(int y, int marginBottom) {
- if (slot != null) {
- slot.positionVertically(y, getAvailableHeight(), marginBottom);
- }
- }
-
- public int getWidth() {
- if (slot != null) {
- return slot.getUsedWidth();
- } else {
- return 0;
- }
- }
-
- public int getHeight() {
- if (slot != null) {
- return slot.getUsedHeight();
- } else {
- return 0;
- }
- }
-
- protected boolean hasRelativeWidth() {
- if (slot != null) {
- return slot.getChild().isRelativeWidth();
- } else {
- return true;
- }
- }
-
- final int row;
- final int col;
- int colspan = 1;
- int rowspan = 1;
-
- private boolean hasContent;
-
- private AlignmentInfo alignment;
-
- ComponentConnectorLayoutSlot slot;
-
- public void updateFromUidl(UIDL cellUidl) {
- // Set cell width
- colspan = cellUidl.hasAttribute("w") ? cellUidl
- .getIntAttribute("w") : 1;
- // Set cell height
- rowspan = cellUidl.hasAttribute("h") ? cellUidl
- .getIntAttribute("h") : 1;
- // ensure we will lose reference to old cells, now overlapped by
- // this cell
- for (int i = 0; i < colspan; i++) {
- for (int j = 0; j < rowspan; j++) {
- if (i > 0 || j > 0) {
- cells[col + i][row + j] = null;
- }
- }
- }
-
- UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested
- // about childUidl
- hasContent = childUidl != null;
- if (hasContent) {
- ComponentConnector childConnector = client
- .getPaintable(childUidl);
-
- if (slot == null || slot.getChild() != childConnector) {
- slot = new ComponentConnectorLayoutSlot(CLASSNAME,
- childConnector, getConnector());
- if (childConnector.isRelativeWidth()) {
- slot.getWrapperElement().getStyle()
- .setWidth(100, Unit.PCT);
- }
- Element slotWrapper = slot.getWrapperElement();
- getElement().appendChild(slotWrapper);
-
- Widget widget = childConnector.getWidget();
- insert(widget, slotWrapper, getWidgetCount(), false);
- Cell oldCell = widgetToCell.put(widget, this);
- if (oldCell != null) {
- oldCell.slot.getWrapperElement().removeFromParent();
- oldCell.slot = null;
- }
- }
-
- }
- }
-
- public void setAlignment(AlignmentInfo alignmentInfo) {
- slot.setAlignment(alignmentInfo);
- }
- }
-
- Cell getCell(int row, int col) {
- return cells[col][row];
- }
-
- /**
- * Creates a new Cell with the given coordinates. If an existing cell was
- * found, returns that one.
- *
- * @param row
- * @param col
- * @return
- */
- Cell createCell(int row, int col) {
- Cell cell = getCell(row, col);
- if (cell == null) {
- cell = new Cell(row, col);
- cells[col][row] = cell;
- }
- return cell;
- }
-
- /**
- * Returns the deepest nested child component which contains "element". The
- * child component is also returned if "element" is part of its caption.
- *
- * @param element
- * An element that is a nested sub element of the root element in
- * this layout
- * @return The Paintable which the element is a part of. Null if the element
- * belongs to the layout and not to a child.
- */
- ComponentConnector getComponent(Element element) {
- return Util.getConnectorForElement(client, this, element);
- }
-
- void setCaption(Widget widget, VCaption caption) {
- VLayoutSlot slot = widgetToCell.get(widget).slot;
-
- if (caption != null) {
- // Logical attach.
- getChildren().add(caption);
- }
-
- // Physical attach if not null, also removes old caption
- slot.setCaption(caption);
-
- if (caption != null) {
- // Adopt.
- adopt(caption);
- }
- }
-
- private void togglePrefixedStyleName(String name, boolean enabled) {
- if (enabled) {
- addStyleDependentName(name);
- } else {
- removeStyleDependentName(name);
- }
- }
-
- void updateMarginStyleNames(MarginInfo marginInfo) {
- togglePrefixedStyleName("margin-top", marginInfo.hasTop());
- togglePrefixedStyleName("margin-right", marginInfo.hasRight());
- togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom());
- togglePrefixedStyleName("margin-left", marginInfo.hasLeft());
- }
-
- void updateSpacingStyleName(boolean spacingEnabled) {
- String styleName = getStylePrimaryName();
- if (spacingEnabled) {
- spacingMeasureElement.addClassName(styleName + "-spacing-on");
- spacingMeasureElement.removeClassName(styleName + "-spacing-off");
- } else {
- spacingMeasureElement.removeClassName(styleName + "-spacing-on");
- spacingMeasureElement.addClassName(styleName + "-spacing-off");
- }
- }
-
- public void setSize(int rows, int cols) {
- if (cells == null) {
- cells = new Cell[cols][rows];
- } else if (cells.length != cols || cells[0].length != rows) {
- Cell[][] newCells = new Cell[cols][rows];
- for (int i = 0; i < cells.length; i++) {
- for (int j = 0; j < cells[i].length; j++) {
- if (i < cols && j < rows) {
- newCells[i][j] = cells[i][j];
- }
- }
- }
- cells = newCells;
- }
- }
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.ClickEventHandler;
+import com.vaadin.client.ui.VImage;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.AbstractEmbeddedState;
import com.vaadin.shared.ui.Connect;
+++ /dev/null
-package com.vaadin.client.ui.image;
-
-import com.google.gwt.user.client.ui.Image;
-
-public class VImage extends Image {
-
- public static final String CLASSNAME = "v-image";
-
- public VImage() {
- setStylePrimaryName(CLASSNAME);
- }
-}
import com.vaadin.client.Util;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VLabel;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.label.LabelState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.label;
-
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.HTML;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.VTooltip;
-
-public class VLabel extends HTML {
-
- public static final String CLASSNAME = "v-label";
- private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w";
-
- private ApplicationConnection connection;
-
- public VLabel() {
- super();
- setStyleName(CLASSNAME);
- sinkEvents(VTooltip.TOOLTIP_EVENTS);
- }
-
- public VLabel(String text) {
- super(text);
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
- event.stopPropagation();
- return;
- }
- }
-
- @Override
- public void setWidth(String width) {
- super.setWidth(width);
- if (width == null || width.equals("")) {
- setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true);
- getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
- } else {
- setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false);
- getElement().getStyle().clearDisplay();
- }
- }
-
- @Override
- public void setText(String text) {
- if (BrowserInfo.get().isIE8()) {
- // #3983 - IE8 incorrectly replaces \n with <br> so we do the
- // escaping manually and set as HTML
- super.setHTML(Util.escapeHTML(text));
- } else {
- super.setText(text);
- }
- }
-
- void setConnection(ApplicationConnection client) {
- connection = client;
- }
-}
import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VLink;
import com.vaadin.shared.ui.BorderStyle;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.link.LinkConstants;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.link;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HTML;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.shared.ui.BorderStyle;
-
-public class VLink extends HTML implements ClickHandler {
-
- public static final String CLASSNAME = "v-link";
-
- @Deprecated
- protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT;
-
- @Deprecated
- protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL;
-
- @Deprecated
- protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE;
-
- protected String src;
-
- protected String target;
-
- protected BorderStyle borderStyle = BorderStyle.DEFAULT;
-
- protected boolean enabled;
-
- protected int targetWidth;
-
- protected int targetHeight;
-
- protected Element errorIndicatorElement;
-
- protected final Element anchor = DOM.createAnchor();
-
- protected final Element captionElement = DOM.createSpan();
-
- protected Icon icon;
-
- protected ApplicationConnection client;
-
- public VLink() {
- super();
- getElement().appendChild(anchor);
- anchor.appendChild(captionElement);
- addClickHandler(this);
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void onClick(ClickEvent event) {
- if (enabled) {
- if (target == null) {
- target = "_self";
- }
- String features;
- switch (borderStyle) {
- case NONE:
- features = "menubar=no,location=no,status=no";
- break;
- case MINIMAL:
- features = "menubar=yes,location=no,status=no";
- break;
- default:
- features = "";
- break;
- }
-
- if (targetWidth > 0) {
- features += (features.length() > 0 ? "," : "") + "width="
- + targetWidth;
- }
- if (targetHeight > 0) {
- features += (features.length() > 0 ? "," : "") + "height="
- + targetHeight;
- }
-
- if (features.length() > 0) {
- // if 'special features' are set, use window.open(), unless
- // a modifier key is held (ctrl to open in new tab etc)
- Event e = DOM.eventGetCurrentEvent();
- if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey()
- && !e.getMetaKey()) {
- Window.open(src, target, features);
- e.preventDefault();
- }
- }
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- final Element target = DOM.eventGetTarget(event);
- if (event.getTypeInt() == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
- }
- if (target == captionElement || target == anchor
- || (icon != null && target == icon.getElement())) {
- super.onBrowserEvent(event);
- }
- if (!enabled) {
- event.preventDefault();
- }
-
- }
-
-}
package com.vaadin.client.ui.listselect;
+import com.vaadin.client.ui.VListSelect;
import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.ListSelect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.listselect;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.user.client.ui.ListBox;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-
-public class VListSelect extends VOptionGroupBase {
-
- public static final String CLASSNAME = "v-select";
-
- private static final int VISIBLE_COUNT = 10;
-
- protected ListBox select;
-
- private int lastSelectedIndex = -1;
-
- public VListSelect() {
- super(new ListBox(true), CLASSNAME);
- select = getOptionsContainer();
- select.addChangeHandler(this);
- select.addClickHandler(this);
- select.setVisibleItemCount(VISIBLE_COUNT);
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames();
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- updateStyleNames();
- }
-
- protected void updateStyleNames() {
- container.setStyleName(getStylePrimaryName());
- select.setStyleName(getStylePrimaryName() + "-select");
- }
-
- protected ListBox getOptionsContainer() {
- return (ListBox) optionsContainer;
- }
-
- @Override
- protected void buildOptions(UIDL uidl) {
- select.setMultipleSelect(isMultiselect());
- select.setEnabled(!isDisabled() && !isReadonly());
- select.clear();
- if (!isMultiselect() && isNullSelectionAllowed()
- && !isNullSelectionItemAvailable()) {
- // can't unselect last item in singleselect mode
- select.addItem("", (String) null);
- }
- for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
- final UIDL optionUidl = (UIDL) i.next();
- select.addItem(optionUidl.getStringAttribute("caption"),
- optionUidl.getStringAttribute("key"));
- if (optionUidl.hasAttribute("selected")) {
- int itemIndex = select.getItemCount() - 1;
- select.setItemSelected(itemIndex, true);
- lastSelectedIndex = itemIndex;
- }
- }
- if (getRows() > 0) {
- select.setVisibleItemCount(getRows());
- }
- }
-
- @Override
- protected String[] getSelectedItems() {
- final ArrayList<String> selectedItemKeys = new ArrayList<String>();
- for (int i = 0; i < select.getItemCount(); i++) {
- if (select.isItemSelected(i)) {
- selectedItemKeys.add(select.getValue(i));
- }
- }
- return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
- }
-
- @Override
- public void onChange(ChangeEvent event) {
- final int si = select.getSelectedIndex();
- if (si == -1 && !isNullSelectionAllowed()) {
- select.setSelectedIndex(lastSelectedIndex);
- } else {
- lastSelectedIndex = si;
- if (isMultiselect()) {
- client.updateVariable(paintableId, "selected",
- getSelectedItems(), isImmediate());
- } else {
- client.updateVariable(paintableId, "selected",
- new String[] { "" + getSelectedItem() }, isImmediate());
- }
- }
- }
-
- @Override
- public void setHeight(String height) {
- select.setHeight(height);
- super.setHeight(height);
- }
-
- @Override
- public void setWidth(String width) {
- select.setWidth(width);
- super.setWidth(width);
- }
-
- @Override
- protected void setTabIndex(int tabIndex) {
- getOptionsContainer().setTabIndex(tabIndex);
- }
-
- @Override
- public void focus() {
- select.setFocus(true);
- }
-}
\ No newline at end of file
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.Icon;
import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VMenuBar;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.menubar;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.HasHTML;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.SimpleFocusablePanel;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.ui.menubar.MenuBarConstants;
-
-public class VMenuBar extends SimpleFocusablePanel implements
- CloseHandler<PopupPanel>, KeyPressHandler, KeyDownHandler,
- FocusHandler, SubPartAware {
-
- // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable,
- // used for the root menu but also used for the sub menus.
-
- /** Set the CSS class name to allow styling. */
- public static final String CLASSNAME = "v-menubar";
- public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu";
-
- /** For server connections **/
- protected String uidlId;
- protected ApplicationConnection client;
-
- protected final VMenuBar hostReference = this;
- protected CustomMenuItem moreItem = null;
-
- // Only used by the root menu bar
- protected VMenuBar collapsedRootItems;
-
- // Construct an empty command to be used when the item has no command
- // associated
- protected static final Command emptyCommand = null;
-
- /** Widget fields **/
- protected boolean subMenu;
- protected ArrayList<CustomMenuItem> items;
- protected Element containerElement;
- protected VOverlay popup;
- protected VMenuBar visibleChildMenu;
- protected boolean menuVisible = false;
- protected VMenuBar parentMenu;
- protected CustomMenuItem selected;
-
- boolean enabled = true;
-
- private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100,
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- iLayout(true);
- }
- });
-
- boolean openRootOnHover;
-
- boolean htmlContentAllowed;
-
- public VMenuBar() {
- // Create an empty horizontal menubar
- this(false, null);
-
- // Navigation is only handled by the root bar
- addFocusHandler(this);
-
- /*
- * Firefox auto-repeat works correctly only if we use a key press
- * handler, other browsers handle it correctly when using a key down
- * handler
- */
- if (BrowserInfo.get().isGecko()) {
- addKeyPressHandler(this);
- } else {
- addKeyDownHandler(this);
- }
- }
-
- public VMenuBar(boolean subMenu, VMenuBar parentMenu) {
-
- items = new ArrayList<CustomMenuItem>();
- popup = null;
- visibleChildMenu = null;
- this.subMenu = subMenu;
-
- containerElement = getElement();
-
- sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
- | Event.ONLOAD);
-
- if (parentMenu == null) {
- // Root menu
- setStyleName(CLASSNAME);
- } else {
- // Child menus inherits style name
- setStyleName(parentMenu.getStyleName());
- }
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames();
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- updateStyleNames();
- }
-
- protected void updateStyleNames() {
- String primaryStyleName = getParentMenu() != null ? getParentMenu()
- .getStylePrimaryName() : getStylePrimaryName();
-
- // Reset the style name for all the items
- for (CustomMenuItem item : items) {
- item.setStyleName(primaryStyleName + "-menuitem");
- }
-
- if (subMenu
- && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) {
- /*
- * Sub-menus should get the sub-menu prefix
- */
- super.setStylePrimaryName(primaryStyleName
- + SUBMENU_CLASSNAME_PREFIX);
- }
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- if (!subMenu) {
- setSelected(null);
- hideChildren();
- menuVisible = false;
- }
- }
-
- void updateSize() {
- // Take from setWidth
- if (!subMenu) {
- // Only needed for root level menu
- hideChildren();
- setSelected(null);
- menuVisible = false;
- }
- }
-
- /**
- * Build the HTML content for a menu item.
- *
- * @param item
- * @return
- */
- protected String buildItemHTML(UIDL item) {
- // Construct html from the text and the optional icon
- StringBuffer itemHTML = new StringBuffer();
- if (item.hasAttribute("separator")) {
- itemHTML.append("<span>---</span>");
- } else {
- // Add submenu indicator
- if (item.getChildCount() > 0) {
- String bgStyle = "";
- itemHTML.append("<span class=\"" + getStylePrimaryName()
- + "-submenu-indicator\"" + bgStyle + ">►</span>");
- }
-
- itemHTML.append("<span class=\"" + getStylePrimaryName()
- + "-menuitem-caption\">");
- if (item.hasAttribute("icon")) {
- itemHTML.append("<img src=\""
- + Util.escapeAttribute(client.translateVaadinUri(item
- .getStringAttribute("icon"))) + "\" class=\""
- + Icon.CLASSNAME + "\" alt=\"\" />");
- }
- String itemText = item.getStringAttribute("text");
- if (!htmlContentAllowed) {
- itemText = Util.escapeHTML(itemText);
- }
- itemHTML.append(itemText);
- itemHTML.append("</span>");
- }
- return itemHTML.toString();
- }
-
- /**
- * This is called by the items in the menu and it communicates the
- * information to the server
- *
- * @param clickedItemId
- * id of the item that was clicked
- */
- public void onMenuClick(int clickedItemId) {
- // Updating the state to the server can not be done before
- // the server connection is known, i.e., before updateFromUIDL()
- // has been called.
- if (uidlId != null && client != null) {
- // Communicate the user interaction parameters to server. This call
- // will initiate an AJAX request to the server.
- client.updateVariable(uidlId, "clickedId", clickedItemId, true);
- }
- }
-
- /** Widget methods **/
-
- /**
- * Returns a list of items in this menu
- */
- public List<CustomMenuItem> getItems() {
- return items;
- }
-
- /**
- * Remove all the items in this menu
- */
- public void clearItems() {
- Element e = getContainerElement();
- while (DOM.getChildCount(e) > 0) {
- DOM.removeChild(e, DOM.getChild(e, 0));
- }
- items.clear();
- }
-
- /**
- * Returns the containing element of the menu
- *
- * @return
- */
- @Override
- public Element getContainerElement() {
- return containerElement;
- }
-
- /**
- * Add a new item to this menu
- *
- * @param html
- * items text
- * @param cmd
- * items command
- * @return the item created
- */
- public CustomMenuItem addItem(String html, Command cmd) {
- CustomMenuItem item = GWT.create(CustomMenuItem.class);
- item.setHTML(html);
- item.setCommand(cmd);
- addItem(item);
- return item;
- }
-
- /**
- * Add a new item to this menu
- *
- * @param item
- */
- public void addItem(CustomMenuItem item) {
- if (items.contains(item)) {
- return;
- }
- DOM.appendChild(getContainerElement(), item.getElement());
- item.setParentMenu(this);
- item.setSelected(false);
- items.add(item);
- }
-
- public void addItem(CustomMenuItem item, int index) {
- if (items.contains(item)) {
- return;
- }
- DOM.insertChild(getContainerElement(), item.getElement(), index);
- item.setParentMenu(this);
- item.setSelected(false);
- items.add(index, item);
- }
-
- /**
- * Remove the given item from this menu
- *
- * @param item
- */
- public void removeItem(CustomMenuItem item) {
- if (items.contains(item)) {
- int index = items.indexOf(item);
-
- DOM.removeChild(getContainerElement(),
- DOM.getChild(getContainerElement(), index));
- items.remove(index);
- }
- }
-
- /*
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
- * .client.Event)
- */
- @Override
- public void onBrowserEvent(Event e) {
- super.onBrowserEvent(e);
-
- // Handle onload events (icon loaded, size changes)
- if (DOM.eventGetType(e) == Event.ONLOAD) {
- VMenuBar parent = getParentMenu();
- if (parent != null) {
- // The onload event for an image in a popup should be sent to
- // the parent, which owns the popup
- parent.iconLoaded();
- } else {
- // Onload events for images in the root menu are handled by the
- // root menu itself
- iconLoaded();
- }
- return;
- }
-
- Element targetElement = DOM.eventGetTarget(e);
- CustomMenuItem targetItem = null;
- for (int i = 0; i < items.size(); i++) {
- CustomMenuItem item = items.get(i);
- if (DOM.isOrHasChild(item.getElement(), targetElement)) {
- targetItem = item;
- }
- }
-
- if (targetItem != null) {
- switch (DOM.eventGetType(e)) {
-
- case Event.ONCLICK:
- if (isEnabled() && targetItem.isEnabled()) {
- itemClick(targetItem);
- }
- if (subMenu) {
- // Prevent moving keyboard focus to child menus
- VMenuBar parent = parentMenu;
- while (parent.getParentMenu() != null) {
- parent = parent.getParentMenu();
- }
- parent.setFocus(true);
- }
-
- break;
-
- case Event.ONMOUSEOVER:
- LazyCloser.cancelClosing();
-
- if (isEnabled() && targetItem.isEnabled()) {
- itemOver(targetItem);
- }
- break;
-
- case Event.ONMOUSEOUT:
- itemOut(targetItem);
- LazyCloser.schedule();
- break;
- }
- } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) {
- // Prevent moving keyboard focus to child menus
- VMenuBar parent = parentMenu;
- while (parent.getParentMenu() != null) {
- parent = parent.getParentMenu();
- }
- parent.setFocus(true);
- }
- }
-
- private boolean isEnabled() {
- return enabled;
- }
-
- private void iconLoaded() {
- iconLoadedExecutioner.trigger();
- }
-
- /**
- * When an item is clicked
- *
- * @param item
- */
- public void itemClick(CustomMenuItem item) {
- if (item.getCommand() != null) {
- setSelected(null);
-
- if (visibleChildMenu != null) {
- visibleChildMenu.hideChildren();
- }
-
- hideParents(true);
- menuVisible = false;
- Scheduler.get().scheduleDeferred(item.getCommand());
-
- } else {
- if (item.getSubMenu() != null
- && item.getSubMenu() != visibleChildMenu) {
- setSelected(item);
- showChildMenu(item);
- menuVisible = true;
- } else if (!subMenu) {
- setSelected(null);
- hideChildren();
- menuVisible = false;
- }
- }
- }
-
- /**
- * When the user hovers the mouse over the item
- *
- * @param item
- */
- public void itemOver(CustomMenuItem item) {
- if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) {
- setSelected(item);
- if (!subMenu && openRootOnHover && !menuVisible) {
- menuVisible = true; // start opening menus
- LazyCloser.prepare(this);
- }
- }
-
- if (menuVisible && visibleChildMenu != item.getSubMenu()
- && popup != null) {
- popup.hide();
- }
-
- if (menuVisible && item.getSubMenu() != null
- && visibleChildMenu != item.getSubMenu()) {
- showChildMenu(item);
- }
- }
-
- /**
- * When the mouse is moved away from an item
- *
- * @param item
- */
- public void itemOut(CustomMenuItem item) {
- if (visibleChildMenu != item.getSubMenu()) {
- hideChildMenu(item);
- setSelected(null);
- } else if (visibleChildMenu == null) {
- setSelected(null);
- }
- }
-
- /**
- * Used to autoclose submenus when they the menu is in a mode which opens
- * root menus on mouse hover.
- */
- private static class LazyCloser extends Timer {
- static LazyCloser INSTANCE;
- private VMenuBar activeRoot;
-
- @Override
- public void run() {
- activeRoot.hideChildren();
- activeRoot.setSelected(null);
- activeRoot.menuVisible = false;
- activeRoot = null;
- }
-
- public static void cancelClosing() {
- if (INSTANCE != null) {
- INSTANCE.cancel();
- }
- }
-
- public static void prepare(VMenuBar vMenuBar) {
- if (INSTANCE == null) {
- INSTANCE = new LazyCloser();
- }
- if (INSTANCE.activeRoot == vMenuBar) {
- INSTANCE.cancel();
- } else if (INSTANCE.activeRoot != null) {
- INSTANCE.cancel();
- INSTANCE.run();
- }
- INSTANCE.activeRoot = vMenuBar;
- }
-
- public static void schedule() {
- if (INSTANCE != null && INSTANCE.activeRoot != null) {
- INSTANCE.schedule(750);
- }
- }
-
- }
-
- /**
- * Shows the child menu of an item. The caller must ensure that the item has
- * a submenu.
- *
- * @param item
- */
- public void showChildMenu(CustomMenuItem item) {
-
- int left = 0;
- int top = 0;
- if (subMenu) {
- left = item.getParentMenu().getAbsoluteLeft()
- + item.getParentMenu().getOffsetWidth();
- top = item.getAbsoluteTop();
- } else {
- left = item.getAbsoluteLeft();
- top = item.getParentMenu().getAbsoluteTop()
- + item.getParentMenu().getOffsetHeight();
- }
- showChildMenuAt(item, top, left);
- }
-
- protected void showChildMenuAt(CustomMenuItem item, int top, int left) {
- final int shadowSpace = 10;
-
- popup = new VOverlay(true, false, true);
- popup.setOwner(this);
-
- /*
- * Use parents primary style name if possible and remove the submenu
- * prefix if needed
- */
- String primaryStyleName = parentMenu != null ? parentMenu
- .getStylePrimaryName() : getStylePrimaryName();
- if (subMenu) {
- primaryStyleName = primaryStyleName.replace(
- SUBMENU_CLASSNAME_PREFIX, "");
- }
- popup.setStyleName(primaryStyleName + "-popup");
-
- // Setting owner and handlers to support tooltips. Needed for tooltip
- // handling of overlay widgets (will direct queries to parent menu)
- if (parentMenu == null) {
- popup.setOwner(this);
- } else {
- VMenuBar parent = parentMenu;
- while (parent.getParentMenu() != null) {
- parent = parent.getParentMenu();
- }
- popup.setOwner(parent);
- }
- if (client != null) {
- client.getVTooltip().connectHandlersToWidget(popup);
- }
-
- popup.setWidget(item.getSubMenu());
- popup.addCloseHandler(this);
- popup.addAutoHidePartner(item.getElement());
-
- // at 0,0 because otherwise IE7 add extra scrollbars (#5547)
- popup.setPopupPosition(0, 0);
-
- item.getSubMenu().onShow();
- visibleChildMenu = item.getSubMenu();
- item.getSubMenu().setParentMenu(this);
-
- popup.show();
-
- if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
- .getOffsetWidth() - shadowSpace) {
- if (subMenu) {
- left = item.getParentMenu().getAbsoluteLeft()
- - popup.getOffsetWidth() - shadowSpace;
- } else {
- left = RootPanel.getBodyElement().getOffsetWidth()
- - popup.getOffsetWidth() - shadowSpace;
- }
- // Accommodate space for shadow
- if (left < shadowSpace) {
- left = shadowSpace;
- }
- }
-
- top = adjustPopupHeight(top, shadowSpace);
-
- popup.setPopupPosition(left, top);
-
- }
-
- private int adjustPopupHeight(int top, final int shadowSpace) {
- // Check that the popup will fit the screen
- int availableHeight = RootPanel.getBodyElement().getOffsetHeight()
- - top - shadowSpace;
- int missingHeight = popup.getOffsetHeight() - availableHeight;
- if (missingHeight > 0) {
- // First move the top of the popup to get more space
- // Don't move above top of screen, don't move more than needed
- int moveUpBy = Math.min(top - shadowSpace, missingHeight);
-
- // Update state
- top -= moveUpBy;
- missingHeight -= moveUpBy;
- availableHeight += moveUpBy;
-
- if (missingHeight > 0) {
- int contentWidth = visibleChildMenu.getOffsetWidth();
-
- // If there's still not enough room, limit height to fit and add
- // a scroll bar
- Style style = popup.getElement().getStyle();
- style.setHeight(availableHeight, Unit.PX);
- style.setOverflowY(Overflow.SCROLL);
-
- // Make room for the scroll bar by adjusting the width of the
- // popup
- style.setWidth(contentWidth + Util.getNativeScrollbarSize(),
- Unit.PX);
- popup.positionOrSizeUpdated();
- }
- }
- return top;
- }
-
- /**
- * Hides the submenu of an item
- *
- * @param item
- */
- public void hideChildMenu(CustomMenuItem item) {
- if (visibleChildMenu != null
- && !(visibleChildMenu == item.getSubMenu())) {
- popup.hide();
- }
- }
-
- /**
- * When the menu is shown.
- */
- public void onShow() {
- // remove possible previous selection
- if (selected != null) {
- selected.setSelected(false);
- selected = null;
- }
- menuVisible = true;
- }
-
- /**
- * Listener method, fired when this menu is closed
- */
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- hideChildren();
- if (event.isAutoClosed()) {
- hideParents(true);
- menuVisible = false;
- }
- visibleChildMenu = null;
- popup = null;
- }
-
- /**
- * Recursively hide all child menus
- */
- public void hideChildren() {
- if (visibleChildMenu != null) {
- visibleChildMenu.hideChildren();
- popup.hide();
- }
- }
-
- /**
- * Recursively hide all parent menus
- */
- public void hideParents(boolean autoClosed) {
- if (visibleChildMenu != null) {
- popup.hide();
- setSelected(null);
- menuVisible = !autoClosed;
- }
-
- if (getParentMenu() != null) {
- getParentMenu().hideParents(autoClosed);
- }
- }
-
- /**
- * Returns the parent menu of this menu, or null if this is the top-level
- * menu
- *
- * @return
- */
- public VMenuBar getParentMenu() {
- return parentMenu;
- }
-
- /**
- * Set the parent menu of this menu
- *
- * @param parent
- */
- public void setParentMenu(VMenuBar parent) {
- parentMenu = parent;
- }
-
- /**
- * Returns the currently selected item of this menu, or null if nothing is
- * selected
- *
- * @return
- */
- public CustomMenuItem getSelected() {
- return selected;
- }
-
- /**
- * Set the currently selected item of this menu
- *
- * @param item
- */
- public void setSelected(CustomMenuItem item) {
- // If we had something selected, unselect
- if (item != selected && selected != null) {
- selected.setSelected(false);
- }
- // If we have a valid selection, select it
- if (item != null) {
- item.setSelected(true);
- }
-
- selected = item;
- }
-
- /**
- *
- * A class to hold information on menu items
- *
- */
- public static class CustomMenuItem extends Widget implements HasHTML {
-
- protected String html = null;
- protected Command command = null;
- protected VMenuBar subMenu = null;
- protected VMenuBar parentMenu = null;
- protected boolean enabled = true;
- protected boolean isSeparator = false;
- protected boolean checkable = false;
- protected boolean checked = false;
- protected boolean selected = false;
- protected String description = null;
-
- private String styleName;
-
- /**
- * Default menu item {@link Widget} constructor for GWT.create().
- *
- * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after
- * constructing a menu item.
- */
- public CustomMenuItem() {
- this("", null);
- }
-
- /**
- * Creates a menu item {@link Widget}.
- *
- * @param html
- * @param cmd
- * @deprecated use the default constructor and {@link #setHTML(String)}
- * and {@link #setCommand(Command)} instead
- */
- @Deprecated
- public CustomMenuItem(String html, Command cmd) {
- // We need spans to allow inline-block in IE
- setElement(DOM.createSpan());
-
- setHTML(html);
- setCommand(cmd);
- setSelected(false);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- updateStyleNames();
-
- // Pass stylename down to submenus
- if (getSubMenu() != null) {
- getSubMenu().setStyleName(style);
- }
- }
-
- public void setSelected(boolean selected) {
- this.selected = selected;
- updateStyleNames();
- }
-
- public void setChecked(boolean checked) {
- if (checkable && !isSeparator) {
- this.checked = checked;
- } else {
- this.checked = false;
- }
- updateStyleNames();
- }
-
- public boolean isChecked() {
- return checked;
- }
-
- public void setCheckable(boolean checkable) {
- if (checkable && !isSeparator) {
- this.checkable = true;
- } else {
- setChecked(false);
- this.checkable = false;
- }
- }
-
- public boolean isCheckable() {
- return checkable;
- }
-
- /*
- * setters and getters for the fields
- */
-
- public void setSubMenu(VMenuBar subMenu) {
- this.subMenu = subMenu;
- }
-
- public VMenuBar getSubMenu() {
- return subMenu;
- }
-
- public void setParentMenu(VMenuBar parentMenu) {
- this.parentMenu = parentMenu;
- updateStyleNames();
- }
-
- protected void updateStyleNames() {
- if (parentMenu == null) {
- // Style names depend on the parent menu's primary style name so
- // don't do updates until the item has a parent
- return;
- }
-
- String primaryStyleName = parentMenu.getStylePrimaryName();
- if (parentMenu.subMenu) {
- primaryStyleName = primaryStyleName.replace(
- SUBMENU_CLASSNAME_PREFIX, "");
- }
-
- if (isSeparator) {
- super.setStyleName(primaryStyleName + "-separator");
- } else {
- super.setStyleName(primaryStyleName + "-menuitem");
- }
-
- if (styleName != null) {
- addStyleDependentName(styleName);
- }
-
- if (enabled) {
- removeStyleDependentName("disabled");
- } else {
- addStyleDependentName("disabled");
- }
-
- if (selected && isSelectable()) {
- addStyleDependentName("selected");
- // needed for IE6 to have a single style name to match for an
- // element
- // TODO Can be optimized now that IE6 is not supported any more
- if (checkable) {
- if (checked) {
- removeStyleDependentName("selected-unchecked");
- addStyleDependentName("selected-checked");
- } else {
- removeStyleDependentName("selected-checked");
- addStyleDependentName("selected-unchecked");
- }
- }
- } else {
- removeStyleDependentName("selected");
- // needed for IE6 to have a single style name to match for an
- // element
- removeStyleDependentName("selected-checked");
- removeStyleDependentName("selected-unchecked");
- }
-
- if (checkable && !isSeparator) {
- if (checked) {
- addStyleDependentName("checked");
- removeStyleDependentName("unchecked");
- } else {
- addStyleDependentName("unchecked");
- removeStyleDependentName("checked");
- }
- }
- }
-
- public VMenuBar getParentMenu() {
- return parentMenu;
- }
-
- public void setCommand(Command command) {
- this.command = command;
- }
-
- public Command getCommand() {
- return command;
- }
-
- @Override
- public String getHTML() {
- return html;
- }
-
- @Override
- public void setHTML(String html) {
- this.html = html;
- DOM.setInnerHTML(getElement(), html);
-
- // Sink the onload event for any icons. The onload
- // events are handled by the parent VMenuBar.
- Util.sinkOnloadForImages(getElement());
- }
-
- @Override
- public String getText() {
- return html;
- }
-
- @Override
- public void setText(String text) {
- setHTML(Util.escapeHTML(text));
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- updateStyleNames();
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- private void setSeparator(boolean separator) {
- isSeparator = separator;
- updateStyleNames();
- if (!separator) {
- setEnabled(enabled);
- }
- }
-
- public boolean isSeparator() {
- return isSeparator;
- }
-
- public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
- setSeparator(uidl.hasAttribute("separator"));
- setEnabled(!uidl
- .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED));
-
- if (!isSeparator()
- && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) {
- // if the selected attribute is present (either true or false),
- // the item is selectable
- setCheckable(true);
- setChecked(uidl
- .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED));
- } else {
- setCheckable(false);
- }
-
- if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) {
- styleName = uidl
- .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE);
- }
-
- if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) {
- description = uidl
- .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION);
- }
-
- updateStyleNames();
- }
-
- public TooltipInfo getTooltip() {
- if (description == null) {
- return null;
- }
-
- return new TooltipInfo(description);
- }
-
- /**
- * Checks if the item can be selected.
- *
- * @return true if it is possible to select this item, false otherwise
- */
- public boolean isSelectable() {
- return !isSeparator() && isEnabled();
- }
-
- }
-
- /**
- * @author Jouni Koivuviita / Vaadin Ltd.
- */
- public void iLayout() {
- iLayout(false);
- updateSize();
- }
-
- public void iLayout(boolean iconLoadEvent) {
- // Only collapse if there is more than one item in the root menu and the
- // menu has an explicit size
- if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems
- .getItems().size() > 0))
- && getElement().getStyle().getProperty("width") != null
- && moreItem != null) {
-
- // Measure the width of the "more" item
- final boolean morePresent = getItems().contains(moreItem);
- addItem(moreItem);
- final int moreItemWidth = moreItem.getOffsetWidth();
- if (!morePresent) {
- removeItem(moreItem);
- }
-
- int availableWidth = LayoutManager.get(client).getInnerWidth(
- getElement());
-
- // Used width includes the "more" item if present
- int usedWidth = getConsumedWidth();
- int diff = availableWidth - usedWidth;
- removeItem(moreItem);
-
- if (diff < 0) {
- // Too many items: collapse last items from root menu
- int widthNeeded = usedWidth - availableWidth;
- if (!morePresent) {
- widthNeeded += moreItemWidth;
- }
- int widthReduced = 0;
-
- while (widthReduced < widthNeeded && getItems().size() > 0) {
- // Move last root menu item to collapsed menu
- CustomMenuItem collapse = getItems().get(
- getItems().size() - 1);
- widthReduced += collapse.getOffsetWidth();
- removeItem(collapse);
- collapsedRootItems.addItem(collapse, 0);
- }
- } else if (collapsedRootItems.getItems().size() > 0) {
- // Space available for items: expand first items from collapsed
- // menu
- int widthAvailable = diff + moreItemWidth;
- int widthGrowth = 0;
-
- while (widthAvailable > widthGrowth
- && collapsedRootItems.getItems().size() > 0) {
- // Move first item from collapsed menu to the root menu
- CustomMenuItem expand = collapsedRootItems.getItems()
- .get(0);
- collapsedRootItems.removeItem(expand);
- addItem(expand);
- widthGrowth += expand.getOffsetWidth();
- if (collapsedRootItems.getItems().size() > 0) {
- widthAvailable -= moreItemWidth;
- }
- if (widthGrowth > widthAvailable) {
- removeItem(expand);
- collapsedRootItems.addItem(expand, 0);
- } else {
- widthAvailable = diff + moreItemWidth;
- }
- }
- }
- if (collapsedRootItems.getItems().size() > 0) {
- addItem(moreItem);
- }
- }
-
- // If a popup is open we might need to adjust the shadow as well if an
- // icon shown in that popup was loaded
- if (popup != null) {
- // Forces a recalculation of the shadow size
- popup.show();
- }
- if (iconLoadEvent) {
- // Size have changed if the width is undefined
- Util.notifyParentOfSizeChange(this, false);
- }
- }
-
- private int getConsumedWidth() {
- int w = 0;
- for (CustomMenuItem item : getItems()) {
- if (!collapsedRootItems.getItems().contains(item)) {
- w += item.getOffsetWidth();
- }
- }
- return w;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
- * .gwt.event.dom.client.KeyPressEvent)
- */
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (handleNavigation(event.getNativeEvent().getKeyCode(),
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- event.preventDefault();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (handleNavigation(event.getNativeEvent().getKeyCode(),
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- event.preventDefault();
- }
- }
-
- /**
- * Get the key that moves the selection upwards. By default it is the up
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationUpKey() {
- return KeyCodes.KEY_UP;
- }
-
- /**
- * Get the key that moves the selection downwards. By default it is the down
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationDownKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * Get the key that moves the selection left. By default it is the left
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationLeftKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * Get the key that moves the selection right. By default it is the right
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationRightKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- /**
- * Get the key that selects a menu item. By default it is the Enter key but
- * by overriding this you can change the key to whatever you want.
- *
- * @return
- */
- protected int getNavigationSelectKey() {
- return KeyCodes.KEY_ENTER;
- }
-
- /**
- * Get the key that closes the menu. By default it is the escape key but by
- * overriding this yoy can change the key to whatever you want.
- *
- * @return
- */
- protected int getCloseMenuKey() {
- return KeyCodes.KEY_ESCAPE;
- }
-
- /**
- * Handles the keyboard events handled by the MenuBar
- *
- * @param event
- * The keyboard event received
- * @return true iff the navigation event was handled
- */
- public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-
- // If tab or shift+tab close menus
- if (keycode == KeyCodes.KEY_TAB) {
- setSelected(null);
- hideChildren();
- menuVisible = false;
- return false;
- }
-
- if (ctrl || shift || !isEnabled()) {
- // Do not handle tab key, nor ctrl keys
- return false;
- }
-
- if (keycode == getNavigationLeftKey()) {
- if (getSelected() == null) {
- // If nothing is selected then select the last item
- setSelected(items.get(items.size() - 1));
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- } else if (visibleChildMenu == null && getParentMenu() == null) {
- // If this is the root menu then move to the left
- int idx = items.indexOf(getSelected());
- if (idx > 0) {
- setSelected(items.get(idx - 1));
- } else {
- setSelected(items.get(items.size() - 1));
- }
-
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- } else if (visibleChildMenu != null) {
- // Redirect all navigation to the submenu
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-
- } else if (getParentMenu().getParentMenu() == null) {
- // Inside a sub menu, whose parent is a root menu item
- VMenuBar root = getParentMenu();
-
- root.getSelected().getSubMenu().setSelected(null);
- root.hideChildren();
-
- // Get the root menus items and select the previous one
- int idx = root.getItems().indexOf(root.getSelected());
- idx = idx > 0 ? idx : root.getItems().size();
- CustomMenuItem selected = root.getItems().get(--idx);
-
- while (selected.isSeparator() || !selected.isEnabled()) {
- idx = idx > 0 ? idx : root.getItems().size();
- selected = root.getItems().get(--idx);
- }
-
- root.setSelected(selected);
- openMenuAndFocusFirstIfPossible(selected);
- } else {
- getParentMenu().getSelected().getSubMenu().setSelected(null);
- getParentMenu().hideChildren();
- }
-
- return true;
-
- } else if (keycode == getNavigationRightKey()) {
-
- if (getSelected() == null) {
- // If nothing is selected then select the first item
- setSelected(items.get(0));
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- } else if (visibleChildMenu == null && getParentMenu() == null) {
- // If this is the root menu then move to the right
- int idx = items.indexOf(getSelected());
-
- if (idx < items.size() - 1) {
- setSelected(items.get(idx + 1));
- } else {
- setSelected(items.get(0));
- }
-
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- } else if (visibleChildMenu == null
- && getSelected().getSubMenu() != null) {
- // If the item has a submenu then show it and move the selection
- // there
- showChildMenu(getSelected());
- menuVisible = true;
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
- } else if (visibleChildMenu == null) {
-
- // Get the root menu
- VMenuBar root = getParentMenu();
- while (root.getParentMenu() != null) {
- root = root.getParentMenu();
- }
-
- // Hide the submenu
- root.hideChildren();
-
- // Get the root menus items and select the next one
- int idx = root.getItems().indexOf(root.getSelected());
- idx = idx < root.getItems().size() - 1 ? idx : -1;
- CustomMenuItem selected = root.getItems().get(++idx);
-
- while (selected.isSeparator() || !selected.isEnabled()) {
- idx = idx < root.getItems().size() - 1 ? idx : -1;
- selected = root.getItems().get(++idx);
- }
-
- root.setSelected(selected);
- openMenuAndFocusFirstIfPossible(selected);
- } else if (visibleChildMenu != null) {
- // Redirect all navigation to the submenu
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
- }
-
- return true;
-
- } else if (keycode == getNavigationUpKey()) {
-
- if (getSelected() == null) {
- // If nothing is selected then select the last item
- setSelected(items.get(items.size() - 1));
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- } else if (visibleChildMenu != null) {
- // Redirect all navigation to the submenu
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
- } else {
- // Select the previous item if possible or loop to the last item
- int idx = items.indexOf(getSelected());
- if (idx > 0) {
- setSelected(items.get(idx - 1));
- } else {
- setSelected(items.get(items.size() - 1));
- }
-
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- }
-
- return true;
-
- } else if (keycode == getNavigationDownKey()) {
-
- if (getSelected() == null) {
- // If nothing is selected then select the first item
- selectFirstItem();
- } else if (visibleChildMenu == null && getParentMenu() == null) {
- // If this is the root menu the show the child menu with arrow
- // down, if there is a child menu
- openMenuAndFocusFirstIfPossible(getSelected());
- } else if (visibleChildMenu != null) {
- // Redirect all navigation to the submenu
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
- } else {
- // Select the next item if possible or loop to the first item
- int idx = items.indexOf(getSelected());
- if (idx < items.size() - 1) {
- setSelected(items.get(idx + 1));
- } else {
- setSelected(items.get(0));
- }
-
- if (!getSelected().isSelectable()) {
- handleNavigation(keycode, ctrl, shift);
- }
- }
- return true;
-
- } else if (keycode == getCloseMenuKey()) {
- setSelected(null);
- hideChildren();
- menuVisible = false;
-
- } else if (keycode == getNavigationSelectKey()) {
- if (getSelected() == null) {
- // If nothing is selected then select the first item
- selectFirstItem();
- } else if (visibleChildMenu != null) {
- // Redirect all navigation to the submenu
- visibleChildMenu.handleNavigation(keycode, ctrl, shift);
- menuVisible = false;
- } else if (visibleChildMenu == null
- && getSelected().getSubMenu() != null) {
- // If the item has a sub menu then show it and move the
- // selection there
- openMenuAndFocusFirstIfPossible(getSelected());
- } else {
- Command command = getSelected().getCommand();
- if (command != null) {
- command.execute();
- }
-
- setSelected(null);
- hideParents(true);
- }
- }
-
- return false;
- }
-
- private void selectFirstItem() {
- for (int i = 0; i < items.size(); i++) {
- CustomMenuItem item = items.get(i);
- if (item.isSelectable()) {
- setSelected(item);
- break;
- }
- }
- }
-
- private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) {
- VMenuBar subMenu = menuItem.getSubMenu();
- if (subMenu == null) {
- // No child menu? Nothing to do
- return;
- }
-
- VMenuBar parentMenu = menuItem.getParentMenu();
- parentMenu.showChildMenu(menuItem);
-
- menuVisible = true;
- // Select the first item in the newly open submenu
- subMenu.selectFirstItem();
-
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
- * .dom.client.FocusEvent)
- */
- @Override
- public void onFocus(FocusEvent event) {
-
- }
-
- private final String SUBPART_PREFIX = "item";
-
- @Override
- public Element getSubPartElement(String subPart) {
- int index = Integer
- .parseInt(subPart.substring(SUBPART_PREFIX.length()));
- CustomMenuItem item = getItems().get(index);
-
- return item.getElement();
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (!getElement().isOrHasChild(subElement)) {
- return null;
- }
-
- Element menuItemRoot = subElement;
- while (menuItemRoot != null && menuItemRoot.getParentElement() != null
- && menuItemRoot.getParentElement() != getElement()) {
- menuItemRoot = menuItemRoot.getParentElement().cast();
- }
- // "menuItemRoot" is now the root of the menu item
-
- final int itemCount = getItems().size();
- for (int i = 0; i < itemCount; i++) {
- if (getItems().get(i).getElement() == menuItemRoot) {
- String name = SUBPART_PREFIX + i;
- return name;
- }
- }
- return null;
- }
-
- /**
- * Get menu item with given DOM element
- *
- * @param element
- * Element used in search
- * @return Menu item or null if not found
- */
- public CustomMenuItem getMenuItemWithElement(Element element) {
- for (int i = 0; i < items.size(); i++) {
- CustomMenuItem item = items.get(i);
- if (DOM.isOrHasChild(item.getElement(), element)) {
- return item;
- }
-
- if (item.getSubMenu() != null) {
- item = item.getSubMenu().getMenuItemWithElement(element);
- if (item != null) {
- return item;
- }
- }
- }
-
- return null;
- }
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VNativeButton;
import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.button.ButtonServerRpc;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.nativebutton;
-
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Button;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.ui.button.ButtonServerRpc;
-
-public class VNativeButton extends Button implements ClickHandler {
-
- public static final String CLASSNAME = "v-nativebutton";
-
- protected String width = null;
-
- protected String paintableId;
-
- protected ApplicationConnection client;
-
- ButtonServerRpc buttonRpcProxy;
-
- protected Element errorIndicatorElement;
-
- protected final Element captionElement = DOM.createSpan();
-
- protected Icon icon;
-
- /**
- * Helper flag to handle special-case where the button is moved from under
- * mouse while clicking it. In this case mouse leaves the button without
- * moving.
- */
- private boolean clickPending;
-
- protected boolean disableOnClick = false;
-
- public VNativeButton() {
- setStyleName(CLASSNAME);
-
- getElement().appendChild(captionElement);
- captionElement.setClassName(getStyleName() + "-caption");
-
- addClickHandler(this);
-
- sinkEvents(Event.ONMOUSEDOWN);
- sinkEvents(Event.ONMOUSEUP);
- }
-
- @Override
- public void setText(String text) {
- captionElement.setInnerText(text);
- }
-
- @Override
- public void setHTML(String html) {
- captionElement.setInnerHTML(html);
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- if (DOM.eventGetType(event) == Event.ONLOAD) {
- Util.notifyParentOfSizeChange(this, true);
-
- } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN
- && event.getButton() == Event.BUTTON_LEFT) {
- clickPending = true;
- } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) {
- clickPending = false;
- } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) {
- if (clickPending) {
- click();
- }
- clickPending = false;
- }
- }
-
- @Override
- public void setWidth(String width) {
- this.width = width;
- super.setWidth(width);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
- * .dom.client.ClickEvent)
- */
- @Override
- public void onClick(ClickEvent event) {
- if (paintableId == null || client == null) {
- return;
- }
-
- if (BrowserInfo.get().isSafari()) {
- VNativeButton.this.setFocus(true);
- }
- if (disableOnClick) {
- setEnabled(false);
- // FIXME: This should be moved to NativeButtonConnector along with
- // buttonRpcProxy
- addStyleName(ApplicationConnection.DISABLED_CLASSNAME);
- buttonRpcProxy.disableOnClick();
- }
-
- // Add mouse details
- MouseEventDetails details = MouseEventDetailsBuilder
- .buildMouseEventDetails(event.getNativeEvent(), getElement());
- buttonRpcProxy.click(details);
-
- clickPending = false;
- }
-
-}
package com.vaadin.client.ui.nativeselect;
+import com.vaadin.client.ui.VNativeSelect;
import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.NativeSelect;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.nativeselect;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.user.client.ui.ListBox;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-
-public class VNativeSelect extends VOptionGroupBase implements Field {
-
- public static final String CLASSNAME = "v-select";
-
- protected ListBox select;
-
- private boolean firstValueIsTemporaryNullItem = false;
-
- public VNativeSelect() {
- super(new ListBox(false), CLASSNAME);
- select = getOptionsContainer();
- select.setVisibleItemCount(1);
- select.addChangeHandler(this);
- select.setStyleName(CLASSNAME + "-select");
-
- }
-
- protected ListBox getOptionsContainer() {
- return (ListBox) optionsContainer;
- }
-
- @Override
- protected void buildOptions(UIDL uidl) {
- select.setEnabled(!isDisabled() && !isReadonly());
- select.clear();
- firstValueIsTemporaryNullItem = false;
-
- if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) {
- // can't unselect last item in singleselect mode
- select.addItem("", (String) null);
- }
- boolean selected = false;
- for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
- final UIDL optionUidl = (UIDL) i.next();
- select.addItem(optionUidl.getStringAttribute("caption"),
- optionUidl.getStringAttribute("key"));
- if (optionUidl.hasAttribute("selected")) {
- select.setItemSelected(select.getItemCount() - 1, true);
- selected = true;
- }
- }
- if (!selected && !isNullSelectionAllowed()) {
- // null-select not allowed, but value not selected yet; add null and
- // remove when something is selected
- select.insertItem("", (String) null, 0);
- select.setItemSelected(0, true);
- firstValueIsTemporaryNullItem = true;
- }
- }
-
- @Override
- protected String[] getSelectedItems() {
- final ArrayList<String> selectedItemKeys = new ArrayList<String>();
- for (int i = 0; i < select.getItemCount(); i++) {
- if (select.isItemSelected(i)) {
- selectedItemKeys.add(select.getValue(i));
- }
- }
- return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
- }
-
- @Override
- public void onChange(ChangeEvent event) {
-
- if (select.isMultipleSelect()) {
- client.updateVariable(paintableId, "selected", getSelectedItems(),
- isImmediate());
- } else {
- client.updateVariable(paintableId, "selected", new String[] { ""
- + getSelectedItem() }, isImmediate());
- }
- if (firstValueIsTemporaryNullItem) {
- // remove temporary empty item
- select.removeItem(0);
- firstValueIsTemporaryNullItem = false;
- }
- }
-
- @Override
- public void setHeight(String height) {
- select.setHeight(height);
- super.setHeight(height);
- }
-
- @Override
- public void setWidth(String width) {
- select.setWidth(width);
- super.setWidth(width);
- }
-
- @Override
- protected void setTabIndex(int tabIndex) {
- getOptionsContainer().setTabIndex(tabIndex);
- }
-
- @Override
- public void focus() {
- select.setFocus(true);
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.notification;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.EventObject;
-import java.util.Iterator;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.Position;
-import com.vaadin.shared.ui.ui.UIConstants;
-
-public class VNotification extends VOverlay {
-
- public static final Position CENTERED = Position.MIDDLE_CENTER;
- public static final Position CENTERED_TOP = Position.TOP_CENTER;
- public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
- public static final Position TOP_LEFT = Position.TOP_LEFT;
- public static final Position TOP_RIGHT = Position.TOP_RIGHT;
- public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
- public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
-
- public static final int DELAY_FOREVER = -1;
- public static final int DELAY_NONE = 0;
-
- private static final String STYLENAME = "v-Notification";
- private static final int mouseMoveThreshold = 7;
- private static final int Z_INDEX_BASE = 20000;
- public static final String STYLE_SYSTEM = "system";
- private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
-
- private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
-
- private int startOpacity = 90;
- private int fadeMsec = 400;
- private int delayMsec = 1000;
-
- private Timer fader;
- private Timer delay;
-
- private int x = -1;
- private int y = -1;
-
- private String temporaryStyle;
-
- private ArrayList<EventListener> listeners;
- private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
-
- /**
- * Default constructor. You should use GWT.create instead.
- */
- public VNotification() {
- setStyleName(STYLENAME);
- sinkEvents(Event.ONCLICK);
- DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int)} instead to enable
- * GWT deferred binding.
- *
- * @param delayMsec
- */
- @Deprecated
- public VNotification(int delayMsec) {
- this();
- this.delayMsec = delayMsec;
- if (BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (isAttached()) {
- fade();
- }
- }
- }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- }
-
- /**
- * @deprecated Use static {@link #createNotification(int, int, int)} instead
- * to enable GWT deferred binding.
- *
- * @param delayMsec
- * @param fadeMsec
- * @param startOpacity
- */
- @Deprecated
- public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
- this(delayMsec);
- this.fadeMsec = fadeMsec;
- this.startOpacity = startOpacity;
- }
-
- public void startDelay() {
- DOM.removeEventPreview(this);
- if (delayMsec > 0) {
- if (delay == null) {
- delay = new Timer() {
- @Override
- public void run() {
- fade();
- }
- };
- delay.schedule(delayMsec);
- }
- } else if (delayMsec == 0) {
- fade();
- }
- }
-
- @Override
- public void show() {
- show(CENTERED);
- }
-
- public void show(String style) {
- show(CENTERED, style);
- }
-
- public void show(com.vaadin.shared.Position position) {
- show(position, null);
- }
-
- public void show(Widget widget, Position position, String style) {
- setWidget(widget);
- show(position, style);
- }
-
- public void show(String html, Position position, String style) {
- setWidget(new HTML(html));
- show(position, style);
- }
-
- public void show(Position position, String style) {
- setOpacity(getElement(), startOpacity);
- if (style != null) {
- temporaryStyle = style;
- addStyleName(style);
- addStyleDependentName(style);
- }
- super.show();
- notifications.add(this);
- setPosition(position);
- positionOrSizeUpdated();
- /**
- * Android 4 fails to render notifications correctly without a little
- * nudge (#8551)
- */
- if (BrowserInfo.get().isAndroid()) {
- Util.setStyleTemporarily(getElement(), "display", "none");
- }
- }
-
- @Override
- public void hide() {
- DOM.removeEventPreview(this);
- cancelDelay();
- cancelFade();
- if (temporaryStyle != null) {
- removeStyleName(temporaryStyle);
- removeStyleDependentName(temporaryStyle);
- temporaryStyle = null;
- }
- super.hide();
- notifications.remove(this);
- fireEvent(new HideEvent(this));
- }
-
- public void fade() {
- DOM.removeEventPreview(this);
- cancelDelay();
- if (fader == null) {
- fader = new Timer() {
- private final long start = new Date().getTime();
-
- @Override
- public void run() {
- /*
- * To make animation smooth, don't count that event happens
- * on time. Reduce opacity according to the actual time
- * spent instead of fixed decrement.
- */
- long now = new Date().getTime();
- long timeEplaced = now - start;
- float remainingFraction = 1 - timeEplaced
- / (float) fadeMsec;
- int opacity = (int) (startOpacity * remainingFraction);
- if (opacity <= 0) {
- cancel();
- hide();
- if (BrowserInfo.get().isOpera()) {
- // tray notification on opera needs to explicitly
- // define
- // size, reset it
- DOM.setStyleAttribute(getElement(), "width", "");
- DOM.setStyleAttribute(getElement(), "height", "");
- }
- } else {
- setOpacity(getElement(), opacity);
- }
- }
- };
- fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
- }
- }
-
- public void setPosition(com.vaadin.shared.Position position) {
- final Element el = getElement();
- DOM.setStyleAttribute(el, "top", "");
- DOM.setStyleAttribute(el, "left", "");
- DOM.setStyleAttribute(el, "bottom", "");
- DOM.setStyleAttribute(el, "right", "");
- switch (position) {
- case TOP_LEFT:
- DOM.setStyleAttribute(el, "top", "0px");
- DOM.setStyleAttribute(el, "left", "0px");
- break;
- case TOP_RIGHT:
- DOM.setStyleAttribute(el, "top", "0px");
- DOM.setStyleAttribute(el, "right", "0px");
- break;
- case BOTTOM_RIGHT:
- DOM.setStyleAttribute(el, "position", "absolute");
- if (BrowserInfo.get().isOpera()) {
- // tray notification on opera needs explicitly defined size
- DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
- DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
- }
- DOM.setStyleAttribute(el, "bottom", "0px");
- DOM.setStyleAttribute(el, "right", "0px");
- break;
- case BOTTOM_LEFT:
- DOM.setStyleAttribute(el, "bottom", "0px");
- DOM.setStyleAttribute(el, "left", "0px");
- break;
- case TOP_CENTER:
- center();
- DOM.setStyleAttribute(el, "top", "0px");
- break;
- case BOTTOM_CENTER:
- center();
- DOM.setStyleAttribute(el, "top", "");
- DOM.setStyleAttribute(el, "bottom", "0px");
- break;
- default:
- case MIDDLE_CENTER:
- center();
- break;
- }
- }
-
- private void cancelFade() {
- if (fader != null) {
- fader.cancel();
- fader = null;
- }
- }
-
- private void cancelDelay() {
- if (delay != null) {
- delay.cancel();
- delay = null;
- }
- }
-
- private void setOpacity(Element el, int opacity) {
- DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
- if (BrowserInfo.get().isIE()) {
- DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
- + ")");
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- DOM.removeEventPreview(this);
- if (fader == null) {
- fade();
- }
- }
-
- @Override
- public boolean onEventPreview(Event event) {
- int type = DOM.eventGetType(event);
- // "modal"
- if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
- if (type == Event.ONCLICK) {
- if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
- fade();
- return false;
- }
- } else if (type == Event.ONKEYDOWN
- && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
- fade();
- return false;
- }
- if (temporaryStyle == STYLE_SYSTEM) {
- return true;
- } else {
- return false;
- }
- }
- // default
- switch (type) {
- case Event.ONMOUSEMOVE:
-
- if (x < 0) {
- x = DOM.eventGetClientX(event);
- y = DOM.eventGetClientY(event);
- } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
- || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
- startDelay();
- }
- break;
- case Event.ONMOUSEDOWN:
- case Event.ONMOUSEWHEEL:
- case Event.ONSCROLL:
- startDelay();
- break;
- case Event.ONKEYDOWN:
- if (event.getRepeat()) {
- return true;
- }
- startDelay();
- break;
- default:
- break;
- }
- return true;
- }
-
- public void addEventListener(EventListener listener) {
- if (listeners == null) {
- listeners = new ArrayList<EventListener>();
- }
- listeners.add(listener);
- }
-
- public void removeEventListener(EventListener listener) {
- if (listeners == null) {
- return;
- }
- listeners.remove(listener);
- }
-
- private void fireEvent(HideEvent event) {
- if (listeners != null) {
- for (Iterator<EventListener> it = listeners.iterator(); it
- .hasNext();) {
- EventListener l = it.next();
- l.notificationHidden(event);
- }
- }
- }
-
- public static void showNotification(ApplicationConnection client,
- final UIDL notification) {
- boolean onlyPlainText = notification
- .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
- String html = "";
- if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
- final String parsedUri = client
- .translateVaadinUri(notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON));
- html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
- String caption = notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
- if (onlyPlainText) {
- caption = Util.escapeHTML(caption);
- caption = caption.replaceAll("\\n", "<br />");
- }
- html += "<h1>" + caption + "</h1>";
- }
- if (notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
- String message = notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
- if (onlyPlainText) {
- message = Util.escapeHTML(message);
- message = message.replaceAll("\\n", "<br />");
- }
- html += "<p>" + message + "</p>";
- }
-
- final String style = notification
- .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
- .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
- : null;
-
- final int pos = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
- Position position = Position.values()[pos];
-
- final int delay = notification
- .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
- createNotification(delay, client.getUIConnector().getWidget()).show(
- html, position, style);
- }
-
- public static VNotification createNotification(int delayMsec, Widget owner) {
- final VNotification notification = GWT.create(VNotification.class);
- notification.delayMsec = delayMsec;
- if (BrowserInfo.get().isTouchDevice()) {
- new Timer() {
- @Override
- public void run() {
- if (notification.isAttached()) {
- notification.fade();
- }
- }
- }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
- }
- notification.setOwner(owner);
- return notification;
- }
-
- public class HideEvent extends EventObject {
-
- public HideEvent(Object source) {
- super(source);
- }
- }
-
- public interface EventListener extends java.util.EventListener {
- public void notificationHidden(HideEvent event);
- }
-
- /**
- * Moves currently visible notifications to the top of the event preview
- * stack. Can be called when opening other overlays such as subwindows to
- * ensure the notifications receive the events they need and don't linger
- * indefinitely. See #7136.
- *
- * TODO Should this be a generic Overlay feature instead?
- */
- public static void bringNotificationsToFront() {
- for (VNotification notification : notifications) {
- DOM.removeEventPreview(notification);
- DOM.addEventPreview(notification);
- }
- }
-}
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractFieldConnector;
-import com.vaadin.client.ui.nativebutton.VNativeButton;
-import com.vaadin.client.ui.textfield.VTextField;
+import com.vaadin.client.ui.VNativeButton;
+import com.vaadin.client.ui.VOptionGroupBase;
+import com.vaadin.client.ui.VTextField;
public abstract class OptionGroupBaseConnector extends AbstractFieldConnector
implements Paintable {
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VOptionGroup;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.optiongroup;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.LoadEvent;
-import com.google.gwt.event.dom.client.LoadHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FocusWidget;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.RadioButton;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.checkbox.VCheckBox;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
-
-public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
- BlurHandler {
-
- public static final String CLASSNAME = "v-select-optiongroup";
-
- protected final Panel panel;
-
- private final Map<CheckBox, String> optionsToKeys;
-
- protected boolean sendFocusEvents = false;
- protected boolean sendBlurEvents = false;
- protected List<HandlerRegistration> focusHandlers = null;
- protected List<HandlerRegistration> blurHandlers = null;
-
- private final LoadHandler iconLoadHandler = new LoadHandler() {
- @Override
- public void onLoad(LoadEvent event) {
- Util.notifyParentOfSizeChange(VOptionGroup.this, true);
- }
- };
-
- /**
- * used to check whether a blur really was a blur of the complete
- * optiongroup: if a control inside this optiongroup gains focus right after
- * blur of another control inside this optiongroup (meaning: if onFocus
- * fires after onBlur has fired), the blur and focus won't be sent to the
- * server side as only a focus change inside this optiongroup occured
- */
- private boolean blurOccured = false;
-
- protected boolean htmlContentAllowed = false;
-
- public VOptionGroup() {
- super(CLASSNAME);
- panel = (Panel) optionsContainer;
- optionsToKeys = new HashMap<CheckBox, String>();
- }
-
- /*
- * Return true if no elements were changed, false otherwise.
- */
- @Override
- protected void buildOptions(UIDL uidl) {
- panel.clear();
- for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
- final UIDL opUidl = (UIDL) it.next();
- CheckBox op;
-
- String itemHtml = opUidl.getStringAttribute("caption");
- if (!htmlContentAllowed) {
- itemHtml = Util.escapeHTML(itemHtml);
- }
-
- String icon = opUidl.getStringAttribute("icon");
- if (icon != null && icon.length() != 0) {
- String iconUrl = client.translateVaadinUri(icon);
- itemHtml = "<img src=\"" + iconUrl + "\" class=\""
- + Icon.CLASSNAME + "\" alt=\"\" />" + itemHtml;
- }
-
- if (isMultiselect()) {
- op = new VCheckBox();
- op.setHTML(itemHtml);
- } else {
- op = new RadioButton(paintableId, itemHtml, true);
- op.setStyleName("v-radiobutton");
- }
-
- if (icon != null && icon.length() != 0) {
- Util.sinkOnloadForImages(op.getElement());
- op.addHandler(iconLoadHandler, LoadEvent.getType());
- }
-
- op.addStyleName(CLASSNAME_OPTION);
- op.setValue(opUidl.getBooleanAttribute("selected"));
- boolean enabled = !opUidl
- .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED)
- && !isReadonly() && !isDisabled();
- op.setEnabled(enabled);
- setStyleName(op.getElement(),
- ApplicationConnection.DISABLED_CLASSNAME, !enabled);
- op.addClickHandler(this);
- optionsToKeys.put(op, opUidl.getStringAttribute("key"));
- panel.add(op);
- }
- }
-
- @Override
- protected String[] getSelectedItems() {
- return selectedKeys.toArray(new String[selectedKeys.size()]);
- }
-
- @Override
- public void onClick(ClickEvent event) {
- super.onClick(event);
- if (event.getSource() instanceof CheckBox) {
- final boolean selected = ((CheckBox) event.getSource()).getValue();
- final String key = optionsToKeys.get(event.getSource());
- if (!isMultiselect()) {
- selectedKeys.clear();
- }
- if (selected) {
- selectedKeys.add(key);
- } else {
- selectedKeys.remove(key);
- }
- client.updateVariable(paintableId, "selected", getSelectedItems(),
- isImmediate());
- }
- }
-
- @Override
- protected void setTabIndex(int tabIndex) {
- for (Iterator<Widget> iterator = panel.iterator(); iterator.hasNext();) {
- FocusWidget widget = (FocusWidget) iterator.next();
- widget.setTabIndex(tabIndex);
- }
- }
-
- @Override
- public void focus() {
- Iterator<Widget> iterator = panel.iterator();
- if (iterator.hasNext()) {
- ((Focusable) iterator.next()).setFocus(true);
- }
- }
-
- @Override
- public void onFocus(FocusEvent arg0) {
- if (!blurOccured) {
- // no blur occured before this focus event
- // panel was blurred => fire the event to the server side if
- // requested by server side
- if (sendFocusEvents) {
- client.updateVariable(paintableId, EventId.FOCUS, "", true);
- }
- } else {
- // blur occured before this focus event
- // another control inside the panel (checkbox / radio box) was
- // blurred => do not fire the focus and set blurOccured to false, so
- // blur will not be fired, too
- blurOccured = false;
- }
- }
-
- @Override
- public void onBlur(BlurEvent arg0) {
- blurOccured = true;
- if (sendBlurEvents) {
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- // check whether blurOccured still is true and then send the
- // event out to the server
- if (blurOccured) {
- client.updateVariable(paintableId, EventId.BLUR, "",
- true);
- blurOccured = false;
- }
- }
- });
- }
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.optiongroup;
-
-import java.util.Set;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.nativebutton.VNativeButton;
-import com.vaadin.client.ui.textfield.VTextField;
-
-public abstract class VOptionGroupBase extends Composite implements Field,
- ClickHandler, ChangeHandler, KeyPressHandler, Focusable {
-
- public static final String CLASSNAME_OPTION = "v-select-option";
-
- protected ApplicationConnection client;
-
- protected String paintableId;
-
- protected Set<String> selectedKeys;
-
- protected boolean immediate;
-
- protected boolean multiselect;
-
- protected boolean disabled;
-
- protected boolean readonly;
-
- protected int cols = 0;
-
- protected int rows = 0;
-
- protected boolean nullSelectionAllowed = true;
-
- protected boolean nullSelectionItemAvailable = false;
-
- /**
- * Widget holding the different options (e.g. ListBox or Panel for radio
- * buttons) (optional, fallbacks to container Panel)
- */
- protected Widget optionsContainer;
-
- /**
- * Panel containing the component
- */
- protected final Panel container;
-
- protected VTextField newItemField;
-
- protected VNativeButton newItemButton;
-
- public VOptionGroupBase(String classname) {
- container = new FlowPanel();
- initWidget(container);
- optionsContainer = container;
- container.setStyleName(classname);
- immediate = false;
- multiselect = false;
- }
-
- /*
- * Call this if you wish to specify your own container for the option
- * elements (e.g. SELECT)
- */
- public VOptionGroupBase(Widget w, String classname) {
- this(classname);
- optionsContainer = w;
- container.add(optionsContainer);
- }
-
- protected boolean isImmediate() {
- return immediate;
- }
-
- protected boolean isMultiselect() {
- return multiselect;
- }
-
- protected boolean isDisabled() {
- return disabled;
- }
-
- protected boolean isReadonly() {
- return readonly;
- }
-
- protected boolean isNullSelectionAllowed() {
- return nullSelectionAllowed;
- }
-
- protected boolean isNullSelectionItemAvailable() {
- return nullSelectionItemAvailable;
- }
-
- /**
- * @return "cols" specified in uidl, 0 if not specified
- */
- protected int getColumns() {
- return cols;
- }
-
- /**
- * @return "rows" specified in uidl, 0 if not specified
- */
-
- protected int getRows() {
- return rows;
- }
-
- abstract protected void setTabIndex(int tabIndex);
-
- @Override
- public void onClick(ClickEvent event) {
- if (event.getSource() == newItemButton
- && !newItemField.getText().equals("")) {
- client.updateVariable(paintableId, "newitem",
- newItemField.getText(), true);
- newItemField.setText("");
- }
- }
-
- @Override
- public void onChange(ChangeEvent event) {
- if (multiselect) {
- client.updateVariable(paintableId, "selected", getSelectedItems(),
- immediate);
- } else {
- client.updateVariable(paintableId, "selected", new String[] { ""
- + getSelectedItem() }, immediate);
- }
- }
-
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (event.getSource() == newItemField
- && event.getCharCode() == KeyCodes.KEY_ENTER) {
- newItemButton.click();
- }
- }
-
- protected abstract void buildOptions(UIDL uidl);
-
- protected abstract String[] getSelectedItems();
-
- protected String getSelectedItem() {
- final String[] sel = getSelectedItems();
- if (sel.length > 0) {
- return sel[0];
- } else {
- return null;
- }
- }
-
-}
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.AbstractLayoutConnector;
import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VOrderedLayout;
+import com.vaadin.client.ui.VOrderedLayout.CaptionPosition;
+import com.vaadin.client.ui.VOrderedLayout.Slot;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout.CaptionPosition;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout.Slot;
import com.vaadin.shared.AbstractFieldState;
import com.vaadin.shared.ComponentConstants;
import com.vaadin.shared.communication.URLReference;
*/
package com.vaadin.client.ui.orderedlayout;
+import com.vaadin.client.ui.VHorizontalLayout;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import com.vaadin.client.StyleConstants;
-
-/**
- * Represents a layout where the children is ordered vertically
- */
-public class VHorizontalLayout extends VOrderedLayout {
-
- public static final String CLASSNAME = "v-horizontallayout";
-
- /**
- * Default constructor
- */
- public VHorizontalLayout() {
- super(false);
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- addStyleName(StyleConstants.UI_LAYOUT);
- addStyleName("v-horizontal");
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.regexp.shared.MatchResult;
-import com.google.gwt.regexp.shared.RegExp;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.layout.ElementResizeListener;
-import com.vaadin.shared.ui.AlignmentInfo;
-import com.vaadin.shared.ui.MarginInfo;
-
-/**
- * Base class for ordered layouts
- */
-public class VOrderedLayout extends FlowPanel {
-
- private static final String ALIGN_CLASS_PREFIX = "v-align-";
-
- protected boolean spacing = false;
-
- protected boolean vertical = true;
-
- protected boolean definedHeight = false;
-
- private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
-
- private Element expandWrapper;
-
- private LayoutManager layoutManager;
-
- public VOrderedLayout(boolean vertical) {
- this.vertical = vertical;
- }
-
- /**
- * Add or move a slot to another index.
- *
- * <p>
- * You should note that the index does not refer to the DOM index if
- * spacings are used. If spacings are used then the index will be adjusted
- * to include the spacings when inserted.
- * </p>
- * <p>
- * For instance when using spacing the index converts to DOM index in the
- * following way:
- *
- * <pre>
- * index : 0 -> DOM index: 0
- * index : 1 -> DOM index: 1
- * index : 2 -> DOM index: 3
- * index : 3 -> DOM index: 5
- * index : 4 -> DOM index: 7
- * </pre>
- *
- * When using this method never account for spacings.
- * </p>
- *
- * @param slot
- * The slot to move or add
- * @param index
- * The index where the slot should be placed.
- */
- void addOrMoveSlot(Slot slot, int index) {
- if (slot.getParent() == this) {
- int currentIndex = getWidgetIndex(slot);
- if (index == currentIndex) {
- return;
- }
- }
-
- insert(slot, index);
-
- /*
- * We need to confirm spacings are correctly applied after each insert.
- */
- setSpacing(spacing);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void insert(Widget child, Element container, int beforeIndex,
- boolean domInsert) {
- // Validate index; adjust if the widget is already a child of this
- // panel.
- beforeIndex = adjustIndex(child, beforeIndex);
-
- // Detach new child.
- child.removeFromParent();
-
- // Logical attach.
- getChildren().insert(child, beforeIndex);
-
- // Physical attach.
- container = expandWrapper != null ? expandWrapper : getElement();
- if (domInsert) {
- if (spacing) {
- if (beforeIndex != 0) {
- /*
- * Since the spacing elements are located at the same DOM
- * level as the slots we need to take them into account when
- * calculating the slot position.
- *
- * The spacing elements are always located before the actual
- * slot except for the first slot which do not have a
- * spacing element like this
- *
- * |<slot1><spacing2><slot2><spacing3><slot3>...|
- */
- beforeIndex = beforeIndex * 2 - 1;
- }
- }
- DOM.insertChild(container, child.getElement(), beforeIndex);
- } else {
- DOM.appendChild(container, child.getElement());
- }
-
- // Adopt.
- adopt(child);
- }
-
- /**
- * Remove a slot from the layout
- *
- * @param widget
- * @return
- */
- public void removeWidget(Widget widget) {
- Slot slot = widgetToSlot.get(widget);
- remove(slot);
- widgetToSlot.remove(widget);
- }
-
- /**
- * Get the containing slot for a widget. If no slot is found a new slot is
- * created and returned.
- *
- * @param widget
- * The widget whose slot you want to get
- *
- * @return
- */
- public Slot getSlot(Widget widget) {
- Slot slot = widgetToSlot.get(widget);
- if (slot == null) {
- slot = new Slot(widget);
- widgetToSlot.put(widget, slot);
- }
- return slot;
- }
-
- /**
- * Gets a slot based on the widget element. If no slot is found then null is
- * returned.
- *
- * @param widgetElement
- * The element of the widget ( Same as getWidget().getElement() )
- * @return
- */
- public Slot getSlot(Element widgetElement) {
- for (Map.Entry<Widget, Slot> entry : widgetToSlot.entrySet()) {
- if (entry.getKey().getElement() == widgetElement) {
- return entry.getValue();
- }
- }
- return null;
- }
-
- /**
- * Defines where the caption should be placed
- */
- public enum CaptionPosition {
- TOP, RIGHT, BOTTOM, LEFT
- }
-
- /**
- * Represents a slot which contains the actual widget in the layout.
- */
- public final class Slot extends SimplePanel {
-
- public static final String SLOT_CLASSNAME = "v-slot";
-
- private Element spacer;
- private Element captionWrap;
- private Element caption;
- private Element captionText;
- private Icon icon;
- private Element errorIcon;
- private Element requiredIcon;
-
- private ElementResizeListener captionResizeListener;
-
- private ElementResizeListener widgetResizeListener;
-
- private ElementResizeListener spacingResizeListener;
-
- // Caption is placed after component unless there is some part which
- // moves it above.
- private CaptionPosition captionPosition = CaptionPosition.RIGHT;
-
- private AlignmentInfo alignment;
-
- private double expandRatio = -1;
-
- /**
- * Constructor
- *
- * @param widget
- * The widget to put in the slot
- *
- * @param layoutManager
- * The layout manager used by the layout
- */
- private Slot(Widget widget) {
- setStyleName(SLOT_CLASSNAME);
- setWidget(widget);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user
- * .client.ui.Widget)
- */
- @Override
- public boolean remove(Widget w) {
- detachListeners();
- return super.remove(w);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt
- * .user.client.ui.Widget)
- */
- @Override
- public void setWidget(Widget w) {
- detachListeners();
- super.setWidget(w);
- attachListeners();
- }
-
- /**
- * Attached resize listeners to the widget, caption and spacing elements
- */
- private void attachListeners() {
- if (getWidget() != null && getLayoutManager() != null) {
- LayoutManager lm = getLayoutManager();
- if (getCaptionElement() != null
- && captionResizeListener != null) {
- lm.addElementResizeListener(getCaptionElement(),
- captionResizeListener);
- }
- if (widgetResizeListener != null) {
- lm.addElementResizeListener(getWidget().getElement(),
- widgetResizeListener);
- }
- if (getSpacingElement() != null
- && spacingResizeListener != null) {
- lm.addElementResizeListener(getSpacingElement(),
- spacingResizeListener);
- }
- }
- }
-
- /**
- * Detaches resize listeners from the widget, caption and spacing
- * elements
- */
- private void detachListeners() {
- if (getWidget() != null && getLayoutManager() != null) {
- LayoutManager lm = getLayoutManager();
- if (getCaptionElement() != null
- && captionResizeListener != null) {
- lm.removeElementResizeListener(getCaptionElement(),
- captionResizeListener);
- }
- if (widgetResizeListener != null) {
- lm.removeElementResizeListener(getWidget().getElement(),
- widgetResizeListener);
- }
- if (getSpacingElement() != null
- && spacingResizeListener != null) {
- lm.removeElementResizeListener(getSpacingElement(),
- spacingResizeListener);
- }
- }
- }
-
- public ElementResizeListener getCaptionResizeListener() {
- return captionResizeListener;
- }
-
- public void setCaptionResizeListener(
- ElementResizeListener captionResizeListener) {
- detachListeners();
- this.captionResizeListener = captionResizeListener;
- attachListeners();
- }
-
- public ElementResizeListener getWidgetResizeListener() {
- return widgetResizeListener;
- }
-
- public void setWidgetResizeListener(
- ElementResizeListener widgetResizeListener) {
- detachListeners();
- this.widgetResizeListener = widgetResizeListener;
- attachListeners();
- }
-
- public ElementResizeListener getSpacingResizeListener() {
- return spacingResizeListener;
- }
-
- public void setSpacingResizeListener(
- ElementResizeListener spacingResizeListener) {
- detachListeners();
- this.spacingResizeListener = spacingResizeListener;
- attachListeners();
- }
-
- /**
- * Returns the alignment for the slot
- *
- */
- public AlignmentInfo getAlignment() {
- return alignment;
- }
-
- /**
- * Sets the style names for the slot containing the widget
- *
- * @param stylenames
- * The style names for the slot
- */
- protected void setStyleNames(String... stylenames) {
- setStyleName(SLOT_CLASSNAME);
- if (stylenames != null) {
- for (String stylename : stylenames) {
- addStyleDependentName(stylename);
- }
- }
-
- // Ensure alignment style names are correct
- setAlignment(alignment);
- }
-
- /**
- * Sets how the widget is aligned inside the slot
- *
- * @param alignment
- * The alignment inside the slot
- */
- public void setAlignment(AlignmentInfo alignment) {
- this.alignment = alignment;
-
- if (alignment != null && alignment.isHorizontalCenter()) {
- addStyleName(ALIGN_CLASS_PREFIX + "center");
- removeStyleName(ALIGN_CLASS_PREFIX + "right");
- } else if (alignment != null && alignment.isRight()) {
- addStyleName(ALIGN_CLASS_PREFIX + "right");
- removeStyleName(ALIGN_CLASS_PREFIX + "center");
- } else {
- removeStyleName(ALIGN_CLASS_PREFIX + "right");
- removeStyleName(ALIGN_CLASS_PREFIX + "center");
- }
-
- if (alignment != null && alignment.isVerticalCenter()) {
- addStyleName(ALIGN_CLASS_PREFIX + "middle");
- removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
- } else if (alignment != null && alignment.isBottom()) {
- addStyleName(ALIGN_CLASS_PREFIX + "bottom");
- removeStyleName(ALIGN_CLASS_PREFIX + "middle");
- } else {
- removeStyleName(ALIGN_CLASS_PREFIX + "middle");
- removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
- }
- }
-
- /**
- * Set how the slot should be expanded relative to the other slots
- *
- * @param expandRatio
- * The ratio of the space the slot should occupy
- *
- */
- public void setExpandRatio(double expandRatio) {
- this.expandRatio = expandRatio;
- }
-
- /**
- * Get the expand ratio for the slot. The expand ratio describes how the
- * slot should be resized compared to other slots in the layout
- *
- * @return
- */
- public double getExpandRatio() {
- return expandRatio;
- }
-
- /**
- * Set the spacing for the slot. The spacing determines if there should
- * be empty space around the slot when the slot.
- *
- * @param spacing
- * Should spacing be enabled
- */
- public void setSpacing(boolean spacing) {
- if (spacing && spacer == null) {
- spacer = DOM.createDiv();
- spacer.addClassName("v-spacing");
-
- /*
- * This has to be done here for the initial render. In other
- * cases where the spacer already exists onAttach will handle
- * it.
- */
- getElement().getParentElement().insertBefore(spacer,
- getElement());
- } else if (!spacing && spacer != null) {
- spacer.removeFromParent();
- spacer = null;
- }
- }
-
- /**
- * Get the element which is added to make the spacing
- *
- * @return
- */
- public Element getSpacingElement() {
- return spacer;
- }
-
- /**
- * Does the slot have spacing
- */
- public boolean hasSpacing() {
- return getSpacingElement() != null;
- }
-
- /**
- * Get the vertical amount in pixels of the spacing
- */
- protected int getVerticalSpacing() {
- if (spacer == null) {
- return 0;
- } else if (getLayoutManager() != null) {
- return getLayoutManager().getOuterHeight(spacer);
- }
- return spacer.getOffsetHeight();
- }
-
- /**
- * Get the horizontal amount of pixels of the spacing
- *
- * @return
- */
- protected int getHorizontalSpacing() {
- if (spacer == null) {
- return 0;
- } else if (getLayoutManager() != null) {
- return getLayoutManager().getOuterWidth(spacer);
- }
- return spacer.getOffsetWidth();
- }
-
- /**
- * Set the position of the caption relative to the slot
- *
- * @param captionPosition
- * The position of the caption
- */
- public void setCaptionPosition(CaptionPosition captionPosition) {
- if (caption == null) {
- return;
- }
- captionWrap.removeClassName("v-caption-on-"
- + this.captionPosition.name().toLowerCase());
-
- this.captionPosition = captionPosition;
- if (captionPosition == CaptionPosition.BOTTOM
- || captionPosition == CaptionPosition.RIGHT) {
- captionWrap.appendChild(caption);
- } else {
- captionWrap.insertFirst(caption);
- }
-
- captionWrap.addClassName("v-caption-on-"
- + captionPosition.name().toLowerCase());
- }
-
- /**
- * Get the position of the caption relative to the slot
- */
- public CaptionPosition getCaptionPosition() {
- return captionPosition;
- }
-
- /**
- * Set the caption of the slot
- *
- * @param captionText
- * The text of the caption
- * @param iconUrl
- * The icon URL
- * @param styles
- * The style names
- * @param error
- * The error message
- * @param showError
- * Should the error message be shown
- * @param required
- * Is the (field) required
- * @param enabled
- * Is the component enabled
- */
- public void setCaption(String captionText, String iconUrl,
- List<String> styles, String error, boolean showError,
- boolean required, boolean enabled) {
-
- // TODO place for optimization: check if any of these have changed
- // since last time, and only run those changes
-
- // Caption wrappers
- if (captionText != null || iconUrl != null || error != null
- || required) {
- if (caption == null) {
- caption = DOM.createDiv();
- captionWrap = DOM.createDiv();
- captionWrap.addClassName(StyleConstants.UI_WIDGET);
- captionWrap.addClassName("v-has-caption");
- getElement().appendChild(captionWrap);
- captionWrap.appendChild(getWidget().getElement());
- }
- } else if (caption != null) {
- getElement().appendChild(getWidget().getElement());
- captionWrap.removeFromParent();
- caption = null;
- captionWrap = null;
- }
-
- // Caption text
- if (captionText != null) {
- if (this.captionText == null) {
- this.captionText = DOM.createSpan();
- this.captionText.addClassName("v-captiontext");
- caption.appendChild(this.captionText);
- }
- if (captionText.trim().equals("")) {
- this.captionText.setInnerHTML(" ");
- } else {
- this.captionText.setInnerText(captionText);
- }
- } else if (this.captionText != null) {
- this.captionText.removeFromParent();
- this.captionText = null;
- }
-
- // Icon
- if (iconUrl != null) {
- if (icon == null) {
- icon = new Icon();
- caption.insertFirst(icon.getElement());
- }
- icon.setUri(iconUrl);
- } else if (icon != null) {
- icon.getElement().removeFromParent();
- icon = null;
- }
-
- // Required
- if (required) {
- if (requiredIcon == null) {
- requiredIcon = DOM.createSpan();
- // TODO decide something better (e.g. use CSS to insert the
- // character)
- requiredIcon.setInnerHTML("*");
- requiredIcon.setClassName("v-required-field-indicator");
- }
- caption.appendChild(requiredIcon);
- } else if (requiredIcon != null) {
- requiredIcon.removeFromParent();
- requiredIcon = null;
- }
-
- // Error
- if (error != null && showError) {
- if (errorIcon == null) {
- errorIcon = DOM.createSpan();
- errorIcon.setClassName("v-errorindicator");
- }
- caption.appendChild(errorIcon);
- } else if (errorIcon != null) {
- errorIcon.removeFromParent();
- errorIcon = null;
- }
-
- if (caption != null) {
- // Styles
- caption.setClassName("v-caption");
-
- if (styles != null) {
- for (String style : styles) {
- caption.addClassName("v-caption-" + style);
- }
- }
-
- if (enabled) {
- caption.removeClassName("v-disabled");
- } else {
- caption.addClassName("v-disabled");
- }
-
- // Caption position
- if (captionText != null || iconUrl != null) {
- setCaptionPosition(CaptionPosition.TOP);
- } else {
- setCaptionPosition(CaptionPosition.RIGHT);
- }
- }
- }
-
- /**
- * Does the slot have a caption
- */
- public boolean hasCaption() {
- return caption != null;
- }
-
- /**
- * Get the slots caption element
- */
- public Element getCaptionElement() {
- return caption;
- }
-
- /**
- * Set if the slot has a relative width
- *
- * @param relativeWidth
- * True if slot uses relative width, false if the slot has a
- * static width
- */
- private boolean relativeWidth = false;
-
- protected void setRelativeWidth(boolean relativeWidth) {
- this.relativeWidth = relativeWidth;
- updateRelativeSize(relativeWidth, "width");
- }
-
- /**
- * Set if the slot has a relative height
- *
- * @param relativeHeight
- * Trie if the slot uses a relative height, false if the slot
- * has a static height
- */
- private boolean relativeHeight = false;
-
- protected void setRelativeHeight(boolean relativeHeight) {
- this.relativeHeight = relativeHeight;
- updateRelativeSize(relativeHeight, "height");
- }
-
- /**
- * Updates the captions size if the slot is relative
- *
- * @param isRelativeSize
- * Is the slot relatived sized
- * @param direction
- * The directorion of the caption
- */
- private void updateRelativeSize(boolean isRelativeSize, String direction) {
- if (isRelativeSize && hasCaption()) {
- captionWrap.getStyle().setProperty(
- direction,
- getWidget().getElement().getStyle()
- .getProperty(direction));
- captionWrap.addClassName("v-has-" + direction);
- } else if (hasCaption()) {
- if (direction.equals("height")) {
- captionWrap.getStyle().clearHeight();
- } else {
- captionWrap.getStyle().clearWidth();
- }
- captionWrap.removeClassName("v-has-" + direction);
- captionWrap.getStyle().clearPaddingTop();
- captionWrap.getStyle().clearPaddingRight();
- captionWrap.getStyle().clearPaddingBottom();
- captionWrap.getStyle().clearPaddingLeft();
- caption.getStyle().clearMarginTop();
- caption.getStyle().clearMarginRight();
- caption.getStyle().clearMarginBottom();
- caption.getStyle().clearMarginLeft();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
- * .user.client.Event)
- */
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (DOM.eventGetType(event) == Event.ONLOAD
- && icon.getElement() == DOM.eventGetTarget(event)) {
- if (getLayoutManager() != null) {
- getLayoutManager().layoutLater();
- } else {
- updateCaptionOffset(caption);
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement()
- */
- @Override
- protected Element getContainerElement() {
- if (captionWrap == null) {
- return getElement();
- } else {
- return captionWrap;
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.Widget#onDetach()
- */
- @Override
- protected void onDetach() {
- if (spacer != null) {
- spacer.removeFromParent();
- }
- super.onDetach();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.Widget#onAttach()
- */
- @Override
- protected void onAttach() {
- super.onAttach();
- if (spacer != null) {
- getElement().getParentElement().insertBefore(spacer,
- getElement());
- }
- }
- }
-
- /**
- * The icon for each widget. Located in the caption of the slot.
- */
- public static class Icon extends UIObject {
-
- public static final String CLASSNAME = "v-icon";
-
- private String myUrl;
-
- /**
- * Constructor
- */
- public Icon() {
- setElement(DOM.createImg());
- DOM.setElementProperty(getElement(), "alt", "");
- setStyleName(CLASSNAME);
- }
-
- /**
- * Set the URL where the icon is located
- *
- * @param url
- * A fully qualified URL
- */
- public void setUri(String url) {
- if (!url.equals(myUrl)) {
- /*
- * Start sinking onload events, widgets responsibility to react.
- * We must do this BEFORE we set src as IE fires the event
- * immediately if the image is found in cache (#2592).
- */
- sinkEvents(Event.ONLOAD);
-
- DOM.setElementProperty(getElement(), "src", url);
- myUrl = url;
- }
- }
- }
-
- /**
- * Set the layout manager for the layout
- *
- * @param manager
- * The layout manager to use
- */
- public void setLayoutManager(LayoutManager manager) {
- layoutManager = manager;
- }
-
- /**
- * Get the layout manager used by this layout
- *
- */
- public LayoutManager getLayoutManager() {
- return layoutManager;
- }
-
- /**
- * Deducts the caption position by examining the wrapping element
- *
- * @param captionWrap
- * The wrapping element
- *
- * @return The caption position
- */
- CaptionPosition getCaptionPositionFromElement(Element captionWrap) {
- RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)");
-
- // Get caption position from the classname
- MatchResult matcher = captionPositionRegexp.exec(captionWrap
- .getClassName());
- if (matcher == null || matcher.getGroupCount() < 2) {
- return CaptionPosition.TOP;
- }
- String captionClass = matcher.getGroup(1);
- CaptionPosition captionPosition = CaptionPosition.valueOf(
- CaptionPosition.class, captionClass.toUpperCase());
- return captionPosition;
- }
-
- /**
- * Update the offset off the caption relative to the slot
- *
- * @param caption
- * The caption element
- */
- void updateCaptionOffset(Element caption) {
-
- Element captionWrap = caption.getParentElement().cast();
-
- Style captionWrapStyle = captionWrap.getStyle();
- captionWrapStyle.clearPaddingTop();
- captionWrapStyle.clearPaddingRight();
- captionWrapStyle.clearPaddingBottom();
- captionWrapStyle.clearPaddingLeft();
-
- Style captionStyle = caption.getStyle();
- captionStyle.clearMarginTop();
- captionStyle.clearMarginRight();
- captionStyle.clearMarginBottom();
- captionStyle.clearMarginLeft();
-
- // Get caption position from the classname
- CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
-
- if (captionPosition == CaptionPosition.LEFT
- || captionPosition == CaptionPosition.RIGHT) {
- int captionWidth;
- if (layoutManager != null) {
- captionWidth = layoutManager.getOuterWidth(caption)
- - layoutManager.getMarginWidth(caption);
- } else {
- captionWidth = caption.getOffsetWidth();
- }
- if (captionWidth > 0) {
- if (captionPosition == CaptionPosition.LEFT) {
- captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX);
- captionStyle.setMarginLeft(-captionWidth, Unit.PX);
- } else {
- captionWrapStyle.setPaddingRight(captionWidth, Unit.PX);
- captionStyle.setMarginRight(-captionWidth, Unit.PX);
- }
- }
- }
- if (captionPosition == CaptionPosition.TOP
- || captionPosition == CaptionPosition.BOTTOM) {
- int captionHeight;
- if (layoutManager != null) {
- captionHeight = layoutManager.getOuterHeight(caption)
- - layoutManager.getMarginHeight(caption);
- } else {
- captionHeight = caption.getOffsetHeight();
- }
- if (captionHeight > 0) {
- if (captionPosition == CaptionPosition.TOP) {
- captionWrapStyle.setPaddingTop(captionHeight, Unit.PX);
- captionStyle.setMarginTop(-captionHeight, Unit.PX);
- } else {
- captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX);
- captionStyle.setMarginBottom(-captionHeight, Unit.PX);
- }
- }
- }
- }
-
- /**
- * Set the margin of the layout
- *
- * @param marginInfo
- * The margin information
- */
- public void setMargin(MarginInfo marginInfo) {
- if (marginInfo != null) {
- setStyleName("v-margin-top", marginInfo.hasTop());
- setStyleName("v-margin-right", marginInfo.hasRight());
- setStyleName("v-margin-bottom", marginInfo.hasBottom());
- setStyleName("v-margin-left", marginInfo.hasLeft());
- }
- }
-
- /**
- * Turn on or off spacing in the layout
- *
- * @param spacing
- * True if spacing should be used, false if not
- */
- public void setSpacing(boolean spacing) {
- this.spacing = spacing;
- for (Slot slot : widgetToSlot.values()) {
- if (getWidgetIndex(slot) > 0) {
- slot.setSpacing(spacing);
- } else {
- slot.setSpacing(false);
- }
- }
- }
-
- /**
- * Triggers a recalculation of the expand width and heights
- */
- private void recalculateExpands() {
- double total = 0;
- for (Slot slot : widgetToSlot.values()) {
- if (slot.getExpandRatio() > -1) {
- total += slot.getExpandRatio();
- } else {
- if (vertical) {
- slot.getElement().getStyle().clearHeight();
- } else {
- slot.getElement().getStyle().clearWidth();
- }
- }
- }
- for (Slot slot : widgetToSlot.values()) {
- if (slot.getExpandRatio() > -1) {
- if (vertical) {
- slot.setHeight((100 * (slot.getExpandRatio() / total))
- + "%");
- if (slot.relativeHeight) {
- Util.notifyParentOfSizeChange(this, true);
- }
- } else {
- slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%");
- if (slot.relativeWidth) {
- Util.notifyParentOfSizeChange(this, true);
- }
- }
- }
- }
- }
-
- /**
- * Removes elements used to expand a slot
- */
- void clearExpand() {
- if (expandWrapper != null) {
- for (; expandWrapper.getChildCount() > 0;) {
- Element el = expandWrapper.getChild(0).cast();
- getElement().appendChild(el);
- if (vertical) {
- el.getStyle().clearHeight();
- el.getStyle().clearMarginTop();
- } else {
- el.getStyle().clearWidth();
- el.getStyle().clearMarginLeft();
- }
- }
- expandWrapper.removeFromParent();
- expandWrapper = null;
- }
- }
-
- /**
- * Adds elements used to expand a slot
- */
- public void updateExpand() {
- boolean isExpanding = false;
- for (Widget slot : getChildren()) {
- if (((Slot) slot).getExpandRatio() > -1) {
- isExpanding = true;
- } else {
- if (vertical) {
- slot.getElement().getStyle().clearHeight();
- } else {
- slot.getElement().getStyle().clearWidth();
- }
- }
- slot.getElement().getStyle().clearMarginLeft();
- slot.getElement().getStyle().clearMarginTop();
- }
-
- if (isExpanding) {
- if (expandWrapper == null) {
- expandWrapper = DOM.createDiv();
- expandWrapper.setClassName("v-expand");
- for (; getElement().getChildCount() > 0;) {
- Node el = getElement().getChild(0);
- expandWrapper.appendChild(el);
- }
- getElement().appendChild(expandWrapper);
- }
-
- int totalSize = 0;
- for (Widget w : getChildren()) {
- Slot slot = (Slot) w;
- if (slot.getExpandRatio() == -1) {
-
- if (layoutManager != null) {
- // TODO check caption position
- if (vertical) {
- int size = layoutManager.getOuterHeight(slot
- .getWidget().getElement())
- - layoutManager.getMarginHeight(slot
- .getWidget().getElement());
- if (slot.hasCaption()) {
- size += layoutManager.getOuterHeight(slot
- .getCaptionElement())
- - layoutManager.getMarginHeight(slot
- .getCaptionElement());
- }
- if (size > 0) {
- totalSize += size;
- }
- } else {
- int max = -1;
- max = layoutManager.getOuterWidth(slot.getWidget()
- .getElement())
- - layoutManager.getMarginWidth(slot
- .getWidget().getElement());
- if (slot.hasCaption()) {
- int max2 = layoutManager.getOuterWidth(slot
- .getCaptionElement())
- - layoutManager.getMarginWidth(slot
- .getCaptionElement());
- max = Math.max(max, max2);
- }
- if (max > 0) {
- totalSize += max;
- }
- }
- } else {
- totalSize += vertical ? slot.getOffsetHeight() : slot
- .getOffsetWidth();
- }
- }
- // TODO fails in Opera, always returns 0
- int spacingSize = vertical ? slot.getVerticalSpacing() : slot
- .getHorizontalSpacing();
- if (spacingSize > 0) {
- totalSize += spacingSize;
- }
- }
-
- // When we set the margin to the first child, we don't need
- // overflow:hidden in the layout root element, since the wrapper
- // would otherwise be placed outside of the layout root element
- // and block events on elements below it.
- if (vertical) {
- expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX);
- expandWrapper.getFirstChildElement().getStyle()
- .setMarginTop(-totalSize, Unit.PX);
- } else {
- expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX);
- expandWrapper.getFirstChildElement().getStyle()
- .setMarginLeft(-totalSize, Unit.PX);
- }
-
- recalculateExpands();
- }
- }
-
- /**
- * Perform a recalculation of the layout height
- */
- public void recalculateLayoutHeight() {
- // Only needed if a horizontal layout is undefined high, and contains
- // relative height children or vertical alignments
- if (vertical || definedHeight) {
- return;
- }
-
- boolean hasRelativeHeightChildren = false;
- boolean hasVAlign = false;
-
- for (Widget slot : getChildren()) {
- Widget widget = ((Slot) slot).getWidget();
- String h = widget.getElement().getStyle().getHeight();
- if (h != null && h.indexOf("%") > -1) {
- hasRelativeHeightChildren = true;
- }
- AlignmentInfo a = ((Slot) slot).getAlignment();
- if (a != null && (a.isVerticalCenter() || a.isBottom())) {
- hasVAlign = true;
- }
- }
-
- if (hasRelativeHeightChildren || hasVAlign) {
- int newHeight;
- if (layoutManager != null) {
- newHeight = layoutManager.getOuterHeight(getElement())
- - layoutManager.getMarginHeight(getElement());
- } else {
- newHeight = getElement().getOffsetHeight();
- }
- VOrderedLayout.this.getElement().getStyle()
- .setHeight(newHeight, Unit.PX);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setHeight(String height) {
- super.setHeight(height);
- definedHeight = (height != null && !"".equals(height));
- }
-
- /**
- * Sets the slots style names. The style names will be prefixed with the
- * v-slot prefix.
- *
- * @param stylenames
- * The style names of the slot.
- */
- public void setSlotStyleNames(Widget widget, String... stylenames) {
- Slot slot = getSlot(widget);
- if (slot == null) {
- throw new IllegalArgumentException(
- "A slot for the widget could not be found. Has the widget been added to the layout?");
- }
- slot.setStyleNames(stylenames);
- }
-
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import com.vaadin.client.StyleConstants;
-
-/**
- * Represents a layout where the children is ordered vertically
- */
-public class VVerticalLayout extends VOrderedLayout {
-
- public static final String CLASSNAME = "v-verticallayout";
-
- /**
- * Default constructor
- */
- public VVerticalLayout() {
- super(true);
- setStyleName(CLASSNAME);
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style);
- addStyleName(StyleConstants.UI_LAYOUT);
- addStyleName("v-vertical");
- }
-}
*/
package com.vaadin.client.ui.orderedlayout;
+import com.vaadin.client.ui.VVerticalLayout;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.ShortcutActionHandler;
import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VPanel;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.ComponentStateUtil;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.panel;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-
-public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner,
- Focusable {
-
- public static final String CLASSNAME = "v-panel";
-
- ApplicationConnection client;
-
- String id;
-
- final Element captionNode = DOM.createDiv();
-
- private final Element captionText = DOM.createSpan();
-
- private Icon icon;
-
- final Element bottomDecoration = DOM.createDiv();
-
- final Element contentNode = DOM.createDiv();
-
- private Element errorIndicatorElement;
-
- ShortcutActionHandler shortcutHandler;
-
- int scrollTop;
-
- int scrollLeft;
-
- private TouchScrollHandler touchScrollHandler;
-
- public VPanel() {
- super();
- DivElement captionWrap = Document.get().createDivElement();
- captionWrap.appendChild(captionNode);
- captionNode.appendChild(captionText);
-
- captionWrap.setClassName(CLASSNAME + "-captionwrap");
- captionNode.setClassName(CLASSNAME + "-caption");
- contentNode.setClassName(CLASSNAME + "-content");
- bottomDecoration.setClassName(CLASSNAME + "-deco");
-
- getElement().appendChild(captionWrap);
-
- /*
- * Make contentNode focusable only by using the setFocus() method. This
- * behaviour can be changed by invoking setTabIndex() in the serverside
- * implementation
- */
- contentNode.setTabIndex(-1);
-
- getElement().appendChild(contentNode);
-
- getElement().appendChild(bottomDecoration);
- setStyleName(CLASSNAME);
- DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
- DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS);
-
- contentNode.getStyle().setProperty("position", "relative");
- getElement().getStyle().setProperty("overflow", "hidden");
-
- makeScrollable();
- }
-
- /**
- * Sets the keyboard focus on the Panel
- *
- * @param focus
- * Should the panel have focus or not.
- */
- public void setFocus(boolean focus) {
- if (focus) {
- getContainerElement().focus();
- } else {
- getContainerElement().blur();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Focusable#focus()
- */
-
- @Override
- public void focus() {
- setFocus(true);
-
- }
-
- @Override
- protected Element getContainerElement() {
- return contentNode;
- }
-
- void setCaption(String text) {
- DOM.setInnerHTML(captionText, text);
- }
-
- void setErrorIndicatorVisible(boolean showError) {
- if (showError) {
- if (errorIndicatorElement == null) {
- errorIndicatorElement = DOM.createSpan();
- DOM.setElementProperty(errorIndicatorElement, "className",
- "v-errorindicator");
- DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
- sinkEvents(Event.MOUSEEVENTS);
- }
- DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
- } else if (errorIndicatorElement != null) {
- DOM.removeChild(captionNode, errorIndicatorElement);
- errorIndicatorElement = null;
- }
- }
-
- void setIconUri(String iconUri, ApplicationConnection client) {
- if (iconUri == null) {
- if (icon != null) {
- DOM.removeChild(captionNode, icon.getElement());
- icon = null;
- }
- } else {
- if (icon == null) {
- icon = new Icon(client);
- DOM.insertChild(captionNode, icon.getElement(), 0);
- }
- icon.setUri(iconUri);
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- final Element target = DOM.eventGetTarget(event);
- final int type = DOM.eventGetType(event);
- if (type == Event.ONKEYDOWN && shortcutHandler != null) {
- shortcutHandler.handleKeyboardEvent(event);
- return;
- }
- if (type == Event.ONSCROLL) {
- int newscrollTop = DOM.getElementPropertyInt(contentNode,
- "scrollTop");
- int newscrollLeft = DOM.getElementPropertyInt(contentNode,
- "scrollLeft");
- if (client != null
- && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
- scrollLeft = newscrollLeft;
- scrollTop = newscrollTop;
- client.updateVariable(id, "scrollTop", scrollTop, false);
- client.updateVariable(id, "scrollLeft", scrollLeft, false);
- }
- }
- }
-
- @Override
- public ShortcutActionHandler getShortcutActionHandler() {
- return shortcutHandler;
- }
-
- /**
- * Ensures the panel is scrollable eg. after style name changes
- */
- void makeScrollable() {
- if (touchScrollHandler == null) {
- touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
- }
- touchScrollHandler.addElement(contentNode);
- }
-}
package com.vaadin.client.ui.passwordfield;
+import com.vaadin.client.ui.VPasswordField;
import com.vaadin.client.ui.textfield.TextFieldConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.PasswordField;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.passwordfield;
-
-import com.google.gwt.user.client.DOM;
-import com.vaadin.client.ui.textfield.VTextField;
-
-/**
- * This class represents a password field.
- *
- * @author Vaadin Ltd.
- *
- */
-public class VPasswordField extends VTextField {
-
- public VPasswordField() {
- super(DOM.createInputPassword());
- }
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.PostLayoutListener;
+import com.vaadin.client.ui.VPopupView;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.popupview.PopupViewServerRpc;
event.isVisible());
}
-}
\ No newline at end of file
+}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.popupview;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.VCaptionWrapper;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.richtextarea.VRichTextArea;
-
-public class VPopupView extends HTML {
-
- public static final String CLASSNAME = "v-popupview";
-
- /** This variable helps to communicate popup visibility to the server */
- boolean hostPopupVisible;
-
- final CustomPopup popup;
- private final Label loading = new Label();
-
- /**
- * loading constructor
- */
- public VPopupView() {
- super();
- popup = new CustomPopup();
-
- setStyleName(CLASSNAME);
- popup.setStyleName(CLASSNAME + "-popup");
- loading.setStyleName(CLASSNAME + "-loading");
-
- setHTML("");
- popup.setWidget(loading);
-
- // When we click to open the popup...
- addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- fireEvent(new VisibilityChangeEvent(true));
- }
- });
-
- // ..and when we close it
- popup.addCloseHandler(new CloseHandler<PopupPanel>() {
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- fireEvent(new VisibilityChangeEvent(false));
- }
- });
-
- popup.setAnimationEnabled(true);
- }
-
-
- void preparePopup(final CustomPopup popup) {
- popup.setVisible(false);
- popup.show();
- }
-
- /**
- * Determines the correct position for a popup and displays the popup at
- * that position.
- *
- * By default, the popup is shown centered relative to its host component,
- * ensuring it is visible on the screen if possible.
- *
- * Can be overridden to customize the popup position.
- *
- * @param popup
- */
- protected void showPopup(final CustomPopup popup) {
- popup.setPopupPosition(0, 0);
-
- popup.setVisible(true);
- }
-
- void center() {
- int windowTop = RootPanel.get().getAbsoluteTop();
- int windowLeft = RootPanel.get().getAbsoluteLeft();
- int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
- int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
-
- int offsetWidth = popup.getOffsetWidth();
- int offsetHeight = popup.getOffsetHeight();
-
- int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
- + VPopupView.this.getOffsetWidth() / 2;
- int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
- + VPopupView.this.getOffsetHeight() / 2;
-
- int left = hostHorizontalCenter - offsetWidth / 2;
- int top = hostVerticalCenter - offsetHeight / 2;
-
- // Don't show the popup outside the screen.
- if ((left + offsetWidth) > windowRight) {
- left -= (left + offsetWidth) - windowRight;
- }
-
- if ((top + offsetHeight) > windowBottom) {
- top -= (top + offsetHeight) - windowBottom;
- }
-
- if (left < 0) {
- left = 0;
- }
-
- if (top < 0) {
- top = 0;
- }
-
- popup.setPopupPosition(left, top);
- }
-
- /**
- * Make sure that we remove the popup when the main widget is removed.
- *
- * @see com.google.gwt.user.client.ui.Widget#onUnload()
- */
- @Override
- protected void onDetach() {
- popup.hide();
- super.onDetach();
- }
-
- private static native void nativeBlur(Element e)
- /*-{
- if(e && e.blur) {
- e.blur();
- }
- }-*/;
-
- /**
- * This class is only protected to enable overriding showPopup, and is
- * currently not intended to be extended or otherwise used directly. Its API
- * (other than it being a VOverlay) is to be considered private and
- * potentially subject to change.
- */
- protected class CustomPopup extends VOverlay {
-
- private ComponentConnector popupComponentConnector = null;
- Widget popupComponentWidget = null;
- VCaptionWrapper captionWrapper = null;
-
- private boolean hasHadMouseOver = false;
- private boolean hideOnMouseOut = true;
- private final Set<Element> activeChildren = new HashSet<Element>();
- private boolean hiding = false;
-
- private ShortcutActionHandler shortcutActionHandler;
-
- public CustomPopup() {
- super(true, false, true); // autoHide, not modal, dropshadow
- setOwner(VPopupView.this);
- // Delegate popup keyboard events to the relevant handler. The
- // events do not propagate automatically because the popup is
- // directly attached to the RootPanel.
- addDomHandler(new KeyDownHandler() {
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (shortcutActionHandler != null) {
- shortcutActionHandler.handleKeyboardEvent(Event
- .as(event.getNativeEvent()));
- }
- }
- }, KeyDownEvent.getType());
- }
-
- // For some reason ONMOUSEOUT events are not always received, so we have
- // to use ONMOUSEMOVE that doesn't target the popup
- @Override
- public boolean onEventPreview(Event event) {
- Element target = DOM.eventGetTarget(event);
- boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
- int type = DOM.eventGetType(event);
-
- // Catch children that use keyboard, so we can unfocus them when
- // hiding
- if (eventTargetsPopup && type == Event.ONKEYPRESS) {
- activeChildren.add(target);
- }
-
- if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
- hasHadMouseOver = true;
- }
-
- if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
- if (hasHadMouseOver && hideOnMouseOut) {
- hide();
- return true;
- }
- }
-
- // Was the TAB key released outside of our popup?
- if (!eventTargetsPopup && type == Event.ONKEYUP
- && event.getKeyCode() == KeyCodes.KEY_TAB) {
- // Should we hide on focus out (mouse out)?
- if (hideOnMouseOut) {
- hide();
- return true;
- }
- }
-
- return super.onEventPreview(event);
- }
-
- @Override
- public void hide(boolean autoClosed) {
- VConsole.log("Hiding popupview");
- hiding = true;
- syncChildren();
- if (popupComponentWidget != null && popupComponentWidget != loading) {
- remove(popupComponentWidget);
- }
- hasHadMouseOver = false;
- shortcutActionHandler = null;
- super.hide(autoClosed);
- }
-
- @Override
- public void show() {
- hiding = false;
-
- // Find the shortcut action handler that should handle keyboard
- // events from the popup. The events do not propagate automatically
- // because the popup is directly attached to the RootPanel.
- Widget widget = VPopupView.this;
- while (shortcutActionHandler == null && widget != null) {
- if (widget instanceof ShortcutActionHandlerOwner) {
- shortcutActionHandler = ((ShortcutActionHandlerOwner) widget)
- .getShortcutActionHandler();
- }
- widget = widget.getParent();
- }
-
- super.show();
- }
-
- /**
- * Try to sync all known active child widgets to server
- */
- public void syncChildren() {
- // Notify children with focus
- if ((popupComponentWidget instanceof Focusable)) {
- ((Focusable) popupComponentWidget).setFocus(false);
- } else {
-
- checkForRTE(popupComponentWidget);
- }
-
- // Notify children that have used the keyboard
- for (Element e : activeChildren) {
- try {
- nativeBlur(e);
- } catch (Exception ignored) {
- }
- }
- activeChildren.clear();
- }
-
- private void checkForRTE(Widget popupComponentWidget2) {
- if (popupComponentWidget2 instanceof VRichTextArea) {
- ((VRichTextArea) popupComponentWidget2)
- .synchronizeContentToServer();
- } else if (popupComponentWidget2 instanceof HasWidgets) {
- HasWidgets hw = (HasWidgets) popupComponentWidget2;
- Iterator<Widget> iterator = hw.iterator();
- while (iterator.hasNext()) {
- checkForRTE(iterator.next());
- }
- }
- }
-
- @Override
- public boolean remove(Widget w) {
-
- popupComponentConnector = null;
- popupComponentWidget = null;
- captionWrapper = null;
-
- return super.remove(w);
- }
-
- public void setPopupConnector(ComponentConnector newPopupComponent) {
-
- if (newPopupComponent != popupComponentConnector) {
- Widget newWidget = newPopupComponent.getWidget();
- setWidget(newWidget);
- popupComponentWidget = newWidget;
- popupComponentConnector = newPopupComponent;
- }
-
- }
-
- public void setHideOnMouseOut(boolean hideOnMouseOut) {
- this.hideOnMouseOut = hideOnMouseOut;
- }
-
- /*
- *
- * We need a hack make popup act as a child of VPopupView in Vaadin's
- * component tree, but work in default GWT manner when closing or
- * opening.
- *
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.Widget#getParent()
- */
- @Override
- public Widget getParent() {
- if (!isAttached() || hiding) {
- return super.getParent();
- } else {
- return VPopupView.this;
- }
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- hiding = false;
- }
-
- @Override
- public Element getContainerElement() {
- return super.getContainerElement();
- }
-
- }// class CustomPopup
-
- public HandlerRegistration addVisibilityChangeHandler(
- final VisibilityChangeHandler visibilityChangeHandler) {
- return addHandler(visibilityChangeHandler,
- VisibilityChangeEvent.getType());
- }
-
-}// class VPopupView
import com.google.gwt.user.client.Timer;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VProgressIndicator;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.progressindicator.ProgressIndicatorServerRpc;
import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.progressindicator;
-
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.HasEnabled;
-import com.google.gwt.user.client.ui.Widget;
-
-public class VProgressIndicator extends Widget implements HasEnabled {
-
- public static final String CLASSNAME = "v-progressindicator";
- Element wrapper = DOM.createDiv();
- Element indicator = DOM.createDiv();
-
- protected boolean indeterminate = false;
- protected float state = 0.0f;
- private boolean enabled;
-
- public VProgressIndicator() {
- setElement(DOM.createDiv());
- getElement().appendChild(wrapper);
- setStyleName(CLASSNAME);
- wrapper.appendChild(indicator);
- indicator.setClassName(CLASSNAME + "-indicator");
- wrapper.setClassName(CLASSNAME + "-wrapper");
- }
-
- public void setIndeterminate(boolean indeterminate) {
- this.indeterminate = indeterminate;
- setStyleName(CLASSNAME + "-indeterminate", indeterminate);
- }
-
- public void setState(float state) {
- final int size = Math.round(100 * state);
- indicator.getStyle().setWidth(size, Unit.PCT);
- }
-
- public boolean isIndeterminate() {
- return indeterminate;
- }
-
- public float getState() {
- return state;
- }
-
- @Override
- public boolean isEnabled() {
- return enabled;
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- setStyleName("v-disabled", !enabled);
-
- }
-
-}
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VRichTextArea;
import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.richtextarea;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.RichTextArea;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-
-/**
- * This class implements a basic client side rich text editor component.
- *
- * @author Vaadin Ltd.
- *
- */
-public class VRichTextArea extends Composite implements Field, ChangeHandler,
- BlurHandler, KeyPressHandler, KeyDownHandler, Focusable {
-
- /**
- * The input node CSS classname.
- */
- public static final String CLASSNAME = "v-richtextarea";
-
- protected String id;
-
- protected ApplicationConnection client;
-
- boolean immediate = false;
-
- RichTextArea rta;
-
- private VRichTextToolbar formatter;
-
- HTML html = new HTML();
-
- private final FlowPanel fp = new FlowPanel();
-
- private boolean enabled = true;
-
- private int extraHorizontalPixels = -1;
- private int extraVerticalPixels = -1;
-
- int maxLength = -1;
-
- private int toolbarNaturalWidth = 500;
-
- HandlerRegistration keyPressHandler;
-
- private ShortcutActionHandlerOwner hasShortcutActionHandler;
-
- String currentValue = "";
-
- private boolean readOnly = false;
-
- public VRichTextArea() {
- createRTAComponents();
- fp.add(formatter);
- fp.add(rta);
-
- initWidget(fp);
- setStyleName(CLASSNAME);
-
- TouchScrollDelegate.enableTouchScrolling(html, html.getElement());
- }
-
- private void createRTAComponents() {
- rta = new RichTextArea();
- rta.setWidth("100%");
- rta.addBlurHandler(this);
- rta.addKeyDownHandler(this);
- formatter = new VRichTextToolbar(rta);
- }
-
- public void setEnabled(boolean enabled) {
- if (this.enabled != enabled) {
- // rta.setEnabled(enabled);
- swapEditableArea();
- this.enabled = enabled;
- }
- }
-
- /**
- * Swaps html to rta and visa versa.
- */
- private void swapEditableArea() {
- if (html.isAttached()) {
- fp.remove(html);
- if (BrowserInfo.get().isWebkit()) {
- fp.remove(formatter);
- createRTAComponents(); // recreate new RTA to bypass #5379
- fp.add(formatter);
- }
- rta.setHTML(currentValue);
- fp.add(rta);
- } else {
- html.setHTML(currentValue);
- fp.remove(rta);
- fp.add(html);
- }
- }
-
- void selectAll() {
- /*
- * There is a timing issue if trying to select all immediately on first
- * render. Simple deferred command is not enough. Using Timer with
- * moderated timeout. If this appears to fail on many (most likely slow)
- * environments, consider increasing the timeout.
- *
- * FF seems to require the most time to stabilize its RTA. On Vaadin
- * tiergarden test machines, 200ms was not enough always (about 50%
- * success rate) - 300 ms was 100% successful. This however was not
- * enough on a sluggish old non-virtualized XP test machine. A bullet
- * proof solution would be nice, GWT 2.1 might however solve these. At
- * least setFocus has a workaround for this kind of issue.
- */
- new Timer() {
- @Override
- public void run() {
- rta.getFormatter().selectAll();
- }
- }.schedule(320);
- }
-
- void setReadOnly(boolean b) {
- if (isReadOnly() != b) {
- swapEditableArea();
- readOnly = b;
- }
- // reset visibility in case enabled state changed and the formatter was
- // recreated
- formatter.setVisible(!readOnly);
- }
-
- private boolean isReadOnly() {
- return readOnly;
- }
-
- // TODO is this really used, or does everything go via onBlur() only?
- @Override
- public void onChange(ChangeEvent event) {
- synchronizeContentToServer();
- }
-
- /**
- * Method is public to let popupview force synchronization on close.
- */
- public void synchronizeContentToServer() {
- if (client != null && id != null) {
- final String html = rta.getHTML();
- if (!html.equals(currentValue)) {
- client.updateVariable(id, "text", html, immediate);
- currentValue = html;
- }
- }
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- synchronizeContentToServer();
- // TODO notify possible server side blur/focus listeners
- }
-
- /**
- * @return space used by components paddings and borders
- */
- private int getExtraHorizontalPixels() {
- if (extraHorizontalPixels < 0) {
- detectExtraSizes();
- }
- return extraHorizontalPixels;
- }
-
- /**
- * @return space used by components paddings and borders
- */
- private int getExtraVerticalPixels() {
- if (extraVerticalPixels < 0) {
- detectExtraSizes();
- }
- return extraVerticalPixels;
- }
-
- /**
- * Detects space used by components paddings and borders.
- */
- private void detectExtraSizes() {
- Element clone = Util.cloneNode(getElement(), false);
- DOM.setElementAttribute(clone, "id", "");
- DOM.setStyleAttribute(clone, "visibility", "hidden");
- DOM.setStyleAttribute(clone, "position", "absolute");
- // due FF3 bug set size to 10px and later subtract it from extra pixels
- DOM.setStyleAttribute(clone, "width", "10px");
- DOM.setStyleAttribute(clone, "height", "10px");
- DOM.appendChild(DOM.getParent(getElement()), clone);
- extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10;
- extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10;
-
- DOM.removeChild(DOM.getParent(getElement()), clone);
- }
-
- @Override
- public void setHeight(String height) {
- if (height.endsWith("px")) {
- int h = Integer.parseInt(height.substring(0, height.length() - 2));
- h -= getExtraVerticalPixels();
- if (h < 0) {
- h = 0;
- }
-
- super.setHeight(h + "px");
- } else {
- super.setHeight(height);
- }
-
- if (height == null || height.equals("")) {
- rta.setHeight("");
- } else {
- /*
- * The formatter height will be initially calculated wrong so we
- * delay the height setting so the DOM has had time to stabilize.
- */
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- int editorHeight = getOffsetHeight()
- - getExtraVerticalPixels()
- - formatter.getOffsetHeight();
- if (editorHeight < 0) {
- editorHeight = 0;
- }
- rta.setHeight(editorHeight + "px");
- }
- });
- }
- }
-
- @Override
- public void setWidth(String width) {
- if (width.endsWith("px")) {
- int w = Integer.parseInt(width.substring(0, width.length() - 2));
- w -= getExtraHorizontalPixels();
- if (w < 0) {
- w = 0;
- }
-
- super.setWidth(w + "px");
- } else if (width.equals("")) {
- /*
- * IE cannot calculate the width of the 100% iframe correctly if
- * there is no width specified for the parent. In this case we would
- * use the toolbar but IE cannot calculate the width of that one
- * correctly either in all cases. So we end up using a default width
- * for a RichTextArea with no width definition in all browsers (for
- * compatibility).
- */
-
- super.setWidth(toolbarNaturalWidth + "px");
- } else {
- super.setWidth(width);
- }
- }
-
- @Override
- public void onKeyPress(KeyPressEvent event) {
- if (maxLength >= 0) {
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- if (rta.getHTML().length() > maxLength) {
- rta.setHTML(rta.getHTML().substring(0, maxLength));
- }
- }
- });
- }
- }
-
- @Override
- public void onKeyDown(KeyDownEvent event) {
- // delegate to closest shortcut action handler
- // throw event from the iframe forward to the shortcuthandler
- ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner()
- .getShortcutActionHandler();
- if (shortcutHandler != null) {
- shortcutHandler
- .handleKeyboardEvent(com.google.gwt.user.client.Event
- .as(event.getNativeEvent()),
- ConnectorMap.get(client).getConnector(this));
- }
- }
-
- private ShortcutActionHandlerOwner getShortcutHandlerOwner() {
- if (hasShortcutActionHandler == null) {
- Widget parent = getParent();
- while (parent != null) {
- if (parent instanceof ShortcutActionHandlerOwner) {
- break;
- }
- parent = parent.getParent();
- }
- hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent;
- }
- return hasShortcutActionHandler;
- }
-
- @Override
- public int getTabIndex() {
- return rta.getTabIndex();
- }
-
- @Override
- public void setAccessKey(char key) {
- rta.setAccessKey(key);
- }
-
- @Override
- public void setFocus(boolean focused) {
- /*
- * Similar issue as with selectAll. Focusing must happen before possible
- * selectall, so keep the timeout here lower.
- */
- new Timer() {
-
- @Override
- public void run() {
- rta.setFocus(true);
- }
- }.schedule(300);
- }
-
- @Override
- public void setTabIndex(int index) {
- rta.setTabIndex(index);
- }
-
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-/*
- * Copyright 2007 Google Inc.
- *
- * 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.
- */
-package com.vaadin.client.ui.richtextarea;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.i18n.client.Constants;
-import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.PushButton;
-import com.google.gwt.user.client.ui.RichTextArea;
-import com.google.gwt.user.client.ui.ToggleButton;
-
-/**
- * A modified version of sample toolbar for use with {@link RichTextArea}. It
- * provides a simple UI for all rich text formatting, dynamically displayed only
- * for the available functionality.
- */
-public class VRichTextToolbar extends Composite {
-
- /**
- * This {@link ClientBundle} is used for all the button icons. Using a
- * bundle allows all of these images to be packed into a single image, which
- * saves a lot of HTTP requests, drastically improving startup time.
- */
- public interface Images extends ClientBundle {
-
- ImageResource bold();
-
- ImageResource createLink();
-
- ImageResource hr();
-
- ImageResource indent();
-
- ImageResource insertImage();
-
- ImageResource italic();
-
- ImageResource justifyCenter();
-
- ImageResource justifyLeft();
-
- ImageResource justifyRight();
-
- ImageResource ol();
-
- ImageResource outdent();
-
- ImageResource removeFormat();
-
- ImageResource removeLink();
-
- ImageResource strikeThrough();
-
- ImageResource subscript();
-
- ImageResource superscript();
-
- ImageResource ul();
-
- ImageResource underline();
- }
-
- /**
- * This {@link Constants} interface is used to make the toolbar's strings
- * internationalizable.
- */
- public interface Strings extends Constants {
-
- String black();
-
- String blue();
-
- String bold();
-
- String color();
-
- String createLink();
-
- String font();
-
- String green();
-
- String hr();
-
- String indent();
-
- String insertImage();
-
- String italic();
-
- String justifyCenter();
-
- String justifyLeft();
-
- String justifyRight();
-
- String large();
-
- String medium();
-
- String normal();
-
- String ol();
-
- String outdent();
-
- String red();
-
- String removeFormat();
-
- String removeLink();
-
- String size();
-
- String small();
-
- String strikeThrough();
-
- String subscript();
-
- String superscript();
-
- String ul();
-
- String underline();
-
- String white();
-
- String xlarge();
-
- String xsmall();
-
- String xxlarge();
-
- String xxsmall();
-
- String yellow();
- }
-
- /**
- * We use an inner EventHandler class to avoid exposing event methods on the
- * RichTextToolbar itself.
- */
- private class EventHandler implements ClickHandler, ChangeHandler,
- KeyUpHandler {
-
- @Override
- @SuppressWarnings("deprecation")
- public void onChange(ChangeEvent event) {
- Object sender = event.getSource();
- if (sender == backColors) {
- basic.setBackColor(backColors.getValue(backColors
- .getSelectedIndex()));
- backColors.setSelectedIndex(0);
- } else if (sender == foreColors) {
- basic.setForeColor(foreColors.getValue(foreColors
- .getSelectedIndex()));
- foreColors.setSelectedIndex(0);
- } else if (sender == fonts) {
- basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
- fonts.setSelectedIndex(0);
- } else if (sender == fontSizes) {
- basic.setFontSize(fontSizesConstants[fontSizes
- .getSelectedIndex() - 1]);
- fontSizes.setSelectedIndex(0);
- }
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public void onClick(ClickEvent event) {
- Object sender = event.getSource();
- if (sender == bold) {
- basic.toggleBold();
- } else if (sender == italic) {
- basic.toggleItalic();
- } else if (sender == underline) {
- basic.toggleUnderline();
- } else if (sender == subscript) {
- basic.toggleSubscript();
- } else if (sender == superscript) {
- basic.toggleSuperscript();
- } else if (sender == strikethrough) {
- extended.toggleStrikethrough();
- } else if (sender == indent) {
- extended.rightIndent();
- } else if (sender == outdent) {
- extended.leftIndent();
- } else if (sender == justifyLeft) {
- basic.setJustification(RichTextArea.Justification.LEFT);
- } else if (sender == justifyCenter) {
- basic.setJustification(RichTextArea.Justification.CENTER);
- } else if (sender == justifyRight) {
- basic.setJustification(RichTextArea.Justification.RIGHT);
- } else if (sender == insertImage) {
- final String url = Window.prompt("Enter an image URL:",
- "http://");
- if (url != null) {
- extended.insertImage(url);
- }
- } else if (sender == createLink) {
- final String url = Window
- .prompt("Enter a link URL:", "http://");
- if (url != null) {
- extended.createLink(url);
- }
- } else if (sender == removeLink) {
- extended.removeLink();
- } else if (sender == hr) {
- extended.insertHorizontalRule();
- } else if (sender == ol) {
- extended.insertOrderedList();
- } else if (sender == ul) {
- extended.insertUnorderedList();
- } else if (sender == removeFormat) {
- extended.removeFormat();
- } else if (sender == richText) {
- // We use the RichTextArea's onKeyUp event to update the toolbar
- // status. This will catch any cases where the user moves the
- // cursur using the keyboard, or uses one of the browser's
- // built-in keyboard shortcuts.
- updateStatus();
- }
- }
-
- @Override
- public void onKeyUp(KeyUpEvent event) {
- if (event.getSource() == richText) {
- // We use the RichTextArea's onKeyUp event to update the toolbar
- // status. This will catch any cases where the user moves the
- // cursor using the keyboard, or uses one of the browser's
- // built-in keyboard shortcuts.
- updateStatus();
- }
- }
- }
-
- private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
- RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
- RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
- RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
- RichTextArea.FontSize.XX_LARGE };
-
- private final Images images = (Images) GWT.create(Images.class);
- private final Strings strings = (Strings) GWT.create(Strings.class);
- private final EventHandler handler = new EventHandler();
-
- private final RichTextArea richText;
- @SuppressWarnings("deprecation")
- private final RichTextArea.BasicFormatter basic;
- @SuppressWarnings("deprecation")
- private final RichTextArea.ExtendedFormatter extended;
-
- private final FlowPanel outer = new FlowPanel();
- private final FlowPanel topPanel = new FlowPanel();
- private final FlowPanel bottomPanel = new FlowPanel();
- private ToggleButton bold;
- private ToggleButton italic;
- private ToggleButton underline;
- private ToggleButton subscript;
- private ToggleButton superscript;
- private ToggleButton strikethrough;
- private PushButton indent;
- private PushButton outdent;
- private PushButton justifyLeft;
- private PushButton justifyCenter;
- private PushButton justifyRight;
- private PushButton hr;
- private PushButton ol;
- private PushButton ul;
- private PushButton insertImage;
- private PushButton createLink;
- private PushButton removeLink;
- private PushButton removeFormat;
-
- private ListBox backColors;
- private ListBox foreColors;
- private ListBox fonts;
- private ListBox fontSizes;
-
- /**
- * Creates a new toolbar that drives the given rich text area.
- *
- * @param richText
- * the rich text area to be controlled
- */
- @SuppressWarnings("deprecation")
- public VRichTextToolbar(RichTextArea richText) {
- this.richText = richText;
- basic = richText.getBasicFormatter();
- extended = richText.getExtendedFormatter();
-
- outer.add(topPanel);
- outer.add(bottomPanel);
- topPanel.setStyleName("gwt-RichTextToolbar-top");
- bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
-
- initWidget(outer);
- setStyleName("gwt-RichTextToolbar");
-
- if (basic != null) {
- topPanel.add(bold = createToggleButton(images.bold(),
- strings.bold()));
- topPanel.add(italic = createToggleButton(images.italic(),
- strings.italic()));
- topPanel.add(underline = createToggleButton(images.underline(),
- strings.underline()));
- topPanel.add(subscript = createToggleButton(images.subscript(),
- strings.subscript()));
- topPanel.add(superscript = createToggleButton(images.superscript(),
- strings.superscript()));
- topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
- strings.justifyLeft()));
- topPanel.add(justifyCenter = createPushButton(
- images.justifyCenter(), strings.justifyCenter()));
- topPanel.add(justifyRight = createPushButton(images.justifyRight(),
- strings.justifyRight()));
- }
-
- if (extended != null) {
- topPanel.add(strikethrough = createToggleButton(
- images.strikeThrough(), strings.strikeThrough()));
- topPanel.add(indent = createPushButton(images.indent(),
- strings.indent()));
- topPanel.add(outdent = createPushButton(images.outdent(),
- strings.outdent()));
- topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
- topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
- topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
- topPanel.add(insertImage = createPushButton(images.insertImage(),
- strings.insertImage()));
- topPanel.add(createLink = createPushButton(images.createLink(),
- strings.createLink()));
- topPanel.add(removeLink = createPushButton(images.removeLink(),
- strings.removeLink()));
- topPanel.add(removeFormat = createPushButton(images.removeFormat(),
- strings.removeFormat()));
- }
-
- if (basic != null) {
- bottomPanel.add(backColors = createColorList("Background"));
- bottomPanel.add(foreColors = createColorList("Foreground"));
- bottomPanel.add(fonts = createFontList());
- bottomPanel.add(fontSizes = createFontSizes());
-
- // We only use these handlers for updating status, so don't hook
- // them up unless at least basic editing is supported.
- richText.addKeyUpHandler(handler);
- richText.addClickHandler(handler);
- }
- }
-
- private ListBox createColorList(String caption) {
- final ListBox lb = new ListBox();
- lb.addChangeHandler(handler);
- lb.setVisibleItemCount(1);
-
- lb.addItem(caption);
- lb.addItem(strings.white(), "white");
- lb.addItem(strings.black(), "black");
- lb.addItem(strings.red(), "red");
- lb.addItem(strings.green(), "green");
- lb.addItem(strings.yellow(), "yellow");
- lb.addItem(strings.blue(), "blue");
- lb.setTabIndex(-1);
- return lb;
- }
-
- private ListBox createFontList() {
- final ListBox lb = new ListBox();
- lb.addChangeHandler(handler);
- lb.setVisibleItemCount(1);
-
- lb.addItem(strings.font(), "");
- lb.addItem(strings.normal(), "inherit");
- lb.addItem("Times New Roman", "Times New Roman");
- lb.addItem("Arial", "Arial");
- lb.addItem("Courier New", "Courier New");
- lb.addItem("Georgia", "Georgia");
- lb.addItem("Trebuchet", "Trebuchet");
- lb.addItem("Verdana", "Verdana");
- lb.setTabIndex(-1);
- return lb;
- }
-
- private ListBox createFontSizes() {
- final ListBox lb = new ListBox();
- lb.addChangeHandler(handler);
- lb.setVisibleItemCount(1);
-
- lb.addItem(strings.size());
- lb.addItem(strings.xxsmall());
- lb.addItem(strings.xsmall());
- lb.addItem(strings.small());
- lb.addItem(strings.medium());
- lb.addItem(strings.large());
- lb.addItem(strings.xlarge());
- lb.addItem(strings.xxlarge());
- lb.setTabIndex(-1);
- return lb;
- }
-
- private PushButton createPushButton(ImageResource img, String tip) {
- final PushButton pb = new PushButton(new Image(img));
- pb.addClickHandler(handler);
- pb.setTitle(tip);
- pb.setTabIndex(-1);
- return pb;
- }
-
- private ToggleButton createToggleButton(ImageResource img, String tip) {
- final ToggleButton tb = new ToggleButton(new Image(img));
- tb.addClickHandler(handler);
- tb.setTitle(tip);
- tb.setTabIndex(-1);
- return tb;
- }
-
- /**
- * Updates the status of all the stateful buttons.
- */
- @SuppressWarnings("deprecation")
- private void updateStatus() {
- if (basic != null) {
- bold.setDown(basic.isBold());
- italic.setDown(basic.isItalic());
- underline.setDown(basic.isUnderlined());
- subscript.setDown(basic.isSubscript());
- superscript.setDown(basic.isSuperscript());
- }
-
- if (extended != null) {
- strikethrough.setDown(extended.isStrikethrough());
- }
- }
-}
import com.vaadin.client.communication.RpcProxy;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VSlider;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.slider.SliderServerRpc;
import com.vaadin.shared.ui.slider.SliderState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-//
-package com.vaadin.client.ui.slider;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HasValue;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ContainerResizedListener;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SimpleFocusablePanel;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.ui.slider.SliderOrientation;
-
-public class VSlider extends SimpleFocusablePanel implements Field,
- ContainerResizedListener, HasValue<Double> {
-
- public static final String CLASSNAME = "v-slider";
-
- /**
- * Minimum size (width or height, depending on orientation) of the slider
- * base.
- */
- private static final int MIN_SIZE = 50;
-
- protected ApplicationConnection client;
-
- protected String id;
-
- protected boolean immediate;
- protected boolean disabled;
- protected boolean readonly;
-
- private int acceleration = 1;
- protected double min;
- protected double max;
- protected int resolution;
- protected Double value;
- protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
-
- private final HTML feedback = new HTML("", false);
- private final VOverlay feedbackPopup = new VOverlay(true, false, true) {
- {
- setOwner(VSlider.this);
- }
-
- @Override
- public void show() {
- super.show();
- updateFeedbackPosition();
- }
- };
-
- /* DOM element for slider's base */
- private final Element base;
- private final int BASE_BORDER_WIDTH = 1;
-
- /* DOM element for slider's handle */
- private final Element handle;
-
- /* DOM element for decrement arrow */
- private final Element smaller;
-
- /* DOM element for increment arrow */
- private final Element bigger;
-
- /* Temporary dragging/animation variables */
- private boolean dragging = false;
-
- private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100,
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- fireValueChanged();
- acceleration = 1;
- }
- });
-
- public VSlider() {
- super();
-
- base = DOM.createDiv();
- handle = DOM.createDiv();
- smaller = DOM.createDiv();
- bigger = DOM.createDiv();
-
- setStyleName(CLASSNAME);
-
- getElement().appendChild(bigger);
- getElement().appendChild(smaller);
- getElement().appendChild(base);
- base.appendChild(handle);
-
- // Hide initially
- smaller.getStyle().setDisplay(Display.NONE);
- bigger.getStyle().setDisplay(Display.NONE);
- handle.getStyle().setVisibility(Visibility.HIDDEN);
-
- sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
- | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
-
- feedbackPopup.setWidget(feedback);
- }
-
- @Override
- public void setStyleName(String style) {
- updateStyleNames(style, false);
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- updateStyleNames(style, true);
- }
-
- protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) {
-
- feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback");
- removeStyleName(getStylePrimaryName() + "-vertical");
-
- if (isPrimaryStyleName) {
- super.setStylePrimaryName(styleName);
- } else {
- super.setStyleName(styleName);
- }
-
- feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback");
- base.setClassName(getStylePrimaryName() + "-base");
- handle.setClassName(getStylePrimaryName() + "-handle");
- smaller.setClassName(getStylePrimaryName() + "-smaller");
- bigger.setClassName(getStylePrimaryName() + "-bigger");
-
- if (isVertical()) {
- addStyleName(getStylePrimaryName() + "-vertical");
- }
- }
-
- void setFeedbackValue(double value) {
- String currentValue = "" + value;
- if (resolution == 0) {
- currentValue = "" + new Double(value).intValue();
- }
- feedback.setText(currentValue);
- }
-
- private void updateFeedbackPosition() {
- if (isVertical()) {
- feedbackPopup.setPopupPosition(
- handle.getAbsoluteLeft() + handle.getOffsetWidth(),
- handle.getAbsoluteTop() + handle.getOffsetHeight() / 2
- - feedbackPopup.getOffsetHeight() / 2);
- } else {
- feedbackPopup.setPopupPosition(
- handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2
- - feedbackPopup.getOffsetWidth() / 2,
- handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight());
- }
- }
-
- void buildBase() {
- final String styleAttribute = isVertical() ? "height" : "width";
- final String oppositeStyleAttribute = isVertical() ? "width" : "height";
- final String domProperty = isVertical() ? "offsetHeight"
- : "offsetWidth";
-
- // clear unnecessary opposite style attribute
- base.getStyle().clearProperty(oppositeStyleAttribute);
-
- final Element p = getElement().getParentElement().cast();
- if (p.getPropertyInt(domProperty) > 50) {
- if (isVertical()) {
- setHeight();
- } else {
- base.getStyle().clearProperty(styleAttribute);
- }
- } else {
- // Set minimum size and adjust after all components have
- // (supposedly) been drawn completely.
- base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- final Element p = getElement().getParentElement().cast();
- if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5)) {
- if (isVertical()) {
- setHeight();
- } else {
- base.getStyle().clearProperty(styleAttribute);
- }
- // Ensure correct position
- setValue(value, false);
- }
- }
- });
- }
-
- if (!isVertical()) {
- // Draw handle with a delay to allow base to gain maximum width
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- buildHandle();
- setValue(value, false);
- }
- });
- } else {
- buildHandle();
- setValue(value, false);
- }
-
- // TODO attach listeners for focusing and arrow keys
- }
-
- void buildHandle() {
- final String handleAttribute = isVertical() ? "marginTop"
- : "marginLeft";
- final String oppositeHandleAttribute = isVertical() ? "marginLeft"
- : "marginTop";
-
- handle.getStyle().setProperty(handleAttribute, "0");
-
- // clear unnecessary opposite handle attribute
- handle.getStyle().clearProperty(oppositeHandleAttribute);
-
- // Restore visibility
- handle.getStyle().setVisibility(Visibility.VISIBLE);
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (disabled || readonly) {
- return;
- }
- final Element targ = DOM.eventGetTarget(event);
-
- if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
- processMouseWheelEvent(event);
- } else if (dragging || targ == handle) {
- processHandleEvent(event);
- } else if (targ == smaller) {
- decreaseValue(true);
- } else if (targ == bigger) {
- increaseValue(true);
- } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
- processBaseEvent(event);
- } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
- || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
-
- if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
- event.getShiftKey())) {
-
- feedbackPopup.show();
-
- delayedValueUpdater.trigger();
-
- DOM.eventPreventDefault(event);
- DOM.eventCancelBubble(event, true);
- }
- } else if (targ.equals(getElement())
- && DOM.eventGetType(event) == Event.ONFOCUS) {
- feedbackPopup.show();
- } else if (targ.equals(getElement())
- && DOM.eventGetType(event) == Event.ONBLUR) {
- feedbackPopup.hide();
- } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
- feedbackPopup.show();
- }
- if (Util.isTouchEvent(event)) {
- event.preventDefault(); // avoid simulated events
- event.stopPropagation();
- }
- }
-
- private void processMouseWheelEvent(final Event event) {
- final int dir = DOM.eventGetMouseWheelVelocityY(event);
-
- if (dir < 0) {
- increaseValue(false);
- } else {
- decreaseValue(false);
- }
-
- delayedValueUpdater.trigger();
-
- DOM.eventPreventDefault(event);
- DOM.eventCancelBubble(event, true);
- }
-
- private void processHandleEvent(Event event) {
- switch (DOM.eventGetType(event)) {
- case Event.ONMOUSEDOWN:
- case Event.ONTOUCHSTART:
- if (!disabled && !readonly) {
- focus();
- feedbackPopup.show();
- dragging = true;
- handle.setClassName(getStylePrimaryName() + "-handle");
- handle.addClassName(getStylePrimaryName() + "-handle-active");
-
- DOM.setCapture(getElement());
- DOM.eventPreventDefault(event); // prevent selecting text
- DOM.eventCancelBubble(event, true);
- event.stopPropagation();
- VConsole.log("Slider move start");
- }
- break;
- case Event.ONMOUSEMOVE:
- case Event.ONTOUCHMOVE:
- if (dragging) {
- VConsole.log("Slider move");
- setValueByEvent(event, false);
- updateFeedbackPosition();
- event.stopPropagation();
- }
- break;
- case Event.ONTOUCHEND:
- feedbackPopup.hide();
- case Event.ONMOUSEUP:
- // feedbackPopup.hide();
- VConsole.log("Slider move end");
- dragging = false;
- handle.setClassName(getStylePrimaryName() + "-handle");
- DOM.releaseCapture(getElement());
- setValueByEvent(event, true);
- event.stopPropagation();
- break;
- default:
- break;
- }
- }
-
- private void processBaseEvent(Event event) {
- if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
- if (!disabled && !readonly && !dragging) {
- setValueByEvent(event, true);
- DOM.eventCancelBubble(event, true);
- }
- }
- }
-
- private void decreaseValue(boolean updateToServer) {
- setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
- updateToServer);
- }
-
- private void increaseValue(boolean updateToServer) {
- setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
- updateToServer);
- }
-
- private void setValueByEvent(Event event, boolean updateToServer) {
- double v = min; // Fallback to min
-
- final int coord = getEventPosition(event);
-
- final int handleSize, baseSize, baseOffset;
- if (isVertical()) {
- handleSize = handle.getOffsetHeight();
- baseSize = base.getOffsetHeight();
- baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
- - handleSize / 2;
- } else {
- handleSize = handle.getOffsetWidth();
- baseSize = base.getOffsetWidth();
- baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
- + handleSize / 2;
- }
-
- if (isVertical()) {
- v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize))
- * (max - min) + min;
- } else {
- v = ((coord - baseOffset) / (double) (baseSize - handleSize))
- * (max - min) + min;
- }
-
- if (v < min) {
- v = min;
- } else if (v > max) {
- v = max;
- }
-
- setValue(v, updateToServer);
- }
-
- /**
- * TODO consider extracting touches support to an impl class specific for
- * webkit (only browser that really supports touches).
- *
- * @param event
- * @return
- */
- protected int getEventPosition(Event event) {
- if (isVertical()) {
- return Util.getTouchOrMouseClientY(event);
- } else {
- return Util.getTouchOrMouseClientX(event);
- }
- }
-
- @Override
- public void iLayout() {
- if (isVertical()) {
- setHeight();
- }
- // Update handle position
- setValue(value, false);
- }
-
- private void setHeight() {
- // Calculate decoration size
- base.getStyle().setHeight(0, Unit.PX);
- base.getStyle().setOverflow(Overflow.HIDDEN);
- int h = getElement().getOffsetHeight();
- if (h < MIN_SIZE) {
- h = MIN_SIZE;
- }
- base.getStyle().setHeight(h, Unit.PX);
- base.getStyle().clearOverflow();
- }
-
- private void fireValueChanged() {
- ValueChangeEvent.fire(VSlider.this, value);
- }
-
- /**
- * Handles the keyboard events handled by the Slider
- *
- * @param event
- * The keyboard event received
- * @return true iff the navigation event was handled
- */
- public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-
- // No support for ctrl moving
- if (ctrl) {
- return false;
- }
-
- if ((keycode == getNavigationUpKey() && isVertical())
- || (keycode == getNavigationRightKey() && !isVertical())) {
- if (shift) {
- for (int a = 0; a < acceleration; a++) {
- increaseValue(false);
- }
- acceleration++;
- } else {
- increaseValue(false);
- }
- return true;
- } else if (keycode == getNavigationDownKey() && isVertical()
- || (keycode == getNavigationLeftKey() && !isVertical())) {
- if (shift) {
- for (int a = 0; a < acceleration; a++) {
- decreaseValue(false);
- }
- acceleration++;
- } else {
- decreaseValue(false);
- }
- return true;
- }
-
- return false;
- }
-
- /**
- * Get the key that increases the vertical slider. By default it is the up
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationUpKey() {
- return KeyCodes.KEY_UP;
- }
-
- /**
- * Get the key that decreases the vertical slider. By default it is the down
- * arrow key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationDownKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * Get the key that decreases the horizontal slider. By default it is the
- * left arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationLeftKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * Get the key that increases the horizontal slider. By default it is the
- * right arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationRightKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- public void setConnection(ApplicationConnection client) {
- this.client = client;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public void setImmediate(boolean immediate) {
- this.immediate = immediate;
- }
-
- public void setDisabled(boolean disabled) {
- this.disabled = disabled;
- }
-
- public void setReadOnly(boolean readonly) {
- this.readonly = readonly;
- }
-
- private boolean isVertical() {
- return orientation == SliderOrientation.VERTICAL;
- }
-
- public void setOrientation(SliderOrientation orientation) {
- if (this.orientation != orientation) {
- this.orientation = orientation;
- updateStyleNames(getStylePrimaryName(), true);
- }
- }
-
- public void setMinValue(double value) {
- min = value;
- }
-
- public void setMaxValue(double value) {
- max = value;
- }
-
- public void setResolution(int resolution) {
- this.resolution = resolution;
- }
-
- @Override
- public HandlerRegistration addValueChangeHandler(
- ValueChangeHandler<Double> handler) {
- return addHandler(handler, ValueChangeEvent.getType());
- }
-
- @Override
- public Double getValue() {
- return value;
- }
-
- @Override
- public void setValue(Double value) {
- if (value < min) {
- value = min;
- } else if (value > max) {
- value = max;
- }
-
- // Update handle position
- final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
- final String domProperty = isVertical() ? "offsetHeight"
- : "offsetWidth";
- final int handleSize = handle.getPropertyInt(domProperty);
- final int baseSize = base.getPropertyInt(domProperty)
- - (2 * BASE_BORDER_WIDTH);
-
- final int range = baseSize - handleSize;
- double v = value.doubleValue();
-
- // Round value to resolution
- if (resolution > 0) {
- v = Math.round(v * Math.pow(10, resolution));
- v = v / Math.pow(10, resolution);
- } else {
- v = Math.round(v);
- }
- final double valueRange = max - min;
- double p = 0;
- if (valueRange > 0) {
- p = range * ((v - min) / valueRange);
- }
- if (p < 0) {
- p = 0;
- }
- if (isVertical()) {
- p = range - p;
- }
- final double pos = p;
-
- handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
-
- // Update value
- this.value = new Double(v);
- setFeedbackValue(v);
- }
-
- @Override
- public void setValue(Double value, boolean fireEvents) {
- if (value == null) {
- return;
- }
-
- setValue(value);
-
- if (fireEvents) {
- fireValueChanged();
- }
- }
-}
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.ClickEventHandler;
import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
+import com.vaadin.client.ui.VAbstractSplitPanel;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc;
*/
package com.vaadin.client.ui.splitpanel;
+import com.vaadin.client.ui.VSplitPanelHorizontal;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.splitpanel.HorizontalSplitPanelState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import java.util.Collections;
-import java.util.List;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.event.dom.client.TouchCancelEvent;
-import com.google.gwt.event.dom.client.TouchCancelHandler;
-import com.google.gwt.event.dom.client.TouchEndEvent;
-import com.google.gwt.event.dom.client.TouchEndHandler;
-import com.google.gwt.event.dom.client.TouchMoveEvent;
-import com.google.gwt.event.dom.client.TouchMoveHandler;
-import com.google.gwt.event.dom.client.TouchStartEvent;
-import com.google.gwt.event.dom.client.TouchStartHandler;
-import com.google.gwt.event.shared.EventHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
-import com.vaadin.shared.ui.Orientation;
-
-public class VAbstractSplitPanel extends ComplexPanel {
-
- private boolean enabled = false;
-
- public static final String CLASSNAME = "v-splitpanel";
-
- private static final int MIN_SIZE = 30;
-
- private Orientation orientation = Orientation.HORIZONTAL;
-
- Widget firstChild;
-
- Widget secondChild;
-
- private final Element wrapper = DOM.createDiv();
-
- private final Element firstContainer = DOM.createDiv();
-
- private final Element secondContainer = DOM.createDiv();
-
- final Element splitter = DOM.createDiv();
-
- private boolean resizing;
-
- private boolean resized = false;
-
- private int origX;
-
- private int origY;
-
- private int origMouseX;
-
- private int origMouseY;
-
- private boolean locked = false;
-
- private boolean positionReversed = false;
-
- List<String> componentStyleNames = Collections.emptyList();
-
- private Element draggingCurtain;
-
- ApplicationConnection client;
-
- boolean immediate;
-
- /* The current position of the split handle in either percentages or pixels */
- String position;
-
- String maximumPosition;
-
- String minimumPosition;
-
- private TouchScrollHandler touchScrollHandler;
-
- protected Element scrolledContainer;
-
- protected int origScrollTop;
-
- public VAbstractSplitPanel() {
- this(Orientation.HORIZONTAL);
- }
-
- public VAbstractSplitPanel(Orientation orientation) {
- setElement(DOM.createDiv());
- setStyleName(StyleConstants.UI_LAYOUT);
- switch (orientation) {
- case HORIZONTAL:
- addStyleName(CLASSNAME + "-horizontal");
- break;
- case VERTICAL:
- default:
- addStyleName(CLASSNAME + "-vertical");
- break;
- }
- // size below will be overridden in update from uidl, initial size
- // needed to keep IE alive
- setWidth(MIN_SIZE + "px");
- setHeight(MIN_SIZE + "px");
- constructDom();
- setOrientation(orientation);
- sinkEvents(Event.MOUSEEVENTS);
-
- makeScrollable();
-
- addDomHandler(new TouchCancelHandler() {
- @Override
- public void onTouchCancel(TouchCancelEvent event) {
- // TODO When does this actually happen??
- VConsole.log("TOUCH CANCEL");
- }
- }, TouchCancelEvent.getType());
- addDomHandler(new TouchStartHandler() {
- @Override
- public void onTouchStart(TouchStartEvent event) {
- Node target = event.getTouches().get(0).getTarget().cast();
- if (splitter.isOrHasChild(target)) {
- onMouseDown(Event.as(event.getNativeEvent()));
- }
- }
- }, TouchStartEvent.getType());
- addDomHandler(new TouchMoveHandler() {
- @Override
- public void onTouchMove(TouchMoveEvent event) {
- if (resizing) {
- onMouseMove(Event.as(event.getNativeEvent()));
- }
- }
- }, TouchMoveEvent.getType());
- addDomHandler(new TouchEndHandler() {
- @Override
- public void onTouchEnd(TouchEndEvent event) {
- if (resizing) {
- onMouseUp(Event.as(event.getNativeEvent()));
- }
- }
- }, TouchEndEvent.getType());
-
- }
-
- protected void constructDom() {
- DOM.appendChild(splitter, DOM.createDiv()); // for styling
- DOM.appendChild(getElement(), wrapper);
- DOM.setStyleAttribute(wrapper, "position", "relative");
- DOM.setStyleAttribute(wrapper, "width", "100%");
- DOM.setStyleAttribute(wrapper, "height", "100%");
-
- DOM.appendChild(wrapper, secondContainer);
- DOM.appendChild(wrapper, firstContainer);
- DOM.appendChild(wrapper, splitter);
-
- DOM.setStyleAttribute(splitter, "position", "absolute");
- DOM.setStyleAttribute(secondContainer, "position", "absolute");
-
- setStylenames();
- }
-
- private void setOrientation(Orientation orientation) {
- this.orientation = orientation;
- if (orientation == Orientation.HORIZONTAL) {
- DOM.setStyleAttribute(splitter, "height", "100%");
- DOM.setStyleAttribute(splitter, "top", "0");
- DOM.setStyleAttribute(firstContainer, "height", "100%");
- DOM.setStyleAttribute(secondContainer, "height", "100%");
- } else {
- DOM.setStyleAttribute(splitter, "width", "100%");
- DOM.setStyleAttribute(splitter, "left", "0");
- DOM.setStyleAttribute(firstContainer, "width", "100%");
- DOM.setStyleAttribute(secondContainer, "width", "100%");
- }
- }
-
- @Override
- public boolean remove(Widget w) {
- boolean removed = super.remove(w);
- if (removed) {
- if (firstChild == w) {
- firstChild = null;
- } else {
- secondChild = null;
- }
- }
- return removed;
- }
-
- void setLocked(boolean newValue) {
- if (locked != newValue) {
- locked = newValue;
- splitterSize = -1;
- setStylenames();
- }
- }
-
- void setPositionReversed(boolean reversed) {
- if (positionReversed != reversed) {
- if (orientation == Orientation.HORIZONTAL) {
- DOM.setStyleAttribute(splitter, "right", "");
- DOM.setStyleAttribute(splitter, "left", "");
- } else if (orientation == Orientation.VERTICAL) {
- DOM.setStyleAttribute(splitter, "top", "");
- DOM.setStyleAttribute(splitter, "bottom", "");
- }
-
- positionReversed = reversed;
- }
- }
-
- /**
- * Converts given split position string (in pixels or percentage) to a
- * floating point pixel value.
- *
- * @param pos
- * @return
- */
- private float convertToPixels(String pos) {
- float posAsFloat;
- if (pos.indexOf("%") > 0) {
- posAsFloat = Math.round(Float.parseFloat(pos.substring(0,
- pos.length() - 1))
- / 100
- * (orientation == Orientation.HORIZONTAL ? getOffsetWidth()
- : getOffsetHeight()));
- } else {
- posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2));
- }
- return posAsFloat;
- }
-
- /**
- * Converts given split position string (in pixels or percentage) to a float
- * percentage value.
- *
- * @param pos
- * @return
- */
- private float convertToPercentage(String pos) {
- if (pos.endsWith("px")) {
- float pixelPosition = Float.parseFloat(pos.substring(0,
- pos.length() - 2));
- int offsetLength = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
- : getOffsetHeight();
-
- // Take splitter size into account at the edge
- if (pixelPosition + getSplitterSize() >= offsetLength) {
- return 100;
- }
-
- return pixelPosition / offsetLength * 100;
- } else {
- assert pos.endsWith("%");
- return Float.parseFloat(pos.substring(0, pos.length() - 1));
- }
- }
-
- /**
- * Returns the given position clamped to the range between current minimum
- * and maximum positions.
- *
- * TODO Should this be in the connector?
- *
- * @param pos
- * Position of the splitter as a CSS string, either pixels or a
- * percentage.
- * @return minimumPosition if pos is less than minimumPosition;
- * maximumPosition if pos is greater than maximumPosition; pos
- * otherwise.
- */
- private String checkSplitPositionLimits(String pos) {
- float positionAsFloat = convertToPixels(pos);
-
- if (maximumPosition != null
- && convertToPixels(maximumPosition) < positionAsFloat) {
- pos = maximumPosition;
- } else if (minimumPosition != null
- && convertToPixels(minimumPosition) > positionAsFloat) {
- pos = minimumPosition;
- }
- return pos;
- }
-
- /**
- * Converts given string to the same units as the split position is.
- *
- * @param pos
- * position to be converted
- * @return converted position string
- */
- private String convertToPositionUnits(String pos) {
- if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) {
- // position is in percentage, pos in pixels
- pos = convertToPercentage(pos) + "%";
- } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) {
- // position is in pixels and pos in percentage
- pos = convertToPixels(pos) + "px";
- }
-
- return pos;
- }
-
- void setSplitPosition(String pos) {
- if (pos == null) {
- return;
- }
-
- pos = checkSplitPositionLimits(pos);
- if (!pos.equals(position)) {
- position = convertToPositionUnits(pos);
- }
-
- // Convert percentage values to pixels
- if (pos.indexOf("%") > 0) {
- int size = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
- : getOffsetHeight();
- float percentage = Float.parseFloat(pos.substring(0,
- pos.length() - 1));
- pos = percentage / 100 * size + "px";
- }
-
- String attributeName;
- if (orientation == Orientation.HORIZONTAL) {
- if (positionReversed) {
- attributeName = "right";
- } else {
- attributeName = "left";
- }
- } else {
- if (positionReversed) {
- attributeName = "bottom";
- } else {
- attributeName = "top";
- }
- }
-
- Style style = splitter.getStyle();
- if (!pos.equals(style.getProperty(attributeName))) {
- style.setProperty(attributeName, pos);
- updateSizes();
- }
- }
-
- void updateSizes() {
- if (!isAttached()) {
- return;
- }
-
- int wholeSize;
- int pixelPosition;
-
- switch (orientation) {
- case HORIZONTAL:
- wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
- pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
-
- // reposition splitter in case it is out of box
- if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
- || (positionReversed && pixelPosition < 0)) {
- pixelPosition = wholeSize - getSplitterSize();
- if (pixelPosition < 0) {
- pixelPosition = 0;
- }
- setSplitPosition(pixelPosition + "px");
- return;
- }
-
- DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px");
- int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
- if (secondContainerWidth < 0) {
- secondContainerWidth = 0;
- }
- DOM.setStyleAttribute(secondContainer, "width",
- secondContainerWidth + "px");
- DOM.setStyleAttribute(secondContainer, "left",
- (pixelPosition + getSplitterSize()) + "px");
-
- LayoutManager layoutManager = LayoutManager.get(client);
- ConnectorMap connectorMap = ConnectorMap.get(client);
- if (firstChild != null) {
- ComponentConnector connector = connectorMap
- .getConnector(firstChild);
- if (connector.isRelativeWidth()) {
- layoutManager.reportWidthAssignedToRelative(connector,
- pixelPosition);
- } else {
- layoutManager.setNeedsMeasure(connector);
- }
- }
- if (secondChild != null) {
- ComponentConnector connector = connectorMap
- .getConnector(secondChild);
- if (connector.isRelativeWidth()) {
- layoutManager.reportWidthAssignedToRelative(connector,
- secondContainerWidth);
- } else {
- layoutManager.setNeedsMeasure(connector);
- }
- }
- break;
- case VERTICAL:
- wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
- pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
-
- // reposition splitter in case it is out of box
- if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
- || (positionReversed && pixelPosition < 0)) {
- pixelPosition = wholeSize - getSplitterSize();
- if (pixelPosition < 0) {
- pixelPosition = 0;
- }
- setSplitPosition(pixelPosition + "px");
- return;
- }
-
- DOM.setStyleAttribute(firstContainer, "height", pixelPosition
- + "px");
- int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
- if (secondContainerHeight < 0) {
- secondContainerHeight = 0;
- }
- DOM.setStyleAttribute(secondContainer, "height",
- secondContainerHeight + "px");
- DOM.setStyleAttribute(secondContainer, "top",
- (pixelPosition + getSplitterSize()) + "px");
-
- layoutManager = LayoutManager.get(client);
- connectorMap = ConnectorMap.get(client);
- if (firstChild != null) {
- ComponentConnector connector = connectorMap
- .getConnector(firstChild);
- if (connector.isRelativeHeight()) {
- layoutManager.reportHeightAssignedToRelative(connector,
- pixelPosition);
- } else {
- layoutManager.setNeedsMeasure(connector);
- }
- }
- if (secondChild != null) {
- ComponentConnector connector = connectorMap
- .getConnector(secondChild);
- if (connector.isRelativeHeight()) {
- layoutManager.reportHeightAssignedToRelative(connector,
- secondContainerHeight);
- } else {
- layoutManager.setNeedsMeasure(connector);
- }
- }
- break;
- }
- }
-
- void setFirstWidget(Widget w) {
- if (firstChild != null) {
- firstChild.removeFromParent();
- }
- if (w != null) {
- super.add(w, firstContainer);
- }
- firstChild = w;
- }
-
- void setSecondWidget(Widget w) {
- if (secondChild != null) {
- secondChild.removeFromParent();
- }
- if (w != null) {
- super.add(w, secondContainer);
- }
- secondChild = w;
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- switch (DOM.eventGetType(event)) {
- case Event.ONMOUSEMOVE:
- // case Event.ONTOUCHMOVE:
- if (resizing) {
- onMouseMove(event);
- }
- break;
- case Event.ONMOUSEDOWN:
- // case Event.ONTOUCHSTART:
- onMouseDown(event);
- break;
- case Event.ONMOUSEOUT:
- // Dragging curtain interferes with click events if added in
- // mousedown so we add it only when needed i.e., if the mouse moves
- // outside the splitter.
- if (resizing) {
- showDraggingCurtain();
- }
- break;
- case Event.ONMOUSEUP:
- // case Event.ONTOUCHEND:
- if (resizing) {
- onMouseUp(event);
- }
- break;
- case Event.ONCLICK:
- resizing = false;
- break;
- }
- // Only fire click event listeners if the splitter isn't moved
- if (Util.isTouchEvent(event) || !resized) {
- super.onBrowserEvent(event);
- } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
- // Reset the resized flag after a mouseup has occured so the next
- // mousedown/mouseup can be interpreted as a click.
- resized = false;
- }
- }
-
- public void onMouseDown(Event event) {
- if (locked || !isEnabled()) {
- return;
- }
- final Element trg = event.getEventTarget().cast();
- if (trg == splitter || trg == DOM.getChild(splitter, 0)) {
- resizing = true;
- DOM.setCapture(getElement());
- origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
- origY = DOM.getElementPropertyInt(splitter, "offsetTop");
- origMouseX = Util.getTouchOrMouseClientX(event);
- origMouseY = Util.getTouchOrMouseClientY(event);
- event.stopPropagation();
- event.preventDefault();
- }
- }
-
- public void onMouseMove(Event event) {
- switch (orientation) {
- case HORIZONTAL:
- final int x = Util.getTouchOrMouseClientX(event);
- onHorizontalMouseMove(x);
- break;
- case VERTICAL:
- default:
- final int y = Util.getTouchOrMouseClientY(event);
- onVerticalMouseMove(y);
- break;
- }
-
- }
-
- private void onHorizontalMouseMove(int x) {
- int newX = origX + x - origMouseX;
- if (newX < 0) {
- newX = 0;
- }
- if (newX + getSplitterSize() > getOffsetWidth()) {
- newX = getOffsetWidth() - getSplitterSize();
- }
-
- if (position.indexOf("%") > 0) {
- position = convertToPositionUnits(newX + "px");
- } else {
- // Reversed position
- if (positionReversed) {
- position = (getOffsetWidth() - newX - getSplitterSize()) + "px";
- } else {
- position = newX + "px";
- }
- }
-
- if (origX != newX) {
- resized = true;
- }
-
- // Reversed position
- if (positionReversed) {
- newX = getOffsetWidth() - newX - getSplitterSize();
- }
-
- setSplitPosition(newX + "px");
- }
-
- private void onVerticalMouseMove(int y) {
- int newY = origY + y - origMouseY;
- if (newY < 0) {
- newY = 0;
- }
-
- if (newY + getSplitterSize() > getOffsetHeight()) {
- newY = getOffsetHeight() - getSplitterSize();
- }
-
- if (position.indexOf("%") > 0) {
- position = convertToPositionUnits(newY + "px");
- } else {
- // Reversed position
- if (positionReversed) {
- position = (getOffsetHeight() - newY - getSplitterSize())
- + "px";
- } else {
- position = newY + "px";
- }
- }
-
- if (origY != newY) {
- resized = true;
- }
-
- // Reversed position
- if (positionReversed) {
- newY = getOffsetHeight() - newY - getSplitterSize();
- }
-
- setSplitPosition(newY + "px");
- }
-
- public void onMouseUp(Event event) {
- DOM.releaseCapture(getElement());
- hideDraggingCurtain();
- resizing = false;
- if (!Util.isTouchEvent(event)) {
- onMouseMove(event);
- }
- fireEvent(new SplitterMoveEvent(this));
- }
-
- public interface SplitterMoveHandler extends EventHandler {
- public void splitterMoved(SplitterMoveEvent event);
-
- public static class SplitterMoveEvent extends
- GwtEvent<SplitterMoveHandler> {
-
- public static final Type<SplitterMoveHandler> TYPE = new Type<SplitterMoveHandler>();
-
- private Widget splitPanel;
-
- public SplitterMoveEvent(Widget splitPanel) {
- this.splitPanel = splitPanel;
- }
-
- @Override
- public com.google.gwt.event.shared.GwtEvent.Type<SplitterMoveHandler> getAssociatedType() {
- return TYPE;
- }
-
- @Override
- protected void dispatch(SplitterMoveHandler handler) {
- handler.splitterMoved(this);
- }
-
- }
- }
-
- String getSplitterPosition() {
- return position;
- }
-
- /**
- * Used in FF to avoid losing mouse capture when pointer is moved on an
- * iframe.
- */
- private void showDraggingCurtain() {
- if (!isDraggingCurtainRequired()) {
- return;
- }
- if (draggingCurtain == null) {
- draggingCurtain = DOM.createDiv();
- DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
- DOM.setStyleAttribute(draggingCurtain, "top", "0px");
- DOM.setStyleAttribute(draggingCurtain, "left", "0px");
- DOM.setStyleAttribute(draggingCurtain, "width", "100%");
- DOM.setStyleAttribute(draggingCurtain, "height", "100%");
- DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
- + VOverlay.Z_INDEX);
-
- DOM.appendChild(wrapper, draggingCurtain);
- }
- }
-
- /**
- * A dragging curtain is required in Gecko and Webkit.
- *
- * @return true if the browser requires a dragging curtain
- */
- private boolean isDraggingCurtainRequired() {
- return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit());
- }
-
- /**
- * Hides dragging curtain
- */
- private void hideDraggingCurtain() {
- if (draggingCurtain != null) {
- DOM.removeChild(wrapper, draggingCurtain);
- draggingCurtain = null;
- }
- }
-
- private int splitterSize = -1;
-
- private int getSplitterSize() {
- if (splitterSize < 0) {
- if (isAttached()) {
- switch (orientation) {
- case HORIZONTAL:
- splitterSize = DOM.getElementPropertyInt(splitter,
- "offsetWidth");
- break;
-
- default:
- splitterSize = DOM.getElementPropertyInt(splitter,
- "offsetHeight");
- break;
- }
- }
- }
- return splitterSize;
- }
-
- void setStylenames() {
- final String splitterClass = CLASSNAME
- + (orientation == Orientation.HORIZONTAL ? "-hsplitter"
- : "-vsplitter");
- final String firstContainerClass = CLASSNAME + "-first-container";
- final String secondContainerClass = CLASSNAME + "-second-container";
- final String lockedSuffix = locked ? "-locked" : "";
-
- splitter.setClassName(splitterClass + lockedSuffix);
- firstContainer.setClassName(firstContainerClass);
- secondContainer.setClassName(secondContainerClass);
-
- for (String styleName : componentStyleNames) {
- splitter.addClassName(splitterClass + "-" + styleName
- + lockedSuffix);
- firstContainer.addClassName(firstContainerClass + "-" + styleName);
- secondContainer
- .addClassName(secondContainerClass + "-" + styleName);
- }
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- /**
- * Ensures the panels are scrollable eg. after style name changes
- */
- void makeScrollable() {
- if (touchScrollHandler == null) {
- touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
- }
- touchScrollHandler.addElement(firstContainer);
- touchScrollHandler.addElement(secondContainer);
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import com.vaadin.shared.ui.Orientation;
-
-public class VSplitPanelHorizontal extends VAbstractSplitPanel {
-
- public VSplitPanelHorizontal() {
- super(Orientation.HORIZONTAL);
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import com.vaadin.shared.ui.Orientation;
-
-public class VSplitPanelVertical extends VAbstractSplitPanel {
-
- public VSplitPanelVertical() {
- super(Orientation.VERTICAL);
- }
-}
*/
package com.vaadin.client.ui.splitpanel;
+import com.vaadin.client.ui.VSplitPanelVertical;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.splitpanel.VerticalSplitPanelState;
import com.vaadin.client.Util;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.PostLayoutListener;
-import com.vaadin.client.ui.table.VScrollTable.ContextMenuDetails;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.client.ui.VScrollTable;
+import com.vaadin.client.ui.VScrollTable.ContextMenuDetails;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.table.TableConstants;
import com.vaadin.shared.ui.table.TableState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.table;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.NodeList;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.dom.client.TableRowElement;
-import com.google.gwt.dom.client.TableSectionElement;
-import com.google.gwt.dom.client.Touch;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.event.dom.client.ScrollEvent;
-import com.google.gwt.event.dom.client.ScrollHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.VTooltip;
-import com.vaadin.client.ui.Action;
-import com.vaadin.client.ui.ActionOwner;
-import com.vaadin.client.ui.FocusableScrollPanel;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TreeAction;
-import com.vaadin.client.ui.dd.DDUtil;
-import com.vaadin.client.ui.dd.VAbstractDropHandler;
-import com.vaadin.client.ui.dd.VAcceptCallback;
-import com.vaadin.client.ui.dd.VDragAndDropManager;
-import com.vaadin.client.ui.dd.VDragEvent;
-import com.vaadin.client.ui.dd.VHasDropHandler;
-import com.vaadin.client.ui.dd.VTransferable;
-import com.vaadin.client.ui.embedded.VEmbedded;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.ui.dd.VerticalDropLocation;
-import com.vaadin.shared.ui.table.TableConstants;
-
-/**
- * VScrollTable
- *
- * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
- * ScrollPanel
- *
- * TableHead contains table's header and widgets + logic for resizing,
- * reordering and hiding columns.
- *
- * ScrollPanel contains VScrollTableBody object which handles content. To save
- * some bandwidth and to improve clients responsiveness with loads of data, in
- * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
- * VScrollTableBody to use the exact same space as non-rendered rows would use.
- * This way we can use seamlessly traditional scrollbars and scrolling to fetch
- * more rows instead of "paging".
- *
- * In VScrollTable we listen to scroll events. On horizontal scrolling we also
- * update TableHeads scroll position which has its scrollbars hidden. On
- * vertical scroll events we will check if we are reaching the end of area where
- * we have rows rendered and
- *
- * TODO implement unregistering for child components in Cells
- */
-public class VScrollTable extends FlowPanel implements HasWidgets,
- ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
- ActionOwner {
-
- public static final String STYLENAME = "v-table";
-
- public enum SelectMode {
- NONE(0), SINGLE(1), MULTI(2);
- private int id;
-
- private SelectMode(int id) {
- this.id = id;
- }
-
- public int getId() {
- return id;
- }
- }
-
- private static final String ROW_HEADER_COLUMN_KEY = "0";
-
- private static final double CACHE_RATE_DEFAULT = 2;
-
- /**
- * The default multi select mode where simple left clicks only selects one
- * item, CTRL+left click selects multiple items and SHIFT-left click selects
- * a range of items.
- */
- private static final int MULTISELECT_MODE_DEFAULT = 0;
-
- /**
- * The simple multiselect mode is what the table used to have before
- * ctrl/shift selections were added. That is that when this is set clicking
- * on an item selects/deselects the item and no ctrl/shift selections are
- * available.
- */
- private static final int MULTISELECT_MODE_SIMPLE = 1;
-
- /**
- * multiple of pagelength which component will cache when requesting more
- * rows
- */
- private double cache_rate = CACHE_RATE_DEFAULT;
- /**
- * fraction of pageLenght which can be scrolled without making new request
- */
- private double cache_react_rate = 0.75 * cache_rate;
-
- public static final char ALIGN_CENTER = 'c';
- public static final char ALIGN_LEFT = 'b';
- public static final char ALIGN_RIGHT = 'e';
- private static final int CHARCODE_SPACE = 32;
- private int firstRowInViewPort = 0;
- private int pageLength = 15;
- private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
-
- protected boolean showRowHeaders = false;
-
- private String[] columnOrder;
-
- protected ApplicationConnection client;
- protected String paintableId;
-
- boolean immediate;
- private boolean nullSelectionAllowed = true;
-
- private SelectMode selectMode = SelectMode.NONE;
-
- private final HashSet<String> selectedRowKeys = new HashSet<String>();
-
- /*
- * When scrolling and selecting at the same time, the selections are not in
- * sync with the server while retrieving new rows (until key is released).
- */
- private HashSet<Object> unSyncedselectionsBeforeRowFetch;
-
- /*
- * These are used when jumping between pages when pressing Home and End
- */
- boolean selectLastItemInNextRender = false;
- boolean selectFirstItemInNextRender = false;
- boolean focusFirstItemInNextRender = false;
- boolean focusLastItemInNextRender = false;
-
- /*
- * The currently focused row
- */
- VScrollTableRow focusedRow;
-
- /*
- * Helper to store selection range start in when using the keyboard
- */
- VScrollTableRow selectionRangeStart;
-
- /*
- * Flag for notifying when the selection has changed and should be sent to
- * the server
- */
- boolean selectionChanged = false;
-
- /*
- * The speed (in pixels) which the scrolling scrolls vertically/horizontally
- */
- private int scrollingVelocity = 10;
-
- private Timer scrollingVelocityTimer = null;
-
- String[] bodyActionKeys;
-
- private boolean enableDebug = false;
-
- private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
- .isTouchDevice()
- && !BrowserInfo.get().requiresTouchScrollDelegate();
-
- private Set<String> noncollapsibleColumns;
-
- /**
- * The last known row height used to preserve the height of a table with
- * custom row heights and a fixed page length after removing the last row
- * from the table.
- *
- * A new VScrollTableBody instance is created every time the number of rows
- * changes causing {@link VScrollTableBody#rowHeight} to be discarded and
- * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)}
- * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
- * round(3 * 19.8) / 3 = 19.66.
- */
- private double lastKnownRowHeight = Double.NaN;
-
- /**
- * Represents a select range of rows
- */
- private class SelectionRange {
- private VScrollTableRow startRow;
- private final int length;
-
- /**
- * Constuctor.
- */
- public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
- VScrollTableRow endRow;
- if (row2.isBefore(row1)) {
- startRow = row2;
- endRow = row1;
- } else {
- startRow = row1;
- endRow = row2;
- }
- length = endRow.getIndex() - startRow.getIndex() + 1;
- }
-
- public SelectionRange(VScrollTableRow row, int length) {
- startRow = row;
- this.length = length;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see java.lang.Object#toString()
- */
-
- @Override
- public String toString() {
- return startRow.getKey() + "-" + length;
- }
-
- private boolean inRange(VScrollTableRow row) {
- return row.getIndex() >= startRow.getIndex()
- && row.getIndex() < startRow.getIndex() + length;
- }
-
- public Collection<SelectionRange> split(VScrollTableRow row) {
- assert row.isAttached();
- ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
-
- int endOfFirstRange = row.getIndex() - 1;
- if (!(endOfFirstRange - startRow.getIndex() < 0)) {
- // create range of first part unless its length is < 1
- ranges.add(new SelectionRange(startRow, endOfFirstRange
- - startRow.getIndex() + 1));
- }
- int startOfSecondRange = row.getIndex() + 1;
- if (!(getEndIndex() - startOfSecondRange < 0)) {
- // create range of second part unless its length is < 1
- VScrollTableRow startOfRange = scrollBody
- .getRowByRowIndex(startOfSecondRange);
- ranges.add(new SelectionRange(startOfRange, getEndIndex()
- - startOfSecondRange + 1));
- }
- return ranges;
- }
-
- private int getEndIndex() {
- return startRow.getIndex() + length - 1;
- }
-
- };
-
- private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
-
- boolean initializedAndAttached = false;
-
- /**
- * Flag to indicate if a column width recalculation is needed due update.
- */
- boolean headerChangedDuringUpdate = false;
-
- protected final TableHead tHead = new TableHead();
-
- final TableFooter tFoot = new TableFooter();
-
- final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true);
-
- private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
-
- @Override
- public void onKeyPress(KeyPressEvent keyPressEvent) {
- // This is used for Firefox only, since Firefox auto-repeat
- // works correctly only if we use a key press handler, other
- // browsers handle it correctly when using a key down handler
- if (!BrowserInfo.get().isGecko()) {
- return;
- }
-
- NativeEvent event = keyPressEvent.getNativeEvent();
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table
- // (prevents scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- // Key code in Firefox/onKeyPress is present only for
- // special keys, otherwise 0 is returned
- int keyCode = event.getKeyCode();
- if (keyCode == 0 && event.getCharCode() == ' ') {
- // Provide a keyCode for space to be compatible with
- // FireFox keypress event
- keyCode = CHARCODE_SPACE;
- }
-
- if (handleNavigation(keyCode,
- event.getCtrlKey() || event.getMetaKey(),
- event.getShiftKey())) {
- event.preventDefault();
- }
-
- startScrollingVelocityTimer();
- }
- }
-
- };
-
- private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
-
- @Override
- public void onKeyUp(KeyUpEvent keyUpEvent) {
- NativeEvent event = keyUpEvent.getNativeEvent();
- int keyCode = event.getKeyCode();
-
- if (!isFocusable()) {
- cancelScrollingVelocityTimer();
- } else if (isNavigationKey(keyCode)) {
- if (keyCode == getNavigationDownKey()
- || keyCode == getNavigationUpKey()) {
- /*
- * in multiselect mode the server may still have value from
- * previous page. Clear it unless doing multiselection or
- * just moving focus.
- */
- if (!event.getShiftKey() && !event.getCtrlKey()) {
- instructServerToForgetPreviousSelections();
- }
- sendSelectedRows();
- }
- cancelScrollingVelocityTimer();
- navKeyDown = false;
- }
- }
- };
-
- private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
-
- @Override
- public void onKeyDown(KeyDownEvent keyDownEvent) {
- NativeEvent event = keyDownEvent.getNativeEvent();
- // This is not used for Firefox
- if (BrowserInfo.get().isGecko()) {
- return;
- }
-
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table
- // (prevents scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
- || event.getMetaKey(), event.getShiftKey())) {
- navKeyDown = true;
- event.preventDefault();
- }
-
- startScrollingVelocityTimer();
- }
- }
- };
- int totalRows;
-
- private Set<String> collapsedColumns;
-
- final RowRequestHandler rowRequestHandler;
- VScrollTableBody scrollBody;
- private int firstvisible = 0;
- private boolean sortAscending;
- private String sortColumn;
- private String oldSortColumn;
- private boolean columnReordering;
-
- /**
- * This map contains captions and icon urls for actions like: * "33_c" ->
- * "Edit" * "33_i" -> "http://dom.com/edit.png"
- */
- private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
- private String[] visibleColOrder;
- private boolean initialContentReceived = false;
- private Element scrollPositionElement;
- boolean enabled;
- boolean showColHeaders;
- boolean showColFooters;
-
- /** flag to indicate that table body has changed */
- private boolean isNewBody = true;
-
- /*
- * Read from the "recalcWidths" -attribute. When it is true, the table will
- * recalculate the widths for columns - desirable in some cases. For #1983,
- * marked experimental.
- */
- boolean recalcWidths = false;
-
- boolean rendering = false;
- private boolean hasFocus = false;
- private int dragmode;
-
- private int multiselectmode;
- int tabIndex;
- private TouchScrollDelegate touchScrollDelegate;
-
- int lastRenderedHeight;
-
- /**
- * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
- * rows (indexes) are in the server side cache (page buffer). -1 means
- * unknown. The server side cache row MUST MATCH the client side cache rows.
- *
- * If the client side cache contains additional rows with e.g. buttons, it
- * will cause out of sync when such a button is pressed.
- *
- * If the server side cache contains additional rows with e.g. buttons,
- * scrolling in the client will cause empty buttons to be rendered
- * (cached=true request for non-existing components)
- */
- int serverCacheFirst = -1;
- int serverCacheLast = -1;
-
- boolean sizeNeedsInit = true;
-
- /**
- * Used to recall the position of an open context menu if we need to close
- * and reopen it during a row update.
- */
- class ContextMenuDetails {
- String rowKey;
- int left;
- int top;
-
- ContextMenuDetails(String rowKey, int left, int top) {
- this.rowKey = rowKey;
- this.left = left;
- this.top = top;
- }
- }
-
- protected ContextMenuDetails contextMenu = null;
-
- public VScrollTable() {
- setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
-
- scrollBodyPanel.addFocusHandler(this);
- scrollBodyPanel.addBlurHandler(this);
-
- scrollBodyPanel.addScrollHandler(this);
-
- /*
- * Firefox auto-repeat works correctly only if we use a key press
- * handler, other browsers handle it correctly when using a key down
- * handler
- */
- if (BrowserInfo.get().isGecko()) {
- scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
- } else {
- scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
- }
- scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
-
- scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
-
- scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU);
- scrollBodyPanel.addDomHandler(new ContextMenuHandler() {
-
- @Override
- public void onContextMenu(ContextMenuEvent event) {
- handleBodyContextMenu(event);
- }
- }, ContextMenuEvent.getType());
-
- setStyleName(STYLENAME);
-
- add(tHead);
- add(scrollBodyPanel);
- add(tFoot);
-
- rowRequestHandler = new RowRequestHandler();
- }
-
- @Override
- public void setStyleName(String style) {
- updateStyleNames(style, false);
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- updateStyleNames(style, true);
- }
-
- private void updateStyleNames(String newStyle, boolean isPrimary) {
- scrollBodyPanel
- .removeStyleName(getStylePrimaryName() + "-body-wrapper");
- scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
-
- if (scrollBody != null) {
- scrollBody.removeStyleName(getStylePrimaryName()
- + "-body-noselection");
- }
-
- if (isPrimary) {
- super.setStylePrimaryName(newStyle);
- } else {
- super.setStyleName(newStyle);
- }
-
- scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
- scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
-
- tHead.updateStyleNames(getStylePrimaryName());
- tFoot.updateStyleNames(getStylePrimaryName());
-
- if (scrollBody != null) {
- scrollBody.updateStyleNames(getStylePrimaryName());
- }
- }
-
- public void init(ApplicationConnection client) {
- this.client = client;
- // Add a handler to clear saved context menu details when the menu
- // closes. See #8526.
- client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() {
-
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- contextMenu = null;
- }
- });
- }
-
- private void handleBodyContextMenu(ContextMenuEvent event) {
- if (enabled && bodyActionKeys != null) {
- int left = Util.getTouchOrMouseClientX(event.getNativeEvent());
- int top = Util.getTouchOrMouseClientY(event.getNativeEvent());
- top += Window.getScrollTop();
- left += Window.getScrollLeft();
- client.getContextMenu().showAt(this, left, top);
-
- // Only prevent browser context menu if there are action handlers
- // registered
- event.stopPropagation();
- event.preventDefault();
- }
- }
-
- /**
- * Fires a column resize event which sends the resize information to the
- * server.
- *
- * @param columnId
- * The columnId of the column which was resized
- * @param originalWidth
- * The width in pixels of the column before the resize event
- * @param newWidth
- * The width in pixels of the column after the resize event
- */
- private void fireColumnResizeEvent(String columnId, int originalWidth,
- int newWidth) {
- client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
- false);
- client.updateVariable(paintableId, "columnResizeEventPrev",
- originalWidth, false);
- client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
- immediate);
-
- }
-
- /**
- * Non-immediate variable update of column widths for a collection of
- * columns.
- *
- * @param columns
- * the columns to trigger the events for.
- */
- private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
- String[] newSizes = new String[columns.size()];
- int ix = 0;
- for (HeaderCell cell : columns) {
- newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
- }
- client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
- false);
- }
-
- /**
- * Moves the focus one step down
- *
- * @return Returns true if succeeded
- */
- private boolean moveFocusDown() {
- return moveFocusDown(0);
- }
-
- /**
- * Moves the focus down by 1+offset rows
- *
- * @return Returns true if succeeded, else false if the selection could not
- * be move downwards
- */
- private boolean moveFocusDown(int offset) {
- if (isSelectable()) {
- if (focusedRow == null && scrollBody.iterator().hasNext()) {
- // FIXME should focus first visible from top, not first rendered
- // ??
- return setRowFocus((VScrollTableRow) scrollBody.iterator()
- .next());
- } else {
- VScrollTableRow next = getNextRow(focusedRow, offset);
- if (next != null) {
- return setRowFocus(next);
- }
- }
- }
-
- return false;
- }
-
- /**
- * Moves the selection one step up
- *
- * @return Returns true if succeeded
- */
- private boolean moveFocusUp() {
- return moveFocusUp(0);
- }
-
- /**
- * Moves the focus row upwards
- *
- * @return Returns true if succeeded, else false if the selection could not
- * be move upwards
- *
- */
- private boolean moveFocusUp(int offset) {
- if (isSelectable()) {
- if (focusedRow == null && scrollBody.iterator().hasNext()) {
- // FIXME logic is exactly the same as in moveFocusDown, should
- // be the opposite??
- return setRowFocus((VScrollTableRow) scrollBody.iterator()
- .next());
- } else {
- VScrollTableRow prev = getPreviousRow(focusedRow, offset);
- if (prev != null) {
- return setRowFocus(prev);
- } else {
- VConsole.log("no previous available");
- }
- }
- }
-
- return false;
- }
-
- /**
- * Selects a row where the current selection head is
- *
- * @param ctrlSelect
- * Is the selection a ctrl+selection
- * @param shiftSelect
- * Is the selection a shift+selection
- * @return Returns truw
- */
- private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
- if (focusedRow != null) {
- // Arrows moves the selection and clears previous selections
- if (isSelectable() && !ctrlSelect && !shiftSelect) {
- deselectAll();
- focusedRow.toggleSelection();
- selectionRangeStart = focusedRow;
- } else if (isSelectable() && ctrlSelect && !shiftSelect) {
- // Ctrl+arrows moves selection head
- selectionRangeStart = focusedRow;
- // No selection, only selection head is moved
- } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
- // Shift+arrows selection selects a range
- focusedRow.toggleShiftSelection(shiftSelect);
- }
- }
- }
-
- /**
- * Sends the selection to the server if changed since the last update/visit.
- */
- protected void sendSelectedRows() {
- sendSelectedRows(immediate);
- }
-
- /**
- * Sends the selection to the server if it has been changed since the last
- * update/visit.
- *
- * @param immediately
- * set to true to immediately send the rows
- */
- protected void sendSelectedRows(boolean immediately) {
- // Don't send anything if selection has not changed
- if (!selectionChanged) {
- return;
- }
-
- // Reset selection changed flag
- selectionChanged = false;
-
- // Note: changing the immediateness of this might require changes to
- // "clickEvent" immediateness also.
- if (isMultiSelectModeDefault()) {
- // Convert ranges to a set of strings
- Set<String> ranges = new HashSet<String>();
- for (SelectionRange range : selectedRowRanges) {
- ranges.add(range.toString());
- }
-
- // Send the selected row ranges
- client.updateVariable(paintableId, "selectedRanges",
- ranges.toArray(new String[selectedRowRanges.size()]), false);
-
- // clean selectedRowKeys so that they don't contain excess values
- for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
- .hasNext();) {
- String key = iterator.next();
- VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
- if (renderedRowByKey != null) {
- for (SelectionRange range : selectedRowRanges) {
- if (range.inRange(renderedRowByKey)) {
- iterator.remove();
- }
- }
- } else {
- // orphaned selected key, must be in a range, ignore
- iterator.remove();
- }
-
- }
- }
-
- // Send the selected rows
- client.updateVariable(paintableId, "selected",
- selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
- immediately);
-
- }
-
- /**
- * Get the key that moves the selection head upwards. By default it is the
- * up arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationUpKey() {
- return KeyCodes.KEY_UP;
- }
-
- /**
- * Get the key that moves the selection head downwards. By default it is the
- * down arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationDownKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * Get the key that scrolls to the left in the table. By default it is the
- * left arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationLeftKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * Get the key that scroll to the right on the table. By default it is the
- * right arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationRightKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- /**
- * Get the key that selects an item in the table. By default it is the space
- * bar key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return
- */
- protected int getNavigationSelectKey() {
- return CHARCODE_SPACE;
- }
-
- /**
- * Get the key the moves the selection one page up in the table. By default
- * this is the Page Up key but by overriding this you can change the key to
- * whatever you want.
- *
- * @return
- */
- protected int getNavigationPageUpKey() {
- return KeyCodes.KEY_PAGEUP;
- }
-
- /**
- * Get the key the moves the selection one page down in the table. By
- * default this is the Page Down key but by overriding this you can change
- * the key to whatever you want.
- *
- * @return
- */
- protected int getNavigationPageDownKey() {
- return KeyCodes.KEY_PAGEDOWN;
- }
-
- /**
- * Get the key the moves the selection to the beginning of the table. By
- * default this is the Home key but by overriding this you can change the
- * key to whatever you want.
- *
- * @return
- */
- protected int getNavigationStartKey() {
- return KeyCodes.KEY_HOME;
- }
-
- /**
- * Get the key the moves the selection to the end of the table. By default
- * this is the End key but by overriding this you can change the key to
- * whatever you want.
- *
- * @return
- */
- protected int getNavigationEndKey() {
- return KeyCodes.KEY_END;
- }
-
- void initializeRows(UIDL uidl, UIDL rowData) {
- if (scrollBody != null) {
- scrollBody.removeFromParent();
- }
- scrollBody = createScrollBody();
-
- scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
- uidl.getIntAttribute("rows"));
- scrollBodyPanel.add(scrollBody);
-
- // New body starts scrolled to the left, make sure the header and footer
- // are also scrolled to the left
- tHead.setHorizontalScrollPosition(0);
- tFoot.setHorizontalScrollPosition(0);
-
- initialContentReceived = true;
- sizeNeedsInit = true;
- scrollBody.restoreRowVisibility();
- }
-
- void updateColumnProperties(UIDL uidl) {
- updateColumnOrder(uidl);
-
- updateCollapsedColumns(uidl);
-
- UIDL vc = uidl.getChildByTagName("visiblecolumns");
- if (vc != null) {
- tHead.updateCellsFromUIDL(vc);
- tFoot.updateCellsFromUIDL(vc);
- }
-
- updateHeader(uidl.getStringArrayAttribute("vcolorder"));
- updateFooter(uidl.getStringArrayAttribute("vcolorder"));
- if (uidl.hasVariable("noncollapsiblecolumns")) {
- noncollapsibleColumns = uidl
- .getStringArrayVariableAsSet("noncollapsiblecolumns");
- }
- }
-
- private void updateCollapsedColumns(UIDL uidl) {
- if (uidl.hasVariable("collapsedcolumns")) {
- tHead.setColumnCollapsingAllowed(true);
- collapsedColumns = uidl
- .getStringArrayVariableAsSet("collapsedcolumns");
- } else {
- tHead.setColumnCollapsingAllowed(false);
- }
- }
-
- private void updateColumnOrder(UIDL uidl) {
- if (uidl.hasVariable("columnorder")) {
- columnReordering = true;
- columnOrder = uidl.getStringArrayVariable("columnorder");
- } else {
- columnReordering = false;
- columnOrder = null;
- }
- }
-
- boolean selectSelectedRows(UIDL uidl) {
- boolean keyboardSelectionOverRowFetchInProgress = false;
-
- if (uidl.hasVariable("selected")) {
- final Set<String> selectedKeys = uidl
- .getStringArrayVariableAsSet("selected");
- if (scrollBody != null) {
- Iterator<Widget> iterator = scrollBody.iterator();
- while (iterator.hasNext()) {
- /*
- * Make the focus reflect to the server side state unless we
- * are currently selecting multiple rows with keyboard.
- */
- VScrollTableRow row = (VScrollTableRow) iterator.next();
- boolean selected = selectedKeys.contains(row.getKey());
- if (!selected
- && unSyncedselectionsBeforeRowFetch != null
- && unSyncedselectionsBeforeRowFetch.contains(row
- .getKey())) {
- selected = true;
- keyboardSelectionOverRowFetchInProgress = true;
- }
- if (selected != row.isSelected()) {
- row.toggleSelection();
- if (!isSingleSelectMode() && !selected) {
- // Update selection range in case a row is
- // unselected from the middle of a range - #8076
- removeRowFromUnsentSelectionRanges(row);
- }
- }
- }
- }
- }
- unSyncedselectionsBeforeRowFetch = null;
- return keyboardSelectionOverRowFetchInProgress;
- }
-
- void updateSortingProperties(UIDL uidl) {
- oldSortColumn = sortColumn;
- if (uidl.hasVariable("sortascending")) {
- sortAscending = uidl.getBooleanVariable("sortascending");
- sortColumn = uidl.getStringVariable("sortcolumn");
- }
- }
-
- void resizeSortedColumnForSortIndicator() {
- // Force recalculation of the captionContainer element inside the header
- // cell to accomodate for the size of the sort arrow.
- HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
- if (sortedHeader != null) {
- tHead.resizeCaptionContainer(sortedHeader);
- }
- // Also recalculate the width of the captionContainer element in the
- // previously sorted header, since this now has more room.
- HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
- if (oldSortedHeader != null) {
- tHead.resizeCaptionContainer(oldSortedHeader);
- }
- }
-
- void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
- firstvisible = uidl.hasVariable("firstvisible") ? uidl
- .getIntVariable("firstvisible") : 0;
- if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
- // received 'surprising' firstvisible from server: scroll there
- firstRowInViewPort = firstvisible;
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstvisible));
- }
- }
-
- protected int measureRowHeightOffset(int rowIx) {
- return (int) (rowIx * scrollBody.getRowHeight());
- }
-
- void updatePageLength(UIDL uidl) {
- int oldPageLength = pageLength;
- if (uidl.hasAttribute("pagelength")) {
- pageLength = uidl.getIntAttribute("pagelength");
- } else {
- // pagelenght is "0" meaning scrolling is turned off
- pageLength = totalRows;
- }
-
- if (oldPageLength != pageLength && initializedAndAttached) {
- // page length changed, need to update size
- sizeNeedsInit = true;
- }
- }
-
- void updateSelectionProperties(UIDL uidl, ComponentState state,
- boolean readOnly) {
- setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl
- .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT);
-
- nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
- .getBooleanAttribute("nsa") : true;
-
- if (uidl.hasAttribute("selectmode")) {
- if (readOnly) {
- selectMode = SelectMode.NONE;
- } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
- selectMode = SelectMode.MULTI;
- } else if (uidl.getStringAttribute("selectmode").equals("single")) {
- selectMode = SelectMode.SINGLE;
- } else {
- selectMode = SelectMode.NONE;
- }
- }
- }
-
- void updateDragMode(UIDL uidl) {
- dragmode = uidl.hasAttribute("dragmode") ? uidl
- .getIntAttribute("dragmode") : 0;
- if (BrowserInfo.get().isIE()) {
- if (dragmode > 0) {
- getElement().setPropertyJSO("onselectstart",
- getPreventTextSelectionIEHack());
- } else {
- getElement().setPropertyJSO("onselectstart", null);
- }
- }
- }
-
- protected void updateTotalRows(UIDL uidl) {
- int newTotalRows = uidl.getIntAttribute("totalrows");
- if (newTotalRows != getTotalRows()) {
- if (scrollBody != null) {
- if (getTotalRows() == 0) {
- tHead.clear();
- tFoot.clear();
- }
- initializedAndAttached = false;
- initialContentReceived = false;
- isNewBody = true;
- }
- setTotalRows(newTotalRows);
- }
- }
-
- protected void setTotalRows(int newTotalRows) {
- totalRows = newTotalRows;
- }
-
- public int getTotalRows() {
- return totalRows;
- }
-
- void focusRowFromBody() {
- if (selectedRowKeys.size() == 1) {
- // try to focus a row currently selected and in viewport
- String selectedRowKey = selectedRowKeys.iterator().next();
- if (selectedRowKey != null) {
- VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
- if (renderedRow == null || !renderedRow.isInViewPort()) {
- setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
- } else {
- setRowFocus(renderedRow);
- }
- }
- } else {
- // multiselect mode
- setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
- }
- }
-
- protected VScrollTableBody createScrollBody() {
- return new VScrollTableBody();
- }
-
- /**
- * Selects the last row visible in the table
- *
- * @param focusOnly
- * Should the focus only be moved to the last row
- */
- void selectLastRenderedRowInViewPort(boolean focusOnly) {
- int index = firstRowInViewPort + getFullyVisibleRowCount();
- VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
- if (lastRowInViewport == null) {
- // this should not happen in normal situations (white space at the
- // end of viewport). Select the last rendered as a fallback.
- lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody
- .getLastRendered());
- if (lastRowInViewport == null) {
- return; // empty table
- }
- }
- setRowFocus(lastRowInViewport);
- if (!focusOnly) {
- selectFocusedRow(false, multiselectPending);
- sendSelectedRows();
- }
- }
-
- /**
- * Selects the first row visible in the table
- *
- * @param focusOnly
- * Should the focus only be moved to the first row
- */
- void selectFirstRenderedRowInViewPort(boolean focusOnly) {
- int index = firstRowInViewPort;
- VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
- if (firstInViewport == null) {
- // this should not happen in normal situations
- return;
- }
- setRowFocus(firstInViewport);
- if (!focusOnly) {
- selectFocusedRow(false, multiselectPending);
- sendSelectedRows();
- }
- }
-
- void setCacheRateFromUIDL(UIDL uidl) {
- setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
- : CACHE_RATE_DEFAULT);
- }
-
- private void setCacheRate(double d) {
- if (cache_rate != d) {
- cache_rate = d;
- cache_react_rate = 0.75 * d;
- }
- }
-
- void updateActionMap(UIDL mainUidl) {
- UIDL actionsUidl = mainUidl.getChildByTagName("actions");
- if (actionsUidl == null) {
- return;
- }
-
- final Iterator<?> it = actionsUidl.getChildIterator();
- while (it.hasNext()) {
- final UIDL action = (UIDL) it.next();
- final String key = action.getStringAttribute("key");
- final String caption = action.getStringAttribute("caption");
- actionMap.put(key + "_c", caption);
- if (action.hasAttribute("icon")) {
- // TODO need some uri handling ??
- actionMap.put(key + "_i", client.translateVaadinUri(action
- .getStringAttribute("icon")));
- } else {
- actionMap.remove(key + "_i");
- }
- }
-
- }
-
- public String getActionCaption(String actionKey) {
- return actionMap.get(actionKey + "_c");
- }
-
- public String getActionIcon(String actionKey) {
- return actionMap.get(actionKey + "_i");
- }
-
- private void updateHeader(String[] strings) {
- if (strings == null) {
- return;
- }
-
- int visibleCols = strings.length;
- int colIndex = 0;
- if (showRowHeaders) {
- tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
- visibleCols++;
- visibleColOrder = new String[visibleCols];
- visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
- colIndex++;
- } else {
- visibleColOrder = new String[visibleCols];
- tHead.removeCell(ROW_HEADER_COLUMN_KEY);
- }
-
- int i;
- for (i = 0; i < strings.length; i++) {
- final String cid = strings[i];
- visibleColOrder[colIndex] = cid;
- tHead.enableColumn(cid, colIndex);
- colIndex++;
- }
-
- tHead.setVisible(showColHeaders);
- setContainerHeight();
-
- }
-
- /**
- * Updates footers.
- * <p>
- * Update headers whould be called before this method is called!
- * </p>
- *
- * @param strings
- */
- private void updateFooter(String[] strings) {
- if (strings == null) {
- return;
- }
-
- // Add dummy column if row headers are present
- int colIndex = 0;
- if (showRowHeaders) {
- tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
- colIndex++;
- } else {
- tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
- }
-
- int i;
- for (i = 0; i < strings.length; i++) {
- final String cid = strings[i];
- tFoot.enableColumn(cid, colIndex);
- colIndex++;
- }
-
- tFoot.setVisible(showColFooters);
- }
-
- /**
- * @param uidl
- * which contains row data
- * @param firstRow
- * first row in data set
- * @param reqRows
- * amount of rows in data set
- */
- void updateBody(UIDL uidl, int firstRow, int reqRows) {
- if (uidl == null || reqRows < 1) {
- // container is empty, remove possibly existing rows
- if (firstRow <= 0) {
- while (scrollBody.getLastRendered() > scrollBody.firstRendered) {
- scrollBody.unlinkRow(false);
- }
- scrollBody.unlinkRow(false);
- }
- return;
- }
-
- scrollBody.renderRows(uidl, firstRow, reqRows);
-
- discardRowsOutsideCacheWindow();
- }
-
- void updateRowsInBody(UIDL partialRowUpdates) {
- if (partialRowUpdates == null) {
- return;
- }
- int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
- int count = partialRowUpdates.getIntAttribute("numurows");
- scrollBody.unlinkRows(firstRowIx, count);
- scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
- }
-
- /**
- * Updates the internal cache by unlinking rows that fall outside of the
- * caching window.
- */
- protected void discardRowsOutsideCacheWindow() {
- int firstRowToKeep = (int) (firstRowInViewPort - pageLength
- * cache_rate);
- int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
- * cache_rate);
- debug("Client side calculated cache rows to keep: " + firstRowToKeep
- + "-" + lastRowToKeep);
-
- if (serverCacheFirst != -1) {
- firstRowToKeep = serverCacheFirst;
- lastRowToKeep = serverCacheLast;
- debug("Server cache rows that override: " + serverCacheFirst + "-"
- + serverCacheLast);
- if (firstRowToKeep < scrollBody.getFirstRendered()
- || lastRowToKeep > scrollBody.getLastRendered()) {
- debug("*** Server wants us to keep " + serverCacheFirst + "-"
- + serverCacheLast + " but we only have rows "
- + scrollBody.getFirstRendered() + "-"
- + scrollBody.getLastRendered() + " rendered!");
- }
- }
- discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
-
- scrollBody.fixSpacers();
-
- scrollBody.restoreRowVisibility();
- }
-
- private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
- /*
- * firstDiscarded and lastDiscarded are only calculated for debug
- * purposes
- */
- int firstDiscarded = -1, lastDiscarded = -1;
- boolean cont = true;
- while (cont && scrollBody.getLastRendered() > optimalFirstRow
- && scrollBody.getFirstRendered() < optimalFirstRow) {
- if (firstDiscarded == -1) {
- firstDiscarded = scrollBody.getFirstRendered();
- }
-
- // removing row from start
- cont = scrollBody.unlinkRow(true);
- }
- if (firstDiscarded != -1) {
- lastDiscarded = scrollBody.getFirstRendered() - 1;
- debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
- }
- firstDiscarded = lastDiscarded = -1;
-
- cont = true;
- while (cont && scrollBody.getLastRendered() > optimalLastRow) {
- if (lastDiscarded == -1) {
- lastDiscarded = scrollBody.getLastRendered();
- }
-
- // removing row from the end
- cont = scrollBody.unlinkRow(false);
- }
- if (lastDiscarded != -1) {
- firstDiscarded = scrollBody.getLastRendered() + 1;
- debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
- }
-
- debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
- + scrollBody.getLastRendered());
- }
-
- /**
- * Inserts rows in the table body or removes them from the table body based
- * on the commands in the UIDL.
- *
- * @param partialRowAdditions
- * the UIDL containing row updates.
- */
- protected void addAndRemoveRows(UIDL partialRowAdditions) {
- if (partialRowAdditions == null) {
- return;
- }
- if (partialRowAdditions.hasAttribute("hide")) {
- scrollBody.unlinkAndReindexRows(
- partialRowAdditions.getIntAttribute("firstprowix"),
- partialRowAdditions.getIntAttribute("numprows"));
- scrollBody.ensureCacheFilled();
- } else {
- if (partialRowAdditions.hasAttribute("delbelow")) {
- scrollBody.insertRowsDeleteBelow(partialRowAdditions,
- partialRowAdditions.getIntAttribute("firstprowix"),
- partialRowAdditions.getIntAttribute("numprows"));
- } else {
- scrollBody.insertAndReindexRows(partialRowAdditions,
- partialRowAdditions.getIntAttribute("firstprowix"),
- partialRowAdditions.getIntAttribute("numprows"));
- }
- }
-
- discardRowsOutsideCacheWindow();
- }
-
- /**
- * Gives correct column index for given column key ("cid" in UIDL).
- *
- * @param colKey
- * @return column index of visible columns, -1 if column not visible
- */
- private int getColIndexByKey(String colKey) {
- // return 0 if asked for rowHeaders
- if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
- return 0;
- }
- for (int i = 0; i < visibleColOrder.length; i++) {
- if (visibleColOrder[i].equals(colKey)) {
- return i;
- }
- }
- return -1;
- }
-
- private boolean isMultiSelectModeSimple() {
- return selectMode == SelectMode.MULTI
- && multiselectmode == MULTISELECT_MODE_SIMPLE;
- }
-
- private boolean isSingleSelectMode() {
- return selectMode == SelectMode.SINGLE;
- }
-
- private boolean isMultiSelectModeAny() {
- return selectMode == SelectMode.MULTI;
- }
-
- private boolean isMultiSelectModeDefault() {
- return selectMode == SelectMode.MULTI
- && multiselectmode == MULTISELECT_MODE_DEFAULT;
- }
-
- private void setMultiSelectMode(int multiselectmode) {
- if (BrowserInfo.get().isTouchDevice()) {
- // Always use the simple mode for touch devices that do not have
- // shift/ctrl keys
- this.multiselectmode = MULTISELECT_MODE_SIMPLE;
- } else {
- this.multiselectmode = multiselectmode;
- }
-
- }
-
- protected boolean isSelectable() {
- return selectMode.getId() > SelectMode.NONE.getId();
- }
-
- private boolean isCollapsedColumn(String colKey) {
- if (collapsedColumns == null) {
- return false;
- }
- if (collapsedColumns.contains(colKey)) {
- return true;
- }
- return false;
- }
-
- private String getColKeyByIndex(int index) {
- return tHead.getHeaderCell(index).getColKey();
- }
-
- private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
- final HeaderCell hcell = tHead.getHeaderCell(colIndex);
-
- // Make sure that the column grows to accommodate the sort indicator if
- // necessary.
- if (w < hcell.getMinWidth()) {
- w = hcell.getMinWidth();
- }
-
- // Set header column width
- hcell.setWidth(w, isDefinedWidth);
-
- // Ensure indicators have been taken into account
- tHead.resizeCaptionContainer(hcell);
-
- // Set body column width
- scrollBody.setColWidth(colIndex, w);
-
- // Set footer column width
- FooterCell fcell = tFoot.getFooterCell(colIndex);
- fcell.setWidth(w, isDefinedWidth);
- }
-
- private int getColWidth(String colKey) {
- return tHead.getHeaderCell(colKey).getWidth();
- }
-
- /**
- * Get a rendered row by its key
- *
- * @param key
- * The key to search with
- * @return
- */
- public VScrollTableRow getRenderedRowByKey(String key) {
- if (scrollBody != null) {
- final Iterator<Widget> it = scrollBody.iterator();
- VScrollTableRow r = null;
- while (it.hasNext()) {
- r = (VScrollTableRow) it.next();
- if (r.getKey().equals(key)) {
- return r;
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the next row to the given row
- *
- * @param row
- * The row to calculate from
- *
- * @return The next row or null if no row exists
- */
- private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
- final Iterator<Widget> it = scrollBody.iterator();
- VScrollTableRow r = null;
- while (it.hasNext()) {
- r = (VScrollTableRow) it.next();
- if (r == row) {
- r = null;
- while (offset >= 0 && it.hasNext()) {
- r = (VScrollTableRow) it.next();
- offset--;
- }
- return r;
- }
- }
-
- return null;
- }
-
- /**
- * Returns the previous row from the given row
- *
- * @param row
- * The row to calculate from
- * @return The previous row or null if no row exists
- */
- private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
- final Iterator<Widget> it = scrollBody.iterator();
- final Iterator<Widget> offsetIt = scrollBody.iterator();
- VScrollTableRow r = null;
- VScrollTableRow prev = null;
- while (it.hasNext()) {
- r = (VScrollTableRow) it.next();
- if (offset < 0) {
- prev = (VScrollTableRow) offsetIt.next();
- }
- if (r == row) {
- return prev;
- }
- offset--;
- }
-
- return null;
- }
-
- protected void reOrderColumn(String columnKey, int newIndex) {
-
- final int oldIndex = getColIndexByKey(columnKey);
-
- // Change header order
- tHead.moveCell(oldIndex, newIndex);
-
- // Change body order
- scrollBody.moveCol(oldIndex, newIndex);
-
- // Change footer order
- tFoot.moveCell(oldIndex, newIndex);
-
- /*
- * Build new columnOrder and update it to server Note that columnOrder
- * also contains collapsed columns so we cannot directly build it from
- * cells vector Loop the old columnOrder and append in order to new
- * array unless on moved columnKey. On new index also put the moved key
- * i == index on columnOrder, j == index on newOrder
- */
- final String oldKeyOnNewIndex = visibleColOrder[newIndex];
- if (showRowHeaders) {
- newIndex--; // columnOrder don't have rowHeader
- }
- // add back hidden rows,
- for (int i = 0; i < columnOrder.length; i++) {
- if (columnOrder[i].equals(oldKeyOnNewIndex)) {
- break; // break loop at target
- }
- if (isCollapsedColumn(columnOrder[i])) {
- newIndex++;
- }
- }
- // finally we can build the new columnOrder for server
- final String[] newOrder = new String[columnOrder.length];
- for (int i = 0, j = 0; j < newOrder.length; i++) {
- if (j == newIndex) {
- newOrder[j] = columnKey;
- j++;
- }
- if (i == columnOrder.length) {
- break;
- }
- if (columnOrder[i].equals(columnKey)) {
- continue;
- }
- newOrder[j] = columnOrder[i];
- j++;
- }
- columnOrder = newOrder;
- // also update visibleColumnOrder
- int i = showRowHeaders ? 1 : 0;
- for (int j = 0; j < newOrder.length; j++) {
- final String cid = newOrder[j];
- if (!isCollapsedColumn(cid)) {
- visibleColOrder[i++] = cid;
- }
- }
- client.updateVariable(paintableId, "columnorder", columnOrder, false);
- if (client.hasEventListeners(this,
- TableConstants.COLUMN_REORDER_EVENT_ID)) {
- client.sendPendingVariableChanges();
- }
- }
-
- @Override
- protected void onDetach() {
- rowRequestHandler.cancel();
- super.onDetach();
- // ensure that scrollPosElement will be detached
- if (scrollPositionElement != null) {
- final Element parent = DOM.getParent(scrollPositionElement);
- if (parent != null) {
- DOM.removeChild(parent, scrollPositionElement);
- }
- }
- }
-
- /**
- * Run only once when component is attached and received its initial
- * content. This function:
- *
- * * Syncs headers and bodys "natural widths and saves the values.
- *
- * * Sets proper width and height
- *
- * * Makes deferred request to get some cache rows
- */
- void sizeInit() {
- sizeNeedsInit = false;
-
- scrollBody.setContainerHeight();
-
- /*
- * We will use browsers table rendering algorithm to find proper column
- * widths. If content and header take less space than available, we will
- * divide extra space relatively to each column which has not width set.
- *
- * Overflow pixels are added to last column.
- */
-
- Iterator<Widget> headCells = tHead.iterator();
- Iterator<Widget> footCells = tFoot.iterator();
- int i = 0;
- int totalExplicitColumnsWidths = 0;
- int total = 0;
- float expandRatioDivider = 0;
-
- final int[] widths = new int[tHead.visibleCells.size()];
-
- tHead.enableBrowserIntelligence();
- tFoot.enableBrowserIntelligence();
-
- // first loop: collect natural widths
- while (headCells.hasNext()) {
- final HeaderCell hCell = (HeaderCell) headCells.next();
- final FooterCell fCell = (FooterCell) footCells.next();
- int w = hCell.getWidth();
- if (hCell.isDefinedWidth()) {
- // server has defined column width explicitly
- totalExplicitColumnsWidths += w;
- } else {
- if (hCell.getExpandRatio() > 0) {
- expandRatioDivider += hCell.getExpandRatio();
- w = 0;
- } else {
- // get and store greater of header width and column width,
- // and
- // store it as a minimumn natural col width
- int headerWidth = hCell.getNaturalColumnWidth(i);
- int footerWidth = fCell.getNaturalColumnWidth(i);
- w = headerWidth > footerWidth ? headerWidth : footerWidth;
- }
- hCell.setNaturalMinimumColumnWidth(w);
- fCell.setNaturalMinimumColumnWidth(w);
- }
- widths[i] = w;
- total += w;
- i++;
- }
-
- tHead.disableBrowserIntelligence();
- tFoot.disableBrowserIntelligence();
-
- boolean willHaveScrollbarz = willHaveScrollbars();
-
- // fix "natural" width if width not set
- if (isDynamicWidth()) {
- int w = total;
- w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
- if (willHaveScrollbarz) {
- w += Util.getNativeScrollbarSize();
- }
- setContentWidth(w);
- }
-
- int availW = scrollBody.getAvailableWidth();
- if (BrowserInfo.get().isIE()) {
- // Hey IE, are you really sure about this?
- availW = scrollBody.getAvailableWidth();
- }
- availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
-
- if (willHaveScrollbarz) {
- availW -= Util.getNativeScrollbarSize();
- }
-
- // TODO refactor this code to be the same as in resize timer
- boolean needsReLayout = false;
-
- if (availW > total) {
- // natural size is smaller than available space
- final int extraSpace = availW - total;
- final int totalWidthR = total - totalExplicitColumnsWidths;
- int checksum = 0;
- needsReLayout = true;
-
- if (extraSpace == 1) {
- // We cannot divide one single pixel so we give it the first
- // undefined column
- headCells = tHead.iterator();
- i = 0;
- checksum = availW;
- while (headCells.hasNext()) {
- HeaderCell hc = (HeaderCell) headCells.next();
- if (!hc.isDefinedWidth()) {
- widths[i]++;
- break;
- }
- i++;
- }
-
- } else if (expandRatioDivider > 0) {
- // visible columns have some active expand ratios, excess
- // space is divided according to them
- headCells = tHead.iterator();
- i = 0;
- while (headCells.hasNext()) {
- HeaderCell hCell = (HeaderCell) headCells.next();
- if (hCell.getExpandRatio() > 0) {
- int w = widths[i];
- final int newSpace = Math.round((extraSpace * (hCell
- .getExpandRatio() / expandRatioDivider)));
- w += newSpace;
- widths[i] = w;
- }
- checksum += widths[i];
- i++;
- }
- } else if (totalWidthR > 0) {
- // no expand ratios defined, we will share extra space
- // relatively to "natural widths" among those without
- // explicit width
- headCells = tHead.iterator();
- i = 0;
- while (headCells.hasNext()) {
- HeaderCell hCell = (HeaderCell) headCells.next();
- if (!hCell.isDefinedWidth()) {
- int w = widths[i];
- final int newSpace = Math.round((float) extraSpace
- * (float) w / totalWidthR);
- w += newSpace;
- widths[i] = w;
- }
- checksum += widths[i];
- i++;
- }
- }
-
- if (extraSpace > 0 && checksum != availW) {
- /*
- * There might be in some cases a rounding error of 1px when
- * extra space is divided so if there is one then we give the
- * first undefined column 1 more pixel
- */
- headCells = tHead.iterator();
- i = 0;
- while (headCells.hasNext()) {
- HeaderCell hc = (HeaderCell) headCells.next();
- if (!hc.isDefinedWidth()) {
- widths[i] += availW - checksum;
- break;
- }
- i++;
- }
- }
-
- } else {
- // bodys size will be more than available and scrollbar will appear
- }
-
- // last loop: set possibly modified values or reset if new tBody
- i = 0;
- headCells = tHead.iterator();
- while (headCells.hasNext()) {
- final HeaderCell hCell = (HeaderCell) headCells.next();
- if (isNewBody || hCell.getWidth() == -1) {
- final int w = widths[i];
- setColWidth(i, w, false);
- }
- i++;
- }
-
- initializedAndAttached = true;
-
- if (needsReLayout) {
- scrollBody.reLayoutComponents();
- }
-
- updatePageLength();
-
- /*
- * Fix "natural" height if height is not set. This must be after width
- * fixing so the components' widths have been adjusted.
- */
- if (isDynamicHeight()) {
- /*
- * We must force an update of the row height as this point as it
- * might have been (incorrectly) calculated earlier
- */
-
- int bodyHeight;
- if (pageLength == totalRows) {
- /*
- * A hack to support variable height rows when paging is off.
- * Generally this is not supported by scrolltable. We want to
- * show all rows so the bodyHeight should be equal to the table
- * height.
- */
- // int bodyHeight = scrollBody.getOffsetHeight();
- bodyHeight = scrollBody.getRequiredHeight();
- } else {
- bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
- * pageLength);
- }
- boolean needsSpaceForHorizontalSrollbar = (total > availW);
- if (needsSpaceForHorizontalSrollbar) {
- bodyHeight += Util.getNativeScrollbarSize();
- }
- scrollBodyPanel.setHeight(bodyHeight + "px");
- Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
- }
-
- isNewBody = false;
-
- if (firstvisible > 0) {
- // Deferred due to some Firefox oddities
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstvisible));
- firstRowInViewPort = firstvisible;
- }
- });
- }
-
- if (enabled) {
- // Do we need cache rows
- if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
- + pageLength + (int) cache_react_rate * pageLength) {
- if (totalRows - 1 > scrollBody.getLastRendered()) {
- // fetch cache rows
- int firstInNewSet = scrollBody.getLastRendered() + 1;
- rowRequestHandler.setReqFirstRow(firstInNewSet);
- int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
- * pageLength);
- if (lastInNewSet > totalRows - 1) {
- lastInNewSet = totalRows - 1;
- }
- rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet
- + 1);
- rowRequestHandler.deferRowFetch(1);
- }
- }
- }
-
- /*
- * Ensures the column alignments are correct at initial loading. <br/>
- * (child components widths are correct)
- */
- scrollBody.reLayoutComponents();
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
- }
- });
- }
-
- /**
- * Note, this method is not official api although declared as protected.
- * Extend at you own risk.
- *
- * @return true if content area will have scrollbars visible.
- */
- protected boolean willHaveScrollbars() {
- if (isDynamicHeight()) {
- if (pageLength < totalRows) {
- return true;
- }
- } else {
- int fakeheight = (int) Math.round(scrollBody.getRowHeight()
- * totalRows);
- int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
- "clientHeight");
- if (fakeheight > availableHeight) {
- return true;
- }
- }
- return false;
- }
-
- private void announceScrollPosition() {
- if (scrollPositionElement == null) {
- scrollPositionElement = DOM.createDiv();
- scrollPositionElement.setClassName(getStylePrimaryName()
- + "-scrollposition");
- scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
- scrollPositionElement.getStyle().setDisplay(Display.NONE);
- getElement().appendChild(scrollPositionElement);
- }
-
- Style style = scrollPositionElement.getStyle();
- style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
- style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
-
- // indexes go from 1-totalRows, as rowheaders in index-mode indicate
- int last = (firstRowInViewPort + pageLength);
- if (last > totalRows) {
- last = totalRows;
- }
- scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
- + " – " + (last) + "..." + "</span>");
- style.setDisplay(Display.BLOCK);
- }
-
- void hideScrollPositionAnnotation() {
- if (scrollPositionElement != null) {
- DOM.setStyleAttribute(scrollPositionElement, "display", "none");
- }
- }
-
- boolean isScrollPositionVisible() {
- return scrollPositionElement != null
- && !scrollPositionElement.getStyle().getDisplay()
- .equals(Display.NONE.toString());
- }
-
- class RowRequestHandler extends Timer {
-
- private int reqFirstRow = 0;
- private int reqRows = 0;
- private boolean isRunning = false;
-
- public void deferRowFetch() {
- deferRowFetch(250);
- }
-
- public boolean isRunning() {
- return isRunning;
- }
-
- public void deferRowFetch(int msec) {
- isRunning = true;
- if (reqRows > 0 && reqFirstRow < totalRows) {
- schedule(msec);
-
- // tell scroll position to user if currently "visible" rows are
- // not rendered
- if (totalRows > pageLength
- && ((firstRowInViewPort + pageLength > scrollBody
- .getLastRendered()) || (firstRowInViewPort < scrollBody
- .getFirstRendered()))) {
- announceScrollPosition();
- } else {
- hideScrollPositionAnnotation();
- }
- }
- }
-
- public void setReqFirstRow(int reqFirstRow) {
- if (reqFirstRow < 0) {
- reqFirstRow = 0;
- } else if (reqFirstRow >= totalRows) {
- reqFirstRow = totalRows - 1;
- }
- this.reqFirstRow = reqFirstRow;
- }
-
- public void setReqRows(int reqRows) {
- this.reqRows = reqRows;
- }
-
- @Override
- public void run() {
- if (client.hasActiveRequest() || navKeyDown) {
- // if client connection is busy, don't bother loading it more
- VConsole.log("Postponed rowfetch");
- schedule(250);
- } else {
-
- int firstToBeRendered = scrollBody.firstRendered;
- if (reqFirstRow < firstToBeRendered) {
- firstToBeRendered = reqFirstRow;
- } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
- firstToBeRendered = firstRowInViewPort
- - (int) (cache_rate * pageLength);
- if (firstToBeRendered < 0) {
- firstToBeRendered = 0;
- }
- }
-
- int lastToBeRendered = scrollBody.lastRendered;
-
- if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
- lastToBeRendered = reqFirstRow + reqRows - 1;
- } else if (firstRowInViewPort + pageLength + pageLength
- * cache_rate < lastToBeRendered) {
- lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
- if (lastToBeRendered >= totalRows) {
- lastToBeRendered = totalRows - 1;
- }
- // due Safari 3.1 bug (see #2607), verify reqrows, original
- // problem unknown, but this should catch the issue
- if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
- reqRows = lastToBeRendered - reqFirstRow;
- }
- }
-
- client.updateVariable(paintableId, "firstToBeRendered",
- firstToBeRendered, false);
-
- client.updateVariable(paintableId, "lastToBeRendered",
- lastToBeRendered, false);
- // remember which firstvisible we requested, in case the server
- // has
- // a differing opinion
- lastRequestedFirstvisible = firstRowInViewPort;
- client.updateVariable(paintableId, "firstvisible",
- firstRowInViewPort, false);
- client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
- false);
- client.updateVariable(paintableId, "reqrows", reqRows, true);
-
- if (selectionChanged) {
- unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
- selectedRowKeys);
- }
- isRunning = false;
- }
- }
-
- public int getReqFirstRow() {
- return reqFirstRow;
- }
-
- /**
- * Sends request to refresh content at this position.
- */
- public void refreshContent() {
- isRunning = true;
- int first = (int) (firstRowInViewPort - pageLength * cache_rate);
- int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
- if (first < 0) {
- reqRows = reqRows + first;
- first = 0;
- }
- setReqFirstRow(first);
- setReqRows(reqRows);
- run();
- }
- }
-
- public class HeaderCell extends Widget {
-
- Element td = DOM.createTD();
-
- Element captionContainer = DOM.createDiv();
-
- Element sortIndicator = DOM.createDiv();
-
- Element colResizeWidget = DOM.createDiv();
-
- Element floatingCopyOfHeaderCell;
-
- private boolean sortable = false;
- private final String cid;
- private boolean dragging;
-
- private int dragStartX;
- private int colIndex;
- private int originalWidth;
-
- private boolean isResizing;
-
- private int headerX;
-
- private boolean moved;
-
- private int closestSlot;
-
- private int width = -1;
-
- private int naturalWidth = -1;
-
- private char align = ALIGN_LEFT;
-
- boolean definedWidth = false;
-
- private float expandRatio = 0;
-
- private boolean sorted;
-
- public void setSortable(boolean b) {
- sortable = b;
- }
-
- /**
- * Makes room for the sorting indicator in case the column that the
- * header cell belongs to is sorted. This is done by resizing the width
- * of the caption container element by the correct amount
- */
- public void resizeCaptionContainer(int rightSpacing) {
- int captionContainerWidth = width
- - colResizeWidget.getOffsetWidth() - rightSpacing;
-
- if (td.getClassName().contains("-asc")
- || td.getClassName().contains("-desc")) {
- // Leave room for the sort indicator
- captionContainerWidth -= sortIndicator.getOffsetWidth();
- }
-
- if (captionContainerWidth < 0) {
- rightSpacing += captionContainerWidth;
- captionContainerWidth = 0;
- }
-
- captionContainer.getStyle().setPropertyPx("width",
- captionContainerWidth);
-
- // Apply/Remove spacing if defined
- if (rightSpacing > 0) {
- colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
- } else {
- colResizeWidget.getStyle().clearMarginLeft();
- }
- }
-
- public void setNaturalMinimumColumnWidth(int w) {
- naturalWidth = w;
- }
-
- public HeaderCell(String colId, String headerText) {
- cid = colId;
-
- setText(headerText);
-
- td.appendChild(colResizeWidget);
-
- // ensure no clipping initially (problem on column additions)
- captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
-
- td.appendChild(sortIndicator);
- td.appendChild(captionContainer);
-
- DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
- | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
-
- setElement(td);
-
- setAlign(ALIGN_LEFT);
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- colResizeWidget.setClassName(primaryStyleName + "-resizer");
- sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
- captionContainer.setClassName(primaryStyleName
- + "-caption-container");
- if (sorted) {
- if (sortAscending) {
- setStyleName(primaryStyleName + "-header-cell-asc");
- } else {
- setStyleName(primaryStyleName + "-header-cell-desc");
- }
- } else {
- setStyleName(primaryStyleName + "-header-cell");
- }
-
- final String ALIGN_PREFIX = primaryStyleName
- + "-caption-container-align-";
-
- switch (align) {
- case ALIGN_CENTER:
- captionContainer.addClassName(ALIGN_PREFIX + "center");
- break;
- case ALIGN_RIGHT:
- captionContainer.addClassName(ALIGN_PREFIX + "right");
- break;
- default:
- captionContainer.addClassName(ALIGN_PREFIX + "left");
- break;
- }
-
- }
-
- public void disableAutoWidthCalculation() {
- definedWidth = true;
- expandRatio = 0;
- }
-
- public void setWidth(int w, boolean ensureDefinedWidth) {
- if (ensureDefinedWidth) {
- definedWidth = true;
- // on column resize expand ratio becomes zero
- expandRatio = 0;
- }
- if (width == -1) {
- // go to default mode, clip content if necessary
- DOM.setStyleAttribute(captionContainer, "overflow", "");
- }
- width = w;
- if (w == -1) {
- DOM.setStyleAttribute(captionContainer, "width", "");
- setWidth("");
- } else {
- tHead.resizeCaptionContainer(this);
-
- /*
- * if we already have tBody, set the header width properly, if
- * not defer it. IE will fail with complex float in table header
- * unless TD width is not explicitly set.
- */
- if (scrollBody != null) {
- int tdWidth = width + scrollBody.getCellExtraWidth();
- setWidth(tdWidth + "px");
- } else {
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- int tdWidth = width
- + scrollBody.getCellExtraWidth();
- setWidth(tdWidth + "px");
- }
- });
- }
- }
- }
-
- public void setUndefinedWidth() {
- definedWidth = false;
- setWidth(-1, false);
- }
-
- /**
- * Detects if width is fixed by developer on server side or resized to
- * current width by user.
- *
- * @return true if defined, false if "natural" width
- */
- public boolean isDefinedWidth() {
- return definedWidth && width >= 0;
- }
-
- public int getWidth() {
- return width;
- }
-
- public void setText(String headerText) {
- DOM.setInnerHTML(captionContainer, headerText);
- }
-
- public String getColKey() {
- return cid;
- }
-
- private void setSorted(boolean sorted) {
- this.sorted = sorted;
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- /**
- * Handle column reordering.
- */
-
- @Override
- public void onBrowserEvent(Event event) {
- if (enabled && event != null) {
- if (isResizing
- || event.getEventTarget().cast() == colResizeWidget) {
- if (dragging
- && (event.getTypeInt() == Event.ONMOUSEUP || event
- .getTypeInt() == Event.ONTOUCHEND)) {
- // Handle releasing column header on spacer #5318
- handleCaptionEvent(event);
- } else {
- onResizeEvent(event);
- }
- } else {
- /*
- * Ensure focus before handling caption event. Otherwise
- * variables changed from caption event may be before
- * variables from other components that fire variables when
- * they lose focus.
- */
- if (event.getTypeInt() == Event.ONMOUSEDOWN
- || event.getTypeInt() == Event.ONTOUCHSTART) {
- scrollBodyPanel.setFocus(true);
- }
- handleCaptionEvent(event);
- boolean stopPropagation = true;
- if (event.getTypeInt() == Event.ONCONTEXTMENU
- && !client.hasEventListeners(VScrollTable.this,
- TableConstants.HEADER_CLICK_EVENT_ID)) {
- // Prevent showing the browser's context menu only when
- // there is a header click listener.
- stopPropagation = false;
- }
- if (stopPropagation) {
- event.stopPropagation();
- event.preventDefault();
- }
- }
- }
- }
-
- private void createFloatingCopy() {
- floatingCopyOfHeaderCell = DOM.createDiv();
- DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
- floatingCopyOfHeaderCell = DOM
- .getChild(floatingCopyOfHeaderCell, 2);
- DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
- VScrollTable.this.getStylePrimaryName() + "-header-drag");
- // otherwise might wrap or be cut if narrow column
- DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto");
- updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
- DOM.getAbsoluteTop(td));
- DOM.appendChild(RootPanel.get().getElement(),
- floatingCopyOfHeaderCell);
- }
-
- private void updateFloatingCopysPosition(int x, int y) {
- x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
- "offsetWidth") / 2;
- DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
- if (y > 0) {
- DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
- + "px");
- }
- }
-
- private void hideFloatingCopy() {
- DOM.removeChild(RootPanel.get().getElement(),
- floatingCopyOfHeaderCell);
- floatingCopyOfHeaderCell = null;
- }
-
- /**
- * Fires a header click event after the user has clicked a column header
- * cell
- *
- * @param event
- * The click event
- */
- private void fireHeaderClickedEvent(Event event) {
- if (client.hasEventListeners(VScrollTable.this,
- TableConstants.HEADER_CLICK_EVENT_ID)) {
- MouseEventDetails details = MouseEventDetailsBuilder
- .buildMouseEventDetails(event);
- client.updateVariable(paintableId, "headerClickEvent",
- details.toString(), false);
- client.updateVariable(paintableId, "headerClickCID", cid, true);
- }
- }
-
- protected void handleCaptionEvent(Event event) {
- switch (DOM.eventGetType(event)) {
- case Event.ONTOUCHSTART:
- case Event.ONMOUSEDOWN:
- if (columnReordering
- && Util.isTouchEventOrLeftMouseButton(event)) {
- if (event.getTypeInt() == Event.ONTOUCHSTART) {
- /*
- * prevent using this event in e.g. scrolling
- */
- event.stopPropagation();
- }
- dragging = true;
- moved = false;
- colIndex = getColIndexByKey(cid);
- DOM.setCapture(getElement());
- headerX = tHead.getAbsoluteLeft();
- event.preventDefault(); // prevent selecting text &&
- // generated touch events
- }
- break;
- case Event.ONMOUSEUP:
- case Event.ONTOUCHEND:
- case Event.ONTOUCHCANCEL:
- if (columnReordering
- && Util.isTouchEventOrLeftMouseButton(event)) {
- dragging = false;
- DOM.releaseCapture(getElement());
- if (moved) {
- hideFloatingCopy();
- tHead.removeSlotFocus();
- if (closestSlot != colIndex
- && closestSlot != (colIndex + 1)) {
- if (closestSlot > colIndex) {
- reOrderColumn(cid, closestSlot - 1);
- } else {
- reOrderColumn(cid, closestSlot);
- }
- }
- }
- if (Util.isTouchEvent(event)) {
- /*
- * Prevent using in e.g. scrolling and prevent generated
- * events.
- */
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- if (!moved) {
- // mouse event was a click to header -> sort column
- if (sortable && Util.isTouchEventOrLeftMouseButton(event)) {
- if (sortColumn.equals(cid)) {
- // just toggle order
- client.updateVariable(paintableId, "sortascending",
- !sortAscending, false);
- } else {
- // set table sorted by this column
- client.updateVariable(paintableId, "sortcolumn",
- cid, false);
- }
- // get also cache columns at the same request
- scrollBodyPanel.setScrollPosition(0);
- firstvisible = 0;
- rowRequestHandler.setReqFirstRow(0);
- rowRequestHandler.setReqRows((int) (2 * pageLength
- * cache_rate + pageLength));
- rowRequestHandler.deferRowFetch(); // some validation +
- // defer 250ms
- rowRequestHandler.cancel(); // instead of waiting
- rowRequestHandler.run(); // run immediately
- }
- fireHeaderClickedEvent(event);
- if (Util.isTouchEvent(event)) {
- /*
- * Prevent using in e.g. scrolling and prevent generated
- * events.
- */
- event.preventDefault();
- event.stopPropagation();
- }
- break;
- }
- break;
- case Event.ONDBLCLICK:
- fireHeaderClickedEvent(event);
- break;
- case Event.ONTOUCHMOVE:
- case Event.ONMOUSEMOVE:
- if (dragging && Util.isTouchEventOrLeftMouseButton(event)) {
- if (event.getTypeInt() == Event.ONTOUCHMOVE) {
- /*
- * prevent using this event in e.g. scrolling
- */
- event.stopPropagation();
- }
- if (!moved) {
- createFloatingCopy();
- moved = true;
- }
-
- final int clientX = Util.getTouchOrMouseClientX(event);
- final int x = clientX + tHead.hTableWrapper.getScrollLeft();
- int slotX = headerX;
- closestSlot = colIndex;
- int closestDistance = -1;
- int start = 0;
- if (showRowHeaders) {
- start++;
- }
- final int visibleCellCount = tHead.getVisibleCellCount();
- for (int i = start; i <= visibleCellCount; i++) {
- if (i > 0) {
- final String colKey = getColKeyByIndex(i - 1);
- slotX += getColWidth(colKey);
- }
- final int dist = Math.abs(x - slotX);
- if (closestDistance == -1 || dist < closestDistance) {
- closestDistance = dist;
- closestSlot = i;
- }
- }
- tHead.focusSlot(closestSlot);
-
- updateFloatingCopysPosition(clientX, -1);
- }
- break;
- default:
- break;
- }
- }
-
- private void onResizeEvent(Event event) {
- switch (DOM.eventGetType(event)) {
- case Event.ONMOUSEDOWN:
- if (!Util.isTouchEventOrLeftMouseButton(event)) {
- return;
- }
- isResizing = true;
- DOM.setCapture(getElement());
- dragStartX = DOM.eventGetClientX(event);
- colIndex = getColIndexByKey(cid);
- originalWidth = getWidth();
- DOM.eventPreventDefault(event);
- break;
- case Event.ONMOUSEUP:
- if (!Util.isTouchEventOrLeftMouseButton(event)) {
- return;
- }
- isResizing = false;
- DOM.releaseCapture(getElement());
- tHead.disableAutoColumnWidthCalculation(this);
-
- // Ensure last header cell is taking into account possible
- // column selector
- HeaderCell lastCell = tHead.getHeaderCell(tHead
- .getVisibleCellCount() - 1);
- tHead.resizeCaptionContainer(lastCell);
- triggerLazyColumnAdjustment(true);
-
- fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
- break;
- case Event.ONMOUSEMOVE:
- if (!Util.isTouchEventOrLeftMouseButton(event)) {
- return;
- }
- if (isResizing) {
- final int deltaX = DOM.eventGetClientX(event) - dragStartX;
- if (deltaX == 0) {
- return;
- }
- tHead.disableAutoColumnWidthCalculation(this);
-
- int newWidth = originalWidth + deltaX;
- if (newWidth < getMinWidth()) {
- newWidth = getMinWidth();
- }
- setColWidth(colIndex, newWidth, true);
- triggerLazyColumnAdjustment(false);
- forceRealignColumnHeaders();
- }
- break;
- default:
- break;
- }
- }
-
- public int getMinWidth() {
- int cellExtraWidth = 0;
- if (scrollBody != null) {
- cellExtraWidth += scrollBody.getCellExtraWidth();
- }
- return cellExtraWidth + sortIndicator.getOffsetWidth();
- }
-
- public String getCaption() {
- return DOM.getInnerText(captionContainer);
- }
-
- public boolean isEnabled() {
- return getParent() != null;
- }
-
- public void setAlign(char c) {
- align = c;
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- public char getAlign() {
- return align;
- }
-
- /**
- * Detects the natural minimum width for the column of this header cell.
- * If column is resized by user or the width is defined by server the
- * actual width is returned. Else the natural min width is returned.
- *
- * @param columnIndex
- * column index hint, if -1 (unknown) it will be detected
- *
- * @return
- */
- public int getNaturalColumnWidth(int columnIndex) {
- if (isDefinedWidth()) {
- return width;
- } else {
- if (naturalWidth < 0) {
- // This is recently revealed column. Try to detect a proper
- // value (greater of header and data
- // cols)
-
- int hw = captionContainer.getOffsetWidth()
- + scrollBody.getCellExtraWidth();
- if (BrowserInfo.get().isGecko()) {
- hw += sortIndicator.getOffsetWidth();
- }
- if (columnIndex < 0) {
- columnIndex = 0;
- for (Iterator<Widget> it = tHead.iterator(); it
- .hasNext(); columnIndex++) {
- if (it.next() == this) {
- break;
- }
- }
- }
- final int cw = scrollBody.getColWidth(columnIndex);
- naturalWidth = (hw > cw ? hw : cw);
- }
- return naturalWidth;
- }
- }
-
- public void setExpandRatio(float floatAttribute) {
- if (floatAttribute != expandRatio) {
- triggerLazyColumnAdjustment(false);
- }
- expandRatio = floatAttribute;
- }
-
- public float getExpandRatio() {
- return expandRatio;
- }
-
- public boolean isSorted() {
- return sorted;
- }
- }
-
- /**
- * HeaderCell that is header cell for row headers.
- *
- * Reordering disabled and clicking on it resets sorting.
- */
- public class RowHeadersHeaderCell extends HeaderCell {
-
- RowHeadersHeaderCell() {
- super(ROW_HEADER_COLUMN_KEY, "");
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- @Override
- protected void updateStyleNames(String primaryStyleName) {
- super.updateStyleNames(primaryStyleName);
- setStyleName(primaryStyleName + "-header-cell-rowheader");
- }
-
- @Override
- protected void handleCaptionEvent(Event event) {
- // NOP: RowHeaders cannot be reordered
- // TODO It'd be nice to reset sorting here
- }
- }
-
- public class TableHead extends Panel implements ActionOwner {
-
- private static final int WRAPPER_WIDTH = 900000;
-
- ArrayList<Widget> visibleCells = new ArrayList<Widget>();
-
- HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
-
- Element div = DOM.createDiv();
- Element hTableWrapper = DOM.createDiv();
- Element hTableContainer = DOM.createDiv();
- Element table = DOM.createTable();
- Element headerTableBody = DOM.createTBody();
- Element tr = DOM.createTR();
-
- private final Element columnSelector = DOM.createDiv();
-
- private int focusedSlot = -1;
-
- public TableHead() {
- if (BrowserInfo.get().isIE()) {
- table.setPropertyInt("cellSpacing", 0);
- }
-
- DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
- DOM.setStyleAttribute(columnSelector, "display", "none");
-
- DOM.appendChild(table, headerTableBody);
- DOM.appendChild(headerTableBody, tr);
- DOM.appendChild(hTableContainer, table);
- DOM.appendChild(hTableWrapper, hTableContainer);
- DOM.appendChild(div, hTableWrapper);
- DOM.appendChild(div, columnSelector);
- setElement(div);
-
- DOM.sinkEvents(columnSelector, Event.ONCLICK);
-
- availableCells.put(ROW_HEADER_COLUMN_KEY,
- new RowHeadersHeaderCell());
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- hTableWrapper.setClassName(primaryStyleName + "-header");
- columnSelector.setClassName(primaryStyleName + "-column-selector");
- setStyleName(primaryStyleName + "-header-wrap");
- for (HeaderCell c : availableCells.values()) {
- c.updateStyleNames(primaryStyleName);
- }
- }
-
- public void resizeCaptionContainer(HeaderCell cell) {
- HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
-
- // Measure column widths
- int columnTotalWidth = 0;
- for (Widget w : visibleCells) {
- columnTotalWidth += w.getOffsetWidth();
- }
-
- if (cell == lastcell
- && columnSelector.getOffsetWidth() > 0
- && columnTotalWidth >= div.getOffsetWidth()
- - columnSelector.getOffsetWidth()
- && !hasVerticalScrollbar()) {
- // Ensure column caption is visible when placed under the column
- // selector widget by shifting and resizing the caption.
- int offset = 0;
- int diff = div.getOffsetWidth() - columnTotalWidth;
- if (diff < columnSelector.getOffsetWidth() && diff > 0) {
- // If the difference is less than the column selectors width
- // then just offset by the
- // difference
- offset = columnSelector.getOffsetWidth() - diff;
- } else {
- // Else offset by the whole column selector
- offset = columnSelector.getOffsetWidth();
- }
- lastcell.resizeCaptionContainer(offset);
- } else {
- cell.resizeCaptionContainer(0);
- }
- }
-
- @Override
- public void clear() {
- for (String cid : availableCells.keySet()) {
- removeCell(cid);
- }
- availableCells.clear();
- availableCells.put(ROW_HEADER_COLUMN_KEY,
- new RowHeadersHeaderCell());
- }
-
- public void updateCellsFromUIDL(UIDL uidl) {
- Iterator<?> it = uidl.getChildIterator();
- HashSet<String> updated = new HashSet<String>();
- boolean refreshContentWidths = false;
- while (it.hasNext()) {
- final UIDL col = (UIDL) it.next();
- final String cid = col.getStringAttribute("cid");
- updated.add(cid);
-
- String caption = buildCaptionHtmlSnippet(col);
- HeaderCell c = getHeaderCell(cid);
- if (c == null) {
- c = new HeaderCell(cid, caption);
- availableCells.put(cid, c);
- if (initializedAndAttached) {
- // we will need a column width recalculation
- initializedAndAttached = false;
- initialContentReceived = false;
- isNewBody = true;
- }
- } else {
- c.setText(caption);
- }
-
- if (col.hasAttribute("sortable")) {
- c.setSortable(true);
- if (cid.equals(sortColumn)) {
- c.setSorted(true);
- } else {
- c.setSorted(false);
- }
- } else {
- c.setSortable(false);
- }
-
- if (col.hasAttribute("align")) {
- c.setAlign(col.getStringAttribute("align").charAt(0));
- } else {
- c.setAlign(ALIGN_LEFT);
-
- }
- if (col.hasAttribute("width")) {
- final String widthStr = col.getStringAttribute("width");
- // Make sure to accomodate for the sort indicator if
- // necessary.
- int width = Integer.parseInt(widthStr);
- if (width < c.getMinWidth()) {
- width = c.getMinWidth();
- }
- if (width != c.getWidth() && scrollBody != null) {
- // Do a more thorough update if a column is resized from
- // the server *after* the header has been properly
- // initialized
- final int colIx = getColIndexByKey(c.cid);
- final int newWidth = width;
- Scheduler.get().scheduleDeferred(
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- setColWidth(colIx, newWidth, true);
- }
- });
- refreshContentWidths = true;
- } else {
- c.setWidth(width, true);
- }
- } else if (recalcWidths) {
- c.setUndefinedWidth();
- }
- if (col.hasAttribute("er")) {
- c.setExpandRatio(col.getFloatAttribute("er"));
- }
- if (col.hasAttribute("collapsed")) {
- // ensure header is properly removed from parent (case when
- // collapsing happens via servers side api)
- if (c.isAttached()) {
- c.removeFromParent();
- headerChangedDuringUpdate = true;
- }
- }
- }
-
- if (refreshContentWidths) {
- // Recalculate the column sizings if any column has changed
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
- @Override
- public void execute() {
- triggerLazyColumnAdjustment(true);
- }
- });
- }
-
- // check for orphaned header cells
- for (Iterator<String> cit = availableCells.keySet().iterator(); cit
- .hasNext();) {
- String cid = cit.next();
- if (!updated.contains(cid)) {
- removeCell(cid);
- cit.remove();
- // we will need a column width recalculation, since columns
- // with expand ratios should expand to fill the void.
- initializedAndAttached = false;
- initialContentReceived = false;
- isNewBody = true;
- }
- }
- }
-
- public void enableColumn(String cid, int index) {
- final HeaderCell c = getHeaderCell(cid);
- if (!c.isEnabled() || getHeaderCell(index) != c) {
- setHeaderCell(index, c);
- if (initializedAndAttached) {
- headerChangedDuringUpdate = true;
- }
- }
- }
-
- public int getVisibleCellCount() {
- return visibleCells.size();
- }
-
- public void setHorizontalScrollPosition(int scrollLeft) {
- hTableWrapper.setScrollLeft(scrollLeft);
- }
-
- public void setColumnCollapsingAllowed(boolean cc) {
- if (cc) {
- columnSelector.getStyle().setDisplay(Display.BLOCK);
- } else {
- columnSelector.getStyle().setDisplay(Display.NONE);
- }
- }
-
- public void disableBrowserIntelligence() {
- hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
- }
-
- public void enableBrowserIntelligence() {
- hTableContainer.getStyle().clearWidth();
- }
-
- public void setHeaderCell(int index, HeaderCell cell) {
- if (cell.isEnabled()) {
- // we're moving the cell
- DOM.removeChild(tr, cell.getElement());
- orphan(cell);
- visibleCells.remove(cell);
- }
- if (index < visibleCells.size()) {
- // insert to right slot
- DOM.insertChild(tr, cell.getElement(), index);
- adopt(cell);
- visibleCells.add(index, cell);
- } else if (index == visibleCells.size()) {
- // simply append
- DOM.appendChild(tr, cell.getElement());
- adopt(cell);
- visibleCells.add(cell);
- } else {
- throw new RuntimeException(
- "Header cells must be appended in order");
- }
- }
-
- public HeaderCell getHeaderCell(int index) {
- if (index >= 0 && index < visibleCells.size()) {
- return (HeaderCell) visibleCells.get(index);
- } else {
- return null;
- }
- }
-
- /**
- * Get's HeaderCell by it's column Key.
- *
- * Note that this returns HeaderCell even if it is currently collapsed.
- *
- * @param cid
- * Column key of accessed HeaderCell
- * @return HeaderCell
- */
- public HeaderCell getHeaderCell(String cid) {
- return availableCells.get(cid);
- }
-
- public void moveCell(int oldIndex, int newIndex) {
- final HeaderCell hCell = getHeaderCell(oldIndex);
- final Element cell = hCell.getElement();
-
- visibleCells.remove(oldIndex);
- DOM.removeChild(tr, cell);
-
- DOM.insertChild(tr, cell, newIndex);
- visibleCells.add(newIndex, hCell);
- }
-
- @Override
- public Iterator<Widget> iterator() {
- return visibleCells.iterator();
- }
-
- @Override
- public boolean remove(Widget w) {
- if (visibleCells.contains(w)) {
- visibleCells.remove(w);
- orphan(w);
- DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
- return true;
- }
- return false;
- }
-
- public void removeCell(String colKey) {
- final HeaderCell c = getHeaderCell(colKey);
- remove(c);
- }
-
- private void focusSlot(int index) {
- removeSlotFocus();
- if (index > 0) {
- Element child = tr.getChild(index - 1).getFirstChild().cast();
- child.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-resizer");
- child.addClassName(VScrollTable.this.getStylePrimaryName()
- + "-focus-slot-right");
- } else {
- Element child = tr.getChild(index).getFirstChild().cast();
- child.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-resizer");
- child.addClassName(VScrollTable.this.getStylePrimaryName()
- + "-focus-slot-left");
- }
- focusedSlot = index;
- }
-
- private void removeSlotFocus() {
- if (focusedSlot < 0) {
- return;
- }
- if (focusedSlot == 0) {
- Element child = tr.getChild(focusedSlot).getFirstChild().cast();
- child.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-resizer");
- } else if (focusedSlot > 0) {
- Element child = tr.getChild(focusedSlot - 1).getFirstChild()
- .cast();
- child.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-resizer");
- }
- focusedSlot = -1;
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (enabled) {
- if (event.getEventTarget().cast() == columnSelector) {
- final int left = DOM.getAbsoluteLeft(columnSelector);
- final int top = DOM.getAbsoluteTop(columnSelector)
- + DOM.getElementPropertyInt(columnSelector,
- "offsetHeight");
- client.getContextMenu().showAt(this, left, top);
- }
- }
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- if (client != null) {
- client.getContextMenu().ensureHidden(this);
- }
- }
-
- class VisibleColumnAction extends Action {
-
- String colKey;
- private boolean collapsed;
- private boolean noncollapsible = false;
- private VScrollTableRow currentlyFocusedRow;
-
- public VisibleColumnAction(String colKey) {
- super(VScrollTable.TableHead.this);
- this.colKey = colKey;
- caption = tHead.getHeaderCell(colKey).getCaption();
- currentlyFocusedRow = focusedRow;
- }
-
- @Override
- public void execute() {
- if (noncollapsible) {
- return;
- }
- client.getContextMenu().hide();
- // toggle selected column
- if (collapsedColumns.contains(colKey)) {
- collapsedColumns.remove(colKey);
- } else {
- tHead.removeCell(colKey);
- collapsedColumns.add(colKey);
- triggerLazyColumnAdjustment(true);
- }
-
- // update variable to server
- client.updateVariable(paintableId, "collapsedcolumns",
- collapsedColumns.toArray(new String[collapsedColumns
- .size()]), false);
- // let rowRequestHandler determine proper rows
- rowRequestHandler.refreshContent();
- lazyRevertFocusToRow(currentlyFocusedRow);
- }
-
- public void setCollapsed(boolean b) {
- collapsed = b;
- }
-
- public void setNoncollapsible(boolean b) {
- noncollapsible = b;
- }
-
- /**
- * Override default method to distinguish on/off columns
- */
-
- @Override
- public String getHTML() {
- final StringBuffer buf = new StringBuffer();
- buf.append("<span class=\"");
- if (collapsed) {
- buf.append("v-off");
- } else {
- buf.append("v-on");
- }
- if (noncollapsible) {
- buf.append(" v-disabled");
- }
- buf.append("\">");
-
- buf.append(super.getHTML());
- buf.append("</span>");
-
- return buf.toString();
- }
-
- }
-
- /*
- * Returns columns as Action array for column select popup
- */
-
- @Override
- public Action[] getActions() {
- Object[] cols;
- if (columnReordering && columnOrder != null) {
- cols = columnOrder;
- } else {
- // if columnReordering is disabled, we need different way to get
- // all available columns
- cols = visibleColOrder;
- cols = new Object[visibleColOrder.length
- + collapsedColumns.size()];
- int i;
- for (i = 0; i < visibleColOrder.length; i++) {
- cols[i] = visibleColOrder[i];
- }
- for (final Iterator<String> it = collapsedColumns.iterator(); it
- .hasNext();) {
- cols[i++] = it.next();
- }
- }
- final Action[] actions = new Action[cols.length];
-
- for (int i = 0; i < cols.length; i++) {
- final String cid = (String) cols[i];
- final HeaderCell c = getHeaderCell(cid);
- final VisibleColumnAction a = new VisibleColumnAction(
- c.getColKey());
- a.setCaption(c.getCaption());
- if (!c.isEnabled()) {
- a.setCollapsed(true);
- }
- if (noncollapsibleColumns.contains(cid)) {
- a.setNoncollapsible(true);
- }
- actions[i] = a;
- }
- return actions;
- }
-
- @Override
- public ApplicationConnection getClient() {
- return client;
- }
-
- @Override
- public String getPaintableId() {
- return paintableId;
- }
-
- /**
- * Returns column alignments for visible columns
- */
- public char[] getColumnAlignments() {
- final Iterator<Widget> it = visibleCells.iterator();
- final char[] aligns = new char[visibleCells.size()];
- int colIndex = 0;
- while (it.hasNext()) {
- aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
- }
- return aligns;
- }
-
- /**
- * Disables the automatic calculation of all column widths by forcing
- * the widths to be "defined" thus turning off expand ratios and such.
- */
- public void disableAutoColumnWidthCalculation(HeaderCell source) {
- for (HeaderCell cell : availableCells.values()) {
- cell.disableAutoWidthCalculation();
- }
- // fire column resize events for all columns but the source of the
- // resize action, since an event will fire separately for this.
- ArrayList<HeaderCell> columns = new ArrayList<HeaderCell>(
- availableCells.values());
- columns.remove(source);
- sendColumnWidthUpdates(columns);
- forceRealignColumnHeaders();
- }
- }
-
- /**
- * A cell in the footer
- */
- public class FooterCell extends Widget {
- private final Element td = DOM.createTD();
- private final Element captionContainer = DOM.createDiv();
- private char align = ALIGN_LEFT;
- private int width = -1;
- private float expandRatio = 0;
- private final String cid;
- boolean definedWidth = false;
- private int naturalWidth = -1;
-
- public FooterCell(String colId, String headerText) {
- cid = colId;
-
- setText(headerText);
-
- // ensure no clipping initially (problem on column additions)
- DOM.setStyleAttribute(captionContainer, "overflow", "visible");
-
- DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
-
- DOM.appendChild(td, captionContainer);
-
- DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
- | Event.ONCONTEXTMENU);
-
- setElement(td);
-
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- captionContainer.setClassName(primaryStyleName
- + "-footer-container");
- }
-
- /**
- * Sets the text of the footer
- *
- * @param footerText
- * The text in the footer
- */
- public void setText(String footerText) {
- if (footerText == null || footerText.equals("")) {
- footerText = " ";
- }
-
- DOM.setInnerHTML(captionContainer, footerText);
- }
-
- /**
- * Set alignment of the text in the cell
- *
- * @param c
- * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
- * ALIGN_RIGHT
- */
- public void setAlign(char c) {
- if (align != c) {
- switch (c) {
- case ALIGN_CENTER:
- DOM.setStyleAttribute(captionContainer, "textAlign",
- "center");
- break;
- case ALIGN_RIGHT:
- DOM.setStyleAttribute(captionContainer, "textAlign",
- "right");
- break;
- default:
- DOM.setStyleAttribute(captionContainer, "textAlign", "");
- break;
- }
- }
- align = c;
- }
-
- /**
- * Get the alignment of the text int the cell
- *
- * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
- */
- public char getAlign() {
- return align;
- }
-
- /**
- * Sets the width of the cell
- *
- * @param w
- * The width of the cell
- * @param ensureDefinedWidth
- * Ensures the the given width is not recalculated
- */
- public void setWidth(int w, boolean ensureDefinedWidth) {
-
- if (ensureDefinedWidth) {
- definedWidth = true;
- // on column resize expand ratio becomes zero
- expandRatio = 0;
- }
- if (width == w) {
- return;
- }
- if (width == -1) {
- // go to default mode, clip content if necessary
- DOM.setStyleAttribute(captionContainer, "overflow", "");
- }
- width = w;
- if (w == -1) {
- DOM.setStyleAttribute(captionContainer, "width", "");
- setWidth("");
- } else {
- /*
- * Reduce width with one pixel for the right border since the
- * footers does not have any spacers between them.
- */
- final int borderWidths = 1;
-
- // Set the container width (check for negative value)
- captionContainer.getStyle().setPropertyPx("width",
- Math.max(w - borderWidths, 0));
-
- /*
- * if we already have tBody, set the header width properly, if
- * not defer it. IE will fail with complex float in table header
- * unless TD width is not explicitly set.
- */
- if (scrollBody != null) {
- int tdWidth = width + scrollBody.getCellExtraWidth()
- - borderWidths;
- setWidth(Math.max(tdWidth, 0) + "px");
- } else {
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- int tdWidth = width
- + scrollBody.getCellExtraWidth()
- - borderWidths;
- setWidth(Math.max(tdWidth, 0) + "px");
- }
- });
- }
- }
- }
-
- /**
- * Sets the width to undefined
- */
- public void setUndefinedWidth() {
- setWidth(-1, false);
- }
-
- /**
- * Detects if width is fixed by developer on server side or resized to
- * current width by user.
- *
- * @return true if defined, false if "natural" width
- */
- public boolean isDefinedWidth() {
- return definedWidth && width >= 0;
- }
-
- /**
- * Returns the pixels width of the footer cell
- *
- * @return The width in pixels
- */
- public int getWidth() {
- return width;
- }
-
- /**
- * Sets the expand ratio of the cell
- *
- * @param floatAttribute
- * The expand ratio
- */
- public void setExpandRatio(float floatAttribute) {
- expandRatio = floatAttribute;
- }
-
- /**
- * Returns the expand ration of the cell
- *
- * @return The expand ratio
- */
- public float getExpandRatio() {
- return expandRatio;
- }
-
- /**
- * Is the cell enabled?
- *
- * @return True if enabled else False
- */
- public boolean isEnabled() {
- return getParent() != null;
- }
-
- /**
- * Handle column clicking
- */
-
- @Override
- public void onBrowserEvent(Event event) {
- if (enabled && event != null) {
- handleCaptionEvent(event);
-
- if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
- scrollBodyPanel.setFocus(true);
- }
- boolean stopPropagation = true;
- if (event.getTypeInt() == Event.ONCONTEXTMENU
- && !client.hasEventListeners(VScrollTable.this,
- TableConstants.FOOTER_CLICK_EVENT_ID)) {
- // Show browser context menu if a footer click listener is
- // not present
- stopPropagation = false;
- }
- if (stopPropagation) {
- event.stopPropagation();
- event.preventDefault();
- }
- }
- }
-
- /**
- * Handles a event on the captions
- *
- * @param event
- * The event to handle
- */
- protected void handleCaptionEvent(Event event) {
- if (event.getTypeInt() == Event.ONMOUSEUP
- || event.getTypeInt() == Event.ONDBLCLICK) {
- fireFooterClickedEvent(event);
- }
- }
-
- /**
- * Fires a footer click event after the user has clicked a column footer
- * cell
- *
- * @param event
- * The click event
- */
- private void fireFooterClickedEvent(Event event) {
- if (client.hasEventListeners(VScrollTable.this,
- TableConstants.FOOTER_CLICK_EVENT_ID)) {
- MouseEventDetails details = MouseEventDetailsBuilder
- .buildMouseEventDetails(event);
- client.updateVariable(paintableId, "footerClickEvent",
- details.toString(), false);
- client.updateVariable(paintableId, "footerClickCID", cid, true);
- }
- }
-
- /**
- * Returns the column key of the column
- *
- * @return The column key
- */
- public String getColKey() {
- return cid;
- }
-
- /**
- * Detects the natural minimum width for the column of this header cell.
- * If column is resized by user or the width is defined by server the
- * actual width is returned. Else the natural min width is returned.
- *
- * @param columnIndex
- * column index hint, if -1 (unknown) it will be detected
- *
- * @return
- */
- public int getNaturalColumnWidth(int columnIndex) {
- if (isDefinedWidth()) {
- return width;
- } else {
- if (naturalWidth < 0) {
- // This is recently revealed column. Try to detect a proper
- // value (greater of header and data
- // cols)
-
- final int hw = ((Element) getElement().getLastChild())
- .getOffsetWidth() + scrollBody.getCellExtraWidth();
- if (columnIndex < 0) {
- columnIndex = 0;
- for (Iterator<Widget> it = tHead.iterator(); it
- .hasNext(); columnIndex++) {
- if (it.next() == this) {
- break;
- }
- }
- }
- final int cw = scrollBody.getColWidth(columnIndex);
- naturalWidth = (hw > cw ? hw : cw);
- }
- return naturalWidth;
- }
- }
-
- public void setNaturalMinimumColumnWidth(int w) {
- naturalWidth = w;
- }
- }
-
- /**
- * HeaderCell that is header cell for row headers.
- *
- * Reordering disabled and clicking on it resets sorting.
- */
- public class RowHeadersFooterCell extends FooterCell {
-
- RowHeadersFooterCell() {
- super(ROW_HEADER_COLUMN_KEY, "");
- }
-
- @Override
- protected void handleCaptionEvent(Event event) {
- // NOP: RowHeaders cannot be reordered
- // TODO It'd be nice to reset sorting here
- }
- }
-
- /**
- * The footer of the table which can be seen in the bottom of the Table.
- */
- public class TableFooter extends Panel {
-
- private static final int WRAPPER_WIDTH = 900000;
-
- ArrayList<Widget> visibleCells = new ArrayList<Widget>();
- HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
-
- Element div = DOM.createDiv();
- Element hTableWrapper = DOM.createDiv();
- Element hTableContainer = DOM.createDiv();
- Element table = DOM.createTable();
- Element headerTableBody = DOM.createTBody();
- Element tr = DOM.createTR();
-
- public TableFooter() {
-
- DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
-
- DOM.appendChild(table, headerTableBody);
- DOM.appendChild(headerTableBody, tr);
- DOM.appendChild(hTableContainer, table);
- DOM.appendChild(hTableWrapper, hTableContainer);
- DOM.appendChild(div, hTableWrapper);
- setElement(div);
-
- availableCells.put(ROW_HEADER_COLUMN_KEY,
- new RowHeadersFooterCell());
-
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- hTableWrapper.setClassName(primaryStyleName + "-footer");
- setStyleName(primaryStyleName + "-footer-wrap");
- for (FooterCell c : availableCells.values()) {
- c.updateStyleNames(primaryStyleName);
- }
- }
-
- @Override
- public void clear() {
- for (String cid : availableCells.keySet()) {
- removeCell(cid);
- }
- availableCells.clear();
- availableCells.put(ROW_HEADER_COLUMN_KEY,
- new RowHeadersFooterCell());
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
- * .ui.Widget)
- */
-
- @Override
- public boolean remove(Widget w) {
- if (visibleCells.contains(w)) {
- visibleCells.remove(w);
- orphan(w);
- DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
- return true;
- }
- return false;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
- */
-
- @Override
- public Iterator<Widget> iterator() {
- return visibleCells.iterator();
- }
-
- /**
- * Gets a footer cell which represents the given columnId
- *
- * @param cid
- * The columnId
- *
- * @return The cell
- */
- public FooterCell getFooterCell(String cid) {
- return availableCells.get(cid);
- }
-
- /**
- * Gets a footer cell by using a column index
- *
- * @param index
- * The index of the column
- * @return The Cell
- */
- public FooterCell getFooterCell(int index) {
- if (index < visibleCells.size()) {
- return (FooterCell) visibleCells.get(index);
- } else {
- return null;
- }
- }
-
- /**
- * Updates the cells contents when updateUIDL request is received
- *
- * @param uidl
- * The UIDL
- */
- public void updateCellsFromUIDL(UIDL uidl) {
- Iterator<?> columnIterator = uidl.getChildIterator();
- HashSet<String> updated = new HashSet<String>();
- while (columnIterator.hasNext()) {
- final UIDL col = (UIDL) columnIterator.next();
- final String cid = col.getStringAttribute("cid");
- updated.add(cid);
-
- String caption = col.hasAttribute("fcaption") ? col
- .getStringAttribute("fcaption") : "";
- FooterCell c = getFooterCell(cid);
- if (c == null) {
- c = new FooterCell(cid, caption);
- availableCells.put(cid, c);
- if (initializedAndAttached) {
- // we will need a column width recalculation
- initializedAndAttached = false;
- initialContentReceived = false;
- isNewBody = true;
- }
- } else {
- c.setText(caption);
- }
-
- if (col.hasAttribute("align")) {
- c.setAlign(col.getStringAttribute("align").charAt(0));
- } else {
- c.setAlign(ALIGN_LEFT);
-
- }
- if (col.hasAttribute("width")) {
- if (scrollBody == null) {
- // Already updated by setColWidth called from
- // TableHeads.updateCellsFromUIDL in case of a server
- // side resize
- final String width = col.getStringAttribute("width");
- c.setWidth(Integer.parseInt(width), true);
- }
- } else if (recalcWidths) {
- c.setUndefinedWidth();
- }
- if (col.hasAttribute("er")) {
- c.setExpandRatio(col.getFloatAttribute("er"));
- }
- if (col.hasAttribute("collapsed")) {
- // ensure header is properly removed from parent (case when
- // collapsing happens via servers side api)
- if (c.isAttached()) {
- c.removeFromParent();
- headerChangedDuringUpdate = true;
- }
- }
- }
-
- // check for orphaned header cells
- for (Iterator<String> cit = availableCells.keySet().iterator(); cit
- .hasNext();) {
- String cid = cit.next();
- if (!updated.contains(cid)) {
- removeCell(cid);
- cit.remove();
- }
- }
- }
-
- /**
- * Set a footer cell for a specified column index
- *
- * @param index
- * The index
- * @param cell
- * The footer cell
- */
- public void setFooterCell(int index, FooterCell cell) {
- if (cell.isEnabled()) {
- // we're moving the cell
- DOM.removeChild(tr, cell.getElement());
- orphan(cell);
- visibleCells.remove(cell);
- }
- if (index < visibleCells.size()) {
- // insert to right slot
- DOM.insertChild(tr, cell.getElement(), index);
- adopt(cell);
- visibleCells.add(index, cell);
- } else if (index == visibleCells.size()) {
- // simply append
- DOM.appendChild(tr, cell.getElement());
- adopt(cell);
- visibleCells.add(cell);
- } else {
- throw new RuntimeException(
- "Header cells must be appended in order");
- }
- }
-
- /**
- * Remove a cell by using the columnId
- *
- * @param colKey
- * The columnId to remove
- */
- public void removeCell(String colKey) {
- final FooterCell c = getFooterCell(colKey);
- remove(c);
- }
-
- /**
- * Enable a column (Sets the footer cell)
- *
- * @param cid
- * The columnId
- * @param index
- * The index of the column
- */
- public void enableColumn(String cid, int index) {
- final FooterCell c = getFooterCell(cid);
- if (!c.isEnabled() || getFooterCell(index) != c) {
- setFooterCell(index, c);
- if (initializedAndAttached) {
- headerChangedDuringUpdate = true;
- }
- }
- }
-
- /**
- * Disable browser measurement of the table width
- */
- public void disableBrowserIntelligence() {
- DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
- + "px");
- }
-
- /**
- * Enable browser measurement of the table width
- */
- public void enableBrowserIntelligence() {
- DOM.setStyleAttribute(hTableContainer, "width", "");
- }
-
- /**
- * Set the horizontal position in the cell in the footer. This is done
- * when a horizontal scrollbar is present.
- *
- * @param scrollLeft
- * The value of the leftScroll
- */
- public void setHorizontalScrollPosition(int scrollLeft) {
- hTableWrapper.setScrollLeft(scrollLeft);
- }
-
- /**
- * Swap cells when the column are dragged
- *
- * @param oldIndex
- * The old index of the cell
- * @param newIndex
- * The new index of the cell
- */
- public void moveCell(int oldIndex, int newIndex) {
- final FooterCell hCell = getFooterCell(oldIndex);
- final Element cell = hCell.getElement();
-
- visibleCells.remove(oldIndex);
- DOM.removeChild(tr, cell);
-
- DOM.insertChild(tr, cell, newIndex);
- visibleCells.add(newIndex, hCell);
- }
- }
-
- /**
- * This Panel can only contain VScrollTableRow type of widgets. This
- * "simulates" very large table, keeping spacers which take room of
- * unrendered rows.
- *
- */
- public class VScrollTableBody extends Panel {
-
- public static final int DEFAULT_ROW_HEIGHT = 24;
-
- private double rowHeight = -1;
-
- private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
-
- /**
- * Due some optimizations row height measuring is deferred and initial
- * set of rows is rendered detached. Flag set on when table body has
- * been attached in dom and rowheight has been measured.
- */
- private boolean tBodyMeasurementsDone = false;
-
- Element preSpacer = DOM.createDiv();
- Element postSpacer = DOM.createDiv();
-
- Element container = DOM.createDiv();
-
- TableSectionElement tBodyElement = Document.get().createTBodyElement();
- Element table = DOM.createTable();
-
- private int firstRendered;
- private int lastRendered;
-
- private char[] aligns;
-
- protected VScrollTableBody() {
- constructDOM();
- setElement(container);
- }
-
- public VScrollTableRow getRowByRowIndex(int indexInTable) {
- int internalIndex = indexInTable - firstRendered;
- if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
- return (VScrollTableRow) renderedRows.get(internalIndex);
- } else {
- return null;
- }
- }
-
- /**
- * @return the height of scrollable body, subpixels ceiled.
- */
- public int getRequiredHeight() {
- return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
- + Util.getRequiredHeight(table);
- }
-
- private void constructDOM() {
- if (BrowserInfo.get().isIE()) {
- table.setPropertyInt("cellSpacing", 0);
- }
-
- table.appendChild(tBodyElement);
- DOM.appendChild(container, preSpacer);
- DOM.appendChild(container, table);
- DOM.appendChild(container, postSpacer);
- if (BrowserInfo.get().requiresTouchScrollDelegate()) {
- NodeList<Node> childNodes = container.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- Element item = (Element) childNodes.getItem(i);
- item.getStyle().setProperty("webkitTransform",
- "translate3d(0,0,0)");
- }
- }
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
- }
-
- protected void updateStyleNames(String primaryStyleName) {
- table.setClassName(primaryStyleName + "-table");
- preSpacer.setClassName(primaryStyleName + "-row-spacer");
- postSpacer.setClassName(primaryStyleName + "-row-spacer");
- for (Widget w : renderedRows) {
- VScrollTableRow row = (VScrollTableRow) w;
- row.updateStyleNames(primaryStyleName);
- }
- }
-
- public int getAvailableWidth() {
- int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
- return availW;
- }
-
- public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
- firstRendered = firstIndex;
- lastRendered = firstIndex + rows - 1;
- final Iterator<?> it = rowData.getChildIterator();
- aligns = tHead.getColumnAlignments();
- while (it.hasNext()) {
- final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
- addRow(row);
- }
- if (isAttached()) {
- fixSpacers();
- }
- }
-
- public void renderRows(UIDL rowData, int firstIndex, int rows) {
- // FIXME REVIEW
- aligns = tHead.getColumnAlignments();
- final Iterator<?> it = rowData.getChildIterator();
- if (firstIndex == lastRendered + 1) {
- while (it.hasNext()) {
- final VScrollTableRow row = prepareRow((UIDL) it.next());
- addRow(row);
- lastRendered++;
- }
- fixSpacers();
- } else if (firstIndex + rows == firstRendered) {
- final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
- int i = rows;
- while (it.hasNext()) {
- i--;
- rowArray[i] = prepareRow((UIDL) it.next());
- }
- for (i = 0; i < rows; i++) {
- addRowBeforeFirstRendered(rowArray[i]);
- firstRendered--;
- }
- } else {
- // completely new set of rows
- while (lastRendered + 1 > firstRendered) {
- unlinkRow(false);
- }
- final VScrollTableRow row = prepareRow((UIDL) it.next());
- firstRendered = firstIndex;
- lastRendered = firstIndex - 1;
- addRow(row);
- lastRendered++;
- setContainerHeight();
- fixSpacers();
- while (it.hasNext()) {
- addRow(prepareRow((UIDL) it.next()));
- lastRendered++;
- }
- fixSpacers();
- }
-
- // this may be a new set of rows due content change,
- // ensure we have proper cache rows
- ensureCacheFilled();
- }
-
- /**
- * Ensure we have the correct set of rows on client side, e.g. if the
- * content on the server side has changed, or the client scroll position
- * has changed since the last request.
- */
- protected void ensureCacheFilled() {
- int reactFirstRow = (int) (firstRowInViewPort - pageLength
- * cache_react_rate);
- int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
- * cache_react_rate);
- if (reactFirstRow < 0) {
- reactFirstRow = 0;
- }
- if (reactLastRow >= totalRows) {
- reactLastRow = totalRows - 1;
- }
- if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
- /*
- * #8040 - scroll position is completely changed since the
- * latest request, so request a new set of rows.
- *
- * TODO: We should probably check whether the fetched rows match
- * the current scroll position right when they arrive, so as to
- * not waste time rendering a set of rows that will never be
- * visible...
- */
- rowRequestHandler.setReqFirstRow(reactFirstRow);
- rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1);
- rowRequestHandler.deferRowFetch(1);
- } else if (lastRendered < reactLastRow) {
- // get some cache rows below visible area
- rowRequestHandler.setReqFirstRow(lastRendered + 1);
- rowRequestHandler.setReqRows(reactLastRow - lastRendered);
- rowRequestHandler.deferRowFetch(1);
- } else if (firstRendered > reactFirstRow) {
- /*
- * Branch for fetching cache above visible area.
- *
- * If cache needed for both before and after visible area, this
- * will be rendered after-cache is received and rendered. So in
- * some rare situations the table may make two cache visits to
- * server.
- */
- rowRequestHandler.setReqFirstRow(reactFirstRow);
- rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
- rowRequestHandler.deferRowFetch(1);
- }
- }
-
- /**
- * Inserts rows as provided in the rowData starting at firstIndex.
- *
- * @param rowData
- * @param firstIndex
- * @param rows
- * the number of rows
- * @return a list of the rows added.
- */
- protected List<VScrollTableRow> insertRows(UIDL rowData,
- int firstIndex, int rows) {
- aligns = tHead.getColumnAlignments();
- final Iterator<?> it = rowData.getChildIterator();
- List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
-
- if (firstIndex == lastRendered + 1) {
- while (it.hasNext()) {
- final VScrollTableRow row = prepareRow((UIDL) it.next());
- addRow(row);
- insertedRows.add(row);
- lastRendered++;
- }
- fixSpacers();
- } else if (firstIndex + rows == firstRendered) {
- final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
- int i = rows;
- while (it.hasNext()) {
- i--;
- rowArray[i] = prepareRow((UIDL) it.next());
- }
- for (i = 0; i < rows; i++) {
- addRowBeforeFirstRendered(rowArray[i]);
- insertedRows.add(rowArray[i]);
- firstRendered--;
- }
- } else {
- // insert in the middle
- int ix = firstIndex;
- while (it.hasNext()) {
- VScrollTableRow row = prepareRow((UIDL) it.next());
- insertRowAt(row, ix);
- insertedRows.add(row);
- lastRendered++;
- ix++;
- }
- fixSpacers();
- }
- return insertedRows;
- }
-
- protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
- int firstIndex, int rows) {
- List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
- rows);
- int actualIxOfFirstRowAfterInserted = firstIndex + rows
- - firstRendered;
- for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
- .size(); ix++) {
- VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
- r.setIndex(r.getIndex() + rows);
- }
- setContainerHeight();
- return inserted;
- }
-
- protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
- int rows) {
- unlinkAllRowsStartingAt(firstIndex);
- insertRows(rowData, firstIndex, rows);
- setContainerHeight();
- }
-
- /**
- * This method is used to instantiate new rows for this table. It
- * automatically sets correct widths to rows cells and assigns correct
- * client reference for child widgets.
- *
- * This method can be called only after table has been initialized
- *
- * @param uidl
- */
- private VScrollTableRow prepareRow(UIDL uidl) {
- final VScrollTableRow row = createRow(uidl, aligns);
- row.initCellWidths();
- return row;
- }
-
- protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
- if (uidl.hasAttribute("gen_html")) {
- // This is a generated row.
- return new VScrollTableGeneratedRow(uidl, aligns2);
- }
- return new VScrollTableRow(uidl, aligns2);
- }
-
- private void addRowBeforeFirstRendered(VScrollTableRow row) {
- row.setIndex(firstRendered - 1);
- if (row.isSelected()) {
- row.addStyleName("v-selected");
- }
- tBodyElement.insertBefore(row.getElement(),
- tBodyElement.getFirstChild());
- adopt(row);
- renderedRows.add(0, row);
- }
-
- private void addRow(VScrollTableRow row) {
- row.setIndex(firstRendered + renderedRows.size());
- if (row.isSelected()) {
- row.addStyleName("v-selected");
- }
- tBodyElement.appendChild(row.getElement());
- // Add to renderedRows before adopt so iterator() will return also
- // this row if called in an attach handler (#9264)
- renderedRows.add(row);
- adopt(row);
- }
-
- private void insertRowAt(VScrollTableRow row, int index) {
- row.setIndex(index);
- if (row.isSelected()) {
- row.addStyleName("v-selected");
- }
- if (index > 0) {
- VScrollTableRow sibling = getRowByRowIndex(index - 1);
- tBodyElement
- .insertAfter(row.getElement(), sibling.getElement());
- } else {
- VScrollTableRow sibling = getRowByRowIndex(index);
- tBodyElement.insertBefore(row.getElement(),
- sibling.getElement());
- }
- adopt(row);
- int actualIx = index - firstRendered;
- renderedRows.add(actualIx, row);
- }
-
- @Override
- public Iterator<Widget> iterator() {
- return renderedRows.iterator();
- }
-
- /**
- * @return false if couldn't remove row
- */
- protected boolean unlinkRow(boolean fromBeginning) {
- if (lastRendered - firstRendered < 0) {
- return false;
- }
- int actualIx;
- if (fromBeginning) {
- actualIx = 0;
- firstRendered++;
- } else {
- actualIx = renderedRows.size() - 1;
- lastRendered--;
- }
- if (actualIx >= 0) {
- unlinkRowAtActualIndex(actualIx);
- fixSpacers();
- return true;
- }
- return false;
- }
-
- protected void unlinkRows(int firstIndex, int count) {
- if (count < 1) {
- return;
- }
- if (firstRendered > firstIndex
- && firstRendered < firstIndex + count) {
- firstIndex = firstRendered;
- }
- int lastIndex = firstIndex + count - 1;
- if (lastRendered < lastIndex) {
- lastIndex = lastRendered;
- }
- for (int ix = lastIndex; ix >= firstIndex; ix--) {
- unlinkRowAtActualIndex(actualIndex(ix));
- lastRendered--;
- }
- fixSpacers();
- }
-
- protected void unlinkAndReindexRows(int firstIndex, int count) {
- unlinkRows(firstIndex, count);
- int actualFirstIx = firstIndex - firstRendered;
- for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
- VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
- r.setIndex(r.getIndex() - count);
- }
- setContainerHeight();
- }
-
- protected void unlinkAllRowsStartingAt(int index) {
- if (firstRendered > index) {
- index = firstRendered;
- }
- for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
- unlinkRowAtActualIndex(actualIndex(ix));
- lastRendered--;
- }
- fixSpacers();
- }
-
- private int actualIndex(int index) {
- return index - firstRendered;
- }
-
- private void unlinkRowAtActualIndex(int index) {
- final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
- .get(index);
- tBodyElement.removeChild(toBeRemoved.getElement());
- orphan(toBeRemoved);
- renderedRows.remove(index);
- }
-
- @Override
- public boolean remove(Widget w) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Fix container blocks height according to totalRows to avoid
- * "bouncing" when scrolling
- */
- private void setContainerHeight() {
- fixSpacers();
- DOM.setStyleAttribute(container, "height",
- measureRowHeightOffset(totalRows) + "px");
- }
-
- private void fixSpacers() {
- int prepx = measureRowHeightOffset(firstRendered);
- if (prepx < 0) {
- prepx = 0;
- }
- preSpacer.getStyle().setPropertyPx("height", prepx);
- int postpx = measureRowHeightOffset(totalRows - 1)
- - measureRowHeightOffset(lastRendered);
- if (postpx < 0) {
- postpx = 0;
- }
- postSpacer.getStyle().setPropertyPx("height", postpx);
- }
-
- public double getRowHeight() {
- return getRowHeight(false);
- }
-
- public double getRowHeight(boolean forceUpdate) {
- if (tBodyMeasurementsDone && !forceUpdate) {
- return rowHeight;
- } else {
- if (tBodyElement.getRows().getLength() > 0) {
- int tableHeight = getTableHeight();
- int rowCount = tBodyElement.getRows().getLength();
- rowHeight = tableHeight / (double) rowCount;
- } else {
- // Special cases if we can't just measure the current rows
- if (!Double.isNaN(lastKnownRowHeight)) {
- // Use previous value if available
- if (BrowserInfo.get().isIE()) {
- /*
- * IE needs to reflow the table element at this
- * point to work correctly (e.g.
- * com.vaadin.tests.components.table.
- * ContainerSizeChange) - the other code paths
- * already trigger reflows, but here it must be done
- * explicitly.
- */
- getTableHeight();
- }
- rowHeight = lastKnownRowHeight;
- } else if (isAttached()) {
- // measure row height by adding a dummy row
- VScrollTableRow scrollTableRow = new VScrollTableRow();
- tBodyElement.appendChild(scrollTableRow.getElement());
- getRowHeight(forceUpdate);
- tBodyElement.removeChild(scrollTableRow.getElement());
- } else {
- // TODO investigate if this can never happen anymore
- return DEFAULT_ROW_HEIGHT;
- }
- }
- lastKnownRowHeight = rowHeight;
- tBodyMeasurementsDone = true;
- return rowHeight;
- }
- }
-
- public int getTableHeight() {
- return table.getOffsetHeight();
- }
-
- /**
- * Returns the width available for column content.
- *
- * @param columnIndex
- * @return
- */
- public int getColWidth(int columnIndex) {
- if (tBodyMeasurementsDone) {
- if (renderedRows.isEmpty()) {
- // no rows yet rendered
- return 0;
- }
- for (Widget row : renderedRows) {
- if (!(row instanceof VScrollTableGeneratedRow)) {
- TableRowElement tr = row.getElement().cast();
- Element wrapperdiv = tr.getCells().getItem(columnIndex)
- .getFirstChildElement().cast();
- return wrapperdiv.getOffsetWidth();
- }
- }
- return 0;
- } else {
- return 0;
- }
- }
-
- /**
- * Sets the content width of a column.
- *
- * Due IE limitation, we must set the width to a wrapper elements inside
- * table cells (with overflow hidden, which does not work on td
- * elements).
- *
- * To get this work properly crossplatform, we will also set the width
- * of td.
- *
- * @param colIndex
- * @param w
- */
- public void setColWidth(int colIndex, int w) {
- for (Widget row : renderedRows) {
- ((VScrollTableRow) row).setCellWidth(colIndex, w);
- }
- }
-
- private int cellExtraWidth = -1;
-
- /**
- * Method to return the space used for cell paddings + border.
- */
- private int getCellExtraWidth() {
- if (cellExtraWidth < 0) {
- detectExtrawidth();
- }
- return cellExtraWidth;
- }
-
- private void detectExtrawidth() {
- NodeList<TableRowElement> rows = tBodyElement.getRows();
- if (rows.getLength() == 0) {
- /* need to temporary add empty row and detect */
- VScrollTableRow scrollTableRow = new VScrollTableRow();
- scrollTableRow.updateStyleNames(VScrollTable.this
- .getStylePrimaryName());
- tBodyElement.appendChild(scrollTableRow.getElement());
- detectExtrawidth();
- tBodyElement.removeChild(scrollTableRow.getElement());
- } else {
- boolean noCells = false;
- TableRowElement item = rows.getItem(0);
- TableCellElement firstTD = item.getCells().getItem(0);
- if (firstTD == null) {
- // content is currently empty, we need to add a fake cell
- // for measuring
- noCells = true;
- VScrollTableRow next = (VScrollTableRow) iterator().next();
- boolean sorted = tHead.getHeaderCell(0) != null ? tHead
- .getHeaderCell(0).isSorted() : false;
- next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
- firstTD = item.getCells().getItem(0);
- }
- com.google.gwt.dom.client.Element wrapper = firstTD
- .getFirstChildElement();
- cellExtraWidth = firstTD.getOffsetWidth()
- - wrapper.getOffsetWidth();
- if (noCells) {
- firstTD.getParentElement().removeChild(firstTD);
- }
- }
- }
-
- private void reLayoutComponents() {
- for (Widget w : this) {
- VScrollTableRow r = (VScrollTableRow) w;
- for (Widget widget : r) {
- client.handleComponentRelativeSize(widget);
- }
- }
- }
-
- public int getLastRendered() {
- return lastRendered;
- }
-
- public int getFirstRendered() {
- return firstRendered;
- }
-
- public void moveCol(int oldIndex, int newIndex) {
-
- // loop all rows and move given index to its new place
- final Iterator<?> rows = iterator();
- while (rows.hasNext()) {
- final VScrollTableRow row = (VScrollTableRow) rows.next();
-
- final Element td = DOM.getChild(row.getElement(), oldIndex);
- if (td != null) {
- DOM.removeChild(row.getElement(), td);
-
- DOM.insertChild(row.getElement(), td, newIndex);
- }
- }
-
- }
-
- /**
- * Restore row visibility which is set to "none" when the row is
- * rendered (due a performance optimization).
- */
- private void restoreRowVisibility() {
- for (Widget row : renderedRows) {
- row.getElement().getStyle().setProperty("visibility", "");
- }
- }
-
- public class VScrollTableRow extends Panel implements ActionOwner {
-
- private static final int TOUCHSCROLL_TIMEOUT = 100;
- private static final int DRAGMODE_MULTIROW = 2;
- protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
- private boolean selected = false;
- protected final int rowKey;
-
- private String[] actionKeys = null;
- private final TableRowElement rowElement;
- private int index;
- private Event touchStart;
-
- private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
- private Timer contextTouchTimeout;
- private Timer dragTouchTimeout;
- private int touchStartY;
- private int touchStartX;
- private TooltipInfo tooltipInfo = null;
- private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
- private boolean isDragging = false;
- private String rowStyle = null;
-
- private VScrollTableRow(int rowKey) {
- this.rowKey = rowKey;
- rowElement = Document.get().createTRElement();
- setElement(rowElement);
- DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
- | Event.TOUCHEVENTS | Event.ONDBLCLICK
- | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS);
- }
-
- public VScrollTableRow(UIDL uidl, char[] aligns) {
- this(uidl.getIntAttribute("key"));
-
- /*
- * Rendering the rows as hidden improves Firefox and Safari
- * performance drastically.
- */
- getElement().getStyle().setProperty("visibility", "hidden");
-
- rowStyle = uidl.getStringAttribute("rowstyle");
- updateStyleNames(VScrollTable.this.getStylePrimaryName());
-
- String rowDescription = uidl.getStringAttribute("rowdescr");
- if (rowDescription != null && !rowDescription.equals("")) {
- tooltipInfo = new TooltipInfo(rowDescription);
- } else {
- tooltipInfo = null;
- }
-
- tHead.getColumnAlignments();
- int col = 0;
- int visibleColumnIndex = -1;
-
- // row header
- if (showRowHeaders) {
- boolean sorted = tHead.getHeaderCell(col).isSorted();
- addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
- "rowheader", true, sorted);
- visibleColumnIndex++;
- }
-
- if (uidl.hasAttribute("al")) {
- actionKeys = uidl.getStringArrayAttribute("al");
- }
-
- addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
-
- if (uidl.hasAttribute("selected") && !isSelected()) {
- toggleSelection();
- }
- }
-
- protected void updateStyleNames(String primaryStyleName) {
-
- if (getStylePrimaryName().contains("odd")) {
- setStyleName(primaryStyleName + "-row-odd");
- } else {
- setStyleName(primaryStyleName + "-row");
- }
-
- if (rowStyle != null) {
- addStyleName(primaryStyleName + "-row-" + rowStyle);
- }
-
- for (int i = 0; i < rowElement.getChildCount(); i++) {
- TableCellElement cell = (TableCellElement) rowElement
- .getChild(i);
- updateCellStyleNames(cell, primaryStyleName);
- }
- }
-
- public TooltipInfo getTooltipInfo() {
- return tooltipInfo;
- }
-
- /**
- * Add a dummy row, used for measurements if Table is empty.
- */
- public VScrollTableRow() {
- this(0);
- addCell(null, "_", 'b', "", true, false);
- }
-
- protected void initCellWidths() {
- final int cells = tHead.getVisibleCellCount();
- for (int i = 0; i < cells; i++) {
- int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
- if (w < 0) {
- w = 0;
- }
- setCellWidth(i, w);
- }
- }
-
- protected void setCellWidth(int cellIx, int width) {
- final Element cell = DOM.getChild(getElement(), cellIx);
- cell.getFirstChildElement().getStyle()
- .setPropertyPx("width", width);
- cell.getStyle().setPropertyPx("width", width);
- }
-
- protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
- int visibleColumnIndex) {
- final Iterator<?> cells = uidl.getChildIterator();
- while (cells.hasNext()) {
- final Object cell = cells.next();
- visibleColumnIndex++;
-
- String columnId = visibleColOrder[visibleColumnIndex];
-
- String style = "";
- if (uidl.hasAttribute("style-" + columnId)) {
- style = uidl.getStringAttribute("style-" + columnId);
- }
-
- String description = null;
- if (uidl.hasAttribute("descr-" + columnId)) {
- description = uidl.getStringAttribute("descr-"
- + columnId);
- }
-
- boolean sorted = tHead.getHeaderCell(col).isSorted();
- if (cell instanceof String) {
- addCell(uidl, cell.toString(), aligns[col++], style,
- isRenderHtmlInCells(), sorted, description);
- } else {
- final ComponentConnector cellContent = client
- .getPaintable((UIDL) cell);
-
- addCell(uidl, cellContent.getWidget(), aligns[col++],
- style, sorted);
- }
- }
- }
-
- /**
- * Overriding this and returning true causes all text cells to be
- * rendered as HTML.
- *
- * @return always returns false in the default implementation
- */
- protected boolean isRenderHtmlInCells() {
- return false;
- }
-
- /**
- * Detects whether row is visible in tables viewport.
- *
- * @return
- */
- public boolean isInViewPort() {
- int absoluteTop = getAbsoluteTop();
- int scrollPosition = scrollBodyPanel.getScrollPosition();
- if (absoluteTop < scrollPosition) {
- return false;
- }
- int maxVisible = scrollPosition
- + scrollBodyPanel.getOffsetHeight() - getOffsetHeight();
- if (absoluteTop > maxVisible) {
- return false;
- }
- return true;
- }
-
- /**
- * Makes a check based on indexes whether the row is before the
- * compared row.
- *
- * @param row1
- * @return true if this rows index is smaller than in the row1
- */
- public boolean isBefore(VScrollTableRow row1) {
- return getIndex() < row1.getIndex();
- }
-
- /**
- * Sets the index of the row in the whole table. Currently used just
- * to set even/odd classname
- *
- * @param indexInWholeTable
- */
- private void setIndex(int indexInWholeTable) {
- index = indexInWholeTable;
- boolean isOdd = indexInWholeTable % 2 == 0;
- // Inverted logic to be backwards compatible with earlier 6.4.
- // It is very strange because rows 1,3,5 are considered "even"
- // and 2,4,6 "odd".
- //
- // First remove any old styles so that both styles aren't
- // applied when indexes are updated.
- String primaryStyleName = getStylePrimaryName();
- if (primaryStyleName != null && !primaryStyleName.equals("")) {
- removeStyleName(getStylePrimaryName());
- }
- if (!isOdd) {
- addStyleName(VScrollTable.this.getStylePrimaryName()
- + "-row-odd");
- } else {
- addStyleName(VScrollTable.this.getStylePrimaryName()
- + "-row");
- }
- }
-
- public int getIndex() {
- return index;
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- client.getContextMenu().ensureHidden(this);
- }
-
- public String getKey() {
- return String.valueOf(rowKey);
- }
-
- public void addCell(UIDL rowUidl, String text, char align,
- String style, boolean textIsHTML, boolean sorted) {
- addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
- }
-
- public void addCell(UIDL rowUidl, String text, char align,
- String style, boolean textIsHTML, boolean sorted,
- String description) {
- // String only content is optimized by not using Label widget
- final TableCellElement td = DOM.createTD().cast();
- initCellWithText(text, align, style, textIsHTML, sorted,
- description, td);
- }
-
- protected void initCellWithText(String text, char align,
- String style, boolean textIsHTML, boolean sorted,
- String description, final TableCellElement td) {
- final Element container = DOM.createDiv();
- container.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-cell-wrapper");
-
- td.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-cell-content");
-
- if (style != null && !style.equals("")) {
- td.addClassName(VScrollTable.this.getStylePrimaryName()
- + "-cell-content-" + style);
- }
-
- if (sorted) {
- td.addClassName(VScrollTable.this.getStylePrimaryName()
- + "-cell-content-sorted");
- }
-
- if (textIsHTML) {
- container.setInnerHTML(text);
- } else {
- container.setInnerText(text);
- }
- if (align != ALIGN_LEFT) {
- switch (align) {
- case ALIGN_CENTER:
- container.getStyle().setProperty("textAlign", "center");
- break;
- case ALIGN_RIGHT:
- default:
- container.getStyle().setProperty("textAlign", "right");
- break;
- }
- }
-
- if (description != null && !description.equals("")) {
- TooltipInfo info = new TooltipInfo(description);
- cellToolTips.put(td, info);
- } else {
- cellToolTips.remove(td);
- }
-
- td.appendChild(container);
- getElement().appendChild(td);
- }
-
- protected void updateCellStyleNames(TableCellElement td,
- String primaryStyleName) {
- Element container = td.getFirstChild().cast();
- container.setClassName(primaryStyleName + "-cell-wrapper");
-
- /*
- * Replace old primary style name with new one
- */
- String className = td.getClassName();
- String oldPrimaryName = className.split("-cell-content")[0];
- td.setClassName(className.replaceAll(oldPrimaryName,
- primaryStyleName));
- }
-
- public void addCell(UIDL rowUidl, Widget w, char align,
- String style, boolean sorted) {
- final TableCellElement td = DOM.createTD().cast();
- initCellWithWidget(w, align, style, sorted, td);
- }
-
- protected void initCellWithWidget(Widget w, char align,
- String style, boolean sorted, final TableCellElement td) {
- final Element container = DOM.createDiv();
- String className = VScrollTable.this.getStylePrimaryName()
- + "-cell-content";
- if (style != null && !style.equals("")) {
- className += " " + VScrollTable.this.getStylePrimaryName()
- + "-cell-content-" + style;
- }
- if (sorted) {
- className += " " + VScrollTable.this.getStylePrimaryName()
- + "-cell-content-sorted";
- }
- td.setClassName(className);
- container.setClassName(VScrollTable.this.getStylePrimaryName()
- + "-cell-wrapper");
- // TODO most components work with this, but not all (e.g.
- // Select)
- // Old comment: make widget cells respect align.
- // text-align:center for IE, margin: auto for others
- if (align != ALIGN_LEFT) {
- switch (align) {
- case ALIGN_CENTER:
- container.getStyle().setProperty("textAlign", "center");
- break;
- case ALIGN_RIGHT:
- default:
- container.getStyle().setProperty("textAlign", "right");
- break;
- }
- }
- td.appendChild(container);
- getElement().appendChild(td);
- // ensure widget not attached to another element (possible tBody
- // change)
- w.removeFromParent();
- container.appendChild(w.getElement());
- adopt(w);
- childWidgets.add(w);
- }
-
- @Override
- public Iterator<Widget> iterator() {
- return childWidgets.iterator();
- }
-
- @Override
- public boolean remove(Widget w) {
- if (childWidgets.contains(w)) {
- orphan(w);
- DOM.removeChild(DOM.getParent(w.getElement()),
- w.getElement());
- childWidgets.remove(w);
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * If there are registered click listeners, sends a click event and
- * returns true. Otherwise, does nothing and returns false.
- *
- * @param event
- * @param targetTdOrTr
- * @param immediate
- * Whether the event is sent immediately
- * @return Whether a click event was sent
- */
- private boolean handleClickEvent(Event event, Element targetTdOrTr,
- boolean immediate) {
- if (!client.hasEventListeners(VScrollTable.this,
- TableConstants.ITEM_CLICK_EVENT_ID)) {
- // Don't send an event if nobody is listening
- return false;
- }
-
- // This row was clicked
- client.updateVariable(paintableId, "clickedKey", "" + rowKey,
- false);
-
- if (getElement() == targetTdOrTr.getParentElement()) {
- // A specific column was clicked
- int childIndex = DOM.getChildIndex(getElement(),
- targetTdOrTr);
- String colKey = null;
- colKey = tHead.getHeaderCell(childIndex).getColKey();
- client.updateVariable(paintableId, "clickedColKey", colKey,
- false);
- }
-
- MouseEventDetails details = MouseEventDetailsBuilder
- .buildMouseEventDetails(event);
-
- client.updateVariable(paintableId, "clickEvent",
- details.toString(), immediate);
-
- return true;
- }
-
- public TooltipInfo getTooltip(
- com.google.gwt.dom.client.Element target) {
-
- TooltipInfo info = null;
-
- if (target.hasTagName("TD")) {
-
- TableCellElement td = (TableCellElement) target.cast();
- info = cellToolTips.get(td);
- }
-
- if (info == null) {
- info = tooltipInfo;
- }
-
- return info;
- }
-
- /**
- * Special handler for touch devices that support native scrolling
- *
- * @return Whether the event was handled by this method.
- */
- private boolean handleTouchEvent(final Event event) {
-
- boolean touchEventHandled = false;
-
- if (enabled && hasNativeTouchScrolling) {
- final Element targetTdOrTr = getEventTargetTdOrTr(event);
- final int type = event.getTypeInt();
-
- switch (type) {
- case Event.ONTOUCHSTART:
- touchEventHandled = true;
- touchStart = event;
- isDragging = false;
- Touch touch = event.getChangedTouches().get(0);
- // save position to fields, touches in events are same
- // instance during the operation.
- touchStartX = touch.getClientX();
- touchStartY = touch.getClientY();
-
- if (dragmode != 0) {
- if (dragTouchTimeout == null) {
- dragTouchTimeout = new Timer() {
-
- @Override
- public void run() {
- if (touchStart != null) {
- // Start a drag if a finger is held
- // in place long enough, then moved
- isDragging = true;
- }
- }
- };
- }
- dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
- }
-
- if (actionKeys != null) {
- if (contextTouchTimeout == null) {
- contextTouchTimeout = new Timer() {
-
- @Override
- public void run() {
- if (touchStart != null) {
- // Open the context menu if finger
- // is held in place long enough.
- showContextMenu(touchStart);
- event.preventDefault();
- touchStart = null;
- }
- }
- };
- }
- contextTouchTimeout
- .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
- }
- break;
- case Event.ONTOUCHMOVE:
- touchEventHandled = true;
- if (isSignificantMove(event)) {
- if (contextTouchTimeout != null) {
- // Moved finger before the context menu timer
- // expired, so let the browser handle this as a
- // scroll.
- contextTouchTimeout.cancel();
- contextTouchTimeout = null;
- }
- if (!isDragging && dragTouchTimeout != null) {
- // Moved finger before the drag timer expired,
- // so let the browser handle this as a scroll.
- dragTouchTimeout.cancel();
- dragTouchTimeout = null;
- }
-
- if (dragmode != 0 && touchStart != null
- && isDragging) {
- event.preventDefault();
- event.stopPropagation();
- startRowDrag(touchStart, type, targetTdOrTr);
- }
- touchStart = null;
- }
- break;
- case Event.ONTOUCHEND:
- case Event.ONTOUCHCANCEL:
- touchEventHandled = true;
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- }
- if (dragTouchTimeout != null) {
- dragTouchTimeout.cancel();
- }
- if (touchStart != null) {
- event.preventDefault();
- event.stopPropagation();
- if (!BrowserInfo.get().isAndroid()) {
- Util.simulateClickFromTouchEvent(touchStart,
- this);
- }
- touchStart = null;
- }
- isDragging = false;
- break;
- }
- }
- return touchEventHandled;
- }
-
- /*
- * React on click that occur on content cells only
- */
-
- @Override
- public void onBrowserEvent(final Event event) {
-
- final boolean touchEventHandled = handleTouchEvent(event);
-
- if (enabled && !touchEventHandled) {
- final int type = event.getTypeInt();
- final Element targetTdOrTr = getEventTargetTdOrTr(event);
- if (type == Event.ONCONTEXTMENU) {
- showContextMenu(event);
- if (enabled
- && (actionKeys != null || client
- .hasEventListeners(
- VScrollTable.this,
- TableConstants.ITEM_CLICK_EVENT_ID))) {
- /*
- * Prevent browser context menu only if there are
- * action handlers or item click listeners
- * registered
- */
- event.stopPropagation();
- event.preventDefault();
- }
- return;
- }
-
- boolean targetCellOrRowFound = targetTdOrTr != null;
-
- switch (type) {
- case Event.ONDBLCLICK:
- if (targetCellOrRowFound) {
- handleClickEvent(event, targetTdOrTr, true);
- }
- break;
- case Event.ONMOUSEUP:
- if (targetCellOrRowFound) {
- /*
- * Queue here, send at the same time as the
- * corresponding value change event - see #7127
- */
- boolean clickEventSent = handleClickEvent(event,
- targetTdOrTr, false);
-
- if (event.getButton() == Event.BUTTON_LEFT
- && isSelectable()) {
-
- // Ctrl+Shift click
- if ((event.getCtrlKey() || event.getMetaKey())
- && event.getShiftKey()
- && isMultiSelectModeDefault()) {
- toggleShiftSelection(false);
- setRowFocus(this);
-
- // Ctrl click
- } else if ((event.getCtrlKey() || event
- .getMetaKey())
- && isMultiSelectModeDefault()) {
- boolean wasSelected = isSelected();
- toggleSelection();
- setRowFocus(this);
- /*
- * next possible range select must start on
- * this row
- */
- selectionRangeStart = this;
- if (wasSelected) {
- removeRowFromUnsentSelectionRanges(this);
- }
-
- } else if ((event.getCtrlKey() || event
- .getMetaKey()) && isSingleSelectMode()) {
- // Ctrl (or meta) click (Single selection)
- if (!isSelected()
- || (isSelected() && nullSelectionAllowed)) {
-
- if (!isSelected()) {
- deselectAll();
- }
-
- toggleSelection();
- setRowFocus(this);
- }
-
- } else if (event.getShiftKey()
- && isMultiSelectModeDefault()) {
- // Shift click
- toggleShiftSelection(true);
-
- } else {
- // click
- boolean currentlyJustThisRowSelected = selectedRowKeys
- .size() == 1
- && selectedRowKeys
- .contains(getKey());
-
- if (!currentlyJustThisRowSelected) {
- if (isSingleSelectMode()
- || isMultiSelectModeDefault()) {
- /*
- * For default multi select mode
- * (ctrl/shift) and for single
- * select mode we need to clear the
- * previous selection before
- * selecting a new one when the user
- * clicks on a row. Only in
- * multiselect/simple mode the old
- * selection should remain after a
- * normal click.
- */
- deselectAll();
- }
- toggleSelection();
- } else if ((isSingleSelectMode() || isMultiSelectModeSimple())
- && nullSelectionAllowed) {
- toggleSelection();
- }/*
- * else NOP to avoid excessive server
- * visits (selection is removed with
- * CTRL/META click)
- */
-
- selectionRangeStart = this;
- setRowFocus(this);
- }
-
- // Remove IE text selection hack
- if (BrowserInfo.get().isIE()) {
- ((Element) event.getEventTarget().cast())
- .setPropertyJSO("onselectstart",
- null);
- }
- // Queue value change
- sendSelectedRows(false);
- }
- /*
- * Send queued click and value change events if any
- * If a click event is sent, send value change with
- * it regardless of the immediate flag, see #7127
- */
- if (immediate || clickEventSent) {
- client.sendPendingVariableChanges();
- }
- }
- break;
- case Event.ONTOUCHEND:
- case Event.ONTOUCHCANCEL:
- if (touchStart != null) {
- /*
- * Touch has not been handled as neither context or
- * drag start, handle it as a click.
- */
- Util.simulateClickFromTouchEvent(touchStart, this);
- touchStart = null;
- }
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- }
- break;
- case Event.ONTOUCHMOVE:
- if (isSignificantMove(event)) {
- /*
- * TODO figure out scroll delegate don't eat events
- * if row is selected. Null check for active
- * delegate is as a workaround.
- */
- if (dragmode != 0
- && touchStart != null
- && (TouchScrollDelegate
- .getActiveScrollDelegate() == null)) {
- startRowDrag(touchStart, type, targetTdOrTr);
- }
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- }
- /*
- * Avoid clicks and drags by clearing touch start
- * flag.
- */
- touchStart = null;
- }
-
- break;
- case Event.ONTOUCHSTART:
- touchStart = event;
- Touch touch = event.getChangedTouches().get(0);
- // save position to fields, touches in events are same
- // isntance during the operation.
- touchStartX = touch.getClientX();
- touchStartY = touch.getClientY();
- /*
- * Prevent simulated mouse events.
- */
- touchStart.preventDefault();
- if (dragmode != 0 || actionKeys != null) {
- new Timer() {
-
- @Override
- public void run() {
- TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
- .getActiveScrollDelegate();
- /*
- * If there's a scroll delegate, check if
- * we're actually scrolling and handle it.
- * If no delegate, do nothing here and let
- * the row handle potential drag'n'drop or
- * context menu.
- */
- if (activeScrollDelegate != null) {
- if (activeScrollDelegate.isMoved()) {
- /*
- * Prevent the row from handling
- * touch move/end events (the
- * delegate handles those) and from
- * doing drag'n'drop or opening a
- * context menu.
- */
- touchStart = null;
- } else {
- /*
- * Scrolling hasn't started, so
- * cancel delegate and let the row
- * handle potential drag'n'drop or
- * context menu.
- */
- activeScrollDelegate
- .stopScrolling();
- }
- }
- }
- }.schedule(TOUCHSCROLL_TIMEOUT);
-
- if (contextTouchTimeout == null
- && actionKeys != null) {
- contextTouchTimeout = new Timer() {
-
- @Override
- public void run() {
- if (touchStart != null) {
- showContextMenu(touchStart);
- touchStart = null;
- }
- }
- };
- }
- if (contextTouchTimeout != null) {
- contextTouchTimeout.cancel();
- contextTouchTimeout
- .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
- }
- }
- break;
- case Event.ONMOUSEDOWN:
- if (targetCellOrRowFound) {
- setRowFocus(this);
- ensureFocus();
- if (dragmode != 0
- && (event.getButton() == NativeEvent.BUTTON_LEFT)) {
- startRowDrag(event, type, targetTdOrTr);
-
- } else if (event.getCtrlKey()
- || event.getShiftKey()
- || event.getMetaKey()
- && isMultiSelectModeDefault()) {
-
- // Prevent default text selection in Firefox
- event.preventDefault();
-
- // Prevent default text selection in IE
- if (BrowserInfo.get().isIE()) {
- ((Element) event.getEventTarget().cast())
- .setPropertyJSO(
- "onselectstart",
- getPreventTextSelectionIEHack());
- }
-
- event.stopPropagation();
- }
- }
- break;
- case Event.ONMOUSEOUT:
- break;
- default:
- break;
- }
- }
- super.onBrowserEvent(event);
- }
-
- private boolean isSignificantMove(Event event) {
- if (touchStart == null) {
- // no touch start
- return false;
- }
- /*
- * TODO calculate based on real distance instead of separate
- * axis checks
- */
- Touch touch = event.getChangedTouches().get(0);
- if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
- return true;
- }
- if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
- return true;
- }
- return false;
- }
-
- /**
- * Checks if the row represented by the row key has been selected
- *
- * @param key
- * The generated row key
- */
- private boolean rowKeyIsSelected(int rowKey) {
- // Check single selections
- if (selectedRowKeys.contains("" + rowKey)) {
- return true;
- }
-
- // Check range selections
- for (SelectionRange r : selectedRowRanges) {
- if (r.inRange(getRenderedRowByKey("" + rowKey))) {
- return true;
- }
- }
- return false;
- }
-
- protected void startRowDrag(Event event, final int type,
- Element targetTdOrTr) {
- VTransferable transferable = new VTransferable();
- transferable.setDragSource(ConnectorMap.get(client)
- .getConnector(VScrollTable.this));
- transferable.setData("itemId", "" + rowKey);
- NodeList<TableCellElement> cells = rowElement.getCells();
- for (int i = 0; i < cells.getLength(); i++) {
- if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
- HeaderCell headerCell = tHead.getHeaderCell(i);
- transferable.setData("propertyId", headerCell.cid);
- break;
- }
- }
-
- VDragEvent ev = VDragAndDropManager.get().startDrag(
- transferable, event, true);
- if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
- && rowKeyIsSelected(rowKey)) {
-
- // Create a drag image of ALL rows
- ev.createDragImage(
- (Element) scrollBody.tBodyElement.cast(), true);
-
- // Hide rows which are not selected
- Element dragImage = ev.getDragImage();
- int i = 0;
- for (Iterator<Widget> iterator = scrollBody.iterator(); iterator
- .hasNext();) {
- VScrollTableRow next = (VScrollTableRow) iterator
- .next();
-
- Element child = (Element) dragImage.getChild(i++);
-
- if (!rowKeyIsSelected(next.rowKey)) {
- child.getStyle().setVisibility(Visibility.HIDDEN);
- }
- }
- } else {
- ev.createDragImage(getElement(), true);
- }
- if (type == Event.ONMOUSEDOWN) {
- event.preventDefault();
- }
- event.stopPropagation();
- }
-
- /**
- * Finds the TD that the event interacts with. Returns null if the
- * target of the event should not be handled. If the event target is
- * the row directly this method returns the TR element instead of
- * the TD.
- *
- * @param event
- * @return TD or TR element that the event targets (the actual event
- * target is this element or a child of it)
- */
- private Element getEventTargetTdOrTr(Event event) {
- final Element eventTarget = event.getEventTarget().cast();
- Widget widget = Util.findWidget(eventTarget, null);
- final Element thisTrElement = getElement();
-
- if (widget != this) {
- /*
- * This is a workaround to make Labels, read only TextFields
- * and Embedded in a Table clickable (see #2688). It is
- * really not a fix as it does not work with a custom read
- * only components (not extending VLabel/VEmbedded).
- */
- while (widget != null && widget.getParent() != this) {
- widget = widget.getParent();
- }
-
- if (!(widget instanceof VLabel)
- && !(widget instanceof VEmbedded)
- && !(widget instanceof VTextField && ((VTextField) widget)
- .isReadOnly())) {
- return null;
- }
- }
- if (eventTarget == thisTrElement) {
- // This was a click on the TR element
- return thisTrElement;
- }
-
- // Iterate upwards until we find the TR element
- Element element = eventTarget;
- while (element != null
- && element.getParentElement().cast() != thisTrElement) {
- element = element.getParentElement().cast();
- }
- return element;
- }
-
- public void showContextMenu(Event event) {
- if (enabled && actionKeys != null) {
- // Show context menu if there are registered action handlers
- int left = Util.getTouchOrMouseClientX(event);
- int top = Util.getTouchOrMouseClientY(event);
- top += Window.getScrollTop();
- left += Window.getScrollLeft();
- contextMenu = new ContextMenuDetails(getKey(), left, top);
- client.getContextMenu().showAt(this, left, top);
- }
- }
-
- /**
- * Has the row been selected?
- *
- * @return Returns true if selected, else false
- */
- public boolean isSelected() {
- return selected;
- }
-
- /**
- * Toggle the selection of the row
- */
- public void toggleSelection() {
- selected = !selected;
- selectionChanged = true;
- if (selected) {
- selectedRowKeys.add(String.valueOf(rowKey));
- addStyleName("v-selected");
- } else {
- removeStyleName("v-selected");
- selectedRowKeys.remove(String.valueOf(rowKey));
- }
- }
-
- /**
- * Is called when a user clicks an item when holding SHIFT key down.
- * This will select a new range from the last focused row
- *
- * @param deselectPrevious
- * Should the previous selected range be deselected
- */
- private void toggleShiftSelection(boolean deselectPrevious) {
-
- /*
- * Ensures that we are in multiselect mode and that we have a
- * previous selection which was not a deselection
- */
- if (isSingleSelectMode()) {
- // No previous selection found
- deselectAll();
- toggleSelection();
- return;
- }
-
- // Set the selectable range
- VScrollTableRow endRow = this;
- VScrollTableRow startRow = selectionRangeStart;
- if (startRow == null) {
- startRow = focusedRow;
- // If start row is null then we have a multipage selection
- // from
- // above
- if (startRow == null) {
- startRow = (VScrollTableRow) scrollBody.iterator()
- .next();
- setRowFocus(endRow);
- }
- }
- // Deselect previous items if so desired
- if (deselectPrevious) {
- deselectAll();
- }
-
- // we'll ensure GUI state from top down even though selection
- // was the opposite way
- if (!startRow.isBefore(endRow)) {
- VScrollTableRow tmp = startRow;
- startRow = endRow;
- endRow = tmp;
- }
- SelectionRange range = new SelectionRange(startRow, endRow);
-
- for (Widget w : scrollBody) {
- VScrollTableRow row = (VScrollTableRow) w;
- if (range.inRange(row)) {
- if (!row.isSelected()) {
- row.toggleSelection();
- }
- selectedRowKeys.add(row.getKey());
- }
- }
-
- // Add range
- if (startRow != endRow) {
- selectedRowRanges.add(range);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.ui.IActionOwner#getActions ()
- */
-
- @Override
- public Action[] getActions() {
- if (actionKeys == null) {
- return new Action[] {};
- }
- final Action[] actions = new Action[actionKeys.length];
- for (int i = 0; i < actions.length; i++) {
- final String actionKey = actionKeys[i];
- final TreeAction a = new TreeAction(this,
- String.valueOf(rowKey), actionKey) {
-
- @Override
- public void execute() {
- super.execute();
- lazyRevertFocusToRow(VScrollTableRow.this);
- }
- };
- a.setCaption(getActionCaption(actionKey));
- a.setIconUrl(getActionIcon(actionKey));
- actions[i] = a;
- }
- return actions;
- }
-
- @Override
- public ApplicationConnection getClient() {
- return client;
- }
-
- @Override
- public String getPaintableId() {
- return paintableId;
- }
-
- private int getColIndexOf(Widget child) {
- com.google.gwt.dom.client.Element widgetCell = child
- .getElement().getParentElement().getParentElement();
- NodeList<TableCellElement> cells = rowElement.getCells();
- for (int i = 0; i < cells.getLength(); i++) {
- if (cells.getItem(i) == widgetCell) {
- return i;
- }
- }
- return -1;
- }
-
- public Widget getWidgetForPaintable() {
- return this;
- }
- }
-
- protected class VScrollTableGeneratedRow extends VScrollTableRow {
-
- private boolean spanColumns;
- private boolean htmlContentAllowed;
-
- public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
- super(uidl, aligns);
- addStyleName("v-table-generated-row");
- }
-
- public boolean isSpanColumns() {
- return spanColumns;
- }
-
- @Override
- protected void initCellWidths() {
- if (spanColumns) {
- setSpannedColumnWidthAfterDOMFullyInited();
- } else {
- super.initCellWidths();
- }
- }
-
- private void setSpannedColumnWidthAfterDOMFullyInited() {
- // Defer setting width on spanned columns to make sure that
- // they are added to the DOM before trying to calculate
- // widths.
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
- @Override
- public void execute() {
- if (showRowHeaders) {
- setCellWidth(0, tHead.getHeaderCell(0).getWidth());
- calcAndSetSpanWidthOnCell(1);
- } else {
- calcAndSetSpanWidthOnCell(0);
- }
- }
- });
- }
-
- @Override
- protected boolean isRenderHtmlInCells() {
- return htmlContentAllowed;
- }
-
- @Override
- protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
- int visibleColumnIndex) {
- htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
- spanColumns = uidl.getBooleanAttribute("gen_span");
-
- final Iterator<?> cells = uidl.getChildIterator();
- if (spanColumns) {
- int colCount = uidl.getChildCount();
- if (cells.hasNext()) {
- final Object cell = cells.next();
- if (cell instanceof String) {
- addSpannedCell(uidl, cell.toString(), aligns[0],
- "", htmlContentAllowed, false, null,
- colCount);
- } else {
- addSpannedCell(uidl, (Widget) cell, aligns[0], "",
- false, colCount);
- }
- }
- } else {
- super.addCellsFromUIDL(uidl, aligns, col,
- visibleColumnIndex);
- }
- }
-
- private void addSpannedCell(UIDL rowUidl, Widget w, char align,
- String style, boolean sorted, int colCount) {
- TableCellElement td = DOM.createTD().cast();
- td.setColSpan(colCount);
- initCellWithWidget(w, align, style, sorted, td);
- }
-
- private void addSpannedCell(UIDL rowUidl, String text, char align,
- String style, boolean textIsHTML, boolean sorted,
- String description, int colCount) {
- // String only content is optimized by not using Label widget
- final TableCellElement td = DOM.createTD().cast();
- td.setColSpan(colCount);
- initCellWithText(text, align, style, textIsHTML, sorted,
- description, td);
- }
-
- @Override
- protected void setCellWidth(int cellIx, int width) {
- if (isSpanColumns()) {
- if (showRowHeaders) {
- if (cellIx == 0) {
- super.setCellWidth(0, width);
- } else {
- // We need to recalculate the spanning TDs width for
- // every cellIx in order to support column resizing.
- calcAndSetSpanWidthOnCell(1);
- }
- } else {
- // Same as above.
- calcAndSetSpanWidthOnCell(0);
- }
- } else {
- super.setCellWidth(cellIx, width);
- }
- }
-
- private void calcAndSetSpanWidthOnCell(final int cellIx) {
- int spanWidth = 0;
- for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
- .getVisibleCellCount(); ix++) {
- spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
- }
- Util.setWidthExcludingPaddingAndBorder((Element) getElement()
- .getChild(cellIx), spanWidth, 13, false);
- }
- }
-
- /**
- * Ensure the component has a focus.
- *
- * TODO the current implementation simply always calls focus for the
- * component. In case the Table at some point implements focus/blur
- * listeners, this method needs to be evolved to conditionally call
- * focus only if not currently focused.
- */
- protected void ensureFocus() {
- if (!hasFocus) {
- scrollBodyPanel.setFocus(true);
- }
-
- }
-
- }
-
- /**
- * Deselects all items
- */
- public void deselectAll() {
- for (Widget w : scrollBody) {
- VScrollTableRow row = (VScrollTableRow) w;
- if (row.isSelected()) {
- row.toggleSelection();
- }
- }
- // still ensure all selects are removed from (not necessary rendered)
- selectedRowKeys.clear();
- selectedRowRanges.clear();
- // also notify server that it clears all previous selections (the client
- // side does not know about the invisible ones)
- instructServerToForgetPreviousSelections();
- }
-
- /**
- * Used in multiselect mode when the client side knows that all selections
- * are in the next request.
- */
- private void instructServerToForgetPreviousSelections() {
- client.updateVariable(paintableId, "clearSelections", true, false);
- }
-
- /**
- * Determines the pagelength when the table height is fixed.
- */
- public void updatePageLength() {
- // Only update if visible and enabled
- if (!isVisible() || !enabled) {
- return;
- }
-
- if (scrollBody == null) {
- return;
- }
-
- if (isDynamicHeight()) {
- return;
- }
-
- int rowHeight = (int) Math.round(scrollBody.getRowHeight());
- int bodyH = scrollBodyPanel.getOffsetHeight();
- int rowsAtOnce = bodyH / rowHeight;
- boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
- if (anotherPartlyVisible) {
- rowsAtOnce++;
- }
- if (pageLength != rowsAtOnce) {
- pageLength = rowsAtOnce;
- client.updateVariable(paintableId, "pagelength", pageLength, false);
-
- if (!rendering) {
- int currentlyVisible = scrollBody.lastRendered
- - scrollBody.firstRendered;
- if (currentlyVisible < pageLength
- && currentlyVisible < totalRows) {
- // shake scrollpanel to fill empty space
- scrollBodyPanel.setScrollPosition(scrollTop + 1);
- scrollBodyPanel.setScrollPosition(scrollTop - 1);
- }
-
- sizeNeedsInit = true;
- }
- }
-
- }
-
- void updateWidth() {
- if (!isVisible()) {
- /*
- * Do not update size when the table is hidden as all column widths
- * will be set to zero and they won't be recalculated when the table
- * is set visible again (until the size changes again)
- */
- return;
- }
-
- if (!isDynamicWidth()) {
- int innerPixels = getOffsetWidth() - getBorderWidth();
- if (innerPixels < 0) {
- innerPixels = 0;
- }
- setContentWidth(innerPixels);
-
- // readjust undefined width columns
- triggerLazyColumnAdjustment(false);
-
- } else {
-
- sizeNeedsInit = true;
-
- // readjust undefined width columns
- triggerLazyColumnAdjustment(false);
- }
-
- /*
- * setting width may affect wheter the component has scrollbars -> needs
- * scrolling or not
- */
- setProperTabIndex();
- }
-
- private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
-
- private final Timer lazyAdjustColumnWidths = new Timer() {
- /**
- * Check for column widths, and available width, to see if we can fix
- * column widths "optimally". Doing this lazily to avoid expensive
- * calculation when resizing is not yet finished.
- */
-
- @Override
- public void run() {
- if (scrollBody == null) {
- // Try again later if we get here before scrollBody has been
- // initalized
- triggerLazyColumnAdjustment(false);
- return;
- }
-
- Iterator<Widget> headCells = tHead.iterator();
- int usedMinimumWidth = 0;
- int totalExplicitColumnsWidths = 0;
- float expandRatioDivider = 0;
- int colIndex = 0;
- while (headCells.hasNext()) {
- final HeaderCell hCell = (HeaderCell) headCells.next();
- if (hCell.isDefinedWidth()) {
- totalExplicitColumnsWidths += hCell.getWidth();
- usedMinimumWidth += hCell.getWidth();
- } else {
- usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
- expandRatioDivider += hCell.getExpandRatio();
- }
- colIndex++;
- }
-
- int availW = scrollBody.getAvailableWidth();
- // Hey IE, are you really sure about this?
- availW = scrollBody.getAvailableWidth();
- int visibleCellCount = tHead.getVisibleCellCount();
- int totalExtraWidth = scrollBody.getCellExtraWidth()
- * visibleCellCount;
- if (willHaveScrollbars()) {
- totalExtraWidth += Util.getNativeScrollbarSize();
- }
- availW -= totalExtraWidth;
- int forceScrollBodyWidth = -1;
-
- int extraSpace = availW - usedMinimumWidth;
- if (extraSpace < 0) {
- if (getTotalRows() == 0) {
- /*
- * Too wide header combined with no rows in the table.
- *
- * No horizontal scrollbars would be displayed because
- * there's no rows that grows too wide causing the
- * scrollBody container div to overflow. Must explicitely
- * force a width to a scrollbar. (see #9187)
- */
- forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
- }
- extraSpace = 0;
- }
-
- if (forceScrollBodyWidth > 0) {
- scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
- Unit.PX);
- } else {
- // Clear width that might have been set to force horizontal
- // scrolling if there are no rows
- scrollBody.container.getStyle().clearWidth();
- }
-
- int totalUndefinedNaturalWidths = usedMinimumWidth
- - totalExplicitColumnsWidths;
-
- // we have some space that can be divided optimally
- HeaderCell hCell;
- colIndex = 0;
- headCells = tHead.iterator();
- int checksum = 0;
- while (headCells.hasNext()) {
- hCell = (HeaderCell) headCells.next();
- if (!hCell.isDefinedWidth()) {
- int w = hCell.getNaturalColumnWidth(colIndex);
- int newSpace;
- if (expandRatioDivider > 0) {
- // divide excess space by expand ratios
- newSpace = Math.round((w + extraSpace
- * hCell.getExpandRatio() / expandRatioDivider));
- } else {
- if (totalUndefinedNaturalWidths != 0) {
- // divide relatively to natural column widths
- newSpace = Math.round(w + (float) extraSpace
- * (float) w / totalUndefinedNaturalWidths);
- } else {
- newSpace = w;
- }
- }
- checksum += newSpace;
- setColWidth(colIndex, newSpace, false);
- } else {
- checksum += hCell.getWidth();
- }
- colIndex++;
- }
-
- if (extraSpace > 0 && checksum != availW) {
- /*
- * There might be in some cases a rounding error of 1px when
- * extra space is divided so if there is one then we give the
- * first undefined column 1 more pixel
- */
- headCells = tHead.iterator();
- colIndex = 0;
- while (headCells.hasNext()) {
- HeaderCell hc = (HeaderCell) headCells.next();
- if (!hc.isDefinedWidth()) {
- setColWidth(colIndex,
- hc.getWidth() + availW - checksum, false);
- break;
- }
- colIndex++;
- }
- }
-
- if (isDynamicHeight() && totalRows == pageLength) {
- // fix body height (may vary if lazy loading is offhorizontal
- // scrollbar appears/disappears)
- int bodyHeight = scrollBody.getRequiredHeight();
- boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
- if (needsSpaceForHorizontalScrollbar) {
- bodyHeight += Util.getNativeScrollbarSize();
- }
- int heightBefore = getOffsetHeight();
- scrollBodyPanel.setHeight(bodyHeight + "px");
- if (heightBefore != getOffsetHeight()) {
- Util.notifyParentOfSizeChange(VScrollTable.this, false);
- }
- }
- scrollBody.reLayoutComponents();
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
- }
- });
-
- forceRealignColumnHeaders();
- }
-
- };
-
- private void forceRealignColumnHeaders() {
- if (BrowserInfo.get().isIE()) {
- /*
- * IE does not fire onscroll event if scroll position is reverted to
- * 0 due to the content element size growth. Ensure headers are in
- * sync with content manually. Safe to use null event as we don't
- * actually use the event object in listener.
- */
- onScroll(null);
- }
- }
-
- /**
- * helper to set pixel size of head and body part
- *
- * @param pixels
- */
- private void setContentWidth(int pixels) {
- tHead.setWidth(pixels + "px");
- scrollBodyPanel.setWidth(pixels + "px");
- tFoot.setWidth(pixels + "px");
- }
-
- private int borderWidth = -1;
-
- /**
- * @return border left + border right
- */
- private int getBorderWidth() {
- if (borderWidth < 0) {
- borderWidth = Util.measureHorizontalPaddingAndBorder(
- scrollBodyPanel.getElement(), 2);
- if (borderWidth < 0) {
- borderWidth = 0;
- }
- }
- return borderWidth;
- }
-
- /**
- * Ensures scrollable area is properly sized. This method is used when fixed
- * size is used.
- */
- private int containerHeight;
-
- private void setContainerHeight() {
- if (!isDynamicHeight()) {
- containerHeight = getOffsetHeight();
- containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
- containerHeight -= tFoot.getOffsetHeight();
- containerHeight -= getContentAreaBorderHeight();
- if (containerHeight < 0) {
- containerHeight = 0;
- }
- scrollBodyPanel.setHeight(containerHeight + "px");
- }
- }
-
- private int contentAreaBorderHeight = -1;
- private int scrollLeft;
- private int scrollTop;
- VScrollTableDropHandler dropHandler;
- private boolean navKeyDown;
- boolean multiselectPending;
-
- /**
- * @return border top + border bottom of the scrollable area of table
- */
- private int getContentAreaBorderHeight() {
- if (contentAreaBorderHeight < 0) {
-
- DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
- "hidden");
- int oh = scrollBodyPanel.getOffsetHeight();
- int ch = scrollBodyPanel.getElement()
- .getPropertyInt("clientHeight");
- contentAreaBorderHeight = oh - ch;
- DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
- "auto");
- }
- return contentAreaBorderHeight;
- }
-
- @Override
- public void setHeight(String height) {
- if (height.length() == 0
- && getElement().getStyle().getHeight().length() != 0) {
- /*
- * Changing from defined to undefined size -> should do a size init
- * to take page length into account again
- */
- sizeNeedsInit = true;
- }
- super.setHeight(height);
- }
-
- void updateHeight() {
- setContainerHeight();
-
- if (initializedAndAttached) {
- updatePageLength();
- }
- if (!rendering) {
- // Webkit may sometimes get an odd rendering bug (white space
- // between header and body), see bug #3875. Running
- // overflow hack here to shake body element a bit.
- // We must run the fix as a deferred command to prevent it from
- // overwriting the scroll position with an outdated value, see
- // #7607.
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
- }
- });
- }
-
- triggerLazyColumnAdjustment(false);
-
- /*
- * setting height may affect wheter the component has scrollbars ->
- * needs scrolling or not
- */
- setProperTabIndex();
-
- }
-
- /*
- * Overridden due Table might not survive of visibility change (scroll pos
- * lost). Example ITabPanel just set contained components invisible and back
- * when changing tabs.
- */
-
- @Override
- public void setVisible(boolean visible) {
- if (isVisible() != visible) {
- super.setVisible(visible);
- if (initializedAndAttached) {
- if (visible) {
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
- }
- });
- }
- }
- }
- }
-
- /**
- * Helper function to build html snippet for column or row headers
- *
- * @param uidl
- * possibly with values caption and icon
- * @return html snippet containing possibly an icon + caption text
- */
- protected String buildCaptionHtmlSnippet(UIDL uidl) {
- String s = uidl.hasAttribute("caption") ? uidl
- .getStringAttribute("caption") : "";
- if (uidl.hasAttribute("icon")) {
- s = "<img src=\""
- + Util.escapeAttribute(client.translateVaadinUri(uidl
- .getStringAttribute("icon")))
- + "\" alt=\"icon\" class=\"v-icon\">" + s;
- }
- return s;
- }
-
- /**
- * This method has logic which rows needs to be requested from server when
- * user scrolls
- */
-
- @Override
- public void onScroll(ScrollEvent event) {
- scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
- scrollTop = scrollBodyPanel.getScrollPosition();
- /*
- * #6970 - IE sometimes fires scroll events for a detached table.
- *
- * FIXME initializedAndAttached should probably be renamed - its name
- * doesn't seem to reflect its semantics. onDetach() doesn't set it to
- * false, and changing that might break something else, so we need to
- * check isAttached() separately.
- */
- if (!initializedAndAttached || !isAttached()) {
- return;
- }
- if (!enabled) {
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
- return;
- }
-
- rowRequestHandler.cancel();
-
- if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
- // due to the webkitoverflowworkaround, top may sometimes report 0
- // for webkit, although it really is not. Expecting to have the
- // correct
- // value available soon.
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- onScroll(null);
- }
- });
- return;
- }
-
- // fix headers horizontal scrolling
- tHead.setHorizontalScrollPosition(scrollLeft);
-
- // fix footers horizontal scrolling
- tFoot.setHorizontalScrollPosition(scrollLeft);
-
- firstRowInViewPort = calcFirstRowInViewPort();
- if (firstRowInViewPort > totalRows - pageLength) {
- firstRowInViewPort = totalRows - pageLength;
- }
-
- int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
- * cache_react_rate);
- if (postLimit > totalRows - 1) {
- postLimit = totalRows - 1;
- }
- int preLimit = (int) (firstRowInViewPort - pageLength
- * cache_react_rate);
- if (preLimit < 0) {
- preLimit = 0;
- }
- final int lastRendered = scrollBody.getLastRendered();
- final int firstRendered = scrollBody.getFirstRendered();
-
- if (postLimit <= lastRendered && preLimit >= firstRendered) {
- // we're within no-react area, no need to request more rows
- // remember which firstvisible we requested, in case the server has
- // a differing opinion
- lastRequestedFirstvisible = firstRowInViewPort;
- client.updateVariable(paintableId, "firstvisible",
- firstRowInViewPort, false);
- return;
- }
-
- if (firstRowInViewPort - pageLength * cache_rate > lastRendered
- || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
- // need a totally new set of rows
- rowRequestHandler
- .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
- int last = firstRowInViewPort + (int) (cache_rate * pageLength)
- + pageLength - 1;
- if (last >= totalRows) {
- last = totalRows - 1;
- }
- rowRequestHandler.setReqRows(last
- - rowRequestHandler.getReqFirstRow() + 1);
- rowRequestHandler.deferRowFetch();
- return;
- }
- if (preLimit < firstRendered) {
- // need some rows to the beginning of the rendered area
- rowRequestHandler
- .setReqFirstRow((int) (firstRowInViewPort - pageLength
- * cache_rate));
- rowRequestHandler.setReqRows(firstRendered
- - rowRequestHandler.getReqFirstRow());
- rowRequestHandler.deferRowFetch();
-
- return;
- }
- if (postLimit > lastRendered) {
- // need some rows to the end of the rendered area
- rowRequestHandler.setReqFirstRow(lastRendered + 1);
- rowRequestHandler.setReqRows((int) ((firstRowInViewPort
- + pageLength + pageLength * cache_rate) - lastRendered));
- rowRequestHandler.deferRowFetch();
- }
- }
-
- protected int calcFirstRowInViewPort() {
- return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
- }
-
- @Override
- public VScrollTableDropHandler getDropHandler() {
- return dropHandler;
- }
-
- private static class TableDDDetails {
- int overkey = -1;
- VerticalDropLocation dropLocation;
- String colkey;
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof TableDDDetails) {
- TableDDDetails other = (TableDDDetails) obj;
- return dropLocation == other.dropLocation
- && overkey == other.overkey
- && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
- }
- return false;
- }
-
- //
- // public int hashCode() {
- // return overkey;
- // }
- }
-
- public class VScrollTableDropHandler extends VAbstractDropHandler {
-
- private static final String ROWSTYLEBASE = "v-table-row-drag-";
- private TableDDDetails dropDetails;
- private TableDDDetails lastEmphasized;
-
- @Override
- public void dragEnter(VDragEvent drag) {
- updateDropDetails(drag);
- super.dragEnter(drag);
- }
-
- private void updateDropDetails(VDragEvent drag) {
- dropDetails = new TableDDDetails();
- Element elementOver = drag.getElementOver();
-
- VScrollTableRow row = Util.findWidget(elementOver, getRowClass());
- if (row != null) {
- dropDetails.overkey = row.rowKey;
- Element tr = row.getElement();
- Element element = elementOver;
- while (element != null && element.getParentElement() != tr) {
- element = (Element) element.getParentElement();
- }
- int childIndex = DOM.getChildIndex(tr, element);
- dropDetails.colkey = tHead.getHeaderCell(childIndex)
- .getColKey();
- dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
- row.getElement(), drag.getCurrentGwtEvent(), 0.2);
- }
-
- drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
- drag.getDropDetails().put(
- "detail",
- dropDetails.dropLocation != null ? dropDetails.dropLocation
- .toString() : null);
-
- }
-
- private Class<? extends Widget> getRowClass() {
- // get the row type this way to make dd work in derived
- // implementations
- return scrollBody.iterator().next().getClass();
- }
-
- @Override
- public void dragOver(VDragEvent drag) {
- TableDDDetails oldDetails = dropDetails;
- updateDropDetails(drag);
- if (!oldDetails.equals(dropDetails)) {
- deEmphasis();
- final TableDDDetails newDetails = dropDetails;
- VAcceptCallback cb = new VAcceptCallback() {
-
- @Override
- public void accepted(VDragEvent event) {
- if (newDetails.equals(dropDetails)) {
- dragAccepted(event);
- }
- /*
- * Else new target slot already defined, ignore
- */
- }
- };
- validate(cb, drag);
- }
- }
-
- @Override
- public void dragLeave(VDragEvent drag) {
- deEmphasis();
- super.dragLeave(drag);
- }
-
- @Override
- public boolean drop(VDragEvent drag) {
- deEmphasis();
- return super.drop(drag);
- }
-
- private void deEmphasis() {
- UIObject.setStyleName(getElement(),
- VScrollTable.this.getStylePrimaryName() + "-drag", false);
- if (lastEmphasized == null) {
- return;
- }
- for (Widget w : scrollBody.renderedRows) {
- VScrollTableRow row = (VScrollTableRow) w;
- if (lastEmphasized != null
- && row.rowKey == lastEmphasized.overkey) {
- String stylename = ROWSTYLEBASE
- + lastEmphasized.dropLocation.toString()
- .toLowerCase();
- VScrollTableRow.setStyleName(row.getElement(), stylename,
- false);
- lastEmphasized = null;
- return;
- }
- }
- }
-
- /**
- * TODO needs different drop modes ?? (on cells, on rows), now only
- * supports rows
- */
- private void emphasis(TableDDDetails details) {
- deEmphasis();
- UIObject.setStyleName(getElement(),
- VScrollTable.this.getStylePrimaryName() + "-drag", true);
- // iterate old and new emphasized row
- for (Widget w : scrollBody.renderedRows) {
- VScrollTableRow row = (VScrollTableRow) w;
- if (details != null && details.overkey == row.rowKey) {
- String stylename = ROWSTYLEBASE
- + details.dropLocation.toString().toLowerCase();
- VScrollTableRow.setStyleName(row.getElement(), stylename,
- true);
- lastEmphasized = details;
- return;
- }
- }
- }
-
- @Override
- protected void dragAccepted(VDragEvent drag) {
- emphasis(dropDetails);
- }
-
- @Override
- public ComponentConnector getConnector() {
- return ConnectorMap.get(client).getConnector(VScrollTable.this);
- }
-
- @Override
- public ApplicationConnection getApplicationConnection() {
- return client;
- }
-
- }
-
- protected VScrollTableRow getFocusedRow() {
- return focusedRow;
- }
-
- /**
- * Moves the selection head to a specific row
- *
- * @param row
- * The row to where the selection head should move
- * @return Returns true if focus was moved successfully, else false
- */
- public boolean setRowFocus(VScrollTableRow row) {
-
- if (!isSelectable()) {
- return false;
- }
-
- // Remove previous selection
- if (focusedRow != null && focusedRow != row) {
- focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
- }
-
- if (row != null) {
-
- // Apply focus style to new selection
- row.addStyleName(getStylePrimaryName() + "-focus");
-
- /*
- * Trying to set focus on already focused row
- */
- if (row == focusedRow) {
- return false;
- }
-
- // Set new focused row
- focusedRow = row;
-
- ensureRowIsVisible(row);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Ensures that the row is visible
- *
- * @param row
- * The row to ensure is visible
- */
- private void ensureRowIsVisible(VScrollTableRow row) {
- if (BrowserInfo.get().isTouchDevice()) {
- // Skip due to android devices that have broken scrolltop will may
- // get odd scrolling here.
- return;
- }
- Util.scrollIntoViewVertically(row.getElement());
- }
-
- /**
- * Handles the keyboard events handled by the table
- *
- * @param event
- * The keyboard event received
- * @return true iff the navigation event was handled
- */
- protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
- if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
- // Do not handle tab key
- return false;
- }
-
- // Down navigation
- if (!isSelectable() && keycode == getNavigationDownKey()) {
- scrollBodyPanel.setScrollPosition(scrollBodyPanel
- .getScrollPosition() + scrollingVelocity);
- return true;
- } else if (keycode == getNavigationDownKey()) {
- if (isMultiSelectModeAny() && moveFocusDown()) {
- selectFocusedRow(ctrl, shift);
-
- } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
- selectFocusedRow(ctrl, shift);
- }
- return true;
- }
-
- // Up navigation
- if (!isSelectable() && keycode == getNavigationUpKey()) {
- scrollBodyPanel.setScrollPosition(scrollBodyPanel
- .getScrollPosition() - scrollingVelocity);
- return true;
- } else if (keycode == getNavigationUpKey()) {
- if (isMultiSelectModeAny() && moveFocusUp()) {
- selectFocusedRow(ctrl, shift);
- } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
- selectFocusedRow(ctrl, shift);
- }
- return true;
- }
-
- if (keycode == getNavigationLeftKey()) {
- // Left navigation
- scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
- .getHorizontalScrollPosition() - scrollingVelocity);
- return true;
-
- } else if (keycode == getNavigationRightKey()) {
- // Right navigation
- scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
- .getHorizontalScrollPosition() + scrollingVelocity);
- return true;
- }
-
- // Select navigation
- if (isSelectable() && keycode == getNavigationSelectKey()) {
- if (isSingleSelectMode()) {
- boolean wasSelected = focusedRow.isSelected();
- deselectAll();
- if (!wasSelected || !nullSelectionAllowed) {
- focusedRow.toggleSelection();
- }
- } else {
- focusedRow.toggleSelection();
- removeRowFromUnsentSelectionRanges(focusedRow);
- }
-
- sendSelectedRows();
- return true;
- }
-
- // Page Down navigation
- if (keycode == getNavigationPageDownKey()) {
- if (isSelectable()) {
- /*
- * If selectable we plagiate MSW behaviour: first scroll to the
- * end of current view. If at the end, scroll down one page
- * length and keep the selected row in the bottom part of
- * visible area.
- */
- if (!isFocusAtTheEndOfTable()) {
- VScrollTableRow lastVisibleRowInViewPort = scrollBody
- .getRowByRowIndex(firstRowInViewPort
- + getFullyVisibleRowCount() - 1);
- if (lastVisibleRowInViewPort != null
- && lastVisibleRowInViewPort != focusedRow) {
- // focused row is not at the end of the table, move
- // focus and select the last visible row
- setRowFocus(lastVisibleRowInViewPort);
- selectFocusedRow(ctrl, shift);
- sendSelectedRows();
- } else {
- int indexOfToBeFocused = focusedRow.getIndex()
- + getFullyVisibleRowCount();
- if (indexOfToBeFocused >= totalRows) {
- indexOfToBeFocused = totalRows - 1;
- }
- VScrollTableRow toBeFocusedRow = scrollBody
- .getRowByRowIndex(indexOfToBeFocused);
-
- if (toBeFocusedRow != null) {
- /*
- * if the next focused row is rendered
- */
- setRowFocus(toBeFocusedRow);
- selectFocusedRow(ctrl, shift);
- // TODO needs scrollintoview ?
- sendSelectedRows();
- } else {
- // scroll down by pixels and return, to wait for
- // new rows, then select the last item in the
- // viewport
- selectLastItemInNextRender = true;
- multiselectPending = shift;
- scrollByPagelenght(1);
- }
- }
- }
- } else {
- /* No selections, go page down by scrolling */
- scrollByPagelenght(1);
- }
- return true;
- }
-
- // Page Up navigation
- if (keycode == getNavigationPageUpKey()) {
- if (isSelectable()) {
- /*
- * If selectable we plagiate MSW behaviour: first scroll to the
- * end of current view. If at the end, scroll down one page
- * length and keep the selected row in the bottom part of
- * visible area.
- */
- if (!isFocusAtTheBeginningOfTable()) {
- VScrollTableRow firstVisibleRowInViewPort = scrollBody
- .getRowByRowIndex(firstRowInViewPort);
- if (firstVisibleRowInViewPort != null
- && firstVisibleRowInViewPort != focusedRow) {
- // focus is not at the beginning of the table, move
- // focus and select the first visible row
- setRowFocus(firstVisibleRowInViewPort);
- selectFocusedRow(ctrl, shift);
- sendSelectedRows();
- } else {
- int indexOfToBeFocused = focusedRow.getIndex()
- - getFullyVisibleRowCount();
- if (indexOfToBeFocused < 0) {
- indexOfToBeFocused = 0;
- }
- VScrollTableRow toBeFocusedRow = scrollBody
- .getRowByRowIndex(indexOfToBeFocused);
-
- if (toBeFocusedRow != null) { // if the next focused row
- // is rendered
- setRowFocus(toBeFocusedRow);
- selectFocusedRow(ctrl, shift);
- // TODO needs scrollintoview ?
- sendSelectedRows();
- } else {
- // unless waiting for the next rowset already
- // scroll down by pixels and return, to wait for
- // new rows, then select the last item in the
- // viewport
- selectFirstItemInNextRender = true;
- multiselectPending = shift;
- scrollByPagelenght(-1);
- }
- }
- }
- } else {
- /* No selections, go page up by scrolling */
- scrollByPagelenght(-1);
- }
-
- return true;
- }
-
- // Goto start navigation
- if (keycode == getNavigationStartKey()) {
- scrollBodyPanel.setScrollPosition(0);
- if (isSelectable()) {
- if (focusedRow != null && focusedRow.getIndex() == 0) {
- return false;
- } else {
- VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
- .iterator().next();
- if (rowByRowIndex.getIndex() == 0) {
- setRowFocus(rowByRowIndex);
- selectFocusedRow(ctrl, shift);
- sendSelectedRows();
- } else {
- // first row of table will come in next row fetch
- if (ctrl) {
- focusFirstItemInNextRender = true;
- } else {
- selectFirstItemInNextRender = true;
- multiselectPending = shift;
- }
- }
- }
- }
- return true;
- }
-
- // Goto end navigation
- if (keycode == getNavigationEndKey()) {
- scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
- if (isSelectable()) {
- final int lastRendered = scrollBody.getLastRendered();
- if (lastRendered + 1 == totalRows) {
- VScrollTableRow rowByRowIndex = scrollBody
- .getRowByRowIndex(lastRendered);
- if (focusedRow != rowByRowIndex) {
- setRowFocus(rowByRowIndex);
- selectFocusedRow(ctrl, shift);
- sendSelectedRows();
- }
- } else {
- if (ctrl) {
- focusLastItemInNextRender = true;
- } else {
- selectLastItemInNextRender = true;
- multiselectPending = shift;
- }
- }
- }
- return true;
- }
-
- return false;
- }
-
- private boolean isFocusAtTheBeginningOfTable() {
- return focusedRow.getIndex() == 0;
- }
-
- private boolean isFocusAtTheEndOfTable() {
- return focusedRow.getIndex() + 1 >= totalRows;
- }
-
- private int getFullyVisibleRowCount() {
- return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody
- .getRowHeight());
- }
-
- private void scrollByPagelenght(int i) {
- int pixels = i * scrollBodyPanel.getOffsetHeight();
- int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
- if (newPixels < 0) {
- newPixels = 0;
- } // else if too high, NOP (all know browsers accept illegally big
- // values here)
- scrollBodyPanel.setScrollPosition(newPixels);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
- * .dom.client.FocusEvent)
- */
-
- @Override
- public void onFocus(FocusEvent event) {
- if (isFocusable()) {
- hasFocus = true;
-
- // Focus a row if no row is in focus
- if (focusedRow == null) {
- focusRowFromBody();
- } else {
- setRowFocus(focusedRow);
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
- * .dom.client.BlurEvent)
- */
-
- @Override
- public void onBlur(BlurEvent event) {
- hasFocus = false;
- navKeyDown = false;
-
- if (BrowserInfo.get().isIE()) {
- // IE sometimes moves focus to a clicked table cell...
- Element focusedElement = Util.getIEFocusedElement();
- if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) {
- // ..in that case, steal the focus back to the focus handler
- // but not if focus is in a child component instead (#7965)
- focus();
- return;
- }
- }
-
- if (isFocusable()) {
- // Unfocus any row
- setRowFocus(null);
- }
- }
-
- /**
- * Removes a key from a range if the key is found in a selected range
- *
- * @param key
- * The key to remove
- */
- private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
- Collection<SelectionRange> newRanges = null;
- for (Iterator<SelectionRange> iterator = selectedRowRanges.iterator(); iterator
- .hasNext();) {
- SelectionRange range = iterator.next();
- if (range.inRange(row)) {
- // Split the range if given row is in range
- Collection<SelectionRange> splitranges = range.split(row);
- if (newRanges == null) {
- newRanges = new ArrayList<SelectionRange>();
- }
- newRanges.addAll(splitranges);
- iterator.remove();
- }
- }
- if (newRanges != null) {
- selectedRowRanges.addAll(newRanges);
- }
- }
-
- /**
- * Can the Table be focused?
- *
- * @return True if the table can be focused, else false
- */
- public boolean isFocusable() {
- if (scrollBody != null && enabled) {
- return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable());
- }
- return false;
- }
-
- private boolean hasHorizontalScrollbar() {
- return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
- }
-
- private boolean hasVerticalScrollbar() {
- return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Focusable#focus()
- */
-
- @Override
- public void focus() {
- if (isFocusable()) {
- scrollBodyPanel.focus();
- }
- }
-
- /**
- * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the
- * component).
- *
- * If the component has no explicit tabIndex a zero is given (default
- * tabbing order based on dom hierarchy) or -1 if the component does not
- * need to gain focus. The component needs no focus if it has no scrollabars
- * (not scrollable) and not selectable. Note that in the future shortcut
- * actions may need focus.
- *
- */
- void setProperTabIndex() {
- int storedScrollTop = 0;
- int storedScrollLeft = 0;
-
- if (BrowserInfo.get().getOperaVersion() >= 11) {
- // Workaround for Opera scroll bug when changing tabIndex (#6222)
- storedScrollTop = scrollBodyPanel.getScrollPosition();
- storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
- }
-
- if (tabIndex == 0 && !isFocusable()) {
- scrollBodyPanel.setTabIndex(-1);
- } else {
- scrollBodyPanel.setTabIndex(tabIndex);
- }
-
- if (BrowserInfo.get().getOperaVersion() >= 11) {
- // Workaround for Opera scroll bug when changing tabIndex (#6222)
- scrollBodyPanel.setScrollPosition(storedScrollTop);
- scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
- }
- }
-
- public void startScrollingVelocityTimer() {
- if (scrollingVelocityTimer == null) {
- scrollingVelocityTimer = new Timer() {
-
- @Override
- public void run() {
- scrollingVelocity++;
- }
- };
- scrollingVelocityTimer.scheduleRepeating(100);
- }
- }
-
- public void cancelScrollingVelocityTimer() {
- if (scrollingVelocityTimer != null) {
- // Remove velocityTimer if it exists and the Table is disabled
- scrollingVelocityTimer.cancel();
- scrollingVelocityTimer = null;
- scrollingVelocity = 10;
- }
- }
-
- /**
- *
- * @param keyCode
- * @return true if the given keyCode is used by the table for navigation
- */
- private boolean isNavigationKey(int keyCode) {
- return keyCode == getNavigationUpKey()
- || keyCode == getNavigationLeftKey()
- || keyCode == getNavigationRightKey()
- || keyCode == getNavigationDownKey()
- || keyCode == getNavigationPageUpKey()
- || keyCode == getNavigationPageDownKey()
- || keyCode == getNavigationEndKey()
- || keyCode == getNavigationStartKey();
- }
-
- public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
- Scheduler.get().scheduleFinally(new ScheduledCommand() {
-
- @Override
- public void execute() {
- if (currentlyFocusedRow != null) {
- setRowFocus(currentlyFocusedRow);
- } else {
- VConsole.log("no row?");
- focusRowFromBody();
- }
- scrollBody.ensureFocus();
- }
- });
- }
-
- @Override
- public Action[] getActions() {
- if (bodyActionKeys == null) {
- return new Action[] {};
- }
- final Action[] actions = new Action[bodyActionKeys.length];
- for (int i = 0; i < actions.length; i++) {
- final String actionKey = bodyActionKeys[i];
- Action bodyAction = new TreeAction(this, null, actionKey);
- bodyAction.setCaption(getActionCaption(actionKey));
- bodyAction.setIconUrl(getActionIcon(actionKey));
- actions[i] = bodyAction;
- }
- return actions;
- }
-
- @Override
- public ApplicationConnection getClient() {
- return client;
- }
-
- @Override
- public String getPaintableId() {
- return paintableId;
- }
-
- /**
- * Add this to the element mouse down event by using element.setPropertyJSO
- * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
- * when the mouse is depressed in the mouse up event.
- *
- * @return Returns the JSO preventing text selection
- */
- private static native JavaScriptObject getPreventTextSelectionIEHack()
- /*-{
- return function(){ return false; };
- }-*/;
-
- public void triggerLazyColumnAdjustment(boolean now) {
- lazyAdjustColumnWidths.cancel();
- if (now) {
- lazyAdjustColumnWidths.run();
- } else {
- lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
- }
- }
-
- private boolean isDynamicWidth() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- return paintable.isUndefinedWidth();
- }
-
- private boolean isDynamicHeight() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- if (paintable == null) {
- // This should be refactored. As isDynamicHeight can be called from
- // a timer it is possible that the connector has been unregistered
- // when this method is called, causing getConnector to return null.
- return false;
- }
- return paintable.isUndefinedHeight();
- }
-
- private void debug(String msg) {
- if (enableDebug) {
- VConsole.error(msg);
- }
- }
-
- public Widget getWidgetForPaintable() {
- return this;
- }
-}
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractComponentContainerConnector;
+import com.vaadin.client.ui.VTabsheetBase;
import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
public abstract class TabsheetBaseConnector extends
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VTabsheet;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.tabsheet.TabsheetState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.tabsheet;
-
-import java.util.Iterator;
-import java.util.List;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.dom.client.TableElement;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.HasBlurHandlers;
-import com.google.gwt.event.dom.client.HasFocusHandlers;
-import com.google.gwt.event.dom.client.HasKeyDownHandlers;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.user.client.ui.impl.FocusImpl;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.ComponentStateUtil;
-import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
-import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
-
-public class VTabsheet extends VTabsheetBase implements Focusable,
- FocusHandler, BlurHandler, KeyDownHandler {
-
- private static class VCloseEvent {
- private Tab tab;
-
- VCloseEvent(Tab tab) {
- this.tab = tab;
- }
-
- public Tab getTab() {
- return tab;
- }
-
- }
-
- private interface VCloseHandler {
- public void onClose(VCloseEvent event);
- }
-
- /**
- * Representation of a single "tab" shown in the TabBar
- *
- */
- private static class Tab extends SimplePanel implements HasFocusHandlers,
- HasBlurHandlers, HasKeyDownHandlers {
- private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
- private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
- + "-first";
- private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
- + "-selected";
- private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
- + "-first";
- private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
- + "-disabled";
-
- private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
- private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
- + "-selected";
-
- private TabCaption tabCaption;
- Element td = getElement();
- private VCloseHandler closeHandler;
-
- private boolean enabledOnServer = true;
- private Element div;
- private TabBar tabBar;
- private boolean hiddenOnServer = false;
-
- private String styleName;
-
- private Tab(TabBar tabBar) {
- super(DOM.createTD());
- this.tabBar = tabBar;
- setStyleName(td, TD_CLASSNAME);
-
- div = DOM.createDiv();
- focusImpl.setTabIndex(td, -1);
- setStyleName(div, DIV_CLASSNAME);
-
- DOM.appendChild(td, div);
-
- tabCaption = new TabCaption(this, getTabsheet()
- .getApplicationConnection());
- add(tabCaption);
-
- addFocusHandler(getTabsheet());
- addBlurHandler(getTabsheet());
- addKeyDownHandler(getTabsheet());
- }
-
- public boolean isHiddenOnServer() {
- return hiddenOnServer;
- }
-
- public void setHiddenOnServer(boolean hiddenOnServer) {
- this.hiddenOnServer = hiddenOnServer;
- }
-
- @Override
- protected Element getContainerElement() {
- // Attach caption element to div, not td
- return div;
- }
-
- public boolean isEnabledOnServer() {
- return enabledOnServer;
- }
-
- public void setEnabledOnServer(boolean enabled) {
- enabledOnServer = enabled;
- setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
- if (!enabled) {
- focusImpl.setTabIndex(td, -1);
- }
- }
-
- public void addClickHandler(ClickHandler handler) {
- tabCaption.addClickHandler(handler);
- }
-
- public void setCloseHandler(VCloseHandler closeHandler) {
- this.closeHandler = closeHandler;
- }
-
- /**
- * Toggles the style names for the Tab
- *
- * @param selected
- * true if the Tab is selected
- * @param first
- * true if the Tab is the first visible Tab
- */
- public void setStyleNames(boolean selected, boolean first) {
- setStyleName(td, TD_FIRST_CLASSNAME, first);
- setStyleName(td, TD_SELECTED_CLASSNAME, selected);
- setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
- setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
- }
-
- public void setTabulatorIndex(int tabIndex) {
- focusImpl.setTabIndex(td, tabIndex);
- }
-
- public boolean isClosable() {
- return tabCaption.isClosable();
- }
-
- public void onClose() {
- closeHandler.onClose(new VCloseEvent(this));
- }
-
- public VTabsheet getTabsheet() {
- return tabBar.getTabsheet();
- }
-
- public void updateFromUIDL(UIDL tabUidl) {
- tabCaption.updateCaption(tabUidl);
-
- // Apply the styleName set for the tab
- String newStyleName = tabUidl
- .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
- // Find the nth td element
- if (newStyleName != null && newStyleName.length() != 0) {
- if (!newStyleName.equals(styleName)) {
- // If we have a new style name
- if (styleName != null && styleName.length() != 0) {
- // Remove old style name if present
- td.removeClassName(TD_CLASSNAME + "-" + styleName);
- }
- // Set new style name
- td.addClassName(TD_CLASSNAME + "-" + newStyleName);
- styleName = newStyleName;
- }
- } else if (styleName != null) {
- // Remove the set stylename if no stylename is present in the
- // uidl
- td.removeClassName(TD_CLASSNAME + "-" + styleName);
- styleName = null;
- }
- }
-
- public void recalculateCaptionWidth() {
- tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
- }
-
- @Override
- public HandlerRegistration addFocusHandler(FocusHandler handler) {
- return addDomHandler(handler, FocusEvent.getType());
- }
-
- @Override
- public HandlerRegistration addBlurHandler(BlurHandler handler) {
- return addDomHandler(handler, BlurEvent.getType());
- }
-
- @Override
- public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
- return addDomHandler(handler, KeyDownEvent.getType());
- }
-
- public void focus() {
- focusImpl.focus(td);
- }
-
- public void blur() {
- focusImpl.blur(td);
- }
- }
-
- public static class TabCaption extends VCaption {
-
- private boolean closable = false;
- private Element closeButton;
- private Tab tab;
- private ApplicationConnection client;
-
- TabCaption(Tab tab, ApplicationConnection client) {
- super(client);
- this.client = client;
- this.tab = tab;
- }
-
- public boolean updateCaption(UIDL uidl) {
- if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION)) {
- setTooltipInfo(new TooltipInfo(
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)));
- } else {
- setTooltipInfo(null);
- }
-
- // TODO need to call this instead of super because the caption does
- // not have an owner
- boolean ret = updateCaptionWithoutOwner(
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
- uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
- uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
-
- setClosable(uidl.hasAttribute("closable"));
-
- return ret;
- }
-
- private VTabsheet getTabsheet() {
- return tab.getTabsheet();
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (closable && event.getTypeInt() == Event.ONCLICK
- && event.getEventTarget().cast() == closeButton) {
- tab.onClose();
- event.stopPropagation();
- event.preventDefault();
- }
-
- super.onBrowserEvent(event);
-
- if (event.getTypeInt() == Event.ONLOAD) {
- getTabsheet().tabSizeMightHaveChanged(getTab());
- }
- }
-
- public Tab getTab() {
- return tab;
- }
-
- public void setClosable(boolean closable) {
- this.closable = closable;
- if (closable && closeButton == null) {
- closeButton = DOM.createSpan();
- closeButton.setInnerHTML("×");
- closeButton
- .setClassName(VTabsheet.CLASSNAME + "-caption-close");
- getElement().insertBefore(closeButton,
- getElement().getLastChild());
- } else if (!closable && closeButton != null) {
- getElement().removeChild(closeButton);
- closeButton = null;
- }
- if (closable) {
- addStyleDependentName("closable");
- } else {
- removeStyleDependentName("closable");
- }
- }
-
- public boolean isClosable() {
- return closable;
- }
-
- @Override
- public int getRequiredWidth() {
- int width = super.getRequiredWidth();
- if (closeButton != null) {
- width += Util.getRequiredWidth(closeButton);
- }
- return width;
- }
-
- public Element getCloseButton() {
- return closeButton;
- }
-
- }
-
- static class TabBar extends ComplexPanel implements ClickHandler,
- VCloseHandler {
-
- private final Element tr = DOM.createTR();
-
- private final Element spacerTd = DOM.createTD();
-
- private Tab selected;
-
- private VTabsheet tabsheet;
-
- TabBar(VTabsheet tabsheet) {
- this.tabsheet = tabsheet;
-
- Element el = DOM.createTable();
- Element tbody = DOM.createTBody();
- DOM.appendChild(el, tbody);
- DOM.appendChild(tbody, tr);
- setStyleName(spacerTd, CLASSNAME + "-spacertd");
- DOM.appendChild(tr, spacerTd);
- DOM.appendChild(spacerTd, DOM.createDiv());
- setElement(el);
- }
-
- @Override
- public void onClose(VCloseEvent event) {
- Tab tab = event.getTab();
- if (!tab.isEnabledOnServer()) {
- return;
- }
- int tabIndex = getWidgetIndex(tab);
- getTabsheet().sendTabClosedEvent(tabIndex);
- }
-
- protected Element getContainerElement() {
- return tr;
- }
-
- public int getTabCount() {
- return getWidgetCount();
- }
-
- public Tab addTab() {
- Tab t = new Tab(this);
- int tabIndex = getTabCount();
-
- // Logical attach
- insert(t, tr, tabIndex, true);
-
- if (tabIndex == 0) {
- // Set the "first" style
- t.setStyleNames(false, true);
- }
-
- t.addClickHandler(this);
- t.setCloseHandler(this);
-
- return t;
- }
-
- @Override
- public void onClick(ClickEvent event) {
- TabCaption caption = (TabCaption) event.getSource();
- Element targetElement = event.getNativeEvent().getEventTarget()
- .cast();
- // the tab should not be focused if the close button was clicked
- if (targetElement == caption.getCloseButton()) {
- return;
- }
-
- int index = getWidgetIndex(caption.getParent());
- // IE needs explicit focus()
- if (BrowserInfo.get().isIE()) {
- getTabsheet().focus();
- }
- getTabsheet().onTabSelected(index);
- }
-
- public VTabsheet getTabsheet() {
- return tabsheet;
- }
-
- public Tab getTab(int index) {
- if (index < 0 || index >= getTabCount()) {
- return null;
- }
- return (Tab) super.getWidget(index);
- }
-
- public void selectTab(int index) {
- final Tab newSelected = getTab(index);
- final Tab oldSelected = selected;
-
- newSelected.setStyleNames(true, isFirstVisibleTab(index));
- newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
-
- if (oldSelected != null && oldSelected != newSelected) {
- oldSelected.setStyleNames(false,
- isFirstVisibleTab(getWidgetIndex(oldSelected)));
- oldSelected.setTabulatorIndex(-1);
- }
-
- // Update the field holding the currently selected tab
- selected = newSelected;
-
- // The selected tab might need more (or less) space
- newSelected.recalculateCaptionWidth();
- getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
- }
-
- public void removeTab(int i) {
- Tab tab = getTab(i);
- if (tab == null) {
- return;
- }
-
- remove(tab);
-
- /*
- * If this widget was selected we need to unmark it as the last
- * selected
- */
- if (tab == selected) {
- selected = null;
- }
-
- // FIXME: Shouldn't something be selected instead?
- }
-
- private boolean isFirstVisibleTab(int index) {
- return getFirstVisibleTab() == index;
- }
-
- /**
- * Returns the index of the first visible tab
- *
- * @return
- */
- private int getFirstVisibleTab() {
- return getNextVisibleTab(-1);
- }
-
- /**
- * Find the next visible tab. Returns -1 if none is found.
- *
- * @param i
- * @return
- */
- private int getNextVisibleTab(int i) {
- int tabs = getTabCount();
- do {
- i++;
- } while (i < tabs && getTab(i).isHiddenOnServer());
-
- if (i == tabs) {
- return -1;
- } else {
- return i;
- }
- }
-
- /**
- * Find the previous visible tab. Returns -1 if none is found.
- *
- * @param i
- * @return
- */
- private int getPreviousVisibleTab(int i) {
- do {
- i--;
- } while (i >= 0 && getTab(i).isHiddenOnServer());
-
- return i;
-
- }
-
- public int scrollLeft(int currentFirstVisible) {
- int prevVisible = getPreviousVisibleTab(currentFirstVisible);
- if (prevVisible == -1) {
- return -1;
- }
-
- Tab newFirst = getTab(prevVisible);
- newFirst.setVisible(true);
- newFirst.recalculateCaptionWidth();
-
- return prevVisible;
- }
-
- public int scrollRight(int currentFirstVisible) {
- int nextVisible = getNextVisibleTab(currentFirstVisible);
- if (nextVisible == -1) {
- return -1;
- }
- Tab currentFirst = getTab(currentFirstVisible);
- currentFirst.setVisible(false);
- currentFirst.recalculateCaptionWidth();
- return nextVisible;
- }
- }
-
- public static final String CLASSNAME = "v-tabsheet";
-
- public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
- public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
-
- final Element tabs; // tabbar and 'scroller' container
- Tab focusedTab;
- /**
- * The tabindex property (position in the browser's focus cycle.) Named like
- * this to avoid confusion with activeTabIndex.
- */
- int tabulatorIndex = 0;
-
- private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
-
- private final Element scroller; // tab-scroller element
- private final Element scrollerNext; // tab-scroller next button element
- private final Element scrollerPrev; // tab-scroller prev button element
-
- /**
- * The index of the first visible tab (when scrolled)
- */
- private int scrollerIndex = 0;
-
- final TabBar tb = new TabBar(this);
- final VTabsheetPanel tp = new VTabsheetPanel();
- final Element contentNode;
-
- private final Element deco;
-
- boolean waitingForResponse;
-
- private String currentStyle;
-
- /**
- * @return Whether the tab could be selected or not.
- */
- private boolean onTabSelected(final int tabIndex) {
- Tab tab = tb.getTab(tabIndex);
- if (client == null || disabled || waitingForResponse) {
- return false;
- }
- if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
- return false;
- }
- if (activeTabIndex != tabIndex) {
- tb.selectTab(tabIndex);
-
- // If this TabSheet already has focus, set the new selected tab
- // as focused.
- if (focusedTab != null) {
- focusedTab = tab;
- }
-
- addStyleDependentName("loading");
- // Hide the current contents so a loading indicator can be shown
- // instead
- Widget currentlyDisplayedWidget = tp.getWidget(tp
- .getVisibleWidget());
- currentlyDisplayedWidget.getElement().getParentElement().getStyle()
- .setVisibility(Visibility.HIDDEN);
- client.updateVariable(id, "selected", tabKeys.get(tabIndex)
- .toString(), true);
- waitingForResponse = true;
- }
- // Note that we return true when tabIndex == activeTabIndex; the active
- // tab could be selected, it's just a no-op.
- return true;
- }
-
- public ApplicationConnection getApplicationConnection() {
- return client;
- }
-
- public void tabSizeMightHaveChanged(Tab tab) {
- // icon onloads may change total width of tabsheet
- if (isDynamicWidth()) {
- updateDynamicWidth();
- }
- updateTabScroller();
-
- }
-
- void sendTabClosedEvent(int tabIndex) {
- client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
- }
-
- boolean isDynamicWidth() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- return paintable.isUndefinedWidth();
- }
-
- boolean isDynamicHeight() {
- ComponentConnector paintable = ConnectorMap.get(client).getConnector(
- this);
- return paintable.isUndefinedHeight();
- }
-
- public VTabsheet() {
- super(CLASSNAME);
-
- addHandler(this, FocusEvent.getType());
- addHandler(this, BlurEvent.getType());
-
- // Tab scrolling
- DOM.setStyleAttribute(getElement(), "overflow", "hidden");
- tabs = DOM.createDiv();
- DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
- scroller = DOM.createDiv();
-
- DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
- scrollerPrev = DOM.createButton();
- DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
- + "Prev");
- DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
- scrollerNext = DOM.createButton();
- DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
- + "Next");
- DOM.sinkEvents(scrollerNext, Event.ONCLICK);
- DOM.appendChild(getElement(), tabs);
-
- // Tabs
- tp.setStyleName(CLASSNAME + "-tabsheetpanel");
- contentNode = DOM.createDiv();
-
- deco = DOM.createDiv();
-
- addStyleDependentName("loading"); // Indicate initial progress
- tb.setStyleName(CLASSNAME + "-tabs");
- DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
- DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
-
- add(tb, tabs);
- DOM.appendChild(scroller, scrollerPrev);
- DOM.appendChild(scroller, scrollerNext);
-
- DOM.appendChild(getElement(), contentNode);
- add(tp, contentNode);
- DOM.appendChild(getElement(), deco);
-
- DOM.appendChild(tabs, scroller);
-
- // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
- // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
- // .getFirstChild(tb.getElement()))), "display", "none");
-
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (event.getTypeInt() == Event.ONCLICK) {
- // Tab scrolling
- if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
- int newFirstIndex = tb.scrollLeft(scrollerIndex);
- if (newFirstIndex != -1) {
- scrollerIndex = newFirstIndex;
- updateTabScroller();
- }
- event.stopPropagation();
- return;
- } else if (isClippedTabs()
- && DOM.eventGetTarget(event) == scrollerNext) {
- int newFirstIndex = tb.scrollRight(scrollerIndex);
-
- if (newFirstIndex != -1) {
- scrollerIndex = newFirstIndex;
- updateTabScroller();
- }
- event.stopPropagation();
- return;
- }
- }
- super.onBrowserEvent(event);
- }
-
- /**
- * Checks if the tab with the selected index has been scrolled out of the
- * view (on the left side).
- *
- * @param index
- * @return
- */
- private boolean scrolledOutOfView(int index) {
- return scrollerIndex > index;
- }
-
- void handleStyleNames(UIDL uidl, ComponentState state) {
- // Add proper stylenames for all elements (easier to prevent unwanted
- // style inheritance)
- if (ComponentStateUtil.hasStyles(state)) {
- final List<String> styles = state.styles;
- if (!currentStyle.equals(styles.toString())) {
- currentStyle = styles.toString();
- final String tabsBaseClass = TABS_CLASSNAME;
- String tabsClass = tabsBaseClass;
- final String contentBaseClass = CLASSNAME + "-content";
- String contentClass = contentBaseClass;
- final String decoBaseClass = CLASSNAME + "-deco";
- String decoClass = decoBaseClass;
- for (String style : styles) {
- tb.addStyleDependentName(style);
- tabsClass += " " + tabsBaseClass + "-" + style;
- contentClass += " " + contentBaseClass + "-" + style;
- decoClass += " " + decoBaseClass + "-" + style;
- }
- DOM.setElementProperty(tabs, "className", tabsClass);
- DOM.setElementProperty(contentNode, "className", contentClass);
- DOM.setElementProperty(deco, "className", decoClass);
- borderW = -1;
- }
- } else {
- tb.setStyleName(CLASSNAME + "-tabs");
- DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
- DOM.setElementProperty(contentNode, "className", CLASSNAME
- + "-content");
- DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
- }
-
- if (uidl.hasAttribute("hidetabs")) {
- tb.setVisible(false);
- addStyleName(CLASSNAME + "-hidetabs");
- } else {
- tb.setVisible(true);
- removeStyleName(CLASSNAME + "-hidetabs");
- }
- }
-
- void updateDynamicWidth() {
- // Find width consumed by tabs
- TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
- .getRows().getItem(0).getCells().getItem(tb.getTabCount());
-
- int spacerWidth = spacerCell.getOffsetWidth();
- DivElement div = (DivElement) spacerCell.getFirstChildElement();
-
- int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
-
- int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
-
- // Find content width
- Style style = tp.getElement().getStyle();
- String overflow = style.getProperty("overflow");
- style.setProperty("overflow", "hidden");
- style.setPropertyPx("width", tabsWidth);
-
- boolean hasTabs = tp.getWidgetCount() > 0;
-
- Style wrapperstyle = null;
- if (hasTabs) {
- wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
- .getParentElement().getStyle();
- wrapperstyle.setPropertyPx("width", tabsWidth);
- }
- // Get content width from actual widget
-
- int contentWidth = 0;
- if (hasTabs) {
- contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
- }
- style.setProperty("overflow", overflow);
-
- // Set widths to max(tabs,content)
- if (tabsWidth < contentWidth) {
- tabsWidth = contentWidth;
- }
-
- int outerWidth = tabsWidth + getContentAreaBorderWidth();
-
- tabs.getStyle().setPropertyPx("width", outerWidth);
- style.setPropertyPx("width", tabsWidth);
- if (hasTabs) {
- wrapperstyle.setPropertyPx("width", tabsWidth);
- }
-
- contentNode.getStyle().setPropertyPx("width", tabsWidth);
- super.setWidth(outerWidth + "px");
- updateOpenTabSize();
- }
-
- @Override
- protected void renderTab(final UIDL tabUidl, int index, boolean selected,
- boolean hidden) {
- Tab tab = tb.getTab(index);
- if (tab == null) {
- tab = tb.addTab();
- }
- tab.updateFromUIDL(tabUidl);
- tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
- tab.setHiddenOnServer(hidden);
-
- if (scrolledOutOfView(index)) {
- // Should not set tabs visible if they are scrolled out of view
- hidden = true;
- }
- // Set the current visibility of the tab (in the browser)
- tab.setVisible(!hidden);
-
- /*
- * Force the width of the caption container so the content will not wrap
- * and tabs won't be too narrow in certain browsers
- */
- tab.recalculateCaptionWidth();
-
- UIDL tabContentUIDL = null;
- ComponentConnector tabContentPaintable = null;
- Widget tabContentWidget = null;
- if (tabUidl.getChildCount() > 0) {
- tabContentUIDL = tabUidl.getChildUIDL(0);
- tabContentPaintable = client.getPaintable(tabContentUIDL);
- tabContentWidget = tabContentPaintable.getWidget();
- }
-
- if (tabContentPaintable != null) {
- /* This is a tab with content information */
-
- int oldIndex = tp.getWidgetIndex(tabContentWidget);
- if (oldIndex != -1 && oldIndex != index) {
- /*
- * The tab has previously been rendered in another position so
- * we must move the cached content to correct position
- */
- tp.insert(tabContentWidget, index);
- }
- } else {
- /* A tab whose content has not yet been loaded */
-
- /*
- * Make sure there is a corresponding empty tab in tp. The same
- * operation as the moving above but for not-loaded tabs.
- */
- if (index < tp.getWidgetCount()) {
- Widget oldWidget = tp.getWidget(index);
- if (!(oldWidget instanceof PlaceHolder)) {
- tp.insert(new PlaceHolder(), index);
- }
- }
-
- }
-
- if (selected) {
- renderContent(tabContentUIDL);
- tb.selectTab(index);
- } else {
- if (tabContentUIDL != null) {
- // updating a drawn child on hidden tab
- if (tp.getWidgetIndex(tabContentWidget) < 0) {
- tp.insert(tabContentWidget, index);
- }
- } else if (tp.getWidgetCount() <= index) {
- tp.add(new PlaceHolder());
- }
- }
- }
-
- public class PlaceHolder extends VLabel {
- public PlaceHolder() {
- super("");
- }
- }
-
- @Override
- protected void selectTab(int index, final UIDL contentUidl) {
- if (index != activeTabIndex) {
- activeTabIndex = index;
- tb.selectTab(activeTabIndex);
- }
- renderContent(contentUidl);
- }
-
- private void renderContent(final UIDL contentUIDL) {
- final ComponentConnector content = client.getPaintable(contentUIDL);
- Widget newWidget = content.getWidget();
- if (tp.getWidgetCount() > activeTabIndex) {
- Widget old = tp.getWidget(activeTabIndex);
- if (old != newWidget) {
- tp.remove(activeTabIndex);
- ConnectorMap paintableMap = ConnectorMap.get(client);
- if (paintableMap.isConnector(old)) {
- paintableMap.unregisterConnector(paintableMap
- .getConnector(old));
- }
- tp.insert(content.getWidget(), activeTabIndex);
- }
- } else {
- tp.add(content.getWidget());
- }
-
- tp.showWidget(activeTabIndex);
-
- VTabsheet.this.iLayout();
- /*
- * The size of a cached, relative sized component must be updated to
- * report correct size to updateOpenTabSize().
- */
- if (contentUIDL.getBooleanAttribute("cached")) {
- client.handleComponentRelativeSize(content.getWidget());
- }
- updateOpenTabSize();
- VTabsheet.this.removeStyleDependentName("loading");
- }
-
- void updateContentNodeHeight() {
- if (!isDynamicHeight()) {
- int contentHeight = getOffsetHeight();
- contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
- contentHeight -= tb.getOffsetHeight();
- if (contentHeight < 0) {
- contentHeight = 0;
- }
-
- // Set proper values for content element
- DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
- } else {
- DOM.setStyleAttribute(contentNode, "height", "");
- }
- }
-
- public void iLayout() {
- updateTabScroller();
- }
-
- /**
- * Sets the size of the visible tab (component). As the tab is set to
- * position: absolute (to work around a firefox flickering bug) we must keep
- * this up-to-date by hand.
- */
- void updateOpenTabSize() {
- /*
- * The overflow=auto element must have a height specified, otherwise it
- * will be just as high as the contents and no scrollbars will appear
- */
- int height = -1;
- int width = -1;
- int minWidth = 0;
-
- if (!isDynamicHeight()) {
- height = contentNode.getOffsetHeight();
- }
- if (!isDynamicWidth()) {
- width = contentNode.getOffsetWidth() - getContentAreaBorderWidth();
- } else {
- /*
- * If the tabbar is wider than the content we need to use the tabbar
- * width as minimum width so scrollbars get placed correctly (at the
- * right edge).
- */
- minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
- }
- tp.fixVisibleTabSize(width, height, minWidth);
-
- }
-
- /**
- * Layouts the tab-scroller elements, and applies styles.
- */
- private void updateTabScroller() {
- if (!isDynamicWidth()) {
- ComponentConnector paintable = ConnectorMap.get(client)
- .getConnector(this);
- DOM.setStyleAttribute(tabs, "width", paintable.getState().width);
- }
-
- // Make sure scrollerIndex is valid
- if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
- scrollerIndex = tb.getFirstVisibleTab();
- } else if (tb.getTabCount() > 0
- && tb.getTab(scrollerIndex).isHiddenOnServer()) {
- scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
- }
-
- boolean scrolled = isScrolledTabs();
- boolean clipped = isClippedTabs();
- if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
- DOM.setStyleAttribute(scroller, "display", "");
- DOM.setElementProperty(scrollerPrev, "className",
- SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
- DOM.setElementProperty(scrollerNext, "className",
- SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
- } else {
- DOM.setStyleAttribute(scroller, "display", "none");
- }
-
- if (BrowserInfo.get().isSafari()) {
- // fix tab height for safari, bugs sometimes if tabs contain icons
- String property = tabs.getStyle().getProperty("height");
- if (property == null || property.equals("")) {
- tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
- }
- /*
- * another hack for webkits. tabscroller sometimes drops without
- * "shaking it" reproducable in
- * com.vaadin.tests.components.tabsheet.TabSheetIcons
- */
- final Style style = scroller.getStyle();
- style.setProperty("whiteSpace", "normal");
- Scheduler.get().scheduleDeferred(new Command() {
-
- @Override
- public void execute() {
- style.setProperty("whiteSpace", "");
- }
- });
- }
-
- }
-
- void showAllTabs() {
- scrollerIndex = tb.getFirstVisibleTab();
- for (int i = 0; i < tb.getTabCount(); i++) {
- Tab t = tb.getTab(i);
- if (!t.isHiddenOnServer()) {
- t.setVisible(true);
- }
- }
- }
-
- private boolean isScrolledTabs() {
- return scrollerIndex > tb.getFirstVisibleTab();
- }
-
- private boolean isClippedTabs() {
- return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
- .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
- - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
- }
-
- private boolean isClipped(Tab tab) {
- return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft()
- + getOffsetWidth() - scroller.getOffsetWidth();
- }
-
- @Override
- protected void clearPaintables() {
-
- int i = tb.getTabCount();
- while (i > 0) {
- tb.removeTab(--i);
- }
- tp.clear();
-
- }
-
- @Override
- protected Iterator<Widget> getWidgetIterator() {
- return tp.iterator();
- }
-
- private int borderW = -1;
-
- int getContentAreaBorderWidth() {
- if (borderW < 0) {
- borderW = Util.measureHorizontalBorder(contentNode);
- }
- return borderW;
- }
-
- @Override
- protected int getTabCount() {
- return tb.getTabCount();
- }
-
- @Override
- protected ComponentConnector getTab(int index) {
- if (tp.getWidgetCount() > index) {
- Widget widget = tp.getWidget(index);
- return ConnectorMap.get(client).getConnector(widget);
- }
- return null;
- }
-
- @Override
- protected void removeTab(int index) {
- tb.removeTab(index);
- /*
- * This must be checked because renderTab automatically removes the
- * active tab content when it changes
- */
- if (tp.getWidgetCount() > index) {
- tp.remove(index);
- }
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- if (focusedTab != null && event.getSource() instanceof Tab) {
- focusedTab = null;
- if (client.hasEventListeners(this, EventId.BLUR)) {
- client.updateVariable(id, EventId.BLUR, "", true);
- }
- }
- }
-
- @Override
- public void onFocus(FocusEvent event) {
- if (focusedTab == null && event.getSource() instanceof Tab) {
- focusedTab = (Tab) event.getSource();
- if (client.hasEventListeners(this, EventId.FOCUS)) {
- client.updateVariable(id, EventId.FOCUS, "", true);
- }
- }
- }
-
- @Override
- public void focus() {
- tb.getTab(activeTabIndex).focus();
- }
-
- public void blur() {
- tb.getTab(activeTabIndex).blur();
- }
-
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (event.getSource() instanceof Tab) {
- int keycode = event.getNativeEvent().getKeyCode();
-
- if (keycode == getPreviousTabKey()) {
- selectPreviousTab();
- } else if (keycode == getNextTabKey()) {
- selectNextTab();
- } else if (keycode == getCloseTabKey()) {
- Tab tab = tb.getTab(activeTabIndex);
- if (tab.isClosable()) {
- tab.onClose();
- }
- }
- }
- }
-
- /**
- * @return The key code of the keyboard shortcut that selects the previous
- * tab in a focused tabsheet.
- */
- protected int getPreviousTabKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * @return The key code of the keyboard shortcut that selects the next tab
- * in a focused tabsheet.
- */
- protected int getNextTabKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- /**
- * @return The key code of the keyboard shortcut that closes the currently
- * selected tab in a focused tabsheet.
- */
- protected int getCloseTabKey() {
- return KeyCodes.KEY_DELETE;
- }
-
- private void selectPreviousTab() {
- int newTabIndex = activeTabIndex;
- // Find the previous visible and enabled tab if any.
- do {
- newTabIndex--;
- } while (newTabIndex >= 0 && !onTabSelected(newTabIndex));
-
- if (newTabIndex >= 0) {
- activeTabIndex = newTabIndex;
- if (isScrolledTabs()) {
- // Scroll until the new active tab is visible
- int newScrollerIndex = scrollerIndex;
- while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
- && newScrollerIndex != -1) {
- newScrollerIndex = tb.scrollLeft(newScrollerIndex);
- }
- scrollerIndex = newScrollerIndex;
- updateTabScroller();
- }
- }
- }
-
- private void selectNextTab() {
- int newTabIndex = activeTabIndex;
- // Find the next visible and enabled tab if any.
- do {
- newTabIndex++;
- } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex));
-
- if (newTabIndex < getTabCount()) {
- activeTabIndex = newTabIndex;
- if (isClippedTabs()) {
- // Scroll until the new active tab is completely visible
- int newScrollerIndex = scrollerIndex;
- while (isClipped(tb.getTab(activeTabIndex))
- && newScrollerIndex != -1) {
- newScrollerIndex = tb.scrollRight(newScrollerIndex);
- }
- scrollerIndex = newScrollerIndex;
- updateTabScroller();
- }
- }
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-package com.vaadin.client.ui.tabsheet;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.UIDL;
-
-public abstract class VTabsheetBase extends ComplexPanel {
-
- protected String id;
- protected ApplicationConnection client;
-
- protected final ArrayList<String> tabKeys = new ArrayList<String>();
- protected int activeTabIndex = 0;
- protected boolean disabled;
- protected boolean readonly;
- protected Set<String> disabledTabKeys = new HashSet<String>();
-
- public VTabsheetBase(String classname) {
- setElement(DOM.createDiv());
- setStyleName(classname);
- }
-
- /**
- * @return a list of currently shown Widgets
- */
- abstract protected Iterator<Widget> getWidgetIterator();
-
- /**
- * Clears current tabs and contents
- */
- abstract protected void clearPaintables();
-
- /**
- * Implement in extending classes. This method should render needed elements
- * and set the visibility of the tab according to the 'selected' parameter.
- */
- protected abstract void renderTab(final UIDL tabUidl, int index,
- boolean selected, boolean hidden);
-
- /**
- * Implement in extending classes. This method should render any previously
- * non-cached content and set the activeTabIndex property to the specified
- * index.
- */
- protected abstract void selectTab(int index, final UIDL contentUidl);
-
- /**
- * Implement in extending classes. This method should return the number of
- * tabs currently rendered.
- */
- protected abstract int getTabCount();
-
- /**
- * Implement in extending classes. This method should return the Paintable
- * corresponding to the given index.
- */
- protected abstract ComponentConnector getTab(int index);
-
- /**
- * Implement in extending classes. This method should remove the rendered
- * tab with the specified index.
- */
- protected abstract void removeTab(int index);
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.tabsheet;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-
-/**
- * A panel that displays all of its child widgets in a 'deck', where only one
- * can be visible at a time. It is used by
- * {@link com.vaadin.client.ui.tabsheet.VTabsheet}.
- *
- * This class has the same basic functionality as the GWT DeckPanel
- * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it
- * doesn't manipulate the child widgets' width and height attributes.
- */
-public class VTabsheetPanel extends ComplexPanel {
-
- private Widget visibleWidget;
-
- private final TouchScrollHandler touchScrollHandler;
-
- /**
- * Creates an empty tabsheet panel.
- */
- public VTabsheetPanel() {
- setElement(DOM.createDiv());
- touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
- }
-
- /**
- * Adds the specified widget to the deck.
- *
- * @param w
- * the widget to be added
- */
- @Override
- public void add(Widget w) {
- Element el = createContainerElement();
- DOM.appendChild(getElement(), el);
- super.add(w, el);
- }
-
- private Element createContainerElement() {
- Element el = DOM.createDiv();
- DOM.setStyleAttribute(el, "position", "absolute");
- hide(el);
- touchScrollHandler.addElement(el);
- return el;
- }
-
- /**
- * Gets the index of the currently-visible widget.
- *
- * @return the visible widget's index
- */
- public int getVisibleWidget() {
- return getWidgetIndex(visibleWidget);
- }
-
- /**
- * Inserts a widget before the specified index.
- *
- * @param w
- * the widget to be inserted
- * @param beforeIndex
- * the index before which it will be inserted
- * @throws IndexOutOfBoundsException
- * if <code>beforeIndex</code> is out of range
- */
- public void insert(Widget w, int beforeIndex) {
- Element el = createContainerElement();
- DOM.insertChild(getElement(), el, beforeIndex);
- super.insert(w, el, beforeIndex, false);
- }
-
- @Override
- public boolean remove(Widget w) {
- Element child = w.getElement();
- Element parent = null;
- if (child != null) {
- parent = DOM.getParent(child);
- }
- final boolean removed = super.remove(w);
- if (removed) {
- if (visibleWidget == w) {
- visibleWidget = null;
- }
- if (parent != null) {
- DOM.removeChild(getElement(), parent);
- }
- touchScrollHandler.removeElement(parent);
- }
- return removed;
- }
-
- /**
- * Shows the widget at the specified index. This causes the currently-
- * visible widget to be hidden.
- *
- * @param index
- * the index of the widget to be shown
- */
- public void showWidget(int index) {
- checkIndexBoundsForAccess(index);
- Widget newVisible = getWidget(index);
- if (visibleWidget != newVisible) {
- if (visibleWidget != null) {
- hide(DOM.getParent(visibleWidget.getElement()));
- }
- visibleWidget = newVisible;
- touchScrollHandler.setElements(visibleWidget.getElement()
- .getParentElement());
- }
- // Always ensure the selected tab is visible. If server prevents a tab
- // change we might end up here with visibleWidget == newVisible but its
- // parent is still hidden.
- unHide(DOM.getParent(visibleWidget.getElement()));
- }
-
- private void hide(Element e) {
- DOM.setStyleAttribute(e, "visibility", "hidden");
- DOM.setStyleAttribute(e, "top", "-100000px");
- DOM.setStyleAttribute(e, "left", "-100000px");
- }
-
- private void unHide(Element e) {
- DOM.setStyleAttribute(e, "top", "0px");
- DOM.setStyleAttribute(e, "left", "0px");
- DOM.setStyleAttribute(e, "visibility", "");
- }
-
- public void fixVisibleTabSize(int width, int height, int minWidth) {
- if (visibleWidget == null) {
- return;
- }
-
- boolean dynamicHeight = false;
-
- if (height < 0) {
- height = visibleWidget.getOffsetHeight();
- dynamicHeight = true;
- }
- if (width < 0) {
- width = visibleWidget.getOffsetWidth();
- }
- if (width < minWidth) {
- width = minWidth;
- }
-
- Element wrapperDiv = (Element) visibleWidget.getElement()
- .getParentElement();
-
- // width first
- getElement().getStyle().setPropertyPx("width", width);
- wrapperDiv.getStyle().setPropertyPx("width", width);
-
- if (dynamicHeight) {
- // height of widget might have changed due wrapping
- height = visibleWidget.getOffsetHeight();
- }
- // v-tabsheet-tabsheetpanel height
- getElement().getStyle().setPropertyPx("height", height);
-
- // widget wrapper height
- if (dynamicHeight) {
- wrapperDiv.getStyle().clearHeight();
- } else {
- // widget wrapper height
- wrapperDiv.getStyle().setPropertyPx("height", height);
- }
- }
-
- public void replaceComponent(Widget oldComponent, Widget newComponent) {
- boolean isVisible = (visibleWidget == oldComponent);
- int widgetIndex = getWidgetIndex(oldComponent);
- remove(oldComponent);
- insert(newComponent, widgetIndex);
- if (isVisible) {
- showWidget(widgetIndex);
- }
- }
-}
package com.vaadin.client.ui.textarea;
+import com.vaadin.client.ui.VTextArea;
import com.vaadin.client.ui.textfield.TextFieldConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.textarea.TextAreaState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.textarea;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.TextAreaElement;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.textfield.VTextField;
-
-/**
- * This class represents a multiline textfield (textarea).
- *
- * TODO consider replacing this with a RichTextArea based implementation. IE
- * does not support CSS height for textareas in Strict mode :-(
- *
- * @author Vaadin Ltd.
- *
- */
-public class VTextArea extends VTextField {
- public static final String CLASSNAME = "v-textarea";
- private boolean wordwrap = true;
- private MaxLengthHandler maxLengthHandler = new MaxLengthHandler();
- private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute();
-
- public VTextArea() {
- super(DOM.createTextArea());
- setStyleName(CLASSNAME);
- if (!browserSupportsMaxLengthAttribute) {
- addKeyUpHandler(maxLengthHandler);
- addChangeHandler(maxLengthHandler);
- sinkEvents(Event.ONPASTE);
- }
- }
-
- public TextAreaElement getTextAreaElement() {
- return super.getElement().cast();
- }
-
- public void setRows(int rows) {
- getTextAreaElement().setRows(rows);
- }
-
- private class MaxLengthHandler implements KeyUpHandler, ChangeHandler {
-
- @Override
- public void onKeyUp(KeyUpEvent event) {
- enforceMaxLength();
- }
-
- public void onPaste(Event event) {
- enforceMaxLength();
- }
-
- @Override
- public void onChange(ChangeEvent event) {
- // Opera does not support paste events so this enforces max length
- // for Opera.
- enforceMaxLength();
- }
-
- }
-
- protected void enforceMaxLength() {
- if (getMaxLength() >= 0) {
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- if (getText().length() > getMaxLength()) {
- setText(getText().substring(0, getMaxLength()));
- }
- }
- });
- }
- }
-
- protected boolean browserSupportsMaxLengthAttribute() {
- BrowserInfo info = BrowserInfo.get();
- if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) {
- return true;
- }
- if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) {
- return true;
- }
- if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) {
- return true;
- }
- if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) {
- return true;
- }
- return false;
- }
-
- @Override
- protected void updateMaxLength(int maxLength) {
- if (browserSupportsMaxLengthAttribute) {
- super.updateMaxLength(maxLength);
- } else {
- // Events handled by MaxLengthHandler. This call enforces max length
- // when the max length value has changed
- enforceMaxLength();
- }
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONPASTE) {
- maxLengthHandler.onPaste(event);
- }
- }
-
- @Override
- public int getCursorPos() {
- // This is needed so that TextBoxImplIE6 is used to return the correct
- // position for old Internet Explorer versions where it has to be
- // detected in a different way.
- return getImpl().getTextAreaCursorPos(getElement());
- }
-
- @Override
- protected void setMaxLengthToElement(int newMaxLength) {
- // There is no maxlength property for textarea. The maximum length is
- // enforced by the KEYUP handler
-
- }
-
- public void setWordwrap(boolean wordwrap) {
- if (wordwrap == this.wordwrap) {
- return; // No change
- }
-
- if (wordwrap) {
- getElement().removeAttribute("wrap");
- getElement().getStyle().clearOverflow();
- } else {
- getElement().setAttribute("wrap", "off");
- getElement().getStyle().setOverflow(Overflow.AUTO);
- }
- if (BrowserInfo.get().isOpera()) {
- // Opera fails to dynamically update the wrap attribute so we detach
- // and reattach the whole TextArea.
- Util.detachAttach(getElement());
- }
- this.wordwrap = wordwrap;
- }
-}
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VTextField;
import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.textfield;
-
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.TextBoxBase;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Field;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.textfield.TextFieldConstants;
-
-/**
- * This class represents a basic text input field with one row.
- *
- * @author Vaadin Ltd.
- *
- */
-public class VTextField extends TextBoxBase implements Field, ChangeHandler,
- FocusHandler, BlurHandler, KeyDownHandler {
-
- /**
- * The input node CSS classname.
- */
- public static final String CLASSNAME = "v-textfield";
- /**
- * This CSS classname is added to the input node on hover.
- */
- public static final String CLASSNAME_FOCUS = "focus";
-
- protected String paintableId;
-
- protected ApplicationConnection client;
-
- protected String valueBeforeEdit = null;
-
- /**
- * Set to false if a text change event has been sent since the last value
- * change event. This means that {@link #valueBeforeEdit} should not be
- * trusted when determining whether a text change even should be sent.
- */
- private boolean valueBeforeEditIsSynced = true;
-
- private boolean immediate = false;
- private int maxLength = -1;
-
- private static final String CLASSNAME_PROMPT = "prompt";
- private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT";
-
- private String inputPrompt = null;
- private boolean prompting = false;
- private int lastCursorPos = -1;
-
- public VTextField() {
- this(DOM.createInputText());
- }
-
- protected VTextField(Element node) {
- super(node);
- setStyleName(CLASSNAME);
- addChangeHandler(this);
- if (BrowserInfo.get().isIE()) {
- // IE does not send change events when pressing enter in a text
- // input so we handle it using a key listener instead
- addKeyDownHandler(this);
- }
- addFocusHandler(this);
- addBlurHandler(this);
- }
-
- /*
- * TODO When GWT adds ONCUT, add it there and remove workaround. See
- * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030
- *
- * Also note that the cut/paste are not totally crossbrowsers compatible.
- * E.g. in Opera mac works via context menu, but on via File->Paste/Cut.
- * Opera might need the polling method for 100% working textchanceevents.
- * Eager polling for a change is bit dum and heavy operation, so I guess we
- * should first try to survive without.
- */
- protected static final int TEXTCHANGE_EVENTS = Event.ONPASTE
- | Event.KEYEVENTS | Event.ONMOUSEUP;
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- if (listenTextChangeEvents
- && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event
- .getTypeInt()) {
- deferTextChangeEvent();
- }
-
- }
-
- /*
- * TODO optimize this so that only changes are sent + make the value change
- * event just a flag that moves the current text to value
- */
- private String lastTextChangeString = null;
-
- private String getLastCommunicatedString() {
- return lastTextChangeString;
- }
-
- private void communicateTextValueToServer() {
- String text = getText();
- if (prompting) {
- // Input prompt visible, text is actually ""
- text = "";
- }
- if (!text.equals(getLastCommunicatedString())) {
- if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) {
- /*
- * Value change for the current text has been enqueued since the
- * last text change event was sent, but we can't know that it
- * has been sent to the server. Ensure that all pending changes
- * are sent now. Sending a value change without a text change
- * will simulate a TextChangeEvent on the server.
- */
- client.sendPendingVariableChanges();
- } else {
- // Default case - just send an immediate text change message
- client.updateVariable(paintableId,
- TextFieldConstants.VAR_CUR_TEXT, text, true);
-
- // Shouldn't investigate valueBeforeEdit to avoid duplicate text
- // change events as the states are not in sync any more
- valueBeforeEditIsSynced = false;
- }
- lastTextChangeString = text;
- }
- }
-
- private Timer textChangeEventTrigger = new Timer() {
-
- @Override
- public void run() {
- if (isAttached()) {
- updateCursorPosition();
- communicateTextValueToServer();
- scheduled = false;
- }
- }
- };
- private boolean scheduled = false;
- protected boolean listenTextChangeEvents;
- protected String textChangeEventMode;
- protected int textChangeEventTimeout;
-
- private void deferTextChangeEvent() {
- if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) {
- return;
- } else {
- textChangeEventTrigger.cancel();
- }
- textChangeEventTrigger.schedule(getTextChangeEventTimeout());
- scheduled = true;
- }
-
- private int getTextChangeEventTimeout() {
- return textChangeEventTimeout;
- }
-
- @Override
- public void setReadOnly(boolean readOnly) {
- boolean wasReadOnly = isReadOnly();
-
- if (readOnly) {
- setTabIndex(-1);
- } else if (wasReadOnly && !readOnly && getTabIndex() == -1) {
- /*
- * Need to manually set tab index to 0 since server will not send
- * the tab index if it is 0.
- */
- setTabIndex(0);
- }
-
- super.setReadOnly(readOnly);
- }
-
- protected void updateFieldContent(final String text) {
- setPrompting(inputPrompt != null && focusedTextField != this
- && (text.equals("")));
-
- String fieldValue;
- if (prompting) {
- fieldValue = isReadOnly() ? "" : inputPrompt;
- addStyleDependentName(CLASSNAME_PROMPT);
- } else {
- fieldValue = text;
- removeStyleDependentName(CLASSNAME_PROMPT);
- }
- setText(fieldValue);
-
- lastTextChangeString = valueBeforeEdit = text;
- valueBeforeEditIsSynced = true;
- }
-
- protected void onCut() {
- if (listenTextChangeEvents) {
- deferTextChangeEvent();
- }
- }
-
- protected native void attachCutEventListener(Element el)
- /*-{
- var me = this;
- el.oncut = $entry(function() {
- me.@com.vaadin.client.ui.textfield.VTextField::onCut()();
- });
- }-*/;
-
- protected native void detachCutEventListener(Element el)
- /*-{
- el.oncut = null;
- }-*/;
-
- @Override
- protected void onDetach() {
- super.onDetach();
- detachCutEventListener(getElement());
- if (focusedTextField == this) {
- focusedTextField = null;
- }
- }
-
- @Override
- protected void onAttach() {
- super.onAttach();
- if (listenTextChangeEvents) {
- detachCutEventListener(getElement());
- }
- }
-
- protected void setMaxLength(int newMaxLength) {
- if (newMaxLength >= 0 && newMaxLength != maxLength) {
- maxLength = newMaxLength;
- updateMaxLength(maxLength);
- } else if (maxLength != -1) {
- maxLength = -1;
- updateMaxLength(maxLength);
- }
-
- }
-
- /**
- * This method is reponsible for updating the DOM or otherwise ensuring that
- * the given max length is enforced. Called when the max length for the
- * field has changed.
- *
- * @param maxLength
- * The new max length
- */
- protected void updateMaxLength(int maxLength) {
- if (maxLength >= 0) {
- getElement().setPropertyInt("maxLength", maxLength);
- } else {
- getElement().removeAttribute("maxLength");
-
- }
- setMaxLengthToElement(maxLength);
- }
-
- protected void setMaxLengthToElement(int newMaxLength) {
- if (newMaxLength >= 0) {
- getElement().setPropertyInt("maxLength", newMaxLength);
- } else {
- getElement().removeAttribute("maxLength");
- }
- }
-
- public int getMaxLength() {
- return maxLength;
- }
-
- @Override
- public void onChange(ChangeEvent event) {
- valueChange(false);
- }
-
- /**
- * Called when the field value might have changed and/or the field was
- * blurred. These are combined so the blur event is sent in the same batch
- * as a possible value change event (these are often connected).
- *
- * @param blurred
- * true if the field was blurred
- */
- public void valueChange(boolean blurred) {
- if (client != null && paintableId != null) {
- boolean sendBlurEvent = false;
- boolean sendValueChange = false;
-
- if (blurred && client.hasEventListeners(this, EventId.BLUR)) {
- sendBlurEvent = true;
- client.updateVariable(paintableId, EventId.BLUR, "", false);
- }
-
- String newText = getText();
- if (!prompting && newText != null
- && !newText.equals(valueBeforeEdit)) {
- sendValueChange = immediate;
- client.updateVariable(paintableId, "text", newText, false);
- valueBeforeEdit = newText;
- valueBeforeEditIsSynced = true;
- }
-
- /*
- * also send cursor position, no public api yet but for easier
- * extension
- */
- updateCursorPosition();
-
- if (sendBlurEvent || sendValueChange) {
- /*
- * Avoid sending text change event as we will simulate it on the
- * server side before value change events.
- */
- textChangeEventTrigger.cancel();
- scheduled = false;
- client.sendPendingVariableChanges();
- }
- }
- }
-
- /**
- * Updates the cursor position variable if it has changed since the last
- * update.
- *
- * @return true iff the value was updated
- */
- protected boolean updateCursorPosition() {
- if (Util.isAttachedAndDisplayed(this)) {
- int cursorPos = getCursorPos();
- if (lastCursorPos != cursorPos) {
- client.updateVariable(paintableId,
- TextFieldConstants.VAR_CURSOR, cursorPos, false);
- lastCursorPos = cursorPos;
- return true;
- }
- }
- return false;
- }
-
- private static VTextField focusedTextField;
-
- public static void flushChangesFromFocusedTextField() {
- if (focusedTextField != null) {
- focusedTextField.onChange(null);
- }
- }
-
- @Override
- public void onFocus(FocusEvent event) {
- addStyleDependentName(CLASSNAME_FOCUS);
- if (prompting) {
- setText("");
- removeStyleDependentName(CLASSNAME_PROMPT);
- setPrompting(false);
- }
- focusedTextField = this;
- if (client.hasEventListeners(this, EventId.FOCUS)) {
- client.updateVariable(paintableId, EventId.FOCUS, "", true);
- }
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- // this is called twice on Chrome when e.g. changing tab while prompting
- // field focused - do not change settings on the second time
- if (focusedTextField != this) {
- return;
- }
- removeStyleDependentName(CLASSNAME_FOCUS);
- focusedTextField = null;
- String text = getText();
- setPrompting(inputPrompt != null && (text == null || "".equals(text)));
- if (prompting) {
- setText(isReadOnly() ? "" : inputPrompt);
- addStyleDependentName(CLASSNAME_PROMPT);
- }
-
- valueChange(true);
- }
-
- private void setPrompting(boolean prompting) {
- this.prompting = prompting;
- }
-
- public void setColumns(int columns) {
- if (columns <= 0) {
- return;
- }
-
- setWidth(columns + "em");
- }
-
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
- valueChange(false);
- }
- }
-
- public void setImmediate(boolean immediate) {
- this.immediate = immediate;
- }
-
- public void setInputPrompt(String inputPrompt) {
- this.inputPrompt = inputPrompt;
- }
-
-}
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.tree.VTree.TreeNode;
+import com.vaadin.client.ui.VTree;
+import com.vaadin.client.ui.VTree.TreeNode;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.MultiSelectMode;
import com.vaadin.shared.ui.tree.TreeConstants;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.tree;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Action;
-import com.vaadin.client.ui.ActionOwner;
-import com.vaadin.client.ui.FocusElementPanel;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.TreeAction;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.dd.DDUtil;
-import com.vaadin.client.ui.dd.VAbstractDropHandler;
-import com.vaadin.client.ui.dd.VAcceptCallback;
-import com.vaadin.client.ui.dd.VDragAndDropManager;
-import com.vaadin.client.ui.dd.VDragEvent;
-import com.vaadin.client.ui.dd.VDropHandler;
-import com.vaadin.client.ui.dd.VHasDropHandler;
-import com.vaadin.client.ui.dd.VTransferable;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.MouseEventDetails.MouseButton;
-import com.vaadin.shared.ui.MultiSelectMode;
-import com.vaadin.shared.ui.dd.VerticalDropLocation;
-import com.vaadin.shared.ui.tree.TreeConstants;
-
-/**
- *
- */
-public class VTree extends FocusElementPanel implements VHasDropHandler,
- FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler,
- SubPartAware, ActionOwner {
-
- public static final String CLASSNAME = "v-tree";
-
- /**
- * @deprecated from 7.0, use {@link MultiSelectMode#DEFAULT} instead.
- */
- @Deprecated
- public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT;
-
- /**
- * @deprecated from 7.0, use {@link MultiSelectMode#SIMPLE} instead.
- */
- @Deprecated
- public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE;
-
- private static final int CHARCODE_SPACE = 32;
-
- final FlowPanel body = new FlowPanel();
-
- Set<String> selectedIds = new HashSet<String>();
- ApplicationConnection client;
- String paintableId;
- boolean selectable;
- boolean isMultiselect;
- private String currentMouseOverKey;
- TreeNode lastSelection;
- TreeNode focusedNode;
- MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
-
- private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
-
- /**
- * This map contains captions and icon urls for actions like: * "33_c" ->
- * "Edit" * "33_i" -> "http://dom.com/edit.png"
- */
- private final HashMap<String, String> actionMap = new HashMap<String, String>();
-
- boolean immediate;
-
- boolean isNullSelectionAllowed = true;
-
- boolean disabled = false;
-
- boolean readonly;
-
- boolean rendering;
-
- private VAbstractDropHandler dropHandler;
-
- int dragMode;
-
- private boolean selectionHasChanged = false;
-
- String[] bodyActionKeys;
-
- public VLazyExecutor iconLoaded = new VLazyExecutor(50,
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- Util.notifyParentOfSizeChange(VTree.this, true);
- }
-
- });
-
- public VTree() {
- super();
- setStyleName(CLASSNAME);
- add(body);
-
- addFocusHandler(this);
- addBlurHandler(this);
-
- /*
- * Listen to context menu events on the empty space in the tree
- */
- sinkEvents(Event.ONCONTEXTMENU);
- addDomHandler(new ContextMenuHandler() {
- @Override
- public void onContextMenu(ContextMenuEvent event) {
- handleBodyContextMenu(event);
- }
- }, ContextMenuEvent.getType());
-
- /*
- * Firefox auto-repeat works correctly only if we use a key press
- * handler, other browsers handle it correctly when using a key down
- * handler
- */
- if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
- addKeyPressHandler(this);
- } else {
- addKeyDownHandler(this);
- }
-
- /*
- * We need to use the sinkEvents method to catch the keyUp events so we
- * can cache a single shift. KeyUpHandler cannot do this. At the same
- * time we catch the mouse down and up events so we can apply the text
- * selection patch in IE
- */
- sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
-
- /*
- * Re-set the tab index to make sure that the FocusElementPanel's
- * (super) focus element gets the tab index and not the element
- * containing the tree.
- */
- setTabIndex(0);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
- * .client.Event)
- */
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONMOUSEDOWN) {
- // Prevent default text selection in IE
- if (BrowserInfo.get().isIE()) {
- ((Element) event.getEventTarget().cast()).setPropertyJSO(
- "onselectstart", applyDisableTextSelectionIEHack());
- }
- } else if (event.getTypeInt() == Event.ONMOUSEUP) {
- // Remove IE text selection hack
- if (BrowserInfo.get().isIE()) {
- ((Element) event.getEventTarget().cast()).setPropertyJSO(
- "onselectstart", null);
- }
- } else if (event.getTypeInt() == Event.ONKEYUP) {
- if (selectionHasChanged) {
- if (event.getKeyCode() == getNavigationDownKey()
- && !event.getShiftKey()) {
- sendSelectionToServer();
- event.preventDefault();
- } else if (event.getKeyCode() == getNavigationUpKey()
- && !event.getShiftKey()) {
- sendSelectionToServer();
- event.preventDefault();
- } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
- sendSelectionToServer();
- event.preventDefault();
- } else if (event.getKeyCode() == getNavigationSelectKey()) {
- sendSelectionToServer();
- event.preventDefault();
- }
- }
- }
- }
-
- public String getActionCaption(String actionKey) {
- return actionMap.get(actionKey + "_c");
- }
-
- public String getActionIcon(String actionKey) {
- return actionMap.get(actionKey + "_i");
- }
-
- /**
- * Returns the first root node of the tree or null if there are no root
- * nodes.
- *
- * @return The first root {@link TreeNode}
- */
- protected TreeNode getFirstRootNode() {
- if (body.getWidgetCount() == 0) {
- return null;
- }
- return (TreeNode) body.getWidget(0);
- }
-
- /**
- * Returns the last root node of the tree or null if there are no root
- * nodes.
- *
- * @return The last root {@link TreeNode}
- */
- protected TreeNode getLastRootNode() {
- if (body.getWidgetCount() == 0) {
- return null;
- }
- return (TreeNode) body.getWidget(body.getWidgetCount() - 1);
- }
-
- /**
- * Returns a list of all root nodes in the Tree in the order they appear in
- * the tree.
- *
- * @return A list of all root {@link TreeNode}s.
- */
- protected List<TreeNode> getRootNodes() {
- ArrayList<TreeNode> rootNodes = new ArrayList<TreeNode>();
- for (int i = 0; i < body.getWidgetCount(); i++) {
- rootNodes.add((TreeNode) body.getWidget(i));
- }
- return rootNodes;
- }
-
- private void updateTreeRelatedDragData(VDragEvent drag) {
-
- currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver());
-
- drag.getDropDetails().put("itemIdOver", currentMouseOverKey);
- if (currentMouseOverKey != null) {
- TreeNode treeNode = getNodeByKey(currentMouseOverKey);
- VerticalDropLocation detail = treeNode.getDropDetail(drag
- .getCurrentGwtEvent());
- Boolean overTreeNode = null;
- if (treeNode != null && !treeNode.isLeaf()
- && detail == VerticalDropLocation.MIDDLE) {
- overTreeNode = true;
- }
- drag.getDropDetails().put("itemIdOverIsNode", overTreeNode);
- drag.getDropDetails().put("detail", detail);
- } else {
- drag.getDropDetails().put("itemIdOverIsNode", null);
- drag.getDropDetails().put("detail", null);
- }
-
- }
-
- private String findCurrentMouseOverKey(Element elementOver) {
- TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class);
- return treeNode == null ? null : treeNode.key;
- }
-
- void updateDropHandler(UIDL childUidl) {
- if (dropHandler == null) {
- dropHandler = new VAbstractDropHandler() {
-
- @Override
- public void dragEnter(VDragEvent drag) {
- }
-
- @Override
- protected void dragAccepted(final VDragEvent drag) {
-
- }
-
- @Override
- public void dragOver(final VDragEvent currentDrag) {
- final Object oldIdOver = currentDrag.getDropDetails().get(
- "itemIdOver");
- final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag
- .getDropDetails().get("detail");
-
- updateTreeRelatedDragData(currentDrag);
- final VerticalDropLocation detail = (VerticalDropLocation) currentDrag
- .getDropDetails().get("detail");
- boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver)
- || (currentMouseOverKey == null && oldIdOver != null);
- boolean detailHasChanded = (detail != null && detail != oldDetail)
- || (detail == null && oldDetail != null);
-
- if (nodeHasChanged || detailHasChanded) {
- final String newKey = currentMouseOverKey;
- TreeNode treeNode = keyToNode.get(oldIdOver);
- if (treeNode != null) {
- // clear old styles
- treeNode.emphasis(null);
- }
- if (newKey != null) {
- validate(new VAcceptCallback() {
- @Override
- public void accepted(VDragEvent event) {
- VerticalDropLocation curDetail = (VerticalDropLocation) event
- .getDropDetails().get("detail");
- if (curDetail == detail
- && newKey.equals(currentMouseOverKey)) {
- getNodeByKey(newKey).emphasis(detail);
- }
- /*
- * Else drag is already on a different
- * node-detail pair, new criteria check is
- * going on
- */
- }
- }, currentDrag);
-
- }
- }
-
- }
-
- @Override
- public void dragLeave(VDragEvent drag) {
- cleanUp();
- }
-
- private void cleanUp() {
- if (currentMouseOverKey != null) {
- getNodeByKey(currentMouseOverKey).emphasis(null);
- currentMouseOverKey = null;
- }
- }
-
- @Override
- public boolean drop(VDragEvent drag) {
- cleanUp();
- return super.drop(drag);
- }
-
- @Override
- public ComponentConnector getConnector() {
- return ConnectorMap.get(client).getConnector(VTree.this);
- }
-
- @Override
- public ApplicationConnection getApplicationConnection() {
- return client;
- }
-
- };
- }
- dropHandler.updateAcceptRules(childUidl);
- }
-
- public void setSelected(TreeNode treeNode, boolean selected) {
- if (selected) {
- if (!isMultiselect) {
- while (selectedIds.size() > 0) {
- final String id = selectedIds.iterator().next();
- final TreeNode oldSelection = getNodeByKey(id);
- if (oldSelection != null) {
- // can be null if the node is not visible (parent
- // collapsed)
- oldSelection.setSelected(false);
- }
- selectedIds.remove(id);
- }
- }
- treeNode.setSelected(true);
- selectedIds.add(treeNode.key);
- } else {
- if (!isNullSelectionAllowed) {
- if (!isMultiselect || selectedIds.size() == 1) {
- return;
- }
- }
- selectedIds.remove(treeNode.key);
- treeNode.setSelected(false);
- }
-
- sendSelectionToServer();
- }
-
- /**
- * Sends the selection to the server
- */
- private void sendSelectionToServer() {
- Command command = new Command() {
- @Override
- public void execute() {
- client.updateVariable(paintableId, "selected",
- selectedIds.toArray(new String[selectedIds.size()]),
- immediate);
- selectionHasChanged = false;
- }
- };
-
- /*
- * Delaying the sending of the selection in webkit to ensure the
- * selection is always sent when the tree has focus and after click
- * events have been processed. This is due to the focusing
- * implementation in FocusImplSafari which uses timeouts when focusing
- * and blurring.
- */
- if (BrowserInfo.get().isWebkit()) {
- Scheduler.get().scheduleDeferred(command);
- } else {
- command.execute();
- }
- }
-
- /**
- * Is a node selected in the tree
- *
- * @param treeNode
- * The node to check
- * @return
- */
- public boolean isSelected(TreeNode treeNode) {
- return selectedIds.contains(treeNode.key);
- }
-
- public class TreeNode extends SimplePanel implements ActionOwner {
-
- public static final String CLASSNAME = "v-tree-node";
- public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
-
- public String key;
-
- String[] actionKeys = null;
-
- boolean childrenLoaded;
-
- Element nodeCaptionDiv;
-
- protected Element nodeCaptionSpan;
-
- FlowPanel childNodeContainer;
-
- private boolean open;
-
- private Icon icon;
-
- private Event mouseDownEvent;
-
- private int cachedHeight = -1;
-
- private boolean focused = false;
-
- public TreeNode() {
- constructDom();
- sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
- | Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
- }
-
- public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) {
- if (cachedHeight < 0) {
- /*
- * Height is cached to avoid flickering (drop hints may change
- * the reported offsetheight -> would change the drop detail)
- */
- cachedHeight = nodeCaptionDiv.getOffsetHeight();
- }
- VerticalDropLocation verticalDropLocation = DDUtil
- .getVerticalDropLocation(nodeCaptionDiv, cachedHeight,
- currentGwtEvent, 0.15);
- return verticalDropLocation;
- }
-
- protected void emphasis(VerticalDropLocation detail) {
- String base = "v-tree-node-drag-";
- UIObject.setStyleName(getElement(), base + "top",
- VerticalDropLocation.TOP == detail);
- UIObject.setStyleName(getElement(), base + "bottom",
- VerticalDropLocation.BOTTOM == detail);
- UIObject.setStyleName(getElement(), base + "center",
- VerticalDropLocation.MIDDLE == detail);
- base = "v-tree-node-caption-drag-";
- UIObject.setStyleName(nodeCaptionDiv, base + "top",
- VerticalDropLocation.TOP == detail);
- UIObject.setStyleName(nodeCaptionDiv, base + "bottom",
- VerticalDropLocation.BOTTOM == detail);
- UIObject.setStyleName(nodeCaptionDiv, base + "center",
- VerticalDropLocation.MIDDLE == detail);
-
- // also add classname to "folder node" into which the drag is
- // targeted
-
- TreeNode folder = null;
- /* Possible parent of this TreeNode will be stored here */
- TreeNode parentFolder = getParentNode();
-
- // TODO fix my bugs
- if (isLeaf()) {
- folder = parentFolder;
- // note, parent folder may be null if this is root node => no
- // folder target exists
- } else {
- if (detail == VerticalDropLocation.TOP) {
- folder = parentFolder;
- } else {
- folder = this;
- }
- // ensure we remove the dragfolder classname from the previous
- // folder node
- setDragFolderStyleName(this, false);
- setDragFolderStyleName(parentFolder, false);
- }
- if (folder != null) {
- setDragFolderStyleName(folder, detail != null);
- }
-
- }
-
- private TreeNode getParentNode() {
- Widget parent2 = getParent().getParent();
- if (parent2 instanceof TreeNode) {
- return (TreeNode) parent2;
- }
- return null;
- }
-
- private void setDragFolderStyleName(TreeNode folder, boolean add) {
- if (folder != null) {
- UIObject.setStyleName(folder.getElement(),
- "v-tree-node-dragfolder", add);
- UIObject.setStyleName(folder.nodeCaptionDiv,
- "v-tree-node-caption-dragfolder", add);
- }
- }
-
- /**
- * Handles mouse selection
- *
- * @param ctrl
- * Was the ctrl-key pressed
- * @param shift
- * Was the shift-key pressed
- * @return Returns true if event was handled, else false
- */
- private boolean handleClickSelection(final boolean ctrl,
- final boolean shift) {
-
- // always when clicking an item, focus it
- setFocusedNode(this, false);
-
- if (!BrowserInfo.get().isOpera()) {
- /*
- * Ensure that the tree's focus element also gains focus
- * (TreeNodes focus is faked using FocusElementPanel in browsers
- * other than Opera).
- */
- focus();
- }
-
- executeEventCommand(new ScheduledCommand() {
-
- @Override
- public void execute() {
-
- if (multiSelectMode == MultiSelectMode.SIMPLE
- || !isMultiselect) {
- toggleSelection();
- lastSelection = TreeNode.this;
- } else if (multiSelectMode == MultiSelectMode.DEFAULT) {
- // Handle ctrl+click
- if (isMultiselect && ctrl && !shift) {
- toggleSelection();
- lastSelection = TreeNode.this;
-
- // Handle shift+click
- } else if (isMultiselect && !ctrl && shift) {
- deselectAll();
- selectNodeRange(lastSelection.key, key);
- sendSelectionToServer();
-
- // Handle ctrl+shift click
- } else if (isMultiselect && ctrl && shift) {
- selectNodeRange(lastSelection.key, key);
-
- // Handle click
- } else {
- // TODO should happen only if this alone not yet
- // selected,
- // now sending excess server calls
- deselectAll();
- toggleSelection();
- lastSelection = TreeNode.this;
- }
- }
- }
- });
-
- return true;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
- * .user.client.Event)
- */
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- final int type = DOM.eventGetType(event);
- final Element target = DOM.eventGetTarget(event);
-
- if (type == Event.ONLOAD && target == icon.getElement()) {
- iconLoaded.trigger();
- }
-
- if (disabled) {
- return;
- }
-
- final boolean inCaption = isCaptionElement(target);
- if (inCaption
- && client.hasEventListeners(VTree.this,
- TreeConstants.ITEM_CLICK_EVENT_ID)
-
- && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
- fireClick(event);
- }
- if (type == Event.ONCLICK) {
- if (getElement() == target) {
- // state change
- toggleState();
- } else if (!readonly && inCaption) {
- if (selectable) {
- // caption click = selection change && possible click
- // event
- if (handleClickSelection(
- event.getCtrlKey() || event.getMetaKey(),
- event.getShiftKey())) {
- event.preventDefault();
- }
- } else {
- // Not selectable, only focus the node.
- setFocusedNode(this);
- }
- }
- event.stopPropagation();
- } else if (type == Event.ONCONTEXTMENU) {
- showContextMenu(event);
- }
-
- if (dragMode != 0 || dropHandler != null) {
- if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
- if (nodeCaptionDiv.isOrHasChild((Node) event
- .getEventTarget().cast())) {
- if (dragMode > 0
- && (type == Event.ONTOUCHSTART || event
- .getButton() == NativeEvent.BUTTON_LEFT)) {
- mouseDownEvent = event; // save event for possible
- // dd operation
- if (type == Event.ONMOUSEDOWN) {
- event.preventDefault(); // prevent text
- // selection
- } else {
- /*
- * FIXME We prevent touch start event to be used
- * as a scroll start event. Note that we cannot
- * easily distinguish whether the user wants to
- * drag or scroll. The same issue is in table
- * that has scrollable area and has drag and
- * drop enable. Some kind of timer might be used
- * to resolve the issue.
- */
- event.stopPropagation();
- }
- }
- }
- } else if (type == Event.ONMOUSEMOVE
- || type == Event.ONMOUSEOUT
- || type == Event.ONTOUCHMOVE) {
-
- if (mouseDownEvent != null) {
- // start actual drag on slight move when mouse is down
- VTransferable t = new VTransferable();
- t.setDragSource(ConnectorMap.get(client).getConnector(
- VTree.this));
- t.setData("itemId", key);
- VDragEvent drag = VDragAndDropManager.get().startDrag(
- t, mouseDownEvent, true);
-
- drag.createDragImage(nodeCaptionDiv, true);
- event.stopPropagation();
-
- mouseDownEvent = null;
- }
- } else if (type == Event.ONMOUSEUP) {
- mouseDownEvent = null;
- }
- if (type == Event.ONMOUSEOVER) {
- mouseDownEvent = null;
- currentMouseOverKey = key;
- event.stopPropagation();
- }
-
- } else if (type == Event.ONMOUSEDOWN
- && event.getButton() == NativeEvent.BUTTON_LEFT) {
- event.preventDefault(); // text selection
- }
- }
-
- /**
- * Checks if the given element is the caption or the icon.
- *
- * @param target
- * The element to check
- * @return true if the element is the caption or the icon
- */
- public boolean isCaptionElement(com.google.gwt.dom.client.Element target) {
- return (target == nodeCaptionSpan || (icon != null && target == icon
- .getElement()));
- }
-
- private void fireClick(final Event evt) {
- /*
- * Ensure we have focus in tree before sending variables. Otherwise
- * previously modified field may contain dirty variables.
- */
- if (!treeHasFocus) {
- if (BrowserInfo.get().isOpera()) {
- if (focusedNode == null) {
- getNodeByKey(key).setFocused(true);
- } else {
- focusedNode.setFocused(true);
- }
- } else {
- focus();
- }
- }
-
- final MouseEventDetails details = MouseEventDetailsBuilder
- .buildMouseEventDetails(evt);
-
- executeEventCommand(new ScheduledCommand() {
-
- @Override
- public void execute() {
- // Determine if we should send the event immediately to the
- // server. We do not want to send the event if there is a
- // selection event happening after this. In all other cases
- // we want to send it immediately.
- boolean sendClickEventNow = true;
-
- if (details.getButton() == MouseButton.LEFT && immediate
- && selectable) {
- // Probably a selection that will cause a value change
- // event to be sent
- sendClickEventNow = false;
-
- // The exception is that user clicked on the
- // currently selected row and null selection is not
- // allowed == no selection event
- if (isSelected() && selectedIds.size() == 1
- && !isNullSelectionAllowed) {
- sendClickEventNow = true;
- }
- }
-
- client.updateVariable(paintableId, "clickedKey", key, false);
- client.updateVariable(paintableId, "clickEvent",
- details.toString(), sendClickEventNow);
- }
- });
- }
-
- /*
- * Must wait for Safari to focus before sending click and value change
- * events (see #6373, #6374)
- */
- private void executeEventCommand(ScheduledCommand command) {
- if (BrowserInfo.get().isWebkit() && !treeHasFocus) {
- Scheduler.get().scheduleDeferred(command);
- } else {
- command.execute();
- }
- }
-
- private void toggleSelection() {
- if (selectable) {
- VTree.this.setSelected(this, !isSelected());
- }
- }
-
- private void toggleState() {
- setState(!getState(), true);
- }
-
- protected void constructDom() {
- addStyleName(CLASSNAME);
-
- nodeCaptionDiv = DOM.createDiv();
- DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
- + "-caption");
- Element wrapper = DOM.createDiv();
- nodeCaptionSpan = DOM.createSpan();
- DOM.appendChild(getElement(), nodeCaptionDiv);
- DOM.appendChild(nodeCaptionDiv, wrapper);
- DOM.appendChild(wrapper, nodeCaptionSpan);
-
- if (BrowserInfo.get().isOpera()) {
- /*
- * Focus the caption div of the node to get keyboard navigation
- * to work without scrolling up or down when focusing a node.
- */
- nodeCaptionDiv.setTabIndex(-1);
- }
-
- childNodeContainer = new FlowPanel();
- childNodeContainer.setStyleName(CLASSNAME + "-children");
- setWidget(childNodeContainer);
- }
-
- public boolean isLeaf() {
- String[] styleNames = getStyleName().split(" ");
- for (String styleName : styleNames) {
- if (styleName.equals(CLASSNAME + "-leaf")) {
- return true;
- }
- }
- return false;
- }
-
- void setState(boolean state, boolean notifyServer) {
- if (open == state) {
- return;
- }
- if (state) {
- if (!childrenLoaded && notifyServer) {
- client.updateVariable(paintableId, "requestChildTree",
- true, false);
- }
- if (notifyServer) {
- client.updateVariable(paintableId, "expand",
- new String[] { key }, true);
- }
- addStyleName(CLASSNAME + "-expanded");
- childNodeContainer.setVisible(true);
-
- } else {
- removeStyleName(CLASSNAME + "-expanded");
- childNodeContainer.setVisible(false);
- if (notifyServer) {
- client.updateVariable(paintableId, "collapse",
- new String[] { key }, true);
- }
- }
- open = state;
-
- if (!rendering) {
- Util.notifyParentOfSizeChange(VTree.this, false);
- }
- }
-
- boolean getState() {
- return open;
- }
-
- void setText(String text) {
- DOM.setInnerText(nodeCaptionSpan, text);
- }
-
- public boolean isChildrenLoaded() {
- return childrenLoaded;
- }
-
- /**
- * Returns the children of the node
- *
- * @return A set of tree nodes
- */
- public List<TreeNode> getChildren() {
- List<TreeNode> nodes = new LinkedList<TreeNode>();
-
- if (!isLeaf() && isChildrenLoaded()) {
- Iterator<Widget> iter = childNodeContainer.iterator();
- while (iter.hasNext()) {
- TreeNode node = (TreeNode) iter.next();
- nodes.add(node);
- }
- }
- return nodes;
- }
-
- @Override
- public Action[] getActions() {
- if (actionKeys == null) {
- return new Action[] {};
- }
- final Action[] actions = new Action[actionKeys.length];
- for (int i = 0; i < actions.length; i++) {
- final String actionKey = actionKeys[i];
- final TreeAction a = new TreeAction(this, String.valueOf(key),
- actionKey);
- a.setCaption(getActionCaption(actionKey));
- a.setIconUrl(getActionIcon(actionKey));
- actions[i] = a;
- }
- return actions;
- }
-
- @Override
- public ApplicationConnection getClient() {
- return client;
- }
-
- @Override
- public String getPaintableId() {
- return paintableId;
- }
-
- /**
- * Adds/removes Vaadin specific style name. This method ought to be
- * called only from VTree.
- *
- * @param selected
- */
- protected void setSelected(boolean selected) {
- // add style name to caption dom structure only, not to subtree
- setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected);
- }
-
- protected boolean isSelected() {
- return VTree.this.isSelected(this);
- }
-
- /**
- * Travels up the hierarchy looking for this node
- *
- * @param child
- * The child which grandparent this is or is not
- * @return True if this is a grandparent of the child node
- */
- public boolean isGrandParentOf(TreeNode child) {
- TreeNode currentNode = child;
- boolean isGrandParent = false;
- while (currentNode != null) {
- currentNode = currentNode.getParentNode();
- if (currentNode == this) {
- isGrandParent = true;
- break;
- }
- }
- return isGrandParent;
- }
-
- public boolean isSibling(TreeNode node) {
- return node.getParentNode() == getParentNode();
- }
-
- public void showContextMenu(Event event) {
- if (!readonly && !disabled) {
- if (actionKeys != null) {
- int left = event.getClientX();
- int top = event.getClientY();
- top += Window.getScrollTop();
- left += Window.getScrollLeft();
- client.getContextMenu().showAt(this, left, top);
- }
- event.stopPropagation();
- event.preventDefault();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.Widget#onDetach()
- */
- @Override
- protected void onDetach() {
- super.onDetach();
- client.getContextMenu().ensureHidden(this);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.UIObject#toString()
- */
- @Override
- public String toString() {
- return nodeCaptionSpan.getInnerText();
- }
-
- /**
- * Is the node focused?
- *
- * @param focused
- * True if focused, false if not
- */
- public void setFocused(boolean focused) {
- if (!this.focused && focused) {
- nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
-
- this.focused = focused;
- if (BrowserInfo.get().isOpera()) {
- nodeCaptionDiv.focus();
- }
- treeHasFocus = true;
- } else if (this.focused && !focused) {
- nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
- this.focused = focused;
- treeHasFocus = false;
- }
- }
-
- /**
- * Scrolls the caption into view
- */
- public void scrollIntoView() {
- Util.scrollIntoViewVertically(nodeCaptionDiv);
- }
-
- public void setIcon(String iconUrl) {
- if (iconUrl != null) {
- // Add icon if not present
- if (icon == null) {
- icon = new Icon(client);
- DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
- icon.getElement(), nodeCaptionSpan);
- }
- icon.setUri(iconUrl);
- } else {
- // Remove icon if present
- if (icon != null) {
- DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv),
- icon.getElement());
- icon = null;
- }
- }
- }
-
- public void setNodeStyleName(String styleName) {
- addStyleName(TreeNode.CLASSNAME + "-" + styleName);
- setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-"
- + styleName, true);
- childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-"
- + styleName);
-
- }
-
- }
-
- @Override
- public VDropHandler getDropHandler() {
- return dropHandler;
- }
-
- public TreeNode getNodeByKey(String key) {
- return keyToNode.get(key);
- }
-
- /**
- * Deselects all items in the tree
- */
- public void deselectAll() {
- for (String key : selectedIds) {
- TreeNode node = keyToNode.get(key);
- if (node != null) {
- node.setSelected(false);
- }
- }
- selectedIds.clear();
- selectionHasChanged = true;
- }
-
- /**
- * Selects a range of nodes
- *
- * @param startNodeKey
- * The start node key
- * @param endNodeKey
- * The end node key
- */
- private void selectNodeRange(String startNodeKey, String endNodeKey) {
-
- TreeNode startNode = keyToNode.get(startNodeKey);
- TreeNode endNode = keyToNode.get(endNodeKey);
-
- // The nodes have the same parent
- if (startNode.getParent() == endNode.getParent()) {
- doSiblingSelection(startNode, endNode);
-
- // The start node is a grandparent of the end node
- } else if (startNode.isGrandParentOf(endNode)) {
- doRelationSelection(startNode, endNode);
-
- // The end node is a grandparent of the start node
- } else if (endNode.isGrandParentOf(startNode)) {
- doRelationSelection(endNode, startNode);
-
- } else {
- doNoRelationSelection(startNode, endNode);
- }
- }
-
- /**
- * Selects a node and deselect all other nodes
- *
- * @param node
- * The node to select
- */
- private void selectNode(TreeNode node, boolean deselectPrevious) {
- if (deselectPrevious) {
- deselectAll();
- }
-
- if (node != null) {
- node.setSelected(true);
- selectedIds.add(node.key);
- lastSelection = node;
- }
- selectionHasChanged = true;
- }
-
- /**
- * Deselects a node
- *
- * @param node
- * The node to deselect
- */
- private void deselectNode(TreeNode node) {
- node.setSelected(false);
- selectedIds.remove(node.key);
- selectionHasChanged = true;
- }
-
- /**
- * Selects all the open children to a node
- *
- * @param node
- * The parent node
- */
- private void selectAllChildren(TreeNode node, boolean includeRootNode) {
- if (includeRootNode) {
- node.setSelected(true);
- selectedIds.add(node.key);
- }
-
- for (TreeNode child : node.getChildren()) {
- if (!child.isLeaf() && child.getState()) {
- selectAllChildren(child, true);
- } else {
- child.setSelected(true);
- selectedIds.add(child.key);
- }
- }
- selectionHasChanged = true;
- }
-
- /**
- * Selects all children until a stop child is reached
- *
- * @param root
- * The root not to start from
- * @param stopNode
- * The node to finish with
- * @param includeRootNode
- * Should the root node be selected
- * @param includeStopNode
- * Should the stop node be selected
- *
- * @return Returns false if the stop child was found, else true if all
- * children was selected
- */
- private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
- boolean includeRootNode, boolean includeStopNode) {
- if (includeRootNode) {
- root.setSelected(true);
- selectedIds.add(root.key);
- }
- if (root.getState() && root != stopNode) {
- for (TreeNode child : root.getChildren()) {
- if (!child.isLeaf() && child.getState() && child != stopNode) {
- if (!selectAllChildrenUntil(child, stopNode, true,
- includeStopNode)) {
- return false;
- }
- } else if (child == stopNode) {
- if (includeStopNode) {
- child.setSelected(true);
- selectedIds.add(child.key);
- }
- return false;
- } else {
- child.setSelected(true);
- selectedIds.add(child.key);
- }
- }
- }
- selectionHasChanged = true;
-
- return true;
- }
-
- /**
- * Select a range between two nodes which have no relation to each other
- *
- * @param startNode
- * The start node to start the selection from
- * @param endNode
- * The end node to end the selection to
- */
- private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
-
- TreeNode commonParent = getCommonGrandParent(startNode, endNode);
- TreeNode startBranch = null, endBranch = null;
-
- // Find the children of the common parent
- List<TreeNode> children;
- if (commonParent != null) {
- children = commonParent.getChildren();
- } else {
- children = getRootNodes();
- }
-
- // Find the start and end branches
- for (TreeNode node : children) {
- if (nodeIsInBranch(startNode, node)) {
- startBranch = node;
- }
- if (nodeIsInBranch(endNode, node)) {
- endBranch = node;
- }
- }
-
- // Swap nodes if necessary
- if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
- TreeNode temp = startBranch;
- startBranch = endBranch;
- endBranch = temp;
-
- temp = startNode;
- startNode = endNode;
- endNode = temp;
- }
-
- // Select all children under the start node
- selectAllChildren(startNode, true);
- TreeNode startParent = startNode.getParentNode();
- TreeNode currentNode = startNode;
- while (startParent != null && startParent != commonParent) {
- List<TreeNode> startChildren = startParent.getChildren();
- for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
- .size(); i++) {
- selectAllChildren(startChildren.get(i), true);
- }
-
- currentNode = startParent;
- startParent = startParent.getParentNode();
- }
-
- // Select nodes until the end node is reached
- for (int i = children.indexOf(startBranch) + 1; i <= children
- .indexOf(endBranch); i++) {
- selectAllChildrenUntil(children.get(i), endNode, true, true);
- }
-
- // Ensure end node was selected
- endNode.setSelected(true);
- selectedIds.add(endNode.key);
- selectionHasChanged = true;
- }
-
- /**
- * Examines the children of the branch node and returns true if a node is in
- * that branch
- *
- * @param node
- * The node to search for
- * @param branch
- * The branch to search in
- * @return True if found, false if not found
- */
- private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
- if (node == branch) {
- return true;
- }
- for (TreeNode child : branch.getChildren()) {
- if (child == node) {
- return true;
- }
- if (!child.isLeaf() && child.getState()) {
- if (nodeIsInBranch(node, child)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Selects a range of items which are in direct relation with each other.<br/>
- * NOTE: The start node <b>MUST</b> be before the end node!
- *
- * @param startNode
- *
- * @param endNode
- */
- private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
- TreeNode currentNode = endNode;
- while (currentNode != startNode) {
- currentNode.setSelected(true);
- selectedIds.add(currentNode.key);
-
- // Traverse children above the selection
- List<TreeNode> subChildren = currentNode.getParentNode()
- .getChildren();
- if (subChildren.size() > 1) {
- selectNodeRange(subChildren.iterator().next().key,
- currentNode.key);
- } else if (subChildren.size() == 1) {
- TreeNode n = subChildren.get(0);
- n.setSelected(true);
- selectedIds.add(n.key);
- }
-
- currentNode = currentNode.getParentNode();
- }
- startNode.setSelected(true);
- selectedIds.add(startNode.key);
- selectionHasChanged = true;
- }
-
- /**
- * Selects a range of items which have the same parent.
- *
- * @param startNode
- * The start node
- * @param endNode
- * The end node
- */
- private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
- TreeNode parent = startNode.getParentNode();
-
- List<TreeNode> children;
- if (parent == null) {
- // Topmost parent
- children = getRootNodes();
- } else {
- children = parent.getChildren();
- }
-
- // Swap start and end point if needed
- if (children.indexOf(startNode) > children.indexOf(endNode)) {
- TreeNode temp = startNode;
- startNode = endNode;
- endNode = temp;
- }
-
- Iterator<TreeNode> childIter = children.iterator();
- boolean startFound = false;
- while (childIter.hasNext()) {
- TreeNode node = childIter.next();
- if (node == startNode) {
- startFound = true;
- }
-
- if (startFound && node != endNode && node.getState()) {
- selectAllChildren(node, true);
- } else if (startFound && node != endNode) {
- node.setSelected(true);
- selectedIds.add(node.key);
- }
-
- if (node == endNode) {
- node.setSelected(true);
- selectedIds.add(node.key);
- break;
- }
- }
- selectionHasChanged = true;
- }
-
- /**
- * Returns the first common parent of two nodes
- *
- * @param node1
- * The first node
- * @param node2
- * The second node
- * @return The common parent or null
- */
- public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
- // If either one does not have a grand parent then return null
- if (node1.getParentNode() == null || node2.getParentNode() == null) {
- return null;
- }
-
- // If the nodes are parents of each other then return null
- if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
- return null;
- }
-
- // Get parents of node1
- List<TreeNode> parents1 = new ArrayList<TreeNode>();
- TreeNode parent1 = node1.getParentNode();
- while (parent1 != null) {
- parents1.add(parent1);
- parent1 = parent1.getParentNode();
- }
-
- // Get parents of node2
- List<TreeNode> parents2 = new ArrayList<TreeNode>();
- TreeNode parent2 = node2.getParentNode();
- while (parent2 != null) {
- parents2.add(parent2);
- parent2 = parent2.getParentNode();
- }
-
- // Search the parents for the first common parent
- for (int i = 0; i < parents1.size(); i++) {
- parent1 = parents1.get(i);
- for (int j = 0; j < parents2.size(); j++) {
- parent2 = parents2.get(j);
- if (parent1 == parent2) {
- return parent1;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Sets the node currently in focus
- *
- * @param node
- * The node to focus or null to remove the focus completely
- * @param scrollIntoView
- * Scroll the node into view
- */
- public void setFocusedNode(TreeNode node, boolean scrollIntoView) {
- // Unfocus previously focused node
- if (focusedNode != null) {
- focusedNode.setFocused(false);
- }
-
- if (node != null) {
- node.setFocused(true);
- }
-
- focusedNode = node;
-
- if (node != null && scrollIntoView) {
- /*
- * Delay scrolling the focused node into view if we are still
- * rendering. #5396
- */
- if (!rendering) {
- node.scrollIntoView();
- } else {
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- focusedNode.scrollIntoView();
- }
- });
- }
- }
- }
-
- /**
- * Focuses a node and scrolls it into view
- *
- * @param node
- * The node to focus
- */
- public void setFocusedNode(TreeNode node) {
- setFocusedNode(node, true);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
- * .dom.client.FocusEvent)
- */
- @Override
- public void onFocus(FocusEvent event) {
- treeHasFocus = true;
- // If no node has focus, focus the first item in the tree
- if (focusedNode == null && lastSelection == null && selectable) {
- setFocusedNode(getFirstRootNode(), false);
- } else if (focusedNode != null && selectable) {
- setFocusedNode(focusedNode, false);
- } else if (lastSelection != null && selectable) {
- setFocusedNode(lastSelection, false);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
- * .dom.client.BlurEvent)
- */
- @Override
- public void onBlur(BlurEvent event) {
- treeHasFocus = false;
- if (focusedNode != null) {
- focusedNode.setFocused(false);
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
- * .gwt.event.dom.client.KeyPressEvent)
- */
- @Override
- public void onKeyPress(KeyPressEvent event) {
- NativeEvent nativeEvent = event.getNativeEvent();
- int keyCode = nativeEvent.getKeyCode();
- if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
- // Provide a keyCode for space to be compatible with FireFox
- // keypress event
- keyCode = CHARCODE_SPACE;
- }
- if (handleKeyNavigation(keyCode,
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (handleKeyNavigation(event.getNativeEvent().getKeyCode(),
- event.isControlKeyDown() || event.isMetaKeyDown(),
- event.isShiftKeyDown())) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- /**
- * Handles the keyboard navigation
- *
- * @param keycode
- * The keycode of the pressed key
- * @param ctrl
- * Was ctrl pressed
- * @param shift
- * Was shift pressed
- * @return Returns true if the key was handled, else false
- */
- protected boolean handleKeyNavigation(int keycode, boolean ctrl,
- boolean shift) {
- // Navigate down
- if (keycode == getNavigationDownKey()) {
- TreeNode node = null;
- // If node is open and has children then move in to the children
- if (!focusedNode.isLeaf() && focusedNode.getState()
- && focusedNode.getChildren().size() > 0) {
- node = focusedNode.getChildren().get(0);
- }
-
- // Else move down to the next sibling
- else {
- node = getNextSibling(focusedNode);
- if (node == null) {
- // Else jump to the parent and try to select the next
- // sibling there
- TreeNode current = focusedNode;
- while (node == null && current.getParentNode() != null) {
- node = getNextSibling(current.getParentNode());
- current = current.getParentNode();
- }
- }
- }
-
- if (node != null) {
- setFocusedNode(node);
- if (selectable) {
- if (!ctrl && !shift) {
- selectNode(node, true);
- } else if (shift && isMultiselect) {
- deselectAll();
- selectNodeRange(lastSelection.key, node.key);
- } else if (shift) {
- selectNode(node, true);
- }
- }
- }
- return true;
- }
-
- // Navigate up
- if (keycode == getNavigationUpKey()) {
- TreeNode prev = getPreviousSibling(focusedNode);
- TreeNode node = null;
- if (prev != null) {
- node = getLastVisibleChildInTree(prev);
- } else if (focusedNode.getParentNode() != null) {
- node = focusedNode.getParentNode();
- }
- if (node != null) {
- setFocusedNode(node);
- if (selectable) {
- if (!ctrl && !shift) {
- selectNode(node, true);
- } else if (shift && isMultiselect) {
- deselectAll();
- selectNodeRange(lastSelection.key, node.key);
- } else if (shift) {
- selectNode(node, true);
- }
- }
- }
- return true;
- }
-
- // Navigate left (close branch)
- if (keycode == getNavigationLeftKey()) {
- if (!focusedNode.isLeaf() && focusedNode.getState()) {
- focusedNode.setState(false, true);
- } else if (focusedNode.getParentNode() != null
- && (focusedNode.isLeaf() || !focusedNode.getState())) {
-
- if (ctrl || !selectable) {
- setFocusedNode(focusedNode.getParentNode());
- } else if (shift) {
- doRelationSelection(focusedNode.getParentNode(),
- focusedNode);
- setFocusedNode(focusedNode.getParentNode());
- } else {
- focusAndSelectNode(focusedNode.getParentNode());
- }
- }
- return true;
- }
-
- // Navigate right (open branch)
- if (keycode == getNavigationRightKey()) {
- if (!focusedNode.isLeaf() && !focusedNode.getState()) {
- focusedNode.setState(true, true);
- } else if (!focusedNode.isLeaf()) {
- if (ctrl || !selectable) {
- setFocusedNode(focusedNode.getChildren().get(0));
- } else if (shift) {
- setSelected(focusedNode, true);
- setFocusedNode(focusedNode.getChildren().get(0));
- setSelected(focusedNode, true);
- } else {
- focusAndSelectNode(focusedNode.getChildren().get(0));
- }
- }
- return true;
- }
-
- // Selection
- if (keycode == getNavigationSelectKey()) {
- if (!focusedNode.isSelected()) {
- selectNode(
- focusedNode,
- (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE)
- && selectable);
- } else {
- deselectNode(focusedNode);
- }
- return true;
- }
-
- // Home selection
- if (keycode == getNavigationStartKey()) {
- TreeNode node = getFirstRootNode();
- if (ctrl || !selectable) {
- setFocusedNode(node);
- } else if (shift) {
- deselectAll();
- selectNodeRange(focusedNode.key, node.key);
- } else {
- selectNode(node, true);
- }
- sendSelectionToServer();
- return true;
- }
-
- // End selection
- if (keycode == getNavigationEndKey()) {
- TreeNode lastNode = getLastRootNode();
- TreeNode node = getLastVisibleChildInTree(lastNode);
- if (ctrl || !selectable) {
- setFocusedNode(node);
- } else if (shift) {
- deselectAll();
- selectNodeRange(focusedNode.key, node.key);
- } else {
- selectNode(node, true);
- }
- sendSelectionToServer();
- return true;
- }
-
- return false;
- }
-
- private void focusAndSelectNode(TreeNode node) {
- /*
- * Keyboard navigation doesn't work reliably if the tree is in
- * multiselect mode as well as isNullSelectionAllowed = false. It first
- * tries to deselect the old focused node, which fails since there must
- * be at least one selection. After this the newly focused node is
- * selected and we've ended up with two selected nodes even though we
- * only navigated with the arrow keys.
- *
- * Because of this, we first select the next node and later de-select
- * the old one.
- */
- TreeNode oldFocusedNode = focusedNode;
- setFocusedNode(node);
- setSelected(focusedNode, true);
- setSelected(oldFocusedNode, false);
- }
-
- /**
- * Traverses the tree to the bottom most child
- *
- * @param root
- * The root of the tree
- * @return The bottom most child
- */
- private TreeNode getLastVisibleChildInTree(TreeNode root) {
- if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
- return root;
- }
- List<TreeNode> children = root.getChildren();
- return getLastVisibleChildInTree(children.get(children.size() - 1));
- }
-
- /**
- * Gets the next sibling in the tree
- *
- * @param node
- * The node to get the sibling for
- * @return The sibling node or null if the node is the last sibling
- */
- private TreeNode getNextSibling(TreeNode node) {
- TreeNode parent = node.getParentNode();
- List<TreeNode> children;
- if (parent == null) {
- children = getRootNodes();
- } else {
- children = parent.getChildren();
- }
-
- int idx = children.indexOf(node);
- if (idx < children.size() - 1) {
- return children.get(idx + 1);
- }
-
- return null;
- }
-
- /**
- * Returns the previous sibling in the tree
- *
- * @param node
- * The node to get the sibling for
- * @return The sibling node or null if the node is the first sibling
- */
- private TreeNode getPreviousSibling(TreeNode node) {
- TreeNode parent = node.getParentNode();
- List<TreeNode> children;
- if (parent == null) {
- children = getRootNodes();
- } else {
- children = parent.getChildren();
- }
-
- int idx = children.indexOf(node);
- if (idx > 0) {
- return children.get(idx - 1);
- }
-
- return null;
- }
-
- /**
- * Add this to the element mouse down event by using element.setPropertyJSO
- * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
- * when the mouse is depressed in the mouse up event.
- *
- * @return Returns the JSO preventing text selection
- */
- private native JavaScriptObject applyDisableTextSelectionIEHack()
- /*-{
- return function(){ return false; };
- }-*/;
-
- /**
- * Get the key that moves the selection head upwards. By default it is the
- * up arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationUpKey() {
- return KeyCodes.KEY_UP;
- }
-
- /**
- * Get the key that moves the selection head downwards. By default it is the
- * down arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationDownKey() {
- return KeyCodes.KEY_DOWN;
- }
-
- /**
- * Get the key that scrolls to the left in the table. By default it is the
- * left arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationLeftKey() {
- return KeyCodes.KEY_LEFT;
- }
-
- /**
- * Get the key that scroll to the right on the table. By default it is the
- * right arrow key but by overriding this you can change the key to whatever
- * you want.
- *
- * @return The keycode of the key
- */
- protected int getNavigationRightKey() {
- return KeyCodes.KEY_RIGHT;
- }
-
- /**
- * Get the key that selects an item in the table. By default it is the space
- * bar key but by overriding this you can change the key to whatever you
- * want.
- *
- * @return
- */
- protected int getNavigationSelectKey() {
- return CHARCODE_SPACE;
- }
-
- /**
- * Get the key the moves the selection one page up in the table. By default
- * this is the Page Up key but by overriding this you can change the key to
- * whatever you want.
- *
- * @return
- */
- protected int getNavigationPageUpKey() {
- return KeyCodes.KEY_PAGEUP;
- }
-
- /**
- * Get the key the moves the selection one page down in the table. By
- * default this is the Page Down key but by overriding this you can change
- * the key to whatever you want.
- *
- * @return
- */
- protected int getNavigationPageDownKey() {
- return KeyCodes.KEY_PAGEDOWN;
- }
-
- /**
- * Get the key the moves the selection to the beginning of the table. By
- * default this is the Home key but by overriding this you can change the
- * key to whatever you want.
- *
- * @return
- */
- protected int getNavigationStartKey() {
- return KeyCodes.KEY_HOME;
- }
-
- /**
- * Get the key the moves the selection to the end of the table. By default
- * this is the End key but by overriding this you can change the key to
- * whatever you want.
- *
- * @return
- */
- protected int getNavigationEndKey() {
- return KeyCodes.KEY_END;
- }
-
- private final String SUBPART_NODE_PREFIX = "n";
- private final String EXPAND_IDENTIFIER = "expand";
-
- /*
- * In webkit, focus may have been requested for this component but not yet
- * gained. Use this to trac if tree has gained the focus on webkit. See
- * FocusImplSafari and #6373
- */
- private boolean treeHasFocus;
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java
- * .lang.String)
- */
- @Override
- public Element getSubPartElement(String subPart) {
- if ("fe".equals(subPart)) {
- if (BrowserInfo.get().isOpera() && focusedNode != null) {
- return focusedNode.getElement();
- }
- return getFocusElement();
- }
-
- if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) {
- boolean expandCollapse = false;
-
- // Node
- String[] nodes = subPart.split("/");
- TreeNode treeNode = null;
- try {
- for (String node : nodes) {
- if (node.startsWith(SUBPART_NODE_PREFIX)) {
-
- // skip SUBPART_NODE_PREFIX"["
- node = node.substring(SUBPART_NODE_PREFIX.length() + 1);
- // skip "]"
- node = node.substring(0, node.length() - 1);
- int position = Integer.parseInt(node);
- if (treeNode == null) {
- treeNode = getRootNodes().get(position);
- } else {
- treeNode = treeNode.getChildren().get(position);
- }
- } else if (node.startsWith(EXPAND_IDENTIFIER)) {
- expandCollapse = true;
- }
- }
-
- if (expandCollapse) {
- return treeNode.getElement();
- } else {
- return treeNode.nodeCaptionSpan;
- }
- } catch (Exception e) {
- // Invalid locator string or node could not be found
- return null;
- }
- }
- return null;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google
- * .gwt.user.client.Element)
- */
- @Override
- public String getSubPartName(Element subElement) {
- // Supported identifiers:
- //
- // n[index]/n[index]/n[index]{/expand}
- //
- // Ends with "/expand" if the target is expand/collapse indicator,
- // otherwise ends with the node
-
- boolean isExpandCollapse = false;
-
- if (!getElement().isOrHasChild(subElement)) {
- return null;
- }
-
- if (subElement == getFocusElement()) {
- return "fe";
- }
-
- TreeNode treeNode = Util.findWidget(subElement, TreeNode.class);
- if (treeNode == null) {
- // Did not click on a node, let somebody else take care of the
- // locator string
- return null;
- }
-
- if (subElement == treeNode.getElement()) {
- // Targets expand/collapse arrow
- isExpandCollapse = true;
- }
-
- ArrayList<Integer> positions = new ArrayList<Integer>();
- while (treeNode.getParentNode() != null) {
- positions.add(0,
- treeNode.getParentNode().getChildren().indexOf(treeNode));
- treeNode = treeNode.getParentNode();
- }
- positions.add(0, getRootNodes().indexOf(treeNode));
-
- String locator = "";
- for (Integer i : positions) {
- locator += SUBPART_NODE_PREFIX + "[" + i + "]/";
- }
-
- locator = locator.substring(0, locator.length() - 1);
- if (isExpandCollapse) {
- locator += "/" + EXPAND_IDENTIFIER;
- }
- return locator;
- }
-
- @Override
- public Action[] getActions() {
- if (bodyActionKeys == null) {
- return new Action[] {};
- }
- final Action[] actions = new Action[bodyActionKeys.length];
- for (int i = 0; i < actions.length; i++) {
- final String actionKey = bodyActionKeys[i];
- final TreeAction a = new TreeAction(this, null, actionKey);
- a.setCaption(getActionCaption(actionKey));
- a.setIconUrl(getActionIcon(actionKey));
- actions[i] = a;
- }
- return actions;
- }
-
- @Override
- public ApplicationConnection getClient() {
- return client;
- }
-
- @Override
- public String getPaintableId() {
- return paintableId;
- }
-
- private void handleBodyContextMenu(ContextMenuEvent event) {
- if (!readonly && !disabled) {
- if (bodyActionKeys != null) {
- int left = event.getNativeEvent().getClientX();
- int top = event.getNativeEvent().getClientY();
- top += Window.getScrollTop();
- left += Window.getScrollLeft();
- client.getContextMenu().showAt(this, left, top);
- }
- event.stopPropagation();
- event.preventDefault();
- }
- }
-
- public void registerAction(String key, String caption, String iconUrl) {
- actionMap.put(key + "_c", caption);
- if (iconUrl != null) {
- actionMap.put(key + "_i", iconUrl);
- } else {
- actionMap.remove(key + "_i");
- }
-
- }
-
- public void registerNode(TreeNode treeNode) {
- keyToNode.put(treeNode.key, treeNode);
- }
-
- public void clearNodeToKeyMap() {
- keyToNode.clear();
- }
-
-}
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.FocusableScrollPanel;
+import com.vaadin.client.ui.VTreeTable;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.client.ui.VTreeTable.PendingNavigationEvent;
import com.vaadin.client.ui.table.TableConnector;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
-import com.vaadin.client.ui.treetable.VTreeTable.PendingNavigationEvent;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.treetable.TreeTableConstants;
import com.vaadin.shared.ui.treetable.TreeTableState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.treetable;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-import com.google.gwt.animation.client.Animation;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.ImageElement;
-import com.google.gwt.dom.client.SpanElement;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComputedStyle;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.table.VScrollTable;
-import com.vaadin.client.ui.treetable.VTreeTable.VTreeTableScrollBody.VTreeTableRow;
-
-public class VTreeTable extends VScrollTable {
-
- static class PendingNavigationEvent {
- final int keycode;
- final boolean ctrl;
- final boolean shift;
-
- public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) {
- this.keycode = keycode;
- this.ctrl = ctrl;
- this.shift = shift;
- }
-
- @Override
- public String toString() {
- String string = "Keyboard event: " + keycode;
- if (ctrl) {
- string += " + ctrl";
- }
- if (shift) {
- string += " + shift";
- }
- return string;
- }
- }
-
- boolean collapseRequest;
- private boolean selectionPending;
- int colIndexOfHierarchy;
- String collapsedRowKey;
- VTreeTableScrollBody scrollBody;
- boolean animationsEnabled;
- LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>();
- boolean focusParentResponsePending;
-
- @Override
- protected VScrollTableBody createScrollBody() {
- scrollBody = new VTreeTableScrollBody();
- return scrollBody;
- }
-
- /*
- * Overridden to allow animation of expands and collapses of nodes.
- */
- @Override
- protected void addAndRemoveRows(UIDL partialRowAdditions) {
- if (partialRowAdditions == null) {
- return;
- }
-
- if (animationsEnabled) {
- if (partialRowAdditions.hasAttribute("hide")) {
- scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
- partialRowAdditions.getIntAttribute("firstprowix"),
- partialRowAdditions.getIntAttribute("numprows"));
- } else {
- scrollBody.insertRowsAnimated(partialRowAdditions,
- partialRowAdditions.getIntAttribute("firstprowix"),
- partialRowAdditions.getIntAttribute("numprows"));
- discardRowsOutsideCacheWindow();
- }
- } else {
- super.addAndRemoveRows(partialRowAdditions);
- }
- }
-
- class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
- private int indentWidth = -1;
-
- VTreeTableScrollBody() {
- super();
- }
-
- @Override
- protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
- if (uidl.hasAttribute("gen_html")) {
- // This is a generated row.
- return new VTreeTableGeneratedRow(uidl, aligns2);
- }
- return new VTreeTableRow(uidl, aligns2);
- }
-
- class VTreeTableRow extends
- VScrollTable.VScrollTableBody.VScrollTableRow {
-
- private boolean isTreeCellAdded = false;
- private SpanElement treeSpacer;
- private boolean open;
- private int depth;
- private boolean canHaveChildren;
- protected Widget widgetInHierarchyColumn;
-
- public VTreeTableRow(UIDL uidl, char[] aligns2) {
- super(uidl, aligns2);
- }
-
- @Override
- public void addCell(UIDL rowUidl, String text, char align,
- String style, boolean textIsHTML, boolean isSorted,
- String description) {
- super.addCell(rowUidl, text, align, style, textIsHTML,
- isSorted, description);
-
- addTreeSpacer(rowUidl);
- }
-
- protected boolean addTreeSpacer(UIDL rowUidl) {
- if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) {
- Element container = (Element) getElement().getLastChild()
- .getFirstChild();
-
- if (rowUidl.hasAttribute("icon")) {
- // icons are in first content cell in TreeTable
- ImageElement icon = Document.get().createImageElement();
- icon.setClassName("v-icon");
- icon.setAlt("icon");
- icon.setSrc(client.translateVaadinUri(rowUidl
- .getStringAttribute("icon")));
- container.insertFirst(icon);
- }
-
- String classname = "v-treetable-treespacer";
- if (rowUidl.getBooleanAttribute("ca")) {
- canHaveChildren = true;
- open = rowUidl.getBooleanAttribute("open");
- classname += open ? " v-treetable-node-open"
- : " v-treetable-node-closed";
- }
-
- treeSpacer = Document.get().createSpanElement();
-
- treeSpacer.setClassName(classname);
- container.insertFirst(treeSpacer);
- depth = rowUidl.hasAttribute("depth") ? rowUidl
- .getIntAttribute("depth") : 0;
- setIndent();
- isTreeCellAdded = true;
- return true;
- }
- return false;
- }
-
- private boolean cellShowsTreeHierarchy(int curColIndex) {
- if (isTreeCellAdded) {
- return false;
- }
- return curColIndex == getHierarchyColumnIndex();
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- if (event.getEventTarget().cast() == treeSpacer
- && treeSpacer.getClassName().contains("node")) {
- if (event.getTypeInt() == Event.ONMOUSEUP) {
- sendToggleCollapsedUpdate(getKey());
- }
- return;
- }
- super.onBrowserEvent(event);
- }
-
- @Override
- public void addCell(UIDL rowUidl, Widget w, char align,
- String style, boolean isSorted) {
- super.addCell(rowUidl, w, align, style, isSorted);
- if (addTreeSpacer(rowUidl)) {
- widgetInHierarchyColumn = w;
- }
-
- }
-
- private void setIndent() {
- if (getIndentWidth() > 0) {
- treeSpacer.getParentElement().getStyle()
- .setPaddingLeft(getIndent(), Unit.PX);
- treeSpacer.getStyle().setWidth(getIndent(), Unit.PX);
- }
- }
-
- @Override
- protected void onAttach() {
- super.onAttach();
- if (getIndentWidth() < 0) {
- detectIndent(this);
- // If we detect indent here then the size of the hierarchy
- // column is still wrong as it has been set when the indent
- // was not known.
- int w = getCellWidthFromDom(getHierarchyColumnIndex());
- if (w >= 0) {
- setColWidth(getHierarchyColumnIndex(), w);
- }
- }
- }
-
- private int getCellWidthFromDom(int cellIndex) {
- final Element cell = DOM.getChild(getElement(), cellIndex);
- String w = cell.getStyle().getProperty("width");
- if (w == null || "".equals(w) || !w.endsWith("px")) {
- return -1;
- } else {
- return Integer.parseInt(w.substring(0, w.length() - 2));
- }
- }
-
- private int getHierarchyAndIconWidth() {
- int consumedSpace = treeSpacer.getOffsetWidth();
- if (treeSpacer.getParentElement().getChildCount() > 2) {
- // icon next to tree spacer
- consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer
- .getNextSibling()).getOffsetWidth();
- }
- return consumedSpace;
- }
-
- @Override
- protected void setCellWidth(int cellIx, int width) {
- if (cellIx == getHierarchyColumnIndex()) {
- // take indentation padding into account if this is the
- // hierarchy column
- int indent = getIndent();
- if (indent != -1) {
- width = Math.max(width - getIndent(), 0);
- }
- }
- super.setCellWidth(cellIx, width);
- }
-
- private int getHierarchyColumnIndex() {
- return colIndexOfHierarchy + (showRowHeaders ? 1 : 0);
- }
-
- private int getIndent() {
- return (depth + 1) * getIndentWidth();
- }
- }
-
- protected class VTreeTableGeneratedRow extends VTreeTableRow {
- private boolean spanColumns;
- private boolean htmlContentAllowed;
-
- public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) {
- super(uidl, aligns);
- addStyleName("v-table-generated-row");
- }
-
- public boolean isSpanColumns() {
- return spanColumns;
- }
-
- @Override
- protected void initCellWidths() {
- if (spanColumns) {
- setSpannedColumnWidthAfterDOMFullyInited();
- } else {
- super.initCellWidths();
- }
- }
-
- private void setSpannedColumnWidthAfterDOMFullyInited() {
- // Defer setting width on spanned columns to make sure that
- // they are added to the DOM before trying to calculate
- // widths.
- Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
- @Override
- public void execute() {
- if (showRowHeaders) {
- setCellWidth(0, tHead.getHeaderCell(0).getWidth());
- calcAndSetSpanWidthOnCell(1);
- } else {
- calcAndSetSpanWidthOnCell(0);
- }
- }
- });
- }
-
- @Override
- protected boolean isRenderHtmlInCells() {
- return htmlContentAllowed;
- }
-
- @Override
- protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
- int visibleColumnIndex) {
- htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
- spanColumns = uidl.getBooleanAttribute("gen_span");
-
- final Iterator<?> cells = uidl.getChildIterator();
- if (spanColumns) {
- int colCount = uidl.getChildCount();
- if (cells.hasNext()) {
- final Object cell = cells.next();
- if (cell instanceof String) {
- addSpannedCell(uidl, cell.toString(), aligns[0],
- "", htmlContentAllowed, false, null,
- colCount);
- } else {
- addSpannedCell(uidl, (Widget) cell, aligns[0], "",
- false, colCount);
- }
- }
- } else {
- super.addCellsFromUIDL(uidl, aligns, col,
- visibleColumnIndex);
- }
- }
-
- private void addSpannedCell(UIDL rowUidl, Widget w, char align,
- String style, boolean sorted, int colCount) {
- TableCellElement td = DOM.createTD().cast();
- td.setColSpan(colCount);
- initCellWithWidget(w, align, style, sorted, td);
- td.getStyle().setHeight(getRowHeight(), Unit.PX);
- if (addTreeSpacer(rowUidl)) {
- widgetInHierarchyColumn = w;
- }
- }
-
- private void addSpannedCell(UIDL rowUidl, String text, char align,
- String style, boolean textIsHTML, boolean sorted,
- String description, int colCount) {
- // String only content is optimized by not using Label widget
- final TableCellElement td = DOM.createTD().cast();
- td.setColSpan(colCount);
- initCellWithText(text, align, style, textIsHTML, sorted,
- description, td);
- td.getStyle().setHeight(getRowHeight(), Unit.PX);
- addTreeSpacer(rowUidl);
- }
-
- @Override
- protected void setCellWidth(int cellIx, int width) {
- if (isSpanColumns()) {
- if (showRowHeaders) {
- if (cellIx == 0) {
- super.setCellWidth(0, width);
- } else {
- // We need to recalculate the spanning TDs width for
- // every cellIx in order to support column resizing.
- calcAndSetSpanWidthOnCell(1);
- }
- } else {
- // Same as above.
- calcAndSetSpanWidthOnCell(0);
- }
- } else {
- super.setCellWidth(cellIx, width);
- }
- }
-
- private void calcAndSetSpanWidthOnCell(final int cellIx) {
- int spanWidth = 0;
- for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
- .getVisibleCellCount(); ix++) {
- spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
- }
- Util.setWidthExcludingPaddingAndBorder((Element) getElement()
- .getChild(cellIx), spanWidth, 13, false);
- }
- }
-
- private int getIndentWidth() {
- return indentWidth;
- }
-
- private void detectIndent(VTreeTableRow vTreeTableRow) {
- indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth();
- if (indentWidth == 0) {
- indentWidth = -1;
- return;
- }
- Iterator<Widget> iterator = iterator();
- while (iterator.hasNext()) {
- VTreeTableRow next = (VTreeTableRow) iterator.next();
- next.setIndent();
- }
- }
-
- protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
- final int firstIndex, final int rows) {
- List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
- for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
- VScrollTableRow row = getRowByRowIndex(ix);
- if (row != null) {
- rowsToDelete.add(row);
- }
- }
- if (!rowsToDelete.isEmpty()) {
- // #8810 Only animate if there's something to animate
- RowCollapseAnimation anim = new RowCollapseAnimation(
- rowsToDelete) {
- @Override
- protected void onComplete() {
- super.onComplete();
- // Actually unlink the rows and update the cache after
- // the
- // animation is done.
- unlinkAndReindexRows(firstIndex, rows);
- discardRowsOutsideCacheWindow();
- ensureCacheFilled();
- }
- };
- anim.run(150);
- }
- }
-
- protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
- int firstIndex, int rows) {
- List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData,
- firstIndex, rows);
- if (!insertedRows.isEmpty()) {
- // Only animate if there's something to animate (#8810)
- RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
- anim.run(150);
- }
- return insertedRows;
- }
-
- /**
- * Prepares the table for animation by copying the background colors of
- * all TR elements to their respective TD elements if the TD element is
- * transparent. This is needed, since if TDs have transparent
- * backgrounds, the rows sliding behind them are visible.
- */
- private class AnimationPreparator {
- private final int lastItemIx;
-
- public AnimationPreparator(int lastItemIx) {
- this.lastItemIx = lastItemIx;
- }
-
- public void prepareTableForAnimation() {
- int ix = lastItemIx;
- VScrollTableRow row = null;
- while ((row = getRowByRowIndex(ix)) != null) {
- copyTRBackgroundsToTDs(row);
- --ix;
- }
- }
-
- private void copyTRBackgroundsToTDs(VScrollTableRow row) {
- Element tr = row.getElement();
- ComputedStyle cs = new ComputedStyle(tr);
- String backgroundAttachment = cs
- .getProperty("backgroundAttachment");
- String backgroundClip = cs.getProperty("backgroundClip");
- String backgroundColor = cs.getProperty("backgroundColor");
- String backgroundImage = cs.getProperty("backgroundImage");
- String backgroundOrigin = cs.getProperty("backgroundOrigin");
- for (int ix = 0; ix < tr.getChildCount(); ix++) {
- Element td = tr.getChild(ix).cast();
- if (!elementHasBackground(td)) {
- td.getStyle().setProperty("backgroundAttachment",
- backgroundAttachment);
- td.getStyle().setProperty("backgroundClip",
- backgroundClip);
- td.getStyle().setProperty("backgroundColor",
- backgroundColor);
- td.getStyle().setProperty("backgroundImage",
- backgroundImage);
- td.getStyle().setProperty("backgroundOrigin",
- backgroundOrigin);
- }
- }
- }
-
- private boolean elementHasBackground(Element element) {
- ComputedStyle cs = new ComputedStyle(element);
- String clr = cs.getProperty("backgroundColor");
- String img = cs.getProperty("backgroundImage");
- return !("rgba(0, 0, 0, 0)".equals(clr.trim())
- || "transparent".equals(clr.trim()) || img == null);
- }
-
- public void restoreTableAfterAnimation() {
- int ix = lastItemIx;
- VScrollTableRow row = null;
- while ((row = getRowByRowIndex(ix)) != null) {
- restoreStyleForTDsInRow(row);
-
- --ix;
- }
- }
-
- private void restoreStyleForTDsInRow(VScrollTableRow row) {
- Element tr = row.getElement();
- for (int ix = 0; ix < tr.getChildCount(); ix++) {
- Element td = tr.getChild(ix).cast();
- td.getStyle().clearProperty("backgroundAttachment");
- td.getStyle().clearProperty("backgroundClip");
- td.getStyle().clearProperty("backgroundColor");
- td.getStyle().clearProperty("backgroundImage");
- td.getStyle().clearProperty("backgroundOrigin");
- }
- }
- }
-
- /**
- * Animates row expansion using the GWT animation framework.
- *
- * The idea is as follows:
- *
- * 1. Insert all rows normally
- *
- * 2. Insert a newly created DIV containing a new TABLE element below
- * the DIV containing the actual scroll table body.
- *
- * 3. Clone the rows that were inserted in step 1 and attach the clones
- * to the new TABLE element created in step 2.
- *
- * 4. The new DIV from step 2 is absolutely positioned so that the last
- * inserted row is just behind the row that was expanded.
- *
- * 5. Hide the contents of the originally inserted rows by setting the
- * DIV.v-table-cell-wrapper to display:none;.
- *
- * 6. Set the height of the originally inserted rows to 0.
- *
- * 7. The animation loop slides the DIV from step 2 downwards, while at
- * the same pace growing the height of each of the inserted rows from 0
- * to full height. The first inserted row grows from 0 to full and after
- * this the second row grows from 0 to full, etc until all rows are full
- * height.
- *
- * 8. Remove the DIV from step 2
- *
- * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
- *
- * 10. DONE
- */
- private class RowExpandAnimation extends Animation {
-
- private final List<VScrollTableRow> rows;
- private Element cloneDiv;
- private Element cloneTable;
- private AnimationPreparator preparator;
-
- /**
- * @param rows
- * List of rows to animate. Must not be empty.
- */
- public RowExpandAnimation(List<VScrollTableRow> rows) {
- this.rows = rows;
- buildAndInsertAnimatingDiv();
- preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
- preparator.prepareTableForAnimation();
- for (VScrollTableRow row : rows) {
- cloneAndAppendRow(row);
- row.addStyleName("v-table-row-animating");
- setCellWrapperDivsToDisplayNone(row);
- row.setHeight(getInitialHeight());
- }
- }
-
- protected String getInitialHeight() {
- return "0px";
- }
-
- private void cloneAndAppendRow(VScrollTableRow row) {
- Element clonedTR = null;
- clonedTR = row.getElement().cloneNode(true).cast();
- clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
- cloneTable.appendChild(clonedTR);
- }
-
- protected double getBaseOffset() {
- return rows.get(0).getAbsoluteTop()
- - rows.get(0).getParent().getAbsoluteTop()
- - rows.size() * getRowHeight();
- }
-
- private void buildAndInsertAnimatingDiv() {
- cloneDiv = DOM.createDiv();
- cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
- cloneTable = DOM.createTable();
- cloneTable.addClassName("v-treetable-animation-clone");
- cloneDiv.appendChild(cloneTable);
- insertAnimatingDiv();
- }
-
- private void insertAnimatingDiv() {
- Element tableBody = getElement().cast();
- Element tableBodyParent = tableBody.getParentElement().cast();
- tableBodyParent.insertAfter(cloneDiv, tableBody);
- }
-
- @Override
- protected void onUpdate(double progress) {
- animateDiv(progress);
- animateRowHeights(progress);
- }
-
- private void animateDiv(double progress) {
- double offset = calculateDivOffset(progress, getRowHeight());
-
- cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
- }
-
- private void animateRowHeights(double progress) {
- double rh = getRowHeight();
- double vlh = calculateHeightOfAllVisibleLines(progress, rh);
- int ix = 0;
-
- while (ix < rows.size()) {
- double height = vlh < rh ? vlh : rh;
- rows.get(ix).setHeight(height + "px");
- vlh -= height;
- ix++;
- }
- }
-
- protected double calculateHeightOfAllVisibleLines(double progress,
- double rh) {
- return rows.size() * rh * progress;
- }
-
- protected double calculateDivOffset(double progress, double rh) {
- return progress * rows.size() * rh;
- }
-
- @Override
- protected void onComplete() {
- preparator.restoreTableAfterAnimation();
- for (VScrollTableRow row : rows) {
- resetCellWrapperDivsDisplayProperty(row);
- row.removeStyleName("v-table-row-animating");
- }
- Element tableBodyParent = (Element) getElement()
- .getParentElement();
- tableBodyParent.removeChild(cloneDiv);
- }
-
- private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
- Element tr = row.getElement();
- for (int ix = 0; ix < tr.getChildCount(); ix++) {
- getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
- }
- }
-
- private Element getWrapperDiv(Element tr, int tdIx) {
- Element td = tr.getChild(tdIx).cast();
- return td.getChild(0).cast();
- }
-
- private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
- Element tr = row.getElement();
- for (int ix = 0; ix < tr.getChildCount(); ix++) {
- getWrapperDiv(tr, ix).getStyle().clearProperty("display");
- }
- }
-
- }
-
- /**
- * This is the inverse of the RowExpandAnimation and is implemented by
- * extending it and overriding the calculation of offsets and heights.
- */
- private class RowCollapseAnimation extends RowExpandAnimation {
-
- private final List<VScrollTableRow> rows;
-
- /**
- * @param rows
- * List of rows to animate. Must not be empty.
- */
- public RowCollapseAnimation(List<VScrollTableRow> rows) {
- super(rows);
- this.rows = rows;
- }
-
- @Override
- protected String getInitialHeight() {
- return getRowHeight() + "px";
- }
-
- @Override
- protected double getBaseOffset() {
- return getRowHeight();
- }
-
- @Override
- protected double calculateHeightOfAllVisibleLines(double progress,
- double rh) {
- return rows.size() * rh * (1 - progress);
- }
-
- @Override
- protected double calculateDivOffset(double progress, double rh) {
- return -super.calculateDivOffset(progress, rh);
- }
- }
- }
-
- /**
- * Icons rendered into first actual column in TreeTable, not to row header
- * cell
- */
- @Override
- protected String buildCaptionHtmlSnippet(UIDL uidl) {
- if (uidl.getTag().equals("column")) {
- return super.buildCaptionHtmlSnippet(uidl);
- } else {
- String s = uidl.getStringAttribute("caption");
- return s;
- }
- }
-
- @Override
- protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
- if (collapseRequest || focusParentResponsePending) {
- // Enqueue the event if there might be pending content changes from
- // the server
- if (pendingNavigationEvents.size() < 10) {
- // Only keep 10 keyboard events in the queue
- PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent(
- keycode, ctrl, shift);
- pendingNavigationEvents.add(pendingNavigationEvent);
- }
- return true;
- }
-
- VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow();
- if (focusedRow != null) {
- if (focusedRow.canHaveChildren
- && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) {
- if (!ctrl) {
- client.updateVariable(paintableId, "selectCollapsed", true,
- false);
- }
- sendSelectedRows(false);
- sendToggleCollapsedUpdate(focusedRow.getKey());
- return true;
- } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) {
- // already expanded, move selection down if next is on a deeper
- // level (is-a-child)
- VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow
- .getParent();
- Iterator<Widget> iterator = body.iterator();
- VTreeTableRow next = null;
- while (iterator.hasNext()) {
- next = (VTreeTableRow) iterator.next();
- if (next == focusedRow) {
- next = (VTreeTableRow) iterator.next();
- break;
- }
- }
- if (next != null) {
- if (next.depth > focusedRow.depth) {
- selectionPending = true;
- return super.handleNavigation(getNavigationDownKey(),
- ctrl, shift);
- }
- } else {
- // Note, a minor change here for a bit false behavior if
- // cache rows is disabled + last visible row + no childs for
- // the node
- selectionPending = true;
- return super.handleNavigation(getNavigationDownKey(), ctrl,
- shift);
- }
- } else if (keycode == KeyCodes.KEY_LEFT) {
- // already collapsed move selection up to parent node
- // do on the server side as the parent is not necessary
- // rendered on the client, could check if parent is visible if
- // a performance issue arises
-
- client.updateVariable(paintableId, "focusParent",
- focusedRow.getKey(), true);
-
- // Set flag that we should enqueue navigation events until we
- // get a response to this request
- focusParentResponsePending = true;
-
- return true;
- }
- }
- return super.handleNavigation(keycode, ctrl, shift);
- }
-
- private void sendToggleCollapsedUpdate(String rowKey) {
- collapsedRowKey = rowKey;
- collapseRequest = true;
- client.updateVariable(paintableId, "toggleCollapsed", rowKey, true);
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONKEYUP && selectionPending) {
- sendSelectedRows();
- }
- }
-
- @Override
- protected void sendSelectedRows(boolean immediately) {
- super.sendSelectedRows(immediately);
- selectionPending = false;
- }
-
- @Override
- protected void reOrderColumn(String columnKey, int newIndex) {
- super.reOrderColumn(columnKey, newIndex);
- // current impl not intelligent enough to survive without visiting the
- // server to redraw content
- client.sendPendingVariableChanges();
- }
-
- @Override
- public void setStyleName(String style) {
- super.setStyleName(style + " v-treetable");
- }
-
- @Override
- protected void updateTotalRows(UIDL uidl) {
- // Make sure that initializedAndAttached & al are not reset when the
- // totalrows are updated on expand/collapse requests.
- int newTotalRows = uidl.getIntAttribute("totalrows");
- setTotalRows(newTotalRows);
- }
-
-}
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.DirectionalManagedLayout;
import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VTwinColSelect;
import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.twincolselect.TwinColSelectState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.twincolselect;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickHandler;
-import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Panel;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.button.VButton;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants;
-
-public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler,
- MouseDownHandler, DoubleClickHandler, SubPartAware {
-
- public static final String CLASSNAME = "v-select-twincol";
-
- private static final int VISIBLE_COUNT = 10;
-
- private static final int DEFAULT_COLUMN_COUNT = 10;
-
- private final DoubleClickListBox options;
-
- private final DoubleClickListBox selections;
-
- FlowPanel captionWrapper;
-
- private HTML optionsCaption = null;
-
- private HTML selectionsCaption = null;
-
- private final VButton add;
-
- private final VButton remove;
-
- private final FlowPanel buttons;
-
- private final Panel panel;
-
- /**
- * A ListBox which catches double clicks
- *
- */
- public class DoubleClickListBox extends ListBox implements
- HasDoubleClickHandlers {
- public DoubleClickListBox(boolean isMultipleSelect) {
- super(isMultipleSelect);
- }
-
- public DoubleClickListBox() {
- super();
- }
-
- @Override
- public HandlerRegistration addDoubleClickHandler(
- DoubleClickHandler handler) {
- return addDomHandler(handler, DoubleClickEvent.getType());
- }
- }
-
- public VTwinColSelect() {
- super(CLASSNAME);
-
- captionWrapper = new FlowPanel();
-
- options = new DoubleClickListBox();
- options.addClickHandler(this);
- options.addDoubleClickHandler(this);
- options.setVisibleItemCount(VISIBLE_COUNT);
- options.setStyleName(CLASSNAME + "-options");
-
- selections = new DoubleClickListBox();
- selections.addClickHandler(this);
- selections.addDoubleClickHandler(this);
- selections.setVisibleItemCount(VISIBLE_COUNT);
- selections.setStyleName(CLASSNAME + "-selections");
-
- buttons = new FlowPanel();
- buttons.setStyleName(CLASSNAME + "-buttons");
- add = new VButton();
- add.setText(">>");
- add.addClickHandler(this);
- remove = new VButton();
- remove.setText("<<");
- remove.addClickHandler(this);
-
- panel = ((Panel) optionsContainer);
-
- panel.add(captionWrapper);
- captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
- // Hide until there actually is a caption to prevent IE from rendering
- // extra empty space
- captionWrapper.setVisible(false);
-
- panel.add(options);
- buttons.add(add);
- final HTML br = new HTML("<span/>");
- br.setStyleName(CLASSNAME + "-deco");
- buttons.add(br);
- buttons.add(remove);
- panel.add(buttons);
- panel.add(selections);
-
- options.addKeyDownHandler(this);
- options.addMouseDownHandler(this);
-
- selections.addMouseDownHandler(this);
- selections.addKeyDownHandler(this);
- }
-
- public HTML getOptionsCaption() {
- if (optionsCaption == null) {
- optionsCaption = new HTML();
- optionsCaption.setStyleName(CLASSNAME + "-caption-left");
- optionsCaption.getElement().getStyle()
- .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
- captionWrapper.add(optionsCaption);
- }
-
- return optionsCaption;
- }
-
- public HTML getSelectionsCaption() {
- if (selectionsCaption == null) {
- selectionsCaption = new HTML();
- selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
- selectionsCaption.getElement().getStyle()
- .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
- captionWrapper.add(selectionsCaption);
- }
-
- return selectionsCaption;
- }
-
- protected void updateCaptions(UIDL uidl) {
- String leftCaption = (uidl
- .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl
- .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
- : null);
- String rightCaption = (uidl
- .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl
- .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
- : null);
-
- boolean hasCaptions = (leftCaption != null || rightCaption != null);
-
- if (leftCaption == null) {
- removeOptionsCaption();
- } else {
- getOptionsCaption().setText(leftCaption);
-
- }
-
- if (rightCaption == null) {
- removeSelectionsCaption();
- } else {
- getSelectionsCaption().setText(rightCaption);
- }
-
- captionWrapper.setVisible(hasCaptions);
- }
-
- private void removeOptionsCaption() {
- if (optionsCaption == null) {
- return;
- }
-
- if (optionsCaption.getParent() != null) {
- captionWrapper.remove(optionsCaption);
- }
-
- optionsCaption = null;
- }
-
- private void removeSelectionsCaption() {
- if (selectionsCaption == null) {
- return;
- }
-
- if (selectionsCaption.getParent() != null) {
- captionWrapper.remove(selectionsCaption);
- }
-
- selectionsCaption = null;
- }
-
- @Override
- protected void buildOptions(UIDL uidl) {
- final boolean enabled = !isDisabled() && !isReadonly();
- options.setMultipleSelect(isMultiselect());
- selections.setMultipleSelect(isMultiselect());
- options.setEnabled(enabled);
- selections.setEnabled(enabled);
- add.setEnabled(enabled && !readonly);
- remove.setEnabled(enabled && !readonly);
- options.clear();
- selections.clear();
- for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
- final UIDL optionUidl = (UIDL) i.next();
- if (optionUidl.hasAttribute("selected")) {
- selections.addItem(optionUidl.getStringAttribute("caption"),
- optionUidl.getStringAttribute("key"));
- } else {
- options.addItem(optionUidl.getStringAttribute("caption"),
- optionUidl.getStringAttribute("key"));
- }
- }
-
- if (getRows() > 0) {
- options.setVisibleItemCount(getRows());
- selections.setVisibleItemCount(getRows());
-
- }
-
- add.setStyleName("v-disabled", readonly);
- remove.setStyleName("v-disabled", readonly);
- }
-
- @Override
- protected String[] getSelectedItems() {
- final ArrayList<String> selectedItemKeys = new ArrayList<String>();
- for (int i = 0; i < selections.getItemCount(); i++) {
- selectedItemKeys.add(selections.getValue(i));
- }
- return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
- }
-
- private boolean[] getSelectionBitmap(ListBox listBox) {
- final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
- for (int i = 0; i < listBox.getItemCount(); i++) {
- if (listBox.isItemSelected(i)) {
- selectedIndexes[i] = true;
- } else {
- selectedIndexes[i] = false;
- }
- }
- return selectedIndexes;
- }
-
- private void addItem() {
- Set<String> movedItems = moveSelectedItems(options, selections);
- selectedKeys.addAll(movedItems);
-
- client.updateVariable(paintableId, "selected",
- selectedKeys.toArray(new String[selectedKeys.size()]),
- isImmediate());
- }
-
- private void removeItem() {
- Set<String> movedItems = moveSelectedItems(selections, options);
- selectedKeys.removeAll(movedItems);
-
- client.updateVariable(paintableId, "selected",
- selectedKeys.toArray(new String[selectedKeys.size()]),
- isImmediate());
- }
-
- private Set<String> moveSelectedItems(ListBox source, ListBox target) {
- final boolean[] sel = getSelectionBitmap(source);
- final Set<String> movedItems = new HashSet<String>();
- int lastSelected = 0;
- for (int i = 0; i < sel.length; i++) {
- if (sel[i]) {
- final int optionIndex = i
- - (sel.length - source.getItemCount());
- movedItems.add(source.getValue(optionIndex));
-
- // Move selection to another column
- final String text = source.getItemText(optionIndex);
- final String value = source.getValue(optionIndex);
- target.addItem(text, value);
- target.setItemSelected(target.getItemCount() - 1, true);
- source.removeItem(optionIndex);
-
- if (source.getItemCount() > 0) {
- lastSelected = optionIndex > 0 ? optionIndex - 1 : 0;
- }
- }
- }
-
- if (source.getItemCount() > 0) {
- source.setSelectedIndex(lastSelected);
- }
-
- // If no items are left move the focus to the selections
- if (source.getItemCount() == 0) {
- target.setFocus(true);
- } else {
- source.setFocus(true);
- }
-
- return movedItems;
- }
-
- @Override
- public void onClick(ClickEvent event) {
- super.onClick(event);
- if (event.getSource() == add) {
- addItem();
-
- } else if (event.getSource() == remove) {
- removeItem();
-
- } else if (event.getSource() == options) {
- // unselect all in other list, to avoid mistakes (i.e wrong button)
- final int c = selections.getItemCount();
- for (int i = 0; i < c; i++) {
- selections.setItemSelected(i, false);
- }
- } else if (event.getSource() == selections) {
- // unselect all in other list, to avoid mistakes (i.e wrong button)
- final int c = options.getItemCount();
- for (int i = 0; i < c; i++) {
- options.setItemSelected(i, false);
- }
- }
- }
-
- void clearInternalHeights() {
- selections.setHeight("");
- options.setHeight("");
- }
-
- void setInternalHeights() {
- int captionHeight = Util.getRequiredHeight(captionWrapper);
- int totalHeight = getOffsetHeight();
-
- String selectHeight = (totalHeight - captionHeight) + "px";
-
- selections.setHeight(selectHeight);
- options.setHeight(selectHeight);
-
- }
-
- void clearInternalWidths() {
- int cols = -1;
- if (getColumns() > 0) {
- cols = getColumns();
- } else {
- cols = DEFAULT_COLUMN_COUNT;
- }
-
- if (cols >= 0) {
- String colWidth = cols + "em";
- String containerWidth = (2 * cols + 4) + "em";
- // Caption wrapper width == optionsSelect + buttons +
- // selectionsSelect
- String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em";
-
- options.setWidth(colWidth);
- if (optionsCaption != null) {
- optionsCaption.setWidth(colWidth);
- }
- selections.setWidth(colWidth);
- if (selectionsCaption != null) {
- selectionsCaption.setWidth(colWidth);
- }
- buttons.setWidth("3.5em");
- optionsContainer.setWidth(containerWidth);
- captionWrapper.setWidth(captionWrapperWidth);
- }
- }
-
- void setInternalWidths() {
- DOM.setStyleAttribute(getElement(), "position", "relative");
- int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder(
- buttons.getElement(), 0);
-
- int buttonWidth = Util.getRequiredWidth(buttons);
- int totalWidth = getOffsetWidth();
-
- int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2;
-
- options.setWidth(spaceForSelect + "px");
- if (optionsCaption != null) {
- optionsCaption.setWidth(spaceForSelect + "px");
- }
-
- selections.setWidth(spaceForSelect + "px");
- if (selectionsCaption != null) {
- selectionsCaption.setWidth(spaceForSelect + "px");
- }
- captionWrapper.setWidth("100%");
- }
-
- @Override
- protected void setTabIndex(int tabIndex) {
- options.setTabIndex(tabIndex);
- selections.setTabIndex(tabIndex);
- add.setTabIndex(tabIndex);
- remove.setTabIndex(tabIndex);
- }
-
- @Override
- public void focus() {
- options.setFocus(true);
- }
-
- /**
- * Get the key that selects an item in the table. By default it is the Enter
- * key but by overriding this you can change the key to whatever you want.
- *
- * @return
- */
- protected int getNavigationSelectKey() {
- return KeyCodes.KEY_ENTER;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
- * .event.dom.client.KeyDownEvent)
- */
- @Override
- public void onKeyDown(KeyDownEvent event) {
- int keycode = event.getNativeKeyCode();
-
- // Catch tab and move between select:s
- if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
- // Prevent default behavior
- event.preventDefault();
-
- // Remove current selections
- for (int i = 0; i < options.getItemCount(); i++) {
- options.setItemSelected(i, false);
- }
-
- // Focus selections
- selections.setFocus(true);
- }
-
- if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
- && event.getSource() == selections) {
- // Prevent default behavior
- event.preventDefault();
-
- // Remove current selections
- for (int i = 0; i < selections.getItemCount(); i++) {
- selections.setItemSelected(i, false);
- }
-
- // Focus options
- options.setFocus(true);
- }
-
- if (keycode == getNavigationSelectKey()) {
- // Prevent default behavior
- event.preventDefault();
-
- // Decide which select the selection was made in
- if (event.getSource() == options) {
- // Prevents the selection to become a single selection when
- // using Enter key
- // as the selection key (default)
- options.setFocus(false);
-
- addItem();
-
- } else if (event.getSource() == selections) {
- // Prevents the selection to become a single selection when
- // using Enter key
- // as the selection key (default)
- selections.setFocus(false);
-
- removeItem();
- }
- }
-
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
- * .gwt.event.dom.client.MouseDownEvent)
- */
- @Override
- public void onMouseDown(MouseDownEvent event) {
- // Ensure that items are deselected when selecting
- // from a different source. See #3699 for details.
- if (event.getSource() == options) {
- for (int i = 0; i < selections.getItemCount(); i++) {
- selections.setItemSelected(i, false);
- }
- } else if (event.getSource() == selections) {
- for (int i = 0; i < options.getItemCount(); i++) {
- options.setItemSelected(i, false);
- }
- }
-
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.
- * google.gwt.event.dom.client.DoubleClickEvent)
- */
- @Override
- public void onDoubleClick(DoubleClickEvent event) {
- if (event.getSource() == options) {
- addItem();
- options.setSelectedIndex(-1);
- options.setFocus(false);
- } else if (event.getSource() == selections) {
- removeItem();
- selections.setSelectedIndex(-1);
- selections.setFocus(false);
- }
-
- }
-
- private static final String SUBPART_OPTION_SELECT = "leftSelect";
- private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
- + "-item";
- private static final String SUBPART_SELECTION_SELECT = "rightSelect";
- private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
- + "-item";
- private static final String SUBPART_LEFT_CAPTION = "leftCaption";
- private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
- private static final String SUBPART_ADD_BUTTON = "add";
- private static final String SUBPART_REMOVE_BUTTON = "remove";
-
- @Override
- public Element getSubPartElement(String subPart) {
- if (SUBPART_OPTION_SELECT.equals(subPart)) {
- return options.getElement();
- } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
- String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
- return (Element) options.getElement().getChild(
- Integer.parseInt(idx));
- } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
- return selections.getElement();
- } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
- String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM
- .length());
- return (Element) selections.getElement().getChild(
- Integer.parseInt(idx));
- } else if (optionsCaption != null
- && SUBPART_LEFT_CAPTION.equals(subPart)) {
- return optionsCaption.getElement();
- } else if (selectionsCaption != null
- && SUBPART_RIGHT_CAPTION.equals(subPart)) {
- return selectionsCaption.getElement();
- } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
- return add.getElement();
- } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
- return remove.getElement();
- }
-
- return null;
- }
-
- @Override
- public String getSubPartName(Element subElement) {
- if (optionsCaption != null
- && optionsCaption.getElement().isOrHasChild(subElement)) {
- return SUBPART_LEFT_CAPTION;
- } else if (selectionsCaption != null
- && selectionsCaption.getElement().isOrHasChild(subElement)) {
- return SUBPART_RIGHT_CAPTION;
- } else if (options.getElement().isOrHasChild(subElement)) {
- if (options.getElement() == subElement) {
- return SUBPART_OPTION_SELECT;
- } else {
- int idx = Util.getChildElementIndex(subElement);
- return SUBPART_OPTION_SELECT_ITEM + idx;
- }
- } else if (selections.getElement().isOrHasChild(subElement)) {
- if (selections.getElement() == subElement) {
- return SUBPART_SELECTION_SELECT;
- } else {
- int idx = Util.getChildElementIndex(subElement);
- return SUBPART_SELECTION_SELECT_ITEM + idx;
- }
- } else if (add.getElement().isOrHasChild(subElement)) {
- return SUBPART_ADD_BUTTON;
- } else if (remove.getElement().isOrHasChild(subElement)) {
- return SUBPART_REMOVE_BUTTON;
- }
-
- return null;
- }
-}
import com.vaadin.client.ui.AbstractComponentContainerConnector;
import com.vaadin.client.ui.ClickEventHandler;
import com.vaadin.client.ui.ShortcutActionHandler;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VUI;
import com.vaadin.client.ui.layout.MayScrollChildren;
-import com.vaadin.client.ui.notification.VNotification;
import com.vaadin.client.ui.window.WindowConnector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.ComponentStateUtil;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.ui;
-
-import java.util.ArrayList;
-
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.logical.shared.HasResizeHandlers;
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.History;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.ui.ui.UIConstants;
-
-/**
- *
- */
-public class VUI extends SimplePanel implements ResizeHandler,
- Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable,
- HasResizeHandlers {
-
- private static final String CLASSNAME = "v-view";
-
- private static int MONITOR_PARENT_TIMER_INTERVAL = 1000;
-
- String theme;
-
- String id;
-
- ShortcutActionHandler actionHandler;
-
- /*
- * Last known window size used to detect whether VView should be layouted
- * again. Detection must check window size, because the VView size might be
- * fixed and thus not automatically adapt to changed window sizes.
- */
- private int windowWidth;
- private int windowHeight;
-
- /*
- * Last know view size used to detect whether new dimensions should be sent
- * to the server.
- */
- private int viewWidth;
- private int viewHeight;
-
- ApplicationConnection connection;
-
- /**
- * Keep track of possible parent size changes when an embedded application.
- *
- * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to
- * keep track of when there is a real change.
- */
- private Timer resizeTimer;
-
- /** stored width of parent for embedded application auto-resize */
- private int parentWidth;
-
- /** stored height of parent for embedded application auto-resize */
- private int parentHeight;
-
- int scrollTop;
-
- int scrollLeft;
-
- boolean rendering;
-
- boolean scrollable;
-
- boolean immediate;
-
- boolean resizeLazy = false;
-
- private HandlerRegistration historyHandlerRegistration;
-
- private TouchScrollHandler touchScrollHandler;
-
- /**
- * The current URI fragment, used to avoid sending updates if nothing has
- * changed.
- */
- String currentFragment;
-
- /**
- * Listener for URI fragment changes. Notifies the server of the new value
- * whenever the value changes.
- */
- private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
-
- @Override
- public void onValueChange(ValueChangeEvent<String> event) {
- String newFragment = event.getValue();
-
- // Send the location to the server if the fragment has changed
- if (!newFragment.equals(currentFragment) && connection != null) {
- currentFragment = newFragment;
- connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
- Window.Location.getHref(), true);
- }
- }
- };
-
- private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- performSizeCheck();
- }
-
- });
-
- public VUI() {
- super();
- setStyleName(CLASSNAME);
-
- // Allow focusing the view by using the focus() method, the view
- // should not be in the document focus flow
- getElement().setTabIndex(-1);
- makeScrollable();
- }
-
- /**
- * Start to periodically monitor for parent element resizes if embedded
- * application (e.g. portlet).
- */
- @Override
- protected void onLoad() {
- super.onLoad();
- if (isMonitoringParentSize()) {
- resizeTimer = new Timer() {
-
- @Override
- public void run() {
- // trigger check to see if parent size has changed,
- // recalculate layouts
- performSizeCheck();
- resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
- }
- };
- resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
- }
- }
-
- @Override
- protected void onAttach() {
- super.onAttach();
- historyHandlerRegistration = History
- .addValueChangeHandler(historyChangeHandler);
- currentFragment = History.getToken();
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- historyHandlerRegistration.removeHandler();
- historyHandlerRegistration = null;
- }
-
- /**
- * Stop monitoring for parent element resizes.
- */
-
- @Override
- protected void onUnload() {
- if (resizeTimer != null) {
- resizeTimer.cancel();
- resizeTimer = null;
- }
- super.onUnload();
- }
-
- /**
- * Called when the window or parent div might have been resized.
- *
- * This immediately checks the sizes of the window and the parent div (if
- * monitoring it) and triggers layout recalculation if they have changed.
- */
- protected void performSizeCheck() {
- windowSizeMaybeChanged(Window.getClientWidth(),
- Window.getClientHeight());
- }
-
- /**
- * Called when the window or parent div might have been resized.
- *
- * This immediately checks the sizes of the window and the parent div (if
- * monitoring it) and triggers layout recalculation if they have changed.
- *
- * @param newWindowWidth
- * The new width of the window
- * @param newWindowHeight
- * The new height of the window
- *
- * @deprecated use {@link #performSizeCheck()}
- */
- @Deprecated
- protected void windowSizeMaybeChanged(int newWindowWidth,
- int newWindowHeight) {
- boolean changed = false;
- ComponentConnector connector = ConnectorMap.get(connection)
- .getConnector(this);
- if (windowWidth != newWindowWidth) {
- windowWidth = newWindowWidth;
- changed = true;
- connector.getLayoutManager().reportOuterWidth(connector,
- newWindowWidth);
- VConsole.log("New window width: " + windowWidth);
- }
- if (windowHeight != newWindowHeight) {
- windowHeight = newWindowHeight;
- changed = true;
- connector.getLayoutManager().reportOuterHeight(connector,
- newWindowHeight);
- VConsole.log("New window height: " + windowHeight);
- }
- Element parentElement = getElement().getParentElement();
- if (isMonitoringParentSize() && parentElement != null) {
- // check also for parent size changes
- int newParentWidth = parentElement.getClientWidth();
- int newParentHeight = parentElement.getClientHeight();
- if (parentWidth != newParentWidth) {
- parentWidth = newParentWidth;
- changed = true;
- VConsole.log("New parent width: " + parentWidth);
- }
- if (parentHeight != newParentHeight) {
- parentHeight = newParentHeight;
- changed = true;
- VConsole.log("New parent height: " + parentHeight);
- }
- }
- if (changed) {
- /*
- * If the window size has changed, layout the VView again and send
- * new size to the server if the size changed. (Just checking VView
- * size would cause us to ignore cases when a relatively sized VView
- * should shrink as the content's size is fixed and would thus not
- * automatically shrink.)
- */
- VConsole.log("Running layout functions due to window or parent resize");
-
- // update size to avoid (most) redundant re-layout passes
- // there can still be an extra layout recalculation if webkit
- // overflow fix updates the size in a deferred block
- if (isMonitoringParentSize() && parentElement != null) {
- parentWidth = parentElement.getClientWidth();
- parentHeight = parentElement.getClientHeight();
- }
-
- sendClientResized();
-
- LayoutManager layoutManager = connector.getLayoutManager();
- if (layoutManager.isLayoutRunning()) {
- layoutManager.layoutLater();
- } else {
- layoutManager.layoutNow();
- }
- }
- }
-
- public String getTheme() {
- return theme;
- }
-
- /**
- * Used to reload host page on theme changes.
- */
- static native void reloadHostPage()
- /*-{
- $wnd.location.reload();
- }-*/;
-
- /**
- * Returns true if the body is NOT generated, i.e if someone else has made
- * the page that we're running in. Otherwise we're in charge of the whole
- * page.
- *
- * @return true if we're running embedded
- */
- public boolean isEmbedded() {
- return !getElement().getOwnerDocument().getBody().getClassName()
- .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME);
- }
-
- /**
- * Returns true if the size of the parent should be checked periodically and
- * the application should react to its changes.
- *
- * @return true if size of parent should be tracked
- */
- protected boolean isMonitoringParentSize() {
- // could also perform a more specific check (Liferay portlet)
- return isEmbedded();
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- int type = DOM.eventGetType(event);
- if (type == Event.ONKEYDOWN && actionHandler != null) {
- actionHandler.handleKeyboardEvent(event);
- return;
- } else if (scrollable && type == Event.ONSCROLL) {
- updateScrollPosition();
- }
- }
-
- /**
- * Updates scroll position from DOM and saves variables to server.
- */
- private void updateScrollPosition() {
- int oldTop = scrollTop;
- int oldLeft = scrollLeft;
- scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
- scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
- if (connection != null && !rendering) {
- if (oldTop != scrollTop) {
- connection.updateVariable(id, "scrollTop", scrollTop, false);
- }
- if (oldLeft != scrollLeft) {
- connection.updateVariable(id, "scrollLeft", scrollLeft, false);
- }
- }
- }
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
- * .gwt.event.logical.shared.ResizeEvent)
- */
-
- @Override
- public void onResize(ResizeEvent event) {
- triggerSizeChangeCheck();
- }
-
- /**
- * Called when a resize event is received.
- *
- * This may trigger a lazy refresh or perform the size check immediately
- * depending on the browser used and whether the server side requests
- * resizes to be lazy.
- */
- private void triggerSizeChangeCheck() {
- /*
- * IE (pre IE9 at least) will give us some false resize events due to
- * problems with scrollbars. Firefox 3 might also produce some extra
- * events. We postpone both the re-layouting and the server side event
- * for a while to deal with these issues.
- *
- * We may also postpone these events to avoid slowness when resizing the
- * browser window. Constantly recalculating the layout causes the resize
- * operation to be really slow with complex layouts.
- */
- boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
-
- if (lazy) {
- delayedResizeExecutor.trigger();
- } else {
- performSizeCheck();
- }
- }
-
- /**
- * Send new dimensions to the server.
- */
- void sendClientResized() {
- Element parentElement = getElement().getParentElement();
- int viewHeight = parentElement.getClientHeight();
- int viewWidth = parentElement.getClientWidth();
-
- ResizeEvent.fire(this, viewWidth, viewHeight);
- }
-
- public native static void goTo(String url)
- /*-{
- $wnd.location = url;
- }-*/;
-
- @Override
- public void onWindowClosing(Window.ClosingEvent event) {
- // Change focus on this window in order to ensure that all state is
- // collected from textfields
- // TODO this is a naive hack, that only works with text fields and may
- // cause some odd issues. Should be replaced with a decent solution, see
- // also related BeforeShortcutActionListener interface. Same interface
- // might be usable here.
- VTextField.flushChangesFromFocusedTextField();
- }
-
- private native static void loadAppIdListFromDOM(ArrayList<String> list)
- /*-{
- var j;
- for(j in $wnd.vaadin.vaadinConfigurations) {
- // $entry not needed as function is not exported
- list.@java.util.Collection::add(Ljava/lang/Object;)(j);
- }
- }-*/;
-
- @Override
- public ShortcutActionHandler getShortcutActionHandler() {
- return actionHandler;
- }
-
- @Override
- public void focus() {
- getElement().focus();
- }
-
- /**
- * Ensures the root is scrollable eg. after style name changes.
- */
- void makeScrollable() {
- if (touchScrollHandler == null) {
- touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
- }
- touchScrollHandler.addElement(getElement());
- }
-
- @Override
- public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) {
- return addHandler(resizeHandler, ResizeEvent.getType());
- }
-
-}
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VUpload;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.ui.Upload;
*/
package com.vaadin.client.ui.upload;
+import com.vaadin.client.ui.VUpload;
+
public class UploadIFrameOnloadStrategy {
- native void hookEvents(com.google.gwt.dom.client.Element iframe,
+ public native void hookEvents(com.google.gwt.dom.client.Element iframe,
VUpload upload)
/*-{
iframe.onload = $entry(function() {
* @param iframe
* the iframe whose onLoad event is to be cleaned
*/
- native void unHookEvents(com.google.gwt.dom.client.Element iframe)
+ public native void unHookEvents(com.google.gwt.dom.client.Element iframe)
/*-{
iframe.onload = null;
}-*/;
package com.vaadin.client.ui.upload;
import com.google.gwt.dom.client.Element;
+import com.vaadin.client.ui.VUpload;
/**
* IE does not have onload, detect onload via readystatechange
*/
public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy {
@Override
- native void hookEvents(Element iframe, VUpload upload)
+ public native void hookEvents(Element iframe, VUpload upload)
/*-{
iframe.onreadystatechange = $entry(function() {
if (iframe.readyState == 'complete') {
}-*/;
@Override
- native void unHookEvents(Element iframe)
+ public native void unHookEvents(Element iframe)
/*-{
iframe.onreadystatechange = null;
}-*/;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.upload;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.FormElement;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.FileUpload;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.Hidden;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.button.VButton;
-
-/**
- *
- * Note, we are not using GWT FormPanel as we want to listen submitcomplete
- * events even though the upload component is already detached.
- *
- */
-public class VUpload extends SimplePanel {
-
- private final class MyFileUpload extends FileUpload {
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- if (event.getTypeInt() == Event.ONCHANGE) {
- if (immediate && fu.getFilename() != null
- && !"".equals(fu.getFilename())) {
- submit();
- }
- } else if (BrowserInfo.get().isIE()
- && event.getTypeInt() == Event.ONFOCUS) {
- // IE and user has clicked on hidden textarea part of upload
- // field. Manually open file selector, other browsers do it by
- // default.
- fireNativeClick(fu.getElement());
- // also remove focus to enable hack if user presses cancel
- // button
- fireNativeBlur(fu.getElement());
- }
- }
- }
-
- public static final String CLASSNAME = "v-upload";
-
- /**
- * FileUpload component that opens native OS dialog to select file.
- */
- FileUpload fu = new MyFileUpload();
-
- Panel panel = new FlowPanel();
-
- UploadIFrameOnloadStrategy onloadstrategy = GWT
- .create(UploadIFrameOnloadStrategy.class);
-
- ApplicationConnection client;
-
- protected String paintableId;
-
- /**
- * Button that initiates uploading
- */
- protected final VButton submitButton;
-
- /**
- * When expecting big files, programmer may initiate some UI changes when
- * uploading the file starts. Bit after submitting file we'll visit the
- * server to check possible changes.
- */
- protected Timer t;
-
- /**
- * some browsers tries to send form twice if submit is called in button
- * click handler, some don't submit at all without it, so we need to track
- * if form is already being submitted
- */
- private boolean submitted = false;
-
- private boolean enabled = true;
-
- private boolean immediate;
-
- private Hidden maxfilesize = new Hidden();
-
- protected FormElement element;
-
- private com.google.gwt.dom.client.Element synthesizedFrame;
-
- protected int nextUploadId;
-
- public VUpload() {
- super(com.google.gwt.dom.client.Document.get().createFormElement());
-
- element = getElement().cast();
- setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
- element.setMethod(FormPanel.METHOD_POST);
-
- setWidget(panel);
- panel.add(maxfilesize);
- panel.add(fu);
- submitButton = new VButton();
- submitButton.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- if (immediate) {
- // fire click on upload (eg. focused button and hit space)
- fireNativeClick(fu.getElement());
- } else {
- submit();
- }
- }
- });
- panel.add(submitButton);
-
- setStyleName(CLASSNAME);
- }
-
- private static native void setEncoding(Element form, String encoding)
- /*-{
- form.enctype = encoding;
- }-*/;
-
- protected void setImmediate(boolean booleanAttribute) {
- if (immediate != booleanAttribute) {
- immediate = booleanAttribute;
- if (immediate) {
- fu.sinkEvents(Event.ONCHANGE);
- fu.sinkEvents(Event.ONFOCUS);
- }
- }
- setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
- }
-
- private static native void fireNativeClick(Element element)
- /*-{
- element.click();
- }-*/;
-
- private static native void fireNativeBlur(Element element)
- /*-{
- element.blur();
- }-*/;
-
- protected void disableUpload() {
- submitButton.setEnabled(false);
- if (!submitted) {
- // Cannot disable the fileupload while submitting or the file won't
- // be submitted at all
- fu.getElement().setPropertyBoolean("disabled", true);
- }
- enabled = false;
- }
-
- protected void enableUpload() {
- submitButton.setEnabled(true);
- fu.getElement().setPropertyBoolean("disabled", false);
- enabled = true;
- if (submitted) {
- /*
- * An old request is still in progress (most likely cancelled),
- * ditching that target frame to make it possible to send a new
- * file. A new target frame is created later."
- */
- cleanTargetFrame();
- submitted = false;
- }
- }
-
- /**
- * Re-creates file input field and populates panel. This is needed as we
- * want to clear existing values from our current file input field.
- */
- private void rebuildPanel() {
- panel.remove(submitButton);
- panel.remove(fu);
- fu = new MyFileUpload();
- fu.setName(paintableId + "_file");
- fu.getElement().setPropertyBoolean("disabled", !enabled);
- panel.add(fu);
- panel.add(submitButton);
- if (immediate) {
- fu.sinkEvents(Event.ONCHANGE);
- }
- }
-
- /**
- * Called by JSNI (hooked via {@link #onloadstrategy})
- */
- private void onSubmitComplete() {
- /* Needs to be run dereferred to avoid various browser issues. */
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- if (submitted) {
- if (client != null) {
- if (t != null) {
- t.cancel();
- }
- VConsole.log("VUpload:Submit complete");
- client.sendPendingVariableChanges();
- }
-
- rebuildPanel();
-
- submitted = false;
- enableUpload();
- if (!isAttached()) {
- /*
- * Upload is complete when upload is already abandoned.
- */
- cleanTargetFrame();
- }
- }
- }
- });
- }
-
- protected void submit() {
- if (fu.getFilename().length() == 0 || submitted || !enabled) {
- VConsole.log("Submit cancelled (disabled, no file or already submitted)");
- return;
- }
- // flush possibly pending variable changes, so they will be handled
- // before upload
- client.sendPendingVariableChanges();
-
- element.submit();
- submitted = true;
- VConsole.log("Submitted form");
-
- disableUpload();
-
- /*
- * Visit server a moment after upload has started to see possible
- * changes from UploadStarted event. Will be cleared on complete.
- */
- t = new Timer() {
- @Override
- public void run() {
- VConsole.log("Visiting server to see if upload started event changed UI.");
- client.updateVariable(paintableId, "pollForStart",
- nextUploadId, true);
- }
- };
- t.schedule(800);
- }
-
- @Override
- protected void onAttach() {
- super.onAttach();
- if (client != null) {
- ensureTargetFrame();
- }
- }
-
- protected void ensureTargetFrame() {
- if (synthesizedFrame == null) {
- // Attach a hidden IFrame to the form. This is the target iframe to
- // which the form will be submitted. We have to create the iframe
- // using innerHTML, because setting an iframe's 'name' property
- // dynamically doesn't work on most browsers.
- DivElement dummy = Document.get().createDivElement();
- dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
- + getFrameName()
- + "' style='position:absolute;width:0;height:0;border:0'>");
- synthesizedFrame = dummy.getFirstChildElement();
- Document.get().getBody().appendChild(synthesizedFrame);
- element.setTarget(getFrameName());
- onloadstrategy.hookEvents(synthesizedFrame, this);
- }
- }
-
- private String getFrameName() {
- return paintableId + "_TGT_FRAME";
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- if (!submitted) {
- cleanTargetFrame();
- }
- }
-
- private void cleanTargetFrame() {
- if (synthesizedFrame != null) {
- Document.get().getBody().removeChild(synthesizedFrame);
- onloadstrategy.unHookEvents(synthesizedFrame);
- synthesizedFrame = null;
- }
- }
-}
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.video;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.VideoElement;
-import com.google.gwt.user.client.Element;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.VMediaBase;
-
-public class VVideo extends VMediaBase {
-
- public static String CLASSNAME = "v-video";
-
- private VideoElement video;
-
- public VVideo() {
- video = Document.get().createVideoElement();
- setMediaElement(video);
- setStyleName(CLASSNAME);
-
- updateDimensionsWhenMetadataLoaded(getElement());
- }
-
- /**
- * Registers a listener that updates the dimensions of the widget when the
- * video metadata has been loaded.
- *
- * @param el
- */
- private native void updateDimensionsWhenMetadataLoaded(Element el)
- /*-{
- var self = this;
- el.addEventListener('loadedmetadata', $entry(function(e) {
- self.@com.vaadin.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight);
- }), false);
-
- }-*/;
-
- /**
- * Updates the dimensions of the widget.
- *
- * @param w
- * @param h
- */
- private void updateElementDynamicSize(int w, int h) {
- video.getStyle().setWidth(w, Unit.PX);
- video.getStyle().setHeight(h, Unit.PX);
- Util.notifyParentOfSizeChange(this, true);
- }
-
- public void setPoster(String poster) {
- video.setPoster(poster);
- }
-
-}
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.MediaBaseConnector;
+import com.vaadin.client.ui.VVideo;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.video.VideoConstants;
import com.vaadin.shared.ui.video.VideoState;
+++ /dev/null
-/*
- * Copyright 2011 Vaadin Ltd.
- *
- * 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.
- */
-
-package com.vaadin.client.ui.window;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.ScrollEvent;
-import com.google.gwt.event.dom.client.ScrollHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Console;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.FocusableScrollPanel;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.shared.EventId;
-
-/**
- * "Sub window" component.
- *
- * @author Vaadin Ltd
- */
-public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
- ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
-
- /**
- * Minimum allowed height of a window. This refers to the content area, not
- * the outer borders.
- */
- private static final int MIN_CONTENT_AREA_HEIGHT = 100;
-
- /**
- * Minimum allowed width of a window. This refers to the content area, not
- * the outer borders.
- */
- private static final int MIN_CONTENT_AREA_WIDTH = 150;
-
- private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
-
- private static boolean orderingDefered;
-
- public static final String CLASSNAME = "v-window";
-
- private static final int STACKING_OFFSET_PIXELS = 15;
-
- public static final int Z_INDEX = 10000;
-
- ComponentConnector layout;
-
- Element contents;
-
- Element header;
-
- Element footer;
-
- private Element resizeBox;
-
- final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
-
- private boolean dragging;
-
- private int startX;
-
- private int startY;
-
- private int origX;
-
- private int origY;
-
- private boolean resizing;
-
- private int origW;
-
- private int origH;
-
- Element closeBox;
-
- protected ApplicationConnection client;
-
- String id;
-
- ShortcutActionHandler shortcutHandler;
-
- /** Last known positionx read from UIDL or updated to application connection */
- private int uidlPositionX = -1;
-
- /** Last known positiony read from UIDL or updated to application connection */
- private int uidlPositionY = -1;
-
- boolean vaadinModality = false;
-
- boolean resizable = true;
-
- private boolean draggable = true;
-
- boolean resizeLazy = false;
-
- private Element modalityCurtain;
- private Element draggingCurtain;
- private Element resizingCurtain;
-
- private Element headerText;
-
- private boolean closable = true;
-
- // If centered (via UIDL), the window should stay in the centered -mode
- // until a position is received from the server, or the user moves or
- // resizes the window.
- boolean centered = false;
-
- boolean immediate;
-
- private Element wrapper;
-
- boolean visibilityChangesDisabled;
-
- int bringToFrontSequence = -1;
-
- private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
- new ScheduledCommand() {
-
- @Override
- public void execute() {
- updateContentsSize();
- }
- });
-
- public VWindow() {
- super(false, false, true); // no autohide, not modal, shadow
- // Different style of shadow for windows
- setShadowStyle("window");
-
- constructDOM();
- contentPanel.addScrollHandler(this);
- contentPanel.addKeyDownHandler(this);
- contentPanel.addFocusHandler(this);
- contentPanel.addBlurHandler(this);
- }
-
- public void bringToFront() {
- int curIndex = windowOrder.indexOf(this);
- if (curIndex + 1 < windowOrder.size()) {
- windowOrder.remove(this);
- windowOrder.add(this);
- for (; curIndex < windowOrder.size(); curIndex++) {
- windowOrder.get(curIndex).setWindowOrder(curIndex);
- }
- }
- }
-
- /**
- * Returns true if this window is the topmost VWindow
- *
- * @return
- */
- private boolean isActive() {
- return equals(getTopmostWindow());
- }
-
- private static VWindow getTopmostWindow() {
- return windowOrder.get(windowOrder.size() - 1);
- }
-
- void setWindowOrderAndPosition() {
- // This cannot be done in the constructor as the widgets are created in
- // a different order than on they should appear on screen
- if (windowOrder.contains(this)) {
- // Already set
- return;
- }
- final int order = windowOrder.size();
- setWindowOrder(order);
- windowOrder.add(this);
- setPopupPosition(order * STACKING_OFFSET_PIXELS, order
- * STACKING_OFFSET_PIXELS);
-
- }
-
- private void setWindowOrder(int order) {
- setZIndex(order + Z_INDEX);
- }
-
- @Override
- protected void setZIndex(int zIndex) {
- super.setZIndex(zIndex);
- if (vaadinModality) {
- DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
- }
- }
-
- protected Element getModalityCurtain() {
- if (modalityCurtain == null) {
- modalityCurtain = DOM.createDiv();
- modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
- }
- return modalityCurtain;
- }
-
- protected void constructDOM() {
- setStyleName(CLASSNAME);
-
- header = DOM.createDiv();
- DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
- headerText = DOM.createDiv();
- DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
- contents = DOM.createDiv();
- DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
- footer = DOM.createDiv();
- DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
- resizeBox = DOM.createDiv();
- DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
- closeBox = DOM.createDiv();
- DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
- DOM.appendChild(footer, resizeBox);
-
- wrapper = DOM.createDiv();
- DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
-
- DOM.appendChild(wrapper, header);
- DOM.appendChild(wrapper, closeBox);
- DOM.appendChild(header, headerText);
- DOM.appendChild(wrapper, contents);
- DOM.appendChild(wrapper, footer);
- DOM.appendChild(super.getContainerElement(), wrapper);
-
- sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
- | Event.ONLOSECAPTURE);
-
- setWidget(contentPanel);
-
- }
-
- /**
- * Calling this method will defer ordering algorithm, to order windows based
- * on servers bringToFront and modality instructions. Non changed windows
- * will be left intact.
- */
- static void deferOrdering() {
- if (!orderingDefered) {
- orderingDefered = true;
- Scheduler.get().scheduleFinally(new Command() {
-
- @Override
- public void execute() {
- doServerSideOrdering();
- VNotification.bringNotificationsToFront();
- }
- });
- }
- }
-
- private static void doServerSideOrdering() {
- orderingDefered = false;
- VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
- Arrays.sort(array, new Comparator<VWindow>() {
-
- @Override
- public int compare(VWindow o1, VWindow o2) {
- /*
- * Order by modality, then by bringtofront sequence.
- */
-
- if (o1.vaadinModality && !o2.vaadinModality) {
- return 1;
- } else if (!o1.vaadinModality && o2.vaadinModality) {
- return -1;
- } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
- return 1;
- } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
- return -1;
- } else {
- return 0;
- }
- }
- });
- for (int i = 0; i < array.length; i++) {
- VWindow w = array[i];
- if (w.bringToFrontSequence != -1 || w.vaadinModality) {
- w.bringToFront();
- w.bringToFrontSequence = -1;
- }
- }
- }
-
- @Override
- public void setVisible(boolean visible) {
- /*
- * Visibility with VWindow works differently than with other Paintables
- * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
- * used to avoid visibility call from
- * ApplicationConnection.updateComponent();
- */
- if (!visibilityChangesDisabled) {
- super.setVisible(visible);
- }
- }
-
- void setDraggable(boolean draggable) {
- if (this.draggable == draggable) {
- return;
- }
-
- this.draggable = draggable;
-
- setCursorProperties();
- }
-
- private void setCursorProperties() {
- if (!draggable) {
- header.getStyle().setProperty("cursor", "default");
- footer.getStyle().setProperty("cursor", "default");
- } else {
- header.getStyle().setProperty("cursor", "");
- footer.getStyle().setProperty("cursor", "");
- }
- }
-
- /**
- * Sets the closable state of the window. Additionally hides/shows the close
- * button according to the new state.
- *
- * @param closable
- * true if the window can be closed by the user
- */
- protected void setClosable(boolean closable) {
- if (this.closable == closable) {
- return;
- }
-
- this.closable = closable;
- if (closable) {
- DOM.setStyleAttribute(closeBox, "display", "");
- } else {
- DOM.setStyleAttribute(closeBox, "display", "none");
- }
-
- }
-
- /**
- * Returns the closable state of the sub window. If the sub window is
- * closable a decoration (typically an X) is shown to the user. By clicking
- * on the X the user can close the window.
- *
- * @return true if the sub window is closable
- */
- protected boolean isClosable() {
- return closable;
- }
-
- @Override
- public void show() {
- if (!windowOrder.contains(this)) {
- // This is needed if the window is hidden and then shown again.
- // Otherwise this VWindow is added to windowOrder in the
- // constructor.
- windowOrder.add(this);
- }
-
- if (vaadinModality) {
- showModalityCurtain();
- }
- super.show();
- }
-
- @Override
- public void hide() {
- if (vaadinModality) {
- hideModalityCurtain();
- }
- super.hide();
-
- // Remove window from windowOrder to avoid references being left
- // hanging.
- windowOrder.remove(this);
- }
-
- void setVaadinModality(boolean modality) {
- vaadinModality = modality;
- if (vaadinModality) {
- if (isAttached()) {
- showModalityCurtain();
- }
- deferOrdering();
- } else {
- if (modalityCurtain != null) {
- if (isAttached()) {
- hideModalityCurtain();
- }
- modalityCurtain = null;
- }
- }
- }
-
- private void showModalityCurtain() {
- DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
- "" + (windowOrder.indexOf(this) + Z_INDEX));
-
- if (isShowing()) {
- getOverlayContainer().insertBefore(getModalityCurtain(),
- getElement());
- } else {
- getOverlayContainer().appendChild(getModalityCurtain());
- }
-
- }
-
- private void hideModalityCurtain() {
- modalityCurtain.removeFromParent();
- }
-
- /*
- * Shows an empty div on top of all other content; used when moving, so that
- * iframes (etc) do not steal event.
- */
- private void showDraggingCurtain() {
- getElement().getParentElement().insertBefore(getDraggingCurtain(),
- getElement());
- }
-
- private void hideDraggingCurtain() {
- if (draggingCurtain != null) {
- draggingCurtain.removeFromParent();
- }
- }
-
- /*
- * Shows an empty div on top of all other content; used when resizing, so
- * that iframes (etc) do not steal event.
- */
- private void showResizingCurtain() {
- getElement().getParentElement().insertBefore(getResizingCurtain(),
- getElement());
- }
-
- private void hideResizingCurtain() {
- if (resizingCurtain != null) {
- resizingCurtain.removeFromParent();
- }
- }
-
- private Element getDraggingCurtain() {
- if (draggingCurtain == null) {
- draggingCurtain = createCurtain();
- draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
- }
-
- return draggingCurtain;
- }
-
- private Element getResizingCurtain() {
- if (resizingCurtain == null) {
- resizingCurtain = createCurtain();
- resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
- }
-
- return resizingCurtain;
- }
-
- private Element createCurtain() {
- Element curtain = DOM.createDiv();
-
- DOM.setStyleAttribute(curtain, "position", "absolute");
- DOM.setStyleAttribute(curtain, "top", "0px");
- DOM.setStyleAttribute(curtain, "left", "0px");
- DOM.setStyleAttribute(curtain, "width", "100%");
- DOM.setStyleAttribute(curtain, "height", "100%");
- DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX);
-
- return curtain;
- }
-
- void setResizable(boolean resizability) {
- resizable = resizability;
- if (resizability) {
- DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
- DOM.setElementProperty(resizeBox, "className", CLASSNAME
- + "-resizebox");
- } else {
- DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
- + CLASSNAME + "-footer-noresize");
- DOM.setElementProperty(resizeBox, "className", CLASSNAME
- + "-resizebox " + CLASSNAME + "-resizebox-disabled");
- }
- }
-
- @Override
- public void setPopupPosition(int left, int top) {
- if (top < 0) {
- // ensure window is not moved out of browser window from top of the
- // screen
- top = 0;
- }
- super.setPopupPosition(left, top);
- if (left != uidlPositionX && client != null) {
- client.updateVariable(id, "positionx", left, false);
- uidlPositionX = left;
- }
- if (top != uidlPositionY && client != null) {
- client.updateVariable(id, "positiony", top, false);
- uidlPositionY = top;
- }
- }
-
- public void setCaption(String c) {
- setCaption(c, null);
- }
-
- public void setCaption(String c, String icon) {
- String html = Util.escapeHTML(c);
- if (icon != null) {
- icon = client.translateVaadinUri(icon);
- html = "<img src=\"" + Util.escapeAttribute(icon)
- + "\" class=\"v-icon\" />" + html;
- }
- DOM.setInnerHTML(headerText, html);
- }
-
- @Override
- protected Element getContainerElement() {
- // in GWT 1.5 this method is used in PopupPanel constructor
- if (contents == null) {
- return super.getContainerElement();
- }
- return contents;
- }
-
- @Override
- public void onBrowserEvent(final Event event) {
- boolean bubble = true;
-
- final int type = event.getTypeInt();
-
- final Element target = DOM.eventGetTarget(event);
-
- if (resizing || resizeBox == target) {
- onResizeEvent(event);
- bubble = false;
- } else if (isClosable() && target == closeBox) {
- if (type == Event.ONCLICK) {
- onCloseClick();
- }
- bubble = false;
- } else if (dragging || !contents.isOrHasChild(target)) {
- onDragEvent(event);
- bubble = false;
- } else if (type == Event.ONCLICK) {
- // clicked inside window, ensure to be on top
- if (!isActive()) {
- bringToFront();
- }
- }
-
- /*
- * If clicking on other than the content, move focus to the window.
- * After that this windows e.g. gets all keyboard shortcuts.
- */
- if (type == Event.ONMOUSEDOWN
- && !contentPanel.getElement().isOrHasChild(target)
- && target != closeBox) {
- contentPanel.focus();
- }
-
- if (!bubble) {
- event.stopPropagation();
- } else {
- // Super.onBrowserEvent takes care of Handlers added by the
- // ClickEventHandler
- super.onBrowserEvent(event);
- }
- }
-
- private void onCloseClick() {
- client.updateVariable(id, "close", true, true);
- }
-
- private void onResizeEvent(Event event) {
- if (resizable && Util.isTouchEventOrLeftMouseButton(event)) {
- switch (event.getTypeInt()) {
- case Event.ONMOUSEDOWN:
- case Event.ONTOUCHSTART:
- if (!isActive()) {
- bringToFront();
- }
- showResizingCurtain();
- if (BrowserInfo.get().isIE()) {
- DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
- }
- resizing = true;
- startX = Util.getTouchOrMouseClientX(event);
- startY = Util.getTouchOrMouseClientY(event);
- origW = getElement().getOffsetWidth();
- origH = getElement().getOffsetHeight();
- DOM.setCapture(getElement());
- event.preventDefault();
- break;
- case Event.ONMOUSEUP:
- case Event.ONTOUCHEND:
- setSize(event, true);
- case Event.ONTOUCHCANCEL:
- DOM.releaseCapture(getElement());
- case Event.ONLOSECAPTURE:
- hideResizingCurtain();
- if (BrowserInfo.get().isIE()) {
- DOM.setStyleAttribute(resizeBox, "visibility", "");
- }
- resizing = false;
- break;
- case Event.ONMOUSEMOVE:
- case Event.ONTOUCHMOVE:
- if (resizing) {
- centered = false;
- setSize(event, false);
- event.preventDefault();
- }
- break;
- default:
- event.preventDefault();
- break;
- }
- }
- }
-
- /**
- * TODO check if we need to support this with touch based devices.
- *
- * Checks if the cursor was inside the browser content area when the event
- * happened.
- *
- * @param event
- * The event to be checked
- * @return true, if the cursor is inside the browser content area
- *
- * false, otherwise
- */
- private boolean cursorInsideBrowserContentArea(Event event) {
- if (event.getClientX() < 0 || event.getClientY() < 0) {
- // Outside to the left or above
- return false;
- }
-
- if (event.getClientX() > Window.getClientWidth()
- || event.getClientY() > Window.getClientHeight()) {
- // Outside to the right or below
- return false;
- }
-
- return true;
- }
-
- private void setSize(Event event, boolean updateVariables) {
- if (!cursorInsideBrowserContentArea(event)) {
- // Only drag while cursor is inside the browser client area
- return;
- }
-
- int w = Util.getTouchOrMouseClientX(event) - startX + origW;
- int minWidth = getMinWidth();
- if (w < minWidth) {
- w = minWidth;
- }
-
- int h = Util.getTouchOrMouseClientY(event) - startY + origH;
- int minHeight = getMinHeight();
- if (h < minHeight) {
- h = minHeight;
- }
-
- setWidth(w + "px");
- setHeight(h + "px");
-
- if (updateVariables) {
- // sending width back always as pixels, no need for unit
- client.updateVariable(id, "width", w, false);
- client.updateVariable(id, "height", h, immediate);
- }
-
- if (updateVariables || !resizeLazy) {
- // Resize has finished or is not lazy
- updateContentsSize();
- } else {
- // Lazy resize - wait for a while before re-rendering contents
- delayedContentsSizeUpdater.trigger();
- }
- }
-
- private void updateContentsSize() {
- // Update child widget dimensions
- if (client != null) {
- client.handleComponentRelativeSize(layout.getWidget());
- client.runDescendentsLayout((HasWidgets) layout.getWidget());
- }
-
- LayoutManager layoutManager = LayoutManager.get(client);
- layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector(
- this));
- layoutManager.layoutNow();
- }
-
- @Override
- public void setWidth(String width) {
- // Override PopupPanel which sets the width to the contents
- getElement().getStyle().setProperty("width", width);
- // Update v-has-width in case undefined window is resized
- setStyleName("v-has-width", width != null && width.length() > 0);
- }
-
- @Override
- public void setHeight(String height) {
- // Override PopupPanel which sets the height to the contents
- getElement().getStyle().setProperty("height", height);
- // Update v-has-height in case undefined window is resized
- setStyleName("v-has-height", height != null && height.length() > 0);
- }
-
- private void onDragEvent(Event event) {
- if (!Util.isTouchEventOrLeftMouseButton(event)) {
- return;
- }
-
- switch (DOM.eventGetType(event)) {
- case Event.ONTOUCHSTART:
- if (event.getTouches().length() > 1) {
- return;
- }
- case Event.ONMOUSEDOWN:
- if (!isActive()) {
- bringToFront();
- }
- beginMovingWindow(event);
- break;
- case Event.ONMOUSEUP:
- case Event.ONTOUCHEND:
- case Event.ONTOUCHCANCEL:
- case Event.ONLOSECAPTURE:
- stopMovingWindow();
- break;
- case Event.ONMOUSEMOVE:
- case Event.ONTOUCHMOVE:
- moveWindow(event);
- break;
- default:
- break;
- }
- }
-
- private void moveWindow(Event event) {
- if (dragging) {
- centered = false;
- if (cursorInsideBrowserContentArea(event)) {
- // Only drag while cursor is inside the browser client area
- final int x = Util.getTouchOrMouseClientX(event) - startX
- + origX;
- final int y = Util.getTouchOrMouseClientY(event) - startY
- + origY;
- setPopupPosition(x, y);
- }
- DOM.eventPreventDefault(event);
- }
- }
-
- private void beginMovingWindow(Event event) {
- if (draggable) {
- showDraggingCurtain();
- dragging = true;
- startX = Util.getTouchOrMouseClientX(event);
- startY = Util.getTouchOrMouseClientY(event);
- origX = DOM.getAbsoluteLeft(getElement());
- origY = DOM.getAbsoluteTop(getElement());
- DOM.setCapture(getElement());
- DOM.eventPreventDefault(event);
- }
- }
-
- private void stopMovingWindow() {
- dragging = false;
- hideDraggingCurtain();
- DOM.releaseCapture(getElement());
- }
-
- @Override
- public boolean onEventPreview(Event event) {
- if (dragging) {
- onDragEvent(event);
- return false;
- } else if (resizing) {
- onResizeEvent(event);
- return false;
- }
-
- // TODO This is probably completely unnecessary as the modality curtain
- // prevents events from reaching other windows and any security check
- // must be done on the server side and not here.
- // The code here is also run many times as each VWindow has an event
- // preview but we cannot check only the current VWindow here (e.g.
- // if(isTopMost) {...}) because PopupPanel will cause all events that
- // are not cancelled here and target this window to be consume():d
- // meaning the event won't be sent to the rest of the preview handlers.
-
- if (getTopmostWindow().vaadinModality) {
- // Topmost window is modal. Cancel the event if it targets something
- // outside that window (except debug console...)
- if (DOM.getCaptureElement() != null) {
- // Allow events when capture is set
- return true;
- }
-
- final Element target = event.getEventTarget().cast();
- if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) {
- // not within the modal window, but let's see if it's in the
- // debug window
- Widget w = Util.findWidget(target, null);
- while (w != null) {
- if (w instanceof Console) {
- return true; // allow debug-window clicks
- } else if (ConnectorMap.get(client).isConnector(w)) {
- return false;
- }
- w = w.getParent();
- }
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void addStyleDependentName(String styleSuffix) {
- // VWindow's getStyleElement() does not return the same element as
- // getElement(), so we need to override this.
- setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
- true);
- }
-
- @Override
- public ShortcutActionHandler getShortcutActionHandler() {
- return shortcutHandler;
- }
-
- @Override
- public void onScroll(ScrollEvent event) {
- client.updateVariable(id, "scrollTop",
- contentPanel.getScrollPosition(), false);
- client.updateVariable(id, "scrollLeft",
- contentPanel.getHorizontalScrollPosition(), false);
-
- }
-
- @Override
- public void onKeyDown(KeyDownEvent event) {
- if (shortcutHandler != null) {
- shortcutHandler
- .handleKeyboardEvent(Event.as(event.getNativeEvent()));
- return;
- }
- }
-
- @Override
- public void onBlur(BlurEvent event) {
- if (client.hasEventListeners(this, EventId.BLUR)) {
- client.updateVariable(id, EventId.BLUR, "", true);
- }
- }
-
- @Override
- public void onFocus(FocusEvent event) {
- if (client.hasEventListeners(this, EventId.FOCUS)) {
- client.updateVariable(id, EventId.FOCUS, "", true);
- }
- }
-
- @Override
- public void focus() {
- contentPanel.focus();
- }
-
- public int getMinHeight() {
- return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight();
- }
-
- private int getDecorationHeight() {
- LayoutManager lm = layout.getLayoutManager();
- int headerHeight = lm.getOuterHeight(header);
- int footerHeight = lm.getOuterHeight(footer);
- return headerHeight + footerHeight;
- }
-
- public int getMinWidth() {
- return MIN_CONTENT_AREA_WIDTH + getDecorationWidth();
- }
-
- private int getDecorationWidth() {
- LayoutManager layoutManager = layout.getLayoutManager();
- return layoutManager.getOuterWidth(getElement())
- - contentPanel.getElement().getOffsetWidth();
- }
-
-}
import com.vaadin.client.ui.ClickEventHandler;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.ShortcutActionHandler;
+import com.vaadin.client.ui.VWindow;
import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.label.VLabel;
+import com.vaadin.client.ui.VLabel;
import com.vaadin.shared.ui.Connect;
import com.vaadin.tests.widgetset.server.DummyLabel;
package com.vaadin.tests.widgetset.client;
import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.label.VLabel;
+import com.vaadin.client.ui.VLabel;
import com.vaadin.shared.ui.Connect;
import com.vaadin.tests.widgetset.server.MissingFromDefaultWidgetsetComponent;
package com.vaadin.tests.widgetset.client;
-import com.vaadin.client.ui.textarea.VTextArea;
+import com.vaadin.client.ui.VTextArea;
public class VExtendedTextArea extends VTextArea {