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

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