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 "nsIScrollableView.h"
57 #include "nsIDOMNode.h"
58 #include "nsIDOMDragEvent.h"
59 #include "nsISelection.h"
60 #include "nsISelectionPrivate.h"
61 #include "nsPresContext.h"
62 #include "nsIDOMDataTransfer.h"
63 #include "nsIEventStateManager.h"
64 #include "nsICanvasElement.h"
66 #include "nsIImageLoadingContent.h"
67 #include "gfxIImageFrame.h"
68 #include "imgIContainer.h"
69 #include "imgIRequest.h"
70 #include "nsIViewObserver.h"
72 #include "nsGUIEvent.h"
73 #include "nsIPrefService.h"
75 #include "gfxContext.h"
76 #include "gfxImageSurface.h"
78 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
80 nsBaseDragService::nsBaseDragService()
81 : mCanDrop(PR_FALSE
), mDoingDrag(PR_FALSE
), mHasImage(PR_FALSE
),
82 mDragAction(DRAGDROP_ACTION_NONE
), mTargetSize(0,0),
83 mImageX(0), mImageY(0), mScreenX(-1), mScreenY(-1), mSuppressLevel(0)
87 nsBaseDragService::~nsBaseDragService()
91 NS_IMPL_ISUPPORTS2(nsBaseDragService
, nsIDragService
, nsIDragSession
)
93 //---------------------------------------------------------
95 nsBaseDragService::SetCanDrop(PRBool aCanDrop
)
101 //---------------------------------------------------------
103 nsBaseDragService::GetCanDrop(PRBool
* aCanDrop
)
105 *aCanDrop
= mCanDrop
;
109 //---------------------------------------------------------
111 nsBaseDragService::SetDragAction(PRUint32 anAction
)
113 mDragAction
= anAction
;
117 //---------------------------------------------------------
119 nsBaseDragService::GetDragAction(PRUint32
* anAction
)
121 *anAction
= mDragAction
;
125 //---------------------------------------------------------
127 nsBaseDragService::SetTargetSize(nsSize aDragTargetSize
)
129 mTargetSize
= aDragTargetSize
;
133 //---------------------------------------------------------
135 nsBaseDragService::GetTargetSize(nsSize
* aDragTargetSize
)
137 *aDragTargetSize
= mTargetSize
;
141 //-------------------------------------------------------------------------
144 nsBaseDragService::GetNumDropItems(PRUint32
* aNumItems
)
147 return NS_ERROR_FAILURE
;
154 // Returns the DOM document where the drag was initiated. This will be
155 // nsnull if the drag began outside of our application.
158 nsBaseDragService::GetSourceDocument(nsIDOMDocument
** aSourceDocument
)
160 *aSourceDocument
= mSourceDocument
.get();
161 NS_IF_ADDREF(*aSourceDocument
);
169 // Returns the DOM node where the drag was initiated. This will be
170 // nsnull if the drag began outside of our application.
173 nsBaseDragService::GetSourceNode(nsIDOMNode
** aSourceNode
)
175 *aSourceNode
= mSourceNode
.get();
176 NS_IF_ADDREF(*aSourceNode
);
182 //-------------------------------------------------------------------------
185 nsBaseDragService::GetData(nsITransferable
* aTransferable
,
188 return NS_ERROR_FAILURE
;
191 //-------------------------------------------------------------------------
193 nsBaseDragService::IsDataFlavorSupported(const char *aDataFlavor
,
196 return NS_ERROR_FAILURE
;
200 nsBaseDragService::GetDataTransfer(nsIDOMDataTransfer
** aDataTransfer
)
202 *aDataTransfer
= mDataTransfer
;
203 NS_IF_ADDREF(*aDataTransfer
);
208 nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer
* aDataTransfer
)
210 mDataTransfer
= aDataTransfer
;
214 //-------------------------------------------------------------------------
216 nsBaseDragService::InvokeDragSession(nsIDOMNode
*aDOMNode
,
217 nsISupportsArray
* aTransferableArray
,
218 nsIScriptableRegion
* aDragRgn
,
219 PRUint32 aActionType
)
221 NS_ENSURE_TRUE(aDOMNode
, NS_ERROR_INVALID_ARG
);
222 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
224 // stash the document of the dom node
225 aDOMNode
->GetOwnerDocument(getter_AddRefs(mSourceDocument
));
226 mSourceNode
= aDOMNode
;
228 // When the mouse goes down, the selection code starts a mouse
229 // capture. However, this gets in the way of determining drag
230 // feedback for things like trees because the event coordinates
231 // are in the wrong coord system. Turn off mouse capture in
232 // the associated view manager.
233 nsCOMPtr
<nsIContent
> contentNode
= do_QueryInterface(aDOMNode
);
235 nsIDocument
* doc
= contentNode
->GetCurrentDoc();
237 nsIPresShell
* presShell
= doc
->GetPrimaryShell();
239 nsIViewManager
* vm
= presShell
->GetViewManager();
242 vm
->GrabMouseEvents(nsnull
, notUsed
);
252 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode
* aDOMNode
,
253 nsISupportsArray
* aTransferableArray
,
254 nsIScriptableRegion
* aRegion
,
255 PRUint32 aActionType
,
257 PRInt32 aImageX
, PRInt32 aImageY
,
258 nsIDOMDragEvent
* aDragEvent
,
259 nsIDOMDataTransfer
* aDataTransfer
)
261 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
262 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
263 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
265 mDataTransfer
= aDataTransfer
;
272 aDragEvent
->GetScreenX(&mScreenX
);
273 aDragEvent
->GetScreenY(&mScreenY
);
275 return InvokeDragSession(aDOMNode
, aTransferableArray
, aRegion
, aActionType
);
279 nsBaseDragService::InvokeDragSessionWithSelection(nsISelection
* aSelection
,
280 nsISupportsArray
* aTransferableArray
,
281 PRUint32 aActionType
,
282 nsIDOMDragEvent
* aDragEvent
,
283 nsIDOMDataTransfer
* aDataTransfer
)
285 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
286 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
287 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
289 mDataTransfer
= aDataTransfer
;
290 mSelection
= aSelection
;
296 aDragEvent
->GetScreenX(&mScreenX
);
297 aDragEvent
->GetScreenY(&mScreenY
);
299 // just get the focused node from the selection
300 // XXXndeakin this should actually be the deepest node that contains both
301 // endpoints of the selection
302 nsCOMPtr
<nsIDOMNode
> node
;
303 aSelection
->GetFocusNode(getter_AddRefs(node
));
305 return InvokeDragSession(node
, aTransferableArray
, nsnull
, aActionType
);
308 //-------------------------------------------------------------------------
310 nsBaseDragService::GetCurrentSession(nsIDragSession
** aSession
)
313 return NS_ERROR_INVALID_ARG
;
315 // "this" also implements a drag session, so say we are one but only
316 // if there is currently a drag going on.
317 if (!mSuppressLevel
&& mDoingDrag
) {
319 NS_ADDREF(*aSession
); // addRef because we're a "getter"
327 //-------------------------------------------------------------------------
329 nsBaseDragService::StartDragSession()
332 return NS_ERROR_FAILURE
;
334 mDoingDrag
= PR_TRUE
;
338 //-------------------------------------------------------------------------
340 nsBaseDragService::EndDragSession(PRBool aDoneDrag
)
343 return NS_ERROR_FAILURE
;
346 if (aDoneDrag
&& !mSuppressLevel
)
347 FireDragEventAtSource(NS_DRAGDROP_END
);
349 mDoingDrag
= PR_FALSE
;
351 // release the source we've been holding on to.
352 mSourceDocument
= nsnull
;
353 mSourceNode
= nsnull
;
355 mDataTransfer
= nsnull
;
356 mHasImage
= PR_FALSE
;
367 nsBaseDragService::FireDragEventAtSource(PRUint32 aMsg
)
369 if (mSourceNode
&& !mSuppressLevel
) {
370 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(mSourceDocument
);
372 nsCOMPtr
<nsIPresShell
> presShell
= doc
->GetPrimaryShell();
374 nsEventStatus status
= nsEventStatus_eIgnore
;
375 nsDragEvent
event(PR_TRUE
, aMsg
, nsnull
);
377 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mSourceNode
);
378 return presShell
->HandleDOMEventWithTarget(content
, &event
, &status
);
387 GetPresShellForContent(nsIDOMNode
* aDOMNode
)
389 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aDOMNode
);
390 nsCOMPtr
<nsIDocument
> document
= content
->GetCurrentDoc();
392 document
->FlushPendingNotifications(Flush_Display
);
394 return document
->GetPrimaryShell();
401 nsBaseDragService::DrawDrag(nsIDOMNode
* aDOMNode
,
402 nsIScriptableRegion
* aRegion
,
403 PRInt32 aScreenX
, PRInt32 aScreenY
,
404 nsRect
* aScreenDragRect
,
405 gfxASurface
** aSurface
,
406 nsPresContext
** aPresContext
)
409 *aPresContext
= nsnull
;
411 // use a default size, in case of an error.
412 aScreenDragRect
->x
= aScreenX
- mImageX
;
413 aScreenDragRect
->y
= aScreenY
- mImageY
;
414 aScreenDragRect
->width
= 20;
415 aScreenDragRect
->height
= 20;
417 // if a drag image was specified, use that, otherwise, use the source node
418 nsCOMPtr
<nsIDOMNode
> dragNode
= mImage
? mImage
.get() : aDOMNode
;
420 // get the presshell for the node being dragged. If the drag image is not in
421 // a document or has no frame, get the presshell from the source drag node
422 nsIPresShell
* presShell
= GetPresShellForContent(dragNode
);
423 if (!presShell
&& mImage
)
424 presShell
= GetPresShellForContent(aDOMNode
);
426 return NS_ERROR_FAILURE
;
428 *aPresContext
= presShell
->GetPresContext();
430 // check if drag images are disabled
431 PRBool enableDragImages
= PR_TRUE
;
432 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID
));
434 prefs
->GetBoolPref(DRAGIMAGES_PREF
, &enableDragImages
);
436 // didn't want an image, so just set the screen rectangle to the frame size
437 if (!enableDragImages
|| !mHasImage
) {
438 // if a region was specified, set the screen rectangle to the area that
439 // the region occupies
441 // the region's coordinates are relative to the root frame
442 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
443 if (rootFrame
&& *aPresContext
) {
445 aRegion
->GetBoundingBox(&dragRect
.x
, &dragRect
.y
, &dragRect
.width
, &dragRect
.height
);
446 dragRect
.ScaleRoundOut(nsPresContext::AppUnitsPerCSSPixel());
447 dragRect
.ScaleRoundOut(1.0 / (*aPresContext
)->AppUnitsPerDevPixel());
449 nsIntRect screenRect
= rootFrame
->GetScreenRectExternal();
450 aScreenDragRect
->SetRect(screenRect
.x
+ dragRect
.x
, screenRect
.y
+ dragRect
.y
,
451 dragRect
.width
, dragRect
.height
);
455 // otherwise, there was no region so just set the rectangle to
456 // the size of the primary frame of the content.
457 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
458 nsIFrame
* frame
= presShell
->GetPrimaryFrameFor(content
);
460 nsIntRect screenRect
= frame
->GetScreenRectExternal();
461 aScreenDragRect
->SetRect(screenRect
.x
, screenRect
.y
,
462 screenRect
.width
, screenRect
.height
);
469 // draw the image for selections
471 nsPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
472 nsRefPtr
<gfxASurface
> surface
= presShell
->RenderSelection(mSelection
, pnt
, aScreenDragRect
);
474 NS_IF_ADDREF(*aSurface
);
478 // if an custom image was specified, check if it is an image node and draw
479 // using the source rather than the displayed image. But if mImage isn't
480 // an image, fall through to RenderNode below.
482 nsCOMPtr
<nsICanvasElement
> canvas
= do_QueryInterface(dragNode
);
484 return DrawDragForImage(*aPresContext
, nsnull
, canvas
, aScreenX
,
485 aScreenY
, aScreenDragRect
, aSurface
);
488 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(dragNode
);
489 // for image nodes, create the drag image from the actual image data
491 return DrawDragForImage(*aPresContext
, imageLoader
, nsnull
, aScreenX
,
492 aScreenY
, aScreenDragRect
, aSurface
);
496 // otherwise, just draw the node
497 nsCOMPtr
<nsIRegion
> clipRegion
;
499 aRegion
->GetRegion(getter_AddRefs(clipRegion
));
501 nsPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
502 nsRefPtr
<gfxASurface
> surface
= presShell
->RenderNode(dragNode
, clipRegion
,
503 pnt
, aScreenDragRect
);
505 // if an image was specified, reposition the drag rectangle to
506 // the supplied offset in mImageX and mImageY.
508 aScreenDragRect
->x
= aScreenX
- mImageX
;
509 aScreenDragRect
->y
= aScreenY
- mImageY
;
513 NS_IF_ADDREF(*aSurface
);
519 nsBaseDragService::DrawDragForImage(nsPresContext
* aPresContext
,
520 nsIImageLoadingContent
* aImageLoader
,
521 nsICanvasElement
* aCanvas
,
522 PRInt32 aScreenX
, PRInt32 aScreenY
,
523 nsRect
* aScreenDragRect
,
524 gfxASurface
** aSurface
)
526 nsCOMPtr
<nsIImage
> img
;
528 nsCOMPtr
<imgIRequest
> imgRequest
;
529 nsresult rv
= aImageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
530 getter_AddRefs(imgRequest
));
531 NS_ENSURE_SUCCESS(rv
, rv
);
533 return NS_ERROR_NOT_AVAILABLE
;
535 nsCOMPtr
<imgIContainer
> imgContainer
;
536 rv
= imgRequest
->GetImage(getter_AddRefs(imgContainer
));
537 NS_ENSURE_SUCCESS(rv
, rv
);
539 return NS_ERROR_NOT_AVAILABLE
;
541 nsCOMPtr
<gfxIImageFrame
> iframe
;
542 imgContainer
->GetCurrentFrame(getter_AddRefs(iframe
));
544 return NS_ERROR_FAILURE
;
546 img
= do_GetInterface(iframe
);
548 return NS_ERROR_FAILURE
;
550 // use the size of the image as the size of the drag image
551 imgContainer
->GetWidth(&aScreenDragRect
->width
);
552 imgContainer
->GetHeight(&aScreenDragRect
->height
);
555 NS_ASSERTION(aCanvas
, "both image and canvas are null");
556 PRUint32 width
, height
;
557 aCanvas
->GetSize(&width
, &height
);
558 aScreenDragRect
->width
= width
;
559 aScreenDragRect
->height
= height
;
562 nsSize srcSize
= aScreenDragRect
->Size();
563 nsSize destSize
= srcSize
;
565 if (destSize
.width
== 0 || destSize
.height
== 0)
566 return NS_ERROR_FAILURE
;
568 // if the image is larger than half the screen size, scale it down. This
569 // scaling algorithm is the same as is used in nsPresShell::PaintRangePaintInfo
570 nsIDeviceContext
* deviceContext
= aPresContext
->DeviceContext();
572 deviceContext
->GetClientRect(maxSize
);
573 nscoord maxWidth
= aPresContext
->AppUnitsToDevPixels(maxSize
.width
>> 1);
574 nscoord maxHeight
= aPresContext
->AppUnitsToDevPixels(maxSize
.height
>> 1);
575 if (destSize
.width
> maxWidth
|| destSize
.height
> maxHeight
) {
577 if (destSize
.width
> maxWidth
)
578 scale
= PR_MIN(scale
, float(maxWidth
) / destSize
.width
);
579 if (destSize
.height
> maxHeight
)
580 scale
= PR_MIN(scale
, float(maxHeight
) / destSize
.height
);
582 destSize
.width
= NSToIntFloor(float(destSize
.width
) * scale
);
583 destSize
.height
= NSToIntFloor(float(destSize
.height
) * scale
);
585 aScreenDragRect
->x
= NSToIntFloor(aScreenX
- float(mImageX
) * scale
);
586 aScreenDragRect
->y
= NSToIntFloor(aScreenY
- float(mImageY
) * scale
);
587 aScreenDragRect
->width
= destSize
.width
;
588 aScreenDragRect
->height
= destSize
.height
;
591 nsRefPtr
<gfxImageSurface
> surface
=
592 new gfxImageSurface(gfxIntSize(destSize
.width
, destSize
.height
),
593 gfxImageSurface::ImageFormatARGB32
);
595 return NS_ERROR_FAILURE
;
597 nsRefPtr
<gfxContext
> ctx
= new gfxContext(surface
);
599 return NS_ERROR_FAILURE
;
602 NS_ADDREF(*aSurface
);
605 gfxRect
outRect(0, 0, destSize
.width
, destSize
.height
);
607 gfxMatrix().Scale(srcSize
.width
/outRect
.Width(), srcSize
.height
/outRect
.Height());
608 img
->Draw(ctx
, scale
, outRect
, nsIntMargin(0,0,0,0),
609 nsIntRect(0, 0, srcSize
.width
, srcSize
.height
));
612 return aCanvas
->RenderContexts(ctx
);
617 nsBaseDragService::ConvertToUnscaledDevPixels(nsPresContext
* aPresContext
,
618 PRInt32
* aScreenX
, PRInt32
* aScreenY
)
620 PRInt32 adj
= aPresContext
->DeviceContext()->UnscaledAppUnitsPerDevPixel();
621 *aScreenX
= nsPresContext::CSSPixelsToAppUnits(*aScreenX
) / adj
;
622 *aScreenY
= nsPresContext::CSSPixelsToAppUnits(*aScreenY
) / adj
;
626 nsBaseDragService::Suppress()
628 EndDragSession(PR_FALSE
);
634 nsBaseDragService::Unsuppress()