com.sun.jna.platform.dnd.DropHandler Maven / Gradle / Ivy
Show all versions of jna-platform Show documentation
/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package com.sun.jna.platform.dnd;
import java.awt.Component;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Provides simplified drop handling for a component.
* Usage:
*
* int actions = DnDConstants.MOVE_OR_COPY;
* Component component = ...;
* DropHandler handler = new DropHandler(component, actions);
*
*
* - Accept drops where the action is the default (i.e. no modifiers) but
* the intersection of source and target actions is not the default.
* Doing so allows the source to adjust the cursor appropriately.
*
- Refuse drops where the user modifiers request an action that is not
* supported (this works for all cases except when the drag source is not
* a {@link DragHandler} and the user explicitly requests a MOVE operation;
* this is indistinguishable from a drag with no modifiers unless we have
* access to the key modifiers, which {@link DragHandler} provides).
*
- Drops may be refused based on data flavor, location, intended drop
* action, or any combination of those, by overriding {@link #canDrop}.
*
- Custom decoration of the drop area may be performed in
* {@link #paintDropTarget(DropTargetEvent, int, Point)} or by providing
* a {@link DropTargetPainter}.
*
*
* The method {@link #getDropAction(DropTargetEvent)} follows these steps to
* determine the appropriate action (if any).
*
* - {@link #isSupported(DataFlavor[])} determines if there are any supported
* flavors
*
- {@link #getDropActionsForFlavors(DataFlavor[])} reduces the supported
* actions based on available flavors. For instance, a text field for file
* paths might support {@link DnDConstants#ACTION_COPY_OR_MOVE} on a plain
* string, but {@link DnDConstants#ACTION_LINK} might be the only action
* supported on a file.
*
- {@link #getDropAction(DropTargetEvent, int, int, int)} relax the action
* if it's the default, or restrict it for user requested actions.
*
- {@link #canDrop(DropTargetEvent, int, Point)} change the action based on
* the location in the drop target component, or any other criteria.
*
*
* Override {@link #drop(DropTargetDropEvent, int)} to handle the drop.
* You should invoke {@link DropTargetDropEvent#dropComplete} as soon
* as the {@link Transferable} data is obtained, to avoid making the DnD
* operation look suspended.
*
* @see DragHandler
* @author twall
*/
// NOTE: you could probably make one of these handlers serve several targets,
// but for simplicity, keep the mapping 1-1-1 handler/droptarget/component
// TODO: look into making use of the existing
// Transferable.SwingDropTarget on JComponent instances instead of
// creating a new DropTarget; we can add self as a listener; probably would
// want to remove the default TransferHandler.DropHandler, which uses
// the TransferHandler to drop
public abstract class DropHandler implements DropTargetListener {
private static final Logger LOG = Logger.getLogger(DropHandler.class.getName());
private int acceptedActions;
private List acceptedFlavors;
private DropTarget dropTarget;
private boolean active = true;
private DropTargetPainter painter;
/** Create a handler that allows the given set of actions. If using
* this constructor, you will need to override {@link #isSupported} to
* indicate which data flavors are allowed.
* @param c target component
* @param acceptedActions accepted actions
*/
public DropHandler(Component c, int acceptedActions) {
this(c, acceptedActions, new DataFlavor[0]);
}
/** Enable handling of drops, indicating what actions and flavors are
* acceptable.
* @param c The component to receive drops
* @param acceptedActions Allowed actions for drops
* @param acceptedFlavors Allowed data flavors for drops
* @see #isSupported
*/
public DropHandler(final Component c, int acceptedActions, DataFlavor[] acceptedFlavors) {
this(c, acceptedActions, acceptedFlavors, null);
}
/** Enable handling of drops, indicating what actions and flavors are
* acceptable, and providing a painter for drop target feedback.
* @param c The component to receive drops
* @param acceptedActions Allowed actions for drops
* @param acceptedFlavors Allowed data flavors for drops
* @param painter Painter to handle drop target feedback
* @see #paintDropTarget
*/
public DropHandler(final Component c, int acceptedActions,
DataFlavor[] acceptedFlavors, DropTargetPainter painter) {
this.acceptedActions = acceptedActions;
this.acceptedFlavors = Arrays.asList(acceptedFlavors);
this.painter = painter;
dropTarget = new DropTarget(c, acceptedActions, this, active);
}
protected DropTarget getDropTarget() {
return dropTarget;
}
/**
* @return Whether this drop target is active.
*/
public boolean isActive() { return active; }
/** Set whether this handler (and thus its drop target) will accept
* any drops.
* @param active whether this handler should accept drops.
*/
public void setActive(boolean active) {
this.active = active;
if (dropTarget != null) {
dropTarget.setActive(active);
}
}
/** Indicate the actions available for the given list of data flavors.
* Override this method if the acceptable drop actions depend
* on the currently available {@link DataFlavor}. The default returns
* the accepted actions passed into the constructor.
* @param dataFlavors currently available flavors
* @return currently acceptable actions.
* @see #getDropAction(DropTargetEvent, int, int, int)
* @see #canDrop(DropTargetEvent, int, Point)
*/
protected int getDropActionsForFlavors(DataFlavor[] dataFlavors) {
return acceptedActions;
}
/** Calculate the effective action. The default implementation
* checks whether any {@link DataFlavor}s are supported, and if so,
* will change the current action from {@link DnDConstants#ACTION_NONE} to
* something in common between the source and destination. Refuse
* user-requested actions if they are not supported (rather than silently
* accepting a non-user-requested action, which is the Java's DnD default
* behavior). The drop action is forced to {@link DnDConstants#ACTION_NONE}
* if there is no supported data flavor.
* @param e {@link DropTargetEvent}
* @return effective drop action
* @see #isSupported(DataFlavor[])
* @see #getDropActionsForFlavors
* @see #getDropAction(DropTargetEvent, int, int, int)
* @see #canDrop(DropTargetEvent, int, Point)
*/
protected int getDropAction(DropTargetEvent e) {
int currentAction = DragHandler.NONE;
int sourceActions = DragHandler.NONE;
Point location = null;
DataFlavor[] flavors = new DataFlavor[0];
if (e instanceof DropTargetDragEvent) {
DropTargetDragEvent ev = (DropTargetDragEvent)e;
currentAction = ev.getDropAction();
sourceActions = ev.getSourceActions();
flavors = ev.getCurrentDataFlavors();
location = ev.getLocation();
}
else if (e instanceof DropTargetDropEvent) {
DropTargetDropEvent ev = (DropTargetDropEvent)e;
currentAction = ev.getDropAction();
sourceActions = ev.getSourceActions();
flavors = ev.getCurrentDataFlavors();
location = ev.getLocation();
}
if (isSupported(flavors)) {
int availableActions = getDropActionsForFlavors(flavors);
currentAction = getDropAction(e, currentAction, sourceActions, availableActions);
if (currentAction != DragHandler.NONE) {
if (canDrop(e, currentAction, location)) {
return currentAction;
}
}
}
return DragHandler.NONE;
}
/* Adjust the drop action depending on whether the
* current action is the default or a specific user-requested action.
* The default implementation will change the current action from
* {@link DnDConstants#ACTION_NONE} if there are actions in
* common between the source and destination. It will refuse user-requested
* actions if they are not supported (rather than silently accepting
* a non-user-requested action, which is the behavior of Swing's default
* drop handlers).
* You can override this method if you wish to adjust the action based
* on the the drag location; if you wish to deny drops based on location,
* override {@link #canDrop} instead. If you wish to adjust
* the action based on the available data flavors, override
* {@link #getDropActionsForFlavor} instead.
* @see #getDropActionsForFlavor
* @see #canDrop(DropTargetEvent, int, Point)
*/
protected int getDropAction(DropTargetEvent e, int currentAction,
int sourceActions, int acceptedActions) {
boolean modifiersActive = modifiersActive(currentAction);
if ((currentAction & acceptedActions) == DragHandler.NONE
&& !modifiersActive) {
int action = acceptedActions & sourceActions;
currentAction = action;
}
else if (modifiersActive) {
int action = currentAction & acceptedActions & sourceActions;
if (action != currentAction) {
currentAction = action;
}
}
return currentAction;
}
/** Returns whether there are key modifiers active ,
* or false if they can't be determined.
* We use the DragHandler hint, if available, or fall back to whether
* the drop action is other than the default (move).
* @param dropAction requested action.
* @return whether any modifiers are active.
*/
protected boolean modifiersActive(int dropAction) {
int mods = DragHandler.getModifiers();
if (mods == DragHandler.UNKNOWN_MODIFIERS) {
if (dropAction == DragHandler.LINK
|| dropAction == DragHandler.COPY) {
return true;
}
// Can't (yet) distinguish between a forced and a default move
// without help from DragHandler
return false;
}
return mods != 0;
}
private String lastAction;
private void describe(String type, DropTargetEvent e) {
if(LOG.isLoggable(Level.FINE)) {
StringBuilder msgBuilder = new StringBuilder();
msgBuilder.append("drop: ");
msgBuilder.append(type);
if (e instanceof DropTargetDragEvent) {
DropTargetContext dtc = e.getDropTargetContext();
DropTarget dt = dtc.getDropTarget();
DropTargetDragEvent ev = (DropTargetDragEvent) e;
msgBuilder.append(": src=");
msgBuilder.append(DragHandler.actionString(ev.getSourceActions()));
msgBuilder.append(" tgt=");
msgBuilder.append(DragHandler.actionString(dt.getDefaultActions()));
msgBuilder.append(" act=");
msgBuilder.append(DragHandler.actionString(ev.getDropAction()));
}
else if (e instanceof DropTargetDropEvent) {
DropTargetContext dtc = e.getDropTargetContext();
DropTarget dt = dtc.getDropTarget();
DropTargetDropEvent ev = (DropTargetDropEvent)e;
msgBuilder.append(": src=");
msgBuilder.append(DragHandler.actionString(ev.getSourceActions()));
msgBuilder.append(" tgt=");
msgBuilder.append(DragHandler.actionString(dt.getDefaultActions()));
msgBuilder.append(" act=");
msgBuilder.append(DragHandler.actionString(ev.getDropAction()));
}
String msg = msgBuilder.toString();
if (!msg.equals(lastAction)) {
LOG.log(Level.FINE, msg);
lastAction = msg;
}
}
}
/** Accept or reject the drag represented by the given event. Returns
* the action determined by {@link #getDropAction(DropTargetEvent)}.
* @param e event
* @return resulting action
*/
protected int acceptOrReject(DropTargetDragEvent e) {
int action = getDropAction(e);
if (action != DragHandler.NONE) {
// NOTE: the action argument (as of 1.5+) is only passed
// to the DropTargetContextPeer, *not* the drag source
e.acceptDrag(action);
}
else {
e.rejectDrag();
}
return action;
}
@Override
public void dragEnter(DropTargetDragEvent e) {
describe("enter(tgt)", e);
int action = acceptOrReject(e);
paintDropTarget(e, action, e.getLocation());
}
@Override
public void dragOver(DropTargetDragEvent e) {
describe("over(tgt)", e);
int action = acceptOrReject(e);
paintDropTarget(e, action, e.getLocation());
}
@Override
public void dragExit(DropTargetEvent e) {
describe("exit(tgt)", e);
paintDropTarget(e, DragHandler.NONE, null);
}
@Override
public void dropActionChanged(DropTargetDragEvent e) {
describe("change(tgt)", e);
int action = acceptOrReject(e);
paintDropTarget(e, action, e.getLocation());
}
/** Indicates the user has initiated a drop. The default performs all
* standard drop validity checking and handling, then invokes
* {@link #drop(DropTargetDropEvent,int)} if the drop looks acceptable.
*/
@Override
public void drop(DropTargetDropEvent e) {
describe("drop(tgt)", e);
int action = getDropAction(e);
if (action != DragHandler.NONE) {
e.acceptDrop(action);
try {
drop(e, action);
// Just in case this hasn't been done yet
e.dropComplete(true);
} catch (Exception ex) {
e.dropComplete(false);
}
} else {
e.rejectDrop();
}
paintDropTarget(e, DragHandler.NONE, e.getLocation());
}
/** Return whether any of the flavors in the given list are accepted.
* The list is compared against the accepted list provided in the
* constructor.
* @param flavors list of transfer flavors to check
* @return whether any of the given flavors are supported
*/
protected boolean isSupported(DataFlavor[] flavors) {
Set set = new HashSet(Arrays.asList(flavors));
set.retainAll(acceptedFlavors);
return !set.isEmpty();
}
/** Update the appearance of the target component. Normally the decoration
* should be painted only if the event is an instance of
* {@link DropTargetDragEvent} with an action that is not
* {@link DragHandler#NONE}. Otherwise the decoration should be removed
* or hidden.
*
* For an easy way to highlight the drop target, consider using a single
* instance of AbstractComponentDecorator
and moving it
* according to the intended drop location.
* @param e The drop target event
* @param action The action for the drop
* @param location The intended drop location, or null if there is none
*/
protected void paintDropTarget(DropTargetEvent e, int action, Point location) {
if (painter != null) {
painter.paintDropTarget(e, action, location);
}
}
/** Indicate whether the given drop action is acceptable at the given
* location. This method is the last check performed by
* {@link #getDropAction(DropTargetEvent)}.
* You may override this method to refuse drops on certain areas
* within the drop target component. The default always returns true.
* @param e event
* @param action requested action
* @param location requested drop location
* @return whether the drop is supported
*/
protected boolean canDrop(DropTargetEvent e, int action, Point location) {
return true;
}
/** Handle an incoming drop with the given action. The action passed in
* might be different from {@link DropTargetDropEvent#getDropAction},
* for instance, if there are no modifiers and the default action is not
* supported. Calling {@link DropTargetDropEvent#dropComplete} is
* recommended as soon as the {@link Transferable} data is obtained; this
* allows the drag source to reset the cursor and any drag images which
* may be in effect.
* @param e event
* @param action requested drop type
* @throws UnsupportedFlavorException dropped item has no supported flavors
* @throws IOException data access failure
*/
protected abstract void drop(DropTargetDropEvent e, int action) throws UnsupportedFlavorException, IOException;
}