rocket.dragndrop.client.DraggablePanel.java Source code

Java tutorial

Introduction

Here is the source code for rocket.dragndrop.client.DraggablePanel.java

Source

/*
 * Copyright Miroslav Pokorny
 * 
 * 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 rocket.dragndrop.client;

import java.util.Iterator;

import rocket.dom.client.Dom;
import rocket.event.client.EventBitMaskConstants;
import rocket.event.client.EventPreviewAdapter;
import rocket.event.client.MouseDownEvent;
import rocket.event.client.MouseEventAdapter;
import rocket.event.client.MouseMoveEvent;
import rocket.event.client.MouseUpEvent;
import rocket.selection.client.Selection;
import rocket.style.client.Css;
import rocket.style.client.CssUnit;
import rocket.style.client.InlineStyle;
import rocket.util.client.Checker;
import rocket.util.client.JavaScript;
import rocket.widget.client.Hijacker;
import rocket.widget.client.Html;
import rocket.widget.client.SimplePanel;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A DraggablePanel is a container for a single widget and marks a particular
 * widget as draggable.
 * 
 * Events are fired at different times during the dragging process. THe
 * recipient will have to handle removing the dragged widget from its parent and
 * adding it to its own panel.
 * 
 * @author Miroslav Pokorny (mP)
 */
public class DraggablePanel extends SimplePanel {

    public DraggablePanel() {
        super();
    }

    @Override
    protected void beforeCreatePanelElement() {
        super.beforeCreatePanelElement();

        this.setDragNDropListeners(createDragNDropListeners());
    }

    @Override
    protected Element createPanelElement() {
        return DOM.createDiv();
    }

    @Override
    protected void checkElement(final Element element) {
        throw new UnsupportedOperationException("checkElement");
    }

    @Override
    protected void afterCreatePanelElement() {
        super.afterCreatePanelElement();

        this.getEventListenerDispatcher().addMouseEventListener(new MouseEventAdapter() {
            public void onMouseDown(final MouseDownEvent event) {
                DraggablePanel.this.onDragStart(event);
            }
        });
    }

    @Override
    protected String getInitialStyleName() {
        return Constants.DRAG_N_DROP_DRAGGABLE_WIDGET_STYLE;
    }

    @Override
    protected int getSunkEventsBitMask() {
        return EventBitMaskConstants.MOUSE_DOWN;
    }

    @Override
    protected void insert0(final Element element, final int index) {
        this.getElement().appendChild(element);
    }

    @Override
    protected void remove0(final Element element, final int index) {
        Dom.removeFromParent(element);
    }

    /**
     * This event is fired whenever a user starts a drag of a draggable widget.
     * 
     * @param mouseDownEvent
     */
    protected void onDragStart(final MouseDownEvent mouseDownEvent) {
        Checker.notNull("parameter:mouseDownEvent", mouseDownEvent);

        while (true) {
            // first disable / clear any selections...
            Selection.disableTextSelection();
            Selection.clearAnySelectedText();

            mouseDownEvent.stop();

            // fire the event...
            Widget dragged = createDraggedWidget();
            dragged.setStyleName(this.getGhostStyle());

            final DragStartEvent dragStartEvent = new DragStartEvent();
            dragStartEvent.setDraggablePanel(this);
            dragStartEvent.setDragged(dragged);
            dragStartEvent.setDraggedElement(mouseDownEvent.getTarget());

            Widget widget = this.getWidget();
            final Element widgetElement = widget.getElement();
            final int widgetLeft = widgetElement.getAbsoluteLeft();
            final int widgetTop = widgetElement.getAbsoluteTop();
            dragStartEvent.setWidget(widget);

            final int mouseLeft = mouseDownEvent.getPageX();
            final int mouseTop = mouseDownEvent.getPageY();
            dragStartEvent.setMousePageX(mouseLeft);
            dragStartEvent.setMousePageY(mouseTop);

            int xOffset = widgetLeft - mouseLeft;
            int yOffset = widgetTop - mouseTop;
            dragStartEvent.setXOffset(xOffset);
            dragStartEvent.setYOffset(yOffset);

            this.getDragNDropListeners().fireDragStarted(dragStartEvent);

            // if cancelled good bye!
            if (dragStartEvent.isCancelled()) {
                break;
            }

            // update ui ( dragged widget etc )
            dragged = dragStartEvent.getDragged();
            xOffset = dragStartEvent.getXOffset();
            yOffset = dragStartEvent.getYOffset();
            widget = dragStartEvent.getWidget();

            final DraggedPanel draggedPanel = this.createDraggedPanel();
            draggedPanel.prepare(widget);
            draggedPanel.setWidget(dragged);
            draggedPanel.setXOffset(xOffset);
            draggedPanel.setYOffset(yOffset);
            draggedPanel.setActualWidget(widget);

            // register an event previewer to handle mouse events(watch for drag
            // move, drop ).
            final EventPreviewAdapter greedy = new EventPreviewAdapter() {
                public void onMouseMove(final MouseMoveEvent event) {
                    DraggablePanel.this.onDragMove(event, draggedPanel);
                }

                public void onMouseUp(final MouseUpEvent event) {
                    DraggablePanel.this.onDropped(event, draggedPanel, this);
                }
            };
            greedy.install();

            final Element dragPanelElement = draggedPanel.getElement();
            final int elementPageX = dragPanelElement.getAbsoluteLeft();
            final int elementPageY = dragPanelElement.getAbsoluteTop();

            // reposition the $dragged so it follows the mouse.
            final int mousePageX = mouseDownEvent.getPageX();
            final int mousePageY = mouseDownEvent.getPageY();
            dragStartEvent.setMousePageX(mousePageX);
            dragStartEvent.setMousePageY(mousePageY);

            final int newX = mousePageX + xOffset - elementPageX;
            final int newY = mousePageY + yOffset - elementPageY;

            final InlineStyle draggedInlineStyle = InlineStyle.getInlineStyle(dragged.getElement());
            draggedInlineStyle.setString(Css.POSITION, "absolute");
            draggedInlineStyle.setInteger(Css.LEFT, newX, CssUnit.PX);
            draggedInlineStyle.setInteger(Css.TOP, newY, CssUnit.PX);
            break;
        }
    }

    protected void onDragMove(final MouseMoveEvent mouseMoveEvent, final DraggedPanel draggedPanel) {
        while (true) {
            mouseMoveEvent.stop();

            // prepare drag move event
            final DragMoveEvent dragMoveEvent = new DragMoveEvent();
            dragMoveEvent.setDraggablePanel(this);

            Widget draggedWidget = draggedPanel.getWidget();
            dragMoveEvent.setDragged(draggedWidget);

            final int xOffset = draggedPanel.getXOffset();
            final int yOffset = draggedPanel.getYOffset();
            dragMoveEvent.setXOffset(xOffset);
            dragMoveEvent.setYOffset(yOffset);

            // fire!
            this.getDragNDropListeners().fireDragMoveStarted(dragMoveEvent);

            if (dragMoveEvent.isCancelled()) {
                break;
            }

            draggedWidget = dragMoveEvent.getDragged();

            // find the absolute position of the DraggablePanel.
            final Element element = draggedPanel.getElement();
            final int elementPageX = DOM.getAbsoluteLeft(element);
            final int elementPageY = DOM.getAbsoluteTop(element);

            // reposition the $dragged so it follows the mouse.
            final int mousePageX = mouseMoveEvent.getPageX();
            final int mousePageY = mouseMoveEvent.getPageY();
            dragMoveEvent.setMousePageX(mousePageX);
            dragMoveEvent.setMousePageY(mousePageY);

            final int newX = mousePageX + xOffset - elementPageX;
            final int newY = mousePageY + yOffset - elementPageY;

            final InlineStyle draggedInlineStyle = InlineStyle.getInlineStyle(draggedWidget.getElement());
            draggedInlineStyle.setString(Css.POSITION, "absolute");
            draggedInlineStyle.setInteger(Css.LEFT, newX, CssUnit.PX);
            draggedInlineStyle.setInteger(Css.TOP, newY, CssUnit.PX);

            draggedPanel.setWidget(draggedWidget);
            break;
        }
    }

    protected void onDropped(final MouseUpEvent mouseUpEvent, final DraggedPanel draggedPanel,
            EventPreviewAdapter previewer) {
        // stop bubbling...
        mouseUpEvent.cancelBubble(true);

        previewer.uninstall();

        // restore widget to its original parent element...
        final Hijacker hijacker = draggedPanel.getHijacker();
        hijacker.restore();

        // reenable selections
        Selection.enableTextSelection();
        Selection.clearAnySelectedText();

        // try and find a drop target...
        final int mouseX = mouseUpEvent.getPageX();
        final int mouseY = mouseUpEvent.getPageY();

        final DropTargetPanel droppedOver = this.findDropTarget(mouseX, mouseY);

        // create the event...
        final DropEvent dropEvent = new DropEvent();
        dropEvent.setDraggablePanel(this);
        dropEvent.setWidget(draggedPanel.getActualWidget());

        final Element droppedOverElement = this.findDroppedOverElement(mouseX, mouseY, droppedOver.getElement());
        dropEvent.setDroppedOverElement(droppedOverElement);
        dropEvent.setDropTargetPanel(droppedOver);

        this.getDragNDropListeners().fireDropped(dropEvent);

        // the dragged and its panel will disappear...
        draggedPanel.removeFromParent();

        // let the drop target add the widget.
        droppedOver.accept(dropEvent);
    }

    protected DraggedPanel createDraggedPanel() {
        return new DraggedPanel();
    }

    /**
     * This panel houses the ghost and is moved around each time the user moves
     * the dragged widget
     */
    class DraggedPanel extends rocket.widget.client.SimplePanel {

        @Override
        protected void checkElement(Element element) {
            throw new UnsupportedOperationException("checkElement");
        }

        @Override
        protected Element createPanelElement() {
            return DOM.createDiv();
        }

        @Override
        protected String getInitialStyleName() {
            return "";
        }

        @Override
        protected int getSunkEventsBitMask() {
            // never receives any events as these are handled by a
            // eventpreview(er)
            return 0;
        }

        @Override
        protected void insert0(final Element element, final int index) {
            DOM.insertChild(this.getElement(), element, 0);
        }

        @Override
        protected void remove0(final Element element, final int index) {
            Dom.removeFromParent(element);
        }

        public void setWidget(final Widget widget) {
            super.setWidget(widget);
            if (null != widget) {
                widget.addStyleName(DraggablePanel.this.getGhostStyle());
            }
        }

        public Widget getWidget() {
            final Widget widget = super.getWidget();
            if (null != widget) {
                widget.removeStyleName(DraggablePanel.this.getGhostStyle());
            }
            return widget;
        }

        protected void prepare(final Widget widget) {
            final Element widgetElement = widget.getElement();

            final Hijacker hijacker = new Hijacker(widgetElement);
            this.setHijacker(hijacker);

            RootPanel.get().add(this);

            // insert the root elementof this panel in the same spot were widget
            // was.
            final Element element = this.getElement();
            final InlineStyle elementInlineStyle = InlineStyle.getInlineStyle(element);
            elementInlineStyle.setString(Css.POSITION, "relative");
            elementInlineStyle.setInteger(Css.LEFT, 0, CssUnit.PX);
            elementInlineStyle.setInteger(Css.TOP, 0, CssUnit.PX);

            hijacker.replace(element);

            element.appendChild(widgetElement);
        }

        /**
         * A hijacker is used to store and eventually restore the widget element
         * to its rightful spot in the dom after a drop.
         */
        private Hijacker hijacker;

        protected Hijacker getHijacker() {
            Checker.notNull("field:hijacker", hijacker);
            return this.hijacker;
        }

        public void setHijacker(final Hijacker hijacker) {
            Checker.notNull("parameter:hijacker", hijacker);
            this.hijacker = hijacker;
        }

        private int xOffset;

        public int getXOffset() {
            return this.xOffset;
        }

        public void setXOffset(final int xOffset) {
            this.xOffset = xOffset;
        }

        private int yOffset;

        public int getYOffset() {
            return this.yOffset;
        }

        public void setYOffset(final int yOffset) {
            this.yOffset = yOffset;
        }

        /**
         * The widget thats having its ghost being dragged
         */
        private Widget actualWidget;

        public Widget getActualWidget() {
            Checker.notNull("field:actualWidget", actualWidget);
            return this.actualWidget;
        }

        public void setActualWidget(final Widget actualWidget) {
            Checker.notNull("parameter:actualWidget", actualWidget);
            this.actualWidget = actualWidget;
        }
    }

    protected String getGhostStyle() {
        return Constants.DRAG_N_DROP_DRAGGED_WIDGET_STYLE;
    }

    /**
     * Factory method that creates the initial ghost, by simply cloning the
     * contents of the current widget.
     * 
     * @return The new dragged widget.
     */
    protected Widget createDraggedWidget() {
        final Element element = this.getWidget().getElement();
        return new Html((Element) element.cloneNode(true).cast());
    }

    /**
     * This method visits all registered drop targets and attempts to find one
     * that includes the given coordinates.
     * 
     * @param x
     * @param y
     * @return The matched DropTargetPanel for the given screen coordinates.
     */
    protected DropTargetPanel findDropTarget(final int x, final int y) {
        DropTargetPanel found = null;
        final Iterator<DropTargetPanel> possibleTargets = DropTargetPanelCollection.getInstance()
                .getDropTargetPanels().iterator();
        while (possibleTargets.hasNext()) {
            final DropTargetPanel possibleTarget = possibleTargets.next();
            final Element otherElement = possibleTarget.getElement();

            final int left = otherElement.getAbsoluteLeft();
            final int right = left + possibleTarget.getOffsetWidth();

            if (x < left || x > right) {
                continue;
            }

            final int top = otherElement.getAbsoluteTop();
            final int bottom = top + possibleTarget.getOffsetHeight();
            if (y < top || y > bottom) {
                continue;
            }

            found = possibleTarget;
            break;
        }

        return found;
    }

    /**
     * Using the given mouse coordinates and starting at the given element(
     * which is the root of the dropTarget) find the element that the mouse was
     * over when the drop occured.
     * 
     * Unfortunately the mouseUpEvent target cannot be read as it gives the
     * ghost rather than the true element.
     * 
     * @param x
     * @param y
     * @param element
     * @return The found element that the drop occured over.
     */
    protected Element findDroppedOverElement(final int x, final int y, final Element element) {
        Element between = element;

        final int childCount = DOM.getChildCount(element);

        for (int i = 0; i < childCount; i++) {
            final Element child = DOM.getChild(element, i);
            if (Dom.isTag(child, "colgroup")) {
                continue;
            }

            final int childOffsetLeft = DOM.getAbsoluteLeft(child);
            if (x < childOffsetLeft) {
                continue;
            }

            final int childOffsetTop = DOM.getAbsoluteTop(child);
            if (y < childOffsetTop) {
                continue;
            }

            final int childOffsetRight = childOffsetLeft + JavaScript.getInteger(child, "offsetWidth");
            if (x > childOffsetRight) {
                continue;
            }
            final int childOffsetBottom = childOffsetTop + JavaScript.getInteger(child, "offsetHeight");
            if (y > childOffsetBottom) {
                continue;
            }

            between = this.findDroppedOverElement(x, y, child);
            break;
        }

        return between;
    }

    /**
     * A list of listeners interested in dragNDrop events.
     */
    private DragNDropListenerCollection dragNDropListeners;

    protected DragNDropListenerCollection getDragNDropListeners() {
        Checker.notNull("field:dragNDropListeners", this.dragNDropListeners);
        return this.dragNDropListeners;
    }

    protected void setDragNDropListeners(final DragNDropListenerCollection dragNDropListeners) {
        Checker.notNull("parameter:dragNDropListeners", dragNDropListeners);
        this.dragNDropListeners = dragNDropListeners;
    }

    protected DragNDropListenerCollection createDragNDropListeners() {
        return new DragNDropListenerCollection();
    }

    public void addDragNDropListener(final DragNDropListener listener) {
        this.getDragNDropListeners().add(listener);
    }

    public void removeDragNDropListener(final DragNDropListener listener) {
        this.getDragNDropListeners().remove(listener);
    }
}