| /* |
| * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package java.awt.dnd; |
| |
| import java.awt.AWTError; |
| import java.awt.Component; |
| import java.awt.Cursor; |
| import java.awt.Image; |
| import java.awt.Point; |
| import java.awt.Toolkit; |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| import java.awt.dnd.peer.DragSourceContextPeer; |
| import java.io.IOException; |
| import java.io.InvalidObjectException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.util.TooManyListenersException; |
| |
| import sun.awt.AWTAccessor; |
| import sun.awt.ComponentFactory; |
| |
| /** |
| * The {@code DragSourceContext} class is responsible for managing the |
| * initiator side of the Drag and Drop protocol. In particular, it is responsible |
| * for managing drag event notifications to the |
| * {@linkplain DragSourceListener DragSourceListeners} |
| * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the |
| * {@link Transferable} representing the source data for the drag operation. |
| * <p> |
| * Note that the {@code DragSourceContext} itself |
| * implements the {@code DragSourceListener} and |
| * {@code DragSourceMotionListener} interfaces. |
| * This is to allow the platform peer |
| * (the {@link DragSourceContextPeer} instance) |
| * created by the {@link DragSource} to notify |
| * the {@code DragSourceContext} of |
| * state changes in the ongoing operation. This allows the |
| * {@code DragSourceContext} object to interpose |
| * itself between the platform and the |
| * listeners provided by the initiator of the drag operation. |
| * <p> |
| * <a id="defaultCursor"></a> |
| * By default, {@code DragSourceContext} sets the cursor as appropriate |
| * for the current state of the drag and drop operation. For example, if |
| * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action}, |
| * and the pointer is over a target that accepts |
| * the move action, the default move cursor is shown. When |
| * the pointer is over an area that does not accept the transfer, |
| * the default "no drop" cursor is shown. |
| * <p> |
| * This default handling mechanism is disabled when a custom cursor is set |
| * by the {@link #setCursor} method. When the default handling is disabled, |
| * it becomes the responsibility |
| * of the developer to keep the cursor up to date, by listening |
| * to the {@code DragSource} events and calling the {@code setCursor()} method. |
| * Alternatively, you can provide custom cursor behavior by providing |
| * custom implementations of the {@code DragSource} |
| * and the {@code DragSourceContext} classes. |
| * |
| * @see DragSourceListener |
| * @see DragSourceMotionListener |
| * @see DnDConstants |
| * @since 1.2 |
| */ |
| |
| public class DragSourceContext |
| implements DragSourceListener, DragSourceMotionListener, Serializable { |
| |
| private static final long serialVersionUID = -115407898692194719L; |
| |
| // used by updateCurrentCursor |
| |
| /** |
| * An {@code int} used by updateCurrentCursor() |
| * indicating that the {@code Cursor} should change |
| * to the default (no drop) {@code Cursor}. |
| */ |
| protected static final int DEFAULT = 0; |
| |
| /** |
| * An {@code int} used by updateCurrentCursor() |
| * indicating that the {@code Cursor} |
| * has entered a {@code DropTarget}. |
| */ |
| protected static final int ENTER = 1; |
| |
| /** |
| * An {@code int} used by updateCurrentCursor() |
| * indicating that the {@code Cursor} is |
| * over a {@code DropTarget}. |
| */ |
| protected static final int OVER = 2; |
| |
| /** |
| * An {@code int} used by updateCurrentCursor() |
| * indicating that the user operation has changed. |
| */ |
| |
| protected static final int CHANGED = 3; |
| |
| static { |
| AWTAccessor.setDragSourceContextAccessor(dsc -> dsc.peer); |
| } |
| |
| /** |
| * Called from {@code DragSource}, this constructor creates a new |
| * {@code DragSourceContext} given the |
| * {@code DragSourceContextPeer} for this Drag, the |
| * {@code DragGestureEvent} that triggered the Drag, the initial |
| * {@code Cursor} to use for the Drag, an (optional) |
| * {@code Image} to display while the Drag is taking place, the offset |
| * of the {@code Image} origin from the hotspot at the instant of the |
| * triggering event, the {@code Transferable} subject data, and the |
| * {@code DragSourceListener} to use during the Drag and Drop |
| * operation. |
| * <br> |
| * If {@code DragSourceContextPeer} is {@code null} |
| * {@code NullPointerException} is thrown. |
| * <br> |
| * If {@code DragGestureEvent} is {@code null} |
| * {@code NullPointerException} is thrown. |
| * <br> |
| * If {@code Cursor} is {@code null} no exception is thrown and |
| * the default drag cursor behavior is activated for this drag operation. |
| * <br> |
| * If {@code Image} is {@code null} no exception is thrown. |
| * <br> |
| * If {@code Image} is not {@code null} and the offset is |
| * {@code null NullPointerException} is thrown. |
| * <br> |
| * If {@code Transferable} is {@code null} |
| * {@code NullPointerException} is thrown. |
| * <br> |
| * If {@code DragSourceListener} is {@code null} no exception |
| * is thrown. |
| * |
| * @param trigger the triggering event |
| * @param dragCursor the initial {@code Cursor} for this drag operation |
| * or {@code null} for the default cursor handling; |
| * see <a href="DragSourceContext.html#defaultCursor">class level documentation</a> |
| * for more details on the cursor handling mechanism during drag and drop |
| * @param dragImage the {@code Image} to drag (or {@code null}) |
| * @param offset the offset of the image origin from the hotspot at the |
| * instant of the triggering event |
| * @param t the {@code Transferable} |
| * @param dsl the {@code DragSourceListener} |
| * |
| * @throws IllegalArgumentException if the {@code Component} associated |
| * with the trigger event is {@code null}. |
| * @throws IllegalArgumentException if the {@code DragSource} for the |
| * trigger event is {@code null}. |
| * @throws IllegalArgumentException if the drag action for the |
| * trigger event is {@code DnDConstants.ACTION_NONE}. |
| * @throws IllegalArgumentException if the source actions for the |
| * {@code DragGestureRecognizer} associated with the trigger |
| * event are equal to {@code DnDConstants.ACTION_NONE}. |
| * @throws NullPointerException if dscp, trigger, or t are null, or |
| * if dragImage is non-null and offset is null |
| */ |
| public DragSourceContext(DragGestureEvent trigger, Cursor dragCursor, |
| Image dragImage, Point offset, Transferable t, |
| DragSourceListener dsl) { |
| Toolkit toolkit = Toolkit.getDefaultToolkit(); |
| if (!(toolkit instanceof ComponentFactory)) { |
| throw new AWTError("Unsupported toolkit: " + toolkit); |
| } |
| DragSourceContextPeer dscp = ((ComponentFactory) toolkit). |
| createDragSourceContextPeer(trigger); |
| |
| if (dscp == null) { |
| throw new NullPointerException("DragSourceContextPeer"); |
| } |
| |
| if (trigger == null) { |
| throw new NullPointerException("Trigger"); |
| } |
| |
| if (trigger.getDragSource() == null) { |
| throw new IllegalArgumentException("DragSource"); |
| } |
| |
| if (trigger.getComponent() == null) { |
| throw new IllegalArgumentException("Component"); |
| } |
| |
| if (trigger.getSourceAsDragGestureRecognizer().getSourceActions() == |
| DnDConstants.ACTION_NONE) { |
| throw new IllegalArgumentException("source actions"); |
| } |
| |
| if (trigger.getDragAction() == DnDConstants.ACTION_NONE) { |
| throw new IllegalArgumentException("no drag action"); |
| } |
| |
| if (t == null) { |
| throw new NullPointerException("Transferable"); |
| } |
| |
| if (dragImage != null && offset == null) { |
| throw new NullPointerException("offset"); |
| } |
| |
| peer = dscp; |
| this.trigger = trigger; |
| cursor = dragCursor; |
| transferable = t; |
| listener = dsl; |
| sourceActions = |
| trigger.getSourceAsDragGestureRecognizer().getSourceActions(); |
| |
| useCustomCursor = (dragCursor != null); |
| |
| updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); |
| } |
| |
| /** |
| * Returns the {@code DragSource} |
| * that instantiated this {@code DragSourceContext}. |
| * |
| * @return the {@code DragSource} that |
| * instantiated this {@code DragSourceContext} |
| */ |
| |
| public DragSource getDragSource() { return trigger.getDragSource(); } |
| |
| /** |
| * Returns the {@code Component} associated with this |
| * {@code DragSourceContext}. |
| * |
| * @return the {@code Component} that started the drag |
| */ |
| |
| public Component getComponent() { return trigger.getComponent(); } |
| |
| /** |
| * Returns the {@code DragGestureEvent} |
| * that initially triggered the drag. |
| * |
| * @return the Event that triggered the drag |
| */ |
| |
| public DragGestureEvent getTrigger() { return trigger; } |
| |
| /** |
| * Returns a bitwise mask of {@code DnDConstants} that |
| * represent the set of drop actions supported by the drag source for the |
| * drag operation associated with this {@code DragSourceContext}. |
| * |
| * @return the drop actions supported by the drag source |
| */ |
| public int getSourceActions() { |
| return sourceActions; |
| } |
| |
| /** |
| * Sets the custom cursor for this drag operation to the specified |
| * {@code Cursor}. If the specified {@code Cursor} |
| * is {@code null}, the default drag cursor behavior is |
| * activated for this drag operation, otherwise it is deactivated. |
| * |
| * @param c the initial {@code Cursor} for this drag operation, |
| * or {@code null} for the default cursor handling; |
| * see {@linkplain Cursor class |
| * level documentation} for more details |
| * on the cursor handling during drag and drop |
| * |
| */ |
| |
| public synchronized void setCursor(Cursor c) { |
| useCustomCursor = (c != null); |
| setCursorImpl(c); |
| } |
| |
| /** |
| * Returns the current custom drag {@code Cursor}. |
| * |
| * @return the current custom drag {@code Cursor}, if it was set |
| * otherwise returns {@code null}. |
| * @see #setCursor |
| */ |
| |
| public Cursor getCursor() { return cursor; } |
| |
| /** |
| * Add a {@code DragSourceListener} to this |
| * {@code DragSourceContext} if one has not already been added. |
| * If a {@code DragSourceListener} already exists, |
| * this method throws a {@code TooManyListenersException}. |
| * |
| * @param dsl the {@code DragSourceListener} to add. |
| * Note that while {@code null} is not prohibited, |
| * it is not acceptable as a parameter. |
| * |
| * @throws TooManyListenersException if |
| * a {@code DragSourceListener} has already been added |
| */ |
| |
| public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException { |
| if (dsl == null) return; |
| |
| if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener"); |
| |
| if (listener != null) |
| throw new TooManyListenersException(); |
| else |
| listener = dsl; |
| } |
| |
| /** |
| * Removes the specified {@code DragSourceListener} |
| * from this {@code DragSourceContext}. |
| * |
| * @param dsl the {@code DragSourceListener} to remove; |
| * note that while {@code null} is not prohibited, |
| * it is not acceptable as a parameter |
| */ |
| |
| public synchronized void removeDragSourceListener(DragSourceListener dsl) { |
| if (listener != null && listener.equals(dsl)) { |
| listener = null; |
| } else |
| throw new IllegalArgumentException(); |
| } |
| |
| /** |
| * Notifies the peer that the {@code Transferable}'s |
| * {@code DataFlavor}s have changed. |
| */ |
| |
| public void transferablesFlavorsChanged() { |
| if (peer != null) peer.transferablesFlavorsChanged(); |
| } |
| |
| /** |
| * Calls {@code dragEnter} on the |
| * {@code DragSourceListener}s registered with this |
| * {@code DragSourceContext} and with the associated |
| * {@code DragSource}, and passes them the specified |
| * {@code DragSourceDragEvent}. |
| * |
| * @param dsde the {@code DragSourceDragEvent} |
| */ |
| public void dragEnter(DragSourceDragEvent dsde) { |
| DragSourceListener dsl = listener; |
| if (dsl != null) { |
| dsl.dragEnter(dsde); |
| } |
| getDragSource().processDragEnter(dsde); |
| |
| updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER); |
| } |
| |
| /** |
| * Calls {@code dragOver} on the |
| * {@code DragSourceListener}s registered with this |
| * {@code DragSourceContext} and with the associated |
| * {@code DragSource}, and passes them the specified |
| * {@code DragSourceDragEvent}. |
| * |
| * @param dsde the {@code DragSourceDragEvent} |
| */ |
| public void dragOver(DragSourceDragEvent dsde) { |
| DragSourceListener dsl = listener; |
| if (dsl != null) { |
| dsl.dragOver(dsde); |
| } |
| getDragSource().processDragOver(dsde); |
| |
| updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER); |
| } |
| |
| /** |
| * Calls {@code dragExit} on the |
| * {@code DragSourceListener}s registered with this |
| * {@code DragSourceContext} and with the associated |
| * {@code DragSource}, and passes them the specified |
| * {@code DragSourceEvent}. |
| * |
| * @param dse the {@code DragSourceEvent} |
| */ |
| public void dragExit(DragSourceEvent dse) { |
| DragSourceListener dsl = listener; |
| if (dsl != null) { |
| dsl.dragExit(dse); |
| } |
| getDragSource().processDragExit(dse); |
| |
| updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT); |
| } |
| |
| /** |
| * Calls {@code dropActionChanged} on the |
| * {@code DragSourceListener}s registered with this |
| * {@code DragSourceContext} and with the associated |
| * {@code DragSource}, and passes them the specified |
| * {@code DragSourceDragEvent}. |
| * |
| * @param dsde the {@code DragSourceDragEvent} |
| */ |
| public void dropActionChanged(DragSourceDragEvent dsde) { |
| DragSourceListener dsl = listener; |
| if (dsl != null) { |
| dsl.dropActionChanged(dsde); |
| } |
| getDragSource().processDropActionChanged(dsde); |
| |
| updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED); |
| } |
| |
| /** |
| * Calls {@code dragDropEnd} on the |
| * {@code DragSourceListener}s registered with this |
| * {@code DragSourceContext} and with the associated |
| * {@code DragSource}, and passes them the specified |
| * {@code DragSourceDropEvent}. |
| * |
| * @param dsde the {@code DragSourceDropEvent} |
| */ |
| public void dragDropEnd(DragSourceDropEvent dsde) { |
| DragSourceListener dsl = listener; |
| if (dsl != null) { |
| dsl.dragDropEnd(dsde); |
| } |
| getDragSource().processDragDropEnd(dsde); |
| } |
| |
| /** |
| * Calls {@code dragMouseMoved} on the |
| * {@code DragSourceMotionListener}s registered with the |
| * {@code DragSource} associated with this |
| * {@code DragSourceContext}, and them passes the specified |
| * {@code DragSourceDragEvent}. |
| * |
| * @param dsde the {@code DragSourceDragEvent} |
| * @since 1.4 |
| */ |
| public void dragMouseMoved(DragSourceDragEvent dsde) { |
| getDragSource().processDragMouseMoved(dsde); |
| } |
| |
| /** |
| * Returns the {@code Transferable} associated with |
| * this {@code DragSourceContext}. |
| * |
| * @return the {@code Transferable} |
| */ |
| public Transferable getTransferable() { return transferable; } |
| |
| /** |
| * If the default drag cursor behavior is active, this method |
| * sets the default drag cursor for the specified actions |
| * supported by the drag source, the drop target action, |
| * and status, otherwise this method does nothing. |
| * |
| * @param sourceAct the actions supported by the drag source |
| * @param targetAct the drop target action |
| * @param status one of the fields {@code DEFAULT}, |
| * {@code ENTER}, {@code OVER}, |
| * {@code CHANGED} |
| */ |
| @SuppressWarnings("fallthrough") |
| protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) { |
| |
| // if the cursor has been previously set then don't do any defaults |
| // processing. |
| |
| if (useCustomCursor) { |
| return; |
| } |
| |
| // do defaults processing |
| |
| Cursor c = null; |
| |
| switch (status) { |
| default: |
| targetAct = DnDConstants.ACTION_NONE; |
| case ENTER: |
| case OVER: |
| case CHANGED: |
| int ra = sourceAct & targetAct; |
| |
| if (ra == DnDConstants.ACTION_NONE) { // no drop possible |
| if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) |
| c = DragSource.DefaultLinkNoDrop; |
| else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) |
| c = DragSource.DefaultMoveNoDrop; |
| else |
| c = DragSource.DefaultCopyNoDrop; |
| } else { // drop possible |
| if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK) |
| c = DragSource.DefaultLinkDrop; |
| else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE) |
| c = DragSource.DefaultMoveDrop; |
| else |
| c = DragSource.DefaultCopyDrop; |
| } |
| } |
| |
| setCursorImpl(c); |
| } |
| |
| private void setCursorImpl(Cursor c) { |
| if (cursor == null || !cursor.equals(c)) { |
| cursor = c; |
| if (peer != null) peer.setCursor(cursor); |
| } |
| } |
| |
| /** |
| * Serializes this {@code DragSourceContext}. This method first |
| * performs default serialization. Next, this object's |
| * {@code Transferable} is written out if and only if it can be |
| * serialized. If not, {@code null} is written instead. In this case, |
| * a {@code DragSourceContext} created from the resulting deserialized |
| * stream will contain a dummy {@code Transferable} which supports no |
| * {@code DataFlavor}s. Finally, this object's |
| * {@code DragSourceListener} is written out if and only if it can be |
| * serialized. If not, {@code null} is written instead. |
| * |
| * @serialData The default serializable fields, in alphabetical order, |
| * followed by either a {@code Transferable} instance, or |
| * {@code null}, followed by either a |
| * {@code DragSourceListener} instance, or |
| * {@code null}. |
| * @since 1.4 |
| */ |
| private void writeObject(ObjectOutputStream s) throws IOException { |
| s.defaultWriteObject(); |
| |
| s.writeObject(SerializationTester.test(transferable) |
| ? transferable : null); |
| s.writeObject(SerializationTester.test(listener) |
| ? listener : null); |
| } |
| |
| /** |
| * Deserializes this {@code DragSourceContext}. This method first |
| * performs default deserialization for all non-{@code transient} |
| * fields. This object's {@code Transferable} and |
| * {@code DragSourceListener} are then deserialized as well by using |
| * the next two objects in the stream. If the resulting |
| * {@code Transferable} is {@code null}, this object's |
| * {@code Transferable} is set to a dummy {@code Transferable} |
| * which supports no {@code DataFlavor}s. |
| * |
| * @since 1.4 |
| */ |
| private void readObject(ObjectInputStream s) |
| throws ClassNotFoundException, IOException |
| { |
| ObjectInputStream.GetField f = s.readFields(); |
| |
| DragGestureEvent newTrigger = (DragGestureEvent)f.get("trigger", null); |
| if (newTrigger == null) { |
| throw new InvalidObjectException("Null trigger"); |
| } |
| if (newTrigger.getDragSource() == null) { |
| throw new InvalidObjectException("Null DragSource"); |
| } |
| if (newTrigger.getComponent() == null) { |
| throw new InvalidObjectException("Null trigger component"); |
| } |
| |
| int newSourceActions = f.get("sourceActions", 0) |
| & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK); |
| if (newSourceActions == DnDConstants.ACTION_NONE) { |
| throw new InvalidObjectException("Invalid source actions"); |
| } |
| int triggerActions = newTrigger.getDragAction(); |
| if (triggerActions != DnDConstants.ACTION_COPY && |
| triggerActions != DnDConstants.ACTION_MOVE && |
| triggerActions != DnDConstants.ACTION_LINK) { |
| throw new InvalidObjectException("No drag action"); |
| } |
| trigger = newTrigger; |
| |
| cursor = (Cursor)f.get("cursor", null); |
| useCustomCursor = f.get("useCustomCursor", false); |
| sourceActions = newSourceActions; |
| |
| transferable = (Transferable)s.readObject(); |
| listener = (DragSourceListener)s.readObject(); |
| |
| // Implementation assumes 'transferable' is never null. |
| if (transferable == null) { |
| if (emptyTransferable == null) { |
| emptyTransferable = new Transferable() { |
| public DataFlavor[] getTransferDataFlavors() { |
| return new DataFlavor[0]; |
| } |
| public boolean isDataFlavorSupported(DataFlavor flavor) |
| { |
| return false; |
| } |
| public Object getTransferData(DataFlavor flavor) |
| throws UnsupportedFlavorException |
| { |
| throw new UnsupportedFlavorException(flavor); |
| } |
| }; |
| } |
| transferable = emptyTransferable; |
| } |
| } |
| |
| private static Transferable emptyTransferable; |
| |
| /* |
| * fields |
| */ |
| private final transient DragSourceContextPeer peer; |
| |
| /** |
| * The event which triggered the start of the drag. |
| * |
| * @serial |
| */ |
| private DragGestureEvent trigger; |
| |
| /** |
| * The current drag cursor. |
| * |
| * @serial |
| */ |
| private Cursor cursor; |
| |
| private transient Transferable transferable; |
| |
| private transient DragSourceListener listener; |
| |
| /** |
| * {@code true} if the custom drag cursor is used instead of the |
| * default one. |
| * |
| * @serial |
| */ |
| private boolean useCustomCursor; |
| |
| /** |
| * A bitwise mask of {@code DnDConstants} that represents the set of |
| * drop actions supported by the drag source for the drag operation associated |
| * with this {@code DragSourceContext.} |
| * |
| * @serial |
| */ |
| private int sourceActions; |
| } |