ch.unifr.pai.twice.dragndrop.client.MPDragNDrop.java Source code

Java tutorial

Introduction

Here is the source code for ch.unifr.pai.twice.dragndrop.client.MPDragNDrop.java

Source

package ch.unifr.pai.twice.dragndrop.client;

/*
 * Copyright 2013 Oliver Schmid
 * 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.
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import ch.unifr.pai.twice.dragndrop.client.DragNDrop.DragNDropIntf;
import ch.unifr.pai.twice.dragndrop.client.configuration.DragConfiguration;
import ch.unifr.pai.twice.dragndrop.client.factories.DropTargetHandlerFactory.Priority;
import ch.unifr.pai.twice.dragndrop.client.intf.DragNDropHandler;
import ch.unifr.pai.twice.dragndrop.client.intf.Draggable;
import ch.unifr.pai.twice.dragndrop.client.intf.DropTargetHandler;
import ch.unifr.pai.twice.dragndrop.client.utils.Triple;
import ch.unifr.pai.twice.dragndrop.client.utils.Tuple;
import ch.unifr.pai.twice.multipointer.client.MultiCursorController;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.HasMouseOutHandlers;
import com.google.gwt.event.dom.client.HasMouseOverHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
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.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A drag and drop implementation for multiple mouse pointers (as well as single mouse pointers)
 * 
 * @author Oliver Schmid
 * 
 */
class MPDragNDrop implements DragNDropIntf {

    /**
     * Currently active drag and drop handlers (for the drags which are currently in progress) registered in a map depending on the UUID of the device which
     * executes the drag
     */
    protected static Map<String, MPDragNDrop> activeHandlers = new HashMap<String, MPDragNDrop>();
    /**
     * The kept reference of the registered event preview for handling mouse events.
     */
    protected static HandlerRegistration handlerReg;
    /**
     * A constant style name attached to widgets which are currently dragged
     */
    protected final static String DRAGGINGSTYLENAME = "dragging";

    /**
     * The widget to be dragged
     */
    protected Widget w;

    /**
     * The calculated percentage of the x-axis offset between the left coordinate of the dragged widget and the mouse position at the beginning of the drag
     */
    protected int percOffsetX;

    /**
     * The calculated percentage of the y-axis offset between the top coordinate of the dragged widget and the mouse poisition at the beginning of the drag
     */
    protected int percOffsetY;
    /**
     * Origin value of the CSS-display attribute of the widget to be dragged (used for resetting the attribute after drag)
     */
    protected String originDisplay;
    /**
     * The {@link DragConfiguration} applicable
     */
    protected DragConfiguration conf;
    /**
     * The global drop target registry (application wide)
     */
    protected static Map<Widget, DropTargetHandler> dropTargetRegistry = new HashMap<Widget, DropTargetHandler>();
    /**
     * Map of a list of devices (UUIDs) currently hovering a specific drop target
     */
    protected static Map<DropTargetHandler, Set<String>> hoverDropTargets = new HashMap<DropTargetHandler, Set<String>>();

    /**
     * Map for the drop target, a device (UUIS) is currently hovering
     */
    protected static Map<String, DropTargetHandler> currentHovering = new HashMap<String, DropTargetHandler>();

    /*
     * (non-Javadoc)
     * @see ch.unifr.pai.twice.dragndrop.client.DragNDrop.DragNDropIntf#initialize(com.google.gwt.user.client.ui.Widget, int, int,
     * ch.unifr.pai.twice.dragndrop.client.configuration.DragConfiguration)
     */
    @Override
    public void initialize(Widget w, int offsetX, int offsetY, DragConfiguration conf) {
        this.w = w;
        this.percOffsetX = (int) (100.0 / w.getOffsetWidth() * offsetX);
        this.percOffsetY = (int) (100.0 / w.getOffsetHeight() * offsetY);
        this.originDisplay = w.getElement().getStyle().getDisplay();
        this.conf = conf;
    }

    /**
     * @return a delay in ms which has to be waited for to start the drag (necessary for separating a simple click from a drag)
     */
    protected int getDragDelayInMs() {
        return 200;
    }

    /**
     * The native preview handler listening for all mouse pointer events which are important to us
     */
    private final NativePreviewHandler nativePreviewHandler = createNativePreviewHandler();

    /**
     * @return the native preview handler handling all mouse events for the drag and drop mechansim
     */
    protected NativePreviewHandler createNativePreviewHandler() {
        return new NativePreviewHandler() {
            /**
             * This event preview handler is active during the actual drag only and prevents the browser default behavior.
             * 
             * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
             */
            @Override
            public void onPreviewNativeEvent(NativePreviewEvent event) {
                switch (event.getTypeInt()) {
                case Event.ONMOUSEDOWN:
                    event.getNativeEvent().preventDefault();
                    break;
                case Event.ONMOUSEMOVE:
                case Event.ONMOUSEUP:
                    event.getNativeEvent().preventDefault();
                    String deviceId = getDeviceId(event.getNativeEvent());
                    MPDragNDrop handler = activeHandlers.get(deviceId);
                    if (handler != null) {
                        handler.onMouseEvent(deviceId, (Event) event.getNativeEvent());
                    }
                    break;
                }
            }
        };
    }

    /**
     * A helper class to hold a value
     * 
     * @author Oliver Schmid
     * 
     * @param <T>
     */
    private static class ValueHolder<T> {
        private T value;

        public void setValue(T value) {
            this.value = value;
        }

        public T getValue() {
            return value;
        }

    }

    /**
     * A simple callback interface
     * 
     * @author Oliver Schmid
     * 
     * @param <T>
     */
    protected static interface Callback<T> {
        void onDone(T value);
    }

    /**
     * Delegates the registration of a generic "dragHandler" to the {@link MouseDownHandler} of the current widget since we are working with mouse pointers in
     * this kind of drag and drop implementation
     * 
     * @param w
     * @param callback
     */
    protected void addDragHandler(Draggable w, final Callback<NativeEvent> callback) {
        w.addMouseDownHandler(new MouseDownHandler() {
            @Override
            public void onMouseDown(final MouseDownEvent event) {
                callback.onDone(event.getNativeEvent());
            }
        });
    }

    /**
     * Delegates the endHandler to the {@link MouseUpHandler} of the current widget since we are working with mouse pointers in this kind of drag and drop
     * implementation
     * 
     * @param w
     * @param callback
     * @return
     */
    protected HandlerRegistration registerEndHandler(Draggable w, final Callback<NativeEvent> callback) {
        return w.addMouseUpHandler(new MouseUpHandler() {

            @Override
            public void onMouseUp(MouseUpEvent event) {
                callback.onDone(event.getNativeEvent());
            }
        });
    }

    /**
     * Helper method to get the device id of the originator of an event.
     * 
     * @param event
     * @return the UUID of the device
     */
    String getDeviceId(NativeEvent event) {
        return MultiCursorController.getUUID(event);
    }

    /**
     * Helper method to get the x-position of a event (relative to the client)
     * 
     * @param event
     * @return
     */
    int getX(NativeEvent event) {
        return event.getClientX();
    }

    /**
     * Helper method to get the y-position of a event (relative to the client)
     * 
     * @param event
     * @return
     */
    int getY(NativeEvent event) {
        return event.getClientY();
    }

    /**
     * @see ch.unifr.pai.twice.dragndrop.client.DragNDrop.DragNDropIntf#makeDraggable(ch.unifr.pai.twice.dragndrop.client.intf.Draggable,
     *      ch.unifr.pai.twice.dragndrop.client.configuration.DragConfiguration, com.google.gwt.dom.client.Element)
     * 
     *      Make the widget draggable by attaching a drag handler to it. If the drag handler is invoked, the logic checks if the widget is currently draggable
     *      (e.g. not locked by another user) and starts the drag by the invocation of
     *      {@link MPDragNDrop#startDrag(Widget, String, int, int, DragConfiguration, Element)} after making sure, that the delay defined by
     *      {@link MPDragNDrop#getDragDelayInMs()} is exceeded and it therefore is a valid drag
     * 
     */
    @Override
    public void makeDraggable(final Draggable w, final DragConfiguration conf, final Element dragProxyTemplate) {
        if (w instanceof Widget) {
            ((Widget) w).addStyleName("draggable");
            addDragHandler(w, new Callback<NativeEvent>() {
                @Override
                public void onDone(NativeEvent event) {
                    if (w.isDraggable()) {
                        final String deviceId = getDeviceId(event);
                        final ValueHolder<Boolean> drag = new ValueHolder<Boolean>();
                        drag.setValue(true);
                        final HandlerRegistration end = registerEndHandler(w, new Callback<NativeEvent>() {

                            @Override
                            public void onDone(NativeEvent event) {

                                if (getDeviceId(event).equals(deviceId))
                                    drag.setValue(false);
                            }
                        });
                        final int offsetX = getX(event) - ((Widget) w).getElement().getAbsoluteLeft();
                        final int offsetY = getY(event) - ((Widget) w).getElement().getAbsoluteTop();
                        DOM.eventPreventDefault((Event) event);
                        Timer t = new Timer() {

                            @Override
                            public void run() {
                                end.removeHandler();
                                if (drag.getValue() && !((Widget) w).getStyleName().contains(DRAGGINGSTYLENAME))
                                    startDrag((Widget) w, deviceId, offsetX, offsetY, conf, dragProxyTemplate);
                            }
                        };
                        t.schedule(getDragDelayInMs());
                    }
                }
            });
        }
    }

    /**
     * Invoked when a drag starts. This method handles the full dragging
     * 
     * @param w
     *            - the widget to be dragged
     * @param deviceId
     *            - the deviceId of the device dragging the widget
     * @param offsetX
     * @param offsetY
     * @param conf
     * @param dragProxyTemplate
     */
    protected void startDrag(Widget w, String deviceId, int offsetX, int offsetY, DragConfiguration conf,
            Element dragProxyTemplate) {

        MultiCursorController.getInstance().notifyCursor(deviceId, "startDrag");
        if (conf == null)
            conf = DragConfiguration.withProxy();
        if (conf.getDragNDropHandler() != null) {
            conf.getDragNDropHandler().onStartDrag(deviceId, w);
        }
        conf.setDragProxy(DOM.clone(
                dragProxyTemplate == null ? w.getElement() : (com.google.gwt.user.client.Element) dragProxyTemplate,
                true));
        conf.getDragProxy().addClassName("drag-proxy");
        conf.getDragProxy().getStyle().setPosition(Position.ABSOLUTE);
        RootPanel.getBodyElement().appendChild(conf.getDragProxy());
        conf.getDragProxy().getStyle().setLeft(w.getElement().getAbsoluteLeft(), Unit.PX);
        conf.getDragProxy().getStyle().setTop(w.getElement().getAbsoluteTop(), Unit.PX);
        conf.getDragProxy().getStyle().setPosition(Position.ABSOLUTE);
        // DOM.eventPreventDefault((Event) event.getNativeEvent());
        if (activeHandlers.isEmpty())
            addEventPreview();
        MPDragNDrop d = createDragNDrop();
        d.initialize(w, offsetX, offsetY, conf);
        activeHandlers.put(deviceId, d);
        if (!conf.isWithProxy()) {
            w.getElement().getStyle().setDisplay(Display.BLOCK);
        } else {
            w.addStyleName(DRAGGINGSTYLENAME);
        }
    }

    /**
     * A delegator method to instantiate a drag and drop handler through deferred binding.
     * 
     * @return
     */
    protected MPDragNDrop createDragNDrop() {
        return GWT.create(MPDragNDrop.class);
    }

    /**
     * Called when a new repositioning event is triggered during the drag.
     * 
     * @param deviceId
     * @param event
     */
    protected void onDrag(String deviceId, Event event) {
        setPosition(getX(event), getY(event), true);
        if (!hoverDropTargets.isEmpty()) {
            MPDragNDrop h = activeHandlers.get(deviceId);
            if (h != null) {
                Triple<Double, Double, DropTargetHandler> handlerOfDropTarget = h.getHandlerOfDropTarget();
                DropTargetHandler dropTarget = null;
                Double intersectionPercentage = null;
                Double intersectionPercentageWithTarget = null;
                if (handlerOfDropTarget != null) {
                    intersectionPercentage = handlerOfDropTarget.getFirst();
                    intersectionPercentageWithTarget = handlerOfDropTarget.getSecond();
                    dropTarget = handlerOfDropTarget.getThird();
                }
                handleHover(dropTarget, intersectionPercentage, intersectionPercentageWithTarget, deviceId, event,
                        h.w, h.conf.getDragProxy());
            }
        }
    }

    /**
     * Handle the hovering of a dragged widget by checking if it hovers a registered drop target
     * 
     * @param dropTarget
     * @param intersectionPercentage
     * @param intersectionPercentageWithTarget
     * @param deviceId
     * @param event
     * @param widget
     * @param dragProxy
     */
    protected void handleHover(DropTargetHandler dropTarget, Double intersectionPercentage,
            Double intersectionPercentageWithTarget, String deviceId, Event event, Widget widget,
            Element dragProxy) {
        DropTargetHandler currentHover = currentHovering.get(deviceId);
        if (currentHover != null && currentHover == dropTarget) {
            return;
        } else {
            if (currentHover != null) {
                hoverDropTargets.get(currentHover).remove(deviceId);
                if (hoverDropTargets.get(currentHover).size() == 0)
                    currentHover.onHoverEnd(deviceId, widget, dragProxy, event);
            }
            if (dropTarget == null || hoverDropTargets.get(dropTarget) == null) {
                currentHovering.remove(deviceId);
            } else {
                hoverDropTargets.get(dropTarget).add(deviceId);
                currentHovering.put(deviceId, dropTarget);
                dropTarget.onHover(deviceId, widget, dragProxy, event, intersectionPercentage,
                        intersectionPercentageWithTarget);
            }
        }
    }

    /**
     * Functionality triggered on the end of the drag (e.g. the release of the mouse button). This method resets all the necessary management as well as the
     * event preview needed during the drag. It checks if the element is dropped above a drop target and triggers the event on the drop target. Additionally, it
     * checks if the drag and drop handler accepts the drop (if {@link DragNDropHandler#onDrop(String, Widget, int, int, Event, DropTargetHandler, boolean)}
     * returns true). If there is a drop target available, it will hand over the information about the acceptance or the rejection of the drop through the
     * widget itself as part of the parameters ("resetByOwner") and lets the drop target decide how to handle the situation. If there is no drop target
     * available and the drop is rejected, the widget is repositioned to its original coordinates. In the end, the onEndHover logic is invoked for the drag and
     * drop handler.
     * 
     * 
     * @param deviceId
     * @param event
     */
    protected void endDrag(String deviceId, Event event) {
        MPDragNDrop h = activeHandlers.get(deviceId);
        if (h != null && h.w != null) {
            MultiCursorController.getInstance().notifyCursor(deviceId, "endDrag");
            h.w.removeStyleName(DRAGGINGSTYLENAME);
            h.w.removeStyleName("drag-proxy");
            Triple<Double, Double, DropTargetHandler> handlerOfDropTarget = h.getHandlerOfDropTarget();
            final int proxyLeft = h.conf.getDragProxy().getAbsoluteLeft();
            final int proxyTop = h.conf.getDragProxy().getAbsoluteTop();

            DropTargetHandler dropTarget = null;
            if (handlerOfDropTarget != null) {
                dropTarget = handlerOfDropTarget.getThird();
            }
            boolean outOfBox = h.outOfBox(event);
            if (h.conf != null && h.conf.getDragNDropHandler() != null) {
                if (h.conf.getDragProxy() != null)
                    h.conf.getDragProxy().removeFromParent();
                boolean resetByOwner = !h.conf.getDragNDropHandler().onDrop(deviceId, h.w, proxyLeft, proxyTop,
                        event, dropTarget, outOfBox);
                // boolean resetByDropTarget = false;
                handleHover(null, Double.valueOf(0), Double.valueOf(0), deviceId, event, h.w,
                        h.conf.getDragProxy());
                if (dropTarget != null) {
                    Scheduler.get()
                            .scheduleDeferred(h.new DropCommand(dropTarget, deviceId, h, event,
                                    handlerOfDropTarget.getFirst(), handlerOfDropTarget.getSecond(), proxyLeft,
                                    proxyTop, resetByOwner));
                } else if (resetByOwner) {
                    h.resetPosition();
                    h.conf.getDragNDropHandler().onEndOfDrop(deviceId, h.w, proxyLeft, proxyTop, event);
                } else {
                    h.conf.getDragNDropHandler().onEndOfDrop(deviceId, h.w, proxyLeft, proxyTop, event);
                }
            }

        }

        activeHandlers.remove(deviceId);
        if (activeHandlers.isEmpty())
            removeEventPreview();
    }

    /**
     * The drop command is a transport object to hand over the information of a drop from the widget's drag handler to a drop target.
     * 
     * @author Oliver Schmid
     * 
     */
    private class DropCommand implements Command {
        private final DropTargetHandler dropTarget;
        private final String deviceId;
        private final MPDragNDrop h;
        private final Event event;
        private final Double intersectionPercentage;
        private final Double intersectionPercentageWithTarget;
        private final boolean resetByOwner;
        private final int proxyLeft;
        private final int proxyTop;

        public DropCommand(DropTargetHandler dropTarget, String deviceId, MPDragNDrop h, Event event,
                Double intersectionPercentage, Double intersectionPercentageWithTarget, int proxyLeft, int proxyTop,
                boolean resetByOwner) {
            super();
            this.dropTarget = dropTarget;
            this.deviceId = deviceId;
            this.h = h;
            this.resetByOwner = resetByOwner;
            this.event = event;
            this.proxyLeft = proxyLeft;
            this.proxyTop = proxyTop;
            this.intersectionPercentage = intersectionPercentage;
            this.intersectionPercentageWithTarget = intersectionPercentageWithTarget;
        }

        /**
         * In the standard behavior, the onDrop method of the drop target is executed (even if the event is rejected by the dragged widget!!!) Then, if either
         * the drop target or the owning device has rejected the drop, the dragged widget is reset to its original position.
         * 
         * @see com.google.gwt.user.client.Command#execute()
         */
        @Override
        public void execute() {
            boolean resetByDropTarget = !dropTarget.onDrop(deviceId, h.w, conf.getDragProxy(), event,
                    intersectionPercentage, intersectionPercentageWithTarget);
            if (resetByDropTarget || resetByOwner) {
                h.resetPosition();
            }
            h.conf.getDragNDropHandler().onEndOfDrop(deviceId, h.w, proxyLeft, proxyTop, event);
        }

    }

    /**
     * 
     * @return the information of the percentage of the area of the dragged proxy which intersects with the drop target, the percentage of the drop target area
     *         which is covered by the proxy and the drop target handler or null if no drop target intersects with the dragged widget
     */
    protected Triple<Double, Double, DropTargetHandler> getHandlerOfDropTarget() {
        Tuple<Widget, Long> target = getHoverDropTarget(conf.getDragProxy());
        if (target == null || target.getFirst() == null || target.getSecond() == null)
            return null;
        else
            return new Triple<Double, Double, DropTargetHandler>(
                    100.0 * target.getSecond() / getAreaOfWidget(conf.getDragProxy()),
                    100.0 * target.getSecond() / getAreaOfWidget(target.getFirst().getElement()),
                    dropTargetRegistry.get(target.getFirst()));
    }

    /**
     * @param e
     *            - a HTML element
     * @return the area of the HTML element in square pixels
     */
    protected long getAreaOfWidget(Element e) {
        return e.getOffsetHeight() * e.getOffsetWidth();
    }

    /**
     * If the configuration of the drag defines a bounding box (a specific area which can not be left by the dragged widget), this method checks if the widget
     * will be outside of the box by the application of the given event
     * 
     * @param event
     *            to be applied
     * @return if the widget is outside of the defined bounding box
     */
    protected boolean outOfBox(Event event) {
        return getX(event) < conf.getMinX() || getY(event) < conf.getMinY() || getX(event) > conf.getMaxX()
                || getY(event) > conf.getMaxY();
    }

    /**
     * Repositions the dragged widget to a specific x and y position taking into account the offset between the original widget coordinates and the mouse
     * pointer position at the begin of the drag. The offset is kept in percentages to make the offset calculation independent of the drag-proxy size.
     * 
     * @param x
     * @param y
     * @param withOffset
     */
    protected void setPosition(int x, int y, boolean withOffset) {
        Element dragProxy = conf.getDragProxy();
        int newX = x - (withOffset ? (int) (dragProxy.getOffsetWidth() / 100.0 * percOffsetX) : 0);
        int newY = y - (withOffset ? (int) (dragProxy.getOffsetHeight() / 100.0 * percOffsetY) : 0);
        dragProxy.getStyle().setLeft(
                Math.max(conf.getMinX(), Math.min(newX, conf.getMaxX() - dragProxy.getOffsetWidth())), Unit.PX);
        dragProxy.getStyle().setTop(
                Math.max(conf.getMinY(), Math.min(newY, conf.getMaxY() - dragProxy.getOffsetHeight())), Unit.PX);
    }

    /**
     * Resets the position of the dragged widget to its original coordinates and removes the dragging styles
     */
    protected void resetPosition() {
        if (conf.isWithProxy()) {
            conf.getDragProxy().removeFromParent();
            conf.setDragProxy(null);
        } else {
            w.getElement().getStyle().setProperty("display", originDisplay);
        }
        for (Widget w2 : dropTargetRegistry.keySet()) {
            String idStyle = w2.getElement().getId() != null && !w2.getElement().getId().equals("")
                    ? "hover-" + w2.getElement().getId()
                    : null;
            if (idStyle != null) {
                w.removeStyleName(idStyle);
            }
        }
    }

    /**
     * Adds the event preview listening for mouse events.
     */
    protected void addEventPreview() {
        handlerReg = Event.addNativePreviewHandler(nativePreviewHandler);
        GWT.log("ADD EVENT PREVIEW");
    }

    /**
     * Removes the event preview listening for mouse events
     */
    protected void removeEventPreview() {
        if (handlerReg != null) {
            handlerReg.removeHandler();
            GWT.log("REMOVE EVENT PREVIEW");
        } else {
            GWT.log("NO REMOVE EVENT PREVIEW");
        }
    }

    /**
     * Handles the different mouse events by type
     * 
     * @param deviceId
     * @param event
     */
    protected void onMouseEvent(String deviceId, Event event) {
        switch (DOM.eventGetType(event)) {
        case Event.ONMOUSEMOVE:
            onDrag(deviceId, event);
            break;
        case Event.ONMOUSEUP:
            endDrag(deviceId, event);
            break;
        }
    }

    /**
     * Returns the drop target that has the highest priority and that has the biggest intersection area with the given element.
     * 
     * @param e
     * @return a tuple of the drop target widget and the intersecting area or null if the element does not intersect with another element
     */
    protected Tuple<Widget, Long> getHoverDropTarget(Element e) {
        Set<Widget> affected = getAffectedDropTargets(e);
        if (affected == null || affected.isEmpty())
            return null;
        Widget max = null;
        long maxArea = 0;
        for (Widget target : affected) {
            boolean widgetIsLeftOfTarget = target.getAbsoluteLeft() - e.getAbsoluteLeft() > 0;
            boolean widgetIsTopOfTarget = target.getAbsoluteTop() - e.getAbsoluteTop() > 0;
            long collision = getCollisionArea(widgetIsLeftOfTarget ? e : target.getElement(),
                    widgetIsLeftOfTarget ? target.getElement() : e, widgetIsTopOfTarget ? e : target.getElement(),
                    widgetIsTopOfTarget ? target.getElement() : e);
            if (collision > maxArea || max == null) {
                max = target;
                maxArea = collision;
            }
        }
        return new Tuple<Widget, Long>(max, maxArea);
    }

    /**
     * This method takes two different elements which are passed multiple times depending on their relative position and calculates the collision area in square
     * pixels.
     * 
     * @param left
     *            - the element which is further left than the other
     * @param right
     *            - the element which is further right than the other
     * @param top
     *            - the element which is further top than the other
     * @param bottom
     *            - the element which is further bottom than the other
     * @return the collision area between the elements in square pixels
     */
    protected long getCollisionArea(Element left, Element right, Element top, Element bottom) {
        int collX = Math.min(left.getAbsoluteLeft() + left.getOffsetWidth(),
                right.getAbsoluteLeft() + right.getOffsetWidth()) - right.getAbsoluteLeft();
        int collY = Math.min(top.getAbsoluteTop() + top.getOffsetHeight(),
                bottom.getAbsoluteTop() + bottom.getOffsetHeight()) - bottom.getAbsoluteTop();
        return collX * collY;
    }

    /**
     * This method looks up all the drop targets which are intersecting with the given element. If the drop targets differ in their priorities ({@link Priority}
     * ), only widgets of the highest priority are returned.
     * 
     * @param e
     *            - a HTML element (typically the HTML element of the dragged widget)
     * @return the drop target widgets which are intersecting with the given element and do have the highest priority
     */
    protected Set<Widget> getAffectedDropTargets(Element e) {
        int w1X = e.getAbsoluteLeft();
        int w1Y = e.getAbsoluteTop();
        int w1Width = e.getOffsetWidth();
        int w1Height = e.getOffsetHeight();
        Map<Integer, HashSet<Widget>> targets = new HashMap<Integer, HashSet<Widget>>();
        for (Widget w2 : dropTargetRegistry.keySet()) {
            int w2X = w2.getAbsoluteLeft();
            int w2Y = w2.getAbsoluteTop();
            boolean xCollision = w1X < w2X ? w2X - w1X < w1Width : w1X - w2X < w2.getOffsetWidth();
            boolean yCollision = w1Y < w2Y ? w2Y - w1Y < w1Height : w1Y - w2Y < w2.getOffsetHeight();
            String idStyle = w2.getElement().getId() != null && !w2.getElement().getId().equals("")
                    ? "hover-" + w2.getElement().getId()
                    : null;
            if (xCollision && yCollision) {
                if (idStyle != null) {
                    e.addClassName(idStyle);
                }
                DropTargetHandler h = dropTargetRegistry.get(w2);
                if (h != null) {
                    int prio = h.getPriority().getValue();
                    HashSet<Widget> widgetsForPrio = targets.get(prio);
                    if (widgetsForPrio == null) {
                        widgetsForPrio = new HashSet<Widget>();
                        targets.put(prio, widgetsForPrio);
                    }
                    widgetsForPrio.add(w2);
                }
            } else if (idStyle != null) {
                e.removeClassName(idStyle);
            }
        }
        if (targets.isEmpty())
            return null;
        int maxprio = 0;
        for (Integer i : targets.keySet()) {
            if (i > maxprio) {
                maxprio = i;
            }
        }
        return targets.get(maxprio);
    }

    /*
     * (non-Javadoc)
     * @see ch.unifr.pai.twice.dragndrop.client.DragNDrop.DragNDropIntf#setDropHandler(com.google.gwt.event.dom.client.HasMouseOverHandlers,
     * ch.unifr.pai.twice.dragndrop.client.intf.DropTargetHandler, boolean)
     */
    @Override
    public void setDropHandler(HasMouseOverHandlers w, DropTargetHandler dropHandler, boolean hoverAware) {
        if (w instanceof Widget && w instanceof HasMouseOutHandlers) {
            if (hoverAware) {
                hoverDropTargets.put(dropHandler, new HashSet<String>());
            }
            dropTargetRegistry.put((Widget) w, dropHandler);
        }
    }

    /*
     * (non-Javadoc)
     * @see ch.unifr.pai.twice.dragndrop.client.DragNDrop.DragNDropIntf#removeDropHandler(com.google.gwt.event.dom.client.HasMouseOverHandlers)
     */
    @Override
    public void removeDropHandler(HasMouseOverHandlers w) {
        dropTargetRegistry.remove(w);
    }
}