diff --git a/mozilla/editor/base/TypeInState.cpp b/mozilla/editor/base/TypeInState.cpp index e6518bed252..1a8b1a1a5eb 100644 --- a/mozilla/editor/base/TypeInState.cpp +++ b/mozilla/editor/base/TypeInState.cpp @@ -56,9 +56,3 @@ NS_IMETHODIMP TypeInState::NotifySelectionChanged() Reset(); return NS_OK; }; - -NS_IMETHODIMP TypeInState::TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset) -{ - //stub - return NS_OK; -} diff --git a/mozilla/editor/base/TypeInState.h b/mozilla/editor/base/TypeInState.h index 30bf98d2731..efa8dc2df1c 100644 --- a/mozilla/editor/base/TypeInState.h +++ b/mozilla/editor/base/TypeInState.h @@ -38,7 +38,6 @@ public: virtual ~TypeInState(); NS_IMETHOD NotifySelectionChanged(); - NS_IMETHOD TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset); void GetEnumForName(nsIAtom *aPropName, PRUint32 &aEnum); void GetPropertyIsString(PRUint32 aProp, PRUint32 &aIsString); diff --git a/mozilla/editor/base/nsEditorShell.cpp b/mozilla/editor/base/nsEditorShell.cpp index 7f9afac5655..e3a85002a7a 100644 --- a/mozilla/editor/base/nsEditorShell.cpp +++ b/mozilla/editor/base/nsEditorShell.cpp @@ -211,7 +211,7 @@ nsEditorShell::nsEditorShell() nsEditorShell::~nsEditorShell() { NS_IF_RELEASE(mStateMaintainer); - + // the only other references we hold are in nsCOMPtrs, so they'll take // care of themselves. } @@ -302,7 +302,6 @@ nsEditorShell::PrepareDocumentForEditing(nsIURI *aUrl) NS_IF_RELEASE(mStateMaintainer); } } - mEditorType = eUninitializedEditorType; mEditor = 0; // clear out the nsCOMPtr @@ -365,8 +364,8 @@ nsEditorShell::PrepareDocumentForEditing(nsIURI *aUrl) { txnMgr->AddListener(NS_STATIC_CAST(nsITransactionListener*, mStateMaintainer)); } - - + + if (NS_SUCCEEDED(rv) && mContentWindow) { nsCOMPtr controller; @@ -3149,6 +3148,26 @@ nsEditorShell::SelectTableCell() return result; } +NS_IMETHODIMP +nsEditorShell::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) +{ + nsresult result = NS_NOINTERFACE; + switch (mEditorType) + { + case eHTMLTextEditorType: + { + nsCOMPtr tableEditor = do_QueryInterface(mEditor); + if (tableEditor) + result = tableEditor->SelectBlockOfCells(aStartCell, aEndCell); + } + break; + default: + result = NS_ERROR_NOT_IMPLEMENTED; + } + return result; +} + + NS_IMETHODIMP nsEditorShell::SelectTableRow() { diff --git a/mozilla/editor/base/nsEditorShell.h b/mozilla/editor/base/nsEditorShell.h index f7d8c412683..6bec5161f67 100644 --- a/mozilla/editor/base/nsEditorShell.h +++ b/mozilla/editor/base/nsEditorShell.h @@ -138,7 +138,7 @@ class nsEditorShell : public nsIEditorShell, nsCOMPtr mSearchContext; // context used for search and replace. Owned by the appshell. - nsInterfaceState* mStateMaintainer; // we hold the owning ref to this. + nsInterfaceState* mStateMaintainer; // we hold the owning ref to this. PRInt32 mWrapColumn; // can't actually set this 'til the editor is created, so we may have to hold on to it for a while diff --git a/mozilla/editor/base/nsHTMLEditor.cpp b/mozilla/editor/base/nsHTMLEditor.cpp index 4a68cb01bbc..4044b759d7d 100644 --- a/mozilla/editor/base/nsHTMLEditor.cpp +++ b/mozilla/editor/base/nsHTMLEditor.cpp @@ -217,6 +217,7 @@ nsHTMLEditor::nsHTMLEditor() , mRules(nsnull) , mIsComposing(PR_FALSE) , mMaxTextLength(-1) +, mSelectingTableCells(PR_FALSE) { // Done in nsEditor // NS_INIT_REFCNT(); diff --git a/mozilla/editor/base/nsHTMLEditor.h b/mozilla/editor/base/nsHTMLEditor.h index e1183b54776..e1c635a80cb 100644 --- a/mozilla/editor/base/nsHTMLEditor.h +++ b/mozilla/editor/base/nsHTMLEditor.h @@ -184,6 +184,7 @@ public: NS_IMETHOD DeleteTableColumn(PRInt32 aNumber); NS_IMETHOD DeleteTableRow(PRInt32 aNumber); NS_IMETHOD SelectTableCell(); + NS_IMETHOD SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell); NS_IMETHOD SelectTableRow(); NS_IMETHOD SelectTableColumn(); NS_IMETHOD SelectTable(); @@ -363,6 +364,8 @@ protected: // failed to set selection to some other content in the document NS_IMETHOD SetSelectionAtDocumentStart(nsIDOMSelection *aSelection); + // end of table editing utilities + NS_IMETHOD ReParentContentOfNode(nsIDOMNode *aNode, nsString &aParentTag, BlockTransformationType aTranformation); @@ -603,6 +606,14 @@ protected: PRBool mCachedUnderlineStyle; nsString mCachedFontName; + // True when selection consists of table cell(s) + PRBool mSelectingTableCells; + + // Used to monitor block of cells selected + // by dragging mouse across the table + nsCOMPtr mStartSelectedCell; + nsCOMPtr mCurrentSelectedCell; + public: static nsIAtom *gTypingTxnName; static nsIAtom *gIMETxnName; diff --git a/mozilla/editor/base/nsInterfaceState.cpp b/mozilla/editor/base/nsInterfaceState.cpp index e517a8bb04d..0cebfe08771 100644 --- a/mozilla/editor/base/nsInterfaceState.cpp +++ b/mozilla/editor/base/nsInterfaceState.cpp @@ -90,13 +90,6 @@ nsInterfaceState::NotifyDocumentCreated() return NS_OK; } -NS_IMETHODIMP -nsInterfaceState::TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset) -{ - //stub - return NS_OK; -} - NS_IMETHODIMP nsInterfaceState::NotifyDocumentWillBeDestroyed() { @@ -335,25 +328,89 @@ nsInterfaceState::SelectionIsCollapsed() nsresult nsInterfaceState::UpdateParagraphState(const char* observerName, const char* attributeName, nsString& ioParaFormat) { - nsStringArray tagList; - - mEditor->GetParagraphTags(&tagList); + nsCOMPtr domSelection; + nsCOMPtr editor = do_QueryInterface(mEditor); + // Get the nsIEditor pointer (mEditor is nsIHTMLEditor) + if (!editor) return NS_ERROR_NULL_POINTER; + nsresult rv = editor->GetSelection(getter_AddRefs(domSelection)); + if (NS_FAILED(rv)) return rv; - PRInt32 numTags = tagList.Count(); - nsAutoString thisTag; - //Note: If numTags == 0, we probably have a text node not in a container - // (directly under ). Consider it normal - if (numTags > 0) + PRBool selectionCollapsed = PR_FALSE; + rv = domSelection->GetIsCollapsed(&selectionCollapsed); + if (NS_FAILED(rv)) return rv; + + // Get anchor and focus nodes: + nsCOMPtr anchorNode; + rv = domSelection->GetAnchorNode(getter_AddRefs(anchorNode)); + if (NS_FAILED(rv)) return rv; + if (!anchorNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr focusNode; + rv = domSelection->GetFocusNode(getter_AddRefs(focusNode)); + if (NS_FAILED(rv)) return rv; + if (!focusNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr anchorNodeBlockParent; + PRBool isBlock; + rv = editor->NodeIsBlock(anchorNode, isBlock); + if (NS_FAILED(rv)) return rv; + + if (isBlock) { - // This will never show the "mixed state" - // TODO: Scan list of tags and if any are different, set to "mixed" - tagList.StringAt(0, thisTag); + anchorNodeBlockParent = anchorNode; + } + else + { + // Get block parent of anchorNode node + // We could simply use this if it was in nsIEditor interface! + //rv = editor->GetBlockParent(anchorNode, getter_AddRefs(anchorNodeBlockParent)); + // if (NS_FAILED(rv)) return rv; + + nsCOMPtrparent; + nsCOMPtrtemp; + rv = anchorNode->GetParentNode(getter_AddRefs(parent)); + while (NS_SUCCEEDED(rv) && parent) + { + rv = editor->NodeIsBlock(parent, isBlock); + if (NS_FAILED(rv)) return rv; + if (isBlock) + { + anchorNodeBlockParent = parent; + break; + } + rv = parent->GetParentNode(getter_AddRefs(temp)); + parent = do_QueryInterface(temp); + } } - if (thisTag != mParagraphFormat) + if (!anchorNodeBlockParent) return NS_ERROR_NULL_POINTER; + + nsAutoString tagName; + + // Check if we have a selection that extends into multiple nodes, + // so we can check for "mixed" selection state + if (selectionCollapsed || focusNode == anchorNode) { - nsresult rv = SetNodeAttribute(observerName, attributeName, thisTag); + // Entire selection is within one block + anchorNodeBlockParent->GetNodeName(tagName); + } + else + { + // We may have different block parent node types WITHIN the selection, + // even if the anchor and focus parents are the same type. + // Getting the list of all block, e.g., by using GetParagraphTags(&tagList) + // is too inefficient for long documents. + // TODO: Change to use selection iterator to detect each block node in the + // selection and if different from the anchorNodeBlockParent, use "mixed" state + // *** Not doing this now reduces risk for Beta1 -- simply assume mixed state + // Note that "mixed" displays as "normal" in UI as of 3/6. + tagName = "mixed"; + } + + if (tagName != mParagraphFormat) + { + rv = SetNodeAttribute(observerName, attributeName, tagName); if (NS_FAILED(rv)) return rv; - mParagraphFormat = thisTag; + mParagraphFormat = tagName; } return NS_OK; } diff --git a/mozilla/editor/base/nsInterfaceState.h b/mozilla/editor/base/nsInterfaceState.h index 24aed7b5899..55e449d88c0 100644 --- a/mozilla/editor/base/nsInterfaceState.h +++ b/mozilla/editor/base/nsInterfaceState.h @@ -58,7 +58,6 @@ public: // nsIDOMSelectionListener interface NS_IMETHOD NotifySelectionChanged(); - NS_IMETHOD TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset); NS_DECL_NSIDOCUMENTSTATELISTENER diff --git a/mozilla/editor/base/nsTableEditor.cpp b/mozilla/editor/base/nsTableEditor.cpp index 923dec7cc22..07ced375fa8 100644 --- a/mozilla/editor/base/nsTableEditor.cpp +++ b/mozilla/editor/base/nsTableEditor.cpp @@ -43,8 +43,11 @@ #include "nsEditorUtils.h" +//#define DEBUG_TABLE 1 + static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); + /*************************************************************************** * stack based helper class for restoring selection after table edit */ @@ -75,6 +78,22 @@ class nsSetCaretAfterTableEdit void CancelSetCaret() {mEd = nsnull; mTable = nsnull;} }; +// Stack-class to turn on/off selection batching for table selection +class nsSelectionBatcher +{ +private: + nsCOMPtr mSelection; +public: + nsSelectionBatcher(nsIDOMSelection *aSelection) : mSelection(aSelection) + { + if (mSelection) mSelection->StartBatchChanges(); + } + virtual ~nsSelectionBatcher() + { + if (mSelection) mSelection->EndBatchChanges(); + } +}; + // Table Editing helper utilities (not exposed in IDL) NS_IMETHODIMP @@ -319,7 +338,7 @@ nsHTMLEditor::GetNextRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow) } return NS_OK; } -#ifdef DEBUG +#ifdef DEBUG_cmanske printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n"); #endif // We arrive here only if a table section has no children @@ -963,8 +982,7 @@ NS_IMETHODIMP nsHTMLEditor::SelectTableCell() { nsCOMPtr cell; - nsresult res = NS_ERROR_FAILURE; - res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); if (NS_FAILED(res)) return res; // Don't fail if we didn't find a table if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; @@ -979,24 +997,280 @@ nsHTMLEditor::SelectTableCell() return res; } +NS_IMETHODIMP +nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) +{ + if (!aStartCell || !aEndCell) return NS_ERROR_NULL_POINTER; + + nsCOMPtr selection; + nsresult res = nsEditor::GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_FAILURE; + + nsCOMPtr table; + res = GetElementOrParentByTagName("table", aStartCell, getter_AddRefs(table)); + if (NS_FAILED(res)) return res; + if (!table) return NS_ERROR_FAILURE; + + nsCOMPtr endTable; + res = GetElementOrParentByTagName("table", aEndCell, getter_AddRefs(endTable)); + if (NS_FAILED(res)) return res; + if (!endTable) return NS_ERROR_FAILURE; + + // We can only select a block if within the same table, + // so do nothing if not within one table + if (table != endTable) return NS_OK; + + PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex; + + // Get starting and ending cells' location in the cellmap + res = GetCellIndexes(aStartCell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + + res = GetCellIndexes(aEndCell, endRowIndex, endColIndex); + if(NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + selection->ClearSelection(); + nsCOMPtr cellNode; + PRBool cellSelected = PR_FALSE; + + PRInt32 startColumn = PR_MIN(startColIndex, endColIndex); + PRInt32 startRow = PR_MIN(startRowIndex, endRowIndex); + PRInt32 maxColumn = PR_MAX(startColIndex, endColIndex); + PRInt32 maxRow = PR_MAX(startRowIndex, endRowIndex); + + nsCOMPtr cell; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for (PRInt32 row = startRow; row <= maxRow; row++) + { + for(PRInt32 col = startColumn; col <= maxColumn; col += actualColSpan) + { + res = GetCellDataAt(table, row, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous locations + if (cell && row == currentRowIndex && col == currentColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(aStartCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } + return res; +} + NS_IMETHODIMP nsHTMLEditor::SelectAllTableCells() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + nsCOMPtr startCell = cell; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 row = 0; row < rowCount; row++) + { + for(PRInt32 col = 0; col < colCount; col += actualColSpan) + { + res = GetCellDataAt(table, row, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && row == currentRowIndex && col == currentColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } NS_IMETHODIMP nsHTMLEditor::SelectTableRow() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + nsCOMPtr startCell = cell; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + //Note: At this point, we could get first and last cells in row, + // then call SelectBlockOfCells, but that would take just + // a little less code, so the following is more efficient + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same row as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 col = 0; col < colCount; col += actualColSpan) + { + res = GetCellDataAt(table, startRowIndex, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == startRowIndex && currentColIndex == startColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } NS_IMETHODIMP nsHTMLEditor::SelectTableColumn() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + nsCOMPtr startCell = cell; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 row = 0; row < rowCount; row += actualRowSpan) + { + res = GetCellDataAt(table, row, startColIndex, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == startRowIndex && currentColIndex == startColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } @@ -1217,22 +1491,16 @@ nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell, PRInt32 &aRowIndex, PRInt32 & return NS_ERROR_FAILURE; } - res = NS_ERROR_FAILURE; // we return an error unless we get the index nsISupports *layoutObject=nsnull; // frames are not ref counted, so don't use an nsCOMPtr - res = nsHTMLEditor::GetLayoutObject(aCell, &layoutObject); + if (NS_FAILED(res)) return res; + if (!layoutObject) return NS_ERROR_FAILURE; - if ((NS_SUCCEEDED(res)) && (nsnull!=layoutObject)) - { // get the table cell interface from the frame - nsITableCellLayout *cellLayoutObject=nsnull; // again, frames are not ref-counted - - res = layoutObject->QueryInterface(NS_GET_IID(nsITableCellLayout), (void**)(&cellLayoutObject)); - if ((NS_SUCCEEDED(res)) && (nsnull!=cellLayoutObject)) - { - res = cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); - } - } - return res; + nsITableCellLayout *cellLayoutObject=nsnull; // again, frames are not ref-counted + res = layoutObject->QueryInterface(NS_GET_IID(nsITableCellLayout), (void**)(&cellLayoutObject)); + if (NS_FAILED(res)) return res; + if (!cellLayoutObject) return NS_ERROR_FAILURE; + return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); } NS_IMETHODIMP @@ -1245,13 +1513,10 @@ nsHTMLEditor::GetTableLayoutObject(nsIDOMElement* aTable, nsITableLayout **table // frames are not ref counted, so don't use an nsCOMPtr nsISupports *layoutObject=nsnull; nsresult res = nsHTMLEditor::GetLayoutObject(aTable, &layoutObject); - if ((NS_SUCCEEDED(res)) && (nsnull!=layoutObject)) - { // get the table interface from the frame - - res = layoutObject->QueryInterface(NS_GET_IID(nsITableLayout), - (void**)(tableLayoutObject)); - } - return res; + if (NS_FAILED(res)) return res; + if (!layoutObject) return NS_ERROR_FAILURE; + return layoutObject->QueryInterface(NS_GET_IID(nsITableLayout), + (void**)(tableLayoutObject)); } //Return actual number of cells (a cell with colspan > 1 counts as just 1) diff --git a/mozilla/editor/composer/src/nsEditorShell.cpp b/mozilla/editor/composer/src/nsEditorShell.cpp index 7f9afac5655..e3a85002a7a 100644 --- a/mozilla/editor/composer/src/nsEditorShell.cpp +++ b/mozilla/editor/composer/src/nsEditorShell.cpp @@ -211,7 +211,7 @@ nsEditorShell::nsEditorShell() nsEditorShell::~nsEditorShell() { NS_IF_RELEASE(mStateMaintainer); - + // the only other references we hold are in nsCOMPtrs, so they'll take // care of themselves. } @@ -302,7 +302,6 @@ nsEditorShell::PrepareDocumentForEditing(nsIURI *aUrl) NS_IF_RELEASE(mStateMaintainer); } } - mEditorType = eUninitializedEditorType; mEditor = 0; // clear out the nsCOMPtr @@ -365,8 +364,8 @@ nsEditorShell::PrepareDocumentForEditing(nsIURI *aUrl) { txnMgr->AddListener(NS_STATIC_CAST(nsITransactionListener*, mStateMaintainer)); } - - + + if (NS_SUCCEEDED(rv) && mContentWindow) { nsCOMPtr controller; @@ -3149,6 +3148,26 @@ nsEditorShell::SelectTableCell() return result; } +NS_IMETHODIMP +nsEditorShell::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) +{ + nsresult result = NS_NOINTERFACE; + switch (mEditorType) + { + case eHTMLTextEditorType: + { + nsCOMPtr tableEditor = do_QueryInterface(mEditor); + if (tableEditor) + result = tableEditor->SelectBlockOfCells(aStartCell, aEndCell); + } + break; + default: + result = NS_ERROR_NOT_IMPLEMENTED; + } + return result; +} + + NS_IMETHODIMP nsEditorShell::SelectTableRow() { diff --git a/mozilla/editor/composer/src/nsEditorShell.h b/mozilla/editor/composer/src/nsEditorShell.h index f7d8c412683..6bec5161f67 100644 --- a/mozilla/editor/composer/src/nsEditorShell.h +++ b/mozilla/editor/composer/src/nsEditorShell.h @@ -138,7 +138,7 @@ class nsEditorShell : public nsIEditorShell, nsCOMPtr mSearchContext; // context used for search and replace. Owned by the appshell. - nsInterfaceState* mStateMaintainer; // we hold the owning ref to this. + nsInterfaceState* mStateMaintainer; // we hold the owning ref to this. PRInt32 mWrapColumn; // can't actually set this 'til the editor is created, so we may have to hold on to it for a while diff --git a/mozilla/editor/composer/src/nsInterfaceState.cpp b/mozilla/editor/composer/src/nsInterfaceState.cpp index e517a8bb04d..0cebfe08771 100644 --- a/mozilla/editor/composer/src/nsInterfaceState.cpp +++ b/mozilla/editor/composer/src/nsInterfaceState.cpp @@ -90,13 +90,6 @@ nsInterfaceState::NotifyDocumentCreated() return NS_OK; } -NS_IMETHODIMP -nsInterfaceState::TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset) -{ - //stub - return NS_OK; -} - NS_IMETHODIMP nsInterfaceState::NotifyDocumentWillBeDestroyed() { @@ -335,25 +328,89 @@ nsInterfaceState::SelectionIsCollapsed() nsresult nsInterfaceState::UpdateParagraphState(const char* observerName, const char* attributeName, nsString& ioParaFormat) { - nsStringArray tagList; - - mEditor->GetParagraphTags(&tagList); + nsCOMPtr domSelection; + nsCOMPtr editor = do_QueryInterface(mEditor); + // Get the nsIEditor pointer (mEditor is nsIHTMLEditor) + if (!editor) return NS_ERROR_NULL_POINTER; + nsresult rv = editor->GetSelection(getter_AddRefs(domSelection)); + if (NS_FAILED(rv)) return rv; - PRInt32 numTags = tagList.Count(); - nsAutoString thisTag; - //Note: If numTags == 0, we probably have a text node not in a container - // (directly under ). Consider it normal - if (numTags > 0) + PRBool selectionCollapsed = PR_FALSE; + rv = domSelection->GetIsCollapsed(&selectionCollapsed); + if (NS_FAILED(rv)) return rv; + + // Get anchor and focus nodes: + nsCOMPtr anchorNode; + rv = domSelection->GetAnchorNode(getter_AddRefs(anchorNode)); + if (NS_FAILED(rv)) return rv; + if (!anchorNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr focusNode; + rv = domSelection->GetFocusNode(getter_AddRefs(focusNode)); + if (NS_FAILED(rv)) return rv; + if (!focusNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr anchorNodeBlockParent; + PRBool isBlock; + rv = editor->NodeIsBlock(anchorNode, isBlock); + if (NS_FAILED(rv)) return rv; + + if (isBlock) { - // This will never show the "mixed state" - // TODO: Scan list of tags and if any are different, set to "mixed" - tagList.StringAt(0, thisTag); + anchorNodeBlockParent = anchorNode; + } + else + { + // Get block parent of anchorNode node + // We could simply use this if it was in nsIEditor interface! + //rv = editor->GetBlockParent(anchorNode, getter_AddRefs(anchorNodeBlockParent)); + // if (NS_FAILED(rv)) return rv; + + nsCOMPtrparent; + nsCOMPtrtemp; + rv = anchorNode->GetParentNode(getter_AddRefs(parent)); + while (NS_SUCCEEDED(rv) && parent) + { + rv = editor->NodeIsBlock(parent, isBlock); + if (NS_FAILED(rv)) return rv; + if (isBlock) + { + anchorNodeBlockParent = parent; + break; + } + rv = parent->GetParentNode(getter_AddRefs(temp)); + parent = do_QueryInterface(temp); + } } - if (thisTag != mParagraphFormat) + if (!anchorNodeBlockParent) return NS_ERROR_NULL_POINTER; + + nsAutoString tagName; + + // Check if we have a selection that extends into multiple nodes, + // so we can check for "mixed" selection state + if (selectionCollapsed || focusNode == anchorNode) { - nsresult rv = SetNodeAttribute(observerName, attributeName, thisTag); + // Entire selection is within one block + anchorNodeBlockParent->GetNodeName(tagName); + } + else + { + // We may have different block parent node types WITHIN the selection, + // even if the anchor and focus parents are the same type. + // Getting the list of all block, e.g., by using GetParagraphTags(&tagList) + // is too inefficient for long documents. + // TODO: Change to use selection iterator to detect each block node in the + // selection and if different from the anchorNodeBlockParent, use "mixed" state + // *** Not doing this now reduces risk for Beta1 -- simply assume mixed state + // Note that "mixed" displays as "normal" in UI as of 3/6. + tagName = "mixed"; + } + + if (tagName != mParagraphFormat) + { + rv = SetNodeAttribute(observerName, attributeName, tagName); if (NS_FAILED(rv)) return rv; - mParagraphFormat = thisTag; + mParagraphFormat = tagName; } return NS_OK; } diff --git a/mozilla/editor/composer/src/nsInterfaceState.h b/mozilla/editor/composer/src/nsInterfaceState.h index 24aed7b5899..55e449d88c0 100644 --- a/mozilla/editor/composer/src/nsInterfaceState.h +++ b/mozilla/editor/composer/src/nsInterfaceState.h @@ -58,7 +58,6 @@ public: // nsIDOMSelectionListener interface NS_IMETHOD NotifySelectionChanged(); - NS_IMETHOD TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset); NS_DECL_NSIDOCUMENTSTATELISTENER diff --git a/mozilla/editor/idl/nsIEditorShell.idl b/mozilla/editor/idl/nsIEditorShell.idl index 3eb95d47fe3..62616c6c3a8 100644 --- a/mozilla/editor/idl/nsIEditorShell.idl +++ b/mozilla/editor/idl/nsIEditorShell.idl @@ -303,6 +303,11 @@ interface nsIEditorShell : nsISupports * selects all cells (not TR in the case of rows) */ void SelectTableCell(); + /** Select a rectangular block of cells: + * all cells falling within the row/column index of startCell + * to through the row/column index of the endCell + */ + void SelectBlockOfCells(in nsIDOMElement startCell, in nsIDOMElement endCell); void SelectTableRow(); void SelectTableColumn(); void SelectTable(); diff --git a/mozilla/editor/libeditor/html/TypeInState.cpp b/mozilla/editor/libeditor/html/TypeInState.cpp index e6518bed252..1a8b1a1a5eb 100644 --- a/mozilla/editor/libeditor/html/TypeInState.cpp +++ b/mozilla/editor/libeditor/html/TypeInState.cpp @@ -56,9 +56,3 @@ NS_IMETHODIMP TypeInState::NotifySelectionChanged() Reset(); return NS_OK; }; - -NS_IMETHODIMP TypeInState::TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset) -{ - //stub - return NS_OK; -} diff --git a/mozilla/editor/libeditor/html/TypeInState.h b/mozilla/editor/libeditor/html/TypeInState.h index 30bf98d2731..efa8dc2df1c 100644 --- a/mozilla/editor/libeditor/html/TypeInState.h +++ b/mozilla/editor/libeditor/html/TypeInState.h @@ -38,7 +38,6 @@ public: virtual ~TypeInState(); NS_IMETHOD NotifySelectionChanged(); - NS_IMETHOD TableCellNotification(nsIDOMNode* aNode, PRInt32 aOffset); void GetEnumForName(nsIAtom *aPropName, PRUint32 &aEnum); void GetPropertyIsString(PRUint32 aProp, PRUint32 &aIsString); diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index 4a68cb01bbc..4044b759d7d 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -217,6 +217,7 @@ nsHTMLEditor::nsHTMLEditor() , mRules(nsnull) , mIsComposing(PR_FALSE) , mMaxTextLength(-1) +, mSelectingTableCells(PR_FALSE) { // Done in nsEditor // NS_INIT_REFCNT(); diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index e1183b54776..e1c635a80cb 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -184,6 +184,7 @@ public: NS_IMETHOD DeleteTableColumn(PRInt32 aNumber); NS_IMETHOD DeleteTableRow(PRInt32 aNumber); NS_IMETHOD SelectTableCell(); + NS_IMETHOD SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell); NS_IMETHOD SelectTableRow(); NS_IMETHOD SelectTableColumn(); NS_IMETHOD SelectTable(); @@ -363,6 +364,8 @@ protected: // failed to set selection to some other content in the document NS_IMETHOD SetSelectionAtDocumentStart(nsIDOMSelection *aSelection); + // end of table editing utilities + NS_IMETHOD ReParentContentOfNode(nsIDOMNode *aNode, nsString &aParentTag, BlockTransformationType aTranformation); @@ -603,6 +606,14 @@ protected: PRBool mCachedUnderlineStyle; nsString mCachedFontName; + // True when selection consists of table cell(s) + PRBool mSelectingTableCells; + + // Used to monitor block of cells selected + // by dragging mouse across the table + nsCOMPtr mStartSelectedCell; + nsCOMPtr mCurrentSelectedCell; + public: static nsIAtom *gTypingTxnName; static nsIAtom *gIMETxnName; diff --git a/mozilla/editor/libeditor/html/nsTableEditor.cpp b/mozilla/editor/libeditor/html/nsTableEditor.cpp index 923dec7cc22..07ced375fa8 100644 --- a/mozilla/editor/libeditor/html/nsTableEditor.cpp +++ b/mozilla/editor/libeditor/html/nsTableEditor.cpp @@ -43,8 +43,11 @@ #include "nsEditorUtils.h" +//#define DEBUG_TABLE 1 + static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); + /*************************************************************************** * stack based helper class for restoring selection after table edit */ @@ -75,6 +78,22 @@ class nsSetCaretAfterTableEdit void CancelSetCaret() {mEd = nsnull; mTable = nsnull;} }; +// Stack-class to turn on/off selection batching for table selection +class nsSelectionBatcher +{ +private: + nsCOMPtr mSelection; +public: + nsSelectionBatcher(nsIDOMSelection *aSelection) : mSelection(aSelection) + { + if (mSelection) mSelection->StartBatchChanges(); + } + virtual ~nsSelectionBatcher() + { + if (mSelection) mSelection->EndBatchChanges(); + } +}; + // Table Editing helper utilities (not exposed in IDL) NS_IMETHODIMP @@ -319,7 +338,7 @@ nsHTMLEditor::GetNextRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow) } return NS_OK; } -#ifdef DEBUG +#ifdef DEBUG_cmanske printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n"); #endif // We arrive here only if a table section has no children @@ -963,8 +982,7 @@ NS_IMETHODIMP nsHTMLEditor::SelectTableCell() { nsCOMPtr cell; - nsresult res = NS_ERROR_FAILURE; - res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); if (NS_FAILED(res)) return res; // Don't fail if we didn't find a table if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; @@ -979,24 +997,280 @@ nsHTMLEditor::SelectTableCell() return res; } +NS_IMETHODIMP +nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) +{ + if (!aStartCell || !aEndCell) return NS_ERROR_NULL_POINTER; + + nsCOMPtr selection; + nsresult res = nsEditor::GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_FAILURE; + + nsCOMPtr table; + res = GetElementOrParentByTagName("table", aStartCell, getter_AddRefs(table)); + if (NS_FAILED(res)) return res; + if (!table) return NS_ERROR_FAILURE; + + nsCOMPtr endTable; + res = GetElementOrParentByTagName("table", aEndCell, getter_AddRefs(endTable)); + if (NS_FAILED(res)) return res; + if (!endTable) return NS_ERROR_FAILURE; + + // We can only select a block if within the same table, + // so do nothing if not within one table + if (table != endTable) return NS_OK; + + PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex; + + // Get starting and ending cells' location in the cellmap + res = GetCellIndexes(aStartCell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + + res = GetCellIndexes(aEndCell, endRowIndex, endColIndex); + if(NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + selection->ClearSelection(); + nsCOMPtr cellNode; + PRBool cellSelected = PR_FALSE; + + PRInt32 startColumn = PR_MIN(startColIndex, endColIndex); + PRInt32 startRow = PR_MIN(startRowIndex, endRowIndex); + PRInt32 maxColumn = PR_MAX(startColIndex, endColIndex); + PRInt32 maxRow = PR_MAX(startRowIndex, endRowIndex); + + nsCOMPtr cell; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for (PRInt32 row = startRow; row <= maxRow; row++) + { + for(PRInt32 col = startColumn; col <= maxColumn; col += actualColSpan) + { + res = GetCellDataAt(table, row, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous locations + if (cell && row == currentRowIndex && col == currentColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(aStartCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } + return res; +} + NS_IMETHODIMP nsHTMLEditor::SelectAllTableCells() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + nsCOMPtr startCell = cell; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 row = 0; row < rowCount; row++) + { + for(PRInt32 col = 0; col < colCount; col += actualColSpan) + { + res = GetCellDataAt(table, row, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && row == currentRowIndex && col == currentColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } NS_IMETHODIMP nsHTMLEditor::SelectTableRow() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + nsCOMPtr startCell = cell; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + //Note: At this point, we could get first and last cells in row, + // then call SelectBlockOfCells, but that would take just + // a little less code, so the following is more efficient + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same row as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 col = 0; col < colCount; col += actualColSpan) + { + res = GetCellDataAt(table, startRowIndex, col, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == startRowIndex && currentColIndex == startColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } NS_IMETHODIMP nsHTMLEditor::SelectTableColumn() { - nsresult res = NS_OK; + nsCOMPtr cell; + nsresult res = GetElementOrParentByTagName("td", nsnull, getter_AddRefs(cell)); + if (NS_FAILED(res)) return res; + + // Don't fail if we didn't find a cell + if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND; + + nsCOMPtr cellNode = do_QueryInterface(cell); + if (!cellNode) return NS_ERROR_FAILURE; + nsCOMPtr startCell = cell; + + // Get location of cell: + nsCOMPtr selection; + nsCOMPtr table; + nsCOMPtr cellParent; + PRInt32 cellOffset, startRowIndex, startColIndex; + + res = GetCellContext(selection, table, cell, cellParent, cellOffset, startRowIndex, startColIndex); + if (NS_FAILED(res)) return res; + if(!cell) return NS_ERROR_NULL_POINTER; + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Suppress nsIDOMSelectionListener notification + // until all selection changes are finished + nsSelectionBatcher selectionBatcher(selection); + + // It is now safe to clear the selection + // BE SURE TO RESET IT BEFORE LEAVING! + res = ClearSelection(); + + // Select all cells in the same column as current cell + PRBool cellSelected = PR_FALSE; + PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; + PRBool isSelected; + for(PRInt32 row = 0; row < rowCount; row += actualRowSpan) + { + res = GetCellDataAt(table, row, startColIndex, *getter_AddRefs(cell), + currentRowIndex, currentColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) break; + // Skip cells that are spanned from previous rows or columns + if (cell && currentRowIndex == startRowIndex && currentColIndex == startColIndex) + { + cellNode = do_QueryInterface(cell); + res = nsEditor::AppendNodeToSelectionAsRange(cellNode); + if (NS_FAILED(res)) break; + cellSelected = PR_TRUE; + } + } + // Safety code to select starting cell if nothing else was selected + if (!cellSelected) + { + cellNode = do_QueryInterface(startCell); + return nsEditor::AppendNodeToSelectionAsRange(cellNode); + } return res; } @@ -1217,22 +1491,16 @@ nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell, PRInt32 &aRowIndex, PRInt32 & return NS_ERROR_FAILURE; } - res = NS_ERROR_FAILURE; // we return an error unless we get the index nsISupports *layoutObject=nsnull; // frames are not ref counted, so don't use an nsCOMPtr - res = nsHTMLEditor::GetLayoutObject(aCell, &layoutObject); + if (NS_FAILED(res)) return res; + if (!layoutObject) return NS_ERROR_FAILURE; - if ((NS_SUCCEEDED(res)) && (nsnull!=layoutObject)) - { // get the table cell interface from the frame - nsITableCellLayout *cellLayoutObject=nsnull; // again, frames are not ref-counted - - res = layoutObject->QueryInterface(NS_GET_IID(nsITableCellLayout), (void**)(&cellLayoutObject)); - if ((NS_SUCCEEDED(res)) && (nsnull!=cellLayoutObject)) - { - res = cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); - } - } - return res; + nsITableCellLayout *cellLayoutObject=nsnull; // again, frames are not ref-counted + res = layoutObject->QueryInterface(NS_GET_IID(nsITableCellLayout), (void**)(&cellLayoutObject)); + if (NS_FAILED(res)) return res; + if (!cellLayoutObject) return NS_ERROR_FAILURE; + return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex); } NS_IMETHODIMP @@ -1245,13 +1513,10 @@ nsHTMLEditor::GetTableLayoutObject(nsIDOMElement* aTable, nsITableLayout **table // frames are not ref counted, so don't use an nsCOMPtr nsISupports *layoutObject=nsnull; nsresult res = nsHTMLEditor::GetLayoutObject(aTable, &layoutObject); - if ((NS_SUCCEEDED(res)) && (nsnull!=layoutObject)) - { // get the table interface from the frame - - res = layoutObject->QueryInterface(NS_GET_IID(nsITableLayout), - (void**)(tableLayoutObject)); - } - return res; + if (NS_FAILED(res)) return res; + if (!layoutObject) return NS_ERROR_FAILURE; + return layoutObject->QueryInterface(NS_GET_IID(nsITableLayout), + (void**)(tableLayoutObject)); } //Return actual number of cells (a cell with colspan > 1 counts as just 1) diff --git a/mozilla/editor/public/nsITableEditor.h b/mozilla/editor/public/nsITableEditor.h index 3d5cbc118e9..9f3171bee44 100644 --- a/mozilla/editor/public/nsITableEditor.h +++ b/mozilla/editor/public/nsITableEditor.h @@ -82,6 +82,17 @@ public: * selects all cells (not TR in the case of rows) */ NS_IMETHOD SelectTableCell()=0; + + /** Select a rectangular block of cells: + * all cells falling within the row/column index of aStartCell + * to through the row/column index of the aEndCell + * aStartCell can be any location relative to aEndCell, + * as long as they are in the same table + * @param aStartCell starting cell in block + * @param aEndCell ending cell in block + */ + NS_IMETHOD SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell)=0; + NS_IMETHOD SelectTableRow()=0; NS_IMETHOD SelectTableColumn()=0; NS_IMETHOD SelectTable()=0; diff --git a/mozilla/editor/ui/composer/skin/editor.css b/mozilla/editor/ui/composer/skin/editor.css index 25ff0e48202..4d7fab17b23 100644 --- a/mozilla/editor/ui/composer/skin/editor.css +++ b/mozilla/editor/ui/composer/skin/editor.css @@ -284,30 +284,37 @@ text.color-caption { } div#ColorButtons { - /* Width needs to be >= 2x color button width - (color.gif=14px + 2 for border) - for initial layout. - Then use relative position to move buttons - to desired overlapping locations + /* Width needs to be enough for overlapping + buttons. Note: color.gif=14px + 2 for borders. + We use relative position to move buttons + to desired positions */ - width: 32px; - max-width: 32px; - height: 19px; - /* This compensates for remaining gap - on right after relative button positioning + width: 30px; + max-width: 30px; + /* Other toolbar buttons are 16px high (+border and margin), + so limit total height of color buttons + region to a compatable height */ - margin-right: -3px; + height: 22px; + max-height: 22px; } +/* The initial layout of these buttons is: + BackColorPopupButton appears above TextColorPopupButton. + BackColorPopupButton must be 1st in XUL so that + TextColorPopupButton is on top after relative positioning, + which places TextColorPopupButton button in upper left and + BackColorPopupButton button at lower right of the ColorButtons div +*/ + titledbutton#TextColorPopupButton { list-style-image:url("chrome://editor/skin/images/color.gif"); border: 1px solid #CCCCCC; padding: 0px; margin: 0px; position: relative; - /* Position top left */ - top: 1px; - left: -14px; + top: -15px; + left: 1px; /* TEMP: Set color here. TODO: Set color from page */ background-color: #AA0000; @@ -323,8 +330,7 @@ titledbutton#BackColorPopupButton { padding: 0px; margin: 0px; position: relative; - /* Position lower right */ - top: 8px; + top: 7px; left: 12px; /* TEMP: Set color here. TODO: Set color from page */