Bug 1253840 - Remove .ini file as it's no longer necessary by passing on all platform...
[gecko.git] / widget / cocoa / nsDragService.mm
blob4ac5c3cbc0d917ad9a5b12fa9c9855006fec7ce5
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Logging.h"
8 #include "gfxContext.h"
9 #include "nsArrayUtils.h"
10 #include "nsDragService.h"
11 #include "nsArrayUtils.h"
12 #include "nsObjCExceptions.h"
13 #include "nsITransferable.h"
14 #include "nsString.h"
15 #include "nsClipboard.h"
16 #include "nsXPCOM.h"
17 #include "nsCOMPtr.h"
18 #include "nsPrimitiveHelpers.h"
19 #include "nsLinebreakConverter.h"
20 #include "nsINode.h"
21 #include "nsRect.h"
22 #include "nsPoint.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/DocumentInlines.h"
26 #include "nsIContent.h"
27 #include "nsView.h"
28 #include "nsCocoaUtils.h"
29 #include "mozilla/gfx/2D.h"
30 #include "gfxPlatform.h"
31 #include "nsDeviceContext.h"
33 using namespace mozilla;
34 using namespace mozilla::gfx;
36 extern mozilla::LazyLogModule sCocoaLog;
38 extern NSPasteboard* globalDragPboard;
39 extern ChildView* gLastDragView;
40 extern NSEvent* gLastDragMouseDownEvent;
41 extern bool gUserCancelledDrag;
43 // This global makes the transferable array available to Cocoa's promised
44 // file destination callback.
45 mozilla::StaticRefPtr<nsIArray> gDraggedTransferables;
47 nsDragService::nsDragService()
48     : mNativeDragView(nil), mNativeDragEvent(nil), mDragImageChanged(false) {}
50 nsDragService::~nsDragService() {}
52 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
53                                            NSPoint* aDragPoint) {
54   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
56   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
58   LayoutDeviceIntRect dragRect(0, 0, 20, 20);
59   NSImage* image = ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
60   if (!image) {
61     // if no image was returned, just draw a rectangle
62     NSSize size;
63     size.width = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
64     size.height = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
65     image = [NSImage imageWithSize:size
66                            flipped:YES
67                     drawingHandler:^BOOL(NSRect dstRect) {
68                       [[NSColor grayColor] set];
69                       NSBezierPath* path = [NSBezierPath bezierPathWithRect:dstRect];
70                       [path setLineWidth:2.0];
71                       [path stroke];
72                       return YES;
73                     }];
74   }
76   LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
77   NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
78   point.y = nsCocoaUtils::FlippedScreenY(point.y);
80   point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
81   *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
83   return image;
85   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
88 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
89                                            CSSIntPoint aPoint, LayoutDeviceIntRect* aDragRect) {
90   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
92   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
94   RefPtr<SourceSurface> surface;
95   nsPresContext* pc;
96   nsresult rv = DrawDrag(aDOMNode, aRegion, aPoint, aDragRect, &surface, &pc);
97   if (pc && (!aDragRect->width || !aDragRect->height)) {
98     // just use some suitable defaults
99     int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
100     aDragRect->SetRect(pc->CSSPixelsToDevPixels(aPoint.x), pc->CSSPixelsToDevPixels(aPoint.y), size,
101                        size);
102   }
104   if (NS_FAILED(rv) || !surface) return nil;
106   uint32_t width = aDragRect->width;
107   uint32_t height = aDragRect->height;
109   RefPtr<DataSourceSurface> dataSurface =
110       Factory::CreateDataSourceSurface(IntSize(width, height), SurfaceFormat::B8G8R8A8);
111   DataSourceSurface::MappedSurface map;
112   if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
113     return nil;
114   }
116   RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
117       BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, dataSurface->GetFormat());
118   if (!dt) {
119     dataSurface->Unmap();
120     return nil;
121   }
123   dt->FillRect(gfx::Rect(0, 0, width, height), SurfacePattern(surface, ExtendMode::CLAMP),
124                DrawOptions(1.0f, CompositionOp::OP_SOURCE));
126   NSBitmapImageRep* imageRep =
127       [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
128                                               pixelsWide:width
129                                               pixelsHigh:height
130                                            bitsPerSample:8
131                                          samplesPerPixel:4
132                                                 hasAlpha:YES
133                                                 isPlanar:NO
134                                           colorSpaceName:NSDeviceRGBColorSpace
135                                              bytesPerRow:width * 4
136                                             bitsPerPixel:32];
138   uint8_t* dest = [imageRep bitmapData];
139   for (uint32_t i = 0; i < height; ++i) {
140     uint8_t* src = map.mData + i * map.mStride;
141     for (uint32_t j = 0; j < width; ++j) {
142       // Reduce transparency overall by multipying by a factor. Remember, Alpha
143       // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
144 #ifdef IS_BIG_ENDIAN
145       dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
146       dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
147       dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
148       dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
149 #else
150       dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
151       dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
152       dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
153       dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
154 #endif
155       src += 4;
156       dest += 4;
157     }
158   }
159   dataSurface->Unmap();
161   NSImage* image =
162       [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
163   [image addRepresentation:imageRep];
164   [imageRep release];
166   return [image autorelease];
168   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
171 nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
172                                               const Maybe<CSSIntRegion>& aRegion,
173                                               uint32_t aActionType) {
174   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
176 #ifdef NIGHTLY_BUILD
177   MOZ_RELEASE_ASSERT(NS_IsMainThread());
178 #endif
180   if (!gLastDragView) {
181     // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView mouseUp:].
182     // If we get here with gLastDragView being null, that means that the mouse button has already
183     // been released. In that case we need to abort the drag because the OS won't know where to drop
184     // whatever's being dragged, and we might end up with a stuck drag & drop session.
185     return NS_ERROR_FAILURE;
186   }
188   mDataItems = aTransferableArray;
190   // Save the transferables away in case a promised file callback is invoked.
191   gDraggedTransferables = aTransferableArray;
193   // We need to retain the view and the event during the drag in case either
194   // gets destroyed.
195   mNativeDragView = [gLastDragView retain];
196   mNativeDragEvent = [gLastDragMouseDownEvent retain];
198   gUserCancelledDrag = false;
200   NSPasteboardItem* pbItem = [NSPasteboardItem new];
201   NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
203   if (gDraggedTransferables) {
204     uint32_t count = 0;
205     gDraggedTransferables->GetLength(&count);
207     for (uint32_t j = 0; j < count; j++) {
208       nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
209       if (!currentTransferable) {
210         return NS_ERROR_FAILURE;
211       }
213       // Transform the transferable to an NSDictionary
214       NSDictionary* pasteboardOutputDict =
215           nsClipboard::PasteboardDictFromTransferable(currentTransferable);
216       if (!pasteboardOutputDict) {
217         return NS_ERROR_FAILURE;
218       }
220       // write everything out to the general pasteboard
221       [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
222       // Gecko is initiating this drag so we always want its own views to
223       // consider it. Add our wildcard type to the pasteboard to accomplish
224       // this.
225       [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
226     }
227   }
228   [pbItem setDataProvider:mNativeDragView forTypes:types];
230   NSPoint draggingPoint;
231   NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
233   NSRect localDragRect = image.alignmentRect;
234   localDragRect.origin.x = draggingPoint.x;
235   localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
237   NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
238   [pbItem release];
239   [dragItem setDraggingFrame:localDragRect contents:image];
241   nsBaseDragService::StartDragSession();
242   nsBaseDragService::OpenDragPopup();
244   NSDraggingSession* draggingSession = [mNativeDragView
245       beginDraggingSessionWithItems:[NSArray arrayWithObject:[dragItem autorelease]]
246                               event:mNativeDragEvent
247                              source:mNativeDragView];
248   draggingSession.animatesToStartingPositionsOnCancelOrFail =
249       !mDataTransfer || mDataTransfer->MozShowFailAnimation();
251   return NS_OK;
253   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
256 NS_IMETHODIMP
257 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
258   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
260   if (!aTransferable) {
261     return NS_ERROR_FAILURE;
262   }
264   // get flavor list that includes all acceptable flavors (including ones obtained through
265   // conversion)
266   nsTArray<nsCString> flavors;
267   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
268   if (NS_FAILED(rv)) {
269     return NS_ERROR_FAILURE;
270   }
272   // if this drag originated within Mozilla we should just use the cached data from
273   // when the drag started if possible
274   if (mDataItems) {
275     nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
276     if (currentTransferable) {
277       for (uint32_t i = 0; i < flavors.Length(); i++) {
278         nsCString& flavorStr = flavors[i];
280         nsCOMPtr<nsISupports> dataSupports;
281         rv = currentTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
282         if (NS_SUCCEEDED(rv)) {
283           aTransferable->SetTransferData(flavorStr.get(), dataSupports);
284           return NS_OK;  // maybe try to fill in more types? Is there a point?
285         }
286       }
287     }
288   }
290   NSArray* droppedItems = [globalDragPboard pasteboardItems];
291   if (!droppedItems) {
292     return NS_ERROR_FAILURE;
293   }
295   uint32_t itemCount = [droppedItems count];
296   if (aItemIndex >= itemCount) {
297     return NS_ERROR_FAILURE;
298   }
300   NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
301   if (!item) {
302     return NS_ERROR_FAILURE;
303   }
305   // now check the actual clipboard for data
306   for (uint32_t i = 0; i < flavors.Length(); i++) {
307     nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable, flavors[i], item);
308   }
310   return NS_OK;
312   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
315 NS_IMETHODIMP
316 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
317   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
319   *_retval = false;
321   if (!globalDragPboard) return NS_ERROR_FAILURE;
323   nsDependentCString dataFlavor(aDataFlavor);
325   // first see if we have data for this in our cached transferable
326   if (mDataItems) {
327     uint32_t dataItemsCount;
328     mDataItems->GetLength(&dataItemsCount);
329     for (unsigned int i = 0; i < dataItemsCount; i++) {
330       nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
331       if (!currentTransferable) continue;
333       nsTArray<nsCString> flavors;
334       nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
335       if (NS_FAILED(rv)) continue;
337       for (uint32_t j = 0; j < flavors.Length(); j++) {
338         if (dataFlavor.Equals(flavors[j])) {
339           *_retval = true;
340           return NS_OK;
341         }
342       }
343     }
344   }
346   const NSString* type = nil;
347   bool allowFileURL = false;
348   if (dataFlavor.EqualsLiteral(kFileMime)) {
349     type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
350     allowFileURL = true;
351   } else if (dataFlavor.EqualsLiteral(kTextMime)) {
352     type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
353   } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
354     type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
355   } else if (dataFlavor.EqualsLiteral(kURLMime) || dataFlavor.EqualsLiteral(kURLDataMime)) {
356     type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
357   } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
358     type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
359   } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
360     type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
361   } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
362     type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
363   }
365   NSString* availableType =
366       [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
367   if (availableType && nsCocoaUtils::IsValidPasteboardType(availableType, allowFileURL)) {
368     *_retval = true;
369   }
371   return NS_OK;
373   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
376 NS_IMETHODIMP
377 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
378   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
380   *aNumItems = 0;
382   // first check to see if we have a number of items cached
383   if (mDataItems) {
384     mDataItems->GetLength(aNumItems);
385     return NS_OK;
386   }
388   NSArray* droppedItems = [globalDragPboard pasteboardItems];
389   if (droppedItems) {
390     *aNumItems = [droppedItems count];
391   }
393   return NS_OK;
395   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
398 NS_IMETHODIMP
399 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) {
400   nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
401   mDragImageChanged = true;
402   return NS_OK;
405 void nsDragService::DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint) {
406   aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
408   // XXX It feels like we should be using the backing scale factor at aPoint
409   // rather than the initial drag view, but I've seen no ill effects of this.
410   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
411   LayoutDeviceIntPoint devPoint = nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
413   // If the image has changed, call enumerateDraggingItemsWithOptions to get
414   // the item being dragged and update its image.
415   if (mDragImageChanged && mNativeDragView) {
416     mDragImageChanged = false;
418     nsPresContext* pc = nullptr;
419     nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
420     if (content) {
421       pc = content->OwnerDoc()->GetPresContext();
422     }
424     if (pc) {
425       void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) =
426           ^(NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
427             // We never add more than one item right now, but check just in case.
428             if (idx > 0) {
429               return;
430             }
432             nsPoint pt =
433                 LayoutDevicePixel::ToAppUnits(devPoint, pc->DeviceContext()->AppUnitsPerDevPixel());
434             CSSIntPoint screenPoint = CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
435                                                   nsPresContext::AppUnitsToIntCSSPixels(pt.y));
437             // Create a new image; if one isn't returned don't change the current one.
438             LayoutDeviceIntRect newRect;
439             NSImage* image = ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
440             if (image) {
441               NSRect draggingRect = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
442               [draggingItem setDraggingFrame:draggingRect contents:image];
443             }
444           };
446       [aSession enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
447                                           forView:nil
448                                           classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
449                                     searchOptions:@{}
450                                        usingBlock:changeImageBlock];
451     }
452   }
454   DragMoved(devPoint.x, devPoint.y);
457 NS_IMETHODIMP
458 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
459   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
461   if (mNativeDragView) {
462     [mNativeDragView release];
463     mNativeDragView = nil;
464   }
465   if (mNativeDragEvent) {
466     [mNativeDragEvent release];
467     mNativeDragEvent = nil;
468   }
470   mUserCancelled = gUserCancelledDrag;
472   nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
473   mDataItems = nullptr;
474   return rv;
476   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);