- /*
- * @(#)BasicDropTargetListener.java 1.10 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
- package javax.swing.plaf.basic;
-
- import java.awt.*;
- import java.awt.dnd.*;
- import javax.swing.*;
- import javax.swing.plaf.UIResource;
-
- import java.awt.event.*;
- import javax.swing.Timer;
-
- /**
- * The Swing DropTarget implementation supports multicast notification
- * to listeners, so this implementation is used as an additional
- * listener that extends the primary drop target functionality
- * (i.e. linkage to the TransferHandler) to include autoscroll and
- * establish an insertion point for the drop. This is used by the ComponentUI
- * of components supporting a selection mechanism, which have a
- * way of indicating a location within their model.
- * <p>
- * The autoscroll functionality is based upon the Swing scrolling mechanism
- * of the Scrollable interface. The unit scroll increment is used to as
- * the scroll amount, and the scrolling is based upon JComponent.getVisibleRect
- * and JComponent.scrollRectToVisible. The band of area around the visible
- * rectangle used to invoke autoscroll is based upon the unit scroll increment
- * as that is assumed to represent the last possible item in the visible region.
- * <p>
- * The subclasses are expected to implement the following methods to manage the
- * insertion location via the components selection mechanism.
- * <ul>
- * <li>saveComponentState
- * <li>restoreComponentState
- * <li>restoreComponentStateForDrop
- * <li>updateInsertionLocation
- * </ul>
- *
- * @author Timothy Prinzing
- * @version 1.10 12/19/03
- */
- class BasicDropTargetListener implements DropTargetListener, UIResource, ActionListener {
-
- /**
- * construct a DropTargetAutoScroller
- * <P>
- * @param c the <code>Component</code>
- * @param p the <code>Point</code>
- */
- protected BasicDropTargetListener() {
- }
-
-
- /**
- * called to save the state of a component in case it needs to
- * be restored because a drop is not performed.
- */
- protected void saveComponentState(JComponent c) {
- }
-
- /**
- * called to restore the state of a component in case a drop
- * is not performed.
- */
- protected void restoreComponentState(JComponent c) {
- }
-
- /**
- * called to restore the state of a component in case a drop
- * is performed.
- */
- protected void restoreComponentStateForDrop(JComponent c) {
- }
-
- /**
- * called to set the insertion location to match the current
- * mouse pointer coordinates.
- */
- protected void updateInsertionLocation(JComponent c, Point p) {
- }
-
- private static final int AUTOSCROLL_INSET = 10;
-
- /**
- * Update the geometry of the autoscroll region. The geometry is
- * maintained as a pair of rectangles. The region can cause
- * a scroll if the pointer sits inside it for the duration of the
- * timer. The region that causes the timer countdown is the area
- * between the two rectangles.
- * <p>
- * This is implemented to use the visible area of the component
- * as the outer rectangle, and the insets are fixed at 10. Should
- * the component be smaller than a total of 20 in any direction,
- * autoscroll will not occur in that direction.
- */
- void updateAutoscrollRegion(JComponent c) {
- // compute the outer
- Rectangle visible = c.getVisibleRect();
- outer.reshape(visible.x, visible.y, visible.width, visible.height);
-
- // compute the insets
- Insets i = new Insets(0, 0, 0, 0);
- if (c instanceof Scrollable) {
- int minSize = 2 * AUTOSCROLL_INSET;
-
- if (visible.width >= minSize) {
- i.left = i.right = AUTOSCROLL_INSET;
- }
-
- if (visible.height >= minSize) {
- i.top = i.bottom = AUTOSCROLL_INSET;
- }
- }
-
- // set the inner from the insets
- inner.reshape(visible.x + i.left,
- visible.y + i.top,
- visible.width - (i.left + i.right),
- visible.height - (i.top + i.bottom));
- }
-
- /**
- * Perform an autoscroll operation. This is implemented to scroll by the
- * unit increment of the Scrollable using scrollRectToVisible. If the
- * cursor is in a corner of the autoscroll region, more than one axis will
- * scroll.
- */
- void autoscroll(JComponent c, Point pos) {
- if (c instanceof Scrollable) {
- Scrollable s = (Scrollable) c;
- if (pos.y < inner.y) {
- // scroll upward
- int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1);
- Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy);
- c.scrollRectToVisible(r);
- } else if (pos.y > (inner.y + inner.height)) {
- // scroll downard
- int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1);
- Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy);
- c.scrollRectToVisible(r);
- }
-
- if (pos.x < inner.x) {
- // scroll left
- int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1);
- Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height);
- c.scrollRectToVisible(r);
- } else if (pos.x > (inner.x + inner.width)) {
- // scroll right
- int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1);
- Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height);
- c.scrollRectToVisible(r);
- }
- }
- }
-
- /**
- * Initializes the internal properties if they haven't been already
- * inited. This is done lazily to avoid loading of desktop properties.
- */
- private void initPropertiesIfNecessary() {
- if (timer == null) {
- Toolkit t = Toolkit.getDefaultToolkit();
- Integer initial = new Integer(100);
- Integer interval = new Integer(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());
-
- try {
- hysteresis = ((Integer)t.getDesktopProperty(
- "DnD.Autoscroll.cursorHysteresis")).intValue();
- } catch (Exception e) {
- // ignore
- }
- }
- }
-
- static JComponent getComponent(DropTargetEvent e) {
- DropTargetContext context = e.getDropTargetContext();
- return (JComponent) context.getComponent();
- }
-
- // --- ActionListener methods --------------------------------------
-
- /**
- * The timer fired, perform autoscroll if the pointer is within the
- * autoscroll region.
- * <P>
- * @param e the <code>ActionEvent</code>
- */
- public synchronized void actionPerformed(ActionEvent e) {
- updateAutoscrollRegion(component);
- if (outer.contains(lastPosition) && !inner.contains(lastPosition)) {
- autoscroll(component, lastPosition);
- }
- }
-
- // --- DropTargetListener methods -----------------------------------
-
- public void dragEnter(DropTargetDragEvent e) {
- component = getComponent(e);
- TransferHandler th = component.getTransferHandler();
- canImport = th.canImport(component, e.getCurrentDataFlavors());
- if (canImport) {
- saveComponentState(component);
- lastPosition = e.getLocation();
- updateAutoscrollRegion(component);
- initPropertiesIfNecessary();
- }
- }
-
- public void dragOver(DropTargetDragEvent e) {
- if (canImport) {
- Point p = e.getLocation();
- updateInsertionLocation(component, p);
-
-
- // check autoscroll
- synchronized(this) {
- if (Math.abs(p.x - lastPosition.x) > hysteresis ||
- Math.abs(p.y - lastPosition.y) > hysteresis) {
- // no autoscroll
- if (timer.isRunning()) timer.stop();
- } else {
- if (!timer.isRunning()) timer.start();
- }
- lastPosition = p;
- }
- }
- }
-
- public void dragExit(DropTargetEvent e) {
- if (canImport) {
- restoreComponentState(component);
- }
- cleanup();
- }
-
- public void drop(DropTargetDropEvent e) {
- if (canImport) {
- restoreComponentStateForDrop(component);
- }
- cleanup();
- }
-
- public void dropActionChanged(DropTargetDragEvent e) {
- }
-
- /**
- * Cleans up internal state after the drop has finished (either succeeded
- * or failed).
- */
- private void cleanup() {
- if (timer != null) {
- timer.stop();
- }
- component = null;
- lastPosition = null;
- }
-
- // --- fields --------------------------------------------------
-
- private Timer timer;
- private Point lastPosition;
- private Rectangle outer = new Rectangle();
- private Rectangle inner = new Rectangle();
- private int hysteresis = 10;
- private boolean canImport;
-
- /**
- * The current component. The value is cached from the drop events and used
- * by the timer. When a drag exits or a drop occurs, this value is cleared.
- */
- private JComponent component;
-
- }
-