Bug 533460 - Allow custom panels/windows to be used as drag/drop feedback images. r=karlt,josh,roc

This commit is contained in:
Neil Deakin 2011-04-25 18:37:20 -07:00
Родитель 82d84500ed
Коммит decfc79ff9
15 изменённых файлов: 206 добавлений и 36 удалений

Просмотреть файл

@ -85,6 +85,7 @@
#include "nsIScreenManager.h"
#include "nsIServiceManager.h"
#include "nsThemeConstants.h"
#include "nsDisplayList.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
@ -124,6 +125,7 @@ nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContex
mShouldAutoPosition(PR_TRUE),
mInContentShell(PR_TRUE),
mIsMenuLocked(PR_FALSE),
mIsDragPopup(PR_FALSE),
mHFlip(PR_FALSE),
mVFlip(PR_FALSE)
{
@ -176,6 +178,12 @@ nsMenuPopupFrame::Init(nsIContent* aContent,
}
}
if (mPopupType == ePopupTypePanel &&
aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::drag, eIgnoreCase)) {
mIsDragPopup = PR_TRUE;
}
nsCOMPtr<nsISupports> cont = PresContext()->GetContainer();
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
PRInt32 type = -1;
@ -274,6 +282,7 @@ nsMenuPopupFrame::CreateWidgetForView(nsIView* aView)
widgetData.clipSiblings = PR_TRUE;
widgetData.mPopupHint = mPopupType;
widgetData.mNoAutoHide = IsNoAutoHide();
widgetData.mIsDragPopup = mIsDragPopup;
nsAutoString title;
if (mContent && widgetData.mNoAutoHide) {
@ -1115,6 +1124,12 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, PRBool aIsMove)
if (!mShouldAutoPosition)
return NS_OK;
// If this is due to a move, return early if the popup hasn't been laid out
// yet. On Windows, this can happen when using a drag popup before it opens.
if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
return NS_OK;
}
nsPresContext* presContext = PresContext();
nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
NS_ASSERTION(rootFrame->GetView() && GetView() &&
@ -1768,6 +1783,19 @@ nsMenuPopupFrame::AttachedDismissalListener()
mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT;
}
nsresult
nsMenuPopupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists)
{
// don't pass events to drag popups
if (aBuilder->IsForEventDelivery() && mIsDragPopup) {
return NS_OK;
}
return nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
}
// helpers /////////////////////////////////////////////////////////////
NS_IMETHODIMP

Просмотреть файл

@ -240,6 +240,8 @@ public:
PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
PRBool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
PRBool IsDragPopup() { return mIsDragPopup; }
// returns the parent menupopup, if any
nsMenuFrame* GetParentMenu() {
nsIFrame* parent = GetParent();
@ -345,6 +347,9 @@ public:
// Return the screen coordinates of the popup, or (-1, -1) if anchored.
nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); }
NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists);
protected:
// returns the popup's level.
@ -449,6 +454,7 @@ protected:
PRPackedBool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup?
PRPackedBool mInContentShell; // True if the popup is in a content shell
PRPackedBool mIsMenuLocked; // Should events inside this menu be ignored?
PRPackedBool mIsDragPopup; // True if this is a popup used for drag feedback
// the flip modes that were used when the popup was opened
PRPackedBool mHFlip;

Просмотреть файл

@ -1402,8 +1402,11 @@ nsXULPopupManager::GetVisiblePopups()
item = mNoHidePanels;
while (item) {
if (item->Frame()->PopupState() == ePopupOpenAndVisible)
// skip panels which are not open and visible as well as draggable popups,
// as those don't respond to events.
if (item->Frame()->PopupState() == ePopupOpenAndVisible && !item->Frame()->IsDragPopup()) {
popups.AppendElement(static_cast<nsIFrame*>(item->Frame()));
}
item = item->GetParent();
}

Просмотреть файл

@ -145,6 +145,8 @@ interface nsIDragService : nsISupports
*/
void suppress();
void unsuppress();
[noscript] void dragMoved(in long aX, in long aY);
};

Просмотреть файл

@ -146,6 +146,7 @@ struct nsWidgetInitData {
PRPackedBool mUnicode;
PRPackedBool mRTL;
PRPackedBool mNoAutoHide; // true for noautohide panels
PRPackedBool mIsDragPopup; // true for drag feedback panels
};
#endif // nsWidgetInitData_h__

Просмотреть файл

@ -4503,6 +4503,23 @@ NSEvent* gLastDragMouseDownEvent = nil;
return handled;
}
// NSDraggingSource
- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
{
// Get the drag service if it isn't already cached. The drag service
// isn't cached when dragging over a different application.
nsCOMPtr<nsIDragService> dragService = mDragService;
if (!dragService) {
dragService = do_GetService(kDragServiceContractID);
}
if (dragService) {
NSPoint pnt = [NSEvent mouseLocation];
FlipCocoaScreenCoordinate(pnt);
dragService->DragMoved(NSToIntRound(pnt.x), NSToIntRound(pnt.y));
}
}
// NSDraggingSource
- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{

Просмотреть файл

@ -298,8 +298,12 @@ nsresult nsCocoaWindow::Create(nsIWidget *aParent,
mBorderStyle, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
if (mWindowType == eWindowType_popup)
if (mWindowType == eWindowType_popup) {
if (aInitData->mIsDragPopup) {
[mWindow setIgnoresMouseEvents:YES];
}
return CreatePopupContentView(newBounds, aHandleEventFunction, aContext, aAppShell, aToolkit);
}
return NS_OK;

Просмотреть файл

@ -302,6 +302,7 @@ nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransf
gDraggedTransferables = aTransferableArray;
nsBaseDragService::StartDragSession();
nsBaseDragService::OpenDragPopup();
// We need to retain the view and the event during the drag in case either gets destroyed.
mNativeDragView = [gLastDragView retain];

Просмотреть файл

@ -69,6 +69,7 @@
#include "nsPresContext.h"
#include "nsIDocument.h"
#include "nsISelection.h"
#include "nsIFrame.h"
// This sets how opaque the drag image is
#define DRAG_IMAGE_ALPHA_LEVEL 0.5
@ -1568,20 +1569,41 @@ nsDragService::SourceDataGet(GtkWidget *aWidget,
void nsDragService::SetDragIcon(GdkDragContext* aContext)
{
if (!mHasImage && !mSelection)
return;
nsIntRect dragRect;
nsPresContext* pc;
nsRefPtr<gfxASurface> surface;
if (mHasImage || mSelection) {
DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY,
&dragRect, getter_AddRefs(surface), &pc);
DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY,
&dragRect, getter_AddRefs(surface), &pc);
if (!pc)
return;
PRInt32 sx = mScreenX, sy = mScreenY;
ConvertToUnscaledDevPixels(pc, &sx, &sy);
PRInt32 offsetX = sx - dragRect.x;
PRInt32 offsetY = sy - dragRect.y;
// If a popup is set as the drag image, use its widget. Otherwise, use
// the surface that DrawDrag created.
if (mDragPopup) {
GtkWidget* gtkWidget = nsnull;
nsIFrame* frame = mDragPopup->GetPrimaryFrame();
if (frame) {
// DrawDrag ensured that this is a popup frame.
nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
if (widget) {
gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
if (gtkWidget) {
OpenDragPopup();
gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
}
}
}
}
if (surface) {
PRInt32 sx = mScreenX, sy = mScreenY;
ConvertToUnscaledDevPixels(pc, &sx, &sy);
PRInt32 offsetX = sx - dragRect.x;
PRInt32 offsetY = sy - dragRect.y;
else if (surface) {
if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
GdkPixbuf* dragPixbuf =
nsImageToPixbuf::SurfaceToPixbuf(surface, dragRect.width, dragRect.height);

Просмотреть файл

@ -4059,16 +4059,21 @@ nsWindow::Create(nsIWidget *aParent,
}
GdkWindowTypeHint gtkTypeHint;
switch (aInitData->mPopupHint) {
case ePopupTypeMenu:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
break;
case ePopupTypeTooltip:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
break;
default:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
break;
if (aInitData->mIsDragPopup) {
gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
}
else {
switch (aInitData->mPopupHint) {
case ePopupTypeMenu:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
break;
case ePopupTypeTooltip:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
break;
default:
gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
break;
}
}
gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);

Просмотреть файл

@ -298,11 +298,11 @@ nsDragService::StartInvokingDragSession(IDataObject * aDataObj,
// XXX not sure why we bother to cache this, it can change during
// the drag
mDragAction = aActionType;
mDoingDrag = PR_TRUE;
mSentLocalDropEvent = PR_FALSE;
// Start dragging
StartDragSession();
OpenDragPopup();
nsRefPtr<IAsyncOperation> pAsyncOp;
// Offer to do an async drag

Просмотреть файл

@ -39,6 +39,12 @@
#include <stdio.h>
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nsIServiceManager.h"
#include "nsToolkit.h"
#include "nsWidgetsCID.h"
#include "nsIDragService.h"
static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
/*
* class nsNativeDragSource
@ -94,6 +100,12 @@ nsNativeDragSource::Release(void)
STDMETHODIMP
nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
{
nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
if (dragService) {
DWORD pos = ::GetMessagePos();
dragService->DragMoved(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
}
if (fEsc) {
mUserCancelled = PR_TRUE;
return DRAGDROP_S_CANCEL;

Просмотреть файл

@ -533,6 +533,11 @@ nsWindow::Create(nsIWidget *aParent,
if (mWindowType == eWindowType_popup) {
if (!aParent)
parent = NULL;
if (aInitData->mIsDragPopup) {
// This flag makes the window transparent to mouse events
extendedStyle |= WS_EX_TRANSPARENT;
}
} else if (mWindowType == eWindowType_invisible) {
// Make sure CreateWindowEx succeeds at creating a toplevel window
style &= ~0x40000000; // WS_CHILDWINDOW

Просмотреть файл

@ -65,6 +65,8 @@
#include "nsIViewObserver.h"
#include "nsRegion.h"
#include "nsGUIEvent.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "mozilla/Preferences.h"
#include "gfxContext.h"
@ -272,6 +274,7 @@ nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode,
mDataTransfer = aDataTransfer;
mSelection = nsnull;
mHasImage = PR_TRUE;
mDragPopup = nsnull;
mImage = aImage;
mImageX = aImageX;
mImageY = aImageY;
@ -299,6 +302,7 @@ nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection,
mDataTransfer = aDataTransfer;
mSelection = aSelection;
mHasImage = PR_TRUE;
mDragPopup = nsnull;
mImage = nsnull;
mImageX = 0;
mImageY = 0;
@ -347,9 +351,21 @@ nsBaseDragService::StartDragSession()
mDoingDrag = PR_TRUE;
// By default dispatch drop also to content.
mOnlyChromeDrop = PR_FALSE;
return NS_OK;
}
void
nsBaseDragService::OpenDragPopup()
{
if (mDragPopup) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
pm->ShowPopupAtScreen(mDragPopup, mScreenX - mImageX, mScreenY - mImageY, PR_FALSE, nsnull);
}
}
}
//-------------------------------------------------------------------------
NS_IMETHODIMP
nsBaseDragService::EndDragSession(PRBool aDoneDrag)
@ -361,6 +377,13 @@ nsBaseDragService::EndDragSession(PRBool aDoneDrag)
if (aDoneDrag && !mSuppressLevel)
FireDragEventAtSource(NS_DRAGDROP_END);
if (mDragPopup) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
pm->HidePopup(mDragPopup, PR_FALSE, PR_TRUE, PR_FALSE);
}
}
mDoingDrag = PR_FALSE;
// release the source we've been holding on to.
@ -370,6 +393,7 @@ nsBaseDragService::EndDragSession(PRBool aDoneDrag)
mDataTransfer = nsnull;
mHasImage = PR_FALSE;
mUserCancelled = PR_FALSE;
mDragPopup = nsnull;
mImage = nsnull;
mImageX = 0;
mImageY = 0;
@ -406,6 +430,23 @@ nsBaseDragService::FireDragEventAtSource(PRUint32 aMsg)
return NS_OK;
}
/* This is used by Windows and Mac to update the position of a popup being
* used as a drag image during the drag. This isn't used on GTK as it manages
* the drag popup itself.
*/
NS_IMETHODIMP
nsBaseDragService::DragMoved(PRInt32 aX, PRInt32 aY)
{
if (mDragPopup) {
nsIFrame* frame = mDragPopup->GetPrimaryFrame();
if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
(static_cast<nsMenuPopupFrame *>(frame))->MoveTo(aX - mImageX, aY - mImageY, PR_TRUE);
}
}
return NS_OK;
}
static nsIPresShell*
GetPresShellForContent(nsIDOMNode* aDOMNode)
{
@ -437,8 +478,8 @@ nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode,
// use a default size, in case of an error.
aScreenDragRect->x = aScreenX - mImageX;
aScreenDragRect->y = aScreenY - mImageY;
aScreenDragRect->width = 20;
aScreenDragRect->height = 20;
aScreenDragRect->width = 1;
aScreenDragRect->height = 1;
// if a drag image was specified, use that, otherwise, use the source node
nsCOMPtr<nsIDOMNode> dragNode = mImage ? mImage.get() : aDOMNode;
@ -498,9 +539,9 @@ nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode,
return NS_OK;
}
// if an custom image was specified, check if it is an image node and draw
// if a 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.
// an image or canvas, fall through to RenderNode below.
if (mImage) {
nsCOMPtr<nsICanvasElementExternal> canvas = do_QueryInterface(dragNode);
if (canvas) {
@ -514,18 +555,31 @@ nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode,
return DrawDragForImage(*aPresContext, imageLoader, nsnull, aScreenX,
aScreenY, aScreenDragRect, aSurface);
}
// If the image is a popup, use that as the image. This allows custom drag
// images that can change during the drag, but means that any platform
// default image handling won't occur.
// XXXndeakin this should be chrome-only
nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
nsIFrame* frame = content->GetPrimaryFrame();
if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
mDragPopup = content;
}
}
// otherwise, just draw the node
nsIntRegion clipRegion;
if (aRegion) {
aRegion->GetRegion(&clipRegion);
}
nsRefPtr<gfxASurface> surface;
if (!mDragPopup) {
// otherwise, just draw the node
nsIntRegion clipRegion;
if (aRegion) {
aRegion->GetRegion(&clipRegion);
}
nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
nsRefPtr<gfxASurface> surface =
presShell->RenderNode(dragNode, aRegion ? &clipRegion : nsnull,
pnt, aScreenDragRect);
nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
surface = presShell->RenderNode(dragNode, aRegion ? &clipRegion : nsnull,
pnt, aScreenDragRect);
}
// if an image was specified, reposition the drag rectangle to
// the supplied offset in mImageX and mImageY.

Просмотреть файл

@ -44,6 +44,7 @@
#include "nsISupportsArray.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDataTransfer.h"
#include "nsIContent.h"
#include "nsCOMPtr.h"
#include "nsPoint.h"
@ -129,6 +130,11 @@ protected:
ConvertToUnscaledDevPixels(nsPresContext* aPresContext,
PRInt32* aScreenX, PRInt32* aScreenY);
/**
* If the drag image is a popup, open the popup when the drag begins.
*/
void OpenDragPopup();
PRPackedBool mCanDrop;
PRPackedBool mOnlyChromeDrop;
PRPackedBool mDoingDrag;
@ -153,6 +159,10 @@ protected:
// set if a selection is being dragged
nsCOMPtr<nsISelection> mSelection;
// set if the image in mImage is a popup. If this case, the popup will be opened
// and moved instead of using a drag image.
nsCOMPtr<nsIContent> mDragPopup;
// the screen position where drag gesture occurred, 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