Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLEditRules.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  *   Daniel Glazman <glazman@netscape.com>
00025  *   Neil Deakin <neil@mozdevgroup.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either of the GNU General Public License Version 2 or later (the "GPL"),
00029  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 /* build on macs with low memory */
00042 #if defined(XP_MAC) && defined(MOZ_MAC_LOWMEM)
00043 #pragma optimization_level 1
00044 #endif
00045 
00046 #include "nsHTMLEditRules.h"
00047 
00048 #include "nsEditor.h"
00049 #include "nsTextEditUtils.h"
00050 #include "nsHTMLEditUtils.h"
00051 #include "nsHTMLCSSUtils.h"
00052 #include "nsHTMLEditor.h"
00053 
00054 #include "nsIServiceManager.h"
00055 #include "nsCRT.h"
00056 #include "nsIContent.h"
00057 #include "nsIContentIterator.h"
00058 #include "nsIDOMNode.h"
00059 #include "nsIDOMText.h"
00060 #include "nsIDOMElement.h"
00061 #include "nsIDOMNodeList.h"
00062 #include "nsISelection.h"
00063 #include "nsISelectionPrivate.h"
00064 #include "nsISelectionController.h"
00065 #include "nsIDOMRange.h"
00066 #include "nsIDOMNSRange.h"
00067 #include "nsIRangeUtils.h"
00068 #include "nsIDOMCharacterData.h"
00069 #include "nsIEnumerator.h"
00070 #include "nsIPresShell.h"
00071 #include "nsIPrefBranch.h"
00072 #include "nsIPrefService.h"
00073 #include "nsIDOMNamedNodeMap.h"
00074 
00075 #include "nsEditorUtils.h"
00076 #include "nsWSRunObject.h"
00077 
00078 #include "InsertTextTxn.h"
00079 #include "DeleteTextTxn.h"
00080 #include "nsReadableUtils.h"
00081 #include "nsUnicharUtils.h"
00082 
00083 
00084 //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
00085 //const static char* kMOZEditorBogusNodeValue="TRUE";
00086 
00087 enum
00088 {
00089   kLonely = 0,
00090   kPrevSib = 1,
00091   kNextSib = 2,
00092   kBothSibs = 3
00093 };
00094 
00095 /********************************************************
00096  *  first some helpful funcotrs we will use
00097  ********************************************************/
00098 
00099 static PRBool IsBlockNode(nsIDOMNode* node)
00100 {
00101   PRBool isBlock (PR_FALSE);
00102   nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
00103   return isBlock;
00104 }
00105 
00106 static PRBool IsInlineNode(nsIDOMNode* node)
00107 {
00108   return !IsBlockNode(node);
00109 }
00110  
00111 class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor
00112 {
00113   public:
00114     virtual PRBool operator()(nsIDOMNode* aNode)  // used to build list of all li's, td's & th's iterator covers
00115     {
00116       if (nsHTMLEditUtils::IsTableCell(aNode)) return PR_TRUE;
00117       if (nsHTMLEditUtils::IsListItem(aNode)) return PR_TRUE;
00118       return PR_FALSE;
00119     }
00120 };
00121 
00122 class nsBRNodeFunctor : public nsBoolDomIterFunctor
00123 {
00124   public:
00125     virtual PRBool operator()(nsIDOMNode* aNode)  
00126     {
00127       if (nsTextEditUtils::IsBreak(aNode)) return PR_TRUE;
00128       return PR_FALSE;
00129     }
00130 };
00131 
00132 class nsEmptyFunctor : public nsBoolDomIterFunctor
00133 {
00134   public:
00135     nsEmptyFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
00136     virtual PRBool operator()(nsIDOMNode* aNode)  
00137     {
00138       if (nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCellOrCaption(aNode))
00139       {
00140         PRBool bIsEmptyNode;
00141         nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, PR_FALSE, PR_FALSE);
00142         if (NS_FAILED(res)) return PR_FALSE;
00143         if (bIsEmptyNode) 
00144           return PR_TRUE;
00145       }
00146       return PR_FALSE;
00147     }
00148   protected:
00149     nsHTMLEditor* mHTMLEditor;
00150 };
00151 
00152 class nsEditableTextFunctor : public nsBoolDomIterFunctor
00153 {
00154   public:
00155     nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
00156     virtual PRBool operator()(nsIDOMNode* aNode)  
00157     {
00158       if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode)) 
00159       {
00160         return PR_TRUE;
00161       }
00162       return PR_FALSE;
00163     }
00164   protected:
00165     nsHTMLEditor* mHTMLEditor;
00166 };
00167 
00168 
00169 /********************************************************
00170  *  routine for making new rules instance
00171  ********************************************************/
00172 
00173 nsresult
00174 NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult)
00175 {
00176   nsHTMLEditRules * rules = new nsHTMLEditRules();
00177   if (rules)
00178     return rules->QueryInterface(NS_GET_IID(nsIEditRules), (void**) aInstancePtrResult);
00179   return NS_ERROR_OUT_OF_MEMORY;
00180 }
00181 
00182 /********************************************************
00183  *  Constructor/Destructor 
00184  ********************************************************/
00185 
00186 nsHTMLEditRules::nsHTMLEditRules() : 
00187 mDocChangeRange(nsnull)
00188 ,mListenerEnabled(PR_TRUE)
00189 ,mReturnInEmptyLIKillsList(PR_TRUE)
00190 ,mDidDeleteSelection(PR_FALSE)
00191 ,mDidRangedDelete(PR_FALSE)
00192 ,mUtilRange(nsnull)
00193 ,mJoinOffset(0)
00194 {
00195   nsString emptyString;
00196   // populate mCachedStyles
00197   mCachedStyles[0] = StyleCache(nsEditProperty::b, emptyString, emptyString);
00198   mCachedStyles[1] = StyleCache(nsEditProperty::i, emptyString, emptyString);
00199   mCachedStyles[2] = StyleCache(nsEditProperty::u, emptyString, emptyString);
00200   mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), emptyString);
00201   mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), emptyString);
00202   mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), emptyString);
00203   mCachedStyles[6] = StyleCache(nsEditProperty::tt, emptyString, emptyString);
00204   mCachedStyles[7] = StyleCache(nsEditProperty::em, emptyString, emptyString);
00205   mCachedStyles[8] = StyleCache(nsEditProperty::strong, emptyString, emptyString);
00206   mCachedStyles[9] = StyleCache(nsEditProperty::dfn, emptyString, emptyString);
00207   mCachedStyles[10] = StyleCache(nsEditProperty::code, emptyString, emptyString);
00208   mCachedStyles[11] = StyleCache(nsEditProperty::samp, emptyString, emptyString);
00209   mCachedStyles[12] = StyleCache(nsEditProperty::var, emptyString, emptyString);
00210   mCachedStyles[13] = StyleCache(nsEditProperty::cite, emptyString, emptyString);
00211   mCachedStyles[14] = StyleCache(nsEditProperty::abbr, emptyString, emptyString);
00212   mCachedStyles[15] = StyleCache(nsEditProperty::acronym, emptyString, emptyString);
00213   mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, emptyString, emptyString);
00214   mCachedStyles[17] = StyleCache(nsEditProperty::sub, emptyString, emptyString);
00215   mCachedStyles[18] = StyleCache(nsEditProperty::sup, emptyString, emptyString);
00216 }
00217 
00218 nsHTMLEditRules::~nsHTMLEditRules()
00219 {
00220   // remove ourselves as a listener to edit actions
00221   // In the normal case, we have already been removed by 
00222   // ~nsHTMLEditor, in which case we will get an error here
00223   // which we ignore.  But this allows us to add the ability to
00224   // switch rule sets on the fly if we want.
00225   mHTMLEditor->RemoveEditActionListener(this);
00226 }
00227 
00228 /********************************************************
00229  *  XPCOM Cruft
00230  ********************************************************/
00231 
00232 NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules)
00233 NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules)
00234 NS_IMPL_QUERY_INTERFACE3(nsHTMLEditRules, nsIHTMLEditRules, nsIEditRules, nsIEditActionListener)
00235 
00236 
00237 /********************************************************
00238  *  Public methods 
00239  ********************************************************/
00240 
00241 NS_IMETHODIMP
00242 nsHTMLEditRules::Init(nsPlaintextEditor *aEditor, PRUint32 aFlags)
00243 {
00244   mHTMLEditor = NS_STATIC_CAST(nsHTMLEditor*, aEditor);
00245   nsresult res;
00246   
00247   // call through to base class Init 
00248   res = nsTextEditRules::Init(aEditor, aFlags);
00249   if (NS_FAILED(res)) return res;
00250 
00251   // cache any prefs we care about
00252   nsCOMPtr<nsIPrefBranch> prefBranch =
00253     do_GetService(NS_PREFSERVICE_CONTRACTID, &res);
00254   if (NS_FAILED(res)) return res;
00255 
00256   char *returnInEmptyLIKillsList = 0;
00257   res = prefBranch->GetCharPref("editor.html.typing.returnInEmptyListItemClosesList",
00258                                 &returnInEmptyLIKillsList);
00259 
00260   if (NS_SUCCEEDED(res) && returnInEmptyLIKillsList)
00261   {
00262     if (!strncmp(returnInEmptyLIKillsList, "false", 5))
00263       mReturnInEmptyLIKillsList = PR_FALSE; 
00264     else
00265       mReturnInEmptyLIKillsList = PR_TRUE; 
00266   }
00267   else
00268   {
00269     mReturnInEmptyLIKillsList = PR_TRUE; 
00270   }
00271   
00272   // make a utility range for use by the listenter
00273   mUtilRange = do_CreateInstance("@mozilla.org/content/range;1");
00274   if (!mUtilRange) return NS_ERROR_NULL_POINTER;
00275    
00276   // set up mDocChangeRange to be whole doc
00277   nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
00278   if (rootElem)
00279   {
00280     // temporarily turn off rules sniffing
00281     nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
00282     if (!mDocChangeRange)
00283     {
00284       mDocChangeRange = do_CreateInstance("@mozilla.org/content/range;1");
00285       if (!mDocChangeRange) return NS_ERROR_NULL_POINTER;
00286     }
00287     mDocChangeRange->SelectNode(rootElem);
00288     res = AdjustSpecialBreaks();
00289     if (NS_FAILED(res)) return res;
00290   }
00291 
00292   // add ourselves as a listener to edit actions
00293   res = mHTMLEditor->AddEditActionListener(this);
00294 
00295   return res;
00296 }
00297 
00298 
00299 NS_IMETHODIMP
00300 nsHTMLEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection)
00301 {
00302   if (mLockRulesSniffing) return NS_OK;
00303   
00304   nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
00305   mDidExplicitlySetInterline = PR_FALSE;
00306 
00307   if (!mActionNesting)
00308   {
00309     // clear our flag about if just deleted a range
00310     mDidRangedDelete = PR_FALSE;
00311     
00312     // remember where our selection was before edit action took place:
00313     
00314     // get selection
00315     nsCOMPtr<nsISelection>selection;
00316     nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
00317     if (NS_FAILED(res)) return res;
00318   
00319     // get the selection start location
00320     nsCOMPtr<nsIDOMNode> selStartNode, selEndNode;
00321     PRInt32 selOffset;
00322     res = mHTMLEditor->GetStartNodeAndOffset(selection, address_of(selStartNode), &selOffset);
00323     if (NS_FAILED(res)) return res;
00324     mRangeItem.startNode = selStartNode;
00325     mRangeItem.startOffset = selOffset;
00326 
00327     // get the selection end location
00328     res = mHTMLEditor->GetEndNodeAndOffset(selection, address_of(selEndNode), &selOffset);
00329     if (NS_FAILED(res)) return res;
00330     mRangeItem.endNode = selEndNode;
00331     mRangeItem.endOffset = selOffset;
00332 
00333     // register this range with range updater to track this as we perturb the doc
00334     (mHTMLEditor->mRangeUpdater).RegisterRangeItem(&mRangeItem);
00335 
00336     // clear deletion state bool
00337     mDidDeleteSelection = PR_FALSE;
00338     
00339     // clear out mDocChangeRange and mUtilRange
00340     nsCOMPtr<nsIDOMNSRange> nsrange;
00341     if(mDocChangeRange)
00342     {
00343       nsrange = do_QueryInterface(mDocChangeRange);
00344       if (!nsrange)
00345         return NS_ERROR_FAILURE;
00346       nsrange->NSDetach();  // clear out our accounting of what changed
00347     }
00348     if(mUtilRange)
00349     {
00350       nsrange = do_QueryInterface(mUtilRange);
00351       if (!nsrange)
00352         return NS_ERROR_FAILURE;
00353       nsrange->NSDetach();  // ditto for mUtilRange.  
00354     }
00355 
00356     // remember current inline styles for deletion and normal insertion operations
00357     if ((action == nsEditor::kOpInsertText)      || 
00358         (action == nsEditor::kOpInsertIMEText)   ||
00359         (action == nsEditor::kOpDeleteSelection) ||
00360         (action == nsEditor::kOpInsertBreak))
00361     {
00362       nsCOMPtr<nsIDOMNode> selNode = selStartNode;
00363       if (aDirection == nsIEditor::eNext)
00364         selNode = selEndNode;
00365       res = CacheInlineStyles(selNode);
00366       if (NS_FAILED(res)) return res;
00367     }
00368     
00369     // check that selection is in subtree defined by body node
00370     ConfirmSelectionInBody();
00371     // let rules remember the top level action
00372     mTheAction = action;
00373   }
00374   mActionNesting++;
00375   return NS_OK;
00376 }
00377 
00378 
00379 NS_IMETHODIMP
00380 nsHTMLEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
00381 {
00382   if (mLockRulesSniffing) return NS_OK;
00383 
00384   nsAutoLockRulesSniffing lockIt(this);
00385 
00386   NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
00387   nsresult res = NS_OK;
00388   if (!--mActionNesting)
00389   {
00390     // do all the tricky stuff
00391     res = AfterEditInner(action, aDirection);
00392 
00393     // free up selectionState range item
00394     (mHTMLEditor->mRangeUpdater).DropRangeItem(&mRangeItem);
00395 
00396     /* After inserting text the cursor Bidi level must be set to the level of the inserted text.
00397      * This is difficult, because we cannot know what the level is until after the Bidi algorithm
00398      * is applied to the whole paragraph.
00399      *
00400      * So we set the cursor Bidi level to UNDEFINED here, and the caret code will set it correctly later
00401      */
00402     if (action == nsEditor::kOpInsertText
00403         || action == nsEditor::kOpInsertIMEText) {
00404       nsCOMPtr<nsIPresShell> shell;
00405       mEditor->GetPresShell(getter_AddRefs(shell));
00406       if (shell) {
00407         shell->UndefineCaretBidiLevel();
00408       }
00409     }
00410   }
00411 
00412   return res;
00413 }
00414 
00415 
00416 nsresult
00417 nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection)
00418 {
00419   ConfirmSelectionInBody();
00420   if (action == nsEditor::kOpIgnore) return NS_OK;
00421   
00422   nsCOMPtr<nsISelection>selection;
00423   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
00424   if (NS_FAILED(res)) return res;
00425   
00426   nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent;
00427   PRInt32 rangeStartOffset = 0, rangeEndOffset = 0;
00428   // do we have a real range to act on?
00429   PRBool bDamagedRange = PR_FALSE;  
00430   if (mDocChangeRange)
00431   {  
00432     mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent));
00433     mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent));
00434     mDocChangeRange->GetStartOffset(&rangeStartOffset);
00435     mDocChangeRange->GetEndOffset(&rangeEndOffset);
00436     if (rangeStartParent && rangeEndParent) 
00437       bDamagedRange = PR_TRUE; 
00438   }
00439   
00440   if (bDamagedRange && !((action == nsEditor::kOpUndo) || (action == nsEditor::kOpRedo)))
00441   {
00442     // dont let any txns in here move the selection around behind our back.
00443     // Note that this won't prevent explicit selection setting from working.
00444     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
00445    
00446     // expand the "changed doc range" as needed
00447     res = PromoteRange(mDocChangeRange, action);
00448     if (NS_FAILED(res)) return res;
00449 
00450     // if we did a ranged deletion, make sure we have a place to put caret.
00451     // Note we only want to do this if the overall operation was deletion,
00452     // not if deletion was done along the way for kOpLoadHTML, kOpInsertText, etc.
00453     // That's why this is here rather than DidDeleteSelection().
00454     if ((action == nsEditor::kOpDeleteSelection) && mDidRangedDelete)
00455     {
00456       res = InsertBRIfNeeded(selection);
00457       if (NS_FAILED(res)) return res;
00458     }  
00459     
00460     // add in any needed <br>s, and remove any unneeded ones.
00461     res = AdjustSpecialBreaks();
00462     if (NS_FAILED(res)) return res;
00463     
00464     // merge any adjacent text nodes
00465     if ( (action != nsEditor::kOpInsertText &&
00466          action != nsEditor::kOpInsertIMEText) )
00467     {
00468       res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
00469       if (NS_FAILED(res)) return res;
00470     }
00471     
00472     // replace newlines with breaks.
00473     // MOOSE:  This is buttUgly.  A better way to 
00474     // organize the action enum is in order.
00475     if (// (action == nsEditor::kOpInsertText) || 
00476         // (action == nsEditor::kOpInsertIMEText) ||
00477         (action == nsHTMLEditor::kOpInsertElement) ||
00478         (action == nsHTMLEditor::kOpInsertQuotation) ||
00479         (action == nsEditor::kOpInsertNode) ||
00480         (action == nsHTMLEditor::kOpHTMLPaste ||
00481         (action == nsHTMLEditor::kOpLoadHTML)))
00482     {
00483       res = ReplaceNewlines(mDocChangeRange);
00484       if (NS_FAILED(res)) return res;
00485     }
00486     
00487     // clean up any empty nodes in the selection
00488     res = RemoveEmptyNodes();
00489     if (NS_FAILED(res)) return res;
00490 
00491     // attempt to transform any unneeded nbsp's into spaces after doing various operations
00492     if ((action == nsEditor::kOpInsertText) || 
00493         (action == nsEditor::kOpInsertIMEText) ||
00494         (action == nsEditor::kOpDeleteSelection) ||
00495         (action == nsEditor::kOpInsertBreak) || 
00496         (action == nsHTMLEditor::kOpHTMLPaste ||
00497         (action == nsHTMLEditor::kOpLoadHTML)))
00498     {
00499       res = AdjustWhitespace(selection);
00500       if (NS_FAILED(res)) return res;
00501       
00502       // also do this for original selection endpoints. 
00503       nsWSRunObject(mHTMLEditor, mRangeItem.startNode, mRangeItem.startOffset).AdjustWhitespace();
00504       // we only need to handle old selection endpoint if it was different from start
00505       if ((mRangeItem.startNode != mRangeItem.endNode) || (mRangeItem.startOffset != mRangeItem.endOffset))
00506       {
00507         nsWSRunObject(mHTMLEditor, mRangeItem.endNode, mRangeItem.endOffset).AdjustWhitespace();
00508       }
00509     }
00510     
00511     // if we created a new block, make sure selection lands in it
00512     if (mNewBlock)
00513     {
00514       res = PinSelectionToNewBlock(selection);
00515       mNewBlock = 0;
00516     }
00517 
00518     // adjust selection for insert text, html paste, and delete actions
00519     if ((action == nsEditor::kOpInsertText) || 
00520         (action == nsEditor::kOpInsertIMEText) ||
00521         (action == nsEditor::kOpDeleteSelection) ||
00522         (action == nsEditor::kOpInsertBreak) || 
00523         (action == nsHTMLEditor::kOpHTMLPaste ||
00524         (action == nsHTMLEditor::kOpLoadHTML)))
00525     {
00526       res = AdjustSelection(selection, aDirection);
00527       if (NS_FAILED(res)) return res;
00528     }
00529 
00530     // check for any styles which were removed inappropriately
00531     if ((action == nsEditor::kOpInsertText)      || 
00532         (action == nsEditor::kOpInsertIMEText)   ||
00533         (action == nsEditor::kOpDeleteSelection) ||
00534         (action == nsEditor::kOpInsertBreak))
00535     {
00536       mHTMLEditor->mTypeInState->UpdateSelState(selection);
00537       res = ReapplyCachedStyles();
00538       if (NS_FAILED(res)) return res;
00539       res = ClearCachedStyles();
00540       if (NS_FAILED(res)) return res;
00541     }    
00542   }
00543 
00544   res = mHTMLEditor->HandleInlineSpellCheck(action, selection, 
00545                                             mRangeItem.startNode, mRangeItem.startOffset,
00546                                             rangeStartParent, rangeStartOffset,
00547                                             rangeEndParent, rangeEndOffset);
00548   if (NS_FAILED(res)) 
00549     return res;
00550 
00551   // detect empty doc
00552   res = CreateBogusNodeIfNeeded(selection);
00553   
00554   // adjust selection HINT if needed
00555   if (NS_FAILED(res)) 
00556     return res;
00557   
00558   if (!mDidExplicitlySetInterline)
00559   {
00560     res = CheckInterlinePosition(selection);
00561   }
00562   
00563   return res;
00564 }
00565 
00566 
00567 NS_IMETHODIMP 
00568 nsHTMLEditRules::WillDoAction(nsISelection *aSelection, 
00569                               nsRulesInfo *aInfo, 
00570                               PRBool *aCancel, 
00571                               PRBool *aHandled)
00572 {
00573   if (!aInfo || !aCancel || !aHandled) 
00574     return NS_ERROR_NULL_POINTER;
00575 #if defined(DEBUG_ftang)
00576   printf("nsHTMLEditRules::WillDoAction action = %d\n", aInfo->action);
00577 #endif
00578 
00579   *aCancel = PR_FALSE;
00580   *aHandled = PR_FALSE;
00581     
00582   // my kingdom for dynamic cast
00583   nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
00584     
00585   switch (info->action)
00586   {
00587     case kInsertText:
00588     case kInsertTextIME:
00589       return WillInsertText(info->action,
00590                             aSelection, 
00591                             aCancel, 
00592                             aHandled,
00593                             info->inString,
00594                             info->outString,
00595                             info->maxLength);
00596     case kLoadHTML:
00597       return WillLoadHTML(aSelection, aCancel);
00598     case kInsertBreak:
00599       return WillInsertBreak(aSelection, aCancel, aHandled);
00600     case kDeleteSelection:
00601       return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
00602     case kMakeList:
00603       return WillMakeList(aSelection, info->blockType, info->entireList, info->bulletType, aCancel, aHandled);
00604     case kIndent:
00605       return WillIndent(aSelection, aCancel, aHandled);
00606     case kOutdent:
00607       return WillOutdent(aSelection, aCancel, aHandled);
00608     case kSetAbsolutePosition:
00609       return WillAbsolutePosition(aSelection, aCancel, aHandled);
00610     case kRemoveAbsolutePosition:
00611       return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
00612     case kAlign:
00613       return WillAlign(aSelection, info->alignType, aCancel, aHandled);
00614     case kMakeBasicBlock:
00615       return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
00616     case kRemoveList:
00617       return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
00618     case kMakeDefListItem:
00619       return WillMakeDefListItem(aSelection, info->blockType, info->entireList, aCancel, aHandled);
00620     case kInsertElement:
00621       return WillInsert(aSelection, aCancel);
00622     case kDecreaseZIndex:
00623       return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
00624     case kIncreaseZIndex:
00625       return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
00626   }
00627   return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
00628 }
00629   
00630   
00631 NS_IMETHODIMP 
00632 nsHTMLEditRules::DidDoAction(nsISelection *aSelection,
00633                              nsRulesInfo *aInfo, nsresult aResult)
00634 {
00635   nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
00636   switch (info->action)
00637   {
00638     case kInsertBreak:
00639       return DidInsertBreak(aSelection, aResult);
00640     case kDeleteSelection:
00641       return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
00642     case kMakeBasicBlock:
00643     case kIndent:
00644     case kOutdent:
00645     case kAlign:
00646       return DidMakeBasicBlock(aSelection, aInfo, aResult);
00647     case kSetAbsolutePosition: {
00648       nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
00649       if (NS_FAILED(rv)) return rv;
00650       return DidAbsolutePosition();
00651       }
00652   }
00653   
00654   // default: pass thru to nsTextEditRules
00655   return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
00656 }
00657   
00658 /********************************************************
00659  *  nsIHTMLEditRules methods
00660  ********************************************************/
00661 
00662 NS_IMETHODIMP 
00663 nsHTMLEditRules::GetListState(PRBool *aMixed, PRBool *aOL, PRBool *aUL, PRBool *aDL)
00664 {
00665   if (!aMixed || !aOL || !aUL || !aDL)
00666     return NS_ERROR_NULL_POINTER;
00667   *aMixed = PR_FALSE;
00668   *aOL = PR_FALSE;
00669   *aUL = PR_FALSE;
00670   *aDL = PR_FALSE;
00671   PRBool bNonList = PR_FALSE;
00672   
00673   nsCOMArray<nsIDOMNode> arrayOfNodes;
00674   nsresult res = GetListActionNodes(arrayOfNodes, PR_FALSE, PR_TRUE);
00675   if (NS_FAILED(res)) return res;
00676 
00677   // examine list type for nodes in selection
00678   PRInt32 listCount = arrayOfNodes.Count();
00679   PRInt32 i;
00680   for (i=listCount-1; i>=0; i--)
00681   {
00682     nsIDOMNode* curNode = arrayOfNodes[i];
00683     
00684     if (nsHTMLEditUtils::IsUnorderedList(curNode))
00685       *aUL = PR_TRUE;
00686     else if (nsHTMLEditUtils::IsOrderedList(curNode))
00687       *aOL = PR_TRUE;
00688     else if (nsEditor::NodeIsType(curNode, nsEditProperty::li))
00689     {
00690       nsCOMPtr<nsIDOMNode> parent;
00691       PRInt32 offset;
00692       res = nsEditor::GetNodeLocation(curNode, address_of(parent), &offset);
00693       if (NS_FAILED(res)) return res;
00694       if (nsHTMLEditUtils::IsUnorderedList(parent))
00695         *aUL = PR_TRUE;
00696       else if (nsHTMLEditUtils::IsOrderedList(parent))
00697         *aOL = PR_TRUE;
00698     }
00699     else if (nsEditor::NodeIsType(curNode, nsEditProperty::dl) ||
00700              nsEditor::NodeIsType(curNode, nsEditProperty::dt) ||
00701              nsEditor::NodeIsType(curNode, nsEditProperty::dd) )
00702     {
00703       *aDL = PR_TRUE;
00704     }
00705     else bNonList = PR_TRUE;
00706   }  
00707   
00708   // hokey arithmetic with booleans
00709   if ( (*aUL + *aOL + *aDL + bNonList) > 1) *aMixed = PR_TRUE;
00710   
00711   return res;
00712 }
00713 
00714 NS_IMETHODIMP 
00715 nsHTMLEditRules::GetListItemState(PRBool *aMixed, PRBool *aLI, PRBool *aDT, PRBool *aDD)
00716 {
00717   if (!aMixed || !aLI || !aDT || !aDD)
00718     return NS_ERROR_NULL_POINTER;
00719   *aMixed = PR_FALSE;
00720   *aLI = PR_FALSE;
00721   *aDT = PR_FALSE;
00722   *aDD = PR_FALSE;
00723   PRBool bNonList = PR_FALSE;
00724   
00725   nsCOMArray<nsIDOMNode> arrayOfNodes;
00726   nsresult res = GetListActionNodes(arrayOfNodes, PR_FALSE, PR_TRUE);
00727   if (NS_FAILED(res)) return res;
00728 
00729   // examine list type for nodes in selection
00730   PRInt32 listCount = arrayOfNodes.Count();
00731   PRInt32 i;
00732   for (i = listCount-1; i>=0; i--)
00733   {
00734     nsIDOMNode* curNode = arrayOfNodes[i];
00735     
00736     if (nsHTMLEditUtils::IsUnorderedList(curNode) ||
00737         nsHTMLEditUtils::IsOrderedList(curNode) ||
00738         nsEditor::NodeIsType(curNode, nsEditProperty::li) )
00739     {
00740       *aLI = PR_TRUE;
00741     }
00742     else if (nsEditor::NodeIsType(curNode, nsEditProperty::dt))
00743     {
00744       *aDT = PR_TRUE;
00745     }
00746     else if (nsEditor::NodeIsType(curNode, nsEditProperty::dd))
00747     {
00748       *aDD = PR_TRUE;
00749     }
00750     else if (nsEditor::NodeIsType(curNode, nsEditProperty::dl))
00751     {
00752       // need to look inside dl and see which types of items it has
00753       PRBool bDT, bDD;
00754       res = GetDefinitionListItemTypes(curNode, bDT, bDD);
00755       if (NS_FAILED(res)) return res;
00756       *aDT |= bDT;
00757       *aDD |= bDD;
00758     }
00759     else bNonList = PR_TRUE;
00760   }  
00761   
00762   // hokey arithmetic with booleans
00763   if ( (*aDT + *aDD + bNonList) > 1) *aMixed = PR_TRUE;
00764   
00765   return res;
00766 }
00767 
00768 NS_IMETHODIMP 
00769 nsHTMLEditRules::GetAlignment(PRBool *aMixed, nsIHTMLEditor::EAlignment *aAlign)
00770 {
00771   // for now, just return first alignment.  we'll lie about
00772   // if it's mixed.  This is for efficiency
00773   // given that our current ui doesn't care if it's mixed.
00774   // cmanske: NOT TRUE! We would like to pay attention to mixed state
00775   //  in Format | Align submenu!
00776 
00777   // this routine assumes that alignment is done ONLY via divs
00778 
00779   // default alignment is left
00780   if (!aMixed || !aAlign)
00781     return NS_ERROR_NULL_POINTER;
00782   *aMixed = PR_FALSE;
00783   *aAlign = nsIHTMLEditor::eLeft;
00784 
00785   // get selection
00786   nsCOMPtr<nsISelection>selection;
00787   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
00788   if (NS_FAILED(res)) return res;
00789 
00790   // get selection location
00791   nsCOMPtr<nsIDOMNode> parent;
00792   nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
00793   if (!rootElem)
00794     return NS_ERROR_FAILURE;
00795 
00796   PRInt32 offset, rootOffset;
00797   res = nsEditor::GetNodeLocation(rootElem, address_of(parent), &rootOffset);
00798   if (NS_FAILED(res)) return res;
00799   res = mHTMLEditor->GetStartNodeAndOffset(selection, address_of(parent), &offset);
00800   if (NS_FAILED(res)) return res;
00801 
00802   // is the selection collapsed?
00803   PRBool bCollapsed;
00804   res = selection->GetIsCollapsed(&bCollapsed);
00805   if (NS_FAILED(res)) return res;
00806   nsCOMPtr<nsIDOMNode> nodeToExamine;
00807   nsCOMPtr<nsISupports> isupports;
00808   if (bCollapsed)
00809   {
00810     // if it is, we want to look at 'parent' and it's ancestors
00811     // for divs with alignment on them
00812     nodeToExamine = parent;
00813   }
00814   else if (mHTMLEditor->IsTextNode(parent)) 
00815   {
00816     // if we are in a text node, then that is the node of interest
00817     nodeToExamine = parent;
00818   }
00819   else if (nsEditor::NodeIsType(parent, nsEditProperty::html) &&
00820            offset == rootOffset)
00821   {
00822     // if we have selected the body, let's look at the first editable node
00823     mHTMLEditor->GetNextNode(parent, offset, PR_TRUE, address_of(nodeToExamine));
00824   }
00825   else
00826   {
00827     nsCOMArray<nsIDOMRange> arrayOfRanges;
00828     res = GetPromotedRanges(selection, arrayOfRanges, kAlign);
00829     if (NS_FAILED(res)) return res;
00830 
00831     // use these ranges to construct a list of nodes to act on.
00832     nsCOMArray<nsIDOMNode> arrayOfNodes;
00833     res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, kAlign, PR_TRUE);
00834     if (NS_FAILED(res)) return res;                                 
00835     nodeToExamine = arrayOfNodes.SafeObjectAt(0);
00836   }
00837 
00838   if (!nodeToExamine) return NS_ERROR_NULL_POINTER;
00839 
00840   PRBool useCSS;
00841   mHTMLEditor->GetIsCSSEnabled(&useCSS);
00842   NS_NAMED_LITERAL_STRING(typeAttrName, "align");
00843   nsIAtom  *dummyProperty = nsnull;
00844   nsCOMPtr<nsIDOMNode> blockParent;
00845   if (mHTMLEditor->IsBlockNode(nodeToExamine))
00846     blockParent = nodeToExamine;
00847   else
00848     blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine);
00849 
00850   if (!blockParent) return NS_ERROR_FAILURE;
00851 
00852   if (useCSS)
00853   {
00854     nsCOMPtr<nsIContent> blockParentContent = do_QueryInterface(blockParent);
00855     if (blockParentContent && 
00856         mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParent, dummyProperty, &typeAttrName))
00857     {
00858       // we are in CSS mode and we know how to align this element with CSS
00859       nsAutoString value;
00860       // let's get the value(s) of text-align or margin-left/margin-right
00861       mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet(blockParent,
00862                                                      dummyProperty,
00863                                                      &typeAttrName,
00864                                                      value,
00865                                                      COMPUTED_STYLE_TYPE);
00866       if (value.EqualsLiteral("center") ||
00867           value.EqualsLiteral("-moz-center") ||
00868           value.EqualsLiteral("auto auto"))
00869       {
00870         *aAlign = nsIHTMLEditor::eCenter;
00871         return NS_OK;
00872       }
00873       if (value.EqualsLiteral("right") ||
00874           value.EqualsLiteral("-moz-right") ||
00875           value.EqualsLiteral("auto 0px"))
00876       {
00877         *aAlign = nsIHTMLEditor::eRight;
00878         return NS_OK;
00879       }
00880       if (value.EqualsLiteral("justify"))
00881       {
00882         *aAlign = nsIHTMLEditor::eJustify;
00883         return NS_OK;
00884       }
00885       *aAlign = nsIHTMLEditor::eLeft;
00886       return NS_OK;
00887     }
00888   }
00889 
00890   // check up the ladder for divs with alignment
00891   nsCOMPtr<nsIDOMNode> temp = nodeToExamine;
00892   PRBool isFirstNodeToExamine = PR_TRUE;
00893   while (nodeToExamine)
00894   {
00895     if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine))
00896     {
00897       // the node to examine is a table and this is not the first node
00898       // we examine; let's break here to materialize the 'inline-block'
00899       // behaviour of html tables regarding to text alignment
00900       return NS_OK;
00901     }
00902     if (nsHTMLEditUtils::SupportsAlignAttr(nodeToExamine))
00903     {
00904       // check for alignment
00905       nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(nodeToExamine);
00906       if (elem)
00907       {
00908         nsAutoString typeAttrVal;
00909         res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal);
00910         ToLowerCase(typeAttrVal);
00911         if (NS_SUCCEEDED(res) && typeAttrVal.Length())
00912         {
00913           if (typeAttrVal.EqualsLiteral("center"))
00914             *aAlign = nsIHTMLEditor::eCenter;
00915           else if (typeAttrVal.EqualsLiteral("right"))
00916             *aAlign = nsIHTMLEditor::eRight;
00917           else if (typeAttrVal.EqualsLiteral("justify"))
00918             *aAlign = nsIHTMLEditor::eJustify;
00919           else
00920             *aAlign = nsIHTMLEditor::eLeft;
00921           return res;
00922         }
00923       }
00924     }
00925     isFirstNodeToExamine = PR_FALSE;
00926     res = nodeToExamine->GetParentNode(getter_AddRefs(temp));
00927     if (NS_FAILED(res)) temp = nsnull;
00928     nodeToExamine = temp; 
00929   }
00930   return NS_OK;
00931 }
00932 
00933 nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) {
00934   nsAutoString direction;
00935   aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction);
00936   return direction.EqualsLiteral("rtl") ?
00937     nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft;
00938 }
00939 
00940 NS_IMETHODIMP 
00941 nsHTMLEditRules::GetIndentState(PRBool *aCanIndent, PRBool *aCanOutdent)
00942 {
00943   if (!aCanIndent || !aCanOutdent)
00944     return NS_ERROR_FAILURE;
00945   *aCanIndent = PR_TRUE;    
00946   *aCanOutdent = PR_FALSE;
00947 
00948   // get selection
00949   nsCOMPtr<nsISelection>selection;
00950   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
00951   if (NS_FAILED(res)) return res;
00952   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00953   if (!selPriv)
00954     return NS_ERROR_FAILURE;
00955 
00956   // contruct a list of nodes to act on.
00957   nsCOMArray<nsIDOMNode> arrayOfNodes;
00958   res = GetNodesFromSelection(selection, kIndent, arrayOfNodes, PR_TRUE);
00959   if (NS_FAILED(res)) return res;
00960 
00961   // examine nodes in selection for blockquotes or list elements;
00962   // these we can outdent.  Note that we return true for canOutdent
00963   // if *any* of the selection is outdentable, rather than all of it.
00964   PRInt32 listCount = arrayOfNodes.Count();
00965   PRInt32 i;
00966   PRBool useCSS;
00967   mHTMLEditor->GetIsCSSEnabled(&useCSS);
00968   for (i=listCount-1; i>=0; i--)
00969   {
00970     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
00971     
00972     if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode))
00973     {
00974       *aCanOutdent = PR_TRUE;
00975       break;
00976     }
00977     else if (useCSS) {
00978       // we are in CSS mode, indentation is done using the margin-left (or margin-right) property
00979       nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
00980       nsAutoString value;
00981       // retrieve its specified value
00982       mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value);
00983       float f;
00984       nsCOMPtr<nsIAtom> unit;
00985       // get its number part and its unit
00986       mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
00987       // if the number part is strictly positive, outdent is possible
00988       if (0 < f) {
00989         *aCanOutdent = PR_TRUE;
00990         break;
00991       }
00992     }
00993   }  
00994   
00995   if (!*aCanOutdent)
00996   {
00997     // if we haven't found something to outdent yet, also check the parents
00998     // of selection endpoints.  We might have a blockquote or list item 
00999     // in the parent heirarchy.
01000     
01001     // gather up info we need for test
01002     nsCOMPtr<nsIDOMNode> parent, tmp, root;
01003     nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
01004     if (!rootElem) return NS_ERROR_NULL_POINTER;
01005     nsCOMPtr<nsISelection> selection;
01006     PRInt32 selOffset;
01007     root = do_QueryInterface(rootElem);
01008     if (!root) return NS_ERROR_NO_INTERFACE;
01009     res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
01010     if (NS_FAILED(res)) return res;
01011     if (!selection) return NS_ERROR_NULL_POINTER;
01012     
01013     // test start parent hierarchy
01014     res = mHTMLEditor->GetStartNodeAndOffset(selection, address_of(parent), &selOffset);
01015     if (NS_FAILED(res)) return res;
01016     while (parent && (parent!=root))
01017     {
01018       if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
01019       {
01020         *aCanOutdent = PR_TRUE;
01021         break;
01022       }
01023       tmp=parent;
01024       tmp->GetParentNode(getter_AddRefs(parent));
01025     }
01026 
01027     // test end parent hierarchy
01028     res = mHTMLEditor->GetEndNodeAndOffset(selection, address_of(parent), &selOffset);
01029     if (NS_FAILED(res)) return res;
01030     while (parent && (parent!=root))
01031     {
01032       if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
01033       {
01034         *aCanOutdent = PR_TRUE;
01035         break;
01036       }
01037       tmp=parent;
01038       tmp->GetParentNode(getter_AddRefs(parent));
01039     }
01040   }
01041   return res;
01042 }
01043 
01044 
01045 NS_IMETHODIMP 
01046 nsHTMLEditRules::GetParagraphState(PRBool *aMixed, nsAString &outFormat)
01047 {
01048   // This routine is *heavily* tied to our ui choices in the paragraph
01049   // style popup.  I cant see a way around that.
01050   if (!aMixed)
01051     return NS_ERROR_NULL_POINTER;
01052   *aMixed = PR_TRUE;
01053   outFormat.Truncate(0);
01054   
01055   PRBool bMixed = PR_FALSE;
01056   // using "x" as an uninitialized value, since "" is meaningful
01057   nsAutoString formatStr(NS_LITERAL_STRING("x")); 
01058   
01059   nsCOMArray<nsIDOMNode> arrayOfNodes;
01060   nsresult res = GetParagraphFormatNodes(arrayOfNodes, PR_TRUE);
01061   if (NS_FAILED(res)) return res;
01062 
01063   // post process list.  We need to replace any block nodes that are not format
01064   // nodes with their content.  This is so we only have to look "up" the heirarchy
01065   // to find format nodes, instead of both up and down.
01066   PRInt32 listCount = arrayOfNodes.Count();
01067   PRInt32 i;
01068   for (i=listCount-1; i>=0; i--)
01069   {
01070     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
01071     nsAutoString format;
01072     // if it is a known format node we have it easy
01073     if (IsBlockNode(curNode) && !nsHTMLEditUtils::IsFormatNode(curNode))
01074     {
01075       // arrayOfNodes.RemoveObject(curNode);
01076       res = AppendInnerFormatNodes(arrayOfNodes, curNode);
01077       if (NS_FAILED(res)) return res;
01078     }
01079   }
01080   
01081   // we might have an empty node list.  if so, find selection parent
01082   // and put that on the list
01083   listCount = arrayOfNodes.Count();
01084   if (!listCount)
01085   {
01086     nsCOMPtr<nsIDOMNode> selNode;
01087     PRInt32 selOffset;
01088     nsCOMPtr<nsISelection>selection;
01089     res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
01090     if (NS_FAILED(res)) return res;
01091     res = mHTMLEditor->GetStartNodeAndOffset(selection, address_of(selNode), &selOffset);
01092     if (NS_FAILED(res)) return res;
01093     if (!selNode) return NS_ERROR_NULL_POINTER;
01094     arrayOfNodes.AppendObject(selNode);
01095     listCount = 1;
01096   }
01097 
01098   // remember root node
01099   nsIDOMElement *rootElem = mHTMLEditor->GetRoot();
01100   if (!rootElem) return NS_ERROR_NULL_POINTER;
01101 
01102   // loop through the nodes in selection and examine their paragraph format
01103   for (i=listCount-1; i>=0; i--)
01104   {
01105     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
01106     nsAutoString format;
01107     // if it is a known format node we have it easy
01108     if (nsHTMLEditUtils::IsFormatNode(curNode))
01109       GetFormatString(curNode, format);
01110     else if (IsBlockNode(curNode))
01111     {
01112       // this is a div or some other non-format block.
01113       // we should ignore it.  It's children were appended to this list
01114       // by AppendInnerFormatNodes() call above.  We will get needed
01115       // info when we examine them instead.
01116       continue;
01117     }
01118     else
01119     {
01120       nsCOMPtr<nsIDOMNode> node, tmp = curNode;
01121       tmp->GetParentNode(getter_AddRefs(node));
01122       while (node)
01123       {
01124         if (node == rootElem)
01125         {
01126           format.Truncate(0);
01127           break;
01128         }
01129         else if (nsHTMLEditUtils::IsFormatNode(node))
01130         {
01131           GetFormatString(node, format);
01132           break;
01133         }
01134         // else keep looking up
01135         tmp = node;
01136         tmp->GetParentNode(getter_AddRefs(node));
01137       }
01138     }
01139     
01140     // if this is the first node, we've found, remember it as the format
01141     if (formatStr.EqualsLiteral("x"))
01142       formatStr = format;
01143     // else make sure it matches previously found format
01144     else if (format != formatStr) 
01145     {
01146       bMixed = PR_TRUE;
01147       break; 
01148     }
01149   }  
01150   
01151   *aMixed = bMixed;
01152   outFormat = formatStr;
01153   return res;
01154 }
01155 
01156 nsresult 
01157 nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray,
01158                                         nsIDOMNode *aNode)
01159 {
01160   if (!aNode) return NS_ERROR_NULL_POINTER;
01161 
01162   nsCOMPtr<nsIDOMNodeList> childList;
01163   nsCOMPtr<nsIDOMNode> child;
01164 
01165   aNode->GetChildNodes(getter_AddRefs(childList));
01166   if (!childList)  return NS_OK;
01167   PRUint32 len, j=0;
01168   childList->GetLength(&len);
01169 
01170   // we only need to place any one inline inside this node onto 
01171   // the list.  They are all the same for purposes of determining
01172   // paragraph style.  We use foundInline to track this as we are 
01173   // going through the children in the loop below.
01174   PRBool foundInline = PR_FALSE;
01175   while (j < len)
01176   {
01177     childList->Item(j, getter_AddRefs(child));
01178     PRBool isBlock = IsBlockNode(child);
01179     PRBool isFormat = nsHTMLEditUtils::IsFormatNode(child);
01180     if (isBlock && !isFormat)  // if it's a div, etc, recurse
01181       AppendInnerFormatNodes(aArray, child);
01182     else if (isFormat)
01183     {
01184       aArray.AppendObject(child);
01185     }
01186     else if (!foundInline)  // if this is the first inline we've found, use it
01187     {
01188       foundInline = PR_TRUE;      
01189       aArray.AppendObject(child);
01190     }
01191     j++;
01192   }
01193   return NS_OK;
01194 }
01195 
01196 nsresult 
01197 nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat)
01198 {
01199   if (!aNode) return NS_ERROR_NULL_POINTER;
01200 
01201   if (nsHTMLEditUtils::IsFormatNode(aNode))
01202   {
01203     nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aNode);
01204     atom->ToString(outFormat);
01205   }
01206   else
01207     outFormat.Truncate();
01208 
01209   return NS_OK;
01210 }    
01211 
01212 /********************************************************
01213  *  Protected rules methods 
01214  ********************************************************/
01215 
01216 nsresult
01217 nsHTMLEditRules::WillInsert(nsISelection *aSelection, PRBool *aCancel)
01218 {
01219   nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel);
01220   if (NS_FAILED(res)) return res; 
01221   
01222   // Adjust selection to prevent insertion after a moz-BR.
01223   // this next only works for collapsed selections right now,
01224   // because selection is a pain to work with when not collapsed.
01225   // (no good way to extend start or end of selection)
01226   PRBool bCollapsed;
01227   res = aSelection->GetIsCollapsed(&bCollapsed);
01228   if (NS_FAILED(res)) return res;
01229   if (!bCollapsed) return NS_OK;
01230 
01231   // if we are after a mozBR in the same block, then move selection
01232   // to be before it
01233   nsCOMPtr<nsIDOMNode> selNode, priorNode;
01234   PRInt32 selOffset;
01235   // get the (collapsed) selection location
01236   res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode),
01237                                            &selOffset);
01238   if (NS_FAILED(res)) return res;
01239   // get prior node
01240   res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset,
01241                                       address_of(priorNode));
01242   if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode))
01243   {
01244     nsCOMPtr<nsIDOMNode> block1, block2;
01245     if (IsBlockNode(selNode)) block1 = selNode;
01246     else block1 = mHTMLEditor->GetBlockNodeParent(selNode);
01247     block2 = mHTMLEditor->GetBlockNodeParent(priorNode);
01248   
01249     if (block1 == block2)
01250     {
01251       // if we are here then the selection is right after a mozBR
01252       // that is in the same block as the selection.  We need to move
01253       // the selection start to be before the mozBR.
01254       res = nsEditor::GetNodeLocation(priorNode, address_of(selNode), &selOffset);
01255       if (NS_FAILED(res)) return res;
01256       res = aSelection->Collapse(selNode,selOffset);
01257       if (NS_FAILED(res)) return res;
01258     }
01259   }
01260 
01261   // we need to get the doc
01262   nsCOMPtr<nsIDOMDocument>doc;
01263   res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
01264   if (NS_FAILED(res)) return res;
01265   if (!doc) return NS_ERROR_NULL_POINTER;
01266     
01267   // for every property that is set, insert a new inline style node
01268   return CreateStyleForInsertText(aSelection, doc);
01269 }    
01270 
01271 #ifdef XXX_DEAD_CODE
01272 nsresult
01273 nsHTMLEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
01274 {
01275   return nsTextEditRules::DidInsert(aSelection, aResult);
01276 }
01277 #endif
01278 
01279 nsresult
01280 nsHTMLEditRules::WillInsertText(PRInt32          aAction,
01281                                 nsISelection *aSelection, 
01282                                 PRBool          *aCancel,
01283                                 PRBool          *aHandled,
01284                                 const nsAString *inString,
01285                                 nsAString       *outString,
01286                                 PRInt32          aMaxLength)
01287 {  
01288   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
01289 
01290 
01291 
01292   if (inString->IsEmpty() && (aAction != kInsertTextIME))
01293   {
01294     // HACK: this is a fix for bug 19395
01295     // I can't outlaw all empty insertions
01296     // because IME transaction depend on them
01297     // There is more work to do to make the 
01298     // world safe for IME.
01299     *aCancel = PR_TRUE;
01300     *aHandled = PR_FALSE;
01301     return NS_OK;
01302   }
01303   
01304   // initialize out param
01305   *aCancel = PR_FALSE;
01306   *aHandled = PR_TRUE;
01307   nsresult res;
01308   nsCOMPtr<nsIDOMNode> selNode;
01309   PRInt32 selOffset;
01310 
01311   PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
01312 
01313   // if the selection isn't collapsed, delete it.
01314   PRBool bCollapsed;
01315   res = aSelection->GetIsCollapsed(&bCollapsed);
01316   if (NS_FAILED(res)) return res;
01317   if (!bCollapsed)
01318   {
01319     res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
01320     if (NS_FAILED(res)) return res;
01321   }
01322 
01323   res = WillInsert(aSelection, aCancel);
01324   if (NS_FAILED(res)) return res;
01325   // initialize out param
01326   // we want to ignore result of WillInsert()
01327   *aCancel = PR_FALSE;
01328   
01329   // get the (collapsed) selection location
01330   res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
01331   if (NS_FAILED(res)) return res;
01332 
01333   // dont put text in places that cant have it
01334   if (!mHTMLEditor->IsTextNode(selNode) &&
01335       !mHTMLEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text")))
01336     return NS_ERROR_FAILURE;
01337 
01338   // we need to get the doc
01339   nsCOMPtr<nsIDOMDocument>doc;
01340   res = mHTMLEditor->GetDocument(getter_AddRefs(doc));
01341   if (NS_FAILED(res)) return res;
01342   if (!doc) return NS_ERROR_NULL_POINTER;
01343     
01344   if (aAction == kInsertTextIME) 
01345   { 
01346     // Right now the nsWSRunObject code bails on empty strings, but IME needs 
01347     // the InsertTextImpl() call to still happen since empty strings are meaningful there.
01348     if (inString->IsEmpty())
01349     {
01350       res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc);
01351     }
01352     else
01353     {
01354       nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
01355       res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc);
01356     }
01357     if (NS_FAILED(res)) return res;
01358   }
01359   else // aAction == kInsertText
01360   {
01361     // find where we are
01362     nsCOMPtr<nsIDOMNode> curNode = selNode;
01363     PRInt32 curOffset = selOffset;
01364     
01365     // is our text going to be PREformatted?  
01366     // We remember this so that we know how to handle tabs.
01367     PRBool isPRE;
01368     res = mHTMLEditor->IsPreformatted(selNode, &isPRE);
01369     if (NS_FAILED(res)) return res;    
01370     
01371     // turn off the edit listener: we know how to
01372     // build the "doc changed range" ourselves, and it's
01373     // must faster to do it once here than to track all
01374     // the changes one at a time.
01375     nsAutoLockListener lockit(&mListenerEnabled); 
01376     
01377     // dont spaz my selection in subtransactions
01378     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
01379     nsAutoString tString(*inString);
01380     const PRUnichar *unicodeBuf = tString.get();
01381     nsCOMPtr<nsIDOMNode> unused;
01382     PRInt32 pos = 0;
01383     NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
01384         
01385     // for efficiency, break out the pre case separately.  This is because
01386     // its a lot cheaper to search the input string for only newlines than
01387     // it is to search for both tabs and newlines.
01388     if (isPRE || bPlaintext)
01389     {
01390       while (unicodeBuf && (pos != -1) && (pos < (PRInt32)(*inString).Length()))
01391       {
01392         PRInt32 oldPos = pos;
01393         PRInt32 subStrLen;
01394         pos = tString.FindChar(nsCRT::LF, oldPos);
01395 
01396         if (pos != -1) 
01397         {
01398           subStrLen = pos - oldPos;
01399           // if first char is newline, then use just it
01400           if (subStrLen == 0)
01401             subStrLen = 1;
01402         }
01403         else
01404         {
01405           subStrLen = tString.Length() - oldPos;
01406           pos = tString.Length();
01407         }
01408 
01409         nsDependentSubstring subStr(tString, oldPos, subStrLen);
01410         
01411         // is it a return?
01412         if (subStr.Equals(newlineStr))
01413         {
01414           res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
01415           pos++;
01416         }
01417         else
01418         {
01419           res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
01420         }
01421         if (NS_FAILED(res)) return res;
01422       }
01423     }
01424     else
01425     {
01426       NS_NAMED_LITERAL_STRING(tabStr, "\t");
01427       NS_NAMED_LITERAL_STRING(spacesStr, "    ");
01428       char specialChars[] = {TAB, nsCRT::LF, 0};
01429       while (unicodeBuf && (pos != -1) && (pos < (PRInt32)inString->Length()))
01430       {
01431         PRInt32 oldPos = pos;
01432         PRInt32 subStrLen;
01433         pos = tString.FindCharInSet(specialChars, oldPos);
01434         
01435         if (pos != -1) 
01436         {
01437           subStrLen = pos - oldPos;
01438           // if first char is newline, then use just it
01439           if (subStrLen == 0)
01440             subStrLen = 1;
01441         }
01442         else
01443         {
01444           subStrLen = tString.Length() - oldPos;
01445           pos = tString.Length();
01446         }
01447 
01448         nsDependentSubstring subStr(tString, oldPos, subStrLen);
01449         nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset);
01450 
01451         // is it a tab?
01452         if (subStr.Equals(tabStr))
01453         {
01454           res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc);
01455           if (NS_FAILED(res)) return res;
01456           pos++;
01457         }
01458         // is it a return?
01459         else if (subStr.Equals(newlineStr))
01460         {
01461           res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
01462           if (NS_FAILED(res)) return res;
01463           pos++;
01464         }
01465         else
01466         {
01467           res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
01468           if (NS_FAILED(res)) return res;
01469         }
01470         if (NS_FAILED(res)) return res;
01471       }
01472     }
01473     nsCOMPtr<nsISelection> selection(aSelection);
01474     nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
01475     selPriv->SetInterlinePosition(PR_FALSE);
01476     if (curNode) aSelection->Collapse(curNode, curOffset);
01477     // manually update the doc changed range so that AfterEdit will clean up
01478     // the correct portion of the document.
01479     if (!mDocChangeRange)
01480     {
01481       mDocChangeRange = do_CreateInstance("@mozilla.org/content/range;1");
01482       if (!mDocChangeRange) return NS_ERROR_NULL_POINTER;
01483     }
01484     res = mDocChangeRange->SetStart(selNode, selOffset);
01485     if (NS_FAILED(res)) return res;
01486     if (curNode)
01487       res = mDocChangeRange->SetEnd(curNode, curOffset);
01488     else
01489       res = mDocChangeRange->SetEnd(selNode, selOffset);
01490     if (NS_FAILED(res)) return res;
01491   }
01492   return res;
01493 }
01494 
01495 nsresult
01496 nsHTMLEditRules::WillLoadHTML(nsISelection *aSelection, PRBool *aCancel)
01497 {
01498   if (!aSelection || !aCancel) return NS_ERROR_NULL_POINTER;
01499 
01500   *aCancel = PR_FALSE;
01501 
01502   // Delete mBogusNode if it exists. If we really need one,
01503   // it will be added during post-processing in AfterEditInner().
01504 
01505   if (mBogusNode)
01506   {
01507     mEditor->DeleteNode(mBogusNode);
01508     mBogusNode = nsnull;
01509   }
01510 
01511   return NS_OK;
01512 }
01513 
01514 nsresult
01515 nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
01516 {
01517   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
01518   // initialize out param
01519   *aCancel = PR_FALSE;
01520   *aHandled = PR_FALSE;
01521   
01522   PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
01523 
01524   // if the selection isn't collapsed, delete it.
01525   PRBool bCollapsed;
01526   nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
01527   if (NS_FAILED(res)) return res;
01528   if (!bCollapsed)
01529   {
01530     res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
01531     if (NS_FAILED(res)) return res;
01532   }
01533   
01534   res = WillInsert(aSelection, aCancel);
01535   if (NS_FAILED(res)) return res;
01536   
01537   // initialize out param
01538   // we want to ignore result of WillInsert()
01539   *aCancel = PR_FALSE;
01540   
01541   // split any mailcites in the way.
01542   // should we abort this if we encounter table cell boundaries?
01543   if (mFlags & nsIPlaintextEditor::eEditorMailMask)
01544   {
01545     res = SplitMailCites(aSelection, bPlaintext, aHandled);
01546     if (NS_FAILED(res)) return res;
01547     if (*aHandled) return NS_OK;
01548   }
01549 
01550   // smart splitting rules
01551   nsCOMPtr<nsIDOMNode> node;
01552   PRInt32 offset;
01553   
01554   res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(node), &offset);
01555   if (NS_FAILED(res)) return res;
01556   if (!node) return NS_ERROR_FAILURE;
01557     
01558   // identify the block
01559   nsCOMPtr<nsIDOMNode> blockParent;
01560   
01561   if (IsBlockNode(node)) 
01562     blockParent = node;
01563   else 
01564     blockParent = mHTMLEditor->GetBlockNodeParent(node);
01565     
01566   if (!blockParent) return NS_ERROR_FAILURE;
01567   
01568   // if block is empty, populate with br.
01569   // (for example, imagine a div that contains the word "text".  the user selects
01570   // "text" and types return.  "text" is deleted leaving an empty block.  we want
01571   // to put in one br to make block have a line.  then code further below will put 
01572   // in a second br.)
01573   PRBool isEmpty;
01574   res = IsEmptyBlock(blockParent, &isEmpty);
01575   if (isEmpty)
01576   {
01577     PRUint32 blockLen;
01578     res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen);
01579     if (NS_FAILED(res)) return res;
01580     nsCOMPtr<nsIDOMNode> brNode;
01581     res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode));
01582     if (NS_FAILED(res)) return res;
01583   }
01584   
01585   nsCOMPtr<nsIDOMNode> listItem = IsInListItem(blockParent);
01586   if (listItem)
01587   {
01588     res = ReturnInListItem(aSelection, listItem, node, offset);
01589     *aHandled = PR_TRUE;
01590     return NS_OK;
01591   }
01592   
01593   // headers: close (or split) header
01594   else if (nsHTMLEditUtils::IsHeader(blockParent))
01595   {
01596     res = ReturnInHeader(aSelection, blockParent, node, offset);
01597     *aHandled = PR_TRUE;
01598     return NS_OK;
01599   }
01600   
01601   // paragraphs: special rules to look for <br>s
01602   else if (nsHTMLEditUtils::IsParagraph(blockParent))
01603   {
01604     res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel, aHandled);
01605     if (NS_FAILED(res)) return res;
01606     // fall through, we may not have handled it in ReturnInParagraph()
01607   }
01608   
01609   // if not already handled then do the standard thing
01610   if (!(*aHandled))
01611   {
01612     res = StandardBreakImpl(node, offset, aSelection);
01613     *aHandled = PR_TRUE;
01614   }
01615   return res;
01616 }
01617 
01618 nsresult
01619 nsHTMLEditRules::StandardBreakImpl(nsIDOMNode *aNode, PRInt32 aOffset, nsISelection *aSelection)
01620 {
01621   nsCOMPtr<nsIDOMNode> brNode;
01622   PRBool bAfterBlock = PR_FALSE;
01623   PRBool bBeforeBlock = PR_FALSE;
01624   nsresult res = NS_OK;
01625   nsCOMPtr<nsIDOMNode> node(aNode);
01626   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
01627   
01628   if (mFlags & nsIPlaintextEditor::eEditorPlaintextMask)
01629   {
01630     res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode));
01631   }
01632   else
01633   {
01634     nsWSRunObject wsObj(mHTMLEditor, node, aOffset);
01635     nsCOMPtr<nsIDOMNode> visNode, linkNode;
01636     PRInt32 visOffset=0, newOffset;
01637     PRInt16 wsType;
01638     res = wsObj.PriorVisibleNode(node, aOffset, address_of(visNode), &visOffset, &wsType);
01639     if (NS_FAILED(res)) return res;
01640     if (wsType & nsWSRunObject::eBlock)
01641       bAfterBlock = PR_TRUE;
01642     res = wsObj.NextVisibleNode(node, aOffset, address_of(visNode), &visOffset, &wsType);
01643     if (NS_FAILED(res)) return res;
01644     if (wsType & nsWSRunObject::eBlock)
01645       bBeforeBlock = PR_TRUE;
01646     if (mHTMLEditor->IsInLink(node, address_of(linkNode)))
01647     {
01648       // split the link
01649       nsCOMPtr<nsIDOMNode> linkParent;
01650       res = linkNode->GetParentNode(getter_AddRefs(linkParent));
01651       if (NS_FAILED(res)) return res;
01652       res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset, &newOffset, PR_TRUE);
01653       if (NS_FAILED(res)) return res;
01654       // reset {node,aOffset} to the point where link was split
01655       node = linkParent;
01656       aOffset = newOffset;
01657     }
01658     res = wsObj.InsertBreak(address_of(node), &aOffset, address_of(brNode), nsIEditor::eNone);
01659   }
01660   if (NS_FAILED(res)) return res;
01661   res = nsEditor::GetNodeLocation(brNode, address_of(node), &aOffset);
01662   if (NS_FAILED(res)) return res;
01663   if (bAfterBlock && bBeforeBlock)
01664   {
01665     // we just placed a br between block boundaries.  
01666     // This is the one case where we want the selection to be before 
01667     // the br we just placed, as the br will be on a new line,
01668     // rather than at end of prior line.
01669     selPriv->SetInterlinePosition(PR_TRUE);
01670     res = aSelection->Collapse(node, aOffset);
01671   }
01672   else
01673   {
01674      nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1);
01675      nsCOMPtr<nsIDOMNode> secondBR;
01676      PRInt32 visOffset=0;
01677      PRInt16 wsType;
01678      res = wsObj.NextVisibleNode(node, aOffset+1, address_of(secondBR), &visOffset, &wsType);
01679      if (NS_FAILED(res)) return res;
01680      if (wsType==nsWSRunObject::eBreak)
01681      {
01682        // the next thing after the break we inserted is another break.  Move the 2nd 
01683        // break to be the first breaks sibling.  This will prevent them from being
01684        // in different inline nodes, which would break SetInterlinePosition().  It will
01685        // also assure that if the user clicks away and then clicks back on their new
01686        // blank line, they will still get the style from the line above.  
01687        nsCOMPtr<nsIDOMNode> brParent;
01688        PRInt32 brOffset;
01689        res = nsEditor::GetNodeLocation(secondBR, address_of(brParent), &brOffset);
01690        if (NS_FAILED(res)) return res;
01691        if ((brParent != node) || (brOffset != (aOffset+1)))
01692        {
01693          res = mHTMLEditor->MoveNode(secondBR, node, aOffset+1);
01694          if (NS_FAILED(res)) return res;
01695        }
01696      }
01697     // SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
01698     // We want the caret to stick to whatever is past the break.  This is
01699     // because the break is on the same line we were on, but the next content
01700     // will be on the following line.
01701     
01702     // An exception to this is if the break has a next sibling that is a block node.
01703     // Then we stick to the left to avoid an uber caret.
01704     nsCOMPtr<nsIDOMNode> siblingNode;
01705     brNode->GetNextSibling(getter_AddRefs(siblingNode));
01706     if (siblingNode && IsBlockNode(siblingNode))
01707       selPriv->SetInterlinePosition(PR_FALSE);
01708     else 
01709       selPriv->SetInterlinePosition(PR_TRUE);
01710     res = aSelection->Collapse(node, aOffset+1);
01711   }
01712   return res;
01713 }
01714 
01715 nsresult
01716 nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
01717 {
01718   return NS_OK;
01719 }
01720 
01721 
01722 nsresult
01723 nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, PRBool aPlaintext, PRBool *aHandled)
01724 {
01725   if (!aSelection || !aHandled)
01726     return NS_ERROR_NULL_POINTER;
01727   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
01728   nsCOMPtr<nsIDOMNode> citeNode, selNode, leftCite, rightCite;
01729   PRInt32 selOffset, newOffset;
01730   nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
01731   if (NS_FAILED(res)) return res;
01732   res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext);
01733   if (NS_FAILED(res)) return res;
01734   if (citeNode)
01735   {
01736     // If our selection is just before a break, nudge it to be
01737     // just after it.  This does two things for us.  It saves us the trouble of having to add
01738     // a break here ourselves to preserve the "blockness" of the inline span mailquote
01739     // (in the inline case), and :
01740     // it means the break wont end up making an empty line that happens to be inside a
01741     // mailquote (in either inline or block case).  
01742     // The latter can confuse a user if they click there and start typing,
01743     // because being in the mailquote may affect wrapping behavior, or font color, etc.
01744     nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
01745     nsCOMPtr<nsIDOMNode> visNode;
01746     PRInt32 visOffset=0;
01747     PRInt16 wsType;
01748     res = wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &wsType);
01749     if (NS_FAILED(res)) return res;
01750     if (wsType==nsWSRunObject::eBreak)
01751     {
01752       // ok, we are just before a break.  is it inside the mailquote?
01753       PRInt32 unused;
01754       if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused))
01755       {
01756         // it is.  so lets reset our selection to be just after it.
01757         res = mHTMLEditor->GetNodeLocation(visNode, address_of(selNode), &selOffset);
01758         if (NS_FAILED(res)) return res;
01759         ++selOffset;
01760       }
01761     }
01762      
01763     nsCOMPtr<nsIDOMNode> brNode;
01764     res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset, 
01765                        PR_TRUE, address_of(leftCite), address_of(rightCite));
01766     if (NS_FAILED(res)) return res;
01767     res = citeNode->GetParentNode(getter_AddRefs(selNode));
01768     if (NS_FAILED(res)) return res;
01769     res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
01770     if (NS_FAILED(res)) return res;
01771     // want selection before the break, and on same line
01772     selPriv->SetInterlinePosition(PR_TRUE);
01773     res = aSelection->Collapse(selNode, newOffset);
01774     if (NS_FAILED(res)) return res;
01775     // if citeNode wasn't a block, we might also want another break before it.
01776     // We need to examine the content both before the br we just added and also
01777     // just after it.  If we dont have another br or block boundary adjacent,
01778     // then we will need a 2nd br added to achieve blank line that user expects.
01779     if (IsInlineNode(citeNode))
01780     {
01781       nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset);
01782       nsCOMPtr<nsIDOMNode> visNode;
01783       PRInt32 visOffset=0;
01784       PRInt16 wsType;
01785       res = wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode), &visOffset, &wsType);
01786       if (NS_FAILED(res)) return res;
01787       if ((wsType==nsWSRunObject::eNormalWS) || 
01788           (wsType==nsWSRunObject::eText)     ||
01789           (wsType==nsWSRunObject::eSpecial))
01790       {
01791         nsWSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1);
01792         res = wsObjAfterBR.NextVisibleNode(selNode, newOffset+1, address_of(visNode), &visOffset, &wsType);
01793         if (NS_FAILED(res)) return res;
01794         if ((wsType==nsWSRunObject::eNormalWS) || 
01795             (wsType==nsWSRunObject::eText)     ||
01796             (wsType==nsWSRunObject::eSpecial))
01797         {
01798           res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
01799           if (NS_FAILED(res)) return res;
01800         }
01801       }
01802     }
01803     // delete any empty cites
01804     PRBool bEmptyCite = PR_FALSE;
01805     if (leftCite)
01806     {
01807       res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, PR_TRUE, PR_FALSE);
01808       if (NS_SUCCEEDED(res) && bEmptyCite)
01809         res = mHTMLEditor->DeleteNode(leftCite);
01810       if (NS_FAILED(res)) return res;
01811     }
01812     if (rightCite)
01813     {
01814       res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, PR_TRUE, PR_FALSE);
01815       if (NS_SUCCEEDED(res) && bEmptyCite)
01816         res = mHTMLEditor->DeleteNode(rightCite);
01817       if (NS_FAILED(res)) return res;
01818     }
01819     *aHandled = PR_TRUE;
01820   }
01821   return NS_OK;
01822 }
01823 
01824 
01825 nsresult
01826 nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection, 
01827                                      nsIEditor::EDirection aAction, 
01828                                      PRBool *aCancel,
01829                                      PRBool *aHandled)
01830 {
01831 
01832   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
01833   // initialize out param
01834   *aCancel = PR_FALSE;
01835   *aHandled = PR_FALSE;
01836 
01837   // remember that we did a selection deletion.  Used by CreateStyleForInsertText()
01838   mDidDeleteSelection = PR_TRUE;
01839   
01840   // if there is only bogus content, cancel the operation
01841   if (mBogusNode) 
01842   {
01843     *aCancel = PR_TRUE;
01844     return NS_OK;
01845   }
01846 
01847   nsresult res = NS_OK;
01848   PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
01849   
01850   PRBool bCollapsed;
01851   res = aSelection->GetIsCollapsed(&bCollapsed);
01852   if (NS_FAILED(res)) return res;
01853   
01854   nsCOMPtr<nsIDOMNode> startNode, selNode;
01855   PRInt32 startOffset, selOffset;
01856   
01857   // first check for table selection mode.  If so,
01858   // hand off to table editor.
01859   {
01860     nsCOMPtr<nsIDOMElement> cell;
01861     res = mHTMLEditor->GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
01862     if (NS_SUCCEEDED(res) && cell)
01863     {
01864       res = mHTMLEditor->DeleteTableCellContents();
01865       *aHandled = PR_TRUE;
01866       return res;
01867     }
01868   }
01869   
01870   res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(startNode), &startOffset);
01871   if (NS_FAILED(res)) return res;
01872   if (!startNode) return NS_ERROR_FAILURE;
01873     
01874   // get the root element  
01875   nsIDOMElement *rootNode = mHTMLEditor->GetRoot();
01876   if (!rootNode) return NS_ERROR_UNEXPECTED;
01877 
01878   if (bCollapsed)
01879   {
01880     // if we are inside an empty block, delete it.
01881     res = CheckForEmptyBlock(startNode, rootNode, aSelection, aHandled);
01882     if (NS_FAILED(res)) return res;
01883     if (*aHandled) return NS_OK;
01884         
01885     // Test for distance between caret and text that will be deleted
01886     res = CheckBidiLevelForDeletion(startNode, startOffset, aAction, aCancel);
01887     if (NS_FAILED(res)) return res;
01888     if (*aCancel) return NS_OK;
01889 
01890     // We should delete nothing.
01891     if (aAction == nsIEditor::eNone)
01892       return NS_OK;
01893 
01894     // what's in the direction we are deleting?
01895     nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset);
01896     nsCOMPtr<nsIDOMNode> visNode;
01897     PRInt32 visOffset;
01898     PRInt16 wsType;
01899 
01900     // find next visible node
01901     if (aAction == nsIEditor::eNext)
01902       res = wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
01903     else
01904       res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
01905     if (NS_FAILED(res)) return res;
01906     
01907     if (!visNode) // can't find anything to delete!
01908     {
01909       *aCancel = PR_TRUE;
01910       return res;
01911     }
01912     
01913     if (wsType==nsWSRunObject::eNormalWS)
01914     {
01915       // we found some visible ws to delete.  Let ws code handle it.
01916       if (aAction == nsIEditor::eNext)
01917         res = wsObj.DeleteWSForward();
01918       else
01919         res = wsObj.DeleteWSBackward();
01920       *aHandled = PR_TRUE;
01921       if (NS_FAILED(res)) return res;
01922       res = InsertBRIfNeeded(aSelection);
01923       return res;
01924     } 
01925     else if (wsType==nsWSRunObject::eText)
01926     {
01927       // found normal text to delete.  
01928       PRInt32 so = visOffset;
01929       PRInt32 eo = visOffset+1;
01930       if (aAction == nsIEditor::ePrevious) 
01931       { 
01932         if (so == 0) return NS_ERROR_UNEXPECTED;
01933         so--; 
01934         eo--; 
01935       }
01936       res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo);
01937       if (NS_FAILED(res)) return res;
01938       nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(visNode));
01939       res = mHTMLEditor->DeleteText(nodeAsText,so,1);
01940       *aHandled = PR_TRUE;
01941       if (NS_FAILED(res)) return res;    
01942       res = InsertBRIfNeeded(aSelection);
01943       return res;
01944     }
01945     else if ( (wsType==nsWSRunObject::eSpecial)  || 
01946               (wsType==nsWSRunObject::eBreak)    ||
01947               nsHTMLEditUtils::IsHR(visNode) ) 
01948     {
01949       // short circuit for invisible breaks.  delete them and recurse.
01950       if (nsTextEditUtils::IsBreak(visNode) && !mHTMLEditor->IsVisBreak(visNode))
01951       {
01952         res = mHTMLEditor->DeleteNode(visNode);
01953         if (NS_FAILED(res)) return res;
01954         return WillDeleteSelection(aSelection, aAction, aCancel, aHandled);
01955       }
01956       
01957       // special handling for backspace when positioned after <hr>
01958       if (aAction == nsIEditor::ePrevious && nsHTMLEditUtils::IsHR(visNode))
01959       {
01960         /*
01961           Only if the caret is positioned at the end-of-hr-line position,
01962           we want to delete the <hr>.
01963           
01964           In other words, we only want to delete, if
01965           our selection position (indicated by startNode and startOffset)
01966           is the position directly after the <hr>,
01967           on the same line as the <hr>.
01968 
01969           To detect this case we check:
01970           startNode == parentOfVisNode
01971           and
01972           startOffset -1 == visNodeOffsetToVisNodeParent
01973           and
01974           interline position is false (left)
01975 
01976           In any other case we set the position to 
01977           startnode -1 and interlineposition to false,
01978           only moving the caret to the end-of-hr-line position.
01979         */
01980 
01981         PRBool moveOnly = PR_TRUE;
01982 
01983         res = nsEditor::GetNodeLocation(visNode, address_of(selNode), &selOffset);
01984         if (NS_FAILED(res)) return res;
01985 
01986         PRBool interLineIsRight;
01987         nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
01988         res = selPriv->GetInterlinePosition(&interLineIsRight);
01989         if (NS_FAILED(res)) return res;
01990 
01991         if (startNode == selNode &&
01992             startOffset -1 == selOffset &&
01993             !interLineIsRight)
01994         {
01995           moveOnly = PR_FALSE;
01996         }
01997         
01998         if (moveOnly)
01999         {
02000           // Go to the position after the <hr>, but to the end of the <hr> line
02001           // by setting the interline position to left.
02002           ++selOffset;
02003           res = aSelection->Collapse(selNode, selOffset);
02004           selPriv->SetInterlinePosition(PR_FALSE);
02005           mDidExplicitlySetInterline = PR_TRUE;
02006           *aHandled = PR_TRUE;
02007 
02008           // There is one exception to the move only case.
02009           // If the <hr> is followed by a <br> we want to delete the <br>.
02010 
02011           PRInt16 otherWSType;
02012           nsCOMPtr<nsIDOMNode> otherNode;
02013           PRInt32 otherOffset;
02014 
02015           res = wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
02016           if (NS_FAILED(res)) return res;
02017 
02018           if (otherWSType == nsWSRunObject::eBreak)
02019           {
02020             // Delete the <br>
02021 
02022             res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode);
02023             if (NS_FAILED(res)) return res;
02024             res = mHTMLEditor->DeleteNode(otherNode);
02025             if (NS_FAILED(res)) return res;
02026           }
02027 
02028           return NS_OK;
02029         }
02030         // else continue with normal delete code
02031       }
02032 
02033       // found break or image, or hr.  
02034       res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode);
02035       if (NS_FAILED(res)) return res;
02036       // remember sibling to visnode, if any
02037       nsCOMPtr<nsIDOMNode> sibling, stepbrother;
02038       mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling));
02039       // delete the node, and join like nodes if appropriate
02040       res = mHTMLEditor->DeleteNode(visNode);
02041       if (NS_FAILED(res)) return res;
02042       // we did something, so lets say so.
02043       *aHandled = PR_TRUE;
02044       // is there a prior node and are they siblings?
02045       if (sibling)
02046          mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother));
02047       if (startNode == stepbrother) 
02048       {
02049         // are they both text nodes?
02050         if (mHTMLEditor->IsTextNode(startNode) && mHTMLEditor->IsTextNode(sibling))
02051         {
02052           // if so, join them!
02053           res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset);
02054           if (NS_FAILED(res)) return res;
02055           // fix up selection
02056           res = aSelection->Collapse(selNode, selOffset);
02057         }
02058       }
02059       if (NS_FAILED(res)) return res;    
02060       res = InsertBRIfNeeded(aSelection);
02061       return res;
02062     }
02063     else if (wsType==nsWSRunObject::eOtherBlock)
02064     {
02065       // make sure it's not a table element.  If so, cancel the operation 
02066       // (translation: users cannot backspace or delete across table cells)
02067       if (nsHTMLEditUtils::IsTableElement(visNode))
02068       {
02069         *aCancel = PR_TRUE;
02070         return NS_OK;
02071       }
02072       
02073       // next to a block.  See if we are between a block and a br.  If so, we really
02074       // want to delete the br.  Else join content at selection to the block.
02075       
02076       PRBool bDeletedBR = PR_FALSE;
02077       PRInt16 otherWSType;
02078       nsCOMPtr<nsIDOMNode> otherNode;
02079       PRInt32 otherOffset;
02080       
02081       // find node in other direction
02082       if (aAction == nsIEditor::eNext)
02083         res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
02084       else
02085         res = wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType);
02086       if (NS_FAILED(res)) return res;
02087       
02088       // first find the adjacent node in the block
02089       nsCOMPtr<nsIDOMNode> leafNode, leftNode, rightNode, leftParent, rightParent;
02090       if (aAction == nsIEditor::ePrevious) 
02091       {
02092         res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode));
02093         if (NS_FAILED(res)) return res;
02094         leftNode = leafNode;
02095         rightNode = startNode;
02096       }
02097       else
02098       {
02099         res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode));
02100         if (NS_FAILED(res)) return res;
02101         leftNode = startNode;
02102         rightNode = leafNode;
02103       }
02104       
02105       if (nsTextEditUtils::IsBreak(otherNode))
02106       {
02107         res = mHTMLEditor->DeleteNode(otherNode);
02108         if (NS_FAILED(res)) return res;
02109         *aHandled = PR_TRUE;
02110         bDeletedBR = PR_TRUE;
02111       }
02112       
02113       // dont cross table boundaries
02114       if (leftNode && rightNode)
02115       {
02116         PRBool bInDifTblElems;
02117         res = InDifferentTableElements(leftNode, rightNode, &bInDifTblElems);
02118         if (NS_FAILED(res) || bInDifTblElems) return res;
02119       }
02120       
02121       if (bDeletedBR)
02122       {
02123         // put selection at edge of block and we are done.
02124         nsCOMPtr<nsIDOMNode> newSelNode;
02125         PRInt32 newSelOffset;
02126         res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset);
02127         if (NS_FAILED(res)) return res;
02128         aSelection->Collapse(newSelNode, newSelOffset);
02129         return res;
02130       }
02131       
02132       // else we are joining content to block
02133       
02134       // find the relavent blocks
02135       if (IsBlockNode(leftNode))
02136         leftParent = leftNode;
02137       else
02138         leftParent = mHTMLEditor->GetBlockNodeParent(leftNode);
02139       if (IsBlockNode(rightNode))
02140         rightParent = rightNode;
02141       else
02142         rightParent = mHTMLEditor->GetBlockNodeParent(rightNode);
02143       
02144       // sanity checks
02145       if (!leftParent || !rightParent)
02146         return NS_ERROR_NULL_POINTER;  
02147       if (leftParent == rightParent)
02148         return NS_ERROR_UNEXPECTED;  
02149       
02150       // now join them
02151       nsCOMPtr<nsIDOMNode> selPointNode = startNode;
02152       PRInt32 selPointOffset = startOffset;
02153       {
02154         nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
02155         res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel);
02156         *aHandled = PR_TRUE;
02157       }
02158       aSelection->Collapse(selPointNode, selPointOffset);
02159       return res;
02160     }
02161     else if (wsType==nsWSRunObject::eThisBlock)
02162     {
02163       // at edge of our block.  Look beside it and see if we can join to an adjacent block
02164       
02165       // make sure it's not a table element.  If so, cancel the operation 
02166       // (translation: users cannot backspace or delete across table cells)
02167       if (nsHTMLEditUtils::IsTableElement(visNode))
02168       {
02169         *aCancel = PR_TRUE;
02170         return NS_OK;
02171       }
02172       
02173       // first find the relavent nodes
02174       nsCOMPtr<nsIDOMNode> leftNode, rightNode, leftParent, rightParent;
02175       if (aAction == nsIEditor::ePrevious) 
02176       {
02177         res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode));
02178         if (NS_FAILED(res)) return res;
02179         rightNode = startNode;
02180       }
02181       else
02182       {
02183         res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode));
02184         if (NS_FAILED(res)) return res;
02185         leftNode = startNode;
02186       }
02187 
02188       // nothing to join
02189       if (!leftNode || !rightNode)
02190       {
02191         *aCancel = PR_TRUE;
02192         return NS_OK;
02193       }
02194 
02195       // dont cross table boundaries
02196       PRBool bInDifTblElems;
02197       res = InDifferentTableElements(leftNode, rightNode, &bInDifTblElems);
02198       if (NS_FAILED(res) || bInDifTblElems) return res;
02199 
02200       // find the relavent blocks
02201       if (IsBlockNode(leftNode))
02202         leftParent = leftNode;
02203       else
02204         leftParent = mHTMLEditor->GetBlockNodeParent(leftNode);
02205       if (IsBlockNode(rightNode))
02206         rightParent = rightNode;
02207       else
02208         rightParent = mHTMLEditor->GetBlockNodeParent(rightNode);
02209       
02210       // sanity checks
02211       if (!leftParent || !rightParent)
02212         return NS_ERROR_NULL_POINTER;  
02213       if (leftParent == rightParent)
02214         return NS_ERROR_UNEXPECTED;  
02215       
02216       // now join them
02217       nsCOMPtr<nsIDOMNode> selPointNode = startNode;
02218       PRInt32 selPointOffset = startOffset;
02219       {
02220         nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
02221         res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel);
02222         *aHandled = PR_TRUE;
02223       }
02224       aSelection->Collapse(selPointNode, selPointOffset);
02225       return res;
02226     }
02227   }
02228 
02229   
02230   // else we have a non collapsed selection
02231   // first adjust the selection
02232   res = ExpandSelectionForDeletion(aSelection);
02233   if (NS_FAILED(res)) return res;
02234   
02235   // remember that we did a ranged delete for the benefit of AfterEditInner().
02236   mDidRangedDelete = PR_TRUE;
02237   
02238   // refresh start and end points
02239   res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(startNode), &startOffset);
02240   if (NS_FAILED(res)) return res;
02241   if (!startNode) return NS_ERROR_FAILURE;
02242   nsCOMPtr<nsIDOMNode> endNode;
02243   PRInt32 endOffset;
02244   res = mHTMLEditor->GetEndNodeAndOffset(aSelection, address_of(endNode), &endOffset);
02245   if (NS_FAILED(res)) return res; 
02246   if (!endNode) return NS_ERROR_FAILURE;
02247 
02248   // figure out if the endpoints are in nodes that can be merged  
02249   // adjust surrounding whitespace in preperation to delete selection
02250   if (!bPlaintext)
02251   {
02252     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02253     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
02254                                             address_of(startNode), &startOffset, 
02255                                             address_of(endNode), &endOffset);
02256     if (NS_FAILED(res)) return res; 
02257   }
02258   
02259   {
02260     // track end location of where we are deleting
02261     nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(endNode), &endOffset);
02262     // we are handling all ranged deletions directly now.
02263     *aHandled = PR_TRUE;
02264     
02265     if (endNode == startNode)
02266     {
02267       res = mHTMLEditor->DeleteSelectionImpl(aAction);
02268       if (NS_FAILED(res)) return res; 
02269     }
02270     else
02271     {
02272       // figure out mailcite ancestors
02273       nsCOMPtr<nsIDOMNode> endCiteNode, startCiteNode;
02274       res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode), 
02275                                     mFlags & nsIPlaintextEditor::eEditorPlaintextMask);
02276       if (NS_FAILED(res)) return res; 
02277       res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode), 
02278                                     mFlags & nsIPlaintextEditor::eEditorPlaintextMask);
02279       if (NS_FAILED(res)) return res; 
02280       
02281       // if we only have a mailcite at one of the two endpoints, set the directionality
02282       // of the deletion so that the selection will end up outside the mailcite.
02283       if (startCiteNode && !endCiteNode)
02284       {
02285         aAction = nsIEditor::eNext;
02286       }
02287       else if (!startCiteNode && endCiteNode)
02288       {
02289         aAction = nsIEditor::ePrevious;
02290       }
02291       
02292       // figure out block parents
02293       nsCOMPtr<nsIDOMNode> leftParent;
02294       nsCOMPtr<nsIDOMNode> rightParent;
02295       if (IsBlockNode(startNode))
02296         leftParent = startNode;
02297       else
02298         leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
02299       if (IsBlockNode(endNode))
02300         rightParent = endNode;
02301       else
02302         rightParent = mHTMLEditor->GetBlockNodeParent(endNode);
02303         
02304       // are endpoint block parents the same?  use default deletion
02305       if (leftParent == rightParent) 
02306       {
02307         res = mHTMLEditor->DeleteSelectionImpl(aAction);
02308       }
02309       else
02310       {
02311         // deleting across blocks
02312         // are the blocks of same type?
02313         
02314         // are the blocks siblings?
02315         nsCOMPtr<nsIDOMNode> leftBlockParent;
02316         nsCOMPtr<nsIDOMNode> rightBlockParent;
02317         leftParent->GetParentNode(getter_AddRefs(leftBlockParent));
02318         rightParent->GetParentNode(getter_AddRefs(rightBlockParent));
02319 
02320         // MOOSE: this could conceivably screw up a table.. fix me.
02321         if (   (leftBlockParent == rightBlockParent)
02322             && (mHTMLEditor->NodesSameType(leftParent, rightParent))  )
02323         {
02324           if (nsHTMLEditUtils::IsParagraph(leftParent))
02325           {
02326             // first delete the selection
02327             res = mHTMLEditor->DeleteSelectionImpl(aAction);
02328             if (NS_FAILED(res)) return res;
02329             // then join para's, insert break
02330             res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
02331             if (NS_FAILED(res)) return res;
02332             // fix up selection
02333             res = aSelection->Collapse(selNode,selOffset);
02334             return res;
02335           }
02336           if (nsHTMLEditUtils::IsListItem(leftParent)
02337               || nsHTMLEditUtils::IsHeader(leftParent))
02338           {
02339             // first delete the selection
02340             res = mHTMLEditor->DeleteSelectionImpl(aAction);
02341             if (NS_FAILED(res)) return res;
02342             // join blocks
02343             res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
02344             if (NS_FAILED(res)) return res;
02345             // fix up selection
02346             res = aSelection->Collapse(selNode,selOffset);
02347             return res;
02348           }
02349         }
02350         
02351         // else blocks not same type, or not siblings.  Delete everything except
02352         // table elements.
02353         nsCOMPtr<nsIEnumerator> enumerator;
02354         nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
02355         res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
02356         if (NS_FAILED(res)) return res;
02357         if (!enumerator) return NS_ERROR_UNEXPECTED;
02358 
02359         for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next())
02360         {
02361           nsCOMPtr<nsISupports> currentItem;
02362           res = enumerator->CurrentItem(getter_AddRefs(currentItem));
02363           if (NS_FAILED(res)) return res;
02364           if (!currentItem) return NS_ERROR_UNEXPECTED;
02365 
02366           // build a list of nodes in the range
02367           nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
02368           nsCOMArray<nsIDOMNode> arrayOfNodes;
02369           nsTrivialFunctor functor;
02370           nsDOMSubtreeIterator iter;
02371           res = iter.Init(range);
02372           if (NS_FAILED(res)) return res;
02373           res = iter.AppendList(functor, arrayOfNodes);
02374           if (NS_FAILED(res)) return res;
02375       
02376           // now that we have the list, delete non table elements
02377           PRInt32 listCount = arrayOfNodes.Count();
02378           PRInt32 j;
02379 
02380           for (j = 0; j < listCount; j++)
02381           {
02382             nsIDOMNode* somenode = arrayOfNodes[0];
02383             res = DeleteNonTableElements(somenode);
02384             arrayOfNodes.RemoveObjectAt(0);
02385           }
02386         }
02387         
02388         // check endopints for possible text deletion.
02389         // we can assume that if text node is found, we can
02390         // delete to end or to begining as appropriate,
02391         // since the case where both sel endpoints in same
02392         // text node was already handled (we wouldn't be here)
02393         if ( mHTMLEditor->IsTextNode(startNode) )
02394         {
02395           // delete to last character
02396           nsCOMPtr<nsIDOMCharacterData>nodeAsText;
02397           PRUint32 len;
02398           nodeAsText = do_QueryInterface(startNode);
02399           nodeAsText->GetLength(&len);
02400           if (len > (PRUint32)startOffset)
02401           {
02402             res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset);
02403             if (NS_FAILED(res)) return res;
02404           }
02405         }
02406         if ( mHTMLEditor->IsTextNode(endNode) )
02407         {
02408           // delete to first character
02409           nsCOMPtr<nsIDOMCharacterData>nodeAsText;
02410           nodeAsText = do_QueryInterface(endNode);
02411           if (endOffset)
02412           {
02413             res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset);
02414             if (NS_FAILED(res)) return res;
02415           }
02416         }
02417       }
02418     }
02419   }
02420   if (aAction == nsIEditor::eNext)
02421   {
02422     res = aSelection->Collapse(endNode,endOffset);
02423   }
02424   else
02425   {
02426     res = aSelection->Collapse(startNode,startOffset);
02427   }
02428   return res;
02429 }  
02430 
02431 
02432 /*****************************************************************************************************
02433 *    InsertBRIfNeeded: determines if a br is needed for current selection to not be spastic.
02434 *    If so, it inserts one.  Callers responsibility to only call with collapsed selection.
02435 *         nsISelection *aSelection      the collapsed selection 
02436 */
02437 nsresult
02438 nsHTMLEditRules::InsertBRIfNeeded(nsISelection *aSelection)
02439 {
02440   if (!aSelection)
02441     return NS_ERROR_NULL_POINTER;
02442   
02443   // get selection  
02444   nsCOMPtr<nsIDOMNode> node;
02445   PRInt32 offset;
02446   nsresult res = mEditor->GetStartNodeAndOffset(aSelection, address_of(node), &offset);
02447   if (NS_FAILED(res)) return res;
02448   if (!node) return NS_ERROR_FAILURE;
02449 
02450   // examine selection
02451   nsWSRunObject wsObj(mHTMLEditor, node, offset);
02452   if (((wsObj.mStartReason & nsWSRunObject::eBlock) || (wsObj.mStartReason & nsWSRunObject::eBreak))
02453       && (wsObj.mEndReason & nsWSRunObject::eBlock))
02454   {
02455     // if we are tucked between block boundaries then insert a br
02456     // first check that we are allowed to
02457     if (mHTMLEditor->CanContainTag(node, NS_LITERAL_STRING("br")))
02458     {
02459       nsCOMPtr<nsIDOMNode> brNode;
02460       res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious);
02461     }
02462   }
02463   return res;
02464 }
02465 
02466 /*****************************************************************************************************
02467 *    GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were
02468 *    trying to have a caret next to it.
02469 *         nsIDOMNode *aNode                  the node 
02470 *         nsIEditor::EDirection aAction      which edge to find: eNext indicates beginning, ePrevious ending
02471 *         nsCOMPtr<nsIDOMNode> *outSelNode   desired sel node
02472 *         PRInt32 *outSelOffset              desired sel offset
02473 */
02474 nsresult
02475 nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction, 
02476                                         nsCOMPtr<nsIDOMNode> *outSelNode, PRInt32 *outSelOffset)
02477 {
02478   if (!aNode || !outSelNode || !outSelOffset)
02479     return NS_ERROR_NULL_POINTER;
02480   
02481   nsresult res = NS_OK;
02482   
02483   // default values
02484   *outSelNode = aNode;
02485   *outSelOffset = 0;
02486   
02487   if (mHTMLEditor->IsTextNode(aNode) || mHTMLEditor->IsContainer(aNode))
02488   {
02489     if (aAction == nsIEditor::ePrevious)
02490     {
02491       PRUint32 len;
02492       res = mHTMLEditor->GetLengthOfDOMNode(aNode, len);
02493       *outSelOffset = PRInt32(len);
02494       if (NS_FAILED(res)) return res;
02495     }
02496   }
02497   else 
02498   {
02499     res = nsEditor::GetNodeLocation(aNode, outSelNode, outSelOffset);
02500     if (NS_FAILED(res)) return res;
02501     if (!nsTextEditUtils::IsBreak(aNode) || mHTMLEditor->IsVisBreak(aNode))
02502     {
02503       if (aAction == nsIEditor::ePrevious)
02504         (*outSelOffset)++;
02505     }
02506   }
02507   return res;
02508 }
02509 
02510 
02511 /*****************************************************************************************************
02512 *    JoinBlocks: this method is used to join two block elements.  The right element is always joined
02513 *    to the left element.  If the elements are the same type and not nested within each other, 
02514 *    JoinNodesSmart is called (example, joining two list items together into one).  If the elements
02515 *    are not the same type, or one is a descendant of the other, we instead destroy the right block
02516 *    placing it's children into leftblock.  DTD containment rules are followed throughout.
02517 *         nsCOMPtr<nsIDOMNode> *aLeftBlock         pointer to the left block
02518 *         nsCOMPtr<nsIDOMNode> *aRightBlock        pointer to the right block; will have contents moved to left block
02519 *         PRBool *aCanceled                        return TRUE if we had to cancel operation
02520 */
02521 nsresult
02522 nsHTMLEditRules::JoinBlocks(nsCOMPtr<nsIDOMNode> *aLeftBlock, 
02523                             nsCOMPtr<nsIDOMNode> *aRightBlock, 
02524                             PRBool *aCanceled)
02525 {
02526   if (!aLeftBlock || !aRightBlock || !*aLeftBlock || !*aRightBlock) return NS_ERROR_NULL_POINTER;
02527   if (nsHTMLEditUtils::IsTableElement(*aLeftBlock) || nsHTMLEditUtils::IsTableElement(*aRightBlock))
02528   {
02529     // do not try to merge table elements
02530     *aCanceled = PR_TRUE;
02531     return NS_OK;
02532   }
02533 
02534   // make sure we dont try to move thing's into HR's, which look like blocks but aren't containers
02535   if (nsHTMLEditUtils::IsHR(*aLeftBlock))
02536   {
02537     nsCOMPtr<nsIDOMNode> realLeft = mHTMLEditor->GetBlockNodeParent(*aLeftBlock);
02538     *aLeftBlock = realLeft;
02539   }
02540   if (nsHTMLEditUtils::IsHR(*aRightBlock))
02541   {
02542     nsCOMPtr<nsIDOMNode> realRight = mHTMLEditor->GetBlockNodeParent(*aRightBlock);
02543     *aRightBlock = realRight;
02544   }
02545 
02546   // bail if both blocks the same
02547   if (*aLeftBlock == *aRightBlock)
02548   {
02549     *aCanceled = PR_TRUE;
02550     return NS_OK;
02551   }
02552   
02553   // special rule here: if we are trying to join list items, and they are in different lists,
02554   // join the lists instead.
02555   PRBool bMergeLists = PR_FALSE;
02556   nsAutoString existingListStr;
02557   PRInt32 theOffset;
02558   nsCOMPtr<nsIDOMNode> leftList, rightList;
02559   if (nsHTMLEditUtils::IsListItem(*aLeftBlock) && nsHTMLEditUtils::IsListItem(*aRightBlock))
02560   {
02561     (*aLeftBlock)->GetParentNode(getter_AddRefs(leftList));
02562     (*aRightBlock)->GetParentNode(getter_AddRefs(rightList));
02563     if (leftList && rightList && (leftList!=rightList))
02564     {
02565       // there are some special complications if the lists are descendants of
02566       // the other lists' items.  Note that it is ok for them to be descendants
02567       // of the other lists themselves, which is the usual case for sublists
02568       // in our impllementation.
02569       if (!nsEditorUtils::IsDescendantOf(leftList, *aRightBlock, &theOffset) &&
02570           !nsEditorUtils::IsDescendantOf(rightList, *aLeftBlock, &theOffset))
02571       {
02572         *aLeftBlock = leftList;
02573         *aRightBlock = rightList;
02574         bMergeLists = PR_TRUE;
02575         mHTMLEditor->GetTagString(leftList, existingListStr);
02576         ToLowerCase(existingListStr);
02577       }
02578     }
02579   }
02580   
02581   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02582   
02583   nsresult res = NS_OK;
02584   PRInt32  rightOffset = 0;
02585   PRInt32  leftOffset  = -1;
02586 
02587   // theOffset below is where you find yourself in aRightBlock when you traverse upwards
02588   // from aLeftBlock
02589   if (nsEditorUtils::IsDescendantOf(*aLeftBlock, *aRightBlock, &rightOffset))
02590   {
02591     // tricky case.  left block is inside right block.
02592     // Do ws adjustment.  This just destroys non-visible ws at boundaries we will be joining.
02593     rightOffset++;
02594     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBlockEnd);
02595     if (NS_FAILED(res)) return res;
02596     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kAfterBlock, &rightOffset);
02597     if (NS_FAILED(res)) return res;
02598     // Do br adjustment.
02599     nsCOMPtr<nsIDOMNode> brNode;
02600     res = CheckForInvisibleBR(*aLeftBlock, kBlockEnd, address_of(brNode));
02601     if (NS_FAILED(res)) return res;
02602     if (bMergeLists)
02603     {
02604       // idea here is to take all children in  rightList that are past
02605       // theOffset, and pull them into leftlist.
02606       nsCOMPtr<nsIDOMNode> childToMove;
02607       nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList));
02608       if (!parent)
02609         return NS_ERROR_NULL_POINTER;
02610 
02611       nsIContent *child = parent->GetChildAt(theOffset);
02612       while (child)
02613       {
02614         childToMove = do_QueryInterface(child);
02615         res = mHTMLEditor->MoveNode(childToMove, leftList, -1);
02616         if (NS_FAILED(res))
02617           return res;
02618 
02619         child = parent->GetChildAt(rightOffset);
02620       }
02621     }
02622     else
02623     {
02624       res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
02625     }
02626     if (brNode) mHTMLEditor->DeleteNode(brNode);
02627   }
02628   // theOffset below is where you find yourself in aLeftBlock when you traverse upwards
02629   // from aRightBlock
02630   else if (nsEditorUtils::IsDescendantOf(*aRightBlock, *aLeftBlock, &leftOffset))
02631   {
02632     // tricky case.  right block is inside left block.
02633     // Do ws adjustment.  This just destroys non-visible ws at boundaries we will be joining.
02634     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kBlockStart);
02635     if (NS_FAILED(res)) return res;
02636     res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBeforeBlock, &leftOffset);
02637     if (NS_FAILED(res)) return res;
02638     // Do br adjustment.
02639     nsCOMPtr<nsIDOMNode> brNode;
02640     res = CheckForInvisibleBR(*aLeftBlock, kBeforeBlock, address_of(brNode), leftOffset);
02641     if (NS_FAILED(res)) return res;
02642     if (bMergeLists)
02643     {
02644       res = MoveContents(rightList, leftList, &leftOffset);
02645     }
02646     else
02647     {
02648       res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
02649     }
02650     if (brNode) mHTMLEditor->DeleteNode(brNode);
02651   }
02652   else
02653   {
02654     // normal case.  blocks are siblings, or at least close enough to siblings.  An example
02655     // of the latter is a <p>paragraph</p><ul><li>one<li>two<li>three</ul>.  The first
02656     // li and the p are not true siblings, but we still want to join them if you backspace
02657     // from li into p.
02658     
02659     // adjust whitespace at block boundaries
02660     res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, *aLeftBlock, *aRightBlock);
02661     if (NS_FAILED(res)) return res;
02662     // Do br adjustment.
02663     nsCOMPtr<nsIDOMNode> brNode;
02664     res = CheckForInvisibleBR(*aLeftBlock, kBlockEnd, address_of(brNode));
02665     if (NS_FAILED(res)) return res;
02666     if (bMergeLists || mHTMLEditor->NodesSameType(*aLeftBlock, *aRightBlock))
02667     {
02668       // nodes are same type.  merge them.
02669       nsCOMPtr<nsIDOMNode> parent;
02670       PRInt32 offset;
02671       res = JoinNodesSmart(*aLeftBlock, *aRightBlock, address_of(parent), &offset);
02672       if (NS_SUCCEEDED(res) && bMergeLists)
02673       {
02674         nsCOMPtr<nsIDOMNode> newBlock;
02675         res = ConvertListType(*aRightBlock, address_of(newBlock), existingListStr, NS_LITERAL_STRING("li"));
02676       }
02677     }
02678     else
02679     {
02680       // nodes are disimilar types. 
02681       res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset);
02682     }
02683     if (NS_SUCCEEDED(res) && brNode)
02684     {
02685       res = mHTMLEditor->DeleteNode(brNode);
02686     }
02687   }
02688   return res;
02689 }
02690 
02691 
02692 /*****************************************************************************************************
02693 *    MoveBlock: this method is used to move the content from rightBlock into leftBlock
02694 *    Note that the "block" might merely be inline nodes between <br>s, or between blocks, etc.
02695 *    DTD containment rules are followed throughout.
02696 *         nsIDOMNode *aLeftBlock         parent to receive moved content
02697 *         nsIDOMNode *aRightBlock        parent to provide moved content
02698 *         PRInt32 aLeftOffset            offset in aLeftBlock to move content to
02699 *         PRInt32 aRightOffset           offset in aLeftBlock to move content to
02700 */
02701 nsresult
02702 nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, PRInt32 aLeftOffset, PRInt32 aRightOffset)
02703 {
02704   nsCOMArray<nsIDOMNode> arrayOfNodes;
02705   nsCOMPtr<nsISupports> isupports;
02706   // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
02707   nsresult res = GetNodesFromPoint(DOMPoint(aRightBlock,aRightOffset), kMakeList, arrayOfNodes, PR_TRUE);
02708   if (NS_FAILED(res)) return res;
02709   PRInt32 listCount = arrayOfNodes.Count();
02710   PRInt32 i;
02711   for (i=0; i<listCount; i++)
02712   {
02713     // get the node to act on
02714     nsIDOMNode* curNode = arrayOfNodes[i];
02715     if (IsBlockNode(curNode))
02716     {
02717       // For block nodes, move their contents only, then delete block.
02718       res = MoveContents(curNode, aLeftBlock, &aLeftOffset); 
02719       if (NS_FAILED(res)) return res;
02720       res = mHTMLEditor->DeleteNode(curNode);
02721     }
02722     else
02723     {
02724       // otherwise move the content as is, checking against the dtd.
02725       res = MoveNodeSmart(curNode, aLeftBlock, &aLeftOffset);
02726     }
02727   }
02728   return res;
02729 }
02730 
02731 /*****************************************************************************************************
02732 *    MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset).
02733 *    DTD containment rules are followed throughout.  aOffset is updated to point _after_
02734 *    inserted content.
02735 *         nsIDOMNode *aSource       the selection.  
02736 *         nsIDOMNode *aDest         parent to receive moved content
02737 *         PRInt32 *aOffset          offset in aNewParent to move content to
02738 */
02739 nsresult
02740 nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, PRInt32 *aOffset)
02741 {
02742   if (!aSource || !aDest || !aOffset) return NS_ERROR_NULL_POINTER;
02743 
02744   nsAutoString tag;
02745   nsresult res;
02746   res = mHTMLEditor->GetTagString(aSource, tag);
02747   if (NS_FAILED(res)) return res;
02748   ToLowerCase(tag);
02749   // check if this node can go into the destination node
02750   if (mHTMLEditor->CanContainTag(aDest, tag))
02751   {
02752     // if it can, move it there
02753     res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset);
02754     if (NS_FAILED(res)) return res;
02755     if (*aOffset != -1) ++(*aOffset);
02756   }
02757   else
02758   {
02759     // if it can't, move it's children, and then delete it.
02760     res = MoveContents(aSource, aDest, aOffset);
02761     if (NS_FAILED(res)) return res;
02762     res = mHTMLEditor->DeleteNode(aSource);
02763     if (NS_FAILED(res)) return res;
02764   }
02765   return NS_OK;
02766 }
02767 
02768 /*****************************************************************************************************
02769 *    MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset).
02770 *    DTD containment rules are followed throughout.  aOffset is updated to point _after_
02771 *    inserted content.  aSource is deleted.
02772 *         nsIDOMNode *aSource       the selection.  
02773 *         nsIDOMNode *aDest         parent to receive moved content
02774 *         PRInt32 *aOffset          offset in aNewParent to move content to
02775 */
02776 nsresult
02777 nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, PRInt32 *aOffset)
02778 {
02779   if (!aSource || !aDest || !aOffset) return NS_ERROR_NULL_POINTER;
02780   if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE;
02781   
02782   nsCOMPtr<nsIDOMNode> child;
02783   nsAutoString tag;
02784   nsresult res;
02785   aSource->GetFirstChild(getter_AddRefs(child));
02786   while (child)
02787   {
02788     res = MoveNodeSmart(child, aDest, aOffset);
02789     if (NS_FAILED(res)) return res;
02790     aSource->GetFirstChild(getter_AddRefs(child));
02791   }
02792   return NS_OK;
02793 }
02794 
02795 
02796 nsresult
02797 nsHTMLEditRules::DeleteNonTableElements(nsIDOMNode *aNode)
02798 {
02799   if (!aNode) return NS_ERROR_NULL_POINTER;
02800   nsresult res = NS_OK;
02801   if (nsHTMLEditUtils::IsTableElementButNotTable(aNode))
02802   {
02803     nsCOMPtr<nsIDOMNodeList> children;
02804     aNode->GetChildNodes(getter_AddRefs(children));
02805     if (children)
02806     {
02807       PRUint32 len;
02808       children->GetLength(&len);
02809       if (!len) return NS_OK;
02810       PRInt32 j;
02811       for (j=len-1; j>=0; j--)
02812       {
02813         nsCOMPtr<nsIDOMNode> node;
02814         children->Item(j,getter_AddRefs(node));
02815         res = DeleteNonTableElements(node);
02816         if (NS_FAILED(res)) return res;
02817 
02818       }
02819     }
02820   }
02821   else
02822   {
02823     res = mHTMLEditor->DeleteNode(aNode);
02824     if (NS_FAILED(res)) return res;
02825   }
02826   return res;
02827 }
02828 
02829 nsresult
02830 nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection, 
02831                                     nsIEditor::EDirection aDir, 
02832                                     nsresult aResult)
02833 {
02834   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
02835   
02836   // find where we are
02837   nsCOMPtr<nsIDOMNode> startNode;
02838   PRInt32 startOffset;
02839   nsresult res = mEditor->GetStartNodeAndOffset(aSelection, address_of(startNode), &startOffset);
02840   if (NS_FAILED(res)) return res;
02841   if (!startNode) return NS_ERROR_FAILURE;
02842   
02843   // find any enclosing mailcite
02844   nsCOMPtr<nsIDOMNode> citeNode;
02845   res = GetTopEnclosingMailCite(startNode, address_of(citeNode), 
02846                                 mFlags & nsIPlaintextEditor::eEditorPlaintextMask);
02847   if (NS_FAILED(res)) return res;
02848   if (citeNode)
02849   {
02850     PRBool isEmpty = PR_TRUE, seenBR = PR_FALSE;
02851     mHTMLEditor->IsEmptyNodeImpl(citeNode, &isEmpty, PR_TRUE, PR_TRUE, PR_FALSE, &seenBR);
02852     if (isEmpty)
02853     {
02854       nsCOMPtr<nsIDOMNode> parent, brNode;
02855       PRInt32 offset;
02856       nsEditor::GetNodeLocation(citeNode, address_of(parent), &offset);
02857       res = mHTMLEditor->DeleteNode(citeNode);
02858       if (NS_FAILED(res)) return res;
02859       if (parent && seenBR)
02860       {
02861         res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
02862         if (NS_FAILED(res)) return res;
02863         aSelection->Collapse(parent, offset);
02864       }
02865     }
02866   }
02867   
02868   // call through to base class
02869   return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
02870 }
02871 
02872 nsresult
02873 nsHTMLEditRules::WillMakeList(nsISelection *aSelection, 
02874                               const nsAString *aListType, 
02875                               PRBool aEntireList,
02876                               const nsAString *aBulletType,
02877                               PRBool *aCancel,
02878                               PRBool *aHandled,
02879                               const nsAString *aItemType)
02880 {
02881   if (!aSelection || !aListType || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
02882 
02883   nsresult res = WillInsert(aSelection, aCancel);
02884   if (NS_FAILED(res)) return res;
02885 
02886   // initialize out param
02887   // we want to ignore result of WillInsert()
02888   *aCancel = PR_FALSE;
02889   *aHandled = PR_FALSE;
02890 
02891   // deduce what tag to use for list items
02892   nsAutoString itemType;
02893   if (aItemType) 
02894     itemType = *aItemType;
02895   else if (aListType->LowerCaseEqualsLiteral("dl"))
02896     itemType.AssignLiteral("dd");
02897   else
02898     itemType.AssignLiteral("li");
02899     
02900   // convert the selection ranges into "promoted" selection ranges:
02901   // this basically just expands the range to include the immediate
02902   // block parent, and then further expands to include any ancestors
02903   // whose children are all in the range
02904   
02905   *aHandled = PR_TRUE;
02906 
02907   res = NormalizeSelection(aSelection);
02908   if (NS_FAILED(res)) return res;
02909   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
02910   
02911   nsCOMArray<nsIDOMNode> arrayOfNodes;
02912   res = GetListActionNodes(arrayOfNodes, aEntireList);
02913   if (NS_FAILED(res)) return res;
02914   
02915   PRInt32 listCount = arrayOfNodes.Count();
02916   
02917   // check if all our nodes are <br>s, or empty inlines
02918   PRBool bOnlyBreaks = PR_TRUE;
02919   PRInt32 j;
02920   for (j=0; j<listCount; j++)
02921   {
02922     nsIDOMNode* curNode = arrayOfNodes[j];
02923     // if curNode is not a Break or empty inline, we're done
02924     if ( (!nsTextEditUtils::IsBreak(curNode)) && (!IsEmptyInline(curNode)) )
02925     {
02926       bOnlyBreaks = PR_FALSE;
02927       break;
02928     }
02929   }
02930   
02931   // if no nodes, we make empty list.  Ditto if the user tried to make a list of some # of breaks.
02932   if (!listCount || bOnlyBreaks) 
02933   {
02934     nsCOMPtr<nsIDOMNode> parent, theList, theListItem;
02935     PRInt32 offset;
02936 
02937     // if only breaks, delete them
02938     if (bOnlyBreaks)
02939     {
02940       for (j=0; j<(PRInt32)listCount; j++)
02941       {
02942         res = mHTMLEditor->DeleteNode(arrayOfNodes[j]);
02943         if (NS_FAILED(res)) return res;
02944       }
02945     }
02946     
02947     // get selection location
02948     res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
02949     if (NS_FAILED(res)) return res;
02950     
02951     // make sure we can put a list here
02952     res = SplitAsNeeded(aListType, address_of(parent), &offset);
02953     if (NS_FAILED(res)) return res;
02954     res = mHTMLEditor->CreateNode(*aListType, parent, offset, getter_AddRefs(theList));
02955     if (NS_FAILED(res)) return res;
02956     res = mHTMLEditor->CreateNode(itemType, theList, 0, getter_AddRefs(theListItem));
02957     if (NS_FAILED(res)) return res;
02958     // remember our new block for postprocessing
02959     mNewBlock = theListItem;
02960     // put selection in new list item
02961     res = aSelection->Collapse(theListItem,0);
02962     selectionResetter.Abort();  // to prevent selection reseter from overriding us.
02963     *aHandled = PR_TRUE;
02964     return res;
02965   }
02966 
02967   // if there is only one node in the array, and it is a list, div, or blockquote,
02968   // then look inside of it until we find inner list or content.
02969 
02970   res = LookInsideDivBQandList(arrayOfNodes);
02971   if (NS_FAILED(res)) return res;                                 
02972 
02973   // Ok, now go through all the nodes and put then in the list, 
02974   // or whatever is approriate.  Wohoo!
02975 
02976   listCount = arrayOfNodes.Count();
02977   nsCOMPtr<nsIDOMNode> curParent;
02978   nsCOMPtr<nsIDOMNode> curList;
02979   nsCOMPtr<nsIDOMNode> prevListItem;
02980   
02981   PRInt32 i;
02982   for (i=0; i<listCount; i++)
02983   {
02984     // here's where we actually figure out what to do
02985     nsCOMPtr<nsIDOMNode> newBlock;
02986     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
02987     PRInt32 offset;
02988     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
02989     if (NS_FAILED(res)) return res;
02990   
02991     // make sure we dont assemble content that is in different table cells into the same list.
02992     // respect table cell boundaries when listifying.
02993     if (curList)
02994     {
02995       PRBool bInDifTblElems;
02996       res = InDifferentTableElements(curList, curNode, &bInDifTblElems);
02997       if (NS_FAILED(res)) return res;
02998       if (bInDifTblElems)
02999         curList = nsnull;
03000     }
03001     
03002     // if curNode is a Break, delete it, and quit remembering prev list item
03003     if (nsTextEditUtils::IsBreak(curNode)) 
03004     {
03005       res = mHTMLEditor->DeleteNode(curNode);
03006       if (NS_FAILED(res)) return res;
03007       prevListItem = 0;
03008       continue;
03009     }
03010     // if curNode is an empty inline container, delete it
03011     else if (IsEmptyInline(curNode)) 
03012     {
03013       res = mHTMLEditor->DeleteNode(curNode);
03014       if (NS_FAILED(res)) return res;
03015       continue;
03016     }
03017     
03018     if (nsHTMLEditUtils::IsList(curNode))
03019     {
03020       nsAutoString existingListStr;
03021       res = mHTMLEditor->GetTagString(curNode, existingListStr);
03022       ToLowerCase(existingListStr);
03023       // do we have a curList already?
03024       if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList))
03025       {
03026         // move all of our children into curList.
03027         // cheezy way to do it: move whole list and then
03028         // RemoveContainer() on the list.
03029         // ConvertListType first: that routine
03030         // handles converting the list item types, if needed
03031         res = mHTMLEditor->MoveNode(curNode, curList, -1);
03032         if (NS_FAILED(res)) return res;
03033         res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
03034         if (NS_FAILED(res)) return res;
03035         res = mHTMLEditor->RemoveBlockContainer(newBlock);
03036         if (NS_FAILED(res)) return res;
03037       }
03038       else
03039       {
03040         // replace list with new list type
03041         res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
03042         if (NS_FAILED(res)) return res;
03043         curList = newBlock;
03044       }
03045       prevListItem = 0;
03046       continue;
03047     }
03048 
03049     if (nsHTMLEditUtils::IsListItem(curNode))
03050     {
03051       nsAutoString existingListStr;
03052       res = mHTMLEditor->GetTagString(curParent, existingListStr);
03053       ToLowerCase(existingListStr);
03054       if ( existingListStr != *aListType )
03055       {
03056         // list item is in wrong type of list.  
03057         // if we dont have a curList, split the old list
03058         // and make a new list of correct type.
03059         if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList))
03060         {
03061           res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
03062           if (NS_FAILED(res)) return res;
03063           nsCOMPtr<nsIDOMNode> p;
03064           PRInt32 o;
03065           res = nsEditor::GetNodeLocation(curParent, address_of(p), &o);
03066           if (NS_FAILED(res)) return res;
03067           res = mHTMLEditor->CreateNode(*aListType, p, o, getter_AddRefs(curList));
03068           if (NS_FAILED(res)) return res;
03069         }
03070         // move list item to new list
03071         res = mHTMLEditor->MoveNode(curNode, curList, -1);
03072         if (NS_FAILED(res)) return res;
03073         // convert list item type if needed
03074         if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
03075         {
03076           res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
03077           if (NS_FAILED(res)) return res;
03078         }
03079       }
03080       else
03081       {
03082         // item is in right type of list.  But we might still have to move it.
03083         // and we might need to convert list item types.
03084         if (!curList)
03085           curList = curParent;
03086         else
03087         {
03088           if (curParent != curList)
03089           {
03090             // move list item to new list
03091             res = mHTMLEditor->MoveNode(curNode, curList, -1);
03092             if (NS_FAILED(res)) return res;
03093           }
03094         }
03095         if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
03096         {
03097           res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
03098           if (NS_FAILED(res)) return res;
03099         }
03100       }
03101       nsCOMPtr<nsIDOMElement> curElement = do_QueryInterface(curNode);
03102       NS_NAMED_LITERAL_STRING(typestr, "type");
03103       if (aBulletType && !aBulletType->IsEmpty()) {
03104         res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType);
03105       }
03106       else {
03107         res = mHTMLEditor->RemoveAttribute(curElement, typestr);
03108       }
03109       if (NS_FAILED(res)) return res;
03110       continue;
03111     }
03112     
03113     // if we hit a div clear our prevListItem, insert divs contents
03114     // into our node array, and remove the div
03115     if (nsHTMLEditUtils::IsDiv(curNode))
03116     {
03117       prevListItem = nsnull;
03118       PRInt32 j=i+1;
03119       res = GetInnerContent(curNode, arrayOfNodes, &j);
03120       if (NS_FAILED(res)) return res;
03121       res = mHTMLEditor->RemoveContainer(curNode);
03122       if (NS_FAILED(res)) return res;
03123       listCount = arrayOfNodes.Count();
03124       continue;
03125     }
03126       
03127     // need to make a list to put things in if we haven't already,
03128     if (!curList)
03129     {
03130       res = SplitAsNeeded(aListType, address_of(curParent), &offset);
03131       if (NS_FAILED(res)) return res;
03132       res = mHTMLEditor->CreateNode(*aListType, curParent, offset, getter_AddRefs(curList));
03133       if (NS_FAILED(res)) return res;
03134       // remember our new block for postprocessing
03135       mNewBlock = curList;
03136       // curList is now the correct thing to put curNode in
03137       prevListItem = 0;
03138     }
03139   
03140     // if curNode isn't a list item, we must wrap it in one
03141     nsCOMPtr<nsIDOMNode> listItem;
03142     if (!nsHTMLEditUtils::IsListItem(curNode))
03143     {
03144       if (IsInlineNode(curNode) && prevListItem)
03145       {
03146         // this is a continuation of some inline nodes that belong together in
03147         // the same list item.  use prevListItem
03148         res = mHTMLEditor->MoveNode(curNode, prevListItem, -1);
03149         if (NS_FAILED(res)) return res;
03150       }
03151       else
03152       {
03153         // don't wrap li around a paragraph.  instead replace paragraph with li
03154         if (nsHTMLEditUtils::IsParagraph(curNode))
03155         {
03156           res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), itemType);
03157         }
03158         else
03159         {
03160           res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), itemType);
03161         }
03162         if (NS_FAILED(res)) return res;
03163         if (IsInlineNode(curNode)) 
03164           prevListItem = listItem;
03165         else
03166           prevListItem = nsnull;
03167       }
03168     }
03169     else
03170     {
03171       listItem = curNode;
03172     }
03173   
03174     if (listItem)  // if we made a new list item, deal with it
03175     {
03176       // tuck the listItem into the end of the active list
03177       res = mHTMLEditor->MoveNode(listItem, curList, -1);
03178       if (NS_FAILED(res)) return res;
03179     }
03180   }
03181 
03182   return res;
03183 }
03184 
03185 
03186 nsresult
03187 nsHTMLEditRules::WillRemoveList(nsISelection *aSelection, 
03188                                 PRBool aOrdered, 
03189                                 PRBool *aCancel,
03190                                 PRBool *aHandled)
03191 {
03192   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
03193   // initialize out param
03194   *aCancel = PR_FALSE;
03195   *aHandled = PR_TRUE;
03196   
03197   nsresult res = NormalizeSelection(aSelection);
03198   if (NS_FAILED(res)) return res;
03199   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
03200   
03201   nsCOMArray<nsIDOMRange> arrayOfRanges;
03202   res = GetPromotedRanges(aSelection, arrayOfRanges, kMakeList);
03203   if (NS_FAILED(res)) return res;
03204   
03205   // use these ranges to contruct a list of nodes to act on.
03206   nsCOMArray<nsIDOMNode> arrayOfNodes;
03207   res = GetListActionNodes(arrayOfNodes, PR_FALSE);
03208   if (NS_FAILED(res)) return res;                                 
03209                                      
03210   // Remove all non-editable nodes.  Leave them be.
03211   PRInt32 listCount = arrayOfNodes.Count();
03212   PRInt32 i;
03213   for (i=listCount-1; i>=0; i--)
03214   {
03215     nsIDOMNode* testNode = arrayOfNodes[i];
03216     if (!mHTMLEditor->IsEditable(testNode))
03217     {
03218       arrayOfNodes.RemoveObjectAt(i);
03219     }
03220   }
03221   
03222   // reset list count
03223   listCount = arrayOfNodes.Count();
03224   
03225   // Only act on lists or list items in the array
03226   nsCOMPtr<nsIDOMNode> curParent;
03227   for (i=0; i<listCount; i++)
03228   {
03229     // here's where we actually figure out what to do
03230     nsIDOMNode* curNode = arrayOfNodes[i];
03231     PRInt32 offset;
03232     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
03233     if (NS_FAILED(res)) return res;
03234     
03235     if (nsHTMLEditUtils::IsListItem(curNode))  // unlist this listitem
03236     {
03237       PRBool bOutOfList;
03238       do
03239       {
03240         res = PopListItem(curNode, &bOutOfList);
03241         if (NS_FAILED(res)) return res;
03242       } while (!bOutOfList); // keep popping it out until it's not in a list anymore
03243     }
03244     else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, move list items out
03245     {
03246       res = RemoveListStructure(curNode);
03247       if (NS_FAILED(res)) return res;
03248     }
03249   }
03250   return res;
03251 }
03252 
03253 
03254 nsresult
03255 nsHTMLEditRules::WillMakeDefListItem(nsISelection *aSelection, 
03256                                      const nsAString *aItemType, 
03257                                      PRBool aEntireList, 
03258                                      PRBool *aCancel,
03259                                      PRBool *aHandled)
03260 {
03261   // for now we let WillMakeList handle this
03262   NS_NAMED_LITERAL_STRING(listType, "dl");
03263   return WillMakeList(aSelection, &listType, aEntireList, nsnull, aCancel, aHandled, aItemType);
03264 }
03265 
03266 nsresult
03267 nsHTMLEditRules::WillMakeBasicBlock(nsISelection *aSelection, 
03268                                     const nsAString *aBlockType, 
03269                                     PRBool *aCancel,
03270                                     PRBool *aHandled)
03271 {
03272   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
03273   // initialize out param
03274   *aCancel = PR_FALSE;
03275   *aHandled = PR_FALSE;
03276   
03277   nsresult res = WillInsert(aSelection, aCancel);
03278   if (NS_FAILED(res)) return res;
03279   // initialize out param
03280   // we want to ignore result of WillInsert()
03281   *aCancel = PR_FALSE;
03282   res = NormalizeSelection(aSelection);
03283   if (NS_FAILED(res)) return res;
03284   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
03285   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
03286   *aHandled = PR_TRUE;
03287   nsString tString(*aBlockType);
03288 
03289   // contruct a list of nodes to act on.
03290   nsCOMArray<nsIDOMNode> arrayOfNodes;
03291   res = GetNodesFromSelection(aSelection, kMakeBasicBlock, arrayOfNodes);
03292   if (NS_FAILED(res)) return res;
03293 
03294   // Remove all non-editable nodes.  Leave them be.
03295   PRInt32 listCount = arrayOfNodes.Count();
03296   PRInt32 i;
03297   for (i=listCount-1; i>=0; i--)
03298   {
03299     if (!mHTMLEditor->IsEditable(arrayOfNodes[i]))
03300     {
03301       arrayOfNodes.RemoveObjectAt(i);
03302     }
03303   }
03304   
03305   // reset list count
03306   listCount = arrayOfNodes.Count();
03307   
03308   // if nothing visible in list, make an empty block
03309   if (ListIsEmptyLine(arrayOfNodes))
03310   {
03311     nsCOMPtr<nsIDOMNode> parent, theBlock;
03312     PRInt32 offset;
03313     
03314     // get selection location
03315     res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
03316     if (NS_FAILED(res)) return res;
03317     if (tString.EqualsLiteral("normal") ||
03318         tString.IsEmpty() ) // we are removing blocks (going to "body text")
03319     {
03320       nsCOMPtr<nsIDOMNode> curBlock = parent;
03321       if (!IsBlockNode(curBlock))
03322         curBlock = mHTMLEditor->GetBlockNodeParent(parent);
03323       nsCOMPtr<nsIDOMNode> curBlockPar;
03324       if (!curBlock) return NS_ERROR_NULL_POINTER;
03325       curBlock->GetParentNode(getter_AddRefs(curBlockPar));
03326       if (nsHTMLEditUtils::IsFormatNode(curBlock))
03327       {
03328         // if the first editable node after selection is a br, consume it.  Otherwise
03329         // it gets pushed into a following block after the split, which is visually bad.
03330         nsCOMPtr<nsIDOMNode> brNode;
03331         res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
03332         if (NS_FAILED(res)) return res;        
03333         if (brNode && nsTextEditUtils::IsBreak(brNode))
03334         {
03335           res = mHTMLEditor->DeleteNode(brNode);
03336           if (NS_FAILED(res)) return res; 
03337         }
03338         // do the splits!
03339         res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, PR_TRUE);
03340         if (NS_FAILED(res)) return res;
03341         // put a br at the split point
03342         res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode));
03343         if (NS_FAILED(res)) return res;
03344         // put selection at the split point
03345         res = aSelection->Collapse(curBlockPar, offset);
03346         selectionResetter.Abort();  // to prevent selection reseter from overriding us.
03347         *aHandled = PR_TRUE;
03348       }
03349       // else nothing to do!
03350     }
03351     else  // we are making a block
03352     {   
03353       // consume a br, if needed
03354       nsCOMPtr<nsIDOMNode> brNode;
03355       res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), PR_TRUE);
03356       if (NS_FAILED(res)) return res;
03357       if (brNode && nsTextEditUtils::IsBreak(brNode))
03358       {
03359         res = mHTMLEditor->DeleteNode(brNode);
03360         if (NS_FAILED(res)) return res;
03361       }
03362       // make sure we can put a block here
03363       res = SplitAsNeeded(aBlockType, address_of(parent), &offset);
03364       if (NS_FAILED(res)) return res;
03365       res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock));
03366       if (NS_FAILED(res)) return res;
03367       // remember our new block for postprocessing
03368       mNewBlock = theBlock;
03369       // delete anything that was in the list of nodes
03370       for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j) 
03371       {
03372         nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
03373         res = mHTMLEditor->DeleteNode(curNode);
03374         if (NS_FAILED(res)) return res;
03375         res = arrayOfNodes.RemoveObjectAt(0);
03376         if (NS_FAILED(res)) return res;
03377       }
03378       // put selection in new block
03379       res = aSelection->Collapse(theBlock,0);
03380       selectionResetter.Abort();  // to prevent selection reseter from overriding us.
03381       *aHandled = PR_TRUE;
03382     }
03383     return res;    
03384   }
03385   else
03386   {
03387     // Ok, now go through all the nodes and make the right kind of blocks, 
03388     // or whatever is approriate.  Wohoo! 
03389     // Note: blockquote is handled a little differently
03390     if (tString.EqualsLiteral("blockquote"))
03391       res = MakeBlockquote(arrayOfNodes);
03392     else if (tString.EqualsLiteral("normal") ||
03393              tString.IsEmpty() )
03394       res = RemoveBlockStyle(arrayOfNodes);
03395     else
03396       res = ApplyBlockStyle(arrayOfNodes, aBlockType);
03397     return res;
03398   }
03399   return res;
03400 }
03401 
03402 nsresult 
03403 nsHTMLEditRules::DidMakeBasicBlock(nsISelection *aSelection,
03404                                    nsRulesInfo *aInfo, nsresult aResult)
03405 {
03406   if (!aSelection) return NS_ERROR_NULL_POINTER;
03407   // check for empty block.  if so, put a moz br in it.
03408   PRBool isCollapsed;
03409   nsresult res = aSelection->GetIsCollapsed(&isCollapsed);
03410   if (NS_FAILED(res)) return res;
03411   if (!isCollapsed) return NS_OK;
03412 
03413   nsCOMPtr<nsIDOMNode> parent;
03414   PRInt32 offset;
03415   res = nsEditor::GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
03416   if (NS_FAILED(res)) return res;
03417   res = InsertMozBRIfNeeded(parent);
03418   return res;
03419 }
03420 
03421 nsresult
03422 nsHTMLEditRules::WillIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
03423 {
03424   PRBool useCSS;
03425   nsresult res;
03426   mHTMLEditor->GetIsCSSEnabled(&useCSS);
03427   
03428   if (useCSS) {
03429     res = WillCSSIndent(aSelection, aCancel, aHandled);
03430   }
03431   else {
03432     res = WillHTMLIndent(aSelection, aCancel, aHandled);
03433   }
03434   return res;
03435 }
03436 
03437 nsresult
03438 nsHTMLEditRules::WillCSSIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
03439 {
03440   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
03441   
03442   nsresult res = WillInsert(aSelection, aCancel);
03443   if (NS_FAILED(res)) return res;
03444 
03445   // initialize out param
03446   // we want to ignore result of WillInsert()
03447   *aCancel = PR_FALSE;
03448   *aHandled = PR_TRUE;
03449 
03450   res = NormalizeSelection(aSelection);
03451   if (NS_FAILED(res)) return res;
03452   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
03453   nsCOMArray<nsIDOMRange>  arrayOfRanges;
03454   nsCOMArray<nsIDOMNode> arrayOfNodes;
03455   
03456   // short circuit: detect case of collapsed selection inside an <li>.
03457   // just sublist that <li>.  This prevents bug 97797.
03458   
03459   PRBool bCollapsed;
03460   nsCOMPtr<nsIDOMNode> liNode;
03461   res = aSelection->GetIsCollapsed(&bCollapsed);
03462   if (NS_FAILED(res)) return res;
03463   if (bCollapsed) 
03464   {
03465     nsCOMPtr<nsIDOMNode> node, block;
03466     PRInt32 offset;
03467     nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(node), &offset);
03468     if (NS_FAILED(res)) return res;
03469     if (IsBlockNode(node)) 
03470       block = node;
03471     else
03472       block = mHTMLEditor->GetBlockNodeParent(node);
03473     if (block && nsHTMLEditUtils::IsListItem(block))
03474       liNode = block;
03475   }
03476   
03477   if (liNode)
03478   {
03479     arrayOfNodes.AppendObject(liNode);
03480   }
03481   else
03482   {
03483     // convert the selection ranges into "promoted" selection ranges:
03484     // this basically just expands the range to include the immediate
03485     // block parent, and then further expands to include any ancestors
03486     // whose children are all in the range
03487     res = GetNodesFromSelection(aSelection, kIndent, arrayOfNodes);
03488     if (NS_FAILED(res)) return res;
03489   }
03490   
03491   NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
03492   // if nothing visible in list, make an empty block
03493   if (ListIsEmptyLine(arrayOfNodes))
03494   {
03495     nsCOMPtr<nsIDOMNode> parent, theBlock;
03496     PRInt32 offset;
03497     nsAutoString quoteType(NS_LITERAL_STRING("div"));
03498     // get selection location
03499     res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
03500     if (NS_FAILED(res)) return res;
03501     // make sure we can put a block here
03502     res = SplitAsNeeded(&quoteType, address_of(parent), &offset);
03503     if (NS_FAILED(res)) return res;
03504     res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
03505     if (NS_FAILED(res)) return res;
03506     // remember our new block for postprocessing
03507     mNewBlock = theBlock;
03508     RelativeChangeIndentationOfElementNode(theBlock, +1);
03509     // delete anything that was in the list of nodes
03510     for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j) 
03511     {
03512       nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
03513       res = mHTMLEditor->DeleteNode(curNode);
03514       if (NS_FAILED(res)) return res;
03515       res = arrayOfNodes.RemoveObjectAt(0);
03516       if (NS_FAILED(res)) return res;
03517     }
03518     // put selection in new block
03519     res = aSelection->Collapse(theBlock,0);
03520     selectionResetter.Abort();  // to prevent selection reseter from overriding us.
03521     *aHandled = PR_TRUE;
03522     return res;
03523   }
03524   // Next we detect all the transitions in the array, where a transition
03525   // means that adjacent nodes in the array don't have the same parent.
03526   
03527   nsVoidArray transitionList;
03528   res = MakeTransitionList(arrayOfNodes, transitionList);
03529   if (NS_FAILED(res)) return res;                                 
03530   
03531   // Ok, now go through all the nodes and put them in a blockquote, 
03532   // or whatever is appropriate.  Wohoo!
03533   PRInt32 i;
03534   nsCOMPtr<nsIDOMNode> curParent;
03535   nsCOMPtr<nsIDOMNode> curQuote;
03536   nsCOMPtr<nsIDOMNode> curList;
03537   PRInt32 listCount = arrayOfNodes.Count();
03538   for (i=0; i<listCount; i++)
03539   {
03540     // here's where we actually figure out what to do
03541     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
03542 
03543     // Ignore all non-editable nodes.  Leave them be.
03544     if (!mHTMLEditor->IsEditable(curNode)) continue;
03545 
03546     PRInt32 offset;
03547     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
03548     if (NS_FAILED(res)) return res;
03549     
03550     // some logic for putting list items into nested lists...
03551     if (nsHTMLEditUtils::IsList(curParent))
03552     {
03553       if (!curList || transitionList[i])
03554       {
03555         nsAutoString listTag;
03556         nsEditor::GetTagString(curParent,listTag);
03557         ToLowerCase(listTag);
03558         // create a new nested list of correct type
03559         res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
03560         if (NS_FAILED(res)) return res;
03561         res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
03562         if (NS_FAILED(res)) return res;
03563         // curList is now the correct thing to put curNode in
03564         // remember our new block for postprocessing
03565         mNewBlock = curList;
03566       }
03567       // tuck the node into the end of the active list
03568       PRUint32 listLen;
03569       res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen);
03570       if (NS_FAILED(res)) return res;
03571       res = mHTMLEditor->MoveNode(curNode, curList, listLen);
03572       if (NS_FAILED(res)) return res;
03573     }
03574     
03575     else // not a list item
03576     {
03577       if (IsBlockNode(curNode)) {
03578         RelativeChangeIndentationOfElementNode(curNode, +1);
03579         curQuote = nsnull;
03580       }
03581       else {
03582         if (!curQuote) // || transitionList[i])
03583         {
03584           NS_NAMED_LITERAL_STRING(divquoteType, "div");
03585           res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset);
03586           if (NS_FAILED(res)) return res;
03587           res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote));
03588           if (NS_FAILED(res)) return res;
03589           RelativeChangeIndentationOfElementNode(curQuote, +1);
03590           // remember our new block for postprocessing
03591           mNewBlock = curQuote;
03592           // curQuote is now the correct thing to put curNode in
03593         }
03594         
03595         // tuck the node into the end of the active blockquote
03596         PRUint32 quoteLen;
03597         res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen);
03598         if (NS_FAILED(res)) return res;
03599         res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen);
03600         if (NS_FAILED(res)) return res;
03601       }
03602     }
03603   }
03604   return res;
03605 }
03606 
03607 nsresult
03608 nsHTMLEditRules::WillHTMLIndent(nsISelection *aSelection, PRBool *aCancel, PRBool * aHandled)
03609 {
03610   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
03611   nsresult res = WillInsert(aSelection, aCancel);
03612   if (NS_FAILED(res)) return res;
03613 
03614   // initialize out param
03615   // we want to ignore result of WillInsert()
03616   *aCancel = PR_FALSE;
03617   *aHandled = PR_TRUE;
03618 
03619   res = NormalizeSelection(aSelection);
03620   if (NS_FAILED(res)) return res;
03621   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
03622   
03623   // convert the selection ranges into "promoted" selection ranges:
03624   // this basically just expands the range to include the immediate
03625   // block parent, and then further expands to include any ancestors
03626   // whose children are all in the range
03627   
03628   nsCOMArray<nsIDOMRange> arrayOfRanges;
03629   res = GetPromotedRanges(aSelection, arrayOfRanges, kIndent);
03630   if (NS_FAILED(res)) return res;
03631   
03632   // use these ranges to contruct a list of nodes to act on.
03633   nsCOMArray<nsIDOMNode> arrayOfNodes;
03634   res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, kIndent);
03635   if (NS_FAILED(res)) return res;                                 
03636                                      
03637   NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
03638 
03639   // if nothing visible in list, make an empty block
03640   if (ListIsEmptyLine(arrayOfNodes))
03641   {
03642     nsCOMPtr<nsIDOMNode> parent, theBlock;
03643     PRInt32 offset;
03644     
03645     // get selection location
03646     res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
03647     if (NS_FAILED(res)) return res;
03648     // make sure we can put a block here
03649     res = SplitAsNeeded(&quoteType, address_of(parent), &offset);
03650     if (NS_FAILED(res)) return res;
03651     res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
03652     if (NS_FAILED(res)) return res;
03653     // remember our new block for postprocessing
03654     mNewBlock = theBlock;
03655     // delete anything that was in the list of nodes
03656     for (PRInt32 j = arrayOfNodes.Count() - 1; j >= 0; --j) 
03657     {
03658       nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
03659       res = mHTMLEditor->DeleteNode(curNode);
03660       if (NS_FAILED(res)) return res;
03661       res = arrayOfNodes.RemoveObjectAt(0);
03662       if (NS_FAILED(res)) return res;
03663     }
03664     // put selection in new block
03665     res = aSelection->Collapse(theBlock,0);
03666     selectionResetter.Abort();  // to prevent selection reseter from overriding us.
03667     *aHandled = PR_TRUE;
03668     return res;
03669   }
03670 
03671   // Ok, now go through all the nodes and put them in a blockquote, 
03672   // or whatever is appropriate.  Wohoo!
03673   PRInt32 i;
03674   nsCOMPtr<nsIDOMNode> curParent, curQuote, curList, indentedLI, sibling;
03675   PRInt32 listCount = arrayOfNodes.Count();
03676   for (i=0; i<listCount; i++)
03677   {
03678     // here's where we actually figure out what to do
03679     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
03680 
03681     // Ignore all non-editable nodes.  Leave them be.
03682     if (!mHTMLEditor->IsEditable(curNode)) continue;
03683 
03684     PRInt32 offset;
03685     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
03686     if (NS_FAILED(res)) return res;
03687      
03688     // some logic for putting list items into nested lists...
03689     if (nsHTMLEditUtils::IsList(curParent))
03690     {
03691       // check to see if curList is still appropriate.  Which it is if
03692       // curNode is still right after it in the same list.
03693       if (curList)
03694       {
03695         sibling = nsnull;
03696         mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
03697       }
03698       
03699       if (!curList || (sibling && sibling != curList) )
03700       {
03701         nsAutoString listTag;
03702         nsEditor::GetTagString(curParent,listTag);
03703         ToLowerCase(listTag);
03704         // create a new nested list of correct type
03705         res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
03706         if (NS_FAILED(res)) return res;
03707         res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
03708         if (NS_FAILED(res)) return res;
03709         // curList is now the correct thing to put curNode in
03710         // remember our new block for postprocessing
03711         mNewBlock = curList;
03712       }
03713       // tuck the node into the end of the active list
03714       res = mHTMLEditor->MoveNode(curNode, curList, -1);
03715       if (NS_FAILED(res)) return res;
03716       // forget curQuote, if any
03717       curQuote = nsnull;
03718     }
03719     
03720     else // not a list item, use blockquote?
03721     {
03722       // if we are inside a list item, we dont want to blockquote, we want
03723       // to sublist the list item.  We may have several nodes listed in the
03724       // array of nodes to act on, that are in the same list item.  Since
03725       // we only want to indent that li once, we must keep track of the most
03726       // recent indented list item, and not indent it if we find another node
03727       // to act on that is still inside the same li.
03728       nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode);
03729       if (listitem)
03730       {
03731         if (indentedLI == listitem) continue;  // already indented this list item
03732         res = nsEditor::GetNodeLocation(listitem, address_of(curParent), &offset);
03733         if (NS_FAILED(res)) return res;
03734         // check to see if curList is still appropriate.  Which it is if
03735         // curNode is still right after it in the same list.
03736         if (curList)
03737         {
03738           sibling = nsnull;
03739           mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
03740         }
03741          
03742         if (!curList || (sibling && sibling != curList) )
03743         {
03744           nsAutoString listTag;
03745           nsEditor::GetTagString(curParent,listTag);
03746           ToLowerCase(listTag);
03747           // create a new nested list of correct type
03748           res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
03749           if (NS_FAILED(res)) return res;
03750           res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
03751           if (NS_FAILED(res)) return res;
03752         }
03753         res = mHTMLEditor->MoveNode(listitem, curList, -1);
03754         if (NS_FAILED(res)) return res;
03755         // remember we indented this li
03756         indentedLI = listitem;
03757       }
03758       
03759       else
03760       {
03761         // need to make a blockquote to put things in if we haven't already,
03762         // or if this node doesn't go in blockquote we used earlier.
03763         // One reason it might not go in prio blockquote is if we are now
03764         // in a different table cell. 
03765         if (curQuote)
03766         {
03767           PRBool bInDifTblElems;
03768           res = InDifferentTableElements(curQuote, curNode, &bInDifTblElems);
03769           if (NS_FAILED(res)) return res;
03770           if (bInDifTblElems)
03771             curQuote = nsnull;
03772         }
03773         
03774         if (!curQuote) 
03775         {
03776           res = SplitAsNeeded(&quoteType, address_of(curParent), &offset);
03777           if (NS_FAILED(res)) return res;
03778           res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote));
03779           if (NS_FAILED(res)) return res;
03780           // remember our new block for postprocessing
03781           mNewBlock = curQuote;
03782           // curQuote is now the correct thing to put curNode in
03783         }
03784           
03785         // tuck the node into the end of the active blockquote
03786         res = mHTMLEditor->MoveNode(curNode, curQuote, -1);
03787         if (NS_FAILED(res)) return res;
03788         // forget curList, if any
03789         curList = nsnull;
03790       }
03791     }
03792   }
03793   return res;
03794 }
03795 
03796 
03797 nsresult
03798 nsHTMLEditRules::WillOutdent(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
03799 {
03800   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
03801   // initialize out param
03802   *aCancel = PR_FALSE;
03803   *aHandled = PR_TRUE;
03804   nsresult res = NS_OK;
03805   nsCOMPtr<nsIDOMNode> rememberedLeftBQ, rememberedRightBQ;
03806   PRBool useCSS;
03807   mHTMLEditor->GetIsCSSEnabled(&useCSS);
03808 
03809   res = NormalizeSelection(aSelection);
03810   if (NS_FAILED(res)) return res;
03811   // some scoping for selection resetting - we may need to tweak it
03812   {
03813     nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
03814     
03815     // convert the selection ranges into "promoted" selection ranges:
03816     // this basically just expands the range to include the immediate
03817     // block parent, and then further expands to include any ancestors
03818     // whose children are all in the range
03819     nsCOMArray<nsIDOMNode> arrayOfNodes;
03820     res = GetNodesFromSelection(aSelection, kOutdent, arrayOfNodes);
03821     if (NS_FAILED(res)) return res;
03822 
03823     // Ok, now go through all the nodes and remove a level of blockquoting, 
03824     // or whatever is appropriate.  Wohoo!
03825 
03826     nsCOMPtr<nsIDOMNode> curBlockQuote, firstBQChild, lastBQChild;
03827     PRBool curBlockQuoteIsIndentedWithCSS = PR_FALSE;
03828     PRInt32 listCount = arrayOfNodes.Count();
03829     PRInt32 i;
03830     nsCOMPtr<nsIDOMNode> curParent;
03831     for (i=0; i<listCount; i++)
03832     {
03833       // here's where we actually figure out what to do
03834       nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
03835       PRInt32 offset;
03836       res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
03837       if (NS_FAILED(res)) return res;
03838       
03839       // is it a blockquote?
03840       if (nsHTMLEditUtils::IsBlockquote(curNode)) 
03841       {
03842         // if it is a blockquote, remove it.
03843         // So we need to finish up dealng with any curBlockQuote first.
03844         if (curBlockQuote)
03845         {
03846           res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
03847                                    curBlockQuoteIsIndentedWithCSS,
03848                                    address_of(rememberedLeftBQ),
03849                                    address_of(rememberedRightBQ));
03850           if (NS_FAILED(res)) return res;
03851           curBlockQuote = 0;  firstBQChild = 0;  lastBQChild = 0;
03852           curBlockQuoteIsIndentedWithCSS = PR_FALSE;
03853         }
03854         res = mHTMLEditor->RemoveBlockContainer(curNode);
03855         if (NS_FAILED(res)) return res;
03856         continue;
03857       }
03858       // is it a list item?
03859       if (nsHTMLEditUtils::IsListItem(curNode)) 
03860       {
03861         // if it is a list item, that means we are not outdenting whole list.
03862         // So we need to finish up dealng with any curBlockQuote, and then
03863         // pop this list item.
03864         if (curBlockQuote)
03865         {
03866           res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
03867                                    curBlockQuoteIsIndentedWithCSS,
03868                                    address_of(rememberedLeftBQ),
03869                                    address_of(rememberedRightBQ));
03870           if (NS_FAILED(res)) return res;
03871           curBlockQuote = 0;  firstBQChild = 0;  lastBQChild = 0;
03872           curBlockQuoteIsIndentedWithCSS = PR_FALSE;
03873         }
03874         PRBool bOutOfList;
03875         res = PopListItem(curNode, &bOutOfList);
03876         if (NS_FAILED(res)) return res;
03877         continue;
03878       }
03879       // do we have a blockquote that we are already committed to removing?
03880       if (curBlockQuote)
03881       {
03882         // if so, is this node a descendant?
03883         if (nsEditorUtils::IsDescendantOf(curNode, curBlockQuote))
03884         {
03885           lastBQChild = curNode;
03886           continue;  // then we dont need to do anything different for this node
03887         }
03888         else
03889         {
03890           // otherwise, we have progressed beyond end of curBlockQuote,
03891           // so lets handle it now.  We need to remove the portion of 
03892           // curBlockQuote that contains [firstBQChild - lastBQChild].
03893           res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
03894                                    curBlockQuoteIsIndentedWithCSS,
03895                                    address_of(rememberedLeftBQ),
03896                                    address_of(rememberedRightBQ));
03897           if (NS_FAILED(res)) return res;
03898           curBlockQuote = 0;  firstBQChild = 0;  lastBQChild = 0;
03899           curBlockQuoteIsIndentedWithCSS = PR_FALSE;
03900           // fall out and handle curNode
03901         }
03902       }
03903       
03904       // are we inside a blockquote?
03905       nsCOMPtr<nsIDOMNode> n = curNode;
03906       nsCOMPtr<nsIDOMNode> tmp;
03907       curBlockQuoteIsIndentedWithCSS = PR_FALSE;
03908       // keep looking up the heirarchy as long as we dont hit the body or a table element
03909       // (other than an entire table)
03910       while (!nsTextEditUtils::IsBody(n) &&   
03911              (nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n)))
03912       {
03913         n->GetParentNode(getter_AddRefs(tmp));
03914         n = tmp;
03915         if (nsHTMLEditUtils::IsBlockquote(n))
03916         {
03917           // if so, remember it, and remember first node we are taking out of it.
03918           curBlockQuote = n;
03919           firstBQChild  = curNode;
03920           lastBQChild   = curNode;
03921           break;
03922         }
03923         else if (useCSS)
03924         {
03925           nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
03926           nsAutoString value;
03927           mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value);
03928           float f;
03929           nsIAtom * unit;
03930           mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, &unit);
03931           if (f > 0)
03932           {
03933             curBlockQuote = n;
03934             firstBQChild  = curNode;
03935             lastBQChild   = curNode;
03936             curBlockQuoteIsIndentedWithCSS = PR_TRUE;
03937             break;
03938           }
03939         }
03940       }
03941 
03942       if (!curBlockQuote)
03943       {
03944         // could not find an enclosing blockquote for this node.  handle list cases.
03945         if (nsHTMLEditUtils::IsList(curParent))  // move node out of list
03946         {
03947           if (nsHTMLEditUtils::IsList(curNode))  // just unwrap this sublist
03948           {
03949             res = mHTMLEditor->RemoveBlockContainer(curNode);
03950             if (NS_FAILED(res)) return res;
03951           }
03952           // handled list item case above
03953         }
03954         else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out
03955         {
03956           nsCOMPtr<nsIDOMNode> child;
03957           curNode->GetLastChild(getter_AddRefs(child));
03958           while (child)
03959           {
03960             if (nsHTMLEditUtils::IsListItem(child))
03961             {
03962               PRBool bOutOfList;
03963               res = PopListItem(child, &bOutOfList);
03964               if (NS_FAILED(res)) return res;
03965             }
03966             else if (nsHTMLEditUtils::IsList(child))
03967             {
03968               // We have an embedded list, so move it out from under the
03969               // parent list. Be sure to put it after the parent list
03970               // because this loop iterates backwards through the parent's
03971               // list of children.
03972 
03973               res = mHTMLEditor->MoveNode(child, curParent, offset + 1);
03974               if (NS_FAILED(res)) return res;
03975             }
03976             else
03977             {
03978               // delete any non- list items for now
03979               res = mHTMLEditor->DeleteNode(child);
03980               if (NS_FAILED(res)) return res;
03981             }
03982             curNode->GetLastChild(getter_AddRefs(child));
03983           }
03984           // delete the now-empty list
03985           res = mHTMLEditor->RemoveBlockContainer(curNode);
03986           if (NS_FAILED(res)) return res;
03987         }
03988         else if (useCSS) {
03989           RelativeChangeIndentationOfElementNode(curNode, -1);
03990         }
03991       }
03992     }
03993     if (curBlockQuote)
03994     {
03995       // we have a blockquote we haven't finished handling
03996       res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
03997                                curBlockQuoteIsIndentedWithCSS,
03998                                address_of(rememberedLeftBQ),
03999                                address_of(rememberedRightBQ));
04000       if (NS_FAILED(res)) return res;
04001     }
04002   }
04003   // make sure selection didn't stick to last piece of content in old bq
04004   // (only a problem for collapsed selections)
04005   if (rememberedLeftBQ || rememberedRightBQ)
04006   {
04007     PRBool bCollapsed;
04008     res = aSelection->GetIsCollapsed(&bCollapsed);
04009     if (bCollapsed)
04010     {
04011       // push selection past end of rememberedLeftBQ
04012       nsCOMPtr<nsIDOMNode> sNode;
04013       PRInt32 sOffset;
04014       mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(sNode), &sOffset);
04015       if (rememberedLeftBQ &&
04016           ((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ)))
04017       {
04018         // selection is inside rememberedLeftBQ - push it past it.
04019         nsEditor::GetNodeLocation(rememberedLeftBQ, address_of(sNode), &sOffset);
04020         sOffset++;
04021         aSelection->Collapse(sNode, sOffset);
04022       }
04023       // and pull selection before beginning of rememberedRightBQ
04024       mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(sNode), &sOffset);
04025       if (rememberedRightBQ &&
04026           ((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ)))
04027       {
04028         // selection is inside rememberedRightBQ - push it before it.
04029         nsEditor::GetNodeLocation(rememberedRightBQ, address_of(sNode), &sOffset);
04030         aSelection->Collapse(sNode, sOffset);
04031       }
04032     }
04033   }
04034   return res;
04035 }
04036 
04037 
04039 // RemovePartOfBlock:  split aBlock and move aStartChild to aEndChild out
04040 //                     of aBlock.  return left side of block (if any) in
04041 //                     aLeftNode.  return right side of block (if any) in
04042 //                     aRightNode.  
04043 //                  
04044 nsresult 
04045 nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock, 
04046                                    nsIDOMNode *aStartChild, 
04047                                    nsIDOMNode *aEndChild,
04048                                    nsCOMPtr<nsIDOMNode> *aLeftNode,
04049                                    nsCOMPtr<nsIDOMNode> *aRightNode)
04050 {
04051   nsCOMPtr<nsIDOMNode> middleNode;
04052   nsresult res = SplitBlock(aBlock, aStartChild, aEndChild,
04053                             aLeftNode, aRightNode,
04054                             address_of(middleNode));
04055   if (NS_FAILED(res)) return res;
04056   // get rid of part of blockquote we are outdenting
04057 
04058   return mHTMLEditor->RemoveBlockContainer(aBlock);
04059 }
04060 
04061 nsresult 
04062 nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock, 
04063                             nsIDOMNode *aStartChild, 
04064                             nsIDOMNode *aEndChild,
04065                             nsCOMPtr<nsIDOMNode> *aLeftNode,
04066                             nsCOMPtr<nsIDOMNode> *aRightNode,
04067                             nsCOMPtr<nsIDOMNode> *aMiddleNode)
04068 {
04069   if (!aBlock || !aStartChild || !aEndChild)
04070     return NS_ERROR_NULL_POINTER;
04071   
04072   nsCOMPtr<nsIDOMNode> startParent, endParent, leftNode, rightNode;
04073   PRInt32 startOffset, endOffset, offset;
04074   nsresult res;
04075 
04076   // get split point location
04077   res = nsEditor::GetNodeLocation(aStartChild, address_of(startParent), &startOffset);
04078   if (NS_FAILED(res)) return res;
04079   
04080   // do the splits!
04081   res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset, 
04082                                    PR_TRUE, address_of(leftNode), address_of(rightNode));
04083   if (NS_FAILED(res)) return res;
04084   if (rightNode)  aBlock = rightNode;
04085 
04086   // remember left portion of block if caller requested
04087   if (aLeftNode) 
04088     *aLeftNode = leftNode;
04089 
04090   // get split point location
04091   res = nsEditor::GetNodeLocation(aEndChild, address_of(endParent), &endOffset);
04092   if (NS_FAILED(res)) return res;
04093   endOffset++;  // want to be after lastBQChild
04094 
04095   // do the splits!
04096   res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset, 
04097                                    PR_TRUE, address_of(leftNode), address_of(rightNode));
04098   if (NS_FAILED(res)) return res;
04099   if (leftNode)  aBlock = leftNode;
04100   
04101   // remember right portion of block if caller requested
04102   if (aRightNode) 
04103     *aRightNode = rightNode;
04104 
04105   if (aMiddleNode)
04106     *aMiddleNode = aBlock;
04107 
04108   return NS_OK;
04109 }
04110 
04111 nsresult
04112 nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock, 
04113                                     nsIDOMNode *aStartChild, 
04114                                     nsIDOMNode *aEndChild,
04115                                     PRBool aIsBlockIndentedWithCSS,
04116                                     nsCOMPtr<nsIDOMNode> *aLeftNode,
04117                                     nsCOMPtr<nsIDOMNode> *aRightNode)
04118 {
04119   nsCOMPtr<nsIDOMNode> middleNode;
04120   nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, 
04121                             aLeftNode,
04122                             aRightNode,
04123                             address_of(middleNode));
04124   if (NS_FAILED(res)) return res;
04125   if (aIsBlockIndentedWithCSS)
04126     res = RelativeChangeIndentationOfElementNode(middleNode, -1);
04127   else
04128     res = mHTMLEditor->RemoveBlockContainer(middleNode);
04129   return res;
04130 }
04131 
04133 // ConvertListType:  convert list type and list item type.
04134 //                
04135 //                  
04136 nsresult 
04137 nsHTMLEditRules::ConvertListType(nsIDOMNode *aList, 
04138                                  nsCOMPtr<nsIDOMNode> *outList,
04139                                  const nsAString& aListType, 
04140                                  const nsAString& aItemType) 
04141 {
04142   if (!aList || !outList) return NS_ERROR_NULL_POINTER;
04143   *outList = aList;  // we might not need to change the list
04144   nsresult res = NS_OK;
04145   nsCOMPtr<nsIDOMNode> child, temp;
04146   aList->GetFirstChild(getter_AddRefs(child));
04147   while (child)
04148   {
04149     if (nsHTMLEditUtils::IsListItem(child) && !nsEditor::NodeIsTypeString(child, aItemType))
04150     {
04151       res = mHTMLEditor->ReplaceContainer(child, address_of(temp), aItemType);
04152       if (NS_FAILED(res)) return res;
04153       child = temp;
04154     }
04155     else if (nsHTMLEditUtils::IsList(child) && !nsEditor::NodeIsTypeString(child, aListType))
04156     {
04157       res = ConvertListType(child, address_of(temp), aListType, aItemType);
04158       if (NS_FAILED(res)) return res;
04159       child = temp;
04160     }
04161     child->GetNextSibling(getter_AddRefs(temp));
04162     child = temp;
04163   }
04164   if (!nsEditor::NodeIsTypeString(aList, aListType))
04165   {
04166     res = mHTMLEditor->ReplaceContainer(aList, outList, aListType);
04167   }
04168   return res;
04169 }
04170 
04171 
04173 // CreateStyleForInsertText:  take care of clearing and setting appropriate
04174 //                            style nodes for text insertion.
04175 //                
04176 //                  
04177 nsresult 
04178 nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocument *aDoc) 
04179 {
04180   if (!aSelection || !aDoc) return NS_ERROR_NULL_POINTER;
04181   if (!mHTMLEditor->mTypeInState) return NS_ERROR_NULL_POINTER;
04182   
04183   PRBool weDidSometing = PR_FALSE;
04184   nsCOMPtr<nsIDOMNode> node, tmp;
04185   PRInt32 offset;
04186   nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(node), &offset);
04187   if (NS_FAILED(res)) return res;
04188   PropItem *item = nsnull;
04189   
04190   // if we deleted selection then also for cached styles
04191   if (mDidDeleteSelection && 
04192       ((mTheAction == nsEditor::kOpInsertText ) ||
04193        (mTheAction == nsEditor::kOpInsertIMEText) ||
04194        (mTheAction == nsEditor::kOpInsertBreak) ||
04195        (mTheAction == nsEditor::kOpDeleteSelection)))
04196   {
04197     res = ReapplyCachedStyles();
04198     if (NS_FAILED(res)) return res;
04199   }
04200   // either way we clear the cached styles array
04201   res = ClearCachedStyles();  
04202   if (NS_FAILED(res)) return res;  
04203 
04204   // next examine our present style and make sure default styles are either present or
04205   // explicitly overridden.  If neither, add the default style to the TypeInState
04206   PRInt32 j, defcon = mHTMLEditor->mDefaultStyles.Count();
04207   for (j=0; j<defcon; j++)
04208   {
04209     PropItem *propItem = (PropItem*)mHTMLEditor->mDefaultStyles[j];
04210     if (!propItem) 
04211       return NS_ERROR_NULL_POINTER;
04212     PRBool bFirst, bAny, bAll;
04213 
04214     // GetInlineProperty also examine TypeInState.  The only gotcha here is that a cleared
04215     // property looks like an unset property.  For now I'm assuming that's not a problem:
04216     // that default styles will always be multivalue styles (like font face or size) where
04217     // clearing the style means we want to go back to the default.  If we ever wanted a 
04218     // "toggle" style like bold for a default, though, I'll have to add code to detect the
04219     // difference between unset and explicitly cleared, else user would never be able to
04220     // unbold, for instance.
04221     nsAutoString curValue;
04222     res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &(propItem->attr), nsnull, 
04223                                              &bFirst, &bAny, &bAll, &curValue, PR_FALSE);
04224     if (NS_FAILED(res)) return res;
04225     
04226     if (!bAny)  // no style set for this prop/attr
04227     {
04228       mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, propItem->value);
04229     }
04230   }
04231   
04232   // process clearing any styles first
04233   mHTMLEditor->mTypeInState->TakeClearProperty(&item);
04234   while (item)
04235   {
04236     nsCOMPtr<nsIDOMNode> leftNode, rightNode, secondSplitParent, newSelParent, savedBR;
04237     res = mHTMLEditor->SplitStyleAbovePoint(address_of(node), &offset, item->tag, &item->attr, address_of(leftNode), address_of(rightNode));
04238     if (NS_FAILED(res)) return res;
04239     PRBool bIsEmptyNode;
04240     if (leftNode)
04241     {
04242       mHTMLEditor->IsEmptyNode(leftNode, &bIsEmptyNode, PR_FALSE, PR_TRUE);
04243       if (bIsEmptyNode)
04244       {
04245         // delete leftNode if it became empty
04246         res = mEditor->DeleteNode(leftNode);
04247         if (NS_FAILED(res)) return res;
04248       }
04249     }
04250     if (rightNode)
04251     {
04252       secondSplitParent = mHTMLEditor->GetLeftmostChild(rightNode);
04253       // don't try to split non-containers (br's, images, hr's, etc)
04254       if (!secondSplitParent) secondSplitParent = rightNode;
04255       if (!mHTMLEditor->IsContainer(secondSplitParent))
04256       {
04257         if (nsTextEditUtils::IsBreak(secondSplitParent))
04258           savedBR = secondSplitParent;
04259 
04260         secondSplitParent->GetParentNode(getter_AddRefs(tmp));
04261         secondSplitParent = tmp;
04262       }
04263       offset = 0;
04264       res = mHTMLEditor->SplitStyleAbovePoint(address_of(secondSplitParent), &offset, item->tag, &(item->attr), address_of(leftNode), address_of(rightNode));
04265       if (NS_FAILED(res)) return res;
04266       // should be impossible to not get a new leftnode here
04267       if (!leftNode) return NS_ERROR_FAILURE;
04268       newSelParent = mHTMLEditor->GetLeftmostChild(leftNode);
04269       if (!newSelParent) newSelParent = leftNode;
04270       // if rightNode starts with a br, suck it out of right node and into leftNode.
04271       // This is so we you don't revert back to the previous style if you happen to click at the end of a line.
04272       if (savedBR)
04273       {
04274         res = mEditor->MoveNode(savedBR, newSelParent, 0);
04275         if (NS_FAILED(res)) return res;
04276       }
04277       mHTMLEditor->IsEmptyNode(rightNode, &bIsEmptyNode, PR_FALSE, PR_TRUE);
04278       if (bIsEmptyNode)
04279       {
04280         // delete rightNode if it became empty
04281         res = mEditor->DeleteNode(rightNode);
04282         if (NS_FAILED(res)) return res;
04283       }
04284       // remove the style on this new heirarchy
04285       PRInt32 newSelOffset = 0;
04286       {
04287         // track the point at the new heirarchy.
04288         // This is so we can know where to put the selection after we call
04289         // RemoveStyleInside().  RemoveStyleInside() could remove any and all of those nodes,
04290         // so I have to use the range tracking system to find the right spot to put selection.
04291         nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(newSelParent), &newSelOffset);
04292         res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
04293         if (NS_FAILED(res)) return res;
04294       }
04295       // reset our node offset values to the resulting new sel point
04296       node = newSelParent;
04297       offset = newSelOffset;
04298     }
04299     // we own item now (TakeClearProperty hands ownership to us)
04300     delete item;
04301     mHTMLEditor->mTypeInState->TakeClearProperty(&item);
04302     weDidSometing = PR_TRUE;
04303   }
04304   
04305   // then process setting any styles
04306   PRInt32 relFontSize;
04307   
04308   res = mHTMLEditor->mTypeInState->TakeRelativeFontSize(&relFontSize);
04309   if (NS_FAILED(res)) return res;
04310   res = mHTMLEditor->mTypeInState->TakeSetProperty(&item);
04311   if (NS_FAILED(res)) return res;
04312   
04313   if (item || relFontSize) // we have at least one style to add; make a
04314   {                        // new text node to insert style nodes above.
04315     if (mHTMLEditor->IsTextNode(node))
04316     {
04317       // if we are in a text node, split it
04318       res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset);
04319       if (NS_FAILED(res)) return res;
04320       node->GetParentNode(getter_AddRefs(tmp));
04321       node = tmp;
04322     }
04323     nsCOMPtr<nsIDOMNode> newNode;
04324     nsCOMPtr<nsIDOMText> nodeAsText;
04325     res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText));
04326     if (NS_FAILED(res)) return res;
04327     if (!nodeAsText) return NS_ERROR_NULL_POINTER;
04328     newNode = do_QueryInterface(nodeAsText);
04329     res = mHTMLEditor->InsertNode(newNode, node, offset);
04330     if (NS_FAILED(res)) return res;
04331     node = newNode;
04332     offset = 0;
04333     weDidSometing = PR_TRUE;
04334 
04335     if (relFontSize)
04336     {
04337       PRInt32 j, dir;
04338       // dir indicated bigger versus smaller.  1 = bigger, -1 = smaller
04339       if (relFontSize > 0) dir=1;
04340       else dir = -1;
04341       for (j=0; j<abs(relFontSize); j++)
04342       {
04343         res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, 0, -1);
04344         if (NS_FAILED(res)) return res;
04345       }
04346     }
04347     
04348     while (item)
04349     {
04350       res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr, &item->value);
04351       if (NS_FAILED(res)) return res;
04352       // we own item now (TakeSetProperty hands ownership to us)
04353       delete item;
04354       mHTMLEditor->mTypeInState->TakeSetProperty(&item);
04355     }
04356   }
04357   if (weDidSometing)
04358     return aSelection->Collapse(node, offset);
04359     
04360   return res;
04361 }
04362 
04363 
04365 // IsEmptyBlock: figure out if aNode is (or is inside) an empty block.
04366 //               A block can have children and still be considered empty,
04367 //               if the children are empty or non-editable.
04368 //                  
04369 nsresult 
04370 nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, 
04371                               PRBool *outIsEmptyBlock, 
04372                               PRBool aMozBRDoesntCount,
04373                               PRBool aListItemsNotEmpty) 
04374 {
04375   if (!aNode || !outIsEmptyBlock) return NS_ERROR_NULL_POINTER;
04376   *outIsEmptyBlock = PR_TRUE;
04377   
04378 //  nsresult res = NS_OK;
04379   nsCOMPtr<nsIDOMNode> nodeToTest;
04380   if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode);
04381 //  else nsCOMPtr<nsIDOMElement> block;
04382 //  looks like I forgot to finish this.  Wonder what I was going to do?
04383 
04384   if (!nodeToTest) return NS_ERROR_NULL_POINTER;
04385   return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock,
04386                      aMozBRDoesntCount, aListItemsNotEmpty);
04387 }
04388 
04389 
04390 nsresult
04391 nsHTMLEditRules::WillAlign(nsISelection *aSelection, 
04392                            const nsAString *alignType, 
04393                            PRBool *aCancel,
04394                            PRBool *aHandled)
04395 {
04396   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
04397 
04398   nsresult res = WillInsert(aSelection, aCancel);
04399   if (NS_FAILED(res)) return res;
04400 
04401   // initialize out param
04402   // we want to ignore result of WillInsert()
04403   *aCancel = PR_FALSE;
04404   *aHandled = PR_FALSE;
04405 
04406   res = NormalizeSelection(aSelection);
04407   if (NS_FAILED(res)) return res;
04408   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
04409 
04410   // convert the selection ranges into "promoted" selection ranges:
04411   // this basically just expands the range to include the immediate
04412   // block parent, and then further expands to include any ancestors
04413   // whose children are all in the range
04414   *aHandled = PR_TRUE;
04415   nsCOMArray<nsIDOMNode> arrayOfNodes;
04416   res = GetNodesFromSelection(aSelection, kAlign, arrayOfNodes);
04417   if (NS_FAILED(res)) return res;
04418 
04419   // if we don't have any nodes, or we have only a single br, then we are
04420   // creating an empty alignment div.  We have to do some different things for these.
04421   PRBool emptyDiv = PR_FALSE;
04422   PRInt32 listCount = arrayOfNodes.Count();
04423   if (!listCount) emptyDiv = PR_TRUE;
04424   if (listCount == 1)
04425   {
04426     nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0];
04427 
04428     if (nsHTMLEditUtils::SupportsAlignAttr(theNode))
04429     {
04430       // the node is a table element, an horiz rule, a paragraph, a div
04431       // or a section header; in HTML 4, it can directly carry the ALIGN
04432       // attribute and we don't need to make a div! If we are in CSS mode,
04433       // all the work is done in AlignBlock
04434       nsCOMPtr<nsIDOMElement> theElem = do_QueryInterface(theNode);
04435       res = AlignBlock(theElem, alignType, PR_TRUE);
04436       if (NS_FAILED(res)) return res;
04437       return NS_OK;
04438     }
04439 
04440     if (nsTextEditUtils::IsBreak(theNode))
04441     {
04442       // The special case emptyDiv code (below) that consumes BRs can
04443       // cause tables to split if the start node of the selection is
04444       // not in a table cell or caption, for example parent is a <tr>.
04445       // Avoid this unnecessary splitting if possible by leaving emptyDiv
04446       // FALSE so that we fall through to the normal case alignment code.
04447       //
04448       // XXX: It seems a little error prone for the emptyDiv special
04449       //      case code to assume that the start node of the selection
04450       //      is the parent of the single node in the arrayOfNodes, as
04451       //      the paragraph above points out. Do we rely on the selection
04452       //      start node because of the fact that arrayOfNodes can be empty?
04453       //      We should probably revisit this issue. - kin
04454 
04455       nsCOMPtr<nsIDOMNode> parent;
04456       PRInt32 offset;
04457       res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
04458 
04459       if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent))
04460         emptyDiv = PR_TRUE;
04461     }
04462   }
04463   if (emptyDiv)
04464   {
04465     PRInt32 offset;
04466     nsCOMPtr<nsIDOMNode> brNode, parent, theDiv, sib;
04467     NS_NAMED_LITERAL_STRING(divType, "div");
04468     res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(parent), &offset);
04469     if (NS_FAILED(res)) return res;
04470     res = SplitAsNeeded(&divType, address_of(parent), &offset);
04471     if (NS_FAILED(res)) return res;
04472     // consume a trailing br, if any.  This is to keep an alignment from
04473     // creating extra lines, if possible.
04474     res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
04475     if (NS_FAILED(res)) return res;
04476     if (brNode && nsTextEditUtils::IsBreak(brNode))
04477     {
04478       // making use of html structure... if next node after where
04479       // we are putting our div is not a block, then the br we 
04480       // found is in same block we are, so its safe to consume it.
04481       res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib));
04482       if (NS_FAILED(res)) return res;
04483       if (!IsBlockNode(sib))
04484       {
04485         res = mHTMLEditor->DeleteNode(brNode);
04486         if (NS_FAILED(res)) return res;
04487       }
04488     }
04489     res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv));
04490     if (NS_FAILED(res)) return res;
04491     // remember our new block for postprocessing
04492     mNewBlock = theDiv;
04493     // set up the alignment on the div, using HTML or CSS
04494     nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(theDiv);
04495     res = AlignBlock(divElem, alignType, PR_TRUE);
04496     if (NS_FAILED(res)) return res;
04497     *aHandled = PR_TRUE;
04498     // put in a moz-br so that it won't get deleted
04499     res = CreateMozBR(theDiv, 0, address_of(brNode));
04500     if (NS_FAILED(res)) return res;
04501     res = aSelection->Collapse(theDiv, 0);
04502     selectionResetter.Abort();  // dont reset our selection in this case.
04503     return res;
04504   }
04505 
04506   // Next we detect all the transitions in the array, where a transition
04507   // means that adjacent nodes in the array don't have the same parent.
04508 
04509   nsVoidArray transitionList;
04510   res = MakeTransitionList(arrayOfNodes, transitionList);
04511   if (NS_FAILED(res)) return res;                                 
04512 
04513   // Ok, now go through all the nodes and give them an align attrib or put them in a div, 
04514   // or whatever is appropriate.  Wohoo!
04515 
04516   PRInt32 i;
04517   nsCOMPtr<nsIDOMNode> curParent;
04518   nsCOMPtr<nsIDOMNode> curDiv;
04519   PRBool useCSS;
04520   mHTMLEditor->GetIsCSSEnabled(&useCSS);
04521   for (i=0; i<listCount; i++)
04522   {
04523     // here's where we actually figure out what to do
04524     nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
04525     PRInt32 offset;
04526     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
04527     if (NS_FAILED(res)) return res;
04528 
04529     // the node is a table element, an horiz rule, a paragraph, a div
04530     // or a section header; in HTML 4, it can directly carry the ALIGN
04531     // attribute and we don't need to nest it, just set the alignment.
04532     // In CSS, assign the corresponding CSS styles in AlignBlock
04533     if (nsHTMLEditUtils::SupportsAlignAttr(curNode))
04534     {
04535       nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
04536       res = AlignBlock(curElem, alignType, PR_FALSE);
04537       if (NS_FAILED(res)) return res;
04538       // clear out curDiv so that we don't put nodes after this one into it
04539       curDiv = 0;
04540       continue;
04541     }
04542 
04543     // Skip insignificant formatting text nodes to prevent
04544     // unnecessary structure splitting!
04545     if (nsEditor::IsTextNode(curNode) &&
04546        ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) ||
04547         nsHTMLEditUtils::IsList(curParent)))
04548       continue;
04549 
04550     // if it's a list item, or a list
04551     // inside a list, forget any "current" div, and instead put divs inside
04552     // the appropriate block (td, li, etc)
04553     if ( nsHTMLEditUtils::IsListItem(curNode)
04554          || nsHTMLEditUtils::IsList(curNode))
04555     {
04556       res = RemoveAlignment(curNode, *alignType, PR_TRUE);
04557       if (NS_FAILED(res)) return res;
04558       if (useCSS) {
04559         nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
04560         NS_NAMED_LITERAL_STRING(attrName, "align");
04561         PRInt32 count;
04562         mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nsnull,
04563                                                                 &attrName, alignType,
04564                                                                 &count, PR_FALSE);
04565         curDiv = 0;
04566         continue;
04567       }
04568       else if (nsHTMLEditUtils::IsList(curParent)) {
04569         // if we don't use CSS, add a contraint to list element : they have
04570         // to be inside another list, ie >= second level of nesting
04571         res = AlignInnerBlocks(curNode, alignType);
04572         if (NS_FAILED(res)) return res;
04573         curDiv = 0;
04574         continue;
04575       }
04576       // clear out curDiv so that we don't put nodes after this one into it
04577     }      
04578 
04579     // need to make a div to put things in if we haven't already,
04580     // or if this node doesn't go in div we used earlier.
04581     if (!curDiv || transitionList[i])
04582     {
04583       NS_NAMED_LITERAL_STRING(divType, "div");
04584       res = SplitAsNeeded(&divType, address_of(curParent), &offset);
04585       if (NS_FAILED(res)) return res;
04586       res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv));
04587       if (NS_FAILED(res)) return res;
04588       // remember our new block for postprocessing
04589       mNewBlock = curDiv;
04590       // set up the alignment on the div
04591       nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv);
04592       res = AlignBlock(divElem, alignType, PR_TRUE);
04593 //      nsAutoString attr(NS_LITERAL_STRING("align"));
04594 //      res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
04595 //      if (NS_FAILED(res)) return res;
04596       // curDiv is now the correct thing to put curNode in
04597     }
04598 
04599     // tuck the node into the end of the active div
04600     res = mHTMLEditor->MoveNode(curNode, curDiv, -1);
04601     if (NS_FAILED(res)) return res;
04602   }
04603 
04604   return res;
04605 }
04606 
04607 
04609 // AlignInnerBlocks: align inside table cells or list items
04610 //       
04611 nsresult
04612 nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType)
04613 {
04614   if (!aNode || !alignType) return NS_ERROR_NULL_POINTER;
04615   nsresult res;
04616   
04617   // gather list of table cells or list items
04618   nsCOMArray<nsIDOMNode> arrayOfNodes;
04619   nsTableCellAndListItemFunctor functor;
04620   nsDOMIterator iter;
04621   res = iter.Init(aNode);
04622   if (NS_FAILED(res)) return res;
04623   res = iter.AppendList(functor, arrayOfNodes);
04624   if (NS_FAILED(res)) return res;
04625   
04626   // now that we have the list, align their contents as requested
04627   PRInt32 listCount = arrayOfNodes.Count();
04628   PRInt32 j;
04629 
04630   for (j = 0; j < listCount; j++)
04631   {
04632     nsIDOMNode* node = arrayOfNodes[0];
04633     res = AlignBlockContents(node, alignType);
04634     if (NS_FAILED(res)) return res;
04635     arrayOfNodes.RemoveObjectAt(0);
04636   }
04637 
04638   return res;  
04639 }
04640 
04641 
04643 // AlignBlockContents: align contents of a block element
04644 //                  
04645 nsresult
04646 nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType)
04647 {
04648   if (!aNode || !alignType) return NS_ERROR_NULL_POINTER;
04649   nsresult res;
04650   nsCOMPtr <nsIDOMNode> firstChild, lastChild, divNode;
04651   
04652   PRBool useCSS;
04653   mHTMLEditor->GetIsCSSEnabled(&useCSS);
04654 
04655   res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild));
04656   if (NS_FAILED(res)) return res;
04657   res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
04658   if (NS_FAILED(res)) return res;
04659   NS_NAMED_LITERAL_STRING(attr, "align");
04660   if (!firstChild)
04661   {
04662     // this cell has no content, nothing to align
04663   }
04664   else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild))
04665   {
04666     // the cell already has a div containing all of it's content: just
04667     // act on this div.
04668     nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(firstChild);
04669     if (useCSS) {
04670       res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, PR_FALSE); 
04671     }
04672     else {
04673       res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
04674     }
04675     if (NS_FAILED(res)) return res;
04676   }
04677   else
04678   {
04679     // else we need to put in a div, set the alignment, and toss in all the children
04680     res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode));
04681     if (NS_FAILED(res)) return res;
04682     // set up the alignment on the div
04683     nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(divNode);
04684     if (useCSS) {
04685       res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, PR_FALSE); 
04686     }
04687     else {
04688       res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
04689     }
04690     if (NS_FAILED(res)) return res;
04691     // tuck the children into the end of the active div
04692     while (lastChild && (lastChild != divNode))
04693     {
04694       res = mHTMLEditor->MoveNode(lastChild, divNode, 0);
04695       if (NS_FAILED(res)) return res;
04696       res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
04697       if (NS_FAILED(res)) return res;
04698     }
04699   }
04700   return res;
04701 }
04702 
04704 // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle
04705 //                     case of deleting from inside an empty block.
04706 //                  
04707 nsresult
04708 nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode, 
04709                                     nsIDOMNode *aBodyNode,
04710                                     nsISelection *aSelection,
04711                                     PRBool *aHandled)
04712 {
04713   // if we are inside an empty block, delete it.
04714   // Note: do NOT delete table elements this way.
04715   nsresult res = NS_OK;
04716   nsCOMPtr<nsIDOMNode> block, emptyBlock;
04717   if (IsBlockNode(aStartNode)) 
04718     block = aStartNode;
04719   else
04720     block = mHTMLEditor->GetBlockNodeParent(aStartNode);
04721   PRBool bIsEmptyNode;
04722   if (block != aBodyNode)  // efficiency hack. avoiding IsEmptyNode() call when in body
04723   {
04724     res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
04725     if (NS_FAILED(res)) return res;
04726     while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode))
04727     {
04728       emptyBlock = block;
04729       block = mHTMLEditor->GetBlockNodeParent(emptyBlock);
04730       res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
04731       if (NS_FAILED(res)) return res;
04732     }
04733   }
04734   
04735   if (emptyBlock)
04736   {
04737     nsCOMPtr<nsIDOMNode> blockParent;
04738     PRInt32 offset;
04739     res = nsEditor::GetNodeLocation(emptyBlock, address_of(blockParent), &offset);
04740     if (NS_FAILED(res)) return res;
04741     if (!blockParent || offset < 0) return NS_ERROR_FAILURE;
04742 
04743     if (nsHTMLEditUtils::IsListItem(emptyBlock))
04744     {
04745       // are we the first list item in the list?
04746       PRBool bIsFirst;
04747       res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst);
04748       if (NS_FAILED(res)) return res;
04749       if (bIsFirst)
04750       {
04751         nsCOMPtr<nsIDOMNode> listParent;
04752         PRInt32 listOffset;
04753         res = nsEditor::GetNodeLocation(blockParent, address_of(listParent), &listOffset);
04754         if (NS_FAILED(res)) return res;
04755         if (!listParent || listOffset < 0) return NS_ERROR_FAILURE;
04756         // if we are a sublist, skip the br creation
04757         if (!nsHTMLEditUtils::IsList(listParent))
04758         {
04759           // create a br before list
04760           nsCOMPtr<nsIDOMNode> brNode;
04761           res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode));
04762           if (NS_FAILED(res)) return res;
04763           // adjust selection to be right before it
04764           res = aSelection->Collapse(listParent, listOffset);
04765           if (NS_FAILED(res)) return res;
04766         }
04767         // else just let selection perculate up.  We'll adjust it in AfterEdit()
04768       }
04769     }
04770     else
04771     {
04772       // adjust selection to be right after it
04773       res = aSelection->Collapse(blockParent, offset+1);
04774       if (NS_FAILED(res)) return res;
04775     }
04776     res = mHTMLEditor->DeleteNode(emptyBlock);
04777     *aHandled = PR_TRUE;
04778   }
04779   return res;
04780 }
04781 
04782 nsresult
04783 nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock, 
04784                                      BRLocation aWhere, 
04785                                      nsCOMPtr<nsIDOMNode> *outBRNode,
04786                                      PRInt32 aOffset)
04787 {
04788   if (!aBlock || !outBRNode) return NS_ERROR_NULL_POINTER;
04789   *outBRNode = nsnull;
04790 
04791   nsCOMPtr<nsIDOMNode> testNode;
04792   PRInt32 testOffset = 0;
04793   PRBool runTest = PR_FALSE;
04794 
04795   if (aWhere == kBlockEnd)
04796   {
04797     nsCOMPtr<nsIDOMNode> rightmostNode;
04798     rightmostNode = mHTMLEditor->GetRightmostChild(aBlock, PR_TRUE); // no block crossing
04799 
04800     if (rightmostNode)
04801     {
04802       nsCOMPtr<nsIDOMNode> nodeParent;
04803       PRInt32 nodeOffset;
04804 
04805       if (NS_SUCCEEDED(nsEditor::GetNodeLocation(rightmostNode,
04806                                                  address_of(nodeParent), 
04807                                                  &nodeOffset)))
04808       {
04809         runTest = PR_TRUE;
04810         testNode = nodeParent;
04811         // use offset + 1, because we want the last node included in our evaluation
04812         testOffset = nodeOffset + 1;
04813       }
04814     }
04815   }
04816   else if (aOffset)
04817   {
04818     runTest = PR_TRUE;
04819     testNode = aBlock;
04820     // we'll check everything to the left of the input position
04821     testOffset = aOffset;
04822   }
04823 
04824   if (runTest)
04825   {
04826     nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset);
04827     if (nsWSRunObject::eBreak == wsTester.mStartReason)
04828     {
04829       *outBRNode = wsTester.mStartReasonNode;
04830     }
04831   }
04832 
04833   return NS_OK;
04834 }
04835 
04836 
04838 // GetInnerContent: aList and aTbl allow the caller to specify what kind 
04839 //                  of content to "look inside".  If aTbl is true, look inside
04840 //                  any table content, and insert the inner content into the
04841 //                  supplied issupportsarray at offset aIndex.  
04842 //                  Similarly with aList and list content.
04843 //                  aIndex is updated to point past inserted elements.
04844 //                  
04845 nsresult
04846 nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray<nsIDOMNode> &outArrayOfNodes, 
04847                                  PRInt32 *aIndex, PRBool aList, PRBool aTbl)
04848 {
04849   if (!aNode || !aIndex) return NS_ERROR_NULL_POINTER;
04850 
04851   nsCOMPtr<nsIDOMNode> node;
04852   
04853   nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node));
04854   while (NS_SUCCEEDED(res) && node)
04855   {
04856     if (  ( aList && (nsHTMLEditUtils::IsList(node)     || 
04857                       nsHTMLEditUtils::IsListItem(node) ) )
04858        || ( aTbl && nsHTMLEditUtils::IsTableElement(node) )  )
04859     {
04860       res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl);
04861       if (NS_FAILED(res)) return res;
04862     }
04863     else
04864     {
04865       outArrayOfNodes.InsertObjectAt(node, *aIndex);
04866       (*aIndex)++;
04867     }
04868     nsCOMPtr<nsIDOMNode> tmp;
04869     res = node->GetNextSibling(getter_AddRefs(tmp));
04870     node = tmp;
04871   }
04872 
04873   return res;
04874 }
04875 
04877 // ExpandSelectionForDeletion: this promotes our selection to include blocks
04878 // that have all their children selected.
04879 //                  
04880 PRBool
04881 nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection)
04882 {
04883   if (!aSelection) 
04884     return NS_ERROR_NULL_POINTER;
04885   
04886   // don't need to touch collapsed selections
04887   PRBool bCollapsed;
04888   nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
04889   if (NS_FAILED(res)) return res;
04890   if (bCollapsed) return res;
04891 
04892   PRInt32 rangeCount;
04893   res = aSelection->GetRangeCount(&rangeCount);
04894   if (NS_FAILED(res)) return res;
04895   
04896   // we don't need to mess with cell selections, and we assume multirange selections are those.
04897   if (rangeCount != 1) return NS_OK;
04898   
04899   // find current sel start and end
04900   nsCOMPtr<nsIDOMRange> range;
04901   res = aSelection->GetRangeAt(0, getter_AddRefs(range));
04902   if (NS_FAILED(res)) return res;
04903   if (!range) return NS_ERROR_NULL_POINTER;
04904   nsCOMPtr<nsIDOMNode> selStartNode, selEndNode, selCommon;
04905   PRInt32 selStartOffset, selEndOffset;
04906   
04907   res = range->GetStartContainer(getter_AddRefs(selStartNode));
04908   if (NS_FAILED(res)) return res;
04909   res = range->GetStartOffset(&selStartOffset);
04910   if (NS_FAILED(res)) return res;
04911   res = range->GetEndContainer(getter_AddRefs(selEndNode));
04912   if (NS_FAILED(res)) return res;
04913   res = range->GetEndOffset(&selEndOffset);
04914   if (NS_FAILED(res)) return res;
04915 
04916   // find current selection common block parent
04917   res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon));
04918   if (NS_FAILED(res)) return res;
04919   if (!IsBlockNode(selCommon))
04920     selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon);
04921 
04922   // set up for loops and cache our root element
04923   PRBool stillLooking = PR_TRUE;
04924   nsCOMPtr<nsIDOMNode> visNode, firstBRParent;
04925   PRInt32 visOffset=0, firstBROffset=0;
04926   PRInt16 wsType;
04927   nsIDOMElement *rootElement = mHTMLEditor->GetRoot();
04928   if (!rootElement)
04929     return NS_ERROR_FAILURE;
04930 
04931   // find previous visible thingy before start of selection
04932   if ((selStartNode!=selCommon) && (selStartNode!=rootElement))
04933   {
04934     while (stillLooking)
04935     {
04936       nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
04937       res = wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode), &visOffset, &wsType);
04938       if (NS_FAILED(res)) return res;
04939       if (wsType == nsWSRunObject::eThisBlock)
04940       {
04941         // we want to keep looking up.  But stop if we are crossing table element
04942         // boundaries, or if we hit the root.
04943         if ( nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
04944             (selCommon == wsObj.mStartReasonNode)                    ||
04945             (rootElement == wsObj.mStartReasonNode) )
04946         {
04947           stillLooking = PR_FALSE;
04948         }
04949         else
04950         { 
04951           nsEditor::GetNodeLocation(wsObj.mStartReasonNode, address_of(selStartNode), &selStartOffset);
04952         }
04953       }
04954       else
04955       {
04956         stillLooking = PR_FALSE;
04957       }
04958     }
04959   }
04960   
04961   stillLooking = PR_TRUE;
04962   // find next visible thingy after end of selection
04963   if ((selEndNode!=selCommon) && (selEndNode!=rootElement))
04964   {
04965     while (stillLooking)
04966     {
04967       nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
04968       res = wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode), &visOffset, &wsType);
04969       if (NS_FAILED(res)) return res;
04970       if (wsType == nsWSRunObject::eBreak)
04971       {
04972         if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode))
04973         {
04974           stillLooking = PR_FALSE;
04975         }
04976         else
04977         { 
04978           if (!firstBRParent)
04979           {
04980             firstBRParent = selEndNode;
04981             firstBROffset = selEndOffset;
04982           }
04983           nsEditor::GetNodeLocation(wsObj.mEndReasonNode, address_of(selEndNode), &selEndOffset);
04984           ++selEndOffset;
04985         }
04986       }
04987       else if (wsType == nsWSRunObject::eThisBlock)
04988       {
04989         // we want to keep looking up.  But stop if we are crossing table element
04990         // boundaries, or if we hit the root.
04991         if ( nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
04992             (selCommon == wsObj.mEndReasonNode)                    ||
04993             (rootElement == wsObj.mEndReasonNode) )
04994         {
04995           stillLooking = PR_FALSE;
04996         }
04997         else
04998         { 
04999           nsEditor::GetNodeLocation(wsObj.mEndReasonNode, address_of(selEndNode), &selEndOffset);
05000           ++selEndOffset;
05001         }
05002        }
05003       else
05004       {
05005         stillLooking = PR_FALSE;
05006       }
05007     }
05008   }
05009   // now set the selection to the new range
05010   aSelection->Collapse(selStartNode, selStartOffset);
05011   
05012   // expand selection endpoint only if we didnt pass a br,
05013   // or if we really needed to pass that br (ie, it's block is now 
05014   // totally selected)
05015   PRBool doEndExpansion = PR_TRUE;
05016   if (firstBRParent)
05017   {
05018     // find block node containing br
05019     nsCOMPtr<nsIDOMNode> brBlock = firstBRParent;
05020     if (!IsBlockNode(brBlock))
05021       brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock);
05022     PRBool nodeBefore=PR_FALSE, nodeAfter=PR_FALSE;
05023     
05024     // create a range that represents expanded selection
05025     nsCOMPtr<nsIDOMRange> range = do_CreateInstance("@mozilla.org/content/range;1");
05026     if (!range) return NS_ERROR_NULL_POINTER;
05027     res = range->SetStart(selStartNode, selStartOffset);
05028     if (NS_FAILED(res)) return res;
05029     res = range->SetEnd(selEndNode, selEndOffset);
05030     if (NS_FAILED(res)) return res;
05031     
05032     // check if block is entirely inside range
05033     nsCOMPtr<nsIContent> brContentBlock = do_QueryInterface(brBlock);
05034     res = mHTMLEditor->sRangeHelper->CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter);
05035     
05036     // if block isn't contained, forgo grabbing the br in the expanded selection
05037     if (nodeBefore || nodeAfter)
05038       doEndExpansion = PR_FALSE;
05039   }
05040   if (doEndExpansion)
05041   {
05042     res = aSelection->Extend(selEndNode, selEndOffset);
05043   }
05044   else
05045   {
05046     // only expand to just before br
05047     res = aSelection->Extend(firstBRParent, firstBROffset);
05048   }
05049   
05050   return res;
05051 }
05052 
05053 #ifdef XXX_DEAD_CODE
05054 
05055 // AtStartOfBlock: is node/offset at the start of the editable material in this block?
05056 //                  
05057 PRBool
05058 nsHTMLEditRules::AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
05059 {
05060   nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
05061   if (nodeAsText && aOffset) return PR_FALSE;  // there are chars in front of us
05062   
05063   nsCOMPtr<nsIDOMNode> priorNode;
05064   nsresult  res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(priorNode));
05065   if (NS_FAILED(res)) return PR_TRUE;
05066   if (!priorNode) return PR_TRUE;
05067   nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(priorNode);
05068   if (blockParent && (blockParent == aBlock)) return PR_FALSE;
05069   return PR_TRUE;
05070 }
05071 
05072 
05074 // AtEndOfBlock: is node/offset at the end of the editable material in this block?
05075 //                  
05076 PRBool
05077 nsHTMLEditRules::AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
05078 {
05079   nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
05080   if (nodeAsText)   
05081   {
05082     PRUint32 strLength;
05083     nodeAsText->GetLength(&strLength);
05084     if ((PRInt32)strLength > aOffset) return PR_FALSE;  // there are chars in after us
05085   }
05086   nsCOMPtr<nsIDOMNode> nextNode;
05087   nsresult  res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nextNode));
05088   if (NS_FAILED(res)) return PR_TRUE;
05089   if (!nextNode) return PR_TRUE;
05090   nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(nextNode);
05091   if (blockParent && (blockParent == aBlock)) return PR_FALSE;
05092   return PR_TRUE;
05093 }
05094 
05095 
05097 // CreateMozDiv: makes a div with type = _moz
05098 //                       
05099 nsresult
05100 nsHTMLEditRules::CreateMozDiv(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outDiv)
05101 {
05102   if (!inParent || !outDiv) return NS_ERROR_NULL_POINTER;
05103   nsAutoString divType= "div";
05104   *outDiv = nsnull;
05105   nsresult res = mHTMLEditor->CreateNode(divType, inParent, inOffset, getter_AddRefs(*outDiv));
05106   if (NS_FAILED(res)) return res;
05107   // give it special moz attr
05108   nsCOMPtr<nsIDOMElement> mozDivElem = do_QueryInterface(*outDiv);
05109   res = mHTMLEditor->SetAttribute(mozDivElem, "type", "_moz");
05110   if (NS_FAILED(res)) return res;
05111   res = AddTrailerBR(*outDiv);
05112   return res;
05113 }
05114 #endif    
05115 
05116 
05118 // NormalizeSelection:  tweak non-collapsed selections to be more "natural".
05119 //    Idea here is to adjust selection endpoint so that they do not cross
05120 //    breaks or block boundaries unless something editable beyond that boundary
05121 //    is also selected.  This adjustment makes it much easier for the various
05122 //    block operations to determine what nodes to act on.
05123 //                       
05124 nsresult 
05125 nsHTMLEditRules::NormalizeSelection(nsISelection *inSelection)
05126 {
05127   if (!inSelection) return NS_ERROR_NULL_POINTER;
05128 
05129   // don't need to touch collapsed selections
05130   PRBool bCollapsed;
05131   nsresult res = inSelection->GetIsCollapsed(&bCollapsed);
05132   if (NS_FAILED(res)) return res;
05133   if (bCollapsed) return res;
05134 
05135   PRInt32 rangeCount;
05136   res = inSelection->GetRangeCount(&rangeCount);
05137   if (NS_FAILED(res)) return res;
05138   
05139   // we don't need to mess with cell selections, and we assume multirange selections are those.
05140   if (rangeCount != 1) return NS_OK;
05141   
05142   nsCOMPtr<nsIDOMRange> range;
05143   res = inSelection->GetRangeAt(0, getter_AddRefs(range));
05144   if (NS_FAILED(res)) return res;
05145   if (!range) return NS_ERROR_NULL_POINTER;
05146   nsCOMPtr<nsIDOMNode> startNode, endNode;
05147   PRInt32 startOffset, endOffset;
05148   nsCOMPtr<nsIDOMNode> newStartNode, newEndNode;
05149   PRInt32 newStartOffset, newEndOffset;
05150   
05151   res = range->GetStartContainer(getter_AddRefs(startNode));
05152   if (NS_FAILED(res)) return res;
05153   res = range->GetStartOffset(&startOffset);
05154   if (NS_FAILED(res)) return res;
05155   res = range->GetEndContainer(getter_AddRefs(endNode));
05156   if (NS_FAILED(res)) return res;
05157   res = range->GetEndOffset(&endOffset);
05158   if (NS_FAILED(res)) return res;
05159   
05160   // adjusted values default to original values
05161   newStartNode = startNode; 
05162   newStartOffset = startOffset;
05163   newEndNode = endNode; 
05164   newEndOffset = endOffset;
05165   
05166   // some locals we need for whitespace code
05167   nsCOMPtr<nsIDOMNode> someNode;
05168   PRInt32 offset;
05169   PRInt16 wsType;
05170 
05171   // let the whitespace code do the heavy lifting
05172   nsWSRunObject wsEndObj(mHTMLEditor, endNode, endOffset);
05173   // is there any intervening visible whitespace?  if so we can't push selection past that,
05174   // it would visibly change maening of users selection
05175   res = wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode), &offset, &wsType);
05176   if (NS_FAILED(res)) return res;
05177   if ((wsType != nsWSRunObject::eText) && (wsType != nsWSRunObject::eNormalWS))
05178   {
05179     // eThisBlock and eOtherBlock conveniently distinquish cases
05180     // of going "down" into a block and "up" out of a block.
05181     if (wsEndObj.mStartReason == nsWSRunObject::eOtherBlock) 
05182     {
05183       // endpoint is just after the close of a block.
05184       nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, PR_TRUE);
05185       if (child)
05186       {
05187         res = nsEditor::GetNodeLocation(child, address_of(newEndNode), &newEndOffset);
05188         if (NS_FAILED(res)) return res;
05189         ++newEndOffset; // offset *after* child
05190       }
05191       // else block is empty - we can leave selection alone here, i think.
05192     }
05193     else if (wsEndObj.mStartReason == nsWSRunObject::eThisBlock)
05194     {
05195       // endpoint is just after start of this block
05196       nsCOMPtr<nsIDOMNode> child;
05197       res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child));
05198       if (child)
05199       {
05200         res = nsEditor::GetNodeLocation(child, address_of(newEndNode), &newEndOffset);
05201         if (NS_FAILED(res)) return res;
05202         ++newEndOffset; // offset *after* child
05203       }
05204       // else block is empty - we can leave selection alone here, i think.
05205     }
05206     else if (wsEndObj.mStartReason == nsWSRunObject::eBreak)
05207     {                                       
05208       // endpoint is just after break.  lets adjust it to before it.
05209       res = nsEditor::GetNodeLocation(wsEndObj.mStartReasonNode, address_of(newEndNode), &newEndOffset);
05210       if (NS_FAILED(res)) return res;
05211     }
05212   }
05213   
05214   
05215   // similar dealio for start of range
05216   nsWSRunObject wsStartObj(mHTMLEditor, startNode, startOffset);
05217   // is there any intervening visible whitespace?  if so we can't push selection past that,
05218   // it would visibly change maening of users selection
05219   res = wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode), &offset, &wsType);
05220   if (NS_FAILED(res)) return res;
05221   if ((wsType != nsWSRunObject::eText) && (wsType != nsWSRunObject::eNormalWS))
05222   {
05223     // eThisBlock and eOtherBlock conveniently distinquish cases
05224     // of going "down" into a block and "up" out of a block.
05225     if (wsStartObj.mEndReason == nsWSRunObject::eOtherBlock) 
05226     {
05227       // startpoint is just before the start of a block.
05228       nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, PR_TRUE);
05229       if (child)
05230       {
05231         res = nsEditor::GetNodeLocation(child, address_of(newStartNode), &newStartOffset);
05232         if (NS_FAILED(res)) return res;
05233       }
05234       // else block is empty - we can leave selection alone here, i think.
05235     }
05236     else if (wsStartObj.mEndReason == nsWSRunObject::eThisBlock)
05237     {
05238       // startpoint is just before end of this block
05239       nsCOMPtr<nsIDOMNode> child;
05240       res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child));
05241       if (child)
05242       {
05243         res = nsEditor::GetNodeLocation(child, address_of(newStartNode), &newStartOffset);
05244         if (NS_FAILED(res)) return res;
05245       }
05246       // else block is empty - we can leave selection alone here, i think.
05247     }
05248     else if (wsStartObj.mEndReason == nsWSRunObject::eBreak)
05249     {                                       
05250       // startpoint is just before a break.  lets adjust it to after it.
05251       res = nsEditor::GetNodeLocation(wsStartObj.mEndReasonNode, address_of(newStartNode), &newStartOffset);
05252       if (NS_FAILED(res)) return res;
05253       ++newStartOffset; // offset *after* break
05254     }
05255   }
05256   
05257   // there is a demented possiblity we have to check for.  We might have a very strange selection
05258   // that is not collapsed and yet does not contain any editable content, and satisfies some of the
05259   // above conditions that cause tweaking.  In this case we don't want to tweak the selection into
05260   // a block it was never in, etc.  There are a variety of strategies one might use to try to
05261   // detect these cases, but I think the most straightforward is to see if the adjusted locations
05262   // "cross" the old values: ie, new end before old start, or new start after old end.  If so 
05263   // then just leave things alone.
05264   
05265   PRInt16 comp;
05266   comp = mHTMLEditor->sRangeHelper->ComparePoints(startNode, startOffset, newEndNode, newEndOffset);
05267   if (comp == 1) return NS_OK;  // new end before old start
05268   comp = mHTMLEditor->sRangeHelper->ComparePoints(newStartNode, newStartOffset, endNode, endOffset);
05269   if (comp == 1) return NS_OK;  // new start after old end
05270   
05271   // otherwise set selection to new values.  
05272   inSelection->Collapse(newStartNode, newStartOffset);
05273   inSelection->Extend(newEndNode, newEndOffset);
05274   return NS_OK;
05275 }
05276 
05277 
05279 // GetPromotedPoint: figure out where a start or end point for a block
05280 //                   operation really is
05281 nsresult
05282 nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt32 aOffset, 
05283                                   PRInt32 actionID, nsCOMPtr<nsIDOMNode> *outNode, PRInt32 *outOffset)
05284 {
05285   nsresult res = NS_OK;
05286   nsCOMPtr<nsIDOMNode> nearNode, node = aNode;
05287   nsCOMPtr<nsIDOMNode> parent = aNode;
05288   PRInt32 pOffset, offset = aOffset;
05289   
05290   // defualt values
05291   *outNode = node;
05292   *outOffset = offset;
05293 
05294   // we do one thing for InsertText actions, something else entirely for other actions
05295   if (actionID == kInsertText)
05296   {
05297     PRBool isSpace, isNBSP; 
05298     nsCOMPtr<nsIDOMNode> temp;   
05299     // for insert text or delete actions, we want to look backwards (or forwards, as appropriate)
05300     // for additional whitespace or nbsp's.  We may have to act on these later even though
05301     // they are outside of the initial selection.  Even if they are in another node!
05302     if (aWhere == kStart)
05303     {
05304       do
05305       {
05306         res = mHTMLEditor->IsPrevCharWhitespace(node, offset, &isSpace, &isNBSP, address_of(temp), &offset);
05307         if (NS_FAILED(res)) return res;
05308         if (isSpace || isNBSP) node = temp;
05309         else break;
05310       } while (node);
05311   
05312       *outNode = node;
05313       *outOffset = offset;
05314     }
05315     else if (aWhere == kEnd)
05316     {
05317       do
05318       {
05319         res = mHTMLEditor->IsNextCharWhitespace(node, offset, &isSpace, &isNBSP, address_of(temp), &offset);
05320         if (NS_FAILED(res)) return res;
05321         if (isSpace || isNBSP) node = temp;
05322         else break;
05323       } while (node);
05324   
05325       *outNode = node;
05326       *outOffset = offset;
05327     }
05328     return res;
05329   }
05330   
05331   // else not kInsertText.  In this case we want to see if we should
05332   // grab any adjacent inline nodes and/or parents and other ancestors
05333   if (aWhere == kStart)
05334   {
05335     // some special casing for text nodes
05336     if (nsEditor::IsTextNode(aNode))  
05337     {
05338       res = nsEditor::GetNodeLocation(aNode, address_of(node), &offset);
05339       if (NS_FAILED(res)) return res;
05340     }
05341 
05342     // look back through any further inline nodes that
05343     // aren't across a <br> from us, and that are enclosed in the same block.
05344     nsCOMPtr<nsIDOMNode> priorNode;
05345     res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(priorNode), PR_TRUE);
05346       
05347     while (priorNode && NS_SUCCEEDED(res))
05348     {
05349       if (mHTMLEditor->IsVisBreak(priorNode))
05350         break;
05351       if (IsBlockNode(priorNode))
05352         break;
05353       res = nsEditor::GetNodeLocation(priorNode, address_of(node), &offset);
05354       if (NS_FAILED(res)) return res;
05355       res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(priorNode), PR_TRUE);
05356       if (NS_FAILED(res)) return res;
05357     }
05358     
05359         
05360     // finding the real start for this point.  look up the tree for as long as we are the 
05361     // first node in the container, and as long as we haven't hit the body node.
05362     res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
05363     if (NS_FAILED(res)) return res;
05364     while (!nearNode && !nsTextEditUtils::IsBody(node))
05365     {
05366       // some cutoffs are here: we don't need to also include them in the aWhere == kEnd case.
05367       // as long as they are in one or the other it will work.
05368       // special case for outdent: don't keep looking up 
05369       // if we have found a blockquote element to act on
05370       if ((actionID == kOutdent) && nsHTMLEditUtils::IsBlockquote(node))
05371         break;
05372 
05373       res = nsEditor::GetNodeLocation(node, address_of(parent), &pOffset);
05374       if (NS_FAILED(res)) return res;
05375       node = parent;
05376       offset = pOffset;
05377       res = mHTMLEditor->GetPriorHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
05378       if (NS_FAILED(res)) return res;
05379     } 
05380     *outNode = node;
05381     *outOffset = offset;
05382     return res;
05383   }
05384   
05385   if (aWhere == kEnd)
05386   {
05387     // some special casing for text nodes
05388     if (nsEditor::IsTextNode(aNode))  
05389     {
05390       res = nsEditor::GetNodeLocation(aNode, address_of(node), &offset);
05391       if (NS_FAILED(res)) return res;
05392       offset++; // want to be after the text node
05393     }
05394 
05395     // look ahead through any further inline nodes that
05396     // aren't across a <br> from us, and that are enclosed in the same block.
05397     nsCOMPtr<nsIDOMNode> nextNode;
05398     res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nextNode), PR_TRUE);
05399       
05400     while (nextNode && NS_SUCCEEDED(res))
05401     {
05402       if (IsBlockNode(nextNode))
05403         break;
05404       res = nsEditor::GetNodeLocation(nextNode, address_of(node), &offset);
05405       if (NS_FAILED(res)) return res;
05406       offset++;
05407       if (mHTMLEditor->IsVisBreak(nextNode))
05408         break;
05409       res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nextNode), PR_TRUE);
05410       if (NS_FAILED(res)) return res;
05411     }
05412     
05413     // finding the real end for this point.  look up the tree for as long as we are the 
05414     // last node in the container, and as long as we haven't hit the body node.
05415     res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
05416     if (NS_FAILED(res)) return res;
05417     while (!nearNode && !nsTextEditUtils::IsBody(node))
05418     {
05419       res = nsEditor::GetNodeLocation(node, address_of(parent), &pOffset);
05420       if (NS_FAILED(res)) return res;
05421       node = parent;
05422       offset = pOffset+1;  // we want to be AFTER nearNode
05423       res = mHTMLEditor->GetNextHTMLNode(node, offset, address_of(nearNode), PR_TRUE);
05424       if (NS_FAILED(res)) return res;
05425     } 
05426     *outNode = node;
05427     *outOffset = offset;
05428     return res;
05429   }
05430   
05431   return res;
05432 }
05433 
05434 
05436 // GetPromotedRanges: run all the selection range endpoint through 
05437 //                    GetPromotedPoint()
05438 //                       
05439 nsresult 
05440 nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection, 
05441                                    nsCOMArray<nsIDOMRange> &outArrayOfRanges, 
05442                                    PRInt32 inOperationType)
05443 {
05444   if (!inSelection) return NS_ERROR_NULL_POINTER;
05445 
05446   PRInt32 rangeCount;
05447   nsresult res = inSelection->GetRangeCount(&rangeCount);
05448   if (NS_FAILED(res)) return res;
05449   
05450   PRInt32 i;
05451   nsCOMPtr<nsIDOMRange> selectionRange;
05452   nsCOMPtr<nsIDOMRange> opRange;
05453 
05454   for (i = 0; i < rangeCount; i++)
05455   {
05456     res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange));
05457     if (NS_FAILED(res)) return res;
05458 
05459     // clone range so we dont muck with actual selection ranges
05460     res = selectionRange->CloneRange(getter_AddRefs(opRange));
05461     if (NS_FAILED(res)) return res;
05462 
05463     // make a new adjusted range to represent the appropriate block content.
05464     // The basic idea is to push out the range endpoints
05465     // to truly enclose the blocks that we will affect.
05466     // This call alters opRange.
05467     res = PromoteRange(opRange, inOperationType);
05468     if (NS_FAILED(res)) return res;
05469       
05470     // stuff new opRange into array
05471     outArrayOfRanges.AppendObject(opRange);
05472   }
05473   return res;
05474 }
05475 
05476 
05478 // PromoteRange: expand a range to include any parents for which all
05479 //               editable children are already in range. 
05480 //                       
05481 nsresult 
05482 nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, 
05483                               PRInt32 inOperationType)
05484 {
05485   if (!inRange) return NS_ERROR_NULL_POINTER;
05486   nsresult res;
05487   nsCOMPtr<nsIDOMNode> startNode, endNode;
05488   PRInt32 startOffset, endOffset;
05489   
05490   res = inRange->GetStartContainer(getter_AddRefs(startNode));
05491   if (NS_FAILED(res)) return res;
05492   res = inRange->GetStartOffset(&startOffset);
05493   if (NS_FAILED(res)) return res;
05494   res = inRange->GetEndContainer(getter_AddRefs(endNode));
05495   if (NS_FAILED(res)) return res;
05496   res = inRange->GetEndOffset(&endOffset);
05497   if (NS_FAILED(res)) return res;
05498   
05499   // MOOSE major hack:
05500   // GetPromotedPoint doesn't really do the right thing for collapsed ranges
05501   // inside block elements that contain nothing but a solo <br>.  It's easier
05502   // to put a workaround here than to revamp GetPromotedPoint.  :-(
05503   if ( (startNode == endNode) && (startOffset == endOffset))
05504   {
05505     nsCOMPtr<nsIDOMNode> block;
05506     if (IsBlockNode(startNode)) 
05507       block = startNode;
05508     else
05509       block = mHTMLEditor->GetBlockNodeParent(startNode);
05510     if (block)
05511     {
05512       PRBool bIsEmptyNode = PR_FALSE;
05513       // check for body
05514       nsIDOMElement *rootElement = mHTMLEditor->GetRoot();
05515       if (!rootElement) return NS_ERROR_UNEXPECTED;
05516       nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(rootElement);
05517       if (block != rootNode)
05518       {
05519         // ok, not body, check if empty
05520         res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
05521       }
05522       if (bIsEmptyNode)
05523       {
05524         PRUint32 numChildren;
05525         nsEditor::GetLengthOfDOMNode(block, numChildren); 
05526         startNode = block;
05527         endNode = block;
05528         startOffset = 0;
05529         endOffset = numChildren;
05530       }
05531     }
05532   }
05533 
05534   // make a new adjusted range to represent the appropriate block content.
05535   // this is tricky.  the basic idea is to push out the range endpoints
05536   // to truly enclose the blocks that we will affect
05537   
05538   nsCOMPtr<nsIDOMNode> opStartNode;
05539   nsCOMPtr<nsIDOMNode> opEndNode;
05540   PRInt32 opStartOffset, opEndOffset;
05541   nsCOMPtr<nsIDOMRange> opRange;
05542   
05543   res = GetPromotedPoint( kStart, startNode, startOffset, inOperationType, address_of(opStartNode), &opStartOffset);
05544   if (NS_FAILED(res)) return res;
05545   res = GetPromotedPoint( kEnd, endNode, endOffset, inOperationType, address_of(opEndNode), &opEndOffset);
05546   if (NS_FAILED(res)) return res;
05547   res = inRange->SetStart(opStartNode, opStartOffset);
05548   if (NS_FAILED(res)) return res;
05549   res = inRange->SetEnd(opEndNode, opEndOffset);
05550   return res;
05551 } 
05552 
05553 class nsUniqueFunctor : public nsBoolDomIterFunctor
05554 {
05555 public:
05556   nsUniqueFunctor(nsCOMArray<nsIDOMNode> &aArray) : mArray(aArray)
05557   {
05558   }
05559   virtual PRBool operator()(nsIDOMNode* aNode)  // used to build list of all nodes iterator covers
05560   {
05561     return mArray.IndexOf(aNode) < 0;
05562   }
05563 
05564 private:
05565   nsCOMArray<nsIDOMNode> &mArray;
05566 };
05567 
05569 // GetNodesForOperation: run through the ranges in the array and construct 
05570 //                       a new array of nodes to be acted on.
05571 //                       
05572 nsresult 
05573 nsHTMLEditRules::GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges, 
05574                                       nsCOMArray<nsIDOMNode>& outArrayOfNodes, 
05575                                       PRInt32 inOperationType,
05576                                       PRBool aDontTouchContent)
05577 {
05578   PRInt32 rangeCount = inArrayOfRanges.Count();
05579   
05580   PRInt32 i;
05581   nsCOMPtr<nsIDOMRange> opRange;
05582 
05583   PRBool useCSS;
05584   mHTMLEditor->GetIsCSSEnabled(&useCSS);
05585 
05586   nsresult res = NS_OK;
05587   
05588   // bust up any inlines that cross our range endpoints,
05589   // but only if we are allowed to touch content.
05590   
05591   if (!aDontTouchContent)
05592   {
05593     nsVoidArray rangeItemArray;
05594     // first register ranges for special editor gravity
05595     // XXXbz doesn't this leak all the nsRangeStore structs on error
05596     // conditions??
05597     for (i = 0; i < (PRInt32)rangeCount; i++)
05598     {
05599       opRange = inArrayOfRanges[0];
05600       nsRangeStore *item = new nsRangeStore();
05601       if (!item) return NS_ERROR_NULL_POINTER;
05602       item->StoreRange(opRange);
05603       mHTMLEditor->mRangeUpdater.RegisterRangeItem(item);
05604       rangeItemArray.AppendElement((void*)item);
05605       inArrayOfRanges.RemoveObjectAt(0);
05606     }    
05607     // now bust up inlines
05608     for (i = rangeCount-1; i >= 0; i--)
05609     {
05610       nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(i);
05611       res = BustUpInlinesAtRangeEndpoints(*item);
05612       if (NS_FAILED(res)) return res;    
05613     } 
05614     // then unregister the ranges
05615     for (i = 0; i < rangeCount; i++)
05616     {
05617       nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(0);
05618       if (!item) return NS_ERROR_NULL_POINTER;
05619       rangeItemArray.RemoveElementAt(0);
05620       mHTMLEditor->mRangeUpdater.DropRangeItem(item);
05621       res = item->GetRange(address_of(opRange));
05622       if (NS_FAILED(res)) return res;
05623       delete item;
05624       inArrayOfRanges.AppendObject(opRange);
05625     }    
05626   }
05627   // gather up a list of all the nodes
05628   for (i = 0; i < rangeCount; i++)
05629   {
05630     opRange = inArrayOfRanges[i];
05631     
05632     nsDOMSubtreeIterator iter;
05633     res = iter.Init(opRange);
05634     if (NS_FAILED(res)) return res;
05635     if (outArrayOfNodes.Count() == 0) {
05636       nsTrivialFunctor functor;
05637       res = iter.AppendList(functor, outArrayOfNodes);
05638       if (NS_FAILED(res)) return res;    
05639     }
05640     else {
05641       nsCOMArray<nsIDOMNode> nodes;
05642       nsUniqueFunctor functor(outArrayOfNodes);
05643       res = iter.AppendList(functor, nodes);
05644       if (NS_FAILED(res)) return res;
05645       if (!outArrayOfNodes.AppendObjects(nodes))
05646         return NS_ERROR_OUT_OF_MEMORY;
05647     }
05648   }    
05649 
05650   // certain operations should not act on li's and td's, but rather inside 
05651   // them.  alter the list as needed
05652   if (inOperationType == kMakeBasicBlock)
05653   {
05654     PRInt32 listCount = outArrayOfNodes.Count();
05655     for (i=listCount-1; i>=0; i--)
05656     {
05657       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
05658       if (nsHTMLEditUtils::IsListItem(node))
05659       {
05660         PRInt32 j=i;
05661         outArrayOfNodes.RemoveObjectAt(i);
05662         res = GetInnerContent(node, outArrayOfNodes, &j);
05663         if (NS_FAILED(res)) return res;
05664       }
05665     }
05666   }
05667   // indent/outdent already do something special for list items, but
05668   // we still need to make sure we dont act on table elements
05669   else if ( (inOperationType == kOutdent)  ||
05670             (inOperationType == kIndent)   ||
05671             (inOperationType == kSetAbsolutePosition))
05672   {
05673     PRInt32 listCount = outArrayOfNodes.Count();
05674     for (i=listCount-1; i>=0; i--)
05675     {
05676       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
05677       if (nsHTMLEditUtils::IsTableElementButNotTable(node))
05678       {
05679         PRInt32 j=i;
05680         outArrayOfNodes.RemoveObjectAt(i);
05681         res = GetInnerContent(node, outArrayOfNodes, &j);
05682         if (NS_FAILED(res)) return res;
05683       }
05684     }
05685   }
05686   // outdent should look inside of divs.
05687   if (inOperationType == kOutdent && !useCSS) 
05688   {
05689     PRInt32 listCount = outArrayOfNodes.Count();
05690     for (i=listCount-1; i>=0; i--)
05691     {
05692       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
05693       if (nsHTMLEditUtils::IsDiv(node))
05694       {
05695         PRInt32 j=i;
05696         outArrayOfNodes.RemoveObjectAt(i);
05697         res = GetInnerContent(node, outArrayOfNodes, &j, PR_FALSE, PR_FALSE);
05698         if (NS_FAILED(res)) return res;
05699       }
05700     }
05701   }
05702 
05703 
05704   // post process the list to break up inline containers that contain br's.
05705   // but only for operations that might care, like making lists or para's...
05706   if ( (inOperationType == kMakeBasicBlock)   ||
05707        (inOperationType == kMakeList)         ||
05708        (inOperationType == kAlign)            ||
05709        (inOperationType == kSetAbsolutePosition) ||
05710        (inOperationType == kIndent)           ||
05711        (inOperationType == kOutdent) )
05712   {
05713     PRInt32 listCount = outArrayOfNodes.Count();
05714     for (i=listCount-1; i>=0; i--)
05715     {
05716       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
05717       if (!aDontTouchContent && IsInlineNode(node) 
05718            && mHTMLEditor->IsContainer(node) && !mHTMLEditor->IsTextNode(node))
05719       {
05720         nsCOMArray<nsIDOMNode> arrayOfInlines;
05721         res = BustUpInlinesAtBRs(node, arrayOfInlines);
05722         if (NS_FAILED(res)) return res;
05723         // put these nodes in outArrayOfNodes, replacing the current node
05724         outArrayOfNodes.RemoveObjectAt(i);
05725         outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i);
05726       }
05727     }
05728   }
05729   return res;
05730 }
05731 
05732 
05733 
05735 // GetChildNodesForOperation: 
05736 //                       
05737 nsresult 
05738 nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, 
05739                                            nsCOMArray<nsIDOMNode>& outArrayOfNodes)
05740 {
05741   if (!inNode) return NS_ERROR_NULL_POINTER;
05742   
05743   nsCOMPtr<nsIDOMNodeList> childNodes;
05744   nsresult res = inNode->GetChildNodes(getter_AddRefs(childNodes));
05745   if (NS_FAILED(res)) return res;
05746   if (!childNodes) return NS_ERROR_NULL_POINTER;
05747   PRUint32 childCount;
05748   res = childNodes->GetLength(&childCount);
05749   if (NS_FAILED(res)) return res;
05750   
05751   PRUint32 i;
05752   nsCOMPtr<nsIDOMNode> node;
05753   for (i = 0; i < childCount; i++)
05754   {
05755     res = childNodes->Item( i, getter_AddRefs(node));
05756     if (!node) return NS_ERROR_FAILURE;
05757     if (!outArrayOfNodes.AppendObject(node))
05758       return NS_ERROR_FAILURE;
05759   }
05760   return res;
05761 }
05762 
05763 
05764 
05766 // GetListActionNodes: 
05767 //                       
05768 nsresult 
05769 nsHTMLEditRules::GetListActionNodes(nsCOMArray<nsIDOMNode> &outArrayOfNodes, 
05770                                     PRBool aEntireList,
05771                                     PRBool aDontTouchContent)
05772 {
05773   nsresult res = NS_OK;
05774   
05775   nsCOMPtr<nsISelection>selection;
05776   res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
05777   if (NS_FAILED(res)) return res;
05778   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
05779   if (!selPriv)
05780     return NS_ERROR_FAILURE;
05781   // added this in so that ui code can ask to change an entire list, even if selection
05782   // is only in part of it.  used by list item dialog.
05783   if (aEntireList)
05784   {       
05785     nsCOMPtr<nsIEnumerator> enumerator;
05786     res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
05787     if (NS_FAILED(res)) return res;
05788     if (!enumerator) return NS_ERROR_UNEXPECTED;
05789 
05790     for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next())
05791     {
05792       nsCOMPtr<nsISupports> currentItem;
05793       res = enumerator->CurrentItem(getter_AddRefs(currentItem));
05794       if (NS_FAILED(res)) return res;
05795       if (!currentItem) return NS_ERROR_UNEXPECTED;
05796 
05797       nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
05798       nsCOMPtr<nsIDOMNode> commonParent, parent, tmp;
05799       range->GetCommonAncestorContainer(getter_AddRefs(commonParent));
05800       if (commonParent)
05801       {
05802         parent = commonParent;
05803         while (parent)
05804         {
05805           if (nsHTMLEditUtils::IsList(parent))
05806           {
05807             outArrayOfNodes.AppendObject(parent);
05808             break;
05809           }
05810           parent->GetParentNode(getter_AddRefs(tmp));
05811           parent = tmp;
05812         }
05813       }
05814     }
05815     // if we didn't find any nodes this way, then try the normal way.  perhaps the
05816     // selection spans multiple lists but with no common list parent.
05817     if (outArrayOfNodes.Count()) return NS_OK;
05818   }
05819   
05820   // contruct a list of nodes to act on.
05821   res = GetNodesFromSelection(selection, kMakeList, outArrayOfNodes, aDontTouchContent);
05822   if (NS_FAILED(res)) return res;                                 
05823                
05824   // pre process our list of nodes...                      
05825   PRInt32 listCount = outArrayOfNodes.Count();
05826   PRInt32 i;
05827   for (i=listCount-1; i>=0; i--)
05828   {
05829     nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
05830 
05831     // Remove all non-editable nodes.  Leave them be.
05832     if (!mHTMLEditor->IsEditable(testNode))
05833     {
05834       outArrayOfNodes.RemoveObjectAt(i);
05835     }
05836     
05837     // scan for table elements and divs.  If we find table elements other than table,
05838     // replace it with a list of any editable non-table content.
05839     if (nsHTMLEditUtils::IsTableElementButNotTable(testNode))
05840     {
05841       PRInt32 j=i;
05842       outArrayOfNodes.RemoveObjectAt(i);
05843       res = GetInnerContent(testNode, outArrayOfNodes, &j, PR_FALSE);
05844       if (NS_FAILED(res)) return res;
05845     }
05846   }
05847 
05848   // if there is only one node in the array, and it is a list, div, or blockquote,
05849   // then look inside of it until we find inner list or content.
05850   res = LookInsideDivBQandList(outArrayOfNodes);
05851   return res;
05852 }
05853 
05854 
05856 // LookInsideDivBQandList: 
05857 //                       
05858 nsresult 
05859 nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray<nsIDOMNode>& aNodeArray)
05860 {
05861   // if there is only one node in the array, and it is a list, div, or blockquote,
05862   // then look inside of it until we find inner list or content.
05863   nsresult res = NS_OK;
05864   PRInt32 listCount = aNodeArray.Count();
05865   if (listCount == 1)
05866   {
05867     nsCOMPtr<nsIDOMNode> curNode = aNodeArray[0];
05868     
05869     while (nsHTMLEditUtils::IsDiv(curNode)
05870            || nsHTMLEditUtils::IsList(curNode)
05871            || nsHTMLEditUtils::IsBlockquote(curNode))
05872     {
05873       // dive as long as there is only one child, and it is a list, div, blockquote
05874       PRUint32 numChildren;
05875       res = mHTMLEditor->CountEditableChildren(curNode, numChildren);
05876       if (NS_FAILED(res)) return res;
05877       
05878       if (numChildren == 1)
05879       {
05880         // keep diving
05881         nsCOMPtr <nsIDOMNode> tmpNode = nsEditor::GetChildAt(curNode, 0);
05882         if (nsHTMLEditUtils::IsDiv(tmpNode)
05883             || nsHTMLEditUtils::IsList(tmpNode)
05884             || nsHTMLEditUtils::IsBlockquote(tmpNode))
05885         {
05886           // check editablility XXX floppy moose
05887           curNode = tmpNode;
05888         }
05889         else break;
05890       }
05891       else break;
05892     }
05893     // we've found innermost list/blockquote/div: 
05894     // replace the one node in the array with these nodes
05895     aNodeArray.RemoveObjectAt(0);
05896     if ((nsHTMLEditUtils::IsDiv(curNode) || nsHTMLEditUtils::IsBlockquote(curNode)))
05897     {
05898       PRInt32 j=0;
05899       res = GetInnerContent(curNode, aNodeArray, &j, PR_FALSE, PR_FALSE);
05900     }
05901     else
05902     {
05903       aNodeArray.AppendObject(curNode);
05904     }
05905   }
05906   return res;
05907 }
05908 
05909 
05911 // GetDefinitionListItemTypes: 
05912 //                       
05913 nsresult 
05914 nsHTMLEditRules::GetDefinitionListItemTypes(nsIDOMNode *aNode, PRBool &aDT, PRBool &aDD)
05915 {
05916   if (!aNode) return NS_ERROR_NULL_POINTER;
05917   aDT = aDD = PR_FALSE;
05918   nsresult res = NS_OK;
05919   nsCOMPtr<nsIDOMNode> child, temp;
05920   res = aNode->GetFirstChild(getter_AddRefs(child));
05921   while (child && NS_SUCCEEDED(res))
05922   {
05923     if (nsEditor::NodeIsType(child, nsEditProperty::dt)) aDT = PR_TRUE;
05924     else if (nsEditor::NodeIsType(child, nsEditProperty::dd)) aDD = PR_TRUE;
05925     res = child->GetNextSibling(getter_AddRefs(temp));
05926     child = temp;
05927   }
05928   return res;
05929 }
05930 
05932 // GetParagraphFormatNodes: 
05933 //                       
05934 nsresult 
05935 nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes,
05936                                          PRBool aDontTouchContent)
05937 {  
05938   nsCOMPtr<nsISelection>selection;
05939   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
05940   if (NS_FAILED(res)) return res;
05941 
05942   // contruct a list of nodes to act on.
05943   res = GetNodesFromSelection(selection, kMakeBasicBlock, outArrayOfNodes, aDontTouchContent);
05944   if (NS_FAILED(res)) return res;
05945 
05946   // pre process our list of nodes...                      
05947   PRInt32 listCount = outArrayOfNodes.Count();
05948   PRInt32 i;
05949   for (i=listCount-1; i>=0; i--)
05950   {
05951     nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
05952 
05953     // Remove all non-editable nodes.  Leave them be.
05954     if (!mHTMLEditor->IsEditable(testNode))
05955     {
05956       outArrayOfNodes.RemoveObjectAt(i);
05957     }
05958     
05959     // scan for table elements.  If we find table elements other than table,
05960     // replace it with a list of any editable non-table content.  Ditto for list elements.
05961     if (nsHTMLEditUtils::IsTableElement(testNode) ||
05962         nsHTMLEditUtils::IsList(testNode) || 
05963         nsHTMLEditUtils::IsListItem(testNode) )
05964     {
05965       PRInt32 j=i;
05966       outArrayOfNodes.RemoveObjectAt(i);
05967       res = GetInnerContent(testNode, outArrayOfNodes, &j);
05968       if (NS_FAILED(res)) return res;
05969     }
05970   }
05971   return res;
05972 }
05973 
05974 
05976 // BustUpInlinesAtRangeEndpoints: 
05977 //                       
05978 nsresult 
05979 nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item)
05980 {
05981   nsresult res = NS_OK;
05982   PRBool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset));
05983 
05984   nsCOMPtr<nsIDOMNode> endInline = GetHighestInlineParent(item.endNode);
05985   
05986   // if we have inline parents above range endpoints, split them
05987   if (endInline && !isCollapsed)
05988   {
05989     nsCOMPtr<nsIDOMNode> resultEndNode;
05990     PRInt32 resultEndOffset;
05991     endInline->GetParentNode(getter_AddRefs(resultEndNode));
05992     res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset,
05993                           &resultEndOffset, PR_TRUE);
05994     if (NS_FAILED(res)) return res;
05995     // reset range
05996     item.endNode = resultEndNode; item.endOffset = resultEndOffset;
05997   }
05998 
05999   nsCOMPtr<nsIDOMNode> startInline = GetHighestInlineParent(item.startNode);
06000 
06001   if (startInline)
06002   {
06003     nsCOMPtr<nsIDOMNode> resultStartNode;
06004     PRInt32 resultStartOffset;
06005     startInline->GetParentNode(getter_AddRefs(resultStartNode));
06006     res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset,
06007                           &resultStartOffset, PR_TRUE);
06008     if (NS_FAILED(res)) return res;
06009     // reset range
06010     item.startNode = resultStartNode; item.startOffset = resultStartOffset;
06011   }
06012   
06013   return res;
06014 }
06015 
06016 
06017 
06019 // BustUpInlinesAtBRs: 
06020 //                       
06021 nsresult 
06022 nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, 
06023                                     nsCOMArray<nsIDOMNode>& outArrayOfNodes)
06024 {
06025   if (!inNode) return NS_ERROR_NULL_POINTER;
06026 
06027   // first step is to build up a list of all the break nodes inside 
06028   // the inline container.
06029   nsCOMArray<nsIDOMNode> arrayOfBreaks;
06030   nsBRNodeFunctor functor;
06031   nsDOMIterator iter;
06032   nsresult res = iter.Init(inNode);
06033   if (NS_FAILED(res)) return res;
06034   res = iter.AppendList(functor, arrayOfBreaks);
06035   if (NS_FAILED(res)) return res;
06036   
06037   // if there aren't any breaks, just put inNode itself in the array
06038   PRInt32 listCount = arrayOfBreaks.Count();
06039   if (!listCount)
06040   {
06041     if (!outArrayOfNodes.AppendObject(inNode))
06042       return NS_ERROR_FAILURE;
06043   }
06044   else
06045   {
06046     // else we need to bust up inNode along all the breaks
06047     nsCOMPtr<nsIDOMNode> breakNode;
06048     nsCOMPtr<nsIDOMNode> inlineParentNode;
06049     nsCOMPtr<nsIDOMNode> leftNode;
06050     nsCOMPtr<nsIDOMNode> rightNode;
06051     nsCOMPtr<nsIDOMNode> splitDeepNode = inNode;
06052     nsCOMPtr<nsIDOMNode> splitParentNode;
06053     PRInt32 splitOffset, resultOffset, i;
06054     inNode->GetParentNode(getter_AddRefs(inlineParentNode));
06055     
06056     for (i=0; i< listCount; i++)
06057     {
06058       breakNode = arrayOfBreaks[i];
06059       if (!breakNode) return NS_ERROR_NULL_POINTER;
06060       if (!splitDeepNode) return NS_ERROR_NULL_POINTER;
06061       res = nsEditor::GetNodeLocation(breakNode, address_of(splitParentNode), &splitOffset);
06062       if (NS_FAILED(res)) return res;
06063       res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset,
06064                           &resultOffset, PR_FALSE, address_of(leftNode), address_of(rightNode));
06065       if (NS_FAILED(res)) return res;
06066       // put left node in node list
06067       if (leftNode)
06068       {
06069         // might not be a left node.  a break might have been at the very
06070         // beginning of inline container, in which case splitnodedeep
06071         // would not actually split anything
06072         if (!outArrayOfNodes.AppendObject(leftNode))
06073           return NS_ERROR_FAILURE;
06074       }
06075       // move break outside of container and also put in node list
06076       res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset);
06077       if (NS_FAILED(res)) return res;
06078       if (!outArrayOfNodes.AppendObject(breakNode))
06079         return  NS_ERROR_FAILURE;
06080       // now rightNode becomes the new node to split
06081       splitDeepNode = rightNode;
06082     }
06083     // now tack on remaining rightNode, if any, to the list
06084     if (rightNode)
06085     {
06086       if (!outArrayOfNodes.AppendObject(rightNode))
06087         return NS_ERROR_FAILURE;
06088     }
06089   }
06090   return res;
06091 }
06092 
06093 
06094 nsCOMPtr<nsIDOMNode> 
06095 nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode)
06096 {
06097   if (!aNode) return nsnull;
06098   if (IsBlockNode(aNode)) return nsnull;
06099   nsCOMPtr<nsIDOMNode> inlineNode, node=aNode;
06100 
06101   while (node && IsInlineNode(node))
06102   {
06103     inlineNode = node;
06104     inlineNode->GetParentNode(getter_AddRefs(node));
06105   }
06106   return inlineNode;
06107 }
06108 
06109 
06111 // GetNodesFromPoint: given a particular operation, construct a list  
06112 //                     of nodes from a point that will be operated on. 
06113 //                       
06114 nsresult 
06115 nsHTMLEditRules::GetNodesFromPoint(DOMPoint point,
06116                                    PRInt32 operation,
06117                                    nsCOMArray<nsIDOMNode> &arrayOfNodes,
06118                                    PRBool dontTouchContent)
06119 {
06120   nsresult res;
06121 
06122   // get our point
06123   nsCOMPtr<nsIDOMNode> node;
06124   PRInt32 offset;
06125   point.GetPoint(node, offset);
06126   
06127   // use it to make a range
06128   nsCOMPtr<nsIDOMRange> range = do_CreateInstance("@mozilla.org/content/range;1");
06129   res = range->SetStart(node, offset);
06130   if (NS_FAILED(res)) return res;
06131   /* SetStart() will also set the end for this new range
06132   res = range->SetEnd(node, offset);
06133   if (NS_FAILED(res)) return res; */
06134   
06135   // expand the range to include adjacent inlines
06136   res = PromoteRange(range, operation);
06137   if (NS_FAILED(res)) return res;
06138       
06139   // make array of ranges
06140   nsCOMArray<nsIDOMRange> arrayOfRanges;
06141   
06142   // stuff new opRange into array
06143   arrayOfRanges.AppendObject(range);
06144   
06145   // use these ranges to contruct a list of nodes to act on.
06146   res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); 
06147   return res;
06148 }
06149 
06150 
06152 // GetNodesFromSelection: given a particular operation, construct a list  
06153 //                     of nodes from the selection that will be operated on. 
06154 //                       
06155 nsresult 
06156 nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection,
06157                                        PRInt32 operation,
06158                                        nsCOMArray<nsIDOMNode>& arrayOfNodes,
06159                                        PRBool dontTouchContent)
06160 {
06161   if (!selection) return NS_ERROR_NULL_POINTER;
06162   nsresult res;
06163   
06164   // promote selection ranges
06165   nsCOMArray<nsIDOMRange> arrayOfRanges;
06166   res = GetPromotedRanges(selection, arrayOfRanges, operation);
06167   if (NS_FAILED(res)) return res;
06168   
06169   // use these ranges to contruct a list of nodes to act on.
06170   res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); 
06171   return res;
06172 }
06173 
06174 
06176 // MakeTransitionList: detect all the transitions in the array, where a 
06177 //                     transition means that adjacent nodes in the array 
06178 //                     don't have the same parent.
06179 //                       
06180 nsresult 
06181 nsHTMLEditRules::MakeTransitionList(nsCOMArray<nsIDOMNode>& inArrayOfNodes, 
06182                                     nsVoidArray &inTransitionArray)
06183 {
06184   PRInt32 listCount = inArrayOfNodes.Count();
06185   PRInt32 i;
06186   nsVoidArray transitionList;
06187   nsCOMPtr<nsIDOMNode> prevElementParent;
06188   nsCOMPtr<nsIDOMNode> curElementParent;
06189   
06190   for (i=0; i<listCount; i++)
06191   {
06192     nsIDOMNode* transNode = inArrayOfNodes[i];
06193     transNode->GetParentNode(getter_AddRefs(curElementParent));
06194     if (curElementParent != prevElementParent)
06195     {
06196       // different parents, or separated by <br>: transition point
06197       inTransitionArray.InsertElementAt((void*)PR_TRUE,i);  
06198     }
06199     else
06200     {
06201       // same parents: these nodes grew up together
06202       inTransitionArray.InsertElementAt((void*)PR_FALSE,i); 
06203     }
06204     prevElementParent = curElementParent;
06205   }
06206   return NS_OK;
06207 }
06208 
06209 
06210 
06211 /********************************************************
06212  *  main implementation methods 
06213  ********************************************************/
06214  
06216 // IsInListItem: if aNode is the descendant of a listitem, return that li.
06217 //               But table element boundaries are stoppers on the search.
06218 //               Also test if aNode is an li itself.
06219 //                       
06220 nsCOMPtr<nsIDOMNode> 
06221 nsHTMLEditRules::IsInListItem(nsIDOMNode *aNode)
06222 {
06223   if (!aNode) return nsnull;  
06224   if (nsHTMLEditUtils::IsListItem(aNode)) return aNode;
06225   
06226   nsCOMPtr<nsIDOMNode> parent, tmp;
06227   aNode->GetParentNode(getter_AddRefs(parent));
06228   
06229   while (parent)
06230   {
06231     if (nsHTMLEditUtils::IsTableElement(parent)) return nsnull;
06232     if (nsHTMLEditUtils::IsListItem(parent)) return parent;
06233     tmp=parent; tmp->GetParentNode(getter_AddRefs(parent));
06234   }
06235   return nsnull;
06236 }
06237 
06238 
06240 // ReturnInHeader: do the right thing for returns pressed in headers
06241 //                       
06242 nsresult 
06243 nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection, 
06244                                 nsIDOMNode *aHeader, 
06245                                 nsIDOMNode *aNode, 
06246                                 PRInt32 aOffset)
06247 {
06248   if (!aSelection || !aHeader || !aNode) return NS_ERROR_NULL_POINTER;  
06249   
06250   // remeber where the header is
06251   nsCOMPtr<nsIDOMNode> headerParent;
06252   PRInt32 offset;
06253   nsresult res = nsEditor::GetNodeLocation(aHeader, address_of(headerParent), &offset);
06254   if (NS_FAILED(res)) return res;
06255 
06256   // get ws code to adjust any ws
06257   nsCOMPtr<nsIDOMNode> selNode = aNode;
06258   res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
06259   if (NS_FAILED(res)) return res;
06260 
06261   // split the header
06262   PRInt32 newOffset;
06263   res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset);
06264   if (NS_FAILED(res)) return res;
06265 
06266   // if the leftand heading is empty, put a mozbr in it
06267   nsCOMPtr<nsIDOMNode> prevItem;
06268   mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem));
06269   if (prevItem && nsHTMLEditUtils::IsHeader(prevItem))
06270   {
06271     PRBool bIsEmptyNode;
06272     res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
06273     if (NS_FAILED(res)) return res;
06274     if (bIsEmptyNode)
06275     {
06276       nsCOMPtr<nsIDOMNode> brNode;
06277       res = CreateMozBR(prevItem, 0, address_of(brNode));
06278       if (NS_FAILED(res)) return res;
06279     }
06280   }
06281   
06282   // if the new (righthand) header node is empty, delete it
06283   PRBool isEmpty;
06284   res = IsEmptyBlock(aHeader, &isEmpty, PR_TRUE);
06285   if (NS_FAILED(res)) return res;
06286   if (isEmpty)
06287   {
06288     res = mHTMLEditor->DeleteNode(aHeader);
06289     if (NS_FAILED(res)) return res;
06290     // layout tells the caret to blink in a weird place
06291     // if we dont place a break after the header.
06292     nsCOMPtr<nsIDOMNode> sibling;
06293     res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling));
06294     if (NS_FAILED(res)) return res;
06295     if (!sibling || !nsTextEditUtils::IsBreak(sibling))
06296     {
06297       res = CreateMozBR(headerParent, offset+1, address_of(sibling));
06298       if (NS_FAILED(res)) return res;
06299     }
06300     res = nsEditor::GetNodeLocation(sibling, address_of(headerParent), &offset);
06301     if (NS_FAILED(res)) return res;
06302     // put selection after break
06303     res = aSelection->Collapse(headerParent,offset+1);
06304   }
06305   else
06306   {
06307     // put selection at front of righthand heading
06308     res = aSelection->Collapse(aHeader,0);
06309   }
06310   return res;
06311 }
06312 
06314 // ReturnInParagraph: do the right thing for returns pressed in paragraphs
06315 //                       
06316 nsresult 
06317 nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection, 
06318                                    nsIDOMNode *aPara, 
06319                                    nsIDOMNode *aNode, 
06320                                    PRInt32 aOffset,
06321                                    PRBool *aCancel,
06322                                    PRBool *aHandled)
06323 {
06324   if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) 
06325     { return NS_ERROR_NULL_POINTER; }
06326   *aCancel = PR_FALSE;
06327   *aHandled = PR_FALSE;
06328 
06329   nsCOMPtr<nsIDOMNode> parent;
06330   PRInt32 offset;
06331   nsresult res = nsEditor::GetNodeLocation(aNode, address_of(parent), &offset);
06332   if (NS_FAILED(res)) return res;
06333 
06334   PRBool  doesCRCreateNewP;
06335   res = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(&doesCRCreateNewP);
06336   if (NS_FAILED(res)) return res;
06337 
06338   PRBool newBRneeded = PR_FALSE;
06339   nsCOMPtr<nsIDOMNode> sibling;
06340 
06341   if (mHTMLEditor->IsTextNode(aNode))
06342   {
06343     nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode);
06344     PRUint32 strLength;
06345     res = textNode->GetLength(&strLength);
06346     if (NS_FAILED(res)) return res;
06347     
06348     // at beginning of text node?
06349     if (!aOffset)
06350     {
06351       // is there a BR prior to it?
06352       mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
06353       if (!sibling ||
06354           !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling))
06355       {
06356         newBRneeded = PR_TRUE;
06357       }
06358     }
06359     else if (aOffset == (PRInt32)strLength)
06360     {
06361       // we're at the end of text node...
06362       // is there a BR after to it?
06363       res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
06364       if (!sibling ||
06365           !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling)) 
06366       {
06367         newBRneeded = PR_TRUE;
06368         offset++;
06369       }
06370     }
06371     else
06372     {
06373       if (doesCRCreateNewP)
06374       {
06375         nsCOMPtr<nsIDOMNode> tmp;
06376         res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp));
06377         if (NS_FAILED(res)) return res;
06378         aNode = tmp;
06379       }
06380 
06381       newBRneeded = PR_TRUE;
06382       offset++;
06383     }
06384   }
06385   else
06386   {
06387     // not in a text node.  
06388     // is there a BR prior to it?
06389     nsCOMPtr<nsIDOMNode> nearNode, selNode = aNode;
06390     res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode));
06391     if (NS_FAILED(res)) return res;
06392     if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode)) 
06393     {
06394       // is there a BR after it?
06395       res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode));
06396       if (NS_FAILED(res)) return res;
06397       if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode)) 
06398       {
06399         newBRneeded = PR_TRUE;
06400       }
06401     }
06402     if (!newBRneeded)
06403       sibling = nearNode;
06404   }
06405   if (newBRneeded)
06406   {
06407     // if CR does not create a new P, default to BR creation
06408     if (!doesCRCreateNewP)
06409       return NS_OK;
06410 
06411     nsCOMPtr<nsIDOMNode> brNode;
06412     res =  mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
06413     sibling = brNode;
06414   }
06415   nsCOMPtr<nsIDOMNode> selNode = aNode;
06416   *aHandled = PR_TRUE;
06417   return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset);
06418 }
06419 
06421 // SplitParagraph: split a paragraph at selection point, possibly deleting a br
06422 //                       
06423 nsresult 
06424 nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara,
06425                                 nsIDOMNode *aBRNode, 
06426                                 nsISelection *aSelection,
06427                                 nsCOMPtr<nsIDOMNode> *aSelNode, 
06428                                 PRInt32 *aOffset)
06429 {
06430   if (!aPara || !aBRNode || !aSelNode || !*aSelNode || !aOffset || !aSelection) 
06431     return NS_ERROR_NULL_POINTER;
06432   nsresult res = NS_OK;
06433   
06434   // split para
06435   PRInt32 newOffset;
06436   // get ws code to adjust any ws
06437   nsCOMPtr<nsIDOMNode> leftPara, rightPara;
06438   res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset);
06439   if (NS_FAILED(res)) return res;
06440   // split the paragraph
06441   res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, PR_FALSE,
06442                                    address_of(leftPara), address_of(rightPara));
06443   if (NS_FAILED(res)) return res;
06444   // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p)
06445   if (mHTMLEditor->IsVisBreak(aBRNode))
06446   {
06447     res = mHTMLEditor->DeleteNode(aBRNode);  
06448     if (NS_FAILED(res)) return res;
06449   }
06450   
06451   // check both halves of para to see if we need mozBR
06452   res = InsertMozBRIfNeeded(leftPara);
06453   if (NS_FAILED(res)) return res;
06454   res = InsertMozBRIfNeeded(rightPara);
06455   if (NS_FAILED(res)) return res;
06456 
06457   // selection to beginning of right hand para;
06458   // look inside any containers that are up front.
06459   nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(rightPara, PR_TRUE);
06460   if (mHTMLEditor->IsTextNode(child) || mHTMLEditor->IsContainer(child))
06461   {
06462     aSelection->Collapse(child,0);
06463   }
06464   else
06465   {
06466     nsCOMPtr<nsIDOMNode> parent;
06467     PRInt32 offset;
06468     res = nsEditor::GetNodeLocation(child, address_of(parent), &offset);
06469     aSelection->Collapse(parent,offset);
06470   }
06471   return res;
06472 }
06473 
06474 
06476 // ReturnInListItem: do the right thing for returns pressed in list items
06477 //                       
06478 nsresult 
06479 nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection, 
06480                                   nsIDOMNode *aListItem, 
06481                                   nsIDOMNode *aNode, 
06482                                   PRInt32 aOffset)
06483 {
06484   if (!aSelection || !aListItem || !aNode) return NS_ERROR_NULL_POINTER;
06485   nsCOMPtr<nsISelection> selection(aSelection);
06486   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
06487   nsresult res = NS_OK;
06488   
06489   nsCOMPtr<nsIDOMNode> listitem;
06490   
06491   // sanity check
06492   NS_PRECONDITION(PR_TRUE == nsHTMLEditUtils::IsListItem(aListItem),
06493                   "expected a list item and didn't get one");
06494   
06495   // if we are in an empty listitem, then we want to pop up out of the list
06496   PRBool isEmpty;
06497   res = IsEmptyBlock(aListItem, &isEmpty, PR_TRUE, PR_FALSE);
06498   if (NS_FAILED(res)) return res;
06499   if (isEmpty && mReturnInEmptyLIKillsList)   // but only if prefs says it's ok
06500   {
06501     nsCOMPtr<nsIDOMNode> list, listparent;
06502     PRInt32 offset, itemOffset;
06503     res = nsEditor::GetNodeLocation(aListItem, address_of(list), &itemOffset);
06504     if (NS_FAILED(res)) return res;
06505     res = nsEditor::GetNodeLocation(list, address_of(listparent), &offset);
06506     if (NS_FAILED(res)) return res;
06507     
06508     // are we the last list item in the list?
06509     PRBool bIsLast;
06510     res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast);
06511     if (NS_FAILED(res)) return res;
06512     if (!bIsLast)
06513     {
06514       // we need to split the list!
06515       nsCOMPtr<nsIDOMNode> tempNode;
06516       res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode));
06517       if (NS_FAILED(res)) return res;
06518     }
06519     // are we in a sublist?
06520     if (nsHTMLEditUtils::IsList(listparent))  //in a sublist
06521     {
06522       // if so, move this list item out of this list and into the grandparent list
06523       res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1);
06524       if (NS_FAILED(res)) return res;
06525       res = aSelection->Collapse(aListItem,0);
06526     }
06527     else
06528     {
06529       // otherwise kill this listitem
06530       res = mHTMLEditor->DeleteNode(aListItem);
06531       if (NS_FAILED(res)) return res;
06532       
06533       // time to insert a break
06534       nsCOMPtr<nsIDOMNode> brNode;
06535       res = CreateMozBR(listparent, offset+1, address_of(brNode));
06536       if (NS_FAILED(res)) return res;
06537       
06538       // set selection to before the moz br
06539       selPriv->SetInterlinePosition(PR_TRUE);
06540       res = aSelection->Collapse(listparent,offset+1);
06541     }
06542     return res;
06543   }
06544   
06545   // else we want a new list item at the same list level.
06546   // get ws code to adjust any ws
06547   nsCOMPtr<nsIDOMNode> selNode = aNode;
06548   res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
06549   if (NS_FAILED(res)) return res;
06550   // now split list item
06551   PRInt32 newOffset;
06552   res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, PR_FALSE);
06553   if (NS_FAILED(res)) return res;
06554   // hack: until I can change the damaged doc range code back to being
06555   // extra inclusive, I have to manually detect certain list items that
06556   // may be left empty.
06557   nsCOMPtr<nsIDOMNode> prevItem;
06558   mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem));
06559 
06560   if (prevItem && nsHTMLEditUtils::IsListItem(prevItem))
06561   {
06562     PRBool bIsEmptyNode;
06563     res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
06564     if (NS_FAILED(res)) return res;
06565     if (bIsEmptyNode)
06566     {
06567       nsCOMPtr<nsIDOMNode> brNode;
06568       res = CreateMozBR(prevItem, 0, address_of(brNode));
06569       if (NS_FAILED(res)) return res;
06570     }
06571     else 
06572     {
06573       res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, PR_TRUE);
06574       if (NS_FAILED(res)) return res;
06575       if (bIsEmptyNode) 
06576       {
06577         nsCOMPtr<nsIAtom> nodeAtom = nsEditor::GetTag(aListItem);
06578         if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt)
06579         {
06580           nsCOMPtr<nsIDOMNode> list;
06581           PRInt32 itemOffset;
06582           res = nsEditor::GetNodeLocation(aListItem, address_of(list), &itemOffset);
06583           if (NS_FAILED(res)) return res;
06584 
06585           nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt"));
06586           nsCOMPtr<nsIDOMNode> newListItem;
06587           res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem));
06588           if (NS_FAILED(res)) return res;
06589           res = mEditor->DeleteNode(aListItem);
06590           if (NS_FAILED(res)) return res;
06591           return aSelection->Collapse(newListItem, 0);
06592         }
06593 
06594         nsCOMPtr<nsIDOMNode> brNode;
06595         res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode));
06596         if (NS_FAILED(res)) return res;
06597         if (brNode) 
06598         {
06599           nsCOMPtr<nsIDOMNode> brParent;
06600           PRInt32 offset;
06601           res = nsEditor::GetNodeLocation(brNode, address_of(brParent), &offset);
06602           return aSelection->Collapse(brParent, offset);
06603         }
06604       }
06605       else
06606       {
06607         nsWSRunObject wsObj(mHTMLEditor, aListItem, 0);
06608         nsCOMPtr<nsIDOMNode> visNode;
06609         PRInt32 visOffset = 0;
06610         PRInt16 wsType;
06611         res = wsObj.NextVisibleNode(aListItem, 0, address_of(visNode), &visOffset, &wsType);
06612         if (NS_FAILED(res)) return res;
06613         if ( (wsType==nsWSRunObject::eSpecial)  || 
06614              (wsType==nsWSRunObject::eBreak)    ||
06615              nsHTMLEditUtils::IsHR(visNode) ) 
06616         {
06617           nsCOMPtr<nsIDOMNode> parent;
06618           PRInt32 offset;
06619           res = nsEditor::GetNodeLocation(visNode, address_of(parent), &offset);
06620           if (NS_FAILED(res)) return res;
06621           return aSelection->Collapse(parent, offset);
06622         }
06623         else
06624         {
06625           return aSelection->Collapse(visNode, visOffset);
06626         }
06627       }
06628     }
06629   }
06630   res = aSelection->Collapse(aListItem,0);
06631   return res;
06632 }
06633 
06634 
06636 // MakeBlockquote:  put the list of nodes into one or more blockquotes.  
06637 //                       
06638 nsresult 
06639 nsHTMLEditRules::MakeBlockquote(nsCOMArray<nsIDOMNode>& arrayOfNodes)
06640 {
06641   // the idea here is to put the nodes into a minimal number of 
06642   // blockquotes.  When the user blockquotes something, they expect
06643   // one blockquote.  That may not be possible (for instance, if they
06644   // have two table cells selected, you need two blockquotes inside the cells).
06645   
06646   nsresult res = NS_OK;
06647   
06648   nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
06649   PRInt32 offset;
06650   PRInt32 listCount = arrayOfNodes.Count();
06651   
06652   nsCOMPtr<nsIDOMNode> prevParent;
06653   
06654   PRInt32 i;
06655   for (i=0; i<listCount; i++)
06656   {
06657     // get the node to act on, and it's location
06658     curNode = arrayOfNodes[i];
06659     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
06660     if (NS_FAILED(res)) return res;
06661 
06662     // if the node is a table element or list item, dive inside
06663     if (nsHTMLEditUtils::IsTableElementButNotTable(curNode) || 
06664         nsHTMLEditUtils::IsListItem(curNode))
06665     {
06666       curBlock = 0;  // forget any previous block
06667       // recursion time
06668       nsCOMArray<nsIDOMNode> childArray;
06669       res = GetChildNodesForOperation(curNode, childArray);
06670       if (NS_FAILED(res)) return res;
06671       res = MakeBlockquote(childArray);
06672       if (NS_FAILED(res)) return res;
06673     }
06674     
06675     // if the node has different parent than previous node,
06676     // further nodes in a new parent
06677     if (prevParent)
06678     {
06679       nsCOMPtr<nsIDOMNode> temp;
06680       curNode->GetParentNode(getter_AddRefs(temp));
06681       if (temp != prevParent)
06682       {
06683         curBlock = 0;  // forget any previous blockquote node we were using
06684         prevParent = temp;
06685       }
06686     }
06687     else     
06688 
06689     {
06690       curNode->GetParentNode(getter_AddRefs(prevParent));
06691     }
06692     
06693     // if no curBlock, make one
06694     if (!curBlock)
06695     {
06696       NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
06697       res = SplitAsNeeded(&quoteType, address_of(curParent), &offset);
06698       if (NS_FAILED(res)) return res;
06699       res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock));
06700       if (NS_FAILED(res)) return res;
06701       // remember our new block for postprocessing
06702       mNewBlock = curBlock;
06703       // note: doesn't matter if we set mNewBlock multiple times.
06704     }
06705       
06706     res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
06707     if (NS_FAILED(res)) return res;
06708   }
06709   return res;
06710 }
06711 
06712 
06713 
06715 // RemoveBlockStyle:  make the nodes have no special block type.  
06716 //                       
06717 nsresult 
06718 nsHTMLEditRules::RemoveBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes)
06719 {
06720   // intent of this routine is to be used for converting to/from
06721   // headers, paragraphs, pre, and address.  Those blocks
06722   // that pretty much just contain inline things...
06723   
06724   nsresult res = NS_OK;
06725   
06726   nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, firstNode, lastNode;
06727   PRInt32 offset;
06728   PRInt32 listCount = arrayOfNodes.Count();
06729     
06730   PRInt32 i;
06731   for (i=0; i<listCount; i++)
06732   {
06733     // get the node to act on, and it's location
06734     curNode = arrayOfNodes[i];
06735     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
06736     if (NS_FAILED(res)) return res;
06737     nsAutoString curNodeTag, curBlockTag;
06738     nsEditor::GetTagString(curNode, curNodeTag);
06739     ToLowerCase(curNodeTag);
06740  
06741     // if curNode is a address, p, header, address, or pre, remove it 
06742     if (nsHTMLEditUtils::IsFormatNode(curNode))
06743     {
06744       // process any partial progress saved
06745       if (curBlock)
06746       {
06747         res = RemovePartOfBlock(curBlock, firstNode, lastNode);
06748         if (NS_FAILED(res)) return res;
06749         curBlock = 0;  firstNode = 0;  lastNode = 0;
06750       }
06751       // remove curent block
06752       res = mHTMLEditor->RemoveBlockContainer(curNode); 
06753       if (NS_FAILED(res)) return res;
06754     }
06755     else if (nsHTMLEditUtils::IsTable(curNode)                    || 
06756              nsHTMLEditUtils::IsTableRow(curNode)                 ||
06757              (curNodeTag.EqualsLiteral("tbody"))      ||
06758              (curNodeTag.EqualsLiteral("td"))         ||
06759              nsHTMLEditUtils::IsList(curNode)                     ||
06760              (curNodeTag.EqualsLiteral("li"))         ||
06761              nsHTMLEditUtils::IsBlockquote(curNode)               ||
06762              nsHTMLEditUtils::IsDiv(curNode))
06763     {
06764       // process any partial progress saved
06765       if (curBlock)
06766       {
06767         res = RemovePartOfBlock(curBlock, firstNode, lastNode);
06768         if (NS_FAILED(res)) return res;
06769         curBlock = 0;  firstNode = 0;  lastNode = 0;
06770       }
06771       // recursion time
06772       nsCOMArray<nsIDOMNode> childArray;
06773       res = GetChildNodesForOperation(curNode, childArray);
06774       if (NS_FAILED(res)) return res;
06775       res = RemoveBlockStyle(childArray);
06776       if (NS_FAILED(res)) return res;
06777     }
06778     else if (IsInlineNode(curNode))
06779     {
06780       if (curBlock)
06781       {
06782         // if so, is this node a descendant?
06783         if (nsEditorUtils::IsDescendantOf(curNode, curBlock))
06784         {
06785           lastNode = curNode;
06786           continue;  // then we dont need to do anything different for this node
06787         }
06788         else
06789         {
06790           // otherwise, we have progressed beyond end of curBlock,
06791           // so lets handle it now.  We need to remove the portion of 
06792           // curBlock that contains [firstNode - lastNode].
06793           res = RemovePartOfBlock(curBlock, firstNode, lastNode);
06794           if (NS_FAILED(res)) return res;
06795           curBlock = 0;  firstNode = 0;  lastNode = 0;
06796           // fall out and handle curNode
06797         }
06798       }
06799       curBlock = mHTMLEditor->GetBlockNodeParent(curNode);
06800       if (nsHTMLEditUtils::IsFormatNode(curBlock))
06801       {
06802         firstNode = curNode;  
06803         lastNode = curNode;
06804       }
06805       else
06806         curBlock = 0;  // not a block kind that we care about.
06807     }
06808     else
06809     { // some node that is already sans block style.  skip over it and
06810       // process any partial progress saved
06811       if (curBlock)
06812       {
06813         res = RemovePartOfBlock(curBlock, firstNode, lastNode);
06814         if (NS_FAILED(res)) return res;
06815         curBlock = 0;  firstNode = 0;  lastNode = 0;
06816       }
06817     }
06818   }
06819   // process any partial progress saved
06820   if (curBlock)
06821   {
06822     res = RemovePartOfBlock(curBlock, firstNode, lastNode);
06823     if (NS_FAILED(res)) return res;
06824     curBlock = 0;  firstNode = 0;  lastNode = 0;
06825   }
06826   return res;
06827 }
06828 
06829 
06831 // ApplyBlockStyle:  do whatever it takes to make the list of nodes into 
06832 //                   one or more blocks of type blockTag.  
06833 //                       
06834 nsresult 
06835 nsHTMLEditRules::ApplyBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes, const nsAString *aBlockTag)
06836 {
06837   // intent of this routine is to be used for converting to/from
06838   // headers, paragraphs, pre, and address.  Those blocks
06839   // that pretty much just contain inline things...
06840   
06841   if (!aBlockTag) return NS_ERROR_NULL_POINTER;
06842   nsresult res = NS_OK;
06843   
06844   nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
06845   PRInt32 offset;
06846   PRInt32 listCount = arrayOfNodes.Count();
06847   nsString tString(*aBlockTag);
06848 
06849   // Remove all non-editable nodes.  Leave them be.
06850   PRInt32 j;
06851   for (j=listCount-1; j>=0; j--)
06852   {
06853     if (!mHTMLEditor->IsEditable(arrayOfNodes[j]))
06854     {
06855       arrayOfNodes.RemoveObjectAt(j);
06856     }
06857   }
06858   
06859   // reset list count
06860   listCount = arrayOfNodes.Count();
06861   
06862   PRInt32 i;
06863   for (i=0; i<listCount; i++)
06864   {
06865     // get the node to act on, and it's location
06866     curNode = arrayOfNodes[i];
06867     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
06868     if (NS_FAILED(res)) return res;
06869     nsAutoString curNodeTag;
06870     nsEditor::GetTagString(curNode, curNodeTag);
06871     ToLowerCase(curNodeTag);
06872  
06873     // is it already the right kind of block?
06874     if (curNodeTag == *aBlockTag)
06875     {
06876       curBlock = 0;  // forget any previous block used for previous inline nodes
06877       continue;  // do nothing to this block
06878     }
06879         
06880     // if curNode is a address, p, header, address, or pre, replace 
06881     // it with a new block of correct type.
06882     // xxx floppy moose: pre cant hold everything the others can
06883     if (nsHTMLEditUtils::IsMozDiv(curNode)     ||
06884         nsHTMLEditUtils::IsFormatNode(curNode))
06885     {
06886       curBlock = 0;  // forget any previous block used for previous inline nodes
06887       res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), *aBlockTag);
06888       if (NS_FAILED(res)) return res;
06889     }
06890     else if (nsHTMLEditUtils::IsTable(curNode)                    || 
06891              (curNodeTag.EqualsLiteral("tbody"))      ||
06892              (curNodeTag.EqualsLiteral("tr"))         ||
06893              (curNodeTag.EqualsLiteral("td"))         ||
06894              nsHTMLEditUtils::IsList(curNode)                     ||
06895              (curNodeTag.EqualsLiteral("li"))         ||
06896              nsHTMLEditUtils::IsBlockquote(curNode)               ||
06897              nsHTMLEditUtils::IsDiv(curNode))
06898     {
06899       curBlock = 0;  // forget any previous block used for previous inline nodes
06900       // recursion time
06901       nsCOMArray<nsIDOMNode> childArray;
06902       res = GetChildNodesForOperation(curNode, childArray);
06903       if (NS_FAILED(res)) return res;
06904       PRInt32 childCount = childArray.Count();
06905       if (childCount)
06906       {
06907         res = ApplyBlockStyle(childArray, aBlockTag);
06908         if (NS_FAILED(res)) return res;
06909       }
06910       else
06911       {
06912         // make sure we can put a block here
06913         res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
06914         if (NS_FAILED(res)) return res;
06915         nsCOMPtr<nsIDOMNode> theBlock;
06916         res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock));
06917         if (NS_FAILED(res)) return res;
06918         // remember our new block for postprocessing
06919         mNewBlock = theBlock;
06920       }
06921     }
06922     
06923     // if the node is a break, we honor it by putting further nodes in a new parent
06924     else if (curNodeTag.EqualsLiteral("br"))
06925     {
06926       if (curBlock)
06927       {
06928         curBlock = 0;  // forget any previous block used for previous inline nodes
06929         res = mHTMLEditor->DeleteNode(curNode);
06930         if (NS_FAILED(res)) return res;
06931       }
06932       else
06933       {
06934         // the break is the first (or even only) node we encountered.  Create a
06935         // block for it.
06936         res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
06937         if (NS_FAILED(res)) return res;
06938         res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
06939         if (NS_FAILED(res)) return res;
06940         // remember our new block for postprocessing
06941         mNewBlock = curBlock;
06942         // note: doesn't matter if we set mNewBlock multiple times.
06943         res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
06944         if (NS_FAILED(res)) return res;
06945       }
06946     }
06947         
06948     
06949     // if curNode is inline, pull it into curBlock
06950     // note: it's assumed that consecutive inline nodes in the 
06951     // arrayOfNodes are actually members of the same block parent.
06952     // this happens to be true now as a side effect of how
06953     // arrayOfNodes is contructed, but some additional logic should
06954     // be added here if that should change
06955     
06956     else if (IsInlineNode(curNode))
06957     {
06958       // if curNode is a non editable, drop it if we are going to <pre>
06959       if (tString.LowerCaseEqualsLiteral("pre") 
06960         && (!mHTMLEditor->IsEditable(curNode)))
06961         continue; // do nothing to this block
06962       
06963       // if no curBlock, make one
06964       if (!curBlock)
06965       {
06966         res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
06967         if (NS_FAILED(res)) return res;
06968         res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
06969         if (NS_FAILED(res)) return res;
06970         // remember our new block for postprocessing
06971         mNewBlock = curBlock;
06972         // note: doesn't matter if we set mNewBlock multiple times.
06973       }
06974       
06975       // if curNode is a Break, replace it with a return if we are going to <pre>
06976       // xxx floppy moose
06977  
06978       // this is a continuation of some inline nodes that belong together in
06979       // the same block item.  use curBlock
06980       res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
06981       if (NS_FAILED(res)) return res;
06982     }
06983   }
06984   return res;
06985 }
06986 
06987 
06989 // SplitAsNeeded:  given a tag name, split inOutParent up to the point   
06990 //                 where we can insert the tag.  Adjust inOutParent and
06991 //                 inOutOffset to pint to new location for tag.
06992 nsresult 
06993 nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag, 
06994                                nsCOMPtr<nsIDOMNode> *inOutParent,
06995                                PRInt32 *inOutOffset)
06996 {
06997   if (!aTag || !inOutParent || !inOutOffset) return NS_ERROR_NULL_POINTER;
06998   if (!*inOutParent) return NS_ERROR_NULL_POINTER;
06999   nsCOMPtr<nsIDOMNode> tagParent, temp, splitNode, parent = *inOutParent;
07000   nsresult res = NS_OK;
07001    
07002   // check that we have a place that can legally contain the tag
07003   while (!tagParent)
07004   {
07005     // sniffing up the parent tree until we find 
07006     // a legal place for the block
07007     if (!parent) break;
07008     if (mHTMLEditor->CanContainTag(parent, *aTag))
07009     {
07010       tagParent = parent;
07011       break;
07012     }
07013     splitNode = parent;
07014     parent->GetParentNode(getter_AddRefs(temp));
07015     parent = temp;
07016   }
07017   if (!tagParent)
07018   {
07019     // could not find a place to build tag!
07020     return NS_ERROR_FAILURE;
07021   }
07022   if (splitNode)
07023   {
07024     // we found a place for block, but above inOutParent.  We need to split nodes.
07025     res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset);
07026     if (NS_FAILED(res)) return res;
07027     *inOutParent = tagParent;
07028   }
07029   return res;
07030 }      
07031 
07033 // JoinNodesSmart:  join two nodes, doing whatever makes sense for their  
07034 //                  children (which often means joining them, too).
07035 //                  aNodeLeft & aNodeRight must be same type of node.
07036 nsresult 
07037 nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, 
07038                                  nsIDOMNode *aNodeRight, 
07039                                  nsCOMPtr<nsIDOMNode> *aOutMergeParent, 
07040                                  PRInt32 *aOutMergeOffset)
07041 {
07042   // check parms
07043   if (!aNodeLeft ||  
07044       !aNodeRight || 
07045       !aOutMergeParent ||
07046       !aOutMergeOffset) 
07047     return NS_ERROR_NULL_POINTER;
07048   
07049   nsresult res = NS_OK;
07050   // caller responsible for:
07051   //   left & right node are same type
07052   PRInt32 parOffset;
07053   nsCOMPtr<nsIDOMNode> parent, rightParent;
07054   res = nsEditor::GetNodeLocation(aNodeLeft, address_of(parent), &parOffset);
07055   if (NS_FAILED(res)) return res;
07056   aNodeRight->GetParentNode(getter_AddRefs(rightParent));
07057 
07058   // if they don't have the same parent, first move the 'right' node 
07059   // to after the 'left' one
07060   if (parent != rightParent)
07061   {
07062     res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset);
07063     if (NS_FAILED(res)) return res;
07064   }
07065   
07066   // defaults for outParams
07067   *aOutMergeParent = aNodeRight;
07068   res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((PRUint32*)aOutMergeOffset));
07069   if (NS_FAILED(res)) return res;
07070 
07071   // separate join rules for differing blocks
07072   if (nsHTMLEditUtils::IsParagraph(aNodeLeft))
07073   {
07074     // for para's, merge deep & add a <br> after merging
07075     res = mHTMLEditor->JoinNodeDeep(aNodeLeft, aNodeRight, aOutMergeParent, aOutMergeOffset);
07076     if (NS_FAILED(res)) return res;
07077     // now we need to insert a br.  
07078     nsCOMPtr<nsIDOMNode> brNode;
07079     res = mHTMLEditor->CreateBR(*aOutMergeParent, *aOutMergeOffset, address_of(brNode));
07080     if (NS_FAILED(res)) return res;
07081     res = nsEditor::GetNodeLocation(brNode, aOutMergeParent, aOutMergeOffset);
07082     if (NS_FAILED(res)) return res;
07083     (*aOutMergeOffset)++;
07084     return res;
07085   }
07086   else if (nsHTMLEditUtils::IsList(aNodeLeft)
07087            || mHTMLEditor->IsTextNode(aNodeLeft))
07088   {
07089     // for list's, merge shallow (wouldn't want to combine list items)
07090     res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
07091     if (NS_FAILED(res)) return res;
07092     return res;
07093   }
07094   else
07095   {
07096     // remember the last left child, and firt right child
07097     nsCOMPtr<nsIDOMNode> lastLeft, firstRight;
07098     res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft));
07099     if (NS_FAILED(res)) return res;
07100     res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight));
07101     if (NS_FAILED(res)) return res;
07102 
07103     // for list items, divs, etc, merge smart
07104     res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
07105     if (NS_FAILED(res)) return res;
07106 
07107     if (lastLeft && firstRight && mHTMLEditor->NodesSameType(lastLeft, firstRight))
07108     {
07109       return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
07110     }
07111   }
07112   return res;
07113 }
07114 
07115 
07116 nsresult 
07117 nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, 
07118                                          nsCOMPtr<nsIDOMNode> *aOutCiteNode,
07119                                          PRBool aPlainText)
07120 {
07121   // check parms
07122   if (!aNode || !aOutCiteNode) 
07123     return NS_ERROR_NULL_POINTER;
07124   
07125   nsresult res = NS_OK;
07126   nsCOMPtr<nsIDOMNode> node, parentNode;
07127   node = do_QueryInterface(aNode);
07128   
07129   while (node)
07130   {
07131     if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) ||
07132          nsHTMLEditUtils::IsMailCite(node) )
07133       *aOutCiteNode = node;
07134     if (nsTextEditUtils::IsBody(node)) break;
07135     
07136     res = node->GetParentNode(getter_AddRefs(parentNode));
07137     if (NS_FAILED(res)) return res;
07138     node = parentNode;
07139   }
07140 
07141   return res;
07142 }
07143 
07144 
07145 nsresult 
07146 nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode)
07147 {
07148   if (!aNode) return NS_ERROR_NULL_POINTER;
07149 
07150   PRBool useCSS;
07151   mHTMLEditor->GetIsCSSEnabled(&useCSS);
07152 
07153   PRInt32 j;
07154   for (j=0; j<SIZE_STYLE_TABLE; j++)
07155   {
07156     PRBool isSet = PR_FALSE;
07157     nsAutoString outValue;
07158     nsCOMPtr<nsIDOMNode> resultNode;
07159     if (!useCSS)
07160     {
07161       mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr), nsnull,
07162                                isSet, getter_AddRefs(resultNode), &outValue);
07163     }
07164     else
07165     {
07166       mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr),
07167                                                     isSet, outValue, COMPUTED_STYLE_TYPE);
07168     }
07169     if (isSet)
07170     {
07171       mCachedStyles[j].mPresent = PR_TRUE;
07172       mCachedStyles[j].value.Assign(outValue);
07173     }
07174   }
07175   return NS_OK;
07176 }
07177 
07178 
07179 nsresult 
07180 nsHTMLEditRules::ReapplyCachedStyles()
07181 {
07182   // The idea here is to examine our cached list of styles
07183   // and see if any have been removed.  If so, add typeinstate
07184   // for them, so that they will be reinserted when new 
07185   // content is added.
07186   
07187   // When we apply cached styles to TypeInState, we always want
07188   // to blow away prior TypeInState:
07189   mHTMLEditor->mTypeInState->Reset();
07190 
07191   // remember if we are in css mode
07192   PRBool useCSS;
07193   mHTMLEditor->GetIsCSSEnabled(&useCSS);
07194 
07195   // get selection point
07196   nsCOMPtr<nsISelection>selection;
07197   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
07198   if (NS_FAILED(res)) return res;
07199   nsCOMPtr<nsIDOMNode> selNode;
07200   PRInt32 selOffset;
07201   res = mHTMLEditor->GetStartNodeAndOffset(selection, address_of(selNode), &selOffset);
07202   if (NS_FAILED(res)) return res;
07203 
07204   res = NS_OK;
07205   PRInt32 j;
07206   for (j=0; j<SIZE_STYLE_TABLE; j++)
07207   {
07208     if (mCachedStyles[j].mPresent)
07209     {
07210       PRBool bFirst, bAny, bAll;
07211       bFirst = bAny = bAll = PR_FALSE;
07212       
07213       nsAutoString curValue;
07214       if (useCSS) // check computed style first in css case
07215       {
07216         mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(selNode, mCachedStyles[j].tag, &(mCachedStyles[j].attr),
07217                                                     bAny, curValue, COMPUTED_STYLE_TYPE);
07218       }
07219       if (!bAny) // then check typeinstate and html style
07220       {
07221         res = mHTMLEditor->GetInlinePropertyBase(mCachedStyles[j].tag, &(mCachedStyles[j].attr), &(mCachedStyles[j].value), 
07222                                                         &bFirst, &bAny, &bAll, &curValue, PR_FALSE);
07223         if (NS_FAILED(res)) return res;
07224       }
07225       // this style has disappeared through deletion.  Add it onto our typeinstate:
07226       if (!bAny) 
07227       {
07228         mHTMLEditor->mTypeInState->SetProp(mCachedStyles[j].tag, mCachedStyles[j].attr, mCachedStyles[j].value);
07229       }
07230     }
07231   }
07232   return NS_OK;
07233 }
07234 
07235 
07236 nsresult
07237 nsHTMLEditRules::ClearCachedStyles()
07238 {
07239   // clear the mPresent bits in mCachedStyles array
07240   
07241   PRInt32 j;
07242   for (j=0; j<SIZE_STYLE_TABLE; j++)
07243   {
07244     mCachedStyles[j].mPresent = PR_FALSE;
07245     mCachedStyles[j].