All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jna.platform.dnd.DropHandler Maven / Gradle / Ivy

There is a newer version: 5.15.0
Show newest version
/* 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; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy