1/*
2 * Copyright (c) 2000, 2014, 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 sun.awt.dnd;
27
28import java.awt.AWTEvent;
29import java.awt.Component;
30import java.awt.Cursor;
31import java.awt.EventQueue;
32import java.awt.Image;
33import java.awt.Point;
34
35import java.awt.datatransfer.Transferable;
36
37import java.awt.dnd.DnDConstants;
38import java.awt.dnd.DragSourceContext;
39import java.awt.dnd.DragSourceEvent;
40import java.awt.dnd.DragSourceDropEvent;
41import java.awt.dnd.DragSourceDragEvent;
42import java.awt.dnd.DragGestureEvent;
43import java.awt.dnd.InvalidDnDOperationException;
44
45import java.awt.dnd.peer.DragSourceContextPeer;
46
47import java.awt.event.InputEvent;
48import java.awt.event.MouseEvent;
49
50import java.util.Map;
51import java.util.SortedMap;
52
53import sun.awt.SunToolkit;
54import sun.awt.datatransfer.DataTransferer;
55import java.awt.datatransfer.DataFlavor;
56
57
58/**
59 * <p>
60 * TBC
61 * </p>
62 *
63 * @since 1.3.1
64 *
65 */
66public abstract class SunDragSourceContextPeer implements DragSourceContextPeer {
67
68    private DragGestureEvent  trigger;
69    private Component         component;
70    private Cursor            cursor;
71    private Image             dragImage;
72    private Point             dragImageOffset;
73    private long              nativeCtxt;
74    private DragSourceContext dragSourceContext;
75    private int               sourceActions;
76
77    private static boolean    dragDropInProgress = false;
78    private static boolean    discardingMouseEvents = false;
79
80    /*
81     * dispatch constants
82     */
83
84    protected static final int DISPATCH_ENTER   = 1;
85    protected static final int DISPATCH_MOTION  = 2;
86    protected static final int DISPATCH_CHANGED = 3;
87    protected static final int DISPATCH_EXIT    = 4;
88    protected static final int DISPATCH_FINISH  = 5;
89    protected static final int DISPATCH_MOUSE_MOVED  = 6;
90
91    /**
92     * construct a new SunDragSourceContextPeer
93     */
94
95    public SunDragSourceContextPeer(DragGestureEvent dge) {
96        trigger = dge;
97        if (trigger != null) {
98            component = trigger.getComponent();
99        } else {
100            component = null;
101        }
102    }
103
104    /**
105     * Synchro messages in AWT
106     */
107    public void startSecondaryEventLoop(){}
108    public void quitSecondaryEventLoop(){}
109
110    /**
111     * initiate a DnD operation ...
112     */
113
114    public void startDrag(DragSourceContext dsc, Cursor c, Image di, Point p)
115      throws InvalidDnDOperationException {
116
117        /* Fix for 4354044: don't initiate a drag if event sequence provided by
118         * DragGestureRecognizer is empty */
119        if (getTrigger().getTriggerEvent() == null) {
120            throw new InvalidDnDOperationException("DragGestureEvent has a null trigger");
121        }
122
123        dragSourceContext = dsc;
124        cursor            = c;
125        sourceActions     = getDragSourceContext().getSourceActions();
126        dragImage         = di;
127        dragImageOffset   = p;
128
129        Transferable transferable  = getDragSourceContext().getTransferable();
130        SortedMap<Long,DataFlavor> formatMap = DataTransferer.getInstance().
131            getFormatsForTransferable(transferable, DataTransferer.adaptFlavorMap
132                (getTrigger().getDragSource().getFlavorMap()));
133        long[] formats = DataTransferer.keysToLongArray(formatMap);
134        startDrag(transferable, formats, formatMap);
135
136        /*
137         * Fix for 4613903.
138         * Filter out all mouse events that are currently on the event queue.
139         */
140        discardingMouseEvents = true;
141        EventQueue.invokeLater(new Runnable() {
142                public void run() {
143                    discardingMouseEvents = false;
144                }
145            });
146    }
147
148    protected abstract void startDrag(Transferable trans,
149                                      long[] formats, Map<Long, DataFlavor> formatMap);
150
151    /**
152     * set cursor
153     */
154
155    public void setCursor(Cursor c) throws InvalidDnDOperationException {
156        synchronized (this) {
157            if (cursor == null || !cursor.equals(c)) {
158                cursor = c;
159                // NOTE: native context can be null at this point.
160                // setNativeCursor() should handle it properly.
161                setNativeCursor(getNativeContext(), c,
162                                c != null ? c.getType() : 0);
163            }
164        }
165    }
166
167    /**
168     * return cursor
169     */
170
171    public Cursor getCursor() {
172        return cursor;
173    }
174
175    /**
176     * Returns the drag image. If there is no image to drag,
177     * the returned value is {@code null}
178     *
179     * @return the reference to the drag image
180     */
181    public Image getDragImage() {
182        return dragImage;
183    }
184
185    /**
186     * Returns an anchor offset for the image to drag.
187     *
188     * @return a {@code Point} object that corresponds
189     * to coordinates of an anchor offset of the image
190     * relative to the upper left corner of the image.
191     * The point {@code (0,0)} returns by default.
192     */
193    public Point getDragImageOffset() {
194        if (dragImageOffset == null) {
195            return new Point(0,0);
196        }
197        return new Point(dragImageOffset);
198    }
199
200    /**
201     * downcall into native code
202     */
203
204
205    protected abstract void setNativeCursor(long nativeCtxt, Cursor c,
206                                            int cType);
207
208    protected synchronized void setTrigger(DragGestureEvent dge) {
209        trigger = dge;
210        if (trigger != null) {
211            component = trigger.getComponent();
212        } else {
213            component = null;
214        }
215    }
216
217    protected DragGestureEvent getTrigger() {
218        return trigger;
219    }
220
221    protected Component getComponent() {
222        return component;
223    }
224
225    protected synchronized void setNativeContext(long ctxt) {
226        nativeCtxt = ctxt;
227    }
228
229    protected synchronized long getNativeContext() {
230        return nativeCtxt;
231    }
232
233    protected DragSourceContext getDragSourceContext() {
234        return dragSourceContext;
235    }
236
237    /**
238     * Notify the peer that the transferables' DataFlavors have changed.
239     *
240     * No longer useful as the transferables are determined at the time
241     * of the drag.
242     */
243
244    public void transferablesFlavorsChanged() {
245    }
246
247
248
249
250
251    protected final void postDragSourceDragEvent(final int targetAction,
252                                                 final int modifiers,
253                                                 final int x, final int y,
254                                                 final int dispatchType) {
255
256        final int dropAction =
257            SunDragSourceContextPeer.convertModifiersToDropAction(modifiers,
258                                                                  sourceActions);
259
260        DragSourceDragEvent event =
261            new DragSourceDragEvent(getDragSourceContext(),
262                                    dropAction,
263                                    targetAction & sourceActions,
264                                    modifiers, x, y);
265        EventDispatcher dispatcher = new EventDispatcher(dispatchType, event);
266
267        SunToolkit.invokeLaterOnAppContext(
268            SunToolkit.targetToAppContext(getComponent()), dispatcher);
269
270        startSecondaryEventLoop();
271    }
272
273    /**
274     * upcall from native code
275     */
276
277    protected void dragEnter(final int targetActions,
278                           final int modifiers,
279                           final int x, final int y) {
280        postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_ENTER);
281    }
282
283    /**
284     * upcall from native code
285     */
286
287    private void dragMotion(final int targetActions,
288                            final int modifiers,
289                            final int x, final int y) {
290        postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_MOTION);
291    }
292
293    /**
294     * upcall from native code
295     */
296
297    private void operationChanged(final int targetActions,
298                                  final int modifiers,
299                                  final int x, final int y) {
300        postDragSourceDragEvent(targetActions, modifiers, x, y, DISPATCH_CHANGED);
301    }
302
303    /**
304     * upcall from native code
305     */
306
307    protected final void dragExit(final int x, final int y) {
308        DragSourceEvent event =
309            new DragSourceEvent(getDragSourceContext(), x, y);
310        EventDispatcher dispatcher =
311            new EventDispatcher(DISPATCH_EXIT, event);
312
313        SunToolkit.invokeLaterOnAppContext(
314            SunToolkit.targetToAppContext(getComponent()), dispatcher);
315
316        startSecondaryEventLoop();
317    }
318
319    /**
320     * upcall from native code
321     */
322
323    private void dragMouseMoved(final int targetActions,
324                                final int modifiers,
325                                final int x, final int y) {
326        postDragSourceDragEvent(targetActions, modifiers, x, y,
327                                DISPATCH_MOUSE_MOVED);
328    }
329
330    /**
331     * upcall from native code via implemented class (do)
332     */
333
334    protected final void dragDropFinished(final boolean success,
335                                          final int operations,
336                                          final int x, final int y) {
337        DragSourceEvent event =
338            new DragSourceDropEvent(getDragSourceContext(),
339                                    operations & sourceActions,
340                                    success, x, y);
341        EventDispatcher dispatcher =
342            new EventDispatcher(DISPATCH_FINISH, event);
343
344        SunToolkit.invokeLaterOnAppContext(
345            SunToolkit.targetToAppContext(getComponent()), dispatcher);
346
347        startSecondaryEventLoop();
348        setNativeContext(0);
349        dragImage = null;
350        dragImageOffset = null;
351    }
352
353    public static void setDragDropInProgress(boolean b)
354      throws InvalidDnDOperationException {
355        synchronized (SunDragSourceContextPeer.class) {
356            if (dragDropInProgress == b) {
357                throw new InvalidDnDOperationException(getExceptionMessage(b));
358            }
359            dragDropInProgress = b;
360        }
361    }
362
363    /**
364     * Filters out all mouse events that were on the java event queue when
365     * startDrag was called.
366     */
367    public static boolean checkEvent(AWTEvent event) {
368        if (discardingMouseEvents && event instanceof MouseEvent) {
369            MouseEvent mouseEvent = (MouseEvent)event;
370            if (!(mouseEvent instanceof SunDropTargetEvent)) {
371                return false;
372            }
373        }
374        return true;
375    }
376
377    public static void checkDragDropInProgress()
378      throws InvalidDnDOperationException {
379        if (dragDropInProgress) {
380            throw new InvalidDnDOperationException(getExceptionMessage(true));
381        }
382    }
383
384    private static String getExceptionMessage(boolean b) {
385        return b ? "Drag and drop in progress" : "No drag in progress";
386    }
387
388    public static int convertModifiersToDropAction(final int modifiers,
389                                                   final int supportedActions) {
390        int dropAction = DnDConstants.ACTION_NONE;
391
392        /*
393         * Fix for 4285634.
394         * Calculate the drop action to match Motif DnD behavior.
395         * If the user selects an operation (by pressing a modifier key),
396         * return the selected operation or ACTION_NONE if the selected
397         * operation is not supported by the drag source.
398         * If the user doesn't select an operation search the set of operations
399         * supported by the drag source for ACTION_MOVE, then for
400         * ACTION_COPY, then for ACTION_LINK and return the first operation
401         * found.
402         */
403        switch (modifiers & (InputEvent.SHIFT_DOWN_MASK |
404                             InputEvent.CTRL_DOWN_MASK)) {
405        case InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK:
406            dropAction = DnDConstants.ACTION_LINK; break;
407        case InputEvent.CTRL_DOWN_MASK:
408            dropAction = DnDConstants.ACTION_COPY; break;
409        case InputEvent.SHIFT_DOWN_MASK:
410            dropAction = DnDConstants.ACTION_MOVE; break;
411        default:
412            if ((supportedActions & DnDConstants.ACTION_MOVE) != 0) {
413                dropAction = DnDConstants.ACTION_MOVE;
414            } else if ((supportedActions & DnDConstants.ACTION_COPY) != 0) {
415                dropAction = DnDConstants.ACTION_COPY;
416            } else if ((supportedActions & DnDConstants.ACTION_LINK) != 0) {
417                dropAction = DnDConstants.ACTION_LINK;
418            }
419        }
420
421        return dropAction & supportedActions;
422    }
423
424    private void cleanup() {
425        trigger = null;
426        component = null;
427        cursor = null;
428        dragSourceContext = null;
429        SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null);
430        SunDragSourceContextPeer.setDragDropInProgress(false);
431    }
432
433    private class EventDispatcher implements Runnable {
434
435        private final int dispatchType;
436
437        private final DragSourceEvent event;
438
439        EventDispatcher(int dispatchType, DragSourceEvent event) {
440            switch (dispatchType) {
441            case DISPATCH_ENTER:
442            case DISPATCH_MOTION:
443            case DISPATCH_CHANGED:
444            case DISPATCH_MOUSE_MOVED:
445                if (!(event instanceof DragSourceDragEvent)) {
446                    throw new IllegalArgumentException("Event: " + event);
447                }
448                break;
449            case DISPATCH_EXIT:
450                break;
451            case DISPATCH_FINISH:
452                if (!(event instanceof DragSourceDropEvent)) {
453                    throw new IllegalArgumentException("Event: " + event);
454                }
455                break;
456            default:
457                throw new IllegalArgumentException("Dispatch type: " +
458                                                   dispatchType);
459            }
460
461            this.dispatchType  = dispatchType;
462            this.event         = event;
463        }
464
465        public void run() {
466            DragSourceContext dragSourceContext =
467                SunDragSourceContextPeer.this.getDragSourceContext();
468            try {
469                switch (dispatchType) {
470                case DISPATCH_ENTER:
471                    dragSourceContext.dragEnter((DragSourceDragEvent)event);
472                    break;
473                case DISPATCH_MOTION:
474                    dragSourceContext.dragOver((DragSourceDragEvent)event);
475                    break;
476                case DISPATCH_CHANGED:
477                    dragSourceContext.dropActionChanged((DragSourceDragEvent)event);
478                    break;
479                case DISPATCH_EXIT:
480                    dragSourceContext.dragExit(event);
481                    break;
482                case DISPATCH_MOUSE_MOVED:
483                    dragSourceContext.dragMouseMoved((DragSourceDragEvent)event);
484                    break;
485                case DISPATCH_FINISH:
486                    try {
487                        dragSourceContext.dragDropEnd((DragSourceDropEvent)event);
488                    } finally {
489                        SunDragSourceContextPeer.this.cleanup();
490                    }
491                    break;
492                default:
493                    throw new IllegalStateException("Dispatch type: " +
494                                                    dispatchType);
495                }
496            } finally {
497                 SunDragSourceContextPeer.this.quitSecondaryEventLoop();
498            }
499        }
500    }
501}
502