com.vaadin.ui.DragAndDropWrapper.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.ui.DragAndDropWrapper.java

Source

/*
 * Copyright 2000-2018 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.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jsoup.nodes.Element;

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.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.server.StreamVariable;
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.shared.ui.draganddropwrapper.DragAndDropWrapperServerRpc;
import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperState;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.dnd.DragSourceExtension;
import com.vaadin.ui.dnd.DropTargetExtension;

/**
 * @author Vaadin Ltd
 * @deprecated Replaced in 8.1 with {@link DragSourceExtension} and
 *             {@link DropTargetExtension}.
 */
@SuppressWarnings("serial")
@Deprecated
public class DragAndDropWrapper extends CustomComponent implements DropTarget, DragSource, LegacyComponent {

    /**
     * @deprecated Since 8.1, will be replaced by FileDropTargetExtension and
     *             FileDropEvent,
     *             https://github.com/vaadin/framework/issues/8891
     */
    @Deprecated
    public class WrapperTransferable extends TransferableImpl {

        private Html5File[] files;

        public WrapperTransferable(Component sourceComponent, Map<String, Object> 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
                            ((Double) rawVariables.get("fs" + i)).longValue(), // size
                            (String) rawVariables.get("ft" + i)); // mime
                    String id = (String) rawVariables.get("fi" + i);
                    files[i] = file;
                    receivers.put(id, new ProxyReceiver(id, file));
                    markAsDirty(); // 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 final DragAndDropWrapperServerRpc rpc = () -> {
        // #19616 RPC to poll the server for changes
    };

    private Map<String, ProxyReceiver> receivers = new HashMap<>();

    public class WrapperTargetDetails extends TargetDetailsImpl {

        public WrapperTargetDetails(Map<String, Object> 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 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"));
        }

    }

    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,

        /**
         * Uses the component defined in
         * {@link #setDragImageComponent(Component)} as the drag image.
         */
        COMPONENT_OTHER,
    }

    private final Map<String, Object> html5DataFlavors = new LinkedHashMap<>();
    private DragStartMode dragStartMode = DragStartMode.NONE;
    private Component dragImageComponent = null;

    private Set<String> sentIds = new HashSet<>();

    /**
     * This is an internal constructor. Use
     * {@link DragAndDropWrapper#DragAndDropWrapper(Component)} instead.
     *
     * @since 7.5.0
     */
    @Deprecated
    public DragAndDropWrapper() {
        super();
        registerRpc(rpc);
    }

    /**
     * Wraps given component in a {@link DragAndDropWrapper}.
     *
     * @param root
     *            the component to be wrapped
     */
    public DragAndDropWrapper(Component root) {
        this();
        setCompositionRoot(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);
        markAsDirty();
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables) {
        // TODO Remove once LegacyComponent is no longer implemented
    }

    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        target.addAttribute(DragAndDropWrapperConstants.DRAG_START_MODE, dragStartMode.ordinal());

        if (dragStartMode.equals(DragStartMode.COMPONENT_OTHER)) {
            if (dragImageComponent != null) {
                target.addAttribute(DragAndDropWrapperConstants.DRAG_START_COMPONENT_ATTRIBUTE,
                        dragImageComponent.getConnectorId());
            } else {
                throw new IllegalArgumentException("DragStartMode.COMPONENT_OTHER set but no component "
                        + "was defined. Please set a component using DragAnd"
                        + "DropWrapper.setDragStartComponent(Component).");
            }
        }
        if (getDropHandler() != null) {
            getDropHandler().getAcceptCriterion().paint(target);
        }
        if (receivers != null && !receivers.isEmpty()) {
            for (Iterator<Entry<String, ProxyReceiver>> it = receivers.entrySet().iterator(); it.hasNext();) {
                Entry<String, ProxyReceiver> entry = it.next();
                String id = entry.getKey();
                ProxyReceiver proxyReceiver = entry.getValue();
                Html5File html5File = proxyReceiver.file;
                if (html5File.getStreamVariable() != null) {
                    if (!sentIds.contains(id)) {
                        target.addVariable(this, "rec-" + id, new ProxyReceiver(id, html5File));

                        /*
                         * if a new batch is requested to be uploaded before the
                         * last one is done, any remaining ids will be replayed.
                         * We want to avoid a new ProxyReceiver to be made since
                         * it'll get a new URL, so we need to keep extra track
                         * on what has been sent.
                         *
                         * See #12330.
                         */
                        sentIds.add(id);

                        // 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;
        markAsDirty();
    }

    @Override
    public TargetDetails translateDropTargetDetails(Map<String, Object> clientVariables) {
        return new WrapperTargetDetails(clientVariables);
    }

    @Override
    public Transferable getTransferable(final Map<String, Object> rawVariables) {
        return new WrapperTransferable(this, rawVariables);
    }

    public void setDragStartMode(DragStartMode dragStartMode) {
        this.dragStartMode = dragStartMode;
        markAsDirty();
    }

    public DragStartMode getDragStartMode() {
        return dragStartMode;
    }

    /**
     * Sets the component that will be used as the drag image. Only used when
     * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
     *
     * @param dragImageComponent
     */
    public void setDragImageComponent(Component dragImageComponent) {
        this.dragImageComponent = dragImageComponent;
        markAsDirty();
    }

    /**
     * Gets the component that will be used as the drag image. Only used when
     * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
     *
     * @return <code>null</code> if no component is set.
     */
    public Component getDragImageComponent() {
        return dragImageComponent;
    }

    final class ProxyReceiver implements StreamVariable {

        private final String id;
        private Html5File file;

        public ProxyReceiver(String id, Html5File file) {
            this.id = id;
            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(id);
            sentIds.remove(id);
            // 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 final 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();
            }

            @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() {

            }
        }

    }

    @Override
    public void readDesign(Element design, DesignContext designContext) {
        super.readDesign(design, designContext);

        for (Element child : design.children()) {
            Component component = designContext.readDesign(child);
            if (getDragStartMode() == DragStartMode.COMPONENT_OTHER && child.hasAttr(":drag-image")) {
                setDragImageComponent(component);
            } else if (getCompositionRoot() == null) {
                setCompositionRoot(component);
            }
        }
    }

    @Override
    public void writeDesign(Element design, DesignContext designContext) {
        super.writeDesign(design, designContext);

        design.appendChild(designContext.createElement(getCompositionRoot()));
        if (getDragStartMode() == DragStartMode.COMPONENT_OTHER) {
            Element child = designContext.createElement(getDragImageComponent());
            child.attr(":drag-image", true);
            design.appendChild(child);
        }
    }

    @Override
    protected DragAndDropWrapperState getState() {
        return (DragAndDropWrapperState) super.getState();
    }

    @Override
    protected DragAndDropWrapperState getState(boolean markAsDirty) {
        return (DragAndDropWrapperState) super.getState(markAsDirty);
    }
}