001    /* DropTarget.java --
002       Copyright (C) 2002, 2003, 2004  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt.dnd;
040    
041    import java.awt.Component;
042    import java.awt.GraphicsEnvironment;
043    import java.awt.HeadlessException;
044    import java.awt.Insets;
045    import java.awt.Point;
046    import java.awt.Rectangle;
047    import java.awt.datatransfer.FlavorMap;
048    import java.awt.datatransfer.SystemFlavorMap;
049    import java.awt.dnd.peer.DropTargetPeer;
050    import java.awt.event.ActionEvent;
051    import java.awt.event.ActionListener;
052    import java.awt.peer.ComponentPeer;
053    import java.awt.peer.LightweightPeer;
054    import java.io.Serializable;
055    import java.util.EventListener;
056    import java.util.TooManyListenersException;
057    
058    import javax.swing.Timer;
059    
060    /**
061     * @author Michael Koch
062     * @since 1.2
063     */
064    public class DropTarget
065      implements DropTargetListener, EventListener, Serializable
066    {
067      /**
068       * Compatible with JDK 1.2+
069       */
070      private static final long serialVersionUID = -6283860791671019047L;
071    
072      protected static class DropTargetAutoScroller
073        implements ActionListener
074      {
075        /**
076         * The threshold that keeps the autoscroller running.
077         */
078        private static final int HYSTERESIS = 10;
079    
080        /**
081         * The initial timer delay.
082         */
083        private static final int DELAY = 100;
084    
085        private Component component;
086        private Point point;
087    
088        /**
089         * The timer that triggers autoscrolling.
090         */
091        private Timer timer;
092    
093        /**
094         * The outer region of the scroller. This is the component's size.
095         */
096        private Rectangle outer;
097    
098        /**
099         * The inner region of the scroller. This is the component size without
100         * the autoscroll insets.
101         */
102        private Rectangle inner;
103    
104        protected DropTargetAutoScroller (Component c, Point p)
105        {
106          component = c;
107          point = p;
108          timer = new Timer(DELAY, this);
109          timer.setCoalesce(true);
110          timer.start();
111        }
112    
113        protected void updateLocation (Point newLocn)
114        {
115          Point previous = point;
116          point = newLocn;
117          if (Math.abs(point.x - previous.x) > HYSTERESIS
118              || Math.abs(point.y - previous.y) > HYSTERESIS)
119            {
120              if (timer.isRunning())
121                timer.stop();
122            }
123          else
124            {
125              if (! timer.isRunning())
126                timer.start();
127            }
128        }
129    
130        protected void stop ()
131        {
132          timer.start();
133        }
134    
135        public void actionPerformed (ActionEvent e)
136        {
137          Autoscroll autoScroll = (Autoscroll) component;
138    
139          // First synchronize the inner and outer rectangles.
140          Insets i = autoScroll.getAutoscrollInsets();
141          int width = component.getWidth();
142          int height = component.getHeight();
143          if (width != outer.width || height != outer.height)
144            outer.setBounds(0, 0, width, height);
145          if (inner.x != i.left || inner.y != i.top)
146            inner.setLocation(i.left, i.top);
147          int inWidth = width - i.left - i.right;
148          int inHeight = height - i.top - i.bottom;
149          if (inWidth != inner.width || inHeight != inner.height)
150            inner.setSize(inWidth, inHeight);
151    
152          // Scroll if the outer rectangle contains the location, but the
153          // inner doesn't.
154          if (outer.contains(point) && ! inner.contains(point))
155            autoScroll.autoscroll(point);
156        }
157      }
158    
159      private Component component;
160      private FlavorMap flavorMap;
161      private int actions;
162      private DropTargetPeer peer;
163      private DropTargetContext dropTargetContext;
164      private DropTargetListener dropTargetListener;
165      private DropTarget.DropTargetAutoScroller autoscroller;
166      private boolean active = true;
167    
168      /**
169       * Creates a <code>DropTarget</code> object.
170       *
171       * @exception HeadlessException If GraphicsEnvironment.isHeadless()
172       * returns true.
173       */
174      public DropTarget ()
175      {
176        this (null, DnDConstants.ACTION_COPY_OR_MOVE, null, true, null);
177      }
178    
179      /**
180       * Creates a <code>DropTarget</code> object.
181       *
182       * @exception HeadlessException If GraphicsEnvironment.isHeadless()
183       * returns true.
184       */
185      public DropTarget (Component c, DropTargetListener dtl)
186      {
187        this (c, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true, null);
188      }
189    
190      /**
191       * Creates a <code>DropTarget</code> object.
192       *
193       * @exception HeadlessException If GraphicsEnvironment.isHeadless()
194       * returns true.
195       */
196      public DropTarget (Component c, int i, DropTargetListener dtl)
197      {
198        this (c, i, dtl, true, null);
199      }
200    
201      /**
202       * Creates a <code>DropTarget</code> object.
203       *
204       * @exception HeadlessException If GraphicsEnvironment.isHeadless()
205       * returns true.
206       */
207      public DropTarget (Component c, int i, DropTargetListener dtl, boolean b)
208      {
209        this (c, i, dtl, b, null);
210      }
211    
212      /**
213       * Creates a <code>DropTarget</code> object.
214       *
215       * @exception HeadlessException If GraphicsEnvironment.isHeadless()
216       * returns true.
217       */
218      public DropTarget (Component c, int i, DropTargetListener dtl, boolean b,
219                         FlavorMap fm)
220      {
221        if (GraphicsEnvironment.isHeadless ())
222          throw new HeadlessException ();
223    
224        setComponent(c);
225        setDefaultActions(i);
226        dropTargetListener = dtl;
227    
228        if (fm == null)
229          flavorMap = SystemFlavorMap.getDefaultFlavorMap();
230        else
231          flavorMap = fm;
232    
233        setActive (b);
234    
235        if (c != null)
236          c.setDropTarget(this);
237      }
238    
239      /**
240       * Sets the component associated with this drop target object.
241       */
242      public void setComponent (Component c)
243      {
244        if (component != null)
245          clearAutoscroll();
246        component = c;
247      }
248    
249      /**
250       * Returns the component associated with this drop target object.
251       */
252      public Component getComponent ()
253      {
254        return component;
255      }
256    
257      /**
258       * Sets the default actions.
259       */
260      public void setDefaultActions (int ops)
261      {
262        actions = ops;
263      }
264    
265      /**
266       * Returns the default actions.
267       */
268      public int getDefaultActions ()
269      {
270        return actions;
271      }
272    
273      public void setActive (boolean active)
274      {
275        this.active = active;
276        if (! active)
277          clearAutoscroll();
278      }
279    
280      public boolean isActive()
281      {
282        return active;
283      }
284    
285      /**
286       * Adds a new <code>DropTargetListener</code>.
287       *
288       * @exception TooManyListenersException Sun's JDK does not, despite
289       * documentation, throw this exception here when you install an additional
290       * <code>DropTargetListener</code>.  So to be compatible, we do the same
291       * thing.
292       */
293      public void addDropTargetListener (DropTargetListener dtl)
294        throws TooManyListenersException
295      {
296        if (dtl == null)
297          return;
298    
299        if (dtl.equals(this))
300          throw new IllegalArgumentException();
301    
302        if (dropTargetListener != null)
303          throw new TooManyListenersException();
304    
305        dropTargetListener = dtl;
306      }
307    
308      public void removeDropTargetListener(DropTargetListener dtl)
309      {
310        if (dropTargetListener != null)
311          dropTargetListener = null;
312      }
313    
314      public void dragEnter(DropTargetDragEvent dtde)
315      {
316        if (active)
317          {
318            if (dropTargetListener != null)
319              dropTargetListener.dragEnter(dtde);
320            initializeAutoscrolling(dtde.getLocation());
321          }
322      }
323    
324      public void dragOver(DropTargetDragEvent dtde)
325      {
326        if (active)
327          {
328            if (dropTargetListener != null)
329              dropTargetListener.dragOver(dtde);
330            updateAutoscroll(dtde.getLocation());
331          }
332      }
333    
334      public void dropActionChanged(DropTargetDragEvent dtde)
335      {
336        if (active)
337          {
338            if (dropTargetListener != null)
339              dropTargetListener.dropActionChanged(dtde);
340            updateAutoscroll(dtde.getLocation());
341          }
342      }
343    
344      public void dragExit(DropTargetEvent dte)
345      {
346        if (active)
347          {
348            if (dropTargetListener != null)
349              dropTargetListener.dragExit(dte);
350            clearAutoscroll();
351          }
352      }
353    
354      public void drop(DropTargetDropEvent dtde)
355      {
356        clearAutoscroll();
357        if (dropTargetListener != null)
358          dropTargetListener.drop(dtde);
359      }
360    
361      public FlavorMap getFlavorMap()
362      {
363        return flavorMap;
364      }
365    
366      public void setFlavorMap(FlavorMap fm)
367      {
368        flavorMap = fm;
369      }
370    
371      public void addNotify(ComponentPeer p)
372      {
373        Component c = component;
374        while (c != null && p instanceof LightweightPeer)
375          {
376            p = c.getPeer();
377            c = c.getParent();
378          }
379    
380        if (p instanceof DropTargetPeer)
381          {
382            peer = ((DropTargetPeer) p);
383            peer.addDropTarget(this);
384          }
385        else
386          peer = null;
387      }
388    
389      public void removeNotify(ComponentPeer p)
390      {
391        ((DropTargetPeer) peer).removeDropTarget(this);
392        peer = null;
393        p = null;
394      }
395    
396      public DropTargetContext getDropTargetContext()
397      {
398        if (dropTargetContext == null)
399          dropTargetContext = createDropTargetContext ();
400    
401        return dropTargetContext;
402      }
403    
404      protected DropTargetContext createDropTargetContext()
405      {
406        if (dropTargetContext == null)
407          dropTargetContext = new DropTargetContext (this);
408    
409        return dropTargetContext;
410      }
411    
412      protected DropTarget.DropTargetAutoScroller createDropTargetAutoScroller
413                                                           (Component c, Point p)
414      {
415        return new DropTarget.DropTargetAutoScroller (c, p);
416      }
417    
418      protected void initializeAutoscrolling(Point p)
419      {
420        if (component instanceof Autoscroll) // Checks for null too.
421          autoscroller = createDropTargetAutoScroller (component, p);
422      }
423    
424      protected void updateAutoscroll(Point dragCursorLocn)
425      {
426        if (autoscroller != null)
427          autoscroller.updateLocation(dragCursorLocn);
428      }
429    
430      protected void clearAutoscroll()
431      {
432        if (autoscroller != null)
433          {
434            autoscroller.stop();
435            autoscroller = null;
436          }
437      }
438    } // class DropTarget