gtk2/gdk/win32/gdkdnd-win32.c
Benjamin Otte 0d31eb8670 dnd: Add gdk_drag_context_get_display()
Also turn it into a readable, construct-only property.

Every GDK object should have this. (Apart from GdkDisplay, obviously.)
2017-12-10 01:09:14 +01:00

3277 lines
93 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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>
/*
* Support for OLE-2 drag and drop added at Archaeopteryx Software, 2001
* For more information, contact Stephan R.A. Deibel (sdeibel@archaeopteryx.com)
*
* Notes on the implementation:
*
* Source drag context, IDragSource 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 targets).
* To account for it the data is transmuted back and forth. There are two
* main points of transmutation:
* * GDK convert selection: transmute W32 data to GTK+ data
* * GDK window property change: transmute GTK+ data to W32 data
*
* There are also two points where data formats are considered:
* * When source drag context is created, it gets a list of GTK+ targets
* that it supports, these are matched to the W32 formats they
* correspond to (possibly with transmutation). New W32 formats for
* GTK+-specific formats are also created here (see below).
* * When target drag context is created, it queries the IDataObject
* for the list of W32 formats it supports and matches these to
* corresponding GTK+ formats that it will be able to provide
* (possibly with transmutation) later. Missing GDK targets for
* W32-specific formats are also created here (see below).
*
* W32 formats and GTK+ targets are both integers (CLIPFORMAT and GdkAtom
* respectively), but 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 either with 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 target format 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 target,
* GDK will also offer "PNG" and CF_DIB W32 formats.
* * If GTK+ application provides UTF8_STRING, 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 UTF8_STRING, 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 accommodate GTK+ application compaibility the code allows
* GTK+ applications to register drop targets that accept GTK+-specific
* data formats, and to register drag sources that provide GTK+-specific
* data formats. This is done by simply registering target atom names
* as clipboard formats. This way two GTK+ applications can exchange
* data in their native formats (both well-known ones, such as UTF8_STRING,
* 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.
*/
/* 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 "gdkdnd.h"
#include "gdkproperty.h"
#include "gdkinternals.h"
#include "gdkprivate-win32.h"
#include "gdkwin32.h"
#include "gdkwin32dnd.h"
#include "gdkdisplayprivate.h"
#include "gdk/gdkdndprivate.h"
#include "gdkwin32dnd-private.h"
#include "gdkdisplay-win32.h"
#include "gdkselection-win32.h"
#include "gdkdeviceprivate.h"
#include <ole2.h>
#include <shlobj.h>
#include <shlguid.h>
#include <objidl.h>
#include <gdk/gdk.h>
#include <glib/gstdio.h>
/* from gdkselection-win32.c */
extern GdkAtom *known_pixbuf_formats;
extern int n_known_pixbuf_formats;
typedef enum {
GDK_DRAG_STATUS_DRAG,
GDK_DRAG_STATUS_MOTION_WAIT,
GDK_DRAG_STATUS_ACTION_WAIT,
GDK_DRAG_STATUS_DROP
} GdkDragStatus;
static GList *contexts;
static GdkDragContext *current_dest_drag = NULL;
static gboolean use_ole2_dnd = FALSE;
G_DEFINE_TYPE (GdkWin32DragContext, gdk_win32_drag_context, GDK_TYPE_DRAG_CONTEXT)
static void
move_drag_window (GdkDragContext *context,
guint x_root,
guint y_root)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
gdk_window_move (context_win32->drag_window,
x_root - context_win32->hot_x,
y_root - context_win32->hot_y);
gdk_window_raise (context_win32->drag_window);
}
static void
gdk_win32_drag_context_init (GdkWin32DragContext *context)
{
if (!use_ole2_dnd)
{
contexts = g_list_prepend (contexts, context);
}
else
{
}
GDK_NOTE (DND, g_print ("gdk_drag_context_init %p\n", context));
}
static void
gdk_win32_drag_context_finalize (GObject *object)
{
GdkDragContext *context;
GdkWin32DragContext *context_win32;
GdkWindow *drag_window;
GDK_NOTE (DND, g_print ("gdk_drag_context_finalize %p\n", object));
g_return_if_fail (GDK_IS_WIN32_DRAG_CONTEXT (object));
context = GDK_DRAG_CONTEXT (object);
context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
if (!use_ole2_dnd)
{
contexts = g_list_remove (contexts, context);
if (context == current_dest_drag)
current_dest_drag = NULL;
}
drag_window = context_win32->drag_window;
g_array_unref (context_win32->droptarget_format_target_map);
G_OBJECT_CLASS (gdk_win32_drag_context_parent_class)->finalize (object);
if (drag_window)
gdk_window_destroy (drag_window);
}
/* Drag Contexts */
static GdkDragContext *
gdk_drag_context_new (GdkDisplay *display)
{
GdkWin32DragContext *context_win32;
GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
GdkDragContext *context;
context_win32 = g_object_new (GDK_TYPE_WIN32_DRAG_CONTEXT,
"display", display,
NULL);
context = GDK_DRAG_CONTEXT(context_win32);
gdk_drag_context_set_device (context, gdk_seat_get_pointer (gdk_display_get_default_seat (display)));
if (win32_display->has_fixed_scale)
context_win32->scale = win32_display->window_scale;
else
context_win32->scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
context_win32->droptarget_format_target_map = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
return context;
}
static GdkDragContext *
gdk_drag_context_find (gboolean is_source,
GdkWindow *source,
GdkWindow *dest)
{
GList *tmp_list = contexts;
GdkDragContext *context;
while (tmp_list)
{
context = (GdkDragContext *)tmp_list->data;
if ((!context->is_source == !is_source) &&
((source == NULL) || (context->source_window && (context->source_window == source))) &&
((dest == NULL) || (context->dest_window && (context->dest_window == dest))))
return context;
tmp_list = tmp_list->next;
}
return NULL;
}
#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]);
typedef struct {
IDropTarget idt;
GdkDragContext *context;
gint ref_count;
GdkWindow *dest_window;
} target_drag_context;
typedef struct {
IDropSource ids;
GdkDragContext *context;
gint ref_count;
} source_drag_context;
typedef struct {
IDataObject ido;
int ref_count;
GdkDragContext *context;
GArray *formats;
} data_object;
typedef struct {
IEnumFORMATETC ief;
int ref_count;
int ix;
data_object *dataobj;
} enum_formats;
static source_drag_context *pending_src_context = NULL;
static source_drag_context *current_src_context = NULL;
static data_object *current_src_object = NULL;
static enum_formats *enum_formats_new (data_object *dataobj);
/* map windows -> target drag contexts. The table
* owns a ref to both objects.
*/
static GHashTable* target_ctx_for_window = NULL;
static ULONG STDMETHODCALLTYPE
idroptarget_addref (LPDROPTARGET This)
{
target_drag_context *ctx = (target_drag_context *) This;
int ref_count = ++ctx->ref_count;
GDK_NOTE (DND, g_print ("idroptarget_addref %p %d\n", This, ref_count));
return ref_count;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_queryinterface (LPDROPTARGET This,
REFIID riid,
LPVOID *ppvObject)
{
GDK_NOTE (DND, {
g_print ("idroptarget_queryinterface %p ", This);
PRINT_GUID (riid);
});
*ppvObject = NULL;
if (IsEqualGUID (riid, &IID_IUnknown))
{
GDK_NOTE (DND, g_print ("...IUnknown S_OK\n"));
idroptarget_addref (This);
*ppvObject = This;
return S_OK;
}
else if (IsEqualGUID (riid, &IID_IDropTarget))
{
GDK_NOTE (DND, g_print ("...IDropTarget S_OK\n"));
idroptarget_addref (This);
*ppvObject = This;
return S_OK;
}
else
{
GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
return E_NOINTERFACE;
}
}
static ULONG STDMETHODCALLTYPE
idroptarget_release (LPDROPTARGET This)
{
target_drag_context *ctx = (target_drag_context *) This;
int ref_count = --ctx->ref_count;
GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count));
if (ref_count == 0)
{
g_object_unref (ctx->context);
g_clear_object (&ctx->dest_window);
g_free (This);
}
return ref_count;
}
static GdkDragAction
get_suggested_action (DWORD grfKeyState)
{
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
/* This is the yucky Windows standard: Force link action if both
* Control and Alt are down, copy if Control is down alone, move if
* Alt is down alone, or use default of move within the app or copy
* when origin of the drag is in another app.
*/
if (grfKeyState & MK_CONTROL && grfKeyState & MK_SHIFT)
return GDK_ACTION_LINK; /* Link action not supported */
else if (grfKeyState & MK_CONTROL)
return GDK_ACTION_COPY;
else if (grfKeyState & MK_ALT)
return GDK_ACTION_MOVE;
else if (sel_win32->dnd_source_state == GDK_WIN32_DND_DRAGGING)
return GDK_ACTION_MOVE;
else
return GDK_ACTION_COPY;
/* Any way to determine when to add in DROPEFFECT_SCROLL? */
}
/* Process pending events -- we don't want to service non-GUI events
* forever so do one iteration and then do more only if theres a
* pending GDK event.
*/
static void
process_pending_events (GdkDisplay *display)
{
g_main_context_iteration (NULL, FALSE);
while (_gdk_event_queue_find_first (display))
g_main_context_iteration (NULL, FALSE);
}
static DWORD
drop_effect_for_action (GdkDragAction action)
{
switch (action)
{
case GDK_ACTION_MOVE:
return DROPEFFECT_MOVE;
case GDK_ACTION_LINK:
return DROPEFFECT_LINK;
case GDK_ACTION_COPY:
return DROPEFFECT_COPY;
default:
return DROPEFFECT_NONE;
}
}
static GdkDragAction
action_for_drop_effect (DWORD effect)
{
switch (effect)
{
case DROPEFFECT_MOVE:
return GDK_ACTION_MOVE;
case DROPEFFECT_LINK:
return GDK_ACTION_LINK;
case DROPEFFECT_COPY:
return GDK_ACTION_COPY;
default:
return 0;
}
}
static void
dnd_event_put (GdkEventType type,
GdkDragContext *context,
gint pt_x,
gint pt_y,
gboolean to_dest_window)
{
GdkEvent *e;
e = gdk_event_new (type);
if (to_dest_window)
g_set_object (&e->dnd.window, context->dest_window);
else
g_set_object (&e->dnd.window, context->source_window);
e->dnd.send_event = FALSE;
g_set_object (&e->dnd.context, context);
e->dnd.time = GDK_CURRENT_TIME;
e->dnd.x_root = pt_x;
e->dnd.y_root = pt_y;
gdk_event_set_device (e, gdk_drag_context_get_device (context));
gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (context)));
GDK_NOTE (EVENTS, _gdk_win32_print_event (e));
gdk_event_put (e);
gdk_event_free (e);
}
static GdkContentFormats *
query_targets (LPDATAOBJECT pDataObj,
GArray *format_target_map)
{
IEnumFORMATETC *pfmt = NULL;
FORMATETC fmt;
GList *result = NULL;
HRESULT hr;
GdkContentFormatsBuilder *builder;
GdkContentFormats *result_formats;
GList *p;
if ((LPDATAOBJECT) current_src_object == pDataObj)
return gdk_content_formats_ref (current_src_object->context->formats);
hr = IDataObject_EnumFormatEtc (pDataObj, DATADIR_GET, &pfmt);
if (SUCCEEDED (hr))
hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);
while (SUCCEEDED (hr) && hr != S_FALSE)
{
gboolean is_predef;
gchar *registered_name = _gdk_win32_get_clipboard_format_name (fmt.cfFormat, &is_predef);
if (registered_name && is_predef)
GDK_NOTE (DND, g_print ("supported built-in source format 0x%x %s\n", fmt.cfFormat, registered_name));
else if (registered_name)
GDK_NOTE (DND, g_print ("supported source format 0x%x %s\n", fmt.cfFormat, registered_name));
else
GDK_NOTE (DND, g_print ("supported unnamed? source format 0x%x\n", fmt.cfFormat));
g_free (registered_name);
_gdk_win32_add_format_to_targets (fmt.cfFormat, format_target_map, &result);
hr = IEnumFORMATETC_Next (pfmt, 1, &fmt, NULL);
}
if (pfmt)
IEnumFORMATETC_Release (pfmt);
builder = gdk_content_formats_builder_new ();
for (p = g_list_reverse (result); p; p = p->next)
gdk_content_formats_builder_add_mime_type (builder, (const gchar *) p->data);
result_formats = gdk_content_formats_builder_free (builder);
g_list_free (result);
return result_formats;
}
static void
set_data_object (LPDATAOBJECT *location, LPDATAOBJECT data_object)
{
if (*location != NULL)
IDataObject_Release (*location);
*location = data_object;
if (*location != NULL)
IDataObject_AddRef (*location);
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragenter (LPDROPTARGET This,
LPDATAOBJECT pDataObj,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
GdkDragContext *context;
GdkWin32DragContext *context_win32;
gint pt_x;
gint pt_y;
GDK_NOTE (DND, g_print ("idroptarget_dragenter %p @ %ld : %ld for dest window 0x%p S_OK\n", This, pt.x, pt.y, ctx->dest_window));
g_clear_object (&ctx->context);
context = gdk_drag_context_new (gdk_window_get_display (ctx->dest_window));
context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
ctx->context = context;
g_set_object (&context->dest_window, ctx->dest_window);
context->protocol = GDK_DRAG_PROTO_OLE2;
context->is_source = FALSE;
context->source_window = NULL;
/* OLE2 DnD does not allow us to get the source window,
* but we *can* find it if it's ours. This is needed to
* support DnD within the same widget, for example.
*/
if (current_src_context && current_src_context->context)
g_set_object (&context->source_window, current_src_context->context->source_window);
else
g_set_object (&context->source_window, NULL); /* FIXME: Root window used to be here. Find a substitute? */
g_set_object (&sel_win32->target_drag_context, context);
context->actions = GDK_ACTION_DEFAULT | GDK_ACTION_COPY | GDK_ACTION_MOVE;
context->suggested_action = GDK_ACTION_MOVE;
context->action = GDK_ACTION_MOVE;
g_array_set_size (context_win32->droptarget_format_target_map, 0);
context->formats = query_targets (pDataObj, context_win32->droptarget_format_target_map);
ctx->context->suggested_action = get_suggested_action (grfKeyState);
set_data_object (&sel_win32->dnd_data_object_target, pDataObj);
pt_x = pt.x / context_win32->scale + _gdk_offset_x;
pt_y = pt.y / context_win32->scale + _gdk_offset_y;
dnd_event_put (GDK_DRAG_ENTER, ctx->context, pt_x, pt_y, TRUE);
dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE);
context_win32->last_key_state = grfKeyState;
context_win32->last_x = pt_x;
context_win32->last_y = pt_y;
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
*pdwEffect = drop_effect_for_action (ctx->context->action);
GDK_NOTE (DND, g_print ("idroptarget_dragenter returns with action %d and drop effect %lu\n", ctx->context->action, *pdwEffect));
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragover (LPDROPTARGET This,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
gint pt_x = pt.x / context_win32->scale + _gdk_offset_x;
gint pt_y = pt.y / context_win32->scale + _gdk_offset_y;
ctx->context->suggested_action = get_suggested_action (grfKeyState);
GDK_NOTE (DND, g_print ("idroptarget_dragover %p @ %ld : %ld, suggests %d action S_OK\n", This, pt.x, pt.y, ctx->context->suggested_action));
if (pt_x != context_win32->last_x ||
pt_y != context_win32->last_y ||
grfKeyState != context_win32->last_key_state)
{
dnd_event_put (GDK_DRAG_MOTION, ctx->context, pt_x, pt_y, TRUE);
context_win32->last_key_state = grfKeyState;
context_win32->last_x = pt_x;
context_win32->last_y = pt_y;
}
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
*pdwEffect = drop_effect_for_action (ctx->context->action);
GDK_NOTE (DND, g_print ("idroptarget_dragover returns with action %d and effect %lu\n", ctx->context->action, *pdwEffect));
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_dragleave (LPDROPTARGET This)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
GDK_NOTE (DND, g_print ("idroptarget_dragleave %p S_OK\n", This));
dnd_event_put (GDK_DRAG_LEAVE, ctx->context, 0, 0, TRUE);
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
g_clear_object (&sel_win32->target_drag_context);
g_clear_object (&ctx->context);
set_data_object (&sel_win32->dnd_data_object_target, NULL);
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
idroptarget_drop (LPDROPTARGET This,
LPDATAOBJECT pDataObj,
DWORD grfKeyState,
POINTL pt,
LPDWORD pdwEffect)
{
target_drag_context *ctx = (target_drag_context *) This;
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
gint pt_x = pt.x / context_win32->scale + _gdk_offset_x;
gint pt_y = pt.y / context_win32->scale + _gdk_offset_y;
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
GDK_NOTE (DND, g_print ("idroptarget_drop %p ", This));
if (pDataObj == NULL)
{
GDK_NOTE (DND, g_print ("E_POINTER\n"));
g_clear_object (&ctx->context);
return E_POINTER;
}
ctx->context->suggested_action = get_suggested_action (grfKeyState);
dnd_event_put (GDK_DROP_START, ctx->context, pt_x, pt_y, TRUE);
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
/* Notify OLE of copy or move */
if (sel_win32->dnd_target_state != GDK_WIN32_DND_DROPPED)
*pdwEffect = DROPEFFECT_NONE;
else
*pdwEffect = drop_effect_for_action (ctx->context->action);
g_clear_object (&sel_win32->target_drag_context);
g_clear_object (&ctx->context);
set_data_object (&sel_win32->dnd_data_object_target, NULL);
GDK_NOTE (DND, g_print ("drop S_OK with effect %lx\n", *pdwEffect));
return S_OK;
}
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;
}
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
{
GDK_NOTE (DND, g_print ("...E_NOINTERFACE\n"));
return E_NOINTERFACE;
}
}
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_clear_object (&ctx->context);
if (current_src_context == ctx)
current_src_context = NULL;
g_free (This);
}
return ref_count;
}
/* Emit GDK events for any changes in mouse events or control key
* state since the last recorded state. Return true if any events
* have been emitted and false otherwise.
*/
static gboolean
send_change_events (GdkDragContext *context,
DWORD key_state,
gboolean esc_pressed)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
POINT pt;
POINT pt_client;
gboolean changed = FALSE;
HWND hwnd = GDK_WINDOW_HWND (context->source_window);
LPARAM lparam;
WPARAM wparam;
gint pt_x;
gint pt_y;
if (!API_CALL (GetCursorPos, (&pt)))
return FALSE;
pt_client = pt;
if (!API_CALL (ScreenToClient, (hwnd, &pt_client)))
return FALSE;
pt_x = pt.x / context_win32->scale + _gdk_offset_x;
pt_y = pt.y / context_win32->scale + _gdk_offset_y;
if (pt_x != context_win32->last_x || pt_y != context_win32->last_y ||
key_state != context_win32->last_key_state)
{
lparam = MAKELPARAM (pt_client.x, pt_client.y);
wparam = key_state;
if (pt_x != context_win32->last_x || pt_y != context_win32->last_y)
{
GDK_NOTE (DND, g_print ("Sending WM_MOUSEMOVE (%ld,%ld)\n", pt.x, pt.y));
SendMessage (hwnd, WM_MOUSEMOVE, wparam, lparam);
}
if ((key_state & MK_LBUTTON) != (context_win32->last_key_state & MK_LBUTTON))
{
if (key_state & MK_LBUTTON)
SendMessage (hwnd, WM_LBUTTONDOWN, wparam, lparam);
else
SendMessage (hwnd, WM_LBUTTONUP, wparam, lparam);
}
if ((key_state & MK_MBUTTON) != (context_win32->last_key_state & MK_MBUTTON))
{
if (key_state & MK_MBUTTON)
SendMessage (hwnd, WM_MBUTTONDOWN, wparam, lparam);
else
SendMessage (hwnd, WM_MBUTTONUP, wparam, lparam);
}
if ((key_state & MK_RBUTTON) != (context_win32->last_key_state & MK_RBUTTON))
{
if (key_state & MK_RBUTTON)
SendMessage (hwnd, WM_RBUTTONDOWN, wparam, lparam);
else
SendMessage (hwnd, WM_RBUTTONUP, wparam, lparam);
}
if ((key_state & MK_CONTROL) != (context_win32->last_key_state & MK_CONTROL))
{
if (key_state & MK_CONTROL)
SendMessage (hwnd, WM_KEYDOWN, VK_CONTROL, 0);
else
SendMessage (hwnd, WM_KEYUP, VK_CONTROL, 0);
}
if ((key_state & MK_SHIFT) != (context_win32->last_key_state & MK_SHIFT))
{
if (key_state & MK_SHIFT)
SendMessage (hwnd, WM_KEYDOWN, VK_SHIFT, 0);
else
SendMessage (hwnd, WM_KEYUP, VK_SHIFT, 0);
}
changed = TRUE;
context_win32->last_key_state = key_state;
context_win32->last_x = pt_x;
context_win32->last_y = pt_y;
}
if (esc_pressed)
{
GDK_NOTE (DND, g_print ("Sending a escape key down message to %p\n", hwnd));
SendMessage (hwnd, WM_KEYDOWN, VK_ESCAPE, 0);
changed = TRUE;
}
return changed;
}
static HRESULT STDMETHODCALLTYPE
idropsource_querycontinuedrag (LPDROPSOURCE This,
BOOL fEscapePressed,
DWORD grfKeyState)
{
source_drag_context *ctx = (source_drag_context *) This;
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p esc=%d keystate=0x%lx with state %d", This, fEscapePressed, grfKeyState, sel_win32->dnd_source_state));
if (send_change_events (ctx->context, grfKeyState, fEscapePressed))
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
if (sel_win32->dnd_source_state == GDK_WIN32_DND_DROPPED)
{
GDK_NOTE (DND, g_print ("DRAGDROP_S_DROP\n"));
return DRAGDROP_S_DROP;
}
else if (sel_win32->dnd_source_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 HRESULT STDMETHODCALLTYPE
idropsource_givefeedback (LPDROPSOURCE This,
DWORD dwEffect)
{
source_drag_context *ctx = (source_drag_context *) This;
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (ctx->context);
GdkDragAction suggested_action;
GdkEvent *e;
POINT pt;
GDK_NOTE (DND, g_print ("idropsource_givefeedback %p with drop effect %lu S_OK\n", This, dwEffect));
if (!API_CALL (GetCursorPos, (&pt)))
return S_OK;
suggested_action = action_for_drop_effect (dwEffect);
ctx->context->action = suggested_action;
if (dwEffect == DROPEFFECT_NONE)
g_clear_object (&ctx->context->dest_window);
else if (ctx->context->dest_window == NULL)
ctx->context->dest_window = NULL; /* FIXME: Root window was here originally. Find a substitute? */
context_win32->last_x = pt.x / context_win32->scale + _gdk_offset_x;
context_win32->last_y = pt.y / context_win32->scale + _gdk_offset_y;
e = gdk_event_new (GDK_DRAG_STATUS);
g_set_object (&e->dnd.window, ctx->context->source_window);
e->dnd.send_event = FALSE;
g_set_object (&e->dnd.context, ctx->context);
e->dnd.time = GDK_CURRENT_TIME;
e->dnd.x_root = context_win32->last_x;
e->dnd.y_root = context_win32->last_y;
gdk_event_set_device (e, gdk_drag_context_get_device (ctx->context));
gdk_event_set_seat (e, gdk_device_get_seat (gdk_drag_context_get_device (ctx->context)));
GDK_NOTE (EVENTS, _gdk_win32_print_event (e));
gdk_event_put (e);
gdk_event_free (e);
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
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)
{
data_object *ctx = (data_object *) This;
gint i;
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++)
if (pFormatEtc->cfFormat == g_array_index (ctx->formats, GdkSelTargetFormat, i).format)
return S_OK;
return DV_E_FORMATETC;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium)
{
data_object *ctx = (data_object *) This;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
HRESULT hr;
GdkEvent e;
gint i;
GdkAtom target;
gint64 loopend;
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);
if (hr != S_OK)
{
GDK_NOTE (DND, g_print ("Unsupported format, returning 0x%lx\n", hr));
return hr;
}
/* Append a GDK_SELECTION_REQUEST event and then hope the app sets the
* property associated with the _gdk_ole2_dnd atom
*/
win32_sel->property_change_format = pFormatEtc->cfFormat;
win32_sel->property_change_data = pMedium;
for (i = 0, target = NULL; i < ctx->formats->len; i++)
{
GdkSelTargetFormat *frec = &g_array_index (ctx->formats, GdkSelTargetFormat, i);
if (frec->format == pFormatEtc->cfFormat)
{
target = frec->target;
win32_sel->property_change_transmute = frec->transmute;
win32_sel->property_change_target_atom = frec->target;
}
}
if (target == NULL)
{
GDK_NOTE (EVENTS, g_print ("(target not found)"));
return E_UNEXPECTED;
}
GDK_NOTE (DND, {
gchar *target_name = gdk_atom_name (target);
g_print ("idataobject_getdata will request target 0x%p (%s) ",
target, target_name);
g_free (target_name);
});
memset (&e, 0, sizeof (GdkEvent));
e.type = GDK_SELECTION_REQUEST;
g_set_object (&e.selection.window, ctx->context->source_window);
e.selection.send_event = FALSE; /* ??? */
/* Both selection and property are OLE2_DND, because change_property()
* will only get the property and not the selection. Theoretically we
* could use two different atoms (SELECTION_OLE2_DND and PROPERTY_OLE2_DND),
* but there's little reason to do so.
*/
e.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
e.selection.target = target;
/* Requestor here is fake, just to allow the event to be processed */
g_set_object (&e.selection.requestor, ctx->context->source_window);
e.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
e.selection.time = GDK_CURRENT_TIME;
GDK_NOTE (EVENTS, _gdk_win32_print_event (&e));
gdk_event_put (&e);
/* Don't hold up longer than one second */
loopend = g_get_monotonic_time () + 1000000000;
while (win32_sel->property_change_data != 0 &&
g_get_monotonic_time () < loopend)
process_pending_events (gdk_device_get_display (gdk_drag_context_get_device (ctx->context)));
if (pMedium->hGlobal == NULL) {
GDK_NOTE (DND, g_print (" E_UNEXPECTED\n"));
return E_UNEXPECTED;
}
GDK_NOTE (DND, g_print (" S_OK\n"));
return S_OK;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getdatahere (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium)
{
GDK_NOTE (DND, g_print ("idataobject_getdatahere %p %s E_UNEXPECTED\n",
This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_querygetdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc)
{
HRESULT hr;
hr = query (This, pFormatEtc);
#define CASE(x) case x: g_print (#x "\n"); break
GDK_NOTE (DND, {
g_print ("idataobject_querygetdata %p %s ",
This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat));
switch (hr)
{
CASE (DV_E_FORMATETC);
CASE (DV_E_LINDEX);
CASE (DV_E_TYMED);
CASE (DV_E_DVASPECT);
CASE (S_OK);
default: g_print ("%#lx", hr);
}
});
return hr;
}
static HRESULT STDMETHODCALLTYPE
idataobject_getcanonicalformatetc (LPDATAOBJECT This,
LPFORMATETC pFormatEtcIn,
LPFORMATETC pFormatEtcOut)
{
GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p E_UNEXPECTED\n", This));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_setdata (LPDATAOBJECT This,
LPFORMATETC pFormatEtc,
LPSTGMEDIUM pMedium,
BOOL fRelease)
{
GDK_NOTE (DND, g_print ("idataobject_setdata %p %s E_UNEXPECTED\n",
This, _gdk_win32_cf_to_string (pFormatEtc->cfFormat)));
return E_UNEXPECTED;
}
static HRESULT STDMETHODCALLTYPE
idataobject_enumformatetc (LPDATAOBJECT This,
DWORD dwDirection,
LPENUMFORMATETC *ppEnumFormatEtc)
{
GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p ", This));
if (dwDirection != DATADIR_GET)
{
GDK_NOTE (DND, g_print ("E_NOTIMPL\n"));
return E_NOTIMPL;
}
*ppEnumFormatEtc = &enum_formats_new ((data_object *) This)->ief;
GDK_NOTE (DND, g_print (" %p S_OK\n", *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)
{
idataobject_release ((LPDATAOBJECT) en->dataobj);
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->dataobj->formats->len)
break;
fmt = g_array_index (en->dataobj->formats, GdkSelTargetFormat, en->ix++).format;
/* 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->dataobj);
new->ix = en->ix;
*ppEnumFormatEtc = &new->ief;
return S_OK;
}
static IDropTargetVtbl idt_vtbl = {
idroptarget_queryinterface,
idroptarget_addref,
idroptarget_release,
idroptarget_dragenter,
idroptarget_dragover,
idroptarget_dragleave,
idroptarget_drop
};
static IDropSourceVtbl ids_vtbl = {
idropsource_queryinterface,
idropsource_addref,
idropsource_release,
idropsource_querycontinuedrag,
idropsource_givefeedback
};
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 target_drag_context *
target_context_new (GdkWindow *window)
{
target_drag_context *result;
result = g_new0 (target_drag_context, 1);
result->idt.lpVtbl = &idt_vtbl;
result->ref_count = 0;
result->dest_window = g_object_ref (window);
idroptarget_addref (&result->idt);
GDK_NOTE (DND, g_print ("target_context_new: %p (window %p)\n", result, result->dest_window));
return result;
}
static source_drag_context *
source_context_new (GdkWindow *window,
GdkContentFormats *formats)
{
GdkDragContext *context;
GdkWin32DragContext *context_win32;
source_drag_context *result;
context = gdk_drag_context_new (gdk_window_get_display (window));
context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
result = g_new0 (source_drag_context, 1);
result->context = context;
result->ids.lpVtbl = &ids_vtbl;
result->ref_count = 0;
result->context->protocol = GDK_DRAG_PROTO_OLE2;
result->context->is_source = TRUE;
result->context->source_window = g_object_ref (window);
result->context->dest_window = NULL;
result->context->formats = gdk_content_formats_ref (formats);
idropsource_addref (&result->ids);
GDK_NOTE (DND, g_print ("source_context_new: %p (drag context %p)\n", result, result->context));
if (current_src_context == NULL)
current_src_context = result;
return result;
}
static data_object *
data_object_new (GdkDragContext *context)
{
data_object *result;
GList *p;
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->context = context;
result->formats = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
mime_types = gdk_content_formats_get_mime_types (context->formats, &n_mime_types);
for (i = 0; i < n_mime_types; i++)
{
gint added_count = 0;
gint j;
GDK_NOTE (DND, g_print ("DataObject supports target 0x%p\n", mime_types[i]));
added_count = _gdk_win32_add_target_to_selformats (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 format 0x%x\n", g_array_index (result->formats, GdkSelTargetFormat, j).format));
}
GDK_NOTE (DND, g_print ("data_object_new: %p\n", result));
return result;
}
static enum_formats *
enum_formats_new (data_object *dataobj)
{
enum_formats *result;
result = g_new0 (enum_formats, 1);
result->ief.lpVtbl = &ief_vtbl;
result->ref_count = 1;
result->ix = 0;
result->dataobj = dataobj;
idataobject_addref ((LPDATAOBJECT) dataobj);
return result;
}
/* From MS Knowledge Base article Q130698 */
static gboolean
resolve_link (HWND hWnd,
wchar_t *link,
gchar **lpszPath)
{
WIN32_FILE_ATTRIBUTE_DATA wfad;
HRESULT hr;
IShellLinkW *pslW = NULL;
IPersistFile *ppf = NULL;
/* Check if the file is empty first because IShellLink::Resolve for
* some reason succeeds with an empty file and returns an empty
* "link target". (#524151)
*/
if (!GetFileAttributesExW (link, GetFileExInfoStandard, &wfad) ||
(wfad.nFileSizeHigh == 0 && wfad.nFileSizeLow == 0))
return FALSE;
/* Assume failure to start with: */
*lpszPath = 0;
/* Call CoCreateInstance to obtain the IShellLink interface
* pointer. This call fails if CoInitialize is not called, so it is
* assumed that CoInitialize has been called.
*/
hr = CoCreateInstance (&CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IShellLinkW,
(LPVOID *)&pslW);
if (SUCCEEDED (hr))
{
/* The IShellLink interface supports the IPersistFile
* interface. Get an interface pointer to it.
*/
hr = pslW->lpVtbl->QueryInterface (pslW,
&IID_IPersistFile,
(LPVOID *) &ppf);
}
if (SUCCEEDED (hr))
{
/* Load the file. */
hr = ppf->lpVtbl->Load (ppf, link, STGM_READ);
}
if (SUCCEEDED (hr))
{
/* Resolve the link by calling the Resolve()
* interface function.
*/
hr = pslW->lpVtbl->Resolve (pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI);
}
if (SUCCEEDED (hr))
{
wchar_t wtarget[MAX_PATH];
hr = pslW->lpVtbl->GetPath (pslW, wtarget, MAX_PATH, NULL, 0);
if (SUCCEEDED (hr))
*lpszPath = g_utf16_to_utf8 (wtarget, -1, NULL, NULL, NULL);
}
if (ppf)
ppf->lpVtbl->Release (ppf);
if (pslW)
pslW->lpVtbl->Release (pslW);
return SUCCEEDED (hr);
}
#if 0
/* Check for filenames like C:\Users\tml\AppData\Local\Temp\d5qtkvvs.bmp */
static gboolean
filename_looks_tempish (const char *filename)
{
char *dirname;
char *p;
const char *q;
gboolean retval = FALSE;
dirname = g_path_get_dirname (filename);
p = dirname;
q = g_get_tmp_dir ();
while (*p && *q &&
((G_IS_DIR_SEPARATOR (*p) && G_IS_DIR_SEPARATOR (*q)) ||
g_ascii_tolower (*p) == g_ascii_tolower (*q)))
p++, q++;
if (!*p && !*q)
retval = TRUE;
g_free (dirname);
return retval;
}
static gboolean
close_it (gpointer data)
{
close (GPOINTER_TO_INT (data));
return FALSE;
}
#endif
static GdkFilterReturn
gdk_dropfiles_filter (GdkXEvent *xev,
GdkEvent *event,
gpointer data)
{
GdkDragContext *context;
GdkWin32DragContext *context_win32;
GString *result;
MSG *msg = (MSG *) xev;
HANDLE hdrop;
POINT pt;
gint nfiles, i;
gchar *fileName, *linkedFile;
GPtrArray *formats;
formats = g_ptr_array_new ();
if (msg->message == WM_DROPFILES)
{
GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd));
context = gdk_drag_context_new (gdk_window_get_display (event->any.window));
context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
context->protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
context->is_source = FALSE;
context->source_window = NULL;
g_set_object (&context->dest_window, event->any.window);
/* WM_DROPFILES drops are always file names */
context->formats = gdk_content_formats_new ((const char *[2]) {
"text/uri-list",
NULL
}, 1);
context->actions = GDK_ACTION_COPY;
context->suggested_action = GDK_ACTION_COPY;
current_dest_drag = context;
event->dnd.type = GDK_DROP_START;
event->dnd.context = current_dest_drag;
gdk_event_set_device (event, gdk_drag_context_get_device (current_dest_drag));
gdk_event_set_seat (event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag)));
hdrop = (HANDLE) msg->wParam;
DragQueryPoint (hdrop, &pt);
ClientToScreen (msg->hwnd, &pt);
event->dnd.x_root = pt.x / context_win32->scale + _gdk_offset_x;
event->dnd.y_root = pt.y / context_win32->scale + _gdk_offset_y;
event->dnd.time = _gdk_win32_get_next_tick (msg->time);
nfiles = DragQueryFile (hdrop, 0xFFFFFFFF, NULL, 0);
result = g_string_new (NULL);
for (i = 0; i < nfiles; i++)
{
gchar *uri;
wchar_t wfn[MAX_PATH];
DragQueryFileW (hdrop, i, wfn, MAX_PATH);
fileName = g_utf16_to_utf8 (wfn, -1, NULL, NULL, NULL);
/* Resolve shortcuts */
if (resolve_link (msg->hwnd, wfn, &linkedFile))
{
uri = g_filename_to_uri (linkedFile, NULL, NULL);
if (uri != NULL)
{
g_string_append (result, uri);
GDK_NOTE (DND, g_print ("... %s link to %s: %s\n",
fileName, linkedFile, uri));
g_free (uri);
}
g_free (fileName);
fileName = linkedFile;
}
else
{
uri = g_filename_to_uri (fileName, NULL, NULL);
if (uri != NULL)
{
g_string_append (result, uri);
GDK_NOTE (DND, g_print ("... %s: %s\n", fileName, uri));
g_free (uri);
}
}
#if 0
/* Awful hack to recognize temp files corresponding to
* images dragged from Firefox... Open the file right here
* so that it is less likely that Firefox manages to delete
* it before the GTK+-using app (typically GIMP) has opened
* it.
*
* Not compiled in for now, because it means images dragged
* from Firefox would stay around in the temp folder which
* is not what Firefox intended. I don't feel comfortable
* with that, both from a geenral sanity point of view, and
* from a privacy point of view. It's better to wait for
* Firefox to fix the problem, for instance by deleting the
* temp file after a longer delay, or to wait until we
* implement the OLE2_DND...
*/
if (filename_looks_tempish (fileName))
{
int fd = g_open (fileName, _O_RDONLY|_O_BINARY, 0);
if (fd == -1)
{
GDK_NOTE (DND, g_print ("Could not open %s, maybe an image dragged from Firefox that it already deleted\n", fileName));
}
else
{
GDK_NOTE (DND, g_print ("Opened %s as %d so that Firefox won't delete it\n", fileName, fd));
g_timeout_add_seconds (1, close_it, GINT_TO_POINTER (fd));
}
}
#endif
g_free (fileName);
g_string_append (result, "\015\012");
}
_gdk_dropfiles_store (result->str);
g_string_free (result, FALSE);
DragFinish (hdrop);
return GDK_FILTER_TRANSLATE;
}
else
return GDK_FILTER_CONTINUE;
}
void
_gdk_dnd_init (void)
{
CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
if (getenv ("GDK_WIN32_USE_EXPERIMENTAL_OLE2_DND"))
use_ole2_dnd = TRUE;
if (use_ole2_dnd)
{
HRESULT hr;
hr = OleInitialize (NULL);
if (! SUCCEEDED (hr))
g_error ("OleInitialize failed");
target_ctx_for_window = g_hash_table_new (g_direct_hash, g_direct_equal);
}
}
void
_gdk_win32_dnd_exit (void)
{
if (use_ole2_dnd)
{
OleUninitialize ();
}
CoUninitialize ();
}
/* Source side */
static void
local_send_leave (GdkDragContext *context,
guint32 time)
{
GdkEvent *tmp_event;
GDK_NOTE (DND, g_print ("local_send_leave: context=%p current_dest_drag=%p\n",
context,
current_dest_drag));
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
tmp_event = gdk_event_new (GDK_DRAG_LEAVE);
g_set_object (&tmp_event->dnd.window, context->dest_window);
/* Pass ownership of context to the event */
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, current_dest_drag);
tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
current_dest_drag = NULL;
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
}
static void
local_send_enter (GdkDragContext *context,
guint32 time)
{
GdkEvent *tmp_event;
GdkDragContext *new_context;
GDK_NOTE (DND, g_print ("local_send_enter: context=%p current_dest_drag=%p\n",
context,
current_dest_drag));
if (current_dest_drag != NULL)
{
g_object_unref (G_OBJECT (current_dest_drag));
current_dest_drag = NULL;
}
new_context = gdk_drag_context_new (gdk_window_get_display (context->source_window));
new_context->protocol = GDK_DRAG_PROTO_LOCAL;
new_context->is_source = FALSE;
g_set_object (&new_context->source_window, context->source_window);
g_set_object (&new_context->dest_window, context->dest_window);
new_context->formats = gdk_content_formats_ref (context->formats);
gdk_window_set_events (new_context->source_window,
gdk_window_get_events (new_context->source_window) |
GDK_PROPERTY_CHANGE_MASK);
new_context->actions = context->actions;
tmp_event = gdk_event_new (GDK_DRAG_ENTER);
g_set_object (&tmp_event->dnd.window, context->dest_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, new_context);
tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
current_dest_drag = new_context;
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
static void
local_send_motion (GdkDragContext *context,
gint x_root,
gint y_root,
GdkDragAction action,
guint32 time)
{
GdkEvent *tmp_event;
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
GDK_NOTE (DND, g_print ("local_send_motion: context=%p (%d,%d) current_dest_drag=%p\n",
context, x_root, y_root,
current_dest_drag));
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
GdkWin32DragContext *current_dest_drag_win32;
tmp_event = gdk_event_new (GDK_DRAG_MOTION);
g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, current_dest_drag);
tmp_event->dnd.time = time;
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag)));
current_dest_drag->suggested_action = action;
tmp_event->dnd.x_root = x_root;
tmp_event->dnd.y_root = y_root;
current_dest_drag_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
current_dest_drag_win32->last_x = x_root;
current_dest_drag_win32->last_y = y_root;
context_win32->drag_status = GDK_DRAG_STATUS_MOTION_WAIT;
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
}
static void
local_send_drop (GdkDragContext *context,
guint32 time)
{
GdkEvent *tmp_event;
GDK_NOTE (DND, g_print ("local_send_drop: context=%p current_dest_drag=%p\n",
context,
current_dest_drag));
if ((current_dest_drag != NULL) &&
(current_dest_drag->protocol == GDK_DRAG_PROTO_LOCAL) &&
(current_dest_drag->source_window == context->source_window))
{
GdkWin32DragContext *context_win32;
/* Pass ownership of context to the event */
tmp_event = gdk_event_new (GDK_DROP_START);
g_set_object (&tmp_event->dnd.window, current_dest_drag->dest_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, current_dest_drag);
tmp_event->dnd.time = GDK_CURRENT_TIME;
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (current_dest_drag));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (current_dest_drag)));
context_win32 = GDK_WIN32_DRAG_CONTEXT (current_dest_drag);
tmp_event->dnd.x_root = context_win32->last_x;
tmp_event->dnd.y_root = context_win32->last_y;
current_dest_drag = NULL;
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
}
static void
gdk_drag_do_leave (GdkDragContext *context,
guint32 time)
{
if (context->dest_window)
{
GDK_NOTE (DND, g_print ("gdk_drag_do_leave\n"));
if (!use_ole2_dnd)
{
if (context->protocol == GDK_DRAG_PROTO_LOCAL)
local_send_leave (context, time);
}
g_clear_object (&context->dest_window);
}
}
static GdkWindow *
create_drag_window (GdkDisplay *display)
{
GdkWindow *window;
window = gdk_window_new_popup (display, &(GdkRectangle) { 0, 0, 100, 100 });
gdk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DND);
return window;
}
GdkDragContext *
_gdk_win32_window_drag_begin (GdkWindow *window,
GdkDevice *device,
GdkContentFormats *formats,
gint x_root,
gint y_root)
{
GdkDragContext *new_context;
GdkWin32DragContext *context_win32;
BYTE kbd_state[256];
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
if (!use_ole2_dnd)
{
g_return_val_if_fail (window != NULL, NULL);
new_context = gdk_drag_context_new (gdk_window_get_display (window));
new_context->is_source = TRUE;
g_set_object (&new_context->source_window, window);
new_context->formats = gdk_content_formats_ref (formats);
new_context->actions = 0;
context_win32 = GDK_WIN32_DRAG_CONTEXT (new_context);
}
else
{
source_drag_context *ctx;
g_return_val_if_fail (window != NULL, NULL);
GDK_NOTE (DND, g_print ("gdk_drag_begin\n"));
ctx = source_context_new (window, formats);
sel_win32->dnd_source_state = GDK_WIN32_DND_PENDING;
pending_src_context = ctx;
new_context = g_object_ref (ctx->context);
context_win32 = GDK_WIN32_DRAG_CONTEXT (new_context);
}
context_win32->start_x = x_root;
context_win32->start_y = y_root;
context_win32->last_x = context_win32->start_x;
context_win32->last_y = context_win32->start_y;
context_win32->last_key_state = 0;
API_CALL (GetKeyboardState, (kbd_state));
if (kbd_state[VK_CONTROL] & 0x80)
context_win32->last_key_state |= MK_CONTROL;
if (kbd_state[VK_SHIFT] & 0x80)
context_win32->last_key_state |= MK_SHIFT;
if (kbd_state[VK_LBUTTON] & 0x80)
context_win32->last_key_state |= MK_LBUTTON;
if (kbd_state[VK_MBUTTON] & 0x80)
context_win32->last_key_state |= MK_MBUTTON;
if (kbd_state[VK_RBUTTON] & 0x80)
context_win32->last_key_state |= MK_RBUTTON;
context_win32->drag_window = create_drag_window (gdk_window_get_display (window));
return new_context;
}
void
_gdk_win32_dnd_do_dragdrop (void)
{
GdkDragContext* drag_ctx;
data_object *dobj;
HRESULT hr;
DWORD dwEffect;
if (!use_ole2_dnd)
return;
if (pending_src_context == NULL)
return;
drag_ctx = pending_src_context->context;
dobj = data_object_new (drag_ctx);
current_src_object = dobj;
/* Start dragging with mainloop inside the OLE2 API. Exits only when done */
GDK_NOTE (DND, g_print ("Calling DoDragDrop\n"));
_gdk_win32_begin_modal_call (GDK_WIN32_MODAL_OP_DND);
hr = DoDragDrop (&dobj->ido, &pending_src_context->ids,
DROPEFFECT_COPY | DROPEFFECT_MOVE,
&dwEffect);
_gdk_win32_end_modal_call (GDK_WIN32_MODAL_OP_DND);
GDK_NOTE (DND, g_print ("DoDragDrop returned %s\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))))));
/* Delete dnd selection after successful move */
if (hr == DRAGDROP_S_DROP && dwEffect == DROPEFFECT_MOVE)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
GdkEvent tmp_event;
memset (&tmp_event, 0, sizeof (tmp_event));
tmp_event.type = GDK_SELECTION_REQUEST;
g_set_object (&tmp_event.selection.window, drag_ctx->source_window);
tmp_event.selection.send_event = FALSE;
tmp_event.selection.selection = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
tmp_event.selection.target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DELETE);
win32_sel->property_change_target_atom = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DELETE);
tmp_event.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
g_set_object (&tmp_event.selection.requestor, drag_ctx->source_window);
tmp_event.selection.time = GDK_CURRENT_TIME; /* ??? */
GDK_NOTE (EVENTS, _gdk_win32_print_event (&tmp_event));
gdk_event_put (&tmp_event);
}
{
GdkEvent *tmp_event;
tmp_event = gdk_event_new (GDK_DROP_FINISHED);
g_set_object (&tmp_event->dnd.window, drag_ctx->source_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, drag_ctx);
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (drag_ctx));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (drag_ctx)));
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
current_src_object = NULL;
dobj->ido.lpVtbl->Release (&dobj->ido);
if (pending_src_context != NULL)
{
pending_src_context->ids.lpVtbl->Release (&pending_src_context->ids);
pending_src_context = NULL;
}
}
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;
}
static GdkWindow *
gdk_win32_drag_context_find_window (GdkDragContext *context,
GdkWindow *drag_window,
gint x_root,
gint y_root,
GdkDragProtocol *protocol)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
GdkWindow *dest_window, *dw;
find_window_enum_arg a;
a.x = x_root * context_win32->scale - _gdk_offset_x;
a.y = y_root * context_win32->scale - _gdk_offset_y;
a.ignore = drag_window ? GDK_WINDOW_HWND (drag_window) : NULL;
a.result = NULL;
GDK_NOTE (DND,
g_print ("gdk_drag_find_window_real: %p %+d%+d\n",
(drag_window ? GDK_WINDOW_HWND (drag_window) : NULL),
a.x, a.y));
EnumWindows (find_window_enum_proc, (LPARAM) &a);
if (a.result == NULL)
dest_window = NULL;
else
{
dw = gdk_win32_handle_table_lookup (a.result);
if (dw)
{
dest_window = gdk_window_get_toplevel (dw);
g_object_ref (dest_window);
}
else
dest_window = gdk_win32_window_foreign_new_for_display (context->display, a.result);
if (use_ole2_dnd)
*protocol = GDK_DRAG_PROTO_OLE2;
else if (context->source_window)
*protocol = GDK_DRAG_PROTO_LOCAL;
else
*protocol = GDK_DRAG_PROTO_WIN32_DROPFILES;
}
GDK_NOTE (DND,
g_print ("gdk_drag_find_window: %p %+d%+d: %p: %p %s\n",
(drag_window ? GDK_WINDOW_HWND (drag_window) : NULL),
x_root, y_root,
a.result,
(dest_window ? GDK_WINDOW_HWND (dest_window) : NULL),
_gdk_win32_drag_protocol_to_string (*protocol)));
return dest_window;
}
static gboolean
gdk_win32_drag_context_drag_motion (GdkDragContext *context,
GdkWindow *dest_window,
GdkDragProtocol protocol,
gint x_root,
gint y_root,
GdkDragAction suggested_action,
GdkDragAction possible_actions,
guint32 time)
{
GdkWin32DragContext *context_win32;
g_return_val_if_fail (context != NULL, FALSE);
context->actions = possible_actions;
GDK_NOTE (DND, g_print ("gdk_drag_motion: @ %+d:%+d %s suggested=%s, possible=%s\n"
" context=%p:{actions=%s,suggested=%s,action=%s}\n",
x_root, y_root,
_gdk_win32_drag_protocol_to_string (protocol),
_gdk_win32_drag_action_to_string (suggested_action),
_gdk_win32_drag_action_to_string (possible_actions),
context,
_gdk_win32_drag_action_to_string (context->actions),
_gdk_win32_drag_action_to_string (context->suggested_action),
_gdk_win32_drag_action_to_string (context->action)));
context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
if (context_win32->drag_window)
move_drag_window (context, x_root, y_root);
if (!use_ole2_dnd)
{
if (context->dest_window == dest_window)
{
GdkDragContext *dest_context;
dest_context = gdk_drag_context_find (FALSE,
context->source_window,
dest_window);
if (dest_context)
dest_context->actions = context->actions;
context->suggested_action = suggested_action;
}
else
{
GdkEvent *tmp_event;
/* Send a leave to the last destination */
gdk_drag_do_leave (context, time);
context_win32->drag_status = GDK_DRAG_STATUS_DRAG;
/* Check if new destination accepts drags, and which protocol */
if (dest_window)
{
g_set_object (&context->dest_window, dest_window);
context->protocol = protocol;
switch (protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_enter (context, time);
break;
default:
break;
}
context->suggested_action = suggested_action;
}
else
{
context->dest_window = NULL;
context->action = 0;
}
/* Push a status event, to let the client know that
* the drag changed
*/
tmp_event = gdk_event_new (GDK_DRAG_STATUS);
g_set_object (&tmp_event->dnd.window, context->source_window);
/* We use this to signal a synthetic status. Perhaps
* we should use an extra field...
*/
tmp_event->dnd.send_event = TRUE;
g_set_object (&tmp_event->dnd.context, context);
tmp_event->dnd.time = time;
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (context));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (context)));
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
/* Send a drag-motion event */
context_win32->last_x = x_root;
context_win32->last_y = y_root;
if (context->dest_window)
{
if (context_win32->drag_status == GDK_DRAG_STATUS_DRAG)
{
switch (context->protocol)
{
case GDK_DRAG_PROTO_LOCAL:
local_send_motion (context, x_root, y_root, suggested_action, time);
break;
case GDK_DRAG_PROTO_NONE:
g_warning ("GDK_DRAG_PROTO_NONE is not valid in gdk_drag_motion()");
break;
default:
break;
}
}
else
{
GDK_NOTE (DND, g_print (" returning TRUE\n"
" context=%p:{actions=%s,suggested=%s,action=%s}\n",
context,
_gdk_win32_drag_action_to_string (context->actions),
_gdk_win32_drag_action_to_string (context->suggested_action),
_gdk_win32_drag_action_to_string (context->action)));
return TRUE;
}
}
}
GDK_NOTE (DND, g_print (" returning FALSE\n"
" context=%p:{actions=%s,suggested=%s,action=%s}\n",
context,
_gdk_win32_drag_action_to_string (context->actions),
_gdk_win32_drag_action_to_string (context->suggested_action),
_gdk_win32_drag_action_to_string (context->action)));
return FALSE;
}
static void
gdk_win32_drag_context_drag_drop (GdkDragContext *context,
guint32 time)
{
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drag_drop\n"));
if (!use_ole2_dnd)
{
if (context->dest_window &&
context->protocol == GDK_DRAG_PROTO_LOCAL)
local_send_drop (context, time);
}
else
{
sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED;
}
}
static void
gdk_win32_drag_context_drag_abort (GdkDragContext *context,
guint32 time)
{
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drag_abort\n"));
if (use_ole2_dnd)
sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
}
/* Destination side */
static void
gdk_win32_drag_context_drag_status (GdkDragContext *context,
GdkDragAction action,
guint32 time)
{
GdkDragContext *src_context;
GdkEvent *tmp_event;
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drag_status: %s\n"
" context=%p:{actions=%s,suggested=%s,action=%s}\n",
_gdk_win32_drag_action_to_string (action),
context,
_gdk_win32_drag_action_to_string (context->actions),
_gdk_win32_drag_action_to_string (context->suggested_action),
_gdk_win32_drag_action_to_string (context->action)));
context->action = action;
if (!use_ole2_dnd)
{
src_context = gdk_drag_context_find (TRUE,
context->source_window,
context->dest_window);
if (src_context)
{
GdkWin32DragContext *src_context_win32 = GDK_WIN32_DRAG_CONTEXT (src_context);
if (src_context_win32->drag_status == GDK_DRAG_STATUS_MOTION_WAIT)
src_context_win32->drag_status = GDK_DRAG_STATUS_DRAG;
tmp_event = gdk_event_new (GDK_DRAG_STATUS);
g_set_object (&tmp_event->dnd.window, context->source_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, src_context);
tmp_event->dnd.time = GDK_CURRENT_TIME; /* FIXME? */
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
if (action == GDK_ACTION_DEFAULT)
action = 0;
src_context->action = action;
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
}
}
static void
gdk_win32_drag_context_drop_reply (GdkDragContext *context,
gboolean ok,
guint32 time)
{
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drop_reply\n"));
if (!use_ole2_dnd)
if (context->dest_window)
{
if (context->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES)
_gdk_dropfiles_store (NULL);
}
}
static void
gdk_win32_drag_context_drop_finish (GdkDragContext *context,
gboolean success,
guint32 time)
{
GdkDragContext *src_context;
GdkEvent *tmp_event;
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
g_return_if_fail (context != NULL);
GDK_NOTE (DND, g_print ("gdk_drop_finish\n"));
if (!use_ole2_dnd)
{
src_context = gdk_drag_context_find (TRUE,
context->source_window,
context->dest_window);
if (src_context)
{
tmp_event = gdk_event_new (GDK_DROP_FINISHED);
g_set_object (&tmp_event->dnd.window, src_context->source_window);
tmp_event->dnd.send_event = FALSE;
g_set_object (&tmp_event->dnd.context, src_context);
gdk_event_set_device (tmp_event, gdk_drag_context_get_device (src_context));
gdk_event_set_seat (tmp_event, gdk_device_get_seat (gdk_drag_context_get_device (src_context)));
GDK_NOTE (EVENTS, _gdk_win32_print_event (tmp_event));
gdk_event_put (tmp_event);
gdk_event_free (tmp_event);
}
}
else
{
gdk_drag_do_leave (context, time);
if (success)
sel_win32->dnd_target_state = GDK_WIN32_DND_DROPPED;
else
sel_win32->dnd_target_state = GDK_WIN32_DND_FAILED;
}
}
#if 0
static GdkFilterReturn
gdk_destroy_filter (GdkXEvent *xev,
GdkEvent *event,
gpointer data)
{
MSG *msg = (MSG *) xev;
if (msg->message == WM_DESTROY)
{
IDropTarget *idtp = (IDropTarget *) data;
GDK_NOTE (DND, g_print ("gdk_destroy_filter: WM_DESTROY: %p\n", msg->hwnd));
#if 0
idtp->lpVtbl->Release (idtp);
#endif
RevokeDragDrop (msg->hwnd);
CoLockObjectExternal ((IUnknown*) idtp, FALSE, TRUE);
}
return GDK_FILTER_CONTINUE;
}
#endif
void
_gdk_win32_window_register_dnd (GdkWindow *window)
{
target_drag_context *ctx;
HRESULT hr;
g_return_if_fail (window != NULL);
if (g_object_get_data (G_OBJECT (window), "gdk-dnd-registered") != NULL)
return;
else
g_object_set_data (G_OBJECT (window), "gdk-dnd-registered", GINT_TO_POINTER (TRUE));
GDK_NOTE (DND, g_print ("gdk_window_register_dnd: %p\n", GDK_WINDOW_HWND (window)));
if (!use_ole2_dnd)
{
/* We always claim to accept dropped files, but in fact we might not,
* of course. This function is called in such a way that it cannot know
* whether the window (widget) in question actually accepts files
* (in gtk, data of type text/uri-list) or not.
*/
gdk_window_add_filter (window, gdk_dropfiles_filter, NULL);
DragAcceptFiles (GDK_WINDOW_HWND (window), TRUE);
}
else
{
/* Return if window is already setup for DND. */
if (g_hash_table_lookup (target_ctx_for_window, GDK_WINDOW_HWND (window)) != NULL)
return;
ctx = target_context_new (window);
hr = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE);
if (!SUCCEEDED (hr))
OTHER_API_FAILED ("CoLockObjectExternal");
else
{
hr = RegisterDragDrop (GDK_WINDOW_HWND (window), &ctx->idt);
if (hr == DRAGDROP_E_ALREADYREGISTERED)
{
g_print ("DRAGDROP_E_ALREADYREGISTERED\n");
CoLockObjectExternal ((IUnknown *) &ctx->idt, FALSE, FALSE);
}
else if (!SUCCEEDED (hr))
OTHER_API_FAILED ("RegisterDragDrop");
else
{
g_object_ref (window);
g_hash_table_insert (target_ctx_for_window, GDK_WINDOW_HWND (window), ctx);
}
}
}
}
static gboolean
gdk_win32_drag_context_drop_status (GdkDragContext *context)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
return ! context_win32->drop_failed;
}
static GdkAtom
gdk_win32_drag_context_get_selection (GdkDragContext *context)
{
switch (context->protocol)
{
case GDK_DRAG_PROTO_LOCAL:
return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION);
case GDK_DRAG_PROTO_WIN32_DROPFILES:
return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DROPFILES_DND);
case GDK_DRAG_PROTO_OLE2:
return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
default:
return NULL;
}
}
static void
gdk_win32_drag_context_set_cursor (GdkDragContext *context,
GdkCursor *cursor)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
GDK_NOTE (DND, g_print ("gdk_drag_context_set_cursor: 0x%p 0x%p\n", context, cursor));
if (!g_set_object (&context_win32->cursor, cursor))
return;
if (context_win32->grab_seat)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
gdk_device_grab (gdk_seat_get_pointer (context_win32->grab_seat),
context_win32->ipc_window,
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 {
GdkWin32DragContext *context;
GdkFrameClock *frame_clock;
gint64 start_time;
};
static void
gdk_drag_anim_destroy (GdkDragAnim *anim)
{
g_object_unref (anim->context);
g_slice_free (GdkDragAnim, anim);
}
static gboolean
gdk_drag_anim_timeout (gpointer data)
{
GdkDragAnim *anim = data;
GdkWin32DragContext *context = anim->context;
GdkFrameClock *frame_clock = anim->frame_clock;
gint64 current_time;
double f;
double t;
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_window_show (context->drag_window);
gdk_window_move (context->drag_window,
context->last_x + (context->start_x - context->last_x) * t - context->hot_x,
context->last_y + (context->start_y - context->last_y) * t - context->hot_y);
gdk_window_set_opacity (context->drag_window, 1.0 - f);
return G_SOURCE_CONTINUE;
}
static void
gdk_win32_drag_context_drop_done (GdkDragContext *context,
gboolean success)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GdkDragAnim *anim;
cairo_surface_t *win_surface;
cairo_surface_t *surface;
cairo_t *cr;
guint id;
GDK_NOTE (DND, g_print ("gdk_drag_context_drop_done: 0x%p %s\n",
context,
success ? "dropped successfully" : "dropped unsuccessfully"));
/* FIXME: This is temporary, until the code is fixed to ensure that
* gdk_drop_finish () is called by GTK.
*/
if (use_ole2_dnd)
{
GdkWin32Selection *sel_win32 = _gdk_win32_selection_get ();
if (success)
sel_win32->dnd_source_state = GDK_WIN32_DND_DROPPED;
else
sel_win32->dnd_source_state = GDK_WIN32_DND_NONE;
}
if (success)
{
gdk_window_hide (win32_context->drag_window);
return;
}
win_surface = _gdk_window_ref_cairo_surface (win32_context->drag_window);
surface = gdk_window_create_similar_surface (win32_context->drag_window,
cairo_surface_get_content (win_surface),
gdk_window_get_width (win32_context->drag_window),
gdk_window_get_height (win32_context->drag_window));
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_window_set_background_pattern (win32_context->drag_window, pattern);
cairo_pattern_destroy (pattern);
*/
cairo_surface_destroy (surface);
anim = g_slice_new0 (GdkDragAnim);
g_set_object (&anim->context, win32_context);
anim->frame_clock = gdk_window_get_frame_clock (win32_context->drag_window);
anim->start_time = gdk_frame_clock_get_frame_time (anim->frame_clock);
id = gdk_threads_add_timeout_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 (GdkDragContext *context)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
GdkSeatCapabilities capabilities;
GdkSeat *seat;
GdkCursor *cursor;
if (!context_win32->ipc_window)
return FALSE;
seat = gdk_device_get_seat (gdk_drag_context_get_device (context));
capabilities = GDK_SEAT_CAPABILITY_ALL;
cursor = gdk_drag_get_cursor (context, gdk_drag_context_get_selected_action (context));
g_set_object (&context_win32->cursor, cursor);
if (gdk_seat_grab (seat, context_win32->ipc_window,
capabilities, FALSE,
context_win32->cursor, NULL, NULL, NULL) != GDK_GRAB_SUCCESS)
return FALSE;
g_set_object (&context_win32->grab_seat, seat);
/* TODO: Should be grabbing keys here, to support keynav. SetWindowsHookEx()? */
return TRUE;
}
static void
drag_context_ungrab (GdkDragContext *context)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
if (!context_win32->grab_seat)
return;
gdk_seat_ungrab (context_win32->grab_seat);
g_clear_object (&context_win32->grab_seat);
/* TODO: Should be ungrabbing keys here */
}
static gboolean
gdk_win32_drag_context_manage_dnd (GdkDragContext *context,
GdkWindow *ipc_window,
GdkDragAction actions)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
if (context_win32->ipc_window)
return FALSE;
if (use_ole2_dnd)
context->protocol = GDK_DRAG_PROTO_OLE2;
else
context->protocol = GDK_DRAG_PROTO_LOCAL;
g_set_object (&context_win32->ipc_window, ipc_window);
if (drag_context_grab (context))
{
context_win32->actions = actions;
move_drag_window (context, context_win32->start_x, context_win32->start_y);
return TRUE;
}
else
{
g_clear_object (&context_win32->ipc_window);
return FALSE;
}
}
static void
gdk_win32_drag_context_cancel (GdkDragContext *context,
GdkDragCancelReason reason)
{
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_drag_context_cancel: 0x%p %s\n",
context,
reason_str));
drag_context_ungrab (context);
gdk_drag_drop_done (context, FALSE);
}
static void
gdk_win32_drag_context_drop_performed (GdkDragContext *context,
guint32 time_)
{
GDK_NOTE (DND, g_print ("gdk_drag_context_drop_performed: 0x%p %u\n",
context,
time_));
gdk_drag_drop (context, time_);
drag_context_ungrab (context);
}
#define BIG_STEP 20
#define SMALL_STEP 1
static void
gdk_drag_get_current_actions (GdkModifierType state,
gint button,
GdkDragAction actions,
GdkDragAction *suggested_action,
GdkDragAction *possible_actions)
{
*suggested_action = 0;
*possible_actions = 0;
if ((button == GDK_BUTTON_MIDDLE || button == GDK_BUTTON_SECONDARY) && (actions & GDK_ACTION_ASK))
{
*suggested_action = GDK_ACTION_ASK;
*possible_actions = actions;
}
else if (state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
{
if ((state & GDK_SHIFT_MASK) && (state & GDK_CONTROL_MASK))
{
if (actions & GDK_ACTION_LINK)
{
*suggested_action = GDK_ACTION_LINK;
*possible_actions = GDK_ACTION_LINK;
}
}
else if (state & GDK_CONTROL_MASK)
{
if (actions & GDK_ACTION_COPY)
{
*suggested_action = GDK_ACTION_COPY;
*possible_actions = GDK_ACTION_COPY;
}
}
else
{
if (actions & GDK_ACTION_MOVE)
{
*suggested_action = GDK_ACTION_MOVE;
*possible_actions = GDK_ACTION_MOVE;
}
}
}
else
{
*possible_actions = actions;
if ((state & (GDK_MOD1_MASK)) && (actions & GDK_ACTION_ASK))
*suggested_action = GDK_ACTION_ASK;
else if (actions & GDK_ACTION_COPY)
*suggested_action = GDK_ACTION_COPY;
else if (actions & GDK_ACTION_MOVE)
*suggested_action = GDK_ACTION_MOVE;
else if (actions & GDK_ACTION_LINK)
*suggested_action = GDK_ACTION_LINK;
}
}
static void
gdk_drag_update (GdkDragContext *context,
gdouble x_root,
gdouble y_root,
GdkModifierType mods,
guint32 evtime)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GdkDragAction action, possible_actions;
GdkWindow *dest_window;
GdkDragProtocol protocol;
gdk_drag_get_current_actions (mods, GDK_BUTTON_PRIMARY, win32_context->actions,
&action, &possible_actions);
gdk_drag_find_window (context,
win32_context->drag_window,
x_root, y_root, &dest_window, &protocol);
gdk_drag_motion (context, dest_window, protocol, x_root, y_root,
action, possible_actions, evtime);
}
static gboolean
gdk_dnd_handle_motion_event (GdkDragContext *context,
const GdkEventMotion *event)
{
GdkModifierType state;
if (!gdk_event_get_state ((GdkEvent *) event, &state))
return FALSE;
GDK_NOTE (DND, g_print ("gdk_dnd_handle_motion_event: 0x%p\n",
context));
gdk_drag_update (context, event->x_root, event->y_root, state,
gdk_event_get_time ((GdkEvent *) event));
return TRUE;
}
static gboolean
gdk_dnd_handle_key_event (GdkDragContext *context,
const GdkEventKey *event)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GdkModifierType state;
GdkWindow *root_window;
GdkDevice *pointer;
gint dx, dy;
GDK_NOTE (DND, g_print ("gdk_dnd_handle_key_event: 0x%p\n",
context));
dx = dy = 0;
state = event->state;
pointer = gdk_device_get_associated_device (gdk_event_get_device ((GdkEvent *) event));
if (event->type == GDK_KEY_PRESS)
{
switch (event->keyval)
{
case GDK_KEY_Escape:
gdk_drag_context_cancel (context, 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_context_get_selected_action (context) != 0) &&
(gdk_drag_context_get_dest_window (context) != NULL))
{
g_signal_emit_by_name (context, "drop-performed",
gdk_event_get_time ((GdkEvent *) event));
}
else
gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET);
return TRUE;
case GDK_KEY_Up:
case GDK_KEY_KP_Up:
dy = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
break;
case GDK_KEY_Down:
case GDK_KEY_KP_Down:
dy = (state & GDK_MOD1_MASK) ? BIG_STEP : SMALL_STEP;
break;
case GDK_KEY_Left:
case GDK_KEY_KP_Left:
dx = (state & GDK_MOD1_MASK) ? -BIG_STEP : -SMALL_STEP;
break;
case GDK_KEY_Right:
case GDK_KEY_KP_Right:
dx = (state & GDK_MOD1_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, NULL, NULL, &state);
if (dx != 0 || dy != 0)
{
win32_context->last_x += dx;
win32_context->last_y += dy;
gdk_device_warp (pointer, win32_context->last_x, win32_context->last_y);
}
gdk_drag_update (context, win32_context->last_x, win32_context->last_y, state,
gdk_event_get_time ((GdkEvent *) event));
return TRUE;
}
static gboolean
gdk_dnd_handle_grab_broken_event (GdkDragContext *context,
const GdkEventGrabBroken *event)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GDK_NOTE (DND, g_print ("gdk_dnd_handle_grab_broken_event: 0x%p\n",
context));
/* 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 IPC window, for
* example, when changing the drag cursor.
*/
if (event->implicit ||
event->grab_window == win32_context->drag_window ||
event->grab_window == win32_context->ipc_window)
return FALSE;
if (gdk_event_get_device ((GdkEvent *) event) !=
gdk_drag_context_get_device (context))
return FALSE;
gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_ERROR);
return TRUE;
}
static gboolean
gdk_dnd_handle_button_event (GdkDragContext *context,
const GdkEventButton *event)
{
GDK_NOTE (DND, g_print ("gdk_dnd_handle_button_event: 0x%p\n",
context));
#if 0
/* FIXME: Check the button matches */
if (event->button != win32_context->button)
return FALSE;
#endif
if ((gdk_drag_context_get_selected_action (context) != 0) &&
(gdk_drag_context_get_dest_window (context) != NULL))
{
g_signal_emit_by_name (context, "drop-performed",
gdk_event_get_time ((GdkEvent *) event));
}
else
gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_NO_TARGET);
return TRUE;
}
gboolean
gdk_dnd_handle_drag_status (GdkDragContext *context,
const GdkEventDND *event)
{
GdkWin32DragContext *context_win32 = GDK_WIN32_DRAG_CONTEXT (context);
GdkDragAction action;
GDK_NOTE (DND, g_print ("gdk_dnd_handle_drag_status: 0x%p\n",
context));
if (context != event->context)
return FALSE;
action = gdk_drag_context_get_selected_action (context);
if (action != context_win32->current_action)
{
context_win32->current_action = action;
g_signal_emit_by_name (context, "action-changed", action);
}
return TRUE;
}
static gboolean
gdk_dnd_handle_drop_finished (GdkDragContext *context,
const GdkEventDND *event)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GDK_NOTE (DND, g_print ("gdk_dnd_handle_drop_finihsed: 0x%p\n",
context));
if (context != event->context)
return FALSE;
g_signal_emit_by_name (context, "dnd-finished");
gdk_drag_drop_done (context, !win32_context->drop_failed);
gdk_win32_selection_clear_targets (gdk_display_get_default (),
gdk_win32_drag_context_get_selection (context));
return TRUE;
}
gboolean
gdk_win32_drag_context_handle_event (GdkDragContext *context,
const GdkEvent *event)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
if (!context->is_source)
return FALSE;
if (!win32_context->grab_seat && event->type != GDK_DROP_FINISHED)
return FALSE;
switch (event->type)
{
case GDK_MOTION_NOTIFY:
return gdk_dnd_handle_motion_event (context, &event->motion);
case GDK_BUTTON_RELEASE:
return gdk_dnd_handle_button_event (context, &event->button);
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
return gdk_dnd_handle_key_event (context, &event->key);
case GDK_GRAB_BROKEN:
return gdk_dnd_handle_grab_broken_event (context, &event->grab_broken);
case GDK_DRAG_STATUS:
return gdk_dnd_handle_drag_status (context, &event->dnd);
case GDK_DROP_FINISHED:
return gdk_dnd_handle_drop_finished (context, &event->dnd);
default:
break;
}
return FALSE;
}
void
gdk_win32_drag_context_action_changed (GdkDragContext *context,
GdkDragAction action)
{
GdkCursor *cursor;
cursor = gdk_drag_get_cursor (context, action);
gdk_drag_context_set_cursor (context, cursor);
}
static GdkWindow *
gdk_win32_drag_context_get_drag_window (GdkDragContext *context)
{
return GDK_WIN32_DRAG_CONTEXT (context)->drag_window;
}
static void
gdk_win32_drag_context_set_hotspot (GdkDragContext *context,
gint hot_x,
gint hot_y)
{
GdkWin32DragContext *win32_context = GDK_WIN32_DRAG_CONTEXT (context);
GDK_NOTE (DND, g_print ("gdk_drag_context_set_hotspot: 0x%p %d:%d\n",
context,
hot_x, hot_y));
win32_context->hot_x = hot_x;
win32_context->hot_y = hot_y;
if (win32_context->grab_seat)
{
/* DnD is managed, update current position */
move_drag_window (context, win32_context->last_x, win32_context->last_y);
}
}
static void
gdk_win32_drag_context_class_init (GdkWin32DragContextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GdkDragContextClass *context_class = GDK_DRAG_CONTEXT_CLASS (klass);
object_class->finalize = gdk_win32_drag_context_finalize;
context_class->find_window = gdk_win32_drag_context_find_window;
context_class->drag_status = gdk_win32_drag_context_drag_status;
context_class->drag_motion = gdk_win32_drag_context_drag_motion;
context_class->drag_abort = gdk_win32_drag_context_drag_abort;
context_class->drag_drop = gdk_win32_drag_context_drag_drop;
context_class->drop_reply = gdk_win32_drag_context_drop_reply;
context_class->drop_finish = gdk_win32_drag_context_drop_finish;
context_class->drop_status = gdk_win32_drag_context_drop_status;
context_class->get_selection = gdk_win32_drag_context_get_selection;
context_class->get_drag_window = gdk_win32_drag_context_get_drag_window;
context_class->set_hotspot = gdk_win32_drag_context_set_hotspot;
context_class->drop_done = gdk_win32_drag_context_drop_done;
context_class->manage_dnd = gdk_win32_drag_context_manage_dnd;
context_class->set_cursor = gdk_win32_drag_context_set_cursor;
context_class->cancel = gdk_win32_drag_context_cancel;
context_class->drop_performed = gdk_win32_drag_context_drop_performed;
context_class->handle_event = gdk_win32_drag_context_handle_event;
context_class->action_changed = gdk_win32_drag_context_action_changed;
}