1. /*
  2. * @(#)BasicDropTargetListener.java 1.8 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.dnd.*;
  10. import javax.swing.*;
  11. import javax.swing.plaf.UIResource;
  12. import java.awt.event.*;
  13. import javax.swing.Timer;
  14. /**
  15. * The Swing DropTarget implementation supports multicast notification
  16. * to listeners, so this implementation is used as an additional
  17. * listener that extends the primary drop target functionality
  18. * (i.e. linkage to the TransferHandler) to include autoscroll and
  19. * establish an insertion point for the drop. This is used by the ComponentUI
  20. * of components supporting a selection mechanism, which have a
  21. * way of indicating a location within their model.
  22. * <p>
  23. * The autoscroll functionality is based upon the Swing scrolling mechanism
  24. * of the Scrollable interface. The unit scroll increment is used to as
  25. * the scroll amount, and the scrolling is based upon JComponent.getVisibleRect
  26. * and JComponent.scrollRectToVisible. The band of area around the visible
  27. * rectangle used to invoke autoscroll is based upon the unit scroll increment
  28. * as that is assumed to represent the last possible item in the visible region.
  29. * <p>
  30. * The subclasses are expected to implement the following methods to manage the
  31. * insertion location via the components selection mechanism.
  32. * <ul>
  33. * <li>saveComponentState
  34. * <li>restoreComponentState
  35. * <li>restoreComponentStateForDrop
  36. * <li>updateInsertionLocation
  37. * </ul>
  38. *
  39. * @author Timothy Prinzing
  40. * @version 1.8 01/23/03
  41. */
  42. class BasicDropTargetListener implements DropTargetListener, UIResource, ActionListener {
  43. /**
  44. * construct a DropTargetAutoScroller
  45. * <P>
  46. * @param c the <code>Component</code>
  47. * @param p the <code>Point</code>
  48. */
  49. protected BasicDropTargetListener() {
  50. }
  51. /**
  52. * called to save the state of a component in case it needs to
  53. * be restored because a drop is not performed.
  54. */
  55. protected void saveComponentState(JComponent c) {
  56. }
  57. /**
  58. * called to restore the state of a component in case a drop
  59. * is not performed.
  60. */
  61. protected void restoreComponentState(JComponent c) {
  62. }
  63. /**
  64. * called to restore the state of a component in case a drop
  65. * is performed.
  66. */
  67. protected void restoreComponentStateForDrop(JComponent c) {
  68. }
  69. /**
  70. * called to set the insertion location to match the current
  71. * mouse pointer coordinates.
  72. */
  73. protected void updateInsertionLocation(JComponent c, Point p) {
  74. }
  75. /**
  76. * Update the geometry of the autoscroll region. The geometry is
  77. * maintained as a pair of rectangles. The region can cause
  78. * a scroll if the pointer sits inside it for the duration of the
  79. * timer. The region that causes the timer countdown is the area
  80. * between the two rectangles.
  81. * <p>
  82. * This is implemented to use the visible area of the component
  83. * as the outer rectangle and the insets are based upon the
  84. * Scrollable information (if any). If the Scrollable is
  85. * scrollable along an axis, the step increment is used as
  86. * the autoscroll inset. If the component is not scrollable,
  87. * the insets will be zero (i.e. autoscroll will not happen).
  88. */
  89. void updateAutoscrollRegion(JComponent c) {
  90. // compute the outer
  91. Rectangle visible = c.getVisibleRect();
  92. outer.reshape(visible.x, visible.y, visible.width, visible.height);
  93. // compute the insets
  94. // TBD - the thing with the scrollable
  95. Insets i = new Insets(0, 0, 0, 0);
  96. if (c instanceof Scrollable) {
  97. Scrollable s = (Scrollable) c;
  98. i.left = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, 1);
  99. i.top = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, 1);
  100. i.right = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, -1);
  101. i.bottom = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, -1);
  102. }
  103. // set the inner from the insets
  104. inner.reshape(visible.x + i.left,
  105. visible.y + i.top,
  106. visible.width - (i.left + i.right),
  107. visible.height - (i.top + i.bottom));
  108. }
  109. /**
  110. * Perform an autoscroll operation. This is implemented to scroll by the
  111. * unit increment of the Scrollable using scrollRectToVisible. If the
  112. * cursor is in a corner of the autoscroll region, more than one axis will
  113. * scroll.
  114. */
  115. void autoscroll(JComponent c, Point pos) {
  116. if (c instanceof Scrollable) {
  117. Scrollable s = (Scrollable) c;
  118. if (pos.y < inner.y) {
  119. // scroll top downward
  120. int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1);
  121. Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy);
  122. c.scrollRectToVisible(r);
  123. } else if (pos.y > (inner.y + inner.height)) {
  124. // scroll bottom upward
  125. int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1);
  126. Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy);
  127. c.scrollRectToVisible(r);
  128. }
  129. if (pos.x < inner.x) {
  130. // scroll left side to the right
  131. int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1);
  132. Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height);
  133. c.scrollRectToVisible(r);
  134. } else if (pos.x > (inner.x + inner.width)) {
  135. // scroll right side to the left
  136. int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1);
  137. Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height);
  138. c.scrollRectToVisible(r);
  139. }
  140. }
  141. }
  142. /**
  143. * Initializes the internal properties if they haven't been already
  144. * inited. This is done lazily to avoid loading of desktop properties.
  145. */
  146. private void initPropertiesIfNecessary() {
  147. if (timer == null) {
  148. Toolkit t = Toolkit.getDefaultToolkit();
  149. Integer initial = new Integer(100);
  150. Integer interval = new Integer(100);
  151. try {
  152. initial = (Integer)t.getDesktopProperty(
  153. "DnD.Autoscroll.initialDelay");
  154. } catch (Exception e) {
  155. // ignore
  156. }
  157. try {
  158. interval = (Integer)t.getDesktopProperty(
  159. "DnD.Autoscroll.interval");
  160. } catch (Exception e) {
  161. // ignore
  162. }
  163. timer = new Timer(interval.intValue(), this);
  164. timer.setCoalesce(true);
  165. timer.setInitialDelay(initial.intValue());
  166. try {
  167. hysteresis = ((Integer)t.getDesktopProperty(
  168. "DnD.Autoscroll.cursorHysteresis")).intValue();
  169. } catch (Exception e) {
  170. // ignore
  171. }
  172. }
  173. }
  174. static JComponent getComponent(DropTargetEvent e) {
  175. DropTargetContext context = e.getDropTargetContext();
  176. return (JComponent) context.getComponent();
  177. }
  178. // --- ActionListener methods --------------------------------------
  179. /**
  180. * The timer fired, perform autoscroll if the pointer is within the
  181. * autoscroll region.
  182. * <P>
  183. * @param e the <code>ActionEvent</code>
  184. */
  185. public synchronized void actionPerformed(ActionEvent e) {
  186. updateAutoscrollRegion(component);
  187. if (outer.contains(lastPosition) && !inner.contains(lastPosition)) {
  188. autoscroll(component, lastPosition);
  189. }
  190. }
  191. // --- DropTargetListener methods -----------------------------------
  192. public void dragEnter(DropTargetDragEvent e) {
  193. component = getComponent(e);
  194. TransferHandler th = component.getTransferHandler();
  195. canImport = th.canImport(component, e.getCurrentDataFlavors());
  196. if (canImport) {
  197. saveComponentState(component);
  198. lastPosition = e.getLocation();
  199. updateAutoscrollRegion(component);
  200. initPropertiesIfNecessary();
  201. }
  202. }
  203. public void dragOver(DropTargetDragEvent e) {
  204. if (canImport) {
  205. Point p = e.getLocation();
  206. updateInsertionLocation(component, p);
  207. // check autoscroll
  208. synchronized(this) {
  209. if (Math.abs(p.x - lastPosition.x) > hysteresis ||
  210. Math.abs(p.y - lastPosition.y) > hysteresis) {
  211. // no autoscroll
  212. if (timer.isRunning()) timer.stop();
  213. } else {
  214. if (!timer.isRunning()) timer.start();
  215. }
  216. lastPosition = p;
  217. }
  218. }
  219. }
  220. public void dragExit(DropTargetEvent e) {
  221. if (canImport) {
  222. restoreComponentState(component);
  223. }
  224. cleanup();
  225. }
  226. public void drop(DropTargetDropEvent e) {
  227. if (canImport) {
  228. restoreComponentStateForDrop(component);
  229. }
  230. cleanup();
  231. }
  232. public void dropActionChanged(DropTargetDragEvent e) {
  233. }
  234. /**
  235. * Cleans up internal state after the drop has finished (either succeeded
  236. * or failed).
  237. */
  238. private void cleanup() {
  239. if (timer != null) {
  240. timer.stop();
  241. }
  242. component = null;
  243. lastPosition = null;
  244. }
  245. // --- fields --------------------------------------------------
  246. private Timer timer;
  247. private Point lastPosition;
  248. private Rectangle outer = new Rectangle();
  249. private Rectangle inner = new Rectangle();
  250. private int hysteresis = 10;
  251. private boolean canImport;
  252. /**
  253. * The current component. The value is cached from the drop events and used
  254. * by the timer. When a drag exits or a drop occurs, this value is cleared.
  255. */
  256. private JComponent component;
  257. }