michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=4 et sw=4 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDragService.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsWindow.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsIFileURL.h" michael@0: #include "nsNetUtil.h" michael@0: #include "prlog.h" michael@0: #include "nsTArray.h" michael@0: #include "nsPrimitiveHelpers.h" michael@0: #include "prtime.h" michael@0: #include "prthread.h" michael@0: #include michael@0: #include michael@0: #include "nsCRT.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #include "gfxASurface.h" michael@0: #include "gfxXlibSurface.h" michael@0: #include "gfxContext.h" michael@0: #include "nsImageToPixbuf.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsISelection.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsGtkUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "gfxPlatform.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: // This sets how opaque the drag image is michael@0: #define DRAG_IMAGE_ALPHA_LEVEL 0.5 michael@0: michael@0: // These values are copied from GtkDragResult (rather than using GtkDragResult michael@0: // directly) so that this code can be compiled against versions of GTK+ that michael@0: // do not have GtkDragResult. michael@0: // GtkDragResult is available from GTK+ version 2.12. michael@0: enum { michael@0: MOZ_GTK_DRAG_RESULT_SUCCESS, michael@0: MOZ_GTK_DRAG_RESULT_NO_TARGET michael@0: }; michael@0: michael@0: static PRLogModuleInfo *sDragLm = nullptr; michael@0: michael@0: // data used for synthetic periodic motion events sent to the source widget michael@0: // grabbing real events for the drag. michael@0: static guint sMotionEventTimerID; michael@0: static GdkEvent *sMotionEvent; michael@0: static GtkWidget *sGrabWidget; michael@0: michael@0: static const char gMimeListType[] = "application/x-moz-internal-item-list"; michael@0: static const char gMozUrlType[] = "_NETSCAPE_URL"; michael@0: static const char gTextUriListType[] = "text/uri-list"; michael@0: static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8"; michael@0: michael@0: static void michael@0: invisibleSourceDragBegin(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gpointer aData); michael@0: michael@0: static void michael@0: invisibleSourceDragEnd(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gpointer aData); michael@0: michael@0: static gboolean michael@0: invisibleSourceDragFailed(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gint aResult, michael@0: gpointer aData); michael@0: michael@0: static void michael@0: invisibleSourceDragDataGet(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: GtkSelectionData *aSelectionData, michael@0: guint aInfo, michael@0: guint32 aTime, michael@0: gpointer aData); michael@0: michael@0: nsDragService::nsDragService() michael@0: : mScheduledTask(eDragTaskNone) michael@0: , mTaskSource(0) michael@0: { michael@0: // We have to destroy the hidden widget before the event loop stops michael@0: // running. michael@0: nsCOMPtr obsServ = michael@0: mozilla::services::GetObserverService(); michael@0: obsServ->AddObserver(this, "quit-application", false); michael@0: michael@0: // our hidden source widget michael@0: mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP); michael@0: // make sure that the widget is realized so that michael@0: // we can use it as a drag source. michael@0: gtk_widget_realize(mHiddenWidget); michael@0: // hook up our internal signals so that we can get some feedback michael@0: // from our drag source michael@0: g_signal_connect(mHiddenWidget, "drag_begin", michael@0: G_CALLBACK(invisibleSourceDragBegin), this); michael@0: g_signal_connect(mHiddenWidget, "drag_data_get", michael@0: G_CALLBACK(invisibleSourceDragDataGet), this); michael@0: g_signal_connect(mHiddenWidget, "drag_end", michael@0: G_CALLBACK(invisibleSourceDragEnd), this); michael@0: // drag-failed is available from GTK+ version 2.12 michael@0: guint dragFailedID = g_signal_lookup("drag-failed", michael@0: G_TYPE_FROM_INSTANCE(mHiddenWidget)); michael@0: if (dragFailedID) { michael@0: g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0, michael@0: g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), michael@0: this, nullptr), michael@0: FALSE); michael@0: } michael@0: michael@0: // set up our logging module michael@0: if (!sDragLm) michael@0: sDragLm = PR_NewLogModule("nsDragService"); michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::nsDragService")); michael@0: mCanDrop = false; michael@0: mTargetDragDataReceived = false; michael@0: mTargetDragData = 0; michael@0: mTargetDragDataLen = 0; michael@0: } michael@0: michael@0: nsDragService::~nsDragService() michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::~nsDragService")); michael@0: if (mTaskSource) michael@0: g_source_remove(mTaskSource); michael@0: michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver) michael@0: michael@0: /* static */ nsDragService* michael@0: nsDragService::GetInstance() michael@0: { michael@0: static const nsIID iid = NS_DRAGSERVICE_CID; michael@0: nsCOMPtr dragService = do_GetService(iid); michael@0: return static_cast(dragService.get()); michael@0: // We rely on XPCOM keeping a reference to the service. michael@0: } michael@0: michael@0: // nsIObserver michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::Observe(nsISupports *aSubject, const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!nsCRT::strcmp(aTopic, "quit-application")) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("nsDragService::Observe(\"quit-application\")")); michael@0: if (mHiddenWidget) { michael@0: gtk_widget_destroy(mHiddenWidget); michael@0: mHiddenWidget = 0; michael@0: } michael@0: TargetResetData(); michael@0: } else { michael@0: NS_NOTREACHED("unexpected topic"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Support for periodic drag events michael@0: michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model michael@0: // and the Xdnd protocol both recommend that drag events are sent periodically, michael@0: // but GTK does not normally provide this. michael@0: // michael@0: // Here GTK is periodically stimulated by copies of the most recent mouse michael@0: // motion events so as to send drag position messages to the destination when michael@0: // appropriate (after it has received a status event from the previous michael@0: // message). michael@0: // michael@0: // (If events were sent only on the destination side then the destination michael@0: // would have no message to which it could reply with a drag status. Without michael@0: // sending a drag status to the source, the destination would not be able to michael@0: // change its feedback re whether it could accept the drop, and so the michael@0: // source's behavior on drop will not be consistent.) michael@0: michael@0: static gboolean michael@0: DispatchMotionEventCopy(gpointer aData) michael@0: { michael@0: // Clear the timer id before OnSourceGrabEventAfter is called during event michael@0: // dispatch. michael@0: sMotionEventTimerID = 0; michael@0: michael@0: GdkEvent *event = sMotionEvent; michael@0: sMotionEvent = nullptr; michael@0: // If there is no longer a grab on the widget, then the drag is over and michael@0: // there is no need to continue drag motion. michael@0: if (gtk_widget_has_grab(sGrabWidget)) { michael@0: gtk_propagate_event(sGrabWidget, event); michael@0: } michael@0: gdk_event_free(event); michael@0: michael@0: // Cancel this timer; michael@0: // We've already started another if the motion event was dispatched. michael@0: return FALSE; michael@0: } michael@0: michael@0: static void michael@0: OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data) michael@0: { michael@0: // If there is no longer a grab on the widget, then the drag motion is michael@0: // over (though the data may not be fetched yet). michael@0: if (!gtk_widget_has_grab(sGrabWidget)) michael@0: return; michael@0: michael@0: if (event->type == GDK_MOTION_NOTIFY) { michael@0: if (sMotionEvent) { michael@0: gdk_event_free(sMotionEvent); michael@0: } michael@0: sMotionEvent = gdk_event_copy(event); michael@0: michael@0: // Update the cursor position. The last of these recorded gets used for michael@0: // the NS_DRAGDROP_END event. michael@0: nsDragService *dragService = static_cast(user_data); michael@0: dragService->SetDragEndPoint(nsIntPoint(event->motion.x_root, michael@0: event->motion.y_root)); michael@0: } else if (sMotionEvent && (event->type == GDK_KEY_PRESS || michael@0: event->type == GDK_KEY_RELEASE)) { michael@0: // Update modifier state from key events. michael@0: sMotionEvent->motion.state = event->key.state; michael@0: } else { michael@0: return; michael@0: } michael@0: michael@0: if (sMotionEventTimerID) { michael@0: g_source_remove(sMotionEventTimerID); michael@0: } michael@0: michael@0: // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source michael@0: // and lower than GTK's idle source that sends drag position messages after michael@0: // motion-notify signals. michael@0: // michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model michael@0: // recommends an interval of 350ms +/- 200ms. michael@0: sMotionEventTimerID = michael@0: g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350, michael@0: DispatchMotionEventCopy, nullptr, nullptr); michael@0: } michael@0: michael@0: static GtkWindow* michael@0: GetGtkWindow(nsIDOMDocument *aDocument) michael@0: { michael@0: nsCOMPtr doc = do_QueryInterface(aDocument); michael@0: if (!doc) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr presShell = doc->GetShell(); michael@0: if (!presShell) michael@0: return nullptr; michael@0: michael@0: nsRefPtr vm = presShell->GetViewManager(); michael@0: if (!vm) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr widget; michael@0: vm->GetRootWidget(getter_AddRefs(widget)); michael@0: if (!widget) michael@0: return nullptr; michael@0: michael@0: GtkWidget *gtkWidget = michael@0: static_cast(widget.get())->GetMozContainerWidget(); michael@0: if (!gtkWidget) michael@0: return nullptr; michael@0: michael@0: GtkWidget *toplevel = nullptr; michael@0: toplevel = gtk_widget_get_toplevel(gtkWidget); michael@0: if (!GTK_IS_WINDOW(toplevel)) michael@0: return nullptr; michael@0: michael@0: return GTK_WINDOW(toplevel); michael@0: } michael@0: michael@0: // nsIDragService michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, michael@0: nsISupportsArray * aArrayTransferables, michael@0: nsIScriptableRegion * aRegion, michael@0: uint32_t aActionType) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::InvokeDragSession")); michael@0: michael@0: // If the previous source drag has not yet completed, signal handlers need michael@0: // to be removed from sGrabWidget and dragend needs to be dispatched to michael@0: // the source node, but we can't call EndDragSession yet because we don't michael@0: // know whether or not the drag succeeded. michael@0: if (mSourceNode) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode, michael@0: aArrayTransferables, michael@0: aRegion, aActionType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // make sure that we have an array of transferables to use michael@0: if (!aArrayTransferables) michael@0: return NS_ERROR_INVALID_ARG; michael@0: // set our reference to the transferables. this will also addref michael@0: // the transferables since we're going to hang onto this beyond the michael@0: // length of this call michael@0: mSourceDataItems = aArrayTransferables; michael@0: // get the list of items we offer for drags michael@0: GtkTargetList *sourceList = GetSourceList(); michael@0: michael@0: if (!sourceList) michael@0: return NS_OK; michael@0: michael@0: // stored temporarily until the drag-begin signal has been received michael@0: mSourceRegion = aRegion; michael@0: michael@0: // save our action type michael@0: GdkDragAction action = GDK_ACTION_DEFAULT; michael@0: michael@0: if (aActionType & DRAGDROP_ACTION_COPY) michael@0: action = (GdkDragAction)(action | GDK_ACTION_COPY); michael@0: if (aActionType & DRAGDROP_ACTION_MOVE) michael@0: action = (GdkDragAction)(action | GDK_ACTION_MOVE); michael@0: if (aActionType & DRAGDROP_ACTION_LINK) michael@0: action = (GdkDragAction)(action | GDK_ACTION_LINK); michael@0: michael@0: // Create a fake event for the drag so we can pass the time (so to speak). michael@0: // If we don't do this, then, when the timestamp for the pending button michael@0: // release event is used for the ungrab, the ungrab can fail due to the michael@0: // timestamp being _earlier_ than CurrentTime. michael@0: GdkEvent event; michael@0: memset(&event, 0, sizeof(GdkEvent)); michael@0: event.type = GDK_BUTTON_PRESS; michael@0: event.button.window = gtk_widget_get_window(mHiddenWidget); michael@0: event.button.time = nsWindow::GetLastUserInputTime(); michael@0: michael@0: // Put the drag widget in the window group of the source node so that the michael@0: // gtk_grab_add during gtk_drag_begin is effective. michael@0: // gtk_window_get_group(nullptr) returns the default window group. michael@0: GtkWindowGroup *window_group = michael@0: gtk_window_get_group(GetGtkWindow(mSourceDocument)); michael@0: gtk_window_group_add_window(window_group, michael@0: GTK_WINDOW(mHiddenWidget)); michael@0: michael@0: #if (MOZ_WIDGET_GTK == 3) michael@0: // Get device for event source michael@0: GdkDisplay *display = gdk_display_get_default(); michael@0: GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); michael@0: event.button.device = gdk_device_manager_get_client_pointer(device_manager); michael@0: #endif michael@0: michael@0: // start our drag. michael@0: GdkDragContext *context = gtk_drag_begin(mHiddenWidget, michael@0: sourceList, michael@0: action, michael@0: 1, michael@0: &event); michael@0: michael@0: mSourceRegion = nullptr; michael@0: michael@0: if (context) { michael@0: StartDragSession(); michael@0: michael@0: // GTK uses another hidden window for receiving mouse events. michael@0: sGrabWidget = gtk_window_group_get_current_grab(window_group); michael@0: if (sGrabWidget) { michael@0: g_object_ref(sGrabWidget); michael@0: // Only motion and key events are required but connect to michael@0: // "event-after" as this is never blocked by other handlers. michael@0: g_signal_connect(sGrabWidget, "event-after", michael@0: G_CALLBACK(OnSourceGrabEventAfter), this); michael@0: } michael@0: // We don't have a drag end point yet. michael@0: mEndDragPoint = nsIntPoint(-1, -1); michael@0: } michael@0: else { michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: gtk_target_list_unref(sourceList); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsDragService::SetAlphaPixmap(SourceSurface *aSurface, michael@0: GdkDragContext *aContext, michael@0: int32_t aXOffset, michael@0: int32_t aYOffset, michael@0: const nsIntRect& dragRect) michael@0: { michael@0: #if (MOZ_WIDGET_GTK == 2) michael@0: GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget); michael@0: michael@0: // Transparent drag icons need, like a lot of transparency-related things, michael@0: // a compositing X window manager michael@0: if (!gdk_screen_is_composited(screen)) michael@0: return false; michael@0: michael@0: GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen); michael@0: if (!alphaColormap) michael@0: return false; michael@0: michael@0: GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height, michael@0: gdk_colormap_get_visual(alphaColormap)->depth); michael@0: if (!pixmap) michael@0: return false; michael@0: michael@0: gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap); michael@0: michael@0: // Make a gfxXlibSurface wrapped around the pixmap to render on michael@0: nsRefPtr xPixmapSurface = michael@0: nsWindow::GetSurfaceForGdkDrawable(GDK_DRAWABLE(pixmap), michael@0: dragRect.Size()); michael@0: if (!xPixmapSurface) michael@0: return false; michael@0: michael@0: RefPtr dt = michael@0: gfxPlatform::GetPlatform()-> michael@0: CreateDrawTargetForSurface(xPixmapSurface, IntSize(dragRect.width, dragRect.height)); michael@0: if (!dt) michael@0: return false; michael@0: michael@0: // Clear it... michael@0: dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height)); michael@0: michael@0: // ...and paint the drag image with translucency michael@0: dt->DrawSurface(aSurface, michael@0: Rect(0, 0, dragRect.width, dragRect.height), michael@0: Rect(0, 0, dragRect.width, dragRect.height), michael@0: DrawSurfaceOptions(), michael@0: DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE)); michael@0: michael@0: // The drag transaction addrefs the pixmap, so we can just unref it from us here michael@0: gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr, michael@0: aXOffset, aYOffset); michael@0: g_object_unref(pixmap); michael@0: return true; michael@0: #else michael@0: // TODO GTK3 michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::StartDragSession() michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::StartDragSession")); michael@0: return nsBaseDragService::StartDragSession(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::EndDragSession(bool aDoneDrag) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::EndDragSession %d", michael@0: aDoneDrag)); michael@0: michael@0: if (sGrabWidget) { michael@0: g_signal_handlers_disconnect_by_func(sGrabWidget, michael@0: FuncToGpointer(OnSourceGrabEventAfter), this); michael@0: g_object_unref(sGrabWidget); michael@0: sGrabWidget = nullptr; michael@0: michael@0: if (sMotionEventTimerID) { michael@0: g_source_remove(sMotionEventTimerID); michael@0: sMotionEventTimerID = 0; michael@0: } michael@0: if (sMotionEvent) { michael@0: gdk_event_free(sMotionEvent); michael@0: sMotionEvent = nullptr; michael@0: } michael@0: } michael@0: michael@0: // unset our drag action michael@0: SetDragAction(DRAGDROP_ACTION_NONE); michael@0: return nsBaseDragService::EndDragSession(aDoneDrag); michael@0: } michael@0: michael@0: // nsIDragSession michael@0: NS_IMETHODIMP michael@0: nsDragService::SetCanDrop(bool aCanDrop) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::SetCanDrop %d", michael@0: aCanDrop)); michael@0: mCanDrop = aCanDrop; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::GetCanDrop(bool *aCanDrop) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetCanDrop")); michael@0: *aCanDrop = mCanDrop; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // count the number of URIs in some text/uri-list format data. michael@0: static uint32_t michael@0: CountTextUriListItems(const char *data, michael@0: uint32_t datalen) michael@0: { michael@0: const char *p = data; michael@0: const char *endPtr = p + datalen; michael@0: uint32_t count = 0; michael@0: michael@0: while (p < endPtr) { michael@0: // skip whitespace (if any) michael@0: while (p < endPtr && *p != '\0' && isspace(*p)) michael@0: p++; michael@0: // if we aren't at the end of the line ... michael@0: if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') michael@0: count++; michael@0: // skip to the end of the line michael@0: while (p < endPtr && *p != '\0' && *p != '\n') michael@0: p++; michael@0: p++; // skip the actual newline as well. michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: // extract an item from text/uri-list formatted data and convert it to michael@0: // unicode. michael@0: static void michael@0: GetTextUriListItem(const char *data, michael@0: uint32_t datalen, michael@0: uint32_t aItemIndex, michael@0: char16_t **convertedText, michael@0: int32_t *convertedTextLen) michael@0: { michael@0: const char *p = data; michael@0: const char *endPtr = p + datalen; michael@0: unsigned int count = 0; michael@0: michael@0: *convertedText = nullptr; michael@0: while (p < endPtr) { michael@0: // skip whitespace (if any) michael@0: while (p < endPtr && *p != '\0' && isspace(*p)) michael@0: p++; michael@0: // if we aren't at the end of the line, we have a url michael@0: if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') michael@0: count++; michael@0: // this is the item we are after ... michael@0: if (aItemIndex + 1 == count) { michael@0: const char *q = p; michael@0: while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') michael@0: q++; michael@0: nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( michael@0: p, q - p, convertedText, convertedTextLen); michael@0: break; michael@0: } michael@0: // skip to the end of the line michael@0: while (p < endPtr && *p != '\0' && *p != '\n') michael@0: p++; michael@0: p++; // skip the actual newline as well. michael@0: } michael@0: michael@0: // didn't find the desired item, so just pass the whole lot michael@0: if (!*convertedText) { michael@0: nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( michael@0: data, datalen, convertedText, convertedTextLen); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::GetNumDropItems(uint32_t * aNumItems) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetNumDropItems")); michael@0: michael@0: if (!mTargetWidget) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("*** warning: GetNumDropItems \ michael@0: called without a valid target widget!\n")); michael@0: *aNumItems = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isList = IsTargetContextList(); michael@0: if (isList) michael@0: mSourceDataItems->Count(aNumItems); michael@0: else { michael@0: GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (mTargetDragData) { michael@0: const char *data = reinterpret_cast(mTargetDragData); michael@0: *aNumItems = CountTextUriListItems(data, mTargetDragDataLen); michael@0: } else michael@0: *aNumItems = 1; michael@0: } michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("%d items", *aNumItems)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::GetData(nsITransferable * aTransferable, michael@0: uint32_t aItemIndex) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetData %d", aItemIndex)); michael@0: michael@0: // make sure that we have a transferable michael@0: if (!aTransferable) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (!mTargetWidget) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("*** warning: GetData \ michael@0: called without a valid target widget!\n")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // get flavor list that includes all acceptable flavors (including michael@0: // ones obtained through conversion). Flavors are nsISupportsStrings michael@0: // so that they can be seen from JS. michael@0: nsCOMPtr flavorList; michael@0: nsresult rv = aTransferable->FlavorsTransferableCanImport( michael@0: getter_AddRefs(flavorList)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // count the number of flavors michael@0: uint32_t cnt; michael@0: flavorList->Count(&cnt); michael@0: unsigned int i; michael@0: michael@0: // check to see if this is an internal list michael@0: bool isList = IsTargetContextList(); michael@0: michael@0: if (isList) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("it's a list...")); michael@0: // find a matching flavor michael@0: for (i = 0; i < cnt; ++i) { michael@0: nsCOMPtr genericWrapper; michael@0: flavorList->GetElementAt(i, getter_AddRefs(genericWrapper)); michael@0: nsCOMPtr currentFlavor; michael@0: currentFlavor = do_QueryInterface(genericWrapper); michael@0: if (!currentFlavor) michael@0: continue; michael@0: michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: PR_LOG(sDragLm, michael@0: PR_LOG_DEBUG, michael@0: ("flavor is %s\n", (const char *)flavorStr)); michael@0: // get the item with the right index michael@0: nsCOMPtr genericItem; michael@0: mSourceDataItems->GetElementAt(aItemIndex, michael@0: getter_AddRefs(genericItem)); michael@0: nsCOMPtr item(do_QueryInterface(genericItem)); michael@0: if (!item) michael@0: continue; michael@0: michael@0: nsCOMPtr data; michael@0: uint32_t tmpDataLen = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("trying to get transfer data for %s\n", michael@0: (const char *)flavorStr)); michael@0: rv = item->GetTransferData(flavorStr, michael@0: getter_AddRefs(data), michael@0: &tmpDataLen); michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("failed.\n")); michael@0: continue; michael@0: } michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("succeeded.\n")); michael@0: rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen); michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(sDragLm, michael@0: PR_LOG_DEBUG, michael@0: ("fail to set transfer data into transferable!\n")); michael@0: continue; michael@0: } michael@0: // ok, we got the data michael@0: return NS_OK; michael@0: } michael@0: // if we got this far, we failed michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Now walk down the list of flavors. When we find one that is michael@0: // actually present, copy out the data into the transferable in that michael@0: // format. SetTransferData() implicitly handles conversions. michael@0: for ( i = 0; i < cnt; ++i ) { michael@0: nsCOMPtr genericWrapper; michael@0: flavorList->GetElementAt(i,getter_AddRefs(genericWrapper)); michael@0: nsCOMPtr currentFlavor; michael@0: currentFlavor = do_QueryInterface(genericWrapper); michael@0: if (currentFlavor) { michael@0: // find our gtk flavor michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE); michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("looking for data in type %s, gdk flavor %ld\n", michael@0: static_cast(flavorStr), gdkFlavor)); michael@0: bool dataFound = false; michael@0: if (gdkFlavor) { michael@0: GetTargetDragData(gdkFlavor); michael@0: } michael@0: if (mTargetDragData) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound = true\n")); michael@0: dataFound = true; michael@0: } michael@0: else { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound = false\n")); michael@0: michael@0: // Dragging and dropping from the file manager would cause us michael@0: // to parse the source text as a nsIFile URL. michael@0: if ( strcmp(flavorStr, kFileMime) == 0 ) { michael@0: gdkFlavor = gdk_atom_intern(kTextMime, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (!mTargetDragData) { michael@0: gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: } michael@0: if (mTargetDragData) { michael@0: const char* text = static_cast(mTargetDragData); michael@0: char16_t* convertedText = nullptr; michael@0: int32_t convertedTextLen = 0; michael@0: michael@0: GetTextUriListItem(text, mTargetDragDataLen, aItemIndex, michael@0: &convertedText, &convertedTextLen); michael@0: michael@0: if (convertedText) { michael@0: nsCOMPtr ioService = do_GetIOService(&rv); michael@0: nsCOMPtr fileURI; michael@0: nsresult rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText), michael@0: nullptr, nullptr, getter_AddRefs(fileURI)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr fileURL = do_QueryInterface(fileURI, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr file; michael@0: rv = fileURL->GetFile(getter_AddRefs(file)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // The common wrapping code at the end of michael@0: // this function assumes the data is text michael@0: // and calls text-specific operations. michael@0: // Make a secret hideout here for nsIFile michael@0: // objects and return early. michael@0: aTransferable->SetTransferData(flavorStr, file, michael@0: convertedTextLen); michael@0: g_free(convertedText); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: g_free(convertedText); michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // if we are looking for text/unicode and we fail to find it michael@0: // on the clipboard first, try again with text/plain. If that michael@0: // is present, convert it to unicode. michael@0: if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("we were looking for text/unicode... \ michael@0: trying with text/plain;charset=utf-8\n")); michael@0: gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (mTargetDragData) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("Got textplain data\n")); michael@0: const char* castedText = michael@0: reinterpret_cast(mTargetDragData); michael@0: char16_t* convertedText = nullptr; michael@0: NS_ConvertUTF8toUTF16 ucs2string(castedText, michael@0: mTargetDragDataLen); michael@0: convertedText = ToNewUnicode(ucs2string); michael@0: if ( convertedText ) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("successfully converted plain text \ michael@0: to unicode.\n")); michael@0: // out with the old, in with the new michael@0: g_free(mTargetDragData); michael@0: mTargetDragData = convertedText; michael@0: mTargetDragDataLen = ucs2string.Length() * 2; michael@0: dataFound = true; michael@0: } // if plain text data on clipboard michael@0: } else { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("we were looking for text/unicode... \ michael@0: trying again with text/plain\n")); michael@0: gdkFlavor = gdk_atom_intern(kTextMime, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (mTargetDragData) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("Got textplain data\n")); michael@0: const char* castedText = michael@0: reinterpret_cast(mTargetDragData); michael@0: char16_t* convertedText = nullptr; michael@0: int32_t convertedTextLen = 0; michael@0: nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( michael@0: castedText, mTargetDragDataLen, michael@0: &convertedText, &convertedTextLen); michael@0: if ( convertedText ) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("successfully converted plain text \ michael@0: to unicode.\n")); michael@0: // out with the old, in with the new michael@0: g_free(mTargetDragData); michael@0: mTargetDragData = convertedText; michael@0: mTargetDragDataLen = convertedTextLen * 2; michael@0: dataFound = true; michael@0: } // if plain text data on clipboard michael@0: } // if plain text flavor present michael@0: } // if plain text charset=utf-8 flavor present michael@0: } // if looking for text/unicode michael@0: michael@0: // if we are looking for text/x-moz-url and we failed to find michael@0: // it on the clipboard, try again with text/uri-list, and then michael@0: // _NETSCAPE_URL michael@0: if (strcmp(flavorStr, kURLMime) == 0) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("we were looking for text/x-moz-url...\ michael@0: trying again with text/uri-list\n")); michael@0: gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (mTargetDragData) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("Got text/uri-list data\n")); michael@0: const char *data = michael@0: reinterpret_cast(mTargetDragData); michael@0: char16_t* convertedText = nullptr; michael@0: int32_t convertedTextLen = 0; michael@0: michael@0: GetTextUriListItem(data, mTargetDragDataLen, aItemIndex, michael@0: &convertedText, &convertedTextLen); michael@0: michael@0: if ( convertedText ) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("successfully converted \ michael@0: _NETSCAPE_URL to unicode.\n")); michael@0: // out with the old, in with the new michael@0: g_free(mTargetDragData); michael@0: mTargetDragData = convertedText; michael@0: mTargetDragDataLen = convertedTextLen * 2; michael@0: dataFound = true; michael@0: } michael@0: } michael@0: else { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("failed to get text/uri-list data\n")); michael@0: } michael@0: if (!dataFound) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("we were looking for text/x-moz-url...\ michael@0: trying again with _NETSCAP_URL\n")); michael@0: gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE); michael@0: GetTargetDragData(gdkFlavor); michael@0: if (mTargetDragData) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("Got _NETSCAPE_URL data\n")); michael@0: const char* castedText = michael@0: reinterpret_cast(mTargetDragData); michael@0: char16_t* convertedText = nullptr; michael@0: int32_t convertedTextLen = 0; michael@0: nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen); michael@0: if ( convertedText ) { michael@0: PR_LOG(sDragLm, michael@0: PR_LOG_DEBUG, michael@0: ("successfully converted _NETSCAPE_URL \ michael@0: to unicode.\n")); michael@0: // out with the old, in with the new michael@0: g_free(mTargetDragData); michael@0: mTargetDragData = convertedText; michael@0: mTargetDragDataLen = convertedTextLen * 2; michael@0: dataFound = true; michael@0: } michael@0: } michael@0: else { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("failed to get _NETSCAPE_URL data\n")); michael@0: } michael@0: } michael@0: } michael@0: michael@0: } // else we try one last ditch effort to find our data michael@0: michael@0: if (dataFound) { michael@0: // the DOM only wants LF, so convert from MacOS line endings michael@0: // to DOM line endings. michael@0: nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( michael@0: flavorStr, michael@0: &mTargetDragData, michael@0: reinterpret_cast(&mTargetDragDataLen)); michael@0: michael@0: // put it into the transferable. michael@0: nsCOMPtr genericDataWrapper; michael@0: nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, michael@0: mTargetDragData, mTargetDragDataLen, michael@0: getter_AddRefs(genericDataWrapper)); michael@0: aTransferable->SetTransferData(flavorStr, michael@0: genericDataWrapper, michael@0: mTargetDragDataLen); michael@0: // we found one, get out of this loop! michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound and converted!\n")); michael@0: break; michael@0: } michael@0: } // if (currentFlavor) michael@0: } // foreach flavor michael@0: michael@0: return NS_OK; michael@0: michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDragService::IsDataFlavorSupported(const char *aDataFlavor, michael@0: bool *_retval) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::IsDataFlavorSupported %s", michael@0: aDataFlavor)); michael@0: if (!_retval) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // set this to no by default michael@0: *_retval = false; michael@0: michael@0: // check to make sure that we have a drag object set, here michael@0: if (!mTargetWidget) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("*** warning: IsDataFlavorSupported \ michael@0: called without a valid target widget!\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check to see if the target context is a list. michael@0: bool isList = IsTargetContextList(); michael@0: // if it is, just look in the internal data since we are the source michael@0: // for it. michael@0: if (isList) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("It's a list..")); michael@0: uint32_t numDragItems = 0; michael@0: // if we don't have mDataItems we didn't start this drag so it's michael@0: // an external client trying to fool us. michael@0: if (!mSourceDataItems) michael@0: return NS_OK; michael@0: mSourceDataItems->Count(&numDragItems); michael@0: for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) { michael@0: nsCOMPtr genericItem; michael@0: mSourceDataItems->GetElementAt(itemIndex, michael@0: getter_AddRefs(genericItem)); michael@0: nsCOMPtr currItem(do_QueryInterface(genericItem)); michael@0: if (currItem) { michael@0: nsCOMPtr flavorList; michael@0: currItem->FlavorsTransferableCanExport( michael@0: getter_AddRefs(flavorList)); michael@0: if (flavorList) { michael@0: uint32_t numFlavors; michael@0: flavorList->Count( &numFlavors ); michael@0: for ( uint32_t flavorIndex = 0; michael@0: flavorIndex < numFlavors ; michael@0: ++flavorIndex ) { michael@0: nsCOMPtr genericWrapper; michael@0: flavorList->GetElementAt(flavorIndex, michael@0: getter_AddRefs(genericWrapper)); michael@0: nsCOMPtr currentFlavor; michael@0: currentFlavor = do_QueryInterface(genericWrapper); michael@0: if (currentFlavor) { michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("checking %s against %s\n", michael@0: (const char *)flavorStr, aDataFlavor)); michael@0: if (strcmp(flavorStr, aDataFlavor) == 0) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("boioioioiooioioioing!\n")); michael@0: *_retval = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check the target context vs. this flavor, one at a time michael@0: GList *tmp; michael@0: for (tmp = gdk_drag_context_list_targets(mTargetDragContext); michael@0: tmp; tmp = tmp->next) { michael@0: /* Bug 331198 */ michael@0: GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); michael@0: gchar *name = nullptr; michael@0: name = gdk_atom_name(atom); michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("checking %s against %s\n", name, aDataFlavor)); michael@0: if (name && (strcmp(name, aDataFlavor) == 0)) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("good!\n")); michael@0: *_retval = true; michael@0: } michael@0: // check for automatic text/uri-list -> text/x-moz-url mapping michael@0: if (!*_retval && michael@0: name && michael@0: (strcmp(name, gTextUriListType) == 0) && michael@0: (strcmp(aDataFlavor, kURLMime) == 0 || michael@0: strcmp(aDataFlavor, kFileMime) == 0)) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("good! ( it's text/uri-list and \ michael@0: we're checking against text/x-moz-url )\n")); michael@0: *_retval = true; michael@0: } michael@0: // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping michael@0: if (!*_retval && michael@0: name && michael@0: (strcmp(name, gMozUrlType) == 0) && michael@0: (strcmp(aDataFlavor, kURLMime) == 0)) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("good! ( it's _NETSCAPE_URL and \ michael@0: we're checking against text/x-moz-url )\n")); michael@0: *_retval = true; michael@0: } michael@0: // check for auto text/plain -> text/unicode mapping michael@0: if (!*_retval && michael@0: name && michael@0: (strcmp(name, kTextMime) == 0) && michael@0: ((strcmp(aDataFlavor, kUnicodeMime) == 0) || michael@0: (strcmp(aDataFlavor, kFileMime) == 0))) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("good! ( it's text plain and we're checking \ michael@0: against text/unicode or application/x-moz-file)\n")); michael@0: *_retval = true; michael@0: } michael@0: g_free(name); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsDragService::ReplyToDragMotion() michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("nsDragService::ReplyToDragMotion %d", mCanDrop)); michael@0: michael@0: GdkDragAction action = (GdkDragAction)0; michael@0: if (mCanDrop) { michael@0: // notify the dragger if we can drop michael@0: switch (mDragAction) { michael@0: case DRAGDROP_ACTION_COPY: michael@0: action = GDK_ACTION_COPY; michael@0: break; michael@0: case DRAGDROP_ACTION_LINK: michael@0: action = GDK_ACTION_LINK; michael@0: break; michael@0: default: michael@0: action = GDK_ACTION_MOVE; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: gdk_drag_status(mTargetDragContext, action, mTargetTime); michael@0: } michael@0: michael@0: void michael@0: nsDragService::TargetDataReceived(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gint aX, michael@0: gint aY, michael@0: GtkSelectionData *aSelectionData, michael@0: guint aInfo, michael@0: guint32 aTime) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::TargetDataReceived")); michael@0: TargetResetData(); michael@0: mTargetDragDataReceived = true; michael@0: gint len = gtk_selection_data_get_length(aSelectionData); michael@0: const guchar* data = gtk_selection_data_get_data(aSelectionData); michael@0: if (len > 0 && data) { michael@0: mTargetDragDataLen = len; michael@0: mTargetDragData = g_malloc(mTargetDragDataLen); michael@0: memcpy(mTargetDragData, data, mTargetDragDataLen); michael@0: } michael@0: else { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("Failed to get data. selection data len was %d\n", michael@0: mTargetDragDataLen)); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsDragService::IsTargetContextList(void) michael@0: { michael@0: bool retval = false; michael@0: michael@0: // gMimeListType drags only work for drags within a single process. The michael@0: // gtk_drag_get_source_widget() function will return nullptr if the source michael@0: // of the drag is another app, so we use it to check if a gMimeListType michael@0: // drop will work or not. michael@0: if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr) michael@0: return retval; michael@0: michael@0: GList *tmp; michael@0: michael@0: // walk the list of context targets and see if one of them is a list michael@0: // of items. michael@0: for (tmp = gdk_drag_context_list_targets(mTargetDragContext); michael@0: tmp; tmp = tmp->next) { michael@0: /* Bug 331198 */ michael@0: GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); michael@0: gchar *name = nullptr; michael@0: name = gdk_atom_name(atom); michael@0: if (name && strcmp(name, gMimeListType) == 0) michael@0: retval = true; michael@0: g_free(name); michael@0: if (retval) michael@0: break; michael@0: } michael@0: return retval; michael@0: } michael@0: michael@0: // Maximum time to wait for a "drag_received" arrived, in microseconds michael@0: #define NS_DND_TIMEOUT 500000 michael@0: michael@0: void michael@0: nsDragService::GetTargetDragData(GdkAtom aFlavor) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("getting data flavor %d\n", aFlavor)); michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("mLastWidget is %p and mLastContext is %p\n", michael@0: mTargetWidget.get(), michael@0: mTargetDragContext.get())); michael@0: // reset our target data areas michael@0: TargetResetData(); michael@0: gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); michael@0: michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("about to start inner iteration.")); michael@0: PRTime entryTime = PR_Now(); michael@0: while (!mTargetDragDataReceived && mDoingDrag) { michael@0: // check the number of iterations michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("doing iteration...\n")); michael@0: PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ michael@0: if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; michael@0: gtk_main_iteration(); michael@0: } michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("finished inner iteration\n")); michael@0: } michael@0: michael@0: void michael@0: nsDragService::TargetResetData(void) michael@0: { michael@0: mTargetDragDataReceived = false; michael@0: // make sure to free old data if we have to michael@0: g_free(mTargetDragData); michael@0: mTargetDragData = 0; michael@0: mTargetDragDataLen = 0; michael@0: } michael@0: michael@0: GtkTargetList * michael@0: nsDragService::GetSourceList(void) michael@0: { michael@0: if (!mSourceDataItems) michael@0: return nullptr; michael@0: nsTArray targetArray; michael@0: GtkTargetEntry *targets; michael@0: GtkTargetList *targetList = 0; michael@0: uint32_t targetCount = 0; michael@0: unsigned int numDragItems = 0; michael@0: michael@0: mSourceDataItems->Count(&numDragItems); michael@0: michael@0: // Check to see if we're dragging > 1 item. michael@0: if (numDragItems > 1) { michael@0: // as the Xdnd protocol only supports a single item (or is it just michael@0: // gtk's implementation?), we don't advertise all flavours listed michael@0: // in the nsITransferable. michael@0: michael@0: // the application/x-moz-internal-item-list format, which preserves michael@0: // all information for drags within the same mozilla instance. michael@0: GtkTargetEntry *listTarget = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: listTarget->target = g_strdup(gMimeListType); michael@0: listTarget->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("automatically adding target %s\n", listTarget->target)); michael@0: targetArray.AppendElement(listTarget); michael@0: michael@0: // check what flavours are supported so we can decide what other michael@0: // targets to advertise. michael@0: nsCOMPtr genericItem; michael@0: mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); michael@0: nsCOMPtr currItem(do_QueryInterface(genericItem)); michael@0: michael@0: if (currItem) { michael@0: nsCOMPtr flavorList; michael@0: currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); michael@0: if (flavorList) { michael@0: uint32_t numFlavors; michael@0: flavorList->Count( &numFlavors ); michael@0: for (uint32_t flavorIndex = 0; michael@0: flavorIndex < numFlavors ; michael@0: ++flavorIndex ) { michael@0: nsCOMPtr genericWrapper; michael@0: flavorList->GetElementAt(flavorIndex, michael@0: getter_AddRefs(genericWrapper)); michael@0: nsCOMPtr currentFlavor; michael@0: currentFlavor = do_QueryInterface(genericWrapper); michael@0: if (currentFlavor) { michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: michael@0: // check if text/x-moz-url is supported. michael@0: // If so, advertise michael@0: // text/uri-list. michael@0: if (strcmp(flavorStr, kURLMime) == 0) { michael@0: listTarget = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: listTarget->target = g_strdup(gTextUriListType); michael@0: listTarget->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("automatically adding target %s\n", michael@0: listTarget->target)); michael@0: targetArray.AppendElement(listTarget); michael@0: } michael@0: } michael@0: } // foreach flavor in item michael@0: } // if valid flavor list michael@0: } // if item is a transferable michael@0: } else if (numDragItems == 1) { michael@0: nsCOMPtr genericItem; michael@0: mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); michael@0: nsCOMPtr currItem(do_QueryInterface(genericItem)); michael@0: if (currItem) { michael@0: nsCOMPtr flavorList; michael@0: currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); michael@0: if (flavorList) { michael@0: uint32_t numFlavors; michael@0: flavorList->Count( &numFlavors ); michael@0: for (uint32_t flavorIndex = 0; michael@0: flavorIndex < numFlavors ; michael@0: ++flavorIndex ) { michael@0: nsCOMPtr genericWrapper; michael@0: flavorList->GetElementAt(flavorIndex, michael@0: getter_AddRefs(genericWrapper)); michael@0: nsCOMPtr currentFlavor; michael@0: currentFlavor = do_QueryInterface(genericWrapper); michael@0: if (currentFlavor) { michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: GtkTargetEntry *target = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: target->target = g_strdup(flavorStr); michael@0: target->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("adding target %s\n", target->target)); michael@0: targetArray.AppendElement(target); michael@0: // Check to see if this is text/unicode. michael@0: // If it is, add text/plain michael@0: // since we automatically support text/plain michael@0: // if we support text/unicode. michael@0: if (strcmp(flavorStr, kUnicodeMime) == 0) { michael@0: GtkTargetEntry *plainUTF8Target = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: plainUTF8Target->target = g_strdup(gTextPlainUTF8Type); michael@0: plainUTF8Target->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("automatically adding target %s\n", michael@0: plainUTF8Target->target)); michael@0: targetArray.AppendElement(plainUTF8Target); michael@0: michael@0: GtkTargetEntry *plainTarget = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: plainTarget->target = g_strdup(kTextMime); michael@0: plainTarget->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("automatically adding target %s\n", michael@0: plainTarget->target)); michael@0: targetArray.AppendElement(plainTarget); michael@0: } michael@0: // Check to see if this is the x-moz-url type. michael@0: // If it is, add _NETSCAPE_URL michael@0: // this is a type used by everybody. michael@0: if (strcmp(flavorStr, kURLMime) == 0) { michael@0: GtkTargetEntry *urlTarget = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); michael@0: urlTarget->target = g_strdup(gMozUrlType); michael@0: urlTarget->flags = 0; michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("automatically adding target %s\n", michael@0: urlTarget->target)); michael@0: targetArray.AppendElement(urlTarget); michael@0: } michael@0: } michael@0: } // foreach flavor in item michael@0: } // if valid flavor list michael@0: } // if item is a transferable michael@0: } // if it is a single item drag michael@0: michael@0: // get all the elements that we created. michael@0: targetCount = targetArray.Length(); michael@0: if (targetCount) { michael@0: // allocate space to create the list of valid targets michael@0: targets = michael@0: (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount); michael@0: uint32_t targetIndex; michael@0: for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) { michael@0: GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex); michael@0: // this is a string reference but it will be freed later. michael@0: targets[targetIndex].target = disEntry->target; michael@0: targets[targetIndex].flags = disEntry->flags; michael@0: targets[targetIndex].info = 0; michael@0: } michael@0: targetList = gtk_target_list_new(targets, targetCount); michael@0: // clean up the target list michael@0: for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) { michael@0: GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex); michael@0: g_free(thisTarget->target); michael@0: g_free(thisTarget); michael@0: } michael@0: g_free(targets); michael@0: } michael@0: return targetList; michael@0: } michael@0: michael@0: void michael@0: nsDragService::SourceEndDragSession(GdkDragContext *aContext, michael@0: gint aResult) michael@0: { michael@0: // this just releases the list of data items that we provide michael@0: mSourceDataItems = nullptr; michael@0: michael@0: if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd) michael@0: // EndDragSession() was already called on drop michael@0: // or SourceEndDragSession on drag-failed michael@0: return; michael@0: michael@0: if (mEndDragPoint.x < 0) { michael@0: // We don't have a drag end point, so guess michael@0: gint x, y; michael@0: GdkDisplay* display = gdk_display_get_default(); michael@0: if (display) { michael@0: gdk_display_get_pointer(display, nullptr, &x, &y, nullptr); michael@0: SetDragEndPoint(nsIntPoint(x, y)); michael@0: } michael@0: } michael@0: michael@0: // Either the drag was aborted or the drop occurred outside the app. michael@0: // The dropEffect of mDataTransfer is not updated for motion outside the michael@0: // app, but is needed for the dragend event, so set it now. michael@0: michael@0: uint32_t dropEffect; michael@0: michael@0: if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) { michael@0: michael@0: // With GTK+ versions 2.10.x and prior the drag may have been michael@0: // cancelled (but no drag-failed signal would have been sent). michael@0: // aContext->dest_window will be non-nullptr only if the drop was michael@0: // sent. michael@0: GdkDragAction action = michael@0: gdk_drag_context_get_dest_window(aContext) ? michael@0: gdk_drag_context_get_actions(aContext) : (GdkDragAction)0; michael@0: michael@0: // Only one bit of action should be set, but, just in case someone michael@0: // does something funny, erring away from MOVE, and not recording michael@0: // unusual action combinations as NONE. michael@0: if (!action) michael@0: dropEffect = DRAGDROP_ACTION_NONE; michael@0: else if (action & GDK_ACTION_COPY) michael@0: dropEffect = DRAGDROP_ACTION_COPY; michael@0: else if (action & GDK_ACTION_LINK) michael@0: dropEffect = DRAGDROP_ACTION_LINK; michael@0: else if (action & GDK_ACTION_MOVE) michael@0: dropEffect = DRAGDROP_ACTION_MOVE; michael@0: else michael@0: dropEffect = DRAGDROP_ACTION_COPY; michael@0: michael@0: } else { michael@0: michael@0: dropEffect = DRAGDROP_ACTION_NONE; michael@0: michael@0: if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) { michael@0: mUserCancelled = true; michael@0: } michael@0: } michael@0: michael@0: if (mDataTransfer) { michael@0: mDataTransfer->SetDropEffectInt(dropEffect); michael@0: } michael@0: michael@0: // Schedule the appropriate drag end dom events. michael@0: Schedule(eDragTaskSourceEnd, nullptr, nullptr, nsIntPoint(), 0); michael@0: } michael@0: michael@0: static void michael@0: CreateUriList(nsISupportsArray *items, gchar **text, gint *length) michael@0: { michael@0: uint32_t i, count; michael@0: GString *uriList = g_string_new(nullptr); michael@0: michael@0: items->Count(&count); michael@0: for (i = 0; i < count; i++) { michael@0: nsCOMPtr genericItem; michael@0: items->GetElementAt(i, getter_AddRefs(genericItem)); michael@0: nsCOMPtr item; michael@0: item = do_QueryInterface(genericItem); michael@0: michael@0: if (item) { michael@0: uint32_t tmpDataLen = 0; michael@0: void *tmpData = nullptr; michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr data; michael@0: rv = item->GetTransferData(kURLMime, michael@0: getter_AddRefs(data), michael@0: &tmpDataLen); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime, michael@0: data, michael@0: &tmpData, michael@0: tmpDataLen); michael@0: char* plainTextData = nullptr; michael@0: char16_t* castedUnicode = reinterpret_cast michael@0: (tmpData); michael@0: int32_t plainTextLen = 0; michael@0: nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText( michael@0: castedUnicode, michael@0: tmpDataLen / 2, michael@0: &plainTextData, michael@0: &plainTextLen); michael@0: if (plainTextData) { michael@0: int32_t j; michael@0: michael@0: // text/x-moz-url is of form url + "\n" + title. michael@0: // We just want the url. michael@0: for (j = 0; j < plainTextLen; j++) michael@0: if (plainTextData[j] == '\n' || michael@0: plainTextData[j] == '\r') { michael@0: plainTextData[j] = '\0'; michael@0: break; michael@0: } michael@0: g_string_append(uriList, plainTextData); michael@0: g_string_append(uriList, "\r\n"); michael@0: // this wasn't allocated with glib michael@0: free(plainTextData); michael@0: } michael@0: if (tmpData) { michael@0: // this wasn't allocated with glib michael@0: free(tmpData); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: *text = uriList->str; michael@0: *length = uriList->len + 1; michael@0: g_string_free(uriList, FALSE); // don't free the data michael@0: } michael@0: michael@0: michael@0: void michael@0: nsDragService::SourceDataGet(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: GtkSelectionData *aSelectionData, michael@0: guint32 aTime) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::SourceDataGet")); michael@0: GdkAtom target = gtk_selection_data_get_target(aSelectionData); michael@0: nsXPIDLCString mimeFlavor; michael@0: gchar *typeName = 0; michael@0: typeName = gdk_atom_name(target); michael@0: if (!typeName) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("failed to get atom name.\n")); michael@0: return; michael@0: } michael@0: michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("Type is %s\n", typeName)); michael@0: // make a copy since |nsXPIDLCString| won't use |g_free|... michael@0: mimeFlavor.Adopt(strdup(typeName)); michael@0: g_free(typeName); michael@0: // check to make sure that we have data items to return. michael@0: if (!mSourceDataItems) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("Failed to get our data items\n")); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr genericItem; michael@0: mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); michael@0: nsCOMPtr item; michael@0: item = do_QueryInterface(genericItem); michael@0: if (item) { michael@0: // if someone was asking for text/plain, lookup unicode instead so michael@0: // we can convert it. michael@0: bool needToDoConversionToPlainText = false; michael@0: const char* actualFlavor = mimeFlavor; michael@0: if (strcmp(mimeFlavor, kTextMime) == 0 || michael@0: strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) { michael@0: actualFlavor = kUnicodeMime; michael@0: needToDoConversionToPlainText = true; michael@0: } michael@0: // if someone was asking for _NETSCAPE_URL we need to convert to michael@0: // plain text but we also need to look for x-moz-url michael@0: else if (strcmp(mimeFlavor, gMozUrlType) == 0) { michael@0: actualFlavor = kURLMime; michael@0: needToDoConversionToPlainText = true; michael@0: } michael@0: // if someone was asking for text/uri-list we need to convert to michael@0: // plain text. michael@0: else if (strcmp(mimeFlavor, gTextUriListType) == 0) { michael@0: actualFlavor = gTextUriListType; michael@0: needToDoConversionToPlainText = true; michael@0: } michael@0: else michael@0: actualFlavor = mimeFlavor; michael@0: michael@0: uint32_t tmpDataLen = 0; michael@0: void *tmpData = nullptr; michael@0: nsresult rv; michael@0: nsCOMPtr data; michael@0: rv = item->GetTransferData(actualFlavor, michael@0: getter_AddRefs(data), michael@0: &tmpDataLen); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsPrimitiveHelpers::CreateDataFromPrimitive (actualFlavor, data, michael@0: &tmpData, tmpDataLen); michael@0: // if required, do the extra work to convert unicode to plain michael@0: // text and replace the output values with the plain text. michael@0: if (needToDoConversionToPlainText) { michael@0: char* plainTextData = nullptr; michael@0: char16_t* castedUnicode = reinterpret_cast michael@0: (tmpData); michael@0: int32_t plainTextLen = 0; michael@0: if (strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) { michael@0: plainTextData = michael@0: ToNewUTF8String( michael@0: nsDependentString(castedUnicode, tmpDataLen / 2), michael@0: (uint32_t*)&plainTextLen); michael@0: } else { michael@0: nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText( michael@0: castedUnicode, michael@0: tmpDataLen / 2, michael@0: &plainTextData, michael@0: &plainTextLen); michael@0: } michael@0: if (tmpData) { michael@0: // this was not allocated using glib michael@0: free(tmpData); michael@0: tmpData = plainTextData; michael@0: tmpDataLen = plainTextLen; michael@0: } michael@0: } michael@0: if (tmpData) { michael@0: // this copies the data michael@0: gtk_selection_data_set(aSelectionData, target, michael@0: 8, michael@0: (guchar *)tmpData, tmpDataLen); michael@0: // this wasn't allocated with glib michael@0: free(tmpData); michael@0: } michael@0: } else { michael@0: if (strcmp(mimeFlavor, gTextUriListType) == 0) { michael@0: // fall back for text/uri-list michael@0: gchar *uriList; michael@0: gint length; michael@0: CreateUriList(mSourceDataItems, &uriList, &length); michael@0: gtk_selection_data_set(aSelectionData, target, michael@0: 8, (guchar *)uriList, length); michael@0: g_free(uriList); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void nsDragService::SetDragIcon(GdkDragContext* aContext) michael@0: { michael@0: if (!mHasImage && !mSelection) michael@0: return; michael@0: michael@0: nsIntRect dragRect; michael@0: nsPresContext* pc; michael@0: RefPtr surface; michael@0: DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY, michael@0: &dragRect, &surface, &pc); michael@0: if (!pc) michael@0: return; michael@0: michael@0: int32_t sx = mScreenX, sy = mScreenY; michael@0: ConvertToUnscaledDevPixels(pc, &sx, &sy); michael@0: michael@0: int32_t offsetX = sx - dragRect.x; michael@0: int32_t offsetY = sy - dragRect.y; michael@0: michael@0: // If a popup is set as the drag image, use its widget. Otherwise, use michael@0: // the surface that DrawDrag created. michael@0: if (mDragPopup) { michael@0: GtkWidget* gtkWidget = nullptr; michael@0: nsIFrame* frame = mDragPopup->GetPrimaryFrame(); michael@0: if (frame) { michael@0: // DrawDrag ensured that this is a popup frame. michael@0: nsCOMPtr widget = frame->GetNearestWidget(); michael@0: if (widget) { michael@0: gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET); michael@0: if (gtkWidget) { michael@0: OpenDragPopup(); michael@0: gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (surface) { michael@0: if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) { michael@0: GdkPixbuf* dragPixbuf = michael@0: nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height); michael@0: if (dragPixbuf) { michael@0: gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY); michael@0: g_object_unref(dragPixbuf); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: invisibleSourceDragBegin(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gpointer aData) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragBegin")); michael@0: nsDragService *dragService = (nsDragService *)aData; michael@0: michael@0: dragService->SetDragIcon(aContext); michael@0: } michael@0: michael@0: static void michael@0: invisibleSourceDragDataGet(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: GtkSelectionData *aSelectionData, michael@0: guint aInfo, michael@0: guint32 aTime, michael@0: gpointer aData) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragDataGet")); michael@0: nsDragService *dragService = (nsDragService *)aData; michael@0: dragService->SourceDataGet(aWidget, aContext, michael@0: aSelectionData, aTime); michael@0: } michael@0: michael@0: static gboolean michael@0: invisibleSourceDragFailed(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gint aResult, michael@0: gpointer aData) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragFailed %i", aResult)); michael@0: nsDragService *dragService = (nsDragService *)aData; michael@0: // End the drag session now (rather than waiting for the drag-end signal) michael@0: // so that operations performed on dropEffect == none can start immediately michael@0: // rather than waiting for the drag-failed animation to finish. michael@0: dragService->SourceEndDragSession(aContext, aResult); michael@0: michael@0: // We should return TRUE to disable the drag-failed animation iff the michael@0: // source performed an operation when dropEffect was none, but the handler michael@0: // of the dragend DOM event doesn't provide this information. michael@0: return FALSE; michael@0: } michael@0: michael@0: static void michael@0: invisibleSourceDragEnd(GtkWidget *aWidget, michael@0: GdkDragContext *aContext, michael@0: gpointer aData) michael@0: { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragEnd")); michael@0: nsDragService *dragService = (nsDragService *)aData; michael@0: michael@0: // The drag has ended. Release the hostages! michael@0: dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS); michael@0: } michael@0: michael@0: // The following methods handle responding to GTK drag signals and michael@0: // tracking state between these signals. michael@0: // michael@0: // In general, GTK does not expect us to run the event loop while handling its michael@0: // drag signals, however our drag event handlers may run the michael@0: // event loop, most often to fetch information about the drag data. michael@0: // michael@0: // GTK, for example, uses the return value from drag-motion signals to michael@0: // determine whether drag-leave signals should be sent. If an event loop is michael@0: // run during drag-motion the XdndLeave message can get processed but when GTK michael@0: // receives the message it does not yet know that it needs to send the michael@0: // drag-leave signal to our widget. michael@0: // michael@0: // After a drag-drop signal, we need to reply with gtk_drag_finish(). michael@0: // However, gtk_drag_finish should happen after the drag-drop signal handler michael@0: // returns so that when the Motif drag protocol is used, the michael@0: // XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START michael@0: // reply sent on return from the drag-drop signal handler. michael@0: // michael@0: // Similarly drag-end for a successful drag and drag-failed are not good michael@0: // times to run a nested event loop as gtk_drag_drop_finished() and michael@0: // gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove michael@0: // drop_timeout until after at least the first of these signals is sent. michael@0: // Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop michael@0: // timeout) could cause gtk_drag_drop_finished to be called again with the michael@0: // same GtkDragSourceInfo, which won't like being destroyed twice. michael@0: // michael@0: // Therefore we reply to the signals immediately and schedule a task to michael@0: // dispatch the Gecko events, which may run the event loop. michael@0: // michael@0: // Action in response to drag-leave signals is also delayed until the event michael@0: // loop runs again so that we find out whether a drag-drop signal follows. michael@0: // michael@0: // A single task is scheduled to manage responses to all three GTK signals. michael@0: // If further signals are received while the task is scheduled, the scheduled michael@0: // response is updated, sometimes effectively compressing successive signals. michael@0: // michael@0: // No Gecko drag events are dispatched (during nested event loops) while other michael@0: // Gecko drag events are in flight. This helps event handlers that may not michael@0: // expect nested events, while accessing an event's dataTransfer for example. michael@0: michael@0: gboolean michael@0: nsDragService::ScheduleMotionEvent(nsWindow *aWindow, michael@0: GdkDragContext *aDragContext, michael@0: nsIntPoint aWindowPoint, guint aTime) michael@0: { michael@0: if (mScheduledTask == eDragTaskMotion) { michael@0: // The drag source has sent another motion message before we've michael@0: // replied to the previous. That shouldn't happen with Xdnd. The michael@0: // spec for Motif drags is less clear, but we'll just update the michael@0: // scheduled task with the new position reply only to the most michael@0: // recent message. michael@0: NS_WARNING("Drag Motion message received before previous reply was sent"); michael@0: } michael@0: michael@0: // Returning TRUE means we'll reply with a status message, unless we first michael@0: // get a leave. michael@0: return Schedule(eDragTaskMotion, aWindow, aDragContext, michael@0: aWindowPoint, aTime); michael@0: } michael@0: michael@0: void michael@0: nsDragService::ScheduleLeaveEvent() michael@0: { michael@0: // We don't know at this stage whether a drop signal will immediately michael@0: // follow. If the drop signal gets sent it will happen before we return michael@0: // to the main loop and the scheduled leave task will be replaced. michael@0: if (!Schedule(eDragTaskLeave, nullptr, nullptr, nsIntPoint(), 0)) { michael@0: NS_WARNING("Drag leave after drop"); michael@0: } michael@0: } michael@0: michael@0: gboolean michael@0: nsDragService::ScheduleDropEvent(nsWindow *aWindow, michael@0: GdkDragContext *aDragContext, michael@0: nsIntPoint aWindowPoint, guint aTime) michael@0: { michael@0: if (!Schedule(eDragTaskDrop, aWindow, michael@0: aDragContext, aWindowPoint, aTime)) { michael@0: NS_WARNING("Additional drag drop ignored"); michael@0: return FALSE; michael@0: } michael@0: michael@0: SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset()); michael@0: michael@0: // We'll reply with gtk_drag_finish(). michael@0: return TRUE; michael@0: } michael@0: michael@0: gboolean michael@0: nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, michael@0: GdkDragContext *aDragContext, michael@0: nsIntPoint aWindowPoint, guint aTime) michael@0: { michael@0: // If there is an existing leave or motion task scheduled, then that michael@0: // will be replaced. When the new task is run, it will dispatch michael@0: // any necessary leave or motion events. michael@0: michael@0: // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled michael@0: // drop event (which could happen if the drop event has not been processed michael@0: // within the allowed time). Otherwise, if we haven't yet run a scheduled michael@0: // drop or end task, just say that we are not ready to receive another michael@0: // drop. michael@0: if (mScheduledTask == eDragTaskSourceEnd || michael@0: (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) michael@0: return FALSE; michael@0: michael@0: mScheduledTask = aTask; michael@0: mPendingWindow = aWindow; michael@0: mPendingDragContext = aDragContext; michael@0: mPendingWindowPoint = aWindowPoint; michael@0: mPendingTime = aTime; michael@0: michael@0: if (!mTaskSource) { michael@0: // High priority is used here because the native events involved have michael@0: // already waited at default priority. Perhaps a lower than default michael@0: // priority could be used for motion tasks because there is a chance michael@0: // that a leave or drop is waiting, but managing different priorities michael@0: // may not be worth the effort. Motion tasks shouldn't queue up as michael@0: // they should be throttled based on replies. michael@0: mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, michael@0: this, nullptr); michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: gboolean michael@0: nsDragService::TaskDispatchCallback(gpointer data) michael@0: { michael@0: nsRefPtr dragService = static_cast(data); michael@0: return dragService->RunScheduledTask(); michael@0: } michael@0: michael@0: gboolean michael@0: nsDragService::RunScheduledTask() michael@0: { michael@0: if (mTargetWindow && mTargetWindow != mPendingWindow) { michael@0: PR_LOG(sDragLm, PR_LOG_DEBUG, michael@0: ("nsDragService: dispatch drag leave (%p)\n", michael@0: mTargetWindow.get())); michael@0: mTargetWindow-> michael@0: DispatchDragEvent(NS_DRAGDROP_EXIT, mTargetWindowPoint, 0); michael@0: michael@0: if (!mSourceNode) { michael@0: // The drag that was initiated in a different app. End the drag michael@0: // session, since we're done with it for now (until the user drags michael@0: // back into this app). michael@0: EndDragSession(false); michael@0: } michael@0: } michael@0: michael@0: // It is possible that the pending state has been updated during dispatch michael@0: // of the leave event. That's fine. michael@0: michael@0: // Now we collect the pending state because, from this point on, we want michael@0: // to use the same state for all events dispatched. All state is updated michael@0: // so that when other tasks are scheduled during dispatch here, this michael@0: // task is considered to have already been run. michael@0: bool positionHasChanged = michael@0: mPendingWindow != mTargetWindow || michael@0: mPendingWindowPoint != mTargetWindowPoint; michael@0: DragTask task = mScheduledTask; michael@0: mScheduledTask = eDragTaskNone; michael@0: mTargetWindow = mPendingWindow.forget(); michael@0: mTargetWindowPoint = mPendingWindowPoint; michael@0: michael@0: if (task == eDragTaskLeave || task == eDragTaskSourceEnd) { michael@0: if (task == eDragTaskSourceEnd) { michael@0: // Dispatch drag end events. michael@0: EndDragSession(true); michael@0: } michael@0: michael@0: // Nothing more to do michael@0: // Returning false removes the task source from the event loop. michael@0: mTaskSource = 0; michael@0: return FALSE; michael@0: } michael@0: michael@0: // This may be the start of a destination drag session. michael@0: StartDragSession(); michael@0: michael@0: // mTargetWidget may be nullptr if the window has been destroyed. michael@0: // (The leave event is not scheduled if a drop task is still scheduled.) michael@0: // We still reply appropriately to indicate that the drop will or didn't michael@0: // succeeed. michael@0: mTargetWidget = mTargetWindow->GetMozContainerWidget(); michael@0: mTargetDragContext.steal(mPendingDragContext); michael@0: mTargetTime = mPendingTime; michael@0: michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model michael@0: // (as at 27 December 2010) indicates that a "drop" event should only be michael@0: // fired (at the current target element) if the current drag operation is michael@0: // not none. The current drag operation will only be set to a non-none michael@0: // value during a "dragover" event. michael@0: // michael@0: // If the user has ended the drag before any dragover events have been michael@0: // sent, then the spec recommends skipping the drop (because the current michael@0: // drag operation is none). However, here we assume that, by releasing michael@0: // the mouse button, the user has indicated that they want to drop, so we michael@0: // proceed with the drop where possible. michael@0: // michael@0: // In order to make the events appear to content in the same way as if the michael@0: // spec is being followed we make sure to dispatch a "dragover" event with michael@0: // appropriate coordinates and check canDrop before the "drop" event. michael@0: // michael@0: // When the Xdnd protocol is used for source/destination communication (as michael@0: // should be the case with GTK source applications) a dragover event michael@0: // should have already been sent during the drag-motion signal, which michael@0: // would have already been received because XdndDrop messages do not michael@0: // contain a position. However, we can't assume the same when the Motif michael@0: // protocol is used. michael@0: if (task == eDragTaskMotion || positionHasChanged) { michael@0: UpdateDragAction(); michael@0: DispatchMotionEvents(); michael@0: michael@0: if (task == eDragTaskMotion) { michael@0: // Reply to tell the source whether we can drop and what michael@0: // action would be taken. michael@0: ReplyToDragMotion(); michael@0: } michael@0: } michael@0: michael@0: if (task == eDragTaskDrop) { michael@0: gboolean success = DispatchDropEvent(); michael@0: michael@0: // Perhaps we should set the del parameter to TRUE when the drag michael@0: // action is move, but we don't know whether the data was successfully michael@0: // transferred. michael@0: gtk_drag_finish(mTargetDragContext, success, michael@0: /* del = */ FALSE, mTargetTime); michael@0: michael@0: // This drag is over, so clear out our reference to the previous michael@0: // window. michael@0: mTargetWindow = nullptr; michael@0: // Make sure to end the drag session. If this drag started in a michael@0: // different app, we won't get a drag_end signal to end it from. michael@0: EndDragSession(true); michael@0: } michael@0: michael@0: // We're done with the drag context. michael@0: mTargetWidget = nullptr; michael@0: mTargetDragContext = nullptr; michael@0: michael@0: // If we got another drag signal while running the sheduled task, that michael@0: // must have happened while running a nested event loop. Leave the task michael@0: // source on the event loop. michael@0: if (mScheduledTask != eDragTaskNone) michael@0: return TRUE; michael@0: michael@0: // We have no task scheduled. michael@0: // Returning false removes the task source from the event loop. michael@0: mTaskSource = 0; michael@0: return FALSE; michael@0: } michael@0: michael@0: // This will update the drag action based on the information in the michael@0: // drag context. Gtk gets this from a combination of the key settings michael@0: // and what the source is offering. michael@0: michael@0: void michael@0: nsDragService::UpdateDragAction() michael@0: { michael@0: // This doesn't look right. dragSession.dragAction is used by michael@0: // nsContentUtils::SetDataTransferInEvent() to set the initial michael@0: // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be michael@0: // more appropriate. GdkDragContext::actions should be used to set michael@0: // dataTransfer.effectAllowed, which doesn't currently happen with michael@0: // external sources. michael@0: michael@0: // default is to do nothing michael@0: int action = nsIDragService::DRAGDROP_ACTION_NONE; michael@0: GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext); michael@0: michael@0: // set the default just in case nothing matches below michael@0: if (gdkAction & GDK_ACTION_DEFAULT) michael@0: action = nsIDragService::DRAGDROP_ACTION_MOVE; michael@0: michael@0: // first check to see if move is set michael@0: if (gdkAction & GDK_ACTION_MOVE) michael@0: action = nsIDragService::DRAGDROP_ACTION_MOVE; michael@0: michael@0: // then fall to the others michael@0: else if (gdkAction & GDK_ACTION_LINK) michael@0: action = nsIDragService::DRAGDROP_ACTION_LINK; michael@0: michael@0: // copy is ctrl michael@0: else if (gdkAction & GDK_ACTION_COPY) michael@0: action = nsIDragService::DRAGDROP_ACTION_COPY; michael@0: michael@0: // update the drag information michael@0: SetDragAction(action); michael@0: } michael@0: michael@0: void michael@0: nsDragService::DispatchMotionEvents() michael@0: { michael@0: mCanDrop = false; michael@0: michael@0: FireDragEventAtSource(NS_DRAGDROP_DRAG); michael@0: michael@0: mTargetWindow-> michael@0: DispatchDragEvent(NS_DRAGDROP_OVER, mTargetWindowPoint, mTargetTime); michael@0: } michael@0: michael@0: // Returns true if the drop was successful michael@0: gboolean michael@0: nsDragService::DispatchDropEvent() michael@0: { michael@0: // We need to check IsDestroyed here because the nsRefPtr michael@0: // only protects this from being deleted, it does NOT protect michael@0: // against nsView::~nsView() calling Destroy() on it, bug 378273. michael@0: if (mTargetWindow->IsDestroyed()) michael@0: return FALSE; michael@0: michael@0: uint32_t msg = mCanDrop ? NS_DRAGDROP_DROP : NS_DRAGDROP_EXIT; michael@0: michael@0: mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime); michael@0: michael@0: return mCanDrop; michael@0: }