/* * @(#)QuaquaDropTargetListener.java * * Copyright (c) 2007-2010 Werner Randelshofer, Immensee, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package ch.randelshofer.quaqua; import java.awt.*; import java.awt.dnd.*; import java.awt.event.*; import javax.swing.*; import javax.swing.plaf.*; /** * QuaquaDropTargetListener is mostly a copy of * javax.swing.basic.BasicDropTargetListener. This probably violates some * licenses, but I don't know any other way to override the behavior of BasicTreeUI. * * @author Werner Randelshofer * @version $Id: QuaquaDropTargetListener.java 361 2010-11-21 11:19:20Z wrandelshofer $ */ public class QuaquaDropTargetListener implements DropTargetListener, UIResource, ActionListener { /** * construct a DropTargetAutoScroller */ protected QuaquaDropTargetListener() { } /** * 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.setBounds(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.setBounds(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 = 100; Integer interval = 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 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; }