Add drag auto-scrolling to trees. bug#28226, r=hyatt.

This commit is contained in:
pinkerton%netscape.com 2000-06-22 08:40:37 +00:00
parent 1372f7334a
commit 1821dfd254
7 changed files with 529 additions and 26 deletions

View File

@ -20,6 +20,7 @@
* Original Author: David W. Hyatt (hyatt@netscape.com)
*
* Contributor(s):
* Mike Pinkerton (pinkerton@netscape.com)
*/
#include "nsCOMPtr.h"
@ -34,12 +35,118 @@
#include "nsCSSFrameConstructor.h"
#include "nsIDocument.h"
#include "nsTreeItemDragCapturer.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMMouseEvent.h"
#include "nsIDragService.h"
#include "nsIServiceManager.h"
#define TICK_FACTOR 50
static NS_DEFINE_IID(kIFrameIID, NS_IFRAME_IID);
nsresult NS_NewAutoScrollTimer(nsXULTreeOuterGroupFrame* aTree, nsDragAutoScrollTimer **aResult) ;
//
// nsDragOverListener
//
// Just a little class that listens for dragOvers to trigger the auto-scrolling
// code.
//
class nsDragOverListener : public nsIDOMDragListener
{
public:
nsDragOverListener ( nsXULTreeOuterGroupFrame* inTree );
virtual ~nsDragOverListener() { } ;
NS_DECL_ISUPPORTS
// nsIDOMDragListener
virtual nsresult HandleEvent(nsIDOMEvent* aEvent);
virtual nsresult DragEnter(nsIDOMEvent* aDragEvent);
virtual nsresult DragOver(nsIDOMEvent* aDragEvent);
virtual nsresult DragExit(nsIDOMEvent* aDragEvent);
virtual nsresult DragDrop(nsIDOMEvent* aDragEvent);
virtual nsresult DragGesture(nsIDOMEvent* aDragEvent);
protected:
nsXULTreeOuterGroupFrame* mTree;
}; // nsDragEnterListener
NS_IMPL_ISUPPORTS2(nsDragOverListener, nsIDOMEventListener, nsIDOMDragListener)
nsDragOverListener :: nsDragOverListener ( nsXULTreeOuterGroupFrame* inTree )
: mTree ( inTree )
{
NS_INIT_REFCNT();
}
//
// DragOver
//
// Kick off our timer/capturing for autoscrolling, the drag has entered us. We
// will continue capturing the mouse until the auto-scroll manager tells us to
// stop (either the drag is over, it left the window, or someone else wants a
// crack at auto-scrolling).
//
nsresult
nsDragOverListener :: DragOver(nsIDOMEvent* aDragEvent)
{
nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aDragEvent) );
if ( mouseEvent ) {
PRInt32 x = 0, y = 0;
mouseEvent->GetClientX ( &x );
mouseEvent->GetClientY ( &y );
mTree->HandleAutoScrollTracking ( nsPoint(x,y) );
}
return NS_OK;
} // DragOver
//
// The rest are stubs
//
nsresult
nsDragOverListener::HandleEvent(nsIDOMEvent* aEvent)
{
return NS_OK;
}
nsresult
nsDragOverListener::DragEnter(nsIDOMEvent* aDragEvent)
{
return NS_OK;
}
nsresult
nsDragOverListener::DragGesture(nsIDOMEvent* aDragEvent)
{
return NS_OK;
}
nsresult
nsDragOverListener::DragExit(nsIDOMEvent* aDragEvent)
{
return NS_OK;
}
nsresult
nsDragOverListener::DragDrop(nsIDOMEvent* aDragEvent)
{
return NS_OK;
}
#ifdef XP_MAC
#pragma mark -
#endif
//
// NS_NewXULTreeOuterGroupFrame
//
@ -66,14 +173,28 @@ NS_NewXULTreeOuterGroupFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRB
// Constructor
nsXULTreeOuterGroupFrame::nsXULTreeOuterGroupFrame(nsIPresShell* aPresShell, PRBool aIsRoot, nsIBoxLayout* aLayoutManager, PRBool aIsHorizontal)
:nsXULTreeGroupFrame(aPresShell, aIsRoot, aLayoutManager, aIsHorizontal),
mRowGroupInfo(nsnull), mRowHeight(0), mCurrentIndex(0), mTwipIndex(0),
mTreeIsSorted(PR_FALSE)
{}
mRowGroupInfo(nsnull), mRowHeight(0), mCurrentIndex(0), mTwipIndex(0), mAutoScrollTimer(nsnull),
mTreeIsSorted(PR_FALSE), mDragOverListener(nsnull), mCurrentlyTrackingAutoScroll(PR_FALSE)
{
}
// Destructor
nsXULTreeOuterGroupFrame::~nsXULTreeOuterGroupFrame()
{
nsCOMPtr<nsIContent> content;
GetContent(getter_AddRefs(content));
nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(content));
// NOTE: the last Remove will delete the drag capturer
if ( receiver && mDragOverListener )
receiver->RemoveEventListener(NS_ConvertASCIItoUCS2("dragover"), mDragOverListener, PR_TRUE);
delete mRowGroupInfo;
StopAutoScrollTimer();
NS_IF_RELEASE ( mAutoScrollTimer );
}
NS_IMETHODIMP_(nsrefcnt)
@ -93,8 +214,15 @@ nsXULTreeOuterGroupFrame::Release(void)
//
NS_INTERFACE_MAP_BEGIN(nsXULTreeOuterGroupFrame)
NS_INTERFACE_MAP_ENTRY(nsIScrollbarMediator)
NS_INTERFACE_MAP_ENTRY(nsIDragTracker)
NS_INTERFACE_MAP_END_INHERITING(nsXULTreeGroupFrame)
//
// Init
//
// Setup scrolling and event listeners for drag auto-scrolling
//
NS_IMETHODIMP
nsXULTreeOuterGroupFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent,
nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow)
@ -124,6 +252,16 @@ nsXULTreeOuterGroupFrame::Init(nsIPresContext* aPresContext, nsIContent* aConten
nsCOMPtr<nsIScrollbarFrame> scrollbarFrame(do_QueryInterface(verticalScrollbar));
scrollbarFrame->SetScrollbarMediator(this);
// Our frame's lifetime is bounded by the lifetime of the content model, so we're guaranteed
// that the content node won't go away on us. As a result, our listener can't go away before the
// frame is deleted. Since the content node holds owning references to our drag capturer, which
// we tear down in the dtor, there is no need to hold an owning ref to it ourselves.
nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(aContent));
if ( receiver ) {
mDragOverListener = new nsDragOverListener(this);
receiver->AddEventListener(NS_ConvertASCIItoUCS2("dragover"), mDragOverListener, PR_FALSE);
}
return rv;
} // Init
@ -290,8 +428,6 @@ nsXULTreeOuterGroupFrame::PositionChanged(PRInt32 aOldIndex, PRInt32 aNewIndex)
if (aOldIndex == aNewIndex)
return NS_OK;
printf("Old Index: %d, New Index: %d\n", aOldIndex, aNewIndex);
PRInt32 oldTwipIndex, newTwipIndex;
oldTwipIndex = (aOldIndex*mOnePixel);
newTwipIndex = (aNewIndex*mOnePixel);
@ -793,6 +929,9 @@ nsXULTreeOuterGroupFrame::EnsureRowIsVisible(PRInt32 aRowIndex)
void
nsXULTreeOuterGroupFrame::ScrollToIndex(PRInt32 aRowIndex)
{
if ( aRowIndex < 0 )
return;
PRInt32 newIndex = aRowIndex;
PRInt32 delta = mCurrentIndex > newIndex ? mCurrentIndex - newIndex : newIndex - mCurrentIndex;
PRBool up = newIndex < mCurrentIndex;
@ -918,3 +1057,219 @@ nsXULTreeOuterGroupFrame :: AttributeChanged ( nsIPresContext* aPresContext, nsI
} // AttributeChanged
#ifdef XP_MAC
#pragma mark -
#endif
//
// StopTracking
//
// Stop tracking auto-scrolling
//
NS_IMETHODIMP
nsXULTreeOuterGroupFrame :: StopTracking ( )
{
// turn off capturing of the mouse.
CaptureMouse ( mPresContext, PR_FALSE );
StopAutoScrollTimer ( );
mCurrentlyTrackingAutoScroll = PR_FALSE;
return NS_OK;
}
//
// HandleAutoScrollTracking
//
// Do all the setup for tracking drag auto-scrolling. This can be called repeatedly, but the
// setup will only be done once. Handles capturing the mouse, starting a timer, and registering
// ourselves with a manager that will let us know when we're to stop tracking.
//
nsresult
nsXULTreeOuterGroupFrame :: HandleAutoScrollTracking ( const nsPoint & aPoint )
{
if ( !mCurrentlyTrackingAutoScroll ) {
CaptureMouse ( mPresContext, PR_TRUE );
// register with the drag auto-scroll manager so we're told when to stop
// tracking
nsCOMPtr<nsIDragService> dragServ ( do_GetService("component://netscape/widget/dragservice") );
if ( dragServ ) {
nsCOMPtr<nsIDragSession> session;
dragServ->GetCurrentSession ( getter_AddRefs(session) );
if ( session )
session->StartTracking(this);
}
mCurrentlyTrackingAutoScroll = PR_TRUE; // setup complete
}
// kick off our timer
StartAutoScrollTimer ( aPoint, 30 );
return NS_OK;
} // HandleAutoScrollTracking
nsresult
nsXULTreeOuterGroupFrame::StartAutoScrollTimer(const nsPoint& aPoint, PRUint32 aDelay)
{
nsresult result;
if (!mAutoScrollTimer) {
result = NS_NewAutoScrollTimer(this, &mAutoScrollTimer);
if (NS_FAILED(result))
return result;
if (!mAutoScrollTimer)
return NS_ERROR_OUT_OF_MEMORY;
}
result = mAutoScrollTimer->SetDelay(aDelay);
if (NS_FAILED(result))
return result;
return DoAutoScroll(aPoint);
}
nsresult
nsXULTreeOuterGroupFrame::StopAutoScrollTimer()
{
if (mAutoScrollTimer)
return mAutoScrollTimer->Stop();
return NS_OK;
}
//
// DoAutoScroll
//
// Given the current mouse position, checks if it is w/in the bounds of the row area
// of the tree. If not, scroll up or down in increments of one row.
//
nsresult
nsXULTreeOuterGroupFrame::DoAutoScroll ( const nsPoint& aPoint )
{
if ( mAutoScrollTimer )
mAutoScrollTimer->Stop();
float pixelsToTwips = 0.0;
mPresContext->GetPixelsToTwips ( &pixelsToTwips );
nsPoint mouseInTwips ( NSToIntRound(aPoint.x * pixelsToTwips), NSToIntRound(aPoint.y * pixelsToTwips) );
// compute the offset to top level in twips and subtract the offset from
// the mouse coord to put it into our coordinates.
nscoord frameOffsetX = 0, frameOffsetY = 0;
nsIFrame* curr = this;
curr->GetParent(&curr);
while ( curr ) {
nsPoint origin;
curr->GetOrigin(origin); // in twips
frameOffsetX += origin.x; // build the offset incrementally
frameOffsetY += origin.y;
curr->GetParent(&curr); // moving up the chain
} // until we reach the top
mouseInTwips.MoveBy ( -frameOffsetX, -frameOffsetY );
const int kMarginHeight = NSToIntRound ( 12 * pixelsToTwips );
if ( mouseInTwips.x <= 0 || mouseInTwips.x >= mRect.width )
UnregisterTracking();
else {
// scroll up or down, accordingly. if the mouse is outside of the scroll margin, stop the tracking altogether.
PRBool continueTracking = PR_TRUE;
if ( mouseInTwips.y < 0 ) {
if ( mouseInTwips.y > -kMarginHeight )
ScrollToIndex ( mCurrentIndex - 1 );
else {
continueTracking = PR_FALSE;
UnregisterTracking();
}
}
else if ( mouseInTwips.y > GetAvailableHeight() ) {
if ( mouseInTwips.y < GetAvailableHeight() + kMarginHeight )
ScrollToIndex ( mCurrentIndex + 1 );
else {
continueTracking = PR_FALSE;
UnregisterTracking();
}
}
// restart the timer
if ( mAutoScrollTimer && continueTracking )
mAutoScrollTimer->Start(aPoint);
}
return NS_OK;
}
//
// UnregisterTracking
//
// Tell the drag session that we're done tracking the mouse
//
void
nsXULTreeOuterGroupFrame :: UnregisterTracking ( )
{
nsCOMPtr<nsIDragService> dragService ( do_GetService("component://netscape/widget/dragservice") );
if ( dragService ) {
// tell anyone interested to stop tracking drags
nsCOMPtr<nsIDragSession> session;
dragService->GetCurrentSession ( getter_AddRefs(session) );
if ( session )
session->StopTracking();
}
} // UnregisterTracking
#ifdef XP_MAC
#pragma mark -
#endif
//
// nsDragAutoScrollTimer Impl
//
NS_IMPL_ISUPPORTS1(nsDragAutoScrollTimer, nsITimerCallback)
//
// Notify
//
// Called when the timer fires. Tells the tree to try to scroll.
//
void
nsDragAutoScrollTimer :: Notify(nsITimer *timer)
{
mTree->DoAutoScroll(mPoint);
}
nsresult
NS_NewAutoScrollTimer(nsXULTreeOuterGroupFrame* aTree, nsDragAutoScrollTimer **aResult)
{
if (!aResult)
return NS_ERROR_NULL_POINTER;
*aResult = new nsDragAutoScrollTimer(aTree);
if (!aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}

View File

@ -20,6 +20,7 @@
* Original Author: David W. Hyatt (hyatt@netscape.com)
*
* Contributor(s):
* Mike Pinkerton (pinkerton@netscape.com)
*/
#ifndef NSXULTREEOUTERGROUPFRAME
@ -32,8 +33,15 @@
#include "nsXULTreeGroupFrame.h"
#include "nsIScrollbarMediator.h"
#include "nsIPresContext.h"
#include "nsITimerCallback.h"
#include "nsITimer.h"
#include "nsIDragTracker.h"
class nsCSSFrameConstructor;
class nsDragOverListener;
class nsDragAutoScrollTimer;
#define TREE_LINE_HEIGHT 225
@ -61,17 +69,95 @@ public:
}
};
class nsXULTreeOuterGroupFrame : public nsXULTreeGroupFrame, public nsIScrollbarMediator
/*-----------------------------------------------------------------*/
//
// nsDragAutoScrollTimer
//
// A timer class for handling auto-scrolling during drags
//
class nsDragAutoScrollTimer : public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS
nsDragAutoScrollTimer ( nsXULTreeOuterGroupFrame* inTree )
: mPoint(0,0), mDelay(30), mTree(inTree)
{
NS_INIT_ISUPPORTS();
}
virtual ~nsDragAutoScrollTimer()
{
if (mTimer)
mTimer->Cancel();
}
nsresult Start(const nsPoint& aPoint)
{
mPoint = aPoint;
if ( !mTimer ) {
nsresult result;
mTimer = do_CreateInstance("component://netscape/timer", &result);
if (NS_FAILED(result))
return result;
}
return mTimer->Init(this, mDelay);
}
nsresult Stop()
{
nsresult result = NS_OK;
if (mTimer) {
mTimer->Cancel();
mTimer = nsnull;
}
return result;
}
nsresult SetDelay(PRUint32 aDelay)
{
mDelay = aDelay;
return NS_OK;
}
NS_IMETHOD_(void) Notify(nsITimer *timer) ;
private:
nsXULTreeOuterGroupFrame *mTree;
nsCOMPtr<nsITimer> mTimer;
nsPoint mPoint;
PRUint32 mDelay;
}; // nsDragAutoScrollTimer
/*-----------------------------------------------------------------*/
class nsXULTreeOuterGroupFrame : public nsXULTreeGroupFrame, public nsIScrollbarMediator,
public nsIDragTracker
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDRAGTRACKER
friend class nsDragAutoScrollTimer;
friend class nsDragOverListener;
friend nsresult NS_NewXULTreeOuterGroupFrame(nsIPresShell* aPresShell,
nsIFrame** aNewFrame,
PRBool aIsRoot = PR_FALSE,
nsIBoxLayout* aLayoutManager = nsnull,
PRBool aDefaultHorizontal = PR_TRUE);
NS_IMETHOD Init(nsIPresContext* aPresContext, nsIContent* aContent,
nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow);
@ -79,8 +165,6 @@ protected:
nsXULTreeOuterGroupFrame(nsIPresShell* aPresShell, PRBool aIsRoot = nsnull, nsIBoxLayout* aLayoutManager = nsnull, PRBool aDefaultHorizontal = PR_TRUE);
virtual ~nsXULTreeOuterGroupFrame();
void ComputeTotalRowCount(PRInt32& aRowCount, nsIContent* aParent);
public:
NS_IMETHOD GetPrefSize(nsBoxLayoutState& aBoxLayoutState, nsSize& aSize)
{
@ -152,15 +236,33 @@ public:
NS_IMETHOD InternalPositionChanged(PRBool aUp, PRInt32 aDelta);
PRBool IsTreeSorted ( ) const { return mTreeIsSorted; }
protected: // Data Members
void ComputeTotalRowCount(PRInt32& aRowCount, nsIContent* aParent);
// Drag Auto-scrolling. Call HandleAutoScrollTracking() to setup everything and
// do the scrolling magic. It can be called multiple times, though the
// setup will only be done once.
nsresult HandleAutoScrollTracking ( const nsPoint & aPoint );
nsresult StartAutoScrollTimer(const nsPoint& aPoint, PRUint32 aDelay);
nsresult StopAutoScrollTimer();
nsresult DoAutoScroll(const nsPoint& aPoint);
void UnregisterTracking ( ) ;
nsXULTreeRowGroupInfo* mRowGroupInfo;
PRInt32 mRowHeight;
nscoord mOnePixel;
PRInt32 mCurrentIndex; // Row-based
PRInt32 mTwipIndex; // Not really accurate. Used to handle thumb dragging
PRPackedBool mTreeIsSorted;
PRPackedBool mCurrentlyTrackingAutoScroll; // used to track if we've done setup already
// our auto-scroll event listener registered with the content model. See the discussion
// in Init() for why this is a weak ref.
nsDragOverListener* mDragOverListener;
nsDragAutoScrollTimer* mAutoScrollTimer; // actually a strong ref
}; // class nsXULTreeOuterGroupFrame

View File

@ -1320,7 +1320,7 @@ NS_IMETHODIMP nsViewManager2::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus *a
//Find the view to which we're initially going to send the event
//for hittesting.
if (nsnull != mMouseGrabber && NS_IS_MOUSE_EVENT(aEvent)) {
if (nsnull != mMouseGrabber && (NS_IS_MOUSE_EVENT(aEvent) || NS_IS_DRAG_EVENT(aEvent))) {
view = mMouseGrabber;
}
else if (nsnull != mKeyGrabber && NS_IS_KEY_EVENT(aEvent)) {

View File

@ -23,6 +23,8 @@
#include "nsISupports.idl"
#include "nsISupportsArray.idl"
#include "nsITransferable.idl"
#include "nsIDragTracker.idl"
%{ C++
#include "nsSize.h"
@ -72,6 +74,22 @@ interface nsIDragSession : nsISupports
*/
boolean isDataFlavorSupported ( in string aDataFlavor ) ;
/**
* Tell the session who needs to know when the drag completes so that it can
* stop tracking the mouse. There can be only one object tracking the drag at
* a time.
*
* @param aTracker - object who needs to know when tracking
* should cease.
*/
void startTracking ( in nsIDragTracker aScroller );
/**
* Call to tell the object that is tracking the drag that it
* can forget about it (mouse left the window or the drop completed).
*/
void stopTracking ( ) ;
};

View File

@ -167,6 +167,12 @@ nsMacWindow :: DragTrackingHandler ( DragTrackingMessage theMessage, WindowPtr t
case kDragTrackingLeaveWindow:
{
// stop any drag tracking in this window
nsCOMPtr<nsIDragSession> session;
sDragService->GetCurrentSession ( getter_AddRefs(session) );
if ( session )
session->StopTracking();
// tell the drag service that we're done with it.
if ( sDragService ) {
sDragService->EndDragSession();
@ -177,10 +183,9 @@ nsMacWindow :: DragTrackingHandler ( DragTrackingMessage theMessage, WindowPtr t
// was in our window.
nsCOMPtr<nsIDragSessionMac> macSession ( do_QueryInterface(sDragService) );
if ( macSession )
macSession->SetDragReference ( 0 );
}
macSession->SetDragReference ( 0 );
}
// let gecko know that the mouse has left the window so it
// can stop tracking and sending enter/exit events to frames.
Point mouseLocGlobal;
@ -217,7 +222,7 @@ nsMacWindow :: DragReceiveHandler (WindowPtr theWindow, void *handlerRefCon,
OSErr result = noErr;
printf("DragReceiveHandler called. We got a drop!!!!, DragRef is %ld\n", theDragRef);
// pass the drop event along to Gecko
Point mouseLocGlobal;
::GetDragMouse ( theDragRef, &mouseLocGlobal, nsnull );
@ -227,26 +232,24 @@ nsMacWindow :: DragReceiveHandler (WindowPtr theWindow, void *handlerRefCon,
// once the event has gone to gecko, check the "canDrop" state in the
// drag session to see what we should return to the OS (drag accepted or not).
nsIDragService* dragService;
nsresult rv = nsServiceManager::GetService(kCDragServiceCID,
NS_GET_IID(nsIDragService),
(nsISupports **)&dragService);
if ( NS_SUCCEEDED(rv) && dragService ) {
nsCOMPtr<nsIDragService> dragService ( do_GetService(kCDragServiceCID) );
if ( dragService ) {
nsCOMPtr<nsIDragSession> dragSession;
dragService->GetCurrentSession ( getter_AddRefs(dragSession) );
if ( dragSession ) {
// stop any drag tracking in this window
dragSession->StopTracking();
// fail if the target has set that it can't accept the drag
PRBool canDrop = PR_FALSE;
if ( NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) )
if ( canDrop == PR_FALSE )
result = dragNotAcceptedErr;
}
// we don't need the drag session anymore, the user has released the
// mouse and the event has already gone to gecko.
dragService->EndDragSession();
nsServiceManager::ReleaseService(kCDragServiceCID, dragService);
}
return result;

View File

@ -178,3 +178,26 @@ NS_IMETHODIMP nsBaseDragService::EndDragSession ()
return NS_OK;
}
NS_IMETHODIMP
nsBaseDragService :: StartTracking ( nsIDragTracker *aScroller )
{
StopTracking();
mCurrentlyTracking = aScroller;
return NS_OK;
}
NS_IMETHODIMP
nsBaseDragService :: StopTracking()
{
if ( mCurrentlyTracking ) {
mCurrentlyTracking->StopTracking();
mCurrentlyTracking = nsnull;
}
return NS_OK;
} // StopTracking

View File

@ -53,7 +53,9 @@ protected:
PRBool mCanDrop;
PRBool mDoingDrag;
nsSize mTargetSize;
PRUint32 mDragAction;
PRUint32 mDragAction;
nsCOMPtr<nsIDragTracker> mCurrentlyTracking;
};
#endif // nsBaseDragService_h__