|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* vim: set ts=4 et sw=4 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsDragService.h" |
|
8 #include "nsIObserverService.h" |
|
9 #include "nsWidgetsCID.h" |
|
10 #include "nsWindow.h" |
|
11 #include "nsIServiceManager.h" |
|
12 #include "nsXPCOM.h" |
|
13 #include "nsISupportsPrimitives.h" |
|
14 #include "nsIIOService.h" |
|
15 #include "nsIFileURL.h" |
|
16 #include "nsNetUtil.h" |
|
17 #include "prlog.h" |
|
18 #include "nsTArray.h" |
|
19 #include "nsPrimitiveHelpers.h" |
|
20 #include "prtime.h" |
|
21 #include "prthread.h" |
|
22 #include <gtk/gtk.h> |
|
23 #include <gdk/gdkx.h> |
|
24 #include "nsCRT.h" |
|
25 #include "mozilla/BasicEvents.h" |
|
26 #include "mozilla/Services.h" |
|
27 |
|
28 #include "gfxASurface.h" |
|
29 #include "gfxXlibSurface.h" |
|
30 #include "gfxContext.h" |
|
31 #include "nsImageToPixbuf.h" |
|
32 #include "nsPresContext.h" |
|
33 #include "nsIContent.h" |
|
34 #include "nsIDocument.h" |
|
35 #include "nsISelection.h" |
|
36 #include "nsViewManager.h" |
|
37 #include "nsIFrame.h" |
|
38 #include "nsGtkUtils.h" |
|
39 #include "mozilla/gfx/2D.h" |
|
40 #include "gfxPlatform.h" |
|
41 |
|
42 using namespace mozilla; |
|
43 using namespace mozilla::gfx; |
|
44 |
|
45 // This sets how opaque the drag image is |
|
46 #define DRAG_IMAGE_ALPHA_LEVEL 0.5 |
|
47 |
|
48 // These values are copied from GtkDragResult (rather than using GtkDragResult |
|
49 // directly) so that this code can be compiled against versions of GTK+ that |
|
50 // do not have GtkDragResult. |
|
51 // GtkDragResult is available from GTK+ version 2.12. |
|
52 enum { |
|
53 MOZ_GTK_DRAG_RESULT_SUCCESS, |
|
54 MOZ_GTK_DRAG_RESULT_NO_TARGET |
|
55 }; |
|
56 |
|
57 static PRLogModuleInfo *sDragLm = nullptr; |
|
58 |
|
59 // data used for synthetic periodic motion events sent to the source widget |
|
60 // grabbing real events for the drag. |
|
61 static guint sMotionEventTimerID; |
|
62 static GdkEvent *sMotionEvent; |
|
63 static GtkWidget *sGrabWidget; |
|
64 |
|
65 static const char gMimeListType[] = "application/x-moz-internal-item-list"; |
|
66 static const char gMozUrlType[] = "_NETSCAPE_URL"; |
|
67 static const char gTextUriListType[] = "text/uri-list"; |
|
68 static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8"; |
|
69 |
|
70 static void |
|
71 invisibleSourceDragBegin(GtkWidget *aWidget, |
|
72 GdkDragContext *aContext, |
|
73 gpointer aData); |
|
74 |
|
75 static void |
|
76 invisibleSourceDragEnd(GtkWidget *aWidget, |
|
77 GdkDragContext *aContext, |
|
78 gpointer aData); |
|
79 |
|
80 static gboolean |
|
81 invisibleSourceDragFailed(GtkWidget *aWidget, |
|
82 GdkDragContext *aContext, |
|
83 gint aResult, |
|
84 gpointer aData); |
|
85 |
|
86 static void |
|
87 invisibleSourceDragDataGet(GtkWidget *aWidget, |
|
88 GdkDragContext *aContext, |
|
89 GtkSelectionData *aSelectionData, |
|
90 guint aInfo, |
|
91 guint32 aTime, |
|
92 gpointer aData); |
|
93 |
|
94 nsDragService::nsDragService() |
|
95 : mScheduledTask(eDragTaskNone) |
|
96 , mTaskSource(0) |
|
97 { |
|
98 // We have to destroy the hidden widget before the event loop stops |
|
99 // running. |
|
100 nsCOMPtr<nsIObserverService> obsServ = |
|
101 mozilla::services::GetObserverService(); |
|
102 obsServ->AddObserver(this, "quit-application", false); |
|
103 |
|
104 // our hidden source widget |
|
105 mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP); |
|
106 // make sure that the widget is realized so that |
|
107 // we can use it as a drag source. |
|
108 gtk_widget_realize(mHiddenWidget); |
|
109 // hook up our internal signals so that we can get some feedback |
|
110 // from our drag source |
|
111 g_signal_connect(mHiddenWidget, "drag_begin", |
|
112 G_CALLBACK(invisibleSourceDragBegin), this); |
|
113 g_signal_connect(mHiddenWidget, "drag_data_get", |
|
114 G_CALLBACK(invisibleSourceDragDataGet), this); |
|
115 g_signal_connect(mHiddenWidget, "drag_end", |
|
116 G_CALLBACK(invisibleSourceDragEnd), this); |
|
117 // drag-failed is available from GTK+ version 2.12 |
|
118 guint dragFailedID = g_signal_lookup("drag-failed", |
|
119 G_TYPE_FROM_INSTANCE(mHiddenWidget)); |
|
120 if (dragFailedID) { |
|
121 g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0, |
|
122 g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), |
|
123 this, nullptr), |
|
124 FALSE); |
|
125 } |
|
126 |
|
127 // set up our logging module |
|
128 if (!sDragLm) |
|
129 sDragLm = PR_NewLogModule("nsDragService"); |
|
130 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::nsDragService")); |
|
131 mCanDrop = false; |
|
132 mTargetDragDataReceived = false; |
|
133 mTargetDragData = 0; |
|
134 mTargetDragDataLen = 0; |
|
135 } |
|
136 |
|
137 nsDragService::~nsDragService() |
|
138 { |
|
139 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::~nsDragService")); |
|
140 if (mTaskSource) |
|
141 g_source_remove(mTaskSource); |
|
142 |
|
143 } |
|
144 |
|
145 NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver) |
|
146 |
|
147 /* static */ nsDragService* |
|
148 nsDragService::GetInstance() |
|
149 { |
|
150 static const nsIID iid = NS_DRAGSERVICE_CID; |
|
151 nsCOMPtr<nsIDragService> dragService = do_GetService(iid); |
|
152 return static_cast<nsDragService*>(dragService.get()); |
|
153 // We rely on XPCOM keeping a reference to the service. |
|
154 } |
|
155 |
|
156 // nsIObserver |
|
157 |
|
158 NS_IMETHODIMP |
|
159 nsDragService::Observe(nsISupports *aSubject, const char *aTopic, |
|
160 const char16_t *aData) |
|
161 { |
|
162 if (!nsCRT::strcmp(aTopic, "quit-application")) { |
|
163 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
164 ("nsDragService::Observe(\"quit-application\")")); |
|
165 if (mHiddenWidget) { |
|
166 gtk_widget_destroy(mHiddenWidget); |
|
167 mHiddenWidget = 0; |
|
168 } |
|
169 TargetResetData(); |
|
170 } else { |
|
171 NS_NOTREACHED("unexpected topic"); |
|
172 return NS_ERROR_UNEXPECTED; |
|
173 } |
|
174 |
|
175 return NS_OK; |
|
176 } |
|
177 |
|
178 // Support for periodic drag events |
|
179 |
|
180 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
|
181 // and the Xdnd protocol both recommend that drag events are sent periodically, |
|
182 // but GTK does not normally provide this. |
|
183 // |
|
184 // Here GTK is periodically stimulated by copies of the most recent mouse |
|
185 // motion events so as to send drag position messages to the destination when |
|
186 // appropriate (after it has received a status event from the previous |
|
187 // message). |
|
188 // |
|
189 // (If events were sent only on the destination side then the destination |
|
190 // would have no message to which it could reply with a drag status. Without |
|
191 // sending a drag status to the source, the destination would not be able to |
|
192 // change its feedback re whether it could accept the drop, and so the |
|
193 // source's behavior on drop will not be consistent.) |
|
194 |
|
195 static gboolean |
|
196 DispatchMotionEventCopy(gpointer aData) |
|
197 { |
|
198 // Clear the timer id before OnSourceGrabEventAfter is called during event |
|
199 // dispatch. |
|
200 sMotionEventTimerID = 0; |
|
201 |
|
202 GdkEvent *event = sMotionEvent; |
|
203 sMotionEvent = nullptr; |
|
204 // If there is no longer a grab on the widget, then the drag is over and |
|
205 // there is no need to continue drag motion. |
|
206 if (gtk_widget_has_grab(sGrabWidget)) { |
|
207 gtk_propagate_event(sGrabWidget, event); |
|
208 } |
|
209 gdk_event_free(event); |
|
210 |
|
211 // Cancel this timer; |
|
212 // We've already started another if the motion event was dispatched. |
|
213 return FALSE; |
|
214 } |
|
215 |
|
216 static void |
|
217 OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data) |
|
218 { |
|
219 // If there is no longer a grab on the widget, then the drag motion is |
|
220 // over (though the data may not be fetched yet). |
|
221 if (!gtk_widget_has_grab(sGrabWidget)) |
|
222 return; |
|
223 |
|
224 if (event->type == GDK_MOTION_NOTIFY) { |
|
225 if (sMotionEvent) { |
|
226 gdk_event_free(sMotionEvent); |
|
227 } |
|
228 sMotionEvent = gdk_event_copy(event); |
|
229 |
|
230 // Update the cursor position. The last of these recorded gets used for |
|
231 // the NS_DRAGDROP_END event. |
|
232 nsDragService *dragService = static_cast<nsDragService*>(user_data); |
|
233 dragService->SetDragEndPoint(nsIntPoint(event->motion.x_root, |
|
234 event->motion.y_root)); |
|
235 } else if (sMotionEvent && (event->type == GDK_KEY_PRESS || |
|
236 event->type == GDK_KEY_RELEASE)) { |
|
237 // Update modifier state from key events. |
|
238 sMotionEvent->motion.state = event->key.state; |
|
239 } else { |
|
240 return; |
|
241 } |
|
242 |
|
243 if (sMotionEventTimerID) { |
|
244 g_source_remove(sMotionEventTimerID); |
|
245 } |
|
246 |
|
247 // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source |
|
248 // and lower than GTK's idle source that sends drag position messages after |
|
249 // motion-notify signals. |
|
250 // |
|
251 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
|
252 // recommends an interval of 350ms +/- 200ms. |
|
253 sMotionEventTimerID = |
|
254 g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350, |
|
255 DispatchMotionEventCopy, nullptr, nullptr); |
|
256 } |
|
257 |
|
258 static GtkWindow* |
|
259 GetGtkWindow(nsIDOMDocument *aDocument) |
|
260 { |
|
261 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument); |
|
262 if (!doc) |
|
263 return nullptr; |
|
264 |
|
265 nsCOMPtr<nsIPresShell> presShell = doc->GetShell(); |
|
266 if (!presShell) |
|
267 return nullptr; |
|
268 |
|
269 nsRefPtr<nsViewManager> vm = presShell->GetViewManager(); |
|
270 if (!vm) |
|
271 return nullptr; |
|
272 |
|
273 nsCOMPtr<nsIWidget> widget; |
|
274 vm->GetRootWidget(getter_AddRefs(widget)); |
|
275 if (!widget) |
|
276 return nullptr; |
|
277 |
|
278 GtkWidget *gtkWidget = |
|
279 static_cast<nsWindow*>(widget.get())->GetMozContainerWidget(); |
|
280 if (!gtkWidget) |
|
281 return nullptr; |
|
282 |
|
283 GtkWidget *toplevel = nullptr; |
|
284 toplevel = gtk_widget_get_toplevel(gtkWidget); |
|
285 if (!GTK_IS_WINDOW(toplevel)) |
|
286 return nullptr; |
|
287 |
|
288 return GTK_WINDOW(toplevel); |
|
289 } |
|
290 |
|
291 // nsIDragService |
|
292 |
|
293 NS_IMETHODIMP |
|
294 nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, |
|
295 nsISupportsArray * aArrayTransferables, |
|
296 nsIScriptableRegion * aRegion, |
|
297 uint32_t aActionType) |
|
298 { |
|
299 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::InvokeDragSession")); |
|
300 |
|
301 // If the previous source drag has not yet completed, signal handlers need |
|
302 // to be removed from sGrabWidget and dragend needs to be dispatched to |
|
303 // the source node, but we can't call EndDragSession yet because we don't |
|
304 // know whether or not the drag succeeded. |
|
305 if (mSourceNode) |
|
306 return NS_ERROR_NOT_AVAILABLE; |
|
307 |
|
308 nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode, |
|
309 aArrayTransferables, |
|
310 aRegion, aActionType); |
|
311 NS_ENSURE_SUCCESS(rv, rv); |
|
312 |
|
313 // make sure that we have an array of transferables to use |
|
314 if (!aArrayTransferables) |
|
315 return NS_ERROR_INVALID_ARG; |
|
316 // set our reference to the transferables. this will also addref |
|
317 // the transferables since we're going to hang onto this beyond the |
|
318 // length of this call |
|
319 mSourceDataItems = aArrayTransferables; |
|
320 // get the list of items we offer for drags |
|
321 GtkTargetList *sourceList = GetSourceList(); |
|
322 |
|
323 if (!sourceList) |
|
324 return NS_OK; |
|
325 |
|
326 // stored temporarily until the drag-begin signal has been received |
|
327 mSourceRegion = aRegion; |
|
328 |
|
329 // save our action type |
|
330 GdkDragAction action = GDK_ACTION_DEFAULT; |
|
331 |
|
332 if (aActionType & DRAGDROP_ACTION_COPY) |
|
333 action = (GdkDragAction)(action | GDK_ACTION_COPY); |
|
334 if (aActionType & DRAGDROP_ACTION_MOVE) |
|
335 action = (GdkDragAction)(action | GDK_ACTION_MOVE); |
|
336 if (aActionType & DRAGDROP_ACTION_LINK) |
|
337 action = (GdkDragAction)(action | GDK_ACTION_LINK); |
|
338 |
|
339 // Create a fake event for the drag so we can pass the time (so to speak). |
|
340 // If we don't do this, then, when the timestamp for the pending button |
|
341 // release event is used for the ungrab, the ungrab can fail due to the |
|
342 // timestamp being _earlier_ than CurrentTime. |
|
343 GdkEvent event; |
|
344 memset(&event, 0, sizeof(GdkEvent)); |
|
345 event.type = GDK_BUTTON_PRESS; |
|
346 event.button.window = gtk_widget_get_window(mHiddenWidget); |
|
347 event.button.time = nsWindow::GetLastUserInputTime(); |
|
348 |
|
349 // Put the drag widget in the window group of the source node so that the |
|
350 // gtk_grab_add during gtk_drag_begin is effective. |
|
351 // gtk_window_get_group(nullptr) returns the default window group. |
|
352 GtkWindowGroup *window_group = |
|
353 gtk_window_get_group(GetGtkWindow(mSourceDocument)); |
|
354 gtk_window_group_add_window(window_group, |
|
355 GTK_WINDOW(mHiddenWidget)); |
|
356 |
|
357 #if (MOZ_WIDGET_GTK == 3) |
|
358 // Get device for event source |
|
359 GdkDisplay *display = gdk_display_get_default(); |
|
360 GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); |
|
361 event.button.device = gdk_device_manager_get_client_pointer(device_manager); |
|
362 #endif |
|
363 |
|
364 // start our drag. |
|
365 GdkDragContext *context = gtk_drag_begin(mHiddenWidget, |
|
366 sourceList, |
|
367 action, |
|
368 1, |
|
369 &event); |
|
370 |
|
371 mSourceRegion = nullptr; |
|
372 |
|
373 if (context) { |
|
374 StartDragSession(); |
|
375 |
|
376 // GTK uses another hidden window for receiving mouse events. |
|
377 sGrabWidget = gtk_window_group_get_current_grab(window_group); |
|
378 if (sGrabWidget) { |
|
379 g_object_ref(sGrabWidget); |
|
380 // Only motion and key events are required but connect to |
|
381 // "event-after" as this is never blocked by other handlers. |
|
382 g_signal_connect(sGrabWidget, "event-after", |
|
383 G_CALLBACK(OnSourceGrabEventAfter), this); |
|
384 } |
|
385 // We don't have a drag end point yet. |
|
386 mEndDragPoint = nsIntPoint(-1, -1); |
|
387 } |
|
388 else { |
|
389 rv = NS_ERROR_FAILURE; |
|
390 } |
|
391 |
|
392 gtk_target_list_unref(sourceList); |
|
393 |
|
394 return rv; |
|
395 } |
|
396 |
|
397 bool |
|
398 nsDragService::SetAlphaPixmap(SourceSurface *aSurface, |
|
399 GdkDragContext *aContext, |
|
400 int32_t aXOffset, |
|
401 int32_t aYOffset, |
|
402 const nsIntRect& dragRect) |
|
403 { |
|
404 #if (MOZ_WIDGET_GTK == 2) |
|
405 GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget); |
|
406 |
|
407 // Transparent drag icons need, like a lot of transparency-related things, |
|
408 // a compositing X window manager |
|
409 if (!gdk_screen_is_composited(screen)) |
|
410 return false; |
|
411 |
|
412 GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen); |
|
413 if (!alphaColormap) |
|
414 return false; |
|
415 |
|
416 GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height, |
|
417 gdk_colormap_get_visual(alphaColormap)->depth); |
|
418 if (!pixmap) |
|
419 return false; |
|
420 |
|
421 gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap); |
|
422 |
|
423 // Make a gfxXlibSurface wrapped around the pixmap to render on |
|
424 nsRefPtr<gfxASurface> xPixmapSurface = |
|
425 nsWindow::GetSurfaceForGdkDrawable(GDK_DRAWABLE(pixmap), |
|
426 dragRect.Size()); |
|
427 if (!xPixmapSurface) |
|
428 return false; |
|
429 |
|
430 RefPtr<DrawTarget> dt = |
|
431 gfxPlatform::GetPlatform()-> |
|
432 CreateDrawTargetForSurface(xPixmapSurface, IntSize(dragRect.width, dragRect.height)); |
|
433 if (!dt) |
|
434 return false; |
|
435 |
|
436 // Clear it... |
|
437 dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height)); |
|
438 |
|
439 // ...and paint the drag image with translucency |
|
440 dt->DrawSurface(aSurface, |
|
441 Rect(0, 0, dragRect.width, dragRect.height), |
|
442 Rect(0, 0, dragRect.width, dragRect.height), |
|
443 DrawSurfaceOptions(), |
|
444 DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE)); |
|
445 |
|
446 // The drag transaction addrefs the pixmap, so we can just unref it from us here |
|
447 gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr, |
|
448 aXOffset, aYOffset); |
|
449 g_object_unref(pixmap); |
|
450 return true; |
|
451 #else |
|
452 // TODO GTK3 |
|
453 return false; |
|
454 #endif |
|
455 } |
|
456 |
|
457 NS_IMETHODIMP |
|
458 nsDragService::StartDragSession() |
|
459 { |
|
460 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::StartDragSession")); |
|
461 return nsBaseDragService::StartDragSession(); |
|
462 } |
|
463 |
|
464 NS_IMETHODIMP |
|
465 nsDragService::EndDragSession(bool aDoneDrag) |
|
466 { |
|
467 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::EndDragSession %d", |
|
468 aDoneDrag)); |
|
469 |
|
470 if (sGrabWidget) { |
|
471 g_signal_handlers_disconnect_by_func(sGrabWidget, |
|
472 FuncToGpointer(OnSourceGrabEventAfter), this); |
|
473 g_object_unref(sGrabWidget); |
|
474 sGrabWidget = nullptr; |
|
475 |
|
476 if (sMotionEventTimerID) { |
|
477 g_source_remove(sMotionEventTimerID); |
|
478 sMotionEventTimerID = 0; |
|
479 } |
|
480 if (sMotionEvent) { |
|
481 gdk_event_free(sMotionEvent); |
|
482 sMotionEvent = nullptr; |
|
483 } |
|
484 } |
|
485 |
|
486 // unset our drag action |
|
487 SetDragAction(DRAGDROP_ACTION_NONE); |
|
488 return nsBaseDragService::EndDragSession(aDoneDrag); |
|
489 } |
|
490 |
|
491 // nsIDragSession |
|
492 NS_IMETHODIMP |
|
493 nsDragService::SetCanDrop(bool aCanDrop) |
|
494 { |
|
495 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::SetCanDrop %d", |
|
496 aCanDrop)); |
|
497 mCanDrop = aCanDrop; |
|
498 return NS_OK; |
|
499 } |
|
500 |
|
501 NS_IMETHODIMP |
|
502 nsDragService::GetCanDrop(bool *aCanDrop) |
|
503 { |
|
504 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetCanDrop")); |
|
505 *aCanDrop = mCanDrop; |
|
506 return NS_OK; |
|
507 } |
|
508 |
|
509 // count the number of URIs in some text/uri-list format data. |
|
510 static uint32_t |
|
511 CountTextUriListItems(const char *data, |
|
512 uint32_t datalen) |
|
513 { |
|
514 const char *p = data; |
|
515 const char *endPtr = p + datalen; |
|
516 uint32_t count = 0; |
|
517 |
|
518 while (p < endPtr) { |
|
519 // skip whitespace (if any) |
|
520 while (p < endPtr && *p != '\0' && isspace(*p)) |
|
521 p++; |
|
522 // if we aren't at the end of the line ... |
|
523 if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') |
|
524 count++; |
|
525 // skip to the end of the line |
|
526 while (p < endPtr && *p != '\0' && *p != '\n') |
|
527 p++; |
|
528 p++; // skip the actual newline as well. |
|
529 } |
|
530 return count; |
|
531 } |
|
532 |
|
533 // extract an item from text/uri-list formatted data and convert it to |
|
534 // unicode. |
|
535 static void |
|
536 GetTextUriListItem(const char *data, |
|
537 uint32_t datalen, |
|
538 uint32_t aItemIndex, |
|
539 char16_t **convertedText, |
|
540 int32_t *convertedTextLen) |
|
541 { |
|
542 const char *p = data; |
|
543 const char *endPtr = p + datalen; |
|
544 unsigned int count = 0; |
|
545 |
|
546 *convertedText = nullptr; |
|
547 while (p < endPtr) { |
|
548 // skip whitespace (if any) |
|
549 while (p < endPtr && *p != '\0' && isspace(*p)) |
|
550 p++; |
|
551 // if we aren't at the end of the line, we have a url |
|
552 if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') |
|
553 count++; |
|
554 // this is the item we are after ... |
|
555 if (aItemIndex + 1 == count) { |
|
556 const char *q = p; |
|
557 while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') |
|
558 q++; |
|
559 nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( |
|
560 p, q - p, convertedText, convertedTextLen); |
|
561 break; |
|
562 } |
|
563 // skip to the end of the line |
|
564 while (p < endPtr && *p != '\0' && *p != '\n') |
|
565 p++; |
|
566 p++; // skip the actual newline as well. |
|
567 } |
|
568 |
|
569 // didn't find the desired item, so just pass the whole lot |
|
570 if (!*convertedText) { |
|
571 nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( |
|
572 data, datalen, convertedText, convertedTextLen); |
|
573 } |
|
574 } |
|
575 |
|
576 NS_IMETHODIMP |
|
577 nsDragService::GetNumDropItems(uint32_t * aNumItems) |
|
578 { |
|
579 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetNumDropItems")); |
|
580 |
|
581 if (!mTargetWidget) { |
|
582 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
583 ("*** warning: GetNumDropItems \ |
|
584 called without a valid target widget!\n")); |
|
585 *aNumItems = 0; |
|
586 return NS_OK; |
|
587 } |
|
588 |
|
589 bool isList = IsTargetContextList(); |
|
590 if (isList) |
|
591 mSourceDataItems->Count(aNumItems); |
|
592 else { |
|
593 GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
|
594 GetTargetDragData(gdkFlavor); |
|
595 if (mTargetDragData) { |
|
596 const char *data = reinterpret_cast<char*>(mTargetDragData); |
|
597 *aNumItems = CountTextUriListItems(data, mTargetDragDataLen); |
|
598 } else |
|
599 *aNumItems = 1; |
|
600 } |
|
601 PR_LOG(sDragLm, PR_LOG_DEBUG, ("%d items", *aNumItems)); |
|
602 return NS_OK; |
|
603 } |
|
604 |
|
605 |
|
606 NS_IMETHODIMP |
|
607 nsDragService::GetData(nsITransferable * aTransferable, |
|
608 uint32_t aItemIndex) |
|
609 { |
|
610 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::GetData %d", aItemIndex)); |
|
611 |
|
612 // make sure that we have a transferable |
|
613 if (!aTransferable) |
|
614 return NS_ERROR_INVALID_ARG; |
|
615 |
|
616 if (!mTargetWidget) { |
|
617 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
618 ("*** warning: GetData \ |
|
619 called without a valid target widget!\n")); |
|
620 return NS_ERROR_FAILURE; |
|
621 } |
|
622 |
|
623 // get flavor list that includes all acceptable flavors (including |
|
624 // ones obtained through conversion). Flavors are nsISupportsStrings |
|
625 // so that they can be seen from JS. |
|
626 nsCOMPtr<nsISupportsArray> flavorList; |
|
627 nsresult rv = aTransferable->FlavorsTransferableCanImport( |
|
628 getter_AddRefs(flavorList)); |
|
629 if (NS_FAILED(rv)) |
|
630 return rv; |
|
631 |
|
632 // count the number of flavors |
|
633 uint32_t cnt; |
|
634 flavorList->Count(&cnt); |
|
635 unsigned int i; |
|
636 |
|
637 // check to see if this is an internal list |
|
638 bool isList = IsTargetContextList(); |
|
639 |
|
640 if (isList) { |
|
641 PR_LOG(sDragLm, PR_LOG_DEBUG, ("it's a list...")); |
|
642 // find a matching flavor |
|
643 for (i = 0; i < cnt; ++i) { |
|
644 nsCOMPtr<nsISupports> genericWrapper; |
|
645 flavorList->GetElementAt(i, getter_AddRefs(genericWrapper)); |
|
646 nsCOMPtr<nsISupportsCString> currentFlavor; |
|
647 currentFlavor = do_QueryInterface(genericWrapper); |
|
648 if (!currentFlavor) |
|
649 continue; |
|
650 |
|
651 nsXPIDLCString flavorStr; |
|
652 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
653 PR_LOG(sDragLm, |
|
654 PR_LOG_DEBUG, |
|
655 ("flavor is %s\n", (const char *)flavorStr)); |
|
656 // get the item with the right index |
|
657 nsCOMPtr<nsISupports> genericItem; |
|
658 mSourceDataItems->GetElementAt(aItemIndex, |
|
659 getter_AddRefs(genericItem)); |
|
660 nsCOMPtr<nsITransferable> item(do_QueryInterface(genericItem)); |
|
661 if (!item) |
|
662 continue; |
|
663 |
|
664 nsCOMPtr<nsISupports> data; |
|
665 uint32_t tmpDataLen = 0; |
|
666 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
667 ("trying to get transfer data for %s\n", |
|
668 (const char *)flavorStr)); |
|
669 rv = item->GetTransferData(flavorStr, |
|
670 getter_AddRefs(data), |
|
671 &tmpDataLen); |
|
672 if (NS_FAILED(rv)) { |
|
673 PR_LOG(sDragLm, PR_LOG_DEBUG, ("failed.\n")); |
|
674 continue; |
|
675 } |
|
676 PR_LOG(sDragLm, PR_LOG_DEBUG, ("succeeded.\n")); |
|
677 rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen); |
|
678 if (NS_FAILED(rv)) { |
|
679 PR_LOG(sDragLm, |
|
680 PR_LOG_DEBUG, |
|
681 ("fail to set transfer data into transferable!\n")); |
|
682 continue; |
|
683 } |
|
684 // ok, we got the data |
|
685 return NS_OK; |
|
686 } |
|
687 // if we got this far, we failed |
|
688 return NS_ERROR_FAILURE; |
|
689 } |
|
690 |
|
691 // Now walk down the list of flavors. When we find one that is |
|
692 // actually present, copy out the data into the transferable in that |
|
693 // format. SetTransferData() implicitly handles conversions. |
|
694 for ( i = 0; i < cnt; ++i ) { |
|
695 nsCOMPtr<nsISupports> genericWrapper; |
|
696 flavorList->GetElementAt(i,getter_AddRefs(genericWrapper)); |
|
697 nsCOMPtr<nsISupportsCString> currentFlavor; |
|
698 currentFlavor = do_QueryInterface(genericWrapper); |
|
699 if (currentFlavor) { |
|
700 // find our gtk flavor |
|
701 nsXPIDLCString flavorStr; |
|
702 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
703 GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE); |
|
704 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
705 ("looking for data in type %s, gdk flavor %ld\n", |
|
706 static_cast<const char*>(flavorStr), gdkFlavor)); |
|
707 bool dataFound = false; |
|
708 if (gdkFlavor) { |
|
709 GetTargetDragData(gdkFlavor); |
|
710 } |
|
711 if (mTargetDragData) { |
|
712 PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound = true\n")); |
|
713 dataFound = true; |
|
714 } |
|
715 else { |
|
716 PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound = false\n")); |
|
717 |
|
718 // Dragging and dropping from the file manager would cause us |
|
719 // to parse the source text as a nsIFile URL. |
|
720 if ( strcmp(flavorStr, kFileMime) == 0 ) { |
|
721 gdkFlavor = gdk_atom_intern(kTextMime, FALSE); |
|
722 GetTargetDragData(gdkFlavor); |
|
723 if (!mTargetDragData) { |
|
724 gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
|
725 GetTargetDragData(gdkFlavor); |
|
726 } |
|
727 if (mTargetDragData) { |
|
728 const char* text = static_cast<char*>(mTargetDragData); |
|
729 char16_t* convertedText = nullptr; |
|
730 int32_t convertedTextLen = 0; |
|
731 |
|
732 GetTextUriListItem(text, mTargetDragDataLen, aItemIndex, |
|
733 &convertedText, &convertedTextLen); |
|
734 |
|
735 if (convertedText) { |
|
736 nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); |
|
737 nsCOMPtr<nsIURI> fileURI; |
|
738 nsresult rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText), |
|
739 nullptr, nullptr, getter_AddRefs(fileURI)); |
|
740 if (NS_SUCCEEDED(rv)) { |
|
741 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv); |
|
742 if (NS_SUCCEEDED(rv)) { |
|
743 nsCOMPtr<nsIFile> file; |
|
744 rv = fileURL->GetFile(getter_AddRefs(file)); |
|
745 if (NS_SUCCEEDED(rv)) { |
|
746 // The common wrapping code at the end of |
|
747 // this function assumes the data is text |
|
748 // and calls text-specific operations. |
|
749 // Make a secret hideout here for nsIFile |
|
750 // objects and return early. |
|
751 aTransferable->SetTransferData(flavorStr, file, |
|
752 convertedTextLen); |
|
753 g_free(convertedText); |
|
754 return NS_OK; |
|
755 } |
|
756 } |
|
757 } |
|
758 g_free(convertedText); |
|
759 } |
|
760 continue; |
|
761 } |
|
762 } |
|
763 |
|
764 // if we are looking for text/unicode and we fail to find it |
|
765 // on the clipboard first, try again with text/plain. If that |
|
766 // is present, convert it to unicode. |
|
767 if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { |
|
768 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
769 ("we were looking for text/unicode... \ |
|
770 trying with text/plain;charset=utf-8\n")); |
|
771 gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE); |
|
772 GetTargetDragData(gdkFlavor); |
|
773 if (mTargetDragData) { |
|
774 PR_LOG(sDragLm, PR_LOG_DEBUG, ("Got textplain data\n")); |
|
775 const char* castedText = |
|
776 reinterpret_cast<char*>(mTargetDragData); |
|
777 char16_t* convertedText = nullptr; |
|
778 NS_ConvertUTF8toUTF16 ucs2string(castedText, |
|
779 mTargetDragDataLen); |
|
780 convertedText = ToNewUnicode(ucs2string); |
|
781 if ( convertedText ) { |
|
782 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
783 ("successfully converted plain text \ |
|
784 to unicode.\n")); |
|
785 // out with the old, in with the new |
|
786 g_free(mTargetDragData); |
|
787 mTargetDragData = convertedText; |
|
788 mTargetDragDataLen = ucs2string.Length() * 2; |
|
789 dataFound = true; |
|
790 } // if plain text data on clipboard |
|
791 } else { |
|
792 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
793 ("we were looking for text/unicode... \ |
|
794 trying again with text/plain\n")); |
|
795 gdkFlavor = gdk_atom_intern(kTextMime, FALSE); |
|
796 GetTargetDragData(gdkFlavor); |
|
797 if (mTargetDragData) { |
|
798 PR_LOG(sDragLm, PR_LOG_DEBUG, ("Got textplain data\n")); |
|
799 const char* castedText = |
|
800 reinterpret_cast<char*>(mTargetDragData); |
|
801 char16_t* convertedText = nullptr; |
|
802 int32_t convertedTextLen = 0; |
|
803 nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode( |
|
804 castedText, mTargetDragDataLen, |
|
805 &convertedText, &convertedTextLen); |
|
806 if ( convertedText ) { |
|
807 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
808 ("successfully converted plain text \ |
|
809 to unicode.\n")); |
|
810 // out with the old, in with the new |
|
811 g_free(mTargetDragData); |
|
812 mTargetDragData = convertedText; |
|
813 mTargetDragDataLen = convertedTextLen * 2; |
|
814 dataFound = true; |
|
815 } // if plain text data on clipboard |
|
816 } // if plain text flavor present |
|
817 } // if plain text charset=utf-8 flavor present |
|
818 } // if looking for text/unicode |
|
819 |
|
820 // if we are looking for text/x-moz-url and we failed to find |
|
821 // it on the clipboard, try again with text/uri-list, and then |
|
822 // _NETSCAPE_URL |
|
823 if (strcmp(flavorStr, kURLMime) == 0) { |
|
824 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
825 ("we were looking for text/x-moz-url...\ |
|
826 trying again with text/uri-list\n")); |
|
827 gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
|
828 GetTargetDragData(gdkFlavor); |
|
829 if (mTargetDragData) { |
|
830 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
831 ("Got text/uri-list data\n")); |
|
832 const char *data = |
|
833 reinterpret_cast<char*>(mTargetDragData); |
|
834 char16_t* convertedText = nullptr; |
|
835 int32_t convertedTextLen = 0; |
|
836 |
|
837 GetTextUriListItem(data, mTargetDragDataLen, aItemIndex, |
|
838 &convertedText, &convertedTextLen); |
|
839 |
|
840 if ( convertedText ) { |
|
841 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
842 ("successfully converted \ |
|
843 _NETSCAPE_URL to unicode.\n")); |
|
844 // out with the old, in with the new |
|
845 g_free(mTargetDragData); |
|
846 mTargetDragData = convertedText; |
|
847 mTargetDragDataLen = convertedTextLen * 2; |
|
848 dataFound = true; |
|
849 } |
|
850 } |
|
851 else { |
|
852 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
853 ("failed to get text/uri-list data\n")); |
|
854 } |
|
855 if (!dataFound) { |
|
856 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
857 ("we were looking for text/x-moz-url...\ |
|
858 trying again with _NETSCAP_URL\n")); |
|
859 gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE); |
|
860 GetTargetDragData(gdkFlavor); |
|
861 if (mTargetDragData) { |
|
862 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
863 ("Got _NETSCAPE_URL data\n")); |
|
864 const char* castedText = |
|
865 reinterpret_cast<char*>(mTargetDragData); |
|
866 char16_t* convertedText = nullptr; |
|
867 int32_t convertedTextLen = 0; |
|
868 nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen); |
|
869 if ( convertedText ) { |
|
870 PR_LOG(sDragLm, |
|
871 PR_LOG_DEBUG, |
|
872 ("successfully converted _NETSCAPE_URL \ |
|
873 to unicode.\n")); |
|
874 // out with the old, in with the new |
|
875 g_free(mTargetDragData); |
|
876 mTargetDragData = convertedText; |
|
877 mTargetDragDataLen = convertedTextLen * 2; |
|
878 dataFound = true; |
|
879 } |
|
880 } |
|
881 else { |
|
882 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
883 ("failed to get _NETSCAPE_URL data\n")); |
|
884 } |
|
885 } |
|
886 } |
|
887 |
|
888 } // else we try one last ditch effort to find our data |
|
889 |
|
890 if (dataFound) { |
|
891 // the DOM only wants LF, so convert from MacOS line endings |
|
892 // to DOM line endings. |
|
893 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( |
|
894 flavorStr, |
|
895 &mTargetDragData, |
|
896 reinterpret_cast<int*>(&mTargetDragDataLen)); |
|
897 |
|
898 // put it into the transferable. |
|
899 nsCOMPtr<nsISupports> genericDataWrapper; |
|
900 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, |
|
901 mTargetDragData, mTargetDragDataLen, |
|
902 getter_AddRefs(genericDataWrapper)); |
|
903 aTransferable->SetTransferData(flavorStr, |
|
904 genericDataWrapper, |
|
905 mTargetDragDataLen); |
|
906 // we found one, get out of this loop! |
|
907 PR_LOG(sDragLm, PR_LOG_DEBUG, ("dataFound and converted!\n")); |
|
908 break; |
|
909 } |
|
910 } // if (currentFlavor) |
|
911 } // foreach flavor |
|
912 |
|
913 return NS_OK; |
|
914 |
|
915 } |
|
916 |
|
917 NS_IMETHODIMP |
|
918 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, |
|
919 bool *_retval) |
|
920 { |
|
921 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::IsDataFlavorSupported %s", |
|
922 aDataFlavor)); |
|
923 if (!_retval) |
|
924 return NS_ERROR_INVALID_ARG; |
|
925 |
|
926 // set this to no by default |
|
927 *_retval = false; |
|
928 |
|
929 // check to make sure that we have a drag object set, here |
|
930 if (!mTargetWidget) { |
|
931 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
932 ("*** warning: IsDataFlavorSupported \ |
|
933 called without a valid target widget!\n")); |
|
934 return NS_OK; |
|
935 } |
|
936 |
|
937 // check to see if the target context is a list. |
|
938 bool isList = IsTargetContextList(); |
|
939 // if it is, just look in the internal data since we are the source |
|
940 // for it. |
|
941 if (isList) { |
|
942 PR_LOG(sDragLm, PR_LOG_DEBUG, ("It's a list..")); |
|
943 uint32_t numDragItems = 0; |
|
944 // if we don't have mDataItems we didn't start this drag so it's |
|
945 // an external client trying to fool us. |
|
946 if (!mSourceDataItems) |
|
947 return NS_OK; |
|
948 mSourceDataItems->Count(&numDragItems); |
|
949 for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) { |
|
950 nsCOMPtr<nsISupports> genericItem; |
|
951 mSourceDataItems->GetElementAt(itemIndex, |
|
952 getter_AddRefs(genericItem)); |
|
953 nsCOMPtr<nsITransferable> currItem(do_QueryInterface(genericItem)); |
|
954 if (currItem) { |
|
955 nsCOMPtr <nsISupportsArray> flavorList; |
|
956 currItem->FlavorsTransferableCanExport( |
|
957 getter_AddRefs(flavorList)); |
|
958 if (flavorList) { |
|
959 uint32_t numFlavors; |
|
960 flavorList->Count( &numFlavors ); |
|
961 for ( uint32_t flavorIndex = 0; |
|
962 flavorIndex < numFlavors ; |
|
963 ++flavorIndex ) { |
|
964 nsCOMPtr<nsISupports> genericWrapper; |
|
965 flavorList->GetElementAt(flavorIndex, |
|
966 getter_AddRefs(genericWrapper)); |
|
967 nsCOMPtr<nsISupportsCString> currentFlavor; |
|
968 currentFlavor = do_QueryInterface(genericWrapper); |
|
969 if (currentFlavor) { |
|
970 nsXPIDLCString flavorStr; |
|
971 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
972 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
973 ("checking %s against %s\n", |
|
974 (const char *)flavorStr, aDataFlavor)); |
|
975 if (strcmp(flavorStr, aDataFlavor) == 0) { |
|
976 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
977 ("boioioioiooioioioing!\n")); |
|
978 *_retval = true; |
|
979 } |
|
980 } |
|
981 } |
|
982 } |
|
983 } |
|
984 } |
|
985 return NS_OK; |
|
986 } |
|
987 |
|
988 // check the target context vs. this flavor, one at a time |
|
989 GList *tmp; |
|
990 for (tmp = gdk_drag_context_list_targets(mTargetDragContext); |
|
991 tmp; tmp = tmp->next) { |
|
992 /* Bug 331198 */ |
|
993 GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); |
|
994 gchar *name = nullptr; |
|
995 name = gdk_atom_name(atom); |
|
996 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
997 ("checking %s against %s\n", name, aDataFlavor)); |
|
998 if (name && (strcmp(name, aDataFlavor) == 0)) { |
|
999 PR_LOG(sDragLm, PR_LOG_DEBUG, ("good!\n")); |
|
1000 *_retval = true; |
|
1001 } |
|
1002 // check for automatic text/uri-list -> text/x-moz-url mapping |
|
1003 if (!*_retval && |
|
1004 name && |
|
1005 (strcmp(name, gTextUriListType) == 0) && |
|
1006 (strcmp(aDataFlavor, kURLMime) == 0 || |
|
1007 strcmp(aDataFlavor, kFileMime) == 0)) { |
|
1008 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1009 ("good! ( it's text/uri-list and \ |
|
1010 we're checking against text/x-moz-url )\n")); |
|
1011 *_retval = true; |
|
1012 } |
|
1013 // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping |
|
1014 if (!*_retval && |
|
1015 name && |
|
1016 (strcmp(name, gMozUrlType) == 0) && |
|
1017 (strcmp(aDataFlavor, kURLMime) == 0)) { |
|
1018 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1019 ("good! ( it's _NETSCAPE_URL and \ |
|
1020 we're checking against text/x-moz-url )\n")); |
|
1021 *_retval = true; |
|
1022 } |
|
1023 // check for auto text/plain -> text/unicode mapping |
|
1024 if (!*_retval && |
|
1025 name && |
|
1026 (strcmp(name, kTextMime) == 0) && |
|
1027 ((strcmp(aDataFlavor, kUnicodeMime) == 0) || |
|
1028 (strcmp(aDataFlavor, kFileMime) == 0))) { |
|
1029 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1030 ("good! ( it's text plain and we're checking \ |
|
1031 against text/unicode or application/x-moz-file)\n")); |
|
1032 *_retval = true; |
|
1033 } |
|
1034 g_free(name); |
|
1035 } |
|
1036 return NS_OK; |
|
1037 } |
|
1038 |
|
1039 void |
|
1040 nsDragService::ReplyToDragMotion() |
|
1041 { |
|
1042 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1043 ("nsDragService::ReplyToDragMotion %d", mCanDrop)); |
|
1044 |
|
1045 GdkDragAction action = (GdkDragAction)0; |
|
1046 if (mCanDrop) { |
|
1047 // notify the dragger if we can drop |
|
1048 switch (mDragAction) { |
|
1049 case DRAGDROP_ACTION_COPY: |
|
1050 action = GDK_ACTION_COPY; |
|
1051 break; |
|
1052 case DRAGDROP_ACTION_LINK: |
|
1053 action = GDK_ACTION_LINK; |
|
1054 break; |
|
1055 default: |
|
1056 action = GDK_ACTION_MOVE; |
|
1057 break; |
|
1058 } |
|
1059 } |
|
1060 |
|
1061 gdk_drag_status(mTargetDragContext, action, mTargetTime); |
|
1062 } |
|
1063 |
|
1064 void |
|
1065 nsDragService::TargetDataReceived(GtkWidget *aWidget, |
|
1066 GdkDragContext *aContext, |
|
1067 gint aX, |
|
1068 gint aY, |
|
1069 GtkSelectionData *aSelectionData, |
|
1070 guint aInfo, |
|
1071 guint32 aTime) |
|
1072 { |
|
1073 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::TargetDataReceived")); |
|
1074 TargetResetData(); |
|
1075 mTargetDragDataReceived = true; |
|
1076 gint len = gtk_selection_data_get_length(aSelectionData); |
|
1077 const guchar* data = gtk_selection_data_get_data(aSelectionData); |
|
1078 if (len > 0 && data) { |
|
1079 mTargetDragDataLen = len; |
|
1080 mTargetDragData = g_malloc(mTargetDragDataLen); |
|
1081 memcpy(mTargetDragData, data, mTargetDragDataLen); |
|
1082 } |
|
1083 else { |
|
1084 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1085 ("Failed to get data. selection data len was %d\n", |
|
1086 mTargetDragDataLen)); |
|
1087 } |
|
1088 } |
|
1089 |
|
1090 bool |
|
1091 nsDragService::IsTargetContextList(void) |
|
1092 { |
|
1093 bool retval = false; |
|
1094 |
|
1095 // gMimeListType drags only work for drags within a single process. The |
|
1096 // gtk_drag_get_source_widget() function will return nullptr if the source |
|
1097 // of the drag is another app, so we use it to check if a gMimeListType |
|
1098 // drop will work or not. |
|
1099 if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr) |
|
1100 return retval; |
|
1101 |
|
1102 GList *tmp; |
|
1103 |
|
1104 // walk the list of context targets and see if one of them is a list |
|
1105 // of items. |
|
1106 for (tmp = gdk_drag_context_list_targets(mTargetDragContext); |
|
1107 tmp; tmp = tmp->next) { |
|
1108 /* Bug 331198 */ |
|
1109 GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); |
|
1110 gchar *name = nullptr; |
|
1111 name = gdk_atom_name(atom); |
|
1112 if (name && strcmp(name, gMimeListType) == 0) |
|
1113 retval = true; |
|
1114 g_free(name); |
|
1115 if (retval) |
|
1116 break; |
|
1117 } |
|
1118 return retval; |
|
1119 } |
|
1120 |
|
1121 // Maximum time to wait for a "drag_received" arrived, in microseconds |
|
1122 #define NS_DND_TIMEOUT 500000 |
|
1123 |
|
1124 void |
|
1125 nsDragService::GetTargetDragData(GdkAtom aFlavor) |
|
1126 { |
|
1127 PR_LOG(sDragLm, PR_LOG_DEBUG, ("getting data flavor %d\n", aFlavor)); |
|
1128 PR_LOG(sDragLm, PR_LOG_DEBUG, ("mLastWidget is %p and mLastContext is %p\n", |
|
1129 mTargetWidget.get(), |
|
1130 mTargetDragContext.get())); |
|
1131 // reset our target data areas |
|
1132 TargetResetData(); |
|
1133 gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); |
|
1134 |
|
1135 PR_LOG(sDragLm, PR_LOG_DEBUG, ("about to start inner iteration.")); |
|
1136 PRTime entryTime = PR_Now(); |
|
1137 while (!mTargetDragDataReceived && mDoingDrag) { |
|
1138 // check the number of iterations |
|
1139 PR_LOG(sDragLm, PR_LOG_DEBUG, ("doing iteration...\n")); |
|
1140 PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ |
|
1141 if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; |
|
1142 gtk_main_iteration(); |
|
1143 } |
|
1144 PR_LOG(sDragLm, PR_LOG_DEBUG, ("finished inner iteration\n")); |
|
1145 } |
|
1146 |
|
1147 void |
|
1148 nsDragService::TargetResetData(void) |
|
1149 { |
|
1150 mTargetDragDataReceived = false; |
|
1151 // make sure to free old data if we have to |
|
1152 g_free(mTargetDragData); |
|
1153 mTargetDragData = 0; |
|
1154 mTargetDragDataLen = 0; |
|
1155 } |
|
1156 |
|
1157 GtkTargetList * |
|
1158 nsDragService::GetSourceList(void) |
|
1159 { |
|
1160 if (!mSourceDataItems) |
|
1161 return nullptr; |
|
1162 nsTArray<GtkTargetEntry*> targetArray; |
|
1163 GtkTargetEntry *targets; |
|
1164 GtkTargetList *targetList = 0; |
|
1165 uint32_t targetCount = 0; |
|
1166 unsigned int numDragItems = 0; |
|
1167 |
|
1168 mSourceDataItems->Count(&numDragItems); |
|
1169 |
|
1170 // Check to see if we're dragging > 1 item. |
|
1171 if (numDragItems > 1) { |
|
1172 // as the Xdnd protocol only supports a single item (or is it just |
|
1173 // gtk's implementation?), we don't advertise all flavours listed |
|
1174 // in the nsITransferable. |
|
1175 |
|
1176 // the application/x-moz-internal-item-list format, which preserves |
|
1177 // all information for drags within the same mozilla instance. |
|
1178 GtkTargetEntry *listTarget = |
|
1179 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1180 listTarget->target = g_strdup(gMimeListType); |
|
1181 listTarget->flags = 0; |
|
1182 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1183 ("automatically adding target %s\n", listTarget->target)); |
|
1184 targetArray.AppendElement(listTarget); |
|
1185 |
|
1186 // check what flavours are supported so we can decide what other |
|
1187 // targets to advertise. |
|
1188 nsCOMPtr<nsISupports> genericItem; |
|
1189 mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); |
|
1190 nsCOMPtr<nsITransferable> currItem(do_QueryInterface(genericItem)); |
|
1191 |
|
1192 if (currItem) { |
|
1193 nsCOMPtr <nsISupportsArray> flavorList; |
|
1194 currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); |
|
1195 if (flavorList) { |
|
1196 uint32_t numFlavors; |
|
1197 flavorList->Count( &numFlavors ); |
|
1198 for (uint32_t flavorIndex = 0; |
|
1199 flavorIndex < numFlavors ; |
|
1200 ++flavorIndex ) { |
|
1201 nsCOMPtr<nsISupports> genericWrapper; |
|
1202 flavorList->GetElementAt(flavorIndex, |
|
1203 getter_AddRefs(genericWrapper)); |
|
1204 nsCOMPtr<nsISupportsCString> currentFlavor; |
|
1205 currentFlavor = do_QueryInterface(genericWrapper); |
|
1206 if (currentFlavor) { |
|
1207 nsXPIDLCString flavorStr; |
|
1208 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
1209 |
|
1210 // check if text/x-moz-url is supported. |
|
1211 // If so, advertise |
|
1212 // text/uri-list. |
|
1213 if (strcmp(flavorStr, kURLMime) == 0) { |
|
1214 listTarget = |
|
1215 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1216 listTarget->target = g_strdup(gTextUriListType); |
|
1217 listTarget->flags = 0; |
|
1218 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1219 ("automatically adding target %s\n", |
|
1220 listTarget->target)); |
|
1221 targetArray.AppendElement(listTarget); |
|
1222 } |
|
1223 } |
|
1224 } // foreach flavor in item |
|
1225 } // if valid flavor list |
|
1226 } // if item is a transferable |
|
1227 } else if (numDragItems == 1) { |
|
1228 nsCOMPtr<nsISupports> genericItem; |
|
1229 mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); |
|
1230 nsCOMPtr<nsITransferable> currItem(do_QueryInterface(genericItem)); |
|
1231 if (currItem) { |
|
1232 nsCOMPtr <nsISupportsArray> flavorList; |
|
1233 currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); |
|
1234 if (flavorList) { |
|
1235 uint32_t numFlavors; |
|
1236 flavorList->Count( &numFlavors ); |
|
1237 for (uint32_t flavorIndex = 0; |
|
1238 flavorIndex < numFlavors ; |
|
1239 ++flavorIndex ) { |
|
1240 nsCOMPtr<nsISupports> genericWrapper; |
|
1241 flavorList->GetElementAt(flavorIndex, |
|
1242 getter_AddRefs(genericWrapper)); |
|
1243 nsCOMPtr<nsISupportsCString> currentFlavor; |
|
1244 currentFlavor = do_QueryInterface(genericWrapper); |
|
1245 if (currentFlavor) { |
|
1246 nsXPIDLCString flavorStr; |
|
1247 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
1248 GtkTargetEntry *target = |
|
1249 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1250 target->target = g_strdup(flavorStr); |
|
1251 target->flags = 0; |
|
1252 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1253 ("adding target %s\n", target->target)); |
|
1254 targetArray.AppendElement(target); |
|
1255 // Check to see if this is text/unicode. |
|
1256 // If it is, add text/plain |
|
1257 // since we automatically support text/plain |
|
1258 // if we support text/unicode. |
|
1259 if (strcmp(flavorStr, kUnicodeMime) == 0) { |
|
1260 GtkTargetEntry *plainUTF8Target = |
|
1261 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1262 plainUTF8Target->target = g_strdup(gTextPlainUTF8Type); |
|
1263 plainUTF8Target->flags = 0; |
|
1264 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1265 ("automatically adding target %s\n", |
|
1266 plainUTF8Target->target)); |
|
1267 targetArray.AppendElement(plainUTF8Target); |
|
1268 |
|
1269 GtkTargetEntry *plainTarget = |
|
1270 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1271 plainTarget->target = g_strdup(kTextMime); |
|
1272 plainTarget->flags = 0; |
|
1273 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1274 ("automatically adding target %s\n", |
|
1275 plainTarget->target)); |
|
1276 targetArray.AppendElement(plainTarget); |
|
1277 } |
|
1278 // Check to see if this is the x-moz-url type. |
|
1279 // If it is, add _NETSCAPE_URL |
|
1280 // this is a type used by everybody. |
|
1281 if (strcmp(flavorStr, kURLMime) == 0) { |
|
1282 GtkTargetEntry *urlTarget = |
|
1283 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
|
1284 urlTarget->target = g_strdup(gMozUrlType); |
|
1285 urlTarget->flags = 0; |
|
1286 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1287 ("automatically adding target %s\n", |
|
1288 urlTarget->target)); |
|
1289 targetArray.AppendElement(urlTarget); |
|
1290 } |
|
1291 } |
|
1292 } // foreach flavor in item |
|
1293 } // if valid flavor list |
|
1294 } // if item is a transferable |
|
1295 } // if it is a single item drag |
|
1296 |
|
1297 // get all the elements that we created. |
|
1298 targetCount = targetArray.Length(); |
|
1299 if (targetCount) { |
|
1300 // allocate space to create the list of valid targets |
|
1301 targets = |
|
1302 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount); |
|
1303 uint32_t targetIndex; |
|
1304 for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) { |
|
1305 GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex); |
|
1306 // this is a string reference but it will be freed later. |
|
1307 targets[targetIndex].target = disEntry->target; |
|
1308 targets[targetIndex].flags = disEntry->flags; |
|
1309 targets[targetIndex].info = 0; |
|
1310 } |
|
1311 targetList = gtk_target_list_new(targets, targetCount); |
|
1312 // clean up the target list |
|
1313 for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) { |
|
1314 GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex); |
|
1315 g_free(thisTarget->target); |
|
1316 g_free(thisTarget); |
|
1317 } |
|
1318 g_free(targets); |
|
1319 } |
|
1320 return targetList; |
|
1321 } |
|
1322 |
|
1323 void |
|
1324 nsDragService::SourceEndDragSession(GdkDragContext *aContext, |
|
1325 gint aResult) |
|
1326 { |
|
1327 // this just releases the list of data items that we provide |
|
1328 mSourceDataItems = nullptr; |
|
1329 |
|
1330 if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd) |
|
1331 // EndDragSession() was already called on drop |
|
1332 // or SourceEndDragSession on drag-failed |
|
1333 return; |
|
1334 |
|
1335 if (mEndDragPoint.x < 0) { |
|
1336 // We don't have a drag end point, so guess |
|
1337 gint x, y; |
|
1338 GdkDisplay* display = gdk_display_get_default(); |
|
1339 if (display) { |
|
1340 gdk_display_get_pointer(display, nullptr, &x, &y, nullptr); |
|
1341 SetDragEndPoint(nsIntPoint(x, y)); |
|
1342 } |
|
1343 } |
|
1344 |
|
1345 // Either the drag was aborted or the drop occurred outside the app. |
|
1346 // The dropEffect of mDataTransfer is not updated for motion outside the |
|
1347 // app, but is needed for the dragend event, so set it now. |
|
1348 |
|
1349 uint32_t dropEffect; |
|
1350 |
|
1351 if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) { |
|
1352 |
|
1353 // With GTK+ versions 2.10.x and prior the drag may have been |
|
1354 // cancelled (but no drag-failed signal would have been sent). |
|
1355 // aContext->dest_window will be non-nullptr only if the drop was |
|
1356 // sent. |
|
1357 GdkDragAction action = |
|
1358 gdk_drag_context_get_dest_window(aContext) ? |
|
1359 gdk_drag_context_get_actions(aContext) : (GdkDragAction)0; |
|
1360 |
|
1361 // Only one bit of action should be set, but, just in case someone |
|
1362 // does something funny, erring away from MOVE, and not recording |
|
1363 // unusual action combinations as NONE. |
|
1364 if (!action) |
|
1365 dropEffect = DRAGDROP_ACTION_NONE; |
|
1366 else if (action & GDK_ACTION_COPY) |
|
1367 dropEffect = DRAGDROP_ACTION_COPY; |
|
1368 else if (action & GDK_ACTION_LINK) |
|
1369 dropEffect = DRAGDROP_ACTION_LINK; |
|
1370 else if (action & GDK_ACTION_MOVE) |
|
1371 dropEffect = DRAGDROP_ACTION_MOVE; |
|
1372 else |
|
1373 dropEffect = DRAGDROP_ACTION_COPY; |
|
1374 |
|
1375 } else { |
|
1376 |
|
1377 dropEffect = DRAGDROP_ACTION_NONE; |
|
1378 |
|
1379 if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) { |
|
1380 mUserCancelled = true; |
|
1381 } |
|
1382 } |
|
1383 |
|
1384 if (mDataTransfer) { |
|
1385 mDataTransfer->SetDropEffectInt(dropEffect); |
|
1386 } |
|
1387 |
|
1388 // Schedule the appropriate drag end dom events. |
|
1389 Schedule(eDragTaskSourceEnd, nullptr, nullptr, nsIntPoint(), 0); |
|
1390 } |
|
1391 |
|
1392 static void |
|
1393 CreateUriList(nsISupportsArray *items, gchar **text, gint *length) |
|
1394 { |
|
1395 uint32_t i, count; |
|
1396 GString *uriList = g_string_new(nullptr); |
|
1397 |
|
1398 items->Count(&count); |
|
1399 for (i = 0; i < count; i++) { |
|
1400 nsCOMPtr<nsISupports> genericItem; |
|
1401 items->GetElementAt(i, getter_AddRefs(genericItem)); |
|
1402 nsCOMPtr<nsITransferable> item; |
|
1403 item = do_QueryInterface(genericItem); |
|
1404 |
|
1405 if (item) { |
|
1406 uint32_t tmpDataLen = 0; |
|
1407 void *tmpData = nullptr; |
|
1408 nsresult rv = NS_OK; |
|
1409 nsCOMPtr<nsISupports> data; |
|
1410 rv = item->GetTransferData(kURLMime, |
|
1411 getter_AddRefs(data), |
|
1412 &tmpDataLen); |
|
1413 |
|
1414 if (NS_SUCCEEDED(rv)) { |
|
1415 nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime, |
|
1416 data, |
|
1417 &tmpData, |
|
1418 tmpDataLen); |
|
1419 char* plainTextData = nullptr; |
|
1420 char16_t* castedUnicode = reinterpret_cast<char16_t*> |
|
1421 (tmpData); |
|
1422 int32_t plainTextLen = 0; |
|
1423 nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText( |
|
1424 castedUnicode, |
|
1425 tmpDataLen / 2, |
|
1426 &plainTextData, |
|
1427 &plainTextLen); |
|
1428 if (plainTextData) { |
|
1429 int32_t j; |
|
1430 |
|
1431 // text/x-moz-url is of form url + "\n" + title. |
|
1432 // We just want the url. |
|
1433 for (j = 0; j < plainTextLen; j++) |
|
1434 if (plainTextData[j] == '\n' || |
|
1435 plainTextData[j] == '\r') { |
|
1436 plainTextData[j] = '\0'; |
|
1437 break; |
|
1438 } |
|
1439 g_string_append(uriList, plainTextData); |
|
1440 g_string_append(uriList, "\r\n"); |
|
1441 // this wasn't allocated with glib |
|
1442 free(plainTextData); |
|
1443 } |
|
1444 if (tmpData) { |
|
1445 // this wasn't allocated with glib |
|
1446 free(tmpData); |
|
1447 } |
|
1448 } |
|
1449 } |
|
1450 } |
|
1451 *text = uriList->str; |
|
1452 *length = uriList->len + 1; |
|
1453 g_string_free(uriList, FALSE); // don't free the data |
|
1454 } |
|
1455 |
|
1456 |
|
1457 void |
|
1458 nsDragService::SourceDataGet(GtkWidget *aWidget, |
|
1459 GdkDragContext *aContext, |
|
1460 GtkSelectionData *aSelectionData, |
|
1461 guint32 aTime) |
|
1462 { |
|
1463 PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::SourceDataGet")); |
|
1464 GdkAtom target = gtk_selection_data_get_target(aSelectionData); |
|
1465 nsXPIDLCString mimeFlavor; |
|
1466 gchar *typeName = 0; |
|
1467 typeName = gdk_atom_name(target); |
|
1468 if (!typeName) { |
|
1469 PR_LOG(sDragLm, PR_LOG_DEBUG, ("failed to get atom name.\n")); |
|
1470 return; |
|
1471 } |
|
1472 |
|
1473 PR_LOG(sDragLm, PR_LOG_DEBUG, ("Type is %s\n", typeName)); |
|
1474 // make a copy since |nsXPIDLCString| won't use |g_free|... |
|
1475 mimeFlavor.Adopt(strdup(typeName)); |
|
1476 g_free(typeName); |
|
1477 // check to make sure that we have data items to return. |
|
1478 if (!mSourceDataItems) { |
|
1479 PR_LOG(sDragLm, PR_LOG_DEBUG, ("Failed to get our data items\n")); |
|
1480 return; |
|
1481 } |
|
1482 |
|
1483 nsCOMPtr<nsISupports> genericItem; |
|
1484 mSourceDataItems->GetElementAt(0, getter_AddRefs(genericItem)); |
|
1485 nsCOMPtr<nsITransferable> item; |
|
1486 item = do_QueryInterface(genericItem); |
|
1487 if (item) { |
|
1488 // if someone was asking for text/plain, lookup unicode instead so |
|
1489 // we can convert it. |
|
1490 bool needToDoConversionToPlainText = false; |
|
1491 const char* actualFlavor = mimeFlavor; |
|
1492 if (strcmp(mimeFlavor, kTextMime) == 0 || |
|
1493 strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) { |
|
1494 actualFlavor = kUnicodeMime; |
|
1495 needToDoConversionToPlainText = true; |
|
1496 } |
|
1497 // if someone was asking for _NETSCAPE_URL we need to convert to |
|
1498 // plain text but we also need to look for x-moz-url |
|
1499 else if (strcmp(mimeFlavor, gMozUrlType) == 0) { |
|
1500 actualFlavor = kURLMime; |
|
1501 needToDoConversionToPlainText = true; |
|
1502 } |
|
1503 // if someone was asking for text/uri-list we need to convert to |
|
1504 // plain text. |
|
1505 else if (strcmp(mimeFlavor, gTextUriListType) == 0) { |
|
1506 actualFlavor = gTextUriListType; |
|
1507 needToDoConversionToPlainText = true; |
|
1508 } |
|
1509 else |
|
1510 actualFlavor = mimeFlavor; |
|
1511 |
|
1512 uint32_t tmpDataLen = 0; |
|
1513 void *tmpData = nullptr; |
|
1514 nsresult rv; |
|
1515 nsCOMPtr<nsISupports> data; |
|
1516 rv = item->GetTransferData(actualFlavor, |
|
1517 getter_AddRefs(data), |
|
1518 &tmpDataLen); |
|
1519 if (NS_SUCCEEDED(rv)) { |
|
1520 nsPrimitiveHelpers::CreateDataFromPrimitive (actualFlavor, data, |
|
1521 &tmpData, tmpDataLen); |
|
1522 // if required, do the extra work to convert unicode to plain |
|
1523 // text and replace the output values with the plain text. |
|
1524 if (needToDoConversionToPlainText) { |
|
1525 char* plainTextData = nullptr; |
|
1526 char16_t* castedUnicode = reinterpret_cast<char16_t*> |
|
1527 (tmpData); |
|
1528 int32_t plainTextLen = 0; |
|
1529 if (strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) { |
|
1530 plainTextData = |
|
1531 ToNewUTF8String( |
|
1532 nsDependentString(castedUnicode, tmpDataLen / 2), |
|
1533 (uint32_t*)&plainTextLen); |
|
1534 } else { |
|
1535 nsPrimitiveHelpers::ConvertUnicodeToPlatformPlainText( |
|
1536 castedUnicode, |
|
1537 tmpDataLen / 2, |
|
1538 &plainTextData, |
|
1539 &plainTextLen); |
|
1540 } |
|
1541 if (tmpData) { |
|
1542 // this was not allocated using glib |
|
1543 free(tmpData); |
|
1544 tmpData = plainTextData; |
|
1545 tmpDataLen = plainTextLen; |
|
1546 } |
|
1547 } |
|
1548 if (tmpData) { |
|
1549 // this copies the data |
|
1550 gtk_selection_data_set(aSelectionData, target, |
|
1551 8, |
|
1552 (guchar *)tmpData, tmpDataLen); |
|
1553 // this wasn't allocated with glib |
|
1554 free(tmpData); |
|
1555 } |
|
1556 } else { |
|
1557 if (strcmp(mimeFlavor, gTextUriListType) == 0) { |
|
1558 // fall back for text/uri-list |
|
1559 gchar *uriList; |
|
1560 gint length; |
|
1561 CreateUriList(mSourceDataItems, &uriList, &length); |
|
1562 gtk_selection_data_set(aSelectionData, target, |
|
1563 8, (guchar *)uriList, length); |
|
1564 g_free(uriList); |
|
1565 return; |
|
1566 } |
|
1567 } |
|
1568 } |
|
1569 } |
|
1570 |
|
1571 void nsDragService::SetDragIcon(GdkDragContext* aContext) |
|
1572 { |
|
1573 if (!mHasImage && !mSelection) |
|
1574 return; |
|
1575 |
|
1576 nsIntRect dragRect; |
|
1577 nsPresContext* pc; |
|
1578 RefPtr<SourceSurface> surface; |
|
1579 DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY, |
|
1580 &dragRect, &surface, &pc); |
|
1581 if (!pc) |
|
1582 return; |
|
1583 |
|
1584 int32_t sx = mScreenX, sy = mScreenY; |
|
1585 ConvertToUnscaledDevPixels(pc, &sx, &sy); |
|
1586 |
|
1587 int32_t offsetX = sx - dragRect.x; |
|
1588 int32_t offsetY = sy - dragRect.y; |
|
1589 |
|
1590 // If a popup is set as the drag image, use its widget. Otherwise, use |
|
1591 // the surface that DrawDrag created. |
|
1592 if (mDragPopup) { |
|
1593 GtkWidget* gtkWidget = nullptr; |
|
1594 nsIFrame* frame = mDragPopup->GetPrimaryFrame(); |
|
1595 if (frame) { |
|
1596 // DrawDrag ensured that this is a popup frame. |
|
1597 nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(); |
|
1598 if (widget) { |
|
1599 gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET); |
|
1600 if (gtkWidget) { |
|
1601 OpenDragPopup(); |
|
1602 gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY); |
|
1603 } |
|
1604 } |
|
1605 } |
|
1606 } |
|
1607 else if (surface) { |
|
1608 if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) { |
|
1609 GdkPixbuf* dragPixbuf = |
|
1610 nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height); |
|
1611 if (dragPixbuf) { |
|
1612 gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY); |
|
1613 g_object_unref(dragPixbuf); |
|
1614 } |
|
1615 } |
|
1616 } |
|
1617 } |
|
1618 |
|
1619 static void |
|
1620 invisibleSourceDragBegin(GtkWidget *aWidget, |
|
1621 GdkDragContext *aContext, |
|
1622 gpointer aData) |
|
1623 { |
|
1624 PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragBegin")); |
|
1625 nsDragService *dragService = (nsDragService *)aData; |
|
1626 |
|
1627 dragService->SetDragIcon(aContext); |
|
1628 } |
|
1629 |
|
1630 static void |
|
1631 invisibleSourceDragDataGet(GtkWidget *aWidget, |
|
1632 GdkDragContext *aContext, |
|
1633 GtkSelectionData *aSelectionData, |
|
1634 guint aInfo, |
|
1635 guint32 aTime, |
|
1636 gpointer aData) |
|
1637 { |
|
1638 PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragDataGet")); |
|
1639 nsDragService *dragService = (nsDragService *)aData; |
|
1640 dragService->SourceDataGet(aWidget, aContext, |
|
1641 aSelectionData, aTime); |
|
1642 } |
|
1643 |
|
1644 static gboolean |
|
1645 invisibleSourceDragFailed(GtkWidget *aWidget, |
|
1646 GdkDragContext *aContext, |
|
1647 gint aResult, |
|
1648 gpointer aData) |
|
1649 { |
|
1650 PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragFailed %i", aResult)); |
|
1651 nsDragService *dragService = (nsDragService *)aData; |
|
1652 // End the drag session now (rather than waiting for the drag-end signal) |
|
1653 // so that operations performed on dropEffect == none can start immediately |
|
1654 // rather than waiting for the drag-failed animation to finish. |
|
1655 dragService->SourceEndDragSession(aContext, aResult); |
|
1656 |
|
1657 // We should return TRUE to disable the drag-failed animation iff the |
|
1658 // source performed an operation when dropEffect was none, but the handler |
|
1659 // of the dragend DOM event doesn't provide this information. |
|
1660 return FALSE; |
|
1661 } |
|
1662 |
|
1663 static void |
|
1664 invisibleSourceDragEnd(GtkWidget *aWidget, |
|
1665 GdkDragContext *aContext, |
|
1666 gpointer aData) |
|
1667 { |
|
1668 PR_LOG(sDragLm, PR_LOG_DEBUG, ("invisibleSourceDragEnd")); |
|
1669 nsDragService *dragService = (nsDragService *)aData; |
|
1670 |
|
1671 // The drag has ended. Release the hostages! |
|
1672 dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS); |
|
1673 } |
|
1674 |
|
1675 // The following methods handle responding to GTK drag signals and |
|
1676 // tracking state between these signals. |
|
1677 // |
|
1678 // In general, GTK does not expect us to run the event loop while handling its |
|
1679 // drag signals, however our drag event handlers may run the |
|
1680 // event loop, most often to fetch information about the drag data. |
|
1681 // |
|
1682 // GTK, for example, uses the return value from drag-motion signals to |
|
1683 // determine whether drag-leave signals should be sent. If an event loop is |
|
1684 // run during drag-motion the XdndLeave message can get processed but when GTK |
|
1685 // receives the message it does not yet know that it needs to send the |
|
1686 // drag-leave signal to our widget. |
|
1687 // |
|
1688 // After a drag-drop signal, we need to reply with gtk_drag_finish(). |
|
1689 // However, gtk_drag_finish should happen after the drag-drop signal handler |
|
1690 // returns so that when the Motif drag protocol is used, the |
|
1691 // XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START |
|
1692 // reply sent on return from the drag-drop signal handler. |
|
1693 // |
|
1694 // Similarly drag-end for a successful drag and drag-failed are not good |
|
1695 // times to run a nested event loop as gtk_drag_drop_finished() and |
|
1696 // gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove |
|
1697 // drop_timeout until after at least the first of these signals is sent. |
|
1698 // Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop |
|
1699 // timeout) could cause gtk_drag_drop_finished to be called again with the |
|
1700 // same GtkDragSourceInfo, which won't like being destroyed twice. |
|
1701 // |
|
1702 // Therefore we reply to the signals immediately and schedule a task to |
|
1703 // dispatch the Gecko events, which may run the event loop. |
|
1704 // |
|
1705 // Action in response to drag-leave signals is also delayed until the event |
|
1706 // loop runs again so that we find out whether a drag-drop signal follows. |
|
1707 // |
|
1708 // A single task is scheduled to manage responses to all three GTK signals. |
|
1709 // If further signals are received while the task is scheduled, the scheduled |
|
1710 // response is updated, sometimes effectively compressing successive signals. |
|
1711 // |
|
1712 // No Gecko drag events are dispatched (during nested event loops) while other |
|
1713 // Gecko drag events are in flight. This helps event handlers that may not |
|
1714 // expect nested events, while accessing an event's dataTransfer for example. |
|
1715 |
|
1716 gboolean |
|
1717 nsDragService::ScheduleMotionEvent(nsWindow *aWindow, |
|
1718 GdkDragContext *aDragContext, |
|
1719 nsIntPoint aWindowPoint, guint aTime) |
|
1720 { |
|
1721 if (mScheduledTask == eDragTaskMotion) { |
|
1722 // The drag source has sent another motion message before we've |
|
1723 // replied to the previous. That shouldn't happen with Xdnd. The |
|
1724 // spec for Motif drags is less clear, but we'll just update the |
|
1725 // scheduled task with the new position reply only to the most |
|
1726 // recent message. |
|
1727 NS_WARNING("Drag Motion message received before previous reply was sent"); |
|
1728 } |
|
1729 |
|
1730 // Returning TRUE means we'll reply with a status message, unless we first |
|
1731 // get a leave. |
|
1732 return Schedule(eDragTaskMotion, aWindow, aDragContext, |
|
1733 aWindowPoint, aTime); |
|
1734 } |
|
1735 |
|
1736 void |
|
1737 nsDragService::ScheduleLeaveEvent() |
|
1738 { |
|
1739 // We don't know at this stage whether a drop signal will immediately |
|
1740 // follow. If the drop signal gets sent it will happen before we return |
|
1741 // to the main loop and the scheduled leave task will be replaced. |
|
1742 if (!Schedule(eDragTaskLeave, nullptr, nullptr, nsIntPoint(), 0)) { |
|
1743 NS_WARNING("Drag leave after drop"); |
|
1744 } |
|
1745 } |
|
1746 |
|
1747 gboolean |
|
1748 nsDragService::ScheduleDropEvent(nsWindow *aWindow, |
|
1749 GdkDragContext *aDragContext, |
|
1750 nsIntPoint aWindowPoint, guint aTime) |
|
1751 { |
|
1752 if (!Schedule(eDragTaskDrop, aWindow, |
|
1753 aDragContext, aWindowPoint, aTime)) { |
|
1754 NS_WARNING("Additional drag drop ignored"); |
|
1755 return FALSE; |
|
1756 } |
|
1757 |
|
1758 SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset()); |
|
1759 |
|
1760 // We'll reply with gtk_drag_finish(). |
|
1761 return TRUE; |
|
1762 } |
|
1763 |
|
1764 gboolean |
|
1765 nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, |
|
1766 GdkDragContext *aDragContext, |
|
1767 nsIntPoint aWindowPoint, guint aTime) |
|
1768 { |
|
1769 // If there is an existing leave or motion task scheduled, then that |
|
1770 // will be replaced. When the new task is run, it will dispatch |
|
1771 // any necessary leave or motion events. |
|
1772 |
|
1773 // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled |
|
1774 // drop event (which could happen if the drop event has not been processed |
|
1775 // within the allowed time). Otherwise, if we haven't yet run a scheduled |
|
1776 // drop or end task, just say that we are not ready to receive another |
|
1777 // drop. |
|
1778 if (mScheduledTask == eDragTaskSourceEnd || |
|
1779 (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) |
|
1780 return FALSE; |
|
1781 |
|
1782 mScheduledTask = aTask; |
|
1783 mPendingWindow = aWindow; |
|
1784 mPendingDragContext = aDragContext; |
|
1785 mPendingWindowPoint = aWindowPoint; |
|
1786 mPendingTime = aTime; |
|
1787 |
|
1788 if (!mTaskSource) { |
|
1789 // High priority is used here because the native events involved have |
|
1790 // already waited at default priority. Perhaps a lower than default |
|
1791 // priority could be used for motion tasks because there is a chance |
|
1792 // that a leave or drop is waiting, but managing different priorities |
|
1793 // may not be worth the effort. Motion tasks shouldn't queue up as |
|
1794 // they should be throttled based on replies. |
|
1795 mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, |
|
1796 this, nullptr); |
|
1797 } |
|
1798 return TRUE; |
|
1799 } |
|
1800 |
|
1801 gboolean |
|
1802 nsDragService::TaskDispatchCallback(gpointer data) |
|
1803 { |
|
1804 nsRefPtr<nsDragService> dragService = static_cast<nsDragService*>(data); |
|
1805 return dragService->RunScheduledTask(); |
|
1806 } |
|
1807 |
|
1808 gboolean |
|
1809 nsDragService::RunScheduledTask() |
|
1810 { |
|
1811 if (mTargetWindow && mTargetWindow != mPendingWindow) { |
|
1812 PR_LOG(sDragLm, PR_LOG_DEBUG, |
|
1813 ("nsDragService: dispatch drag leave (%p)\n", |
|
1814 mTargetWindow.get())); |
|
1815 mTargetWindow-> |
|
1816 DispatchDragEvent(NS_DRAGDROP_EXIT, mTargetWindowPoint, 0); |
|
1817 |
|
1818 if (!mSourceNode) { |
|
1819 // The drag that was initiated in a different app. End the drag |
|
1820 // session, since we're done with it for now (until the user drags |
|
1821 // back into this app). |
|
1822 EndDragSession(false); |
|
1823 } |
|
1824 } |
|
1825 |
|
1826 // It is possible that the pending state has been updated during dispatch |
|
1827 // of the leave event. That's fine. |
|
1828 |
|
1829 // Now we collect the pending state because, from this point on, we want |
|
1830 // to use the same state for all events dispatched. All state is updated |
|
1831 // so that when other tasks are scheduled during dispatch here, this |
|
1832 // task is considered to have already been run. |
|
1833 bool positionHasChanged = |
|
1834 mPendingWindow != mTargetWindow || |
|
1835 mPendingWindowPoint != mTargetWindowPoint; |
|
1836 DragTask task = mScheduledTask; |
|
1837 mScheduledTask = eDragTaskNone; |
|
1838 mTargetWindow = mPendingWindow.forget(); |
|
1839 mTargetWindowPoint = mPendingWindowPoint; |
|
1840 |
|
1841 if (task == eDragTaskLeave || task == eDragTaskSourceEnd) { |
|
1842 if (task == eDragTaskSourceEnd) { |
|
1843 // Dispatch drag end events. |
|
1844 EndDragSession(true); |
|
1845 } |
|
1846 |
|
1847 // Nothing more to do |
|
1848 // Returning false removes the task source from the event loop. |
|
1849 mTaskSource = 0; |
|
1850 return FALSE; |
|
1851 } |
|
1852 |
|
1853 // This may be the start of a destination drag session. |
|
1854 StartDragSession(); |
|
1855 |
|
1856 // mTargetWidget may be nullptr if the window has been destroyed. |
|
1857 // (The leave event is not scheduled if a drop task is still scheduled.) |
|
1858 // We still reply appropriately to indicate that the drop will or didn't |
|
1859 // succeeed. |
|
1860 mTargetWidget = mTargetWindow->GetMozContainerWidget(); |
|
1861 mTargetDragContext.steal(mPendingDragContext); |
|
1862 mTargetTime = mPendingTime; |
|
1863 |
|
1864 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
|
1865 // (as at 27 December 2010) indicates that a "drop" event should only be |
|
1866 // fired (at the current target element) if the current drag operation is |
|
1867 // not none. The current drag operation will only be set to a non-none |
|
1868 // value during a "dragover" event. |
|
1869 // |
|
1870 // If the user has ended the drag before any dragover events have been |
|
1871 // sent, then the spec recommends skipping the drop (because the current |
|
1872 // drag operation is none). However, here we assume that, by releasing |
|
1873 // the mouse button, the user has indicated that they want to drop, so we |
|
1874 // proceed with the drop where possible. |
|
1875 // |
|
1876 // In order to make the events appear to content in the same way as if the |
|
1877 // spec is being followed we make sure to dispatch a "dragover" event with |
|
1878 // appropriate coordinates and check canDrop before the "drop" event. |
|
1879 // |
|
1880 // When the Xdnd protocol is used for source/destination communication (as |
|
1881 // should be the case with GTK source applications) a dragover event |
|
1882 // should have already been sent during the drag-motion signal, which |
|
1883 // would have already been received because XdndDrop messages do not |
|
1884 // contain a position. However, we can't assume the same when the Motif |
|
1885 // protocol is used. |
|
1886 if (task == eDragTaskMotion || positionHasChanged) { |
|
1887 UpdateDragAction(); |
|
1888 DispatchMotionEvents(); |
|
1889 |
|
1890 if (task == eDragTaskMotion) { |
|
1891 // Reply to tell the source whether we can drop and what |
|
1892 // action would be taken. |
|
1893 ReplyToDragMotion(); |
|
1894 } |
|
1895 } |
|
1896 |
|
1897 if (task == eDragTaskDrop) { |
|
1898 gboolean success = DispatchDropEvent(); |
|
1899 |
|
1900 // Perhaps we should set the del parameter to TRUE when the drag |
|
1901 // action is move, but we don't know whether the data was successfully |
|
1902 // transferred. |
|
1903 gtk_drag_finish(mTargetDragContext, success, |
|
1904 /* del = */ FALSE, mTargetTime); |
|
1905 |
|
1906 // This drag is over, so clear out our reference to the previous |
|
1907 // window. |
|
1908 mTargetWindow = nullptr; |
|
1909 // Make sure to end the drag session. If this drag started in a |
|
1910 // different app, we won't get a drag_end signal to end it from. |
|
1911 EndDragSession(true); |
|
1912 } |
|
1913 |
|
1914 // We're done with the drag context. |
|
1915 mTargetWidget = nullptr; |
|
1916 mTargetDragContext = nullptr; |
|
1917 |
|
1918 // If we got another drag signal while running the sheduled task, that |
|
1919 // must have happened while running a nested event loop. Leave the task |
|
1920 // source on the event loop. |
|
1921 if (mScheduledTask != eDragTaskNone) |
|
1922 return TRUE; |
|
1923 |
|
1924 // We have no task scheduled. |
|
1925 // Returning false removes the task source from the event loop. |
|
1926 mTaskSource = 0; |
|
1927 return FALSE; |
|
1928 } |
|
1929 |
|
1930 // This will update the drag action based on the information in the |
|
1931 // drag context. Gtk gets this from a combination of the key settings |
|
1932 // and what the source is offering. |
|
1933 |
|
1934 void |
|
1935 nsDragService::UpdateDragAction() |
|
1936 { |
|
1937 // This doesn't look right. dragSession.dragAction is used by |
|
1938 // nsContentUtils::SetDataTransferInEvent() to set the initial |
|
1939 // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be |
|
1940 // more appropriate. GdkDragContext::actions should be used to set |
|
1941 // dataTransfer.effectAllowed, which doesn't currently happen with |
|
1942 // external sources. |
|
1943 |
|
1944 // default is to do nothing |
|
1945 int action = nsIDragService::DRAGDROP_ACTION_NONE; |
|
1946 GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext); |
|
1947 |
|
1948 // set the default just in case nothing matches below |
|
1949 if (gdkAction & GDK_ACTION_DEFAULT) |
|
1950 action = nsIDragService::DRAGDROP_ACTION_MOVE; |
|
1951 |
|
1952 // first check to see if move is set |
|
1953 if (gdkAction & GDK_ACTION_MOVE) |
|
1954 action = nsIDragService::DRAGDROP_ACTION_MOVE; |
|
1955 |
|
1956 // then fall to the others |
|
1957 else if (gdkAction & GDK_ACTION_LINK) |
|
1958 action = nsIDragService::DRAGDROP_ACTION_LINK; |
|
1959 |
|
1960 // copy is ctrl |
|
1961 else if (gdkAction & GDK_ACTION_COPY) |
|
1962 action = nsIDragService::DRAGDROP_ACTION_COPY; |
|
1963 |
|
1964 // update the drag information |
|
1965 SetDragAction(action); |
|
1966 } |
|
1967 |
|
1968 void |
|
1969 nsDragService::DispatchMotionEvents() |
|
1970 { |
|
1971 mCanDrop = false; |
|
1972 |
|
1973 FireDragEventAtSource(NS_DRAGDROP_DRAG); |
|
1974 |
|
1975 mTargetWindow-> |
|
1976 DispatchDragEvent(NS_DRAGDROP_OVER, mTargetWindowPoint, mTargetTime); |
|
1977 } |
|
1978 |
|
1979 // Returns true if the drop was successful |
|
1980 gboolean |
|
1981 nsDragService::DispatchDropEvent() |
|
1982 { |
|
1983 // We need to check IsDestroyed here because the nsRefPtr |
|
1984 // only protects this from being deleted, it does NOT protect |
|
1985 // against nsView::~nsView() calling Destroy() on it, bug 378273. |
|
1986 if (mTargetWindow->IsDestroyed()) |
|
1987 return FALSE; |
|
1988 |
|
1989 uint32_t msg = mCanDrop ? NS_DRAGDROP_DROP : NS_DRAGDROP_EXIT; |
|
1990 |
|
1991 mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime); |
|
1992 |
|
1993 return mCanDrop; |
|
1994 } |