Back to index

lightning-sunbird  0.9+nobinonly
nsTableEditor.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nscore.h"
00040 #include "nsIDOMDocument.h"
00041 #include "nsEditor.h"
00042 #include "nsIDOMText.h"
00043 #include "nsIDOMElement.h"
00044 #include "nsIDOMAttr.h"
00045 #include "nsIDOMNode.h"
00046 #include "nsIDOMNodeList.h"
00047 #include "nsIDOMRange.h"
00048 #include "nsISelection.h"
00049 #include "nsISelectionPrivate.h"
00050 #include "nsLayoutCID.h"
00051 #include "nsIContent.h"
00052 #include "nsIContentIterator.h"
00053 #include "nsIAtom.h"
00054 #include "nsIDOMHTMLTableElement.h"
00055 #include "nsIDOMHTMLTableCellElement.h"
00056 #include "nsITableCellLayout.h" // For efficient access to table cell
00057 #include "nsITableLayout.h"     //  data owned by the table and cell frames
00058 #include "nsHTMLEditor.h"
00059 #include "nsISelectionPrivate.h"  // For nsISelectionPrivate::TABLESELECTION_ defines
00060 #include "nsVoidArray.h"
00061 
00062 #include "nsEditorUtils.h"
00063 #include "nsTextEditUtils.h"
00064 #include "nsHTMLEditUtils.h"
00065 #include "nsLayoutErrors.h"
00066 
00067 
00068 
00069 /***************************************************************************
00070  * stack based helper class for restoring selection after table edit
00071  */
00072 class nsSetSelectionAfterTableEdit
00073 {
00074   private:
00075     nsCOMPtr<nsITableEditor> mEd;
00076     nsCOMPtr<nsIDOMElement> mTable;
00077     PRInt32 mCol, mRow, mDirection, mSelected;
00078   public:
00079     nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, 
00080                                  PRInt32 aRow, PRInt32 aCol, PRInt32 aDirection, 
00081                                  PRBool aSelected) : 
00082         mEd(do_QueryInterface(aEd))
00083     { 
00084       mTable = aTable; 
00085       mRow = aRow; 
00086       mCol = aCol; 
00087       mDirection = aDirection;
00088       mSelected = aSelected;
00089     } 
00090     
00091     ~nsSetSelectionAfterTableEdit() 
00092     { 
00093       if (mEd)
00094         mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected);
00095     }
00096     // This is needed to abort the caret reset in the destructor
00097     //  when one method yields control to another
00098     void CancelSetCaret() {mEd = nsnull; mTable = nsnull;}
00099 };
00100 
00101 // Stack-class to turn on/off selection batching for table selection
00102 class nsSelectionBatcher
00103 {
00104 private:
00105   nsCOMPtr<nsISelectionPrivate> mSelection;
00106 public:
00107   nsSelectionBatcher(nsISelection *aSelection)
00108   {
00109     nsCOMPtr<nsISelection> sel(aSelection);
00110     mSelection = do_QueryInterface(sel);
00111     if (mSelection)  mSelection->StartBatchChanges();
00112   }
00113   virtual ~nsSelectionBatcher() 
00114   { 
00115     if (mSelection) mSelection->EndBatchChanges();
00116   }
00117 };
00118 
00119 // Table Editing helper utilities (not exposed in IDL)
00120 
00121 NS_IMETHODIMP
00122 nsHTMLEditor::InsertCell(nsIDOMElement *aCell, PRInt32 aRowSpan, PRInt32 aColSpan, 
00123                          PRBool aAfter, PRBool aIsHeader, nsIDOMElement **aNewCell)
00124 {
00125   if (!aCell) return NS_ERROR_NULL_POINTER;
00126   if (aNewCell) *aNewCell = nsnull;
00127 
00128   // And the parent and offsets needed to do an insert
00129   nsCOMPtr<nsIDOMNode> cellParent;
00130   nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent));
00131   if (NS_FAILED(res)) return res;
00132   if (!cellParent) return NS_ERROR_NULL_POINTER;
00133 
00134 
00135   PRInt32 cellOffset;
00136   res = GetChildOffset(aCell, cellParent, cellOffset);
00137   if (NS_FAILED(res)) return res;
00138 
00139   nsCOMPtr<nsIDOMElement> newCell;
00140   if (aIsHeader)
00141     res = CreateElementWithDefaults(NS_LITERAL_STRING("th"), getter_AddRefs(newCell));
00142   else
00143     res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
00144     
00145   if(NS_FAILED(res)) return res;
00146   if(!newCell) return NS_ERROR_FAILURE;
00147 
00148   //Optional: return new cell created
00149   if (aNewCell)
00150   {
00151     *aNewCell = newCell.get();
00152     NS_ADDREF(*aNewCell);
00153   }
00154 
00155   if( aRowSpan > 1)
00156   {
00157     // Note: Do NOT use editor transaction for this
00158     nsAutoString newRowSpan;
00159     newRowSpan.AppendInt(aRowSpan, 10);
00160     newCell->SetAttribute(NS_LITERAL_STRING("rowspan"), newRowSpan);
00161   }
00162   if( aColSpan > 1)
00163   {
00164     // Note: Do NOT use editor transaction for this
00165     nsAutoString newColSpan;
00166     newColSpan.AppendInt(aColSpan, 10);
00167     newCell->SetAttribute(NS_LITERAL_STRING("colspan"), newColSpan);
00168   }
00169   if(aAfter) cellOffset++;
00170 
00171   //Don't let Rules System change the selection
00172   nsAutoTxnsConserveSelection dontChangeSelection(this);
00173   return InsertNode(newCell, cellParent, cellOffset);
00174 }
00175 
00176 NS_IMETHODIMP nsHTMLEditor::SetColSpan(nsIDOMElement *aCell, PRInt32 aColSpan)
00177 {
00178   if (!aCell) return NS_ERROR_NULL_POINTER;
00179   nsAutoString newSpan;
00180   newSpan.AppendInt(aColSpan, 10);
00181   return SetAttribute(aCell, NS_LITERAL_STRING("colspan"), newSpan);
00182 }
00183 
00184 NS_IMETHODIMP nsHTMLEditor::SetRowSpan(nsIDOMElement *aCell, PRInt32 aRowSpan)
00185 {
00186   if (!aCell) return NS_ERROR_NULL_POINTER;
00187   nsAutoString newSpan;
00188   newSpan.AppendInt(aRowSpan, 10);
00189   return SetAttribute(aCell, NS_LITERAL_STRING("rowspan"), newSpan);
00190 }
00191 
00192 /****************************************************************/
00193 
00194 // Table Editing interface methods
00195 
00196 NS_IMETHODIMP
00197 nsHTMLEditor::InsertTableCell(PRInt32 aNumber, PRBool aAfter)
00198 {
00199   nsCOMPtr<nsIDOMElement> table;
00200   nsCOMPtr<nsIDOMElement> curCell;
00201   nsCOMPtr<nsIDOMNode> cellParent;
00202   PRInt32 cellOffset, startRowIndex, startColIndex;
00203   nsresult res = GetCellContext(nsnull,
00204                                 getter_AddRefs(table), 
00205                                 getter_AddRefs(curCell), 
00206                                 getter_AddRefs(cellParent), &cellOffset,
00207                                 &startRowIndex, &startColIndex);
00208   if (NS_FAILED(res)) return res;
00209   // Don't fail if no cell found
00210   if (!curCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
00211 
00212   // Get more data for current cell in row we are inserting at (we need COLSPAN)
00213   PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
00214   PRBool  isSelected;
00215   res = GetCellDataAt(table, startRowIndex, startColIndex,
00216                       getter_AddRefs(curCell),
00217                       &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan,
00218                       &actualRowSpan, &actualColSpan, &isSelected);
00219   if (NS_FAILED(res)) return res;
00220   if (!curCell) return NS_ERROR_FAILURE;
00221   PRInt32 newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex;
00222   //We control selection resetting after the insert...
00223   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, PR_FALSE);
00224   //...so suppress Rules System selection munging
00225   nsAutoTxnsConserveSelection dontChangeSelection(this);
00226 
00227   PRInt32 i;
00228   for (i = 0; i < aNumber; i++)
00229   {
00230     nsCOMPtr<nsIDOMElement> newCell;
00231     res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
00232     if (NS_SUCCEEDED(res) && newCell)
00233     {
00234       if (aAfter) cellOffset++;
00235       res = InsertNode(newCell, cellParent, cellOffset);
00236       if(NS_FAILED(res)) break;
00237     }
00238   }
00239   return res;
00240 }
00241 
00242 
00243 NS_IMETHODIMP 
00244 nsHTMLEditor::GetFirstRow(nsIDOMElement* aTableElement, nsIDOMNode** aRowNode)
00245 {
00246   if (!aRowNode) return NS_ERROR_NULL_POINTER;
00247 
00248   *aRowNode = nsnull;  
00249 
00250   if (!aTableElement) return NS_ERROR_NULL_POINTER;
00251 
00252   nsCOMPtr<nsIDOMElement> tableElement;
00253   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTableElement, getter_AddRefs(tableElement));
00254   if (NS_FAILED(res)) return res;
00255   if (!tableElement) return NS_ERROR_NULL_POINTER;
00256 
00257   nsCOMPtr<nsIDOMNode> tableChild;
00258   res = tableElement->GetFirstChild(getter_AddRefs(tableChild));
00259   if (NS_FAILED(res)) return res;
00260 
00261   while (tableChild)
00262   {
00263     nsCOMPtr<nsIContent> content = do_QueryInterface(tableChild);
00264     if (content)
00265     {
00266       nsIAtom *atom = content->Tag();
00267 
00268       if (atom == nsEditProperty::tr)
00269       {
00270         // Found a row directly under <table>
00271         *aRowNode = tableChild;
00272         NS_ADDREF(*aRowNode);
00273         return NS_OK;
00274       }
00275       // Look for row in one of the row container elements      
00276       if (atom == nsEditProperty::tbody ||
00277           atom == nsEditProperty::thead ||
00278           atom == nsEditProperty::tfoot)
00279       {
00280         nsCOMPtr<nsIDOMNode> rowNode;
00281         res = tableChild->GetFirstChild(getter_AddRefs(rowNode));
00282         if (NS_FAILED(res)) return res;
00283         
00284         // We can encounter textnodes here -- must find a row
00285         while (rowNode && !nsHTMLEditUtils::IsTableRow(rowNode))
00286         {
00287           nsCOMPtr<nsIDOMNode> nextNode;
00288           res = rowNode->GetNextSibling(getter_AddRefs(nextNode));
00289           if (NS_FAILED(res)) return res;
00290 
00291           rowNode = nextNode;
00292         }
00293         if(rowNode)
00294         {
00295           *aRowNode = rowNode.get();
00296           NS_ADDREF(*aRowNode);
00297           return NS_OK;
00298         }
00299       }
00300     }
00301     // Here if table child was a CAPTION or COLGROUP
00302     //  or child of a row parent wasn't a row (bad HTML?),
00303     //  or first child was a textnode
00304     // Look in next table child
00305     nsCOMPtr<nsIDOMNode> nextChild;
00306     res = tableChild->GetNextSibling(getter_AddRefs(nextChild));
00307     if (NS_FAILED(res)) return res;
00308 
00309     tableChild = nextChild;
00310   };
00311   // If here, row was not found
00312   return NS_EDITOR_ELEMENT_NOT_FOUND;
00313 }
00314 
00315 NS_IMETHODIMP 
00316 nsHTMLEditor::GetNextRow(nsIDOMNode* aCurrentRowNode, nsIDOMNode **aRowNode)
00317 {
00318   if (!aRowNode) return NS_ERROR_NULL_POINTER;
00319 
00320   *aRowNode = nsnull;  
00321 
00322   if (!aCurrentRowNode) return NS_ERROR_NULL_POINTER;
00323 
00324   if (!nsHTMLEditUtils::IsTableRow(aCurrentRowNode))
00325     return NS_ERROR_FAILURE;
00326   
00327   nsCOMPtr<nsIDOMNode> nextRow;
00328   nsresult res = aCurrentRowNode->GetNextSibling(getter_AddRefs(nextRow));
00329   if (NS_FAILED(res)) return res;
00330 
00331   nsCOMPtr<nsIDOMNode> nextNode;
00332 
00333   // Skip over any textnodes here
00334   while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow))
00335   {
00336     res = nextRow->GetNextSibling(getter_AddRefs(nextNode));
00337     if (NS_FAILED(res)) return res;
00338   
00339     nextRow = nextNode;
00340   }
00341   if(nextRow)
00342   {
00343     *aRowNode = nextRow.get();
00344     NS_ADDREF(*aRowNode);
00345     return NS_OK;
00346   }
00347 
00348   // No row found, search for rows in other table sections
00349   nsCOMPtr<nsIDOMNode> rowParent;
00350   res = aCurrentRowNode->GetParentNode(getter_AddRefs(rowParent));
00351   if (NS_FAILED(res)) return res;
00352   if (!rowParent) return NS_ERROR_NULL_POINTER;
00353 
00354   nsCOMPtr<nsIDOMNode> parentSibling;
00355   res = rowParent->GetNextSibling(getter_AddRefs(parentSibling));
00356   if (NS_FAILED(res)) return res;
00357 
00358   while (parentSibling)
00359   {
00360     res = parentSibling->GetFirstChild(getter_AddRefs(nextRow));
00361     if (NS_FAILED(res)) return res;
00362   
00363     // We can encounter textnodes here -- must find a row
00364     while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow))
00365     {
00366       res = nextRow->GetNextSibling(getter_AddRefs(nextNode));
00367       if (NS_FAILED(res)) return res;
00368 
00369       nextRow = nextNode;
00370     }
00371     if(nextRow)
00372     {
00373       *aRowNode = nextRow.get();
00374       NS_ADDREF(*aRowNode);
00375       return NS_OK;
00376     }
00377 #ifdef DEBUG_cmanske
00378     printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n");
00379 #endif
00380     // We arrive here only if a table section has no children 
00381     //  or first child of section is not a row (bad HTML or more "_moz_text" nodes!)
00382     // So look for another section sibling
00383     res = parentSibling->GetNextSibling(getter_AddRefs(nextNode));
00384     if (NS_FAILED(res)) return res;
00385 
00386     parentSibling = nextNode;
00387   }
00388   // If here, row was not found
00389   return NS_EDITOR_ELEMENT_NOT_FOUND;
00390 }
00391 
00392 NS_IMETHODIMP 
00393 nsHTMLEditor::GetFirstCellInRow(nsIDOMNode* aRowNode, nsIDOMNode** aCellNode)
00394 {
00395   if (!aCellNode) return NS_ERROR_NULL_POINTER;
00396 
00397   *aCellNode = nsnull;
00398 
00399   if (!aRowNode) return NS_ERROR_NULL_POINTER;
00400 
00401   nsCOMPtr<nsIDOMNode> rowChild;
00402   nsresult res = aRowNode->GetFirstChild(getter_AddRefs(rowChild));
00403   if (NS_FAILED(res)) return res;
00404 
00405   while (rowChild && !nsHTMLEditUtils::IsTableCell(rowChild))
00406   {
00407     // Skip over textnodes
00408     nsCOMPtr<nsIDOMNode> nextChild;
00409     res = rowChild->GetNextSibling(getter_AddRefs(nextChild));
00410     if (NS_FAILED(res)) return res;
00411 
00412     rowChild = nextChild;
00413   };
00414   if (rowChild)
00415   {
00416     *aCellNode = rowChild.get();
00417     NS_ADDREF(*aCellNode);
00418     return NS_OK;
00419   }
00420   // If here, cell was not found
00421   return NS_EDITOR_ELEMENT_NOT_FOUND;
00422 }
00423 
00424 NS_IMETHODIMP 
00425 nsHTMLEditor::GetNextCellInRow(nsIDOMNode* aCurrentCellNode, nsIDOMNode** aCellNode)
00426 {
00427   if (!aCellNode) return NS_ERROR_NULL_POINTER;
00428 
00429   *aCellNode = nsnull;
00430 
00431   if (!aCurrentCellNode) return NS_ERROR_NULL_POINTER;
00432 
00433   nsCOMPtr<nsIDOMNode> nextCell;
00434   nsresult res = aCurrentCellNode->GetNextSibling(getter_AddRefs(nextCell));
00435   if (NS_FAILED(res)) return res;
00436 
00437   while (nextCell && !nsHTMLEditUtils::IsTableCell(nextCell))
00438   {
00439     // Skip over textnodes
00440     nsCOMPtr<nsIDOMNode> nextChild;
00441     res = nextCell->GetNextSibling(getter_AddRefs(nextChild));
00442     if (NS_FAILED(res)) return res;
00443 
00444     nextCell = nextChild;
00445   };
00446   if (nextCell)
00447   {
00448     *aCellNode = nextCell.get();
00449     NS_ADDREF(*aCellNode);
00450     return NS_OK;
00451   }
00452   // If here, cell was not found
00453   return NS_EDITOR_ELEMENT_NOT_FOUND;
00454 }
00455 
00456 NS_IMETHODIMP 
00457 nsHTMLEditor::GetLastCellInRow(nsIDOMNode* aRowNode, nsIDOMNode** aCellNode)
00458 {
00459   if (!aCellNode) return NS_ERROR_NULL_POINTER;
00460 
00461   *aCellNode = nsnull;
00462 
00463   if (!aRowNode) return NS_ERROR_NULL_POINTER;
00464 
00465   nsCOMPtr<nsIDOMNode> rowChild;
00466   nsresult res = aRowNode->GetLastChild(getter_AddRefs(rowChild));
00467   if (NS_FAILED(res)) return res;
00468 
00469   while (rowChild && !nsHTMLEditUtils::IsTableCell(rowChild))
00470   {
00471     // Skip over textnodes
00472     nsCOMPtr<nsIDOMNode> previousChild;
00473     res = rowChild->GetPreviousSibling(getter_AddRefs(previousChild));
00474     if (NS_FAILED(res)) return res;
00475 
00476     rowChild = previousChild;
00477   };
00478   if (rowChild)
00479   {
00480     *aCellNode = rowChild.get();
00481     NS_ADDREF(*aCellNode);
00482     return NS_OK;
00483   }
00484   // If here, cell was not found
00485   return NS_EDITOR_ELEMENT_NOT_FOUND;
00486 }
00487 
00488 NS_IMETHODIMP
00489 nsHTMLEditor::InsertTableColumn(PRInt32 aNumber, PRBool aAfter)
00490 {
00491   nsCOMPtr<nsISelection> selection;
00492   nsCOMPtr<nsIDOMElement> table;
00493   nsCOMPtr<nsIDOMElement> curCell;
00494   PRInt32 startRowIndex, startColIndex;
00495   nsresult res = GetCellContext(getter_AddRefs(selection),
00496                                 getter_AddRefs(table), 
00497                                 getter_AddRefs(curCell), 
00498                                 nsnull, nsnull,
00499                                 &startRowIndex, &startColIndex);
00500   if (NS_FAILED(res)) return res;
00501   // Don't fail if no cell found
00502   if (!curCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
00503 
00504   // Get more data for current cell (we need ROWSPAN)
00505   PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
00506   PRBool  isSelected;
00507   res = GetCellDataAt(table, startRowIndex, startColIndex,
00508                       getter_AddRefs(curCell),
00509                       &curStartRowIndex, &curStartColIndex,
00510                       &rowSpan, &colSpan, 
00511                       &actualRowSpan, &actualColSpan, &isSelected);
00512   if (NS_FAILED(res)) return res;
00513   if (!curCell) return NS_ERROR_FAILURE;
00514 
00515   nsAutoEditBatch beginBatching(this);
00516   // Prevent auto insertion of BR in new cell until we're done
00517   nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
00518 
00519   // Use column after current cell if requested
00520   if (aAfter)
00521   {
00522     startColIndex += actualColSpan;
00523     //Detect when user is adding after a COLSPAN=0 case
00524     // Assume they want to stop the "0" behavior and
00525     // really add a new column. Thus we set the 
00526     // colspan to its true value
00527     if (colSpan == 0)
00528       SetColSpan(curCell, actualColSpan);
00529   }
00530    
00531   PRInt32 rowCount, colCount, rowIndex;
00532   res = GetTableSize(table, &rowCount, &colCount);
00533   if (NS_FAILED(res)) return res;
00534 
00535   //We reset caret in destructor...
00536   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
00537   //.. so suppress Rules System selection munging
00538   nsAutoTxnsConserveSelection dontChangeSelection(this);
00539 
00540   // If we are inserting after all existing columns
00541   // Make sure table is "well formed"
00542   //  before appending new column
00543   if (startColIndex >= colCount)
00544     NormalizeTable(table);
00545 
00546   nsCOMPtr<nsIDOMNode> rowNode;
00547   for ( rowIndex = 0; rowIndex < rowCount; rowIndex++)
00548   {
00549 #ifdef DEBUG_cmanske
00550     if (rowIndex == rowCount-1)
00551       printf(" ***InsertTableColumn: Inserting cell at last row: %d\n", rowIndex);
00552 #endif
00553 
00554     if (startColIndex < colCount)
00555     {
00556       // We are inserting before an existing column
00557       res = GetCellDataAt(table, rowIndex, startColIndex,
00558                           getter_AddRefs(curCell),
00559                           &curStartRowIndex, &curStartColIndex,
00560                           &rowSpan, &colSpan, 
00561                           &actualRowSpan, &actualColSpan, &isSelected);
00562       if (NS_FAILED(res)) return res;
00563 
00564       // Don't fail entire process if we fail to find a cell
00565       //  (may fail just in particular rows with < adequate cells per row)
00566       if (curCell)
00567       {
00568         if (curStartColIndex < startColIndex)
00569         {
00570           // We have a cell spanning this location
00571           // Simply increase its colspan to keep table rectangular
00572           // Note: we do nothing if colsSpan=0,
00573           //  since it should automatically span the new column
00574           if (colSpan > 0)
00575             SetColSpan(curCell, colSpan+aNumber);
00576         } else {
00577           // Simply set selection to the current cell 
00578           //  so we can let InsertTableCell() do the work
00579           // Insert a new cell before current one
00580           selection->Collapse(curCell, 0);
00581           res = InsertTableCell(aNumber, PR_FALSE);
00582         }
00583       }
00584     } else {
00585       // Get current row and append new cells after last cell in row
00586       if(rowIndex == 0)
00587         res = GetFirstRow(table.get(), getter_AddRefs(rowNode));
00588       else
00589       {
00590         nsCOMPtr<nsIDOMNode> nextRow;
00591         res = GetNextRow(rowNode.get(), getter_AddRefs(nextRow));
00592         rowNode = nextRow;
00593       }
00594       if (NS_FAILED(res)) return res;
00595 
00596       if (rowNode)
00597       {
00598         nsCOMPtr<nsIDOMNode> lastCell;
00599         res = GetLastCellInRow(rowNode, getter_AddRefs(lastCell));
00600         if (NS_FAILED(res)) return res;
00601         if (!lastCell) return NS_ERROR_FAILURE;
00602 
00603         curCell = do_QueryInterface(lastCell);
00604         if (curCell)
00605         {
00606           // Simply add same number of cells to each row
00607           // Although tempted to check cell indexes for curCell,
00608           //  the effects of COLSPAN>1 in some cells makes this futile!
00609           // We must use NormalizeTable first to assure
00610           //  that there are cells in each cellmap location
00611           selection->Collapse(curCell, 0);
00612           res = InsertTableCell(aNumber, PR_TRUE);
00613         }
00614       }
00615     }
00616   }
00617   return res;
00618 }
00619 
00620 NS_IMETHODIMP
00621 nsHTMLEditor::InsertTableRow(PRInt32 aNumber, PRBool aAfter)
00622 {
00623   nsCOMPtr<nsISelection> selection;
00624   nsCOMPtr<nsIDOMElement> table;
00625   nsCOMPtr<nsIDOMElement> curCell;
00626   
00627   PRInt32 startRowIndex, startColIndex;
00628   nsresult res = GetCellContext(nsnull,
00629                                 getter_AddRefs(table), 
00630                                 getter_AddRefs(curCell), 
00631                                 nsnull, nsnull, 
00632                                 &startRowIndex, &startColIndex);
00633   if (NS_FAILED(res)) return res;
00634   // Don't fail if no cell found
00635   if (!curCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
00636 
00637   // Get more data for current cell in row we are inserting at (we need COLSPAN)
00638   PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
00639   PRBool  isSelected;
00640   res = GetCellDataAt(table, startRowIndex, startColIndex,
00641                       getter_AddRefs(curCell),
00642                       &curStartRowIndex, &curStartColIndex,
00643                       &rowSpan, &colSpan, 
00644                       &actualRowSpan, &actualColSpan, &isSelected);
00645   if (NS_FAILED(res)) return res;
00646   if (!curCell) return NS_ERROR_FAILURE;
00647   
00648   PRInt32 rowCount, colCount;
00649   res = GetTableSize(table, &rowCount, &colCount);
00650   if (NS_FAILED(res)) return res;
00651 
00652   nsAutoEditBatch beginBatching(this);
00653   // Prevent auto insertion of BR in new cell until we're done
00654   nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
00655 
00656   if (aAfter)
00657   {
00658     // Use row after current cell
00659     startRowIndex += actualRowSpan;
00660 
00661     //Detect when user is adding after a ROWSPAN=0 case
00662     // Assume they want to stop the "0" behavior and
00663     // really add a new row. Thus we set the 
00664     // rowspan to its true value
00665     if (rowSpan == 0)
00666       SetRowSpan(curCell, actualRowSpan);
00667   }
00668 
00669   //We control selection resetting after the insert...
00670   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
00671   //...so suppress Rules System selection munging
00672   nsAutoTxnsConserveSelection dontChangeSelection(this);
00673 
00674   nsCOMPtr<nsIDOMElement> cellForRowParent;
00675   PRInt32 cellsInRow = 0;
00676   if (startRowIndex < rowCount)
00677   {
00678     // We are inserting above an existing row
00679     // Get each cell in the insert row to adjust for COLSPAN effects while we
00680     //   count how many cells are needed
00681     PRInt32 colIndex = 0;
00682     // This returns NS_TABLELAYOUT_CELL_NOT_FOUND when we run past end of row,
00683     //   which passes the NS_SUCCEEDED macro
00684     while ( NS_OK == GetCellDataAt(table, startRowIndex, colIndex,
00685                                    getter_AddRefs(curCell), 
00686                                    &curStartRowIndex, &curStartColIndex,
00687                                    &rowSpan, &colSpan, 
00688                                    &actualRowSpan, &actualColSpan,
00689                                    &isSelected) )
00690     {
00691       if (curCell)
00692       {
00693         if (curStartRowIndex < startRowIndex)
00694         {
00695           // We have a cell spanning this location
00696           // Simply increase its rowspan
00697           //Note that if rowSpan == 0, we do nothing,
00698           //  since that cell should automatically extend into the new row
00699           if (rowSpan > 0)
00700             SetRowSpan(curCell, rowSpan+aNumber);
00701         } else {
00702           // We have a cell in the insert row
00703 
00704           // Count the number of cells we need to add to the new row
00705           cellsInRow += actualColSpan;
00706 
00707           // Save cell we will use below
00708           if (!cellForRowParent)
00709             cellForRowParent = curCell;
00710         }
00711         // Next cell in row
00712         colIndex += actualColSpan;
00713       }
00714       else
00715         colIndex++;
00716     }
00717   } else {
00718     // We are adding a new row after all others
00719     // If it weren't for colspan=0 effect, 
00720     // we could simply use colCount for number of new cells...
00721     cellsInRow = colCount;
00722     
00723     // ...but we must compensate for all cells with rowSpan = 0 in the last row
00724     PRInt32 lastRow = rowCount-1;
00725     PRInt32 tempColIndex = 0;
00726     while ( NS_OK == GetCellDataAt(table, lastRow, tempColIndex,
00727                                    getter_AddRefs(curCell), 
00728                                    &curStartRowIndex, &curStartColIndex,
00729                                    &rowSpan, &colSpan, 
00730                                    &actualRowSpan, &actualColSpan,
00731                                    &isSelected) )
00732     {
00733       if (rowSpan == 0)
00734         cellsInRow -= actualColSpan;
00735       
00736       tempColIndex += actualColSpan;
00737 
00738       // Save cell from the last row that we will use below
00739       if (!cellForRowParent && curStartRowIndex == lastRow)
00740         cellForRowParent = curCell;
00741     }
00742   }
00743 
00744   if (cellsInRow > 0)
00745   {
00746     // The row parent and offset where we will insert new row
00747     nsCOMPtr<nsIDOMNode> parentOfRow;
00748     PRInt32 newRowOffset;
00749 
00750     NS_NAMED_LITERAL_STRING(trStr, "tr");
00751     if (cellForRowParent)
00752     {
00753       nsCOMPtr<nsIDOMElement> parentRow;
00754       res = GetElementOrParentByTagName(trStr, cellForRowParent, getter_AddRefs(parentRow));
00755       if (NS_FAILED(res)) return res;
00756       if (!parentRow) return NS_ERROR_NULL_POINTER;
00757 
00758       parentRow->GetParentNode(getter_AddRefs(parentOfRow));
00759       if (!parentOfRow) return NS_ERROR_NULL_POINTER;
00760 
00761       res = GetChildOffset(parentRow, parentOfRow, newRowOffset);
00762       if (NS_FAILED(res)) return res;
00763       
00764       // Adjust for when adding past the end 
00765       if (aAfter && startRowIndex >= rowCount)
00766         newRowOffset++;
00767     }
00768     else
00769       return NS_ERROR_FAILURE;
00770 
00771     for (PRInt32 row = 0; row < aNumber; row++)
00772     {
00773       // Create a new row
00774       nsCOMPtr<nsIDOMElement> newRow;
00775       res = CreateElementWithDefaults(trStr, getter_AddRefs(newRow));
00776       if (NS_SUCCEEDED(res))
00777       {
00778         if (!newRow) return NS_ERROR_FAILURE;
00779       
00780         for (PRInt32 i = 0; i < cellsInRow; i++)
00781         {
00782           nsCOMPtr<nsIDOMElement> newCell;
00783           res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell));
00784           if (NS_FAILED(res)) return res;
00785           if (!newCell) return NS_ERROR_FAILURE;
00786 
00787           // Don't use transaction system yet! (not until entire row is inserted)
00788           nsCOMPtr<nsIDOMNode>resultNode;
00789           res = newRow->AppendChild(newCell, getter_AddRefs(resultNode));
00790           if (NS_FAILED(res)) return res;
00791         }
00792         // Use transaction system to insert the entire row+cells
00793         // (Note that rows are inserted at same childoffset each time)
00794         res = InsertNode(newRow, parentOfRow, newRowOffset);
00795         if (NS_FAILED(res)) return res;
00796       }
00797     }
00798   }
00799   return res;
00800 }
00801 
00802 // Editor helper only
00803 // XXX Code changed for bug 217717 and now we don't need aSelection param
00804 //     TODO: Remove aSelection param
00805 NS_IMETHODIMP
00806 nsHTMLEditor::DeleteTable2(nsIDOMElement *aTable, nsISelection *aSelection)
00807 {
00808   if (!aTable) return NS_ERROR_NULL_POINTER;
00809 
00810   // Select the table
00811   nsresult res = ClearSelection();
00812   if (NS_SUCCEEDED(res))
00813     res = AppendNodeToSelectionAsRange(aTable);
00814   if (NS_FAILED(res)) return res;
00815 
00816   return DeleteSelection(nsIEditor::eNext);
00817 }
00818 
00819 NS_IMETHODIMP
00820 nsHTMLEditor::DeleteTable()
00821 {
00822   nsCOMPtr<nsISelection> selection;
00823   nsCOMPtr<nsIDOMElement> table;
00824   nsresult res = GetCellContext(getter_AddRefs(selection),
00825                                 getter_AddRefs(table), 
00826                                 nsnull, nsnull, nsnull, nsnull, nsnull);
00827     
00828   if (NS_FAILED(res)) return res;
00829 
00830   nsAutoEditBatch beginBatching(this);
00831   return DeleteTable2(table, selection);
00832 }
00833 
00834 NS_IMETHODIMP
00835 nsHTMLEditor::DeleteTableCell(PRInt32 aNumber)
00836 {
00837   nsCOMPtr<nsISelection> selection;
00838   nsCOMPtr<nsIDOMElement> table;
00839   nsCOMPtr<nsIDOMElement> cell;
00840   PRInt32 startRowIndex, startColIndex;
00841 
00842 
00843   nsresult res = GetCellContext(getter_AddRefs(selection),
00844                          getter_AddRefs(table), 
00845                          getter_AddRefs(cell), 
00846                          nsnull, nsnull,
00847                          &startRowIndex, &startColIndex);
00848 
00849   if (NS_FAILED(res)) return res;
00850   // Don't fail if we didn't find a table or cell
00851   if (!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
00852 
00853   nsAutoEditBatch beginBatching(this);
00854   // Prevent rules testing until we're done
00855   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
00856 
00857   nsCOMPtr<nsIDOMElement> firstCell;
00858   nsCOMPtr<nsIDOMRange> range;
00859   res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
00860   if (NS_FAILED(res)) return res;
00861 
00862   PRInt32 rangeCount;
00863   res = selection->GetRangeCount(&rangeCount);
00864   if (NS_FAILED(res)) return res;
00865 
00866   if (firstCell && rangeCount > 1)
00867   {
00868     // When > 1 selected cell,
00869     //  ignore aNumber and use selected cells
00870     cell = firstCell;
00871 
00872     PRInt32 rowCount, colCount;
00873     res = GetTableSize(table, &rowCount, &colCount);
00874     if (NS_FAILED(res)) return res;
00875 
00876     // Get indexes -- may be different than original cell
00877     res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
00878     if (NS_FAILED(res)) return res;
00879 
00880     // The setCaret object will call SetSelectionAfterTableEdit in it's destructor
00881     nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
00882     nsAutoTxnsConserveSelection dontChangeSelection(this);
00883 
00884     PRBool  checkToDeleteRow = PR_TRUE;
00885     PRBool  checkToDeleteColumn = PR_TRUE;
00886     while (cell)
00887     {
00888       PRBool deleteRow = PR_FALSE;
00889       PRBool deleteCol = PR_FALSE;
00890 
00891       if (checkToDeleteRow)
00892       {
00893         // Optimize to delete an entire row
00894         // Clear so we don't repeat AllCellsInRowSelected within the same row
00895         checkToDeleteRow = PR_FALSE;
00896 
00897         deleteRow = AllCellsInRowSelected(table, startRowIndex, colCount);
00898         if (deleteRow)
00899         {
00900           // First, find the next cell in a different row
00901           //   to continue after we delete this row
00902           PRInt32 nextRow = startRowIndex;
00903           while (nextRow == startRowIndex)
00904           {
00905             res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
00906             if (NS_FAILED(res)) return res;
00907             if (!cell) break;
00908             res = GetCellIndexes(cell, &nextRow, &startColIndex);
00909             if (NS_FAILED(res)) return res;
00910           }
00911           // Delete entire row
00912           res = DeleteRow(table, startRowIndex);          
00913           if (NS_FAILED(res)) return res;
00914 
00915           if (cell)
00916           {
00917             // For the next cell: Subtract 1 for row we deleted
00918             startRowIndex = nextRow - 1;
00919             // Set true since we know we will look at a new row next
00920             checkToDeleteRow = PR_TRUE;
00921           }
00922         }
00923       }
00924       if (!deleteRow)
00925       {
00926         if (checkToDeleteColumn)
00927         {
00928           // Optimize to delete an entire column
00929           // Clear this so we don't repeat AllCellsInColSelected within the same Col
00930           checkToDeleteColumn = PR_FALSE;
00931 
00932           deleteCol = AllCellsInColumnSelected(table, startColIndex, colCount);
00933           if (deleteCol)
00934           {
00935             // First, find the next cell in a different column
00936             //   to continue after we delete this column
00937             PRInt32 nextCol = startColIndex;
00938             while (nextCol == startColIndex)
00939             {
00940               res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
00941               if (NS_FAILED(res)) return res;
00942               if (!cell) break;
00943               res = GetCellIndexes(cell, &startRowIndex, &nextCol);
00944               if (NS_FAILED(res)) return res;
00945             }
00946             // Delete entire Col
00947             res = DeleteColumn(table, startColIndex);          
00948             if (NS_FAILED(res)) return res;
00949             if (cell) 
00950             {
00951               // For the next cell, subtract 1 for col. deleted
00952               startColIndex = nextCol - 1;
00953               // Set true since we know we will look at a new column next
00954               checkToDeleteColumn = PR_TRUE;
00955             }
00956           }
00957         }
00958         if (!deleteCol)
00959         {
00960           // First get the next cell to delete
00961           nsCOMPtr<nsIDOMElement> nextCell;
00962           res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(nextCell));
00963           if (NS_FAILED(res)) return res;
00964 
00965           // Then delete the cell
00966           res = DeleteNode(cell);
00967           if (NS_FAILED(res)) return res;
00968           
00969           // The next cell to delete
00970           cell = nextCell;
00971           if (cell)
00972           {
00973             res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
00974             if (NS_FAILED(res)) return res;
00975           }
00976         }
00977       }
00978     }
00979   }
00980   else for (PRInt32 i = 0; i < aNumber; i++)
00981   {
00982     res = GetCellContext(getter_AddRefs(selection),
00983                          getter_AddRefs(table), 
00984                          getter_AddRefs(cell), 
00985                          nsnull, nsnull,
00986                          &startRowIndex, &startColIndex);
00987     if (NS_FAILED(res)) return res;
00988     // Don't fail if no cell found
00989     if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
00990 
00991     if (1 == GetNumberOfCellsInRow(table, startRowIndex))
00992     {
00993       nsCOMPtr<nsIDOMElement> parentRow;
00994       res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow));
00995       if (NS_FAILED(res)) return res;
00996       if (!parentRow) return NS_ERROR_NULL_POINTER;
00997 
00998       // We should delete the row instead,
00999       //  but first check if its the only row left
01000       //  so we can delete the entire table
01001       PRInt32 rowCount, colCount;
01002       res = GetTableSize(table, &rowCount, &colCount);
01003       if (NS_FAILED(res)) return res;
01004       
01005       if (rowCount == 1)
01006         return DeleteTable2(table, selection);
01007     
01008       // We need to call DeleteTableRow to handle cells with rowspan 
01009       res = DeleteTableRow(1);
01010       if (NS_FAILED(res)) return res;
01011     } 
01012     else
01013     {
01014       // More than 1 cell in the row
01015 
01016       // The setCaret object will call SetSelectionAfterTableEdit in it's destructor
01017       nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
01018       nsAutoTxnsConserveSelection dontChangeSelection(this);
01019 
01020       res = DeleteNode(cell);
01021       // If we fail, don't try to delete any more cells???
01022       if (NS_FAILED(res)) return res;
01023     }
01024   }
01025   return NS_OK;
01026 }
01027 
01028 NS_IMETHODIMP
01029 nsHTMLEditor::DeleteTableCellContents()
01030 {
01031   nsCOMPtr<nsISelection> selection;
01032   nsCOMPtr<nsIDOMElement> table;
01033   nsCOMPtr<nsIDOMElement> cell;
01034   PRInt32 startRowIndex, startColIndex;
01035   nsresult res;
01036   res = GetCellContext(getter_AddRefs(selection),
01037                        getter_AddRefs(table), 
01038                        getter_AddRefs(cell), 
01039                        nsnull, nsnull,
01040                        &startRowIndex, &startColIndex);
01041   if (NS_FAILED(res)) return res;
01042   // Don't fail if no cell found
01043   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01044 
01045 
01046   nsAutoEditBatch beginBatching(this);
01047   // Prevent rules testing until we're done
01048   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
01049   //Don't let Rules System change the selection
01050   nsAutoTxnsConserveSelection dontChangeSelection(this);
01051 
01052 
01053   nsCOMPtr<nsIDOMElement> firstCell;
01054   nsCOMPtr<nsIDOMRange> range;
01055   res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
01056   if (NS_FAILED(res)) return res;
01057 
01058 
01059   if (firstCell)
01060   {
01061     cell = firstCell;
01062     res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
01063     if (NS_FAILED(res)) return res;
01064   }
01065 
01066   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
01067 
01068   while (cell)
01069   {
01070     DeleteCellContents(cell);
01071     if (firstCell)
01072     {
01073       // We doing a selected cells, so do all of them
01074       res = GetNextSelectedCell(nsnull, getter_AddRefs(cell));
01075       if (NS_FAILED(res)) return res;
01076     }
01077     else
01078       cell = nsnull;
01079   }
01080   return NS_OK;
01081 }
01082 
01083 NS_IMETHODIMP
01084 nsHTMLEditor::DeleteCellContents(nsIDOMElement *aCell)
01085 {
01086   if (!aCell) return NS_ERROR_NULL_POINTER;
01087 
01088   // Prevent rules testing until we're done
01089   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
01090 
01091   nsCOMPtr<nsIDOMNode> child;
01092   PRBool hasChild;
01093   aCell->HasChildNodes(&hasChild);
01094 
01095   while (hasChild)
01096   {
01097     aCell->GetLastChild(getter_AddRefs(child));
01098     nsresult res = DeleteNode(child);
01099     if (NS_FAILED(res)) return res;
01100     aCell->HasChildNodes(&hasChild);
01101   }
01102   return NS_OK;
01103 }
01104 
01105 NS_IMETHODIMP
01106 nsHTMLEditor::DeleteTableColumn(PRInt32 aNumber)
01107 {
01108   nsCOMPtr<nsISelection> selection;
01109   nsCOMPtr<nsIDOMElement> table;
01110   nsCOMPtr<nsIDOMElement> cell;
01111   PRInt32 startRowIndex, startColIndex, rowCount, colCount;
01112   nsresult res = GetCellContext(getter_AddRefs(selection),
01113                                 getter_AddRefs(table), 
01114                                 getter_AddRefs(cell), 
01115                                 nsnull, nsnull,
01116                                 &startRowIndex, &startColIndex);
01117   if (NS_FAILED(res)) return res;
01118   // Don't fail if no cell found
01119   if (!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01120 
01121   res = GetTableSize(table, &rowCount, &colCount);
01122   if (NS_FAILED(res)) return res;
01123 
01124   // Shortcut the case of deleting all columns in table
01125   if(startColIndex == 0 && aNumber >= colCount)
01126     return DeleteTable2(table, selection);
01127 
01128   // Check for counts too high
01129   aNumber = PR_MIN(aNumber,(colCount-startColIndex));
01130 
01131   nsAutoEditBatch beginBatching(this);
01132   // Prevent rules testing until we're done
01133   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
01134 
01135   // Test if deletion is controlled by selected cells
01136   nsCOMPtr<nsIDOMElement> firstCell;
01137   nsCOMPtr<nsIDOMRange> range;
01138   res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
01139   if (NS_FAILED(res)) return res;
01140 
01141   PRInt32 rangeCount;
01142   res = selection->GetRangeCount(&rangeCount);
01143   if (NS_FAILED(res)) return res;
01144 
01145   if (firstCell && rangeCount > 1)
01146   {
01147     // Fetch indexes again - may be different for selected cells
01148     res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
01149     if (NS_FAILED(res)) return res;
01150   }
01151   //We control selection resetting after the insert...
01152   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
01153 
01154   if (firstCell && rangeCount > 1)
01155   {
01156     // Use selected cells to determine what rows to delete
01157     cell = firstCell;
01158 
01159     while (cell)
01160     {
01161       if (cell != firstCell)
01162       {
01163         res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
01164         if (NS_FAILED(res)) return res;
01165       }
01166       // Find the next cell in a different column
01167       // to continue after we delete this column
01168       PRInt32 nextCol = startColIndex;
01169       while (nextCol == startColIndex)
01170       {
01171         res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
01172         if (NS_FAILED(res)) return res;
01173         if (!cell) break;
01174         res = GetCellIndexes(cell, &startRowIndex, &nextCol);
01175         if (NS_FAILED(res)) return res;
01176       }
01177       res = DeleteColumn(table, startColIndex);          
01178       if (NS_FAILED(res)) return res;
01179     }
01180   }
01181   else for (PRInt32 i = 0; i < aNumber; i++)
01182   {
01183     res = DeleteColumn(table, startColIndex);
01184     if (NS_FAILED(res)) return res;
01185   }
01186   return NS_OK;
01187 }
01188 
01189 NS_IMETHODIMP
01190 nsHTMLEditor::DeleteColumn(nsIDOMElement *aTable, PRInt32 aColIndex)
01191 {
01192   if (!aTable) return NS_ERROR_NULL_POINTER;
01193 
01194   nsCOMPtr<nsIDOMElement> cell;
01195   nsCOMPtr<nsIDOMElement> cellInDeleteCol;
01196   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
01197   PRBool  isSelected;
01198   PRInt32 rowIndex = 0;
01199   nsresult res = NS_OK;
01200    
01201   do {
01202     res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
01203                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
01204                         &actualRowSpan, &actualColSpan, &isSelected);
01205 
01206     if (NS_FAILED(res)) return res;
01207 
01208     if (cell)
01209     {
01210       // Find cells that don't start in column we are deleting
01211       if (startColIndex < aColIndex || colSpan > 1 || colSpan == 0)
01212       {
01213         // We have a cell spanning this location
01214         // Decrease its colspan to keep table rectangular,
01215         // but if colSpan=0, it will adjust automatically
01216         if (colSpan > 0)
01217         {
01218           NS_ASSERTION((colSpan > 1),"Bad COLSPAN in DeleteTableColumn");
01219           SetColSpan(cell, colSpan-1);
01220         }
01221         if (startColIndex == aColIndex)
01222         {
01223           // Cell is in column to be deleted, but must have colspan > 1,
01224           // so delete contents of cell instead of cell itself
01225           // (We must have reset colspan above)
01226           DeleteCellContents(cell);
01227         }
01228         // To next cell in column
01229         rowIndex += actualRowSpan;
01230       } 
01231       else 
01232       {
01233         // Delete the cell
01234         if (1 == GetNumberOfCellsInRow(aTable, rowIndex))
01235         {
01236           // Only 1 cell in row - delete the row
01237           nsCOMPtr<nsIDOMElement> parentRow;
01238           res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow));
01239           if (NS_FAILED(res)) return res;
01240           if(!parentRow) return NS_ERROR_NULL_POINTER;
01241 
01242           //  But first check if its the only row left
01243           //  so we can delete the entire table
01244           //  (This should never happen but it's the safe thing to do)
01245           PRInt32 rowCount, colCount;
01246           res = GetTableSize(aTable, &rowCount, &colCount);
01247           if (NS_FAILED(res)) return res;
01248 
01249           if (rowCount == 1)
01250           {
01251             nsCOMPtr<nsISelection> selection;
01252             res = GetSelection(getter_AddRefs(selection));
01253             if (NS_FAILED(res)) return res;
01254             if (!selection) return NS_ERROR_FAILURE;
01255             return DeleteTable2(aTable, selection);
01256           }
01257     
01258           // Delete the row by placing caret in cell we were to delete
01259           // We need to call DeleteTableRow to handle cells with rowspan 
01260           res = DeleteRow(aTable, startRowIndex);
01261           if (NS_FAILED(res)) return res;
01262 
01263           // Note that we don't incremenet rowIndex
01264           // since a row was deleted and "next" 
01265           // row now has current rowIndex
01266         } 
01267         else 
01268         {
01269           // A more "normal" deletion
01270           res = DeleteNode(cell);
01271           if (NS_FAILED(res)) return res;
01272 
01273           //Skip over any rows spanned by this cell
01274           rowIndex += actualRowSpan;
01275         }
01276       }
01277     }
01278   } while (cell);    
01279 
01280   return NS_OK;
01281 }
01282 
01283 NS_IMETHODIMP
01284 nsHTMLEditor::DeleteTableRow(PRInt32 aNumber)
01285 {
01286   nsCOMPtr<nsISelection> selection;
01287   nsCOMPtr<nsIDOMElement> table;
01288   nsCOMPtr<nsIDOMElement> cell;
01289   PRInt32 startRowIndex, startColIndex;
01290   PRInt32 rowCount, colCount;
01291   nsresult res =  GetCellContext(getter_AddRefs(selection),
01292                                  getter_AddRefs(table), 
01293                                  getter_AddRefs(cell), 
01294                                  nsnull, nsnull,
01295                                  &startRowIndex, &startColIndex);
01296   if (NS_FAILED(res)) return res;
01297   // Don't fail if no cell found
01298   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01299 
01300   res = GetTableSize(table, &rowCount, &colCount);
01301   if (NS_FAILED(res)) return res;
01302 
01303   // Shortcut the case of deleting all rows in table
01304   if(startRowIndex == 0 && aNumber >= rowCount)
01305     return DeleteTable2(table, selection);
01306 
01307   nsAutoEditBatch beginBatching(this);
01308   // Prevent rules testing until we're done
01309   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
01310 
01311   nsCOMPtr<nsIDOMElement> firstCell;
01312   nsCOMPtr<nsIDOMRange> range;
01313   res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell));
01314   if (NS_FAILED(res)) return res;
01315 
01316   PRInt32 rangeCount;
01317   res = selection->GetRangeCount(&rangeCount);
01318   if (NS_FAILED(res)) return res;
01319 
01320   if (firstCell && rangeCount > 1)
01321   {
01322     // Fetch indexes again - may be different for selected cells
01323     res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex);
01324     if (NS_FAILED(res)) return res;
01325   }
01326 
01327   //We control selection resetting after the insert...
01328   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, PR_FALSE);
01329   // Don't change selection during deletions
01330   nsAutoTxnsConserveSelection dontChangeSelection(this);
01331 
01332   if (firstCell && rangeCount > 1)
01333   {
01334     // Use selected cells to determine what rows to delete
01335     cell = firstCell;
01336 
01337     while (cell)
01338     {
01339       if (cell != firstCell)
01340       {
01341         res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
01342         if (NS_FAILED(res)) return res;
01343       }
01344       // Find the next cell in a different row
01345       // to continue after we delete this row
01346       PRInt32 nextRow = startRowIndex;
01347       while (nextRow == startRowIndex)
01348       {
01349         res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
01350         if (NS_FAILED(res)) return res;
01351         if (!cell) break;
01352         res = GetCellIndexes(cell, &nextRow, &startColIndex);
01353         if (NS_FAILED(res)) return res;
01354       }
01355       // Delete entire row
01356       res = DeleteRow(table, startRowIndex);          
01357       if (NS_FAILED(res)) return res;
01358     }
01359   }
01360   else
01361   {
01362     // Check for counts too high
01363     aNumber = PR_MIN(aNumber,(rowCount-startRowIndex));
01364 
01365     for (PRInt32 i = 0; i < aNumber; i++)
01366     {
01367       res = DeleteRow(table, startRowIndex);
01368       // If failed in current row, try the next
01369       if (NS_FAILED(res))
01370         startRowIndex++;
01371     
01372       // Check if there's a cell in the "next" row
01373       res = GetCellAt(table, startRowIndex, startColIndex, getter_AddRefs(cell));
01374       if (NS_FAILED(res)) return res;
01375       if(!cell)
01376         break;
01377     }
01378   }
01379   return NS_OK;
01380 }
01381 
01382 // Helper that doesn't batch or change the selection
01383 NS_IMETHODIMP
01384 nsHTMLEditor::DeleteRow(nsIDOMElement *aTable, PRInt32 aRowIndex)
01385 {
01386   if (!aTable) return NS_ERROR_NULL_POINTER;
01387 
01388   nsCOMPtr<nsIDOMElement> cell;
01389   nsCOMPtr<nsIDOMElement> cellInDeleteRow;
01390   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
01391   PRBool  isSelected;
01392   PRInt32 colIndex = 0;
01393   nsresult res = NS_OK;
01394    
01395   // Prevent rules testing until we're done
01396   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
01397 
01398   // The list of cells we will change rowspan in
01399   //  and the new rowspan values for each
01400   nsVoidArray spanCellList;
01401   nsVoidArray newSpanList;
01402 
01403   // Scan through cells in row to do rowspan adjustments
01404   // Note that after we delete row, startRowIndex will point to the
01405   //   cells in the next row to be deleted
01406   do {
01407     res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
01408                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
01409                         &actualRowSpan, &actualColSpan, &isSelected);
01410   
01411     // We don't fail if we don't find a cell, so this must be real bad
01412     if(NS_FAILED(res)) return res;
01413 
01414     // Compensate for cells that don't start or extend below the row we are deleting
01415     if (cell)
01416     {
01417       if (startRowIndex < aRowIndex)
01418       {
01419         // Cell starts in row above us
01420         // Decrease its rowspan to keep table rectangular
01421         //  but we don't need to do this if rowspan=0,
01422         //  since it will automatically adjust
01423         if (rowSpan > 0)
01424         {
01425           // Build list of cells to change rowspan
01426           // We can't do it now since it upsets cell map,
01427           //  so we will do it after deleting the row
01428           spanCellList.AppendElement((void*)cell.get());
01429           newSpanList.AppendElement((void*)PR_MAX((aRowIndex - startRowIndex), actualRowSpan-1));
01430         }
01431       }
01432       else 
01433       {
01434         if (rowSpan > 1)
01435         {
01436           //Cell spans below row to delete,
01437           //  so we must insert new cells to keep rows below even
01438           // Note that we test "rowSpan" so we don't do this if rowSpan = 0 (automatic readjustment)
01439           res = SplitCellIntoRows(aTable, startRowIndex, startColIndex,
01440                                   aRowIndex - startRowIndex + 1, // The row above the row to insert new cell into
01441                                   actualRowSpan - 1, nsnull);    // Span remaining below
01442           if (NS_FAILED(res)) return res;
01443         }
01444         if (!cellInDeleteRow)
01445           cellInDeleteRow = cell; // Reference cell to find row to delete
01446       }
01447       // Skip over other columns spanned by this cell
01448       colIndex += actualColSpan;
01449     }
01450   } while (cell);
01451 
01452   // Things are messed up if we didn't find a cell in the row!
01453   if (!cellInDeleteRow)
01454     return NS_ERROR_FAILURE;
01455 
01456   // Delete the entire row
01457   nsCOMPtr<nsIDOMElement> parentRow;
01458   res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cellInDeleteRow, getter_AddRefs(parentRow));
01459   if (NS_FAILED(res)) return res;
01460 
01461   if (parentRow)
01462   {
01463     res = DeleteNode(parentRow);
01464     if (NS_FAILED(res)) return res;
01465   }
01466 
01467   // Now we can set new rowspans for cells stored above  
01468   nsIDOMElement *cellPtr;
01469   PRInt32 newSpan;
01470   PRInt32 count;
01471   while ((count = spanCellList.Count()))
01472   {
01473     // go backwards to keep nsVoidArray from mem-moving everything each time
01474     count--; // nsVoidArray is zero based
01475     cellPtr = (nsIDOMElement*)spanCellList.ElementAt(count);
01476     spanCellList.RemoveElementAt(count);
01477     newSpan = NS_PTR_TO_INT32(newSpanList.ElementAt(count));
01478     newSpanList.RemoveElementAt(count);
01479     if (cellPtr)
01480     {
01481       res = SetRowSpan(cellPtr, newSpan);
01482       if (NS_FAILED(res)) return res;
01483     }
01484   }
01485   return NS_OK;
01486 }
01487 
01488 
01489 NS_IMETHODIMP 
01490 nsHTMLEditor::SelectTable()
01491 {
01492   nsCOMPtr<nsIDOMElement> table;
01493   nsresult res = NS_ERROR_FAILURE;
01494   res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nsnull, getter_AddRefs(table));
01495   if (NS_FAILED(res)) return res;
01496   // Don't fail if we didn't find a table
01497   if (!table) return NS_OK;
01498 
01499   res = ClearSelection();
01500   if (NS_SUCCEEDED(res))
01501     res = AppendNodeToSelectionAsRange(table);
01502 
01503   return res;
01504 }
01505 
01506 NS_IMETHODIMP 
01507 nsHTMLEditor::SelectTableCell()
01508 {
01509   nsCOMPtr<nsIDOMElement> cell;
01510   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
01511   if (NS_FAILED(res)) return res;
01512   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01513 
01514   res = ClearSelection();
01515   if (NS_SUCCEEDED(res))
01516     res = AppendNodeToSelectionAsRange(cell);
01517 
01518   return res;
01519 }
01520 
01521 NS_IMETHODIMP 
01522 nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell)
01523 {
01524   if (!aStartCell || !aEndCell) return NS_ERROR_NULL_POINTER;
01525   
01526   nsCOMPtr<nsISelection> selection;
01527   nsresult res = GetSelection(getter_AddRefs(selection));
01528   if (NS_FAILED(res)) return res;
01529   if (!selection) return NS_ERROR_FAILURE;
01530 
01531   NS_NAMED_LITERAL_STRING(tableStr, "table");
01532   nsCOMPtr<nsIDOMElement> table;
01533   res = GetElementOrParentByTagName(tableStr, aStartCell, getter_AddRefs(table));
01534   if (NS_FAILED(res)) return res;
01535   if (!table) return NS_ERROR_FAILURE;
01536 
01537   nsCOMPtr<nsIDOMElement> endTable;
01538   res = GetElementOrParentByTagName(tableStr, aEndCell, getter_AddRefs(endTable));
01539   if (NS_FAILED(res)) return res;
01540   if (!endTable) return NS_ERROR_FAILURE;
01541   
01542   // We can only select a block if within the same table,
01543   //  so do nothing if not within one table
01544   if (table != endTable) return NS_OK;
01545 
01546   PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex;
01547 
01548   // Get starting and ending cells' location in the cellmap
01549   res = GetCellIndexes(aStartCell, &startRowIndex, &startColIndex);
01550   if(NS_FAILED(res)) return res;
01551 
01552   res = GetCellIndexes(aEndCell, &endRowIndex, &endColIndex);
01553   if(NS_FAILED(res)) return res;
01554 
01555   // Suppress nsISelectionListener notification
01556   //  until all selection changes are finished
01557   nsSelectionBatcher selectionBatcher(selection);
01558 
01559   // Examine all cell nodes in current selection and 
01560   //  remove those outside the new block cell region
01561   PRInt32 minColumn = PR_MIN(startColIndex, endColIndex);
01562   PRInt32 minRow    = PR_MIN(startRowIndex, endRowIndex);
01563   PRInt32 maxColumn   = PR_MAX(startColIndex, endColIndex);
01564   PRInt32 maxRow      = PR_MAX(startRowIndex, endRowIndex);
01565 
01566   nsCOMPtr<nsIDOMElement> cell;
01567   PRInt32 currentRowIndex, currentColIndex;
01568   nsCOMPtr<nsIDOMRange> range;
01569   res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
01570   if (NS_FAILED(res)) return res;
01571   if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK;
01572 
01573   while (cell)
01574   {
01575     res = GetCellIndexes(cell, &currentRowIndex, &currentColIndex);
01576     if (NS_FAILED(res)) return res;
01577 
01578     if (currentRowIndex < maxRow || currentRowIndex > maxRow || 
01579         currentColIndex < maxColumn || currentColIndex > maxColumn)
01580     {
01581       selection->RemoveRange(range);
01582       // Since we've removed the range, decrement pointer to next range
01583       mSelectedCellIndex--;
01584     }    
01585     res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell));
01586     if (NS_FAILED(res)) return res;
01587   }
01588 
01589   PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan;
01590   PRBool  isSelected;
01591   for (PRInt32 row = minRow; row <= maxRow; row++)
01592   {
01593     for(PRInt32 col = minColumn; col <= maxColumn; col += PR_MAX(actualColSpan, 1))
01594     {
01595       res = GetCellDataAt(table, row, col, getter_AddRefs(cell),
01596                           &currentRowIndex, &currentColIndex,
01597                           &rowSpan, &colSpan, 
01598                           &actualRowSpan, &actualColSpan, &isSelected);
01599       if (NS_FAILED(res)) break;
01600       // Skip cells that already selected or are spanned from previous locations
01601       if (!isSelected && cell && row == currentRowIndex && col == currentColIndex)
01602       {
01603         res = AppendNodeToSelectionAsRange(cell);
01604         if (NS_FAILED(res)) break;
01605       }
01606     }
01607   }
01608   return res;
01609 }
01610 
01611 NS_IMETHODIMP 
01612 nsHTMLEditor::SelectAllTableCells()
01613 {
01614   nsCOMPtr<nsIDOMElement> cell;
01615   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
01616   if (NS_FAILED(res)) return res;
01617   
01618   // Don't fail if we didn't find a cell
01619   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01620 
01621   nsCOMPtr<nsIDOMElement> startCell = cell;
01622   
01623   // Get parent table
01624   nsCOMPtr<nsIDOMElement> table;
01625   res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table));
01626   if (NS_FAILED(res)) return res;
01627   if(!table) return NS_ERROR_NULL_POINTER;
01628 
01629   PRInt32 rowCount, colCount;
01630   res = GetTableSize(table, &rowCount, &colCount);
01631   if (NS_FAILED(res)) return res;
01632 
01633   nsCOMPtr<nsISelection> selection;
01634   res = GetSelection(getter_AddRefs(selection));
01635   if (NS_FAILED(res)) return res;
01636   if (!selection) return NS_ERROR_FAILURE;
01637 
01638   // Suppress nsISelectionListener notification
01639   //  until all selection changes are finished
01640   nsSelectionBatcher selectionBatcher(selection);
01641 
01642   // It is now safe to clear the selection
01643   // BE SURE TO RESET IT BEFORE LEAVING!
01644   res = ClearSelection();
01645 
01646   // Select all cells in the same column as current cell
01647   PRBool cellSelected = PR_FALSE;
01648   PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
01649   PRBool  isSelected;
01650   for(PRInt32 row = 0; row < rowCount; row++)
01651   {
01652     for(PRInt32 col = 0; col < colCount; col += PR_MAX(actualColSpan, 1))
01653     {
01654       res = GetCellDataAt(table, row, col, getter_AddRefs(cell),
01655                           &currentRowIndex, &currentColIndex,
01656                           &rowSpan, &colSpan, 
01657                           &actualRowSpan, &actualColSpan, &isSelected);
01658       if (NS_FAILED(res)) break;
01659       // Skip cells that are spanned from previous rows or columns
01660       if (cell && row == currentRowIndex && col == currentColIndex)
01661       {
01662         res =  AppendNodeToSelectionAsRange(cell);
01663         if (NS_FAILED(res)) break;
01664         cellSelected = PR_TRUE;
01665       }
01666     }
01667   }
01668   // Safety code to select starting cell if nothing else was selected
01669   if (!cellSelected)
01670   {
01671     return AppendNodeToSelectionAsRange(startCell);
01672   }
01673   return res;
01674 }
01675 
01676 NS_IMETHODIMP 
01677 nsHTMLEditor::SelectTableRow()
01678 {
01679   nsCOMPtr<nsIDOMElement> cell;
01680   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
01681   if (NS_FAILED(res)) return res;
01682   
01683   // Don't fail if we didn't find a cell
01684   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01685   nsCOMPtr<nsIDOMElement> startCell = cell;
01686 
01687   // Get table and location of cell:
01688   nsCOMPtr<nsISelection> selection;
01689   nsCOMPtr<nsIDOMElement> table;
01690   PRInt32 startRowIndex, startColIndex;
01691 
01692   res = GetCellContext(getter_AddRefs(selection),
01693                        getter_AddRefs(table), 
01694                        getter_AddRefs(cell),
01695                        nsnull, nsnull,
01696                        &startRowIndex, &startColIndex);
01697   if (NS_FAILED(res)) return res;
01698   if (!table) return NS_ERROR_FAILURE;
01699   
01700   PRInt32 rowCount, colCount;
01701   res = GetTableSize(table, &rowCount, &colCount);
01702   if (NS_FAILED(res)) return res;
01703 
01704   //Note: At this point, we could get first and last cells in row,
01705   //  then call SelectBlockOfCells, but that would take just
01706   //  a little less code, so the following is more efficient
01707 
01708   // Suppress nsISelectionListener notification
01709   //  until all selection changes are finished
01710   nsSelectionBatcher selectionBatcher(selection);
01711 
01712   // It is now safe to clear the selection
01713   // BE SURE TO RESET IT BEFORE LEAVING!
01714   res = ClearSelection();
01715 
01716   // Select all cells in the same row as current cell
01717   PRBool cellSelected = PR_FALSE;
01718   PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
01719   PRBool  isSelected;
01720   for(PRInt32 col = 0; col < colCount; col += PR_MAX(actualColSpan, 1))
01721   {
01722     res = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell),
01723                         &currentRowIndex, &currentColIndex, &rowSpan, &colSpan, 
01724                         &actualRowSpan, &actualColSpan, &isSelected);
01725     if (NS_FAILED(res)) break;
01726     // Skip cells that are spanned from previous rows or columns
01727     if (cell && currentRowIndex == startRowIndex && currentColIndex == col)
01728     {
01729       res = AppendNodeToSelectionAsRange(cell);
01730       if (NS_FAILED(res)) break;
01731       cellSelected = PR_TRUE;
01732     }
01733   }
01734   // Safety code to select starting cell if nothing else was selected
01735   if (!cellSelected)
01736   {
01737     return AppendNodeToSelectionAsRange(startCell);
01738   }
01739   return res;
01740 }
01741 
01742 NS_IMETHODIMP 
01743 nsHTMLEditor::SelectTableColumn()
01744 {
01745   nsCOMPtr<nsIDOMElement> cell;
01746   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
01747   if (NS_FAILED(res)) return res;
01748   
01749   // Don't fail if we didn't find a cell
01750   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01751 
01752   nsCOMPtr<nsIDOMElement> startCell = cell;
01753   
01754   // Get location of cell:
01755   nsCOMPtr<nsISelection> selection;
01756   nsCOMPtr<nsIDOMElement> table;
01757   PRInt32 startRowIndex, startColIndex;
01758 
01759   res = GetCellContext(getter_AddRefs(selection),
01760                        getter_AddRefs(table), 
01761                        getter_AddRefs(cell),
01762                        nsnull, nsnull,
01763                        &startRowIndex, &startColIndex);
01764   if (NS_FAILED(res)) return res;
01765   if (!table) return NS_ERROR_FAILURE;
01766 
01767   PRInt32 rowCount, colCount;
01768   res = GetTableSize(table, &rowCount, &colCount);
01769   if (NS_FAILED(res)) return res;
01770 
01771   // Suppress nsISelectionListener notification
01772   //  until all selection changes are finished
01773   nsSelectionBatcher selectionBatcher(selection);
01774 
01775   // It is now safe to clear the selection
01776   // BE SURE TO RESET IT BEFORE LEAVING!
01777   res = ClearSelection();
01778 
01779   // Select all cells in the same column as current cell
01780   PRBool cellSelected = PR_FALSE;
01781   PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex;
01782   PRBool  isSelected;
01783   for(PRInt32 row = 0; row < rowCount; row += PR_MAX(actualRowSpan, 1))
01784   {
01785     res = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell),
01786                         &currentRowIndex, &currentColIndex, &rowSpan, &colSpan, 
01787                         &actualRowSpan, &actualColSpan, &isSelected);
01788     if (NS_FAILED(res)) break;
01789     // Skip cells that are spanned from previous rows or columns
01790     if (cell && currentRowIndex == row && currentColIndex == startColIndex)
01791     {
01792       res = AppendNodeToSelectionAsRange(cell);
01793       if (NS_FAILED(res)) break;
01794       cellSelected = PR_TRUE;
01795     }
01796   }
01797   // Safety code to select starting cell if nothing else was selected
01798   if (!cellSelected)
01799   {
01800     return AppendNodeToSelectionAsRange(startCell);
01801   }
01802   return res;
01803 }
01804 
01805 NS_IMETHODIMP 
01806 nsHTMLEditor::SplitTableCell()
01807 {
01808   nsCOMPtr<nsIDOMElement> table;
01809   nsCOMPtr<nsIDOMElement> cell;
01810   PRInt32 startRowIndex, startColIndex, actualRowSpan, actualColSpan;
01811   // Get cell, table, etc. at selection anchor node
01812   nsresult res = GetCellContext(nsnull,
01813                                 getter_AddRefs(table), 
01814                                 getter_AddRefs(cell),
01815                                 nsnull, nsnull,
01816                                 &startRowIndex, &startColIndex);
01817   if (NS_FAILED(res)) return res;
01818   if(!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
01819 
01820   // We need rowspan and colspan data
01821   res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan);
01822   if (NS_FAILED(res)) return res;
01823 
01824   // Must have some span to split
01825   if (actualRowSpan <= 1 && actualColSpan <= 1)
01826     return NS_OK;
01827   
01828   nsAutoEditBatch beginBatching(this);
01829   // Prevent auto insertion of BR in new cell until we're done
01830   nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
01831 
01832   // We reset selection  
01833   nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, PR_FALSE);
01834   //...so suppress Rules System selection munging
01835   nsAutoTxnsConserveSelection dontChangeSelection(this);
01836 
01837   nsCOMPtr<nsIDOMElement> newCell;
01838   PRInt32 rowIndex = startRowIndex;
01839   PRInt32 rowSpanBelow, colSpanAfter;
01840 
01841   // Split up cell row-wise first into rowspan=1 above, and the rest below,
01842   //  whittling away at the cell below until no more extra span
01843   for (rowSpanBelow = actualRowSpan-1; rowSpanBelow >= 0; rowSpanBelow--)
01844   {
01845     // We really split row-wise only if we had rowspan > 1
01846     if (rowSpanBelow > 0)
01847     {
01848       res = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow, getter_AddRefs(newCell));
01849       if (NS_FAILED(res)) return res;
01850       CopyCellBackgroundColor(newCell, cell);
01851     }
01852     PRInt32 colIndex = startColIndex;
01853     // Now split the cell with rowspan = 1 into cells if it has colSpan > 1
01854     for (colSpanAfter = actualColSpan-1; colSpanAfter > 0; colSpanAfter--)
01855     {
01856       res = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter, getter_AddRefs(newCell));
01857       if (NS_FAILED(res)) return res;
01858       CopyCellBackgroundColor(newCell, cell);
01859       colIndex++;
01860     }
01861     // Point to the new cell and repeat
01862     rowIndex++;
01863   }
01864   return res;
01865 }
01866 
01867 nsresult
01868 nsHTMLEditor::CopyCellBackgroundColor(nsIDOMElement *destCell, nsIDOMElement *sourceCell)
01869 {
01870   if (!destCell || !sourceCell) return NS_ERROR_NULL_POINTER;
01871 
01872   // Copy backgournd color to new cell
01873   NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
01874   nsAutoString color;
01875   PRBool isSet;
01876   nsresult res = GetAttributeValue(sourceCell, bgcolor, color, &isSet);
01877 
01878   if (NS_SUCCEEDED(res) && isSet)
01879     res = SetAttribute(destCell, bgcolor, color);
01880 
01881   return res;
01882 }
01883 
01884 NS_IMETHODIMP 
01885 nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex,
01886                                    PRInt32 aColSpanLeft, PRInt32 aColSpanRight,
01887                                    nsIDOMElement **aNewCell)
01888 {
01889   if (!aTable) return NS_ERROR_NULL_POINTER;
01890   if (aNewCell) *aNewCell = nsnull;
01891 
01892   nsCOMPtr<nsIDOMElement> cell;
01893   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
01894   PRBool  isSelected;
01895   nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
01896                                &startRowIndex, &startColIndex,
01897                                &rowSpan, &colSpan, 
01898                                &actualRowSpan, &actualColSpan, &isSelected);
01899   if (NS_FAILED(res)) return res;
01900   if (!cell) return NS_ERROR_NULL_POINTER;
01901   
01902   // We can't split!
01903   if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan)
01904     return NS_OK;
01905 
01906   // Reduce colspan of cell to split
01907   res = SetColSpan(cell, aColSpanLeft);
01908   if (NS_FAILED(res)) return res;
01909   
01910   // Insert new cell after using the remaining span
01911   //  and always get the new cell so we can copy the background color;
01912   nsCOMPtr<nsIDOMElement> newCell;
01913   res = InsertCell(cell, actualRowSpan, aColSpanRight, PR_TRUE, PR_FALSE, getter_AddRefs(newCell));
01914   if (NS_FAILED(res)) return res;
01915   if (newCell)
01916   {
01917     if (aNewCell)
01918     {
01919       *aNewCell = newCell.get();
01920       NS_ADDREF(*aNewCell);
01921     }
01922     res = CopyCellBackgroundColor(newCell, cell);
01923   }
01924   return res;
01925 }
01926 
01927 NS_IMETHODIMP
01928 nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aColIndex,
01929                                 PRInt32 aRowSpanAbove, PRInt32 aRowSpanBelow, 
01930                                 nsIDOMElement **aNewCell)
01931 {
01932   if (!aTable) return NS_ERROR_NULL_POINTER;
01933   if (aNewCell) *aNewCell = nsnull;
01934 
01935   nsCOMPtr<nsIDOMElement> cell;
01936   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
01937   PRBool  isSelected;
01938   nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell),
01939                                &startRowIndex, &startColIndex,
01940                                &rowSpan, &colSpan, 
01941                                &actualRowSpan, &actualColSpan, &isSelected);
01942   if (NS_FAILED(res)) return res;
01943   if (!cell) return NS_ERROR_NULL_POINTER;
01944   
01945   // We can't split!
01946   if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan)
01947     return NS_OK;
01948 
01949   PRInt32 rowCount, colCount;
01950   res = GetTableSize(aTable, &rowCount, &colCount);
01951   if (NS_FAILED(res)) return res;
01952 
01953   nsCOMPtr<nsIDOMElement> cell2;
01954   nsCOMPtr<nsIDOMElement> lastCellFound;
01955   PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
01956   PRBool  isSelected2;
01957   PRInt32 colIndex = 0;
01958   PRBool insertAfter = (startColIndex > 0);
01959   // This is the row we will insert new cell into
01960   PRInt32 rowBelowIndex = startRowIndex+aRowSpanAbove;
01961   
01962   // Find a cell to insert before or after
01963   do 
01964   {
01965     // Search for a cell to insert before
01966     res = GetCellDataAt(aTable, rowBelowIndex, 
01967                         colIndex, getter_AddRefs(cell2),
01968                         &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, 
01969                         &actualRowSpan2, &actualColSpan2, &isSelected2);
01970     // If we fail here, it could be because row has bad rowspan values,
01971     //   such as all cells having rowspan > 1 (Call FixRowSpan first!)
01972     if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE;
01973 
01974     // Skip over cells spanned from above (like the one we are splitting!)
01975     if (cell2 && startRowIndex2 == rowBelowIndex)
01976     {
01977       if (insertAfter)
01978       {
01979         // New cell isn't first in row,
01980         // so stop after we find the cell just before new cell's column
01981         if ((startColIndex2 + actualColSpan2) == startColIndex)
01982           break;
01983 
01984         // If cell found is AFTER desired new cell colum,
01985         //  we have multiple cells with rowspan > 1 that
01986         //  prevented us from finding a cell to insert after...
01987         if (startColIndex2 > startColIndex)
01988         {
01989           // ... so instead insert before the cell we found
01990           insertAfter = PR_FALSE;
01991           break;
01992         }
01993       }
01994       else
01995       {
01996         break; // Inserting before, so stop at first cell in row we want to insert into
01997       }
01998       lastCellFound = cell2;
01999     }
02000     // Skip to next available cellmap location
02001     colIndex += PR_MAX(actualColSpan2, 1);
02002 
02003     // Done when past end of total number of columns
02004     if (colIndex > colCount)
02005         break;
02006 
02007   } while(PR_TRUE);
02008 
02009   if (!cell2 && lastCellFound)
02010   {
02011     // Edge case where we didn't find a cell to insert after
02012     //  or before because column(s) before desired column 
02013     //  and all columns after it are spanned from above. 
02014     //  We can insert after the last cell we found 
02015     cell2 = lastCellFound;
02016     insertAfter = PR_TRUE; // Should always be true, but let's be sure
02017   }
02018 
02019   // Reduce rowspan of cell to split
02020   res = SetRowSpan(cell, aRowSpanAbove);
02021   if (NS_FAILED(res)) return res;
02022 
02023 
02024   // Insert new cell after using the remaining span
02025   //  and always get the new cell so we can copy the background color;
02026   nsCOMPtr<nsIDOMElement> newCell;
02027   res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, PR_FALSE, getter_AddRefs(newCell));
02028   if (NS_FAILED(res)) return res;
02029   if (newCell)
02030   {
02031     if (aNewCell)
02032     {
02033       *aNewCell = newCell.get();
02034       NS_ADDREF(*aNewCell);
02035     }
02036     res = CopyCellBackgroundColor(newCell, cell2);
02037   }
02038   return res;
02039 }
02040 
02041 NS_IMETHODIMP
02042 nsHTMLEditor::SwitchTableCellHeaderType(nsIDOMElement *aSourceCell, nsIDOMElement **aNewCell)
02043 {
02044   if (!aSourceCell) return NS_ERROR_NULL_POINTER;
02045 
02046   nsAutoEditBatch beginBatching(this);
02047   // Prevent auto insertion of BR in new cell created by ReplaceContainer
02048   nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
02049 
02050   nsCOMPtr<nsIDOMNode> newNode;
02051 
02052   // Save current selection to restore when done
02053   // This is needed so ReplaceContainer can monitor selection
02054   //  when replacing nodes
02055   nsCOMPtr<nsISelection>selection;
02056   nsresult res = GetSelection(getter_AddRefs(selection));
02057   if (NS_FAILED(res)) return res;
02058   if (!selection) return NS_ERROR_FAILURE;
02059   nsAutoSelectionReset selectionResetter(selection, this);
02060 
02061   // Set to the opposite of current type
02062   nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aSourceCell);
02063   nsString newCellType( (atom == nsEditProperty::td) ? NS_LITERAL_STRING("th") : NS_LITERAL_STRING("td"));
02064 
02065   // This creates new node, moves children, copies attributes (PR_TRUE)
02066   //   and manages the selection!
02067   res = ReplaceContainer(aSourceCell, address_of(newNode), newCellType, nsnull, nsnull, PR_TRUE);
02068   if (NS_FAILED(res)) return res;
02069   if (!newNode) return NS_ERROR_FAILURE;
02070 
02071   // Return the new cell
02072   if (aNewCell)
02073   {
02074     nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode);
02075     *aNewCell = newElement.get();
02076     NS_ADDREF(*aNewCell);
02077   }
02078 
02079   return NS_OK;
02080 }
02081 
02082 NS_IMETHODIMP 
02083 nsHTMLEditor::JoinTableCells(PRBool aMergeNonContiguousContents)
02084 {
02085   nsCOMPtr<nsIDOMElement> table;
02086   nsCOMPtr<nsIDOMElement> targetCell;
02087   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02088   PRBool  isSelected;
02089   nsCOMPtr<nsIDOMElement> cell2;
02090   PRInt32 startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2;
02091   PRBool  isSelected2;
02092 
02093   // Get cell, table, etc. at selection anchor node
02094   nsresult res = GetCellContext(nsnull,
02095                                 getter_AddRefs(table), 
02096                                 getter_AddRefs(targetCell),
02097                                 nsnull, nsnull,
02098                                 &startRowIndex, &startColIndex);
02099   if (NS_FAILED(res)) return res;
02100   if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
02101 
02102   nsAutoEditBatch beginBatching(this);
02103   //Don't let Rules System change the selection
02104   nsAutoTxnsConserveSelection dontChangeSelection(this);
02105 
02106   // Note: We dont' use nsSetSelectionAfterTableEdit here so the selection
02107   //  is retained after joining. This leaves the target cell selected
02108   //  as well as the "non-contiguous" cells, so user can see what happened.
02109 
02110   nsCOMPtr<nsIDOMElement> firstCell;
02111   PRInt32 firstRowIndex, firstColIndex;
02112   res = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex, getter_AddRefs(firstCell));
02113   if (NS_FAILED(res)) return res;
02114   
02115   PRBool joinSelectedCells = PR_FALSE;
02116   if (firstCell)
02117   {
02118     nsCOMPtr<nsIDOMElement> secondCell;
02119     res = GetNextSelectedCell(nsnull, getter_AddRefs(secondCell));
02120     if (NS_FAILED(res)) return res;
02121 
02122     // If only one cell is selected, join with cell to the right
02123     joinSelectedCells = (secondCell != nsnull);
02124   }
02125 
02126   if (joinSelectedCells)
02127   {
02128     // We have selected cells: Join just contiguous cells
02129     //  and just merge contents if not contiguous
02130 
02131     PRInt32 rowCount, colCount;
02132     res = GetTableSize(table, &rowCount, &colCount);
02133     if (NS_FAILED(res)) return res;
02134 
02135     // Get spans for cell we will merge into
02136     PRInt32 firstRowSpan, firstColSpan;
02137     res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan);
02138     if (NS_FAILED(res)) return res;
02139 
02140     // This defines the last indexes along the "edges"
02141     //  of the contiguous block of cells, telling us
02142     //  that we can join adjacent cells to the block
02143     // Start with same as the first values,
02144     //  then expand as we find adjacent selected cells
02145     PRInt32 lastRowIndex = firstRowIndex;
02146     PRInt32 lastColIndex = firstColIndex;
02147     PRInt32 rowIndex, colIndex;
02148 
02149     // First pass: Determine boundaries of contiguous rectangular block 
02150     //  that we will join into one cell,
02151     //  favoring adjacent cells in the same row
02152     for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++)
02153     {
02154       PRInt32 currentRowCount = rowCount;
02155       // Be sure each row doesn't have rowspan errors
02156       res = FixBadRowSpan(table, rowIndex, rowCount);
02157       if (NS_FAILED(res)) return res;
02158       // Adjust rowcount by number of rows we removed
02159       lastRowIndex -= (currentRowCount-rowCount);
02160 
02161       PRBool cellFoundInRow = PR_FALSE;
02162       PRBool lastRowIsSet = PR_FALSE;
02163       PRInt32 lastColInRow = 0;
02164       PRInt32 firstColInRow = firstColIndex;
02165       for (colIndex = firstColIndex; colIndex < colCount; colIndex += PR_MAX(actualColSpan2, 1))
02166       {
02167         res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
02168                             &startRowIndex2, &startColIndex2,
02169                             &rowSpan2, &colSpan2, 
02170                             &actualRowSpan2, &actualColSpan2, &isSelected2);
02171         if (NS_FAILED(res)) return res;
02172 
02173         if (isSelected2)
02174         {
02175           if (!cellFoundInRow)
02176             // We've just found the first selected cell in this row
02177             firstColInRow = colIndex;
02178 
02179           if (rowIndex > firstRowIndex && firstColInRow != firstColIndex)
02180           {
02181             // We're in at least the second row,
02182             // but left boundary is "ragged" (not the same as 1st row's start)
02183             //Let's just end block on previous row
02184             // and keep previous lastColIndex
02185             //TODO: We could try to find the Maximum firstColInRow
02186             //      so our block can still extend down more rows?
02187             lastRowIndex = PR_MAX(0,rowIndex - 1);
02188             lastRowIsSet = PR_TRUE;
02189             break;
02190           }
02191           // Save max selected column in this row, including extra colspan
02192           lastColInRow = colIndex + (actualColSpan2-1);
02193           cellFoundInRow = PR_TRUE;
02194         }
02195         else if (cellFoundInRow)
02196         {
02197           // No cell or not selected, but at least one cell in row was found
02198           
02199           if (rowIndex > (firstRowIndex+1) && colIndex <= lastColIndex)
02200           {
02201             // Cell is in a column less than current right border in 
02202             //  the third or higher selected row, so stop block at the previous row
02203             lastRowIndex = PR_MAX(0,rowIndex - 1);
02204             lastRowIsSet = PR_TRUE;
02205           }
02206           // We're done with this row
02207           break;
02208         }
02209       } // End of column loop
02210 
02211       // Done with this row 
02212       if (cellFoundInRow) 
02213       {
02214         if (rowIndex == firstRowIndex)
02215         {
02216           // First row always initializes the right boundary
02217           lastColIndex = lastColInRow;
02218         }
02219 
02220         // If we didn't determine last row above...
02221         if (!lastRowIsSet)
02222         {
02223           if (colIndex < lastColIndex)
02224           {
02225             // (don't think we ever get here?)
02226             // Cell is in a column less than current right boundary,
02227             //  so stop block at the previous row
02228             lastRowIndex = PR_MAX(0,rowIndex - 1);
02229           }
02230           else
02231           {
02232             // Go on to examine next row
02233             lastRowIndex = rowIndex+1;
02234           }
02235         }
02236         // Use the minimum col we found so far for right boundary
02237         lastColIndex = PR_MIN(lastColIndex, lastColInRow);
02238       }
02239       else
02240       {
02241         // No selected cells in this row -- stop at row above
02242         //  and leave last column at its previous value
02243         lastRowIndex = PR_MAX(0,rowIndex - 1);
02244       }
02245     }
02246   
02247     // The list of cells we will delete after joining
02248     nsVoidArray deleteList;
02249 
02250     // 2nd pass: Do the joining and merging
02251     for (rowIndex = 0; rowIndex < rowCount; rowIndex++)
02252     {
02253       for (colIndex = 0; colIndex < colCount; colIndex += PR_MAX(actualColSpan2, 1))
02254       {
02255         res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
02256                             &startRowIndex2, &startColIndex2,
02257                             &rowSpan2, &colSpan2, 
02258                             &actualRowSpan2, &actualColSpan2, &isSelected2);
02259         if (NS_FAILED(res)) return res;
02260 
02261         // If this is 0, we are past last cell in row, so exit the loop
02262         if (actualColSpan2 == 0)
02263           break;
02264 
02265         // Merge only selected cells (skip cell we're merging into, of course)
02266         if (isSelected2 && cell2 != firstCell)
02267         {
02268           if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex && 
02269               colIndex >= firstColIndex && colIndex <= lastColIndex)
02270           {
02271             // We are within the join region
02272             // Problem: It is very tricky to delete cells as we merge,
02273             //  since that will upset the cellmap
02274             //  Instead, build a list of cells to delete and do it later
02275             NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above");
02276 
02277             if (actualColSpan2 > 1)
02278             {
02279               //Check if cell "hangs" off the boundary because of colspan > 1
02280               //  Use split methods to chop off excess
02281               PRInt32 extraColSpan = (startColIndex2 + actualColSpan2) - (lastColIndex+1);
02282               if ( extraColSpan > 0)
02283               {
02284                 res = SplitCellIntoColumns(table, startRowIndex2, startColIndex2, 
02285                                            actualColSpan2-extraColSpan, extraColSpan, nsnull);
02286                 if (NS_FAILED(res)) return res;
02287               }
02288             }
02289 
02290             res = MergeCells(firstCell, cell2, PR_FALSE);
02291             if (NS_FAILED(res)) return res;
02292             
02293             // Add cell to list to delete
02294             deleteList.AppendElement((void *)cell2.get());
02295           }
02296           else if (aMergeNonContiguousContents)
02297           {
02298             // Cell is outside join region -- just merge the contents
02299             res = MergeCells(firstCell, cell2, PR_FALSE);
02300             if (NS_FAILED(res)) return res;
02301           }
02302         }
02303       }
02304     }
02305 
02306     // All cell contents are merged. Delete the empty cells we accumulated
02307     // Prevent rules testing until we're done
02308     nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
02309 
02310     nsIDOMElement *elementPtr;
02311     PRInt32 count;
02312     while ((count = deleteList.Count()))
02313     {
02314       // go backwards to keep nsVoidArray from mem-moving everything each time
02315       count--; // nsVoidArray is zero based
02316       elementPtr = (nsIDOMElement*)deleteList.ElementAt(count);
02317       deleteList.RemoveElementAt(count);
02318       if (elementPtr)
02319       {
02320         nsCOMPtr<nsIDOMNode> node = do_QueryInterface(elementPtr);
02321         res = DeleteNode(node);
02322         if (NS_FAILED(res)) return res;
02323       }
02324     }
02325     // Cleanup selection: remove ranges where cells were deleted
02326     nsCOMPtr<nsISelection> selection;
02327     res = GetSelection(getter_AddRefs(selection));
02328     if (NS_FAILED(res)) return res;
02329     if (!selection) return NS_ERROR_FAILURE;
02330 
02331     PRInt32 rangeCount;
02332     res = selection->GetRangeCount(&rangeCount);
02333     if (NS_FAILED(res)) return res;
02334 
02335     nsCOMPtr<nsIDOMRange> range;
02336     PRInt32 i;
02337     for (i = 0; i < rangeCount; i++)
02338     {
02339       res = selection->GetRangeAt(i, getter_AddRefs(range));
02340       if (NS_FAILED(res)) return res;
02341       if (!range) return NS_ERROR_FAILURE;
02342 
02343       nsCOMPtr<nsIDOMElement> deletedCell;
02344       res = GetCellFromRange(range, getter_AddRefs(deletedCell));
02345       if (!deletedCell)
02346       {
02347         selection->RemoveRange(range);
02348         rangeCount--;
02349         i--;
02350       }
02351     }
02352 
02353     // Set spans for the cell everthing merged into
02354     res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1);
02355     if (NS_FAILED(res)) return res;
02356     res = SetColSpan(firstCell, lastColIndex-firstColIndex+1);
02357     if (NS_FAILED(res)) return res;
02358     
02359     
02360     // Fixup disturbances in table layout
02361     NormalizeTable(table);
02362   }
02363   else
02364   {
02365     // Joining with cell to the right -- get rowspan and colspan data of target cell
02366     res = GetCellDataAt(table, startRowIndex, startColIndex, getter_AddRefs(targetCell),
02367                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02368                         &actualRowSpan, &actualColSpan, &isSelected);
02369     if (NS_FAILED(res)) return res;
02370     if (!targetCell) return NS_ERROR_NULL_POINTER;
02371 
02372     // Get data for cell to the right
02373     res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, getter_AddRefs(cell2),
02374                         &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, 
02375                         &actualRowSpan2, &actualColSpan2, &isSelected2);
02376     if (NS_FAILED(res)) return res;
02377     if(!cell2) return NS_OK; // Don't fail if there's no cell
02378 
02379     // sanity check
02380     NS_ASSERTION((startRowIndex >= startRowIndex2),"JoinCells: startRowIndex < startRowIndex2");
02381 
02382     // Figure out span of merged cell starting from target's starting row
02383     // to handle case of merged cell starting in a row above
02384     PRInt32 spanAboveMergedCell = startRowIndex - startRowIndex2;
02385     PRInt32 effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell;
02386 
02387     if (effectiveRowSpan2 > actualRowSpan)
02388     {
02389       // Cell to the right spans into row below target
02390       // Split off portion below target cell's bottom-most row
02391       res = SplitCellIntoRows(table, startRowIndex2, startColIndex2,
02392                               spanAboveMergedCell+actualRowSpan, 
02393                               effectiveRowSpan2-actualRowSpan, nsnull);
02394       if (NS_FAILED(res)) return res;
02395     }
02396 
02397     // Move contents from cell to the right
02398     // Delete the cell now only if it starts in the same row
02399     //   and has enough row "height"
02400     res = MergeCells(targetCell, cell2, 
02401                      (startRowIndex2 == startRowIndex) && 
02402                      (effectiveRowSpan2 >= actualRowSpan));
02403     if (NS_FAILED(res)) return res;
02404 
02405     if (effectiveRowSpan2 < actualRowSpan)
02406     {
02407       // Merged cell is "shorter" 
02408       // (there are cells(s) below it that are row-spanned by target cell)
02409       // We could try splitting those cells, but that's REAL messy,
02410       //  so the safest thing to do is NOT really join the cells
02411       return NS_OK;
02412     }
02413 
02414     if( spanAboveMergedCell > 0 )
02415     {
02416       // Cell we merged started in a row above the target cell
02417       // Reduce rowspan to give room where target cell will extend it's colspan
02418       res = SetRowSpan(cell2, spanAboveMergedCell);
02419       if (NS_FAILED(res)) return res;
02420     }
02421 
02422     // Reset target cell's colspan to encompass cell to the right
02423     res = SetColSpan(targetCell, actualColSpan+actualColSpan2);
02424     if (NS_FAILED(res)) return res;
02425   }
02426   return res;
02427 }
02428 
02429 NS_IMETHODIMP 
02430 nsHTMLEditor::MergeCells(nsCOMPtr<nsIDOMElement> aTargetCell, 
02431                          nsCOMPtr<nsIDOMElement> aCellToMerge,
02432                          PRBool aDeleteCellToMerge)
02433 {
02434   if (!aTargetCell || !aCellToMerge) return NS_ERROR_NULL_POINTER;
02435 
02436   nsresult res = NS_OK;
02437 
02438   // Prevent rules testing until we're done
02439   nsAutoRules beginRulesSniffing(this, kOpDeleteNode, nsIEditor::eNext);
02440 
02441   // Don't need to merge if cell is empty
02442   if (!IsEmptyCell(aCellToMerge))
02443   {
02444     // Get index of last child in target cell
02445     nsCOMPtr<nsIDOMNodeList> childNodes;
02446     nsCOMPtr<nsIDOMNode> cellChild;
02447     res = aTargetCell->GetChildNodes(getter_AddRefs(childNodes));
02448     // If we fail or don't have children, 
02449     //  we insert at index 0
02450     PRInt32 insertIndex = 0;
02451 
02452     if ((NS_SUCCEEDED(res)) && (childNodes))
02453     {
02454       // Start inserting just after last child
02455       PRUint32 len;
02456       res = childNodes->GetLength(&len);
02457       if (NS_FAILED(res)) return res;
02458       if (len == 1 && IsEmptyCell(aTargetCell))
02459       {
02460           // Delete the empty node
02461           nsCOMPtr<nsIDOMNode> tempNode;
02462           res = childNodes->Item(0, getter_AddRefs(cellChild));
02463           if (NS_FAILED(res)) return res;
02464           res = DeleteNode(cellChild);
02465           if (NS_FAILED(res)) return res;
02466           insertIndex = 0;
02467       }
02468       else
02469         insertIndex = (PRInt32)len;
02470     }
02471 
02472     // Move the contents
02473     PRBool hasChild;
02474     aCellToMerge->HasChildNodes(&hasChild);
02475     while (hasChild)
02476     {
02477       aCellToMerge->GetLastChild(getter_AddRefs(cellChild));
02478       res = DeleteNode(cellChild);
02479       if (NS_FAILED(res)) return res;
02480 
02481       res = InsertNode(cellChild, aTargetCell, insertIndex);
02482       if (NS_FAILED(res)) return res;
02483 
02484       aCellToMerge->HasChildNodes(&hasChild);
02485     }
02486   }
02487 
02488   // Delete cells whose contents were moved
02489   if (aDeleteCellToMerge)
02490     res = DeleteNode(aCellToMerge);
02491 
02492   return res;
02493 }
02494 
02495 
02496 NS_IMETHODIMP 
02497 nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32& aNewRowCount)
02498 {
02499   if (!aTable) return NS_ERROR_NULL_POINTER;
02500 
02501   PRInt32 rowCount, colCount;
02502   nsresult res = GetTableSize(aTable, &rowCount, &colCount);
02503   if (NS_FAILED(res)) return res;
02504 
02505   nsCOMPtr<nsIDOMElement>cell;
02506   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02507   PRBool  isSelected;
02508 
02509   PRInt32 minRowSpan = -1;
02510   PRInt32 colIndex;
02511   
02512   for( colIndex = 0; colIndex < colCount; colIndex += PR_MAX(actualColSpan, 1))
02513   {
02514     res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
02515                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02516                         &actualRowSpan, &actualColSpan, &isSelected);
02517     // NOTE: This is a *real* failure. 
02518     // GetCellDataAt passes if cell is missing from cellmap
02519     if(NS_FAILED(res)) return res;
02520     if (!cell) break;
02521     if(rowSpan > 0 && 
02522        startRowIndex == aRowIndex &&
02523        (rowSpan < minRowSpan || minRowSpan == -1))
02524     {
02525       minRowSpan = rowSpan;
02526     }
02527     NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
02528   }
02529   if(minRowSpan > 1)
02530   {
02531     // The amount to reduce everyone's rowspan
02532     // so at least one cell has rowspan = 1
02533     PRInt32 rowsReduced = minRowSpan - 1;
02534     for(colIndex = 0; colIndex < colCount; colIndex += PR_MAX(actualColSpan, 1))
02535     {
02536       res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell),
02537                           &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02538                           &actualRowSpan, &actualColSpan, &isSelected);
02539       if(NS_FAILED(res)) return res;
02540       // Fixup rowspans only for cells starting in current row
02541       if(cell && rowSpan > 0 &&
02542          startRowIndex == aRowIndex && 
02543          startColIndex ==  colIndex )
02544       {
02545         res = SetRowSpan(cell, rowSpan-rowsReduced);
02546         if(NS_FAILED(res)) return res;
02547       }
02548       NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan");
02549     }
02550   }
02551   return GetTableSize(aTable, &aNewRowCount, &colCount);
02552 }
02553 
02554 NS_IMETHODIMP 
02555 nsHTMLEditor::FixBadColSpan(nsIDOMElement *aTable, PRInt32 aColIndex, PRInt32& aNewColCount)
02556 {
02557   if (!aTable) return NS_ERROR_NULL_POINTER;
02558 
02559   PRInt32 rowCount, colCount;
02560   nsresult res = GetTableSize(aTable, &rowCount, &colCount);
02561   if (NS_FAILED(res)) return res;
02562 
02563   nsCOMPtr<nsIDOMElement> cell;
02564   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02565   PRBool  isSelected;
02566 
02567   PRInt32 minColSpan = -1;
02568   PRInt32 rowIndex;
02569   
02570   for( rowIndex = 0; rowIndex < rowCount; rowIndex += PR_MAX(actualRowSpan, 1))
02571   {
02572     res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
02573                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02574                         &actualRowSpan, &actualColSpan, &isSelected);
02575     // NOTE: This is a *real* failure. 
02576     // GetCellDataAt passes if cell is missing from cellmap
02577     if(NS_FAILED(res)) return res;
02578     if (!cell) break;
02579     if(colSpan > 0 && 
02580        startColIndex == aColIndex &&
02581        (colSpan < minColSpan || minColSpan == -1))
02582     {
02583       minColSpan = colSpan;
02584     }
02585     NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
02586   }
02587   if(minColSpan > 1)
02588   {
02589     // The amount to reduce everyone's colspan
02590     // so at least one cell has colspan = 1
02591     PRInt32 colsReduced = minColSpan - 1;
02592     for(rowIndex = 0; rowIndex < rowCount; rowIndex += PR_MAX(actualRowSpan, 1))
02593     {
02594       res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell),
02595                           &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02596                           &actualRowSpan, &actualColSpan, &isSelected);
02597       if(NS_FAILED(res)) return res;
02598       // Fixup colspans only for cells starting in current column
02599       if(cell && colSpan > 0 &&
02600          startColIndex == aColIndex && 
02601          startRowIndex ==  rowIndex )
02602       {
02603         res = SetColSpan(cell, colSpan-colsReduced);
02604         if(NS_FAILED(res)) return res;
02605       }
02606       NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan");
02607     }
02608   }
02609   return GetTableSize(aTable, &rowCount, &aNewColCount);
02610 }
02611 
02612 NS_IMETHODIMP 
02613 nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable)
02614 {
02615   nsCOMPtr<nsISelection>selection;
02616   nsresult res = GetSelection(getter_AddRefs(selection));
02617   if (NS_FAILED(res)) return res;
02618   if (!selection) return NS_ERROR_FAILURE;
02619 
02620   nsCOMPtr<nsIDOMElement> table;
02621   res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table));
02622   if (NS_FAILED(res)) return res;
02623   // Don't fail if we didn't find a table
02624   if (!table)         return NS_OK;
02625 
02626   PRInt32 rowCount, colCount, rowIndex, colIndex;
02627   res = GetTableSize(table, &rowCount, &colCount);
02628   if (NS_FAILED(res)) return res;
02629 
02630   // Save current selection
02631   nsAutoSelectionReset selectionResetter(selection, this);
02632 
02633   nsAutoEditBatch beginBatching(this);
02634   // Prevent auto insertion of BR in new cell until we're done
02635   nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
02636 
02637   nsCOMPtr<nsIDOMElement> cell;
02638   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02639   PRBool  isSelected;
02640 
02641   // Scan all cells in each row to detect bad rowspan values
02642   for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
02643   {
02644     res = FixBadRowSpan(table, rowIndex, rowCount);
02645     if (NS_FAILED(res)) return res;
02646   }
02647   // and same for colspans
02648   for(colIndex = 0; colIndex < colCount; colIndex++)
02649   {
02650     res = FixBadColSpan(table, colIndex, colCount);
02651     if (NS_FAILED(res)) return res;
02652   }
02653 
02654   // Fill in missing cellmap locations with empty cells
02655   for(rowIndex = 0; rowIndex < rowCount; rowIndex++)
02656   {
02657     nsCOMPtr<nsIDOMElement> previousCellInRow;
02658 
02659     for(colIndex = 0; colIndex < colCount; colIndex++)
02660     {
02661       res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell),
02662                           &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02663                           &actualRowSpan, &actualColSpan, &isSelected);
02664       // NOTE: This is a *real* failure. 
02665       // GetCellDataAt passes if cell is missing from cellmap
02666       if(NS_FAILED(res)) return res;
02667       if (!cell)
02668       {
02669         //We are missing a cell at a cellmap location
02670 #ifdef DEBUG
02671         printf("NormalizeTable found missing cell at row=%d, col=%d\n", rowIndex, colIndex);
02672 #endif
02673         // Add a cell after the previous Cell in the current row
02674         if(previousCellInRow)
02675         {
02676           // Insert a new cell after (PR_TRUE), and return the new cell to us
02677           res = InsertCell(previousCellInRow, 1, 1, PR_TRUE, PR_FALSE, getter_AddRefs(cell));
02678           if (NS_FAILED(res)) return res;
02679 
02680           // Set this so we use returned new "cell" to set previousCellInRow below
02681           if(cell)
02682             startRowIndex = rowIndex;   
02683         } else {
02684           // We don't have any cells in this row -- We are really messed up!
02685 #ifdef DEBUG
02686           printf("NormalizeTable found no cells in row=%d, col=%d\n", rowIndex, colIndex);
02687 #endif
02688           return NS_ERROR_FAILURE;
02689         }
02690       }
02691       // Save the last cell found in the same row we are scanning
02692       if(startRowIndex == rowIndex)
02693       {
02694         previousCellInRow = cell;
02695       }
02696     }
02697   }
02698   return res;
02699 }
02700 
02701 NS_IMETHODIMP 
02702 nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell,
02703                              PRInt32 *aRowIndex, PRInt32 *aColIndex)
02704 {
02705   NS_ENSURE_ARG_POINTER(aRowIndex);
02706   *aColIndex=0; // initialize out params
02707   NS_ENSURE_ARG_POINTER(aColIndex);
02708   *aRowIndex=0;
02709   nsresult res=NS_ERROR_NOT_INITIALIZED;
02710   if (!aCell)
02711   {
02712     // Get the selected cell or the cell enclosing the selection anchor
02713     nsCOMPtr<nsIDOMElement> cell;
02714     res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cell));
02715     if (NS_SUCCEEDED(res) && cell)
02716       aCell = cell;
02717     else
02718       return NS_ERROR_FAILURE;
02719   }
02720 
02721   nsISupports *layoutObject=nsnull; // frames are not ref counted, so don't use an nsCOMPtr
02722   res = nsHTMLEditor::GetLayoutObject(aCell, &layoutObject);
02723   if (NS_FAILED(res)) return res;
02724   if (!layoutObject)  return NS_ERROR_FAILURE;
02725 
02726   nsITableCellLayout *cellLayoutObject=nsnull; // again, frames are not ref-counted
02727   res = layoutObject->QueryInterface(NS_GET_IID(nsITableCellLayout), (void**)(&cellLayoutObject));
02728   if (NS_FAILED(res)) return res;
02729   if (!cellLayoutObject)  return NS_ERROR_FAILURE;
02730   return cellLayoutObject->GetCellIndexes(*aRowIndex, *aColIndex);
02731 }
02732 
02733 NS_IMETHODIMP
02734 nsHTMLEditor::GetTableLayoutObject(nsIDOMElement* aTable, nsITableLayout **tableLayoutObject)
02735 {
02736   *tableLayoutObject=nsnull;
02737   if (!aTable)
02738     return NS_ERROR_NOT_INITIALIZED;
02739   
02740   // frames are not ref counted, so don't use an nsCOMPtr
02741   nsISupports *layoutObject=nsnull;
02742   nsresult res = GetLayoutObject(aTable, &layoutObject); 
02743   if (NS_FAILED(res)) return res;
02744   if (!layoutObject)  return NS_ERROR_FAILURE;
02745   return layoutObject->QueryInterface(NS_GET_IID(nsITableLayout), 
02746                                       (void**)(tableLayoutObject)); 
02747 }
02748 
02749 //Return actual number of cells (a cell with colspan > 1 counts as just 1)
02750 PRBool nsHTMLEditor::GetNumberOfCellsInRow(nsIDOMElement* aTable, PRInt32 rowIndex)
02751 {
02752   PRInt32 cellCount = 0;
02753   nsCOMPtr<nsIDOMElement> cell;
02754   PRInt32 colIndex = 0;
02755   nsresult res;
02756   do {
02757     PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02758     PRBool  isSelected;
02759     res = GetCellDataAt(aTable, rowIndex, colIndex, getter_AddRefs(cell),
02760                         &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02761                         &actualRowSpan, &actualColSpan, &isSelected);
02762     if (NS_FAILED(res)) return res;
02763     if (cell)
02764     {
02765       // Only count cells that start in row we are working with
02766       if (startRowIndex == rowIndex)
02767         cellCount++;
02768       
02769       //Next possible location for a cell
02770       colIndex += actualColSpan;
02771     }
02772     else
02773       colIndex++;
02774 
02775   } while (cell);
02776 
02777   return cellCount;
02778 }
02779 
02780 /* Not scriptable: For convenience in C++ 
02781    Use GetTableRowCount and GetTableColumnCount from JavaScript
02782 */
02783 NS_IMETHODIMP
02784 nsHTMLEditor::GetTableSize(nsIDOMElement *aTable,
02785                            PRInt32* aRowCount, PRInt32* aColCount)
02786 {
02787   NS_ENSURE_ARG_POINTER(aRowCount);
02788   NS_ENSURE_ARG_POINTER(aColCount);
02789   nsresult res;
02790   *aRowCount = 0;
02791   *aColCount = 0;
02792   nsCOMPtr<nsIDOMElement> table;
02793   // Get the selected talbe or the table enclosing the selection anchor
02794   res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table));
02795   if (NS_FAILED(res)) return res;
02796   if (!table)         return NS_ERROR_FAILURE;
02797   
02798   // frames are not ref counted, so don't use an nsCOMPtr
02799   nsITableLayout *tableLayoutObject;
02800   res = GetTableLayoutObject(table.get(), &tableLayoutObject);
02801   if (NS_FAILED(res)) return res;
02802   if (!tableLayoutObject)
02803     return NS_ERROR_FAILURE;
02804 
02805   return tableLayoutObject->GetTableSize(*aRowCount, *aColCount); 
02806 }
02807 
02808 NS_IMETHODIMP 
02809 nsHTMLEditor::GetCellDataAt(nsIDOMElement* aTable, PRInt32 aRowIndex,
02810                             PRInt32 aColIndex, nsIDOMElement **aCell, 
02811                             PRInt32* aStartRowIndex, PRInt32* aStartColIndex, 
02812                             PRInt32* aRowSpan, PRInt32* aColSpan, 
02813                             PRInt32* aActualRowSpan, PRInt32* aActualColSpan, 
02814                             PRBool* aIsSelected)
02815 {
02816   NS_ENSURE_ARG_POINTER(aStartRowIndex);
02817   NS_ENSURE_ARG_POINTER(aStartColIndex);
02818   NS_ENSURE_ARG_POINTER(aRowSpan);
02819   NS_ENSURE_ARG_POINTER(aColSpan);
02820   NS_ENSURE_ARG_POINTER(aActualRowSpan);
02821   NS_ENSURE_ARG_POINTER(aActualColSpan);
02822   NS_ENSURE_ARG_POINTER(aIsSelected);
02823   if (!aCell) return NS_ERROR_NULL_POINTER;
02824 
02825   nsresult res=NS_ERROR_FAILURE;
02826   *aStartRowIndex = 0;
02827   *aStartColIndex = 0;
02828   *aRowSpan = 0;
02829   *aColSpan = 0;
02830   *aActualRowSpan = 0;
02831   *aActualColSpan = 0;
02832   *aIsSelected = PR_FALSE;
02833 
02834   *aCell = nsnull;
02835 
02836   if (!aTable)
02837   {
02838     // Get the selected table or the table enclosing the selection anchor
02839     nsCOMPtr<nsIDOMElement> table;
02840     res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nsnull, getter_AddRefs(table));
02841     if (NS_FAILED(res)) return res;
02842     if (table)
02843       aTable = table;
02844     else
02845       return NS_ERROR_FAILURE;
02846   }
02847   
02848   // frames are not ref counted, so don't use an nsCOMPtr
02849   nsITableLayout *tableLayoutObject;
02850   res = GetTableLayoutObject(aTable, &tableLayoutObject);
02851   if (NS_FAILED(res)) return res;
02852   if (!tableLayoutObject) return NS_ERROR_FAILURE;
02853 
02854   // Note that this returns NS_TABLELAYOUT_CELL_NOT_FOUND when
02855   //  the index(es) are out of bounds
02856   nsCOMPtr<nsIDOMElement> cell;
02857   res = tableLayoutObject->GetCellDataAt(aRowIndex, aColIndex,
02858                                          *getter_AddRefs(cell), 
02859                                          *aStartRowIndex, *aStartColIndex,
02860                                          *aRowSpan, *aColSpan, 
02861                                          *aActualRowSpan, *aActualColSpan, 
02862                                          *aIsSelected);
02863   if (cell)
02864   {
02865     *aCell = cell.get();
02866     NS_ADDREF(*aCell);
02867   }
02868   // Convert to editor's generic "not found" return value
02869   if (res == NS_TABLELAYOUT_CELL_NOT_FOUND) res = NS_EDITOR_ELEMENT_NOT_FOUND;
02870   return res;
02871 }
02872 
02873 // When all you want is the cell
02874 NS_IMETHODIMP 
02875 nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, nsIDOMElement **aCell)
02876 {
02877   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
02878   PRBool  isSelected;
02879   return GetCellDataAt(aTable, aRowIndex, aColIndex, aCell, 
02880                        &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02881                        &actualRowSpan, &actualColSpan, &isSelected);
02882 }
02883 
02884 // When all you want are the rowspan and colspan (not exposed in nsITableEditor)
02885 NS_IMETHODIMP
02886 nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, PRInt32 aRowIndex, PRInt32 aColIndex, 
02887                              PRInt32& aActualRowSpan, PRInt32& aActualColSpan)
02888 {
02889   nsCOMPtr<nsIDOMElement> cell;    
02890   PRInt32 startRowIndex, startColIndex, rowSpan, colSpan;
02891   PRBool  isSelected;
02892   return GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell), 
02893                        &startRowIndex, &startColIndex, &rowSpan, &colSpan, 
02894                        &aActualRowSpan, &aActualColSpan, &isSelected);
02895 }
02896 
02897 NS_IMETHODIMP
02898 nsHTMLEditor::GetCellContext(nsISelection **aSelection,
02899                              nsIDOMElement   **aTable,
02900                              nsIDOMElement   **aCell,
02901                              nsIDOMNode      **aCellParent, PRInt32 *aCellOffset,
02902                              PRInt32 *aRowIndex, PRInt32 *aColIndex)
02903 {
02904   // Initialize return pointers
02905   if (aSelection) *aSelection = nsnull;
02906   if (aTable) *aTable = nsnull;
02907   if (aCell) *aCell = nsnull;
02908   if (aCellParent) *aCellParent = nsnull;
02909   if (aCellOffset) *aCellOffset = 0;
02910   if (aRowIndex) *aRowIndex = 0;
02911   if (aColIndex) *aColIndex = 0;
02912 
02913   nsCOMPtr <nsISelection> selection;
02914   nsresult res = GetSelection(getter_AddRefs(selection));
02915   if (NS_FAILED(res)) return res;
02916   if (!selection) return NS_ERROR_FAILURE;
02917 
02918   if (aSelection)
02919   {
02920     *aSelection = selection.get();
02921     NS_ADDREF(*aSelection);
02922   }
02923   nsCOMPtr <nsIDOMElement> table;
02924   nsCOMPtr <nsIDOMElement> cell;
02925 
02926   // Caller may supply the cell...
02927   if (aCell && *aCell)
02928     cell = *aCell;
02929 
02930   // ...but if not supplied,
02931   //    get cell if it's the child of selection anchor node,
02932   //    or get the enclosing by a cell
02933   if (!cell)
02934   {
02935     // Find a selected or enclosing table element
02936     nsCOMPtr<nsIDOMElement> cellOrTableElement;
02937     PRInt32 selectedCount;
02938     nsAutoString tagName;
02939     res = GetSelectedOrParentTableElement(tagName, &selectedCount,
02940                                           getter_AddRefs(cellOrTableElement));
02941     if (NS_FAILED(res)) return res;
02942     if (tagName.EqualsLiteral("table"))
02943     {
02944       // We have a selected table, not a cell
02945       if (aTable)
02946       {
02947         *aTable = cellOrTableElement.get();
02948         NS_ADDREF(*aTable);
02949       }
02950       return NS_OK;
02951     }
02952     if (!tagName.EqualsLiteral("td"))
02953       return NS_EDITOR_ELEMENT_NOT_FOUND;
02954 
02955     // We found a cell
02956     cell = cellOrTableElement;
02957   }
02958   if (aCell)
02959   {
02960     *aCell = cell.get();
02961     NS_ADDREF(*aCell);
02962   }
02963 
02964   // Get containing table
02965   res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table));
02966   if (NS_FAILED(res)) return res;
02967   // Cell must be in a table, so fail if not found
02968   if (!table) return NS_ERROR_FAILURE;
02969   if (aTable)
02970   {
02971     *aTable = table.get();
02972     NS_ADDREF(*aTable);
02973   }
02974 
02975   // Get the rest of the related data only if requested
02976   if (aRowIndex || aColIndex)
02977   {
02978     PRInt32 rowIndex, colIndex;
02979     // Get current cell location so we can put caret back there when done
02980     res = GetCellIndexes(cell, &rowIndex, &colIndex);
02981     if(NS_FAILED(res)) return res;
02982     if (aRowIndex) *aRowIndex = rowIndex;
02983     if (aColIndex) *aColIndex = colIndex;
02984   }
02985   if (aCellParent)
02986   {
02987     nsCOMPtr <nsIDOMNode> cellParent;
02988     // Get the immediate parent of the cell
02989     res = cell->GetParentNode(getter_AddRefs(cellParent));
02990     if (NS_FAILED(res)) return res;
02991     // Cell has to have a parent, so fail if not found
02992     if (!cellParent) return NS_ERROR_FAILURE;
02993 
02994     *aCellParent = cellParent.get();
02995     NS_ADDREF(*aCellParent);
02996 
02997     if (aCellOffset)
02998       res = GetChildOffset(cell, cellParent, *aCellOffset);
02999   }
03000 
03001   return res;
03002 }
03003 
03004 nsresult 
03005 nsHTMLEditor::GetCellFromRange(nsIDOMRange *aRange, nsIDOMElement **aCell)
03006 {
03007   // Note: this might return a node that is outside of the range.
03008   // Use carefully.
03009   if (!aRange || !aCell) return NS_ERROR_NULL_POINTER;
03010 
03011   *aCell = nsnull;
03012 
03013   nsCOMPtr<nsIDOMNode> startParent;
03014   nsresult res = aRange->GetStartContainer(getter_AddRefs(startParent));
03015   if (NS_FAILED(res)) return res;
03016   if (!startParent) return NS_ERROR_FAILURE;
03017 
03018   PRInt32 startOffset;
03019   res = aRange->GetStartOffset(&startOffset);
03020   if (NS_FAILED(res)) return res;
03021 
03022   nsCOMPtr<nsIDOMNode> childNode = GetChildAt(startParent, startOffset);
03023   // This means selection is probably at a text node (or end of doc?)
03024   if (!childNode) return NS_ERROR_FAILURE;
03025 
03026   nsCOMPtr<nsIDOMNode> endParent;
03027   res = aRange->GetEndContainer(getter_AddRefs(endParent));
03028   if (NS_FAILED(res)) return res;
03029   if (!startParent) return NS_ERROR_FAILURE;
03030 
03031   PRInt32 endOffset;
03032   res = aRange->GetEndOffset(&endOffset);
03033   if (NS_FAILED(res)) return res;
03034   
03035   // If a cell is deleted, the range is collapse
03036   //   (startOffset == endOffset)
03037   //   so tell caller the cell wasn't found
03038   if (startParent == endParent && 
03039       endOffset == startOffset+1 &&
03040       nsHTMLEditUtils::IsTableCell(childNode))
03041   {
03042     // Should we also test if frame is selected? (Use GetCellDataAt())
03043     // (Let's not for now -- more efficient)
03044     nsCOMPtr<nsIDOMElement> cellElement = do_QueryInterface(childNode);
03045     *aCell = cellElement.get();
03046     NS_ADDREF(*aCell);
03047     return NS_OK;
03048   }
03049   return NS_EDITOR_ELEMENT_NOT_FOUND;
03050 }
03051 
03052 NS_IMETHODIMP 
03053 nsHTMLEditor::GetFirstSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell)
03054 {
03055   if (!aCell) return NS_ERROR_NULL_POINTER;
03056   *aCell = nsnull;
03057   if (aRange) *aRange = nsnull;
03058 
03059   nsCOMPtr<nsISelection> selection;
03060   nsresult res = GetSelection(getter_AddRefs(selection));
03061   if (NS_FAILED(res)) return res;
03062   if (!selection) return NS_ERROR_FAILURE;
03063 
03064   nsCOMPtr<nsIDOMRange> range;
03065   res = selection->GetRangeAt(0, getter_AddRefs(range));
03066   if (NS_FAILED(res)) return res;
03067   if (!range) return NS_ERROR_FAILURE;
03068 
03069   mSelectedCellIndex = 0;
03070 
03071   res = GetCellFromRange(range, aCell);
03072   // Failure here probably means selection is in a text node,
03073   //  so there's no selected cell
03074   if (NS_FAILED(res)) return NS_EDITOR_ELEMENT_NOT_FOUND;
03075   // No cell means range was collapsed (cell was deleted)
03076   if (!*aCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
03077 
03078   if (aRange)
03079   {
03080     *aRange = range.get();
03081     NS_ADDREF(*aRange);
03082   }
03083 
03084   // Setup for next cell
03085   mSelectedCellIndex = 1;
03086 
03087   return res;  
03088 }
03089 
03090 NS_IMETHODIMP
03091 nsHTMLEditor::GetNextSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell)
03092 {
03093   if (!aCell) return NS_ERROR_NULL_POINTER;
03094   *aCell = nsnull;
03095   if (aRange) *aRange = nsnull;
03096 
03097   nsCOMPtr<nsISelection> selection;
03098   nsresult res = GetSelection(getter_AddRefs(selection));
03099   if (NS_FAILED(res)) return res;
03100   if (!selection) return NS_ERROR_FAILURE;
03101 
03102   PRInt32 rangeCount;
03103   res = selection->GetRangeCount(&rangeCount);
03104   if (NS_FAILED(res)) return res;
03105 
03106   // Don't even try if index exceeds range count
03107   if (mSelectedCellIndex >= rangeCount) 
03108     return NS_EDITOR_ELEMENT_NOT_FOUND;
03109 
03110   // Scan through ranges to find next valid selected cell
03111   nsCOMPtr<nsIDOMRange> range;
03112   for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++)
03113   {
03114     res = selection->GetRangeAt(mSelectedCellIndex, getter_AddRefs(range));
03115     if (NS_FAILED(res)) return res;
03116     if (!range) return NS_ERROR_FAILURE;
03117 
03118     res = GetCellFromRange(range, aCell);
03119     // Failure here means the range doesn't contain a cell
03120     if (NS_FAILED(res)) return NS_EDITOR_ELEMENT_NOT_FOUND;
03121     
03122     // We found a selected cell
03123     if (*aCell) break;
03124 #ifdef DEBUG_cmanske
03125     else
03126       printf("GetNextSelectedCell: Collapsed range found\n");
03127 #endif
03128 
03129     // If we didn't find a cell, continue to next range in selection
03130   }
03131   // No cell means all remaining ranges were collapsed (cells were deleted)
03132   if (!*aCell) return NS_EDITOR_ELEMENT_NOT_FOUND;
03133 
03134   if (aRange)
03135   {
03136     *aRange = range.get();
03137     NS_ADDREF(*aRange);
03138   }
03139 
03140   // Setup for next cell
03141   mSelectedCellIndex++;
03142 
03143   return res;  
03144 }
03145 
03146 NS_IMETHODIMP 
03147 nsHTMLEditor::GetFirstSelectedCellInTable(PRInt32 *aRowIndex, PRInt32 *aColIndex, nsIDOMElement **aCell)
03148 {
03149   if (!aCell) return NS_ERROR_NULL_POINTER;
03150   *aCell = nsnull;
03151   if (aRowIndex)
03152     *aRowIndex = 0;
03153   if (aColIndex)
03154     *aColIndex = 0;
03155 
03156   nsCOMPtr<nsIDOMElement> cell;
03157   nsresult res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
03158   if (NS_FAILED(res)) return res;
03159   if (!cell) return NS_EDITOR_ELEMENT_NOT_FOUND;
03160 
03161   *aCell = cell.get();
03162   NS_ADDREF(*aCell);
03163 
03164   // Also return the row and/or column if requested
03165   if (aRowIndex || aColIndex)
03166   {
03167     PRInt32 startRowIndex, startColIndex;
03168     res = GetCellIndexes(cell, &startRowIndex, &startColIndex);
03169     if(NS_FAILED(res)) return res;
03170 
03171     if (aRowIndex)
03172       *aRowIndex = startRowIndex;
03173 
03174     if (aColIndex)
03175       *aColIndex = startColIndex;
03176   }
03177 
03178   return res;
03179 }
03180 
03181 NS_IMETHODIMP
03182 nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, PRInt32 aRow, PRInt32 aCol, 
03183                                      PRInt32 aDirection, PRBool aSelected)
03184 {
03185   if (!aTable) return NS_ERROR_NOT_INITIALIZED;
03186 
03187   nsCOMPtr<nsISelection>selection;
03188   nsresult res = GetSelection(getter_AddRefs(selection));
03189   if (NS_FAILED(res)) return res;
03190   
03191   if (!selection)
03192   {
03193 #ifdef DEBUG_cmanske
03194     printf("Selection not found after table manipulation!\n");
03195 #endif
03196     return NS_ERROR_FAILURE;
03197   }
03198 
03199   nsCOMPtr<nsIDOMElement> cell;
03200   PRBool done = PR_FALSE;
03201   do {
03202     res = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell));
03203     if (NS_SUCCEEDED(res))
03204     {
03205       if (cell)
03206       {
03207         if (aSelected)
03208         {
03209           // Reselect the cell
03210           return SelectElement(cell);
03211         }
03212         else
03213         {
03214           // Set the caret to deepest first child
03215           //   but don't go into nested tables
03216           // TODO: Should we really be placing the caret at the END
03217           //  of the cell content?
03218           return CollapseSelectionToDeepestNonTableFirstChild(selection, cell);
03219         }
03220       } else {
03221         // Setup index to find another cell in the 
03222         //   direction requested, but move in
03223         //   other direction if already at beginning of row or column
03224         switch (aDirection)
03225         {
03226           case ePreviousColumn:
03227             if (aCol == 0)
03228             {
03229               if (aRow > 0)
03230                 aRow--;
03231               else
03232                 done = PR_TRUE;
03233             }
03234             else
03235               aCol--;
03236             break;
03237           case ePreviousRow:
03238             if (aRow == 0)
03239             {
03240               if (aCol > 0)
03241                 aCol--;
03242               else
03243                 done = PR_TRUE;
03244             }
03245             else
03246               aRow--;
03247             break;
03248           default:
03249             done = PR_TRUE;
03250         }
03251       }
03252     }
03253     else
03254       break;
03255   } while (!done);
03256 
03257   // We didn't find a cell
03258   // Set selection to just before the table
03259   nsCOMPtr<nsIDOMNode> tableParent;
03260   PRInt32 tableOffset;
03261   res = aTable->GetParentNode(getter_AddRefs(tableParent));
03262   if(NS_SUCCEEDED(res) && tableParent)
03263   {
03264     if(NS_SUCCEEDED(GetChildOffset(aTable, tableParent, tableOffset)))
03265       return selection->Collapse(tableParent, tableOffset);
03266   }
03267   // Last resort: Set selection to start of doc
03268   // (it's very bad to not have a valid selection!)
03269   return SetSelectionAtDocumentStart(selection);
03270 }
03271 
03272 NS_IMETHODIMP 
03273 nsHTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName,
03274                                               PRInt32 *aSelectedCount,
03275                                               nsIDOMElement** aTableElement)
03276 {
03277   NS_ENSURE_ARG_POINTER(aTableElement);
03278   NS_ENSURE_ARG_POINTER(aSelectedCount);
03279   *aTableElement = nsnull;
03280   aTagName.Truncate();
03281   *aSelectedCount = 0;
03282 
03283   nsCOMPtr<nsISelection> selection;
03284   nsresult res = GetSelection(getter_AddRefs(selection));
03285   if (NS_FAILED(res)) return res;
03286   if (!selection) return NS_ERROR_FAILURE;
03287 
03288   // Try to get the first selected cell
03289   nsCOMPtr<nsIDOMElement> tableOrCellElement;
03290   res = GetFirstSelectedCell(nsnull, getter_AddRefs(tableOrCellElement));
03291   if (NS_FAILED(res)) return res;
03292 
03293   NS_NAMED_LITERAL_STRING(tdName, "td");
03294 
03295   if (tableOrCellElement)
03296   {
03297       // Each cell is in its own selection range,
03298       //  so count signals multiple-cell selection
03299       res = selection->GetRangeCount(aSelectedCount);
03300       if (NS_FAILED(res)) return res;
03301       aTagName = tdName;
03302   }
03303   else
03304   {
03305     nsCOMPtr<nsIDOMNode> anchorNode;
03306     res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
03307     if(NS_FAILED(res)) return res;
03308     if (!anchorNode)  return NS_ERROR_FAILURE;
03309 
03310     nsCOMPtr<nsIDOMNode> selectedNode;
03311 
03312     // Get child of anchor node, if exists
03313     PRBool hasChildren;
03314     anchorNode->HasChildNodes(&hasChildren);
03315 
03316     if (hasChildren)
03317     {
03318       PRInt32 anchorOffset;
03319       res = selection->GetAnchorOffset(&anchorOffset);
03320       if (NS_FAILED(res)) return res;
03321       selectedNode = GetChildAt(anchorNode, anchorOffset);
03322       if (!selectedNode)
03323       {
03324         selectedNode = anchorNode;
03325         // If anchor doesn't have a child, we can't be selecting a table element,
03326         //  so don't do the following:
03327       }
03328       else
03329       {
03330         nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(selectedNode);
03331 
03332         if (atom == nsEditProperty::td)
03333         {
03334           tableOrCellElement = do_QueryInterface(selectedNode);
03335           aTagName = tdName;
03336           // Each cell is in its own selection range,
03337           //  so count signals multiple-cell selection
03338           res = selection->GetRangeCount(aSelectedCount);
03339           if (NS_FAILED(res)) return res;
03340         }
03341         else if (atom == nsEditProperty::table)
03342         {
03343           tableOrCellElement = do_QueryInterface(selectedNode);
03344           aTagName.AssignLiteral("table");
03345           *aSelectedCount = 1;
03346         }
03347         else if (atom == nsEditProperty::tr)
03348         {
03349           tableOrCellElement = do_QueryInterface(selectedNode);
03350           aTagName.AssignLiteral("tr");
03351           *aSelectedCount = 1;
03352         }
03353       }
03354     }
03355     if (!tableOrCellElement)
03356     {
03357       // Didn't find a table element -- find a cell parent
03358       res = GetElementOrParentByTagName(tdName, anchorNode, getter_AddRefs(tableOrCellElement));
03359       if(NS_FAILED(res)) return res;
03360       if (tableOrCellElement)
03361         aTagName = tdName;
03362     }
03363   }
03364   if (tableOrCellElement)
03365   {
03366     *aTableElement = tableOrCellElement.get();
03367     NS_ADDREF(*aTableElement);
03368   }
03369   return res;
03370 }
03371 
03372 static PRBool IndexNotTested(nsVoidArray *aArray, PRInt32 aIndex)
03373 {
03374   if (aArray)
03375   {
03376     PRInt32 count = aArray->Count();
03377     for (PRInt32 i = 0; i < count; i++)
03378     {
03379       if(aIndex == NS_PTR_TO_INT32(aArray->ElementAt(i)))
03380         return PR_FALSE;
03381     }
03382   }
03383   return PR_TRUE;
03384 }
03385 
03386 NS_IMETHODIMP 
03387 nsHTMLEditor::GetSelectedCellsType(nsIDOMElement *aElement, PRUint32 *aSelectionType)
03388 {
03389   NS_ENSURE_ARG_POINTER(aSelectionType);
03390   *aSelectionType = 0;
03391 
03392   // Be sure we have a table element 
03393   //  (if aElement is null, this uses selection's anchor node)
03394   nsCOMPtr<nsIDOMElement> table;
03395 
03396   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aElement, getter_AddRefs(table));
03397   if (NS_FAILED(res)) return res;
03398 
03399   PRInt32 rowCount, colCount;
03400   res = GetTableSize(table, &rowCount, &colCount);
03401   if (NS_FAILED(res)) return res;
03402 
03403   // Traverse all selected cells 
03404   nsCOMPtr<nsIDOMElement> selectedCell;
03405   res = GetFirstSelectedCell(nsnull, getter_AddRefs(selectedCell));
03406   if (NS_FAILED(res)) return res;
03407   if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK;
03408   
03409   // We have at least one selected cell, so set return value
03410   *aSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
03411 
03412   // Store indexes of each row/col to avoid duplication of searches
03413   nsVoidArray indexArray;
03414 
03415   PRBool allCellsInRowAreSelected = PR_FALSE;
03416   PRBool allCellsInColAreSelected = PR_FALSE;
03417   while (NS_SUCCEEDED(res) && selectedCell)
03418   {
03419     // Get the cell's location in the cellmap
03420     PRInt32 startRowIndex, startColIndex;
03421     res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
03422     if(NS_FAILED(res)) return res;
03423     
03424     if (IndexNotTested(&indexArray, startColIndex))
03425     {
03426       indexArray.AppendElement((void*)startColIndex);
03427       allCellsInRowAreSelected = AllCellsInRowSelected(table, startRowIndex, colCount);
03428       // We're done as soon as we fail for any row
03429       if (!allCellsInRowAreSelected) break;
03430     }
03431     res = GetNextSelectedCell(nsnull, getter_AddRefs(selectedCell));
03432   }
03433 
03434   if (allCellsInRowAreSelected)
03435   {
03436     *aSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
03437     return NS_OK;
03438   }
03439   // Test for columns
03440 
03441   // Empty the indexArray
03442   indexArray.Clear();
03443 
03444   // Start at first cell again
03445   res = GetFirstSelectedCell(nsnull, getter_AddRefs(selectedCell));
03446   while (NS_SUCCEEDED(res) && selectedCell)
03447   {
03448     // Get the cell's location in the cellmap
03449     PRInt32 startRowIndex, startColIndex;
03450     res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex);
03451     if(NS_FAILED(res)) return res;
03452   
03453     if (IndexNotTested(&indexArray, startRowIndex))
03454     {
03455       indexArray.AppendElement((void*)startColIndex);
03456       allCellsInColAreSelected = AllCellsInColumnSelected(table, startColIndex, rowCount);
03457       // We're done as soon as we fail for any column
03458       if (!allCellsInRowAreSelected) break;
03459     }
03460     res = GetNextSelectedCell(nsnull, getter_AddRefs(selectedCell));
03461   }
03462   if (allCellsInColAreSelected)
03463     *aSelectionType = nsISelectionPrivate::TABLESELECTION_COLUMN;
03464 
03465   return NS_OK;
03466 }
03467 
03468 PRBool 
03469 nsHTMLEditor::AllCellsInRowSelected(nsIDOMElement *aTable, PRInt32 aRowIndex, PRInt32 aNumberOfColumns)
03470 {
03471   if (!aTable) return PR_FALSE;
03472 
03473   PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
03474   PRBool  isSelected;
03475 
03476   for( PRInt32 col = 0; col < aNumberOfColumns; col += PR_MAX(actualColSpan, 1))
03477   {
03478     nsCOMPtr<nsIDOMElement> cell;    
03479     nsresult res = GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell),
03480                                  &curStartRowIndex, &curStartColIndex,
03481                                  &rowSpan, &colSpan,
03482                                  &actualRowSpan, &actualColSpan, &isSelected);
03483  
03484     if (NS_FAILED(res)) return PR_FALSE;
03485     // If no cell, we may have a "ragged" right edge,
03486     //   so return TRUE only if we already found a cell in the row
03487     if (!cell) return (col > 0) ? PR_TRUE : PR_FALSE;
03488 
03489     // Return as soon as a non-selected cell is found
03490     if (!isSelected)
03491       return PR_FALSE;
03492 
03493     NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in AllCellsInRowSelected");
03494   }
03495   return PR_TRUE;
03496 }
03497 
03498 PRBool 
03499 nsHTMLEditor::AllCellsInColumnSelected(nsIDOMElement *aTable, PRInt32 aColIndex, PRInt32 aNumberOfRows)
03500 {
03501   if (!aTable) return PR_FALSE;
03502 
03503   PRInt32 curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan;
03504   PRBool  isSelected;
03505 
03506   for( PRInt32 row = 0; row < aNumberOfRows; row += PR_MAX(actualRowSpan, 1))
03507   {
03508     nsCOMPtr<nsIDOMElement> cell;    
03509     nsresult res = GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell),
03510                                  &curStartRowIndex, &curStartColIndex,
03511                                  &rowSpan, &colSpan,
03512                                  &actualRowSpan, &actualColSpan, &isSelected);
03513     
03514     if (NS_FAILED(res)) return PR_FALSE;
03515     // If no cell, we must have a "ragged" right edge on the last column
03516     //   so return TRUE only if we already found a cell in the row
03517     if (!cell) return (row > 0) ? PR_TRUE : PR_FALSE;
03518 
03519     // Return as soon as a non-selected cell is found
03520     if (!isSelected)
03521       return PR_FALSE;
03522   }
03523   return PR_TRUE;
03524 }
03525 
03526 PRBool 
03527 nsHTMLEditor::IsEmptyCell(nsIDOMElement *aCell)
03528 {
03529   nsCOMPtr<nsIDOMNode> cellChild;
03530 
03531   // Check if target only contains empty text node or <br>
03532   nsresult res = aCell->GetFirstChild(getter_AddRefs(cellChild));
03533   if (NS_FAILED(res)) return PR_FALSE;
03534 
03535   if (cellChild)
03536   {
03537     nsCOMPtr<nsIDOMNode> nextChild;
03538     res = cellChild->GetNextSibling(getter_AddRefs(nextChild));
03539     if (NS_FAILED(res)) return PR_FALSE;
03540     if (!nextChild)
03541     {
03542       // We insert a single break into a cell by default
03543       //   to have some place to locate a cursor -- it is dispensable
03544       PRBool isEmpty = nsTextEditUtils::IsBreak(cellChild);
03545       // Or check if no real content
03546       if (!isEmpty)
03547       {
03548         res = IsEmptyNode(cellChild, &isEmpty, PR_FALSE, PR_FALSE);
03549         if (NS_FAILED(res)) return PR_FALSE;
03550       }
03551 
03552       return isEmpty;
03553     }
03554   }
03555   return PR_FALSE;
03556 }