/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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 Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Original Author: David W. Hyatt (hyatt@netscape.com) * * Contributor(s): */ #include "nsCOMPtr.h" #include "nsTreeFrame.h" #include "nsIStyleContext.h" #include "nsIContent.h" #include "nsCSSRendering.h" #include "nsTreeCellFrame.h" #include "nsTableColFrame.h" #include "nsTreeRowFrame.h" #include "nsCellMap.h" #include "nsIDOMXULTreeElement.h" #include "nsINameSpaceManager.h" #include "nsTreeRowGroupFrame.h" #include "nsXULAtoms.h" #include "nsHTMLAtoms.h" #include "nsIDOMNodeList.h" #include "nsIDOMXULTreeElement.h" #include "nsTreeTwistyListener.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIReflowCommand.h" #include "nsHTMLParts.h" #include "nsIDOMRange.h" #include "nsIComponentManager.h" #include "nsLayoutCID.h" #include "nsLayoutAtoms.h" // // NS_NewTreeFrame // // Creates a new tree frame // nsresult NS_NewTreeFrame (nsIPresShell* aPresShell, nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsTreeFrame* it = new (aPresShell) nsTreeFrame; if (!it) return NS_ERROR_OUT_OF_MEMORY; *aNewFrame = it; return NS_OK; } // NS_NewTreeFrame // Constructor nsTreeFrame::nsTreeFrame() :nsTableFrame(),mSlatedForReflow(PR_FALSE), mTwistyListener(nsnull), mGeneration(0), mUseGeneration(PR_TRUE), mFixedRows(-1), mReflowStopped(PR_FALSE) { } // Destructor nsTreeFrame::~nsTreeFrame() { } NS_IMPL_ADDREF_INHERITED(nsTreeFrame, nsTableFrame) NS_IMPL_RELEASE_INHERITED(nsTreeFrame, nsTableFrame) NS_IMETHODIMP nsTreeFrame::QueryInterface(REFNSIID aIID, void** aInstancePtr) { if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } *aInstancePtr = NULL; if (aIID.Equals(NS_GET_IID(nsISelfScrollingFrame))) { *aInstancePtr = (void*)(nsISelfScrollingFrame*) this; return NS_OK; } if (aIID.Equals(NS_GET_IID(nsITreeFrame))) { *aInstancePtr = (void*)(nsITreeFrame*) this; return NS_OK; } return nsTableFrame::QueryInterface(aIID, aInstancePtr); } // this is overriden because the behavior for a table is to force the layout // to auto layout if the table is auto width. This doesn't work for trees. PRBool nsTreeFrame::IsAutoLayout(const nsHTMLReflowState* aReflowState) { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); // a fixed table-layout table with an auto width is not considered as such // for purposes of requiring pass1 reflow and assigning a layout strategy if (NS_STYLE_TABLE_LAYOUT_FIXED == tableStyle->mLayoutStrategy) { return PR_FALSE; } return PR_TRUE; } void nsTreeFrame::SetSelection(nsIPresContext* aPresContext, nsTreeCellFrame* aFrame) { nsCOMPtr cellContent; aFrame->GetContent(getter_AddRefs(cellContent)); if (!cellContent) return; nsCOMPtr rowContent; cellContent->GetParent(*getter_AddRefs(rowContent)); if (!rowContent) return; nsCOMPtr itemContent; rowContent->GetParent(*getter_AddRefs(itemContent)); nsCOMPtr treeElement = do_QueryInterface(mContent); nsCOMPtr cellElement = do_QueryInterface(cellContent); nsCOMPtr itemElement = do_QueryInterface(itemContent); nsCOMPtr kSuppressSelectChange = dont_AddRef(NS_NewAtom("suppressonselect")); mContent->SetAttribute(kNameSpaceID_None, kSuppressSelectChange, "true", PR_FALSE); treeElement->SelectItem(itemElement); mContent->UnsetAttribute(kNameSpaceID_None, kSuppressSelectChange, PR_FALSE); treeElement->SelectCell(cellElement); } void nsTreeFrame::ToggleSelection(nsIPresContext* aPresContext, nsTreeCellFrame* aFrame) { nsCOMPtr cellContent; aFrame->GetContent(getter_AddRefs(cellContent)); nsCOMPtr rowContent; cellContent->GetParent(*getter_AddRefs(rowContent)); nsCOMPtr itemContent; rowContent->GetParent(*getter_AddRefs(itemContent)); nsCOMPtr treeElement = do_QueryInterface(mContent); nsCOMPtr cellElement = do_QueryInterface(cellContent); nsCOMPtr itemElement = do_QueryInterface(itemContent); nsCOMPtr kSuppressSelectChange = dont_AddRef(NS_NewAtom("suppressonselect")); mContent->SetAttribute(kNameSpaceID_None, kSuppressSelectChange, "true", PR_FALSE); treeElement->ToggleItemSelection(itemElement); mContent->UnsetAttribute(kNameSpaceID_None, kSuppressSelectChange, PR_FALSE); treeElement->ToggleCellSelection(cellElement); } void nsTreeFrame::RangedSelection(nsIPresContext* aPresContext, nsTreeCellFrame* aEndFrame) { nsCOMPtr endCellContent; aEndFrame->GetContent(getter_AddRefs(endCellContent)); if (!endCellContent) return; nsCOMPtr endRowContent; endCellContent->GetParent(*getter_AddRefs(endRowContent)); if (!endRowContent) return; nsCOMPtr endItemContent; endRowContent->GetParent(*getter_AddRefs(endItemContent)); if (!endItemContent) return; nsCOMPtr treeElement = do_QueryInterface(mContent); nsCOMPtr endElement = do_QueryInterface(endItemContent); treeElement->SelectItemRange(nsnull, endElement); } void nsTreeFrame::GetTreeBody(nsTreeRowGroupFrame** aResult) { nsIFrame* curr = mFrames.FirstChild(); while (curr) { nsCOMPtr content; curr->GetContent(getter_AddRefs(content)); if (content) { nsCOMPtr tag; content->GetTag(*getter_AddRefs(tag)); if (tag && tag.get() == nsXULAtoms::treechildren) { // This is our actual treechildren frame. nsTreeRowGroupFrame* rowGroup = (nsTreeRowGroupFrame*)curr; // XXX I am evil. *aResult = rowGroup; return; } } curr->GetNextSibling(&curr); } } NS_IMETHODIMP nsTreeFrame::HandleEvent(nsIPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus) { NS_ENSURE_ARG_POINTER(aEventStatus); *aEventStatus = nsEventStatus_eConsumeDoDefault; if (aEvent->message == NS_KEY_PRESS) { nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; PRUint32 keyCode = keyEvent->keyCode; if (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN || keyCode == NS_VK_LEFT || keyCode == NS_VK_RIGHT || keyCode == NS_VK_ENTER) { // Get our treechildren child frame. nsTreeRowGroupFrame* treeRowGroup = nsnull; GetTreeBody(&treeRowGroup); if (!treeRowGroup) return NS_OK; // No tree body. Just bail. nsCOMPtr treeElement = do_QueryInterface(mContent); nsCOMPtr itemNodeList; nsCOMPtr cellNodeList; treeElement->GetSelectedItems(getter_AddRefs(itemNodeList)); treeElement->GetSelectedCells(getter_AddRefs(cellNodeList)); PRUint32 itemLength; PRUint32 cellLength; itemNodeList->GetLength(&itemLength); cellNodeList->GetLength(&cellLength); PRInt32 rowIndex = -1; PRInt32 cellIndex = 0; if (cellLength != 0 && itemLength == 0) { nsCOMPtr node; cellNodeList->Item(0, getter_AddRefs(node)); nsCOMPtr content = do_QueryInterface(node); treeRowGroup->IndexOfCell(aPresContext, content, rowIndex, cellIndex); } else if (cellLength == 0 && itemLength != 0) { nsCOMPtr node; itemNodeList->Item(0, getter_AddRefs(node)); nsCOMPtr content = do_QueryInterface(node); nsTableRowFrame* firstRow=nsnull; treeRowGroup->GetFirstRowFrame(&firstRow); nsCOMPtr rowContent; if (firstRow) { firstRow->GetContent(getter_AddRefs(rowContent)); treeRowGroup->IndexOfRow(aPresContext, rowContent, rowIndex); } } else if (cellLength != 0 && itemLength != 0) { nsCOMPtr node; cellNodeList->Item(0, getter_AddRefs(node)); nsCOMPtr content = do_QueryInterface(node); treeRowGroup->IndexOfCell(aPresContext, content, rowIndex, cellIndex); } // We now have a valid row and cell index for the current selection. Based on the // direction, let's adjust the row and column index. if (rowIndex == -1) rowIndex = 0; else if (keyCode == NS_VK_DOWN) rowIndex++; else if (keyCode == NS_VK_UP) rowIndex--; // adjust for zero-based mRowCount nsTableRowFrame* firstRow=nsnull; treeRowGroup->GetFirstRowFrame(&firstRow); if (firstRow) { PRInt32 rowNumber = rowIndex - firstRow->GetRowIndex(); if (!treeRowGroup->IsValidRow(rowNumber)) return NS_OK; // Ensure that the required index is visible. treeRowGroup->EnsureRowIsVisible(rowNumber); } // Now that the row is scrolled into view, we have a frame created. We can retrieve the cell. nsTreeCellFrame* cellFrame=nsnull; treeRowGroup->GetCellFrameAtIndex(rowIndex, cellIndex, &cellFrame); if (!cellFrame) return NS_OK; // No cell. Whatever. Bail. // We got it! Perform the selection on an up/down. if (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) SetSelection(aPresContext, cellFrame); else if (keyCode == NS_VK_ENTER || keyCode == NS_VK_RETURN) cellFrame->ToggleOpenClose(); else if (keyCode == NS_VK_LEFT) cellFrame->Close(); else if (keyCode == NS_VK_RIGHT) cellFrame->Open(); } } return NS_OK; } void nsTreeFrame::MoveUp(nsIPresContext* aPresContext, nsTreeCellFrame* pFrame) { PRInt32 rowIndex; pFrame->GetRowIndex(rowIndex); PRInt32 colIndex; pFrame->GetColIndex(colIndex); if (rowIndex > 0) { MoveToRowCol(aPresContext, rowIndex-1, colIndex); } } void nsTreeFrame::MoveDown(nsIPresContext* aPresContext, nsTreeCellFrame* pFrame) { PRInt32 rowIndex; pFrame->GetRowIndex(rowIndex); PRInt32 colIndex; pFrame->GetColIndex(colIndex); PRInt32 totalRows = mCellMap->GetRowCount(); if (rowIndex < totalRows-1) { MoveToRowCol(aPresContext, rowIndex+1, colIndex); } } void nsTreeFrame::MoveLeft(nsIPresContext* aPresContext, nsTreeCellFrame* pFrame) { PRInt32 rowIndex; pFrame->GetRowIndex(rowIndex); PRInt32 colIndex; pFrame->GetColIndex(colIndex); if (colIndex > 0) { MoveToRowCol(aPresContext, rowIndex, colIndex-1); } } void nsTreeFrame::MoveRight(nsIPresContext* aPresContext, nsTreeCellFrame* aFrame) { PRInt32 rowIndex; aFrame->GetRowIndex(rowIndex); PRInt32 colIndex; aFrame->GetColIndex(colIndex); PRInt32 totalCols = mCellMap->GetColCount(); if (colIndex < totalCols-1) { MoveToRowCol(aPresContext, rowIndex, colIndex+1); } } void nsTreeFrame::MoveToRowCol(nsIPresContext* aPresContext, PRInt32 aRow, PRInt32 aCol) { nsTableCellFrame* cellFrame = mCellMap->GetCellInfoAt(aRow, aCol); // We now have the cell that should be selected. nsTreeCellFrame* treeCell = NS_STATIC_CAST(nsTreeCellFrame*, cellFrame); SetSelection(aPresContext, treeCell); } NS_IMETHODIMP nsTreeFrame::Destroy(nsIPresContext* aPresContext) { nsCOMPtr target = do_QueryInterface(mContent); target->RemoveEventListener("mousedown", mTwistyListener, PR_TRUE); mTwistyListener = nsnull; return nsTableFrame::Destroy(aPresContext); } NS_IMETHODIMP nsTreeFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { NS_ASSERTION(aReflowState.mComputedWidth != NS_UNCONSTRAINEDSIZE, "Reflowing tree with unconstrained width!!!!"); //NS_ASSERTION(aReflowState.mComputedHeight != NS_UNCONSTRAINEDSIZE, // "Reflowing tree with unconstrained height!!!!"); //printf("Tree Width: %d, Tree Height: %d\n", aReflowState.mComputedWidth, aReflowState.mComputedHeight); nsresult rv = NS_OK; mSlatedForReflow = PR_FALSE; nsRect rect; GetRect(rect); if (rect.width != aReflowState.mComputedWidth && aReflowState.reason == eReflowReason_Resize) { // We're doing a resize and changing the width of the table. All rows must // reflow. Reset our generation. SetUseGeneration(PR_FALSE); } if (UseGeneration()) { ++mGeneration; } rv = nsTableFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); if (aReflowState.mComputedWidth != NS_UNCONSTRAINEDSIZE) aDesiredSize.width = aReflowState.mComputedWidth + aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right; if (aReflowState.mComputedHeight != NS_UNCONSTRAINEDSIZE) aDesiredSize.height = aReflowState.mComputedHeight + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom; aDesiredSize.ascent = aDesiredSize.height; if (mFixedRows != -1) { PRInt32 totalRows = mCellMap->GetRowCount(); if (totalRows < mFixedRows) { if (totalRows == 0) aDesiredSize.height = 0 + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;; // Get a single cell and use it as a multiplicative factor. nsTableCellFrame* cellFrame = GetCellInfoAt(0, 0); nsRect rect; cellFrame->GetRect(rect); aDesiredSize.height = (mFixedRows*rect.height) + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;; } else { // Find out the total height of our frame children. nsIFrame* child = mFrames.FirstChild(); PRInt32 height = 0; while (child) { nsRect rect; child->GetRect(rect); height += rect.height; child->GetNextSibling(&child); } aDesiredSize.height = height + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom; } } if (!UseGeneration()) SetUseGeneration(PR_TRUE); return rv; } NS_IMETHODIMP nsTreeFrame::DidReflow(nsIPresContext* aPresContext, nsDidReflowStatus aStatus) { nsresult rv = nsTableFrame::DidReflow(aPresContext, aStatus); return rv; } NS_IMETHODIMP nsTreeFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { nsresult rv = nsTableFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); // Create the menu bar listener. mTwistyListener = new nsTreeTwistyListener(); nsCOMPtr target = do_QueryInterface(mContent); target->AddEventListener("mousedown", mTwistyListener, PR_TRUE); nsAutoString value; nsCOMPtr element = do_QueryInterface(mContent); element->GetAttribute("rows", value); if (!value.IsEmpty()) { PRInt32 dummy; PRInt32 count = value.ToInteger(&dummy); mFixedRows = count; } return rv; } PRBool nsTreeFrame::ContainsFlexibleColumn(PRInt32 aStartIndex, PRInt32 aEndIndex, nsTableColFrame** aResult) { for (PRInt32 i = aEndIndex; i >= aStartIndex; i--) { nsTableColFrame* result = GetColFrame(i); nsCOMPtr colContent; result->GetContent(getter_AddRefs(colContent)); nsCOMPtr fixedAtom = dont_AddRef(NS_NewAtom("fixed")); if (colContent) { nsAutoString fixedValue; colContent->GetAttribute(kNameSpaceID_None, fixedAtom, fixedValue); if (!fixedValue.Equals("true")) { // We are a proportional column. if (aResult) *aResult = result; return PR_TRUE; } } } return PR_FALSE; } PRInt32 nsTreeFrame::GetInsertionIndex(nsIFrame *aFrame) { nsIFrame *child = mFrames.FirstChild(); PRInt32 index=0; while (child) { if (child==aFrame) { return index; } const nsStyleDisplay *display; child->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (IsRowGroup(display->mDisplay)) { PRBool done=PR_FALSE; index = ((nsTreeRowGroupFrame*)child)->GetInsertionIndex(aFrame, index, done); if (done) return index; } child->GetNextSibling(&child); } return index; } NS_IMETHODIMP nsTreeFrame::ScrollByLines(nsIPresContext* aPresContext, PRInt32 lines) { // Get our treechildren child frame. nsTreeRowGroupFrame* treeRowGroup = nsnull; GetTreeBody(&treeRowGroup); if (!treeRowGroup) return NS_OK; // No tree body. Just bail. treeRowGroup->ScrollByLines(aPresContext, lines); return NS_OK; } NS_IMETHODIMP nsTreeFrame::ScrollByPages(nsIPresContext* aPresContext, PRInt32 pages) { PRInt32 lines; // Get our treechildren child frame. nsTreeRowGroupFrame* treeRowGroup = nsnull; GetTreeBody(&treeRowGroup); if (!treeRowGroup) return NS_OK; // No tree body. Just bail. PRInt32 absPages = (pages > 0) ? pages : -pages; PRInt32 treeRows; treeRowGroup->GetRowCount(treeRows); lines = (absPages * treeRows) - 1; if (pages < 0) lines = -lines; #ifdef DEBUG_bryner printf("nsTreeFrame::ScrollByPages : scrolling treeRowGroup by %d lines\n", lines); treeRowGroup->ScrollByLines(aPresContext, lines); #endif return NS_OK; } NS_IMETHODIMP nsTreeFrame::CollapseScrollbar(nsIPresContext* aPresContext, PRBool aHide) { // Get our treechildren child frame. nsTreeRowGroupFrame* treeRowGroup = nsnull; GetTreeBody(&treeRowGroup); if (!treeRowGroup) return NS_OK; // No tree body. Just bail. treeRowGroup->CollapseScrollbar(aHide, aPresContext, nsnull); return NS_OK; } NS_IMETHODIMP nsTreeFrame::EnsureRowIsVisible(PRInt32 aRowIndex) { // Get our treechildren child frame. nsTreeRowGroupFrame* treeRowGroup = nsnull; GetTreeBody(&treeRowGroup); if (!treeRowGroup) return NS_OK; treeRowGroup->EnsureRowIsVisible(aRowIndex); return NS_OK; }