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