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.

DragAndDropWrapper.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.OutputStream;
  18. import java.util.HashMap;
  19. import java.util.HashSet;
  20. import java.util.Iterator;
  21. import java.util.LinkedHashMap;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import java.util.Set;
  25. import org.jsoup.nodes.Element;
  26. import com.vaadin.event.Transferable;
  27. import com.vaadin.event.TransferableImpl;
  28. import com.vaadin.event.dd.DragSource;
  29. import com.vaadin.event.dd.DropHandler;
  30. import com.vaadin.event.dd.DropTarget;
  31. import com.vaadin.event.dd.TargetDetails;
  32. import com.vaadin.event.dd.TargetDetailsImpl;
  33. import com.vaadin.server.PaintException;
  34. import com.vaadin.server.PaintTarget;
  35. import com.vaadin.server.StreamVariable;
  36. import com.vaadin.shared.MouseEventDetails;
  37. import com.vaadin.shared.ui.dd.HorizontalDropLocation;
  38. import com.vaadin.shared.ui.dd.VerticalDropLocation;
  39. import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperConstants;
  40. import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperServerRpc;
  41. import com.vaadin.ui.declarative.DesignContext;
  42. @SuppressWarnings("serial")
  43. public class DragAndDropWrapper extends CustomComponent
  44. implements DropTarget, DragSource, LegacyComponent {
  45. public class WrapperTransferable extends TransferableImpl {
  46. private Html5File[] files;
  47. public WrapperTransferable(Component sourceComponent,
  48. Map<String, Object> rawVariables) {
  49. super(sourceComponent, rawVariables);
  50. Integer fc = (Integer) rawVariables.get("filecount");
  51. if (fc != null) {
  52. files = new Html5File[fc];
  53. for (int i = 0; i < fc; i++) {
  54. Html5File file = new Html5File(
  55. (String) rawVariables.get("fn" + i), // name
  56. ((Double) rawVariables.get("fs" + i)).longValue(), // size
  57. (String) rawVariables.get("ft" + i)); // mime
  58. String id = (String) rawVariables.get("fi" + i);
  59. files[i] = file;
  60. receivers.put(id, new ProxyReceiver(id, file));
  61. markAsDirty(); // paint Receivers
  62. }
  63. }
  64. }
  65. /**
  66. * The component in wrapper that is being dragged or null if the
  67. * transferable is not a component (most likely an html5 drag).
  68. *
  69. * @return
  70. */
  71. public Component getDraggedComponent() {
  72. Component object = (Component) getData("component");
  73. return object;
  74. }
  75. /**
  76. * @return the mouse down event that started the drag and drop operation
  77. */
  78. public MouseEventDetails getMouseDownEvent() {
  79. return MouseEventDetails.deSerialize((String) getData("mouseDown"));
  80. }
  81. public Html5File[] getFiles() {
  82. return files;
  83. }
  84. public String getText() {
  85. String data = (String) getData("Text"); // IE, html5
  86. if (data == null) {
  87. // check for "text/plain" (webkit)
  88. data = (String) getData("text/plain");
  89. }
  90. return data;
  91. }
  92. public String getHtml() {
  93. String data = (String) getData("Html"); // IE, html5
  94. if (data == null) {
  95. // check for "text/plain" (webkit)
  96. data = (String) getData("text/html");
  97. }
  98. return data;
  99. }
  100. }
  101. private final DragAndDropWrapperServerRpc rpc = new DragAndDropWrapperServerRpc() {
  102. @Override
  103. public void poll() {
  104. // #19616 RPC to poll the server for changes
  105. }
  106. };
  107. private Map<String, ProxyReceiver> receivers = new HashMap<String, ProxyReceiver>();
  108. public class WrapperTargetDetails extends TargetDetailsImpl {
  109. public WrapperTargetDetails(Map<String, Object> rawDropData) {
  110. super(rawDropData, DragAndDropWrapper.this);
  111. }
  112. /**
  113. * @return the absolute position of wrapper on the page
  114. */
  115. public Integer getAbsoluteLeft() {
  116. return (Integer) getData("absoluteLeft");
  117. }
  118. /**
  119. *
  120. * @return the absolute position of wrapper on the page
  121. */
  122. public Integer getAbsoluteTop() {
  123. return (Integer) getData("absoluteTop");
  124. }
  125. /**
  126. * @return a detail about the drags vertical position over the wrapper.
  127. */
  128. public VerticalDropLocation getVerticalDropLocation() {
  129. return VerticalDropLocation
  130. .valueOf((String) getData("verticalLocation"));
  131. }
  132. /**
  133. * @return a detail about the drags horizontal position over the
  134. * wrapper.
  135. */
  136. public HorizontalDropLocation getHorizontalDropLocation() {
  137. return HorizontalDropLocation
  138. .valueOf((String) getData("horizontalLocation"));
  139. }
  140. }
  141. public enum DragStartMode {
  142. /**
  143. * {@link DragAndDropWrapper} does not start drag events at all
  144. */
  145. NONE,
  146. /**
  147. * The component on which the drag started will be shown as drag image.
  148. */
  149. COMPONENT,
  150. /**
  151. * The whole wrapper is used as a drag image when dragging.
  152. */
  153. WRAPPER,
  154. /**
  155. * The whole wrapper is used to start an HTML5 drag.
  156. *
  157. * NOTE: In Internet Explorer 6 to 8, this prevents user interactions
  158. * with the wrapper's contents. For example, clicking a button inside
  159. * the wrapper will no longer work.
  160. */
  161. HTML5,
  162. /**
  163. * Uses the component defined in
  164. * {@link #setDragImageComponent(Component)} as the drag image.
  165. */
  166. COMPONENT_OTHER,
  167. }
  168. private final Map<String, Object> html5DataFlavors = new LinkedHashMap<String, Object>();
  169. private DragStartMode dragStartMode = DragStartMode.NONE;
  170. private Component dragImageComponent = null;
  171. private Set<String> sentIds = new HashSet<String>();
  172. /**
  173. * This is an internal constructor. Use
  174. * {@link DragAndDropWrapper#DragAndDropWrapper(Component)} instead.
  175. *
  176. * @since 7.5.0
  177. */
  178. @Deprecated
  179. public DragAndDropWrapper() {
  180. super();
  181. registerRpc(rpc);
  182. }
  183. /**
  184. * Wraps given component in a {@link DragAndDropWrapper}.
  185. *
  186. * @param root
  187. * the component to be wrapped
  188. */
  189. public DragAndDropWrapper(Component root) {
  190. this();
  191. setCompositionRoot(root);
  192. }
  193. /**
  194. * Sets data flavors available in the DragAndDropWrapper is used to start an
  195. * HTML5 style drags. Most commonly the "Text" flavor should be set.
  196. * Multiple data types can be set.
  197. *
  198. * @param type
  199. * the string identifier of the drag "payload". E.g. "Text" or
  200. * "text/html"
  201. * @param value
  202. * the value
  203. */
  204. public void setHTML5DataFlavor(String type, Object value) {
  205. html5DataFlavors.put(type, value);
  206. markAsDirty();
  207. }
  208. @Override
  209. public void changeVariables(Object source, Map<String, Object> variables) {
  210. // TODO Remove once LegacyComponent is no longer implemented
  211. }
  212. @Override
  213. public void paintContent(PaintTarget target) throws PaintException {
  214. target.addAttribute(DragAndDropWrapperConstants.DRAG_START_MODE,
  215. dragStartMode.ordinal());
  216. if (dragStartMode.equals(DragStartMode.COMPONENT_OTHER)) {
  217. if (dragImageComponent != null) {
  218. target.addAttribute(
  219. DragAndDropWrapperConstants.DRAG_START_COMPONENT_ATTRIBUTE,
  220. dragImageComponent.getConnectorId());
  221. } else {
  222. throw new IllegalArgumentException(
  223. "DragStartMode.COMPONENT_OTHER set but no component "
  224. + "was defined. Please set a component using DragAnd"
  225. + "DropWrapper.setDragStartComponent(Component).");
  226. }
  227. }
  228. if (getDropHandler() != null) {
  229. getDropHandler().getAcceptCriterion().paint(target);
  230. }
  231. if (receivers != null && receivers.size() > 0) {
  232. for (Iterator<Entry<String, ProxyReceiver>> it = receivers
  233. .entrySet().iterator(); it.hasNext();) {
  234. Entry<String, ProxyReceiver> entry = it.next();
  235. String id = entry.getKey();
  236. ProxyReceiver proxyReceiver = entry.getValue();
  237. Html5File html5File = proxyReceiver.file;
  238. if (html5File.getStreamVariable() != null) {
  239. if (!sentIds.contains(id)) {
  240. target.addVariable(this, "rec-" + id,
  241. new ProxyReceiver(id, html5File));
  242. /*
  243. * if a new batch is requested to be uploaded before the
  244. * last one is done, any remaining ids will be replayed.
  245. * We want to avoid a new ProxyReceiver to be made since
  246. * it'll get a new URL, so we need to keep extra track
  247. * on what has been sent.
  248. *
  249. * See #12330.
  250. */
  251. sentIds.add(id);
  252. // these are cleaned from receivers once the upload has
  253. // started
  254. }
  255. } else {
  256. // instructs the client side not to send the file
  257. target.addVariable(this, "rec-" + id, (String) null);
  258. // forget the file from subsequent paints
  259. it.remove();
  260. }
  261. }
  262. }
  263. target.addAttribute(DragAndDropWrapperConstants.HTML5_DATA_FLAVORS,
  264. html5DataFlavors);
  265. }
  266. private DropHandler dropHandler;
  267. @Override
  268. public DropHandler getDropHandler() {
  269. return dropHandler;
  270. }
  271. public void setDropHandler(DropHandler dropHandler) {
  272. this.dropHandler = dropHandler;
  273. markAsDirty();
  274. }
  275. @Override
  276. public TargetDetails translateDropTargetDetails(
  277. Map<String, Object> clientVariables) {
  278. return new WrapperTargetDetails(clientVariables);
  279. }
  280. @Override
  281. public Transferable getTransferable(
  282. final Map<String, Object> rawVariables) {
  283. return new WrapperTransferable(this, rawVariables);
  284. }
  285. public void setDragStartMode(DragStartMode dragStartMode) {
  286. this.dragStartMode = dragStartMode;
  287. markAsDirty();
  288. }
  289. public DragStartMode getDragStartMode() {
  290. return dragStartMode;
  291. }
  292. /**
  293. * Sets the component that will be used as the drag image. Only used when
  294. * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
  295. *
  296. * @param dragImageComponent
  297. */
  298. public void setDragImageComponent(Component dragImageComponent) {
  299. this.dragImageComponent = dragImageComponent;
  300. markAsDirty();
  301. }
  302. /**
  303. * Gets the component that will be used as the drag image. Only used when
  304. * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
  305. *
  306. * @return <code>null</code> if no component is set.
  307. */
  308. public Component getDragImageComponent() {
  309. return dragImageComponent;
  310. }
  311. final class ProxyReceiver implements StreamVariable {
  312. private String id;
  313. private Html5File file;
  314. public ProxyReceiver(String id, Html5File file) {
  315. this.id = id;
  316. this.file = file;
  317. }
  318. private boolean listenProgressOfUploadedFile;
  319. @Override
  320. public OutputStream getOutputStream() {
  321. if (file.getStreamVariable() == null) {
  322. return null;
  323. }
  324. return file.getStreamVariable().getOutputStream();
  325. }
  326. @Override
  327. public boolean listenProgress() {
  328. return file.getStreamVariable().listenProgress();
  329. }
  330. @Override
  331. public void onProgress(StreamingProgressEvent event) {
  332. file.getStreamVariable()
  333. .onProgress(new ReceivingEventWrapper(event));
  334. }
  335. @Override
  336. public void streamingStarted(StreamingStartEvent event) {
  337. listenProgressOfUploadedFile = file.getStreamVariable() != null;
  338. if (listenProgressOfUploadedFile) {
  339. file.getStreamVariable()
  340. .streamingStarted(new ReceivingEventWrapper(event));
  341. }
  342. // no need tell to the client about this receiver on next paint
  343. receivers.remove(id);
  344. sentIds.remove(id);
  345. // let the terminal GC the streamvariable and not to accept other
  346. // file uploads to this variable
  347. event.disposeStreamVariable();
  348. }
  349. @Override
  350. public void streamingFinished(StreamingEndEvent event) {
  351. if (listenProgressOfUploadedFile) {
  352. file.getStreamVariable()
  353. .streamingFinished(new ReceivingEventWrapper(event));
  354. }
  355. }
  356. @Override
  357. public void streamingFailed(final StreamingErrorEvent event) {
  358. if (listenProgressOfUploadedFile) {
  359. file.getStreamVariable()
  360. .streamingFailed(new ReceivingEventWrapper(event));
  361. }
  362. }
  363. @Override
  364. public boolean isInterrupted() {
  365. return file.getStreamVariable().isInterrupted();
  366. }
  367. /*
  368. * With XHR2 file posts we can't provide as much information from the
  369. * terminal as with multipart request. This helper class wraps the
  370. * terminal event and provides the lacking information from the
  371. * Html5File.
  372. */
  373. class ReceivingEventWrapper implements StreamingErrorEvent,
  374. StreamingEndEvent, StreamingStartEvent, StreamingProgressEvent {
  375. private StreamingEvent wrappedEvent;
  376. ReceivingEventWrapper(StreamingEvent e) {
  377. wrappedEvent = e;
  378. }
  379. @Override
  380. public String getMimeType() {
  381. return file.getType();
  382. }
  383. @Override
  384. public String getFileName() {
  385. return file.getFileName();
  386. }
  387. @Override
  388. public long getContentLength() {
  389. return file.getFileSize();
  390. }
  391. public StreamVariable getReceiver() {
  392. return ProxyReceiver.this;
  393. }
  394. @Override
  395. public Exception getException() {
  396. if (wrappedEvent instanceof StreamingErrorEvent) {
  397. return ((StreamingErrorEvent) wrappedEvent).getException();
  398. }
  399. return null;
  400. }
  401. @Override
  402. public long getBytesReceived() {
  403. return wrappedEvent.getBytesReceived();
  404. }
  405. /**
  406. * Calling this method has no effect. DD files are receive only once
  407. * anyway.
  408. */
  409. @Override
  410. public void disposeStreamVariable() {
  411. }
  412. }
  413. }
  414. @Override
  415. public void readDesign(Element design, DesignContext designContext) {
  416. super.readDesign(design, designContext);
  417. for (Element child : design.children()) {
  418. Component component = designContext.readDesign(child);
  419. if (getDragStartMode() == DragStartMode.COMPONENT_OTHER
  420. && child.hasAttr(":drag-image")) {
  421. setDragImageComponent(component);
  422. } else if (getCompositionRoot() == null) {
  423. setCompositionRoot(component);
  424. }
  425. }
  426. }
  427. @Override
  428. public void writeDesign(Element design, DesignContext designContext) {
  429. super.writeDesign(design, designContext);
  430. design.appendChild(designContext.createElement(getCompositionRoot()));
  431. if (getDragStartMode() == DragStartMode.COMPONENT_OTHER) {
  432. Element child = designContext
  433. .createElement(getDragImageComponent());
  434. child.attr(":drag-image", true);
  435. design.appendChild(child);
  436. }
  437. }
  438. }