/* * @(#)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. *
* 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. *
* The subclasses are expected to implement the following methods to manage the * insertion location via the components selection mechanism. *
* @param c the Component
* @param p the Point
*/
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.
*
* 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. *
* @param e the ActionEvent
*/
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;
}