diff --git a/editor/base/nsEditorShell.cpp b/editor/base/nsEditorShell.cpp index 7dc456d70a76..5d91474185cb 100644 --- a/editor/base/nsEditorShell.cpp +++ b/editor/base/nsEditorShell.cpp @@ -272,17 +272,6 @@ nsEditorShell::~nsEditorShell() NS_IF_RELEASE(mStateMaintainer); NS_IF_RELEASE(mParserObserver); - // Remove our document mouse event listener - if (mMouseListenerP) - { - nsCOMPtr erP; - nsresult rv = GetDocumentEventReceiver(getter_AddRefs(erP)); - if (NS_SUCCEEDED(rv) && erP) - { - erP->RemoveEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener)); - mMouseListenerP = nsnull; - } - } // the only other references we hold are in nsCOMPtrs, so they'll take // care of themselves. } @@ -347,6 +336,27 @@ nsEditorShell::Init() return NS_OK; } +NS_IMETHODIMP +nsEditorShell::Shutdown() +{ + nsresult rv = NS_OK; + // Remove our document mouse event listener + if (mMouseListenerP) + { + nsCOMPtr erP; + rv = GetDocumentEventReceiver(getter_AddRefs(erP)); + if (NS_SUCCEEDED(rv)) + { + if (erP) + { + erP->RemoveEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener)); + mMouseListenerP = nsnull; + } + else rv = NS_ERROR_NULL_POINTER; + } + } + return rv; +} nsresult nsEditorShell::ResetEditingState() @@ -3336,6 +3346,37 @@ nsEditorShell::GetFirstSelectedCell(nsIDOMElement **aOutElement) return result; } +NS_IMETHODIMP +nsEditorShell::GetFirstSelectedCellInTable(PRInt32 *aRowIndex, PRInt32 *aColIndex, nsIDOMElement **aOutElement) +{ + if (!aOutElement || !aRowIndex || !aColIndex) + return NS_ERROR_NULL_POINTER; + + nsresult result = NS_NOINTERFACE; + + switch (mEditorType) + { + case eHTMLTextEditorType: + { + nsCOMPtr tableEditor = do_QueryInterface(mEditor); + if (tableEditor) + { + result = tableEditor->GetFirstSelectedCellInTable(aOutElement, aRowIndex, aColIndex); + // Don't return NS_EDITOR_ELEMENT_NOT_FOUND (passes NS_SUCCEEDED macro) + // to JavaScript + if(NS_SUCCEEDED(result)) return NS_OK; + } + + break; + } + + case ePlainTextEditorType: + default: + result = NS_ERROR_NOT_IMPLEMENTED; + } + + return result; +} NS_IMETHODIMP nsEditorShell::GetNextSelectedCell(nsIDOMElement **aOutElement) diff --git a/editor/base/nsHTMLEditor.h b/editor/base/nsHTMLEditor.h index 20df24ca023a..bd9b4811f8ac 100644 --- a/editor/base/nsHTMLEditor.h +++ b/editor/base/nsHTMLEditor.h @@ -204,7 +204,8 @@ public: PRBool& aIsSelected); NS_IMETHOD GetFirstRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow); NS_IMETHOD GetNextRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow); - NS_IMETHOD SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection); + NS_IMETHOD SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, + PRInt32 aDirection, PRBool aSelected); NS_IMETHOD GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsString& aTagName, PRInt32 &aSelectedCount); NS_IMETHOD GetSelectedCellsType(nsIDOMElement *aElement, PRUint32 &aSelectionType); // Finds the first selected cell in first range of selection @@ -217,20 +218,9 @@ public: // aRange is optional: returns the range around the cell NS_IMETHOD GetNextSelectedCell(nsIDOMElement **aCell, nsIDOMRange **aRange); + // Upper-left-most selected cell in table + NS_IMETHOD GetFirstSelectedCellInTable(nsIDOMElement **aCell, PRInt32 *aRowIndex, PRInt32 *aColIndex); -// Selection and navigation - /* obsolete - NS_IMETHOD MoveSelectionUp(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionDown(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionNext(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionPrevious(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD SelectNext(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD SelectPrevious(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD ScrollUp(nsIAtom *aIncrement); - NS_IMETHOD ScrollDown(nsIAtom *aIncrement); - NS_IMETHOD ScrollIntoView(PRBool aScrollToBegin); - */ - /* miscellaneous */ // This sets background on the appropriate container element (table, cell,) // or calls into nsTextEditor to set the page background @@ -405,6 +395,17 @@ protected: nsIDOMNode **aCellParent, PRInt32 *aCellOffset, PRInt32 *aRowIndex, PRInt32 *aColIndex); + NS_IMETHOD GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32& aActualRowSpan, PRInt32& aActualColSpan); + + NS_IMETHOD SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aColSpanLeft, PRInt32 aColSpanRight, nsIDOMElement **aNewCell); + + NS_IMETHOD SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow, nsIDOMElement **aNewCell); + + NS_IMETHOD FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount); + // Fallback method: Call this after using ClearSelection() and you // failed to set selection to some other content in the document NS_IMETHOD SetSelectionAtDocumentStart(nsIDOMSelection *aSelection); diff --git a/editor/base/nsTableEditor.cpp b/editor/base/nsTableEditor.cpp index 7f1e0e06b286..aa507a6282ec 100644 --- a/editor/base/nsTableEditor.cpp +++ b/editor/base/nsTableEditor.cpp @@ -53,27 +53,29 @@ static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); /*************************************************************************** * stack based helper class for restoring selection after table edit */ -class nsSetCaretAfterTableEdit +class nsSetSelectionAfterTableEdit { private: nsCOMPtr mEd; nsCOMPtr mTable; - PRInt32 mCol, mRow, mDirection; + PRInt32 mCol, mRow, mDirection, mSelected; public: - nsSetCaretAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, - PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection) : + nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, + PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection, + PRBool aSelected) : mEd(do_QueryInterface(aEd)) { mTable = aTable; mRow = aRow; mCol = aCol; mDirection = aDirection; + mSelected = aSelected; } - ~nsSetCaretAfterTableEdit() + ~nsSetSelectionAfterTableEdit() { if (mEd) - mEd->SetCaretAfterTableEdit(mTable, mRow, mCol, mDirection); + mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected); } // This is needed to abort the caret reset in the destructor // when one method yields control to another @@ -108,43 +110,42 @@ nsHTMLEditor::InsertCell(nsIDOMElement *aCell, PRInt32 aRowSpan, PRInt32 aColSpa // And the parent and offsets needed to do an insert nsCOMPtr cellParent; nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent)); - if( NS_SUCCEEDED(res) && cellParent) + if (NS_FAILED(res)) return res; + if (!cellParent) return NS_ERROR_NULL_POINTER; + + + PRInt32 cellOffset; + res = GetChildOffset(aCell, cellParent, cellOffset); + if (NS_FAILED(res)) return res; + + nsCOMPtr newCell; + res = CreateElementWithDefaults(NS_ConvertASCIItoUCS2("td"), getter_AddRefs(newCell)); + if(NS_FAILED(res)) return res; + if(!newCell) return NS_ERROR_FAILURE; + + //Optional: return new cell created + if (aNewCell) { - PRInt32 cellOffset; - res = GetChildOffset(aCell, cellParent, cellOffset); - if( NS_SUCCEEDED(res)) - { - nsCOMPtr newCell; - res = CreateElementWithDefaults(NS_ConvertASCIItoUCS2("td"), getter_AddRefs(newCell)); - if(NS_FAILED(res)) return res; - if(!newCell) return NS_ERROR_FAILURE; - - //Optional: return new cell created - if (aNewCell) - { - *aNewCell = newCell.get(); - NS_ADDREF(*aNewCell); - } - if( aRowSpan > 1) - { - nsAutoString newRowSpan(aRowSpan); - // Note: Do NOT use editor txt for this - newCell->SetAttribute(NS_ConvertASCIItoUCS2("rowspan"), newRowSpan); - } - if( aColSpan > 1) - { - nsAutoString newColSpan(aColSpan); - // Note: Do NOT use editor txt for this - newCell->SetAttribute(NS_ConvertASCIItoUCS2("colspan"), newColSpan); - } - if(aAfter) cellOffset++; - - //Don't let Rules System change the selection - nsAutoTxnsConserveSelection dontChangeSelection(this); - res = nsEditor::InsertNode(newCell, cellParent, cellOffset); - } + *aNewCell = newCell.get(); + NS_ADDREF(*aNewCell); } - return res; + if( aRowSpan > 1) + { + nsAutoString newRowSpan(aRowSpan); + // Note: Do NOT use editor txt for this + newCell->SetAttribute(NS_ConvertASCIItoUCS2("rowspan"), newRowSpan); + } + if( aColSpan > 1) + { + nsAutoString newColSpan(aColSpan); + // Note: Do NOT use editor txt for this + newCell->SetAttribute(NS_ConvertASCIItoUCS2("colspan"), newColSpan); + } + if(aAfter) cellOffset++; + + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + return nsEditor::InsertNode(newCell, cellParent, cellOffset); } PRBool IsRowNode(nsCOMPtr &aNode) @@ -208,7 +209,7 @@ nsHTMLEditor::InsertTableCell(PRInt32 aNumber, PRBool aAfter) if (!curCell) return NS_ERROR_FAILURE; PRInt32 newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex; //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, PR_FALSE); //...so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -397,7 +398,7 @@ nsHTMLEditor::InsertTableColumn(PRInt32 aNumber, PRBool aAfter) if (NS_FAILED(res)) return res; //We reset caret in destructor... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE); //.. so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -528,7 +529,7 @@ nsHTMLEditor::InsertTableRow(PRInt32 aNumber, PRBool aAfter) } //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); //...so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -707,9 +708,9 @@ nsHTMLEditor::DeleteTableCell(PRInt32 aNumber) // More than 1 cell in the row // We clear the selection to avoid problems when nodes in the selection are deleted, - // The setCaret object will call SetCaretAfterTableEdit in it's destructor + // The setCaret object will call SetSelectionAfterTableEdit in it's destructor selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); nsAutoTxnsConserveSelection dontChangeSelection(this); res = DeleteNode(cell); @@ -740,7 +741,7 @@ nsHTMLEditor::DeleteTableCellContents() // We clear the selection to avoid problems when nodes in the selection are deleted, selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -870,7 +871,8 @@ nsHTMLEditor::DeleteTableColumn(PRInt32 aNumber) } else { selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, curStartRowIndex, curStartColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, curStartRowIndex, curStartColIndex, + ePreviousColumn, PR_FALSE); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -916,13 +918,13 @@ nsHTMLEditor::DeleteTableRow(PRInt32 aNumber) nsAutoEditBatch beginBatching(this); //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE); // Check for counts too high aNumber = PR_MIN(aNumber,(rowCount-startRowIndex)); // We clear the selection to avoid problems when nodes in the selection are deleted, - // Be sure to set it correctly later (in SetCaretAfterTableEdit)! + // Be sure to set it correctly later (in SetSelectionAfterTableEdit)! selection->ClearSelection(); // Scan through cells in row to do rowspan adjustments @@ -1320,13 +1322,16 @@ nsHTMLEditor::SplitTableCell() { nsCOMPtr table; nsCOMPtr targetCell; - PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; - PRBool isSelected; + PRInt32 startRowIndex, startColIndex, actualRowSpan, actualColSpan; nsCOMPtr cell2; - PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; - PRBool isSelected2; +// PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; +// PRBool isSelected2; - // Get cell and table at selection anchor node (and other data) + nsAutoEditBatch beginBatching(this); + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + + // Get cell, table, etc. at selection anchor node nsresult res = GetCellContext(nsnull, getter_AddRefs(table), getter_AddRefs(targetCell), @@ -1336,14 +1341,113 @@ nsHTMLEditor::SplitTableCell() if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; // We need rowspan and colspan data - res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); + res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan); //TODO: FINISH THIS! return res; } +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aColSpanLeft, PRInt32 aColSpanRight, + nsIDOMElement **aNewCell) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_NULL_POINTER; + + // We can't split! + if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) + return NS_OK; + + // Reduce colspan of cell to split + res = SetColSpan(cell, aColSpanLeft); + if (NS_FAILED(res)) return res; + + // Insert new cell after using the remaining span; + return InsertCell(cell, actualRowSpan, aColSpanRight, PR_TRUE, aNewCell); +} + +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow, + nsIDOMElement **aNewCell) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_NULL_POINTER; + + // We can't split! + if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) + return NS_OK; + + nsCOMPtr cell2; + PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; + PRBool isSelected2; + PRInt32 colIndex = startColIndex; + PRBool insertAfter = (startColIndex > 0); + + // Find a cell to insert before or after + do + { + // Search for a cell to insert before + res = GetCellDataAt(aTable, startRowIndex+aRowSpanAbove, + colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + // If we fail here, it could be because row has bad rowspan values, + // such as all cells having rowspan > 1 (Call FixRowSpan first!) + if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE; + + // 0 value results in infinite loop! + NS_ASSERTION(actualColSpan2 > 0, "ColSpan=0 in SplitCellIntoRows"); + if (startRowIndex2 == startRowIndex) + { + if (insertAfter) + { + // If cell found is AFTER desired new cell colum, + // we have multiple cells with rowspan > 1 that + // fool us into thinking we insert after... + if (startColIndex2 > startColIndex2) + { + // ... so insert before the cell we found + insertAfter = PR_FALSE; + break; + } + // New cell isn't first in row, + // so stop after we find the last cell before new cell's column + if ((startColIndex2 + actualColSpan2) >= startColIndex) break; + } + else + { + break; // Inserting before, so stop at first cell in row we want to insert into + } + } + // Skip to next available cellmap location + colIndex += actualColSpan2; + } while(PR_TRUE); + + res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, aNewCell); + if (NS_FAILED(res)) return res; + + // Reduce rowspan of cell to split + return SetRowSpan(cell, aRowSpanAbove); +} + NS_IMETHODIMP nsHTMLEditor::JoinTableCells() { @@ -1355,7 +1459,7 @@ nsHTMLEditor::JoinTableCells() PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; PRBool isSelected2; - // Get cell and table at selection anchor node (and other data) + // Get cell, table, etc. at selection anchor node nsresult res = GetCellContext(nsnull, getter_AddRefs(table), getter_AddRefs(targetCell), @@ -1364,42 +1468,234 @@ nsHTMLEditor::JoinTableCells() if (NS_FAILED(res)) return res; if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; - PRInt32 rowCount, colCount; - res = GetTableSize(table, rowCount, colCount); - if (NS_FAILED(res)) return res; - - // We need rowspan and colspan data - res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - if (NS_FAILED(res)) return res; - if(!targetCell) return NS_ERROR_NULL_POINTER; - - nsCOMPtr firstCell; - res = GetFirstSelectedCell(getter_AddRefs(firstCell), nsnull); - if (NS_FAILED(res)) return res; - - //We reset caret in destructor... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); nsAutoEditBatch beginBatching(this); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); + nsCOMPtr firstCell; + PRInt32 firstRowIndex, firstColIndex; + res = GetFirstSelectedCellInTable(getter_AddRefs(firstCell), &firstRowIndex, &firstColIndex); + if (NS_FAILED(res)) return res; + + if (firstCell) { - // Merge selected cells into uppper-left-most cell + // We have selected cells: Join contiguous cells + // and just merge contents if not contiguous + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Get spans for cell we will merge into + PRInt32 firstRowSpan, firstColSpan; + res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan); + if (NS_FAILED(res)) return res; + + //We reset caret in destructor. Last param = true to select cell when done + nsSetSelectionAfterTableEdit setCaret(this, table, firstRowIndex, firstColIndex, ePreviousColumn, PR_TRUE); + + // This defines the last indexes along the "edges" + // of the contiguous block of cells, telling us + // that we can join adjacent cells to the block + // Start with same as the first values, + // then expand as we find adjacent selected cells + PRInt32 lastRowIndex = firstRowIndex; + PRInt32 lastColIndex = firstColIndex; + PRInt32 rowIndex, colIndex; + + // First pass: Determine boundaries of contiguous rectangular block + // that we will join into one cell, + // favoring adjacent cells in the same row + for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) + { + PRInt32 currentRowCount = rowCount; + // Be sure each row doesn't have rowspan errors + res = FixBadRowSpan(table, rowIndex, rowCount); + if (NS_FAILED(res)) return res; + // Adjust rowcount by number of rows we removed + lastRowIndex -= (currentRowCount-rowCount); + + PRBool cellFoundInRow = PR_FALSE; + PRBool lastRowIsSet = PR_FALSE; + PRInt32 lastColInRow = 0; + PRInt32 firstColInRow = firstColIndex; + for (colIndex = firstColIndex; colIndex < colCount; colIndex += actualColSpan2) + { + res = GetCellDataAt(table, rowIndex, colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + if (NS_FAILED(res)) return res; + + NS_ASSERTION(actualColSpan2 > 0, "JoinTableCells: ColSpan=0"); + if (isSelected2) + { + if (!cellFoundInRow) + // We've just found the first selected cell in this row + firstColInRow = colIndex; + + if(rowIndex > firstRowIndex && firstColInRow != firstColIndex) + { + // We're in at least the second row, + // but left boundary is "ragged" (not the same as 1st row's start) + //Let's just end block on previous row + // and keep previous lastColIndex + //TODO: We could try to find the Maximum firstColInRow + // so our block can still extend down more rows? + lastRowIndex = PR_MAX(0,rowIndex - 1); + lastRowIsSet = PR_TRUE; + break; + } + // Save max selected column in this row, including extra colspan + lastColInRow = colIndex + (actualColSpan2-1); + cellFoundInRow = PR_TRUE; + } + else if (cellFoundInRow) + { + // No cell or not selected, but at one in row was found + if (colIndex <= lastColIndex) + { + // Cell is in a column less than current right border, + // so stop block at the previous row + lastRowIndex = PR_MAX(0,rowIndex - 1); + lastRowIsSet = PR_TRUE; + } + // We're done with this row + break; + } + } // End of column loop + + // Done with this row + if (cellFoundInRow) + { + if (rowIndex == firstRowIndex) + { + // First row always initializes the right boundary + lastColIndex = lastColInRow; + } + + // If we didn't determine last row above... + if (!lastRowIsSet) + { + if (colIndex < lastColIndex) + { + // (don't think we ever get here?) + // Cell is in a column less than current right boundary, + // so stop block at the previous row + lastRowIndex = PR_MAX(0,rowIndex - 1); + } + else + { + // Go on to examine next row + lastRowIndex = rowIndex+1; + // Use the minimun col we found so far for right boundary + lastColIndex = PR_MIN(lastColIndex, lastColInRow); + } + } + } + else + { + // No selected cells in this row -- stop at row above + // and leave last column at its previous value + lastRowIndex = PR_MAX(0,rowIndex - 1); + } + } + + // The list of cells we will delete after joining + nsVoidArray deleteList; + + // 2nd pass: Do the joining and merging + for (rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + for (colIndex = 0; colIndex < colCount; colIndex+= actualColSpan2) + { + res = GetCellDataAt(table, rowIndex, colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + if (NS_FAILED(res)) return res; + + // Merge only selected cells (skip cell we're merging into, of course) + if (isSelected2 && cell2 != firstCell) + { + if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex && + colIndex >= firstColIndex && colIndex <= lastColIndex) + { + // We are within the join region + // Problem: It is very tricky to delete cells as we merge, + // since that will upset the cellmap + // Instead, build a list of cells to delete and do it later + NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above"); + + res = MergeCells(firstCell, cell2, PR_FALSE); + if (NS_FAILED(res)) return res; + + //TODO: Check if cell "hangs" off the boundary because of colspan or rowspan > 1 + // Use split methods to chop off excess + + // Add cell to list to delete + deleteList.AppendElement((void *)cell2.get()); + } + else + { + // Cell is outside join region -- just merge the contents + res = MergeCells(firstCell, cell2, PR_FALSE); + if (NS_FAILED(res)) return res; + } + } + } + } + + // All cell contents are merged. Delete the empty cells we accumulated + nsIDOMElement *elementPtr; + PRInt32 count; + while ((count = deleteList.Count())) + { + // go backwards to keep nsVoidArray from memmoving everything each time + count--; // nsVoidArray is zero based + elementPtr = (nsIDOMElement*)deleteList.ElementAt(count); + deleteList.RemoveElementAt(count); + if (elementPtr) + { + nsCOMPtr node = do_QueryInterface(elementPtr); + res = DeleteNode(node); + if (NS_FAILED(res)) return res; + // Should we delete this??? + //delete elementPtr; + } + } + // Set spans for the cell everthing merged into + res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1); + if (NS_FAILED(res)) return res; + res = SetColSpan(firstCell, lastColIndex-firstColIndex+1); + if (NS_FAILED(res)) return res; } else { - // Merge with cell to the right + // Joining with cell to the right -- get rowspan and colspan data of target cell + res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!targetCell) return NS_ERROR_NULL_POINTER; + + //We reset caret in destructor. Select cell when done if target was already selected + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, isSelected); + + // Get data for cell to the right res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, *getter_AddRefs(cell2), startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2, isSelected2); if (NS_FAILED(res)) return res; - if(!cell2) return NS_ERROR_NULL_POINTER; + if(!cell2) return NS_OK; // Don't fail if there's no cell + + if( actualRowSpan2 > actualRowSpan ) + { + //TODO: SPLIT CELL + } // Move contents and delete cell to the right res = MergeCells(targetCell, cell2, PR_TRUE); + if (NS_FAILED(res)) return res; // Reset target cell's spans res = SetColSpan(targetCell, actualColSpan+actualColSpan2); @@ -1462,6 +1758,57 @@ nsHTMLEditor::MergeCells(nsCOMPtr aTargetCell, return res; } + +NS_IMETHODIMP +nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + PRInt32 colCount; + nsresult res = GetTableSize(aTable, colCount, colCount); + if (NS_FAILED(res)) return res; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + + PRInt32 minRowSpan = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32 + PRInt32 colIndex; + + for( colIndex = 0; colIndex < colCount; colIndex++) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + // NOTE: This is a *real* failure. + // GetCellDataAt passes if cell is missing from cellmap + if(NS_FAILED(res)) return res; + if(cell && rowSpan > 0 && rowSpan < minRowSpan) + minRowSpan = rowSpan; + } + if(minRowSpan > 1) + { + // The amount to reduce everyones rowspan + // so at least one cell has rowspan = 1 + PRInt32 rowsReduced = minRowSpan - 1; + for(colIndex = 0; colIndex < colCount; colIndex++) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if(NS_FAILED(res)) return res; + // Fixup rowspans for cells starting in current row + if(cell && rowSpan > 0 && + startRowIndex == aRowIndex && + startColIndex == colIndex ) + { + return SetRowSpan(cell, rowSpan-rowsReduced); + } + } + } + return GetTableSize(aTable, aNewRowCount, colCount); +} + NS_IMETHODIMP nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) { @@ -1482,50 +1829,13 @@ nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; PRBool isSelected; -#if 0 -// This isn't working yet -- layout errors contribute! - - // First scan all cells in row to detect bad rowspan values + // First scan all cells in each row to detect bad rowspan values for(rowIndex = 0; rowIndex < rowCount; rowIndex++) { - PRInt32 minRowSpan = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32 - for(colIndex = 0; colIndex < colCount; colIndex++) - { - res = GetCellDataAt(aTable, rowIndex, colIndex, *getter_AddRefs(cell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - // NOTE: This is a *real* failure. - // GetCellDataAt passes if cell is missing from cellmap - if(NS_FAILED(res)) return res; - if(cell && rowSpan > 0 && rowSpan < minRowSpan) - minRowSpan = rowSpan; - } - if(minRowSpan > 1) - { - PRInt32 spanDiff = minRowSpan - 1; - for(colIndex = 0; colIndex < colCount; colIndex++) - { - res = GetCellDataAt(aTable, rowIndex, colIndex, *getter_AddRefs(cell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - // NOTE: This is a *real* failure. - // GetCellDataAt passes if cell is missing from cellmap - if(NS_FAILED(res)) return res; - // Fixup rowspans for cells starting in current row - if(cell && rowSpan > 0 && - startRowIndex == rowIndex && - startColIndex == colIndex ) - { - // Set rowspan so there's at least one cell with ROWSPAN=1 - SetRowSpan(cell, rowSpan-spanDiff); - } - } - } + + res = FixBadRowSpan(aTable, rowIndex, rowCount); + if (NS_FAILED(res)) return res; } - // Get Table size again in case above changed anything - res = GetTableSize(table, rowCount, colCount); - if (NS_FAILED(res)) return res; -#endif // Fill in missing cellmap locations with empty cells for(rowIndex = 0; rowIndex < rowCount; rowIndex++) @@ -1733,6 +2043,19 @@ nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIn actualRowSpan, actualColSpan, isSelected); } +// When all you want are the rowspan and colspan (not exposed in nsITableEditor) +NS_IMETHODIMP +nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32& aActualRowSpan, PRInt32& aActualColSpan) +{ + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan; + PRBool isSelected; + return GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + aActualRowSpan, aActualColSpan, isSelected); +} + NS_IMETHODIMP nsHTMLEditor::GetCellContext(nsIDOMSelection **aSelection, nsIDOMElement **aTable, @@ -1923,8 +2246,68 @@ nsHTMLEditor::GetNextSelectedCell(nsIDOMElement **aCell, nsIDOMRange **aRange) return res; } +NS_IMETHODIMP +nsHTMLEditor::GetFirstSelectedCellInTable(nsIDOMElement **aCell, PRInt32 *aRowIndex, PRInt32 *aColIndex) +{ + if (!aCell) return NS_ERROR_NULL_POINTER; + *aCell = nsnull; + if (aRowIndex) + *aRowIndex = 0; + if (aColIndex) + *aColIndex = 0; + + nsCOMPtr cell; + nsresult res = GetFirstSelectedCell(getter_AddRefs(cell), nsnull); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_FAILURE; + + PRInt32 startRowIndex, startColIndex; + res = GetCellIndexes(cell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + + // Start with first cell selected + nsCOMPtr firstCell = cell; + PRInt32 firstRowIndex = startRowIndex; + PRInt32 firstColIndex = startColIndex; + + while (cell) + { + nsresult res = GetNextSelectedCell(getter_AddRefs(cell), nsnull); + if (NS_FAILED(res)) return res; + res = GetCellIndexes(cell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + if (cell) + { + // Find the topmost row + if (startRowIndex <= firstRowIndex) + { + // Then save the left-most cell in that row + if (startRowIndex < firstRowIndex || + startColIndex < startColIndex) + { + firstCell = cell; + firstRowIndex = startRowIndex; + firstColIndex = startColIndex; + } + } + } + } + if (NS_SUCCEEDED(res)) + { + *aCell = firstCell.get(); + NS_ADDREF(*aCell); + + if (aRowIndex) + *aRowIndex = firstRowIndex; + if (aColIndex) + *aColIndex = firstColIndex; + } + return res; +} + NS_IMETHODIMP -nsHTMLEditor::SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection) +nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, + PRInt32 aDirection, PRBool aSelected) { nsresult res = NS_ERROR_NOT_INITIALIZED; if (!aTable) return res; @@ -1950,11 +2333,19 @@ nsHTMLEditor::SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt3 { if (cell) { - // Set the caret to deepest first child - // but don't go into nested tables - // TODO: Should we really be placing the caret at the END - // of the cell content? - return CollapseSelectionToDeepestNonTableFirstChild(selection, cellNode); + if (aSelected) + { + // Reselect the cell + return SelectElement(cell); + } + else + { + // Set the caret to deepest first child + // but don't go into nested tables + // TODO: Should we really be placing the caret at the END + // of the cell content? + return CollapseSelectionToDeepestNonTableFirstChild(selection, cellNode); + } } else { // Setup index to find another cell in the // direction requested, but move in @@ -2052,7 +2443,7 @@ nsHTMLEditor::GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsS if (tag == tdName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = tdName; // Each cell is in its own selection range, // so count signals multiple-cell selection @@ -2061,13 +2452,13 @@ nsHTMLEditor::GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsS } else if(tag == tableName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = tableName; aSelectedCount = 1; } else if(tag == trName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = trName; aSelectedCount = 1; } diff --git a/editor/composer/src/nsEditorShell.cpp b/editor/composer/src/nsEditorShell.cpp index 7dc456d70a76..5d91474185cb 100644 --- a/editor/composer/src/nsEditorShell.cpp +++ b/editor/composer/src/nsEditorShell.cpp @@ -272,17 +272,6 @@ nsEditorShell::~nsEditorShell() NS_IF_RELEASE(mStateMaintainer); NS_IF_RELEASE(mParserObserver); - // Remove our document mouse event listener - if (mMouseListenerP) - { - nsCOMPtr erP; - nsresult rv = GetDocumentEventReceiver(getter_AddRefs(erP)); - if (NS_SUCCEEDED(rv) && erP) - { - erP->RemoveEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener)); - mMouseListenerP = nsnull; - } - } // the only other references we hold are in nsCOMPtrs, so they'll take // care of themselves. } @@ -347,6 +336,27 @@ nsEditorShell::Init() return NS_OK; } +NS_IMETHODIMP +nsEditorShell::Shutdown() +{ + nsresult rv = NS_OK; + // Remove our document mouse event listener + if (mMouseListenerP) + { + nsCOMPtr erP; + rv = GetDocumentEventReceiver(getter_AddRefs(erP)); + if (NS_SUCCEEDED(rv)) + { + if (erP) + { + erP->RemoveEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener)); + mMouseListenerP = nsnull; + } + else rv = NS_ERROR_NULL_POINTER; + } + } + return rv; +} nsresult nsEditorShell::ResetEditingState() @@ -3336,6 +3346,37 @@ nsEditorShell::GetFirstSelectedCell(nsIDOMElement **aOutElement) return result; } +NS_IMETHODIMP +nsEditorShell::GetFirstSelectedCellInTable(PRInt32 *aRowIndex, PRInt32 *aColIndex, nsIDOMElement **aOutElement) +{ + if (!aOutElement || !aRowIndex || !aColIndex) + return NS_ERROR_NULL_POINTER; + + nsresult result = NS_NOINTERFACE; + + switch (mEditorType) + { + case eHTMLTextEditorType: + { + nsCOMPtr tableEditor = do_QueryInterface(mEditor); + if (tableEditor) + { + result = tableEditor->GetFirstSelectedCellInTable(aOutElement, aRowIndex, aColIndex); + // Don't return NS_EDITOR_ELEMENT_NOT_FOUND (passes NS_SUCCEEDED macro) + // to JavaScript + if(NS_SUCCEEDED(result)) return NS_OK; + } + + break; + } + + case ePlainTextEditorType: + default: + result = NS_ERROR_NOT_IMPLEMENTED; + } + + return result; +} NS_IMETHODIMP nsEditorShell::GetNextSelectedCell(nsIDOMElement **aOutElement) diff --git a/editor/idl/nsIEditorShell.idl b/editor/idl/nsIEditorShell.idl index e6ee09efcb8b..0dbec5558bca 100644 --- a/editor/idl/nsIEditorShell.idl +++ b/editor/idl/nsIEditorShell.idl @@ -99,6 +99,11 @@ interface nsIEditorShell : nsISupports void Init(); + /* Do any cleanup that needs the content window here + * because it's gone by the time destructor is called + */ + void Shutdown(); + /* Access for the common alert dialog */ void AlertWithTitle(in wstring title, in wstring msg); @@ -219,6 +224,12 @@ interface nsIEditorShell : nsISupports */ nsIDOMElement GetFirstSelectedCell(); + /** Get first selected cell in table: the upper-left-most cell + * Returns null if ranges don't contain cell selections + * rowIndex and colIndex are those of the cell if found + */ + nsIDOMElement GetFirstSelectedCellInTable(out PRInt32 rowIndex, out PRInt32 colIndex ); + /** Get next selected cell element from first selection range. * Assumes cell-selection model where each cell * is in a separate range (selection parent node is table row) diff --git a/editor/libeditor/html/nsHTMLEditor.h b/editor/libeditor/html/nsHTMLEditor.h index 20df24ca023a..bd9b4811f8ac 100644 --- a/editor/libeditor/html/nsHTMLEditor.h +++ b/editor/libeditor/html/nsHTMLEditor.h @@ -204,7 +204,8 @@ public: PRBool& aIsSelected); NS_IMETHOD GetFirstRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow); NS_IMETHOD GetNextRow(nsIDOMElement* aTableElement, nsIDOMElement* &aRow); - NS_IMETHOD SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection); + NS_IMETHOD SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, + PRInt32 aDirection, PRBool aSelected); NS_IMETHOD GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsString& aTagName, PRInt32 &aSelectedCount); NS_IMETHOD GetSelectedCellsType(nsIDOMElement *aElement, PRUint32 &aSelectionType); // Finds the first selected cell in first range of selection @@ -217,20 +218,9 @@ public: // aRange is optional: returns the range around the cell NS_IMETHOD GetNextSelectedCell(nsIDOMElement **aCell, nsIDOMRange **aRange); + // Upper-left-most selected cell in table + NS_IMETHOD GetFirstSelectedCellInTable(nsIDOMElement **aCell, PRInt32 *aRowIndex, PRInt32 *aColIndex); -// Selection and navigation - /* obsolete - NS_IMETHOD MoveSelectionUp(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionDown(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionNext(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD MoveSelectionPrevious(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD SelectNext(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD SelectPrevious(nsIAtom *aIncrement, PRBool aExtendSelection); - NS_IMETHOD ScrollUp(nsIAtom *aIncrement); - NS_IMETHOD ScrollDown(nsIAtom *aIncrement); - NS_IMETHOD ScrollIntoView(PRBool aScrollToBegin); - */ - /* miscellaneous */ // This sets background on the appropriate container element (table, cell,) // or calls into nsTextEditor to set the page background @@ -405,6 +395,17 @@ protected: nsIDOMNode **aCellParent, PRInt32 *aCellOffset, PRInt32 *aRowIndex, PRInt32 *aColIndex); + NS_IMETHOD GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32& aActualRowSpan, PRInt32& aActualColSpan); + + NS_IMETHOD SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aColSpanLeft, PRInt32 aColSpanRight, nsIDOMElement **aNewCell); + + NS_IMETHOD SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow, nsIDOMElement **aNewCell); + + NS_IMETHOD FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount); + // Fallback method: Call this after using ClearSelection() and you // failed to set selection to some other content in the document NS_IMETHOD SetSelectionAtDocumentStart(nsIDOMSelection *aSelection); diff --git a/editor/libeditor/html/nsTableEditor.cpp b/editor/libeditor/html/nsTableEditor.cpp index 7f1e0e06b286..aa507a6282ec 100644 --- a/editor/libeditor/html/nsTableEditor.cpp +++ b/editor/libeditor/html/nsTableEditor.cpp @@ -53,27 +53,29 @@ static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); /*************************************************************************** * stack based helper class for restoring selection after table edit */ -class nsSetCaretAfterTableEdit +class nsSetSelectionAfterTableEdit { private: nsCOMPtr mEd; nsCOMPtr mTable; - PRInt32 mCol, mRow, mDirection; + PRInt32 mCol, mRow, mDirection, mSelected; public: - nsSetCaretAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, - PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection) : + nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, + PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection, + PRBool aSelected) : mEd(do_QueryInterface(aEd)) { mTable = aTable; mRow = aRow; mCol = aCol; mDirection = aDirection; + mSelected = aSelected; } - ~nsSetCaretAfterTableEdit() + ~nsSetSelectionAfterTableEdit() { if (mEd) - mEd->SetCaretAfterTableEdit(mTable, mRow, mCol, mDirection); + mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected); } // This is needed to abort the caret reset in the destructor // when one method yields control to another @@ -108,43 +110,42 @@ nsHTMLEditor::InsertCell(nsIDOMElement *aCell, PRInt32 aRowSpan, PRInt32 aColSpa // And the parent and offsets needed to do an insert nsCOMPtr cellParent; nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent)); - if( NS_SUCCEEDED(res) && cellParent) + if (NS_FAILED(res)) return res; + if (!cellParent) return NS_ERROR_NULL_POINTER; + + + PRInt32 cellOffset; + res = GetChildOffset(aCell, cellParent, cellOffset); + if (NS_FAILED(res)) return res; + + nsCOMPtr newCell; + res = CreateElementWithDefaults(NS_ConvertASCIItoUCS2("td"), getter_AddRefs(newCell)); + if(NS_FAILED(res)) return res; + if(!newCell) return NS_ERROR_FAILURE; + + //Optional: return new cell created + if (aNewCell) { - PRInt32 cellOffset; - res = GetChildOffset(aCell, cellParent, cellOffset); - if( NS_SUCCEEDED(res)) - { - nsCOMPtr newCell; - res = CreateElementWithDefaults(NS_ConvertASCIItoUCS2("td"), getter_AddRefs(newCell)); - if(NS_FAILED(res)) return res; - if(!newCell) return NS_ERROR_FAILURE; - - //Optional: return new cell created - if (aNewCell) - { - *aNewCell = newCell.get(); - NS_ADDREF(*aNewCell); - } - if( aRowSpan > 1) - { - nsAutoString newRowSpan(aRowSpan); - // Note: Do NOT use editor txt for this - newCell->SetAttribute(NS_ConvertASCIItoUCS2("rowspan"), newRowSpan); - } - if( aColSpan > 1) - { - nsAutoString newColSpan(aColSpan); - // Note: Do NOT use editor txt for this - newCell->SetAttribute(NS_ConvertASCIItoUCS2("colspan"), newColSpan); - } - if(aAfter) cellOffset++; - - //Don't let Rules System change the selection - nsAutoTxnsConserveSelection dontChangeSelection(this); - res = nsEditor::InsertNode(newCell, cellParent, cellOffset); - } + *aNewCell = newCell.get(); + NS_ADDREF(*aNewCell); } - return res; + if( aRowSpan > 1) + { + nsAutoString newRowSpan(aRowSpan); + // Note: Do NOT use editor txt for this + newCell->SetAttribute(NS_ConvertASCIItoUCS2("rowspan"), newRowSpan); + } + if( aColSpan > 1) + { + nsAutoString newColSpan(aColSpan); + // Note: Do NOT use editor txt for this + newCell->SetAttribute(NS_ConvertASCIItoUCS2("colspan"), newColSpan); + } + if(aAfter) cellOffset++; + + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + return nsEditor::InsertNode(newCell, cellParent, cellOffset); } PRBool IsRowNode(nsCOMPtr &aNode) @@ -208,7 +209,7 @@ nsHTMLEditor::InsertTableCell(PRInt32 aNumber, PRBool aAfter) if (!curCell) return NS_ERROR_FAILURE; PRInt32 newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex; //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, PR_FALSE); //...so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -397,7 +398,7 @@ nsHTMLEditor::InsertTableColumn(PRInt32 aNumber, PRBool aAfter) if (NS_FAILED(res)) return res; //We reset caret in destructor... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE); //.. so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -528,7 +529,7 @@ nsHTMLEditor::InsertTableRow(PRInt32 aNumber, PRBool aAfter) } //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); //...so suppress Rules System selection munging nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -707,9 +708,9 @@ nsHTMLEditor::DeleteTableCell(PRInt32 aNumber) // More than 1 cell in the row // We clear the selection to avoid problems when nodes in the selection are deleted, - // The setCaret object will call SetCaretAfterTableEdit in it's destructor + // The setCaret object will call SetSelectionAfterTableEdit in it's destructor selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); nsAutoTxnsConserveSelection dontChangeSelection(this); res = DeleteNode(cell); @@ -740,7 +741,7 @@ nsHTMLEditor::DeleteTableCellContents() // We clear the selection to avoid problems when nodes in the selection are deleted, selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -870,7 +871,8 @@ nsHTMLEditor::DeleteTableColumn(PRInt32 aNumber) } else { selection->ClearSelection(); - nsSetCaretAfterTableEdit setCaret(this, table, curStartRowIndex, curStartColIndex, ePreviousColumn); + nsSetSelectionAfterTableEdit setCaret(this, table, curStartRowIndex, curStartColIndex, + ePreviousColumn, PR_FALSE); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); @@ -916,13 +918,13 @@ nsHTMLEditor::DeleteTableRow(PRInt32 aNumber) nsAutoEditBatch beginBatching(this); //We control selection resetting after the insert... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow); + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE); // Check for counts too high aNumber = PR_MIN(aNumber,(rowCount-startRowIndex)); // We clear the selection to avoid problems when nodes in the selection are deleted, - // Be sure to set it correctly later (in SetCaretAfterTableEdit)! + // Be sure to set it correctly later (in SetSelectionAfterTableEdit)! selection->ClearSelection(); // Scan through cells in row to do rowspan adjustments @@ -1320,13 +1322,16 @@ nsHTMLEditor::SplitTableCell() { nsCOMPtr table; nsCOMPtr targetCell; - PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; - PRBool isSelected; + PRInt32 startRowIndex, startColIndex, actualRowSpan, actualColSpan; nsCOMPtr cell2; - PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; - PRBool isSelected2; +// PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; +// PRBool isSelected2; - // Get cell and table at selection anchor node (and other data) + nsAutoEditBatch beginBatching(this); + //Don't let Rules System change the selection + nsAutoTxnsConserveSelection dontChangeSelection(this); + + // Get cell, table, etc. at selection anchor node nsresult res = GetCellContext(nsnull, getter_AddRefs(table), getter_AddRefs(targetCell), @@ -1336,14 +1341,113 @@ nsHTMLEditor::SplitTableCell() if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; // We need rowspan and colspan data - res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); + res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan); //TODO: FINISH THIS! return res; } +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aColSpanLeft, PRInt32 aColSpanRight, + nsIDOMElement **aNewCell) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_NULL_POINTER; + + // We can't split! + if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) + return NS_OK; + + // Reduce colspan of cell to split + res = SetColSpan(cell, aColSpanLeft); + if (NS_FAILED(res)) return res; + + // Insert new cell after using the remaining span; + return InsertCell(cell, actualRowSpan, aColSpanRight, PR_TRUE, aNewCell); +} + +NS_IMETHODIMP +nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow, + nsIDOMElement **aNewCell) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_NULL_POINTER; + + // We can't split! + if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) + return NS_OK; + + nsCOMPtr cell2; + PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; + PRBool isSelected2; + PRInt32 colIndex = startColIndex; + PRBool insertAfter = (startColIndex > 0); + + // Find a cell to insert before or after + do + { + // Search for a cell to insert before + res = GetCellDataAt(aTable, startRowIndex+aRowSpanAbove, + colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + // If we fail here, it could be because row has bad rowspan values, + // such as all cells having rowspan > 1 (Call FixRowSpan first!) + if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE; + + // 0 value results in infinite loop! + NS_ASSERTION(actualColSpan2 > 0, "ColSpan=0 in SplitCellIntoRows"); + if (startRowIndex2 == startRowIndex) + { + if (insertAfter) + { + // If cell found is AFTER desired new cell colum, + // we have multiple cells with rowspan > 1 that + // fool us into thinking we insert after... + if (startColIndex2 > startColIndex2) + { + // ... so insert before the cell we found + insertAfter = PR_FALSE; + break; + } + // New cell isn't first in row, + // so stop after we find the last cell before new cell's column + if ((startColIndex2 + actualColSpan2) >= startColIndex) break; + } + else + { + break; // Inserting before, so stop at first cell in row we want to insert into + } + } + // Skip to next available cellmap location + colIndex += actualColSpan2; + } while(PR_TRUE); + + res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, aNewCell); + if (NS_FAILED(res)) return res; + + // Reduce rowspan of cell to split + return SetRowSpan(cell, aRowSpanAbove); +} + NS_IMETHODIMP nsHTMLEditor::JoinTableCells() { @@ -1355,7 +1459,7 @@ nsHTMLEditor::JoinTableCells() PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; PRBool isSelected2; - // Get cell and table at selection anchor node (and other data) + // Get cell, table, etc. at selection anchor node nsresult res = GetCellContext(nsnull, getter_AddRefs(table), getter_AddRefs(targetCell), @@ -1364,42 +1468,234 @@ nsHTMLEditor::JoinTableCells() if (NS_FAILED(res)) return res; if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; - PRInt32 rowCount, colCount; - res = GetTableSize(table, rowCount, colCount); - if (NS_FAILED(res)) return res; - - // We need rowspan and colspan data - res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - if (NS_FAILED(res)) return res; - if(!targetCell) return NS_ERROR_NULL_POINTER; - - nsCOMPtr firstCell; - res = GetFirstSelectedCell(getter_AddRefs(firstCell), nsnull); - if (NS_FAILED(res)) return res; - - //We reset caret in destructor... - nsSetCaretAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn); nsAutoEditBatch beginBatching(this); //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); + nsCOMPtr firstCell; + PRInt32 firstRowIndex, firstColIndex; + res = GetFirstSelectedCellInTable(getter_AddRefs(firstCell), &firstRowIndex, &firstColIndex); + if (NS_FAILED(res)) return res; + + if (firstCell) { - // Merge selected cells into uppper-left-most cell + // We have selected cells: Join contiguous cells + // and just merge contents if not contiguous + + PRInt32 rowCount, colCount; + res = GetTableSize(table, rowCount, colCount); + if (NS_FAILED(res)) return res; + + // Get spans for cell we will merge into + PRInt32 firstRowSpan, firstColSpan; + res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan); + if (NS_FAILED(res)) return res; + + //We reset caret in destructor. Last param = true to select cell when done + nsSetSelectionAfterTableEdit setCaret(this, table, firstRowIndex, firstColIndex, ePreviousColumn, PR_TRUE); + + // This defines the last indexes along the "edges" + // of the contiguous block of cells, telling us + // that we can join adjacent cells to the block + // Start with same as the first values, + // then expand as we find adjacent selected cells + PRInt32 lastRowIndex = firstRowIndex; + PRInt32 lastColIndex = firstColIndex; + PRInt32 rowIndex, colIndex; + + // First pass: Determine boundaries of contiguous rectangular block + // that we will join into one cell, + // favoring adjacent cells in the same row + for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) + { + PRInt32 currentRowCount = rowCount; + // Be sure each row doesn't have rowspan errors + res = FixBadRowSpan(table, rowIndex, rowCount); + if (NS_FAILED(res)) return res; + // Adjust rowcount by number of rows we removed + lastRowIndex -= (currentRowCount-rowCount); + + PRBool cellFoundInRow = PR_FALSE; + PRBool lastRowIsSet = PR_FALSE; + PRInt32 lastColInRow = 0; + PRInt32 firstColInRow = firstColIndex; + for (colIndex = firstColIndex; colIndex < colCount; colIndex += actualColSpan2) + { + res = GetCellDataAt(table, rowIndex, colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + if (NS_FAILED(res)) return res; + + NS_ASSERTION(actualColSpan2 > 0, "JoinTableCells: ColSpan=0"); + if (isSelected2) + { + if (!cellFoundInRow) + // We've just found the first selected cell in this row + firstColInRow = colIndex; + + if(rowIndex > firstRowIndex && firstColInRow != firstColIndex) + { + // We're in at least the second row, + // but left boundary is "ragged" (not the same as 1st row's start) + //Let's just end block on previous row + // and keep previous lastColIndex + //TODO: We could try to find the Maximum firstColInRow + // so our block can still extend down more rows? + lastRowIndex = PR_MAX(0,rowIndex - 1); + lastRowIsSet = PR_TRUE; + break; + } + // Save max selected column in this row, including extra colspan + lastColInRow = colIndex + (actualColSpan2-1); + cellFoundInRow = PR_TRUE; + } + else if (cellFoundInRow) + { + // No cell or not selected, but at one in row was found + if (colIndex <= lastColIndex) + { + // Cell is in a column less than current right border, + // so stop block at the previous row + lastRowIndex = PR_MAX(0,rowIndex - 1); + lastRowIsSet = PR_TRUE; + } + // We're done with this row + break; + } + } // End of column loop + + // Done with this row + if (cellFoundInRow) + { + if (rowIndex == firstRowIndex) + { + // First row always initializes the right boundary + lastColIndex = lastColInRow; + } + + // If we didn't determine last row above... + if (!lastRowIsSet) + { + if (colIndex < lastColIndex) + { + // (don't think we ever get here?) + // Cell is in a column less than current right boundary, + // so stop block at the previous row + lastRowIndex = PR_MAX(0,rowIndex - 1); + } + else + { + // Go on to examine next row + lastRowIndex = rowIndex+1; + // Use the minimun col we found so far for right boundary + lastColIndex = PR_MIN(lastColIndex, lastColInRow); + } + } + } + else + { + // No selected cells in this row -- stop at row above + // and leave last column at its previous value + lastRowIndex = PR_MAX(0,rowIndex - 1); + } + } + + // The list of cells we will delete after joining + nsVoidArray deleteList; + + // 2nd pass: Do the joining and merging + for (rowIndex = 0; rowIndex < rowCount; rowIndex++) + { + for (colIndex = 0; colIndex < colCount; colIndex+= actualColSpan2) + { + res = GetCellDataAt(table, rowIndex, colIndex, *getter_AddRefs(cell2), + startRowIndex2, startColIndex2, rowSpan2, colSpan2, + actualRowSpan2, actualColSpan2, isSelected2); + if (NS_FAILED(res)) return res; + + // Merge only selected cells (skip cell we're merging into, of course) + if (isSelected2 && cell2 != firstCell) + { + if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex && + colIndex >= firstColIndex && colIndex <= lastColIndex) + { + // We are within the join region + // Problem: It is very tricky to delete cells as we merge, + // since that will upset the cellmap + // Instead, build a list of cells to delete and do it later + NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above"); + + res = MergeCells(firstCell, cell2, PR_FALSE); + if (NS_FAILED(res)) return res; + + //TODO: Check if cell "hangs" off the boundary because of colspan or rowspan > 1 + // Use split methods to chop off excess + + // Add cell to list to delete + deleteList.AppendElement((void *)cell2.get()); + } + else + { + // Cell is outside join region -- just merge the contents + res = MergeCells(firstCell, cell2, PR_FALSE); + if (NS_FAILED(res)) return res; + } + } + } + } + + // All cell contents are merged. Delete the empty cells we accumulated + nsIDOMElement *elementPtr; + PRInt32 count; + while ((count = deleteList.Count())) + { + // go backwards to keep nsVoidArray from memmoving everything each time + count--; // nsVoidArray is zero based + elementPtr = (nsIDOMElement*)deleteList.ElementAt(count); + deleteList.RemoveElementAt(count); + if (elementPtr) + { + nsCOMPtr node = do_QueryInterface(elementPtr); + res = DeleteNode(node); + if (NS_FAILED(res)) return res; + // Should we delete this??? + //delete elementPtr; + } + } + // Set spans for the cell everthing merged into + res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1); + if (NS_FAILED(res)) return res; + res = SetColSpan(firstCell, lastColIndex-firstColIndex+1); + if (NS_FAILED(res)) return res; } else { - // Merge with cell to the right + // Joining with cell to the right -- get rowspan and colspan data of target cell + res = GetCellDataAt(table, startRowIndex, startColIndex, *getter_AddRefs(targetCell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if (NS_FAILED(res)) return res; + if (!targetCell) return NS_ERROR_NULL_POINTER; + + //We reset caret in destructor. Select cell when done if target was already selected + nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, isSelected); + + // Get data for cell to the right res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, *getter_AddRefs(cell2), startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2, isSelected2); if (NS_FAILED(res)) return res; - if(!cell2) return NS_ERROR_NULL_POINTER; + if(!cell2) return NS_OK; // Don't fail if there's no cell + + if( actualRowSpan2 > actualRowSpan ) + { + //TODO: SPLIT CELL + } // Move contents and delete cell to the right res = MergeCells(targetCell, cell2, PR_TRUE); + if (NS_FAILED(res)) return res; // Reset target cell's spans res = SetColSpan(targetCell, actualColSpan+actualColSpan2); @@ -1462,6 +1758,57 @@ nsHTMLEditor::MergeCells(nsCOMPtr aTargetCell, return res; } + +NS_IMETHODIMP +nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount) +{ + if (!aTable) return NS_ERROR_NULL_POINTER; + + PRInt32 colCount; + nsresult res = GetTableSize(aTable, colCount, colCount); + if (NS_FAILED(res)) return res; + + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; + PRBool isSelected; + + PRInt32 minRowSpan = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32 + PRInt32 colIndex; + + for( colIndex = 0; colIndex < colCount; colIndex++) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + // NOTE: This is a *real* failure. + // GetCellDataAt passes if cell is missing from cellmap + if(NS_FAILED(res)) return res; + if(cell && rowSpan > 0 && rowSpan < minRowSpan) + minRowSpan = rowSpan; + } + if(minRowSpan > 1) + { + // The amount to reduce everyones rowspan + // so at least one cell has rowspan = 1 + PRInt32 rowsReduced = minRowSpan - 1; + for(colIndex = 0; colIndex < colCount; colIndex++) + { + res = GetCellDataAt(aTable, aRowIndex, colIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + actualRowSpan, actualColSpan, isSelected); + if(NS_FAILED(res)) return res; + // Fixup rowspans for cells starting in current row + if(cell && rowSpan > 0 && + startRowIndex == aRowIndex && + startColIndex == colIndex ) + { + return SetRowSpan(cell, rowSpan-rowsReduced); + } + } + } + return GetTableSize(aTable, aNewRowCount, colCount); +} + NS_IMETHODIMP nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) { @@ -1482,50 +1829,13 @@ nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; PRBool isSelected; -#if 0 -// This isn't working yet -- layout errors contribute! - - // First scan all cells in row to detect bad rowspan values + // First scan all cells in each row to detect bad rowspan values for(rowIndex = 0; rowIndex < rowCount; rowIndex++) { - PRInt32 minRowSpan = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32 - for(colIndex = 0; colIndex < colCount; colIndex++) - { - res = GetCellDataAt(aTable, rowIndex, colIndex, *getter_AddRefs(cell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - // NOTE: This is a *real* failure. - // GetCellDataAt passes if cell is missing from cellmap - if(NS_FAILED(res)) return res; - if(cell && rowSpan > 0 && rowSpan < minRowSpan) - minRowSpan = rowSpan; - } - if(minRowSpan > 1) - { - PRInt32 spanDiff = minRowSpan - 1; - for(colIndex = 0; colIndex < colCount; colIndex++) - { - res = GetCellDataAt(aTable, rowIndex, colIndex, *getter_AddRefs(cell), - startRowIndex, startColIndex, rowSpan, colSpan, - actualRowSpan, actualColSpan, isSelected); - // NOTE: This is a *real* failure. - // GetCellDataAt passes if cell is missing from cellmap - if(NS_FAILED(res)) return res; - // Fixup rowspans for cells starting in current row - if(cell && rowSpan > 0 && - startRowIndex == rowIndex && - startColIndex == colIndex ) - { - // Set rowspan so there's at least one cell with ROWSPAN=1 - SetRowSpan(cell, rowSpan-spanDiff); - } - } - } + + res = FixBadRowSpan(aTable, rowIndex, rowCount); + if (NS_FAILED(res)) return res; } - // Get Table size again in case above changed anything - res = GetTableSize(table, rowCount, colCount); - if (NS_FAILED(res)) return res; -#endif // Fill in missing cellmap locations with empty cells for(rowIndex = 0; rowIndex < rowCount; rowIndex++) @@ -1733,6 +2043,19 @@ nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIn actualRowSpan, actualColSpan, isSelected); } +// When all you want are the rowspan and colspan (not exposed in nsITableEditor) +NS_IMETHODIMP +nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, + PRInt32& aActualRowSpan, PRInt32& aActualColSpan) +{ + nsCOMPtr cell; + PRInt32 startRowIndex, startColIndex, rowSpan, colSpan; + PRBool isSelected; + return GetCellDataAt(aTable, aRowIndex, aColIndex, *getter_AddRefs(cell), + startRowIndex, startColIndex, rowSpan, colSpan, + aActualRowSpan, aActualColSpan, isSelected); +} + NS_IMETHODIMP nsHTMLEditor::GetCellContext(nsIDOMSelection **aSelection, nsIDOMElement **aTable, @@ -1923,8 +2246,68 @@ nsHTMLEditor::GetNextSelectedCell(nsIDOMElement **aCell, nsIDOMRange **aRange) return res; } +NS_IMETHODIMP +nsHTMLEditor::GetFirstSelectedCellInTable(nsIDOMElement **aCell, PRInt32 *aRowIndex, PRInt32 *aColIndex) +{ + if (!aCell) return NS_ERROR_NULL_POINTER; + *aCell = nsnull; + if (aRowIndex) + *aRowIndex = 0; + if (aColIndex) + *aColIndex = 0; + + nsCOMPtr cell; + nsresult res = GetFirstSelectedCell(getter_AddRefs(cell), nsnull); + if (NS_FAILED(res)) return res; + if (!cell) return NS_ERROR_FAILURE; + + PRInt32 startRowIndex, startColIndex; + res = GetCellIndexes(cell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + + // Start with first cell selected + nsCOMPtr firstCell = cell; + PRInt32 firstRowIndex = startRowIndex; + PRInt32 firstColIndex = startColIndex; + + while (cell) + { + nsresult res = GetNextSelectedCell(getter_AddRefs(cell), nsnull); + if (NS_FAILED(res)) return res; + res = GetCellIndexes(cell, startRowIndex, startColIndex); + if(NS_FAILED(res)) return res; + if (cell) + { + // Find the topmost row + if (startRowIndex <= firstRowIndex) + { + // Then save the left-most cell in that row + if (startRowIndex < firstRowIndex || + startColIndex < startColIndex) + { + firstCell = cell; + firstRowIndex = startRowIndex; + firstColIndex = startColIndex; + } + } + } + } + if (NS_SUCCEEDED(res)) + { + *aCell = firstCell.get(); + NS_ADDREF(*aCell); + + if (aRowIndex) + *aRowIndex = firstRowIndex; + if (aColIndex) + *aColIndex = firstColIndex; + } + return res; +} + NS_IMETHODIMP -nsHTMLEditor::SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection) +nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, + PRInt32 aDirection, PRBool aSelected) { nsresult res = NS_ERROR_NOT_INITIALIZED; if (!aTable) return res; @@ -1950,11 +2333,19 @@ nsHTMLEditor::SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt3 { if (cell) { - // Set the caret to deepest first child - // but don't go into nested tables - // TODO: Should we really be placing the caret at the END - // of the cell content? - return CollapseSelectionToDeepestNonTableFirstChild(selection, cellNode); + if (aSelected) + { + // Reselect the cell + return SelectElement(cell); + } + else + { + // Set the caret to deepest first child + // but don't go into nested tables + // TODO: Should we really be placing the caret at the END + // of the cell content? + return CollapseSelectionToDeepestNonTableFirstChild(selection, cellNode); + } } else { // Setup index to find another cell in the // direction requested, but move in @@ -2052,7 +2443,7 @@ nsHTMLEditor::GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsS if (tag == tdName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = tdName; // Each cell is in its own selection range, // so count signals multiple-cell selection @@ -2061,13 +2452,13 @@ nsHTMLEditor::GetSelectedOrParentTableElement(nsIDOMElement* &aTableElement, nsS } else if(tag == tableName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = tableName; aSelectedCount = 1; } else if(tag == trName) { - tableElement = do_QueryInterface(anchorNode); + tableElement = do_QueryInterface(selectedNode); aTagName = trName; aSelectedCount = 1; } diff --git a/editor/public/nsITableEditor.h b/editor/public/nsITableEditor.h index d8a5d3058467..ebdc70d78833 100644 --- a/editor/public/nsITableEditor.h +++ b/editor/public/nsITableEditor.h @@ -221,19 +221,20 @@ public: /** Preferred direction to search for neighboring cell * when trying to locate a cell to place caret in after * a table editing action. - * Used for aDirection param in SetCaretAfterTableEdit + * Used for aDirection param in SetSelectionAfterTableEdit */ enum { eNoSearch, ePreviousColumn, ePreviousRow }; - /** Reset a collapsed selection (the caret) after table editing + /** Reset a selected cell or collapsed selection (the caret) after table editing * * @param aTable A table in the document * @param aRow The row ... * @param aCol ... and column defining the cell * where we will try to place the caret + * @param aSelected If true, we select the whole cell instead of setting caret * @param aDirection If cell at (aCol, aRow) is not found, * search for previous cell in the same * column (aPreviousColumn) or row (ePreviousRow) @@ -241,10 +242,11 @@ public: * If no cell is found, caret is place just before table; * and if that fails, at beginning of document. * Thus we generally don't worry about the return value - * and can use the nsSetCaretAfterTableEdit stack-based + * and can use the nsSetSelectionAfterTableEdit stack-based * object to insure we reset the caret in a table-editing method. */ - NS_IMETHOD SetCaretAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection)=0; + NS_IMETHOD SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, + PRInt32 aDirection, PRBool aSelected)=0; /** Examine the current selection and find * a selected TABLE, TD or TH, or TR element. @@ -291,6 +293,17 @@ public: */ NS_IMETHOD GetFirstSelectedCell(nsIDOMElement **aCell, nsIDOMRange **aRange)=0; + /** Get first selected element from first selection range. + * Assumes cell-selection model where each cell + * is in a separate range (selection parent node is table row) + * @param aCell Selected cell or null if ranges don't contain cell selections + * @param aRowIndex Optional: if not null, return the row index of first cell + * @param aColIndex Optional: if not null, return the column index of first cell + * + * Returns NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found (passes NS_SUCCEEDED macro) + */ + NS_IMETHOD GetFirstSelectedCellInTable(nsIDOMElement **aCell, PRInt32 *aRowIndex, PRInt32 *aColIndex)=0; + /** Get next selected cell element from first selection range. * Assumes cell-selection model where each cell * is in a separate range (selection parent node is table row)