blob: 63ceb3f1706370bb33a3b5fbd6d4c19377c52582 [file] [log] [blame]
/*
* Copyright (c) 1997, 2015, 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 name="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;
}