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
This commit is contained in:
enndeakin%sympatico.ca
2007-03-20 12:14:52 +00:00
parent 8e89d6ed4b
commit ae6cfb8b02
20 changed files with 1099 additions and 190 deletions

View File

@@ -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<nsITransferable> 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<nsIDOMEventTarget> target;
inMouseEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(target));
nsCOMPtr<nsIDragService> 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<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(inMouseEvent));
if (isSelection) {
nsCOMPtr<nsIContent> 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<nsIDOMNode> 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<nsIDOMNode> 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);

View File

@@ -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);

View File

@@ -90,6 +90,18 @@ nsresult NS_NewContentSubtreeIterator(nsIContentIterator** aInstancePtrResult);
nsresult
nsRange::CompareNodeToRange(nsIContent* aNode, nsIDOMRange* aRange,
PRBool *outNodeBefore, PRBool *outNodeAfter)
{
nsresult rv;
nsCOMPtr<nsIRange> 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)

View File

@@ -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,

View File

@@ -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<nsIDOMNode> domnode = do_QueryInterface(eventTarget);
nsCOMPtr<nsIScriptableRegion> selRegion;
nsCOMPtr<nsISelection> 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<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aDragEvent));
rv = dragService->InvokeDragSessionWithSelection(selection, transferableArray,
flags, mouseEvent);
if (NS_FAILED(rv)) return rv;
aDragEvent->StopPropagation();

View File

@@ -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 E> 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<gfxASurface> 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<gfxASurface> RenderSelection(nsISelection* aSelection,
nsPoint& aPoint,
nsRect* aScreenRect) = 0;
void AddWeakFrame(nsWeakFrame* aWeakFrame);
void RemoveWeakFrame(nsWeakFrame* aWeakFrame);

View File

@@ -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<nsIRange> 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<gfxASurface> RenderNode(nsIDOMNode* aNode,
nsIRegion* aRegion,
nsPoint& aPoint,
nsRect* aScreenRect);
virtual already_AddRefed<gfxASurface> 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<gfxASurface>
PaintRangePaintInfo(nsTArray<nsAutoPtr<RangePaintInfo> >* 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<nsIRange> 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<gfxASurface>
PresShell::PaintRangePaintInfo(nsTArray<nsAutoPtr<RangePaintInfo> >* 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<nsIRenderingContext> 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<nsFrameSelection> frameSelection;
if (aSelection) {
nsCOMPtr<nsISelectionPrivate> 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<gfxASurface>
PresShell::RenderNode(nsIDOMNode* aNode,
nsIRegion* aRegion,
nsPoint& aPoint,
nsRect* aScreenRect)
{
// create a temporary rendering context for text measuring
nsCOMPtr<nsIRenderingContext> 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<nsAutoPtr<RangePaintInfo> > rangeItems;
nsCOMPtr<nsIDOMRange> 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<gfxASurface>
PresShell::RenderSelection(nsISelection* aSelection,
nsPoint& aPoint,
nsRect* aScreenRect)
{
// create a temporary rendering context for text measuring
nsCOMPtr<nsIRenderingContext> 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<nsAutoPtr<RangePaintInfo> > 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<nsIDOMRange> 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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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 <image> or
* <canvas>, 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
*/

View File

@@ -41,7 +41,10 @@
#include "nsBaseDragService.h"
#include <Cocoa/Cocoa.h>
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_

View File

@@ -60,6 +60,8 @@
#include "nsIFrame.h"
#include "nsIView.h"
#include "nsIRegion.h"
#include "gfxASurface.h"
#include "gfxContext.h"
#import <Cocoa/Cocoa.h>
@@ -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<nsIContent> 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<nsIRegion> 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<nsIWidget> 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<gfxASurface> 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<gfxImageSurface> imgSurface = new gfxImageSurface(
gfxIntSize(width, height), gfxImageSurface::ImageFormatARGB32);
if (!imgSurface)
return nsnull;
nsRefPtr<gfxContext> 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;
}

View File

@@ -58,6 +58,10 @@
#include <gdk/gdkx.h>
#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<gfxASurface> 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);
}

View File

@@ -77,12 +77,25 @@ nsImageToPixbuf::ImageToPixbuf(nsIImage* aImage)
nsRefPtr<gfxASurface> surface;
aImage->GetSurface(getter_AddRefs(surface));
return SurfaceToPixbuf(surface, width, height);
#else
nsCOMPtr<nsIGdkPixbufImage> 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<gfxImageSurface> 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<nsIGdkPixbufImage> img(do_QueryInterface(aImage));
if (img)
return img->GetGdkPixbuf();
return NULL;
#endif
}
#endif

View File

@@ -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() {}
};

View File

@@ -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)

View File

@@ -52,6 +52,7 @@ REQUIRES = xpcom \
layout \
content \
dom \
imglib2 \
pref \
locale \
necko \

View File

@@ -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<nsIDOMNode> 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<nsIDOMNode> 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<nsIContent> 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<gfxASurface> 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<nsIImageLoadingContent> 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<nsIRegion> clipRegion;
if (aRegion)
aRegion->GetRegion(getter_AddRefs(clipRegion));
nsPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
nsRefPtr<gfxASurface> 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<imgIRequest> imgRequest;
nsresult rv = aImageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(imgRequest));
NS_ENSURE_SUCCESS(rv, rv);
if (!imgRequest)
return NS_ERROR_NOT_AVAILABLE;
nsCOMPtr<imgIContainer> 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<gfxImageSurface> surface =
new gfxImageSurface(gfxIntSize(destRect.width, destRect.height),
gfxImageSurface::ImageFormatARGB32);
if (!surface)
return NS_ERROR_FAILURE;
*aSurface = surface;
NS_ADDREF(*aSurface);
nsCOMPtr<nsIRenderingContext> 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

View File

@@ -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<nsIDOMNode> mSourceNode;
nsCOMPtr<nsIDOMDocument> 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<nsIDOMNode> mImage;
// offset of cursor within the image
PRInt32 mImageX;
PRInt32 mImageY;
// set if a selection is being dragged
nsCOMPtr<nsISelection> 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__

View File

@@ -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