You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VDragAndDropWrapper.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Map;
  6. import com.google.gwt.core.client.JavaScriptObject;
  7. import com.google.gwt.core.client.JsArrayString;
  8. import com.google.gwt.event.dom.client.MouseDownEvent;
  9. import com.google.gwt.event.dom.client.MouseDownHandler;
  10. import com.google.gwt.user.client.Command;
  11. import com.google.gwt.user.client.DeferredCommand;
  12. import com.google.gwt.user.client.Element;
  13. import com.google.gwt.user.client.ui.Widget;
  14. import com.google.gwt.xhr.client.ReadyStateChangeHandler;
  15. import com.google.gwt.xhr.client.XMLHttpRequest;
  16. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  17. import com.vaadin.terminal.gwt.client.BrowserInfo;
  18. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  19. import com.vaadin.terminal.gwt.client.Paintable;
  20. import com.vaadin.terminal.gwt.client.RenderInformation;
  21. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  22. import com.vaadin.terminal.gwt.client.UIDL;
  23. import com.vaadin.terminal.gwt.client.Util;
  24. import com.vaadin.terminal.gwt.client.ui.dd.DDUtil;
  25. import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation;
  26. import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler;
  27. import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback;
  28. import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
  29. import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent;
  30. import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler;
  31. import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler;
  32. import com.vaadin.terminal.gwt.client.ui.dd.VHtml5DragEvent;
  33. import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File;
  34. import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File.Callback;
  35. import com.vaadin.terminal.gwt.client.ui.dd.VTransferable;
  36. import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
  37. /**
  38. *
  39. * Must have features pending:
  40. *
  41. * drop details: locations + sizes in document hierarchy up to wrapper
  42. *
  43. */
  44. public class VDragAndDropWrapper extends VCustomComponent implements
  45. VHasDropHandler {
  46. private static final String CLASSNAME = "v-ddwrapper";
  47. public VDragAndDropWrapper() {
  48. super();
  49. hookHtml5Events(getElement());
  50. setStyleName(CLASSNAME);
  51. addDomHandler(new MouseDownHandler() {
  52. public void onMouseDown(MouseDownEvent event) {
  53. if (dragStarMode > 0) {
  54. VTransferable transferable = new VTransferable();
  55. transferable.setDragSource(VDragAndDropWrapper.this);
  56. Paintable paintable;
  57. Widget w = Util.findWidget((Element) event.getNativeEvent()
  58. .getEventTarget().cast(), null);
  59. while (w != null && !(w instanceof Paintable)) {
  60. w = w.getParent();
  61. }
  62. paintable = (Paintable) w;
  63. transferable.setData("component", paintable);
  64. VDragEvent startDrag = VDragAndDropManager.get().startDrag(
  65. transferable, event.getNativeEvent(), true);
  66. transferable.setData("mouseDown", new MouseEventDetails(
  67. event.getNativeEvent()).serialize());
  68. if (dragStarMode == WRAPPER) {
  69. startDrag.createDragImage(getElement(), true);
  70. } else {
  71. startDrag.createDragImage(
  72. ((Widget) paintable).getElement(), true);
  73. }
  74. event.preventDefault(); // prevent text selection
  75. }
  76. }
  77. }, MouseDownEvent.getType());
  78. }
  79. private ApplicationConnection client;
  80. private VAbstractDropHandler dropHandler;
  81. private VDragEvent vaadinDragEvent;
  82. private final static int NONE = 0;
  83. private final static int COMPONENT = 1;
  84. private final static int WRAPPER = 2;
  85. private int dragStarMode;
  86. private int filecounter = 0;
  87. private boolean dragLeavPostponed;
  88. @Override
  89. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  90. this.client = client;
  91. super.updateFromUIDL(uidl, client);
  92. if (!uidl.hasAttribute("cached") && !uidl.hasAttribute("hidden")) {
  93. int childCount = uidl.getChildCount();
  94. if (childCount > 1) {
  95. UIDL childUIDL = uidl.getChildUIDL(1);
  96. if (dropHandler == null) {
  97. dropHandler = new CustomDropHandler();
  98. }
  99. dropHandler.updateAcceptRules(childUIDL);
  100. } else {
  101. dropHandler = null;
  102. }
  103. dragStarMode = uidl.getIntAttribute("dragStartMode");
  104. }
  105. }
  106. public boolean html5DragEnter(VHtml5DragEvent event) {
  107. if (dropHandler == null) {
  108. return true;
  109. }
  110. if (dragLeavPostponed) {
  111. // returned quickly back to wrapper
  112. dragLeavPostponed = false;
  113. return false;
  114. }
  115. VTransferable transferable = new VTransferable();
  116. transferable.setDragSource(this);
  117. vaadinDragEvent = VDragAndDropManager.get().startDrag(transferable,
  118. event, false);
  119. VDragAndDropManager.get().setCurrentDropHandler(getDropHandler());
  120. event.preventDefault();
  121. event.stopPropagation();
  122. return false;
  123. }
  124. public boolean html5DragLeave(VHtml5DragEvent event) {
  125. if (dropHandler == null) {
  126. return true;
  127. }
  128. dragLeavPostponed = true;
  129. DeferredCommand.addCommand(new Command() {
  130. public void execute() {
  131. // Yes, dragleave happens before drop. Makes no sense to me.
  132. // IMO shouldn't fire leave at all if drop happens (I guess this
  133. // is what IE does).
  134. // In Vaadin we fire it only if drop did not happen.
  135. if (dragLeavPostponed
  136. && vaadinDragEvent != null
  137. && VDragAndDropManager.get().getCurrentDropHandler() == getDropHandler()) {
  138. VDragAndDropManager.get().interruptDrag();
  139. }
  140. dragLeavPostponed = false;
  141. }
  142. });
  143. event.preventDefault();
  144. event.stopPropagation();
  145. return false;
  146. }
  147. public boolean html5DragOver(VHtml5DragEvent event) {
  148. if (dropHandler == null) {
  149. return true;
  150. }
  151. vaadinDragEvent.setCurrentGwtEvent(event);
  152. getDropHandler().dragOver(vaadinDragEvent);
  153. // needed to be set for Safari, otherwise drop will not happen
  154. if (BrowserInfo.get().isWebkit()) {
  155. String s = event.getEffectAllowed();
  156. if ("all".equals(s) || s.contains("opy")) {
  157. event.setDragEffect("copy");
  158. } else {
  159. event.setDragEffect(s);
  160. }
  161. }
  162. event.preventDefault();
  163. event.stopPropagation();
  164. return false;
  165. }
  166. public boolean html5DragDrop(VHtml5DragEvent event) {
  167. if (dropHandler == null || !currentlyValid) {
  168. return true;
  169. }
  170. VTransferable transferable = vaadinDragEvent.getTransferable();
  171. JsArrayString types = event.getTypes();
  172. for (int i = 0; i < types.length(); i++) {
  173. String type = types.get(i);
  174. if (isAcceptedType(type)) {
  175. String data = event.getDataAsText(type);
  176. if (data != null) {
  177. transferable.setData(type, data);
  178. }
  179. }
  180. }
  181. int fileCount = event.getFileCount();
  182. if (fileCount > 0) {
  183. transferable.setData("filecount", fileCount);
  184. for (int i = 0; i < fileCount; i++) {
  185. final int fileId = filecounter++;
  186. final VHtml5File file = event.getFile(i);
  187. transferable.setData("fi" + i, "" + fileId);
  188. transferable.setData("fn" + i, file.getName());
  189. transferable.setData("ft" + i, file.getType());
  190. transferable.setData("fs" + i, file.getSize());
  191. postFile(fileId, file);
  192. }
  193. }
  194. VDragAndDropManager.get().endDrag();
  195. vaadinDragEvent = null;
  196. event.preventDefault();
  197. event.stopPropagation();
  198. return false;
  199. }
  200. protected String[] acceptedTypes = new String[] { "Text", "Url",
  201. "text/html", "text/plain", "text/rtf" };
  202. private boolean isAcceptedType(String type) {
  203. for (String t : acceptedTypes) {
  204. if (t.equals(type)) {
  205. return true;
  206. }
  207. }
  208. return false;
  209. }
  210. static class ExtendedXHR extends XMLHttpRequest {
  211. protected ExtendedXHR() {
  212. }
  213. public final native void sendBinary(JavaScriptObject data)
  214. /*-{
  215. //this.overrideMimeType('text/plain; charset=x-user-defined-binary');
  216. this.sendAsBinary(data);
  217. }-*/;
  218. }
  219. /**
  220. * Currently supports only FF36 as no other browser supports natively File
  221. * api.
  222. *
  223. * @param fileId
  224. * @param data
  225. */
  226. private void postFile(final int fileId, final VHtml5File file) {
  227. DeferredCommand.addCommand(new Command() {
  228. public void execute() {
  229. /*
  230. * File contents is sent deferred to allow quick reaction on GUI
  231. * although file upload may last long.
  232. */
  233. file.readAsBinary(new Callback() {
  234. public void handleFile(final JavaScriptObject object) {
  235. ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
  236. .create();
  237. String name = "XHRFILE" + getPid() + "." + fileId;
  238. extendedXHR
  239. .setOnReadyStateChange(new ReadyStateChangeHandler() {
  240. public void onReadyStateChange(
  241. XMLHttpRequest xhr) {
  242. if (xhr.getReadyState() == XMLHttpRequest.DONE) {
  243. client.sendPendingVariableChanges();
  244. xhr.clearOnReadyStateChange();
  245. }
  246. }
  247. });
  248. extendedXHR.open("POST", client.getAppUri());
  249. multipartSend(extendedXHR, object, name);
  250. }
  251. });
  252. }
  253. });
  254. }
  255. private String getPid() {
  256. return client.getPid(this);
  257. }
  258. private native void multipartSend(JavaScriptObject xhr,
  259. JavaScriptObject data, String name)
  260. /*-{
  261. var boundaryString = "------------------------------------------VAADINXHRFILEUPLOAD";
  262. var boundary = "--" + boundaryString;
  263. var CRLF = "\r\n";
  264. xhr.setRequestHeader("Content-type", "multipart/form-data; boundary=\"" + boundaryString + "\"");
  265. var requestBody = boundary
  266. + CRLF
  267. + "Content-Disposition: form-data; name=\""+name+"\"; filename=\"file\""
  268. + CRLF
  269. + "Content-Type: application/octet-stream" // hard coded, type sent separately
  270. + CRLF + CRLF + data.target.result + CRLF + boundary + "--" + CRLF;
  271. xhr.setRequestHeader("Content-Length", requestBody.length);
  272. xhr.sendAsBinary(requestBody);
  273. }-*/;
  274. public VDropHandler getDropHandler() {
  275. return dropHandler;
  276. }
  277. protected VerticalDropLocation verticalDropLocation;
  278. protected HorizontalDropLocation horizontalDropLocation;
  279. private VerticalDropLocation emphasizedVDrop;
  280. private HorizontalDropLocation emphasizedHDrop;
  281. /**
  282. * Flag used by html5 dd
  283. */
  284. private boolean currentlyValid;
  285. private static final String OVER_STYLE = "v-ddwrapper-over";
  286. public class CustomDropHandler extends VAbstractDropHandler {
  287. @Override
  288. public void dragEnter(VDragEvent drag) {
  289. updateDropDetails(drag);
  290. currentlyValid = false;
  291. super.dragEnter(drag);
  292. }
  293. @Override
  294. public void dragLeave(VDragEvent drag) {
  295. deEmphasis(true);
  296. dragLeavPostponed = false;
  297. }
  298. @Override
  299. public void dragOver(final VDragEvent drag) {
  300. boolean detailsChanged = updateDropDetails(drag);
  301. if (detailsChanged) {
  302. currentlyValid = false;
  303. validate(new VAcceptCallback() {
  304. public void accepted(VDragEvent event) {
  305. dragAccepted(drag);
  306. }
  307. }, drag);
  308. }
  309. }
  310. @Override
  311. public boolean drop(VDragEvent drag) {
  312. deEmphasis(true);
  313. Map<String, Object> dd = drag.getDropDetails();
  314. // this is absolute layout based, and we may want to set
  315. // component
  316. // relatively to where the drag ended.
  317. // need to add current location of the drop area
  318. int absoluteLeft = getAbsoluteLeft();
  319. int absoluteTop = getAbsoluteTop();
  320. dd.put("absoluteLeft", absoluteLeft);
  321. dd.put("absoluteTop", absoluteTop);
  322. if (verticalDropLocation != null) {
  323. dd.put("verticalLocation", verticalDropLocation.toString());
  324. dd.put("horizontalLocation", horizontalDropLocation.toString());
  325. }
  326. return super.drop(drag);
  327. }
  328. @Override
  329. protected void dragAccepted(VDragEvent drag) {
  330. currentlyValid = true;
  331. emphasis(drag);
  332. }
  333. @Override
  334. public Paintable getPaintable() {
  335. return VDragAndDropWrapper.this;
  336. }
  337. public ApplicationConnection getApplicationConnection() {
  338. return client;
  339. }
  340. }
  341. /**
  342. * Prototype code, memory leak risk.
  343. *
  344. * @param el
  345. */
  346. private native void hookHtml5Events(Element el)
  347. /*-{
  348. var me = this;
  349. if(el.addEventListener) {
  350. el.addEventListener("dragenter", function(ev) {
  351. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  352. }, false);
  353. el.addEventListener("dragleave", function(ev) {
  354. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  355. }, false);
  356. el.addEventListener("dragover", function(ev) {
  357. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  358. }, false);
  359. el.addEventListener("drop", function(ev) {
  360. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  361. }, false);
  362. } else {
  363. el.attachEvent("ondragenter", function(ev) {
  364. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  365. });
  366. el.attachEvent("ondragleave", function(ev) {
  367. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  368. });
  369. el.attachEvent("ondragover", function(ev) {
  370. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  371. });
  372. el.attachEvent("ondrop", function(ev) {
  373. return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev);
  374. });
  375. }
  376. }-*/;
  377. public boolean updateDropDetails(VDragEvent drag) {
  378. VerticalDropLocation oldVL = verticalDropLocation;
  379. verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(),
  380. drag.getCurrentGwtEvent().getClientY(), 0.2);
  381. drag.getDropDetails().put("verticalLocation",
  382. verticalDropLocation.toString());
  383. HorizontalDropLocation oldHL = horizontalDropLocation;
  384. horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(),
  385. drag.getCurrentGwtEvent().getClientX(), 0.2);
  386. drag.getDropDetails().put("horizontalLocation",
  387. horizontalDropLocation.toString());
  388. if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) {
  389. return true;
  390. } else {
  391. return false;
  392. }
  393. }
  394. protected void deEmphasis(boolean doLayout) {
  395. Size size = null;
  396. if (doLayout) {
  397. size = new RenderInformation.Size(getOffsetWidth(),
  398. getOffsetHeight());
  399. }
  400. if (emphasizedVDrop != null) {
  401. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false);
  402. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
  403. + emphasizedVDrop.toString().toLowerCase(), false);
  404. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
  405. + emphasizedHDrop.toString().toLowerCase(), false);
  406. }
  407. if (doLayout) {
  408. handleVaadinRelatedSizeChange(size);
  409. }
  410. }
  411. protected void emphasis(VDragEvent drag) {
  412. Size size = new RenderInformation.Size(getOffsetWidth(),
  413. getOffsetHeight());
  414. deEmphasis(false);
  415. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true);
  416. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
  417. + verticalDropLocation.toString().toLowerCase(), true);
  418. VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
  419. + horizontalDropLocation.toString().toLowerCase(), true);
  420. emphasizedVDrop = verticalDropLocation;
  421. emphasizedHDrop = horizontalDropLocation;
  422. // TODO build (to be an example) an emphasis mode where drag image
  423. // is fitted before or after the content
  424. handleVaadinRelatedSizeChange(size);
  425. }
  426. protected void handleVaadinRelatedSizeChange(Size originalSize) {
  427. if (isDynamicHeight() || isDynamicWidth()) {
  428. if (!originalSize.equals(new RenderInformation.Size(
  429. getOffsetWidth(), getOffsetHeight()))) {
  430. Util.notifyParentOfSizeChange(VDragAndDropWrapper.this, false);
  431. }
  432. }
  433. client.handleComponentRelativeSize(VDragAndDropWrapper.this);
  434. Util.notifyParentOfSizeChange(this, false);
  435. }
  436. }