1. /*
  2. * @(#)DropTarget.java 1.30 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.dnd;
  8. import java.util.TooManyListenersException;
  9. import java.io.Serializable;
  10. import java.awt.AWTEvent;
  11. import java.awt.Component;
  12. import java.awt.Dimension;
  13. import java.awt.Insets;
  14. import java.awt.Point;
  15. import java.awt.Rectangle;
  16. import java.awt.Toolkit;
  17. import java.awt.event.ActionEvent;
  18. import java.awt.event.ActionListener;
  19. import java.awt.dnd.DnDConstants;
  20. import java.awt.dnd.DropTargetContext;
  21. import java.awt.dnd.DropTargetDragEvent;
  22. import java.awt.dnd.DropTargetDropEvent;
  23. import java.awt.dnd.DropTargetListener;
  24. import java.awt.datatransfer.FlavorMap;
  25. import java.awt.datatransfer.SystemFlavorMap;
  26. import java.awt.dnd.Autoscroll;
  27. import javax.swing.Timer;
  28. import java.awt.peer.ComponentPeer;
  29. import java.awt.peer.LightweightPeer;
  30. import java.awt.dnd.peer.DropTargetPeer;
  31. /**
  32. * The <code>DropTarget</code> is associated
  33. * with a <code>Component</code> when that <code>Component</code>
  34. * wishes
  35. * to accept drops during Drag and Drop operations.
  36. *
  37. * @version 1.30
  38. * @since JDK1.2
  39. */
  40. public class DropTarget implements DropTargetListener, Serializable {
  41. static final long serialVersionUID = -6283860791671019047L;
  42. /*
  43. * default FlavorMap for the system
  44. */
  45. static private final FlavorMap defaultFlavorMap = SystemFlavorMap.getDefaultFlavorMap();
  46. /**
  47. * Construct a new DropTarget given the <code>Component</code>
  48. * to associate itself with, an <code>int</code> representing
  49. * the default acceptable action(s) to
  50. * support, a <code>DropTargetListener</code>
  51. * to handle event processing, a <code>boolean</code> indicating
  52. * if the <code>DropTarget</code> is currently accepting drops, and
  53. * a <code>FlavorMap</code> to use (or null).
  54. * <P>
  55. * @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
  56. * @param ops The default acceptable actions for this <code>DropTarget</code>
  57. * @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
  58. * @param act Is the <code>DropTarget</code> accepting drops.
  59. * @param fm The <code>FlavorMap</code> to use or null
  60. *
  61. */
  62. public DropTarget(Component c, int ops, DropTargetListener dtl, boolean act, FlavorMap fm) {
  63. super();
  64. component = c;
  65. setDefaultActions(ops);
  66. if (dtl != null) try {
  67. addDropTargetListener(dtl);
  68. } catch (TooManyListenersException tmle) {
  69. // do nothing!
  70. }
  71. if (c != null) {
  72. c.setDropTarget(this);
  73. setActive(act);
  74. }
  75. if (fm != null) flavorMap = fm;
  76. }
  77. /**
  78. * Construct a <code>DropTarget</code> given the <code>Component</code>
  79. * to associate itself with, an <code>int</code> representing
  80. * the default acceptable action(s)
  81. * to support, a <code>DropTargetListener</code>
  82. * to handle event processing, and a <code>boolean</code> indicating
  83. * if the <code>DropTarget</code> is currently accepting drops.
  84. * <P>
  85. * @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
  86. * @param ops The default acceptable actions for this <code>DropTarget</code>
  87. * @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
  88. * @param act Is the <code>DropTarget</code> accepting drops.
  89. *
  90. */
  91. public DropTarget(Component c, int ops, DropTargetListener dtl, boolean act) {
  92. this(c, ops, dtl, act, null);
  93. }
  94. /**
  95. * Construct a <code>DropTarget</code>.
  96. */
  97. public DropTarget() {
  98. this(null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null);
  99. }
  100. /**
  101. * Construct a <code>DropTarget</code> given the <code>Component</code>
  102. * to associate itself with, and the <code>DropTargetListener</code>
  103. * to handle event processing.
  104. * <P>
  105. * @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
  106. * @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
  107. */
  108. public DropTarget(Component c, DropTargetListener dtl) {
  109. this(c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null);
  110. }
  111. /**
  112. * Construct a <code>DropTarget</code> given the <code>Component</code>
  113. * to associate itself with, an <code>int</code> representing
  114. * the default acceptable action(s) to support, and a
  115. * <code>DropTargetListener</code> to handle event processing.
  116. * <P>
  117. * @param c The <code>Component</code> with which this <code>DropTarget</code> is associated
  118. * @param ops The default acceptable actions for this <code>DropTarget</code>
  119. * @param dtl The <code>DropTargetListener</code> for this <code>DropTarget</code>
  120. */
  121. public DropTarget(Component c, int ops, DropTargetListener dtl) {
  122. this(c, ops, dtl, true);
  123. }
  124. /**
  125. * Note: this interface is required to permit the safe association
  126. * of a DropTarget with a Component in one of two ways, either:
  127. * <code> component.setDropTarget(droptarget); </code>
  128. * or <code> droptarget.setComponent(component); </code>
  129. * <P>
  130. * @param c The new <code>Component</code> this <code>DropTarget</code>
  131. * is to be associated with.<P>
  132. */
  133. public synchronized void setComponent(Component c) {
  134. if (component == c || component != null && component.equals(c))
  135. return;
  136. Component old;
  137. ComponentPeer oldPeer = null;
  138. if ((old = component) != null) {
  139. clearAutoscroll();
  140. component = null;
  141. if (componentPeer != null) {
  142. oldPeer = componentPeer;
  143. removeNotify(componentPeer);
  144. }
  145. old.setDropTarget(null);
  146. }
  147. if ((component = c) != null) try {
  148. c.setDropTarget(this);
  149. } catch (Exception e) { // undo the change
  150. if (old != null) {
  151. old.setDropTarget(this);
  152. addNotify(oldPeer);
  153. }
  154. }
  155. }
  156. /**
  157. * This method returns the <code>Component</code> associated
  158. * with this <code>DropTarget</code>.
  159. * <P>
  160. * @return the current </code>Component</code>
  161. */
  162. public synchronized Component getComponent() {
  163. return component;
  164. }
  165. /**
  166. * Sets the default acceptable actions for this <code>DropTarget</code>
  167. * <P>
  168. * @param ops the default actions
  169. * <P>
  170. * @see java.awt.dnd.DnDConstants
  171. */
  172. public synchronized void setDefaultActions(int ops) {
  173. actions = ops & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE);
  174. if (dropTargetContext != null) dropTargetContext.setTargetActions(actions);
  175. }
  176. /**
  177. * This method returns an <code>int</code> representing the
  178. * current action(s) supported by this <code>DropTarget</code>.
  179. * <P>
  180. * @return the current default actions
  181. */
  182. public synchronized int getDefaultActions() {
  183. return actions;
  184. }
  185. /**
  186. * Set the DropTarget active if <code>true</code>,
  187. * inactive if <code>false</code>.
  188. * <P>
  189. * @param isActive sets the <code>DropTarget</code> (in)active.
  190. */
  191. public synchronized void setActive(boolean isActive) {
  192. if (isActive != active) {
  193. active = isActive;
  194. }
  195. if (!active) clearAutoscroll();
  196. }
  197. /**
  198. * This method returns a <code>boolean</code>
  199. * indicating whether or not this <code>DropTarget</code>
  200. * is currently active (ready to accept drops).
  201. * <P>
  202. * @return is the <code>DropTarget</code> active?
  203. */
  204. public synchronized boolean isActive() {
  205. return active;
  206. }
  207. /**
  208. * Add a new <code>DropTargetListener</code> (UNICAST SOURCE)
  209. * <P>
  210. * @param dtl The new <code>DropTargetListener</code>
  211. * <P>
  212. * @throws <code>TooManyListenersException</code> if a
  213. * <code>DropTargetListener</code> is already added to this
  214. * <code>DropTarget</code>.
  215. */
  216. public synchronized void addDropTargetListener(DropTargetListener dtl) throws TooManyListenersException {
  217. if (dtl == null) return;
  218. if (equals(dtl)) throw new IllegalArgumentException("DropTarget may not be its own Listener");
  219. if (dtListener == null)
  220. dtListener = dtl;
  221. else
  222. throw new TooManyListenersException();
  223. }
  224. /**
  225. * Remove the current <code>DropTargetListener</code> (UNICAST SOURCE)
  226. * <P>
  227. * @param dtl the DropTargetListener to deregister.
  228. */
  229. public synchronized void removeDropTargetListener(DropTargetListener dtl) {
  230. if (dtl != null && dtListener != null) {
  231. if(dtListener.equals(dtl))
  232. dtListener = null;
  233. else
  234. throw new IllegalArgumentException("listener mismatch");
  235. }
  236. }
  237. /**
  238. * The <code>DropTarget</code> intercepts
  239. * dragEnter() notifications before the
  240. * registered <code>DropTargetListener</code> gets them.
  241. * <P>
  242. * @param dtde the <code>DropTargetDragEvent</code>
  243. */
  244. public synchronized void dragEnter(DropTargetDragEvent dtde) {
  245. if (!active) return;
  246. if (dtListener != null) {
  247. dtListener.dragEnter(dtde);
  248. } else
  249. dtde.getDropTargetContext().setTargetActions(DnDConstants.ACTION_NONE);
  250. initializeAutoscrolling(dtde.getLocation());
  251. }
  252. /**
  253. * The <code>DropTarget</code>
  254. * intercepts dragOver() notifications before the
  255. * registered <code>DropTargetListener</code> gets them.
  256. * <P>
  257. * @param dtde the <code>DropTargetDragEvent</code>
  258. */
  259. public synchronized void dragOver(DropTargetDragEvent dtde) {
  260. if (!active) return;
  261. if (dtListener != null && active) dtListener.dragOver(dtde);
  262. updateAutoscroll(dtde.getLocation());
  263. }
  264. /**
  265. * The <code>DropTarget</code> intercepts
  266. * dropActionChanged() notifications before the
  267. * registered <code>DropTargetListener</code> gets them.
  268. * <P>
  269. * @param dtde the DropTargetDragEvent
  270. */
  271. public void dropActionChanged(DropTargetDragEvent dtde) {
  272. if (!active) return;
  273. if (dtListener != null) dtListener.dropActionChanged(dtde);
  274. updateAutoscroll(dtde.getLocation());
  275. }
  276. /**
  277. * The <code>DropTarget</code> intercepts
  278. * dragExit() notifications before the
  279. * registered <code>DropTargetListener</code> gets them.
  280. * <P>
  281. * @param dte the <code>DropTargetEvent</code>
  282. */
  283. public synchronized void dragExit(DropTargetEvent dte) {
  284. if (!active) return;
  285. if (dtListener != null && active) dtListener.dragExit(dte);
  286. clearAutoscroll();
  287. }
  288. /**
  289. * The <code>DropTarget</code> intercepts drop() notifications before the
  290. * registered <code>DropTargetListener</code> gets them.
  291. * <P>
  292. * @param dtde the <code>DropTargetDropEvent</code>
  293. */
  294. public synchronized void drop(DropTargetDropEvent dtde) {
  295. if (dtListener != null && active)
  296. dtListener.drop(dtde);
  297. else { // we should'nt get here ...
  298. dtde.rejectDrop();
  299. }
  300. }
  301. /**
  302. * This method returns the <code>FlavorMap</code>
  303. * associated with this <code>DropTarget</code>
  304. * <P>
  305. * @return the FlavorMap for this DropTarget
  306. */
  307. public FlavorMap getFlavorMap() { return flavorMap; }
  308. /**
  309. * This method sets the <code>FlavorMap</code> associated
  310. * with this <code>DropTarget</code>.
  311. * <P>
  312. * @param fm set the new <code>FlavorMap</code>, or null for default
  313. */
  314. public void setFlavorMap(FlavorMap fm) {
  315. flavorMap = fm == null ? defaultFlavorMap : fm;
  316. }
  317. /**
  318. * Notify the DropTarget that it has been associated with a Component
  319. *
  320. **********************************************************************
  321. * This method is usually called from java.awt.Component.addNotify() of
  322. * the Component associated with this DropTarget to notify the DropTarget
  323. * that a ComponentPeer has been associated with that Component.
  324. *
  325. * Calling this method, other than to notify this DropTarget of the
  326. * association of the ComponentPeer with the Component may result in
  327. * a malfunction of the DnD system.
  328. **********************************************************************
  329. * <P>
  330. * @param peer The Peer of the Component we are associated with!
  331. *
  332. */
  333. public void addNotify(ComponentPeer peer) {
  334. /*
  335. * FIX THIS FOR BETA4
  336. */
  337. // java.security.AccessController.checkPermission(new AWTPermission("setDTarget"));
  338. if (peer == componentPeer) return;
  339. componentPeer = peer;
  340. for (Component c = component; peer instanceof LightweightPeer; c = c.getParent())
  341. peer = c.getPeer();
  342. try {
  343. ((DropTargetPeer)(nativePeer = peer)).addDropTarget(this);
  344. } catch (ClassCastException cce) {
  345. nativePeer = null;
  346. // throw new InvalidDnDOperationException("No Native Peer support");
  347. }
  348. }
  349. /**
  350. * Notify the DropTarget that it has been disassociated from a Component
  351. *
  352. **********************************************************************
  353. * This method is usually called from java.awt.Component.removeNotify() of
  354. * the Component associated with this DropTarget to notify the DropTarget
  355. * that a ComponentPeer has been disassociated with that Component.
  356. *
  357. * Calling this method, other than to notify this DropTarget of the
  358. * disassociation of the ComponentPeer from the Component may result in
  359. * a malfunction of the DnD system.
  360. **********************************************************************
  361. * <P>
  362. * @param peer The Peer of the Component we are being disassociated from!
  363. */
  364. public void removeNotify(ComponentPeer peer) {
  365. if (nativePeer != null)
  366. ((DropTargetPeer)nativePeer).removeDropTarget(this);
  367. componentPeer = nativePeer = null;
  368. }
  369. /**
  370. * This method returns the <code>DropTargetContext</code> associated
  371. * with this <code>DropTarget</code>.
  372. * <P>
  373. * @return the <code>DropTargetContext</code> associated with this <code>DropTarget</code>.
  374. */
  375. public DropTargetContext getDropTargetContext() {
  376. if (dropTargetContext == null) dropTargetContext = createDropTargetContext();
  377. return dropTargetContext;
  378. }
  379. /**
  380. * Create the DropTargetContext associated with this DropTarget.
  381. * Subclasses may override this method to instantiate their own
  382. * DropTargetContext subclass.
  383. *
  384. * This call is typically *only* called by the platform's
  385. * DropTargetContextPeer as a drag operation encounters this
  386. * DropTarget. Accessing the Context while no Drag is current
  387. * has undefined results.
  388. */
  389. protected DropTargetContext createDropTargetContext() {
  390. return new DropTargetContext(this);
  391. }
  392. /*********************************************************************/
  393. /**
  394. * this protected nested class implements autoscrolling
  395. */
  396. protected static class DropTargetAutoScroller implements ActionListener {
  397. /**
  398. * construct a DropTargetAutoScroller
  399. * <P>
  400. * @param c the <code>Component</code>
  401. * @param p the <code>Point</code>
  402. */
  403. protected DropTargetAutoScroller(Component c, Point p) {
  404. super();
  405. component = c;
  406. autoScroll = (Autoscroll)component;
  407. Toolkit t = Toolkit.getDefaultToolkit();
  408. Integer initial = new Integer(100);
  409. Integer interval = new Integer(100);
  410. try {
  411. initial = (Integer)t.getDesktopProperty("DnD.Autoscroll.initialDelay");
  412. } catch (Exception e) {
  413. // ignore
  414. }
  415. try {
  416. interval = (Integer)t.getDesktopProperty("DnD.Autoscroll.interval");
  417. } catch (Exception e) {
  418. // ignore
  419. }
  420. timer = new Timer(interval.intValue(), this);
  421. timer.setCoalesce(true);
  422. timer.setInitialDelay(initial.intValue());
  423. locn = p;
  424. prev = p;
  425. try {
  426. hysteresis = ((Integer)t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis")).intValue();
  427. } catch (Exception e) {
  428. // ignore
  429. }
  430. timer.start();
  431. }
  432. /**
  433. * update the geometry of the autoscroll region
  434. */
  435. private void updateRegion() {
  436. Insets i = autoScroll.getAutoscrollInsets();
  437. Dimension size = component.getSize();
  438. if (size.width != outer.width || size.height != outer.height)
  439. outer.reshape(0, 0, size.width, size.height);
  440. if (inner.x != i.left || inner.y != i.top)
  441. inner.setLocation(i.left, i.top);
  442. int newWidth = size.width - (i.left + i.right);
  443. int newHeight = size.height - (i.top + i.bottom);
  444. if (newWidth != inner.width || newHeight != inner.height)
  445. inner.setSize(newWidth, newHeight);
  446. }
  447. /**
  448. * cause autoscroll to occur
  449. * <P>
  450. * @param newLocn the <code>Point</code>
  451. */
  452. protected synchronized void updateLocation(Point newLocn) {
  453. prev = locn;
  454. locn = newLocn;
  455. if (Math.abs(locn.x - prev.x) > hysteresis ||
  456. Math.abs(locn.y - prev.y) > hysteresis) {
  457. if (timer.isRunning()) timer.stop();
  458. } else {
  459. if (!timer.isRunning()) timer.start();
  460. }
  461. }
  462. /**
  463. * cause autoscrolling to stop
  464. */
  465. protected void stop() { timer.stop(); }
  466. /**
  467. * cause autoscroll to occur
  468. * <P>
  469. * @param e the <code>ActionEvent</code>
  470. */
  471. public synchronized void actionPerformed(ActionEvent e) {
  472. updateRegion();
  473. if (outer.contains(locn) && !inner.contains(locn))
  474. autoScroll.autoscroll(locn);
  475. }
  476. /*
  477. * fields
  478. */
  479. private Component component;
  480. private Autoscroll autoScroll;
  481. private Timer timer;
  482. private Point locn;
  483. private Point prev;
  484. private Rectangle outer = new Rectangle();
  485. private Rectangle inner = new Rectangle();
  486. private int hysteresis = 10;
  487. }
  488. /*********************************************************************/
  489. /**
  490. * create an embedded autoscroller
  491. * <P>
  492. * @param c the <code>Component</code>
  493. * @param p the <code>Point</code>
  494. */
  495. protected DropTargetAutoScroller createDropTargetAutoScroller(Component c, Point p) {
  496. return new DropTargetAutoScroller(c, p);
  497. }
  498. /**
  499. * initialize autoscrolling
  500. * <P>
  501. * @param p the <code>Point</code>
  502. */
  503. protected void initializeAutoscrolling(Point p) {
  504. if (component == null || !(component instanceof Autoscroll)) return;
  505. autoScroller = createDropTargetAutoScroller(component, p);
  506. }
  507. /**
  508. * update autoscrolling with current cursor locn
  509. * <P>
  510. * @param dragCursorLocn the <code>Point</code>
  511. */
  512. protected void updateAutoscroll(Point dragCursorLocn) {
  513. if (autoScroller != null) autoScroller.updateLocation(dragCursorLocn);
  514. }
  515. /**
  516. * clear autoscrolling
  517. */
  518. protected void clearAutoscroll() {
  519. if (autoScroller != null) {
  520. autoScroller.stop();
  521. autoScroller = null;
  522. }
  523. }
  524. /*
  525. * The DropTargetContext associated with this DropTarget
  526. */
  527. private transient DropTargetContext dropTargetContext;
  528. /*
  529. * The Component associated with this DropTarget
  530. */
  531. private Component component;
  532. /*
  533. * That Component's Peer
  534. */
  535. private transient ComponentPeer componentPeer;
  536. /*
  537. * That Component's "native" Peer
  538. */
  539. private transient ComponentPeer nativePeer;
  540. /*
  541. * Default permissable actions supported by this DropTarget
  542. *
  543. * @see #setDefaultActions
  544. * @see #getDefaultActions
  545. */
  546. int actions = DnDConstants.ACTION_COPY_OR_MOVE;
  547. /*
  548. * Is the Target accepting DND ops ...
  549. */
  550. boolean active = true;
  551. /*
  552. * the auto scrolling object
  553. */
  554. private transient DropTargetAutoScroller autoScroller;
  555. /*
  556. * The delegate
  557. */
  558. private DropTargetListener dtListener;
  559. /*
  560. * The FlavorMap
  561. */
  562. private transient FlavorMap flavorMap = defaultFlavorMap;
  563. }