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