forked from AuroraMiddleware/gtk
f28aa1ba02
GdkEvent has been a "I-can't-believe-this-is-not-OOP" type for ages, using a union of sub-types. This has always been problematic when it comes to implementing accessor functions: either you get generic API that takes a GdkEvent and uses a massive switch() to determine which event types have the data you're looking for; or you create namespaced accessors, but break language bindings horribly, as boxed types cannot have derived types. The recent conversion of GskRenderNode (which had similar issues) to GTypeInstance, and the fact that GdkEvent is now a completely opaque type, provide us with the chance of moving GdkEvent to GTypeInstance, and have sub-types for GdkEvent. The change from boxed type to GTypeInstance is pretty small, all things considered, but ends up cascading to a larger commit, as we still have backends and code in GTK trying to access GdkEvent structures directly. Additionally, the naming of the public getter functions requires renaming all the data structures to conform to the namespace/type-name pattern.
2584 lines
76 KiB
C
2584 lines
76 KiB
C
/* GDK - The GIMP Drawing Kit
|
|
* Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald
|
|
* Copyright (C) 2001 Archaeopteryx Software Inc.
|
|
* Copyright (C) 1998-2002 Tor Lillqvist
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
|
|
* file for a list of people on the GTK+ Team. See the ChangeLog
|
|
* files for a list of changes. These files are distributed with
|
|
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <string.h>
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
|
|
/*
|
|
* Support for OLE-2 drag and drop added at Archaeopteryx Software, 2001
|
|
* For more information, do not contact Stephan R.A. Deibel (sdeibel@archaeopteryx.com),
|
|
* because the code went through multiple modifications since then.
|
|
*
|
|
* Notes on the implementation:
|
|
*
|
|
* Source drag context, IDropSource and IDataObject for it are created
|
|
* (almost) simultaneously, whereas target drag context and IDropTarget
|
|
* are separated in time - IDropTarget is created when a window is made
|
|
* to accept drops, while target drag context is created when a dragging
|
|
* cursor enters the window and is destroyed when that cursor leaves
|
|
* the window.
|
|
*
|
|
* There's a mismatch between data types supported by W32 (W32 formats)
|
|
* and by GTK+ (GDK contentformats).
|
|
* To account for it the data is transmuted back and forth. There are two
|
|
* main points of transmutation:
|
|
* * GdkWin32HDATAOutputStream: transmutes GTK+ data to W32 data
|
|
* * GdkWin32Drop: transmutes W32 data to GTK+ data
|
|
*
|
|
* There are also two points where data formats are considered:
|
|
* * When source drag context is created, it gets a list of GDK contentformats
|
|
* that it supports, these are matched to the W32 formats they
|
|
* correspond to (possibly with transmutation). New W32 formats for
|
|
* GTK+-specific contentformats are also created here (see below).
|
|
* * When target drop context is created, it queries the IDataObject
|
|
* for the list of W32 formats it supports and matches these to
|
|
* corresponding GDK contentformats that it will be able to provide
|
|
* (possibly with transmutation) later. Missing GDK contentformats for
|
|
* W32-specific formats are also created here (see below).
|
|
*
|
|
* W32 formats are integers (CLIPFORMAT), while GTK+ contentformats
|
|
* are mime/type strings, and cannot be used interchangeably.
|
|
*
|
|
* To accommodate advanced GTK+ applications the code allows them to
|
|
* register drop targets that accept W32 data formats, and to register
|
|
* drag sources that provide W32 data formats. To do that they must
|
|
* register with the mime/type "application/x.windows.ZZZ", where
|
|
* ZZZ is the string name of the format in question
|
|
* (for example, "Shell IDList Array") or, for unnamed pre-defined
|
|
* formats, register with the stringified constant name of the format
|
|
* in question (for example, "CF_UNICODETEXT").
|
|
* If such contentformat is accepted/provided, GDK will not try to
|
|
* transmute it to/from something else. Otherwise GDK will do the following
|
|
* transmutation:
|
|
* * If GTK+ application provides image/png, image/gif or image/jpeg,
|
|
* GDK will claim to also provide "PNG", "GIF" or "JFIF" respectively,
|
|
* and will pass these along verbatim.
|
|
* * If GTK+ application provides any GdkPixbuf-compatible contentformat,
|
|
* GDK will also offer "PNG" and CF_DIB W32 formats.
|
|
* * If GTK+ application provides text/plain;charset=utf8, GDK will also offer
|
|
* CF_UNICODETEXT (UTF-16-encoded) and CF_TEXT (encoded with thread-
|
|
* and locale-depenant codepage), and will do the conversion when such
|
|
* data is requested.
|
|
* * If GTK+ application accepts image/png, image/gif or image/jpeg,
|
|
* GDK will claim to also accept "PNG", "GIF" or "JFIF" respectively,
|
|
* and will pass these along verbatim.
|
|
* * If GTK+ application accepts image/bmp, GDK will
|
|
* claim to accept CF_DIB W32 format, and will convert
|
|
* it, changing the header, when such data is provided.
|
|
* * If GTK+ application accepts text/plain;charset=utf8, GDK will
|
|
* claim to accept CF_UNICODETEXT and CF_TEXT, and will do
|
|
* the conversion when such data is provided.
|
|
* * If GTK+ application accepts text/uri-list, GDK will
|
|
* claim to accept "Shell IDList Array", and will do the
|
|
* conversion when such data is provided.
|
|
*
|
|
* Currently the conversion from text/uri-list to "Shell IDList Array" is not
|
|
* implemented, so it's not possible to drag & drop files from GTK+
|
|
* applications to non-GTK+ applications the same way one can drag files
|
|
* from Windows Explorer.
|
|
*
|
|
* To increase inter-GTK compatibility, GDK will register GTK+-specific
|
|
* formats by their mime/types, as-is (i.e "text/plain;charset=utf-8", for example).
|
|
* That way two GTK+ applications can exchange data in their native formats
|
|
* (both well-known ones, such as text/plain;charset=utf8, and special,
|
|
* known only to specific applications). This will work just
|
|
* fine as long as both applications agree on what kind of data is stored
|
|
* under such format exactly.
|
|
*
|
|
* Note that clipboard format space is limited, there can only be 16384
|
|
* of them for a particular user session. Therefore it is highly inadvisable
|
|
* to create and register such formats out of the whole cloth, dynamically.
|
|
* If more flexibility is needed, register one format that has some
|
|
* internal indicators of the kind of data it contains, then write the application
|
|
* in such a way that it requests the data and inspects its header before deciding
|
|
* whether to accept it or not. For details see GTK+ drag & drop documentation
|
|
* on the "drag-motion" and "drag-data-received" signals.
|
|
*
|
|
* How managed DnD works:
|
|
* GTK widget detects a drag gesture and calls
|
|
* S: gdk_drag_begin_from_point() -> backend:drag_begin()
|
|
* which creates the source drag context and the drag window,
|
|
* and grabs the pointing device. GDK layer adds the context
|
|
* to a list of currently-active contexts.
|
|
*
|
|
* From that point forward the context gets any events emitted
|
|
* by GDK, and can prevent these events from going anywhere else.
|
|
* They are all handled in
|
|
* S: gdk_drag_handle_source_event() -> backend:handle_event()
|
|
* (except for wayland backend - it doesn't have that function).
|
|
*
|
|
* That function catches the following events:
|
|
* GDK_MOTION_NOTIFY
|
|
* GDK_BUTTON_RELEASE
|
|
* GDK_KEY_PRESS
|
|
* GDK_KEY_RELEASE
|
|
* GDK_GRAB_BROKEN
|
|
*
|
|
* GDK_MOTION_NOTIFY is emitted by the backend in response to
|
|
* mouse movement.
|
|
* Drag context handles it by calling a bunch of functions to
|
|
* determine the state of the drag actions from the keys being
|
|
* pressed, finding the drag window (another backend function
|
|
* routed through GDK layer) and finally calls
|
|
* S: gdk_drag_motion -> backend:drag_motion()
|
|
* to notify the backend (i.e. itself) that the drag cursor
|
|
* moved.
|
|
* The response to that is to move the drag window and
|
|
* do various bookkeeping.
|
|
* W32: OLE2 protocol does nothing (other than moving the
|
|
* drag window) in response to this, as all the functions
|
|
* that GDK could perform here are already handled by the
|
|
* OS driving the DnD process via DoDragDrop() call.
|
|
* The LOCAL protocol, on the other hande, does a lot,
|
|
* similar to what X11 backend does with XDND - it sends
|
|
* GDK_DRAG_LEAVE and GDK_DRAG_ENTER, emits GDK_DRAG_MOTION.
|
|
*
|
|
* GDK_BUTTON_RELEASE checks the
|
|
* released button - if it's the button that was used to
|
|
* initiate the drag, the "drop-performed" signal is emitted,
|
|
* otherwise the drag is cancelled.
|
|
*
|
|
* GDK_KEY_PRESS and GDK_KEY_RELEASE handler does exactly the same thing as
|
|
* GDK_MOTION_NOTIFY handler, but only after it checks the pressed
|
|
* keys to emit "drop-performed" signal (on Space, Enter etc),
|
|
* cancel the drag (on Escape) or move the drag cursor (arrow keys).
|
|
*
|
|
* GDK_GRAB_BROKEN handler cancels the drag for most broken grabs
|
|
* (except for some special cases where the backend itself does
|
|
* temporary grabs as part of DnD, such as changing the cursor).
|
|
*
|
|
* GDK_DRAG_ENTER, GDK_DRAG_LEAVE, GDK_DRAG_MOTION and GDK_DROP_START
|
|
* events are emitted when
|
|
* the OS notifies the process about these things happening.
|
|
* For X11 backend that is done in Xdnd event filters,
|
|
* for W32 backend this is done in IDropSource/IDropTarget
|
|
* object methods for the OLE2 protocol, whereas for the
|
|
* LOCAL protocol these events are emitted only by GDK itself
|
|
* (with the exception of WM_DROPFILES message, which causes
|
|
* GDK to create a drop context and then immediately finish
|
|
* the drag, providing the list of files it got from the message).
|
|
*
|
|
*/
|
|
|
|
/* The mingw.org compiler does not export GUIDS in it's import library. To work
|
|
* around that, define INITGUID to have the GUIDS declared. */
|
|
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
|
|
#define INITGUID
|
|
#endif
|
|
|
|
/* For C-style COM wrapper macros */
|
|
#define COBJMACROS
|
|
|
|
#include "gdkdrag.h"
|
|
#include "gdkinternals.h"
|
|
#include "gdkprivate-win32.h"
|
|
#include "gdkwin32.h"
|
|
#include "gdkwin32dnd.h"
|
|
#include "gdkdisplayprivate.h"
|
|
#include "gdk/gdkdragprivate.h"
|
|
#include "gdkwin32dnd-private.h"
|
|
#include "gdkdisplay-win32.h"
|
|
#include "gdkdeviceprivate.h"
|
|
#include "gdkhdataoutputstream-win32.h"
|
|
|
|
#include <ole2.h>
|
|
|
|
#include <shlobj.h>
|
|
#include <shlguid.h>
|
|
#include <objidl.h>
|
|
#include "gdkintl.h"
|
|
|
|
#include <gdk/gdk.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
/* Just to avoid calling RegisterWindowMessage() every time */
|
|
static UINT thread_wakeup_message;
|
|
|
|
typedef struct
|
|
{
|
|
IDropSource ids;
|
|
IDropSourceNotify idsn;
|
|
gint ref_count;
|
|
GdkDrag *drag;
|
|
|
|
/* These are thread-local
|
|
* copies of the similar fields from GdkWin32Drag
|
|
*/
|
|
GdkWin32DragUtilityData util_data;
|
|
|
|
/* Cached here, so that we don't have to look in
|
|
* the context every time.
|
|
*/
|
|
HWND source_window_handle;
|
|
guint scale;
|
|
|
|
/* We get this from the OS via IDropSourceNotify and pass it to the
|
|
* main thread.
|
|
* Will be INVALID_HANDLE_VALUE (not NULL!) when unset.
|
|
*/
|
|
HWND dest_window_handle;
|
|
} source_drag_context;
|
|
|
|
typedef struct {
|
|
IDataObject ido;
|
|
int ref_count;
|
|
GdkDrag *drag;
|
|
GArray *formats;
|
|
} data_object;
|
|
|
|
typedef struct {
|
|
IEnumFORMATETC ief;
|
|
int ref_count;
|
|
int ix;
|
|
GArray *formats;
|
|
} enum_formats;
|
|
|
|
typedef enum _GdkWin32DnDThreadQueueItemType GdkWin32DnDThreadQueueItemType;
|
|
|
|
enum _GdkWin32DnDThreadQueueItemType
|
|
{
|
|
GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK = 1,
|
|
GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO = 2,
|
|
GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP = 3,
|
|
GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA = 4,
|
|
GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE = 5,
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadQueueItem GdkWin32DnDThreadQueueItem;
|
|
|
|
struct _GdkWin32DnDThreadQueueItem
|
|
{
|
|
GdkWin32DnDThreadQueueItemType item_type;
|
|
|
|
/* This is used by the DnD thread to communicate the identity
|
|
* of the drag context to the main thread. This is thread-safe
|
|
* because DnD thread holds a reference to the context.
|
|
*/
|
|
gpointer opaque_context;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadDoDragDrop GdkWin32DnDThreadDoDragDrop;
|
|
|
|
/* This is used both to signal the DnD thread that it needs
|
|
* to call DoDragDrop(), *and* to signal the main thread
|
|
* that the DoDragDrop() call returned.
|
|
*/
|
|
struct _GdkWin32DnDThreadDoDragDrop
|
|
{
|
|
GdkWin32DnDThreadQueueItem base;
|
|
|
|
source_drag_context *src_context;
|
|
data_object *src_object;
|
|
DWORD allowed_drop_effects;
|
|
|
|
DWORD received_drop_effect;
|
|
HRESULT received_result;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadGetData GdkWin32DnDThreadGetData;
|
|
|
|
/* This is used both to signal the main thread that the DnD thread
|
|
* needs DnD data, and to give that data to the DnD thread.
|
|
*/
|
|
struct _GdkWin32DnDThreadGetData
|
|
{
|
|
GdkWin32DnDThreadQueueItem base;
|
|
|
|
GdkWin32ContentFormatPair pair;
|
|
GdkWin32HDataOutputStream *stream;
|
|
|
|
STGMEDIUM produced_data_medium;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadGiveFeedback GdkWin32DnDThreadGiveFeedback;
|
|
|
|
struct _GdkWin32DnDThreadGiveFeedback
|
|
{
|
|
GdkWin32DnDThreadQueueItem base;
|
|
|
|
DWORD received_drop_effect;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadDragInfo GdkWin32DnDThreadDragInfo;
|
|
|
|
struct _GdkWin32DnDThreadDragInfo
|
|
{
|
|
GdkWin32DnDThreadQueueItem base;
|
|
|
|
BOOL received_escape_pressed;
|
|
DWORD received_keyboard_mods;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThreadUpdateDragState GdkWin32DnDThreadUpdateDragState;
|
|
|
|
struct _GdkWin32DnDThreadUpdateDragState
|
|
{
|
|
GdkWin32DnDThreadQueueItem base;
|
|
|
|
gpointer opaque_ddd;
|
|
GdkWin32DragUtilityData produced_util_data;
|
|
};
|
|
|
|
typedef struct _GdkWin32DnDThread GdkWin32DnDThread;
|
|
|
|
struct _GdkWin32DnDThread
|
|
{
|
|
/* We receive instructions from the main thread in this queue */
|
|
GAsyncQueue *input_queue;
|
|
|
|
/* We can't peek the queue or "unpop" queue items,
|
|
* so the items that we can't act upon (yet) got
|
|
* to be stored *somewhere*.
|
|
*/
|
|
GList *dequeued_items;
|
|
|
|
source_drag_context *src_context;
|
|
data_object *src_object;
|
|
};
|
|
|
|
/* The code is much more secure if we don't rely on the OS to keep
|
|
* this around for us.
|
|
*/
|
|
static GdkWin32DnDThread *dnd_thread_data = NULL;
|
|
|
|
static gboolean
|
|
dnd_queue_is_empty ()
|
|
{
|
|
return g_atomic_int_get (&_win32_clipdrop->dnd_queue_counter) == 0;
|
|
}
|
|
|
|
static void
|
|
decrement_dnd_queue_counter ()
|
|
{
|
|
g_atomic_int_dec_and_test (&_win32_clipdrop->dnd_queue_counter);
|
|
}
|
|
|
|
static void
|
|
increment_dnd_queue_counter ()
|
|
{
|
|
g_atomic_int_inc (&_win32_clipdrop->dnd_queue_counter);
|
|
}
|
|
|
|
static void
|
|
free_queue_item (GdkWin32DnDThreadQueueItem *item)
|
|
{
|
|
GdkWin32DnDThreadGetData *getdata;
|
|
|
|
switch (item->item_type)
|
|
{
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP:
|
|
/* Don't unref anything, it's all done in the main thread,
|
|
* when it receives a DoDragDrop reply.
|
|
*/
|
|
break;
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE:
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK:
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO:
|
|
/* These have no data to clean up */
|
|
break;
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA:
|
|
getdata = (GdkWin32DnDThreadGetData *) item;
|
|
|
|
switch (getdata->produced_data_medium.tymed)
|
|
{
|
|
case TYMED_FILE:
|
|
case TYMED_ISTREAM:
|
|
case TYMED_ISTORAGE:
|
|
case TYMED_GDI:
|
|
case TYMED_MFPICT:
|
|
case TYMED_ENHMF:
|
|
g_critical ("Unsupported STGMEDIUM type");
|
|
break;
|
|
case TYMED_NULL:
|
|
break;
|
|
case TYMED_HGLOBAL:
|
|
GlobalFree (getdata->produced_data_medium.hGlobal);
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_free (item);
|
|
}
|
|
|
|
static gboolean
|
|
process_dnd_queue (gboolean timed,
|
|
guint64 end_time,
|
|
GdkWin32DnDThreadGetData *getdata_check)
|
|
{
|
|
GdkWin32DnDThreadQueueItem *item;
|
|
GdkWin32DnDThreadUpdateDragState *updatestate;
|
|
GdkWin32DnDThreadDoDragDrop *ddd;
|
|
|
|
while (TRUE)
|
|
{
|
|
if (timed)
|
|
{
|
|
guint64 current_time = g_get_monotonic_time ();
|
|
|
|
if (current_time >= end_time)
|
|
break;
|
|
|
|
item = g_async_queue_timeout_pop (dnd_thread_data->input_queue, end_time - current_time);
|
|
}
|
|
else
|
|
{
|
|
item = g_async_queue_try_pop (dnd_thread_data->input_queue);
|
|
}
|
|
|
|
if (item == NULL)
|
|
break;
|
|
|
|
decrement_dnd_queue_counter ();
|
|
|
|
switch (item->item_type)
|
|
{
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP:
|
|
/* We don't support more than one DnD at a time */
|
|
free_queue_item (item);
|
|
break;
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE:
|
|
updatestate = (GdkWin32DnDThreadUpdateDragState *) item;
|
|
ddd = (GdkWin32DnDThreadDoDragDrop *) updatestate->opaque_ddd;
|
|
ddd->src_context->util_data = updatestate->produced_util_data;
|
|
free_queue_item (item);
|
|
break;
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA:
|
|
if (item == (GdkWin32DnDThreadQueueItem *) getdata_check)
|
|
return TRUE;
|
|
|
|
free_queue_item (item);
|
|
break;
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK:
|
|
case GDK_WIN32_DND_THREAD_QUEUE_ITEM_DRAG_INFO:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
_gdk_win32_local_drag_drop_response (GdkDrag *drag,
|
|
GdkDragAction action)
|
|
{
|
|
GDK_NOTE (DND, g_print ("_gdk_win32_local_drag_drop_response: 0x%p\n",
|
|
drag));
|
|
|
|
g_signal_emit_by_name (drag, "dnd-finished");
|
|
gdk_drag_drop_done (drag, action != 0);
|
|
}
|
|
|
|
static gboolean
|
|
do_drag_drop_response (gpointer user_data)
|
|
{
|
|
GdkWin32DnDThreadDoDragDrop *ddd = (GdkWin32DnDThreadDoDragDrop *) user_data;
|
|
HRESULT hr = ddd->received_result;
|
|
GdkDrag *drag = GDK_DRAG (ddd->base.opaque_context);
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
gpointer table_value = g_hash_table_lookup (clipdrop->active_source_drags, drag);
|
|
|
|
if (ddd == table_value)
|
|
{
|
|
GDK_NOTE (DND, g_print ("DoDragDrop returned %s with effect %lu\n",
|
|
(hr == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP" :
|
|
(hr == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" :
|
|
(hr == E_UNEXPECTED ? "E_UNEXPECTED" :
|
|
g_strdup_printf ("%#.8lx", hr)))), ddd->received_drop_effect));
|
|
|
|
drag_win32->drop_failed = !(SUCCEEDED (hr) || hr == DRAGDROP_S_DROP);
|
|
|
|
/* We used to delete the selection here,
|
|
* now GTK does that automatically in response to
|
|
* the "dnd-finished" signal,
|
|
* if the operation was successful and was a move.
|
|
*/
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finished: 0x%p\n",
|
|
drag));
|
|
|
|
g_signal_emit_by_name (drag, "dnd-finished");
|
|
gdk_drag_drop_done (drag, !drag_win32->drop_failed);
|
|
}
|
|
else
|
|
{
|
|
if (!table_value)
|
|
g_critical ("Did not find drag 0x%p in the active drags table", drag);
|
|
else
|
|
g_critical ("Found drag 0x%p in the active drags table, but the record doesn't match (0x%p != 0x%p)", drag, ddd, table_value);
|
|
}
|
|
|
|
/* 3rd parties could keep a reference to this object,
|
|
* but we won't keep the drag alive that long.
|
|
* Neutralize it (attempts to get its data will fail)
|
|
* by nullifying the drag pointer (it doesn't hold
|
|
* a reference, so no unreffing).
|
|
*/
|
|
g_clear_object (&ddd->src_object->drag);
|
|
|
|
IDropSource_Release (&ddd->src_context->ids);
|
|
IDataObject_Release (&ddd->src_object->ido);
|
|
|
|
g_hash_table_remove (clipdrop->active_source_drags, drag);
|
|
free_queue_item (&ddd->base);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
received_drag_context_data (GObject *drag,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
GdkWin32DnDThreadGetData *getdata = (GdkWin32DnDThreadGetData *) user_data;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
if (!gdk_drag_write_finish (GDK_DRAG (drag), result, &error))
|
|
{
|
|
HANDLE handle;
|
|
gboolean is_hdata;
|
|
|
|
GDK_NOTE (DND, g_printerr ("%p: failed to write HData-backed stream: %s\n", drag, error->message));
|
|
g_error_free (error);
|
|
g_output_stream_close (G_OUTPUT_STREAM (getdata->stream), NULL, NULL);
|
|
handle = gdk_win32_hdata_output_stream_get_handle (getdata->stream, &is_hdata);
|
|
|
|
if (is_hdata)
|
|
API_CALL (GlobalFree, (handle));
|
|
else
|
|
API_CALL (CloseHandle, (handle));
|
|
}
|
|
else
|
|
{
|
|
g_output_stream_close (G_OUTPUT_STREAM (getdata->stream), NULL, NULL);
|
|
getdata->produced_data_medium.tymed = TYMED_HGLOBAL;
|
|
getdata->produced_data_medium.hGlobal = gdk_win32_hdata_output_stream_get_handle (getdata->stream, NULL);
|
|
}
|
|
|
|
g_clear_object (&getdata->stream);
|
|
increment_dnd_queue_counter ();
|
|
g_async_queue_push (clipdrop->dnd_queue, getdata);
|
|
API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
|
|
}
|
|
|
|
static gboolean
|
|
get_data_response (gpointer user_data)
|
|
{
|
|
GdkWin32DnDThreadGetData *getdata = (GdkWin32DnDThreadGetData *) user_data;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
GdkDrag *drag = GDK_DRAG (getdata->base.opaque_context);
|
|
gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag);
|
|
|
|
GDK_NOTE (DND, g_print ("idataobject_getdata will request target 0x%p (%s)",
|
|
getdata->pair.contentformat, getdata->pair.contentformat));
|
|
|
|
/* This just verifies that we got the right drag,
|
|
* we don't need the ddd struct itself.
|
|
*/
|
|
if (ddd)
|
|
{
|
|
GError *error = NULL;
|
|
GOutputStream *stream = gdk_win32_hdata_output_stream_new (&getdata->pair, &error);
|
|
|
|
if (stream)
|
|
{
|
|
getdata->stream = GDK_WIN32_HDATA_OUTPUT_STREAM (stream);
|
|
gdk_drag_write_async (drag,
|
|
getdata->pair.contentformat,
|
|
stream,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
received_drag_context_data,
|
|
getdata);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
}
|
|
|
|
increment_dnd_queue_counter ();
|
|
g_async_queue_push (clipdrop->dnd_queue, getdata);
|
|
API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
do_drag_drop (GdkWin32DnDThreadDoDragDrop *ddd)
|
|
{
|
|
HRESULT hr;
|
|
|
|
dnd_thread_data->src_object = ddd->src_object;
|
|
dnd_thread_data->src_context = ddd->src_context;
|
|
|
|
hr = DoDragDrop (&dnd_thread_data->src_object->ido,
|
|
&dnd_thread_data->src_context->ids,
|
|
ddd->allowed_drop_effects,
|
|
&ddd->received_drop_effect);
|
|
|
|
ddd->received_result = hr;
|
|
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, do_drag_drop_response, ddd, NULL);
|
|
}
|
|
|
|
gpointer
|
|
_gdk_win32_dnd_thread_main (gpointer data)
|
|
{
|
|
GAsyncQueue *queue = (GAsyncQueue *) data;
|
|
GdkWin32DnDThreadQueueItem *item;
|
|
MSG msg;
|
|
HRESULT hr;
|
|
|
|
g_assert (dnd_thread_data == NULL);
|
|
|
|
dnd_thread_data = g_new0 (GdkWin32DnDThread, 1);
|
|
dnd_thread_data->input_queue = queue;
|
|
|
|
CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
|
|
|
|
hr = OleInitialize (NULL);
|
|
|
|
if (!SUCCEEDED (hr))
|
|
g_error ("OleInitialize failed");
|
|
|
|
/* Create a message queue */
|
|
PeekMessage (&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
|
|
|
|
thread_wakeup_message = RegisterWindowMessage ("GDK_WORKER_THREAD_WEAKEUP");
|
|
|
|
/* Signal the main thread that we're ready.
|
|
* This is the only time the queue works in reverse.
|
|
*/
|
|
g_async_queue_push (queue, GUINT_TO_POINTER (GetCurrentThreadId ()));
|
|
|
|
while (GetMessage (&msg, NULL, 0, 0))
|
|
{
|
|
if (!dnd_queue_is_empty ())
|
|
{
|
|
while ((item = g_async_queue_try_pop (queue)) != NULL)
|
|
{
|
|
decrement_dnd_queue_counter ();
|
|
|
|
if (item->item_type != GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP)
|
|
{
|
|
free_queue_item (item);
|
|
continue;
|
|
}
|
|
|
|
do_drag_drop ((GdkWin32DnDThreadDoDragDrop *) item);
|
|
API_CALL (PostThreadMessage, (GetCurrentThreadId (), thread_wakeup_message, 0, 0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Just to be safe, although this mostly does nothing */
|
|
TranslateMessage (&msg);
|
|
DispatchMessage (&msg);
|
|
}
|
|
|
|
g_async_queue_unref (queue);
|
|
g_clear_pointer (&dnd_thread_data, g_free);
|
|
|
|
OleUninitialize ();
|
|
CoUninitialize ();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* For the LOCAL protocol */
|
|
typedef enum {
|
|
GDK_DRAG_STATUS_DRAG,
|
|
GDK_DRAG_STATUS_MOTION_WAIT,
|
|
GDK_DRAG_STATUS_ACTION_WAIT,
|
|
GDK_DRAG_STATUS_DROP
|
|
} GdkDragStatus;
|
|
|
|
static gboolean use_ole2_dnd = TRUE;
|
|
|
|
static gboolean drag_context_grab (GdkDrag *drag);
|
|
|
|
G_DEFINE_TYPE (GdkWin32Drag, gdk_win32_drag, GDK_TYPE_DRAG)
|
|
|
|
static void
|
|
move_drag_surface (GdkDrag *drag,
|
|
guint x_root,
|
|
guint y_root)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
gdk_win32_surface_move (drag_win32->drag_surface,
|
|
x_root - drag_win32->hot_x,
|
|
y_root - drag_win32->hot_y);
|
|
gdk_win32_surface_raise (drag_win32->drag_surface);
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_init (GdkWin32Drag *drag)
|
|
{
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
drag->handle_events = TRUE;
|
|
drag->dest_window = INVALID_HANDLE_VALUE;
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_init %p\n", drag));
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_finalize (GObject *object)
|
|
{
|
|
GdkDrag *drag;
|
|
GdkWin32Drag *drag_win32;
|
|
GdkSurface *drag_surface;
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_finalize %p\n", object));
|
|
|
|
g_return_if_fail (GDK_IS_WIN32_DRAG (object));
|
|
|
|
drag = GDK_DRAG (object);
|
|
drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
gdk_drag_set_cursor (drag, NULL);
|
|
|
|
g_set_object (&drag_win32->grab_surface, NULL);
|
|
drag_surface = drag_win32->drag_surface;
|
|
|
|
G_OBJECT_CLASS (gdk_win32_drag_parent_class)->finalize (object);
|
|
|
|
if (drag_surface)
|
|
gdk_surface_destroy (drag_surface);
|
|
}
|
|
|
|
/* Drag Contexts */
|
|
|
|
static GdkDrag *
|
|
gdk_drag_new (GdkDisplay *display,
|
|
GdkSurface *surface,
|
|
GdkContentProvider *content,
|
|
GdkDragAction actions,
|
|
GdkDevice *device,
|
|
GdkDragProtocol protocol)
|
|
{
|
|
GdkWin32Drag *drag_win32;
|
|
GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
|
|
GdkDrag *drag;
|
|
|
|
drag_win32 = g_object_new (GDK_TYPE_WIN32_DRAG,
|
|
"device", device,
|
|
"content", content,
|
|
"surface", surface,
|
|
"actions", actions,
|
|
NULL);
|
|
|
|
drag = GDK_DRAG (drag_win32);
|
|
|
|
if (win32_display->has_fixed_scale)
|
|
drag_win32->scale = win32_display->surface_scale;
|
|
else
|
|
drag_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
|
|
|
|
drag_win32->protocol = protocol;
|
|
|
|
return drag;
|
|
}
|
|
|
|
#define PRINT_GUID(guid) \
|
|
g_print ("%.08lx-%.04x-%.04x-%.02x%.02x-%.02x%.02x%.02x%.02x%.02x%.02x", \
|
|
((gulong *) guid)[0], \
|
|
((gushort *) guid)[2], \
|
|
((gushort *) guid)[3], \
|
|
((guchar *) guid)[8], \
|
|
((guchar *) guid)[9], \
|
|
((guchar *) guid)[10], \
|
|
((guchar *) guid)[11], \
|
|
((guchar *) guid)[12], \
|
|
((guchar *) guid)[13], \
|
|
((guchar *) guid)[14], \
|
|
((guchar *) guid)[15]);
|
|
|
|
static enum_formats *enum_formats_new (GArray *formats);
|
|
|
|
/* Finds a GdkDrag object that corresponds to a DnD operation
|
|
* which is currently targetting the dest_window
|
|
* Does not give a reference.
|
|
*/
|
|
GdkDrag *
|
|
_gdk_win32_find_drag_for_dest_window (HWND dest_window)
|
|
{
|
|
GHashTableIter iter;
|
|
GdkWin32Drag *drag_win32;
|
|
GdkWin32DnDThreadDoDragDrop *ddd;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
g_hash_table_iter_init (&iter, clipdrop->active_source_drags);
|
|
|
|
while (g_hash_table_iter_next (&iter, (gpointer *) &drag_win32, (gpointer *) &ddd))
|
|
if (ddd->src_context->dest_window_handle == dest_window)
|
|
return GDK_DRAG (drag_win32);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GdkDragAction
|
|
action_for_drop_effect (DWORD effect)
|
|
{
|
|
GdkDragAction action = 0;
|
|
|
|
if (effect & DROPEFFECT_MOVE)
|
|
action |= GDK_ACTION_MOVE;
|
|
if (effect & DROPEFFECT_LINK)
|
|
action |= GDK_ACTION_LINK;
|
|
if (effect & DROPEFFECT_COPY)
|
|
action |= GDK_ACTION_COPY;
|
|
|
|
return action;
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
idropsource_addref (LPDROPSOURCE This)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) This;
|
|
|
|
int ref_count = ++ctx->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n", This, ref_count));
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
typedef struct _GdkWin32DnDEnterLeaveNotify GdkWin32DnDEnterLeaveNotify;
|
|
|
|
struct _GdkWin32DnDEnterLeaveNotify
|
|
{
|
|
gpointer opaque_context;
|
|
HWND target_window_handle;
|
|
};
|
|
|
|
static gboolean
|
|
notify_dnd_enter (gpointer user_data)
|
|
{
|
|
GdkWin32DnDEnterLeaveNotify *notify = (GdkWin32DnDEnterLeaveNotify *) user_data;
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (notify->opaque_context);
|
|
|
|
drag_win32->dest_window = notify->target_window_handle;
|
|
|
|
g_free (notify);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
notify_dnd_leave (gpointer user_data)
|
|
{
|
|
GdkWin32DnDEnterLeaveNotify *notify = (GdkWin32DnDEnterLeaveNotify *) user_data;
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (notify->opaque_context);
|
|
|
|
if (notify->target_window_handle != drag_win32->dest_window)
|
|
g_warning ("DnD leave says that the window handle is 0x%p, but drag has 0x%p", notify->target_window_handle, drag_win32->dest_window);
|
|
|
|
drag_win32->dest_window = INVALID_HANDLE_VALUE;
|
|
|
|
g_free (notify);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idropsourcenotify_dragentertarget (IDropSourceNotify *This,
|
|
HWND hwndTarget)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) (((char *) This) - G_STRUCT_OFFSET (source_drag_context, idsn));
|
|
GdkWin32DnDEnterLeaveNotify *notify;
|
|
|
|
if (!dnd_queue_is_empty ())
|
|
process_dnd_queue (FALSE, 0, NULL);
|
|
|
|
GDK_NOTE (DND, g_print ("idropsourcenotify_dragentertarget %p (SDC %p) 0x%p\n", This, ctx, hwndTarget));
|
|
|
|
ctx->dest_window_handle = hwndTarget;
|
|
|
|
notify = g_new0 (GdkWin32DnDEnterLeaveNotify, 1);
|
|
notify->target_window_handle = hwndTarget;
|
|
notify->opaque_context = ctx->drag;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, notify_dnd_enter, notify, NULL);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idropsourcenotify_dragleavetarget (IDropSourceNotify *This)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) (((char *) This) - G_STRUCT_OFFSET (source_drag_context, idsn));
|
|
GdkWin32DnDEnterLeaveNotify *notify;
|
|
|
|
if (!dnd_queue_is_empty ())
|
|
process_dnd_queue (FALSE, 0, NULL);
|
|
|
|
GDK_NOTE (DND, g_print ("idropsourcenotify_dragleavetarget %p (SDC %p) 0x%p\n", This, ctx, ctx->dest_window_handle));
|
|
|
|
notify = g_new0 (GdkWin32DnDEnterLeaveNotify, 1);
|
|
notify->target_window_handle = ctx->dest_window_handle;
|
|
ctx->dest_window_handle = INVALID_HANDLE_VALUE;
|
|
notify->opaque_context = ctx->drag;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, notify_dnd_leave, notify, NULL);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idropsource_queryinterface (LPDROPSOURCE This,
|
|
REFIID riid,
|
|
LPVOID *ppvObject)
|
|
{
|
|
GDK_NOTE (DND, {
|
|
g_print ("idropsource_queryinterface %p ", This);
|
|
PRINT_GUID (riid);
|
|
});
|
|
|
|
*ppvObject = NULL;
|
|
|
|
if (IsEqualGUID (riid, &IID_IUnknown))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
|
|
idropsource_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualGUID (riid, &IID_IDropSource))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IDropSource S_OK\n"));
|
|
idropsource_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualGUID (riid, &IID_IDropSourceNotify))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IDropSourceNotify S_OK\n"));
|
|
idropsource_addref (This);
|
|
*ppvObject = &((source_drag_context *) This)->idsn;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
unref_context_in_main_thread (gpointer opaque_context)
|
|
{
|
|
GdkDrag *drag = GDK_DRAG (opaque_context);
|
|
|
|
g_clear_object (&drag);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
idropsource_release (LPDROPSOURCE This)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) This;
|
|
|
|
int ref_count = --ctx->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count));
|
|
|
|
if (ref_count == 0)
|
|
{
|
|
g_idle_add (unref_context_in_main_thread, ctx->drag);
|
|
g_free (This);
|
|
}
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
/* NOTE: This method is called continuously, even if nothing is
|
|
* happening, as long as the drag operation is in progress.
|
|
* It is OK to return a "safe" value (S_OK, to keep the drag
|
|
* operation going) even if something notable happens, because
|
|
* we will have another opportunity to return the "right" value
|
|
* (once we know what it is, after GTK processes the events we
|
|
* send out) very soon.
|
|
* Note that keyboard-related state in this function is nonsense,
|
|
* as DoDragDrop doesn't get precise information about the keyboard,
|
|
* especially the fEscapePressed argument.
|
|
*/
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idropsource_querycontinuedrag (LPDROPSOURCE This,
|
|
BOOL fEscapePressed,
|
|
DWORD grfKeyState)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) This;
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p esc=%d keystate=0x%lx with state %d\n", This, fEscapePressed, grfKeyState, ctx->util_data.state));
|
|
|
|
if (!dnd_queue_is_empty ())
|
|
process_dnd_queue (FALSE, 0, NULL);
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag state %d\n", ctx->util_data.state));
|
|
|
|
if (ctx->util_data.state == GDK_WIN32_DND_DROPPED)
|
|
{
|
|
GDK_NOTE (DND, g_print ("DRAGDROP_S_DROP\n"));
|
|
return DRAGDROP_S_DROP;
|
|
}
|
|
else if (ctx->util_data.state == GDK_WIN32_DND_NONE)
|
|
{
|
|
GDK_NOTE (DND, g_print ("DRAGDROP_S_CANCEL\n"));
|
|
return DRAGDROP_S_CANCEL;
|
|
}
|
|
else
|
|
{
|
|
GDK_NOTE (DND, g_print ("S_OK\n"));
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
static void
|
|
maybe_emit_action_changed (GdkWin32Drag *drag_win32,
|
|
GdkDragAction actions)
|
|
{
|
|
if (actions != drag_win32->current_action)
|
|
{
|
|
drag_win32->current_action = actions;
|
|
gdk_drag_set_selected_action (GDK_DRAG (drag_win32), actions);
|
|
}
|
|
}
|
|
|
|
void
|
|
_gdk_win32_local_drag_give_feedback (GdkDrag *drag,
|
|
GdkDragAction actions)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
if (drag_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
|
|
drag_win32->drag_status = GDK_DRAG_STATUS_DRAG;
|
|
|
|
GDK_NOTE (DND, g_print ("_gdk_win32_local_drag_give_feedback: 0x%p\n",
|
|
drag));
|
|
|
|
maybe_emit_action_changed (drag_win32, actions);
|
|
}
|
|
|
|
static gboolean
|
|
give_feedback (gpointer user_data)
|
|
{
|
|
GdkWin32DnDThreadGiveFeedback *feedback = (GdkWin32DnDThreadGiveFeedback *) user_data;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, feedback->base.opaque_context);
|
|
|
|
if (ddd)
|
|
{
|
|
GdkDrag *drag = GDK_DRAG (feedback->base.opaque_context);
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
|
|
drag));
|
|
|
|
maybe_emit_action_changed (drag_win32, action_for_drop_effect (feedback->received_drop_effect));
|
|
}
|
|
|
|
free_queue_item (&feedback->base);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idropsource_givefeedback (LPDROPSOURCE This,
|
|
DWORD dwEffect)
|
|
{
|
|
source_drag_context *ctx = (source_drag_context *) This;
|
|
GdkWin32DnDThreadGiveFeedback *feedback;
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_givefeedback %p with drop effect %lu S_OK\n", This, dwEffect));
|
|
|
|
if (!dnd_queue_is_empty ())
|
|
process_dnd_queue (FALSE, 0, NULL);
|
|
|
|
feedback = g_new0 (GdkWin32DnDThreadGiveFeedback, 1);
|
|
feedback->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_GIVE_FEEDBACK;
|
|
feedback->base.opaque_context = ctx->drag;
|
|
feedback->received_drop_effect = dwEffect;
|
|
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, give_feedback, feedback, NULL);
|
|
|
|
GDK_NOTE (DND, g_print ("idropsource_givefeedback %p returns\n", This));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
idataobject_addref (LPDATAOBJECT This)
|
|
{
|
|
data_object *dobj = (data_object *) This;
|
|
int ref_count = ++dobj->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("idataobject_addref %p %d\n", This, ref_count));
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_queryinterface (LPDATAOBJECT This,
|
|
REFIID riid,
|
|
LPVOID *ppvObject)
|
|
{
|
|
GDK_NOTE (DND, {
|
|
g_print ("idataobject_queryinterface %p ", This);
|
|
PRINT_GUID (riid);
|
|
});
|
|
|
|
*ppvObject = NULL;
|
|
|
|
if (IsEqualGUID (riid, &IID_IUnknown))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
|
|
idataobject_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualGUID (riid, &IID_IDataObject))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IDataObject S_OK\n"));
|
|
idataobject_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
idataobject_release (LPDATAOBJECT This)
|
|
{
|
|
data_object *dobj = (data_object *) This;
|
|
int ref_count = --dobj->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("idataobject_release %p %d\n", This, ref_count));
|
|
|
|
if (ref_count == 0)
|
|
{
|
|
g_array_free (dobj->formats, TRUE);
|
|
g_free (This);
|
|
}
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
static HRESULT
|
|
query (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtc,
|
|
GdkWin32ContentFormatPair **pair)
|
|
{
|
|
data_object *ctx = (data_object *) This;
|
|
gint i;
|
|
|
|
if (pair)
|
|
*pair = NULL;
|
|
|
|
if (!pFormatEtc)
|
|
return DV_E_FORMATETC;
|
|
|
|
if (pFormatEtc->lindex != -1)
|
|
return DV_E_LINDEX;
|
|
|
|
if ((pFormatEtc->tymed & TYMED_HGLOBAL) == 0)
|
|
return DV_E_TYMED;
|
|
|
|
if ((pFormatEtc->dwAspect & DVASPECT_CONTENT) == 0)
|
|
return DV_E_DVASPECT;
|
|
|
|
for (i = 0; i < ctx->formats->len; i++)
|
|
{
|
|
GdkWin32ContentFormatPair *p = &g_array_index (ctx->formats, GdkWin32ContentFormatPair, i);
|
|
if (pFormatEtc->cfFormat == p->w32format)
|
|
{
|
|
if (pair)
|
|
*pair = p;
|
|
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return DV_E_FORMATETC;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_getdata (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtc,
|
|
LPSTGMEDIUM pMedium)
|
|
{
|
|
data_object *ctx = (data_object *) This;
|
|
HRESULT hr;
|
|
GdkWin32DnDThreadGetData *getdata;
|
|
GdkWin32ContentFormatPair *pair;
|
|
|
|
if (ctx->drag == NULL)
|
|
return E_FAIL;
|
|
|
|
GDK_NOTE (DND, g_print ("idataobject_getdata %p %s ",
|
|
This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
|
|
|
|
/* Check whether we can provide requested format */
|
|
hr = query (This, pFormatEtc, &pair);
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
GDK_NOTE (DND, g_print ("Unsupported format, returning 0x%lx\n", hr));
|
|
return hr;
|
|
}
|
|
|
|
if (!dnd_queue_is_empty ())
|
|
process_dnd_queue (FALSE, 0, NULL);
|
|
|
|
getdata = g_new0 (GdkWin32DnDThreadGetData, 1);
|
|
getdata->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_GET_DATA;
|
|
getdata->base.opaque_context = (gpointer) ctx->drag;
|
|
getdata->pair = *pair;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, get_data_response, getdata, NULL);
|
|
|
|
if (!process_dnd_queue (TRUE, g_get_monotonic_time () + G_USEC_PER_SEC * 30, getdata))
|
|
return E_FAIL;
|
|
|
|
if (getdata->produced_data_medium.tymed == TYMED_NULL)
|
|
{
|
|
free_queue_item (&getdata->base);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
memcpy (pMedium, &getdata->produced_data_medium, sizeof (*pMedium));
|
|
|
|
/* To ensure that the data isn't freed */
|
|
getdata->produced_data_medium.tymed = TYMED_NULL;
|
|
|
|
free_queue_item (&getdata->base);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_getdatahere (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtc,
|
|
LPSTGMEDIUM pMedium)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_getdatahere %p E_NOTIMPL\n", This));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_querygetdata (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtc)
|
|
{
|
|
HRESULT hr;
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread != g_thread_self ());
|
|
|
|
hr = query (This, pFormatEtc, NULL);
|
|
|
|
GDK_NOTE (DND,
|
|
g_print ("idataobject_querygetdata %p 0x%08x fmt, %p ptd, %lu aspect, %ld lindex, %0lx tymed - %s, return %#lx (%s)\n",
|
|
This, pFormatEtc->cfFormat, pFormatEtc->ptd, pFormatEtc->dwAspect, pFormatEtc->lindex, pFormatEtc->tymed, _gdk_win32_cf_to_string (pFormatEtc->cfFormat),
|
|
hr, (hr == S_OK) ? "S_OK" : (hr == DV_E_FORMATETC) ? "DV_E_FORMATETC" : (hr == DV_E_LINDEX) ? "DV_E_LINDEX" : (hr == DV_E_TYMED) ? "DV_E_TYMED" : (hr == DV_E_DVASPECT) ? "DV_E_DVASPECT" : "uknown meaning"));
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_getcanonicalformatetc (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtcIn,
|
|
LPFORMATETC pFormatEtcOut)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p E_NOTIMPL\n", This));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_setdata (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatEtc,
|
|
LPSTGMEDIUM pMedium,
|
|
BOOL fRelease)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_setdata %p %s E_NOTIMPL\n",
|
|
This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_enumformatetc (LPDATAOBJECT This,
|
|
DWORD dwDirection,
|
|
LPENUMFORMATETC *ppEnumFormatEtc)
|
|
{
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread != g_thread_self ());
|
|
|
|
if (dwDirection != DATADIR_GET)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p E_NOTIMPL", This));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
*ppEnumFormatEtc = &enum_formats_new (((data_object *) This)->formats)->ief;
|
|
|
|
GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p -> %p S_OK", This, *ppEnumFormatEtc));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_dadvise (LPDATAOBJECT This,
|
|
LPFORMATETC pFormatetc,
|
|
DWORD advf,
|
|
LPADVISESINK pAdvSink,
|
|
DWORD *pdwConnection)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_dadvise %p E_NOTIMPL\n", This));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_dunadvise (LPDATAOBJECT This,
|
|
DWORD dwConnection)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_dunadvise %p E_NOTIMPL\n", This));
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
idataobject_enumdadvise (LPDATAOBJECT This,
|
|
LPENUMSTATDATA *ppenumAdvise)
|
|
{
|
|
GDK_NOTE (DND, g_print ("idataobject_enumdadvise %p OLE_E_ADVISENOTSUPPORTED\n", This));
|
|
|
|
return OLE_E_ADVISENOTSUPPORTED;
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
ienumformatetc_addref (LPENUMFORMATETC This)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
int ref_count = ++en->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_addref %p %d\n", This, ref_count));
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
ienumformatetc_queryinterface (LPENUMFORMATETC This,
|
|
REFIID riid,
|
|
LPVOID *ppvObject)
|
|
{
|
|
GDK_NOTE (DND, {
|
|
g_print ("ienumformatetc_queryinterface %p", This);
|
|
PRINT_GUID (riid);
|
|
});
|
|
|
|
*ppvObject = NULL;
|
|
|
|
if (IsEqualGUID (riid, &IID_IUnknown))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
|
|
ienumformatetc_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else if (IsEqualGUID (riid, &IID_IEnumFORMATETC))
|
|
{
|
|
GDK_NOTE (DND, g_print ("...IEnumFORMATETC S_OK\n"));
|
|
ienumformatetc_addref (This);
|
|
*ppvObject = This;
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
|
|
return E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
static ULONG STDMETHODCALLTYPE
|
|
ienumformatetc_release (LPENUMFORMATETC This)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
int ref_count = --en->ref_count;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_release %p %d\n", This, ref_count));
|
|
|
|
if (ref_count == 0)
|
|
{
|
|
g_array_unref (en->formats);
|
|
g_free (This);
|
|
}
|
|
|
|
return ref_count;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
ienumformatetc_next (LPENUMFORMATETC This,
|
|
ULONG celt,
|
|
LPFORMATETC elts,
|
|
ULONG *nelt)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
ULONG i, n;
|
|
ULONG formats_to_get = celt;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_next %p %d %ld ", This, en->ix, celt));
|
|
|
|
n = 0;
|
|
for (i = 0; i < formats_to_get; i++)
|
|
{
|
|
UINT fmt;
|
|
if (en->ix >= en->formats->len)
|
|
break;
|
|
fmt = g_array_index (en->formats, GdkWin32ContentFormatPair, en->ix++).w32format;
|
|
/* skip internals */
|
|
if (fmt == 0 || fmt > 0xFFFF)
|
|
{
|
|
formats_to_get += 1;
|
|
continue;
|
|
}
|
|
elts[n].cfFormat = fmt;
|
|
elts[n].ptd = NULL;
|
|
elts[n].dwAspect = DVASPECT_CONTENT;
|
|
elts[n].lindex = -1;
|
|
elts[n].tymed = TYMED_HGLOBAL;
|
|
|
|
n++;
|
|
}
|
|
|
|
if (nelt != NULL)
|
|
*nelt = n;
|
|
|
|
GDK_NOTE (DND, g_print ("%s\n", (n == celt) ? "S_OK" : "S_FALSE"));
|
|
|
|
if (n == celt)
|
|
return S_OK;
|
|
else
|
|
return S_FALSE;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
ienumformatetc_skip (LPENUMFORMATETC This,
|
|
ULONG celt)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_skip %p %d %ld S_OK\n", This, en->ix, celt));
|
|
|
|
en->ix += celt;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
ienumformatetc_reset (LPENUMFORMATETC This)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_reset %p S_OK\n", This));
|
|
|
|
en->ix = 0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT STDMETHODCALLTYPE
|
|
ienumformatetc_clone (LPENUMFORMATETC This,
|
|
LPENUMFORMATETC *ppEnumFormatEtc)
|
|
{
|
|
enum_formats *en = (enum_formats *) This;
|
|
enum_formats *new;
|
|
|
|
GDK_NOTE (DND, g_print ("ienumformatetc_clone %p S_OK\n", This));
|
|
|
|
new = enum_formats_new (en->formats);
|
|
|
|
new->ix = en->ix;
|
|
|
|
*ppEnumFormatEtc = &new->ief;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static IDropSourceVtbl ids_vtbl = {
|
|
idropsource_queryinterface,
|
|
idropsource_addref,
|
|
idropsource_release,
|
|
idropsource_querycontinuedrag,
|
|
idropsource_givefeedback
|
|
};
|
|
|
|
static IDropSourceNotifyVtbl idsn_vtbl = {
|
|
(HRESULT (STDMETHODCALLTYPE *) (IDropSourceNotify *, REFIID , LPVOID *)) idropsource_queryinterface,
|
|
(ULONG (STDMETHODCALLTYPE *) (IDropSourceNotify *)) idropsource_addref,
|
|
(ULONG (STDMETHODCALLTYPE *) (IDropSourceNotify *)) idropsource_release,
|
|
idropsourcenotify_dragentertarget,
|
|
idropsourcenotify_dragleavetarget
|
|
};
|
|
|
|
static IDataObjectVtbl ido_vtbl = {
|
|
idataobject_queryinterface,
|
|
idataobject_addref,
|
|
idataobject_release,
|
|
idataobject_getdata,
|
|
idataobject_getdatahere,
|
|
idataobject_querygetdata,
|
|
idataobject_getcanonicalformatetc,
|
|
idataobject_setdata,
|
|
idataobject_enumformatetc,
|
|
idataobject_dadvise,
|
|
idataobject_dunadvise,
|
|
idataobject_enumdadvise
|
|
};
|
|
|
|
static IEnumFORMATETCVtbl ief_vtbl = {
|
|
ienumformatetc_queryinterface,
|
|
ienumformatetc_addref,
|
|
ienumformatetc_release,
|
|
ienumformatetc_next,
|
|
ienumformatetc_skip,
|
|
ienumformatetc_reset,
|
|
ienumformatetc_clone
|
|
};
|
|
|
|
static source_drag_context *
|
|
source_context_new (GdkDrag *drag,
|
|
GdkContentFormats *formats)
|
|
{
|
|
GdkWin32Drag *drag_win32;
|
|
source_drag_context *result;
|
|
GdkSurface *surface;
|
|
|
|
drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
g_object_get (drag, "surface", &surface, NULL);
|
|
|
|
result = g_new0 (source_drag_context, 1);
|
|
result->drag = g_object_ref (drag);
|
|
result->ids.lpVtbl = &ids_vtbl;
|
|
result->idsn.lpVtbl = &idsn_vtbl;
|
|
result->ref_count = 1;
|
|
result->source_window_handle = GDK_SURFACE_HWND (surface);
|
|
result->scale = drag_win32->scale;
|
|
result->util_data.state = GDK_WIN32_DND_PENDING; /* Implicit */
|
|
result->dest_window_handle = INVALID_HANDLE_VALUE;
|
|
|
|
g_object_unref (surface);
|
|
|
|
GDK_NOTE (DND, g_print ("source_context_new: %p (drag %p)\n", result, result->drag));
|
|
|
|
return result;
|
|
}
|
|
|
|
static data_object *
|
|
data_object_new (GdkDrag *drag)
|
|
{
|
|
data_object *result;
|
|
const char * const *mime_types;
|
|
gsize n_mime_types, i;
|
|
|
|
result = g_new0 (data_object, 1);
|
|
|
|
result->ido.lpVtbl = &ido_vtbl;
|
|
result->ref_count = 1;
|
|
result->drag = drag;
|
|
result->formats = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
|
|
|
|
mime_types = gdk_content_formats_get_mime_types (gdk_drag_get_formats (drag), &n_mime_types);
|
|
|
|
for (i = 0; i < n_mime_types; i++)
|
|
{
|
|
gint added_count = 0;
|
|
gint j;
|
|
|
|
GDK_NOTE (DND, g_print ("DataObject supports contentformat 0x%p (%s)\n", mime_types[i], mime_types[i]));
|
|
|
|
added_count = _gdk_win32_add_contentformat_to_pairs (mime_types[i], result->formats);
|
|
|
|
for (j = 0; j < added_count && result->formats->len - 1 - j >= 0; j++)
|
|
GDK_NOTE (DND, g_print ("DataObject will support w32format 0x%x\n", g_array_index (result->formats, GdkWin32ContentFormatPair, j).w32format));
|
|
}
|
|
|
|
GDK_NOTE (DND, g_print ("data_object_new: %p\n", result));
|
|
|
|
return result;
|
|
}
|
|
|
|
static enum_formats *
|
|
enum_formats_new (GArray *formats)
|
|
{
|
|
enum_formats *result;
|
|
|
|
result = g_new0 (enum_formats, 1);
|
|
|
|
result->ief.lpVtbl = &ief_vtbl;
|
|
result->ref_count = 1;
|
|
result->ix = 0;
|
|
result->formats = g_array_ref (formats);
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
_gdk_drag_init (void)
|
|
{
|
|
CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
|
|
|
|
if (g_strcmp0 (getenv ("GDK_WIN32_OLE2_DND"), "0") == 0)
|
|
use_ole2_dnd = FALSE;
|
|
|
|
if (use_ole2_dnd)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = OleInitialize (NULL);
|
|
|
|
if (! SUCCEEDED (hr))
|
|
g_error ("OleInitialize failed");
|
|
}
|
|
}
|
|
|
|
void
|
|
_gdk_win32_dnd_exit (void)
|
|
{
|
|
if (use_ole2_dnd)
|
|
{
|
|
OleUninitialize ();
|
|
}
|
|
|
|
CoUninitialize ();
|
|
}
|
|
|
|
static GdkSurface *
|
|
create_drag_surface (GdkDisplay *display)
|
|
{
|
|
GdkSurface *surface;
|
|
|
|
surface = gdk_surface_new_temp (display, &(GdkRectangle) { 0, 0, 100, 100 });
|
|
|
|
return surface;
|
|
}
|
|
|
|
GdkDrag *
|
|
_gdk_win32_surface_drag_begin (GdkSurface *surface,
|
|
GdkDevice *device,
|
|
GdkContentProvider *content,
|
|
GdkDragAction actions,
|
|
gint dx,
|
|
gint dy)
|
|
{
|
|
GdkDrag *drag;
|
|
GdkWin32Drag *drag_win32;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
double px, py;
|
|
int x_root, y_root;
|
|
|
|
g_return_val_if_fail (surface != NULL, NULL);
|
|
|
|
drag = gdk_drag_new (gdk_surface_get_display (surface),
|
|
surface,
|
|
content,
|
|
actions,
|
|
device,
|
|
use_ole2_dnd ? GDK_DRAG_PROTO_OLE2 : GDK_DRAG_PROTO_LOCAL);
|
|
drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("_gdk_win32_surface_drag_begin\n"));
|
|
|
|
gdk_device_get_position (device, &px, &py);
|
|
x_root = round (px) + dx;
|
|
y_root = round (py) + dy;
|
|
|
|
drag_win32->start_x = x_root;
|
|
drag_win32->start_y = y_root;
|
|
drag_win32->util_data.last_x = drag_win32->start_x;
|
|
drag_win32->util_data.last_y = drag_win32->start_y;
|
|
|
|
g_set_object (&drag_win32->grab_surface, surface);
|
|
|
|
drag_win32->drag_surface = create_drag_surface (gdk_surface_get_display (surface));
|
|
|
|
if (!drag_context_grab (drag))
|
|
{
|
|
g_object_unref (drag);
|
|
return FALSE;
|
|
}
|
|
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2)
|
|
{
|
|
GdkWin32DnDThreadDoDragDrop *ddd = g_new0 (GdkWin32DnDThreadDoDragDrop, 1);
|
|
source_drag_context *source_ctx;
|
|
data_object *data_obj;
|
|
|
|
source_ctx = source_context_new (drag, gdk_drag_get_formats (drag));
|
|
data_obj = data_object_new (drag);
|
|
|
|
ddd->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_DO_DRAG_DROP;
|
|
ddd->base.opaque_context = drag_win32;
|
|
ddd->src_context = source_ctx;
|
|
ddd->src_object = data_obj;
|
|
ddd->allowed_drop_effects = 0;
|
|
if (actions & GDK_ACTION_COPY)
|
|
ddd->allowed_drop_effects |= DROPEFFECT_COPY;
|
|
if (actions & GDK_ACTION_MOVE)
|
|
ddd->allowed_drop_effects |= DROPEFFECT_MOVE;
|
|
if (actions & GDK_ACTION_LINK)
|
|
ddd->allowed_drop_effects |= DROPEFFECT_LINK;
|
|
|
|
g_hash_table_replace (clipdrop->active_source_drags, g_object_ref (drag), ddd);
|
|
increment_dnd_queue_counter ();
|
|
g_async_queue_push (clipdrop->dnd_queue, ddd);
|
|
API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
|
|
|
|
drag_win32->util_data.state = GDK_WIN32_DND_PENDING;
|
|
}
|
|
|
|
move_drag_surface (drag, x_root, y_root);
|
|
|
|
return drag;
|
|
}
|
|
|
|
/* TODO: remove this?
|
|
* window finder is only used by our gdk_drag_update() to
|
|
* find the window at drag coordinates - which is
|
|
* something IDropSourceNotify already gives us.
|
|
* Unless, of course, we keep the LOCAL protocol around.
|
|
*/
|
|
typedef struct {
|
|
gint x;
|
|
gint y;
|
|
HWND ignore;
|
|
HWND result;
|
|
} find_window_enum_arg;
|
|
|
|
static BOOL CALLBACK
|
|
find_window_enum_proc (HWND hwnd,
|
|
LPARAM lparam)
|
|
{
|
|
RECT rect;
|
|
POINT tl, br;
|
|
find_window_enum_arg *a = (find_window_enum_arg *) lparam;
|
|
|
|
if (hwnd == a->ignore)
|
|
return TRUE;
|
|
|
|
if (!IsWindowVisible (hwnd))
|
|
return TRUE;
|
|
|
|
tl.x = tl.y = 0;
|
|
ClientToScreen (hwnd, &tl);
|
|
GetClientRect (hwnd, &rect);
|
|
br.x = rect.right;
|
|
br.y = rect.bottom;
|
|
ClientToScreen (hwnd, &br);
|
|
|
|
if (a->x >= tl.x && a->y >= tl.y && a->x < br.x && a->y < br.y)
|
|
{
|
|
a->result = hwnd;
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
/* Finds the HWND under cursor. Local DnD protcol
|
|
* uses this function, since local protocol is implemented
|
|
* entirely in GDK and cannot rely on the OS to notify
|
|
* drop targets about drags that move over them.
|
|
*/
|
|
static HWND
|
|
gdk_win32_drag_find_window (GdkDrag *drag,
|
|
GdkSurface *drag_surface,
|
|
gint x_root,
|
|
gint y_root)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
find_window_enum_arg a;
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
a.x = x_root * drag_win32->scale - _gdk_offset_x;
|
|
a.y = y_root * drag_win32->scale - _gdk_offset_y;
|
|
a.ignore = drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL;
|
|
a.result = INVALID_HANDLE_VALUE;
|
|
|
|
GDK_NOTE (DND,
|
|
g_print ("gdk_win32_drag_find_window: %p %+d%+d\n",
|
|
(drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL),
|
|
a.x, a.y));
|
|
|
|
EnumWindows (find_window_enum_proc, (LPARAM) &a);
|
|
|
|
GDK_NOTE (DND,
|
|
g_print ("gdk_win32_drag_find_window: %p %+d%+d: %p\n",
|
|
(drag_surface ? GDK_SURFACE_HWND (drag_surface) : NULL),
|
|
x_root, y_root,
|
|
a.result));
|
|
|
|
return a.result;
|
|
}
|
|
|
|
static DWORD
|
|
manufacture_keystate_from_GMT (GdkModifierType state)
|
|
{
|
|
DWORD key_state = 0;
|
|
|
|
if (state & GDK_ALT_MASK)
|
|
key_state |= MK_ALT;
|
|
if (state & GDK_CONTROL_MASK)
|
|
key_state |= MK_CONTROL;
|
|
if (state & GDK_SHIFT_MASK)
|
|
key_state |= MK_SHIFT;
|
|
if (state & GDK_BUTTON1_MASK)
|
|
key_state |= MK_LBUTTON;
|
|
if (state & GDK_BUTTON2_MASK)
|
|
key_state |= MK_MBUTTON;
|
|
if (state & GDK_BUTTON3_MASK)
|
|
key_state |= MK_RBUTTON;
|
|
|
|
return key_state;
|
|
}
|
|
|
|
/* This only works if dest_window our window and the DnD operation
|
|
* is currently local to the application.
|
|
*/
|
|
static GdkDrop *
|
|
_gdk_win32_get_drop_for_dest_window (HWND dest_window)
|
|
{
|
|
GdkSurface *drop_surface = gdk_win32_handle_table_lookup (dest_window);
|
|
GdkDrop *result = NULL;
|
|
|
|
if (drop_surface)
|
|
result = _gdk_win32_get_drop_for_dest_surface (drop_surface);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_win32_local_drag_motion (GdkDrag *drag,
|
|
HWND dest_window,
|
|
gint x_root,
|
|
gint y_root,
|
|
GdkDragAction possible_actions,
|
|
DWORD key_state,
|
|
guint32 time_)
|
|
{
|
|
GdkWin32Drag *drag_win32;
|
|
GdkDrop *drop;
|
|
GdkDragAction actions;
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
g_return_val_if_fail (drag != NULL, FALSE);
|
|
|
|
drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window);
|
|
|
|
actions = gdk_drag_get_actions (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_local_drag_motion: @ %+d:%+d possible=%s\n"
|
|
" dest=%p (current %p) drop=%p drag=%p:{actions=%s,action=%s}\n",
|
|
x_root, y_root,
|
|
_gdk_win32_drag_action_to_string (possible_actions),
|
|
dest_window, drag_win32->dest_window, drop, drag,
|
|
_gdk_win32_drag_action_to_string (actions),
|
|
_gdk_win32_drag_action_to_string (gdk_drag_get_selected_action (drag))));
|
|
|
|
if (drag_win32->dest_window != dest_window)
|
|
{
|
|
/* Send a leave to the last destination */
|
|
if (drop)
|
|
_gdk_win32_local_drop_target_dragleave (drop, time_);
|
|
|
|
drag_win32->dest_window = dest_window;
|
|
drag_win32->drag_status = GDK_DRAG_STATUS_DRAG;
|
|
|
|
_gdk_win32_local_drop_target_dragenter (drag,
|
|
gdk_win32_handle_table_lookup (dest_window),
|
|
x_root,
|
|
y_root,
|
|
key_state,
|
|
time_,
|
|
&actions);
|
|
|
|
drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window);
|
|
maybe_emit_action_changed (drag_win32, actions);
|
|
}
|
|
|
|
/* Send a drag-motion event */
|
|
|
|
drag_win32->util_data.last_x = x_root;
|
|
drag_win32->util_data.last_y = y_root;
|
|
|
|
if (drop != NULL &&
|
|
drag_win32->drag_status == GDK_DRAG_STATUS_DRAG &&
|
|
_gdk_win32_local_drop_target_will_emit_motion (drop, x_root, y_root, key_state))
|
|
{
|
|
actions = gdk_drag_get_actions (drag);
|
|
drag_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT;
|
|
|
|
_gdk_win32_local_drop_target_dragover (drop, drag, x_root, y_root, key_state, time_, &actions);
|
|
|
|
maybe_emit_action_changed (drag_win32, actions);
|
|
}
|
|
|
|
GDK_NOTE (DND, g_print (" returning %s\n"
|
|
" drag=%p:{actions=%s,action=%s}\n",
|
|
(drop != NULL && drag_win32->drag_status == GDK_DRAG_STATUS_DRAG) ? "TRUE" : "FALSE",
|
|
drag,
|
|
_gdk_win32_drag_action_to_string (gdk_drag_get_actions (drag)),
|
|
_gdk_win32_drag_action_to_string (gdk_drag_get_selected_action (drag))));
|
|
return (drop != NULL && drag_win32->drag_status == GDK_DRAG_STATUS_DRAG);
|
|
}
|
|
|
|
static void
|
|
send_source_state_update (GdkWin32Clipdrop *clipdrop,
|
|
GdkWin32Drag *drag_win32,
|
|
gpointer *ddd)
|
|
{
|
|
GdkWin32DnDThreadUpdateDragState *status = g_new0 (GdkWin32DnDThreadUpdateDragState, 1);
|
|
status->base.item_type = GDK_WIN32_DND_THREAD_QUEUE_ITEM_UPDATE_DRAG_STATE;
|
|
status->opaque_ddd = ddd;
|
|
status->produced_util_data = drag_win32->util_data;
|
|
increment_dnd_queue_counter ();
|
|
g_async_queue_push (clipdrop->dnd_queue, status);
|
|
API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id, thread_wakeup_message, 0, 0));
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_drop (GdkDrag *drag,
|
|
guint32 time_)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
g_return_if_fail (drag != NULL);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_drop\n"));
|
|
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL)
|
|
{
|
|
GdkDrop *drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window);
|
|
|
|
if (drop)
|
|
{
|
|
GdkDragAction actions;
|
|
|
|
actions = gdk_drag_get_actions (drag);
|
|
_gdk_win32_local_drop_target_drop (drop, drag, time_, &actions);
|
|
maybe_emit_action_changed (drag_win32, actions);
|
|
_gdk_win32_local_drag_drop_response (drag, actions);
|
|
}
|
|
}
|
|
else if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2)
|
|
{
|
|
gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag);
|
|
|
|
drag_win32->util_data.state = GDK_WIN32_DND_DROPPED;
|
|
|
|
if (ddd)
|
|
send_source_state_update (clipdrop, drag_win32, ddd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_set_cursor (GdkDrag *drag,
|
|
GdkCursor *cursor)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_set_cursor: 0x%p 0x%p\n", drag, cursor));
|
|
|
|
if (!g_set_object (&drag_win32->cursor, cursor))
|
|
return;
|
|
|
|
if (drag_win32->grab_seat)
|
|
{
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
gdk_device_grab (gdk_seat_get_pointer (drag_win32->grab_seat),
|
|
drag_win32->grab_surface,
|
|
GDK_OWNERSHIP_APPLICATION, FALSE,
|
|
GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
|
|
cursor, GDK_CURRENT_TIME);
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
}
|
|
}
|
|
|
|
static double
|
|
ease_out_cubic (double t)
|
|
{
|
|
double p = t - 1;
|
|
return p * p * p + 1;
|
|
}
|
|
|
|
#define ANIM_TIME 500000 /* half a second */
|
|
|
|
typedef struct _GdkDragAnim GdkDragAnim;
|
|
struct _GdkDragAnim {
|
|
GdkWin32Drag *drag;
|
|
GdkFrameClock *frame_clock;
|
|
gint64 start_time;
|
|
};
|
|
|
|
static void
|
|
gdk_drag_anim_destroy (GdkDragAnim *anim)
|
|
{
|
|
g_object_unref (anim->drag);
|
|
g_slice_free (GdkDragAnim, anim);
|
|
}
|
|
|
|
static gboolean
|
|
gdk_drag_anim_timeout (gpointer data)
|
|
{
|
|
GdkDragAnim *anim = data;
|
|
GdkWin32Drag *drag = anim->drag;
|
|
GdkFrameClock *frame_clock = anim->frame_clock;
|
|
gint64 current_time;
|
|
double f;
|
|
double t;
|
|
gint x, y;
|
|
|
|
if (!frame_clock)
|
|
return G_SOURCE_REMOVE;
|
|
|
|
current_time = gdk_frame_clock_get_frame_time (frame_clock);
|
|
|
|
f = (current_time - anim->start_time) / (double) ANIM_TIME;
|
|
|
|
if (f >= 1.0)
|
|
return G_SOURCE_REMOVE;
|
|
|
|
t = ease_out_cubic (f);
|
|
|
|
gdk_win32_surface_show (drag->drag_surface, FALSE);
|
|
x = (drag->util_data.last_x +
|
|
(drag->start_x - drag->util_data.last_x) * t -
|
|
drag->hot_x);
|
|
y = (drag->util_data.last_y +
|
|
(drag->start_y - drag->util_data.last_y) * t -
|
|
drag->hot_y);
|
|
gdk_win32_surface_move (drag->drag_surface, x, y);
|
|
gdk_win32_surface_set_opacity (drag->drag_surface, 1.0 - f);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_drop_done (GdkDrag *drag,
|
|
gboolean success)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
GdkDragAnim *anim;
|
|
/*
|
|
cairo_surface_t *win_surface;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
*/
|
|
guint id;
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_done: 0x%p %s\n",
|
|
drag,
|
|
success ? "dropped successfully" : "dropped unsuccessfully"));
|
|
|
|
/* FIXME: This is temporary, until the code is fixed to ensure that
|
|
* gdk_drag_finish () is called by GTK.
|
|
*/
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2)
|
|
{
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
gpointer ddd = g_hash_table_lookup (clipdrop->active_source_drags, drag);
|
|
|
|
if (success)
|
|
drag_win32->util_data.state = GDK_WIN32_DND_DROPPED;
|
|
else
|
|
drag_win32->util_data.state = GDK_WIN32_DND_NONE;
|
|
|
|
if (ddd)
|
|
send_source_state_update (clipdrop, drag_win32, ddd);
|
|
}
|
|
else if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL)
|
|
{
|
|
|
|
}
|
|
|
|
drag_win32->handle_events = FALSE;
|
|
|
|
if (success)
|
|
{
|
|
gdk_surface_hide (drag_win32->drag_surface);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
win_surface = _gdk_surface_ref_cairo_surface (drag_win32->drag_surface);
|
|
surface = gdk_surface_create_similar_surface (drag_win32->drag_surface,
|
|
cairo_surface_get_content (win_surface),
|
|
gdk_surface_get_width (drag_win32->drag_surface),
|
|
gdk_surface_get_height (drag_win32->drag_surface));
|
|
cr = cairo_create (surface);
|
|
cairo_set_source_surface (cr, win_surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (win_surface);
|
|
|
|
pattern = cairo_pattern_create_for_surface (surface);
|
|
|
|
gdk_surface_set_background_pattern (drag_win32->drag_surface, pattern);
|
|
|
|
cairo_pattern_destroy (pattern);
|
|
cairo_surface_destroy (surface);
|
|
*/
|
|
|
|
anim = g_slice_new0 (GdkDragAnim);
|
|
g_set_object (&anim->drag, drag_win32);
|
|
anim->frame_clock = gdk_surface_get_frame_clock (drag_win32->drag_surface);
|
|
anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_done: animate the drag window from %d : %d to %d : %d\n",
|
|
drag_win32->util_data.last_x, drag_win32->util_data.last_y,
|
|
drag_win32->start_x, drag_win32->start_y));
|
|
|
|
id = g_timeout_add_full (G_PRIORITY_DEFAULT, 17,
|
|
gdk_drag_anim_timeout, anim,
|
|
(GDestroyNotify) gdk_drag_anim_destroy);
|
|
g_source_set_name_by_id (id, "[gtk] gdk_drag_anim_timeout");
|
|
}
|
|
|
|
static gboolean
|
|
drag_context_grab (GdkDrag *drag)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
GdkSeatCapabilities capabilities;
|
|
GdkSeat *seat;
|
|
GdkCursor *cursor;
|
|
|
|
GDK_NOTE (DND, g_print ("drag_context_grab: 0x%p with grab surface 0x%p\n",
|
|
drag,
|
|
drag_win32->grab_surface));
|
|
|
|
if (!drag_win32->grab_surface)
|
|
return FALSE;
|
|
|
|
seat = gdk_device_get_seat (gdk_drag_get_device (drag));
|
|
|
|
capabilities = GDK_SEAT_CAPABILITY_ALL;
|
|
|
|
cursor = gdk_drag_get_cursor (drag, gdk_drag_get_selected_action (drag));
|
|
g_set_object (&drag_win32->cursor, cursor);
|
|
|
|
if (gdk_seat_grab (seat, drag_win32->grab_surface,
|
|
capabilities, FALSE,
|
|
drag_win32->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS)
|
|
return FALSE;
|
|
|
|
g_set_object (&drag_win32->grab_seat, seat);
|
|
|
|
/* TODO: Should be grabbing keys here, to support keynav. SetWindowsHookEx()? */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
drag_context_ungrab (GdkDrag *drag)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("drag_context_ungrab: 0x%p 0x%p\n",
|
|
drag,
|
|
drag_win32->grab_seat));
|
|
|
|
if (!drag_win32->grab_seat)
|
|
return;
|
|
|
|
gdk_seat_ungrab (drag_win32->grab_seat);
|
|
|
|
g_clear_object (&drag_win32->grab_seat);
|
|
|
|
/* TODO: Should be ungrabbing keys here */
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_cancel (GdkDrag *drag,
|
|
GdkDragCancelReason reason)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
const gchar *reason_str = NULL;
|
|
switch (reason)
|
|
{
|
|
case GDK_DRAG_CANCEL_NO_TARGET:
|
|
reason_str = "no target";
|
|
break;
|
|
case GDK_DRAG_CANCEL_USER_CANCELLED:
|
|
reason_str = "user cancelled";
|
|
break;
|
|
case GDK_DRAG_CANCEL_ERROR:
|
|
reason_str = "error";
|
|
break;
|
|
default:
|
|
reason_str = "<unknown>";
|
|
break;
|
|
}
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_cancel: 0x%p %s\n",
|
|
drag,
|
|
reason_str));
|
|
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL)
|
|
{
|
|
GdkDrop *drop = _gdk_win32_get_drop_for_dest_window (drag_win32->dest_window);
|
|
if (drop)
|
|
_gdk_win32_local_drop_target_dragleave (drop, GDK_CURRENT_TIME);
|
|
drop = NULL;
|
|
}
|
|
|
|
gdk_drag_set_cursor (drag, NULL);
|
|
drag_context_ungrab (drag);
|
|
gdk_drag_drop_done (drag, FALSE);
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_drop_performed (GdkDrag *drag,
|
|
guint32 time_)
|
|
{
|
|
GDK_NOTE (DND, g_print ("gdk_win32_drag_drop_performed: 0x%p %u\n",
|
|
drag,
|
|
time_));
|
|
|
|
gdk_win32_drag_drop (drag, time_);
|
|
gdk_drag_set_cursor (drag, NULL);
|
|
drag_context_ungrab (drag);
|
|
}
|
|
|
|
#define BIG_STEP 20
|
|
#define SMALL_STEP 1
|
|
|
|
static void
|
|
gdk_local_drag_update (GdkDrag *drag,
|
|
gdouble x_root,
|
|
gdouble y_root,
|
|
DWORD grfKeyState,
|
|
guint32 evtime)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
HWND dest_window;
|
|
|
|
g_assert (_win32_main_thread == NULL ||
|
|
_win32_main_thread == g_thread_self ());
|
|
|
|
dest_window = gdk_win32_drag_find_window (drag,
|
|
drag_win32->drag_surface,
|
|
x_root, y_root);
|
|
|
|
gdk_win32_local_drag_motion (drag, dest_window, x_root, y_root,
|
|
gdk_drag_get_actions (drag),
|
|
grfKeyState, evtime);
|
|
}
|
|
|
|
static gboolean
|
|
gdk_dnd_handle_motion_event (GdkDrag *drag,
|
|
GdkEvent *event)
|
|
{
|
|
GdkModifierType state;
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
DWORD key_state;
|
|
double x, y;
|
|
double x_root, y_root;
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_motion_event: 0x%p\n", drag));
|
|
|
|
state = gdk_event_get_modifier_state (event);
|
|
gdk_event_get_position (event, &x, &y);
|
|
|
|
x_root = x + _gdk_offset_x;
|
|
y_root = y + _gdk_offset_y;
|
|
|
|
if (drag_win32->drag_surface)
|
|
move_drag_surface (drag, x_root, y_root);
|
|
|
|
key_state = manufacture_keystate_from_GMT (state);
|
|
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL)
|
|
{
|
|
gdk_local_drag_update (drag, x_root, y_root, key_state,
|
|
gdk_event_get_time (event));
|
|
}
|
|
else if (drag_win32->protocol == GDK_DRAG_PROTO_OLE2)
|
|
{
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
GDK_NOTE (DND, g_print ("Post WM_MOUSEMOVE keystate=%lu\n", key_state));
|
|
|
|
drag_win32->util_data.last_x = x_root;
|
|
drag_win32->util_data.last_y = y_root;
|
|
|
|
API_CALL (PostThreadMessage, (clipdrop->dnd_thread_id,
|
|
WM_MOUSEMOVE,
|
|
key_state,
|
|
MAKELPARAM (x * drag_win32->scale,
|
|
y * drag_win32->scale)));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_dnd_handle_key_event (GdkDrag *drag,
|
|
GdkEvent *event)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
GdkModifierType state;
|
|
GdkDevice *pointer;
|
|
gint dx, dy;
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_key_event: 0x%p\n", drag));
|
|
|
|
state = gdk_event_get_modifier_state (event);
|
|
|
|
dx = dy = 0;
|
|
pointer = gdk_device_get_associated_device (gdk_event_get_device (event));
|
|
|
|
if (gdk_event_get_event_type (event) == GDK_KEY_PRESS)
|
|
{
|
|
switch (gdk_key_event_get_keyval (event))
|
|
{
|
|
case GDK_KEY_Escape:
|
|
gdk_drag_cancel (drag, GDK_DRAG_CANCEL_USER_CANCELLED);
|
|
return TRUE;
|
|
|
|
case GDK_KEY_space:
|
|
case GDK_KEY_Return:
|
|
case GDK_KEY_ISO_Enter:
|
|
case GDK_KEY_KP_Enter:
|
|
case GDK_KEY_KP_Space:
|
|
if ((gdk_drag_get_selected_action (drag) != 0) &&
|
|
(drag_win32->dest_window != INVALID_HANDLE_VALUE))
|
|
{
|
|
g_signal_emit_by_name (drag, "drop-performed");
|
|
}
|
|
else
|
|
gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET);
|
|
|
|
return TRUE;
|
|
|
|
case GDK_KEY_Up:
|
|
case GDK_KEY_KP_Up:
|
|
dy = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP;
|
|
break;
|
|
|
|
case GDK_KEY_Down:
|
|
case GDK_KEY_KP_Down:
|
|
dy = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP;
|
|
break;
|
|
|
|
case GDK_KEY_Left:
|
|
case GDK_KEY_KP_Left:
|
|
dx = (state & GDK_ALT_MASK) ? -BIG_STEP : -SMALL_STEP;
|
|
break;
|
|
|
|
case GDK_KEY_Right:
|
|
case GDK_KEY_KP_Right:
|
|
dx = (state & GDK_ALT_MASK) ? BIG_STEP : SMALL_STEP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* The state is not yet updated in the event, so we need
|
|
* to query it here.
|
|
*/
|
|
_gdk_device_query_state (pointer, NULL, NULL, NULL, NULL, &state);
|
|
|
|
if (dx != 0 || dy != 0)
|
|
{
|
|
drag_win32->util_data.last_x += dx;
|
|
drag_win32->util_data.last_y += dy;
|
|
}
|
|
|
|
if (drag_win32->drag_surface)
|
|
move_drag_surface (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y);
|
|
|
|
if (drag_win32->protocol == GDK_DRAG_PROTO_LOCAL)
|
|
gdk_local_drag_update (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y,
|
|
manufacture_keystate_from_GMT (state),
|
|
gdk_event_get_time (event));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_dnd_handle_grab_broken_event (GdkDrag *drag,
|
|
GdkEvent *event)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_grab_broken_event: 0x%p\n", drag));
|
|
|
|
/* Don't cancel if we break the implicit grab from the initial button_press.
|
|
* Also, don't cancel if we re-grab on the widget or on our grab window, for
|
|
* example, when changing the drag cursor.
|
|
*/
|
|
if (/* FIXME: event->implicit || */
|
|
gdk_grab_broken_event_get_grab_surface (event) == drag_win32->drag_surface ||
|
|
gdk_grab_broken_event_get_grab_surface (event) == drag_win32->grab_surface)
|
|
return FALSE;
|
|
|
|
if (gdk_event_get_device (event) != gdk_drag_get_device (drag))
|
|
return FALSE;
|
|
|
|
gdk_drag_cancel (drag, GDK_DRAG_CANCEL_ERROR);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_dnd_handle_button_event (GdkDrag *drag,
|
|
GdkEvent *event)
|
|
{
|
|
GDK_NOTE (DND, g_print ("gdk_dnd_handle_button_event: 0x%p\n", drag));
|
|
|
|
#if 0
|
|
/* FIXME: Check the button matches */
|
|
if (event->button != drag_win32->button)
|
|
return FALSE;
|
|
#endif
|
|
|
|
if ((gdk_drag_get_selected_action (drag) != 0))
|
|
{
|
|
g_signal_emit_by_name (drag, "drop-performed");
|
|
}
|
|
else
|
|
gdk_drag_cancel (drag, GDK_DRAG_CANCEL_NO_TARGET);
|
|
|
|
/* Make sure GTK gets mouse release button event */
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
gdk_win32_drag_handle_event (GdkDrag *drag,
|
|
GdkEvent *event)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
if (!drag_win32->grab_seat)
|
|
return FALSE;
|
|
if (!drag_win32->handle_events)
|
|
{
|
|
/* FIXME: remove this functionality once gtk no longer calls DnD after drag_done() */
|
|
g_warning ("Got an event %d for drag context %p, even though it's done!",
|
|
event->event_type, drag);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (gdk_event_get_event_type (event))
|
|
{
|
|
case GDK_MOTION_NOTIFY:
|
|
return gdk_dnd_handle_motion_event (drag, event);
|
|
case GDK_BUTTON_RELEASE:
|
|
return gdk_dnd_handle_button_event (drag, event);
|
|
case GDK_KEY_PRESS:
|
|
case GDK_KEY_RELEASE:
|
|
return gdk_dnd_handle_key_event (drag, event);
|
|
case GDK_GRAB_BROKEN:
|
|
return gdk_dnd_handle_grab_broken_event (drag, event);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GdkSurface *
|
|
gdk_win32_drag_get_drag_surface (GdkDrag *drag)
|
|
{
|
|
return GDK_WIN32_DRAG (drag)->drag_surface;
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_set_hotspot (GdkDrag *drag,
|
|
gint hot_x,
|
|
gint hot_y)
|
|
{
|
|
GdkWin32Drag *drag_win32 = GDK_WIN32_DRAG (drag);
|
|
|
|
GDK_NOTE (DND, g_print ("gdk_drag_set_hotspot: 0x%p %d:%d\n",
|
|
drag,
|
|
hot_x, hot_y));
|
|
|
|
drag_win32->hot_x = hot_x;
|
|
drag_win32->hot_y = hot_y;
|
|
|
|
if (drag_win32->grab_seat)
|
|
{
|
|
/* DnD is managed, update current position */
|
|
move_drag_surface (drag, drag_win32->util_data.last_x, drag_win32->util_data.last_y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gdk_win32_drag_class_init (GdkWin32DragClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GdkDragClass *drag_class = GDK_DRAG_CLASS (klass);
|
|
|
|
object_class->finalize = gdk_win32_drag_finalize;
|
|
|
|
drag_class->get_drag_surface = gdk_win32_drag_get_drag_surface;
|
|
drag_class->set_hotspot = gdk_win32_drag_set_hotspot;
|
|
drag_class->drop_done = gdk_win32_drag_drop_done;
|
|
drag_class->set_cursor = gdk_win32_drag_set_cursor;
|
|
drag_class->cancel = gdk_win32_drag_cancel;
|
|
drag_class->drop_performed = gdk_win32_drag_drop_performed;
|
|
drag_class->handle_event = gdk_win32_drag_handle_event;
|
|
}
|