View Javadoc

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