1/*
2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.awt.dnd;
27
28import java.awt.AWTError;
29import java.awt.Component;
30import java.awt.Cursor;
31import java.awt.Image;
32import java.awt.Point;
33import java.awt.Toolkit;
34import java.awt.datatransfer.DataFlavor;
35import java.awt.datatransfer.Transferable;
36import java.awt.datatransfer.UnsupportedFlavorException;
37import java.awt.dnd.peer.DragSourceContextPeer;
38import java.io.IOException;
39import java.io.InvalidObjectException;
40import java.io.ObjectInputStream;
41import java.io.ObjectOutputStream;
42import java.io.Serializable;
43import java.util.TooManyListenersException;
44
45import sun.awt.AWTAccessor;
46import sun.awt.ComponentFactory;
47
48/**
49 * The {@code DragSourceContext} class is responsible for managing the
50 * initiator side of the Drag and Drop protocol. In particular, it is responsible
51 * for managing drag event notifications to the
52 * {@linkplain DragSourceListener DragSourceListeners}
53 * and {@linkplain DragSourceMotionListener DragSourceMotionListeners}, and providing the
54 * {@link Transferable} representing the source data for the drag operation.
55 * <p>
56 * Note that the {@code DragSourceContext} itself
57 * implements the {@code DragSourceListener} and
58 * {@code DragSourceMotionListener} interfaces.
59 * This is to allow the platform peer
60 * (the {@link DragSourceContextPeer} instance)
61 * created by the {@link DragSource} to notify
62 * the {@code DragSourceContext} of
63 * state changes in the ongoing operation. This allows the
64 * {@code DragSourceContext} object to interpose
65 * itself between the platform and the
66 * listeners provided by the initiator of the drag operation.
67 * <p>
68 * <a id="defaultCursor"></a>
69 * By default, {@code DragSourceContext} sets the cursor as appropriate
70 * for the current state of the drag and drop operation. For example, if
71 * the user has chosen {@linkplain DnDConstants#ACTION_MOVE the move action},
72 * and the pointer is over a target that accepts
73 * the move action, the default move cursor is shown. When
74 * the pointer is over an area that does not accept the transfer,
75 * the default "no drop" cursor is shown.
76 * <p>
77 * This default handling mechanism is disabled when a custom cursor is set
78 * by the {@link #setCursor} method. When the default handling is disabled,
79 * it becomes the responsibility
80 * of the developer to keep the cursor up to date, by listening
81 * to the {@code DragSource} events and calling the {@code setCursor()} method.
82 * Alternatively, you can provide custom cursor behavior by providing
83 * custom implementations of the {@code DragSource}
84 * and the {@code DragSourceContext} classes.
85 *
86 * @see DragSourceListener
87 * @see DragSourceMotionListener
88 * @see DnDConstants
89 * @since 1.2
90 */
91
92public class DragSourceContext
93    implements DragSourceListener, DragSourceMotionListener, Serializable {
94
95    private static final long serialVersionUID = -115407898692194719L;
96
97    // used by updateCurrentCursor
98
99    /**
100     * An {@code int} used by updateCurrentCursor()
101     * indicating that the {@code Cursor} should change
102     * to the default (no drop) {@code Cursor}.
103     */
104    protected static final int DEFAULT = 0;
105
106    /**
107     * An {@code int} used by updateCurrentCursor()
108     * indicating that the {@code Cursor}
109     * has entered a {@code DropTarget}.
110     */
111    protected static final int ENTER   = 1;
112
113    /**
114     * An {@code int} used by updateCurrentCursor()
115     * indicating that the {@code Cursor} is
116     * over a {@code DropTarget}.
117     */
118    protected static final int OVER    = 2;
119
120    /**
121     * An {@code int} used by updateCurrentCursor()
122     * indicating that the user operation has changed.
123     */
124
125    protected static final int CHANGED = 3;
126
127    static {
128        AWTAccessor.setDragSourceContextAccessor(dsc -> dsc.peer);
129    }
130
131    /**
132     * Called from {@code DragSource}, this constructor creates a new
133     * {@code DragSourceContext} given the
134     * {@code DragSourceContextPeer} for this Drag, the
135     * {@code DragGestureEvent} that triggered the Drag, the initial
136     * {@code Cursor} to use for the Drag, an (optional)
137     * {@code Image} to display while the Drag is taking place, the offset
138     * of the {@code Image} origin from the hotspot at the instant of the
139     * triggering event, the {@code Transferable} subject data, and the
140     * {@code DragSourceListener} to use during the Drag and Drop
141     * operation.
142     * <br>
143     * If {@code DragSourceContextPeer} is {@code null}
144     * {@code NullPointerException} is thrown.
145     * <br>
146     * If {@code DragGestureEvent} is {@code null}
147     * {@code NullPointerException} is thrown.
148     * <br>
149     * If {@code Cursor} is {@code null} no exception is thrown and
150     * the default drag cursor behavior is activated for this drag operation.
151     * <br>
152     * If {@code Image} is {@code null} no exception is thrown.
153     * <br>
154     * If {@code Image} is not {@code null} and the offset is
155     * {@code null NullPointerException} is thrown.
156     * <br>
157     * If {@code Transferable} is {@code null}
158     * {@code NullPointerException} is thrown.
159     * <br>
160     * If {@code DragSourceListener} is {@code null} no exception
161     * is thrown.
162     *
163     * @param trigger    the triggering event
164     * @param dragCursor     the initial {@code Cursor} for this drag operation
165     *                       or {@code null} for the default cursor handling;
166     *                       see <a href="DragSourceContext.html#defaultCursor">class level documentation</a>
167     *                       for more details on the cursor handling mechanism during drag and drop
168     * @param dragImage  the {@code Image} to drag (or {@code null})
169     * @param offset     the offset of the image origin from the hotspot at the
170     *                   instant of the triggering event
171     * @param t          the {@code Transferable}
172     * @param dsl        the {@code DragSourceListener}
173     *
174     * @throws IllegalArgumentException if the {@code Component} associated
175     *         with the trigger event is {@code null}.
176     * @throws IllegalArgumentException if the {@code DragSource} for the
177     *         trigger event is {@code null}.
178     * @throws IllegalArgumentException if the drag action for the
179     *         trigger event is {@code DnDConstants.ACTION_NONE}.
180     * @throws IllegalArgumentException if the source actions for the
181     *         {@code DragGestureRecognizer} associated with the trigger
182     *         event are equal to {@code DnDConstants.ACTION_NONE}.
183     * @throws NullPointerException if dscp, trigger, or t are null, or
184     *         if dragImage is non-null and offset is null
185     */
186    public DragSourceContext(DragGestureEvent trigger, Cursor dragCursor,
187                             Image dragImage, Point offset, Transferable t,
188                             DragSourceListener dsl) {
189        Toolkit toolkit = Toolkit.getDefaultToolkit();
190        if (!(toolkit instanceof ComponentFactory)) {
191            throw new AWTError("Unsupported toolkit: " + toolkit);
192        }
193        DragSourceContextPeer dscp = ((ComponentFactory) toolkit).
194                createDragSourceContextPeer(trigger);
195
196        if (dscp == null) {
197            throw new NullPointerException("DragSourceContextPeer");
198        }
199
200        if (trigger == null) {
201            throw new NullPointerException("Trigger");
202        }
203
204        if (trigger.getDragSource() == null) {
205            throw new IllegalArgumentException("DragSource");
206        }
207
208        if (trigger.getComponent() == null) {
209            throw new IllegalArgumentException("Component");
210        }
211
212        if (trigger.getSourceAsDragGestureRecognizer().getSourceActions() ==
213                 DnDConstants.ACTION_NONE) {
214            throw new IllegalArgumentException("source actions");
215        }
216
217        if (trigger.getDragAction() == DnDConstants.ACTION_NONE) {
218            throw new IllegalArgumentException("no drag action");
219        }
220
221        if (t == null) {
222            throw new NullPointerException("Transferable");
223        }
224
225        if (dragImage != null && offset == null) {
226            throw new NullPointerException("offset");
227        }
228
229        peer         = dscp;
230        this.trigger = trigger;
231        cursor       = dragCursor;
232        transferable = t;
233        listener     = dsl;
234        sourceActions =
235            trigger.getSourceAsDragGestureRecognizer().getSourceActions();
236
237        useCustomCursor = (dragCursor != null);
238
239        updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT);
240    }
241
242    /**
243     * Returns the {@code DragSource}
244     * that instantiated this {@code DragSourceContext}.
245     *
246     * @return the {@code DragSource} that
247     *   instantiated this {@code DragSourceContext}
248     */
249
250    public DragSource   getDragSource() { return trigger.getDragSource(); }
251
252    /**
253     * Returns the {@code Component} associated with this
254     * {@code DragSourceContext}.
255     *
256     * @return the {@code Component} that started the drag
257     */
258
259    public Component    getComponent() { return trigger.getComponent(); }
260
261    /**
262     * Returns the {@code DragGestureEvent}
263     * that initially triggered the drag.
264     *
265     * @return the Event that triggered the drag
266     */
267
268    public DragGestureEvent getTrigger() { return trigger; }
269
270    /**
271     * Returns a bitwise mask of {@code DnDConstants} that
272     * represent the set of drop actions supported by the drag source for the
273     * drag operation associated with this {@code DragSourceContext}.
274     *
275     * @return the drop actions supported by the drag source
276     */
277    public int  getSourceActions() {
278        return sourceActions;
279    }
280
281    /**
282     * Sets the custom cursor for this drag operation to the specified
283     * {@code Cursor}.  If the specified {@code Cursor}
284     * is {@code null}, the default drag cursor behavior is
285     * activated for this drag operation, otherwise it is deactivated.
286     *
287     * @param c     the initial {@code Cursor} for this drag operation,
288     *                       or {@code null} for the default cursor handling;
289     *                       see {@linkplain Cursor class
290     *                       level documentation} for more details
291     *                       on the cursor handling during drag and drop
292     *
293     */
294
295    public synchronized void setCursor(Cursor c) {
296        useCustomCursor = (c != null);
297        setCursorImpl(c);
298    }
299
300    /**
301     * Returns the current custom drag {@code Cursor}.
302     *
303     * @return the current custom drag {@code Cursor}, if it was set
304     *         otherwise returns {@code null}.
305     * @see #setCursor
306     */
307
308    public Cursor getCursor() { return cursor; }
309
310    /**
311     * Add a {@code DragSourceListener} to this
312     * {@code DragSourceContext} if one has not already been added.
313     * If a {@code DragSourceListener} already exists,
314     * this method throws a {@code TooManyListenersException}.
315     *
316     * @param dsl the {@code DragSourceListener} to add.
317     * Note that while {@code null} is not prohibited,
318     * it is not acceptable as a parameter.
319     *
320     * @throws TooManyListenersException if
321     * a {@code DragSourceListener} has already been added
322     */
323
324    public synchronized void addDragSourceListener(DragSourceListener dsl) throws TooManyListenersException {
325        if (dsl == null) return;
326
327        if (equals(dsl)) throw new IllegalArgumentException("DragSourceContext may not be its own listener");
328
329        if (listener != null)
330            throw new TooManyListenersException();
331        else
332            listener = dsl;
333    }
334
335    /**
336     * Removes the specified {@code DragSourceListener}
337     * from  this {@code DragSourceContext}.
338     *
339     * @param dsl the {@code DragSourceListener} to remove;
340     *     note that while {@code null} is not prohibited,
341     *     it is not acceptable as a parameter
342     */
343
344    public synchronized void removeDragSourceListener(DragSourceListener dsl) {
345        if (listener != null && listener.equals(dsl)) {
346            listener = null;
347        } else
348            throw new IllegalArgumentException();
349    }
350
351    /**
352     * Notifies the peer that the {@code Transferable}'s
353     * {@code DataFlavor}s have changed.
354     */
355
356    public void transferablesFlavorsChanged() {
357        if (peer != null) peer.transferablesFlavorsChanged();
358    }
359
360    /**
361     * Calls {@code dragEnter} on the
362     * {@code DragSourceListener}s registered with this
363     * {@code DragSourceContext} and with the associated
364     * {@code DragSource}, and passes them the specified
365     * {@code DragSourceDragEvent}.
366     *
367     * @param dsde the {@code DragSourceDragEvent}
368     */
369    public void dragEnter(DragSourceDragEvent dsde) {
370        DragSourceListener dsl = listener;
371        if (dsl != null) {
372            dsl.dragEnter(dsde);
373        }
374        getDragSource().processDragEnter(dsde);
375
376        updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), ENTER);
377    }
378
379    /**
380     * Calls {@code dragOver} on the
381     * {@code DragSourceListener}s registered with this
382     * {@code DragSourceContext} and with the associated
383     * {@code DragSource}, and passes them the specified
384     * {@code DragSourceDragEvent}.
385     *
386     * @param dsde the {@code DragSourceDragEvent}
387     */
388    public void dragOver(DragSourceDragEvent dsde) {
389        DragSourceListener dsl = listener;
390        if (dsl != null) {
391            dsl.dragOver(dsde);
392        }
393        getDragSource().processDragOver(dsde);
394
395        updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), OVER);
396    }
397
398    /**
399     * Calls {@code dragExit} on the
400     * {@code DragSourceListener}s registered with this
401     * {@code DragSourceContext} and with the associated
402     * {@code DragSource}, and passes them the specified
403     * {@code DragSourceEvent}.
404     *
405     * @param dse the {@code DragSourceEvent}
406     */
407    public void dragExit(DragSourceEvent dse) {
408        DragSourceListener dsl = listener;
409        if (dsl != null) {
410            dsl.dragExit(dse);
411        }
412        getDragSource().processDragExit(dse);
413
414        updateCurrentCursor(DnDConstants.ACTION_NONE, DnDConstants.ACTION_NONE, DEFAULT);
415    }
416
417    /**
418     * Calls {@code dropActionChanged} on the
419     * {@code DragSourceListener}s registered with this
420     * {@code DragSourceContext} and with the associated
421     * {@code DragSource}, and passes them the specified
422     * {@code DragSourceDragEvent}.
423     *
424     * @param dsde the {@code DragSourceDragEvent}
425     */
426    public void dropActionChanged(DragSourceDragEvent dsde) {
427        DragSourceListener dsl = listener;
428        if (dsl != null) {
429            dsl.dropActionChanged(dsde);
430        }
431        getDragSource().processDropActionChanged(dsde);
432
433        updateCurrentCursor(getSourceActions(), dsde.getTargetActions(), CHANGED);
434    }
435
436    /**
437     * Calls {@code dragDropEnd} on the
438     * {@code DragSourceListener}s registered with this
439     * {@code DragSourceContext} and with the associated
440     * {@code DragSource}, and passes them the specified
441     * {@code DragSourceDropEvent}.
442     *
443     * @param dsde the {@code DragSourceDropEvent}
444     */
445    public void dragDropEnd(DragSourceDropEvent dsde) {
446        DragSourceListener dsl = listener;
447        if (dsl != null) {
448            dsl.dragDropEnd(dsde);
449        }
450        getDragSource().processDragDropEnd(dsde);
451    }
452
453    /**
454     * Calls {@code dragMouseMoved} on the
455     * {@code DragSourceMotionListener}s registered with the
456     * {@code DragSource} associated with this
457     * {@code DragSourceContext}, and them passes the specified
458     * {@code DragSourceDragEvent}.
459     *
460     * @param dsde the {@code DragSourceDragEvent}
461     * @since 1.4
462     */
463    public void dragMouseMoved(DragSourceDragEvent dsde) {
464        getDragSource().processDragMouseMoved(dsde);
465    }
466
467    /**
468     * Returns the {@code Transferable} associated with
469     * this {@code DragSourceContext}.
470     *
471     * @return the {@code Transferable}
472     */
473    public Transferable getTransferable() { return transferable; }
474
475    /**
476     * If the default drag cursor behavior is active, this method
477     * sets the default drag cursor for the specified actions
478     * supported by the drag source, the drop target action,
479     * and status, otherwise this method does nothing.
480     *
481     * @param sourceAct the actions supported by the drag source
482     * @param targetAct the drop target action
483     * @param status one of the fields {@code DEFAULT},
484     *               {@code ENTER}, {@code OVER},
485     *               {@code CHANGED}
486     */
487    @SuppressWarnings("fallthrough")
488    protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) {
489
490        // if the cursor has been previously set then don't do any defaults
491        // processing.
492
493        if (useCustomCursor) {
494            return;
495        }
496
497        // do defaults processing
498
499        Cursor c = null;
500
501        switch (status) {
502            default:
503                targetAct = DnDConstants.ACTION_NONE;
504            case ENTER:
505            case OVER:
506            case CHANGED:
507                int    ra = sourceAct & targetAct;
508
509                if (ra == DnDConstants.ACTION_NONE) { // no drop possible
510                    if ((sourceAct & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
511                        c = DragSource.DefaultLinkNoDrop;
512                    else if ((sourceAct & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
513                        c = DragSource.DefaultMoveNoDrop;
514                    else
515                        c = DragSource.DefaultCopyNoDrop;
516                } else { // drop possible
517                    if ((ra & DnDConstants.ACTION_LINK) == DnDConstants.ACTION_LINK)
518                        c = DragSource.DefaultLinkDrop;
519                    else if ((ra & DnDConstants.ACTION_MOVE) == DnDConstants.ACTION_MOVE)
520                        c = DragSource.DefaultMoveDrop;
521                    else
522                        c = DragSource.DefaultCopyDrop;
523                }
524        }
525
526        setCursorImpl(c);
527    }
528
529    private void setCursorImpl(Cursor c) {
530        if (cursor == null || !cursor.equals(c)) {
531            cursor = c;
532            if (peer != null) peer.setCursor(cursor);
533        }
534    }
535
536    /**
537     * Serializes this {@code DragSourceContext}. This method first
538     * performs default serialization. Next, this object's
539     * {@code Transferable} is written out if and only if it can be
540     * serialized. If not, {@code null} is written instead. In this case,
541     * a {@code DragSourceContext} created from the resulting deserialized
542     * stream will contain a dummy {@code Transferable} which supports no
543     * {@code DataFlavor}s. Finally, this object's
544     * {@code DragSourceListener} is written out if and only if it can be
545     * serialized. If not, {@code null} is written instead.
546     *
547     * @serialData The default serializable fields, in alphabetical order,
548     *             followed by either a {@code Transferable} instance, or
549     *             {@code null}, followed by either a
550     *             {@code DragSourceListener} instance, or
551     *             {@code null}.
552     * @since 1.4
553     */
554    private void writeObject(ObjectOutputStream s) throws IOException {
555        s.defaultWriteObject();
556
557        s.writeObject(SerializationTester.test(transferable)
558                      ? transferable : null);
559        s.writeObject(SerializationTester.test(listener)
560                      ? listener : null);
561    }
562
563    /**
564     * Deserializes this {@code DragSourceContext}. This method first
565     * performs default deserialization for all non-{@code transient}
566     * fields. This object's {@code Transferable} and
567     * {@code DragSourceListener} are then deserialized as well by using
568     * the next two objects in the stream. If the resulting
569     * {@code Transferable} is {@code null}, this object's
570     * {@code Transferable} is set to a dummy {@code Transferable}
571     * which supports no {@code DataFlavor}s.
572     *
573     * @since 1.4
574     */
575    private void readObject(ObjectInputStream s)
576        throws ClassNotFoundException, IOException
577    {
578        ObjectInputStream.GetField f = s.readFields();
579
580        DragGestureEvent newTrigger = (DragGestureEvent)f.get("trigger", null);
581        if (newTrigger == null) {
582            throw new InvalidObjectException("Null trigger");
583        }
584        if (newTrigger.getDragSource() == null) {
585            throw new InvalidObjectException("Null DragSource");
586        }
587        if (newTrigger.getComponent() == null) {
588            throw new InvalidObjectException("Null trigger component");
589        }
590
591        int newSourceActions = f.get("sourceActions", 0)
592                & (DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK);
593        if (newSourceActions == DnDConstants.ACTION_NONE) {
594            throw new InvalidObjectException("Invalid source actions");
595        }
596        int triggerActions = newTrigger.getDragAction();
597        if (triggerActions != DnDConstants.ACTION_COPY &&
598                triggerActions != DnDConstants.ACTION_MOVE &&
599                triggerActions != DnDConstants.ACTION_LINK) {
600            throw new InvalidObjectException("No drag action");
601        }
602        trigger = newTrigger;
603
604        cursor = (Cursor)f.get("cursor", null);
605        useCustomCursor = f.get("useCustomCursor", false);
606        sourceActions = newSourceActions;
607
608        transferable = (Transferable)s.readObject();
609        listener = (DragSourceListener)s.readObject();
610
611        // Implementation assumes 'transferable' is never null.
612        if (transferable == null) {
613            if (emptyTransferable == null) {
614                emptyTransferable = new Transferable() {
615                        public DataFlavor[] getTransferDataFlavors() {
616                            return new DataFlavor[0];
617                        }
618                        public boolean isDataFlavorSupported(DataFlavor flavor)
619                        {
620                            return false;
621                        }
622                        public Object getTransferData(DataFlavor flavor)
623                            throws UnsupportedFlavorException
624                        {
625                            throw new UnsupportedFlavorException(flavor);
626                        }
627                    };
628            }
629            transferable = emptyTransferable;
630        }
631    }
632
633    private static Transferable emptyTransferable;
634
635    /*
636     * fields
637     */
638    private final transient DragSourceContextPeer peer;
639
640    /**
641     * The event which triggered the start of the drag.
642     *
643     * @serial
644     */
645    private DragGestureEvent    trigger;
646
647    /**
648     * The current drag cursor.
649     *
650     * @serial
651     */
652    private Cursor              cursor;
653
654    private transient Transferable      transferable;
655
656    private transient DragSourceListener    listener;
657
658    /**
659     * {@code true} if the custom drag cursor is used instead of the
660     * default one.
661     *
662     * @serial
663     */
664    private boolean useCustomCursor;
665
666    /**
667     * A bitwise mask of {@code DnDConstants} that represents the set of
668     * drop actions supported by the drag source for the drag operation associated
669     * with this {@code DragSourceContext.}
670     *
671     * @serial
672     */
673    private int sourceActions;
674}
675