1. /*
  2. * @(#)SynthDropTargetListener.java 1.2 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 com.sun.java.swing.plaf.gtk;
  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 Shannon Hickey
  40. * @version 1.2 01/23/03 (based on revision 1.7 of BasicDropTargetListener)
  41. */
  42. class SynthDropTargetListener implements DropTargetListener, UIResource, ActionListener {
  43. /**
  44. * called to save the state of a component in case it needs to
  45. * be restored because a drop is not performed.
  46. */
  47. protected void saveComponentState(JComponent c) {
  48. }
  49. /**
  50. * called to restore the state of a component in case a drop
  51. * is not performed.
  52. */
  53. protected void restoreComponentState(JComponent c) {
  54. }
  55. /**
  56. * called to restore the state of a component in case a drop
  57. * is performed.
  58. */
  59. protected void restoreComponentStateForDrop(JComponent c) {
  60. }
  61. /**
  62. * called to set the insertion location to match the current
  63. * mouse pointer coordinates.
  64. */
  65. protected void updateInsertionLocation(JComponent c, Point p) {
  66. }
  67. /**
  68. * Update the geometry of the autoscroll region. The geometry is
  69. * maintained as a pair of rectangles. The region can cause
  70. * a scroll if the pointer sits inside it for the duration of the
  71. * timer. The region that causes the timer countdown is the area
  72. * between the two rectangles.
  73. * <p>
  74. * This is implemented to use the visible area of the component
  75. * as the outer rectangle and the insets are based upon the
  76. * Scrollable information (if any). If the Scrollable is
  77. * scrollable along an axis, the step increment is used as
  78. * the autoscroll inset. If the component is not scrollable,
  79. * the insets will be zero (i.e. autoscroll will not happen).
  80. */
  81. void updateAutoscrollRegion(JComponent c) {
  82. // compute the outer
  83. Rectangle visible = c.getVisibleRect();
  84. outer.reshape(visible.x, visible.y, visible.width, visible.height);
  85. // compute the insets
  86. // TBD - the thing with the scrollable
  87. Insets i = new Insets(0, 0, 0, 0);
  88. if (c instanceof Scrollable) {
  89. Scrollable s = (Scrollable) c;
  90. i.left = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, 1);
  91. i.top = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, 1);
  92. i.right = s.getScrollableUnitIncrement(visible, SwingConstants.HORIZONTAL, -1);
  93. i.bottom = s.getScrollableUnitIncrement(visible, SwingConstants.VERTICAL, -1);
  94. }
  95. // set the inner from the insets
  96. inner.reshape(visible.x + i.left,
  97. visible.y + i.top,
  98. visible.width - (i.left + i.right),
  99. visible.height - (i.top + i.bottom));
  100. }
  101. /**
  102. * Perform an autoscroll operation. This is implemented to scroll by the
  103. * unit increment of the Scrollable using scrollRectToVisible. If the
  104. * cursor is in a corner of the autoscroll region, more than one axis will
  105. * scroll.
  106. */
  107. void autoscroll(JComponent c, Point pos) {
  108. if (c instanceof Scrollable) {
  109. Scrollable s = (Scrollable) c;
  110. if (pos.y < inner.y) {
  111. // scroll top downward
  112. int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, 1);
  113. Rectangle r = new Rectangle(inner.x, outer.y - dy, inner.width, dy);
  114. c.scrollRectToVisible(r);
  115. } else if (pos.y > (inner.y + inner.height)) {
  116. // scroll bottom upward
  117. int dy = s.getScrollableUnitIncrement(outer, SwingConstants.VERTICAL, -1);
  118. Rectangle r = new Rectangle(inner.x, outer.y + outer.height, inner.width, dy);
  119. c.scrollRectToVisible(r);
  120. }
  121. if (pos.x < inner.x) {
  122. // scroll left side to the right
  123. int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, 1);
  124. Rectangle r = new Rectangle(outer.x - dx, inner.y, dx, inner.height);
  125. c.scrollRectToVisible(r);
  126. } else if (pos.x > (inner.x + inner.width)) {
  127. // scroll right side to the left
  128. int dx = s.getScrollableUnitIncrement(outer, SwingConstants.HORIZONTAL, -1);
  129. Rectangle r = new Rectangle(outer.x + outer.width, inner.y, dx, inner.height);
  130. c.scrollRectToVisible(r);
  131. }
  132. }
  133. }
  134. /**
  135. * Initializes the internal properties if they haven't been already
  136. * inited. This is done lazily to avoid loading of desktop properties.
  137. */
  138. private void initPropertiesIfNecessary() {
  139. if (timer == null) {
  140. Toolkit t = Toolkit.getDefaultToolkit();
  141. Integer initial = new Integer(100);
  142. Integer interval = new Integer(100);
  143. try {
  144. initial = (Integer)t.getDesktopProperty(
  145. "DnD.Autoscroll.initialDelay");
  146. } catch (Exception e) {
  147. // ignore
  148. }
  149. try {
  150. interval = (Integer)t.getDesktopProperty(
  151. "DnD.Autoscroll.interval");
  152. } catch (Exception e) {
  153. // ignore
  154. }
  155. timer = new Timer(interval.intValue(), this);
  156. timer.setCoalesce(true);
  157. timer.setInitialDelay(initial.intValue());
  158. try {
  159. hysteresis = ((Integer)t.getDesktopProperty(
  160. "DnD.Autoscroll.cursorHysteresis")).intValue();
  161. } catch (Exception e) {
  162. // ignore
  163. }
  164. }
  165. }
  166. static JComponent getComponent(DropTargetEvent e) {
  167. DropTargetContext context = e.getDropTargetContext();
  168. return (JComponent) context.getComponent();
  169. }
  170. // --- ActionListener methods --------------------------------------
  171. /**
  172. * The timer fired, perform autoscroll if the pointer is within the
  173. * autoscroll region.
  174. * <P>
  175. * @param e the <code>ActionEvent</code>
  176. */
  177. public synchronized void actionPerformed(ActionEvent e) {
  178. updateAutoscrollRegion(component);
  179. if (outer.contains(lastPosition) && !inner.contains(lastPosition)) {
  180. autoscroll(component, lastPosition);
  181. }
  182. }
  183. // --- DropTargetListener methods -----------------------------------
  184. public void dragEnter(DropTargetDragEvent e) {
  185. component = getComponent(e);
  186. TransferHandler th = component.getTransferHandler();
  187. canImport = th.canImport(component, e.getCurrentDataFlavors());
  188. if (canImport) {
  189. saveComponentState(component);
  190. lastPosition = e.getLocation();
  191. updateAutoscrollRegion(component);
  192. initPropertiesIfNecessary();
  193. }
  194. }
  195. public void dragOver(DropTargetDragEvent e) {
  196. if (canImport) {
  197. Point p = e.getLocation();
  198. updateInsertionLocation(component, p);
  199. // check autoscroll
  200. synchronized(this) {
  201. if (Math.abs(p.x - lastPosition.x) > hysteresis ||
  202. Math.abs(p.y - lastPosition.y) > hysteresis) {
  203. // no autoscroll
  204. if (timer.isRunning()) timer.stop();
  205. } else {
  206. if (!timer.isRunning()) timer.start();
  207. }
  208. lastPosition = p;
  209. }
  210. }
  211. }
  212. public void dragExit(DropTargetEvent e) {
  213. if (canImport) {
  214. restoreComponentState(component);
  215. }
  216. cleanup();
  217. }
  218. public void drop(DropTargetDropEvent e) {
  219. if (canImport) {
  220. restoreComponentStateForDrop(component);
  221. }
  222. cleanup();
  223. }
  224. public void dropActionChanged(DropTargetDragEvent e) {
  225. }
  226. /**
  227. * Cleans up internal state after the drop has finished (either succeeded
  228. * or failed).
  229. */
  230. private void cleanup() {
  231. if (timer != null) {
  232. timer.stop();
  233. }
  234. component = null;
  235. lastPosition = null;
  236. }
  237. // --- fields --------------------------------------------------
  238. private Timer timer;
  239. private Point lastPosition;
  240. private Rectangle outer = new Rectangle();
  241. private Rectangle inner = new Rectangle();
  242. private int hysteresis = 10;
  243. private boolean canImport;
  244. /**
  245. * The current component. The value is cached from the drop events and used
  246. * by the timer. When a drag exits or a drop occurs, this value is cleared.
  247. */
  248. private JComponent component;
  249. }