/* * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1999. * Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Mike Pinkerton */ #include "nsTreeItemDragCapturer.h" #include "nsCOMPtr.h" #include "nsIDOMMouseEvent.h" #include "nsIPresContext.h" #include "nsIContent.h" #include "nsIDOMElement.h" #include "nsXULAtoms.h" #include "nsIEventStateManager.h" #include "nsISupportsPrimitives.h" #include "nsINameSpaceManager.h" #include "nsIDOMXULDocument.h" #include "nsIDocument.h" #include "nsIPresShell.h" #include "nsIDOMEventTarget.h" #include "nsIView.h" #include "nsIFrame.h" #include "nsXULTreeGroupFrame.h" #include "nsXULTreeOuterGroupFrame.h" NS_IMPL_ADDREF(nsTreeItemDragCapturer) NS_IMPL_RELEASE(nsTreeItemDragCapturer) NS_IMPL_QUERY_INTERFACE2(nsTreeItemDragCapturer, nsIDOMEventListener, nsIDOMDragListener ) // // nsTreeItemDragCapturer ctor // // Init member variables. We can't really do much of anything important here because // any subframes might not be totally intialized yet, or in the hash table // nsTreeItemDragCapturer :: nsTreeItemDragCapturer ( nsXULTreeGroupFrame* inTreeItem, nsIPresContext* inPresContext ) : mTreeItem(inTreeItem), mPresContext(inPresContext), mCurrentDropLoc(kNoDropLoc) { NS_INIT_REFCNT(); // we really need this all over the place. just be safe that we have it. NS_ASSERTION ( mPresContext, "no pres context set on tree drag listener" ); NS_ASSERTION ( mTreeItem, "tree item invalid" ); } // nsTreeItemDragCapturer ctor // // nsTreeItemDragCapturer dtor // // Cleanup. // nsTreeItemDragCapturer::~nsTreeItemDragCapturer() { } //////////////////////////////////////////////////////////////////////// nsresult nsTreeItemDragCapturer::HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; } //////////////////////////////////////////////////////////////////////// nsresult nsTreeItemDragCapturer::DragGesture(nsIDOMEvent* aDragEvent) { // this code should all be in JS. return NS_OK; } //////////////////////////////////////////////////////////////////////// nsresult nsTreeItemDragCapturer::DragEnter(nsIDOMEvent* aDragEvent) { // We don't need to do anything special here. If anything does need to be done, // the code should all be in JS. return NS_OK; } // // ComputeDropPosition // // Compute where the drop feedback should be drawn and the relative position to the current // row on a drop. |outBefore| indicates if the drop will go before or after this row. // |outDropOnMe| is true if the row has children (it's a folder) and the mouse is in the // middle region of the row height. If this is the case, |outBefore| is meaningless. // void nsTreeItemDragCapturer :: ComputeDropPosition ( nsIDOMEvent* aDragEvent, nscoord* outYLoc, PRBool* outBefore, PRBool* outDropOnMe ) { *outYLoc = kNoDropLoc; *outBefore = PR_FALSE; *outDropOnMe = PR_FALSE; float p2t; mPresContext->GetScaledPixelsToTwips(&p2t); nscoord onePixel = NSIntPixelsToTwips(1, p2t); // Gecko trickery to get mouse coordinates into the right coordinate system for // comparison with the row. nsPoint pnt(0,0); nsRect rowRect; ConvertEventCoordsToRowCoords ( aDragEvent, &pnt, &rowRect); // check the outer group frame to see if the tree allows drops between rows. If it // doesn't then basically the entire row is a drop zone for a container. PRBool allowsDropsBetweenRows = PR_TRUE; nsXULTreeOuterGroupFrame* outerGroup = mTreeItem->GetOuterFrame(); if ( outerGroup ) allowsDropsBetweenRows = outerGroup->CanDropBetweenRows(); // now check if item is a container PRBool isContainer = PR_FALSE; nsCOMPtr treeItemContent; mTreeItem->GetContent ( getter_AddRefs(treeItemContent) ); nsCOMPtr treeItemNode ( do_QueryInterface(treeItemContent) ); if ( treeItemNode ) { nsAutoString value; treeItemNode->GetAttribute(NS_LITERAL_STRING("container"), value); // can't use an atom here =( isContainer = value.Equals(NS_LITERAL_STRING("true")); } else NS_WARNING("Not a DOM element"); if ( allowsDropsBetweenRows ) { // if we have a container, the area is broken up into 3 pieces (top, middle, bottom). If // it isn't it's only broken up into two (top and bottom) if ( isContainer ) { if (pnt.y <= (rowRect.y + (rowRect.height / 4))) { *outBefore = PR_TRUE; *outYLoc = 0; } else if (pnt.y >= (rowRect.y + PRInt32(float(rowRect.height) *0.75))) { *outBefore = PR_FALSE; *outYLoc = rowRect.y + rowRect.height - onePixel; } else { // we're on the container *outDropOnMe = PR_TRUE; *outYLoc = kContainerDropLoc; } } // if row is a container else { if (pnt.y <= (rowRect.y + (rowRect.height / 2))) { *outBefore = PR_TRUE; *outYLoc = 0; } else *outYLoc = rowRect.y + rowRect.height - onePixel; } // else is not a container } // if can drop between rows else { // the tree doesn't allow drops between rows, therefore only drops on containers // are valid. For this case, make the entire cell a dropzone for this row as there // is no concept of above and below. if ( isContainer ) { *outYLoc = kContainerDropLoc; *outDropOnMe = PR_TRUE; } } // else only allow drops on containers } // ComputeDropPosition // // ConvertEventCoordsToRowCoords // // Get the mouse coordinates from the DOM event, but they will be in the // window/widget coordinate system. We must first get them into the frame-relative // coordinate system of the row. Yuck. // void nsTreeItemDragCapturer :: ConvertEventCoordsToRowCoords ( nsIDOMEvent* inDragEvent, nsPoint* outCoords, nsRect* outRowRect ) { // get mouse coordinates and translate them into twips nsCOMPtr mouseEvent(do_QueryInterface(inDragEvent)); PRInt32 x,y = 0; mouseEvent->GetClientX(&x); mouseEvent->GetClientY(&y); float p2t; mPresContext->GetScaledPixelsToTwips(&p2t); nscoord xp = NSIntPixelsToTwips(x, p2t); nscoord yp = NSIntPixelsToTwips(y, p2t); // get the rect of the row (not the tree item) that the mouse is over. This is // where we need to start computing things from. nsIFrame* rowFrame; mTreeItem->FirstChild(mPresContext, nsnull, &rowFrame); NS_ASSERTION ( rowFrame, "couldn't get rowGroup's row frame" ); rowFrame->GetRect(*outRowRect); // compute the offset to top level in twips float t2p; mPresContext->GetTwipsToPixels(&t2p); PRInt32 frameOffsetX = 0, frameOffsetY = 0; nsIFrame* curr = rowFrame; 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 // subtract the offset from the mouse coord to put it into row relative coordinates. nsPoint pnt(xp, yp); pnt.MoveBy ( -frameOffsetX, -frameOffsetY ); // Find the tree's view and take it's scroll position into account nsIView *containingView = nsnull; nsPoint unusedOffset(0,0); mTreeItem->GetOffsetFromView(mPresContext, unusedOffset, &containingView); NS_ASSERTION(containingView, "No containing view!"); if ( !containingView ) return; nscoord viewOffsetToParentX = 0, viewOffsetToParentY = 0; containingView->GetPosition ( &viewOffsetToParentX, &viewOffsetToParentY ); pnt.MoveBy ( -viewOffsetToParentX, -viewOffsetToParentY ); if ( outCoords ) *outCoords = pnt; } // ConvertEventCoordsToRowCoords // // DragOver // // The mouse has moved over the tree while a drag is happening. We really just want to // "annotate" the tree item with the current drop location. We don't want to make any judgement // as this stage as to whether or not the drag should be accepted or draw any feedback. // nsresult nsTreeItemDragCapturer::DragOver(nsIDOMEvent* aDragEvent) { // if we are not the target of the event, bail. if ( ! IsEventTargetMyTreeItem(aDragEvent) ) return NS_OK; // Check to see if the drop feedback should be drawn above, below, or on // the row (if a container). nscoord yLoc = 0; PRBool onMe = PR_FALSE; PRBool beforeMe = PR_FALSE; ComputeDropPosition(aDragEvent, &yLoc, &beforeMe, &onMe); if ( yLoc != mCurrentDropLoc ) { // stash the new location in the treeItem's content. Note that the tree drawing code doesn't // care at all about "dd-droplocation", only the coordinate so there is no need to send the // AttributeChanged() about that attribute. nsCOMPtr content; mTreeItem->GetContent ( getter_AddRefs(content) ); if ( content ) { char buffer[10]; sprintf(buffer, "%d", yLoc); NS_NAMED_LITERAL_STRING(trueStr, "true"); NS_NAMED_LITERAL_STRING(falseStr, "false"); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropLocationCoord, NS_ConvertASCIItoUCS2(buffer), PR_TRUE ); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropLocation, beforeMe ? trueStr : falseStr, PR_FALSE ); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropOn, onMe ? trueStr : falseStr, PR_TRUE ); } // cache the current drop location mCurrentDropLoc = yLoc; } // NS_OK means event is NOT consumed. We want to make sure JS gets this so it // can determine if the drag is allowed. return NS_OK; } //////////////////////////////////////////////////////////////////////// nsresult nsTreeItemDragCapturer::DragExit(nsIDOMEvent* aDragEvent) { // if we are not the target of the event, bail. if ( !IsEventTargetMyTreeItem(aDragEvent) ) return NS_OK; nsCOMPtr content; mTreeItem->GetContent ( getter_AddRefs(content) ); NS_ASSERTION ( content, "can't get content node, we're in trouble" ); if ( content ) { // tell the treeItem to not do any more drop feedback. Note that the tree code doesn't // care at all about "dd-droplocation", only the coordinate so there is no need to send the // AttributeChanged() about that attribute. char buffer[10]; sprintf(buffer, "%d", kNoDropLoc); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropLocationCoord, NS_ConvertASCIItoUCS2(buffer), PR_TRUE ); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropLocation, NS_LITERAL_STRING("false"), PR_TRUE ); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddDropOn, NS_LITERAL_STRING("false"), PR_TRUE ); content->SetAttr ( kNameSpaceID_None, nsXULAtoms::ddTriggerRepaintRestore, NS_LITERAL_STRING("1"), PR_TRUE ); } // cache the current drop location mCurrentDropLoc = kNoDropLoc; return NS_OK; // don't consume event } //////////////////////////////////////////////////////////////////////// nsresult nsTreeItemDragCapturer::DragDrop(nsIDOMEvent* aMouseEvent) { // this code should all be in JS. return NS_OK; } // // IsEventTargetMyTreeItem // // Since the tree has many nested row groups we will definately see this event several // times on the way down, but we only care when it gets to the bottom of the chain. // PRBool nsTreeItemDragCapturer :: IsEventTargetMyTreeItem ( nsIDOMEvent* inEvent ) { NS_ASSERTION ( inEvent, "null drag event passed to me" ); nsresult retVal = PR_FALSE; // get dom node for our associated treeItem frame nsCOMPtr myContent; mTreeItem->GetContent ( getter_AddRefs(myContent) ); nsCOMPtr myDomNode ( do_QueryInterface(myContent) ); // get the treeItem associated with the target. Remember that the tree cell is the // actual target, so we have to go up two levels to get to its treeItem. nsCOMPtr target; nsCOMPtr targetCell; nsCOMPtr targetRow; nsCOMPtr targetTreeItem; inEvent->GetTarget ( getter_AddRefs(target) ); if ( target ) { targetCell = do_QueryInterface( target ); if ( targetCell ) { targetCell->GetParentNode(getter_AddRefs(targetRow)); if ( targetRow ) { targetRow->GetParentNode(getter_AddRefs(targetTreeItem)); // the critical comparison. are if ( myDomNode == targetTreeItem ) retVal = PR_TRUE; } } } return retVal; } // IsEventTargetMyTreeItem