--- /dev/null
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.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.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.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.VCaptionWrapper;
++import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler;
++import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.terminal.gwt.client.ui.VOverlay;
+import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea;
+
+public class VPopupView extends HTML {
+
+ public static final String CLASSNAME = "v-popupview";
+
+ /** For server-client communication */
+ String uidlId;
+ ApplicationConnection client;
+
+ /** 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) {
+ updateState(true);
+ }
+ });
+
+ // ..and when we close it
+ popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ updateState(false);
+ }
+ });
+
+ popup.setAnimationEnabled(true);
+ }
+
+ /**
+ * Update popup visibility to server
+ *
+ * @param visibility
+ */
+ private void updateState(boolean visible) {
+ // If we know the server connection
+ // then update the current situation
+ if (uidlId != null && client != null && isAttached()) {
+ client.updateVariable(uidlId, "popupVisibility", visible, 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 popupComponentPaintable = 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
++
++ // 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) {
+ 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) {
+
+ popupComponentPaintable = null;
+ popupComponentWidget = null;
+ captionWrapper = null;
+
+ return super.remove(w);
+ }
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ ComponentConnector newPopupComponent = client.getPaintable(uidl
+ .getChildUIDL(0));
+
+ if (newPopupComponent != popupComponentPaintable) {
+ Widget newWidget = newPopupComponent.getWidget();
+ setWidget(newWidget);
+ popupComponentWidget = newWidget;
+ popupComponentPaintable = 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
+
+}// class VPopupView
--- /dev/null
- float posAsFloat = 0;
-
- if (pos.indexOf("px") > 0) {
- int posAsInt = Integer.parseInt(pos.substring(0, pos.length() - 2));
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.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.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.BrowserInfo;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.ConnectorMap;
+import com.vaadin.terminal.gwt.client.LayoutManager;
+import com.vaadin.terminal.gwt.client.Util;
+import com.vaadin.terminal.gwt.client.VConsole;
+import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate;
+import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.terminal.gwt.client.ui.VOverlay;
+import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
+
+public class VAbstractSplitPanel extends ComplexPanel {
+
+ private boolean enabled = false;
+
+ public static final String CLASSNAME = "v-splitpanel";
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ private static final int MIN_SIZE = 30;
+
+ private int 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(int orientation) {
+ setElement(DOM.createDiv());
+ switch (orientation) {
+ case ORIENTATION_HORIZONTAL:
+ setStyleName(CLASSNAME + "-horizontal");
+ break;
+ case ORIENTATION_VERTICAL:
+ default:
+ setStyleName(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(int 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) {
- // 100% needs special handling
- if (posAsInt + getSplitterSize() >= offsetLength) {
- posAsInt = offsetLength;
++ if (pos.endsWith("px")) {
++ float pixelPosition = Float.parseFloat(pos.substring(0,
++ pos.length() - 2));
+ int offsetLength = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth()
+ : getOffsetHeight();
+
- posAsFloat = ((float) posAsInt / (float) offsetLength * 100);
++ // Take splitter size into account at the edge
++ if (pixelPosition + getSplitterSize() >= offsetLength) {
++ return 100;
+ }
- posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 1));
+
++ return pixelPosition / offsetLength * 100;
+ } else {
- return posAsFloat;
++ 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 ORIENTATION_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 ORIENTATION_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 ORIENTATION_HORIZONTAL:
+ final int x = Util.getTouchOrMouseClientX(event);
+ onHorizontalMouseMove(x);
+ break;
+ case ORIENTATION_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 ORIENTATION_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);
+ }
+}