Java tutorial
/** * Sencha GXT 4.0.0 - Sencha for GWT * Copyright (c) 2006-2015, Sencha Inc. * * licensing@sencha.com * http://www.sencha.com/products/gxt/license/ * * ================================================================================ * Open Source License * ================================================================================ * This version of Sencha GXT is licensed under the terms of the Open Source GPL v3 * license. You may use this license only if you are prepared to distribute and * share the source code of your application under the GPL v3 license: * http://www.gnu.org/licenses/gpl.html * * If you are NOT prepared to distribute and share the source code of your * application under the GPL v3 license, other commercial and oem licenses * are available for an alternate download of Sencha GXT. * * Please see the Sencha GXT Licensing page at: * http://www.sencha.com/products/gxt/license/ * * For clarification or additional options, please contact: * licensing@sencha.com * ================================================================================ * * * ================================================================================ * Disclaimer * ================================================================================ * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY, * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING. * ================================================================================ */ package com.sencha.gxt.fx.client; import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.KeyCodes; 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.event.shared.SimpleEventBus; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Widget; import com.sencha.gxt.core.client.Style; import com.sencha.gxt.core.client.dom.XDOM; import com.sencha.gxt.core.client.dom.XElement; import com.sencha.gxt.core.client.gestures.DragGestureRecognizer; import com.sencha.gxt.core.client.gestures.TouchData; import com.sencha.gxt.core.client.gestures.TouchEventToGestureAdapter; import com.sencha.gxt.core.client.resources.CommonStyles; import com.sencha.gxt.core.client.resources.CommonStyles.Styles; import com.sencha.gxt.core.client.util.BaseEventPreview; import com.sencha.gxt.core.client.util.Point; import com.sencha.gxt.core.client.util.Rectangle; import com.sencha.gxt.core.shared.event.GroupingHandlerRegistration; import com.sencha.gxt.fx.client.DragCancelEvent.DragCancelHandler; import com.sencha.gxt.fx.client.DragCancelEvent.HasDragCancelHandlers; import com.sencha.gxt.fx.client.DragEndEvent.DragEndHandler; import com.sencha.gxt.fx.client.DragEndEvent.HasDragEndHandlers; import com.sencha.gxt.fx.client.DragHandler.HasDragHandlers; import com.sencha.gxt.fx.client.DragMoveEvent.DragMoveHandler; import com.sencha.gxt.fx.client.DragMoveEvent.HasDragMoveHandlers; import com.sencha.gxt.fx.client.DragStartEvent.DragStartHandler; import com.sencha.gxt.fx.client.DragStartEvent.HasDragStartHandlers; /** * Adds drag behavior to any widget. Drag operations can be initiated from the * widget itself, or another widget, such as the header in a dialog. * * <p/> * It is possible to specify event targets that will be ignored. If the target * element has the {@link Styles#nodrag()} style (as returned by * {@link CommonStyles#get()}) it will not trigger a drag operation. */ public class Draggable implements HasDragStartHandlers, HasDragEndHandlers, HasDragMoveHandlers, HasDragCancelHandlers, HasDragHandlers { public interface DraggableAppearance { void addUnselectableStyle(Element element); Element createProxy(); void removeUnselectableStyle(Element element); void setProxyStyle(String proxyClass); } protected int conX, conY, conWidth, conHeight; protected int dragStartX, dragStartY; protected int lastX, lastY; protected XElement proxyEl; protected Rectangle startBounds; protected DragGestureRecognizer dragGestureRecognizer; private final DraggableAppearance appearance; private int clientWidth, clientHeight; private boolean constrainClient = true; private boolean constrainHorizontal; private boolean constrainVertical; private Widget container; private boolean dragging; private Widget dragWidget; private XElement dragWidgetElement; private boolean enabled = true; private Widget handleWidget; private GroupingHandlerRegistration handlerRegistrations; private boolean moveAfterProxyDrag = true; private BaseEventPreview eventPreview; private boolean sizeProxyToSource = true; private int startDragDistance = 2; private Element startElement; // config private boolean updateZIndex = true; private boolean useProxy = true; private int xLeft = Style.DEFAULT, xRight = Style.DEFAULT; private int xTop = Style.DEFAULT, xBottom = Style.DEFAULT; private SimpleEventBus eventBus; /** * Creates a new draggable instance. * * @param dragWidget the widget to be dragged */ public Draggable(Widget dragWidget) { this(dragWidget, dragWidget, GWT.<DraggableAppearance>create(DraggableAppearance.class)); } /** * Creates a new draggable instance. * * @param dragWidget the widget to be dragged * @param appearance the appearance with which to render the component */ public Draggable(Widget dragWidget, DraggableAppearance appearance) { this(dragWidget, dragWidget, appearance); } /** * Create a new draggable instance. * * @param dragWidget the widget to be dragged * @param handleWidget the widget drags will be initiated from */ public Draggable(final Widget dragWidget, final Widget handleWidget) { this(dragWidget, handleWidget, GWT.<DraggableAppearance>create(DraggableAppearance.class)); } /** * Create a new draggable instance. * * @param dragWidget the widget to be dragged * @param handleWidget the widget drags will be initiated from * @param appearance the appearance with which to render the component */ public Draggable(final Widget dragWidget, final Widget handleWidget, DraggableAppearance appearance) { this.dragWidget = dragWidget; this.handleWidget = handleWidget; this.appearance = appearance; handleWidget.getElement().getStyle().setProperty("touchAction", "none"); handleWidget.getElement().getStyle().setProperty("msTouchAction", "none"); dragWidgetElement = dragWidget.getElement().cast(); // Determines where the drop target is for mouse // Mouse over provides drop over element target eventPreview = new BaseEventPreview() { @Override public boolean onPreview(NativePreviewEvent event) { Event e = event.getNativeEvent().cast(); e.preventDefault(); switch (event.getTypeInt()) { case Event.ONKEYDOWN: if (dragging && e.getKeyCode() == KeyCodes.KEY_ESCAPE) { cancelDrag(); } break; case Event.ONMOUSEMOVE: onMouseMove(e); break; case Event.ONMOUSEUP: stopDrag(e); break; } return true; } }; eventPreview.setAutoHide(false); MouseDownHandler mouseDownHandler = new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { Draggable.this.onMouseDown(event); } }; // Register mouse and touch listeners handlerRegistrations = new GroupingHandlerRegistration(); handlerRegistrations.add(handleWidget.addDomHandler(mouseDownHandler, MouseDownEvent.getType())); dragGestureRecognizer = getDragGestureRecognizer(); } /** * Crates the drag gesture recognizer and passes along the touch events to Draggable. * * @return the grag gesture recognizer */ protected DragGestureRecognizer getDragGestureRecognizer() { if (dragGestureRecognizer == null) { dragGestureRecognizer = new DragGestureRecognizer() { @Override protected boolean onStart(TouchData startedTouch) { super.onStart(startedTouch); return onTouchStart(startedTouch); } @Override protected void onMove(List<TouchData> touches) { super.onMove(touches); onTouchMove(touches); } @Override protected void onCancel(List<TouchData> touches) { super.onCancel(touches); onTouchEnd(touches); } @Override protected void onEnd(List<TouchData> touches) { super.onEnd(touches); onTouchEnd(touches); } }; TouchEventToGestureAdapter touchEventsGestureAdapter = new TouchEventToGestureAdapter(handleWidget, dragGestureRecognizer); handlerRegistrations.add(touchEventsGestureAdapter.getHandlerRegistration()); } return dragGestureRecognizer; } protected void onTouchEnd(List<TouchData> touches) { stopDrag(touches.get(0).getLastNativeEvent().<Event>cast()); } protected void onTouchMove(List<TouchData> touches) { TouchData touch = touches.get(0); Point pos = touch.getLastPosition(); handleMove(pos.getX(), pos.getY(), touch.getLastNativeEvent().<Event>cast()); } protected boolean onTouchStart(TouchData startedTouch) { if (!enabled) { return false; } Element target = startedTouch.getStartElement().asElement(); if (hasClassName(target, CommonStyles.get().nodrag())) { return false; } if (!handleWidget.getElement().isOrHasChild(target)) { return false; } Point position = startedTouch.getStartPosition(); Element start = startedTouch.getStartElement() != null ? startedTouch.getStartElement().asElement() : null; handleStart(position.getX(), position.getY(), startedTouch.getLastNativeEvent().<Event>cast(), start); return true; } @Override public HandlerRegistration addDragCancelHandler(DragCancelHandler handler) { return ensureHandlers().addHandler(DragCancelEvent.getType(), handler); } @Override public HandlerRegistration addDragEndHandler(DragEndHandler handler) { return ensureHandlers().addHandler(DragEndEvent.getType(), handler); } @Override public HandlerRegistration addDragHandler(DragHandler handler) { GroupingHandlerRegistration reg = new GroupingHandlerRegistration(); reg.add(ensureHandlers().addHandler(DragStartEvent.getType(), handler)); reg.add(ensureHandlers().addHandler(DragEndEvent.getType(), handler)); reg.add(ensureHandlers().addHandler(DragMoveEvent.getType(), handler)); reg.add(ensureHandlers().addHandler(DragCancelEvent.getType(), handler)); return reg; } @Override public HandlerRegistration addDragMoveHandler(DragMoveHandler handler) { return ensureHandlers().addHandler(DragMoveEvent.getType(), handler); } @Override public HandlerRegistration addDragStartHandler(DragStartHandler handler) { return ensureHandlers().addHandler(DragStartEvent.getType(), handler); } /** * Cancels the drag if running. */ public void cancelDrag() { eventPreview.remove(); if (dragging) { dragging = false; if (isUseProxy()) { proxyEl.disableTextSelection(false); proxyEl.getStyle().setVisibility(Visibility.HIDDEN); proxyEl.removeFromParent(); } else { dragWidgetElement.setXY(startBounds.getX(), startBounds.getY()); } ensureHandlers().fireEventFromSource(new DragCancelEvent(dragWidget, startElement), this); afterDrag(); } startElement = null; } /** * Returns the drag container. * * @return the drag container */ public Widget getContainer() { return container; } /** * Specifies a container to which the drag widget is constrained. * * @param container the container */ public void setContainer(Widget container) { this.container = container; } /** * Returns the drag handleWidget. * * @return the drag handleWidget */ public Widget getDragHandle() { return handleWidget; } /** * Returns the widget being dragged. * * @return the drag widget */ public Widget getDragWidget() { return dragWidget; } /** * Returns the number of pixels the cursor must move before dragging begins. * * @return the distance in pixels */ public int getStartDragDistance() { return startDragDistance; } /** * Specifies how far the cursor must move after mousedown to start dragging * (defaults to 2). * * @param startDragDistance the start distance in pixels */ public void setStartDragDistance(int startDragDistance) { this.startDragDistance = startDragDistance; } /** * Returns true if drag is constrained to the viewport. * * @return the constrain client state */ public boolean isConstrainClient() { return constrainClient; } /** * True to set constrain movement to the viewport (defaults to true). * * @param constrainClient true to constrain to viewport */ public void setConstrainClient(boolean constrainClient) { this.constrainClient = constrainClient; } /** * Returns true if horizontal movement is constrained. * * @return the horizontal constrain state */ public boolean isConstrainHorizontal() { return constrainHorizontal; } /** * True to stop horizontal movement (defaults to false). * * @param constrainHorizontal true to stop horizontal movement */ public void setConstrainHorizontal(boolean constrainHorizontal) { this.constrainHorizontal = constrainHorizontal; } /** * Returns true if vertical movement is constrained. * * @return true if vertical movement is constrained */ public boolean isConstrainVertical() { return constrainVertical; } /** * True to stop vertical movement (defaults to false). * * @param constrainVertical true to stop vertical movement */ public void setConstrainVertical(boolean constrainVertical) { this.constrainVertical = constrainVertical; } /** * Returns <code>true</code> if a drag is in progress. * * @return the drag state */ public boolean isDragging() { return dragging; } /** * Returns <code>true</code> if enabled. * * @return the enable state */ public boolean isEnabled() { return enabled; } /** * Enables dragging if the argument is <code>true</code>, and disables it * otherwise. * * @param enabled the new enabled state */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Returns true if the drag widget is moved after a proxy drag. * * @return the move after proxy state */ public boolean isMoveAfterProxyDrag() { return moveAfterProxyDrag; } /** * True to move source widget after a proxy drag (defaults to true). * * @param moveAfterProxyDrag true to move after a proxy drag */ public void setMoveAfterProxyDrag(boolean moveAfterProxyDrag) { this.moveAfterProxyDrag = moveAfterProxyDrag; } /** * Returns true if the proxy element is sized to match the drag widget. * * @return the size proxy to source state */ public boolean isSizeProxyToSource() { return sizeProxyToSource; } /** * True to set proxy dimensions the same as the drag widget (defaults to * true). * * @param sizeProxyToSource true to update proxy size */ public void setSizeProxyToSource(boolean sizeProxyToSource) { this.sizeProxyToSource = sizeProxyToSource; } /** * Returns true if the z-index is updated after a drag. * * @return the update z-index state */ public boolean isUpdateZIndex() { return updateZIndex; } /** * True if the CSS z-index should be updated on the widget being dragged. * Setting this value to <code>true</code> will ensure that the dragged * element is always displayed over all other widgets (defaults to true). * * @param updateZIndex true update the z-index */ public void setUpdateZIndex(boolean updateZIndex) { this.updateZIndex = updateZIndex; } /** * Returns true if proxy element is enabled. * * @return the use proxy state */ public boolean isUseProxy() { return useProxy; } /** * True to use a proxy widget during drag operation (defaults to true). * * @param useProxy true use a proxy */ public void setUseProxy(boolean useProxy) { this.useProxy = useProxy; } /** * Removes the drag handles. */ public void release() { cancelDrag(); handlerRegistrations.removeHandler(); } /** * Sets the proxy element. * * @param element the proxy element */ public void setProxy(XElement element) { proxyEl = element; } public void setProxyStyle(String proxyClass) { appearance.setProxyStyle(proxyClass); } /** * Constrains the horizontal travel. * * @param left the number of pixels the element can move to the left * @param right the number of pixels the element can move to the right */ public void setXConstraint(int left, int right) { xLeft = left; xRight = right; } /** * Constrains the vertical travel. * * @param top the number of pixels the element can move to the up * @param bottom the number of pixels the element can move to the down */ public void setYConstraint(int top, int bottom) { xTop = top; xBottom = bottom; } protected void afterDrag() { appearance.removeUnselectableStyle(Document.get().getBody()); Shim.get().uncover(); } protected XElement createProxy() { return proxyEl = appearance.createProxy().cast(); } protected void handleStart(int x, int y, Event event, Element target) { startBounds = dragWidgetElement.getBounds(); startElement = target; dragStartX = x; dragStartY = y; eventPreview.add(); clientWidth = Window.getClientWidth() + XDOM.getBodyScrollLeft(); clientHeight = Window.getClientHeight() + XDOM.getBodyScrollTop(); if (container != null) { conX = container.getAbsoluteLeft(); conY = container.getAbsoluteTop(); conWidth = container.getOffsetWidth(); conHeight = container.getOffsetHeight(); } if (startDragDistance == 0) { startDrag(event); } } protected void onMouseDown(MouseDownEvent e) { if (!enabled || e.getNativeEvent().getButton() != Event.BUTTON_LEFT) { return; } Element target = e.getNativeEvent().getEventTarget().cast(); if (hasClassName(target, CommonStyles.get().nodrag())) { return; } // still allow text selection, prevent drag of other elements if ((!"input".equalsIgnoreCase(target.getTagName()) && !"textarea".equalsIgnoreCase(target.getTagName())) || target.getPropertyBoolean("disabled")) { e.getNativeEvent().preventDefault(); } handleStart(e.getClientX(), e.getClientY(), e.getNativeEvent().<Event>cast(), target); } protected void handleMove(int x, int y, Event event) { if (!dragging && (Math.abs(dragStartX - x) > startDragDistance || Math.abs(dragStartY - y) > startDragDistance)) { startDrag(event); } if (dragging) { int left = constrainHorizontal ? startBounds.getX() : startBounds.getX() + (x - dragStartX); int top = constrainVertical ? startBounds.getY() : startBounds.getY() + (y - dragStartY); if (constrainClient) { if (!constrainHorizontal) { int width = startBounds.getWidth(); left = Math.max(left, 0); left = Math.max(0, Math.min(clientWidth - width, left)); } if (!constrainVertical) { top = Math.max(top, 0); int height = startBounds.getHeight(); if (Math.min(clientHeight - height, top) > 0) { top = Math.max(2, Math.min(clientHeight - height, top)); } } } if (container != null) { int width = startBounds.getWidth(); int height = startBounds.getHeight(); if (!constrainHorizontal) { left = Math.max(left, conX); left = Math.min(conX + conWidth - width, left); } if (!constrainVertical) { top = Math.min(conY + conHeight - height, top); top = Math.max(top, conY); } } if (!constrainHorizontal) { if (xLeft != Style.DEFAULT) { left = Math.max(startBounds.getX() - xLeft, left); } if (xRight != Style.DEFAULT) { left = Math.min(startBounds.getX() + xRight, left); } } if (!constrainVertical) { if (xTop != Style.DEFAULT) { top = Math.max(startBounds.getY() - xTop, top); } if (xBottom != Style.DEFAULT) { top = Math.min(startBounds.getY() + xBottom, top); } } lastX = left; lastY = top; DragMoveEvent evt = new DragMoveEvent(dragWidget, startElement, lastX, lastY, event); ensureHandlers().fireEventFromSource(evt, this); if (evt.isCancelled()) { cancelDrag(); return; } int tl = evt.getX() != lastX ? evt.getX() : lastX; int tt = evt.getY() != lastY ? evt.getY() : lastY; if (useProxy) { proxyEl.setXY(tl, tt); } else { dragWidgetElement.setXY(tl, tt); } } } protected void onMouseMove(Event event) { Element elem = event.getEventTarget().cast(); // elem.getClassName throwing GWT exception when dragged widget is over SVG / VML if (hasClassName(elem, "x-insert")) { return; } handleMove(event.getClientX(), event.getClientY(), event); } protected void startDrag(Event event) { DragStartEvent de = new DragStartEvent(dragWidget, startElement, startBounds.getX(), startBounds.getY(), event); ensureHandlers().fireEventFromSource(de, this); if (de.isCancelled()) { cancelDrag(); return; } dragging = true; appearance.addUnselectableStyle(Document.get().getBody()); if (!useProxy) { dragWidget.getElement().<XElement>cast().makePositionable(); } if (event != null) { event.preventDefault(); } Shim.get().cover(true); lastX = startBounds.getX(); lastY = startBounds.getY(); if (useProxy) { if (proxyEl == null) { createProxy(); } if (container == null) { Document.get().getBody().appendChild(proxyEl); } else { container.getElement().appendChild(proxyEl); } proxyEl.setVisibility(true); proxyEl.setZIndex(XDOM.getTopZIndex()); proxyEl.makePositionable(true); if (sizeProxyToSource) { proxyEl.setBounds(startBounds); } else { proxyEl.setXY(startBounds.getX() + 50, startBounds.getY() + 50); } // did listeners change size? if (de.getHeight() > 0 && de.getWidth() > 0) { proxyEl.setSize(de.getWidth(), de.getHeight(), true); } else if (de.getHeight() > 0) { proxyEl.setHeight(de.getHeight(), true); } else if (de.getWidth() > 0) { proxyEl.setWidth(de.getWidth(), true); } } else if (updateZIndex) { dragWidget.getElement().<XElement>cast().setZIndex(XDOM.getTopZIndex()); } } protected void stopDrag(Event event) { eventPreview.remove(); if (dragging) { dragging = false; if (isUseProxy()) { if (isMoveAfterProxyDrag()) { Rectangle rect = proxyEl.getBounds(); dragWidget.getElement().<XElement>cast().setXY(rect.getX(), rect.getY()); } proxyEl.setVisibility(false); proxyEl.disableTextSelection(false); proxyEl.removeFromParent(); } DragEndEvent de = new DragEndEvent(dragWidget, startElement, lastX, lastY, event); ensureHandlers().fireEventFromSource(de, this); afterDrag(); } startElement = null; } SimpleEventBus ensureHandlers() { return eventBus == null ? eventBus = new SimpleEventBus() : eventBus; } private native boolean hasClassName(Element elem, String className) /*-{ return !!elem.hasAttribute && elem.hasAttribute("class") && elem.getAttribute("class").indexOf(className) != -1; }-*/; }