From: Matti Tahvonen Date: Fri, 29 Jan 2010 08:12:00 +0000 (+0000) Subject: dd X-Git-Tag: 6.7.0.beta1~1988^2~44 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9c57035c34e8228c602b61672587c30b3be2fe1f;p=vaadin-framework.git dd svn changeset:11054/svn branch:6.3_dd --- diff --git a/src/com/vaadin/event/AbstractDropHandler.java b/src/com/vaadin/event/AbstractDropHandler.java new file mode 100644 index 0000000000..da15dc61b6 --- /dev/null +++ b/src/com/vaadin/event/AbstractDropHandler.java @@ -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 index 0000000000..e0048d933d --- /dev/null +++ b/src/com/vaadin/event/ComponentTransferrable.java @@ -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 index 0000000000..8d131eb741 --- /dev/null +++ b/src/com/vaadin/event/DataBindedTransferrable.java @@ -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 index 0000000000..b1bd2a6316 --- /dev/null +++ b/src/com/vaadin/event/DragRequest.java @@ -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 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 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(); + } + responseData.put(key, value); + } + +} diff --git a/src/com/vaadin/event/DropHandler.java b/src/com/vaadin/event/DropHandler.java new file mode 100644 index 0000000000..f44ca15569 --- /dev/null +++ b/src/com/vaadin/event/DropHandler.java @@ -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 index 0000000000..73ce74c392 --- /dev/null +++ b/src/com/vaadin/event/HasDropHandler.java @@ -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 index 0000000000..5ce4adfb8f --- /dev/null +++ b/src/com/vaadin/event/Transferable.java @@ -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 getDataFlawors(); + +} diff --git a/src/com/vaadin/terminal/TransferTranslator.java b/src/com/vaadin/terminal/TransferTranslator.java new file mode 100644 index 0000000000..01d56a73ed --- /dev/null +++ b/src/com/vaadin/terminal/TransferTranslator.java @@ -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. + *

+ * 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 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 index 0000000000..6bdfa7a9bb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VDragDropPane.java @@ -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 index 0000000000..db656829fa --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AbstractDropHandler.java @@ -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 index 0000000000..913cda5a76 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCallback.java @@ -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 index 0000000000..34f9f6dc66 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriteria.java @@ -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 index 0000000000..db4cb200d1 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterion.java @@ -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 criterion = new HashMap(); + 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 index 0000000000..5444cfb7e6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/AcceptCriterionImpl.java @@ -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 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 index 0000000000..8dc17e0f78 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/DragAndDropManager.java @@ -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). + *

+ * 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. + *

+ * Cancels possible existing drag. TODO figure out if this is always a bug + * if one is active. Maybe a good and cheap lifesaver thought. + *

+ * If possible, method automatically detects current {@link DropHandler} and + * fires {@link DropHandler#dragEnter(DragEvent)} event on it. + *

+ * 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 index 0000000000..abed4679ee --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/DragEvent.java @@ -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 index 0000000000..45fbdb31df --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/DropHandler.java @@ -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 index 0000000000..9814a6f6b8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/HasDropHandler.java @@ -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 index 0000000000..230cda8a6f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/Html5DragEvent.java @@ -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 index 0000000000..52ea08fe7e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/Transferable.java @@ -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 getDataFlawors() { + return variables.keySet(); + } + + private final Map variables = new HashMap(); + + /** + * This method should only be called by {@link DragAndDropManager}. + * + * @return data in this Transferable that needs to be moved to server. + */ + public Map 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 index 0000000000..3371c741ae --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -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 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 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 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 td = new HashMap(); + + 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 getDataFlawors() { + return td.keySet(); + } + + }; + } else { + transferable = new Transferable() { + private Map td = new HashMap(); + + public Object getData(String dataFlawor) { + return td.get(dataFlawor); + } + + public void setData(String dataFlawor, Object value) { + td.put(dataFlawor, value); + } + + public Collection 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) 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 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 index 0000000000..293ea8a6ae --- /dev/null +++ b/src/com/vaadin/ui/DragDropPane.java @@ -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 index 0000000000..ede2fec472 --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/AcceptAnythingWindow.java @@ -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 index 0000000000..3b3a9c13a0 --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/AcceptFromComponent.java @@ -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 index 0000000000..01490cd248 --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/CustomDDImplementation.java @@ -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 index 0000000000..9fee73da0c --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/DDTest1.java @@ -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 index 0000000000..6777eade4d --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/DDTest2.java @@ -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 index 0000000000..2be0dc8d7e --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/VMyDragSource.java @@ -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 index 0000000000..9bc7920335 --- /dev/null +++ b/tests/src/com/vaadin/tests/dd/VMyDropTarget.java @@ -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; + + } + +}