From ae6cfb8b02fe88f78f4e9650308b29bff23e30c0 Mon Sep 17 00:00:00 2001 From: "enndeakin%sympatico.ca" Date: Tue, 20 Mar 2007 12:14:52 +0000 Subject: [PATCH] Bug 178513, support translucent drag feedback when dragging nodes and selections on Mac and GTK, r=vlad,josh,roc,sr=roc git-svn-id: svn://10.0.0.236/trunk@222094 18797224-902f-48f8-a5cc-f745e15eee43 --- .../base/src/nsContentAreaDragDrop.cpp | 69 +-- .../content/base/src/nsContentAreaDragDrop.h | 4 +- mozilla/content/base/src/nsRange.cpp | 12 + mozilla/content/base/src/nsRange.h | 3 + .../text/nsPlaintextDataTransfer.cpp | 11 +- mozilla/layout/base/nsIPresShell.h | 38 +- mozilla/layout/base/nsPresShell.cpp | 424 ++++++++++++++++++ .../base/resources/content/messengerdnd.js | 6 +- mozilla/toolkit/content/nsDragAndDrop.js | 4 +- mozilla/widget/public/nsIDragService.idl | 51 ++- mozilla/widget/src/cocoa/nsDragService.h | 6 + mozilla/widget/src/cocoa/nsDragService.mm | 222 +++++---- mozilla/widget/src/gtk2/nsDragService.cpp | 28 +- mozilla/widget/src/gtk2/nsImageToPixbuf.cpp | 35 +- mozilla/widget/src/gtk2/nsImageToPixbuf.h | 6 + mozilla/widget/src/mac/nsDragService.cpp | 16 +- mozilla/widget/src/xpwidgets/Makefile.in | 1 + .../src/xpwidgets/nsBaseDragService.cpp | 289 +++++++++++- .../widget/src/xpwidgets/nsBaseDragService.h | 60 ++- .../global/resources/content/nsDragAndDrop.js | 4 +- 20 files changed, 1099 insertions(+), 190 deletions(-) diff --git a/mozilla/content/base/src/nsContentAreaDragDrop.cpp b/mozilla/content/base/src/nsContentAreaDragDrop.cpp index f4cd3310b13..474ec7bfadf 100644 --- a/mozilla/content/base/src/nsContentAreaDragDrop.cpp +++ b/mozilla/content/base/src/nsContentAreaDragDrop.cpp @@ -96,6 +96,8 @@ #include "nsIMIMEService.h" #include "imgIRequest.h" #include "nsContentCID.h" +#include "nsISelectionController.h" +#include "nsFrameSelection.h" // private clipboard data flavors for html copy, used by editor when pasting #define kHTMLContext "text/_moz_htmlcontext" @@ -117,14 +119,9 @@ NS_INTERFACE_MAP_END class nsTransferableFactory { public: - static nsresult CreateFromEvent(nsIDOMEvent* inMouseEvent, - nsIFlavorDataProvider *inFlavorDataProvider, - nsITransferable** outTrans); - -protected: nsTransferableFactory(nsIDOMEvent* inMouseEvent, nsIFlavorDataProvider *inFlavorDataProvider); - nsresult Produce(nsITransferable** outTrans); + nsresult Produce(PRBool *aDragSelection, nsITransferable** outTrans); private: nsresult ConvertStringsToTransferable(nsITransferable** outTrans); @@ -652,15 +649,6 @@ nsContentAreaDragDrop::GetEventDocument(nsIDOMEvent* inEvent, } } -nsresult -nsContentAreaDragDrop::CreateTransferable(nsIDOMEvent* inMouseEvent, - nsITransferable** outTrans) -{ - return nsTransferableFactory::CreateFromEvent(inMouseEvent, - NS_STATIC_CAST(nsIFlavorDataProvider*, this), - outTrans); -} - nsresult nsContentAreaDragDrop::GetHookEnumeratorFromEvent(nsIDOMEvent* inEvent, nsISimpleEnumerator **outEnumerator) @@ -737,10 +725,10 @@ nsContentAreaDragDrop::DragGesture(nsIDOMEvent* inMouseEvent) } } + PRBool isSelection = PR_FALSE; nsCOMPtr trans; - nsresult rv = CreateTransferable(inMouseEvent, getter_AddRefs(trans)); - if (NS_FAILED(rv)) - return rv; + nsTransferableFactory factory(inMouseEvent, NS_STATIC_CAST(nsIFlavorDataProvider*, this)); + factory.Produce(&isSelection, getter_AddRefs(trans)); if (trans) { // if the client has provided an override callback, let them manipulate @@ -784,7 +772,6 @@ nsContentAreaDragDrop::DragGesture(nsIDOMEvent* inMouseEvent) // kick off the drag nsCOMPtr target; inMouseEvent->GetTarget(getter_AddRefs(target)); - nsCOMPtr targetNode(do_QueryInterface(target)); nsCOMPtr dragService = do_GetService("@mozilla.org/widget/dragservice;1"); @@ -792,10 +779,31 @@ nsContentAreaDragDrop::DragGesture(nsIDOMEvent* inMouseEvent) return NS_ERROR_FAILURE; } - dragService->InvokeDragSession(targetNode, transArray, nsnull, - nsIDragService::DRAGDROP_ACTION_COPY + - nsIDragService::DRAGDROP_ACTION_MOVE + - nsIDragService::DRAGDROP_ACTION_LINK); + PRUint32 action = nsIDragService::DRAGDROP_ACTION_COPY + + nsIDragService::DRAGDROP_ACTION_MOVE + + nsIDragService::DRAGDROP_ACTION_LINK; + + nsCOMPtr mouseEvent(do_QueryInterface(inMouseEvent)); + + if (isSelection) { + nsCOMPtr targetContent(do_QueryInterface(target)); + nsIDocument* doc = targetContent->GetCurrentDoc(); + if (doc) { + nsIPresShell* presShell = doc->GetShellAt(0); + if (presShell) { + nsISelection* selection = + presShell->GetCurrentSelection(nsISelectionController::SELECTION_NORMAL); + return dragService->InvokeDragSessionWithSelection(selection, + transArray, + action, + mouseEvent); + } + } + } + + nsCOMPtr targetNode(do_QueryInterface(target)); + dragService->InvokeDragSessionWithImage(targetNode, transArray, nsnull, + action, nsnull, 0, 0, mouseEvent); } return NS_OK; @@ -926,16 +934,6 @@ nsContentAreaDragDrop::GetFlavorData(nsITransferable *aTransferable, return rv; } - -nsresult -nsTransferableFactory::CreateFromEvent(nsIDOMEvent* inMouseEvent, - nsIFlavorDataProvider *dataProvider, - nsITransferable** outTrans) -{ - nsTransferableFactory factory(inMouseEvent, dataProvider); - return factory.Produce(outTrans); -} - nsTransferableFactory::nsTransferableFactory(nsIDOMEvent* inMouseEvent, nsIFlavorDataProvider *dataProvider) : mInstanceAlreadyUsed(PR_FALSE), @@ -1044,7 +1042,8 @@ nsTransferableFactory::GetNodeString(nsIDOMNode* inNode, nsresult -nsTransferableFactory::Produce(nsITransferable** outTrans) +nsTransferableFactory::Produce(PRBool* aDragSelection, + nsITransferable** outTrans) { if (mInstanceAlreadyUsed) { return NS_ERROR_FAILURE; @@ -1093,6 +1092,7 @@ nsTransferableFactory::Produce(nsITransferable** outTrans) // if set, serialize the content under this node nsCOMPtr nodeToSerialize; PRBool useSelectedText = PR_FALSE; + *aDragSelection = PR_FALSE; { PRBool haveSelectedContent = PR_FALSE; @@ -1149,6 +1149,7 @@ nsTransferableFactory::Produce(nsITransferable** outTrans) } useSelectedText = PR_TRUE; + *aDragSelection = PR_TRUE; } else if (selectedImageOrLinkNode) { // an image is selected image = do_QueryInterface(selectedImageOrLinkNode); diff --git a/mozilla/content/base/src/nsContentAreaDragDrop.h b/mozilla/content/base/src/nsContentAreaDragDrop.h index 618a73529e4..de2af39d8f1 100644 --- a/mozilla/content/base/src/nsContentAreaDragDrop.h +++ b/mozilla/content/base/src/nsContentAreaDragDrop.h @@ -109,9 +109,7 @@ private: static nsresult SaveURIToFile(nsAString& inSourceURIString, nsIFile* inDestFile); - - nsresult CreateTransferable(nsIDOMEvent* inMouseEvent, - nsITransferable** outTrans); + void ExtractURLFromData(const nsACString & inFlavor, nsISupports* inDataWrapper, PRUint32 inDataLen, nsAString & outURL); diff --git a/mozilla/content/base/src/nsRange.cpp b/mozilla/content/base/src/nsRange.cpp index 05f919a95d1..d1d88641324 100644 --- a/mozilla/content/base/src/nsRange.cpp +++ b/mozilla/content/base/src/nsRange.cpp @@ -90,6 +90,18 @@ nsresult NS_NewContentSubtreeIterator(nsIContentIterator** aInstancePtrResult); nsresult nsRange::CompareNodeToRange(nsIContent* aNode, nsIDOMRange* aRange, PRBool *outNodeBefore, PRBool *outNodeAfter) +{ + nsresult rv; + nsCOMPtr range = do_QueryInterface(aRange, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return CompareNodeToRange(aNode, range, outNodeBefore, outNodeAfter); +} + +// static +nsresult +nsRange::CompareNodeToRange(nsIContent* aNode, nsIRange* aRange, + PRBool *outNodeBefore, PRBool *outNodeAfter) { // create a pair of dom points that expresses location of node: // NODE(start), NODE(end) diff --git a/mozilla/content/base/src/nsRange.h b/mozilla/content/base/src/nsRange.h index 5f3df03c602..3b3946b5862 100644 --- a/mozilla/content/base/src/nsRange.h +++ b/mozilla/content/base/src/nsRange.h @@ -127,6 +127,9 @@ public: static nsresult CompareNodeToRange(nsIContent* aNode, nsIDOMRange* aRange, PRBool *outNodeBefore, PRBool *outNodeAfter); + static nsresult CompareNodeToRange(nsIContent* aNode, nsIRange* aRange, + PRBool *outNodeBefore, + PRBool *outNodeAfter); protected: void DoSetRange(nsINode* aStartN, PRInt32 aStartOffset, diff --git a/mozilla/editor/libeditor/text/nsPlaintextDataTransfer.cpp b/mozilla/editor/libeditor/text/nsPlaintextDataTransfer.cpp index 730e6a64544..402f9b729e3 100644 --- a/mozilla/editor/libeditor/text/nsPlaintextDataTransfer.cpp +++ b/mozilla/editor/libeditor/text/nsPlaintextDataTransfer.cpp @@ -64,6 +64,8 @@ // Misc #include "nsEditorUtils.h" #include "nsContentCID.h" +#include "nsISelectionPrivate.h" +#include "nsFrameSelection.h" NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable) { @@ -401,11 +403,18 @@ NS_IMETHODIMP nsPlaintextEditor::DoDrag(nsIDOMEvent *aDragEvent) if (NS_FAILED(rv)) return rv; nsCOMPtr domnode = do_QueryInterface(eventTarget); + nsCOMPtr selRegion; + nsCOMPtr selection; + rv = GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(rv)) return rv; + unsigned int flags; // in some cases we'll want to cut rather than copy... hmmmmm... flags = nsIDragService::DRAGDROP_ACTION_COPY + nsIDragService::DRAGDROP_ACTION_MOVE; - rv = dragService->InvokeDragSession(domnode, transferableArray, nsnull, flags); + nsCOMPtr mouseEvent(do_QueryInterface(aDragEvent)); + rv = dragService->InvokeDragSessionWithSelection(selection, transferableArray, + flags, mouseEvent); if (NS_FAILED(rv)) return rv; aDragEvent->StopPropagation(); diff --git a/mozilla/layout/base/nsIPresShell.h b/mozilla/layout/base/nsIPresShell.h index 9e22498e309..07c281c8d28 100644 --- a/mozilla/layout/base/nsIPresShell.h +++ b/mozilla/layout/base/nsIPresShell.h @@ -88,6 +88,7 @@ class nsILayoutHistoryState; class nsIReflowCallback; class nsISupportsArray; class nsIDOMNode; +class nsIRegion; class nsIStyleFrameConstruction; class nsIStyleSheet; class nsCSSFrameConstructor; @@ -95,13 +96,14 @@ class nsISelection; template class nsCOMArray; class nsWeakFrame; class nsIScrollableFrame; +class gfxASurface; typedef short SelectionType; -// c37fd598-ed7f-4f39-bdf9-96642c691c7b +// A68CFCE1-2A15-47F0-B61B-F2C34079A909 #define NS_IPRESSHELL_IID \ -{ 0xc37fd598, 0xed7f, 0x4f39, \ - { 0xbd, 0xf9, 0x96, 0x64, 0x2c, 0x69, 0x1c, 0x7b } } +{ 0xa78cfce1, 0x2a15, 0x47f0, \ + { 0xb6, 0x1b, 0xf2, 0xc3, 0x40, 0x79, 0xa9, 0x09 } } // Constants for ScrollContentIntoView() function #define NS_PRESSHELL_SCROLL_TOP 0 @@ -681,6 +683,36 @@ public: nscolor aBackgroundColor, nsIRenderingContext** aRenderedContext) = 0; + /** + * Renders a node aNode to a surface and returns it. The aRegion may be used + * to clip the rendering. This region is measured in device pixels from the + * edge of the presshell area. The aPoint, aScreenRect and aSurface + * arguments function in a similar manner as RenderSelection. + */ + virtual already_AddRefed RenderNode(nsIDOMNode* aNode, + nsIRegion* aRegion, + nsPoint& aPoint, + nsRect* aScreenRect) = 0; + + /* + * Renders a selection to a surface and returns it. This method is primarily + * intended to create the drag feedback when dragging a selection. + * + * aScreenRect will be filled in with the bounding rectangle of the + * selection area on screen. + * + * If the area of the selection is large, the image will be scaled down. + * The argument aPoint is used in this case as a reference point when + * determining the new screen rectangle after scaling. Typically, this + * will be the mouse position, so that the screen rectangle is positioned + * such that the mouse is over the same point in the scaled image as in + * the original. When scaling does not occur, the mouse point isn't used + * as the position can be determined from the displayed frames. + */ + virtual already_AddRefed RenderSelection(nsISelection* aSelection, + nsPoint& aPoint, + nsRect* aScreenRect) = 0; + void AddWeakFrame(nsWeakFrame* aWeakFrame); void RemoveWeakFrame(nsWeakFrame* aWeakFrame); diff --git a/mozilla/layout/base/nsPresShell.cpp b/mozilla/layout/base/nsPresShell.cpp index 060021f0fba..4430eb6450e 100644 --- a/mozilla/layout/base/nsPresShell.cpp +++ b/mozilla/layout/base/nsPresShell.cpp @@ -89,6 +89,7 @@ #include "nsContentUtils.h" #include "nsISelection.h" #include "nsISelectionController.h" +#include "nsISelectionPrivate.h" #include "nsLayoutCID.h" #include "nsGkAtoms.h" #include "nsIDOMRange.h" @@ -97,6 +98,7 @@ #include "nsIDOM3Node.h" #include "nsIDOMNodeList.h" #include "nsIDOMElement.h" +#include "nsRange.h" #include "nsCSSPseudoElements.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" @@ -142,6 +144,7 @@ #include "nsIAttribute.h" #include "nsIGlobalHistory2.h" #include "nsDisplayList.h" +#include "nsIRegion.h" #include "nsRegion.h" #ifdef MOZ_REFLOW_PERF_DSP @@ -160,6 +163,8 @@ #include "nsEventDispatcher.h" #include "nsThreadUtils.h" #include "nsStyleSheetService.h" +#include "gfxImageSurface.h" +#include "gfxContext.h" // Drag & Drop, Clipboard #include "nsWidgetsCID.h" @@ -205,6 +210,26 @@ static void ColorToString(nscolor aColor, nsAutoString &aString); // Class ID's static NS_DEFINE_CID(kFrameSelectionCID, NS_FRAMESELECTION_CID); +// RangePaintInfo is used to paint ranges to offscreen buffers +struct RangePaintInfo { + nsCOMPtr mRange; + nsDisplayListBuilder mBuilder; + nsDisplayList mList; + + // offset of builder's reference frame to the root frame + nsPoint mRootOffset; + + RangePaintInfo(nsIRange* aRange, nsIFrame* aFrame) + : mRange(aRange), mBuilder(aFrame, PR_FALSE, PR_FALSE) + { + } + + ~RangePaintInfo() + { + mList.DeleteAll(); + } +}; + #undef NOISY // ---------------------------------------------------------------------- @@ -899,6 +924,15 @@ public: nscolor aBackgroundColor, nsIRenderingContext** aRenderedContext); + virtual already_AddRefed RenderNode(nsIDOMNode* aNode, + nsIRegion* aRegion, + nsPoint& aPoint, + nsRect* aScreenRect); + + virtual already_AddRefed RenderSelection(nsISelection* aSelection, + nsPoint& aPoint, + nsRect* aScreenRect); + //nsIViewObserver interface NS_IMETHOD Paint(nsIView *aView, @@ -1075,6 +1109,39 @@ protected: nsresult SetPrefNoScriptRule(); nsresult SetPrefNoFramesRule(void); + // methods for painting a range to an offscreen buffer + + // given a display list, clip the items within the list to + // the range + nsRect ClipListToRange(nsDisplayListBuilder *aBuilder, + nsDisplayList* aList, + nsIRange* aRange, + nsIRenderingContext* aRenderingContext); + + // create a RangePaintInfo for the range aRange containing the + // display list needed to paint the range to a surface + RangePaintInfo* CreateRangePaintInfo(nsIDOMRange* aRange, + nsIRenderingContext* aRenderingContext, + nsRect& aSurfaceRect); + + /* + * Paint the items to a new surface and return it. + * + * aSelection - selection being painted, if any + * aRegion - clip region, if any + * aArea - area that the surface occupies, relative to the root frame + * aPoint - reference point, typically the mouse position + * aScreenRect - [out] set to the area of the screen the painted area should + * be displayed at + */ + already_AddRefed + PaintRangePaintInfo(nsTArray >* aItems, + nsISelection* aSelection, + nsIRegion* aRegion, + nsRect aArea, + nsPoint& aPoint, + nsRect* aScreenRect); + /** * Methods to handle changes to user and UA sheet lists that we get * notified about. @@ -4906,6 +4973,363 @@ PresShell::RenderOffscreen(nsRect aRect, PRBool aUntrusted, return NS_OK; } +/* + * Clip the display list aList to a range. Returns the clipped + * rectangle surrounding the range. + */ +nsRect +PresShell::ClipListToRange(nsDisplayListBuilder *aBuilder, + nsDisplayList* aList, + nsIRange* aRange, + nsIRenderingContext* aRenderingContext) +{ + // iterate though the display items and add up the bounding boxes of each. + // This will allow the total area of the frames within the range to be + // determined. To do this, remove an item from the bottom of the list, check + // whether it should be part of the range, and if so, append it to the top + // of the temporary list tmpList. If the item is a text frame at the end of + // the selection range, wrap it in an nsDisplayClip to clip the display to + // the portion of the text frame that is part of the selection. Then, append + // the wrapper to the top of the list. Otherwise, just delete the item and + // don't append it. + nsRect surfaceRect; + nsDisplayList tmpList; + + nsDisplayItem* i; + while ((i = aList->RemoveBottom())) { + // itemToInsert indiciates the item that should be inserted into the + // temporary list. If null, no item should be inserted. + nsDisplayItem* itemToInsert = nsnull; + nsIFrame* frame = i->GetUnderlyingFrame(); + if (frame) { + nsIContent* content = frame->GetContent(); + if (content) { + PRBool atStart = (content == aRange->GetStartParent()); + PRBool atEnd = (content == aRange->GetEndParent()); + if ((atStart || atEnd) && frame->GetType() == nsGkAtoms::textFrame) { + PRInt32 frameStartOffset, frameEndOffset; + frame->GetOffsets(frameStartOffset, frameEndOffset); + + PRInt32 hilightStart = + atStart ? PR_MAX(aRange->StartOffset(), frameStartOffset) : frameStartOffset; + PRInt32 hilightEnd = + atEnd ? PR_MIN(aRange->EndOffset(), frameEndOffset) : frameEndOffset; + if (hilightStart < hilightEnd) { + // determine the location of the start and end edges of the range. + nsPoint startPoint, endPoint; + nsPresContext* presContext = GetPresContext(); + frame->GetPointFromOffset(presContext, aRenderingContext, + hilightStart, &startPoint); + frame->GetPointFromOffset(presContext, aRenderingContext, + hilightEnd, &endPoint); + + // the clip rectangle is determined by taking the the start and + // end points of the range, offset from the reference frame. + // Because of rtl, the end point may be to the left of the + // start point, so x is set to the lowest value + nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize()); + nscoord x = PR_MIN(startPoint.x, endPoint.x); + textRect.x += x; + textRect.width = PR_MAX(startPoint.x, endPoint.x) - x; + surfaceRect.UnionRect(surfaceRect, textRect); + + // wrap the item in an nsDisplayClip so that it can be clipped to + // the selection. If the allocation fails, fall through and delete + // the item below. + itemToInsert = new (aBuilder)nsDisplayClip(frame, i, textRect); + } + } + else { + // if the node is within the range, append it to the temporary list + PRBool before, after; + nsRange::CompareNodeToRange(content, aRange, &before, &after); + if (!before && !after) { + itemToInsert = i; + surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder)); + } + } + } + } + + // insert the item into the list if necessary. If the item has a child + // list, insert that as well + nsDisplayList* sublist = i->GetList(); + if (itemToInsert || sublist) { + tmpList.AppendToTop(itemToInsert ? itemToInsert : i); + // if the item is a list, iterate over it as well + if (sublist) + surfaceRect.UnionRect(surfaceRect, + ClipListToRange(aBuilder, sublist, aRange, aRenderingContext)); + } + else { + // otherwise, just delete the item and don't readd it to the list + i->~nsDisplayItem(); + } + } + + // now add all the items back onto the original list again + aList->AppendToTop(&tmpList); + + return surfaceRect; +} + +RangePaintInfo* +PresShell::CreateRangePaintInfo(nsIDOMRange* aRange, + nsIRenderingContext* aRenderingContext, + nsRect& aSurfaceRect) +{ + RangePaintInfo* info = nsnull; + + nsCOMPtr range = do_QueryInterface(aRange); + if (!range) + return nsnull; + + // get the common ancestor of the two endpoints of the range + nsINode* ancestor = nsContentUtils::GetCommonAncestor(range->GetStartParent(), + range->GetEndParent()); + NS_ASSERTION(!ancestor || ancestor->IsNodeOfType(nsINode::eCONTENT), + "common ancestor is not content"); + if (!ancestor || !ancestor->IsNodeOfType(nsINode::eCONTENT)) + return nsnull; + + nsIContent* ancestorContent = NS_STATIC_CAST(nsIContent*, ancestor); + + nsIFrame* ancestorFrame = GetPrimaryFrameFor(ancestorContent); + + // use the nearest ancestor frame that includes all continuations as the + // root for building the display list + while (ancestorFrame && ancestorFrame->GetNextInFlow()) + ancestorFrame = ancestorFrame->GetParent(); + + if (!ancestorFrame) + return nsnull; + + info = new RangePaintInfo(range, ancestorFrame); + if (!info) + return nsnull; + + nsRect ancestorRect = ancestorFrame->GetOverflowRect(); + + // get a display list containing the range + info->mBuilder.SetPaintAllFrames(); + info->mBuilder.EnterPresShell(ancestorFrame, ancestorRect); + ancestorFrame->BuildDisplayListForStackingContext(&info->mBuilder, + ancestorRect, &info->mList); + info->mBuilder.LeavePresShell(ancestorFrame, ancestorRect); + + nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, + range, aRenderingContext); + + // determine the offset of the reference frame for the display list + // to the root frame. This will allow the coordinates used when painting + // to all be offset from the same point + info->mRootOffset = ancestorFrame->GetOffsetTo(GetRootFrame()); + rangeRect.MoveBy(info->mRootOffset); + aSurfaceRect.UnionRect(aSurfaceRect, rangeRect); + + return info; +} + +already_AddRefed +PresShell::PaintRangePaintInfo(nsTArray >* aItems, + nsISelection* aSelection, + nsIRegion* aRegion, + nsRect aArea, + nsPoint& aPoint, + nsRect* aScreenRect) +{ + nsPresContext* pc = GetPresContext(); + if (!pc) + return nsnull; + + nsIDeviceContext* deviceContext = pc->DeviceContext(); + + // use the rectangle to create the surface + nsRect pixelArea = aArea; + pixelArea.ScaleRoundOut(1.0 / pc->AppUnitsPerDevPixel()); + + // if the area of the image is larger than the maximum area, scale it down + float scale = 0.0; + nsIntRect rootScreenRect = GetRootFrame()->GetScreenRect(); + + // if the image is larger in one or both directions than half the size of + // the available screen area, scale the image down to that size. + nsRect maxSize; + deviceContext->GetClientRect(maxSize); + nscoord maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1); + nscoord maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1); + PRBool resize = (pixelArea.width > maxWidth || pixelArea.height > maxHeight); + if (resize) { + scale = 1.0; + // divide the maximum size by the image size in both directions. Whichever + // direction produces the smallest result determines how much should be + // scaled. + if (pixelArea.width > maxWidth) + scale = PR_MIN(scale, float(maxWidth) / pixelArea.width); + if (pixelArea.height > maxHeight) + scale = PR_MIN(scale, float(maxHeight) / pixelArea.height); + + pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale); + pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale); + + // adjust the screen position based on the rescaled size + nscoord left = rootScreenRect.x + pixelArea.x; + nscoord top = rootScreenRect.y + pixelArea.y; + aScreenRect->x = NSToIntFloor(aPoint.x - float(aPoint.x - left) * scale); + aScreenRect->y = NSToIntFloor(aPoint.y - float(aPoint.y - top) * scale); + } + else { + // move aScreenRect to the position of the surface in screen coordinates + aScreenRect->MoveTo(rootScreenRect.x + pixelArea.x, rootScreenRect.y + pixelArea.y); + } + aScreenRect->width = pixelArea.width; + aScreenRect->height = pixelArea.height; + + gfxImageSurface* surface = + new gfxImageSurface(gfxIntSize(pixelArea.width, pixelArea.height), + gfxImageSurface::ImageFormatARGB32); + if (!surface) + return nsnull; + + // clear the image + gfxContext context(surface); + context.SetOperator(gfxContext::OPERATOR_CLEAR); + context.Rectangle(gfxRect(0, 0, pixelArea.width, pixelArea.height)); + context.Fill(); + + nsCOMPtr rc; + deviceContext->CreateRenderingContextInstance(*getter_AddRefs(rc)); + rc->Init(deviceContext, surface); + + if (aRegion) + rc->SetClipRegion(*aRegion, nsClipCombine_kReplace); + + if (resize) + rc->Scale(scale, scale); + + // translate so that points are relative to the surface area + rc->Translate(-aArea.x, -aArea.y); + + // temporarily hide the selection so that text is drawn normally. If a + // selection is being rendered, use that, otherwise use the presshell's + // selection. + nsCOMPtr frameSelection; + if (aSelection) { + nsCOMPtr selpriv = do_QueryInterface(aSelection); + selpriv->GetFrameSelection(getter_AddRefs(frameSelection)); + } + else { + frameSelection = FrameSelection(); + } + PRInt16 oldDisplaySelection = frameSelection->GetDisplaySelection(); + frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN); + + // next, paint each range in the selection + PRInt32 count = aItems->Length(); + for (PRInt32 i = 0; i < count; i++) { + RangePaintInfo* rangeInfo = (*aItems)[i]; + // the display lists paint relative to the offset from the reference + // frame, so translate the rendering context + nsIRenderingContext::AutoPushTranslation + translate(rc, rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); + + aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y); + rangeInfo->mList.Paint(&rangeInfo->mBuilder, rc, aArea); + aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); + } + + // restore the old selection display state + frameSelection->SetDisplaySelection(oldDisplaySelection); + + NS_ADDREF(surface); + return surface; +} + +already_AddRefed +PresShell::RenderNode(nsIDOMNode* aNode, + nsIRegion* aRegion, + nsPoint& aPoint, + nsRect* aScreenRect) +{ + // create a temporary rendering context for text measuring + nsCOMPtr tmprc; + nsresult rv = CreateRenderingContext(GetRootFrame(), getter_AddRefs(tmprc)); + NS_ENSURE_SUCCESS(rv, nsnull); + + // area will hold the size of the surface needed to draw the node, measured + // from the root frame. + nsRect area; + nsTArray > rangeItems; + + nsCOMPtr range; + NS_NewRange(getter_AddRefs(range)); + range->SelectNode(aNode); + + RangePaintInfo* info = CreateRangePaintInfo(range, tmprc, area); + if (info && !rangeItems.AppendElement(info)) { + delete info; + return nsnull; + } + + if (aRegion) { + // combine the area with the supplied region + nsRect rrectPixels; + aRegion->GetBoundingBox(&rrectPixels.x, &rrectPixels.y, + &rrectPixels.width, &rrectPixels.height); + + nsRect rrect = rrectPixels; + rrect.ScaleRoundOut(nsPresContext::AppUnitsPerCSSPixel()); + area.IntersectRect(area, rrect); + + nsPresContext* pc = GetPresContext(); + if (!pc) + return nsnull; + + // move the region so that it is offset from the topleft corner of the surface + aRegion->Offset(-rrectPixels.x + (rrectPixels.x - pc->AppUnitsToDevPixels(area.x)), + -rrectPixels.y + (rrectPixels.y - pc->AppUnitsToDevPixels(area.y))); + } + + return PaintRangePaintInfo(&rangeItems, nsnull, aRegion, area, aPoint, + aScreenRect); +} + +already_AddRefed +PresShell::RenderSelection(nsISelection* aSelection, + nsPoint& aPoint, + nsRect* aScreenRect) +{ + // create a temporary rendering context for text measuring + nsCOMPtr tmprc; + nsresult rv = CreateRenderingContext(GetRootFrame(), getter_AddRefs(tmprc)); + NS_ENSURE_SUCCESS(rv, nsnull); + + // area will hold the size of the surface needed to draw the selection, + // measured from the root frame. + nsRect area; + nsTArray > rangeItems; + + // iterate over each range and collect them into the rangeItems array. + // This is done so that the size of selection can be determined so as + // to allocate a surface area + PRInt32 numRanges; + aSelection->GetRangeCount(&numRanges); + for (PRInt32 r = 0; r < numRanges; r++) + { + nsCOMPtr range; + aSelection->GetRangeAt(r, getter_AddRefs(range)); + + RangePaintInfo* info = CreateRangePaintInfo(range, tmprc, area); + if (info && !rangeItems.AppendElement(info)) { + delete info; + return nsnull; + } + } + + return PaintRangePaintInfo(&rangeItems, aSelection, nsnull, area, aPoint, + aScreenRect); +} + NS_IMETHODIMP PresShell::Paint(nsIView* aView, nsIRenderingContext* aRenderingContext, diff --git a/mozilla/mailnews/base/resources/content/messengerdnd.js b/mozilla/mailnews/base/resources/content/messengerdnd.js index 89b6f134633..15bb0bd1046 100644 --- a/mozilla/mailnews/base/resources/content/messengerdnd.js +++ b/mozilla/mailnews/base/resources/content/messengerdnd.js @@ -455,8 +455,10 @@ function BeginDragTree(event, tree, selArray, flavor) transArray.AppendElement(genTrans); } - dragService.invokeDragSession ( event.target, transArray, region, nsIDragService.DRAGDROP_ACTION_COPY + - nsIDragService.DRAGDROP_ACTION_MOVE ); + dragService.invokeDragSessionWithImage(event.target, transArray, region, + nsIDragService.DRAGDROP_ACTION_COPY + + nsIDragService.DRAGDROP_ACTION_MOVE, + null, 0, 0, event); dragStarted = true; diff --git a/mozilla/toolkit/content/nsDragAndDrop.js b/mozilla/toolkit/content/nsDragAndDrop.js index 03eefd231bb..2bdbde7de00 100644 --- a/mozilla/toolkit/content/nsDragAndDrop.js +++ b/mozilla/toolkit/content/nsDragAndDrop.js @@ -429,7 +429,9 @@ var nsDragAndDrop = { count < transferData.dataList.length); try { - this.mDragService.invokeDragSession(aEvent.target, transArray, region, dragAction.action); + this.mDragService.invokeDragSessionWithImage(aEvent.target, transArray, + region, dragAction.action, + null, 0, 0, aEvent); } catch(ex) { // this could be because the user pressed escape to diff --git a/mozilla/widget/public/nsIDragService.idl b/mozilla/widget/public/nsIDragService.idl index d5bf7b72158..99fb57e9472 100644 --- a/mozilla/widget/public/nsIDragService.idl +++ b/mozilla/widget/public/nsIDragService.idl @@ -44,16 +44,17 @@ interface nsIDOMNode; +interface nsIDOMMouseEvent; +interface nsISelection; - -[scriptable, uuid(8B5314BB-DB01-11d2-96CE-0060B0FB9956)] +[scriptable, uuid(D2875740-95D3-4F12-8E97-9FAB76C87093)] interface nsIDragService : nsISupports { const long DRAGDROP_ACTION_NONE = 0; const long DRAGDROP_ACTION_COPY = 1; const long DRAGDROP_ACTION_MOVE = 2; const long DRAGDROP_ACTION_LINK = 4; - + /** * Starts a modal drag session with an array of transaferables * @@ -65,6 +66,50 @@ interface nsIDragService : nsISupports void invokeDragSession ( in nsIDOMNode aDOMNode, in nsISupportsArray aTransferables, in nsIScriptableRegion aRegion, in unsigned long aActionType ); + /** + * Starts a modal drag session using an image. The first four arguments are + * the same as invokeDragSession. + * + * A custom image may be specified using the aImage argument. If this is + * supplied, the aImageX and aImageY arguments specify the offset within + * the image where the cursor would be positioned. That is, when the image + * is drawn, it is offset up and left the amount so that the cursor appears + * at that location within the image. + * + * If aImage is null, aImageX and aImageY are not used and the image is instead + * determined from the source node aDOMNode, and the offset calculated so that + * the initial location for the image appears in the same screen position as + * where the element is located. The node must be within a document. + * + * Currently, supported images are all DOM nodes. If this is an HTML or + * , the drag image is taken from the image data. If the element is in + * a document, it will be rendered at its displayed size, othewise, it will be + * rendered at its real size. For other types of elements, the element is + * rendered into an offscreen buffer in the same manner as it is currently + * displayed. The document selection is hidden while drawing. + * + * The aDragEvent must be supplied as the current screen coordinates of the + * event are needed to calculate the image location. + */ + void invokeDragSessionWithImage(in nsIDOMNode aDOMNode, + in nsISupportsArray aTransferableArray, + in nsIScriptableRegion aRegion, + in unsigned long aActionType, + in nsIDOMNode aImage, + in long aImageX, + in long aImageY, + in nsIDOMMouseEvent aDragEvent); + + /** + * Start a modal drag session using the selection as the drag image. + * The aDragEvent must be supplied as the current screen coordinates of the + * event are needed to calculate the image location. + */ + void invokeDragSessionWithSelection(in nsISelection aSelection, + in nsISupportsArray aTransferableArray, + in unsigned long aActionType, + in nsIDOMMouseEvent aDragEvent); + /** * Returns the current Drag Session */ diff --git a/mozilla/widget/src/cocoa/nsDragService.h b/mozilla/widget/src/cocoa/nsDragService.h index 8d0a2abd251..e7f290b26b8 100644 --- a/mozilla/widget/src/cocoa/nsDragService.h +++ b/mozilla/widget/src/cocoa/nsDragService.h @@ -41,7 +41,10 @@ #include "nsBaseDragService.h" +#include + class nsILocalFile; +class nsIDOMDragEvent; class nsDragService : public nsBaseDragService { @@ -63,6 +66,9 @@ public: private: + NSImage* ConstructDragImage(nsIDOMNode* aDOMNode, + nsRect* aDragRect, + nsIScriptableRegion* aRegion); }; #endif // nsDragService_h_ diff --git a/mozilla/widget/src/cocoa/nsDragService.mm b/mozilla/widget/src/cocoa/nsDragService.mm index 3c2d4e3e395..4552c089771 100644 --- a/mozilla/widget/src/cocoa/nsDragService.mm +++ b/mozilla/widget/src/cocoa/nsDragService.mm @@ -60,6 +60,8 @@ #include "nsIFrame.h" #include "nsIView.h" #include "nsIRegion.h" +#include "gfxASurface.h" +#include "gfxContext.h" #import @@ -82,90 +84,6 @@ nsDragService::~nsDragService() } - -// Returns the rect for the drag in native view coordinates. -// -// Note that for text drags, this returns an incorrect rect. This bug -// exists in the old carbon implementation too. -static NSRect GetDragRect(nsIDOMNode* node, nsIScriptableRegion* aDragRgn) -{ - // Set up a default rect in case something goes wrong. - // It'll at least indicate that *something* is getting dragged - NSRect outRect = NSMakeRect(0, 0, 50, 50); - - // we're going to need an nsIFrame* no matter what, so get it now - if (!node) - return outRect; - nsCOMPtr content = do_QueryInterface(node); - if (!content) - return outRect; - nsIDocument* doc = content->GetCurrentDoc(); - if (!doc) - return outRect; - nsIPresShell* presShell = doc->GetShellAt(0); - if (!presShell) - return outRect; - nsIFrame* frame = presShell->GetPrimaryFrameFor(content); - if (!frame) - return outRect; - - if (aDragRgn) { - nsCOMPtr geckoRegion; - aDragRgn->GetRegion(getter_AddRefs(geckoRegion)); - if (!geckoRegion) - return outRect; - - // bounding box for the drag is in window coordinates - PRInt32 x, y, width, height; - geckoRegion->GetBoundingBox(&x, &y, &width, &height); - nsRect rect = nsRect(x, y, width, height); - // printf("drag region is x=%d, y=%d, width=%d, height=%d\n", x, y, width, height); - - nsCOMPtr widget = frame->GetWindow(); - if (!widget) - return outRect; - - nsRect widgetScreenBounds; - widget->GetScreenBounds(widgetScreenBounds); - - outRect.origin.x = (float)(rect.x - widgetScreenBounds.x); - outRect.origin.y = (float)(rect.y - widgetScreenBounds.y + rect.height); - outRect.size.width = (float)rect.width; - outRect.size.height = (float)rect.height; - } - else { - nsRect rect = frame->GetRect(); - - // find offset from our view - nsIView *containingView = nsnull; - nsPoint viewOffset(0,0); - frame->GetOffsetFromView(viewOffset, &containingView); - if (!containingView) - return outRect; - - // get the widget offset - nsPoint widgetOffset; - containingView->GetNearestWidget(&widgetOffset); - - nsPresContext* presContext = frame->GetPresContext(); - - nsRect screenOffset; - screenOffset.MoveBy(presContext->AppUnitsToDevPixels(widgetOffset.x + - viewOffset.x), - presContext->AppUnitsToDevPixels(widgetOffset.y + - viewOffset.y)); - - outRect.origin.x = (float)screenOffset.x; - outRect.origin.y = (float)screenOffset.y + - (float)presContext->AppUnitsToDevPixels(rect.height); - outRect.size.width = (float)presContext->AppUnitsToDevPixels(rect.width); - outRect.size.height = (float)presContext->AppUnitsToDevPixels(rect.height); - } - - return outRect; -} - - static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray) { if (!aTransferableArray) @@ -315,6 +233,79 @@ static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray) } +NSImage* +nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode, + nsRect* aDragRect, + nsIScriptableRegion* aRegion) +{ + NSPoint screenPoint = [[globalDragView window] convertBaseToScreen:[globalDragEvent locationInWindow]]; + // Y coordinates are bottom to top, so reverse this + if ([[NSScreen screens] count] > 0) + screenPoint.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - screenPoint.y; + + nsRefPtr surface; + nsresult rv = DrawDrag(aDOMNode, aRegion, + NSToIntRound(screenPoint.x), NSToIntRound(screenPoint.y), + aDragRect, getter_AddRefs(surface)); + if (NS_FAILED(rv) || !surface) + return nsnull; + + PRUint32 width = aDragRect->width; + PRUint32 height = aDragRect->height; + + nsRefPtr imgSurface = new gfxImageSurface( + gfxIntSize(width, height), gfxImageSurface::ImageFormatARGB32); + if (!imgSurface) + return nsnull; + + nsRefPtr context = new gfxContext(imgSurface); + if (!context) + return nsnull; + + context->SetOperator(gfxContext::OPERATOR_SOURCE); + context->SetSource(surface); + context->Paint(); + + PRUint32* imageData = (PRUint32*)imgSurface->Data(); + PRInt32 stride = imgSurface->Stride(); + + NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:width * 4 + bitsPerPixel:32]; + + PRUint8* dest = [imageRep bitmapData]; + for (PRUint32 i = 0; i < height; ++i) { + PRUint8* src = (PRUint8 *)imageData + i * stride; + for (PRUint32 j = 0; j < width; ++j) { +#ifdef IS_BIG_ENDIAN + dest[0] = src[1]; + dest[1] = src[2]; + dest[2] = src[3] + dest[3] = PRUint8(src[0] * 0.8); // reduce transparency overall +#else + dest[0] = src[2]; + dest[1] = src[1]; + dest[2] = src[0]; + dest[3] = PRUint8(src[3] * 0.8); // reduce transparency overall +#endif + src += 4; + dest += 4; + } + } + + NSImage* image = [NSImage alloc]; + [image addRepresentation:imageRep]; + + return [image autorelease]; +} + // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the // stack when InvokeDragSession gets called. @@ -328,48 +319,47 @@ nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransf if (NS_FAILED(SetUpDragClipboard(aTransferableArray))) return NS_ERROR_FAILURE; - // create the image for the drag, it isn't awesome but it'll do for now - NSRect dragRect = GetDragRect(aDOMNode, aDragRgn); - NSImage* image = [[[NSImage alloc] initWithSize:dragRect.size] autorelease]; - [image lockFocus]; - [[NSColor grayColor] set]; - NSBezierPath* path = [NSBezierPath bezierPath]; - [path setLineWidth:2.0]; - [path moveToPoint:NSMakePoint(0, 0)]; - [path lineToPoint:NSMakePoint(0, dragRect.size.height)]; - [path lineToPoint:NSMakePoint(dragRect.size.width, dragRect.size.height)]; - [path lineToPoint:NSMakePoint(dragRect.size.width, 0)]; - [path lineToPoint:NSMakePoint(0, 0)]; - [path stroke]; - [image unlockFocus]; + nsRect dragRect(0, 0, 20, 20); + NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn); + if (!image) { + // if no image was returned, just draw a rectangle + NSSize size; + size.width = dragRect.width; + size.height = dragRect.height; + image = [[NSImage alloc] initWithSize:size]; + [image lockFocus]; + [[NSColor grayColor] set]; + NSBezierPath* path = [NSBezierPath bezierPath]; + [path setLineWidth:2.0]; + [path moveToPoint:NSMakePoint(0, 0)]; + [path lineToPoint:NSMakePoint(0, size.height)]; + [path lineToPoint:NSMakePoint(size.width, size.height)]; + [path lineToPoint:NSMakePoint(size.width, 0)]; + [path lineToPoint:NSMakePoint(0, 0)]; + [path stroke]; + [image unlockFocus]; + } - // Make sure that the drag rect encompasses the current mouse location. - // This correction code has been very methodically tested, on every edge of - // a drag rect. Do not change this code unless you've done that. - // - // Get the mouse coordinates in local view coords - NSPoint localPoint = [globalDragView convertPoint:[globalDragEvent locationInWindow] fromView:nil]; - // fix up the X coords - if (localPoint.x < dragRect.origin.x) - dragRect.origin.x = localPoint.x; - else if (localPoint.x > (dragRect.origin.x + dragRect.size.width)) - dragRect.origin.x = localPoint.x - dragRect.size.width; - // now fix up the Y coords - if (localPoint.y < (dragRect.origin.y - dragRect.size.height)) - dragRect.origin.y = localPoint.y + dragRect.size.height; - else if (localPoint.y > dragRect.origin.y) - dragRect.origin.y = localPoint.y; + NSPoint point; + point.x = dragRect.x; + if ([[NSScreen screens] count] > 0) + point.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - dragRect.YMost(); + else + point.y = dragRect.y; + point = [[globalDragView window] convertScreenToBase: point]; + NSPoint localPoint = [globalDragView convertPoint:point fromView:nil]; + nsBaseDragService::StartDragSession(); [globalDragView dragImage:image - at:dragRect.origin + at:localPoint offset:NSMakeSize(0,0) event:globalDragEvent pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] source:globalDragView slideBack:YES]; nsBaseDragService::EndDragSession(); - + return NS_OK; } diff --git a/mozilla/widget/src/gtk2/nsDragService.cpp b/mozilla/widget/src/gtk2/nsDragService.cpp index 41d8f72e807..9e9dce85053 100644 --- a/mozilla/widget/src/gtk2/nsDragService.cpp +++ b/mozilla/widget/src/gtk2/nsDragService.cpp @@ -58,6 +58,10 @@ #include #include "nsCRT.h" +#ifdef MOZ_CAIRO_GFX +#include "gfxASurface.h" +#include "nsImageToPixbuf.h" +#endif static PRLogModuleInfo *sDragLm = NULL; @@ -198,8 +202,30 @@ nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode, action, 1, &event); - // make sure to set our default icon + +#ifdef MOZ_CAIRO_GFX + GdkPixbuf* dragPixbuf = nsnull; + nsRect dragRect; + if (mHasImage || mSelection) { + nsRefPtr surface; + DrawDrag(aDOMNode, aRegion, mScreenX, mScreenY, + &dragRect, getter_AddRefs(surface)); + if (surface) { + dragPixbuf = + nsImageToPixbuf::SurfaceToPixbuf(surface, dragRect.width, dragRect.height); + } + } + + if (dragPixbuf) + gtk_drag_set_icon_pixbuf(context, dragPixbuf, + mScreenX - NSToIntRound(dragRect.x), + mScreenY - NSToIntRound(dragRect.y)); + else + gtk_drag_set_icon_default(context); +#else + // use a default icon gtk_drag_set_icon_default(context); +#endif gtk_target_list_unref(sourceList); } diff --git a/mozilla/widget/src/gtk2/nsImageToPixbuf.cpp b/mozilla/widget/src/gtk2/nsImageToPixbuf.cpp index 3cf93ab16ef..0b4585fc5f5 100644 --- a/mozilla/widget/src/gtk2/nsImageToPixbuf.cpp +++ b/mozilla/widget/src/gtk2/nsImageToPixbuf.cpp @@ -77,12 +77,25 @@ nsImageToPixbuf::ImageToPixbuf(nsIImage* aImage) nsRefPtr surface; aImage->GetSurface(getter_AddRefs(surface)); + return SurfaceToPixbuf(surface, width, height); +#else + nsCOMPtr img(do_QueryInterface(aImage)); + if (img) + return img->GetGdkPixbuf(); + return NULL; +#endif +} + +#ifdef MOZ_CAIRO_GFX +GdkPixbuf* +nsImageToPixbuf::SurfaceToPixbuf(gfxASurface* aSurface, PRInt32 aWidth, PRInt32 aHeight) +{ nsRefPtr imgSurface; - if (surface->GetType() == gfxASurface::SurfaceTypeImage) { + if (aSurface->GetType() == gfxASurface::SurfaceTypeImage) { imgSurface = NS_STATIC_CAST(gfxImageSurface*, - NS_STATIC_CAST(gfxASurface*, surface.get())); + NS_STATIC_CAST(gfxASurface*, aSurface)); } else { - imgSurface = new gfxImageSurface(gfxIntSize(width, height), + imgSurface = new gfxImageSurface(gfxIntSize(aWidth, aHeight), gfxImageSurface::ImageFormatARGB32); if (!imgSurface) @@ -93,12 +106,12 @@ nsImageToPixbuf::ImageToPixbuf(nsIImage* aImage) return nsnull; context->SetOperator(gfxContext::OPERATOR_SOURCE); - context->SetSource(surface); + context->SetSource(aSurface); context->Paint(); } GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, PR_TRUE, 8, - width, height); + aWidth, aHeight); if (!pixbuf) return nsnull; @@ -110,8 +123,8 @@ nsImageToPixbuf::ImageToPixbuf(nsIImage* aImage) gfxASurface::gfxImageFormat format = imgSurface->Format(); - for (PRInt32 row = 0; row < height; ++row) { - for (PRInt32 col = 0; col < width; ++col) { + for (PRInt32 row = 0; row < aHeight; ++row) { + for (PRInt32 col = 0; col < aWidth; ++col) { guchar* pixel = pixels + row * rowstride + 4 * col; PRUint32* cairoPixel = NS_REINTERPRET_CAST(PRUint32*, @@ -143,11 +156,5 @@ nsImageToPixbuf::ImageToPixbuf(nsIImage* aImage) } return pixbuf; -#else - nsCOMPtr img(do_QueryInterface(aImage)); - if (img) - return img->GetGdkPixbuf(); - return NULL; -#endif } - +#endif diff --git a/mozilla/widget/src/gtk2/nsImageToPixbuf.h b/mozilla/widget/src/gtk2/nsImageToPixbuf.h index 84f75b1612a..dbc6cb97745 100644 --- a/mozilla/widget/src/gtk2/nsImageToPixbuf.h +++ b/mozilla/widget/src/gtk2/nsImageToPixbuf.h @@ -40,6 +40,8 @@ #include "nsIImageToPixbuf.h" +class gfxASurface; + class nsImageToPixbuf : public nsIImageToPixbuf { public: NS_DECL_ISUPPORTS @@ -48,6 +50,10 @@ class nsImageToPixbuf : public nsIImageToPixbuf { // Friendlier version of ConvertImageToPixbuf for callers inside of // widget static GdkPixbuf* ImageToPixbuf(nsIImage* aImage); +#ifdef MOZ_CAIRO_GFX + static GdkPixbuf* SurfaceToPixbuf(gfxASurface* aSurface, + PRInt32 aWidth, PRInt32 aHeight); +#endif private: ~nsImageToPixbuf() {} }; diff --git a/mozilla/widget/src/mac/nsDragService.cpp b/mozilla/widget/src/mac/nsDragService.cpp index 73f70555081..a67ef14ec3a 100644 --- a/mozilla/widget/src/mac/nsDragService.cpp +++ b/mozilla/widget/src/mac/nsDragService.cpp @@ -294,15 +294,21 @@ nsDragService::InvokeDragSession (nsIDOMNode *aDOMNode, nsISupportsArray * aTran theEvent.when = TickCount(); theEvent.modifiers = 0L; - // since we don't have the original mouseDown location, just assume the drag + // if we don't have the original mouseDown location, just assume the drag // started in the middle of the frame. This prevents us from having the mouse // and the region we're dragging separated by a big gap (which could happen if // we used the current mouse position). I know this isn't exactly right, and you can // see it if you're paying attention, but who pays such close attention? - Rect dragRect; - ::GetRegionBounds(theDragRgn, &dragRect); - theEvent.where.v = dragRect.top + ((dragRect.bottom - dragRect.top) / 2); - theEvent.where.h = dragRect.left + ((dragRect.right - dragRect.left) / 2); + if (mScreenX == -1 || mScreenY == -1) { + Rect dragRect; + ::GetRegionBounds(theDragRgn, &dragRect); + theEvent.where.v = dragRect.top + ((dragRect.bottom - dragRect.top) / 2); + theEvent.where.h = dragRect.left + ((dragRect.right - dragRect.left) / 2); + } + else { + theEvent.where.v = mScreenY; + theEvent.where.h = mScreenX; + } // register drag send proc which will call us back when asked for the actual // flavor data (instead of placing it all into the drag manager) diff --git a/mozilla/widget/src/xpwidgets/Makefile.in b/mozilla/widget/src/xpwidgets/Makefile.in index 5a3487fae4b..45687963117 100644 --- a/mozilla/widget/src/xpwidgets/Makefile.in +++ b/mozilla/widget/src/xpwidgets/Makefile.in @@ -52,6 +52,7 @@ REQUIRES = xpcom \ layout \ content \ dom \ + imglib2 \ pref \ locale \ necko \ diff --git a/mozilla/widget/src/xpwidgets/nsBaseDragService.cpp b/mozilla/widget/src/xpwidgets/nsBaseDragService.cpp index 116c9a89531..14138374063 100644 --- a/mozilla/widget/src/xpwidgets/nsBaseDragService.cpp +++ b/mozilla/widget/src/xpwidgets/nsBaseDragService.cpp @@ -47,14 +47,33 @@ #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsCOMPtr.h" +#include "nsIInterfaceRequestorUtils.h" #include "nsIFrame.h" #include "nsIDocument.h" #include "nsIContent.h" #include "nsIPresShell.h" #include "nsIViewManager.h" +#include "nsIScrollableView.h" #include "nsIDOMNode.h" +#include "nsIDOMMouseEvent.h" +#include "nsISelection.h" +#include "nsISelectionPrivate.h" #include "nsPresContext.h" +#include "nsIEventStateManager.h" +#include "nsICanvasElement.h" +#include "nsIImage.h" +#include "nsIImageLoadingContent.h" +#include "gfxIImageFrame.h" +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "nsIViewObserver.h" +#include "nsRegion.h" +#ifdef MOZ_CAIRO_GFX +#include "gfxContext.h" +#include "gfxImageSurface.h" + +#endif NS_IMPL_ADDREF(nsBaseDragService) NS_IMPL_RELEASE(nsBaseDragService) @@ -67,8 +86,9 @@ NS_IMPL_QUERY_INTERFACE2(nsBaseDragService, nsIDragService, nsIDragSession) // //------------------------------------------------------------------------- nsBaseDragService::nsBaseDragService() - : mCanDrop(PR_FALSE), mDoingDrag(PR_FALSE), - mDragAction(DRAGDROP_ACTION_NONE), mTargetSize(0,0) + : mCanDrop(PR_FALSE), mDoingDrag(PR_FALSE), mHasImage(PR_FALSE), + mDragAction(DRAGDROP_ACTION_NONE), mTargetSize(0,0), + mImageX(0), mImageY(0), mScreenX(-1), mScreenY(-1) { } @@ -188,12 +208,11 @@ nsBaseDragService::IsDataFlavorSupported(const char *aDataFlavor, return NS_ERROR_FAILURE; } - //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::InvokeDragSession(nsIDOMNode *aDOMNode, - nsISupportsArray * anArrayTransferables, - nsIScriptableRegion * aRegion, + nsISupportsArray* aTransferableArray, + nsIScriptableRegion* aDragRgn, PRUint32 aActionType) { NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG); @@ -225,6 +244,53 @@ nsBaseDragService::InvokeDragSession(nsIDOMNode *aDOMNode, return NS_OK; } +NS_IMETHODIMP +nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode, + nsISupportsArray* aTransferableArray, + nsIScriptableRegion* aRegion, + PRUint32 aActionType, + nsIDOMNode* aImage, + PRInt32 aImageX, PRInt32 aImageY, + nsIDOMMouseEvent* aDragEvent) +{ + NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); + + mSelection = nsnull; + mHasImage = PR_TRUE; + mImage = aImage; + mImageX = aImageX; + mImageY = aImageY; + + aDragEvent->GetScreenX(&mScreenX); + aDragEvent->GetScreenY(&mScreenY); + + return InvokeDragSession(aDOMNode, aTransferableArray, aRegion, aActionType); +} + +NS_IMETHODIMP +nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection, + nsISupportsArray* aTransferableArray, + PRUint32 aActionType, + nsIDOMMouseEvent* aDragEvent) +{ + NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); + NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); + + mSelection = aSelection; + mHasImage = PR_TRUE; + mImage = nsnull; + mImageX = 0; + mImageY = 0; + + aDragEvent->GetScreenX(&mScreenX); + aDragEvent->GetScreenY(&mScreenY); + + // just get the focused node from the selection + nsCOMPtr node; + aSelection->GetFocusNode(getter_AddRefs(node)); + + return InvokeDragSession(node, aTransferableArray, nsnull, aActionType); +} //------------------------------------------------------------------------- NS_IMETHODIMP @@ -269,6 +335,219 @@ nsBaseDragService::EndDragSession() // release the source we've been holding on to. mSourceDocument = nsnull; mSourceNode = nsnull; + mSelection = nsnull; + mHasImage = PR_FALSE; + mImage = nsnull; + mImageX = 0; + mImageY = 0; + mScreenX = -1; + mScreenY = -1; return NS_OK; } + +#ifdef MOZ_CAIRO_GFX + +static nsIPresShell* +GetPresShellForContent(nsIDOMNode* aDOMNode) +{ + nsIContent* content; + CallQueryInterface(aDOMNode, &content); + + nsIDocument *document = content->GetCurrentDoc(); + if (document) { + document->FlushPendingNotifications(Flush_Display); + + return document->GetShellAt(0); + } + + return nsnull; +} + +nsresult +nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode, + nsIScriptableRegion* aRegion, + PRInt32 aScreenX, PRInt32 aScreenY, + nsRect* aScreenDragRect, + gfxASurface** aSurface) +{ + *aSurface = nsnull; + + // use a default size, in case of an error. + aScreenDragRect->x = aScreenX - mImageX; + aScreenDragRect->y = aScreenY - mImageY; + aScreenDragRect->width = 20; + aScreenDragRect->height = 20; + + // if a drag image was specified, use that, otherwise, use the source node + nsCOMPtr dragNode = mImage ? mImage.get() : aDOMNode; + + // get the presshell for the node being dragged. If the drag image is not in + // a document or has no frame, get the presshell from the source drag node + nsIPresShell* presShell = GetPresShellForContent(dragNode); + if (!presShell && mImage) + presShell = GetPresShellForContent(aDOMNode); + if (!presShell) + return NS_ERROR_FAILURE; + + // didn't want an image, so just set the screen rectangle to the frame size + if (!mHasImage) { + // if a region was specified, set the screen rectangle to the area that + // the region occupies + if (aRegion) { + // the region's coordinates are relative to the root frame + nsPresContext* pc = presShell->GetPresContext(); + nsIFrame* rootFrame = presShell->GetRootFrame(); + if (rootFrame && pc) { + nsRect dragRect; + aRegion->GetBoundingBox(&dragRect.x, &dragRect.y, &dragRect.width, &dragRect.height); + dragRect.ScaleRoundOut(nsPresContext::AppUnitsPerCSSPixel()); + dragRect.ScaleRoundOut(1.0 / pc->AppUnitsPerDevPixel()); + + nsIntRect screenRect = rootFrame->GetScreenRectExternal(); + aScreenDragRect->SetRect(screenRect.x + dragRect.x, screenRect.y + dragRect.y, + dragRect.width, dragRect.height); + } + } + else { + // otherwise, there was no region so just set the rectangle to + // the size of the primary frame of the content. + nsCOMPtr content = do_QueryInterface(dragNode); + nsIFrame* frame = presShell->GetPrimaryFrameFor(content); + if (frame) { + nsIntRect screenRect = frame->GetScreenRectExternal(); + aScreenDragRect->SetRect(screenRect.x, screenRect.y, + screenRect.width, screenRect.height); + } + } + + return NS_OK; + } + + // draw the image for selections + if (mSelection) { + nsPoint pnt(aScreenDragRect->x, aScreenDragRect->y); + nsRefPtr surface = presShell->RenderSelection(mSelection, pnt, aScreenDragRect); + *aSurface = surface; + NS_IF_ADDREF(*aSurface); + return NS_OK; + } + + // if an custom image was specified, check if it is an image node and draw + // using the source rather than the displayed image. But if mImage isn't + // an image, fall through to RenderNode below. + if (mImage) { + nsCOMPtr imageLoader = do_QueryInterface(dragNode); + // for image nodes, create the drag image from the actual image data + if (imageLoader) { + nsPresContext* pc = presShell->GetPresContext(); + if (!pc) + return NS_ERROR_FAILURE; + + return DrawDragForImage(pc, imageLoader, aScreenX, aScreenY, + aScreenDragRect, aSurface); + } + } + + // otherwise, just draw the node + nsCOMPtr clipRegion; + if (aRegion) + aRegion->GetRegion(getter_AddRefs(clipRegion)); + + nsPoint pnt(aScreenDragRect->x, aScreenDragRect->y); + nsRefPtr surface = presShell->RenderNode(dragNode, clipRegion, + pnt, aScreenDragRect); + + // if an image was specified, reposition the drag rectangle to + // the supplied offset in mImageX and mImageY. + if (mImage) { + aScreenDragRect->x = aScreenX - mImageX; + aScreenDragRect->y = aScreenY - mImageY; + } + + *aSurface = surface; + NS_IF_ADDREF(*aSurface); + + return NS_OK; +} + +nsresult +nsBaseDragService::DrawDragForImage(nsPresContext* aPresContext, + nsIImageLoadingContent* aImageLoader, + PRInt32 aScreenX, PRInt32 aScreenY, + nsRect* aScreenDragRect, + gfxASurface** aSurface) +{ + nsCOMPtr imgRequest; + nsresult rv = aImageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(imgRequest)); + NS_ENSURE_SUCCESS(rv, rv); + if (!imgRequest) + return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr imgContainer; + rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); + NS_ENSURE_SUCCESS(rv, rv); + if (!imgContainer) + return NS_ERROR_NOT_AVAILABLE; + + // use the size of the image as the size of the drag image + imgContainer->GetWidth(&aScreenDragRect->width); + imgContainer->GetHeight(&aScreenDragRect->height); + + nsRect srcRect = *aScreenDragRect; + srcRect.MoveTo(0, 0); + nsRect destRect = srcRect; + + if (destRect.width == 0 || destRect.height == 0) + return NS_ERROR_FAILURE; + + // if the image is larger than half the screen size, scale it down. This + // scaling algorithm is the same as is used in nsPresShell::PaintRangePaintInfo + nsIDeviceContext* deviceContext = aPresContext->DeviceContext(); + nsRect maxSize; + deviceContext->GetClientRect(maxSize); + nscoord maxWidth = aPresContext->AppUnitsToDevPixels(maxSize.width >> 1); + nscoord maxHeight = aPresContext->AppUnitsToDevPixels(maxSize.height >> 1); + if (destRect.width > maxWidth || destRect.height > maxHeight) { + float scale = 1.0; + if (destRect.width > maxWidth) + scale = PR_MIN(scale, float(maxWidth) / destRect.width); + if (destRect.height > maxHeight) + scale = PR_MIN(scale, float(maxHeight) / destRect.height); + + destRect.width = NSToIntFloor(float(destRect.width) * scale); + destRect.height = NSToIntFloor(float(destRect.height) * scale); + + aScreenDragRect->x = NSToIntFloor(aScreenX - float(mImageX) * scale); + aScreenDragRect->y = NSToIntFloor(aScreenY - float(mImageY) * scale); + aScreenDragRect->width = destRect.width; + aScreenDragRect->height = destRect.height; + } + + nsRefPtr surface = + new gfxImageSurface(gfxIntSize(destRect.width, destRect.height), + gfxImageSurface::ImageFormatARGB32); + if (!surface) + return NS_ERROR_FAILURE; + + *aSurface = surface; + NS_ADDREF(*aSurface); + + nsCOMPtr rc; + deviceContext->CreateRenderingContextInstance(*getter_AddRefs(rc)); + rc->Init(deviceContext, surface); + + // clear the image before drawing + gfxContext context(surface); + context.SetOperator(gfxContext::OPERATOR_CLEAR); + context.Rectangle(gfxRect(0, 0, destRect.width, destRect.height)); + context.Fill(); + + PRInt32 upp = aPresContext->AppUnitsPerDevPixel(); + srcRect.ScaleRoundOut(upp); + destRect.ScaleRoundOut(upp); + return rc->DrawImage(imgContainer, srcRect, destRect); +} + +#endif diff --git a/mozilla/widget/src/xpwidgets/nsBaseDragService.h b/mozilla/widget/src/xpwidgets/nsBaseDragService.h index 5e15b740919..e858c8c76a6 100644 --- a/mozilla/widget/src/xpwidgets/nsBaseDragService.h +++ b/mozilla/widget/src/xpwidgets/nsBaseDragService.h @@ -44,11 +44,16 @@ #include "nsISupportsArray.h" #include "nsIDOMDocument.h" #include "nsCOMPtr.h" +#include "nsIRenderingContext.h" + +#ifdef MOZ_CAIRO_GFX +#include "gfxImageSurface.h" +#endif class nsIDOMNode; class nsIFrame; class nsPresContext; - +class nsIImageLoadingContent; /** * XP DragService wrapper base class @@ -71,13 +76,66 @@ public: protected: +#ifdef MOZ_CAIRO_GFX + /** + * Draw the drag image, if any, to a surface and return it. The drag image + * is constructed from mImage if specified, or aDOMNode if mImage is null. + * + * aRegion may be used to draw only a subset of the element. This region + * should be supplied using x and y coordinates measured in css pixels + * that are relative to the upper-left corner of the window. + * + * aScreenX and aScreenY should be the screen coordinates of the mouse click + * for the drag. + * + * On return, aScreenDragRect will contain the screen coordinates of the + * area being dragged. This is used by the platform-specific part of the + * drag service to determine the drag feedback. + * + * If there is no drag image, the returned surface will be null, but + * aScreenDragRect will still be set to the drag area. + */ + nsresult DrawDrag(nsIDOMNode* aDOMNode, + nsIScriptableRegion* aRegion, + PRInt32 aScreenX, PRInt32 aScreenY, + nsRect* aScreenDragRect, + gfxASurface** aSurface); + + /** + * Draw a drag image for an image node. This is called by DrawDrag. + */ + nsresult DrawDragForImage(nsPresContext* aPresContext, + nsIImageLoadingContent* aImageLoader, + PRInt32 aScreenX, PRInt32 aScreenY, + nsRect* aScreenDragRect, + gfxASurface** aSurface); +#endif + PRPackedBool mCanDrop; PRPackedBool mDoingDrag; + // true if mImage should be used to set a drag image + PRPackedBool mHasImage; + PRUint32 mDragAction; nsSize mTargetSize; nsCOMPtr mSourceNode; nsCOMPtr mSourceDocument; // the document at the drag source. will be null // if it came from outside the app. + + // used to determine the image to appear on the cursor while dragging + nsCOMPtr mImage; + // offset of cursor within the image + PRInt32 mImageX; + PRInt32 mImageY; + + // set if a selection is being dragged + nsCOMPtr mSelection; + + // the screen position where drag gesture occured, used for positioning the + // drag image when no image is specified. If a value is -1, no event was + // supplied so the screen position is not known + PRInt32 mScreenX; + PRInt32 mScreenY; }; #endif // nsBaseDragService_h__ diff --git a/mozilla/xpfe/global/resources/content/nsDragAndDrop.js b/mozilla/xpfe/global/resources/content/nsDragAndDrop.js index 3b924fbc393..d5778948447 100644 --- a/mozilla/xpfe/global/resources/content/nsDragAndDrop.js +++ b/mozilla/xpfe/global/resources/content/nsDragAndDrop.js @@ -429,7 +429,9 @@ var nsDragAndDrop = { count < transferData.dataList.length); try { - this.mDragService.invokeDragSession(aEvent.target, transArray, region, dragAction.action); + this.mDragService.invokeDragSessionWithImage(aEvent.target, transArray, + region, dragAction.action, + null, 0, 0, aEvent); } catch(ex) { // this could be because the user pressed escape to