/* * Copyright 2011 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.vaadin.ui; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import com.vaadin.event.Transferable; import com.vaadin.event.TransferableImpl; import com.vaadin.event.dd.DragSource; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.DropTarget; import com.vaadin.event.dd.TargetDetails; import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.dd.HorizontalDropLocation; import com.vaadin.shared.ui.dd.VerticalDropLocation; import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperConstants; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.Vaadin6Component; @SuppressWarnings("serial") public class DragAndDropWrapper extends CustomComponent implements DropTarget, DragSource, Vaadin6Component { public class WrapperTransferable extends TransferableImpl { private Html5File[] files; public WrapperTransferable(Component sourceComponent, Map rawVariables) { super(sourceComponent, rawVariables); Integer fc = (Integer) rawVariables.get("filecount"); if (fc != null) { files = new Html5File[fc]; for (int i = 0; i < fc; i++) { Html5File file = new Html5File( (String) rawVariables.get("fn" + i), // name (Integer) rawVariables.get("fs" + i), // size (String) rawVariables.get("ft" + i)); // mime String id = (String) rawVariables.get("fi" + i); files[i] = file; receivers.put(id, file); requestRepaint(); // paint Receivers } } } /** * The component in wrapper that is being dragged or null if the * transferable is not a component (most likely an html5 drag). * * @return */ public Component getDraggedComponent() { Component object = (Component) getData("component"); return object; } /** * @return the mouse down event that started the drag and drop operation */ public MouseEventDetails getMouseDownEvent() { return MouseEventDetails.deSerialize((String) getData("mouseDown")); } public Html5File[] getFiles() { return files; } public String getText() { String data = (String) getData("Text"); // IE, html5 if (data == null) { // check for "text/plain" (webkit) data = (String) getData("text/plain"); } return data; } public String getHtml() { String data = (String) getData("Html"); // IE, html5 if (data == null) { // check for "text/plain" (webkit) data = (String) getData("text/html"); } return data; } } private Map receivers = new HashMap(); public class WrapperTargetDetails extends TargetDetailsImpl { public WrapperTargetDetails(Map rawDropData) { super(rawDropData, DragAndDropWrapper.this); } /** * @return the absolute position of wrapper on the page */ public Integer getAbsoluteLeft() { return (Integer) getData("absoluteLeft"); } /** * * @return the absolute position of wrapper on the page */ public Integer getAbsoluteTop() { return (Integer) getData("absoluteTop"); } /** * @return details about the actual event that caused the event details. * Practically mouse move or mouse up. */ public MouseEventDetails getMouseEvent() { return MouseEventDetails .deSerialize((String) getData("mouseEvent")); } /** * @return a detail about the drags vertical position over the wrapper. */ public VerticalDropLocation getVerticalDropLocation() { return VerticalDropLocation .valueOf((String) getData("verticalLocation")); } /** * @return a detail about the drags horizontal position over the * wrapper. */ public HorizontalDropLocation getHorizontalDropLocation() { return HorizontalDropLocation .valueOf((String) getData("horizontalLocation")); } /** * @deprecated use {@link #getVerticalDropLocation()} instead */ @Deprecated public VerticalDropLocation verticalDropLocation() { return getVerticalDropLocation(); } /** * @deprecated use {@link #getHorizontalDropLocation()} instead */ @Deprecated public HorizontalDropLocation horizontalDropLocation() { return getHorizontalDropLocation(); } } public enum DragStartMode { /** * {@link DragAndDropWrapper} does not start drag events at all */ NONE, /** * The component on which the drag started will be shown as drag image. */ COMPONENT, /** * The whole wrapper is used as a drag image when dragging. */ WRAPPER, /** * The whole wrapper is used to start an HTML5 drag. * * NOTE: In Internet Explorer 6 to 8, this prevents user interactions * with the wrapper's contents. For example, clicking a button inside * the wrapper will no longer work. */ HTML5, } private final Map html5DataFlavors = new LinkedHashMap(); private DragStartMode dragStartMode = DragStartMode.NONE; /** * Wraps given component in a {@link DragAndDropWrapper}. * * @param root * the component to be wrapped */ public DragAndDropWrapper(Component root) { super(root); } /** * Sets data flavors available in the DragAndDropWrapper is used to start an * HTML5 style drags. Most commonly the "Text" flavor should be set. * Multiple data types can be set. * * @param type * the string identifier of the drag "payload". E.g. "Text" or * "text/html" * @param value * the value */ public void setHTML5DataFlavor(String type, Object value) { html5DataFlavors.put(type, value); requestRepaint(); } @Override public void changeVariables(Object source, Map variables) { // TODO Remove once Vaadin6Component is no longer implemented } @Override public void paintContent(PaintTarget target) throws PaintException { target.addAttribute(DragAndDropWrapperConstants.DRAG_START_MODE, dragStartMode.ordinal()); if (getDropHandler() != null) { getDropHandler().getAcceptCriterion().paint(target); } if (receivers != null && receivers.size() > 0) { for (Iterator> it = receivers.entrySet() .iterator(); it.hasNext();) { Entry entry = it.next(); String id = entry.getKey(); Html5File html5File = entry.getValue(); if (html5File.getStreamVariable() != null) { target.addVariable(this, "rec-" + id, new ProxyReceiver( html5File)); // these are cleaned from receivers once the upload has // started } else { // instructs the client side not to send the file target.addVariable(this, "rec-" + id, (String) null); // forget the file from subsequent paints it.remove(); } } } target.addAttribute(DragAndDropWrapperConstants.HTML5_DATA_FLAVORS, html5DataFlavors); } private DropHandler dropHandler; @Override public DropHandler getDropHandler() { return dropHandler; } public void setDropHandler(DropHandler dropHandler) { this.dropHandler = dropHandler; requestRepaint(); } @Override public TargetDetails translateDropTargetDetails( Map clientVariables) { return new WrapperTargetDetails(clientVariables); } @Override public Transferable getTransferable(final Map rawVariables) { return new WrapperTransferable(this, rawVariables); } public void setDragStartMode(DragStartMode dragStartMode) { this.dragStartMode = dragStartMode; requestRepaint(); } public DragStartMode getDragStartMode() { return dragStartMode; } final class ProxyReceiver implements StreamVariable { private Html5File file; public ProxyReceiver(Html5File file) { this.file = file; } private boolean listenProgressOfUploadedFile; @Override public OutputStream getOutputStream() { if (file.getStreamVariable() == null) { return null; } return file.getStreamVariable().getOutputStream(); } @Override public boolean listenProgress() { return file.getStreamVariable().listenProgress(); } @Override public void onProgress(StreamingProgressEvent event) { file.getStreamVariable().onProgress( new ReceivingEventWrapper(event)); } @Override public void streamingStarted(StreamingStartEvent event) { listenProgressOfUploadedFile = file.getStreamVariable() != null; if (listenProgressOfUploadedFile) { file.getStreamVariable().streamingStarted( new ReceivingEventWrapper(event)); } // no need tell to the client about this receiver on next paint receivers.remove(file); // let the terminal GC the streamvariable and not to accept other // file uploads to this variable event.disposeStreamVariable(); } @Override public void streamingFinished(StreamingEndEvent event) { if (listenProgressOfUploadedFile) { file.getStreamVariable().streamingFinished( new ReceivingEventWrapper(event)); } } @Override public void streamingFailed(final StreamingErrorEvent event) { if (listenProgressOfUploadedFile) { file.getStreamVariable().streamingFailed( new ReceivingEventWrapper(event)); } } @Override public boolean isInterrupted() { return file.getStreamVariable().isInterrupted(); } /* * With XHR2 file posts we can't provide as much information from the * terminal as with multipart request. This helper class wraps the * terminal event and provides the lacking information from the * Html5File. */ class ReceivingEventWrapper implements StreamingErrorEvent, StreamingEndEvent, StreamingStartEvent, StreamingProgressEvent { private StreamingEvent wrappedEvent; ReceivingEventWrapper(StreamingEvent e) { wrappedEvent = e; } @Override public String getMimeType() { return file.getType(); } @Override public String getFileName() { return file.getFileName(); } @Override public long getContentLength() { return file.getFileSize(); } public StreamVariable getReceiver() { return ProxyReceiver.this; } @Override public Exception getException() { if (wrappedEvent instanceof StreamingErrorEvent) { return ((StreamingErrorEvent) wrappedEvent).getException(); } return null; } @Override public long getBytesReceived() { return wrappedEvent.getBytesReceived(); } /** * Calling this method has no effect. DD files are receive only once * anyway. */ @Override public void disposeStreamVariable() { } } } }