]> source.dussan.org Git - vaadin-framework.git/commitdiff
dd
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 29 Jan 2010 08:12:00 +0000 (08:12 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 29 Jan 2010 08:12:00 +0000 (08:12 +0000)
svn changeset:11054/svn branch:6.3_dd

29 files changed:
src/com/vaadin/event/AbstractDropHandler.java [new file with mode: 0644]
src/com/vaadin/event/ComponentTransferrable.java [new file with mode: 0644]
src/com/vaadin/event/DataBindedTransferrable.java [new file with mode: 0644]
src/com/vaadin/event/DragRequest.java [new file with mode: 0644]
src/com/vaadin/event/DropHandler.java [new file with mode: 0644]
src/com/vaadin/event/HasDropHandler.java [new file with mode: 0644]
src/com/vaadin/event/Transferable.java [new file with mode: 0644]
src/com/vaadin/terminal/TransferTranslator.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/VDragDropPane.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/AbstractDropHandler.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCallback.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriteria.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterionImpl.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/DragAndDropManager.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/DragEvent.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/DropHandler.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/HasDropHandler.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/dd/Transferable.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/DragAndDropService.java [new file with mode: 0644]
src/com/vaadin/ui/DragDropPane.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/AcceptAnythingWindow.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/AcceptFromComponent.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/CustomDDImplementation.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/DDTest1.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/DDTest2.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/VMyDragSource.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/VMyDropTarget.java [new file with mode: 0644]

diff --git a/src/com/vaadin/event/AbstractDropHandler.java b/src/com/vaadin/event/AbstractDropHandler.java
new file mode 100644 (file)
index 0000000..da15dc6
--- /dev/null
@@ -0,0 +1,246 @@
+package com.vaadin.event;
+
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+import com.vaadin.ui.Component;
+
+/**
+ * An implementation of DropHandler interface.
+ * 
+ * AcceptCriterion may be used to configure accept rules. Using them can result
+ * client side verifiable accept rules for quick feedback in UI. Still rules are
+ * also validate on server so implementor don't need to double check validity on
+ * {@link #receive(Transferable)} method.
+ * 
+ */
+public abstract class AbstractDropHandler implements DropHandler {
+    /**
+     * Criterion that can be used create policy to accept/discard dragged
+     * content (presented by {@link Transferable}).
+     * 
+     * TODO figure out how this can be done partly on client at least in some
+     * cases. isClientSideFilterable() tms.
+     * 
+     */
+    public interface AcceptCriterion {
+        public boolean accepts(Transferable transferable);
+    }
+
+    public interface ClientSideVerifiable extends AcceptCriterion {
+
+        /**
+         * May depend on state, like in OR or AND, so to be really
+         * ClientSideVerifiable needs to return true here (instead of just
+         * implementing marker interface).
+         */
+        public boolean isClientSideVerifiable();
+
+        public void paint(PaintTarget target) throws PaintException;
+
+    }
+
+    private static final class AcceptAll implements ClientSideVerifiable {
+        public boolean accepts(Transferable transferable) {
+            return true;
+        }
+
+        public boolean isClientSideVerifiable() {
+            return true;
+        }
+
+        public void paint(PaintTarget target) throws PaintException {
+            target.startTag("acceptCriterion");
+            target.addAttribute("name", "acceptAll");
+            target.endTag("acceptCriterion");
+        }
+    }
+
+    public static class And implements ClientSideVerifiable {
+        private AcceptCriterion f1;
+        private AcceptCriterion f2;
+
+        public And(AcceptCriterion f1, AcceptCriterion f2) {
+            this.f1 = f1;
+            this.f2 = f2;
+        }
+
+        public boolean accepts(Transferable transferable) {
+            return f1.accepts(transferable) && f2.accepts(transferable);
+        }
+
+        public boolean isClientSideVerifiable() {
+            boolean a1 = f1 instanceof ClientSideVerifiable ? ((ClientSideVerifiable) f1)
+                    .isClientSideVerifiable()
+                    : false;
+            boolean a2 = f2 instanceof ClientSideVerifiable ? ((ClientSideVerifiable) f2)
+                    .isClientSideVerifiable()
+                    : false;
+            return a1 && a2;
+        }
+
+        public void paint(PaintTarget target) throws PaintException {
+            target.startTag("acceptCriterion");
+            target.addAttribute("name", "and");
+            ((ClientSideVerifiable) f1).paint(target);
+            ((ClientSideVerifiable) f2).paint(target);
+            target.endTag("acceptCriterion");
+        }
+    }
+
+    public static class ComponentFilter implements ClientSideVerifiable {
+        private Component component;
+
+        public ComponentFilter(Component component) {
+            this.component = component;
+        }
+
+        public boolean accepts(Transferable transferable) {
+            if (transferable instanceof ComponentTransferrable) {
+                return ((ComponentTransferrable) transferable)
+                        .getSourceComponent() == component;
+            } else {
+                return false;
+            }
+        }
+
+        public boolean isClientSideVerifiable() {
+            return true;
+        }
+
+        public void paint(PaintTarget target) throws PaintException {
+            target.startTag("acceptCriterion");
+            target.addAttribute("name", "component");
+            target.addAttribute("component", component);
+            target.endTag("acceptCriterion");
+        }
+    }
+
+    private static final class IsDataBinded implements ClientSideVerifiable {
+        public boolean accepts(Transferable transferable) {
+            if (transferable instanceof DataBindedTransferrable) {
+                return ((DataBindedTransferrable) transferable).getItemId() != null;
+            }
+            return false;
+        }
+
+        public boolean isClientSideVerifiable() {
+            return true;
+        }
+
+        public void paint(PaintTarget target) throws PaintException {
+            target.startTag("acceptCriterion");
+            target.addAttribute("name", "needsItemId");
+            target.endTag("acceptCriterion");
+        }
+    }
+
+    public class Not implements AcceptCriterion {
+        private AcceptCriterion acceptCriterion;
+
+        public Not(AcceptCriterion acceptCriterion) {
+            this.acceptCriterion = acceptCriterion;
+        }
+
+        public boolean accepts(Transferable transferable) {
+            return !acceptCriterion.accepts(transferable);
+        }
+
+    }
+
+    public class Or implements AcceptCriterion {
+        private AcceptCriterion f1;
+        private AcceptCriterion f2;
+
+        Or(AcceptCriterion f1, AcceptCriterion f2) {
+            this.f1 = f1;
+            this.f2 = f2;
+        }
+
+        public boolean accepts(Transferable transferable) {
+            return f1.accepts(transferable) || f2.accepts(transferable);
+        }
+    }
+
+    public static class OverTreeNode implements ClientSideVerifiable {
+
+        public boolean accepts(Transferable transferable) {
+            try {
+                return transferable.getData("detail").toString().toLowerCase()
+                        .equals("center");
+            } catch (Exception e) {
+                return false;
+            }
+        }
+
+        public boolean isClientSideVerifiable() {
+            return true;
+        }
+
+        public void paint(PaintTarget target) throws PaintException {
+            target.startTag("acceptCriterion");
+            target.addAttribute("name", "overTreeNode");
+            target.endTag("acceptCriterion");
+        }
+
+    }
+
+    public static final AcceptCriterion CRITERION_ACCEPT_ALL = new AcceptAll();
+
+    public static final AcceptCriterion CRITERION_HAS_ITEM_ID = new IsDataBinded();
+
+    private AcceptCriterion acceptCriterion = CRITERION_ACCEPT_ALL;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @seecom.vaadin.event.DropHandler#acceptTransferrable(com.vaadin.event.
+     * Transferable)
+     */
+    public boolean acceptTransferrable(Transferable transferable) {
+        return acceptCriterion.accepts(transferable);
+    }
+
+    private boolean clientSideVerifiable() {
+        if (acceptCriterion instanceof ClientSideVerifiable) {
+            return ((ClientSideVerifiable) acceptCriterion)
+                    .isClientSideVerifiable();
+        }
+        return false;
+    }
+
+    public void handleDragRequest(DragRequest event) {
+        boolean acceptTransferrable = acceptTransferrable(event
+                .getTransferrable());
+        if (acceptTransferrable) {
+            if (event.getType() == DragEventType.DROP) {
+                receive(event.getTransferrable());
+            } else {
+                event.setResponseParameter("accepted", true);
+            }
+
+        }
+    }
+
+    public void paint(PaintTarget target) throws PaintException {
+        target.startTag("dh");
+        if (!clientSideVerifiable()) {
+            target.addAttribute("serverValidate", true);
+        } else {
+            ((ClientSideVerifiable) acceptCriterion).paint(target);
+        }
+        target.endTag("dh");
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.event.DropHandler#receive(com.vaadin.event.Transferable)
+     */
+    public abstract void receive(Transferable transferable);
+
+    public void setAcceptCriterion(AcceptCriterion acceptCriterion) {
+        this.acceptCriterion = acceptCriterion;
+    }
+
+}
diff --git a/src/com/vaadin/event/ComponentTransferrable.java b/src/com/vaadin/event/ComponentTransferrable.java
new file mode 100644 (file)
index 0000000..e0048d9
--- /dev/null
@@ -0,0 +1,12 @@
+package com.vaadin.event;
+
+import com.vaadin.ui.Component;
+
+public interface ComponentTransferrable extends Transferable {
+
+    /**
+     * @return the component where the drag operation started
+     */
+    public Component getSourceComponent();
+
+}
diff --git a/src/com/vaadin/event/DataBindedTransferrable.java b/src/com/vaadin/event/DataBindedTransferrable.java
new file mode 100644 (file)
index 0000000..8d131eb
--- /dev/null
@@ -0,0 +1,9 @@
+package com.vaadin.event;
+
+public interface DataBindedTransferrable extends ComponentTransferrable {
+
+    public Object getItemId();
+
+    public Object getPropertyId();
+
+}
diff --git a/src/com/vaadin/event/DragRequest.java b/src/com/vaadin/event/DragRequest.java
new file mode 100644 (file)
index 0000000..b1bd2a6
--- /dev/null
@@ -0,0 +1,46 @@
+package com.vaadin.event;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+
+public class DragRequest {
+
+    private DragEventType dragEventType;
+    private Transferable transferable;
+    private Map<String, Object> responseData;
+
+    public DragRequest(DragEventType dragEventType, Transferable transferable) {
+        this.dragEventType = dragEventType;
+        this.transferable = transferable;
+    }
+
+    public Transferable getTransferrable() {
+        return transferable;
+    }
+
+    public DragEventType getType() {
+        return dragEventType;
+    }
+
+    public Map<String, Object> getResponseData() {
+        return responseData;
+    }
+
+    /**
+     * DropHanler can pass simple parameters back to client side.
+     * 
+     * TODO define which types are supported (most likely the same as in UIDL)
+     * 
+     * @param key
+     * @param value
+     */
+    public void setResponseParameter(String key, Object value) {
+        if (responseData == null) {
+            responseData = new HashMap<String, Object>();
+        }
+        responseData.put(key, value);
+    }
+
+}
diff --git a/src/com/vaadin/event/DropHandler.java b/src/com/vaadin/event/DropHandler.java
new file mode 100644 (file)
index 0000000..f44ca15
--- /dev/null
@@ -0,0 +1,7 @@
+package com.vaadin.event;
+
+public interface DropHandler {
+
+    public void handleDragRequest(DragRequest event);
+
+}
\ No newline at end of file
diff --git a/src/com/vaadin/event/HasDropHandler.java b/src/com/vaadin/event/HasDropHandler.java
new file mode 100644 (file)
index 0000000..73ce74c
--- /dev/null
@@ -0,0 +1,13 @@
+package com.vaadin.event;
+
+import com.vaadin.ui.Component;
+
+/**
+ * Implementing component most commonly has also setDropHandler method, but
+ * not polluting interface here as component might also have internal
+ * AbstractDropHandler implementation.
+ * 
+ */
+public interface HasDropHandler extends Component {
+    public DropHandler getDropHandler();
+}
\ No newline at end of file
diff --git a/src/com/vaadin/event/Transferable.java b/src/com/vaadin/event/Transferable.java
new file mode 100644 (file)
index 0000000..5ce4adf
--- /dev/null
@@ -0,0 +1,13 @@
+package com.vaadin.event;
+
+import java.util.Collection;
+
+public interface Transferable {
+
+    public Object getData(String dataFlawor);
+
+    public void setData(String dataFlawor, Object value);
+
+    public Collection<String> getDataFlawors();
+
+}
diff --git a/src/com/vaadin/terminal/TransferTranslator.java b/src/com/vaadin/terminal/TransferTranslator.java
new file mode 100644 (file)
index 0000000..01d56a7
--- /dev/null
@@ -0,0 +1,28 @@
+package com.vaadin.terminal;
+
+import java.util.Map;
+
+import com.vaadin.event.Transferable;
+
+public interface TransferTranslator {
+
+    /**
+     * Translate translators may convert client side variables to meaningful
+     * values on server side. For example in Selects we convert item identifiers
+     * to generated string keys for the client side. Translators in Selects
+     * should convert them back to item identifiers.
+     * <p>
+     * Translator should remove variables it handled from rawVariables. All non
+     * handled variables are added to Transferable automatically by terminal.
+     * 
+     * @param transferable
+     *            the Transferable object if one has been created for this drag
+     *            and drop operation, null if Transferable is not yet
+     *            instantiated
+     * @param rawVariables
+     * @return
+     */
+    public Transferable getTransferrable(Transferable transferable,
+            Map<String, Object> rawVariables, boolean isDropTarget);
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragDropPane.java b/src/com/vaadin/terminal/gwt/client/ui/VDragDropPane.java
new file mode 100644 (file)
index 0000000..6bdfa7a
--- /dev/null
@@ -0,0 +1,243 @@
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Container;
+import com.vaadin.terminal.gwt.client.MouseEventDetails;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.ui.dd.AbstractDropHandler;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager;
+import com.vaadin.terminal.gwt.client.ui.dd.DragEvent;
+import com.vaadin.terminal.gwt.client.ui.dd.HasDropHandler;
+import com.vaadin.terminal.gwt.client.ui.dd.Html5DragEvent;
+import com.vaadin.terminal.gwt.client.ui.dd.Transferable;
+
+public class VDragDropPane extends VAbsoluteLayout implements Container,
+        HasDropHandler {
+
+    private String paintableId;
+
+    /**
+     * DragEvent is stored here in case of HTML5 drag event.
+     */
+    private DragEvent vaadinDragEvent;
+
+    public VDragDropPane() {
+        super();
+        addDomHandler(new MouseDownHandler() {
+            public void onMouseDown(MouseDownEvent event) {
+                EventTarget eventTarget = event.getNativeEvent()
+                        .getEventTarget();
+                Paintable paintable = client.getPaintable((Element) eventTarget
+                        .cast());
+                Transferable transferable = new Transferable();
+                transferable.setComponent(paintable);
+                DragEvent drag = DragAndDropManager.get().startDrag(
+                        transferable, event.getNativeEvent(), true);
+                Element cloneNode = (Element) ((Widget) paintable).getElement()
+                        .cloneNode(true);
+                cloneNode.getStyle().setBackgroundColor("#999");
+                cloneNode.getStyle().setOpacity(0.4);
+                drag.setDragImage(cloneNode);
+                drag.getTransferrable().setData(
+                        "mouseDown",
+                        new MouseEventDetails(event.getNativeEvent())
+                                .serialize());
+                event.preventDefault(); // prevent text selection
+            }
+        }, MouseDownEvent.getType());
+
+        hookHtml5Events(getElement());
+        getStyleElement().getStyle().setBackgroundColor("yellow");
+
+    }
+
+    /**
+     * Prototype code, memory leak risk.
+     * 
+     * @param el
+     */
+    private native void hookHtml5Events(Element el)
+    /*-{
+
+        var me = this;
+         
+        el.addEventListener("dragenter",  function(ev) {
+            return me.@com.vaadin.terminal.gwt.client.ui.VDragDropPane::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent;)(ev);
+        }, false);
+        
+        el.addEventListener("dragleave",  function(ev) {
+            return me.@com.vaadin.terminal.gwt.client.ui.VDragDropPane::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent;)(ev);
+        }, false);
+
+        el.addEventListener("dragover",  function(ev) {
+            return me.@com.vaadin.terminal.gwt.client.ui.VDragDropPane::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent;)(ev);
+        }, false);
+
+        el.addEventListener("drop",  function(ev) {
+            return me.@com.vaadin.terminal.gwt.client.ui.VDragDropPane::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent;)(ev);
+        }, false);
+        
+    }-*/;
+
+    public boolean html5DragEnter(Html5DragEvent event) {
+        ApplicationConnection.getConsole().log("HTML 5 Drag Enter");
+        Transferable transferable = new Transferable();
+
+        // TODO refine api somehow so that we will now not use the event preview
+        // method provided by manager
+        vaadinDragEvent = DragAndDropManager.get().startDrag(transferable,
+                event, false);
+        event.preventDefault();
+        event.stopPropagation();
+        return false;
+    }
+
+    public boolean html5DragLeave(Html5DragEvent event) {
+        ApplicationConnection.getConsole().log("HTML 5 Drag Leave posponed...");
+        DeferredCommand.addCommand(new Command() {
+            public void execute() {
+                // Yes, dragleave happens before drop. Makes no sense to me.
+                // IMO shouldn't fire leave at all if drop happens (I guess this
+                // is what IE does).
+                // In Vaadin we fire it only if drop did not happen.
+                if (vaadinDragEvent != null) {
+                    ApplicationConnection.getConsole().log(
+                            "...HTML 5 Drag Leave");
+                    getDropHandler().dragLeave(vaadinDragEvent);
+                }
+            }
+        });
+        event.preventDefault();
+        event.stopPropagation();
+        return false;
+    }
+
+    public boolean html5DragOver(Html5DragEvent event) {
+        ApplicationConnection.getConsole().log("HTML 5 Drag Over");
+        getDropHandler().dragOver(vaadinDragEvent);
+        // needed to be set for Safari, otherwise drop will not happen
+        String s = event.getEffectAllowed();
+        if ("all".equals(s) || s.contains("opy")) {
+            event.setDragEffect("copy");
+        } else {
+            event.setDragEffect(s);
+            ApplicationConnection.getConsole().log("Drag effect set to " + s);
+        }
+        event.preventDefault();
+        event.stopPropagation();
+        return false;
+    }
+
+    public boolean html5DragDrop(Html5DragEvent event) {
+        ApplicationConnection.getConsole().log("HTML 5 Drag Drop");
+        Transferable transferable = vaadinDragEvent.getTransferrable();
+
+        JsArrayString types = event.getTypes();
+        for (int i = 0; i < types.length(); i++) {
+            String type = types.get(i);
+            ApplicationConnection.getConsole().log("Type: " + type);
+            if ("text/plain".equals(type)) {
+                String data = event.getDataAsText(type);
+                ApplicationConnection.getConsole().log(type + " : " + data);
+                transferable.setData("text/plain", data);
+            }
+        }
+
+        String fileAsString = event.getFileAsString(0);
+        if (fileAsString != null) {
+            ApplicationConnection.getConsole().log(fileAsString);
+            transferable.setData("fileContents", fileAsString);
+        }
+
+        DragAndDropManager.get().endDrag();
+        vaadinDragEvent = null;
+        event.preventDefault();
+        event.stopPropagation();
+
+        return false;
+    }
+
+    @Override
+    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+        super.updateFromUIDL(uidl, client);
+        if (!uidl.hasAttribute("cached")) {
+            int childCount = uidl.getChildCount();
+            UIDL childUIDL = uidl.getChildUIDL(childCount - 1);
+            getDropHandler().updateRules(childUIDL);
+        }
+    }
+
+    private AbstractDropHandler dropHandler;
+
+    public AbstractDropHandler getDropHandler() {
+        if (dropHandler == null) {
+            dropHandler = new AbstractDropHandler() {
+
+                @Override
+                public Paintable getPaintable() {
+                    return VDragDropPane.this;
+                }
+
+                @Override
+                public void dragLeave(DragEvent drag) {
+                    ApplicationConnection.getConsole().log("DragLeave");
+                    getStyleElement().getStyle().setBackgroundColor("yellow");
+                }
+
+                @Override
+                public boolean drop(DragEvent drag) {
+                    ApplicationConnection.getConsole().log(
+                            "Drop" + drag.sinceStart());
+
+                    if (getStyleElement().getStyle().getBackgroundColor()
+                            .equals("yellow")) {
+                        // not accepted
+                        ApplicationConnection.getConsole().log(
+                                "Drop was not accepted");
+                        return false;
+                    }
+
+                    Transferable transferable = drag.getTransferrable();
+
+                    // this is absolute layout based, and we may want to set
+                    // component
+                    // relatively to where the drag ended.
+                    // need to add current location of the drop area
+
+                    int absoluteLeft = getAbsoluteLeft();
+                    int absoluteTop = getAbsoluteTop();
+
+                    transferable.setData("absoluteLeft", absoluteLeft);
+                    transferable.setData("absoluteTop", absoluteTop);
+
+                    getStyleElement().getStyle().setBackgroundColor("yellow");
+                    return super.drop(drag);
+                }
+
+                @Override
+                protected void dragAccepted(DragEvent drag) {
+                    getStyleElement().getStyle().setBackgroundColor("green");
+                }
+
+                public ApplicationConnection getApplicationConnection() {
+                    return client;
+                }
+            };
+        }
+        return dropHandler;
+    }
+
+    public Paintable getPaintable() {
+        return this;
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AbstractDropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AbstractDropHandler.java
new file mode 100644 (file)
index 0000000..db65682
--- /dev/null
@@ -0,0 +1,97 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.ValueMap;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+
+public abstract class AbstractDropHandler implements DropHandler {
+
+    private boolean serverValidate;
+    private UIDL criterioUIDL;
+
+    public void updateRules(UIDL uidl) {
+        serverValidate = uidl.getBooleanAttribute("serverValidate");
+        int childCount = uidl.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            UIDL childUIDL = uidl.getChildUIDL(i);
+            if (childUIDL.getTag().equals("acceptCriterion")) {
+                criterioUIDL = childUIDL;
+                // TODO consider parsing the criteria tree here instead of
+                // translating uidl during validates()
+
+            }
+        }
+    }
+
+    public boolean validateOnServer() {
+        return serverValidate;
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public void dragOver(DragEvent currentDrag) {
+
+    }
+
+    /**
+     * Default implementation does nothing.
+     */
+    public void dragLeave(DragEvent drag) {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * If transferrable is accepted (either via server visit or client side
+     * rules) the default implementation calls {@link #dragAccepted(DragEvent)}
+     * method.
+     */
+    public void dragEnter(final DragEvent drag) {
+        if (serverValidate) {
+            DragAndDropManager.get().visitServer(DragEventType.ENTER,
+                    new AcceptCallback() {
+                        public void handleResponse(ValueMap responseData) {
+                            if (responseData.containsKey("accepted")) {
+                                dragAccepted(drag);
+                            }
+                        }
+                    });
+        } else if (validates(drag.getTransferrable())) {
+            dragAccepted(drag);
+        }
+    }
+
+    abstract protected void dragAccepted(DragEvent drag);
+
+    /**
+     * Returns true if client side rules are met.
+     * 
+     * @param transferable
+     * @return
+     */
+    protected boolean validates(Transferable transferable) {
+        if (criterioUIDL != null) {
+            String criteriaName = criterioUIDL.getStringAttribute("name");
+            AcceptCriteria acceptCriteria = AcceptCriterion.get(criteriaName);
+            if (acceptCriteria != null) {
+                // ApplicationConnection.getConsole().log(
+                // "Criteria : " + acceptCriteria.getClass().getName());
+                return acceptCriteria.accept(transferable, criterioUIDL);
+            }
+        }
+        return false;
+    }
+
+    public boolean drop(DragEvent drag) {
+        if (serverValidate) {
+            return true;
+        } else {
+            return validates(drag.getTransferrable());
+        }
+    }
+
+    public abstract Paintable getPaintable();
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCallback.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCallback.java
new file mode 100644 (file)
index 0000000..913cda5
--- /dev/null
@@ -0,0 +1,19 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.vaadin.terminal.gwt.client.ValueMap;
+
+public interface AcceptCallback {
+
+    /**
+     * This method is called by {@link DragAndDropManager} if the
+     * {@link DragEvent} is still active. Developer can update for example drag
+     * icon or target emphasis based on the information returned from server
+     * side. If the drag and drop operation ends or the
+     * {@link AbstractDropHandler} has changed before response arrives, the
+     * method is never called.
+     * 
+     * @param responseData
+     */
+    public void handleResponse(ValueMap responseData);
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriteria.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriteria.java
new file mode 100644 (file)
index 0000000..34f9f6d
--- /dev/null
@@ -0,0 +1,9 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.vaadin.terminal.gwt.client.UIDL;
+
+public interface AcceptCriteria {
+
+    public boolean accept(Transferable transferable, UIDL configuration);
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java
new file mode 100644 (file)
index 0000000..db4cb20
--- /dev/null
@@ -0,0 +1,21 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.core.client.GWT;
+
+public class AcceptCriterion {
+    protected static Map<String, AcceptCriteria> criterion = new HashMap<String, AcceptCriteria>();
+    private static AcceptCriterionImpl impl;
+
+    static {
+        impl = GWT.create(AcceptCriterionImpl.class);
+        impl.populateCriterionMap(criterion);
+    }
+
+    public static AcceptCriteria get(String name) {
+        return criterion.get(name);
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterionImpl.java b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterionImpl.java
new file mode 100644 (file)
index 0000000..5444cfb
--- /dev/null
@@ -0,0 +1,97 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import java.util.Map;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.UIDL;
+
+public class AcceptCriterionImpl {
+
+    private final class OverTreeNode implements AcceptCriteria {
+        public boolean accept(Transferable transferable, UIDL configuration) {
+            Boolean containsKey = (Boolean) transferable
+                    .getData("itemIdOverIsNode");
+            if (containsKey != null && containsKey.booleanValue()) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private final class ComponentCriteria implements AcceptCriteria {
+        public boolean accept(Transferable transferable, UIDL configuration) {
+            try {
+                // FIXME should have access to client too, change transferrable
+                // to DragEvent??
+                Paintable component = transferable.getComponent();
+                String requiredPid = configuration
+                        .getStringAttribute("component");
+                String pid = ((Widget) component).getElement()
+                        .getPropertyString("tkPid");
+                return pid.equals(requiredPid);
+            } catch (Exception e) {
+            }
+            return false;
+        }
+    }
+
+    private final class And implements AcceptCriteria {
+        public boolean accept(Transferable transferable, UIDL configuration) {
+            UIDL childUIDL = configuration.getChildUIDL(0);
+            UIDL childUIDL2 = configuration.getChildUIDL(1);
+            AcceptCriteria acceptCriteria = AcceptCriterion.get(childUIDL
+                    .getStringAttribute("name"));
+            AcceptCriteria acceptCriteria2 = AcceptCriterion.get(childUIDL2
+                    .getStringAttribute("name"));
+            if (acceptCriteria == null || acceptCriteria2 == null) {
+                ApplicationConnection.getConsole().log(
+                        "And criteria didn't found a chidl criteria");
+                return false;
+            }
+            boolean accept = acceptCriteria.accept(transferable, childUIDL);
+            boolean accept2 = acceptCriteria2.accept(transferable, childUIDL2);
+            return accept && accept2;
+        }
+    }
+
+    private final class AcceptAll implements AcceptCriteria {
+        public boolean accept(Transferable transferable, UIDL configuration) {
+            return true;
+        }
+    }
+
+    private final class HasItemId implements AcceptCriteria {
+        public boolean accept(Transferable transferable, UIDL configuration) {
+            return transferable.getItemId() != null;
+        }
+    }
+
+    /**
+     * TODO this method could be written by generator
+     * 
+     * TODO consider moving implementations to top level classes
+     * 
+     * TODO use fully qualified names of server side counterparts as keys
+     */
+    public void populateCriterionMap(Map<String, AcceptCriteria> map) {
+        AcceptCriteria crit;
+
+        crit = new HasItemId();
+        map.put("needsItemId", crit);
+
+        crit = new AcceptAll();
+        map.put("acceptAll", crit);
+
+        crit = new And();
+        map.put("and", crit);
+
+        crit = new OverTreeNode();
+        map.put("overTreeNode", crit);
+
+        crit = new ComponentCriteria();
+        map.put("component", crit);
+
+    }
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/DragAndDropManager.java b/src/com/vaadin/terminal/gwt/client/ui/dd/DragAndDropManager.java
new file mode 100644 (file)
index 0000000..8dc17e0
--- /dev/null
@@ -0,0 +1,391 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+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.MouseEventDetails;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.ValueMap;
+
+/**
+ * Helper class to manage the state of drag and drop event on Vaadin client
+ * side. Can be used to implement most of the drag and drop operation
+ * automatically via cross-browser event preview method or just as a helper when
+ * implementing own low level drag and drop operation (like with HTML5 api).
+ * <p>
+ * Singleton. Only one drag and drop operation can be active anyways. Use
+ * {@link #get()} to get instance.
+ * 
+ */
+public class DragAndDropManager {
+
+    public enum DragEventType {
+        ENTER, LEAVE, OVER, DROP
+    }
+
+    private static final String DD_SERVICE = "DD";
+
+    private static DragAndDropManager instance;
+    private HandlerRegistration handlerRegistration;
+    private DragEvent currentDrag;
+
+    /**
+     * If dragging is currently on a drophandler, this field has reference to it
+     */
+    private DropHandler currentDropHandler;
+
+    public DropHandler getCurrentDropHandler() {
+        return currentDropHandler;
+    }
+
+    /**
+     * If drag and drop operation is not handled by {@link DragAndDropManager}s
+     * internal handler, this can be used to update current {@link DropHandler}.
+     * 
+     * @param currentDropHandler
+     */
+    public void setCurrentDropHandler(DropHandler currentDropHandler) {
+        this.currentDropHandler = currentDropHandler;
+    }
+
+    private AcceptCallback acceptCallback;
+
+    public static DragAndDropManager get() {
+        if (instance == null) {
+            instance = new DragAndDropManager();
+        }
+        return instance;
+    }
+
+    /* Singleton */
+    private DragAndDropManager() {
+    }
+
+    /**
+     * This method is used to start Vaadin client side drag and drop operation.
+     * Operation may be started by virtually any Widget.
+     * <p>
+     * Cancels possible existing drag. TODO figure out if this is always a bug
+     * if one is active. Maybe a good and cheap lifesaver thought.
+     * <p>
+     * If possible, method automatically detects current {@link DropHandler} and
+     * fires {@link DropHandler#dragEnter(DragEvent)} event on it.
+     * <p>
+     * May also be used to control the drag and drop operation. If this option
+     * is used, {@link DropHandler} is searched on mouse events and appropriate
+     * methods on it called automatically.
+     * 
+     * @param transferable
+     * @param nativeEvent
+     * @param handleDragEvents
+     *            if true, {@link DragAndDropManager} handles the drag and drop
+     *            operation GWT event preview.
+     * @return
+     */
+    public DragEvent startDrag(Transferable transferable,
+            NativeEvent startEvent, boolean handleDragEvents) {
+        interruptDrag();
+
+        currentDrag = new DragEvent(transferable, startEvent);
+        DropHandler dh = null;
+        if (startEvent != null) {
+            dh = findDragTarget((Element) startEvent.getEventTarget().cast());
+        }
+        if (dh != null) {
+            // drag has started on a DropHandler, kind of drag over happens
+            currentDropHandler = dh;
+            updateCurrentEvent(startEvent);
+            dh.dragEnter(currentDrag);
+        }
+
+        if (handleDragEvents) {
+
+            handlerRegistration = Event
+                    .addNativePreviewHandler(new NativePreviewHandler() {
+
+                        public void onPreviewNativeEvent(
+                                NativePreviewEvent event) {
+                            updateCurrentEvent(event.getNativeEvent());
+                            updateDragImagePosition();
+
+                            NativeEvent nativeEvent = event.getNativeEvent();
+                            Element targetElement = (Element) nativeEvent
+                                    .getEventTarget().cast();
+                            if (dragElement != null
+                                    && targetElement.isOrHasChild(dragElement)) {
+                                ApplicationConnection.getConsole().log(
+                                        "Event on dragImage, ignored");
+                                event.cancel();
+                                nativeEvent.stopPropagation();
+                                return;
+                            }
+
+                            int typeInt = event.getTypeInt();
+                            switch (typeInt) {
+                            case Event.ONMOUSEOVER:
+                                ApplicationConnection.getConsole().log(
+                                        event.getNativeEvent().getType());
+                                DropHandler target = findDragTarget(targetElement);
+                                if (target != null && target != currentDrag) {
+                                    currentDropHandler = target;
+                                    target.dragEnter(currentDrag);
+                                } else if (target == null
+                                        && currentDropHandler != null) {
+                                    ApplicationConnection.getConsole().log(
+                                            "Invalid state!?");
+                                    currentDropHandler = null;
+                                }
+                                break;
+                            case Event.ONMOUSEOUT:
+                                ApplicationConnection.getConsole().log(
+                                        event.getNativeEvent().getType());
+
+                                Element relatedTarget = (Element) nativeEvent
+                                        .getRelatedEventTarget().cast();
+                                DropHandler newDragHanler = findDragTarget(relatedTarget);
+                                if (dragElement != null
+                                        && dragElement
+                                                .isOrHasChild(relatedTarget)) {
+                                    ApplicationConnection.getConsole().log(
+                                            "Mouse out of dragImage, ignored");
+                                    return;
+                                }
+
+                                if (currentDropHandler != null
+                                        && currentDropHandler != newDragHanler) {
+                                    currentDropHandler.dragLeave(currentDrag);
+                                    currentDropHandler = null;
+                                    acceptCallback = null;
+                                }
+                                break;
+                            case Event.ONMOUSEMOVE:
+                                if (currentDropHandler != null) {
+                                    currentDropHandler.dragOver(currentDrag);
+                                }
+                                nativeEvent.preventDefault();
+
+                                break;
+
+                            case Event.ONMOUSEUP:
+                                endDrag();
+                                break;
+
+                            default:
+                                break;
+                            }
+
+                        }
+
+                    });
+        }
+        return currentDrag;
+    }
+
+    private void interruptDrag() {
+        if (currentDrag != null) {
+            ApplicationConnection.getConsole()
+                    .log("Drag operation interrupted");
+            if (currentDropHandler != null) {
+                currentDrag.currentGwtEvent = null;
+                currentDropHandler.dragLeave(currentDrag);
+                currentDropHandler = null;
+            }
+            currentDrag = null;
+        }
+    }
+
+    private void updateDragImagePosition() {
+        if (currentDrag.currentGwtEvent != null && dragElement != null) {
+            Style style = dragElement.getStyle();
+            int clientY = currentDrag.currentGwtEvent.getClientY() + 6;
+            int clientX = currentDrag.currentGwtEvent.getClientX() + 6;
+            style.setTop(clientY, Unit.PX);
+            style.setLeft(clientX, Unit.PX);
+        }
+    }
+
+    /**
+     * First seeks the widget from this element, then iterates widgets until one
+     * implement HasDropHandler. Returns DropHandler from that.
+     * 
+     * @param element
+     * @return
+     */
+    private DropHandler findDragTarget(Element element) {
+
+        EventListener eventListener = Event.getEventListener(element);
+        while (eventListener == null) {
+            element = element.getParentElement();
+            if (element == null) {
+                break;
+            }
+            eventListener = Event.getEventListener(element);
+        }
+        if (eventListener == null) {
+            ApplicationConnection.getConsole().log(
+                    "No suitable DropHandler found");
+            return null;
+        } else {
+            Widget w = (Widget) eventListener;
+            while (!(w instanceof HasDropHandler)) {
+                w = w.getParent();
+                if (w == null) {
+                    break;
+                }
+            }
+            if (w == null) {
+                ApplicationConnection.getConsole().log(
+                        "No suitable DropHandler found2");
+                return null;
+            } else {
+                DropHandler dh = ((HasDropHandler) w).getDropHandler();
+                if (dh == null) {
+                    ApplicationConnection.getConsole().log(
+                            "No suitable DropHandler found3");
+                }
+                return dh;
+            }
+        }
+    }
+
+    private void updateCurrentEvent(NativeEvent event) {
+        currentDrag.currentGwtEvent = event;
+    }
+
+    public void endDrag() {
+        if (handlerRegistration != null) {
+            handlerRegistration.removeHandler();
+            handlerRegistration = null;
+        }
+        if (currentDropHandler != null) {
+            // we have dropped on a drop target
+            boolean sendTransferrableToServer = currentDropHandler
+                    .drop(currentDrag);
+            if (sendTransferrableToServer) {
+                doRequest(DragEventType.DROP);
+            }
+            currentDropHandler = null;
+            acceptCallback = null;
+
+        }
+
+        currentDrag = null;
+
+        if (dragElement != null) {
+            RootPanel.getBodyElement().removeChild(dragElement);
+            dragElement = null;
+        }
+    }
+
+    private int visitId = 0;
+    private Element dragElement;
+
+    /**
+     * Visits server during drag and drop procedure. Transferable and event type
+     * is given to server side counterpart of DropHandler.
+     * 
+     * If another server visit is started before the current is received, the
+     * current is just dropped. TODO consider if callback should have
+     * interrupted() method for cleanup.
+     * 
+     * @param acceptCallback
+     */
+    public void visitServer(DragEventType type, AcceptCallback acceptCallback) {
+        doRequest(type);
+        this.acceptCallback = acceptCallback;
+    }
+
+    private void doRequest(DragEventType drop) {
+        Paintable paintable = currentDropHandler.getPaintable();
+        ApplicationConnection client = currentDropHandler
+                .getApplicationConnection();
+        /*
+         * For drag events we are using special id that are routed to
+         * "drag service" which then again finds the corresponding DropHandler
+         * on server side.
+         * 
+         * TODO add rest of the data in Transferable
+         * 
+         * TODO implement partial updates to Transferable (currently the whole
+         * Transferable is sent on each request)
+         */
+        visitId++;
+        client.updateVariable(DD_SERVICE, "visitId", visitId, false);
+        client.updateVariable(DD_SERVICE, "eventId", currentDrag.getEventId(),
+                false);
+        client.updateVariable(DD_SERVICE, "dhowner", paintable, false);
+
+        Transferable transferable = currentDrag.getTransferrable();
+
+        if (transferable.getItemId() != null) {
+            client.updateVariable(DD_SERVICE, "itemId", transferable
+                    .getItemId(), false);
+        }
+        if (transferable.getPropertyId() != null) {
+            client.updateVariable(DD_SERVICE, "propertyId", transferable
+                    .getPropertyId(), false);
+        }
+
+        client.updateVariable(DD_SERVICE, "component", transferable
+                .getComponent(), false);
+
+        client.updateVariable(DD_SERVICE, "type", drop.ordinal(), false);
+
+        if (currentDrag.currentGwtEvent != null) {
+            try {
+                MouseEventDetails mouseEventDetails = new MouseEventDetails(
+                        currentDrag.currentGwtEvent);
+                transferable.setData("mouseEvent", mouseEventDetails
+                        .serialize());
+            } catch (Exception e) {
+                // NOP, (at least oophm on Safari) can't serialize html dd event
+                // to
+                // mouseevent
+            }
+        } else {
+            transferable.setData("mouseEvent", null);
+        }
+
+        client.updateVariable(DD_SERVICE, "payload", transferable
+                .getVariableMap(), true);
+
+    }
+
+    public void handleServerResponse(ValueMap valueMap) {
+        if (acceptCallback == null) {
+            return;
+        }
+        int visitId = valueMap.getInt("visitId");
+        if (this.visitId == visitId) {
+            acceptCallback.handleResponse(valueMap);
+            acceptCallback = null;
+        }
+    }
+
+    void setDragElement(Element node) {
+        if (currentDrag != null) {
+            if (dragElement != null && dragElement != node) {
+                RootPanel.getBodyElement().removeChild(dragElement);
+            } else if (node == dragElement) {
+                return;
+            }
+
+            dragElement = node;
+            Style style = node.getStyle();
+            style.setPosition(Position.ABSOLUTE);
+            style.setZIndex(600000);
+            RootPanel.getBodyElement().appendChild(node);
+        }
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/DragEvent.java b/src/com/vaadin/terminal/gwt/client/ui/dd/DragEvent.java
new file mode 100644 (file)
index 0000000..abed467
--- /dev/null
@@ -0,0 +1,66 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import java.util.Date;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.user.client.Element;
+
+/**
+ * DragEvent used by Vaadin client side engine. Supports components, items,
+ * properties and custom payload (HTML5 style).
+ * 
+ * 
+ */
+public class DragEvent {
+
+    private static int eventId = 0;
+
+    private Transferable transferable;
+
+    NativeEvent currentGwtEvent;
+
+    private NativeEvent startEvent;
+
+    private int id;
+
+    private Date start;
+
+    DragEvent(Transferable t, NativeEvent startEvent) {
+        transferable = t;
+        this.startEvent = startEvent;
+        id = eventId++;
+        start = new Date();
+    }
+
+    public Transferable getTransferrable() {
+        return transferable;
+    }
+
+    public NativeEvent getCurrentGwtEvent() {
+        return currentGwtEvent;
+    }
+
+    public int getEventId() {
+        return id;
+    }
+
+    public long sinceStart() {
+        return new Date().getTime() - start.getTime();
+    }
+
+    /**
+     * Sets the element that will be used as "drag icon".
+     * 
+     * TODO decide if this method should be here or in {@link Transferable} (in
+     * HTML5 it is in DataTransfer) or {@link DragAndDropManager}
+     * 
+     * TODO should be possible to override behaviour an set to HTML5
+     * DataTransfer
+     * 
+     * @param node
+     */
+    public void setDragImage(Element node) {
+        DragAndDropManager.get().setDragElement(node);
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/DropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/DropHandler.java
new file mode 100644 (file)
index 0000000..45fbdb3
--- /dev/null
@@ -0,0 +1,59 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Paintable;
+
+/**
+ * Vaadin Widgets (TODO or Paintables, see {@link HasDropHandler}) that want to
+ * receive something via drag and drop implement this interface.
+ */
+public interface DropHandler {
+
+    /**
+     * Called by D'D' manager when drag gets over this drop handler.
+     * 
+     * @param drag
+     */
+    public void dragEnter(DragEvent drag);
+
+    /**
+     * Called by D'D' manager when drag gets out this drop handler.
+     * 
+     * @param drag
+     */
+    public void dragLeave(DragEvent drag);
+
+    /**
+     * The actual drop happened on this drop handler.
+     * 
+     * @param drag
+     * @return true if Tranferrable of this drag event needs to be sent to
+     *         server, false if drop was finally canceled or no server visit is
+     *         needed
+     */
+    public boolean drop(DragEvent drag);
+
+    /**
+     * When drag is over current drag handler.
+     * 
+     * With drag implementation by {@link DragAndDropManager} will be called
+     * when mouse is moved. HTML5 implementations call this continuously even
+     * though mouse is not moved.
+     * 
+     * @param currentDrag
+     */
+    public void dragOver(DragEvent currentDrag);
+
+    /**
+     * Returns the Paintable into which this DragHandler is assosiated
+     */
+    public Paintable getPaintable();
+
+    /**
+     * Returns the application connection to which this {@link DropHandler}
+     * belongs to. DragAndDropManager uses this fucction to send Transferable to
+     * server side.
+     */
+    public ApplicationConnection getApplicationConnection();
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/HasDropHandler.java b/src/com/vaadin/terminal/gwt/client/ui/dd/HasDropHandler.java
new file mode 100644 (file)
index 0000000..9814a6f
--- /dev/null
@@ -0,0 +1,14 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.vaadin.terminal.gwt.client.Paintable;
+
+/**
+ * Used to detect Widget from widget tree that has {@link #getDropHandler()}
+ * 
+ * Decide whether to get rid of this class. If so, {@link AbstractDropHandler} must
+ * extend {@link Paintable}.
+ * 
+ */
+public interface HasDropHandler {
+    public DropHandler getDropHandler();
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent.java b/src/com/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent.java
new file mode 100644 (file)
index 0000000..230cda8
--- /dev/null
@@ -0,0 +1,46 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.NativeEvent;
+
+public class Html5DragEvent extends NativeEvent {
+    protected Html5DragEvent() {
+    }
+
+    public final native JsArrayString getTypes()
+    /*-{
+        return this.dataTransfer.types;
+     }-*/;
+
+    public final native String getDataAsText(String type)
+    /*-{
+         var v = this.dataTransfer.getData(type);
+         return v;
+     }-*/;
+
+    /**
+     * Works on FF 3.6 and possibly with gears.
+     * 
+     * @param index
+     * @return
+     */
+    public final native String getFileAsString(int index)
+    /*-{
+        if(this.dataTransfer.files.length > 0 && this.dataTransfer.files[0].getAsText) {
+            return this.dataTransfer.files[index].getAsText("UTF-8");
+        }
+        return null;
+    }-*/;
+
+    public final native void setDragEffect(String effect)
+    /*-{
+        try {
+            this.dataTransfer.dropEffect = effect;
+        } catch (e){}
+     }-*/;
+
+    public final native String getEffectAllowed()
+    /*-{
+            return this.dataTransfer.effectAllowed;
+     }-*/;
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/Transferable.java b/src/com/vaadin/terminal/gwt/client/ui/dd/Transferable.java
new file mode 100644 (file)
index 0000000..52ea08f
--- /dev/null
@@ -0,0 +1,101 @@
+package com.vaadin.terminal.gwt.client.ui.dd;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.terminal.TransferTranslator;
+import com.vaadin.terminal.gwt.client.Paintable;
+
+/**
+ * Client side counterpart for Transferable in com.vaadin.event.Transferable
+ * 
+ */
+public class Transferable {
+
+    /**
+     * @return the component
+     */
+    public Paintable getComponent() {
+        return component;
+    }
+
+    /**
+     * @param component
+     *            the component to set
+     */
+    public void setComponent(Paintable component) {
+        this.component = component;
+    }
+
+    /**
+     * This is commonly actually a key to property id on client side than the
+     * actual propertyId.
+     * 
+     * Translated by terminal and {@link TransferTranslator}
+     * 
+     * @return the propertyId
+     */
+    public String getPropertyId() {
+        return (String) variables.get("propertyId");
+    }
+
+    /**
+     * This is commonly actually a key to property id on client side than the
+     * actual propertyId.
+     * 
+     * Translated by terminal and {@link TransferTranslator}
+     * 
+     * @param propertyId
+     *            the propertyId to set
+     */
+    public void setPropertyId(String propertyId) {
+        variables.put("propertyId", propertyId);
+    }
+
+    /**
+     * @return the itemId
+     */
+    public String getItemId() {
+        return (String) variables.get("itemId");
+    }
+
+    /**
+     * This is commonly actually a key to item id on client side than the actual
+     * itemId.
+     * 
+     * Translated by terminal and {@link TransferTranslator}
+     * 
+     * @param itemId
+     *            the itemId to set
+     */
+    public void setItemId(String itemId) {
+        variables.put("itemId", itemId);
+    }
+
+    private Paintable component;
+
+    public Object getData(String dataFlawor) {
+        return variables.get(dataFlawor);
+    }
+
+    public void setData(String dataFlawor, Object value) {
+        variables.put(dataFlawor, value);
+    }
+
+    public Collection<String> getDataFlawors() {
+        return variables.keySet();
+    }
+
+    private final Map<String, Object> variables = new HashMap<String, Object>();
+
+    /**
+     * This method should only be called by {@link DragAndDropManager}.
+     * 
+     * @return data in this Transferable that needs to be moved to server.
+     */
+    public Map<String, Object> getVariableMap() {
+        return variables;
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java
new file mode 100644 (file)
index 0000000..3371c74
--- /dev/null
@@ -0,0 +1,177 @@
+package com.vaadin.terminal.gwt.server;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.event.ComponentTransferrable;
+import com.vaadin.event.DragRequest;
+import com.vaadin.event.DropHandler;
+import com.vaadin.event.HasDropHandler;
+import com.vaadin.event.Transferable;
+import com.vaadin.terminal.TransferTranslator;
+import com.vaadin.terminal.VariableOwner;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+import com.vaadin.ui.Component;
+
+public class DragAndDropService implements VariableOwner {
+
+    private static final long serialVersionUID = -4745268869323400203L;
+
+    private int lastVisitId;
+
+    private DragRequest currentRequest;
+
+    private int currentEventId;
+
+    private Transferable transferable;
+
+    public void changeVariables(Object source, Map<String, Object> variables) {
+        HasDropHandler dropHandlerOwner = (HasDropHandler) variables
+                .get("dhowner");
+        if (dropHandlerOwner == null) {
+            return;
+        }
+        lastVisitId = (Integer) variables.get("visitId");
+
+        currentRequest = constructDragRequest(variables, dropHandlerOwner);
+
+        DropHandler dropHandler = (dropHandlerOwner).getDropHandler();
+        dropHandler.handleDragRequest(currentRequest);
+        if (currentRequest.getType() == DragEventType.DROP) {
+            // TODO transferable should also be cleaned on each non-dnd
+            // variable change (if visited server, but drop did not happen ->
+            // should do cleanup to release memory)
+            transferable = null;
+        }
+
+    }
+
+    private DragRequest constructDragRequest(Map<String, Object> variables,
+            HasDropHandler dropHandlerOwner) {
+        Transferable transferable = constructTransferrable(variables,
+                dropHandlerOwner);
+
+        int type = (Integer) variables.get("type");
+        DragRequest dragRequest = new DragRequest(DragEventType.values()[type],
+                transferable);
+        return dragRequest;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Transferable constructTransferrable(Map<String, Object> variables,
+            HasDropHandler dropHandlerOwner) {
+        int eventId = (Integer) variables.get("eventId");
+        if (currentEventId != eventId) {
+            transferable = null;
+        }
+        currentEventId = eventId;
+
+        final Component sourceComponent = (Component) variables
+                .get("component");
+        if (sourceComponent != null
+                && sourceComponent instanceof TransferTranslator) {
+            transferable = ((TransferTranslator) sourceComponent)
+                    .getTransferrable(transferable, variables, false);
+        } else {
+            if (transferable == null) {
+                if (sourceComponent != null) {
+                    transferable = new ComponentTransferrable() {
+
+                        private Map<String, Object> td = new HashMap<String, Object>();
+
+                        public Component getSourceComponent() {
+                            return sourceComponent;
+                        }
+
+                        public Object getData(String dataFlawor) {
+                            return td.get(dataFlawor);
+                        }
+
+                        public void setData(String dataFlawor, Object value) {
+                            td.put(dataFlawor, value);
+                        }
+
+                        public Collection<String> getDataFlawors() {
+                            return td.keySet();
+                        }
+
+                    };
+                } else {
+                    transferable = new Transferable() {
+                        private Map<String, Object> td = new HashMap<String, Object>();
+
+                        public Object getData(String dataFlawor) {
+                            return td.get(dataFlawor);
+                        }
+
+                        public void setData(String dataFlawor, Object value) {
+                            td.put(dataFlawor, value);
+                        }
+
+                        public Collection<String> getDataFlawors() {
+                            return td.keySet();
+                        }
+
+                    };
+                }
+            }
+        }
+        /*
+         * Also let dropHandler translate variables if it implements
+         * TransferTranslator
+         */
+        if (dropHandlerOwner instanceof TransferTranslator) {
+            transferable = ((TransferTranslator) dropHandlerOwner)
+                    .getTransferrable(transferable, variables, true);
+        }
+
+        /*
+         * Add remaining (non-handled) variables to transferable as is
+         */
+        variables = (Map<String, Object>) variables.get("payload");
+        for (String key : variables.keySet()) {
+            transferable.setData(key, variables.get(key));
+        }
+
+        return transferable;
+    }
+
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public boolean isImmediate() {
+        return true;
+    }
+
+    void printJSONResponse(PrintWriter outWriter) {
+        if (isDirty()) {
+            // TODO paint responsedata
+            outWriter.print(", dd : {");
+            outWriter.print("visitId:");
+            outWriter.print(lastVisitId);
+            Map<String, Object> responseData = currentRequest.getResponseData();
+            if (responseData != null) {
+                for (String key : responseData.keySet()) {
+                    Object object = responseData.get(key);
+                    outWriter.print(",\"");
+                    // TODO JSON escaping for key and object
+                    outWriter.print(key);
+                    outWriter.print("\":");
+                    outWriter.print(object);
+                }
+            }
+            outWriter.print("}");
+            currentRequest = null;
+        }
+    }
+
+    private boolean isDirty() {
+        if (currentRequest != null) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/vaadin/ui/DragDropPane.java b/src/com/vaadin/ui/DragDropPane.java
new file mode 100644 (file)
index 0000000..293ea8a
--- /dev/null
@@ -0,0 +1,164 @@
+package com.vaadin.ui;
+
+import com.vaadin.event.AbstractDropHandler;
+import com.vaadin.event.ComponentTransferrable;
+import com.vaadin.event.DataBindedTransferrable;
+import com.vaadin.event.HasDropHandler;
+import com.vaadin.event.Transferable;
+import com.vaadin.terminal.PaintException;
+import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.MouseEventDetails;
+
+/**
+ * Test class that implements various component related Drag and Drop features.
+ * 
+ * TODO consider implementing this kind of general purpose layout class:
+ * 
+ * - extend simple component (CssLayout or CustomComponent, instead of absolute
+ * layout)
+ * 
+ * - implement both drag source and drop handler with server side api
+ * 
+ * - implement html5 drop target etc
+ * 
+ * - include a lots of details for drop event (coordinates & sizes of drop
+ * position and widget/Paintable hierarchy up to drop handler)
+ * 
+ * This way we could have one rather complex dd component that could be used (by
+ * wrapping layouts) in most common situations with server side api. Core
+ * layouts wouldn't need changes (and have regression risk/ performance
+ * penalty).
+ * 
+ */
+@SuppressWarnings("serial")
+@ClientWidget(com.vaadin.terminal.gwt.client.ui.VDragDropPane.class)
+public class DragDropPane extends AbsoluteLayout implements HasDropHandler {
+
+    private AbstractDropHandler abstractDropHandler;
+
+    public DragDropPane(AbstractDropHandler dropHandler) {
+        setWidth("400px");
+        setHeight("300px");
+
+        if (dropHandler == null) {
+            dropHandler = new AbstractDropHandler() {
+                @Override
+                public void receive(Transferable transferable) {
+                    if (transferable instanceof ComponentTransferrable) {
+                        ComponentTransferrable ctr = (ComponentTransferrable) transferable;
+                        Component component = ctr.getSourceComponent();
+
+                        if (component.getParent() != DragDropPane.this) {
+                            if (transferable instanceof DataBindedTransferrable) {
+                                // Item has been dragged, construct a Label from
+                                // Item id
+                                Label l = new Label();
+                                l.setSizeUndefined();
+                                l
+                                        .setValue("ItemId : "
+                                                + ((DataBindedTransferrable) transferable)
+                                                        .getItemId());
+                                DragDropPane.this.addComponent(l);
+                                component = l;
+
+                            } else {
+                                // we have a component that is been dragged, add
+                                // it
+                                // to
+                                // this
+                                DragDropPane.this.addComponent(component);
+                            }
+                            Integer left = (Integer) transferable
+                                    .getData("absoluteLeft");
+                            Integer top = (Integer) transferable
+                                    .getData("absoluteTop");
+
+                            MouseEventDetails eventDetails = MouseEventDetails
+                                    .deSerialize((String) transferable
+                                            .getData("mouseEvent"));
+
+                            int clientX = eventDetails.getClientX();
+                            int clientY = eventDetails.getClientY();
+
+                            try {
+                                DragDropPane.this.getPosition(component)
+                                        .setTopValue(clientY - top);
+                                DragDropPane.this.getPosition(component)
+                                        .setLeftValue(clientX - left);
+                            } catch (Exception e) {
+                                // TODO: handle exception
+                            }
+                        } else {
+                            // drag ended inside the this Pane
+
+                            MouseEventDetails start = MouseEventDetails
+                                    .deSerialize((String) transferable
+                                            .getData("mouseDown"));
+                            MouseEventDetails eventDetails = MouseEventDetails
+                                    .deSerialize((String) transferable
+                                            .getData("mouseEvent"));
+
+                            int deltaX = eventDetails.getClientX()
+                                    - start.getClientX();
+                            int deltaY = eventDetails.getClientY()
+                                    - start.getClientY();
+
+                            ComponentPosition p = DragDropPane.this
+                                    .getPosition(component);
+                            p.setTopValue(p.getTopValue() + deltaY);
+                            p.setLeftValue(p.getLeftValue() + deltaX);
+
+                        }
+
+                    } else {
+                        // drag coming outside of Vaadin
+                        String object = (String) transferable
+                                .getData("text/plain");
+
+                        String content = (String) transferable
+                                .getData("fileContents");
+
+                        Label l = new Label();
+                        l.setCaption("Generated from HTML5 drag:");
+                        if (object != null) {
+                            l.setValue(object);
+                        } else {
+                            l.setValue("HTML5 dd");
+                        }
+
+                        l.setDescription(content);
+                        l.setSizeUndefined();
+
+                        DragDropPane.this.addComponent(l);
+
+                    }
+
+                }
+            };
+            if (dropHandler instanceof AbstractDropHandler) {
+                AbstractDropHandler new_name = dropHandler;
+                new_name
+                        .setAcceptCriterion(AbstractDropHandler.CRITERION_ACCEPT_ALL);
+            }
+        }
+        abstractDropHandler = dropHandler;
+    }
+
+    public DragDropPane() {
+        this(null);
+    }
+
+    @Override
+    public void paintContent(PaintTarget target) throws PaintException {
+        super.paintContent(target);
+        if (abstractDropHandler instanceof AbstractDropHandler) {
+            AbstractDropHandler new_name = abstractDropHandler;
+            new_name.paint(target);
+        }
+    }
+
+    public AbstractDropHandler getDropHandler() {
+        return abstractDropHandler;
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/AcceptAnythingWindow.java b/tests/src/com/vaadin/tests/dd/AcceptAnythingWindow.java
new file mode 100644 (file)
index 0000000..ede2fec
--- /dev/null
@@ -0,0 +1,17 @@
+package com.vaadin.tests.dd;
+
+import com.vaadin.ui.DragDropPane;
+import com.vaadin.ui.Window;
+
+public class AcceptAnythingWindow extends Window {
+
+    public AcceptAnythingWindow() {
+        setCaption("Drop anything here");
+        DragDropPane pane = new DragDropPane();
+        setContent(pane);
+        pane.setSizeFull();
+        setWidth("250px");
+        setHeight("100px");
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/AcceptFromComponent.java b/tests/src/com/vaadin/tests/dd/AcceptFromComponent.java
new file mode 100644 (file)
index 0000000..3b3a9c1
--- /dev/null
@@ -0,0 +1,33 @@
+package com.vaadin.tests.dd;
+
+import com.vaadin.event.ComponentTransferrable;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.AbstractDropHandler.AcceptCriterion;
+import com.vaadin.ui.DragDropPane;
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Window;
+
+public class AcceptFromComponent extends Window {
+
+    public AcceptFromComponent(final Tree tree1) {
+        setCaption("Checks the source is tree1 on server");
+
+        DragDropPane pane = new DragDropPane();
+        setContent(pane);
+        pane.getDropHandler().setAcceptCriterion(new AcceptCriterion() {
+            public boolean accepts(Transferable transferable) {
+                if (transferable instanceof ComponentTransferrable) {
+                    ComponentTransferrable componentTransferrable = (ComponentTransferrable) transferable;
+                    if (componentTransferrable.getSourceComponent() == tree1) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        });
+        pane.setSizeFull();
+        setWidth("450px");
+        setHeight("150px");
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/CustomDDImplementation.java b/tests/src/com/vaadin/tests/dd/CustomDDImplementation.java
new file mode 100644 (file)
index 0000000..01490cd
--- /dev/null
@@ -0,0 +1,92 @@
+package com.vaadin.tests.dd;
+
+import com.vaadin.event.AbstractDropHandler;
+import com.vaadin.event.DragRequest;
+import com.vaadin.event.DropHandler;
+import com.vaadin.event.HasDropHandler;
+import com.vaadin.event.Transferable;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.ClientWidget;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.CustomComponent;
+import com.vaadin.ui.Layout;
+
+/**
+ * Test/Example/Draft code how to build custom DD implementation using the thing
+ * framework provided by Vaadin.
+ * 
+ */
+public class CustomDDImplementation extends CustomComponent {
+
+    public CustomDDImplementation() {
+        Layout l = new CssLayout();
+        l.addComponent(new MyDropTarget());
+        l.addComponent(new MyDragSource());
+    }
+
+    /**
+     * Server side component that accepts drags must implement HasDropHandler
+     * that have one method to get reference of DropHandler.
+     * 
+     * DropHandler may be implemented directly or probably most commonly using a
+     * half baked implementation {@link AbstractDropHandler}.
+     * 
+     * Check the @ClientWidget
+     * 
+     */
+    @ClientWidget(VMyDropTarget.class)
+    class MyDropTarget extends AbstractComponent implements HasDropHandler {
+
+        public DropHandler getDropHandler() {
+            return new DropHandler() {
+                public void handleDragRequest(DragRequest event) {
+                    Transferable transferable = event.getTransferrable();
+                    DragEventType type = event.getType();
+                    switch (type) {
+                    case DROP:
+                        // Do something with data
+
+                        break;
+
+                    case ENTER:
+                        // eg. validate transferrable
+                        if (transferable.getDataFlawors().contains("Foo")) {
+                            event.getResponseData().put("valueFor",
+                                    "clientSideCallBack");
+                        }
+
+                        break;
+                    case OVER:
+
+                        break;
+                    case LEAVE:
+
+                        break;
+                    default:
+                        break;
+                    }
+
+                }
+            };
+        }
+
+    }
+
+    /**
+     * Server side implementation of source does not necessary need to contain
+     * anything.
+     * 
+     * Check the @ClientWidget
+     * 
+     * However component might have different modes to support starting drag
+     * operations that are controlled via server side api.
+     * 
+     */
+    @ClientWidget(VMyDragSource.class)
+    public class MyDragSource extends AbstractComponent implements Component {
+
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/DDTest1.java b/tests/src/com/vaadin/tests/dd/DDTest1.java
new file mode 100644 (file)
index 0000000..9fee73d
--- /dev/null
@@ -0,0 +1,263 @@
+package com.vaadin.tests.dd;
+
+import java.util.Collection;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.util.HierarchicalContainer;
+import com.vaadin.event.AbstractDropHandler;
+import com.vaadin.event.ComponentTransferrable;
+import com.vaadin.event.DataBindedTransferrable;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.AbstractDropHandler.AcceptCriterion;
+import com.vaadin.terminal.ExternalResource;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.DragDropPane;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.Link;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Table.DragModes;
+
+/**
+ * DD playground. Better quality example/prototype codes in {@link DDTest2}.
+ */
+public class DDTest1 extends TestBase {
+
+    @Override
+    protected void setup() {
+        GridLayout gl = new GridLayout(3, 2);
+        gl.setSizeFull();
+        gl.setSpacing(true);
+        Layout main = gl;
+
+        DragDropPane pane1 = new DragDropPane();
+        pane1.setSizeFull();
+        pane1.setCaption("Pane1");
+
+        Label label = new Label("Foo");
+        label.setSizeUndefined();
+
+        pane1.addComponent(label);
+
+        Link l = new Link("This is link", new ExternalResource(
+                "http://www.google.com/"));
+        pane1.addComponent(l, "top:100px; left: 20px;");
+
+        label = new Label("Bar");
+        label.setSizeUndefined();
+        pane1.addComponent(label);
+
+        DragDropPane pane2 = new DragDropPane();
+        pane2.setDebugId("pane2");
+        pane2.setSizeFull();
+        pane2
+                .setCaption("Pane2 (accept needs server side visit, only \"Bar\")");
+
+        AcceptCriterion f = new AcceptCriterion() {
+            public boolean accepts(Transferable transferable) {
+                // System.out.println("Simulating 500ms processing...");
+                // try {
+                // Thread.sleep(200);
+                // } catch (InterruptedException e) {
+                // // TODO Auto-generated catch block
+                // e.printStackTrace();
+                // }
+                // System.out.println("Done get to work.");
+                if (transferable instanceof ComponentTransferrable) {
+                    ComponentTransferrable ct = (ComponentTransferrable) transferable;
+
+                    Component component = ct.getSourceComponent();
+                    if (component != null) {
+                        if (component.toString() != null
+                                && component.toString().contains("Bar")) {
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            }
+        };
+        pane2.getDropHandler().setAcceptCriterion(f);
+
+        DragDropPane pane3 = new DragDropPane();
+        pane3.setSizeFull();
+        pane3.setCaption("Pane3");
+
+        final Tree t = new Tree(
+                "Tree with sorting enabled. Also allows dragging elsewhere.");
+
+        final HierarchicalContainer idx = new HierarchicalContainer();
+        t.setContainerDataSource(idx);
+        t.setDebugId("perseys");
+        t.addItem("Foo");
+        t.addItem("Bar");
+        t.addItem("Bar1");
+        t.addItem("Bar2");
+        t.addItem("Bar3");
+        t.addItem("Bar4");
+        t.addItem("Bar5");
+        t.addItem("Child");
+        t.setParent("Child", "Foo");
+        t.setSizeFull();
+
+        /*
+         * Moves items in tree (and could work in Table too). Also supports
+         * "building" tree.
+         * 
+         * TODO fix algorithm, broken in some cases.
+         */
+        AbstractDropHandler itemSorter = new AbstractDropHandler() {
+
+            @Override
+            public void receive(Transferable transferable) {
+                // TODO set properties, so same sorter could be used in Table
+                if (transferable instanceof DataBindedTransferrable) {
+                    DataBindedTransferrable transferrable2 = (DataBindedTransferrable) transferable;
+
+                    Object itemId = transferrable2.getItemId();
+
+                    Object itemIdOver = transferable.getData("itemIdOver");
+
+                    String detail = ((String) transferable.getData("detail"))
+                            .toLowerCase();
+
+                    if ("center".equals(detail)) {
+                        t.setParent(itemId, itemIdOver);
+                        return;
+                    } else if ("top".equals(detail)) {
+                        // if on top of the caption area, add before
+                        itemIdOver = idx.prevItemId(itemIdOver);
+                    }
+
+                    if (itemId.equals(itemIdOver)) {
+                        // the location is same
+                        return;
+                    }
+
+                    HierarchicalContainer subtree = getSubTree(idx, itemId);
+                    boolean removed = idx.removeItem(itemId);
+
+                    if (removed) {
+
+                        if (detail == null) {
+                            System.err
+                                    .println("No detail of drop place available");
+                        }
+
+                        Item addItemAfter = idx
+                                .addItemAfter(itemIdOver, itemId);
+                        populateSubTree(idx, subtree, itemId);
+                        // ensure the same parent as with related item
+                        Object parent = idx.getParent(itemIdOver);
+                        idx.setParent(itemId, parent);
+                    }
+                }
+
+            }
+
+            private void populateSubTree(HierarchicalContainer idx,
+                    HierarchicalContainer subtree, Object itemId) {
+                Collection children = subtree.getChildren(itemId);
+                if (children != null) {
+
+                    for (Object childId : children) {
+                        Item addItem = idx.addItem(childId);
+                        if (addItem != null) {
+                            // did not exist, populate properties
+                            Item item = subtree.getItem(itemId);
+                            Collection<?> itemPropertyIds = item
+                                    .getItemPropertyIds();
+                            for (Object propId : itemPropertyIds) {
+                                addItem.getItemProperty(propId)
+                                        .setValue(
+                                                item.getItemProperty(propId)
+                                                        .getValue());
+                            }
+                        }
+                        idx.setParent(childId, itemId);
+                        populateSubTree(idx, subtree, childId);
+                    }
+                }
+
+            }
+
+            private HierarchicalContainer getSubTree(HierarchicalContainer idx,
+                    Object itemId) {
+                HierarchicalContainer hierarchicalContainer = new HierarchicalContainer();
+                Collection containerPropertyIds = idx.getContainerPropertyIds();
+                for (Object object : containerPropertyIds) {
+                    hierarchicalContainer.addContainerProperty(object, idx
+                            .getType(object), null);
+                }
+                hierarchicalContainer.addItem(itemId);
+                copyChildren(idx, hierarchicalContainer, itemId);
+                return hierarchicalContainer;
+            }
+
+            private void copyChildren(HierarchicalContainer source,
+                    HierarchicalContainer target, Object itemId) {
+                Collection children = source.getChildren(itemId);
+                if (children != null) {
+                    for (Object childId : children) {
+                        Item item = source.getItem(childId);
+                        Item addedItem = target.addItem(childId);
+                        target.setParent(childId, itemId);
+                        Collection<?> itemPropertyIds = item
+                                .getItemPropertyIds();
+                        for (Object propertyId : itemPropertyIds) {
+                            addedItem.getItemProperty(propertyId)
+                                    .setValue(
+                                            item.getItemProperty(propertyId)
+                                                    .getValue());
+                        }
+                        copyChildren(source, target, childId);
+                    }
+                }
+
+            }
+
+        };
+
+        /*
+         * Accept only drags that have item identifiers
+         */
+        itemSorter
+                .setAcceptCriterion(AbstractDropHandler.CRITERION_HAS_ITEM_ID);
+
+        t.setDropHandler(itemSorter);
+
+        Table ta = new Table("Test table");
+        ta.setContainerDataSource(idx);
+        ta.addContainerProperty("Foos", String.class, "Foo");
+        ta.addContainerProperty("Bars", String.class, "Bar");
+        ta.setRowHeaderMode(Table.ROW_HEADER_MODE_ID);
+        ta.setSizeFull();
+        ta.setDragMode(DragModes.ROWS);
+
+        main.addComponent(pane1);
+        main.addComponent(pane2);
+        main.addComponent(pane3);
+        main.addComponent(t);
+        main.addComponent(ta);
+        main.addComponent(new Link("Foo", new ExternalResource(
+                "http://www.itmill.com/")));
+
+        getLayout().setSizeFull();
+        addComponent(main);
+
+    }
+
+    @Override
+    protected String getDescription() {
+        return "Random DD tests";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 119;
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/DDTest2.java b/tests/src/com/vaadin/tests/dd/DDTest2.java
new file mode 100644 (file)
index 0000000..6777ead
--- /dev/null
@@ -0,0 +1,215 @@
+package com.vaadin.tests.dd;
+
+import java.util.Collection;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.util.HierarchicalContainer;
+import com.vaadin.demo.tutorial.addressbook.data.Person;
+import com.vaadin.demo.tutorial.addressbook.data.PersonContainer;
+import com.vaadin.event.AbstractDropHandler;
+import com.vaadin.event.DataBindedTransferrable;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.AbstractDropHandler.AcceptCriterion;
+import com.vaadin.event.AbstractDropHandler.And;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.ThemeResource;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Window;
+
+public class DDTest2 extends TestBase {
+
+    java.util.Random r = new java.util.Random(1);
+
+    HorizontalLayout hl = new HorizontalLayout();
+    Tree tree1 = new Tree("Tree that accepts table rows to folders");
+    Table table = new Table("Drag rows to Tree on left or right");
+    Tree tree2 = new Tree("Accepts items, copies values");
+
+    @Override
+    protected void setup() {
+        Window w = getLayout().getWindow();
+        /* darn reindeer has no icons */
+        w.setTheme("runo");
+
+        hl.addComponent(tree1);
+        hl.addComponent(table);
+        hl.addComponent(tree2);
+        hl.setWidth("100%");
+        hl.setSpacing(true);
+        hl.setExpandRatio(table, 1);
+        popuplateTrees();
+        table.setWidth("100%");
+        table.setPageLength(10);
+        populateTable();
+        addComponent(hl);
+
+        /*
+         * Make table rows draggable
+         */
+        table.setDragMode(Table.DragModes.ROWS);
+
+        AbstractDropHandler dropHandler = new AbstractDropHandler() {
+            @Override
+            public void receive(Transferable transferable) {
+                /*
+                 * We know transferrable is from table, so it is of type
+                 * DataBindedTransferrable
+                 */
+                DataBindedTransferrable tr = (DataBindedTransferrable) transferable;
+                Object itemId = tr.getItemId();
+                Table fromTable = (Table) tr.getSourceComponent();
+                String name = fromTable.getItem(itemId).getItemProperty("Name")
+                        .toString();
+                tree1.addItem(name);
+                tree1.setChildrenAllowed(name, false);
+
+                /*
+                 * As we also accept only drops on folders, we know data
+                 * contains itemIdOver
+                 */
+                Object idOver = tr.getData("itemIdOver");
+                tree1.setParent(name, idOver);
+
+                /*
+                 * Remove the item from table
+                 */
+                table.removeItem(itemId);
+
+            }
+        };
+        AcceptCriterion onNode = new AbstractDropHandler.OverTreeNode();
+        AcceptCriterion fromTree = new AbstractDropHandler.ComponentFilter(
+                table);
+        And and = new AbstractDropHandler.And(fromTree, onNode);
+        dropHandler.setAcceptCriterion(and);
+        tree1.setDropHandler(dropHandler);
+
+        /*
+         * First step done. tree1 now accepts drags only from table and only
+         * over tree nodes aka "folders"
+         */
+
+        /*
+         * Now set the rightmost tree accept any item drag. On drop, copy from
+         * source. Also make drags from tree1 possible.
+         */
+
+        dropHandler = new AbstractDropHandler() {
+            @Override
+            public void receive(Transferable transferable) {
+                if (transferable instanceof DataBindedTransferrable) {
+                    DataBindedTransferrable tr = (DataBindedTransferrable) transferable;
+
+                    Object itemId = tree2.addItem();
+                    tree2.setParent(itemId, tr.getData("itemIdOver"));
+                    if (tr.getSourceComponent() == tree1) {
+                        // use item id from tree1 as caption
+                        tree2.setItemCaption(itemId, (String) tr.getItemId());
+                        // if comes from tree1, move subtree too
+                        copySubTree(tr.getItemId(), itemId);
+                    } else if (tr.getSourceComponent() == table) {
+                        // comes from table, override caption with name
+                        String name = (String) table.getItem(tr.getItemId())
+                                .getItemProperty("Name").getValue();
+                        tree2.setItemCaption(itemId, name);
+                    } else if (tr.getSourceComponent() == tree2) {
+                        tree2.setItemCaption(itemId, tree2.getItemCaption(tr
+                                .getItemId()));
+                    }
+                }
+            }
+
+            private void copySubTree(Object itemId, Object itemIdTo) {
+                Collection children = tree1.getChildren(itemId);
+                if (children != null) {
+                    for (Object childId : children) {
+                        Object newItemId = tree2.addItem();
+                        tree2.setItemCaption(newItemId, (String) childId);
+                        tree2.setParent(newItemId, itemIdTo);
+                        copySubTree(childId, newItemId);
+                    }
+                }
+            }
+        };
+        dropHandler
+                .setAcceptCriterion(AbstractDropHandler.CRITERION_HAS_ITEM_ID);
+
+        tree2.setDropHandler(dropHandler);
+
+        /*
+         * Finally add two windows with DragDropPane. First accept anything,
+         * second has server side accept rule to allow only drops from Tree1.
+         * Check the code in implementing classes.
+         */
+        Window acceptAnyThing = new AcceptAnythingWindow();
+        Window acceptFromTree1viaServerCheck = new AcceptFromComponent(tree1);
+
+        w.addWindow(acceptAnyThing);
+        acceptAnyThing.setPositionY(450);
+        acceptAnyThing.setPositionX(0);
+        w.addWindow(acceptFromTree1viaServerCheck);
+        acceptFromTree1viaServerCheck.setPositionY(450);
+        acceptFromTree1viaServerCheck.setPositionX(300);
+
+    }
+
+    private void populateTable() {
+        table.addContainerProperty("Name", String.class, "");
+        table.addContainerProperty("Weight", Integer.class, 0);
+
+        PersonContainer testData = PersonContainer.createWithTestData();
+
+        for (int i = 0; i < 10; i++) {
+            Item addItem = table.addItem("Item" + i);
+            Person p = testData.getIdByIndex(i);
+            addItem.getItemProperty("Name").setValue(
+                    p.getFirstName() + " " + p.getLastName());
+            addItem.getItemProperty("Weight").setValue(50 + r.nextInt(60));
+        }
+
+    }
+
+    private final static ThemeResource FOLDER = new ThemeResource(
+            "icons/16/folder.png");
+    private final static ThemeResource DOC = new ThemeResource(
+            "icons/16/document.png");
+
+    private void popuplateTrees() {
+        HierarchicalContainer hc = new HierarchicalContainer();
+        hc.addContainerProperty("icon", Resource.class, DOC);
+        Item addItem = hc.addItem("Fats");
+        addItem.getItemProperty("icon").setValue(FOLDER);
+        hc.addItem("Tarja");
+        hc.setParent("Tarja", "Fats");
+        hc.setChildrenAllowed("Tarja", false);
+        addItem = hc.addItem("Thins");
+        addItem.getItemProperty("icon").setValue(FOLDER);
+        addItem = hc.addItem("Anorectic");
+        addItem.getItemProperty("icon").setValue(FOLDER);
+        hc.setParent("Anorectic", "Thins");
+        addItem = hc.addItem("Normal weighted");
+        addItem.getItemProperty("icon").setValue(FOLDER);
+
+        tree1.setContainerDataSource(hc);
+        tree1.setItemIconPropertyId("icon");
+
+        tree2.setContainerDataSource(new HierarchicalContainer());
+
+        tree2.addItem("/");
+
+    }
+
+    @Override
+    protected String getDescription() {
+        return "dd";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 119;
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/VMyDragSource.java b/tests/src/com/vaadin/tests/dd/VMyDragSource.java
new file mode 100644 (file)
index 0000000..2be0dc8
--- /dev/null
@@ -0,0 +1,91 @@
+package com.vaadin.tests.dd;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager;
+import com.vaadin.terminal.gwt.client.ui.dd.Transferable;
+
+/**
+ * Example code to implement Component that has something to drag.
+ */
+public class VMyDragSource extends Composite implements Paintable,
+        MouseDownHandler, MouseMoveHandler, MouseOutHandler {
+
+    private boolean mouseDown;
+    private MouseDownEvent mDownEvent;
+    private ApplicationConnection client;
+
+    public VMyDragSource() {
+        FlowPanel fp = new FlowPanel();
+        initWidget(fp);
+
+        HTML html = new HTML("DragThis");
+
+        fp.add(html);
+
+        html.addMouseDownHandler(this);
+        html.addMouseMoveHandler(this);
+        html.addMouseOutHandler(this);
+
+    }
+
+    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+        if (client.updateComponent(this, uidl, true)) {
+            return;
+        }
+        this.client = client;
+    }
+
+    /*
+     * Below a sophisticated drag start implementation. Drag event is started if
+     * mouse is moved 5 pixels with left mouse key down.
+     */
+
+    public void onMouseDown(MouseDownEvent event) {
+        if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
+            mouseDown = true;
+            mDownEvent = event;
+        }
+    }
+
+    public void onMouseMove(MouseMoveEvent event) {
+        if (mouseDown) {
+            int deltaX = Math.abs(mDownEvent.getClientX() - event.getClientX());
+            int deltaY = Math.abs(mDownEvent.getClientY() - event.getClientY());
+            if (deltaX > 5 || deltaY > 5) {
+                // Start the drag and drop operation
+
+                // create Transferable, that contains the payload
+                Transferable transferable = new Transferable();
+                transferable.setData("Text", "myPayload");
+
+                // Tell DragAndDropManager to start a drag and drop operation.
+                // Also let it handle all events (last parameter true). Could
+                // also do all event handling here too.
+                DragAndDropManager.get().startDrag(transferable,
+                        mDownEvent.getNativeEvent(), true);
+
+                mouseDown = false;
+                mDownEvent = null;
+            }
+        }
+
+    }
+
+    public void onMouseOut(MouseOutEvent event) {
+        mouseDown = false;
+        mDownEvent = null;
+    }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/VMyDropTarget.java b/tests/src/com/vaadin/tests/dd/VMyDropTarget.java
new file mode 100644 (file)
index 0000000..9bc7920
--- /dev/null
@@ -0,0 +1,62 @@
+package com.vaadin.tests.dd;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.ValueMap;
+import com.vaadin.terminal.gwt.client.ui.dd.AcceptCallback;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager;
+import com.vaadin.terminal.gwt.client.ui.dd.DragEvent;
+import com.vaadin.terminal.gwt.client.ui.dd.DropHandler;
+import com.vaadin.terminal.gwt.client.ui.dd.HasDropHandler;
+import com.vaadin.terminal.gwt.client.ui.dd.DragAndDropManager.DragEventType;
+
+public class VMyDropTarget extends Composite implements HasDropHandler,
+        DropHandler, Paintable {
+
+    private ApplicationConnection client;
+
+    public void dragEnter(DragEvent drag) {
+        DragAndDropManager.get().visitServer(DragEventType.ENTER,
+                new AcceptCallback() {
+                    public void handleResponse(ValueMap responseData) {
+                        // show hints, error messages etc
+                    }
+                });
+    }
+
+    public void dragLeave(DragEvent drag) {
+        // TODO Auto-generated method stub
+    }
+
+    public void dragOver(DragEvent currentDrag) {
+        // TODO Auto-generated method stub
+    }
+
+    public boolean drop(DragEvent drag) {
+        // TODO Auto-generated method stub
+        // return true to tell DDManager do server visit
+        return false;
+    }
+
+    public Paintable getPaintable() {
+        // Drophandler implemented by Paintable itself
+        return this;
+    }
+
+    public DropHandler getDropHandler() {
+        // Drophandler implemented by Paintable itself
+        return this;
+    }
+
+    public ApplicationConnection getApplicationConnection() {
+        return client;
+    }
+
+    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+        this.client = client;
+
+    }
+
+}