Files
Mozilla/mozilla/layout/xul/base/src/nsXULTreeGroupFrame.cpp
hyatt%netscape.com f9a19ba7cd Fix for 112980, nsITheme API and dynamic skin switching re-enabling. r=ben, sr=waterson
git-svn-id: svn://10.0.0.236/trunk@110648 18797224-902f-48f8-a5cc-f745e15eee43
2001-12-17 22:51:39 +00:00

1074 lines
33 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape 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/NPL/
*
* 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 the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Original Author: David W. Hyatt (hyatt@netscape.com)
* Mike Pinkerton (pinkerton@netscape.com)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "nsXULTreeGroupFrame.h"
#include "nsCSSFrameConstructor.h"
#include "nsBoxLayoutState.h"
#include "nsISupportsArray.h"
#include "nsTreeItemDragCapturer.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMElement.h"
#include "nsIRenderingContext.h"
#include "nsIDeviceContext.h"
#include "nsXULAtoms.h"
#include "nsIViewManager.h"
#include "nsINameSpaceManager.h"
#include "nsXULTreeOuterGroupFrame.h"
#include "nsIDocument.h"
#include "nsIBindingManager.h"
#include "nsUnicharUtils.h"
//
// Prototypes
//
static void LocateIndentationFrame ( nsIPresContext* aPresContext, nsIFrame* aParentFrame,
nsIFrame** aResult ) ;
//
// NS_NewXULTreeGroupFrame
//
// Creates a new TreeGroup frame
//
nsresult
NS_NewXULTreeGroupFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRBool aIsRoot,
nsIBoxLayout* aLayoutManager)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsXULTreeGroupFrame* it = new (aPresShell) nsXULTreeGroupFrame(aPresShell, aIsRoot, aLayoutManager);
if (!it)
return NS_ERROR_OUT_OF_MEMORY;
*aNewFrame = it;
return NS_OK;
} // NS_NewXULTreeGroupFrame
NS_IMETHODIMP_(nsrefcnt)
nsXULTreeGroupFrame::AddRef(void)
{
return NS_OK;
}
NS_IMETHODIMP_(nsrefcnt)
nsXULTreeGroupFrame::Release(void)
{
return NS_OK;
}
//
// QueryInterface
//
NS_INTERFACE_MAP_BEGIN(nsXULTreeGroupFrame)
NS_INTERFACE_MAP_ENTRY(nsIXULTreeSlice)
NS_INTERFACE_MAP_END_INHERITING(nsBoxFrame)
// Constructor
nsXULTreeGroupFrame::nsXULTreeGroupFrame(nsIPresShell* aPresShell, PRBool aIsRoot, nsIBoxLayout* aLayoutManager)
: nsBoxFrame(aPresShell, aIsRoot, aLayoutManager),
mFrameConstructor(nsnull),
mPresContext(nsnull),
mOuterFrame(nsnull),
mAvailableHeight(10000),
mTopFrame(nsnull),
mBottomFrame(nsnull),
mLinkupFrame(nsnull),
mContentChain(nsnull),
mOnScreenRowCount(-1),
mYDropLoc(nsTreeItemDragCapturer::kNoDropLoc),
mDropOnContainer(PR_FALSE)
{}
// Destructor
nsXULTreeGroupFrame::~nsXULTreeGroupFrame()
{
nsCOMPtr<nsIContent> content;
GetContent(getter_AddRefs(content));
nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(content));
// NOTE: the last Remove will delete the drag capturer
if ( receiver ) {
receiver->RemoveEventListener(NS_LITERAL_STRING("dragover"), mDragCapturer, PR_TRUE);
receiver->RemoveEventListener(NS_LITERAL_STRING("dragexit"), mDragCapturer, PR_TRUE);
}
}
//
// Init
//
// Setup event capturers for drag and drop. 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 drag capturer 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.
//
NS_IMETHODIMP
nsXULTreeGroupFrame::Init ( nsIPresContext* aPresContext, nsIContent* aContent,
nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow )
{
nsresult retVal = nsBoxFrame::Init ( aPresContext, aContent, aParent, aContext, aPrevInFlow );
nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(aContent));
if ( NS_SUCCEEDED(retVal) && receiver ) {
// register our drag over and exit capturers. These annotate the content object
// with enough info to determine where the drop would happen so that JS down the
// line can do the right thing.
nsresult rv1, rv2;
mDragCapturer = new nsTreeItemDragCapturer(this, aPresContext);
rv1 = receiver->AddEventListener(NS_LITERAL_STRING("dragover"), mDragCapturer, PR_TRUE);
rv2 = receiver->AddEventListener(NS_LITERAL_STRING("dragexit"), mDragCapturer, PR_TRUE);
NS_ASSERTION ( NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2), "Couldn't hookup drag capturer on tree" );
}
// it's ok to ignore the failure of the event listeners. Nothing bad will come of it.
return retVal;
} // Init
NS_IMETHODIMP
nsXULTreeGroupFrame::Redraw(nsBoxLayoutState& aState,
const nsRect* aDamageRect,
PRBool aImmediate)
{
return nsBoxFrame::Redraw(aState, aDamageRect, aImmediate);
}
void nsXULTreeGroupFrame::LocateFrame(nsIFrame* aStartFrame, nsIFrame** aResult)
{
if (aStartFrame == nsnull)
*aResult = mFrames.FirstChild();
else aStartFrame->GetNextSibling(aResult);
}
NS_IMETHODIMP
nsXULTreeGroupFrame::NeedsRecalc()
{
mOnScreenRowCount = -1;
return nsBoxFrame::NeedsRecalc();
}
NS_IMETHODIMP
nsXULTreeGroupFrame::GetOnScreenRowCount(PRInt32* aCount)
{
if (mOnScreenRowCount == -1)
{
mOnScreenRowCount = 0;
nsIBox* box = nsnull;
GetChildBox(&box);
while(box) {
PRInt32 count = 0;
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(box));
if (slice) {
slice->GetOnScreenRowCount(&count);
mOnScreenRowCount += count;
}
box->GetNextBox(&box);
}
}
*aCount = mOnScreenRowCount;
return NS_OK;
}
nsIFrame*
nsXULTreeGroupFrame::GetFirstFrame()
{
LocateFrame(nsnull, &mTopFrame);
return mTopFrame;
}
nsIFrame*
nsXULTreeGroupFrame::GetNextFrame(nsIFrame* aPrevFrame)
{
nsIFrame* result;
LocateFrame(aPrevFrame, &result);
return result;
}
nsIFrame*
nsXULTreeGroupFrame::GetLastFrame()
{
return mFrames.LastChild();
}
nsIBox*
nsXULTreeGroupFrame::GetFirstTreeBox(PRBool* aCreated)
{
if (aCreated)
*aCreated = PR_FALSE;
// Clear ourselves out.
mLinkupFrame = nsnull;
mBottomFrame = mTopFrame;
// If we have a frame and no content chain (e.g., unresolved/uncreated content)
if (mTopFrame && !mContentChain) {
nsCOMPtr<nsIBox> box(do_QueryInterface(mTopFrame));
return box;
}
// See if we have any frame whatsoever.
LocateFrame(nsnull, &mTopFrame);
mBottomFrame = mTopFrame;
nsCOMPtr<nsIContent> startContent;
if (mContentChain) {
nsCOMPtr<nsISupports> supports;
mContentChain->GetElementAt(0, getter_AddRefs(supports));
nsCOMPtr<nsIContent> chainContent = do_QueryInterface(supports);
startContent = chainContent;
if (mTopFrame) {
// We have a content chain. If the top frame is the same as our content
// chain, we can go ahead and destroy our content chain and return the
// top frame.
nsCOMPtr<nsIContent> topContent;
mTopFrame->GetContent(getter_AddRefs(topContent));
if (chainContent.get() == topContent.get()) {
// The two content nodes are the same. Our content chain has
// been synched up, and we can now remove our element and
// pass the content chain inwards.
InitSubContentChain((nsXULTreeGroupFrame*)mTopFrame);
nsCOMPtr<nsIBox> box(do_QueryInterface(mTopFrame));
return box;
}
else mLinkupFrame = mTopFrame; // We have some frames that we'll eventually catch up with.
// Cache the pointer to the first of these frames, so
// we'll know it when we hit it.
}
}
else if (mTopFrame) {
nsCOMPtr<nsIBox> box(do_QueryInterface(mTopFrame));
return box;
}
// We don't have a top frame instantiated. Let's
// try to make one.
// If startContent is initialized, we have a content chain, and
// we're using that content node to make our frame.
// Otherwise we have nothing, and we should just try to grab the first child.
if (!startContent) {
nsCOMPtr<nsIContent> content(mContent);
nsCOMPtr<nsIContent> bindingParent;
mContent->GetBindingParent(getter_AddRefs(bindingParent));
if (bindingParent) {
nsCOMPtr<nsIDocument> doc;
bindingParent->GetDocument(*getter_AddRefs(doc));
nsCOMPtr<nsIBindingManager> bindingManager;
doc->GetBindingManager(getter_AddRefs(bindingManager));
nsCOMPtr<nsIAtom> tag;
PRInt32 namespaceID;
bindingManager->ResolveTag(bindingParent, &namespaceID, getter_AddRefs(tag));
if (tag.get() == nsXULAtoms::tree)
content = bindingParent;
}
PRInt32 childCount;
content->ChildCount(childCount);
nsCOMPtr<nsIContent> childContent;
if (childCount > 0) {
content->ChildAt(0, *getter_AddRefs(childContent));
startContent = childContent;
}
}
if (startContent) {
PRBool isAppend = (mLinkupFrame == nsnull);
mFrameConstructor->CreateTreeWidgetContent(mPresContext, this, nsnull, startContent,
&mTopFrame, isAppend, PR_FALSE,
nsnull);
if (aCreated)
*aCreated = PR_TRUE;
//if (mTopFrame)
// mOuterFrame->PostReflowCallback();
mBottomFrame = mTopFrame;
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(mTopFrame));
PRBool isGroup = PR_FALSE;
if (slice)
slice->IsGroupFrame(&isGroup);
if (isGroup && mContentChain) {
// We have just instantiated a row group, and we have a content chain. This
// means we need to potentially pass a sub-content chain to the instantiated
// frame, so that it can also sync up with its children.
InitSubContentChain((nsXULTreeGroupFrame*)mTopFrame);
}
SetContentChain(nsnull);
nsCOMPtr<nsIBox> box(do_QueryInterface(mTopFrame));
return box;
}
return nsnull;
}
nsIBox*
nsXULTreeGroupFrame::GetNextTreeBox(nsIBox* aBox, PRBool* aCreated)
{
if (aCreated)
*aCreated = PR_FALSE;
// We're ultra-cool. We build our frames on the fly.
nsIFrame* result;
nsIFrame* frame;
aBox->GetFrame(&frame);
LocateFrame(frame, &result);
if (result && (result == mLinkupFrame)) {
// We haven't really found a result. We've only found a result if
// the linkup frame is really the next frame following the
// previous frame.
nsCOMPtr<nsIContent> prevContent;
frame->GetContent(getter_AddRefs(prevContent));
nsCOMPtr<nsIContent> linkupContent;
mLinkupFrame->GetContent(getter_AddRefs(linkupContent));
PRInt32 i, j;
mContent->IndexOf(prevContent, i);
mContent->IndexOf(linkupContent, j);
if (i+1==j) {
// We have found a match and successfully linked back up with our
// old frame.
mBottomFrame = mLinkupFrame;
mLinkupFrame = nsnull;
nsCOMPtr<nsIBox> box(do_QueryInterface(result));
return box;
}
else result = nsnull; // No true linkup. We need to make a frame.
}
if (!result) {
// No result found. See if there's a content node that wants a frame.
PRInt32 i, childCount;
nsCOMPtr<nsIContent> prevContent;
frame->GetContent(getter_AddRefs(prevContent));
nsCOMPtr<nsIContent> parentContent;
prevContent->GetParent(*getter_AddRefs(parentContent));
parentContent->IndexOf(prevContent, i);
parentContent->ChildCount(childCount);
if (i+1 < childCount) {
// There is a content node that wants a frame.
nsCOMPtr<nsIContent> nextContent;
parentContent->ChildAt(i+1, *getter_AddRefs(nextContent));
nsIFrame* prevFrame = nsnull; // Default is to append
PRBool isAppend = PR_TRUE;
if (mLinkupFrame) {
// This will be an insertion, since we have frames on the end.
aBox->GetFrame(&prevFrame);
isAppend = PR_FALSE;
}
mFrameConstructor->CreateTreeWidgetContent(mPresContext, this, prevFrame, nextContent,
&result, isAppend, PR_FALSE,
nsnull);
if (aCreated)
*aCreated = PR_TRUE;
//if (result)
// mOuterFrame->PostReflowCallback();
}
}
mBottomFrame = result;
nsCOMPtr<nsIBox> box(do_QueryInterface(result));
return box;
}
NS_IMETHODIMP
nsXULTreeGroupFrame::TreeInsertFrames(nsIFrame* aPrevFrame, nsIFrame* aFrameList)
{
// insert the frames to our info list
nsBoxLayoutState state(mPresContext);
Insert(state, aPrevFrame, aFrameList);
mFrames.InsertFrames(nsnull, aPrevFrame, aFrameList);
MarkDirtyChildren(state);
return NS_OK;
}
NS_IMETHODIMP
nsXULTreeGroupFrame::TreeAppendFrames(nsIFrame* aFrameList)
{
// append them after
nsBoxLayoutState state(mPresContext);
Append(state,aFrameList);
mFrames.AppendFrames(nsnull, aFrameList);
MarkDirtyChildren(state);
return NS_OK;
}
void
nsXULTreeGroupFrame::OnContentInserted(nsIPresContext* aPresContext, nsIFrame* aNextSibling, PRInt32 aIndex)
{
nsIFrame* currFrame = aNextSibling;
// this will probably never happen -
// if we haven't created the topframe, it doesn't matter if
// content was inserted
if (mTopFrame == nsnull) return;
// if we're inserting the item before the first visible content,
// then ignore it because it will end up off-screen
// (except of course in the case of the first row, where we're
// actually adding visible content)
if(aNextSibling == mTopFrame) {
if (aIndex > 0) // We aren't at the front, so we have to be offscreen.
return; // Just bail.
nsCOMPtr<nsIContent> content;
aNextSibling->GetContent(getter_AddRefs(content));
PRInt32 siblingIndex;
mContent->IndexOf(content, siblingIndex);
if (siblingIndex == 1 && mOuterFrame->GetYPosition() == 0)
// We just inserted an item in front of the first of our children
// and we're at the top, such that we have to show the row.
// This item is our new visible top row.
mTopFrame = nsnull;
else
// The newly inserted row is offscreen. We can just bail.
return;
}
while (currFrame) {
nsIFrame* nextFrame;
currFrame->GetNextSibling(&nextFrame);
mFrameConstructor->RemoveMappingsForFrameSubtree(aPresContext, currFrame, nsnull);
nsBoxLayoutState state(aPresContext);
Remove(state, currFrame);
mFrames.DestroyFrame(aPresContext, currFrame);
currFrame = nextFrame;
}
nsBoxLayoutState state(aPresContext);
MarkDirtyChildren(state);
}
void nsXULTreeGroupFrame::OnContentRemoved(nsIPresContext* aPresContext,
nsIFrame* aChildFrame,
PRInt32 aIndex, PRInt32& aOnScreenRowCount)
{
if (!mFrameConstructor)
return;
// if we're removing the top row, the new top row is the next row
if (mTopFrame && mTopFrame == aChildFrame)
mTopFrame->GetNextSibling(&mTopFrame);
// Go ahead and delete the frame.
nsBoxLayoutState state(aPresContext);
if (aChildFrame) {
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(aChildFrame));
if (slice)
slice->GetOnScreenRowCount(&aOnScreenRowCount);
mFrameConstructor->RemoveMappingsForFrameSubtree(aPresContext, aChildFrame, nsnull);
Remove(state, aChildFrame);
mFrames.DestroyFrame(aPresContext, aChildFrame);
}
MarkDirtyChildren(state);
nsCOMPtr<nsIPresShell> shell;
aPresContext->GetShell(getter_AddRefs(shell));
shell->FlushPendingNotifications(PR_FALSE);
}
PRBool nsXULTreeGroupFrame::ContinueReflow(nscoord height)
{
if (height <= 0) {
nsIFrame* lastChild = GetLastFrame();
nsIFrame* startingPoint = mBottomFrame;
if (startingPoint == nsnull) {
// We just want to delete everything but the first item.
startingPoint = GetFirstFrame();
}
if (lastChild != startingPoint) {
// We have some hangers on (probably caused by shrinking the size of the window).
// Nuke them.
nsIFrame* currFrame;
startingPoint->GetNextSibling(&currFrame);
nsBoxLayoutState state(mPresContext);
while (currFrame) {
nsIFrame* nextFrame;
currFrame->GetNextSibling(&nextFrame);
mFrameConstructor->RemoveMappingsForFrameSubtree(mPresContext, currFrame, nsnull);
Remove(state, currFrame);
mFrames.DestroyFrame(mPresContext, currFrame);
currFrame = nextFrame;
}
MarkDirtyChildren(state);
}
return PR_FALSE;
}
else
return PR_TRUE;
}
void nsXULTreeGroupFrame::DestroyRows(PRInt32& aRowsToLose)
{
// We need to destroy frames until our row count has been properly
// reduced. A reflow will then pick up and create the new frames.
nsIFrame* childFrame = GetFirstFrame();
while (childFrame && aRowsToLose > 0) {
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(childFrame));
if (!slice) continue;
PRBool isRowGroup;
slice->IsGroupFrame(&isRowGroup);
if (isRowGroup)
{
nsXULTreeGroupFrame* childGroup = (nsXULTreeGroupFrame*)childFrame;
childGroup->DestroyRows(aRowsToLose);
if (aRowsToLose == 0) {
nsIBox* box = nsnull;
childGroup->GetChildBox(&box);
if (box) // Still have kids. Just return.
return;
}
}
else
{
// Lost a row.
aRowsToLose--;
}
nsIFrame* nextFrame = GetNextFrame(childFrame);
mFrameConstructor->RemoveMappingsForFrameSubtree(mPresContext, childFrame, nsnull);
nsBoxLayoutState state(mPresContext);
Remove(state, childFrame);
mFrames.DestroyFrame(mPresContext, childFrame);
MarkDirtyChildren(state);
mTopFrame = childFrame = nextFrame;
}
}
void nsXULTreeGroupFrame::ReverseDestroyRows(PRInt32& aRowsToLose)
{
// We need to destroy frames until our row count has been properly
// reduced. A reflow will then pick up and create the new frames.
nsIFrame* childFrame = GetLastFrame();
while (childFrame && aRowsToLose > 0) {
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(childFrame));
if (!slice) continue;
PRBool isRowGroup;
slice->IsGroupFrame(&isRowGroup);
if (isRowGroup)
{
nsXULTreeGroupFrame* childGroup = (nsXULTreeGroupFrame*)childFrame;
childGroup->ReverseDestroyRows(aRowsToLose);
if (aRowsToLose == 0) {
nsIBox* box = nsnull;
childGroup->GetChildBox(&box);
if (box) // Still have kids. Just return.
return;
}
}
else
{
// Lost a row.
aRowsToLose--;
}
nsIFrame* prevFrame;
prevFrame = mFrames.GetPrevSiblingFor(childFrame);
mFrameConstructor->RemoveMappingsForFrameSubtree(mPresContext, childFrame, nsnull);
nsBoxLayoutState state(mPresContext);
Remove(state, childFrame);
mFrames.DestroyFrame(mPresContext, childFrame);
MarkDirtyChildren(state);
mBottomFrame = childFrame = prevFrame;
}
}
void
nsXULTreeGroupFrame::GetFirstRowContent(nsIContent** aResult)
{
*aResult = nsnull;
nsIFrame* kid = GetFirstFrame();
while (kid) {
nsCOMPtr<nsIXULTreeSlice> slice(do_QueryInterface(kid));
if (!slice)
continue;
PRBool isRowGroup;
slice->IsGroupFrame(&isRowGroup);
if (isRowGroup) {
((nsXULTreeGroupFrame*)kid)->GetFirstRowContent(aResult);
if (*aResult)
return;
}
else {
kid->GetContent(aResult); // The ADDREF happens here.
return;
}
kid = GetNextFrame(kid);
}
}
void nsXULTreeGroupFrame::SetContentChain(nsISupportsArray* aContentChain)
{
NS_IF_RELEASE(mContentChain);
mContentChain = aContentChain;
NS_IF_ADDREF(mContentChain);
}
void nsXULTreeGroupFrame::InitSubContentChain(nsXULTreeGroupFrame* aRowGroupFrame)
{
if (mContentChain) {
mContentChain->RemoveElementAt(0);
PRUint32 chainSize;
mContentChain->Count(&chainSize);
if (chainSize > 0 && aRowGroupFrame) {
aRowGroupFrame->SetContentChain(mContentChain);
}
// The chain is dead. Long live the chain.
SetContentChain(nsnull);
}
}
#ifdef XP_MAC
#pragma mark -
#endif
//
// [ Drag And Drop Methods ]
//
// These will hopefully all go away (and their 20K of extra code) when we convert over
// to using the style system to do drop feedback.
//
//
// ForceDrawFrame
//
// Force this frame to drop and give me twenty.
// NOTE: try to consolodate with the version in the toolbar code.
//
void
nsXULTreeGroupFrame :: ForceDrawFrame(nsIPresContext* aPresContext, nsIFrame * aFrame)
{
if (aFrame == nsnull) {
return;
}
nsRect rect;
nsIView * view;
nsPoint pnt;
aFrame->GetOffsetFromView(aPresContext, pnt, &view);
aFrame->GetRect(rect);
rect.x = pnt.x;
rect.y = pnt.y;
if (view) {
nsCOMPtr<nsIViewManager> viewMgr;
view->GetViewManager(*getter_AddRefs(viewMgr));
if (viewMgr)
viewMgr->UpdateView(view, rect, NS_VMREFRESH_IMMEDIATE);
}
} // ForceDrawFrame
//
// AttributeChanged
//
// Track several attributes set by the d&d drop feedback tracking mechanism. The first
// is the "dd-triggerrepaint" attribute so JS can trigger a repaint when it
// needs up update the drop feedback. The second is the x (or y, if bar is vertical)
// coordinate of where the drop feedback bar should be drawn.
//
NS_IMETHODIMP
nsXULTreeGroupFrame::AttributeChanged(nsIPresContext* aPresContext, nsIContent* aChild,
PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRInt32 aModType, PRInt32 aHint)
{
nsresult rv = NS_OK;
PRInt32 ignore;
if ( aAttribute == nsXULAtoms::ddTriggerRepaint )
ForceDrawFrame ( aPresContext, this );
else if ( aAttribute == nsXULAtoms::ddTriggerRepaintRestore ) {
// Repaint the entire tree with no special attributes set.
ForceDrawFrame ( aPresContext, NS_REINTERPRET_CAST(nsIFrame*, mOuterFrame) );
}
else if ( aAttribute == nsXULAtoms::ddDropLocationCoord ) {
nsAutoString attribute;
aChild->GetAttr ( kNameSpaceID_None, aAttribute, attribute );
mYDropLoc = attribute.ToInteger(&ignore);
}
else if ( aAttribute == nsXULAtoms::ddDropOn ) {
nsAutoString attribute;
aChild->GetAttr ( kNameSpaceID_None, aAttribute, attribute );
ToLowerCase(attribute);
mDropOnContainer = attribute.Equals(NS_LITERAL_STRING("true"));
}
else
rv = nsBoxFrame::AttributeChanged ( aPresContext, aChild, aNameSpaceID, aAttribute, aModType, aHint );
return rv;
} // AttributeChanged
//
// Paint
//
// Used to draw the drop feedback based on attributes set by the drag event capturer
//
NS_IMETHODIMP
nsXULTreeGroupFrame :: Paint ( nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer, PRUint32 aFlags)
{
nsresult res = NS_OK;
res = nsBoxFrame::Paint ( aPresContext, aRenderingContext, aDirtyRect, aWhichLayer );
if ( (aWhichLayer == eFramePaintLayer_Content) &&
(mYDropLoc != nsTreeItemDragCapturer::kNoDropLoc || mDropOnContainer) )
PaintDropFeedback ( aPresContext, aRenderingContext, PR_FALSE );
return res;
} // Paint
//
// PaintDropFeedback
//
// Depending on the various flags, dispatch to the appropriate method for drawing
// drop feedback.
//
void
nsXULTreeGroupFrame :: PaintDropFeedback ( nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext,
PRBool aPaintSorted )
{
// lookup the drop marker color. default to black if not found.
nscolor color = GetColorFromStyleContext ( aPresContext, nsXULAtoms::ddDropMarker, NS_RGB(0,0,0) );
// find the twips-to-pixels conversion. We have decided not to cache this for
// space reasons.
float p2t = 20.0;
nsCOMPtr<nsIDeviceContext> dc;
aRenderingContext.GetDeviceContext ( *getter_AddRefs(dc) );
if ( dc )
dc->GetDevUnitsToTwips ( p2t );
if ( aPaintSorted )
PaintSortedDropFeedback ( color, aRenderingContext, p2t );
else if ( !mOuterFrame->IsTreeSorted() ) {
// draw different drop feedback depending on if we are dropping on the
// container or above/below it
if ( !mDropOnContainer )
PaintInBetweenDropFeedback ( color, aRenderingContext, aPresContext, p2t );
else
PaintOnContainerDropFeedback ( color, aRenderingContext, aPresContext, p2t );
} // else tree not sorted
} // PaintDropFeedback
//
// PaintSortedDropFeedback
//
// Draws the drop feedback for when the tree is sorted, so line-item drop feedback is
// not appliable.
//
void
nsXULTreeGroupFrame :: PaintSortedDropFeedback ( nscolor inColor, nsIRenderingContext& inRenderingContext,
float & inPixelsToTwips )
{
// two pixels wide
const PRInt32 borderWidth = NSToIntRound(2 * inPixelsToTwips);
nsRect top ( 0, 0, mRect.width, borderWidth );
nsRect left ( 0, 0, borderWidth, mRect.height );
nsRect right ( mRect.width - borderWidth, 0, borderWidth, mRect.height );
nsRect bottom ( 0, mRect.height - borderWidth, mRect.width, borderWidth );
inRenderingContext.SetColor(inColor);
inRenderingContext.FillRect ( top );
inRenderingContext.FillRect ( left );
inRenderingContext.FillRect ( bottom );
inRenderingContext.FillRect ( right );
} // PaintSortedDropFeedback
//
// PaintOnContainerDropFeedback
//
// Draws the drop feedback for when the row is a container and it is open. We let
// CSS handle the operation of painting the row bg so it's skinnable.
//
void
nsXULTreeGroupFrame :: PaintOnContainerDropFeedback ( nscolor inColor, nsIRenderingContext& inRenderingContext,
nsIPresContext* inPresContext, float & inPixelsToTwips )
{
if ( IsOpenContainer() ) {
PRInt32 horizIndent = 0;
nsIFrame* firstChild = nsnull;
FindFirstChildTreeItemFrame ( inPresContext, &firstChild );
if ( firstChild )
horizIndent = FindIndentation(inPresContext, firstChild);
inRenderingContext.SetColor(inColor);
nsRect dividingLine ( horizIndent, mRect.height - NSToIntRound(2 * inPixelsToTwips),
NSToIntRound(50 * inPixelsToTwips), NSToIntRound(2 * inPixelsToTwips) );
inRenderingContext.DrawRect ( dividingLine );
}
} // PaintOnContainerDropFeedback
//
// PaintInBetweenDropFeedback
//
// Draw the feedback for when the drop is to go in between two nodes, but only if the tree
// allows that.
//
void
nsXULTreeGroupFrame :: PaintInBetweenDropFeedback ( nscolor inColor, nsIRenderingContext& inRenderingContext,
nsIPresContext* inPresContext, float & inPixelsToTwips )
{
if ( mOuterFrame->CanDropBetweenRows() ) {
// the normal case is that we can just look at this frame to find the indentation we need. However,
// when we're an _open container_ and are being asked to draw the line _after_, we need to use the
// indentation of our first child instead. ick.
PRInt32 horizIndent = 0;
if ( IsOpenContainer() && mYDropLoc > 0 ) {
nsIFrame* firstChild = nsnull;
FindFirstChildTreeItemFrame ( inPresContext, &firstChild );
if ( firstChild )
horizIndent = FindIndentation(inPresContext, firstChild);
} // if open container and drop after
else
horizIndent = FindIndentation(inPresContext, this);
inRenderingContext.SetColor(inColor);
nsRect dividingLine ( horizIndent, mYDropLoc,
NSToIntRound(50 * inPixelsToTwips), NSToIntRound(2 * inPixelsToTwips) );
inRenderingContext.FillRect(dividingLine);
}
} // PaintInBetweenDropFeedback
//
// FindIndentation
//
// Compute horizontal offset for dividing line by finding a treeindentation tag
// and using its right coordinate.
//
// NOTE: We assume this indentation tag is in the first column.
// NOTE: We aren't caching this value because of space reasons....
//
PRInt32
nsXULTreeGroupFrame :: FindIndentation ( nsIPresContext* inPresContext, nsIFrame* inStartFrame ) const
{
PRInt32 indentInTwips = 0;
if ( !inStartFrame ) return 0;
nsIFrame* treeRowFrame;
inStartFrame->FirstChild ( inPresContext, nsnull, &treeRowFrame );
if ( !treeRowFrame ) return 0;
nsIFrame* treeCellFrame;
treeRowFrame->FirstChild ( inPresContext, nsnull, &treeCellFrame );
if ( !treeCellFrame ) return 0;
nsIFrame* treeIndentFrame = nsnull;
LocateIndentationFrame ( inPresContext, treeCellFrame, &treeIndentFrame );
if ( treeIndentFrame ) {
nsRect treeIndentBounds;
treeIndentFrame->GetRect ( treeIndentBounds );
indentInTwips = treeIndentBounds.x + treeIndentBounds.width;
}
return indentInTwips;
} // FindIndentation
//
// LocateIndentationFrame
//
// Recursively locate the <treeindentation> tag
//
void
LocateIndentationFrame ( nsIPresContext* aPresContext, nsIFrame* aParentFrame,
nsIFrame** aResult)
{
// Check ourselves.
*aResult = nsnull;
nsCOMPtr<nsIContent> content;
aParentFrame->GetContent(getter_AddRefs(content));
nsCOMPtr<nsIAtom> tagName;
content->GetTag ( *getter_AddRefs(tagName) );
if ( tagName.get() == nsXULAtoms::treeindentation ) {
*aResult = aParentFrame;
return;
}
// Check our kids.
nsIFrame* currFrame;
aParentFrame->FirstChild(aPresContext, nsnull, &currFrame);
while (currFrame) {
LocateIndentationFrame(aPresContext, currFrame, aResult);
if (*aResult)
return;
currFrame->GetNextSibling(&currFrame);
}
}
//
// FindFirstChildTreeItemFrame
//
// Locates the first <treeitem> in our child list. Assumes that we are a container and that
// the children are visible.
//
void
nsXULTreeGroupFrame :: FindFirstChildTreeItemFrame ( nsIPresContext* inPresContext,
nsIFrame** outChild ) const
{
*outChild = nsnull;
// first find the <treechildren> tag in our child list.
nsIFrame* currChildFrame = nsnull;
FirstChild ( inPresContext, nsnull, &currChildFrame );
while ( currChildFrame ) {
nsCOMPtr<nsIContent> content;
currChildFrame->GetContent ( getter_AddRefs(content) );
nsCOMPtr<nsIAtom> tagName;
content->GetTag ( *getter_AddRefs(tagName) );
if ( tagName.get() == nsXULAtoms::treechildren )
break;
currChildFrame->GetNextSibling ( &currChildFrame );
} // foreach child of the treeItem
//NS_ASSERTION ( currChildFrame, "Can't find <treechildren>" );
// |currChildFrame| now holds the correct frame if we found it
if ( currChildFrame )
currChildFrame->FirstChild ( inPresContext, nsnull, outChild );
} // FindFirstChildTreeItemFrame
//
// IsOpenContainer
//
// Determine if a node is both a container and open
//
PRBool
nsXULTreeGroupFrame :: IsOpenContainer ( ) const
{
PRBool isOpenContainer = PR_FALSE;
nsCOMPtr<nsIDOMElement> me ( do_QueryInterface(mContent) );
if ( me ) {
nsAutoString isContainer, isOpen;
me->GetAttribute(NS_LITERAL_STRING("container"), isContainer);
me->GetAttribute(NS_LITERAL_STRING("open"), isOpen);
isOpenContainer = (isContainer.Equals(NS_LITERAL_STRING("true")) &&
isOpen.Equals(NS_LITERAL_STRING("true")));
}
return isOpenContainer;
} // IsOpenContainer
//
// GetColorFromStyleContext
//
// A little helper to root out a color from the current style context. Returns
// the given default color if we can't find it, for whatever reason.
//
nscolor
nsXULTreeGroupFrame :: GetColorFromStyleContext ( nsIPresContext* inPresContext, nsIAtom* inAtom,
nscolor inDefaultColor )
{
nscolor retColor = inDefaultColor;
// go looking for the psuedo-style. We have decided not to cache this for space reasons.
nsCOMPtr<nsIStyleContext> markerStyle;
inPresContext->ProbePseudoStyleContextFor(mContent, inAtom, mStyleContext,
PR_FALSE, getter_AddRefs(markerStyle));
// dig out the color we want.
if ( markerStyle ) {
const nsStyleColor* styleColor =
NS_STATIC_CAST(const nsStyleColor*, markerStyle->GetStyleData(eStyleStruct_Color));
retColor = styleColor->mColor;
}
return retColor;
} // GetColorFromStyleContext