1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Mats Palmgren <mats.palmgren@bredband.net>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsBaseDragService.h"
40 #include "nsITransferable.h"
42 #include "nsIServiceManager.h"
43 #include "nsITransferable.h"
44 #include "nsISupportsArray.h"
46 #include "nsIRegion.h"
48 #include "nsISupportsPrimitives.h"
50 #include "nsIInterfaceRequestorUtils.h"
52 #include "nsIDocument.h"
53 #include "nsIContent.h"
54 #include "nsIPresShell.h"
55 #include "nsIViewManager.h"
56 #include "nsIDOMNode.h"
57 #include "nsIDOMDragEvent.h"
58 #include "nsISelection.h"
59 #include "nsISelectionPrivate.h"
60 #include "nsPresContext.h"
61 #include "nsIDOMDataTransfer.h"
62 #include "nsIEventStateManager.h"
63 #include "nsICanvasElementExternal.h"
64 #include "nsIImageLoadingContent.h"
65 #include "imgIContainer.h"
66 #include "imgIRequest.h"
67 #include "nsIViewObserver.h"
69 #include "nsGUIEvent.h"
70 #include "nsIPrefService.h"
72 #include "gfxContext.h"
73 #include "gfxPlatform.h"
75 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
77 nsBaseDragService::nsBaseDragService()
78 : mCanDrop(PR_FALSE
), mOnlyChromeDrop(PR_FALSE
), mDoingDrag(PR_FALSE
),
79 mHasImage(PR_FALSE
), mUserCancelled(PR_FALSE
),
80 mDragAction(DRAGDROP_ACTION_NONE
), mTargetSize(0,0),
81 mImageX(0), mImageY(0), mScreenX(-1), mScreenY(-1), mSuppressLevel(0),
82 mInputSource(nsIDOMNSMouseEvent::MOZ_SOURCE_MOUSE
)
86 nsBaseDragService::~nsBaseDragService()
90 NS_IMPL_ISUPPORTS2(nsBaseDragService
, nsIDragService
, nsIDragSession
)
92 //---------------------------------------------------------
94 nsBaseDragService::SetCanDrop(PRBool aCanDrop
)
100 //---------------------------------------------------------
102 nsBaseDragService::GetCanDrop(PRBool
* aCanDrop
)
104 *aCanDrop
= mCanDrop
;
107 //---------------------------------------------------------
109 nsBaseDragService::SetOnlyChromeDrop(PRBool aOnlyChrome
)
111 mOnlyChromeDrop
= aOnlyChrome
;
115 //---------------------------------------------------------
117 nsBaseDragService::GetOnlyChromeDrop(PRBool
* aOnlyChrome
)
119 *aOnlyChrome
= mOnlyChromeDrop
;
123 //---------------------------------------------------------
125 nsBaseDragService::SetDragAction(PRUint32 anAction
)
127 mDragAction
= anAction
;
131 //---------------------------------------------------------
133 nsBaseDragService::GetDragAction(PRUint32
* anAction
)
135 *anAction
= mDragAction
;
139 //---------------------------------------------------------
141 nsBaseDragService::SetTargetSize(nsSize aDragTargetSize
)
143 mTargetSize
= aDragTargetSize
;
147 //---------------------------------------------------------
149 nsBaseDragService::GetTargetSize(nsSize
* aDragTargetSize
)
151 *aDragTargetSize
= mTargetSize
;
155 //-------------------------------------------------------------------------
158 nsBaseDragService::GetNumDropItems(PRUint32
* aNumItems
)
161 return NS_ERROR_FAILURE
;
168 // Returns the DOM document where the drag was initiated. This will be
169 // nsnull if the drag began outside of our application.
172 nsBaseDragService::GetSourceDocument(nsIDOMDocument
** aSourceDocument
)
174 *aSourceDocument
= mSourceDocument
.get();
175 NS_IF_ADDREF(*aSourceDocument
);
183 // Returns the DOM node where the drag was initiated. This will be
184 // nsnull if the drag began outside of our application.
187 nsBaseDragService::GetSourceNode(nsIDOMNode
** aSourceNode
)
189 *aSourceNode
= mSourceNode
.get();
190 NS_IF_ADDREF(*aSourceNode
);
196 //-------------------------------------------------------------------------
199 nsBaseDragService::GetData(nsITransferable
* aTransferable
,
202 return NS_ERROR_FAILURE
;
205 //-------------------------------------------------------------------------
207 nsBaseDragService::IsDataFlavorSupported(const char *aDataFlavor
,
210 return NS_ERROR_FAILURE
;
214 nsBaseDragService::GetDataTransfer(nsIDOMDataTransfer
** aDataTransfer
)
216 *aDataTransfer
= mDataTransfer
;
217 NS_IF_ADDREF(*aDataTransfer
);
222 nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer
* aDataTransfer
)
224 mDataTransfer
= aDataTransfer
;
228 //-------------------------------------------------------------------------
230 nsBaseDragService::InvokeDragSession(nsIDOMNode
*aDOMNode
,
231 nsISupportsArray
* aTransferableArray
,
232 nsIScriptableRegion
* aDragRgn
,
233 PRUint32 aActionType
)
235 NS_ENSURE_TRUE(aDOMNode
, NS_ERROR_INVALID_ARG
);
236 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
238 // stash the document of the dom node
239 aDOMNode
->GetOwnerDocument(getter_AddRefs(mSourceDocument
));
240 mSourceNode
= aDOMNode
;
241 mEndDragPoint
= nsIntPoint(0, 0);
243 // When the mouse goes down, the selection code starts a mouse
244 // capture. However, this gets in the way of determining drag
245 // feedback for things like trees because the event coordinates
246 // are in the wrong coord system, so turn off mouse capture.
247 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(mSourceDocument
);
249 nsCOMPtr
<nsIViewObserver
> viewObserver
= do_QueryInterface(doc
->GetShell());
251 viewObserver
->ClearMouseCapture(nsnull
);
259 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode
* aDOMNode
,
260 nsISupportsArray
* aTransferableArray
,
261 nsIScriptableRegion
* aRegion
,
262 PRUint32 aActionType
,
264 PRInt32 aImageX
, PRInt32 aImageY
,
265 nsIDOMDragEvent
* aDragEvent
,
266 nsIDOMDataTransfer
* aDataTransfer
)
268 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
269 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
270 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
272 mDataTransfer
= aDataTransfer
;
279 aDragEvent
->GetScreenX(&mScreenX
);
280 aDragEvent
->GetScreenY(&mScreenY
);
282 nsCOMPtr
<nsIDOMNSMouseEvent
> mouseEvent
= do_QueryInterface(aDragEvent
);
283 mouseEvent
->GetMozInputSource(&mInputSource
);
285 return InvokeDragSession(aDOMNode
, aTransferableArray
, aRegion
, aActionType
);
289 nsBaseDragService::InvokeDragSessionWithSelection(nsISelection
* aSelection
,
290 nsISupportsArray
* aTransferableArray
,
291 PRUint32 aActionType
,
292 nsIDOMDragEvent
* aDragEvent
,
293 nsIDOMDataTransfer
* aDataTransfer
)
295 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
296 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
297 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
299 mDataTransfer
= aDataTransfer
;
300 mSelection
= aSelection
;
306 aDragEvent
->GetScreenX(&mScreenX
);
307 aDragEvent
->GetScreenY(&mScreenY
);
309 nsCOMPtr
<nsIDOMNSMouseEvent
> mouseEvent
= do_QueryInterface(aDragEvent
);
310 mouseEvent
->GetMozInputSource(&mInputSource
);
312 // just get the focused node from the selection
313 // XXXndeakin this should actually be the deepest node that contains both
314 // endpoints of the selection
315 nsCOMPtr
<nsIDOMNode
> node
;
316 aSelection
->GetFocusNode(getter_AddRefs(node
));
318 return InvokeDragSession(node
, aTransferableArray
, nsnull
, aActionType
);
321 //-------------------------------------------------------------------------
323 nsBaseDragService::GetCurrentSession(nsIDragSession
** aSession
)
326 return NS_ERROR_INVALID_ARG
;
328 // "this" also implements a drag session, so say we are one but only
329 // if there is currently a drag going on.
330 if (!mSuppressLevel
&& mDoingDrag
) {
332 NS_ADDREF(*aSession
); // addRef because we're a "getter"
340 //-------------------------------------------------------------------------
342 nsBaseDragService::StartDragSession()
345 return NS_ERROR_FAILURE
;
347 mDoingDrag
= PR_TRUE
;
348 // By default dispatch drop also to content.
349 mOnlyChromeDrop
= PR_FALSE
;
353 //-------------------------------------------------------------------------
355 nsBaseDragService::EndDragSession(PRBool aDoneDrag
)
358 return NS_ERROR_FAILURE
;
361 if (aDoneDrag
&& !mSuppressLevel
)
362 FireDragEventAtSource(NS_DRAGDROP_END
);
364 mDoingDrag
= PR_FALSE
;
366 // release the source we've been holding on to.
367 mSourceDocument
= nsnull
;
368 mSourceNode
= nsnull
;
370 mDataTransfer
= nsnull
;
371 mHasImage
= PR_FALSE
;
372 mUserCancelled
= PR_FALSE
;
378 mInputSource
= nsIDOMNSMouseEvent::MOZ_SOURCE_MOUSE
;
384 nsBaseDragService::FireDragEventAtSource(PRUint32 aMsg
)
386 if (mSourceNode
&& !mSuppressLevel
) {
387 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(mSourceDocument
);
389 nsCOMPtr
<nsIPresShell
> presShell
= doc
->GetShell();
391 nsEventStatus status
= nsEventStatus_eIgnore
;
392 nsDragEvent
event(PR_TRUE
, aMsg
, nsnull
);
393 event
.inputSource
= mInputSource
;
394 if (aMsg
== NS_DRAGDROP_END
) {
395 event
.refPoint
.x
= mEndDragPoint
.x
;
396 event
.refPoint
.y
= mEndDragPoint
.y
;
397 event
.userCancelled
= mUserCancelled
;
400 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mSourceNode
);
401 return presShell
->HandleDOMEventWithTarget(content
, &event
, &status
);
410 GetPresShellForContent(nsIDOMNode
* aDOMNode
)
412 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aDOMNode
);
416 nsCOMPtr
<nsIDocument
> document
= content
->GetCurrentDoc();
418 document
->FlushPendingNotifications(Flush_Display
);
420 return document
->GetShell();
427 nsBaseDragService::DrawDrag(nsIDOMNode
* aDOMNode
,
428 nsIScriptableRegion
* aRegion
,
429 PRInt32 aScreenX
, PRInt32 aScreenY
,
430 nsIntRect
* aScreenDragRect
,
431 gfxASurface
** aSurface
,
432 nsPresContext
** aPresContext
)
435 *aPresContext
= nsnull
;
437 // use a default size, in case of an error.
438 aScreenDragRect
->x
= aScreenX
- mImageX
;
439 aScreenDragRect
->y
= aScreenY
- mImageY
;
440 aScreenDragRect
->width
= 20;
441 aScreenDragRect
->height
= 20;
443 // if a drag image was specified, use that, otherwise, use the source node
444 nsCOMPtr
<nsIDOMNode
> dragNode
= mImage
? mImage
.get() : aDOMNode
;
446 // get the presshell for the node being dragged. If the drag image is not in
447 // a document or has no frame, get the presshell from the source drag node
448 nsIPresShell
* presShell
= GetPresShellForContent(dragNode
);
449 if (!presShell
&& mImage
)
450 presShell
= GetPresShellForContent(aDOMNode
);
452 return NS_ERROR_FAILURE
;
454 *aPresContext
= presShell
->GetPresContext();
456 // check if drag images are disabled
457 PRBool enableDragImages
= PR_TRUE
;
458 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
460 prefs
->GetBoolPref(DRAGIMAGES_PREF
, &enableDragImages
);
462 // didn't want an image, so just set the screen rectangle to the frame size
463 if (!enableDragImages
|| !mHasImage
) {
464 // if a region was specified, set the screen rectangle to the area that
465 // the region occupies
467 // the region's coordinates are relative to the root frame
468 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
469 if (rootFrame
&& *aPresContext
) {
471 aRegion
->GetBoundingBox(&dragRect
.x
, &dragRect
.y
, &dragRect
.width
, &dragRect
.height
);
472 dragRect
= dragRect
.ToAppUnits(nsPresContext::AppUnitsPerCSSPixel()).
473 ToOutsidePixels((*aPresContext
)->AppUnitsPerDevPixel());
475 nsIntRect screenRect
= rootFrame
->GetScreenRectExternal();
476 aScreenDragRect
->SetRect(screenRect
.x
+ dragRect
.x
, screenRect
.y
+ dragRect
.y
,
477 dragRect
.width
, dragRect
.height
);
481 // otherwise, there was no region so just set the rectangle to
482 // the size of the primary frame of the content.
483 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
484 nsIFrame
* frame
= content
->GetPrimaryFrame();
486 nsIntRect screenRect
= frame
->GetScreenRectExternal();
487 aScreenDragRect
->SetRect(screenRect
.x
, screenRect
.y
,
488 screenRect
.width
, screenRect
.height
);
495 // draw the image for selections
497 nsIntPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
498 nsRefPtr
<gfxASurface
> surface
= presShell
->RenderSelection(mSelection
, pnt
, aScreenDragRect
);
500 NS_IF_ADDREF(*aSurface
);
504 // if an custom image was specified, check if it is an image node and draw
505 // using the source rather than the displayed image. But if mImage isn't
506 // an image, fall through to RenderNode below.
508 nsCOMPtr
<nsICanvasElementExternal
> canvas
= do_QueryInterface(dragNode
);
510 return DrawDragForImage(*aPresContext
, nsnull
, canvas
, aScreenX
,
511 aScreenY
, aScreenDragRect
, aSurface
);
514 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(dragNode
);
515 // for image nodes, create the drag image from the actual image data
517 return DrawDragForImage(*aPresContext
, imageLoader
, nsnull
, aScreenX
,
518 aScreenY
, aScreenDragRect
, aSurface
);
522 // otherwise, just draw the node
523 nsIntRegion clipRegion
;
525 nsCOMPtr
<nsIRegion
> clipIRegion
;
526 aRegion
->GetRegion(getter_AddRefs(clipIRegion
));
528 clipRegion
= clipIRegion
->GetUnderlyingRegion();
532 nsIntPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
533 nsRefPtr
<gfxASurface
> surface
=
534 presShell
->RenderNode(dragNode
, aRegion
? &clipRegion
: nsnull
,
535 pnt
, aScreenDragRect
);
537 // if an image was specified, reposition the drag rectangle to
538 // the supplied offset in mImageX and mImageY.
540 aScreenDragRect
->x
= aScreenX
- mImageX
;
541 aScreenDragRect
->y
= aScreenY
- mImageY
;
545 NS_IF_ADDREF(*aSurface
);
551 nsBaseDragService::DrawDragForImage(nsPresContext
* aPresContext
,
552 nsIImageLoadingContent
* aImageLoader
,
553 nsICanvasElementExternal
* aCanvas
,
554 PRInt32 aScreenX
, PRInt32 aScreenY
,
555 nsIntRect
* aScreenDragRect
,
556 gfxASurface
** aSurface
)
558 nsCOMPtr
<imgIContainer
> imgContainer
;
560 nsCOMPtr
<imgIRequest
> imgRequest
;
561 nsresult rv
= aImageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
562 getter_AddRefs(imgRequest
));
563 NS_ENSURE_SUCCESS(rv
, rv
);
565 return NS_ERROR_NOT_AVAILABLE
;
567 rv
= imgRequest
->GetImage(getter_AddRefs(imgContainer
));
568 NS_ENSURE_SUCCESS(rv
, rv
);
570 return NS_ERROR_NOT_AVAILABLE
;
572 // use the size of the image as the size of the drag image
573 imgContainer
->GetWidth(&aScreenDragRect
->width
);
574 imgContainer
->GetHeight(&aScreenDragRect
->height
);
577 NS_ASSERTION(aCanvas
, "both image and canvas are null");
578 nsIntSize sz
= aCanvas
->GetSizeExternal();
579 aScreenDragRect
->width
= sz
.width
;
580 aScreenDragRect
->height
= sz
.height
;
583 nsIntSize srcSize
= aScreenDragRect
->Size();
584 nsIntSize destSize
= srcSize
;
586 if (destSize
.width
== 0 || destSize
.height
== 0)
587 return NS_ERROR_FAILURE
;
589 // if the image is larger than half the screen size, scale it down. This
590 // scaling algorithm is the same as is used in nsPresShell::PaintRangePaintInfo
591 nsIDeviceContext
* deviceContext
= aPresContext
->DeviceContext();
593 deviceContext
->GetClientRect(maxSize
);
594 nscoord maxWidth
= aPresContext
->AppUnitsToDevPixels(maxSize
.width
>> 1);
595 nscoord maxHeight
= aPresContext
->AppUnitsToDevPixels(maxSize
.height
>> 1);
596 if (destSize
.width
> maxWidth
|| destSize
.height
> maxHeight
) {
598 if (destSize
.width
> maxWidth
)
599 scale
= PR_MIN(scale
, float(maxWidth
) / destSize
.width
);
600 if (destSize
.height
> maxHeight
)
601 scale
= PR_MIN(scale
, float(maxHeight
) / destSize
.height
);
603 destSize
.width
= NSToIntFloor(float(destSize
.width
) * scale
);
604 destSize
.height
= NSToIntFloor(float(destSize
.height
) * scale
);
606 aScreenDragRect
->x
= NSToIntFloor(aScreenX
- float(mImageX
) * scale
);
607 aScreenDragRect
->y
= NSToIntFloor(aScreenY
- float(mImageY
) * scale
);
608 aScreenDragRect
->width
= destSize
.width
;
609 aScreenDragRect
->height
= destSize
.height
;
612 nsRefPtr
<gfxASurface
> surface
=
613 gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(destSize
.width
, destSize
.height
),
614 gfxASurface::CONTENT_COLOR_ALPHA
);
616 return NS_ERROR_FAILURE
;
618 nsRefPtr
<gfxContext
> ctx
= new gfxContext(surface
);
620 return NS_ERROR_FAILURE
;
623 NS_ADDREF(*aSurface
);
626 gfxRect
outRect(0, 0, destSize
.width
, destSize
.height
);
628 gfxMatrix().Scale(srcSize
.width
/outRect
.Width(), srcSize
.height
/outRect
.Height());
629 nsIntRect
imgSize(0, 0, srcSize
.width
, srcSize
.height
);
630 imgContainer
->Draw(ctx
, gfxPattern::FILTER_GOOD
, scale
, outRect
, imgSize
,
631 destSize
, imgIContainer::FLAG_SYNC_DECODE
);
634 return aCanvas
->RenderContextsExternal(ctx
, gfxPattern::FILTER_GOOD
);
639 nsBaseDragService::ConvertToUnscaledDevPixels(nsPresContext
* aPresContext
,
640 PRInt32
* aScreenX
, PRInt32
* aScreenY
)
642 PRInt32 adj
= aPresContext
->DeviceContext()->UnscaledAppUnitsPerDevPixel();
643 *aScreenX
= nsPresContext::CSSPixelsToAppUnits(*aScreenX
) / adj
;
644 *aScreenY
= nsPresContext::CSSPixelsToAppUnits(*aScreenY
) / adj
;
648 nsBaseDragService::Suppress()
650 EndDragSession(PR_FALSE
);
656 nsBaseDragService::Unsuppress()