CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / editor / libeditor / html / nsTableEditor.cpp
blob3e82720d6a7e58a7c4ac54238afb9205eba34021
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nscore.h"
40 #include "nsIDOMDocument.h"
41 #include "nsEditor.h"
42 #include "nsIDOMText.h"
43 #include "nsIDOMElement.h"
44 #include "nsIDOMAttr.h"
45 #include "nsIDOMNode.h"
46 #include "nsIDOMNodeList.h"
47 #include "nsIDOMRange.h"
48 #include "nsIFrame.h"
49 #include "nsIPresShell.h"
50 #include "nsISelection.h"
51 #include "nsISelectionPrivate.h"
52 #include "nsLayoutCID.h"
53 #include "nsIContent.h"
54 #include "nsIContentIterator.h"
55 #include "nsIAtom.h"
56 #include "nsIDOMHTMLTableElement.h"
57 #include "nsIDOMHTMLTableCellElement.h"
58 #include "nsITableCellLayout.h" // For efficient access to table cell
59 #include "nsITableLayout.h" // data owned by the table and cell frames
60 #include "nsHTMLEditor.h"
61 #include "nsISelectionPrivate.h" // For nsISelectionPrivate::TABLESELECTION_ defines
62 #include "nsTArray.h"
64 #include "nsEditorUtils.h"
65 #include "nsTextEditUtils.h"
66 #include "nsHTMLEditUtils.h"
67 #include "nsLayoutErrors.h"
71 /***************************************************************************
72 * stack based helper class for restoring selection after table edit
74 class NS_STACK_CLASS nsSetSelectionAfterTableEdit
76 private:
77 nsCOMPtr<nsITableEditor> mEd;
78 nsCOMPtr<nsIDOMElement> mTable;
79 PRInt32 mCol, mRow, mDirection, mSelected;
80 public:
81 nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable,
82 PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection,
83 PRBool aSelected) :
84 mEd(do_QueryInterface(aEd))
86 mTable = aTable;
87 mRow = aRow;
88 mCol = aCol;
89 mDirection = aDirection;
90 mSelected = aSelected;
93 ~nsSetSelectionAfterTableEdit()
95 if (mEd)
96 mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected);
98 // This is needed to abort the caret reset in the destructor
99 // when one method yields control to another
100 void CancelSetCaret() {mEd = nsnull; mTable = nsnull;}
103 // Stack-class to turn on/off selection batching for table selection
104 class NS_STACK_CLASS nsSelectionBatcherForTable
106 private:
107 nsCOMPtr<nsISelectionPrivate> mSelection;
108 public:
109 nsSelectionBatcherForTable(nsISelection *aSelection)
111 nsCOMPtr<nsISelection> sel(aSelection);
112 mSelection = do_QueryInterface(sel);
113 if (mSelection) mSelection->StartBatchChanges();
115 virtual ~nsSelectionBatcherForTable()
117 if (mSelection) mSelection->EndBatchChanges();
121 // Table Editing helper utilities (not exposed in IDL)
123 NS_IMETHODIMP
124 nsHTMLEditor::InsertCell(nsIDOMElement *aCell, PRInt32 aRowSpan, PRInt32 aColSpan,
125 PRBool aAfter, PRBool aIsHeader, nsIDOMElement **aNewCell)
127 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
128 if (aNewCell) *aNewCell = nsnull;
130 // And the parent and offsets needed to do an insert
131 nsCOMPtr<nsIDOMNode> cellParent;
132 nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent));
133 NS_ENSURE_SUCCESS(res, res);
134 NS_ENSURE_TRUE(cellParent, NS_ERROR_NULL_POINTER);
137 PRInt32 cellOffset;
138 res = GetChildOffset(aCell, cellParent, cellOffset);
139 NS_ENSURE_SUCCESS(res, res);
141 nsCOMPtr<nsIDOMElement> newCell;
142 if (aIsHeader)
143 res = CreateElementWithDefaults(NS_LITERAL_STRING("th"), getter_AddRefs(newCell));
144 else
145 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
147 if(NS_FAILED(res)) return res;
148 if(!newCell) return NS_ERROR_FAILURE;
150 //Optional: return new cell created
151 if (aNewCell)
153 *aNewCell = newCell.get();
154 NS_ADDREF(*aNewCell);
157 if( aRowSpan > 1)
159 // Note: Do NOT use editor transaction for this
160 nsAutoString newRowSpan;
161 newRowSpan.AppendInt(aRowSpan, 10);
162 newCell->SetAttribute(NS_LITERAL_STRING("rowspan"), newRowSpan);
164 if( aColSpan > 1)
166 // Note: Do NOT use editor transaction for this
167 nsAutoString newColSpan;
168 newColSpan.AppendInt(aColSpan, 10);
169 newCell->SetAttribute(NS_LITERAL_STRING("colspan"), newColSpan);
171 if(aAfter) cellOffset++;
173 //Don't let Rules System change the selection
174 nsAutoTxnsConserveSelection dontChangeSelection(this);
175 return InsertNode(newCell, cellParent, cellOffset);
178 NS_IMETHODIMP nsHTMLEditor::SetColSpan(nsIDOMElement *aCell, PRInt32 aColSpan)
180 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
181 nsAutoString newSpan;
182 newSpan.AppendInt(aColSpan, 10);
183 return SetAttribute(aCell, NS_LITERAL_STRING("colspan"), newSpan);
186 NS_IMETHODIMP nsHTMLEditor::SetRowSpan(nsIDOMElement *aCell, PRInt32 aRowSpan)
188 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
189 nsAutoString newSpan;
190 newSpan.AppendInt(aRowSpan, 10);
191 return SetAttribute(aCell, NS_LITERAL_STRING("rowspan"), newSpan);
194 /****************************************************************/
196 // Table Editing interface methods
198 NS_IMETHODIMP
199 nsHTMLEditor::InsertTableCell(PRInt32 aNumber, PRBool aAfter)
201 nsCOMPtr<nsIDOMElement> table;
202 nsCOMPtr<nsIDOMElement> curCell;
203 nsCOMPtr<nsIDOMNode> cellParent;
204 PRInt32 cellOffset, startRowIndex, startColIndex;
205 nsresult res = GetCellContext(nsnull,
206 getter_AddRefs(table),
207 getter_AddRefs(curCell),
208 getter_AddRefs(cellParent), &cellOffset,
209 &startRowIndex, &startColIndex);
210 NS_ENSURE_SUCCESS(res, res);
211 // Don't fail if no cell found
212 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND);
214 // Get more data for current cell in row we are inserting at (we need COLSPAN)
215 PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
216 PRBool isSelected;
217 res = GetCellDataAt(table, startRowIndex, startColIndex,
218 getter_AddRefs(curCell),
219 &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
220 &actualRowSpan, &actualColSpan, &isSelected);
221 NS_ENSURE_SUCCESS(res, res);
222 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
223 PRInt32 newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex;
224 //We control selection resetting after the insert...
225 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, PR_FALSE);
226 //...so suppress Rules System selection munging
227 nsAutoTxnsConserveSelection dontChangeSelection(this);
229 PRInt32 i;
230 for (i = 0; i < aNumber; i++)
232 nsCOMPtr<nsIDOMElement> newCell;
233 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
234 if (NS_SUCCEEDED(res) && newCell)
236 if (aAfter) cellOffset++;
237 res = InsertNode(newCell, cellParent, cellOffset);
238 if(NS_FAILED(res)) break;
241 return res;
245 NS_IMETHODIMP
246 nsHTMLEditor::GetFirstRow(nsIDOMElement* aTableElement, nsIDOMNode** aRowNode)
248 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
250 *aRowNode = nsnull;
252 NS_ENSURE_TRUE(aTableElement, NS_ERROR_NULL_POINTER);
254 nsCOMPtr<nsIDOMElement> tableElement;
255 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTableElement, getter_AddRefs(tableElement));
256 NS_ENSURE_SUCCESS(res, res);
257 NS_ENSURE_TRUE(tableElement, NS_ERROR_NULL_POINTER);
259 nsCOMPtr<nsIDOMNode> tableChild;
260 res = tableElement->GetFirstChild(getter_AddRefs(tableChild));
261 NS_ENSURE_SUCCESS(res, res);
263 while (tableChild)
265 nsCOMPtr<nsIContent> content = do_QueryInterface(tableChild);
266 if (content)
268 nsIAtom *atom = content->Tag();
270 if (atom == nsEditProperty::tr)
272 // Found a row directly under <table>
273 *aRowNode = tableChild;
274 NS_ADDREF(*aRowNode);
275 return NS_OK;
277 // Look for row in one of the row container elements
278 if (atom == nsEditProperty::tbody ||
279 atom == nsEditProperty::thead ||
280 atom == nsEditProperty::tfoot)
282 nsCOMPtr<nsIDOMNode> rowNode;
283 res = tableChild->GetFirstChild(getter_AddRefs(rowNode));
284 NS_ENSURE_SUCCESS(res, res);
286 // We can encounter textnodes here -- must find a row
287 while (rowNode && !nsHTMLEditUtils::IsTableRow(rowNode))
289 nsCOMPtr<nsIDOMNode> nextNode;
290 res = rowNode->GetNextSibling(getter_AddRefs(nextNode));
291 NS_ENSURE_SUCCESS(res, res);
293 rowNode = nextNode;
295 if(rowNode)
297 *aRowNode = rowNode.get();
298 NS_ADDREF(*aRowNode);
299 return NS_OK;
303 // Here if table child was a CAPTION or COLGROUP
304 // or child of a row parent wasn't a row (bad HTML?),
305 // or first child was a textnode
306 // Look in next table child
307 nsCOMPtr<nsIDOMNode> nextChild;
308 res = tableChild->GetNextSibling(getter_AddRefs(nextChild));
309 NS_ENSURE_SUCCESS(res, res);
311 tableChild = nextChild;
313 // If here, row was not found
314 return NS_EDITOR_ELEMENT_NOT_FOUND;
317 NS_IMETHODIMP
318 nsHTMLEditor::GetNextRow(nsIDOMNode* aCurrentRowNode, nsIDOMNode **aRowNode)
320 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
322 *aRowNode = nsnull;
324 NS_ENSURE_TRUE(aCurrentRowNode, NS_ERROR_NULL_POINTER);
326 if (!nsHTMLEditUtils::IsTableRow(aCurrentRowNode))
327 return NS_ERROR_FAILURE;
329 nsCOMPtr<nsIDOMNode> nextRow;
330 nsresult res = aCurrentRowNode->GetNextSibling(getter_AddRefs(nextRow));
331 NS_ENSURE_SUCCESS(res, res);
333 nsCOMPtr<nsIDOMNode> nextNode;
335 // Skip over any textnodes here
336 while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow))
338 res = nextRow->GetNextSibling(getter_AddRefs(nextNode));
339 NS_ENSURE_SUCCESS(res, res);
341 nextRow = nextNode;
343 if(nextRow)
345 *aRowNode = nextRow.get();
346 NS_ADDREF(*aRowNode);
347 return NS_OK;
350 // No row found, search for rows in other table sections
351 nsCOMPtr<nsIDOMNode> rowParent;
352 res = aCurrentRowNode->GetParentNode(getter_AddRefs(rowParent));
353 NS_ENSURE_SUCCESS(res, res);
354 NS_ENSURE_TRUE(rowParent, NS_ERROR_NULL_POINTER);
356 nsCOMPtr<nsIDOMNode> parentSibling;
357 res = rowParent->GetNextSibling(getter_AddRefs(parentSibling));
358 NS_ENSURE_SUCCESS(res, res);
360 while (parentSibling)
362 res = parentSibling->GetFirstChild(getter_AddRefs(nextRow));
363 NS_ENSURE_SUCCESS(res, res);
365 // We can encounter textnodes here -- must find a row
366 while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow))
368 res = nextRow->GetNextSibling(getter_AddRefs(nextNode));
369 NS_ENSURE_SUCCESS(res, res);
371 nextRow = nextNode;
373 if(nextRow)
375 *aRowNode = nextRow.get();
376 NS_ADDREF(*aRowNode);
377 return NS_OK;
379 #ifdef DEBUG_cmanske
380 printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n");
381 #endif
382 // We arrive here only if a table section has no children
383 // or first child of section is not a row (bad HTML or more "_moz_text" nodes!)
384 // So look for another section sibling
385 res = parentSibling->GetNextSibling(getter_AddRefs(nextNode));
386 NS_ENSURE_SUCCESS(res, res);
388 parentSibling = nextNode;
390 // If here, row was not found
391 return NS_EDITOR_ELEMENT_NOT_FOUND;
394 NS_IMETHODIMP
395 nsHTMLEditor::GetLastCellInRow(nsIDOMNode* aRowNode, nsIDOMNode** aCellNode)
397 NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER);
399 *aCellNode = nsnull;
401 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER);
403 nsCOMPtr<nsIDOMNode> rowChild;
404 nsresult res = aRowNode->GetLastChild(getter_AddRefs(rowChild));
405 NS_ENSURE_SUCCESS(res, res);
407 while (rowChild && !nsHTMLEditUtils::IsTableCell(rowChild))
409 // Skip over textnodes
410 nsCOMPtr<nsIDOMNode> previousChild;
411 res = rowChild->GetPreviousSibling(getter_AddRefs(previousChild));
412 NS_ENSURE_SUCCESS(res, res);
414 rowChild = previousChild;
416 if (rowChild)
418 *aCellNode = rowChild.get();
419 NS_ADDREF(*aCellNode);
420 return NS_OK;
422 // If here, cell was not found
423 return NS_EDITOR_ELEMENT_NOT_FOUND;
426 NS_IMETHODIMP
427 nsHTMLEditor::InsertTableColumn(PRInt32 aNumber, PRBool aAfter)
429 nsCOMPtr<nsISelection> selection;
430 nsCOMPtr<nsIDOMElement> table;
431 nsCOMPtr<nsIDOMElement> curCell;
432 PRInt32 startRowIndex, startColIndex;
433 nsresult res = GetCellContext(getter_AddRefs(selection),
434 getter_AddRefs(table),
435 getter_AddRefs(curCell),
436 nsnull, nsnull,
437 &startRowIndex, &startColIndex);
438 NS_ENSURE_SUCCESS(res, res);
439 // Don't fail if no cell found
440 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND);
442 // Get more data for current cell (we need ROWSPAN)
443 PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
444 PRBool isSelected;
445 res = GetCellDataAt(table, startRowIndex, startColIndex,
446 getter_AddRefs(curCell),
447 &curStartRowIndex, &curStartColIndex,
448 &rowSpan, &colSpan,
449 &actualRowSpan, &actualColSpan, &isSelected);
450 NS_ENSURE_SUCCESS(res, res);
451 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
453 nsAutoEditBatch beginBatching(this);
454 // Prevent auto insertion of BR in new cell until we're done
455 nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
457 // Use column after current cell if requested
458 if (aAfter)
460 startColIndex += actualColSpan;
461 //Detect when user is adding after a COLSPAN=0 case
462 // Assume they want to stop the "0" behavior and
463 // really add a new column. Thus we set the
464 // colspan to its true value
465 if (colSpan == 0)
466 SetColSpan(curCell, actualColSpan);
469 PRInt32 rowCount, colCount, rowIndex;
470 res = GetTableSize(table, &rowCount, &colCount);
471 NS_ENSURE_SUCCESS(res, res);
473 //We reset caret in destructor...
474 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
475 //.. so suppress Rules System selection munging
476 nsAutoTxnsConserveSelection dontChangeSelection(this);
478 // If we are inserting after all existing columns
479 // Make sure table is "well formed"
480 // before appending new column
481 if (startColIndex >= colCount)
482 NormalizeTable(table);
484 nsCOMPtr<nsIDOMNode> rowNode;
485 for ( rowIndex = 0; rowIndex < rowCount; rowIndex++)
487 #ifdef DEBUG_cmanske
488 if (rowIndex == rowCount-1)
489 printf(" ***InsertTableColumn: Inserting cell at last row: %d\n", rowIndex);
490 #endif
492 if (startColIndex < colCount)
494 // We are inserting before an existing column
495 res = GetCellDataAt(table, rowIndex, startColIndex,
496 getter_AddRefs(curCell),
497 &curStartRowIndex, &curStartColIndex,
498 &rowSpan, &colSpan,
499 &actualRowSpan, &actualColSpan, &isSelected);
500 NS_ENSURE_SUCCESS(res, res);
502 // Don't fail entire process if we fail to find a cell
503 // (may fail just in particular rows with < adequate cells per row)
504 if (curCell)
506 if (curStartColIndex < startColIndex)
508 // We have a cell spanning this location
509 // Simply increase its colspan to keep table rectangular
510 // Note: we do nothing if colsSpan=0,
511 // since it should automatically span the new column
512 if (colSpan > 0)
513 SetColSpan(curCell, colSpan+aNumber);
514 } else {
515 // Simply set selection to the current cell
516 // so we can let InsertTableCell() do the work
517 // Insert a new cell before current one
518 selection->Collapse(curCell, 0);
519 res = InsertTableCell(aNumber, PR_FALSE);
522 } else {
523 // Get current row and append new cells after last cell in row
524 if(rowIndex == 0)
525 res = GetFirstRow(table.get(), getter_AddRefs(rowNode));
526 else
528 nsCOMPtr<nsIDOMNode> nextRow;
529 res = GetNextRow(rowNode.get(), getter_AddRefs(nextRow));
530 rowNode = nextRow;
532 NS_ENSURE_SUCCESS(res, res);
534 if (rowNode)
536 nsCOMPtr<nsIDOMNode> lastCell;
537 res = GetLastCellInRow(rowNode, getter_AddRefs(lastCell));
538 NS_ENSURE_SUCCESS(res, res);
539 NS_ENSURE_TRUE(lastCell, NS_ERROR_FAILURE);
541 curCell = do_QueryInterface(lastCell);
542 if (curCell)
544 // Simply add same number of cells to each row
545 // Although tempted to check cell indexes for curCell,
546 // the effects of COLSPAN>1 in some cells makes this futile!
547 // We must use NormalizeTable first to assure
548 // that there are cells in each cellmap location
549 selection->Collapse(curCell, 0);
550 res = InsertTableCell(aNumber, PR_TRUE);
555 return res;
558 NS_IMETHODIMP
559 nsHTMLEditor::InsertTableRow(PRInt32 aNumber, PRBool aAfter)
561 nsCOMPtr<nsISelection> selection;
562 nsCOMPtr<nsIDOMElement> table;
563 nsCOMPtr<nsIDOMElement> curCell;
565 PRInt32 startRowIndex, startColIndex;
566 nsresult res = GetCellContext(nsnull,
567 getter_AddRefs(table),
568 getter_AddRefs(curCell),
569 nsnull, nsnull,
570 &startRowIndex, &startColIndex);
571 NS_ENSURE_SUCCESS(res, res);
572 // Don't fail if no cell found
573 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND);
575 // Get more data for current cell in row we are inserting at (we need COLSPAN)
576 PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
577 PRBool isSelected;
578 res = GetCellDataAt(table, startRowIndex, startColIndex,
579 getter_AddRefs(curCell),
580 &curStartRowIndex, &curStartColIndex,
581 &rowSpan, &colSpan,
582 &actualRowSpan, &actualColSpan, &isSelected);
583 NS_ENSURE_SUCCESS(res, res);
584 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE);
586 PRInt32 rowCount, colCount;
587 res = GetTableSize(table, &rowCount, &colCount);
588 NS_ENSURE_SUCCESS(res, res);
590 nsAutoEditBatch beginBatching(this);
591 // Prevent auto insertion of BR in new cell until we're done
592 nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
594 if (aAfter)
596 // Use row after current cell
597 startRowIndex += actualRowSpan;
599 //Detect when user is adding after a ROWSPAN=0 case
600 // Assume they want to stop the "0" behavior and
601 // really add a new row. Thus we set the
602 // rowspan to its true value
603 if (rowSpan == 0)
604 SetRowSpan(curCell, actualRowSpan);
607 //We control selection resetting after the insert...
608 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
609 //...so suppress Rules System selection munging
610 nsAutoTxnsConserveSelection dontChangeSelection(this);
612 nsCOMPtr<nsIDOMElement> cellForRowParent;
613 PRInt32 cellsInRow = 0;
614 if (startRowIndex < rowCount)
616 // We are inserting above an existing row
617 // Get each cell in the insert row to adjust for COLSPAN effects while we
618 // count how many cells are needed
619 PRInt32 colIndex = 0;
620 // This returns NS_TABLELAYOUT_CELL_NOT_FOUND when we run past end of row,
621 // which passes the NS_SUCCEEDED macro
622 while ( NS_OK == GetCellDataAt(table, startRowIndex, colIndex,
623 getter_AddRefs(curCell),
624 &curStartRowIndex, &curStartColIndex,
625 &rowSpan, &colSpan,
626 &actualRowSpan, &actualColSpan,
627 &isSelected) )
629 if (curCell)
631 if (curStartRowIndex < startRowIndex)
633 // We have a cell spanning this location
634 // Simply increase its rowspan
635 //Note that if rowSpan == 0, we do nothing,
636 // since that cell should automatically extend into the new row
637 if (rowSpan > 0)
638 SetRowSpan(curCell, rowSpan+aNumber);
639 } else {
640 // We have a cell in the insert row
642 // Count the number of cells we need to add to the new row
643 cellsInRow += actualColSpan;
645 // Save cell we will use below
646 if (!cellForRowParent)
647 cellForRowParent = curCell;
649 // Next cell in row
650 colIndex += actualColSpan;
652 else
653 colIndex++;
655 } else {
656 // We are adding a new row after all others
657 // If it weren't for colspan=0 effect,
658 // we could simply use colCount for number of new cells...
659 cellsInRow = colCount;
661 // ...but we must compensate for all cells with rowSpan = 0 in the last row
662 PRInt32 lastRow = rowCount-1;
663 PRInt32 tempColIndex = 0;
664 while ( NS_OK == GetCellDataAt(table, lastRow, tempColIndex,
665 getter_AddRefs(curCell),
666 &curStartRowIndex, &curStartColIndex,
667 &rowSpan, &colSpan,
668 &actualRowSpan, &actualColSpan,
669 &isSelected) )
671 if (rowSpan == 0)
672 cellsInRow -= actualColSpan;
674 tempColIndex += actualColSpan;
676 // Save cell from the last row that we will use below
677 if (!cellForRowParent && curStartRowIndex == lastRow)
678 cellForRowParent = curCell;
682 if (cellsInRow > 0)
684 // The row parent and offset where we will insert new row
685 nsCOMPtr<nsIDOMNode> parentOfRow;
686 PRInt32 newRowOffset;
688 NS_NAMED_LITERAL_STRING(trStr, "tr");
689 if (cellForRowParent)
691 nsCOMPtr<nsIDOMElement> parentRow;
692 res = GetElementOrParentByTagName(trStr, cellForRowParent, getter_AddRefs(parentRow));
693 NS_ENSURE_SUCCESS(res, res);
694 NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER);
696 parentRow->GetParentNode(getter_AddRefs(parentOfRow));
697 NS_ENSURE_TRUE(parentOfRow, NS_ERROR_NULL_POINTER);
699 res = GetChildOffset(parentRow, parentOfRow, newRowOffset);
700 NS_ENSURE_SUCCESS(res, res);
702 // Adjust for when adding past the end
703 if (aAfter && startRowIndex >= rowCount)
704 newRowOffset++;
706 else
707 return NS_ERROR_FAILURE;
709 for (PRInt32 row = 0; row < aNumber; row++)
711 // Create a new row
712 nsCOMPtr<nsIDOMElement> newRow;
713 res = CreateElementWithDefaults(trStr, getter_AddRefs(newRow));
714 if (NS_SUCCEEDED(res))
716 NS_ENSURE_TRUE(newRow, NS_ERROR_FAILURE);
718 for (PRInt32 i = 0; i < cellsInRow; i++)
720 nsCOMPtr<nsIDOMElement> newCell;
721 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
722 NS_ENSURE_SUCCESS(res, res);
723 NS_ENSURE_TRUE(newCell, NS_ERROR_FAILURE);
725 // Don't use transaction system yet! (not until entire row is inserted)
726 nsCOMPtr<nsIDOMNode>resultNode;
727 res = newRow->AppendChild(newCell, getter_AddRefs(resultNode));
728 NS_ENSURE_SUCCESS(res, res);
730 // Use transaction system to insert the entire row+cells
731 // (Note that rows are inserted at same childoffset each time)
732 res = InsertNode(newRow, parentOfRow, newRowOffset);
733 NS_ENSURE_SUCCESS(res, res);
737 return res;
740 // Editor helper only
741 // XXX Code changed for bug 217717 and now we don't need aSelection param
742 // TODO: Remove aSelection param
743 NS_IMETHODIMP
744 nsHTMLEditor::DeleteTable2(nsIDOMElement *aTable, nsISelection *aSelection)
746 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
748 // Select the table
749 nsresult res = ClearSelection();
750 if (NS_SUCCEEDED(res))
751 res = AppendNodeToSelectionAsRange(aTable);
752 NS_ENSURE_SUCCESS(res, res);
754 return DeleteSelection(nsIEditor::eNext);
757 NS_IMETHODIMP
758 nsHTMLEditor::DeleteTable()
760 nsCOMPtr<nsISelection> selection;
761 nsCOMPtr<nsIDOMElement> table;
762 nsresult res = GetCellContext(getter_AddRefs(selection),
763 getter_AddRefs(table),
764 nsnull, nsnull, nsnull, nsnull, nsnull);
766 NS_ENSURE_SUCCESS(res, res);
768 nsAutoEditBatch beginBatching(this);
769 return DeleteTable2(table, selection);
772 NS_IMETHODIMP
773 nsHTMLEditor::DeleteTableCell(PRInt32 aNumber)
775 nsCOMPtr<nsISelection> selection;
776 nsCOMPtr<nsIDOMElement> table;
777 nsCOMPtr<nsIDOMElement> cell;
778 PRInt32 startRowIndex, startColIndex;
781 nsresult res = GetCellContext(getter_AddRefs(selection),
782 getter_AddRefs(table),
783 getter_AddRefs(cell),
784 nsnull, nsnull,
785 &startRowIndex, &startColIndex);
787 NS_ENSURE_SUCCESS(res, res);
788 // Don't fail if we didn't find a table or cell
789 NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND);
791 nsAutoEditBatch beginBatching(this);
792 // Prevent rules testing until we're done
793 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
795 nsCOMPtr<nsIDOMElement> firstCell;
796 nsCOMPtr<nsIDOMRange> range;
797 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
798 NS_ENSURE_SUCCESS(res, res);
800 PRInt32 rangeCount;
801 res = selection->GetRangeCount(&rangeCount);
802 NS_ENSURE_SUCCESS(res, res);
804 if (firstCell && rangeCount > 1)
806 // When > 1 selected cell,
807 // ignore aNumber and use selected cells
808 cell = firstCell;
810 PRInt32 rowCount, colCount;
811 res = GetTableSize(table, &rowCount, &colCount);
812 NS_ENSURE_SUCCESS(res, res);
814 // Get indexes -- may be different than original cell
815 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
816 NS_ENSURE_SUCCESS(res, res);
818 // The setCaret object will call SetSelectionAfterTableEdit in it's destructor
819 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
820 nsAutoTxnsConserveSelection dontChangeSelection(this);
822 PRBool checkToDeleteRow = PR_TRUE;
823 PRBool checkToDeleteColumn = PR_TRUE;
824 while (cell)
826 PRBool deleteRow = PR_FALSE;
827 PRBool deleteCol = PR_FALSE;
829 if (checkToDeleteRow)
831 // Optimize to delete an entire row
832 // Clear so we don't repeat AllCellsInRowSelected within the same row
833 checkToDeleteRow = PR_FALSE;
835 deleteRow = AllCellsInRowSelected(table, startRowIndex, colCount);
836 if (deleteRow)
838 // First, find the next cell in a different row
839 // to continue after we delete this row
840 PRInt32 nextRow = startRowIndex;
841 while (nextRow == startRowIndex)
843 res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
844 NS_ENSURE_SUCCESS(res, res);
845 if (!cell) break;
846 res = GetCellIndexes(cell, &nextRow, &startColIndex);
847 NS_ENSURE_SUCCESS(res, res);
849 // Delete entire row
850 res = DeleteRow(table, startRowIndex);
851 NS_ENSURE_SUCCESS(res, res);
853 if (cell)
855 // For the next cell: Subtract 1 for row we deleted
856 startRowIndex = nextRow - 1;
857 // Set true since we know we will look at a new row next
858 checkToDeleteRow = PR_TRUE;
862 if (!deleteRow)
864 if (checkToDeleteColumn)
866 // Optimize to delete an entire column
867 // Clear this so we don't repeat AllCellsInColSelected within the same Col
868 checkToDeleteColumn = PR_FALSE;
870 deleteCol = AllCellsInColumnSelected(table, startColIndex, colCount);
871 if (deleteCol)
873 // First, find the next cell in a different column
874 // to continue after we delete this column
875 PRInt32 nextCol = startColIndex;
876 while (nextCol == startColIndex)
878 res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
879 NS_ENSURE_SUCCESS(res, res);
880 if (!cell) break;
881 res = GetCellIndexes(cell, &startRowIndex, &nextCol);
882 NS_ENSURE_SUCCESS(res, res);
884 // Delete entire Col
885 res = DeleteColumn(table, startColIndex);
886 NS_ENSURE_SUCCESS(res, res);
887 if (cell)
889 // For the next cell, subtract 1 for col. deleted
890 startColIndex = nextCol - 1;
891 // Set true since we know we will look at a new column next
892 checkToDeleteColumn = PR_TRUE;
896 if (!deleteCol)
898 // First get the next cell to delete
899 nsCOMPtr<nsIDOMElement> nextCell;
900 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(nextCell));
901 NS_ENSURE_SUCCESS(res, res);
903 // Then delete the cell
904 res = DeleteNode(cell);
905 NS_ENSURE_SUCCESS(res, res);
907 // The next cell to delete
908 cell = nextCell;
909 if (cell)
911 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
912 NS_ENSURE_SUCCESS(res, res);
918 else for (PRInt32 i = 0; i < aNumber; i++)
920 res = GetCellContext(getter_AddRefs(selection),
921 getter_AddRefs(table),
922 getter_AddRefs(cell),
923 nsnull, nsnull,
924 &startRowIndex, &startColIndex);
925 NS_ENSURE_SUCCESS(res, res);
926 // Don't fail if no cell found
927 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
929 if (1 == GetNumberOfCellsInRow(table, startRowIndex))
931 nsCOMPtr<nsIDOMElement> parentRow;
932 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow));
933 NS_ENSURE_SUCCESS(res, res);
934 NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER);
936 // We should delete the row instead,
937 // but first check if its the only row left
938 // so we can delete the entire table
939 PRInt32 rowCount, colCount;
940 res = GetTableSize(table, &rowCount, &colCount);
941 NS_ENSURE_SUCCESS(res, res);
943 if (rowCount == 1)
944 return DeleteTable2(table, selection);
946 // We need to call DeleteTableRow to handle cells with rowspan
947 res = DeleteTableRow(1);
948 NS_ENSURE_SUCCESS(res, res);
950 else
952 // More than 1 cell in the row
954 // The setCaret object will call SetSelectionAfterTableEdit in it's destructor
955 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
956 nsAutoTxnsConserveSelection dontChangeSelection(this);
958 res = DeleteNode(cell);
959 // If we fail, don't try to delete any more cells???
960 NS_ENSURE_SUCCESS(res, res);
963 return NS_OK;
966 NS_IMETHODIMP
967 nsHTMLEditor::DeleteTableCellContents()
969 nsCOMPtr<nsISelection> selection;
970 nsCOMPtr<nsIDOMElement> table;
971 nsCOMPtr<nsIDOMElement> cell;
972 PRInt32 startRowIndex, startColIndex;
973 nsresult res;
974 res = GetCellContext(getter_AddRefs(selection),
975 getter_AddRefs(table),
976 getter_AddRefs(cell),
977 nsnull, nsnull,
978 &startRowIndex, &startColIndex);
979 NS_ENSURE_SUCCESS(res, res);
980 // Don't fail if no cell found
981 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
984 nsAutoEditBatch beginBatching(this);
985 // Prevent rules testing until we're done
986 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
987 //Don't let Rules System change the selection
988 nsAutoTxnsConserveSelection dontChangeSelection(this);
991 nsCOMPtr<nsIDOMElement> firstCell;
992 nsCOMPtr<nsIDOMRange> range;
993 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
994 NS_ENSURE_SUCCESS(res, res);
997 if (firstCell)
999 cell = firstCell;
1000 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
1001 NS_ENSURE_SUCCESS(res, res);
1004 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
1006 while (cell)
1008 DeleteCellContents(cell);
1009 if (firstCell)
1011 // We doing a selected cells, so do all of them
1012 res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
1013 NS_ENSURE_SUCCESS(res, res);
1015 else
1016 cell = nsnull;
1018 return NS_OK;
1021 NS_IMETHODIMP
1022 nsHTMLEditor::DeleteCellContents(nsIDOMElement *aCell)
1024 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
1026 // Prevent rules testing until we're done
1027 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
1029 nsCOMPtr<nsIDOMNode> child;
1030 PRBool hasChild;
1031 aCell->HasChildNodes(&hasChild);
1033 while (hasChild)
1035 aCell->GetLastChild(getter_AddRefs(child));
1036 nsresult res = DeleteNode(child);
1037 NS_ENSURE_SUCCESS(res, res);
1038 aCell->HasChildNodes(&hasChild);
1040 return NS_OK;
1043 NS_IMETHODIMP
1044 nsHTMLEditor::DeleteTableColumn(PRInt32 aNumber)
1046 nsCOMPtr<nsISelection> selection;
1047 nsCOMPtr<nsIDOMElement> table;
1048 nsCOMPtr<nsIDOMElement> cell;
1049 PRInt32 startRowIndex, startColIndex, rowCount, colCount;
1050 nsresult res = GetCellContext(getter_AddRefs(selection),
1051 getter_AddRefs(table),
1052 getter_AddRefs(cell),
1053 nsnull, nsnull,
1054 &startRowIndex, &startColIndex);
1055 NS_ENSURE_SUCCESS(res, res);
1056 // Don't fail if no cell found
1057 NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1059 res = GetTableSize(table, &rowCount, &colCount);
1060 NS_ENSURE_SUCCESS(res, res);
1062 // Shortcut the case of deleting all columns in table
1063 if(startColIndex == 0 && aNumber >= colCount)
1064 return DeleteTable2(table, selection);
1066 // Check for counts too high
1067 aNumber = NS_MIN(aNumber,(colCount-startColIndex));
1069 nsAutoEditBatch beginBatching(this);
1070 // Prevent rules testing until we're done
1071 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
1073 // Test if deletion is controlled by selected cells
1074 nsCOMPtr<nsIDOMElement> firstCell;
1075 nsCOMPtr<nsIDOMRange> range;
1076 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
1077 NS_ENSURE_SUCCESS(res, res);
1079 PRInt32 rangeCount;
1080 res = selection->GetRangeCount(&rangeCount);
1081 NS_ENSURE_SUCCESS(res, res);
1083 if (firstCell && rangeCount > 1)
1085 // Fetch indexes again - may be different for selected cells
1086 res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
1087 NS_ENSURE_SUCCESS(res, res);
1089 //We control selection resetting after the insert...
1090 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
1092 if (firstCell && rangeCount > 1)
1094 // Use selected cells to determine what rows to delete
1095 cell = firstCell;
1097 while (cell)
1099 if (cell != firstCell)
1101 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
1102 NS_ENSURE_SUCCESS(res, res);
1104 // Find the next cell in a different column
1105 // to continue after we delete this column
1106 PRInt32 nextCol = startColIndex;
1107 while (nextCol == startColIndex)
1109 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1110 NS_ENSURE_SUCCESS(res, res);
1111 if (!cell) break;
1112 res = GetCellIndexes(cell, &startRowIndex, &nextCol);
1113 NS_ENSURE_SUCCESS(res, res);
1115 res = DeleteColumn(table, startColIndex);
1116 NS_ENSURE_SUCCESS(res, res);
1119 else for (PRInt32 i = 0; i < aNumber; i++)
1121 res = DeleteColumn(table, startColIndex);
1122 NS_ENSURE_SUCCESS(res, res);
1124 return NS_OK;
1127 NS_IMETHODIMP
1128 nsHTMLEditor::DeleteColumn(nsIDOMElement *aTable, PRInt32 aColIndex)
1130 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1132 nsCOMPtr<nsIDOMElement> cell;
1133 nsCOMPtr<nsIDOMElement> cellInDeleteCol;
1134 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
1135 PRBool isSelected;
1136 PRInt32 rowIndex = 0;
1137 nsresult res = NS_OK;
1139 do {
1140 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
1141 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1142 &actualRowSpan, &actualColSpan, &isSelected);
1144 NS_ENSURE_SUCCESS(res, res);
1146 if (cell)
1148 // Find cells that don't start in column we are deleting
1149 if (startColIndex < aColIndex || colSpan > 1 || colSpan == 0)
1151 // We have a cell spanning this location
1152 // Decrease its colspan to keep table rectangular,
1153 // but if colSpan=0, it will adjust automatically
1154 if (colSpan > 0)
1156 NS_ASSERTION((colSpan > 1),"Bad COLSPAN in DeleteTableColumn");
1157 SetColSpan(cell, colSpan-1);
1159 if (startColIndex == aColIndex)
1161 // Cell is in column to be deleted, but must have colspan > 1,
1162 // so delete contents of cell instead of cell itself
1163 // (We must have reset colspan above)
1164 DeleteCellContents(cell);
1166 // To next cell in column
1167 rowIndex += actualRowSpan;
1169 else
1171 // Delete the cell
1172 if (1 == GetNumberOfCellsInRow(aTable, rowIndex))
1174 // Only 1 cell in row - delete the row
1175 nsCOMPtr<nsIDOMElement> parentRow;
1176 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow));
1177 NS_ENSURE_SUCCESS(res, res);
1178 if(!parentRow) return NS_ERROR_NULL_POINTER;
1180 // But first check if its the only row left
1181 // so we can delete the entire table
1182 // (This should never happen but it's the safe thing to do)
1183 PRInt32 rowCount, colCount;
1184 res = GetTableSize(aTable, &rowCount, &colCount);
1185 NS_ENSURE_SUCCESS(res, res);
1187 if (rowCount == 1)
1189 nsCOMPtr<nsISelection> selection;
1190 res = GetSelection(getter_AddRefs(selection));
1191 NS_ENSURE_SUCCESS(res, res);
1192 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1193 return DeleteTable2(aTable, selection);
1196 // Delete the row by placing caret in cell we were to delete
1197 // We need to call DeleteTableRow to handle cells with rowspan
1198 res = DeleteRow(aTable, startRowIndex);
1199 NS_ENSURE_SUCCESS(res, res);
1201 // Note that we don't incremenet rowIndex
1202 // since a row was deleted and "next"
1203 // row now has current rowIndex
1205 else
1207 // A more "normal" deletion
1208 res = DeleteNode(cell);
1209 NS_ENSURE_SUCCESS(res, res);
1211 //Skip over any rows spanned by this cell
1212 rowIndex += actualRowSpan;
1216 } while (cell);
1218 return NS_OK;
1221 NS_IMETHODIMP
1222 nsHTMLEditor::DeleteTableRow(PRInt32 aNumber)
1224 nsCOMPtr<nsISelection> selection;
1225 nsCOMPtr<nsIDOMElement> table;
1226 nsCOMPtr<nsIDOMElement> cell;
1227 PRInt32 startRowIndex, startColIndex;
1228 PRInt32 rowCount, colCount;
1229 nsresult res = GetCellContext(getter_AddRefs(selection),
1230 getter_AddRefs(table),
1231 getter_AddRefs(cell),
1232 nsnull, nsnull,
1233 &startRowIndex, &startColIndex);
1234 NS_ENSURE_SUCCESS(res, res);
1235 // Don't fail if no cell found
1236 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1238 res = GetTableSize(table, &rowCount, &colCount);
1239 NS_ENSURE_SUCCESS(res, res);
1241 // Shortcut the case of deleting all rows in table
1242 if(startRowIndex == 0 && aNumber >= rowCount)
1243 return DeleteTable2(table, selection);
1245 nsAutoEditBatch beginBatching(this);
1246 // Prevent rules testing until we're done
1247 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
1249 nsCOMPtr<nsIDOMElement> firstCell;
1250 nsCOMPtr<nsIDOMRange> range;
1251 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
1252 NS_ENSURE_SUCCESS(res, res);
1254 PRInt32 rangeCount;
1255 res = selection->GetRangeCount(&rangeCount);
1256 NS_ENSURE_SUCCESS(res, res);
1258 if (firstCell && rangeCount > 1)
1260 // Fetch indexes again - may be different for selected cells
1261 res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
1262 NS_ENSURE_SUCCESS(res, res);
1265 //We control selection resetting after the insert...
1266 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
1267 // Don't change selection during deletions
1268 nsAutoTxnsConserveSelection dontChangeSelection(this);
1270 if (firstCell && rangeCount > 1)
1272 // Use selected cells to determine what rows to delete
1273 cell = firstCell;
1275 while (cell)
1277 if (cell != firstCell)
1279 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
1280 NS_ENSURE_SUCCESS(res, res);
1282 // Find the next cell in a different row
1283 // to continue after we delete this row
1284 PRInt32 nextRow = startRowIndex;
1285 while (nextRow == startRowIndex)
1287 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1288 NS_ENSURE_SUCCESS(res, res);
1289 if (!cell) break;
1290 res = GetCellIndexes(cell, &nextRow, &startColIndex);
1291 NS_ENSURE_SUCCESS(res, res);
1293 // Delete entire row
1294 res = DeleteRow(table, startRowIndex);
1295 NS_ENSURE_SUCCESS(res, res);
1298 else
1300 // Check for counts too high
1301 aNumber = NS_MIN(aNumber,(rowCount-startRowIndex));
1303 for (PRInt32 i = 0; i < aNumber; i++)
1305 res = DeleteRow(table, startRowIndex);
1306 // If failed in current row, try the next
1307 if (NS_FAILED(res))
1308 startRowIndex++;
1310 // Check if there's a cell in the "next" row
1311 res = GetCellAt(table, startRowIndex, startColIndex, getter_AddRefs(cell));
1312 NS_ENSURE_SUCCESS(res, res);
1313 if(!cell)
1314 break;
1317 return NS_OK;
1320 // Helper that doesn't batch or change the selection
1321 NS_IMETHODIMP
1322 nsHTMLEditor::DeleteRow(nsIDOMElement *aTable, PRInt32 aRowIndex)
1324 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1326 nsCOMPtr<nsIDOMElement> cell;
1327 nsCOMPtr<nsIDOMElement> cellInDeleteRow;
1328 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
1329 PRBool isSelected;
1330 PRInt32 colIndex = 0;
1331 nsresult res = NS_OK;
1333 // Prevent rules testing until we're done
1334 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
1336 // The list of cells we will change rowspan in
1337 // and the new rowspan values for each
1338 nsTArray<nsIDOMElement*> spanCellList;
1339 nsTArray<PRInt32> newSpanList;
1341 // Scan through cells in row to do rowspan adjustments
1342 // Note that after we delete row, startRowIndex will point to the
1343 // cells in the next row to be deleted
1344 do {
1345 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
1346 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
1347 &actualRowSpan, &actualColSpan, &isSelected);
1349 // We don't fail if we don't find a cell, so this must be real bad
1350 if(NS_FAILED(res)) return res;
1352 // Compensate for cells that don't start or extend below the row we are deleting
1353 if (cell)
1355 if (startRowIndex < aRowIndex)
1357 // Cell starts in row above us
1358 // Decrease its rowspan to keep table rectangular
1359 // but we don't need to do this if rowspan=0,
1360 // since it will automatically adjust
1361 if (rowSpan > 0)
1363 // Build list of cells to change rowspan
1364 // We can't do it now since it upsets cell map,
1365 // so we will do it after deleting the row
1366 spanCellList.AppendElement(cell);
1367 newSpanList.AppendElement(NS_MAX((aRowIndex - startRowIndex), actualRowSpan-1));
1370 else
1372 if (rowSpan > 1)
1374 //Cell spans below row to delete,
1375 // so we must insert new cells to keep rows below even
1376 // Note that we test "rowSpan" so we don't do this if rowSpan = 0 (automatic readjustment)
1377 res = SplitCellIntoRows(aTable, startRowIndex, startColIndex,
1378 aRowIndex - startRowIndex + 1, // The row above the row to insert new cell into
1379 actualRowSpan - 1, nsnull); // Span remaining below
1380 NS_ENSURE_SUCCESS(res, res);
1382 if (!cellInDeleteRow)
1383 cellInDeleteRow = cell; // Reference cell to find row to delete
1385 // Skip over other columns spanned by this cell
1386 colIndex += actualColSpan;
1388 } while (cell);
1390 // Things are messed up if we didn't find a cell in the row!
1391 NS_ENSURE_TRUE(cellInDeleteRow, NS_ERROR_FAILURE);
1393 // Delete the entire row
1394 nsCOMPtr<nsIDOMElement> parentRow;
1395 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cellInDeleteRow, getter_AddRefs(parentRow));
1396 NS_ENSURE_SUCCESS(res, res);
1398 if (parentRow)
1400 res = DeleteNode(parentRow);
1401 NS_ENSURE_SUCCESS(res, res);
1404 // Now we can set new rowspans for cells stored above
1405 for (PRUint32 i = 0, n = spanCellList.Length(); i < n; i++)
1407 nsIDOMElement *cellPtr = spanCellList[i];
1408 if (cellPtr)
1410 res = SetRowSpan(cellPtr, newSpanList[i]);
1411 NS_ENSURE_SUCCESS(res, res);
1414 return NS_OK;
1418 NS_IMETHODIMP
1419 nsHTMLEditor::SelectTable()
1421 nsCOMPtr<nsIDOMElement> table;
1422 nsresult res = NS_ERROR_FAILURE;
1423 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nsnull, getter_AddRefs(table));
1424 NS_ENSURE_SUCCESS(res, res);
1425 // Don't fail if we didn't find a table
1426 NS_ENSURE_TRUE(table, NS_OK);
1428 res = ClearSelection();
1429 if (NS_SUCCEEDED(res))
1430 res = AppendNodeToSelectionAsRange(table);
1432 return res;
1435 NS_IMETHODIMP
1436 nsHTMLEditor::SelectTableCell()
1438 nsCOMPtr<nsIDOMElement> cell;
1439 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
1440 NS_ENSURE_SUCCESS(res, res);
1441 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1443 res = ClearSelection();
1444 if (NS_SUCCEEDED(res))
1445 res = AppendNodeToSelectionAsRange(cell);
1447 return res;
1450 NS_IMETHODIMP
1451 nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell)
1453 NS_ENSURE_TRUE(aStartCell && aEndCell, NS_ERROR_NULL_POINTER);
1455 nsCOMPtr<nsISelection> selection;
1456 nsresult res = GetSelection(getter_AddRefs(selection));
1457 NS_ENSURE_SUCCESS(res, res);
1458 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1460 NS_NAMED_LITERAL_STRING(tableStr, "table");
1461 nsCOMPtr<nsIDOMElement> table;
1462 res = GetElementOrParentByTagName(tableStr, aStartCell, getter_AddRefs(table));
1463 NS_ENSURE_SUCCESS(res, res);
1464 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1466 nsCOMPtr<nsIDOMElement> endTable;
1467 res = GetElementOrParentByTagName(tableStr, aEndCell, getter_AddRefs(endTable));
1468 NS_ENSURE_SUCCESS(res, res);
1469 NS_ENSURE_TRUE(endTable, NS_ERROR_FAILURE);
1471 // We can only select a block if within the same table,
1472 // so do nothing if not within one table
1473 if (table != endTable) return NS_OK;
1475 PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex;
1477 // Get starting and ending cells' location in the cellmap
1478 res = GetCellIndexes(aStartCell, &startRowIndex, &startColIndex);
1479 if(NS_FAILED(res)) return res;
1481 res = GetCellIndexes(aEndCell, &endRowIndex, &endColIndex);
1482 if(NS_FAILED(res)) return res;
1484 // Suppress nsISelectionListener notification
1485 // until all selection changes are finished
1486 nsSelectionBatcherForTable selectionBatcher(selection);
1488 // Examine all cell nodes in current selection and
1489 // remove those outside the new block cell region
1490 PRInt32 minColumn = NS_MIN(startColIndex, endColIndex);
1491 PRInt32 minRow = NS_MIN(startRowIndex, endRowIndex);
1492 PRInt32 maxColumn = NS_MAX(startColIndex, endColIndex);
1493 PRInt32 maxRow = NS_MAX(startRowIndex, endRowIndex);
1495 nsCOMPtr<nsIDOMElement> cell;
1496 PRInt32 currentRowIndex, currentColIndex;
1497 nsCOMPtr<nsIDOMRange> range;
1498 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1499 NS_ENSURE_SUCCESS(res, res);
1500 if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK;
1502 while (cell)
1504 res = GetCellIndexes(cell, &currentRowIndex, &currentColIndex);
1505 NS_ENSURE_SUCCESS(res, res);
1507 if (currentRowIndex < maxRow || currentRowIndex > maxRow ||
1508 currentColIndex < maxColumn || currentColIndex > maxColumn)
1510 selection->RemoveRange(range);
1511 // Since we've removed the range, decrement pointer to next range
1512 mSelectedCellIndex--;
1514 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
1515 NS_ENSURE_SUCCESS(res, res);
1518 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan;
1519 PRBool isSelected;
1520 for (PRInt32 row = minRow; row <= maxRow; row++)
1522 for(PRInt32 col = minColumn; col <= maxColumn; col += NS_MAX(actualColSpan, 1))
1524 res = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1525 &currentRowIndex, &currentColIndex,
1526 &rowSpan, &colSpan,
1527 &actualRowSpan, &actualColSpan, &isSelected);
1528 if (NS_FAILED(res)) break;
1529 // Skip cells that already selected or are spanned from previous locations
1530 if (!isSelected && cell && row == currentRowIndex && col == currentColIndex)
1532 res = AppendNodeToSelectionAsRange(cell);
1533 if (NS_FAILED(res)) break;
1537 return res;
1540 NS_IMETHODIMP
1541 nsHTMLEditor::SelectAllTableCells()
1543 nsCOMPtr<nsIDOMElement> cell;
1544 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
1545 NS_ENSURE_SUCCESS(res, res);
1547 // Don't fail if we didn't find a cell
1548 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1550 nsCOMPtr<nsIDOMElement> startCell = cell;
1552 // Get parent table
1553 nsCOMPtr<nsIDOMElement> table;
1554 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table));
1555 NS_ENSURE_SUCCESS(res, res);
1556 if(!table) return NS_ERROR_NULL_POINTER;
1558 PRInt32 rowCount, colCount;
1559 res = GetTableSize(table, &rowCount, &colCount);
1560 NS_ENSURE_SUCCESS(res, res);
1562 nsCOMPtr<nsISelection> selection;
1563 res = GetSelection(getter_AddRefs(selection));
1564 NS_ENSURE_SUCCESS(res, res);
1565 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1567 // Suppress nsISelectionListener notification
1568 // until all selection changes are finished
1569 nsSelectionBatcherForTable selectionBatcher(selection);
1571 // It is now safe to clear the selection
1572 // BE SURE TO RESET IT BEFORE LEAVING!
1573 res = ClearSelection();
1575 // Select all cells in the same column as current cell
1576 PRBool cellSelected = PR_FALSE;
1577 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
1578 PRBool isSelected;
1579 for(PRInt32 row = 0; row < rowCount; row++)
1581 for(PRInt32 col = 0; col < colCount; col += NS_MAX(actualColSpan, 1))
1583 res = GetCellDataAt(table, row, col, getter_AddRefs(cell),
1584 &currentRowIndex, &currentColIndex,
1585 &rowSpan, &colSpan,
1586 &actualRowSpan, &actualColSpan, &isSelected);
1587 if (NS_FAILED(res)) break;
1588 // Skip cells that are spanned from previous rows or columns
1589 if (cell && row == currentRowIndex && col == currentColIndex)
1591 res = AppendNodeToSelectionAsRange(cell);
1592 if (NS_FAILED(res)) break;
1593 cellSelected = PR_TRUE;
1597 // Safety code to select starting cell if nothing else was selected
1598 if (!cellSelected)
1600 return AppendNodeToSelectionAsRange(startCell);
1602 return res;
1605 NS_IMETHODIMP
1606 nsHTMLEditor::SelectTableRow()
1608 nsCOMPtr<nsIDOMElement> cell;
1609 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
1610 NS_ENSURE_SUCCESS(res, res);
1612 // Don't fail if we didn't find a cell
1613 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1614 nsCOMPtr<nsIDOMElement> startCell = cell;
1616 // Get table and location of cell:
1617 nsCOMPtr<nsISelection> selection;
1618 nsCOMPtr<nsIDOMElement> table;
1619 PRInt32 startRowIndex, startColIndex;
1621 res = GetCellContext(getter_AddRefs(selection),
1622 getter_AddRefs(table),
1623 getter_AddRefs(cell),
1624 nsnull, nsnull,
1625 &startRowIndex, &startColIndex);
1626 NS_ENSURE_SUCCESS(res, res);
1627 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1629 PRInt32 rowCount, colCount;
1630 res = GetTableSize(table, &rowCount, &colCount);
1631 NS_ENSURE_SUCCESS(res, res);
1633 //Note: At this point, we could get first and last cells in row,
1634 // then call SelectBlockOfCells, but that would take just
1635 // a little less code, so the following is more efficient
1637 // Suppress nsISelectionListener notification
1638 // until all selection changes are finished
1639 nsSelectionBatcherForTable selectionBatcher(selection);
1641 // It is now safe to clear the selection
1642 // BE SURE TO RESET IT BEFORE LEAVING!
1643 res = ClearSelection();
1645 // Select all cells in the same row as current cell
1646 PRBool cellSelected = PR_FALSE;
1647 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
1648 PRBool isSelected;
1649 for(PRInt32 col = 0; col < colCount; col += NS_MAX(actualColSpan, 1))
1651 res = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell),
1652 &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1653 &actualRowSpan, &actualColSpan, &isSelected);
1654 if (NS_FAILED(res)) break;
1655 // Skip cells that are spanned from previous rows or columns
1656 if (cell && currentRowIndex == startRowIndex && currentColIndex == col)
1658 res = AppendNodeToSelectionAsRange(cell);
1659 if (NS_FAILED(res)) break;
1660 cellSelected = PR_TRUE;
1663 // Safety code to select starting cell if nothing else was selected
1664 if (!cellSelected)
1666 return AppendNodeToSelectionAsRange(startCell);
1668 return res;
1671 NS_IMETHODIMP
1672 nsHTMLEditor::SelectTableColumn()
1674 nsCOMPtr<nsIDOMElement> cell;
1675 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
1676 NS_ENSURE_SUCCESS(res, res);
1678 // Don't fail if we didn't find a cell
1679 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
1681 nsCOMPtr<nsIDOMElement> startCell = cell;
1683 // Get location of cell:
1684 nsCOMPtr<nsISelection> selection;
1685 nsCOMPtr<nsIDOMElement> table;
1686 PRInt32 startRowIndex, startColIndex;
1688 res = GetCellContext(getter_AddRefs(selection),
1689 getter_AddRefs(table),
1690 getter_AddRefs(cell),
1691 nsnull, nsnull,
1692 &startRowIndex, &startColIndex);
1693 NS_ENSURE_SUCCESS(res, res);
1694 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
1696 PRInt32 rowCount, colCount;
1697 res = GetTableSize(table, &rowCount, &colCount);
1698 NS_ENSURE_SUCCESS(res, res);
1700 // Suppress nsISelectionListener notification
1701 // until all selection changes are finished
1702 nsSelectionBatcherForTable selectionBatcher(selection);
1704 // It is now safe to clear the selection
1705 // BE SURE TO RESET IT BEFORE LEAVING!
1706 res = ClearSelection();
1708 // Select all cells in the same column as current cell
1709 PRBool cellSelected = PR_FALSE;
1710 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
1711 PRBool isSelected;
1712 for(PRInt32 row = 0; row < rowCount; row += NS_MAX(actualRowSpan, 1))
1714 res = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell),
1715 &currentRowIndex, &currentColIndex, &rowSpan, &colSpan,
1716 &actualRowSpan, &actualColSpan, &isSelected);
1717 if (NS_FAILED(res)) break;
1718 // Skip cells that are spanned from previous rows or columns
1719 if (cell && currentRowIndex == row && currentColIndex == startColIndex)
1721 res = AppendNodeToSelectionAsRange(cell);
1722 if (NS_FAILED(res)) break;
1723 cellSelected = PR_TRUE;
1726 // Safety code to select starting cell if nothing else was selected
1727 if (!cellSelected)
1729 return AppendNodeToSelectionAsRange(startCell);
1731 return res;
1734 NS_IMETHODIMP
1735 nsHTMLEditor::SplitTableCell()
1737 nsCOMPtr<nsIDOMElement> table;
1738 nsCOMPtr<nsIDOMElement> cell;
1739 PRInt32 startRowIndex, startColIndex, actualRowSpan, actualColSpan;
1740 // Get cell, table, etc. at selection anchor node
1741 nsresult res = GetCellContext(nsnull,
1742 getter_AddRefs(table),
1743 getter_AddRefs(cell),
1744 nsnull, nsnull,
1745 &startRowIndex, &startColIndex);
1746 NS_ENSURE_SUCCESS(res, res);
1747 if(!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
1749 // We need rowspan and colspan data
1750 res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan);
1751 NS_ENSURE_SUCCESS(res, res);
1753 // Must have some span to split
1754 if (actualRowSpan <= 1 && actualColSpan <= 1)
1755 return NS_OK;
1757 nsAutoEditBatch beginBatching(this);
1758 // Prevent auto insertion of BR in new cell until we're done
1759 nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
1761 // We reset selection
1762 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
1763 //...so suppress Rules System selection munging
1764 nsAutoTxnsConserveSelection dontChangeSelection(this);
1766 nsCOMPtr<nsIDOMElement> newCell;
1767 PRInt32 rowIndex = startRowIndex;
1768 PRInt32 rowSpanBelow, colSpanAfter;
1770 // Split up cell row-wise first into rowspan=1 above, and the rest below,
1771 // whittling away at the cell below until no more extra span
1772 for (rowSpanBelow = actualRowSpan-1; rowSpanBelow >= 0; rowSpanBelow--)
1774 // We really split row-wise only if we had rowspan > 1
1775 if (rowSpanBelow > 0)
1777 res = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow, getter_AddRefs(newCell));
1778 NS_ENSURE_SUCCESS(res, res);
1779 CopyCellBackgroundColor(newCell, cell);
1781 PRInt32 colIndex = startColIndex;
1782 // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
1783 for (colSpanAfter = actualColSpan-1; colSpanAfter > 0; colSpanAfter--)
1785 res = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter, getter_AddRefs(newCell));
1786 NS_ENSURE_SUCCESS(res, res);
1787 CopyCellBackgroundColor(newCell, cell);
1788 colIndex++;
1790 // Point to the new cell and repeat
1791 rowIndex++;
1793 return res;
1796 nsresult
1797 nsHTMLEditor::CopyCellBackgroundColor(nsIDOMElement *destCell, nsIDOMElement *sourceCell)
1799 NS_ENSURE_TRUE(destCell && sourceCell, NS_ERROR_NULL_POINTER);
1801 // Copy backgournd color to new cell
1802 NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
1803 nsAutoString color;
1804 PRBool isSet;
1805 nsresult res = GetAttributeValue(sourceCell, bgcolor, color, &isSet);
1807 if (NS_SUCCEEDED(res) && isSet)
1808 res = SetAttribute(destCell, bgcolor, color);
1810 return res;
1813 NS_IMETHODIMP
1814 nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex,
1815 PRInt32 aColSpanLeft, PRInt32 aColSpanRight,
1816 nsIDOMElement **aNewCell)
1818 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1819 if (aNewCell) *aNewCell = nsnull;
1821 nsCOMPtr<nsIDOMElement> cell;
1822 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
1823 PRBool isSelected;
1824 nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
1825 &startRowIndex, &startColIndex,
1826 &rowSpan, &colSpan,
1827 &actualRowSpan, &actualColSpan, &isSelected);
1828 NS_ENSURE_SUCCESS(res, res);
1829 NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
1831 // We can't split!
1832 if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan)
1833 return NS_OK;
1835 // Reduce colspan of cell to split
1836 res = SetColSpan(cell, aColSpanLeft);
1837 NS_ENSURE_SUCCESS(res, res);
1839 // Insert new cell after using the remaining span
1840 // and always get the new cell so we can copy the background color;
1841 nsCOMPtr<nsIDOMElement> newCell;
1842 res = InsertCell(cell, actualRowSpan, aColSpanRight, PR_TRUE, PR_FALSE, getter_AddRefs(newCell));
1843 NS_ENSURE_SUCCESS(res, res);
1844 if (newCell)
1846 if (aNewCell)
1848 *aNewCell = newCell.get();
1849 NS_ADDREF(*aNewCell);
1851 res = CopyCellBackgroundColor(newCell, cell);
1853 return res;
1856 NS_IMETHODIMP
1857 nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex,
1858 PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow,
1859 nsIDOMElement **aNewCell)
1861 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
1862 if (aNewCell) *aNewCell = nsnull;
1864 nsCOMPtr<nsIDOMElement> cell;
1865 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
1866 PRBool isSelected;
1867 nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
1868 &startRowIndex, &startColIndex,
1869 &rowSpan, &colSpan,
1870 &actualRowSpan, &actualColSpan, &isSelected);
1871 NS_ENSURE_SUCCESS(res, res);
1872 NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER);
1874 // We can't split!
1875 if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan)
1876 return NS_OK;
1878 PRInt32 rowCount, colCount;
1879 res = GetTableSize(aTable, &rowCount, &colCount);
1880 NS_ENSURE_SUCCESS(res, res);
1882 nsCOMPtr<nsIDOMElement> cell2;
1883 nsCOMPtr<nsIDOMElement> lastCellFound;
1884 PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
1885 PRBool isSelected2;
1886 PRInt32 colIndex = 0;
1887 PRBool insertAfter = (startColIndex > 0);
1888 // This is the row we will insert new cell into
1889 PRInt32 rowBelowIndex = startRowIndex+aRowSpanAbove;
1891 // Find a cell to insert before or after
1894 // Search for a cell to insert before
1895 res = GetCellDataAt(aTable, rowBelowIndex,
1896 colIndex, getter_AddRefs(cell2),
1897 &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2,
1898 &actualRowSpan2, &actualColSpan2, &isSelected2);
1899 // If we fail here, it could be because row has bad rowspan values,
1900 // such as all cells having rowspan > 1 (Call FixRowSpan first!)
1901 if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE;
1903 // Skip over cells spanned from above (like the one we are splitting!)
1904 if (cell2 && startRowIndex2 == rowBelowIndex)
1906 if (insertAfter)
1908 // New cell isn't first in row,
1909 // so stop after we find the cell just before new cell's column
1910 if ((startColIndex2 + actualColSpan2) == startColIndex)
1911 break;
1913 // If cell found is AFTER desired new cell colum,
1914 // we have multiple cells with rowspan > 1 that
1915 // prevented us from finding a cell to insert after...
1916 if (startColIndex2 > startColIndex)
1918 // ... so instead insert before the cell we found
1919 insertAfter = PR_FALSE;
1920 break;
1923 else
1925 break; // Inserting before, so stop at first cell in row we want to insert into
1927 lastCellFound = cell2;
1929 // Skip to next available cellmap location
1930 colIndex += NS_MAX(actualColSpan2, 1);
1932 // Done when past end of total number of columns
1933 if (colIndex > colCount)
1934 break;
1936 } while(PR_TRUE);
1938 if (!cell2 && lastCellFound)
1940 // Edge case where we didn't find a cell to insert after
1941 // or before because column(s) before desired column
1942 // and all columns after it are spanned from above.
1943 // We can insert after the last cell we found
1944 cell2 = lastCellFound;
1945 insertAfter = PR_TRUE; // Should always be true, but let's be sure
1948 // Reduce rowspan of cell to split
1949 res = SetRowSpan(cell, aRowSpanAbove);
1950 NS_ENSURE_SUCCESS(res, res);
1953 // Insert new cell after using the remaining span
1954 // and always get the new cell so we can copy the background color;
1955 nsCOMPtr<nsIDOMElement> newCell;
1956 res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, PR_FALSE, getter_AddRefs(newCell));
1957 NS_ENSURE_SUCCESS(res, res);
1958 if (newCell)
1960 if (aNewCell)
1962 *aNewCell = newCell.get();
1963 NS_ADDREF(*aNewCell);
1965 res = CopyCellBackgroundColor(newCell, cell2);
1967 return res;
1970 NS_IMETHODIMP
1971 nsHTMLEditor::SwitchTableCellHeaderType(nsIDOMElement *aSourceCell, nsIDOMElement **aNewCell)
1973 NS_ENSURE_TRUE(aSourceCell, NS_ERROR_NULL_POINTER);
1975 nsAutoEditBatch beginBatching(this);
1976 // Prevent auto insertion of BR in new cell created by ReplaceContainer
1977 nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
1979 nsCOMPtr<nsIDOMNode> newNode;
1981 // Save current selection to restore when done
1982 // This is needed so ReplaceContainer can monitor selection
1983 // when replacing nodes
1984 nsCOMPtr<nsISelection>selection;
1985 nsresult res = GetSelection(getter_AddRefs(selection));
1986 NS_ENSURE_SUCCESS(res, res);
1987 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1988 nsAutoSelectionReset selectionResetter(selection, this);
1990 // Set to the opposite of current type
1991 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aSourceCell);
1992 nsString newCellType( (atom == nsEditProperty::td) ? NS_LITERAL_STRING("th") : NS_LITERAL_STRING("td"));
1994 // This creates new node, moves children, copies attributes (PR_TRUE)
1995 // and manages the selection!
1996 res = ReplaceContainer(aSourceCell, address_of(newNode), newCellType, nsnull, nsnull, PR_TRUE);
1997 NS_ENSURE_SUCCESS(res, res);
1998 NS_ENSURE_TRUE(newNode, NS_ERROR_FAILURE);
2000 // Return the new cell
2001 if (aNewCell)
2003 nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode);
2004 *aNewCell = newElement.get();
2005 NS_ADDREF(*aNewCell);
2008 return NS_OK;
2011 NS_IMETHODIMP
2012 nsHTMLEditor::JoinTableCells(PRBool aMergeNonContiguousContents)
2014 nsCOMPtr<nsIDOMElement> table;
2015 nsCOMPtr<nsIDOMElement> targetCell;
2016 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2017 PRBool isSelected;
2018 nsCOMPtr<nsIDOMElement> cell2;
2019 PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
2020 PRBool isSelected2;
2022 // Get cell, table, etc. at selection anchor node
2023 nsresult res = GetCellContext(nsnull,
2024 getter_AddRefs(table),
2025 getter_AddRefs(targetCell),
2026 nsnull, nsnull,
2027 &startRowIndex, &startColIndex);
2028 NS_ENSURE_SUCCESS(res, res);
2029 if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
2031 nsAutoEditBatch beginBatching(this);
2032 //Don't let Rules System change the selection
2033 nsAutoTxnsConserveSelection dontChangeSelection(this);
2035 // Note: We dont' use nsSetSelectionAfterTableEdit here so the selection
2036 // is retained after joining. This leaves the target cell selected
2037 // as well as the "non-contiguous" cells, so user can see what happened.
2039 nsCOMPtr<nsIDOMElement> firstCell;
2040 PRInt32 firstRowIndex, firstColIndex;
2041 res = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex, getter_AddRefs(firstCell));
2042 NS_ENSURE_SUCCESS(res, res);
2044 PRBool joinSelectedCells = PR_FALSE;
2045 if (firstCell)
2047 nsCOMPtr<nsIDOMElement> secondCell;
2048 res = GetNextSelectedCell(nsnull, getter_AddRefs(secondCell));
2049 NS_ENSURE_SUCCESS(res, res);
2051 // If only one cell is selected, join with cell to the right
2052 joinSelectedCells = (secondCell != nsnull);
2055 if (joinSelectedCells)
2057 // We have selected cells: Join just contiguous cells
2058 // and just merge contents if not contiguous
2060 PRInt32 rowCount, colCount;
2061 res = GetTableSize(table, &rowCount, &colCount);
2062 NS_ENSURE_SUCCESS(res, res);
2064 // Get spans for cell we will merge into
2065 PRInt32 firstRowSpan, firstColSpan;
2066 res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan);
2067 NS_ENSURE_SUCCESS(res, res);
2069 // This defines the last indexes along the "edges"
2070 // of the contiguous block of cells, telling us
2071 // that we can join adjacent cells to the block
2072 // Start with same as the first values,
2073 // then expand as we find adjacent selected cells
2074 PRInt32 lastRowIndex = firstRowIndex;
2075 PRInt32 lastColIndex = firstColIndex;
2076 PRInt32 rowIndex, colIndex;
2078 // First pass: Determine boundaries of contiguous rectangular block
2079 // that we will join into one cell,
2080 // favoring adjacent cells in the same row
2081 for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++)
2083 PRInt32 currentRowCount = rowCount;
2084 // Be sure each row doesn't have rowspan errors
2085 res = FixBadRowSpan(table, rowIndex, rowCount);
2086 NS_ENSURE_SUCCESS(res, res);
2087 // Adjust rowcount by number of rows we removed
2088 lastRowIndex -= (currentRowCount-rowCount);
2090 PRBool cellFoundInRow = PR_FALSE;
2091 PRBool lastRowIsSet = PR_FALSE;
2092 PRInt32 lastColInRow = 0;
2093 PRInt32 firstColInRow = firstColIndex;
2094 for (colIndex = firstColIndex; colIndex < colCount; colIndex += NS_MAX(actualColSpan2, 1))
2096 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
2097 &startRowIndex2, &startColIndex2,
2098 &rowSpan2, &colSpan2,
2099 &actualRowSpan2, &actualColSpan2, &isSelected2);
2100 NS_ENSURE_SUCCESS(res, res);
2102 if (isSelected2)
2104 if (!cellFoundInRow)
2105 // We've just found the first selected cell in this row
2106 firstColInRow = colIndex;
2108 if (rowIndex > firstRowIndex && firstColInRow != firstColIndex)
2110 // We're in at least the second row,
2111 // but left boundary is "ragged" (not the same as 1st row's start)
2112 //Let's just end block on previous row
2113 // and keep previous lastColIndex
2114 //TODO: We could try to find the Maximum firstColInRow
2115 // so our block can still extend down more rows?
2116 lastRowIndex = NS_MAX(0,rowIndex - 1);
2117 lastRowIsSet = PR_TRUE;
2118 break;
2120 // Save max selected column in this row, including extra colspan
2121 lastColInRow = colIndex + (actualColSpan2-1);
2122 cellFoundInRow = PR_TRUE;
2124 else if (cellFoundInRow)
2126 // No cell or not selected, but at least one cell in row was found
2128 if (rowIndex > (firstRowIndex+1) && colIndex <= lastColIndex)
2130 // Cell is in a column less than current right border in
2131 // the third or higher selected row, so stop block at the previous row
2132 lastRowIndex = NS_MAX(0,rowIndex - 1);
2133 lastRowIsSet = PR_TRUE;
2135 // We're done with this row
2136 break;
2138 } // End of column loop
2140 // Done with this row
2141 if (cellFoundInRow)
2143 if (rowIndex == firstRowIndex)
2145 // First row always initializes the right boundary
2146 lastColIndex = lastColInRow;
2149 // If we didn't determine last row above...
2150 if (!lastRowIsSet)
2152 if (colIndex < lastColIndex)
2154 // (don't think we ever get here?)
2155 // Cell is in a column less than current right boundary,
2156 // so stop block at the previous row
2157 lastRowIndex = NS_MAX(0,rowIndex - 1);
2159 else
2161 // Go on to examine next row
2162 lastRowIndex = rowIndex+1;
2165 // Use the minimum col we found so far for right boundary
2166 lastColIndex = NS_MIN(lastColIndex, lastColInRow);
2168 else
2170 // No selected cells in this row -- stop at row above
2171 // and leave last column at its previous value
2172 lastRowIndex = NS_MAX(0,rowIndex - 1);
2176 // The list of cells we will delete after joining
2177 nsTArray<nsIDOMElement*> deleteList;
2179 // 2nd pass: Do the joining and merging
2180 for (rowIndex = 0; rowIndex < rowCount; rowIndex++)
2182 for (colIndex = 0; colIndex < colCount; colIndex += NS_MAX(actualColSpan2, 1))
2184 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
2185 &startRowIndex2, &startColIndex2,
2186 &rowSpan2, &colSpan2,
2187 &actualRowSpan2, &actualColSpan2, &isSelected2);
2188 NS_ENSURE_SUCCESS(res, res);
2190 // If this is 0, we are past last cell in row, so exit the loop
2191 if (actualColSpan2 == 0)
2192 break;
2194 // Merge only selected cells (skip cell we're merging into, of course)
2195 if (isSelected2 && cell2 != firstCell)
2197 if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex &&
2198 colIndex >= firstColIndex && colIndex <= lastColIndex)
2200 // We are within the join region
2201 // Problem: It is very tricky to delete cells as we merge,
2202 // since that will upset the cellmap
2203 // Instead, build a list of cells to delete and do it later
2204 NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above");
2206 if (actualColSpan2 > 1)
2208 //Check if cell "hangs" off the boundary because of colspan > 1
2209 // Use split methods to chop off excess
2210 PRInt32 extraColSpan = (startColIndex2 + actualColSpan2) - (lastColIndex+1);
2211 if ( extraColSpan > 0)
2213 res = SplitCellIntoColumns(table, startRowIndex2, startColIndex2,
2214 actualColSpan2-extraColSpan, extraColSpan, nsnull);
2215 NS_ENSURE_SUCCESS(res, res);
2219 res = MergeCells(firstCell, cell2, PR_FALSE);
2220 NS_ENSURE_SUCCESS(res, res);
2222 // Add cell to list to delete
2223 deleteList.AppendElement(cell2.get());
2225 else if (aMergeNonContiguousContents)
2227 // Cell is outside join region -- just merge the contents
2228 res = MergeCells(firstCell, cell2, PR_FALSE);
2229 NS_ENSURE_SUCCESS(res, res);
2235 // All cell contents are merged. Delete the empty cells we accumulated
2236 // Prevent rules testing until we're done
2237 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
2239 for (PRUint32 i = 0, n = deleteList.Length(); i < n; i++)
2241 nsIDOMElement *elementPtr = deleteList[i];
2242 if (elementPtr)
2244 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(elementPtr);
2245 res = DeleteNode(node);
2246 NS_ENSURE_SUCCESS(res, res);
2249 // Cleanup selection: remove ranges where cells were deleted
2250 nsCOMPtr<nsISelection> selection;
2251 res = GetSelection(getter_AddRefs(selection));
2252 NS_ENSURE_SUCCESS(res, res);
2253 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2255 PRInt32 rangeCount;
2256 res = selection->GetRangeCount(&rangeCount);
2257 NS_ENSURE_SUCCESS(res, res);
2259 nsCOMPtr<nsIDOMRange> range;
2260 PRInt32 i;
2261 for (i = 0; i < rangeCount; i++)
2263 res = selection->GetRangeAt(i, getter_AddRefs(range));
2264 NS_ENSURE_SUCCESS(res, res);
2265 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2267 nsCOMPtr<nsIDOMElement> deletedCell;
2268 res = GetCellFromRange(range, getter_AddRefs(deletedCell));
2269 if (!deletedCell)
2271 selection->RemoveRange(range);
2272 rangeCount--;
2273 i--;
2277 // Set spans for the cell everthing merged into
2278 res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1);
2279 NS_ENSURE_SUCCESS(res, res);
2280 res = SetColSpan(firstCell, lastColIndex-firstColIndex+1);
2281 NS_ENSURE_SUCCESS(res, res);
2284 // Fixup disturbances in table layout
2285 NormalizeTable(table);
2287 else
2289 // Joining with cell to the right -- get rowspan and colspan data of target cell
2290 res = GetCellDataAt(table, startRowIndex, startColIndex, getter_AddRefs(targetCell),
2291 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2292 &actualRowSpan, &actualColSpan, &isSelected);
2293 NS_ENSURE_SUCCESS(res, res);
2294 NS_ENSURE_TRUE(targetCell, NS_ERROR_NULL_POINTER);
2296 // Get data for cell to the right
2297 res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, getter_AddRefs(cell2),
2298 &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2,
2299 &actualRowSpan2, &actualColSpan2, &isSelected2);
2300 NS_ENSURE_SUCCESS(res, res);
2301 if(!cell2) return NS_OK; // Don't fail if there's no cell
2303 // sanity check
2304 NS_ASSERTION((startRowIndex >= startRowIndex2),"JoinCells: startRowIndex < startRowIndex2");
2306 // Figure out span of merged cell starting from target's starting row
2307 // to handle case of merged cell starting in a row above
2308 PRInt32 spanAboveMergedCell = startRowIndex - startRowIndex2;
2309 PRInt32 effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell;
2311 if (effectiveRowSpan2 > actualRowSpan)
2313 // Cell to the right spans into row below target
2314 // Split off portion below target cell's bottom-most row
2315 res = SplitCellIntoRows(table, startRowIndex2, startColIndex2,
2316 spanAboveMergedCell+actualRowSpan,
2317 effectiveRowSpan2-actualRowSpan, nsnull);
2318 NS_ENSURE_SUCCESS(res, res);
2321 // Move contents from cell to the right
2322 // Delete the cell now only if it starts in the same row
2323 // and has enough row "height"
2324 res = MergeCells(targetCell, cell2,
2325 (startRowIndex2 == startRowIndex) &&
2326 (effectiveRowSpan2 >= actualRowSpan));
2327 NS_ENSURE_SUCCESS(res, res);
2329 if (effectiveRowSpan2 < actualRowSpan)
2331 // Merged cell is "shorter"
2332 // (there are cells(s) below it that are row-spanned by target cell)
2333 // We could try splitting those cells, but that's REAL messy,
2334 // so the safest thing to do is NOT really join the cells
2335 return NS_OK;
2338 if( spanAboveMergedCell > 0 )
2340 // Cell we merged started in a row above the target cell
2341 // Reduce rowspan to give room where target cell will extend it's colspan
2342 res = SetRowSpan(cell2, spanAboveMergedCell);
2343 NS_ENSURE_SUCCESS(res, res);
2346 // Reset target cell's colspan to encompass cell to the right
2347 res = SetColSpan(targetCell, actualColSpan+actualColSpan2);
2348 NS_ENSURE_SUCCESS(res, res);
2350 return res;
2353 NS_IMETHODIMP
2354 nsHTMLEditor::MergeCells(nsCOMPtr<nsIDOMElement> aTargetCell,
2355 nsCOMPtr<nsIDOMElement> aCellToMerge,
2356 PRBool aDeleteCellToMerge)
2358 NS_ENSURE_TRUE(aTargetCell && aCellToMerge, NS_ERROR_NULL_POINTER);
2360 nsresult res = NS_OK;
2362 // Prevent rules testing until we're done
2363 nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
2365 // Don't need to merge if cell is empty
2366 if (!IsEmptyCell(aCellToMerge))
2368 // Get index of last child in target cell
2369 nsCOMPtr<nsIDOMNodeList> childNodes;
2370 nsCOMPtr<nsIDOMNode> cellChild;
2371 res = aTargetCell->GetChildNodes(getter_AddRefs(childNodes));
2372 // If we fail or don't have children,
2373 // we insert at index 0
2374 PRInt32 insertIndex = 0;
2376 if ((NS_SUCCEEDED(res)) && (childNodes))
2378 // Start inserting just after last child
2379 PRUint32 len;
2380 res = childNodes->GetLength(&len);
2381 NS_ENSURE_SUCCESS(res, res);
2382 if (len == 1 && IsEmptyCell(aTargetCell))
2384 // Delete the empty node
2385 nsCOMPtr<nsIDOMNode> tempNode;
2386 res = childNodes->Item(0, getter_AddRefs(cellChild));
2387 NS_ENSURE_SUCCESS(res, res);
2388 res = DeleteNode(cellChild);
2389 NS_ENSURE_SUCCESS(res, res);
2390 insertIndex = 0;
2392 else
2393 insertIndex = (PRInt32)len;
2396 // Move the contents
2397 PRBool hasChild;
2398 aCellToMerge->HasChildNodes(&hasChild);
2399 while (hasChild)
2401 aCellToMerge->GetLastChild(getter_AddRefs(cellChild));
2402 res = DeleteNode(cellChild);
2403 NS_ENSURE_SUCCESS(res, res);
2405 res = InsertNode(cellChild, aTargetCell, insertIndex);
2406 NS_ENSURE_SUCCESS(res, res);
2408 aCellToMerge->HasChildNodes(&hasChild);
2412 // Delete cells whose contents were moved
2413 if (aDeleteCellToMerge)
2414 res = DeleteNode(aCellToMerge);
2416 return res;
2420 NS_IMETHODIMP
2421 nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount)
2423 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
2425 PRInt32 rowCount, colCount;
2426 nsresult res = GetTableSize(aTable, &rowCount, &colCount);
2427 NS_ENSURE_SUCCESS(res, res);
2429 nsCOMPtr<nsIDOMElement>cell;
2430 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2431 PRBool isSelected;
2433 PRInt32 minRowSpan = -1;
2434 PRInt32 colIndex;
2436 for( colIndex = 0; colIndex < colCount; colIndex += NS_MAX(actualColSpan, 1))
2438 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2439 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2440 &actualRowSpan, &actualColSpan, &isSelected);
2441 // NOTE: This is a *real* failure.
2442 // GetCellDataAt passes if cell is missing from cellmap
2443 if(NS_FAILED(res)) return res;
2444 if (!cell) break;
2445 if(rowSpan > 0 &&
2446 startRowIndex == aRowIndex &&
2447 (rowSpan < minRowSpan || minRowSpan == -1))
2449 minRowSpan = rowSpan;
2451 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
2453 if(minRowSpan > 1)
2455 // The amount to reduce everyone's rowspan
2456 // so at least one cell has rowspan = 1
2457 PRInt32 rowsReduced = minRowSpan - 1;
2458 for(colIndex = 0; colIndex < colCount; colIndex += NS_MAX(actualColSpan, 1))
2460 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
2461 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2462 &actualRowSpan, &actualColSpan, &isSelected);
2463 if(NS_FAILED(res)) return res;
2464 // Fixup rowspans only for cells starting in current row
2465 if(cell && rowSpan > 0 &&
2466 startRowIndex == aRowIndex &&
2467 startColIndex == colIndex )
2469 res = SetRowSpan(cell, rowSpan-rowsReduced);
2470 if(NS_FAILED(res)) return res;
2472 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
2475 return GetTableSize(aTable, &aNewRowCount, &colCount);
2478 NS_IMETHODIMP
2479 nsHTMLEditor::FixBadColSpan(nsIDOMElement *aTable, PRInt32 aColIndex, PRInt32& aNewColCount)
2481 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER);
2483 PRInt32 rowCount, colCount;
2484 nsresult res = GetTableSize(aTable, &rowCount, &colCount);
2485 NS_ENSURE_SUCCESS(res, res);
2487 nsCOMPtr<nsIDOMElement> cell;
2488 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2489 PRBool isSelected;
2491 PRInt32 minColSpan = -1;
2492 PRInt32 rowIndex;
2494 for( rowIndex = 0; rowIndex < rowCount; rowIndex += NS_MAX(actualRowSpan, 1))
2496 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
2497 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2498 &actualRowSpan, &actualColSpan, &isSelected);
2499 // NOTE: This is a *real* failure.
2500 // GetCellDataAt passes if cell is missing from cellmap
2501 if(NS_FAILED(res)) return res;
2502 if (!cell) break;
2503 if(colSpan > 0 &&
2504 startColIndex == aColIndex &&
2505 (colSpan < minColSpan || minColSpan == -1))
2507 minColSpan = colSpan;
2509 NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
2511 if(minColSpan > 1)
2513 // The amount to reduce everyone's colspan
2514 // so at least one cell has colspan = 1
2515 PRInt32 colsReduced = minColSpan - 1;
2516 for(rowIndex = 0; rowIndex < rowCount; rowIndex += NS_MAX(actualRowSpan, 1))
2518 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
2519 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2520 &actualRowSpan, &actualColSpan, &isSelected);
2521 if(NS_FAILED(res)) return res;
2522 // Fixup colspans only for cells starting in current column
2523 if(cell && colSpan > 0 &&
2524 startColIndex == aColIndex &&
2525 startRowIndex == rowIndex )
2527 res = SetColSpan(cell, colSpan-colsReduced);
2528 if(NS_FAILED(res)) return res;
2530 NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
2533 return GetTableSize(aTable, &rowCount, &aNewColCount);
2536 NS_IMETHODIMP
2537 nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable)
2539 nsCOMPtr<nsISelection>selection;
2540 nsresult res = GetSelection(getter_AddRefs(selection));
2541 NS_ENSURE_SUCCESS(res, res);
2542 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2544 nsCOMPtr<nsIDOMElement> table;
2545 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table));
2546 NS_ENSURE_SUCCESS(res, res);
2547 // Don't fail if we didn't find a table
2548 NS_ENSURE_TRUE(table, NS_OK);
2550 PRInt32 rowCount, colCount, rowIndex, colIndex;
2551 res = GetTableSize(table, &rowCount, &colCount);
2552 NS_ENSURE_SUCCESS(res, res);
2554 // Save current selection
2555 nsAutoSelectionReset selectionResetter(selection, this);
2557 nsAutoEditBatch beginBatching(this);
2558 // Prevent auto insertion of BR in new cell until we're done
2559 nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
2561 nsCOMPtr<nsIDOMElement> cell;
2562 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2563 PRBool isSelected;
2565 // Scan all cells in each row to detect bad rowspan values
2566 for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
2568 res = FixBadRowSpan(table, rowIndex, rowCount);
2569 NS_ENSURE_SUCCESS(res, res);
2571 // and same for colspans
2572 for(colIndex = 0; colIndex < colCount; colIndex++)
2574 res = FixBadColSpan(table, colIndex, colCount);
2575 NS_ENSURE_SUCCESS(res, res);
2578 // Fill in missing cellmap locations with empty cells
2579 for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
2581 nsCOMPtr<nsIDOMElement> previousCellInRow;
2583 for(colIndex = 0; colIndex < colCount; colIndex++)
2585 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell),
2586 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2587 &actualRowSpan, &actualColSpan, &isSelected);
2588 // NOTE: This is a *real* failure.
2589 // GetCellDataAt passes if cell is missing from cellmap
2590 if(NS_FAILED(res)) return res;
2591 if (!cell)
2593 //We are missing a cell at a cellmap location
2594 #ifdef DEBUG
2595 printf("NormalizeTable found missing cell at row=%d, col=%d\n", rowIndex, colIndex);
2596 #endif
2597 // Add a cell after the previous Cell in the current row
2598 if(previousCellInRow)
2600 // Insert a new cell after (PR_TRUE), and return the new cell to us
2601 res = InsertCell(previousCellInRow, 1, 1, PR_TRUE, PR_FALSE, getter_AddRefs(cell));
2602 NS_ENSURE_SUCCESS(res, res);
2604 // Set this so we use returned new "cell" to set previousCellInRow below
2605 if(cell)
2606 startRowIndex = rowIndex;
2607 } else {
2608 // We don't have any cells in this row -- We are really messed up!
2609 #ifdef DEBUG
2610 printf("NormalizeTable found no cells in row=%d, col=%d\n", rowIndex, colIndex);
2611 #endif
2612 return NS_ERROR_FAILURE;
2615 // Save the last cell found in the same row we are scanning
2616 if(startRowIndex == rowIndex)
2618 previousCellInRow = cell;
2622 return res;
2625 NS_IMETHODIMP
2626 nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell,
2627 PRInt32 *aRowIndex, PRInt32 *aColIndex)
2629 NS_ENSURE_ARG_POINTER(aRowIndex);
2630 *aColIndex=0; // initialize out params
2631 NS_ENSURE_ARG_POINTER(aColIndex);
2632 *aRowIndex=0;
2633 nsresult res=NS_ERROR_NOT_INITIALIZED;
2634 if (!aCell)
2636 // Get the selected cell or the cell enclosing the selection anchor
2637 nsCOMPtr<nsIDOMElement> cell;
2638 res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
2639 if (NS_SUCCEEDED(res) && cell)
2640 aCell = cell;
2641 else
2642 return NS_ERROR_FAILURE;
2645 NS_ENSURE_TRUE(mPresShellWeak, NS_ERROR_NOT_INITIALIZED);
2646 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
2647 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2649 nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aCell) );
2650 NS_ENSURE_TRUE(nodeAsContent, NS_ERROR_FAILURE);
2651 // frames are not ref counted, so don't use an nsCOMPtr
2652 nsIFrame *layoutObject = nodeAsContent->GetPrimaryFrame();
2653 NS_ENSURE_TRUE(layoutObject, NS_ERROR_FAILURE);
2655 nsITableCellLayout *cellLayoutObject = do_QueryFrame(layoutObject);
2656 NS_ENSURE_TRUE(cellLayoutObject, NS_ERROR_FAILURE);
2657 return cellLayoutObject->GetCellIndexes(*aRowIndex, *aColIndex);
2660 NS_IMETHODIMP
2661 nsHTMLEditor::GetTableLayoutObject(nsIDOMElement* aTable, nsITableLayout **tableLayoutObject)
2663 *tableLayoutObject=nsnull;
2664 NS_ENSURE_TRUE(aTable, NS_ERROR_NOT_INITIALIZED);
2665 NS_ENSURE_TRUE(mPresShellWeak, NS_ERROR_NOT_INITIALIZED);
2666 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
2667 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
2669 nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aTable) );
2670 NS_ENSURE_TRUE(nodeAsContent, NS_ERROR_FAILURE);
2671 // frames are not ref counted, so don't use an nsCOMPtr
2672 nsIFrame *layoutObject = nodeAsContent->GetPrimaryFrame();
2673 NS_ENSURE_TRUE(layoutObject, NS_ERROR_FAILURE);
2675 *tableLayoutObject = do_QueryFrame(layoutObject);
2676 return *tableLayoutObject ? NS_OK : NS_NOINTERFACE;
2679 //Return actual number of cells (a cell with colspan > 1 counts as just 1)
2680 PRBool nsHTMLEditor::GetNumberOfCellsInRow(nsIDOMElement* aTable, PRInt32 rowIndex)
2682 PRInt32 cellCount = 0;
2683 nsCOMPtr<nsIDOMElement> cell;
2684 PRInt32 colIndex = 0;
2685 nsresult res;
2686 do {
2687 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2688 PRBool isSelected;
2689 res = GetCellDataAt(aTable, rowIndex, colIndex, getter_AddRefs(cell),
2690 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2691 &actualRowSpan, &actualColSpan, &isSelected);
2692 NS_ENSURE_SUCCESS(res, res);
2693 if (cell)
2695 // Only count cells that start in row we are working with
2696 if (startRowIndex == rowIndex)
2697 cellCount++;
2699 //Next possible location for a cell
2700 colIndex += actualColSpan;
2702 else
2703 colIndex++;
2705 } while (cell);
2707 return cellCount;
2710 /* Not scriptable: For convenience in C++
2711 Use GetTableRowCount and GetTableColumnCount from JavaScript
2713 NS_IMETHODIMP
2714 nsHTMLEditor::GetTableSize(nsIDOMElement *aTable,
2715 PRInt32* aRowCount, PRInt32* aColCount)
2717 NS_ENSURE_ARG_POINTER(aRowCount);
2718 NS_ENSURE_ARG_POINTER(aColCount);
2719 nsresult res;
2720 *aRowCount = 0;
2721 *aColCount = 0;
2722 nsCOMPtr<nsIDOMElement> table;
2723 // Get the selected talbe or the table enclosing the selection anchor
2724 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table));
2725 NS_ENSURE_SUCCESS(res, res);
2726 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
2728 // frames are not ref counted, so don't use an nsCOMPtr
2729 nsITableLayout *tableLayoutObject;
2730 res = GetTableLayoutObject(table.get(), &tableLayoutObject);
2731 NS_ENSURE_SUCCESS(res, res);
2732 NS_ENSURE_TRUE(tableLayoutObject, NS_ERROR_FAILURE);
2734 return tableLayoutObject->GetTableSize(*aRowCount, *aColCount);
2737 NS_IMETHODIMP
2738 nsHTMLEditor::GetCellDataAt(nsIDOMElement* aTable, PRInt32 aRowIndex,
2739 PRInt32 aColIndex, nsIDOMElement **aCell,
2740 PRInt32* aStartRowIndex, PRInt32* aStartColIndex,
2741 PRInt32* aRowSpan, PRInt32* aColSpan,
2742 PRInt32* aActualRowSpan, PRInt32* aActualColSpan,
2743 PRBool* aIsSelected)
2745 NS_ENSURE_ARG_POINTER(aStartRowIndex);
2746 NS_ENSURE_ARG_POINTER(aStartColIndex);
2747 NS_ENSURE_ARG_POINTER(aRowSpan);
2748 NS_ENSURE_ARG_POINTER(aColSpan);
2749 NS_ENSURE_ARG_POINTER(aActualRowSpan);
2750 NS_ENSURE_ARG_POINTER(aActualColSpan);
2751 NS_ENSURE_ARG_POINTER(aIsSelected);
2752 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2754 nsresult res=NS_ERROR_FAILURE;
2755 *aStartRowIndex = 0;
2756 *aStartColIndex = 0;
2757 *aRowSpan = 0;
2758 *aColSpan = 0;
2759 *aActualRowSpan = 0;
2760 *aActualColSpan = 0;
2761 *aIsSelected = PR_FALSE;
2763 *aCell = nsnull;
2765 if (!aTable)
2767 // Get the selected table or the table enclosing the selection anchor
2768 nsCOMPtr<nsIDOMElement> table;
2769 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nsnull, getter_AddRefs(table));
2770 NS_ENSURE_SUCCESS(res, res);
2771 if (table)
2772 aTable = table;
2773 else
2774 return NS_ERROR_FAILURE;
2777 // frames are not ref counted, so don't use an nsCOMPtr
2778 nsITableLayout *tableLayoutObject;
2779 res = GetTableLayoutObject(aTable, &tableLayoutObject);
2780 NS_ENSURE_SUCCESS(res, res);
2781 NS_ENSURE_TRUE(tableLayoutObject, NS_ERROR_FAILURE);
2783 // Note that this returns NS_TABLELAYOUT_CELL_NOT_FOUND when
2784 // the index(es) are out of bounds
2785 nsCOMPtr<nsIDOMElement> cell;
2786 res = tableLayoutObject->GetCellDataAt(aRowIndex, aColIndex,
2787 *getter_AddRefs(cell),
2788 *aStartRowIndex, *aStartColIndex,
2789 *aRowSpan, *aColSpan,
2790 *aActualRowSpan, *aActualColSpan,
2791 *aIsSelected);
2792 if (cell)
2794 *aCell = cell.get();
2795 NS_ADDREF(*aCell);
2797 // Convert to editor's generic "not found" return value
2798 if (res == NS_TABLELAYOUT_CELL_NOT_FOUND) res = NS_EDITOR_ELEMENT_NOT_FOUND;
2799 return res;
2802 // When all you want is the cell
2803 NS_IMETHODIMP
2804 nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, nsIDOMElement **aCell)
2806 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
2807 PRBool isSelected;
2808 return GetCellDataAt(aTable, aRowIndex, aColIndex, aCell,
2809 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2810 &actualRowSpan, &actualColSpan, &isSelected);
2813 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
2814 NS_IMETHODIMP
2815 nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex,
2816 PRInt32& aActualRowSpan, PRInt32& aActualColSpan)
2818 nsCOMPtr<nsIDOMElement> cell;
2819 PRInt32 startRowIndex, startColIndex, rowSpan, colSpan;
2820 PRBool isSelected;
2821 return GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
2822 &startRowIndex, &startColIndex, &rowSpan, &colSpan,
2823 &aActualRowSpan, &aActualColSpan, &isSelected);
2826 NS_IMETHODIMP
2827 nsHTMLEditor::GetCellContext(nsISelection **aSelection,
2828 nsIDOMElement **aTable,
2829 nsIDOMElement **aCell,
2830 nsIDOMNode **aCellParent, PRInt32 *aCellOffset,
2831 PRInt32 *aRowIndex, PRInt32 *aColIndex)
2833 // Initialize return pointers
2834 if (aSelection) *aSelection = nsnull;
2835 if (aTable) *aTable = nsnull;
2836 if (aCell) *aCell = nsnull;
2837 if (aCellParent) *aCellParent = nsnull;
2838 if (aCellOffset) *aCellOffset = 0;
2839 if (aRowIndex) *aRowIndex = 0;
2840 if (aColIndex) *aColIndex = 0;
2842 nsCOMPtr <nsISelection> selection;
2843 nsresult res = GetSelection(getter_AddRefs(selection));
2844 NS_ENSURE_SUCCESS(res, res);
2845 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2847 if (aSelection)
2849 *aSelection = selection.get();
2850 NS_ADDREF(*aSelection);
2852 nsCOMPtr <nsIDOMElement> table;
2853 nsCOMPtr <nsIDOMElement> cell;
2855 // Caller may supply the cell...
2856 if (aCell && *aCell)
2857 cell = *aCell;
2859 // ...but if not supplied,
2860 // get cell if it's the child of selection anchor node,
2861 // or get the enclosing by a cell
2862 if (!cell)
2864 // Find a selected or enclosing table element
2865 nsCOMPtr<nsIDOMElement> cellOrTableElement;
2866 PRInt32 selectedCount;
2867 nsAutoString tagName;
2868 res = GetSelectedOrParentTableElement(tagName, &selectedCount,
2869 getter_AddRefs(cellOrTableElement));
2870 NS_ENSURE_SUCCESS(res, res);
2871 if (tagName.EqualsLiteral("table"))
2873 // We have a selected table, not a cell
2874 if (aTable)
2876 *aTable = cellOrTableElement.get();
2877 NS_ADDREF(*aTable);
2879 return NS_OK;
2881 if (!tagName.EqualsLiteral("td"))
2882 return NS_EDITOR_ELEMENT_NOT_FOUND;
2884 // We found a cell
2885 cell = cellOrTableElement;
2887 if (aCell)
2889 *aCell = cell.get();
2890 NS_ADDREF(*aCell);
2893 // Get containing table
2894 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table));
2895 NS_ENSURE_SUCCESS(res, res);
2896 // Cell must be in a table, so fail if not found
2897 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
2898 if (aTable)
2900 *aTable = table.get();
2901 NS_ADDREF(*aTable);
2904 // Get the rest of the related data only if requested
2905 if (aRowIndex || aColIndex)
2907 PRInt32 rowIndex, colIndex;
2908 // Get current cell location so we can put caret back there when done
2909 res = GetCellIndexes(cell, &rowIndex, &colIndex);
2910 if(NS_FAILED(res)) return res;
2911 if (aRowIndex) *aRowIndex = rowIndex;
2912 if (aColIndex) *aColIndex = colIndex;
2914 if (aCellParent)
2916 nsCOMPtr <nsIDOMNode> cellParent;
2917 // Get the immediate parent of the cell
2918 res = cell->GetParentNode(getter_AddRefs(cellParent));
2919 NS_ENSURE_SUCCESS(res, res);
2920 // Cell has to have a parent, so fail if not found
2921 NS_ENSURE_TRUE(cellParent, NS_ERROR_FAILURE);
2923 *aCellParent = cellParent.get();
2924 NS_ADDREF(*aCellParent);
2926 if (aCellOffset)
2927 res = GetChildOffset(cell, cellParent, *aCellOffset);
2930 return res;
2933 nsresult
2934 nsHTMLEditor::GetCellFromRange(nsIDOMRange *aRange, nsIDOMElement **aCell)
2936 // Note: this might return a node that is outside of the range.
2937 // Use carefully.
2938 NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER);
2940 *aCell = nsnull;
2942 nsCOMPtr<nsIDOMNode> startParent;
2943 nsresult res = aRange->GetStartContainer(getter_AddRefs(startParent));
2944 NS_ENSURE_SUCCESS(res, res);
2945 NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
2947 PRInt32 startOffset;
2948 res = aRange->GetStartOffset(&startOffset);
2949 NS_ENSURE_SUCCESS(res, res);
2951 nsCOMPtr<nsIDOMNode> childNode = GetChildAt(startParent, startOffset);
2952 // This means selection is probably at a text node (or end of doc?)
2953 NS_ENSURE_TRUE(childNode, NS_ERROR_FAILURE);
2955 nsCOMPtr<nsIDOMNode> endParent;
2956 res = aRange->GetEndContainer(getter_AddRefs(endParent));
2957 NS_ENSURE_SUCCESS(res, res);
2958 NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
2960 PRInt32 endOffset;
2961 res = aRange->GetEndOffset(&endOffset);
2962 NS_ENSURE_SUCCESS(res, res);
2964 // If a cell is deleted, the range is collapse
2965 // (startOffset == endOffset)
2966 // so tell caller the cell wasn't found
2967 if (startParent == endParent &&
2968 endOffset == startOffset+1 &&
2969 nsHTMLEditUtils::IsTableCell(childNode))
2971 // Should we also test if frame is selected? (Use GetCellDataAt())
2972 // (Let's not for now -- more efficient)
2973 nsCOMPtr<nsIDOMElement> cellElement = do_QueryInterface(childNode);
2974 *aCell = cellElement.get();
2975 NS_ADDREF(*aCell);
2976 return NS_OK;
2978 return NS_EDITOR_ELEMENT_NOT_FOUND;
2981 NS_IMETHODIMP
2982 nsHTMLEditor::GetFirstSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell)
2984 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
2985 *aCell = nsnull;
2986 if (aRange) *aRange = nsnull;
2988 nsCOMPtr<nsISelection> selection;
2989 nsresult res = GetSelection(getter_AddRefs(selection));
2990 NS_ENSURE_SUCCESS(res, res);
2991 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
2993 nsCOMPtr<nsIDOMRange> range;
2994 res = selection->GetRangeAt(0, getter_AddRefs(range));
2995 NS_ENSURE_SUCCESS(res, res);
2996 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
2998 mSelectedCellIndex = 0;
3000 res = GetCellFromRange(range, aCell);
3001 // Failure here probably means selection is in a text node,
3002 // so there's no selected cell
3003 NS_ENSURE_SUCCESS(res, NS_EDITOR_ELEMENT_NOT_FOUND);
3004 // No cell means range was collapsed (cell was deleted)
3005 NS_ENSURE_TRUE(*aCell, NS_EDITOR_ELEMENT_NOT_FOUND);
3007 if (aRange)
3009 *aRange = range.get();
3010 NS_ADDREF(*aRange);
3013 // Setup for next cell
3014 mSelectedCellIndex = 1;
3016 return res;
3019 NS_IMETHODIMP
3020 nsHTMLEditor::GetNextSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell)
3022 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
3023 *aCell = nsnull;
3024 if (aRange) *aRange = nsnull;
3026 nsCOMPtr<nsISelection> selection;
3027 nsresult res = GetSelection(getter_AddRefs(selection));
3028 NS_ENSURE_SUCCESS(res, res);
3029 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
3031 PRInt32 rangeCount;
3032 res = selection->GetRangeCount(&rangeCount);
3033 NS_ENSURE_SUCCESS(res, res);
3035 // Don't even try if index exceeds range count
3036 if (mSelectedCellIndex >= rangeCount)
3037 return NS_EDITOR_ELEMENT_NOT_FOUND;
3039 // Scan through ranges to find next valid selected cell
3040 nsCOMPtr<nsIDOMRange> range;
3041 for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++)
3043 res = selection->GetRangeAt(mSelectedCellIndex, getter_AddRefs(range));
3044 NS_ENSURE_SUCCESS(res, res);
3045 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
3047 res = GetCellFromRange(range, aCell);
3048 // Failure here means the range doesn't contain a cell
3049 NS_ENSURE_SUCCESS(res, NS_EDITOR_ELEMENT_NOT_FOUND);
3051 // We found a selected cell
3052 if (*aCell) break;
3053 #ifdef DEBUG_cmanske
3054 else
3055 printf("GetNextSelectedCell: Collapsed range found\n");
3056 #endif
3058 // If we didn't find a cell, continue to next range in selection
3060 // No cell means all remaining ranges were collapsed (cells were deleted)
3061 NS_ENSURE_TRUE(*aCell, NS_EDITOR_ELEMENT_NOT_FOUND);
3063 if (aRange)
3065 *aRange = range.get();
3066 NS_ADDREF(*aRange);
3069 // Setup for next cell
3070 mSelectedCellIndex++;
3072 return res;
3075 NS_IMETHODIMP
3076 nsHTMLEditor::GetFirstSelectedCellInTable(PRInt32 *aRowIndex, PRInt32 *aColIndex, nsIDOMElement **aCell)
3078 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
3079 *aCell = nsnull;
3080 if (aRowIndex)
3081 *aRowIndex = 0;
3082 if (aColIndex)
3083 *aColIndex = 0;
3085 nsCOMPtr<nsIDOMElement> cell;
3086 nsresult res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
3087 NS_ENSURE_SUCCESS(res, res);
3088 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND);
3090 *aCell = cell.get();
3091 NS_ADDREF(*aCell);
3093 // Also return the row and/or column if requested
3094 if (aRowIndex || aColIndex)
3096 PRInt32 startRowIndex, startColIndex;
3097 res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
3098 if(NS_FAILED(res)) return res;
3100 if (aRowIndex)
3101 *aRowIndex = startRowIndex;
3103 if (aColIndex)
3104 *aColIndex = startColIndex;
3107 return res;
3110 NS_IMETHODIMP
3111 nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol,
3112 PRInt32 aDirection, PRBool aSelected)
3114 NS_ENSURE_TRUE(aTable, NS_ERROR_NOT_INITIALIZED);
3116 nsCOMPtr<nsISelection>selection;
3117 nsresult res = GetSelection(getter_AddRefs(selection));
3118 NS_ENSURE_SUCCESS(res, res);
3120 if (!selection)
3122 #ifdef DEBUG_cmanske
3123 printf("Selection not found after table manipulation!\n");
3124 #endif
3125 return NS_ERROR_FAILURE;
3128 nsCOMPtr<nsIDOMElement> cell;
3129 PRBool done = PR_FALSE;
3130 do {
3131 res = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell));
3132 if (NS_SUCCEEDED(res))
3134 if (cell)
3136 if (aSelected)
3138 // Reselect the cell
3139 return SelectElement(cell);
3141 else
3143 // Set the caret to deepest first child
3144 // but don't go into nested tables
3145 // TODO: Should we really be placing the caret at the END
3146 // of the cell content?
3147 return CollapseSelectionToDeepestNonTableFirstChild(selection, cell);
3149 } else {
3150 // Setup index to find another cell in the
3151 // direction requested, but move in
3152 // other direction if already at beginning of row or column
3153 switch (aDirection)
3155 case ePreviousColumn:
3156 if (aCol == 0)
3158 if (aRow > 0)
3159 aRow--;
3160 else
3161 done = PR_TRUE;
3163 else
3164 aCol--;
3165 break;
3166 case ePreviousRow:
3167 if (aRow == 0)
3169 if (aCol > 0)
3170 aCol--;
3171 else
3172 done = PR_TRUE;
3174 else
3175 aRow--;
3176 break;
3177 default:
3178 done = PR_TRUE;
3182 else
3183 break;
3184 } while (!done);
3186 // We didn't find a cell
3187 // Set selection to just before the table
3188 nsCOMPtr<nsIDOMNode> tableParent;
3189 PRInt32 tableOffset;
3190 res = aTable->GetParentNode(getter_AddRefs(tableParent));
3191 if(NS_SUCCEEDED(res) && tableParent)
3193 if(NS_SUCCEEDED(GetChildOffset(aTable, tableParent, tableOffset)))
3194 return selection->Collapse(tableParent, tableOffset);
3196 // Last resort: Set selection to start of doc
3197 // (it's very bad to not have a valid selection!)
3198 return SetSelectionAtDocumentStart(selection);
3201 NS_IMETHODIMP
3202 nsHTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName,
3203 PRInt32 *aSelectedCount,
3204 nsIDOMElement** aTableElement)
3206 NS_ENSURE_ARG_POINTER(aTableElement);
3207 NS_ENSURE_ARG_POINTER(aSelectedCount);
3208 *aTableElement = nsnull;
3209 aTagName.Truncate();
3210 *aSelectedCount = 0;
3212 nsCOMPtr<nsISelection> selection;
3213 nsresult res = GetSelection(getter_AddRefs(selection));
3214 NS_ENSURE_SUCCESS(res, res);
3215 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
3217 // Try to get the first selected cell
3218 nsCOMPtr<nsIDOMElement> tableOrCellElement;
3219 res = GetFirstSelectedCell(nsnull, getter_AddRefs(tableOrCellElement));
3220 NS_ENSURE_SUCCESS(res, res);
3222 NS_NAMED_LITERAL_STRING(tdName, "td");
3224 if (tableOrCellElement)
3226 // Each cell is in its own selection range,
3227 // so count signals multiple-cell selection
3228 res = selection->GetRangeCount(aSelectedCount);
3229 NS_ENSURE_SUCCESS(res, res);
3230 aTagName = tdName;
3232 else
3234 nsCOMPtr<nsIDOMNode> anchorNode;
3235 res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
3236 if(NS_FAILED(res)) return res;
3237 NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE);
3239 nsCOMPtr<nsIDOMNode> selectedNode;
3241 // Get child of anchor node, if exists
3242 PRBool hasChildren;
3243 anchorNode->HasChildNodes(&hasChildren);
3245 if (hasChildren)
3247 PRInt32 anchorOffset;
3248 res = selection->GetAnchorOffset(&anchorOffset);
3249 NS_ENSURE_SUCCESS(res, res);
3250 selectedNode = GetChildAt(anchorNode, anchorOffset);
3251 if (!selectedNode)
3253 selectedNode = anchorNode;
3254 // If anchor doesn't have a child, we can't be selecting a table element,
3255 // so don't do the following:
3257 else
3259 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(selectedNode);
3261 if (atom == nsEditProperty::td)
3263 tableOrCellElement = do_QueryInterface(selectedNode);
3264 aTagName = tdName;
3265 // Each cell is in its own selection range,
3266 // so count signals multiple-cell selection
3267 res = selection->GetRangeCount(aSelectedCount);
3268 NS_ENSURE_SUCCESS(res, res);
3270 else if (atom == nsEditProperty::table)
3272 tableOrCellElement = do_QueryInterface(selectedNode);
3273 aTagName.AssignLiteral("table");
3274 *aSelectedCount = 1;
3276 else if (atom == nsEditProperty::tr)
3278 tableOrCellElement = do_QueryInterface(selectedNode);
3279 aTagName.AssignLiteral("tr");
3280 *aSelectedCount = 1;
3284 if (!tableOrCellElement)
3286 // Didn't find a table element -- find a cell parent
3287 res = GetElementOrParentByTagName(tdName, anchorNode, getter_AddRefs(tableOrCellElement));
3288 if(NS_FAILED(res)) return res;
3289 if (tableOrCellElement)
3290 aTagName = tdName;
3293 if (tableOrCellElement)
3295 *aTableElement = tableOrCellElement.get();
3296 NS_ADDREF(*aTableElement);
3298 return res;
3301 NS_IMETHODIMP
3302 nsHTMLEditor::GetSelectedCellsType(nsIDOMElement *aElement, PRUint32 *aSelectionType)
3304 NS_ENSURE_ARG_POINTER(aSelectionType);
3305 *aSelectionType = 0;
3307 // Be sure we have a table element
3308 // (if aElement is null, this uses selection's anchor node)
3309 nsCOMPtr<nsIDOMElement> table;
3311 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aElement, getter_AddRefs(table));
3312 NS_ENSURE_SUCCESS(res, res);
3314 PRInt32 rowCount, colCount;
3315 res = GetTableSize(table, &rowCount, &colCount);
3316 NS_ENSURE_SUCCESS(res, res);
3318 // Traverse all selected cells
3319 nsCOMPtr<nsIDOMElement> selectedCell;
3320 res = GetFirstSelectedCell(nsnull, getter_AddRefs(selectedCell));
3321 NS_ENSURE_SUCCESS(res, res);
3322 if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK;
3324 // We have at least one selected cell, so set return value
3325 *aSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
3327 // Store indexes of each row/col to avoid duplication of searches
3328 nsTArray<PRInt32> indexArray;
3330 PRBool allCellsInRowAreSelected = PR_FALSE;
3331 PRBool allCellsInColAreSelected = PR_FALSE;
3332 while (NS_SUCCEEDED(res) && selectedCell)
3334 // Get the cell's location in the cellmap
3335 PRInt32 startRowIndex, startColIndex;
3336 res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
3337 if(NS_FAILED(res)) return res;
3339 if (!indexArray.Contains(startColIndex))
3341 indexArray.AppendElement(startColIndex);
3342 allCellsInRowAreSelected = AllCellsInRowSelected(table, startRowIndex, colCount);
3343 // We're done as soon as we fail for any row
3344 if (!allCellsInRowAreSelected) break;
3346 res = GetNextSelectedCell(nsnull, getter_AddRefs(selectedCell));
3349 if (allCellsInRowAreSelected)
3351 *aSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
3352 return NS_OK;
3354 // Test for columns
3356 // Empty the indexArray
3357 indexArray.Clear();
3359 // Start at first cell again
3360 res = GetFirstSelectedCell(nsnull, getter_AddRefs(selectedCell));
3361 while (NS_SUCCEEDED(res) && selectedCell)
3363 // Get the cell's location in the cellmap
3364 PRInt32 startRowIndex, startColIndex;
3365 res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
3366 if(NS_FAILED(res)) return res;
3368 if (!indexArray.Contains(startRowIndex))
3370 indexArray.AppendElement(startColIndex);
3371 allCellsInColAreSelected = AllCellsInColumnSelected(table, startColIndex, rowCount);
3372 // We're done as soon as we fail for any column
3373 if (!allCellsInRowAreSelected) break;
3375 res = GetNextSelectedCell(nsnull, getter_AddRefs(selectedCell));
3377 if (allCellsInColAreSelected)
3378 *aSelectionType = nsISelectionPrivate::TABLESELECTION_COLUMN;
3380 return NS_OK;
3383 PRBool
3384 nsHTMLEditor::AllCellsInRowSelected(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aNumberOfColumns)
3386 NS_ENSURE_TRUE(aTable, PR_FALSE);
3388 PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
3389 PRBool isSelected;
3391 for( PRInt32 col = 0; col < aNumberOfColumns; col += NS_MAX(actualColSpan, 1))
3393 nsCOMPtr<nsIDOMElement> cell;
3394 nsresult res = GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell),
3395 &curStartRowIndex, &curStartColIndex,
3396 &rowSpan, &colSpan,
3397 &actualRowSpan, &actualColSpan, &isSelected);
3399 NS_ENSURE_SUCCESS(res, PR_FALSE);
3400 // If no cell, we may have a "ragged" right edge,
3401 // so return TRUE only if we already found a cell in the row
3402 NS_ENSURE_TRUE(cell, (col > 0) ? PR_TRUE : PR_FALSE);
3404 // Return as soon as a non-selected cell is found
3405 NS_ENSURE_TRUE(isSelected, PR_FALSE);
3407 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in AllCellsInRowSelected");
3409 return PR_TRUE;
3412 PRBool
3413 nsHTMLEditor::AllCellsInColumnSelected(nsIDOMElement *aTable, PRInt32 aColIndex, PRInt32 aNumberOfRows)
3415 NS_ENSURE_TRUE(aTable, PR_FALSE);
3417 PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
3418 PRBool isSelected;
3420 for( PRInt32 row = 0; row < aNumberOfRows; row += NS_MAX(actualRowSpan, 1))
3422 nsCOMPtr<nsIDOMElement> cell;
3423 nsresult res = GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell),
3424 &curStartRowIndex, &curStartColIndex,
3425 &rowSpan, &colSpan,
3426 &actualRowSpan, &actualColSpan, &isSelected);
3428 NS_ENSURE_SUCCESS(res, PR_FALSE);
3429 // If no cell, we must have a "ragged" right edge on the last column
3430 // so return TRUE only if we already found a cell in the row
3431 NS_ENSURE_TRUE(cell, (row > 0) ? PR_TRUE : PR_FALSE);
3433 // Return as soon as a non-selected cell is found
3434 NS_ENSURE_TRUE(isSelected, PR_FALSE);
3436 return PR_TRUE;
3439 PRBool
3440 nsHTMLEditor::IsEmptyCell(nsIDOMElement *aCell)
3442 nsCOMPtr<nsIDOMNode> cellChild;
3444 // Check if target only contains empty text node or <br>
3445 nsresult res = aCell->GetFirstChild(getter_AddRefs(cellChild));
3446 NS_ENSURE_SUCCESS(res, PR_FALSE);
3448 if (cellChild)
3450 nsCOMPtr<nsIDOMNode> nextChild;
3451 res = cellChild->GetNextSibling(getter_AddRefs(nextChild));
3452 NS_ENSURE_SUCCESS(res, PR_FALSE);
3453 if (!nextChild)
3455 // We insert a single break into a cell by default
3456 // to have some place to locate a cursor -- it is dispensable
3457 PRBool isEmpty = nsTextEditUtils::IsBreak(cellChild);
3458 // Or check if no real content
3459 if (!isEmpty)
3461 res = IsEmptyNode(cellChild, &isEmpty, PR_FALSE, PR_FALSE);
3462 NS_ENSURE_SUCCESS(res, PR_FALSE);
3465 return isEmpty;
3468 return PR_FALSE;