/* GDK - The GIMP Drawing Kit * Copyright (C) 1995-1999 Peter Mattis, Spencer Kimball and Josh MacDonald * 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * 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 /* #define OLE2_DND */ #define INITGUID #include "gdkdnd.h" #include "gdkproperty.h" #include "gdkinternals.h" #include "gdkprivate-win32.h" #ifdef OLE2_DND #include #else #include #endif #include #include #include typedef struct _GdkDragContextPrivateWin32 GdkDragContextPrivateWin32; typedef enum { GDK_DRAG_STATUS_DRAG, GDK_DRAG_STATUS_MOTION_WAIT, GDK_DRAG_STATUS_ACTION_WAIT, GDK_DRAG_STATUS_DROP } GtkDragStatus; typedef enum { GDK_DRAG_SOURCE, GDK_DRAG_TARGET } GdkDragKind; #ifdef OLE2_DND #define PRINT_GUID(guid) \ g_print ("guid = %.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 FORMATETC *formats; static int nformats; #endif /* OLE2_DND */ /* Structure that holds information about a drag in progress. * this is used on both source and destination sides. */ struct _GdkDragContextPrivateWin32 { #ifdef OLE2_DND gboolean being_finalized; gint ref_count; IUnknown *iface; #endif guint16 last_x; /* Coordinates from last event */ guint16 last_y; HWND dest_xid; guint drag_status : 4; /* Current status of drag */ guint drop_failed : 1; /* Whether the drop was unsuccessful */ }; #define PRIVATE_DATA(context) ((GdkDragContextPrivateWin32 *) GDK_DRAG_CONTEXT (context)->windowing_data) static GdkDragContext *current_dest_drag = NULL; static void gdk_drag_context_init (GdkDragContext *dragcontext); static void gdk_drag_context_class_init (GdkDragContextClass *klass); static void gdk_drag_context_finalize (GObject *object); static gpointer parent_class = NULL; static GList *contexts; G_DEFINE_TYPE (GdkDragContext, gdk_drag_context, G_TYPE_OBJECT) static void gdk_drag_context_init (GdkDragContext *dragcontext) { GdkDragContextPrivateWin32 *private; private = G_TYPE_INSTANCE_GET_PRIVATE (dragcontext, GDK_TYPE_DRAG_CONTEXT, GdkDragContextPrivateWin32); dragcontext->windowing_data = private; #ifdef OLE2_DND private->being_finalized = FALSE; private->ref_count = 1; private->iface = NULL; #endif contexts = g_list_prepend (contexts, dragcontext); GDK_NOTE (DND, g_print ("gdk_drag_context_init %p\n", dragcontext)); } static void gdk_drag_context_class_init (GdkDragContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = gdk_drag_context_finalize; g_type_class_add_private (object_class, sizeof (GdkDragContextPrivateWin32)); } static void gdk_drag_context_finalize (GObject *object) { GdkDragContext *context = GDK_DRAG_CONTEXT (object); GDK_NOTE (DND, g_print ("gdk_drag_context_finalize %p\n", object)); g_list_free (context->targets); if (context->source_window) g_object_unref (context->source_window); if (context->dest_window) g_object_unref (context->dest_window); contexts = g_list_remove (contexts, context); if (context == current_dest_drag) current_dest_drag = NULL; #ifdef OLE2_DND { GdkDragContextPrivateWin32 *private = PRIVATE_DATA (context); if (private->iface) { private->being_finalized = TRUE; private->iface->lpVtbl->Release (private->iface); private->iface = NULL; } } #endif G_OBJECT_CLASS (parent_class)->finalize (object); } /* Drag Contexts */ GdkDragContext * gdk_drag_context_new (void) { return g_object_new (GDK_TYPE_DRAG_CONTEXT, NULL); } void gdk_drag_context_ref (GdkDragContext *context) { g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); g_object_ref (context); } void gdk_drag_context_unref (GdkDragContext *context) { g_return_if_fail (GDK_IS_DRAG_CONTEXT (context)); g_object_unref (context); } static GdkDragContext * gdk_drag_context_find (gboolean is_source, GdkWindow *source, GdkWindow *dest) { GList *tmp_list = contexts; GdkDragContext *context; GdkDragContextPrivateWin32 *private; while (tmp_list) { context = (GdkDragContext *)tmp_list->data; private = PRIVATE_DATA (context); 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; } #ifdef OLE2_DND typedef struct { IDropTarget idt; GdkDragContext *context; } target_drag_context; typedef struct { IDropSource ids; GdkDragContext *context; } source_drag_context; typedef struct { IDataObject ido; int ref_count; } data_object; typedef struct { IEnumFORMATETC ief; int ref_count; int ix; } enum_formats; static enum_formats *enum_formats_new (void); static ULONG STDMETHODCALLTYPE idroptarget_addref (LPDROPTARGET This) { target_drag_context *ctx = (target_drag_context *) This; GdkDragContextPrivateWin32 *private = PRIVATE_DATA (ctx->context); int ref_count = ++private->ref_count; GDK_NOTE (DND, g_print ("idroptarget_addref %p %d\n", This, ref_count)); g_object_ref (G_OBJECT (ctx->context)); return ref_count; } static HRESULT STDMETHODCALLTYPE idroptarget_queryinterface (LPDROPTARGET This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, g_print ("idroptarget_queryinterface %p\n", This)); *ppvObject = NULL; PRINT_GUID (riid); if (IsEqualGUID (riid, &IID_IUnknown)) { g_print ("...IUnknown\n"); idroptarget_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDropTarget)) { g_print ("...IDropTarget\n"); idroptarget_addref (This); *ppvObject = This; return S_OK; } else { g_print ("...Nope\n"); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE idroptarget_release (LPDROPTARGET This) { target_drag_context *ctx = (target_drag_context *) This; GdkDragContextPrivateWin32 *private = PRIVATE_DATA (ctx->context); int ref_count = --private->ref_count; GDK_NOTE (DND, g_print ("idroptarget_release %p %d\n", This, ref_count)); if (!private->being_finalized) g_object_unref (G_OBJECT (ctx->context)); if (ref_count == 0) g_free (This); return ref_count; } static HRESULT STDMETHODCALLTYPE idroptarget_dragenter (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { IEnumFORMATETC* iefp; FORMATETC formatetc; GDK_NOTE (DND, g_print ("idroptarget_dragenter %p formats:\n", This)); pDataObj->lpVtbl->EnumFormatEtc (pDataObj, DATADIR_GET, &iefp); while (iefp->lpVtbl->Next (iefp, 1, &formatetc, NULL) == S_OK) { GDK_NOTE (DND, g_print (" %s\n", _gdk_win32_cf_to_string (formatetc.cfFormat))); } iefp->lpVtbl->Release (iefp); *pdwEffect = DROPEFFECT_COPY; return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_dragover (LPDROPTARGET This, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { GDK_NOTE (DND, g_print ("idroptarget_dragover %p\n", This)); *pdwEffect = DROPEFFECT_COPY; return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_dragleave (LPDROPTARGET This) { GDK_NOTE (DND, g_print ("idroptarget_dragleave %p\n", This)); return S_OK; } static HRESULT STDMETHODCALLTYPE idroptarget_drop (LPDROPTARGET This, LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) { GDK_NOTE (DND, g_print ("idroptarget_drop %p\n", This)); *pdwEffect = DROPEFFECT_COPY; return S_OK; } static ULONG STDMETHODCALLTYPE idropsource_addref (LPDROPSOURCE This) { source_drag_context *ctx = (source_drag_context *) This; GdkDragContextPrivateWin32 *private = PRIVATE_DATA (ctx->context); int ref_count = ++private->ref_count; GDK_NOTE (DND, g_print ("idropsource_addref %p %d\n", This, ref_count)); g_object_ref (G_OBJECT (ctx->context)); return ref_count; } static HRESULT STDMETHODCALLTYPE idropsource_queryinterface (LPDROPSOURCE This, REFIID riid, LPVOID *ppvObject) { GDK_NOTE (DND, g_print ("idropsource_queryinterface %p\n", This)); *ppvObject = NULL; PRINT_GUID (riid); if (IsEqualGUID (riid, &IID_IUnknown)) { g_print ("...IUnknown\n"); idropsource_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDropSource)) { g_print ("...IDropSource\n"); idropsource_addref (This); *ppvObject = This; return S_OK; } else { g_print ("...Nope\n"); return E_NOINTERFACE; } } static ULONG STDMETHODCALLTYPE idropsource_release (LPDROPSOURCE This) { source_drag_context *ctx = (source_drag_context *) This; GdkDragContextPrivateWin32 *private = PRIVATE_DATA (ctx->context); int ref_count = --private->ref_count; GDK_NOTE (DND, g_print ("idropsource_release %p %d\n", This, ref_count)); if (!private->being_finalized) g_object_unref (G_OBJECT (ctx->context)); if (ref_count == 0) g_free (This); return ref_count; } static HRESULT STDMETHODCALLTYPE idropsource_querycontinuedrag (LPDROPSOURCE This, BOOL fEscapePressed, DWORD grfKeyState) { GDK_NOTE (DND, g_print ("idropsource_querycontinuedrag %p\n", This)); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idropsource_givefeedback (LPDROPSOURCE This, DWORD dwEffect) { GDK_NOTE (DND, g_print ("idropsource_givefeedback %p\n", This)); return E_UNEXPECTED; } 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\n", This)); *ppvObject = NULL; PRINT_GUID (riid); if (IsEqualGUID (riid, &IID_IUnknown)) { g_print ("...IUnknown\n"); idataobject_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IDataObject)) { g_print ("...IDataObject\n"); idataobject_addref (This); *ppvObject = This; return S_OK; } else { g_print ("...Nope\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_free (This); return ref_count; } static HRESULT STDMETHODCALLTYPE idataobject_getdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { GDK_NOTE (DND, g_print ("idataobject_getdata %p\n", This)); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_getdatahere (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { GDK_NOTE (DND, g_print ("idataobject_getdatahere %p\n", This)); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_querygetdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc) { int i; GDK_NOTE (DND, g_print ("idataobject_querygetdata %p %#x", This, pFormatEtc->cfFormat)); for (i = 0; i < nformats; i++) if (pFormatEtc->cfFormat == formats[i].cfFormat) { GDK_NOTE (DND, g_print (" S_OK\n")); return S_OK; } GDK_NOTE (DND, g_print (" DV_E_FORMATETC\n")); return DV_E_FORMATETC; } static HRESULT STDMETHODCALLTYPE idataobject_getcanonicalformatetc (LPDATAOBJECT This, LPFORMATETC pFormatEtcIn, LPFORMATETC pFormatEtcOut) { GDK_NOTE (DND, g_print ("idataobject_getcanonicalformatetc %p\n", This)); return E_FAIL; } static HRESULT STDMETHODCALLTYPE idataobject_setdata (LPDATAOBJECT This, LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease) { GDK_NOTE (DND, g_print ("idataobject_setdata %p\n", This)); return E_UNEXPECTED; } static HRESULT STDMETHODCALLTYPE idataobject_enumformatetc (LPDATAOBJECT This, DWORD dwDirection, LPENUMFORMATETC *ppEnumFormatEtc) { GDK_NOTE (DND, g_print ("idataobject_enumformatetc %p\n", This)); if (dwDirection != DATADIR_GET) return E_NOTIMPL; *ppEnumFormatEtc = &enum_formats_new ()->ief; 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\n", This)); return E_FAIL; } static HRESULT STDMETHODCALLTYPE idataobject_dunadvise (LPDATAOBJECT This, DWORD dwConnection) { GDK_NOTE (DND, g_print ("idataobject_dunadvise %p\n", This)); return E_FAIL; } static HRESULT STDMETHODCALLTYPE idataobject_enumdadvise (LPDATAOBJECT This, LPENUMSTATDATA *ppenumAdvise) { GDK_NOTE (DND, g_print ("idataobject_enumdadvise %p\n", This)); return E_FAIL; } 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\n", This)); *ppvObject = NULL; PRINT_GUID (riid); if (IsEqualGUID (riid, &IID_IUnknown)) { g_print ("...IUnknown\n"); ienumformatetc_addref (This); *ppvObject = This; return S_OK; } else if (IsEqualGUID (riid, &IID_IEnumFORMATETC)) { g_print ("...IEnumFORMATETC\n"); ienumformatetc_addref (This); *ppvObject = This; return S_OK; } else { g_print ("...Nope\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_free (This); return ref_count; } static HRESULT STDMETHODCALLTYPE ienumformatetc_next (LPENUMFORMATETC This, ULONG celt, LPFORMATETC elts, ULONG *nelt) { enum_formats *en = (enum_formats *) This; int i, n; GDK_NOTE (DND, g_print ("ienumformatetc_next %p %d %ld\n", This, en->ix, celt)); n = 0; for (i = 0; i < celt; i++) { if (en->ix >= nformats) break; elts[i] = formats[en->ix++]; n++; } if (nelt != NULL) *nelt = n; 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\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\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\n", This)); new = enum_formats_new (); 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 (void) { target_drag_context *result; GdkDragContextPrivateWin32 *private; result = g_new0 (target_drag_context, 1); result->idt.lpVtbl = &idt_vtbl; result->context = gdk_drag_context_new (); result->context->is_source = FALSE; private = result->context->windowing_data; private->iface = (IUnknown *) &result->idt; idroptarget_addref (&result->idt); GDK_NOTE (DND, g_print ("target_context_new: %p\n", result)); return result; } static source_drag_context * source_context_new (void) { source_drag_context *result; GdkDragContextPrivateWin32 *private; result = g_new0 (source_drag_context, 1); result->ids.lpVtbl = &ids_vtbl; result->context = gdk_drag_context_new (); result->context->is_source = TRUE; private = result->context->windowing_data; private->iface = (IUnknown *) &result->ids; idropsource_addref (&result->ids); GDK_NOTE (DND, g_print ("source_context_new: %p\n", result)); return result; } static data_object * data_object_new (void) { data_object *result; result = g_new0 (data_object, 1); result->ido.lpVtbl = &ido_vtbl; result->ref_count = 1; GDK_NOTE (DND, g_print ("data_object_new: %p\n", result)); return result; } static enum_formats * enum_formats_new (void) { enum_formats *result; result = g_new0 (enum_formats, 1); result->ief.lpVtbl = &ief_vtbl; result->ref_count = 1; result->ix = 0; GDK_NOTE (DND, g_print ("enum_formats_new: %p\n", result)); return result; } #endif /* From MS Knowledge Base article Q130698 */ static gboolean resolve_link (HWND hWnd, wchar_t *link, gchar **lpszPath) { WIN32_FILE_ATTRIBUTE_DATA wfad; HRESULT hres; 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. */ hres = CoCreateInstance (&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID *)&pslW); if (SUCCEEDED (hres)) { /* The IShellLink interface supports the IPersistFile * interface. Get an interface pointer to it. */ hres = pslW->lpVtbl->QueryInterface (pslW, &IID_IPersistFile, (LPVOID *) &ppf); } if (SUCCEEDED (hres)) { /* Load the file. */ hres = ppf->lpVtbl->Load (ppf, link, STGM_READ); } if (SUCCEEDED (hres)) { /* Resolve the link by calling the Resolve() * interface function. */ hres = pslW->lpVtbl->Resolve (pslW, hWnd, SLR_ANY_MATCH | SLR_NO_UI); } if (SUCCEEDED (hres)) { wchar_t wtarget[MAX_PATH]; hres = pslW->lpVtbl->GetPath (pslW, wtarget, MAX_PATH, NULL, 0); if (SUCCEEDED (hres)) *lpszPath = g_utf16_to_utf8 (wtarget, -1, NULL, NULL, NULL); } if (ppf) ppf->lpVtbl->Release (ppf); if (pslW) pslW->lpVtbl->Release (pslW); return SUCCEEDED (hres); } static GdkFilterReturn gdk_dropfiles_filter (GdkXEvent *xev, GdkEvent *event, gpointer data) { GdkDragContext *context; GdkDragContextPrivateWin32 *private; GString *result; MSG *msg = (MSG *) xev; HANDLE hdrop; POINT pt; gint nfiles, i; gchar *fileName, *linkedFile; if (msg->message == WM_DROPFILES) { GDK_NOTE (DND, g_print ("WM_DROPFILES: %p\n", msg->hwnd)); context = gdk_drag_context_new (); private = PRIVATE_DATA (context); context->protocol = GDK_DRAG_PROTO_WIN32_DROPFILES; context->is_source = FALSE; context->source_window = _gdk_root; g_object_ref (context->source_window); context->dest_window = event->any.window; g_object_ref (context->dest_window); /* WM_DROPFILES drops are always file names */ context->targets = g_list_append (NULL, GUINT_TO_POINTER (_text_uri_list)); 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; hdrop = (HANDLE) msg->wParam; DragQueryPoint (hdrop, &pt); ClientToScreen (msg->hwnd, &pt); event->dnd.x_root = pt.x + _gdk_offset_x; event->dnd.y_root = pt.y + _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); g_free (linkedFile); 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); } } 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); } } 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; } /************************************************************* ************************** Public API *********************** *************************************************************/ void _gdk_dnd_init (void) { #ifdef OLE2_DND HRESULT hres; hres = OleInitialize (NULL); if (! SUCCEEDED (hres)) g_error ("OleInitialize failed"); nformats = 2; formats = g_new (FORMATETC, nformats); formats[0].cfFormat = CF_TEXT; formats[0].ptd = NULL; formats[0].dwAspect = DVASPECT_CONTENT; formats[0].lindex = -1; formats[0].tymed = TYMED_HGLOBAL; formats[1].cfFormat = CF_GDIOBJFIRST; formats[1].ptd = NULL; formats[1].dwAspect = DVASPECT_CONTENT; formats[1].lindex = -1; formats[1].tymed = TYMED_HGLOBAL; #endif } void _gdk_win32_dnd_exit (void) { #ifdef OLE2_DND OleUninitialize (); #endif } /* 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.dnd.type = GDK_DRAG_LEAVE; tmp_event.dnd.window = context->dest_window; /* Pass ownership of context to the event */ tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = current_dest_drag; tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */ current_dest_drag = NULL; gdk_event_put (&tmp_event); } } static void local_send_enter (GdkDragContext *context, guint32 time) { GdkEvent tmp_event; GdkDragContextPrivateWin32 *private; GdkDragContext *new_context; GDK_NOTE (DND, g_print ("local_send_enter: context=%p current_dest_drag=%p\n", context, current_dest_drag)); private = PRIVATE_DATA (context); if (current_dest_drag != NULL) { g_object_unref (G_OBJECT (current_dest_drag)); current_dest_drag = NULL; } new_context = gdk_drag_context_new (); new_context->protocol = GDK_DRAG_PROTO_LOCAL; new_context->is_source = FALSE; new_context->source_window = context->source_window; g_object_ref (new_context->source_window); new_context->dest_window = context->dest_window; g_object_ref (new_context->dest_window); new_context->targets = g_list_copy (context->targets); 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.dnd.type = GDK_DRAG_ENTER; tmp_event.dnd.window = context->dest_window; tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = new_context; tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */ current_dest_drag = new_context; gdk_event_put (&tmp_event); } static void local_send_motion (GdkDragContext *context, gint x_root, gint y_root, GdkDragAction action, guint32 time) { GdkEvent tmp_event; GDK_NOTE (DND, g_print ("local_send_motion: 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.dnd.type = GDK_DRAG_MOTION; tmp_event.dnd.window = current_dest_drag->dest_window; tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = current_dest_drag; tmp_event.dnd.time = time; current_dest_drag->suggested_action = action; tmp_event.dnd.x_root = x_root; tmp_event.dnd.y_root = y_root; PRIVATE_DATA (current_dest_drag)->last_x = x_root; PRIVATE_DATA (current_dest_drag)->last_y = y_root; PRIVATE_DATA (context)->drag_status = GDK_DRAG_STATUS_MOTION_WAIT; gdk_event_put (&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)) { GdkDragContextPrivateWin32 *private; private = PRIVATE_DATA (current_dest_drag); /* Pass ownership of context to the event */ tmp_event.dnd.type = GDK_DROP_START; tmp_event.dnd.window = current_dest_drag->dest_window; tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = current_dest_drag; tmp_event.dnd.time = GDK_CURRENT_TIME; tmp_event.dnd.x_root = private->last_x; tmp_event.dnd.y_root = private->last_y; current_dest_drag = NULL; gdk_event_put (&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")); switch (context->protocol) { case GDK_DRAG_PROTO_LOCAL: local_send_leave (context, time); break; default: break; } g_object_unref (context->dest_window); context->dest_window = NULL; } } GdkDragContext * gdk_drag_begin (GdkWindow *window, GList *targets) { #ifdef OLE2_DND source_drag_context *ctx; data_object *dobj; HRESULT hResult; DWORD dwEffect; HGLOBAL global; STGMEDIUM medium; g_return_val_if_fail (window != NULL, NULL); GDK_NOTE (DND, g_print ("gdk_drag_begin\n")); ctx = source_context_new (); ctx->context->protocol = GDK_DRAG_PROTO_OLE2; ctx->context->source_window = window; g_object_ref (window); ctx->context->targets = g_list_copy (targets); ctx->context->actions = 0; dobj = data_object_new (); global = GlobalAlloc (GMEM_FIXED, sizeof (ctx)); memcpy (&global, ctx, sizeof (ctx)); medium.tymed = TYMED_HGLOBAL; medium.hGlobal = global; medium.pUnkForRelease = NULL; dobj->ido.lpVtbl->SetData (&dobj->ido, &formats[1], &medium, TRUE); hResult = DoDragDrop (&dobj->ido, &ctx->ids, DROPEFFECT_MOVE|DROPEFFECT_COPY, &dwEffect); GDK_NOTE (DND, g_print ("DoDragDrop returned %s\n", (hResult == DRAGDROP_S_DROP ? "DRAGDROP_S_DROP" : (hResult == DRAGDROP_S_CANCEL ? "DRAGDROP_S_CANCEL" : (hResult == E_UNEXPECTED ? "E_UNEXPECTED" : g_strdup_printf ("%#.8lx", hResult)))))); dobj->ido.lpVtbl->Release (&dobj->ido); ctx->ids.lpVtbl->Release (&ctx->ids); return ctx->context; #else GdkDragContext *new_context; g_return_val_if_fail (window != NULL, NULL); new_context = gdk_drag_context_new (); new_context->is_source = TRUE; new_context->source_window = window; g_object_ref (window); new_context->targets = g_list_copy (targets); new_context->actions = 0; return new_context; #endif } GdkNativeWindow gdk_drag_get_protocol_for_display (GdkDisplay *display, GdkNativeWindow xid, GdkDragProtocol *protocol) { GdkWindow *window; window = gdk_window_lookup (xid); if (window && gdk_window_get_window_type (window) != GDK_WINDOW_FOREIGN) { if (g_object_get_data (G_OBJECT (window), "gdk-dnd-registered") != NULL) { #ifdef OLE2_DND *protocol = GDK_DRAG_PROTO_OLE2; #else *protocol = GDK_DRAG_PROTO_LOCAL; #endif return xid; } } return 0; } 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; } void gdk_drag_find_window_for_screen (GdkDragContext *context, GdkWindow *drag_window, GdkScreen *screen, gint x_root, gint y_root, GdkWindow **dest_window, GdkDragProtocol *protocol) { find_window_enum_arg a; a.x = x_root - _gdk_offset_x; a.y = y_root - _gdk_offset_y; a.ignore = drag_window ? GDK_WINDOW_HWND (drag_window) : NULL; a.result = NULL; EnumWindows (find_window_enum_proc, (LPARAM) &a); if (a.result == NULL) *dest_window = NULL; else { *dest_window = gdk_win32_handle_table_lookup (a.result); if (*dest_window) { *dest_window = gdk_window_get_toplevel (*dest_window); g_object_ref (*dest_window); } if (context->source_window) #ifdef OLE2_DND *protocol = GDK_DRAG_PROTO_OLE2; #else *protocol = GDK_DRAG_PROTO_LOCAL; #endif else *protocol = GDK_DRAG_PROTO_WIN32_DROPFILES; } GDK_NOTE (DND, g_print ("gdk_drag_find_window: %p %+d%+d: %p: %p %d\n", (drag_window ? GDK_WINDOW_HWND (drag_window) : NULL), x_root, y_root, a.result, (*dest_window ? GDK_WINDOW_HWND (*dest_window) : NULL), *protocol)); } gboolean gdk_drag_motion (GdkDragContext *context, GdkWindow *dest_window, GdkDragProtocol protocol, gint x_root, gint y_root, GdkDragAction suggested_action, GdkDragAction possible_actions, guint32 time) { GdkDragContextPrivateWin32 *private; g_return_val_if_fail (context != NULL, FALSE); context->actions = possible_actions; GDK_NOTE (DND, g_print ("gdk_drag_motion: protocol=%s\n" " suggested_action=%s, possible_actions=%s\n" " context=%p:actions=%s, suggested_action=%s, action=%s\n", _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))); private = PRIVATE_DATA (context); 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 temp_event; /* Send a leave to the last destination */ gdk_drag_do_leave (context, time); private->drag_status = GDK_DRAG_STATUS_DRAG; /* Check if new destination accepts drags, and which protocol */ if (dest_window) { context->dest_window = dest_window; g_object_ref (context->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 */ temp_event.dnd.type = GDK_DRAG_STATUS; temp_event.dnd.window = context->source_window; /* We use this to signal a synthetic status. Perhaps * we should use an extra field... */ temp_event.dnd.send_event = TRUE; temp_event.dnd.context = context; temp_event.dnd.time = time; gdk_event_put (&temp_event); } /* Send a drag-motion event */ private->last_x = x_root; private->last_y = y_root; if (context->dest_window) { if (private->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_action=%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_action=%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; } void gdk_drag_drop (GdkDragContext *context, guint32 time) { g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_drop\n")); if (context->dest_window) { switch (context->protocol) { case GDK_DRAG_PROTO_LOCAL: local_send_drop (context, time); break; case GDK_DRAG_PROTO_NONE: g_warning ("GDK_DRAG_PROTO_NONE is not valid in gdk_drag_drop()"); break; default: break; } } } void gdk_drag_abort (GdkDragContext *context, guint32 time) { g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_abort\n")); gdk_drag_do_leave (context, time); } /* Destination side */ void gdk_drag_status (GdkDragContext *context, GdkDragAction action, guint32 time) { GdkDragContextPrivateWin32 *private; GdkDragContext *src_context; GdkEvent tmp_event; g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drag_status\n")); private = PRIVATE_DATA (context); GDK_NOTE (DND, g_print ("gdk_drag_status: action=%s\n" " context=%p:actions=%s, suggested_action=%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; src_context = gdk_drag_context_find (TRUE, context->source_window, context->dest_window); if (src_context) { GdkDragContextPrivateWin32 *private = PRIVATE_DATA (src_context); if (private->drag_status == GDK_DRAG_STATUS_MOTION_WAIT) private->drag_status = GDK_DRAG_STATUS_DRAG; tmp_event.dnd.type = GDK_DRAG_STATUS; tmp_event.dnd.window = context->source_window; tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = src_context; tmp_event.dnd.time = GDK_CURRENT_TIME; /* FIXME? */ if (action == GDK_ACTION_DEFAULT) action = 0; src_context->action = action; gdk_event_put (&tmp_event); } } void gdk_drop_reply (GdkDragContext *context, gboolean ok, guint32 time) { g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drop_reply\n")); if (context->dest_window) { switch (context->protocol) { case GDK_DRAG_PROTO_WIN32_DROPFILES: _gdk_dropfiles_store (NULL); break; default: break; } } } void gdk_drop_finish (GdkDragContext *context, gboolean success, guint32 time) { GdkDragContextPrivateWin32 *private; GdkDragContext *src_context; GdkEvent tmp_event; g_return_if_fail (context != NULL); GDK_NOTE (DND, g_print ("gdk_drop_finish\n")); private = PRIVATE_DATA (context); src_context = gdk_drag_context_find (TRUE, context->source_window, context->dest_window); if (src_context) { tmp_event.dnd.type = GDK_DROP_FINISHED; tmp_event.dnd.window = src_context->source_window; tmp_event.dnd.send_event = FALSE; tmp_event.dnd.context = src_context; gdk_event_put (&tmp_event); } } #ifdef OLE2_DND 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_window_register_dnd (GdkWindow *window) { #ifdef OLE2_DND target_drag_context *ctx; HRESULT hres; #endif 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))); /* 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); #ifdef OLE2_DND /* Register for OLE2 d&d */ ctx = target_context_new (); ctx->context->protocol = GDK_DRAG_PROTO_OLE2; hres = CoLockObjectExternal ((IUnknown *) &ctx->idt, TRUE, FALSE); if (!SUCCEEDED (hres)) OTHER_API_FAILED ("CoLockObjectExternal"); else { hres = RegisterDragDrop (GDK_WINDOW_HWND (window), &ctx->idt); if (hres == DRAGDROP_E_ALREADYREGISTERED) { g_print ("DRAGDROP_E_ALREADYREGISTERED\n"); #if 0 ctx->idt.lpVtbl->Release (&ctx->idt); #endif CoLockObjectExternal ((IUnknown *) &ctx->idt, FALSE, FALSE); } else if (!SUCCEEDED (hres)) OTHER_API_FAILED ("RegisterDragDrop"); else { gdk_window_add_filter (window, gdk_destroy_filter, &ctx->idt); } } #endif } /************************************************************* * gdk_drag_get_selection: * Returns the selection atom for the current source window * arguments: * * results: *************************************************************/ GdkAtom gdk_drag_get_selection (GdkDragContext *context) { if (context->protocol == GDK_DRAG_PROTO_LOCAL) return _local_dnd; else if (context->protocol == GDK_DRAG_PROTO_WIN32_DROPFILES) return _gdk_win32_dropfiles; else if (context->protocol == GDK_DRAG_PROTO_OLE2) return _gdk_ole2_dnd; else return GDK_NONE; } gboolean gdk_drag_drop_succeeded (GdkDragContext *context) { GdkDragContextPrivateWin32 *private; g_return_val_if_fail (context != NULL, FALSE); private = PRIVATE_DATA (context); /* FIXME: Can we set drop_failed when the drop has failed? */ return !private->drop_failed; }