/* * 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.util.TooManyListenersException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.awt.Component; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.datatransfer.FlavorMap; import java.awt.datatransfer.SystemFlavorMap; import javax.swing.Timer; import java.awt.peer.ComponentPeer; import java.awt.peer.LightweightPeer; import java.awt.dnd.peer.DropTargetPeer; import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.ComponentAccessor; /** * The {@code DropTarget} is associated * with a {@code Component} when that {@code Component} * wishes * to accept drops during Drag and Drop operations. * <P> * Each * {@code DropTarget} is associated with a {@code FlavorMap}. * The default {@code FlavorMap} hereafter designates the * {@code FlavorMap} returned by {@code SystemFlavorMap.getDefaultFlavorMap()}. * * @since 1.2 */ public class DropTarget implements DropTargetListener, Serializable { private static final long serialVersionUID = -6283860791671019047L; /** * Creates a new DropTarget given the {@code Component} * to associate itself with, an {@code int} representing * the default acceptable action(s) to * support, a {@code DropTargetListener} * to handle event processing, a {@code boolean} indicating * if the {@code DropTarget} is currently accepting drops, and * a {@code FlavorMap} to use (or null for the default {@code FlavorMap}). * <P> * The Component will receive drops only if it is enabled. * @param c The {@code Component} with which this {@code DropTarget} is associated * @param ops The default acceptable actions for this {@code DropTarget} * @param dtl The {@code DropTargetListener} for this {@code DropTarget} * @param act Is the {@code DropTarget} accepting drops. * @param fm The {@code FlavorMap} to use, or null for the default {@code FlavorMap} * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true * @see java.awt.GraphicsEnvironment#isHeadless */ public DropTarget(Component c, int ops, DropTargetListener dtl, boolean act, FlavorMap fm) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { throw new HeadlessException(); } component = c; setDefaultActions(ops); if (dtl != null) try { addDropTargetListener(dtl); } catch (TooManyListenersException tmle) { // do nothing! } if (c != null) { c.setDropTarget(this); setActive(act); } if (fm != null) { flavorMap = fm; } else { flavorMap = SystemFlavorMap.getDefaultFlavorMap(); } } /** * Creates a {@code DropTarget} given the {@code Component} * to associate itself with, an {@code int} representing * the default acceptable action(s) * to support, a {@code DropTargetListener} * to handle event processing, and a {@code boolean} indicating * if the {@code DropTarget} is currently accepting drops. * <P> * The Component will receive drops only if it is enabled. * @param c The {@code Component} with which this {@code DropTarget} is associated * @param ops The default acceptable actions for this {@code DropTarget} * @param dtl The {@code DropTargetListener} for this {@code DropTarget} * @param act Is the {@code DropTarget} accepting drops. * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true * @see java.awt.GraphicsEnvironment#isHeadless */ public DropTarget(Component c, int ops, DropTargetListener dtl, boolean act) throws HeadlessException { this(c, ops, dtl, act, null); } /** * Creates a {@code DropTarget}. * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true * @see java.awt.GraphicsEnvironment#isHeadless */ public DropTarget() throws HeadlessException { this(null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null); } /** * Creates a {@code DropTarget} given the {@code Component} * to associate itself with, and the {@code DropTargetListener} * to handle event processing. * <P> * The Component will receive drops only if it is enabled. * @param c The {@code Component} with which this {@code DropTarget} is associated * @param dtl The {@code DropTargetListener} for this {@code DropTarget} * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true * @see java.awt.GraphicsEnvironment#isHeadless */ public DropTarget(Component c, DropTargetListener dtl) throws HeadlessException { this(c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null); } /** * Creates a {@code DropTarget} given the {@code Component} * to associate itself with, an {@code int} representing * the default acceptable action(s) to support, and a * {@code DropTargetListener} to handle event processing. * <P> * The Component will receive drops only if it is enabled. * @param c The {@code Component} with which this {@code DropTarget} is associated * @param ops The default acceptable actions for this {@code DropTarget} * @param dtl The {@code DropTargetListener} for this {@code DropTarget} * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true * @see java.awt.GraphicsEnvironment#isHeadless */ public DropTarget(Component c, int ops, DropTargetListener dtl) throws HeadlessException { this(c, ops, dtl, true); } /** * Note: this interface is required to permit the safe association * of a DropTarget with a Component in one of two ways, either: * {@code component.setDropTarget(droptarget);} * or {@code droptarget.setComponent(component);} * <P> * The Component will receive drops only if it is enabled. * @param c The new {@code Component} this {@code DropTarget} * is to be associated with. */ public synchronized void setComponent(Component c) { if (component == c || component != null && component.equals(c)) return; final Component old = component; if (old != null) { clearAutoscroll(); component = null; removeNotify(); old.setDropTarget(null); } if ((component = c) != null) try { c.setDropTarget(this); } catch (Exception e) { // undo the change if (old != null) { old.setDropTarget(this); addNotify(); } } } /** * Gets the {@code Component} associated * with this {@code DropTarget}. * * @return the current {@code Component} */ public synchronized Component getComponent() { return component; } /** * Sets the default acceptable actions for this {@code DropTarget} * * @param ops the default actions * @see java.awt.dnd.DnDConstants */ public void setDefaultActions(int ops) { getDropTargetContext().setTargetActions(ops & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE)); } /* * Called by DropTargetContext.setTargetActions() * with appropriate synchronization. */ void doSetDefaultActions(int ops) { actions = ops; } /** * Gets an {@code int} representing the * current action(s) supported by this {@code DropTarget}. * * @return the current default actions */ public int getDefaultActions() { return actions; } /** * Sets the DropTarget active if {@code true}, * inactive if {@code false}. * * @param isActive sets the {@code DropTarget} (in)active. */ public synchronized void setActive(boolean isActive) { if (isActive != active) { active = isActive; } if (!active) clearAutoscroll(); } /** * Reports whether or not * this {@code DropTarget} * is currently active (ready to accept drops). * * @return {@code true} if active, {@code false} if not */ public boolean isActive() { return active; } /** * Adds a new {@code DropTargetListener} (UNICAST SOURCE). * * @param dtl The new {@code DropTargetListener} * * @throws TooManyListenersException if a * {@code DropTargetListener} is already added to this * {@code DropTarget}. */ public synchronized void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException { if (dtl == null) return; if (equals(dtl)) throw new IllegalArgumentException("DropTarget may not be its own Listener"); if (dtListener == null) dtListener = dtl; else throw new TooManyListenersException(); } /** * Removes the current {@code DropTargetListener} (UNICAST SOURCE). * * @param dtl the DropTargetListener to deregister. */ public synchronized void removeDropTargetListener(DropTargetListener dtl) { if (dtl != null && dtListener != null) { if(dtListener.equals(dtl)) dtListener = null; else throw new IllegalArgumentException("listener mismatch"); } } /** * Calls {@code dragEnter} on the registered * {@code DropTargetListener} and passes it * the specified {@code DropTargetDragEvent}. * Has no effect if this {@code DropTarget} * is not active. * * @param dtde the {@code DropTargetDragEvent} * * @throws NullPointerException if this {@code DropTarget} * is active and {@code dtde} is {@code null} * * @see #isActive */ public synchronized void dragEnter(DropTargetDragEvent dtde) { isDraggingInside = true; if (!active) return; if (dtListener != null) { dtListener.dragEnter(dtde); } else dtde.getDropTargetContext().setTargetActions(DnDConstants.ACTION_NONE); initializeAutoscrolling(dtde.getLocation()); } /** * Calls {@code dragOver} on the registered * {@code DropTargetListener} and passes it * the specified {@code DropTargetDragEvent}. * Has no effect if this {@code DropTarget} * is not active. * * @param dtde the {@code DropTargetDragEvent} * * @throws NullPointerException if this {@code DropTarget} * is active and {@code dtde} is {@code null} * * @see #isActive */ public synchronized void dragOver(DropTargetDragEvent dtde) { if (!active) return; if (dtListener != null && active) dtListener.dragOver(dtde); updateAutoscroll(dtde.getLocation()); } /** * Calls {@code dropActionChanged} on the registered * {@code DropTargetListener} and passes it * the specified {@code DropTargetDragEvent}. * Has no effect if this {@code DropTarget} * is not active. * * @param dtde the {@code DropTargetDragEvent} * * @throws NullPointerException if this {@code DropTarget} * is active and {@code dtde} is {@code null} * * @see #isActive */ public synchronized void dropActionChanged(DropTargetDragEvent dtde) { if (!active) return; if (dtListener != null) dtListener.dropActionChanged(dtde); updateAutoscroll(dtde.getLocation()); } /** * Calls {@code dragExit} on the registered * {@code DropTargetListener} and passes it * the specified {@code DropTargetEvent}. * Has no effect if this {@code DropTarget} * is not active. * <p> * This method itself does not throw any exception * for null parameter but for exceptions thrown by * the respective method of the listener. * * @param dte the {@code DropTargetEvent} * * @see #isActive */ public synchronized void dragExit(DropTargetEvent dte) { isDraggingInside = false; if (!active) return; if (dtListener != null && active) dtListener.dragExit(dte); clearAutoscroll(); } /** * Calls {@code drop} on the registered * {@code DropTargetListener} and passes it * the specified {@code DropTargetDropEvent} * if this {@code DropTarget} is active. * * @param dtde the {@code DropTargetDropEvent} * * @throws NullPointerException if {@code dtde} is null * and at least one of the following is true: this * {@code DropTarget} is not active, or there is * no a {@code DropTargetListener} registered. * * @see #isActive */ public synchronized void drop(DropTargetDropEvent dtde) { isDraggingInside = false; clearAutoscroll(); if (dtListener != null && active) dtListener.drop(dtde); else { // we shouldn't get here ... dtde.rejectDrop(); } } /** * Gets the {@code FlavorMap} * associated with this {@code DropTarget}. * If no {@code FlavorMap} has been set for this * {@code DropTarget}, it is associated with the default * {@code FlavorMap}. * * @return the FlavorMap for this DropTarget */ public FlavorMap getFlavorMap() { return flavorMap; } /** * Sets the {@code FlavorMap} associated * with this {@code DropTarget}. * * @param fm the new {@code FlavorMap}, or null to * associate the default FlavorMap with this DropTarget. */ public void setFlavorMap(FlavorMap fm) { flavorMap = fm == null ? SystemFlavorMap.getDefaultFlavorMap() : fm; } /** * Notify the DropTarget that it has been associated with a Component * ********************************************************************** * This method is usually called from java.awt.Component.addNotify() of * the Component associated with this DropTarget to notify the DropTarget * that a ComponentPeer has been associated with that Component. * * Calling this method, other than to notify this DropTarget of the * association of the ComponentPeer with the Component may result in * a malfunction of the DnD system. ********************************************************************** */ public void addNotify() { final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); ComponentPeer peer = acc.getPeer(component); if (peer == null || peer == componentPeer) { return; } componentPeer = peer; for (Component c = component; c != null && peer instanceof LightweightPeer; c = c.getParent()) { peer = acc.getPeer(c); } if (peer instanceof DropTargetPeer) { nativePeer = (DropTargetPeer) peer; ((DropTargetPeer)peer).addDropTarget(this); } else { nativePeer = null; } } /** * Notify the DropTarget that it has been disassociated from a Component * ********************************************************************** * This method is usually called from java.awt.Component.removeNotify() of * the Component associated with this DropTarget to notify the DropTarget * that a ComponentPeer has been disassociated with that Component. * * Calling this method, other than to notify this DropTarget of the * disassociation of the ComponentPeer from the Component may result in * a malfunction of the DnD system. ********************************************************************** */ public void removeNotify() { if (nativePeer != null) { nativePeer.removeDropTarget(this); } componentPeer = null; nativePeer = null; synchronized (this) { if (isDraggingInside) { dragExit(new DropTargetEvent(getDropTargetContext())); } } } /** * Gets the {@code DropTargetContext} associated * with this {@code DropTarget}. * * @return the {@code DropTargetContext} associated with this {@code DropTarget}. */ public DropTargetContext getDropTargetContext() { return dropTargetContext; } /** * Creates the DropTargetContext associated with this DropTarget. * Subclasses may override this method to instantiate their own * DropTargetContext subclass. * * This call is typically *only* called by the platform's * DropTargetContextPeer as a drag operation encounters this * DropTarget. Accessing the Context while no Drag is current * has undefined results. * @return the DropTargetContext associated with this DropTarget */ protected DropTargetContext createDropTargetContext() { return new DropTargetContext(this); } /** * Serializes this {@code DropTarget}. Performs default serialization, * and then writes out this object's {@code DropTargetListener} 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 DropTargetListener} * instance, or {@code null}. * @since 1.4 */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(SerializationTester.test(dtListener) ? dtListener : null); } /** * Deserializes this {@code DropTarget}. This method first performs * default deserialization for all non-{@code transient} fields. An * attempt is then made to deserialize this object's * {@code DropTargetListener} as well. This is first attempted by * deserializing the field {@code dtListener}, because, in releases * prior to 1.4, a non-{@code transient} field of this name stored the * {@code DropTargetListener}. If this fails, the next object in the * stream is used instead. * * @since 1.4 */ private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { ObjectInputStream.GetField f = s.readFields(); try { dropTargetContext = (DropTargetContext)f.get("dropTargetContext", null); } catch (IllegalArgumentException e) { // Pre-1.4 support. 'dropTargetContext' was previously transient } if (dropTargetContext == null) { dropTargetContext = createDropTargetContext(); } component = (Component)f.get("component", null); actions = f.get("actions", DnDConstants.ACTION_COPY_OR_MOVE); active = f.get("active", true); // Pre-1.4 support. 'dtListener' was previously non-transient try { dtListener = (DropTargetListener)f.get("dtListener", null); } catch (IllegalArgumentException e) { // 1.4-compatible byte stream. 'dtListener' was written explicitly dtListener = (DropTargetListener)s.readObject(); } } /*********************************************************************/ /** * this protected nested class implements autoscrolling */ protected static class DropTargetAutoScroller implements ActionListener { /** * construct a DropTargetAutoScroller * * @param c the {@code Component} * @param p the {@code Point} */ protected DropTargetAutoScroller(Component c, Point p) { super(); component = c; autoScroll = (Autoscroll)component; Toolkit t = Toolkit.getDefaultToolkit(); Integer initial = Integer.valueOf(100); Integer interval = Integer.valueOf(100); try { initial = (Integer)t.getDesktopProperty("DnD.Autoscroll.initialDelay"); } catch (Exception e) { // ignore } try { interval = (Integer)t.getDesktopProperty("DnD.Autoscroll.interval"); } catch (Exception e) { // ignore } timer = new Timer(interval.intValue(), this); timer.setCoalesce(true); timer.setInitialDelay(initial.intValue()); locn = p; prev = p; try { hysteresis = ((Integer)t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis")).intValue(); } catch (Exception e) { // ignore } timer.start(); } /** * update the geometry of the autoscroll region */ @SuppressWarnings("deprecation") private void updateRegion() { Insets i = autoScroll.getAutoscrollInsets(); Dimension size = component.getSize(); if (size.width != outer.width || size.height != outer.height) outer.reshape(0, 0, size.width, size.height); if (inner.x != i.left || inner.y != i.top) inner.setLocation(i.left, i.top); int newWidth = size.width - (i.left + i.right); int newHeight = size.height - (i.top + i.bottom); if (newWidth != inner.width || newHeight != inner.height) inner.setSize(newWidth, newHeight); } /** * cause autoscroll to occur * * @param newLocn the {@code Point} */ protected synchronized void updateLocation(Point newLocn) { prev = locn; locn = newLocn; if (Math.abs(locn.x - prev.x) > hysteresis || Math.abs(locn.y - prev.y) > hysteresis) { if (timer.isRunning()) timer.stop(); } else { if (!timer.isRunning()) timer.start(); } } /** * cause autoscrolling to stop */ protected void stop() { timer.stop(); } /** * cause autoscroll to occur * * @param e the {@code ActionEvent} */ public synchronized void actionPerformed(ActionEvent e) { updateRegion(); if (outer.contains(locn) && !inner.contains(locn)) autoScroll.autoscroll(locn); } /* * fields */ private Component component; private Autoscroll autoScroll; private Timer timer; private Point locn; private Point prev; private Rectangle outer = new Rectangle(); private Rectangle inner = new Rectangle(); private int hysteresis = 10; } /*********************************************************************/ /** * create an embedded autoscroller * * @param c the {@code Component} * @param p the {@code Point} * @return an embedded autoscroller */ protected DropTargetAutoScroller createDropTargetAutoScroller(Component c, Point p) { return new DropTargetAutoScroller(c, p); } /** * initialize autoscrolling * * @param p the {@code Point} */ protected void initializeAutoscrolling(Point p) { if (component == null || !(component instanceof Autoscroll)) return; autoScroller = createDropTargetAutoScroller(component, p); } /** * update autoscrolling with current cursor location * * @param dragCursorLocn the {@code Point} */ protected void updateAutoscroll(Point dragCursorLocn) { if (autoScroller != null) autoScroller.updateLocation(dragCursorLocn); } /** * clear autoscrolling */ protected void clearAutoscroll() { if (autoScroller != null) { autoScroller.stop(); autoScroller = null; } } /** * The DropTargetContext associated with this DropTarget. * * @serial */ private DropTargetContext dropTargetContext = createDropTargetContext(); /** * The Component associated with this DropTarget. * * @serial */ private Component component; /* * That Component's Peer */ private transient ComponentPeer componentPeer; /* * That Component's "native" Peer */ private transient DropTargetPeer nativePeer; /** * Default permissible actions supported by this DropTarget. * * @see #setDefaultActions * @see #getDefaultActions * @serial */ int actions = DnDConstants.ACTION_COPY_OR_MOVE; /** * {@code true} if the DropTarget is accepting Drag & Drop operations. * * @serial */ boolean active = true; /* * the auto scrolling object */ private transient DropTargetAutoScroller autoScroller; /* * The delegate */ private transient DropTargetListener dtListener; /* * The FlavorMap */ private transient FlavorMap flavorMap; /* * If the dragging is currently inside this drop target */ private transient boolean isDraggingInside; }