Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

DragAndDropWrapper.java 16KB

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