Back to index

lightning-sunbird  0.9+nobinonly
nsWSRunObject.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  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsTextFragment.h"
00039 #include "nsWSRunObject.h"
00040 #include "nsIDOMNode.h"
00041 #include "nsHTMLEditor.h"
00042 #include "nsTextEditUtils.h"
00043 #include "nsIContent.h"
00044 #include "nsIDOMCharacterData.h"
00045 #include "nsCRT.h"
00046 #include "nsIRangeUtils.h"
00047 
00048 const PRUnichar nbsp = 160;
00049 
00050 static PRBool IsBlockNode(nsIDOMNode* node)
00051 {
00052   PRBool isBlock (PR_FALSE);
00053   nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
00054   return isBlock;
00055 }
00056 
00057 //- constructor / destructor -----------------------------------------------
00058 nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset) :
00059 mNode(aNode)
00060 ,mOffset(aOffset)
00061 ,mPRE(PR_FALSE)
00062 ,mStartNode()
00063 ,mStartOffset(0)
00064 ,mStartReason(0)
00065 ,mStartReasonNode()
00066 ,mEndNode()
00067 ,mEndOffset(0)
00068 ,mEndReason(0)
00069 ,mEndReasonNode()
00070 ,mFirstNBSPNode()
00071 ,mFirstNBSPOffset(0)
00072 ,mLastNBSPNode()
00073 ,mLastNBSPOffset(0)
00074 ,mNodeArray()
00075 ,mStartRun(nsnull)
00076 ,mEndRun(nsnull)
00077 ,mHTMLEditor(aEd)
00078 {
00079   GetWSNodes();
00080   GetRuns();
00081 }
00082 
00083 nsWSRunObject::~nsWSRunObject()
00084 {
00085   ClearRuns();
00086 }
00087 
00088 
00089 
00090 //--------------------------------------------------------------------------------------------
00091 //   public static methods
00092 //--------------------------------------------------------------------------------------------
00093 
00094 nsresult
00095 nsWSRunObject::ScrubBlockBoundary(nsHTMLEditor *aHTMLEd, 
00096                                   nsCOMPtr<nsIDOMNode> *aBlock,
00097                                   BlockBoundary aBoundary,
00098                                   PRInt32 *aOffset)
00099 {
00100   if (!aBlock || !aHTMLEd)
00101     return NS_ERROR_NULL_POINTER;
00102   if ((aBoundary == kBlockStart) || (aBoundary == kBlockEnd))
00103     return ScrubBlockBoundaryInner(aHTMLEd, aBlock, aBoundary);
00104   
00105   // else we are scrubbing an outer boundary - just before or after
00106   // a block element.
00107   if (!aOffset) 
00108     return NS_ERROR_NULL_POINTER;
00109   nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aBlock, aOffset);
00110   nsWSRunObject theWSObj(aHTMLEd, *aBlock, *aOffset);
00111   return theWSObj.Scrub();
00112 }
00113 
00114 nsresult 
00115 nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd, 
00116                                    nsIDOMNode *aLeftParent, 
00117                                    nsIDOMNode *aRightParent)
00118 {
00119   if (!aLeftParent || !aRightParent || !aHTMLEd)
00120     return NS_ERROR_NULL_POINTER;
00121   PRUint32 count;
00122   aHTMLEd->GetLengthOfDOMNode(aLeftParent, count);
00123   nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count);
00124   nsWSRunObject rightWSObj(aHTMLEd, aRightParent, 0);
00125 
00126   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
00127 }
00128 
00129 nsresult 
00130 nsWSRunObject::PrepareToDeleteRange(nsHTMLEditor *aHTMLEd, 
00131                                     nsCOMPtr<nsIDOMNode> *aStartNode,
00132                                     PRInt32 *aStartOffset, 
00133                                     nsCOMPtr<nsIDOMNode> *aEndNode,
00134                                     PRInt32 *aEndOffset)
00135 {
00136   if (!aStartNode || !aEndNode || !*aStartNode || !*aEndNode || !aStartOffset || !aEndOffset || !aHTMLEd)
00137     return NS_ERROR_NULL_POINTER;
00138 
00139   nsAutoTrackDOMPoint trackerStart(aHTMLEd->mRangeUpdater, aStartNode, aStartOffset);
00140   nsAutoTrackDOMPoint trackerEnd(aHTMLEd->mRangeUpdater, aEndNode, aEndOffset);
00141   
00142   nsWSRunObject leftWSObj(aHTMLEd, *aStartNode, *aStartOffset);
00143   nsWSRunObject rightWSObj(aHTMLEd, *aEndNode, *aEndOffset);
00144 
00145   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
00146 }
00147 
00148 nsresult 
00149 nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor *aHTMLEd, 
00150                                    nsIDOMNode *aNode)
00151 {
00152   if (!aNode || !aHTMLEd)
00153     return NS_ERROR_NULL_POINTER;
00154   nsresult res = NS_OK;
00155   
00156   nsCOMPtr<nsIDOMNode> parent;
00157   PRInt32 offset;
00158   res = aHTMLEd->GetNodeLocation(aNode, address_of(parent), &offset);
00159   NS_ENSURE_SUCCESS(res, res);
00160   
00161   nsWSRunObject leftWSObj(aHTMLEd, parent, offset);
00162   nsWSRunObject rightWSObj(aHTMLEd, parent, offset+1);
00163 
00164   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
00165 }
00166 
00167 nsresult 
00168 nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd, 
00169                                           nsCOMPtr<nsIDOMNode> *aSplitNode, 
00170                                           PRInt32 *aSplitOffset)
00171 {
00172   if (!aSplitNode || !aSplitOffset || !*aSplitNode || !aHTMLEd)
00173     return NS_ERROR_NULL_POINTER;
00174 
00175   nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset);
00176   
00177   nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset);
00178 
00179   return wsObj.PrepareToSplitAcrossBlocksPriv();
00180 }
00181 
00182 //--------------------------------------------------------------------------------------------
00183 //   public instance methods
00184 //--------------------------------------------------------------------------------------------
00185 
00186 nsresult 
00187 nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent, 
00188                            PRInt32 *aInOutOffset, 
00189                            nsCOMPtr<nsIDOMNode> *outBRNode, 
00190                            nsIEditor::EDirection aSelect)
00191 {
00192   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
00193   // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
00194   if (!aInOutParent || !aInOutOffset || !outBRNode)
00195     return NS_ERROR_NULL_POINTER;
00196 
00197   nsresult res = NS_OK;
00198   WSFragment *beforeRun, *afterRun;
00199   res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
00200   res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
00201   
00202   {
00203     // some scoping for nsAutoTrackDOMPoint.  This will track our insertion point
00204     // while we tweak any surrounding whitespace
00205     nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset);
00206 
00207     // handle any changes needed to ws run after inserted br
00208     if (!afterRun)
00209     {
00210       // dont need to do anything.  just insert break.  ws wont change.
00211     }
00212     else if (afterRun->mType & eTrailingWS)
00213     {
00214       // dont need to do anything.  just insert break.  ws wont change.
00215     }
00216     else if (afterRun->mType & eLeadingWS)
00217     {
00218       // delete the leading ws that is after insertion point.  We don't
00219       // have to (it would still not be significant after br), but it's 
00220       // just more aesthetically pleasing to.
00221       res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset,
00222                         eOutsideUserSelectAll);
00223       NS_ENSURE_SUCCESS(res, res);
00224     }
00225     else if (afterRun->mType == eNormalWS)
00226     {
00227       // need to determine if break at front of non-nbsp run.  if so
00228       // convert run to nbsp.
00229       WSPoint thePoint;
00230       res = GetCharAfter(*aInOutParent, *aInOutOffset, &thePoint);
00231       if ( (NS_SUCCEEDED(res)) && thePoint.mTextNode && (nsCRT::IsAsciiSpace(thePoint.mChar)) )
00232       {
00233         WSPoint prevPoint;
00234         res = GetCharBefore(thePoint, &prevPoint);
00235         if ( (NS_FAILED(res)) || (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) )
00236         {
00237           // we are at start of non-nbsps.  convert to a single nbsp.
00238           res = ConvertToNBSP(thePoint);
00239           NS_ENSURE_SUCCESS(res, res);
00240         }
00241       }
00242     }
00243     
00244     // handle any changes needed to ws run before inserted br
00245     if (!beforeRun)
00246     {
00247       // dont need to do anything.  just insert break.  ws wont change.
00248     }
00249     else if (beforeRun->mType & eLeadingWS)
00250     {
00251       // dont need to do anything.  just insert break.  ws wont change.
00252     }
00253     else if (beforeRun->mType & eTrailingWS)
00254     {
00255       // need to delete the trailing ws that is before insertion point, because it 
00256       // would become significant after break inserted.
00257       res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset,
00258                         eOutsideUserSelectAll);
00259       NS_ENSURE_SUCCESS(res, res);
00260     }
00261     else if (beforeRun->mType == eNormalWS)
00262     {
00263       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
00264       res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
00265       NS_ENSURE_SUCCESS(res, res);
00266     }
00267   }
00268   
00269   // ready, aim, fire!
00270   return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect);
00271 }
00272 
00273 nsresult 
00274 nsWSRunObject::InsertText(const nsAString& aStringToInsert, 
00275                           nsCOMPtr<nsIDOMNode> *aInOutParent, 
00276                           PRInt32 *aInOutOffset,
00277                           nsIDOMDocument *aDoc)
00278 {
00279   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
00280   // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
00281 
00282   // MOOSE: for now, just getting the ws logic straight.  This implementation
00283   // is very slow.  Will need to replace edit rules impl with a more efficient
00284   // text sink here that does the minimal amount of searching/replacing/copying
00285 
00286   if (!aInOutParent || !aInOutOffset || !aDoc)
00287     return NS_ERROR_NULL_POINTER;
00288 
00289   nsresult res = NS_OK;
00290   if (aStringToInsert.IsEmpty()) return res;
00291   
00292   // string copying sux.  
00293   nsAutoString theString(aStringToInsert);
00294   
00295   WSFragment *beforeRun, *afterRun;
00296   res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
00297   res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
00298   
00299   {
00300     // some scoping for nsAutoTrackDOMPoint.  This will track our insertion point
00301     // while we tweak any surrounding whitespace
00302     nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset);
00303 
00304     // handle any changes needed to ws run after inserted text
00305     if (!afterRun)
00306     {
00307       // dont need to do anything.  just insert text.  ws wont change.
00308     }
00309     else if (afterRun->mType & eTrailingWS)
00310     {
00311       // dont need to do anything.  just insert text.  ws wont change.
00312     }
00313     else if (afterRun->mType & eLeadingWS)
00314     {
00315       // delete the leading ws that is after insertion point, because it 
00316       // would become significant after text inserted.
00317       res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset,
00318                          eOutsideUserSelectAll);
00319       NS_ENSURE_SUCCESS(res, res);
00320     }
00321     else if (afterRun->mType == eNormalWS)
00322     {
00323       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
00324       res = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
00325       NS_ENSURE_SUCCESS(res, res);
00326     }
00327     
00328     // handle any changes needed to ws run before inserted text
00329     if (!beforeRun)
00330     {
00331       // dont need to do anything.  just insert text.  ws wont change.
00332     }
00333     else if (beforeRun->mType & eLeadingWS)
00334     {
00335       // dont need to do anything.  just insert text.  ws wont change.
00336     }
00337     else if (beforeRun->mType & eTrailingWS)
00338     {
00339       // need to delete the trailing ws that is before insertion point, because it 
00340       // would become significant after text inserted.
00341       res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset,
00342                         eOutsideUserSelectAll);
00343       NS_ENSURE_SUCCESS(res, res);
00344     }
00345     else if (beforeRun->mType == eNormalWS)
00346     {
00347       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
00348       res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
00349       NS_ENSURE_SUCCESS(res, res);
00350     }
00351   }
00352   
00353   // next up, tweak head and tail of string as needed.
00354   // first the head:
00355   // there are a variety of circumstances that would require us to convert a 
00356   // leading ws char into an nbsp:
00357   
00358   if (nsCRT::IsAsciiSpace(theString[0]))
00359   {
00360     // we have a leading space
00361     if (beforeRun)
00362     {
00363       if (beforeRun->mType & eLeadingWS) 
00364       {
00365         theString.SetCharAt(nbsp, 0);
00366       }
00367       else if (beforeRun->mType & eNormalWS) 
00368       {
00369         WSPoint wspoint;
00370         res = GetCharBefore(*aInOutParent, *aInOutOffset, &wspoint);
00371         if (NS_SUCCEEDED(res) && wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar))
00372         {
00373           theString.SetCharAt(nbsp, 0);
00374         }
00375       }
00376     }
00377     else
00378     {
00379       if ((mStartReason & eBlock) || (mStartReason == eBreak))
00380       {
00381         theString.SetCharAt(nbsp, 0);
00382       }
00383     }
00384   }
00385 
00386   // then the tail
00387   PRUint32 lastCharIndex = theString.Length()-1;
00388 
00389   if (nsCRT::IsAsciiSpace(theString[lastCharIndex]))
00390   {
00391     // we have a leading space
00392     if (afterRun)
00393     {
00394       if (afterRun->mType & eTrailingWS)
00395       {
00396         theString.SetCharAt(nbsp, lastCharIndex);
00397       }
00398       else if (afterRun->mType & eNormalWS) 
00399       {
00400         WSPoint wspoint;
00401         res = GetCharAfter(*aInOutParent, *aInOutOffset, &wspoint);
00402         if (NS_SUCCEEDED(res) && wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar))
00403         {
00404           theString.SetCharAt(nbsp, lastCharIndex);
00405         }
00406       }
00407     }
00408     else
00409     {
00410       if ((mEndReason & eBlock))
00411       {
00412         theString.SetCharAt(nbsp, lastCharIndex);
00413       }
00414     }
00415   }
00416   
00417   // next scan string for adjacent ws and convert to nbsp/space combos
00418   // MOOSE: don't need to convert tabs here since that is done by WillInsertText() 
00419   // before we are called.  Eventually, all that logic will be pushed down into
00420   // here and made more efficient.
00421   PRUint32 j;
00422   PRBool prevWS = PR_FALSE;
00423   for (j=0; j<=lastCharIndex; j++)
00424   {
00425     if (nsCRT::IsAsciiSpace(theString[j]))
00426     {
00427       if (prevWS)
00428       {
00429         theString.SetCharAt(nbsp, j-1);  // j-1 cant be negative because prevWS starts out false
00430       }
00431       else
00432       {
00433         prevWS = PR_TRUE;
00434       }
00435     }
00436     else
00437     {
00438       prevWS = PR_FALSE;
00439     }
00440   }
00441   
00442   // ready, aim, fire!
00443   res = mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc);
00444   return NS_OK;
00445 }
00446 
00447 nsresult 
00448 nsWSRunObject::DeleteWSBackward()
00449 {
00450   nsresult res = NS_OK;
00451   WSPoint point;
00452   res = GetCharBefore(mNode, mOffset, &point);  
00453   NS_ENSURE_SUCCESS(res, res);
00454   if (!point.mTextNode) return NS_OK;  // nothing to delete
00455   
00456   if (mPRE)  // easy case, preformatted ws
00457   {
00458     if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp))
00459     {
00460       nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
00461       PRInt32 startOffset = point.mOffset;
00462       PRInt32 endOffset = point.mOffset+1;
00463       return DeleteChars(node, startOffset, node, endOffset);
00464     }
00465   }
00466   
00467   // callers job to insure that previous char is really ws.
00468   // If it is normal ws, we need to delete the whole run
00469   if (nsCRT::IsAsciiSpace(point.mChar))
00470   {
00471     nsCOMPtr<nsIDOMNode> startNode, endNode, node(do_QueryInterface(point.mTextNode));
00472     PRInt32 startOffset, endOffset;
00473     res = GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode), 
00474                          &startOffset, address_of(endNode), &endOffset);
00475     NS_ENSURE_SUCCESS(res, res);
00476     
00477     // adjust surrounding ws
00478     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, 
00479                                               address_of(endNode), &endOffset);
00480     NS_ENSURE_SUCCESS(res, res);
00481     
00482     // finally, delete that ws
00483     return DeleteChars(startNode, startOffset, endNode, endOffset);
00484   }
00485   else if (point.mChar == nbsp)
00486   {
00487     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
00488     // adjust surrounding ws
00489     PRInt32 startOffset = point.mOffset;
00490     PRInt32 endOffset = point.mOffset+1;
00491     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, 
00492                                               address_of(node), &endOffset);
00493     NS_ENSURE_SUCCESS(res, res);
00494     
00495     // finally, delete that ws
00496     return DeleteChars(node, startOffset, node, endOffset);
00497   
00498   }
00499   return NS_OK;
00500 }
00501 
00502 nsresult 
00503 nsWSRunObject::DeleteWSForward()
00504 {
00505   nsresult res = NS_OK;
00506   WSPoint point;
00507   res = GetCharAfter(mNode, mOffset, &point);  
00508   NS_ENSURE_SUCCESS(res, res);
00509   if (!point.mTextNode) return NS_OK;  // nothing to delete
00510   
00511   if (mPRE)  // easy case, preformatted ws
00512   {
00513     if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp))
00514     {
00515       nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
00516       PRInt32 startOffset = point.mOffset;
00517       PRInt32 endOffset = point.mOffset+1;
00518       return DeleteChars(node, startOffset, node, endOffset);
00519     }
00520   }
00521   
00522   // callers job to insure that next char is really ws.
00523   // If it is normal ws, we need to delete the whole run
00524   if (nsCRT::IsAsciiSpace(point.mChar))
00525   {
00526     nsCOMPtr<nsIDOMNode> startNode, endNode, node(do_QueryInterface(point.mTextNode));
00527     PRInt32 startOffset, endOffset;
00528     res = GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode), 
00529                          &startOffset, address_of(endNode), &endOffset);
00530     NS_ENSURE_SUCCESS(res, res);
00531     
00532     // adjust surrounding ws
00533     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, 
00534                                               address_of(endNode), &endOffset);
00535     NS_ENSURE_SUCCESS(res, res);
00536     
00537     // finally, delete that ws
00538     return DeleteChars(startNode, startOffset, endNode, endOffset);
00539   }
00540   else if (point.mChar == nbsp)
00541   {
00542     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
00543     // adjust surrounding ws
00544     PRInt32 startOffset = point.mOffset;
00545     PRInt32 endOffset = point.mOffset+1;
00546     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, 
00547                                               address_of(node), &endOffset);
00548     NS_ENSURE_SUCCESS(res, res);
00549     
00550     // finally, delete that ws
00551     return DeleteChars(node, startOffset, node, endOffset);
00552   
00553   }
00554   return NS_OK;
00555 }
00556 
00557 nsresult 
00558 nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode, 
00559                                 PRInt32 aOffset, 
00560                                 nsCOMPtr<nsIDOMNode> *outVisNode, 
00561                                 PRInt32 *outVisOffset,
00562                                 PRInt16 *outType)
00563 {
00564   // Find first visible thing before the point.  position outVisNode/outVisOffset
00565   // just _after_ that thing.  If we don't find anything return start of ws.
00566   if (!aNode || !outVisNode || !outVisOffset || !outType)
00567     return NS_ERROR_NULL_POINTER;
00568     
00569   *outType = eNone;
00570   WSFragment *run;
00571   FindRun(aNode, aOffset, &run, PR_FALSE);
00572   
00573   // is there a visible run there or earlier?
00574   while (run)
00575   {
00576     if (run->mType == eNormalWS)
00577     {
00578       WSPoint point;
00579       GetCharBefore(aNode, aOffset, &point);
00580       if (point.mTextNode)
00581       {
00582         *outVisNode = do_QueryInterface(point.mTextNode);
00583         *outVisOffset = point.mOffset+1;
00584         if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
00585         {
00586           *outType = eNormalWS;
00587         }
00588         else if (!point.mChar)
00589         {
00590           // MOOSE: not possible?
00591           *outType = eNone;
00592         }
00593         else
00594         {
00595           *outType = eText;
00596         }
00597         return NS_OK;
00598       }
00599       // else if no text node then keep looking.  We should eventually fall out of loop
00600     }
00601 
00602     run = run->mLeft;
00603   }
00604   
00605   // if we get here then nothing in ws data to find.  return start reason
00606   *outVisNode = mStartReasonNode;
00607   *outVisOffset = mStartOffset;  // this really isn't meaningful if mStartReasonNode!=mStartNode
00608   *outType = mStartReason;
00609   return NS_OK;
00610 }
00611 
00612 
00613 nsresult 
00614 nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode, 
00615                                 PRInt32 aOffset, 
00616                                 nsCOMPtr<nsIDOMNode> *outVisNode, 
00617                                 PRInt32 *outVisOffset,
00618                                 PRInt16 *outType)
00619 {
00620   // Find first visible thing after the point.  position outVisNode/outVisOffset
00621   // just _before_ that thing.  If we don't find anything return end of ws.
00622   if (!aNode || !outVisNode || !outVisOffset || !outType)
00623     return NS_ERROR_NULL_POINTER;
00624     
00625   WSFragment *run;
00626   FindRun(aNode, aOffset, &run, PR_TRUE);
00627   
00628   // is there a visible run there or later?
00629   while (run)
00630   {
00631     if (run->mType == eNormalWS)
00632     {
00633       WSPoint point;
00634       GetCharAfter(aNode, aOffset, &point);
00635       if (point.mTextNode)
00636       {
00637         *outVisNode = do_QueryInterface(point.mTextNode);
00638         *outVisOffset = point.mOffset;
00639         if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
00640         {
00641           *outType = eNormalWS;
00642         }
00643         else if (!point.mChar)
00644         {
00645           // MOOSE: not possible?
00646           *outType = eNone;
00647         }
00648         else
00649         {
00650           *outType = eText;
00651         }
00652         return NS_OK;
00653       }
00654       // else if no text node then keep looking.  We should eventually fall out of loop
00655     }
00656 
00657     run = run->mRight;
00658   }
00659   
00660   // if we get here then nothing in ws data to find.  return end reason
00661   *outVisNode = mEndReasonNode;
00662   *outVisOffset = mEndOffset; // this really isn't meaningful if mEndReasonNode!=mEndNode
00663   *outType = mEndReason;
00664   return NS_OK;
00665 }
00666 
00667 nsresult 
00668 nsWSRunObject::AdjustWhitespace()
00669 {
00670   // this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
00671   // replacing them with regualr ascii space if possible.  Keeping things simple
00672   // for now and just trying to fix up the trailing ws in the run.
00673   if (!mLastNBSPNode) return NS_OK; // nothing to do!
00674   nsresult res = NS_OK;
00675   WSFragment *curRun = mStartRun;
00676   while (curRun)
00677   {
00678     // look for normal ws run
00679     if (curRun->mType == eNormalWS)
00680     {
00681       res = CheckTrailingNBSPOfRun(curRun);
00682       break;
00683     }
00684     curRun = curRun->mRight;
00685   }
00686   return res;
00687 }
00688 
00689 
00690 //--------------------------------------------------------------------------------------------
00691 //   protected methods
00692 //--------------------------------------------------------------------------------------------
00693 
00694 nsresult
00695 nsWSRunObject::GetWSNodes()
00696 {
00697   // collect up an array of nodes that are contiguous with the insertion point
00698   // and which contain only whitespace.  Stop if you reach non-ws text or a new 
00699   // block boundary.
00700   nsresult res = NS_OK;
00701   
00702   nsCOMPtr<nsIDOMNode> blockParent;
00703   DOMPoint start(mNode, mOffset), end(mNode, mOffset);
00704   if (IsBlockNode(mNode)) blockParent = mNode;
00705   else blockParent = mHTMLEditor->GetBlockNodeParent(mNode);
00706 
00707   // first look backwards to find preceding ws nodes
00708   if (mHTMLEditor->IsTextNode(mNode))
00709   {
00710     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNode));
00711     const nsTextFragment *textFrag = textNode->Text();
00712     
00713     res = PrependNodeToList(mNode);
00714     NS_ENSURE_SUCCESS(res, res);
00715     if (mOffset)
00716     {
00717       PRInt32 pos;
00718       for (pos=mOffset-1; pos>=0; pos--)
00719       {
00720         // sanity bounds check the char position.  bug 136165
00721         if (pos >= textFrag->GetLength())
00722         {
00723           NS_NOTREACHED("looking beyond end of text fragment");
00724           continue;
00725         }
00726         PRUnichar theChar = textFrag->CharAt(pos);
00727         if (!nsCRT::IsAsciiSpace(theChar))
00728         {
00729           if (theChar != nbsp)
00730           {
00731             mStartNode = mNode;
00732             mStartOffset = pos+1;
00733             mStartReason = eText;
00734             mStartReasonNode = mNode;
00735             break;
00736           }
00737           // as we look backwards update our earliest found nbsp
00738           mFirstNBSPNode = mNode;
00739           mFirstNBSPOffset = pos;
00740           // also keep track of latest nbsp so far
00741           if (!mLastNBSPNode)
00742           {
00743             mLastNBSPNode = mNode;
00744             mLastNBSPOffset = pos;
00745           }
00746         }
00747         start.SetPoint(mNode,pos);
00748       }
00749     }
00750   }
00751 
00752   nsCOMPtr<nsIDOMNode> priorNode;
00753   while (!mStartNode)
00754   {
00755     // we haven't found the start of ws yet.  Keep looking
00756     res = GetPreviousWSNode(start, blockParent, address_of(priorNode));
00757     NS_ENSURE_SUCCESS(res, res);
00758     if (priorNode)
00759     {
00760       if (IsBlockNode(priorNode))
00761       {
00762         start.GetPoint(mStartNode, mStartOffset);
00763         mStartReason = eOtherBlock;
00764         mStartReasonNode = priorNode;
00765       }
00766       else if (mHTMLEditor->IsTextNode(priorNode))
00767       {
00768         res = PrependNodeToList(priorNode);
00769         NS_ENSURE_SUCCESS(res, res);
00770         nsCOMPtr<nsITextContent> textNode(do_QueryInterface(priorNode));
00771         if (!textNode) return NS_ERROR_NULL_POINTER;
00772         const nsTextFragment *textFrag = textNode->Text();
00773         PRUint32 len = textNode->TextLength();
00774 
00775         if (len < 1)
00776         {
00777           // Zero length text node. Set start point to it
00778           // so we can get past it!
00779           start.SetPoint(priorNode,0);
00780         }
00781         else
00782         {
00783           PRInt32 pos;
00784           for (pos=len-1; pos>=0; pos--)
00785           {
00786             // sanity bounds check the char position.  bug 136165
00787             if (pos >= textFrag->GetLength())
00788             {
00789               NS_NOTREACHED("looking beyond end of text fragment");
00790               continue;
00791             }
00792             PRUnichar theChar = textFrag->CharAt(pos);
00793             if (!nsCRT::IsAsciiSpace(theChar))
00794             {
00795               if (theChar != nbsp)
00796               {
00797                 mStartNode = priorNode;
00798                 mStartOffset = pos+1;
00799                 mStartReason = eText;
00800                 mStartReasonNode = priorNode;
00801                 break;
00802               }
00803               // as we look backwards update our earliest found nbsp
00804               mFirstNBSPNode = priorNode;
00805               mFirstNBSPOffset = pos;
00806               // also keep track of latest nbsp so far
00807               if (!mLastNBSPNode)
00808               {
00809                 mLastNBSPNode = priorNode;
00810                 mLastNBSPOffset = pos;
00811               }
00812             }
00813             start.SetPoint(priorNode,pos);
00814           }
00815         }
00816       }
00817       else
00818       {
00819         // it's a break or a special node, like <img>, that is not a block and not
00820         // a break but still serves as a terminator to ws runs.
00821         start.GetPoint(mStartNode, mStartOffset);
00822         if (nsTextEditUtils::IsBreak(priorNode))
00823           mStartReason = eBreak;
00824         else
00825           mStartReason = eSpecial;
00826         mStartReasonNode = priorNode;
00827       }
00828     }
00829     else
00830     {
00831       // no prior node means we exhausted blockParent
00832       start.GetPoint(mStartNode, mStartOffset);
00833       mStartReason = eThisBlock;
00834       mStartReasonNode = blockParent;
00835     } 
00836   }
00837   
00838   // then look ahead to find following ws nodes
00839   if (mHTMLEditor->IsTextNode(mNode))
00840   {
00841     // dont need to put it on list. it already is from code above
00842     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNode));
00843     const nsTextFragment *textFrag = textNode->Text();
00844 
00845     PRUint32 len = textNode->TextLength();
00846     if (mOffset<len)
00847     {
00848       PRInt32 pos;
00849       for (pos=mOffset; pos<len; pos++)
00850       {
00851         // sanity bounds check the char position.  bug 136165
00852         if ((pos<0) || (pos>=textFrag->GetLength()))
00853         {
00854           NS_NOTREACHED("looking beyond end of text fragment");
00855           continue;
00856         }
00857         PRUnichar theChar = textFrag->CharAt(pos);
00858         if (!nsCRT::IsAsciiSpace(theChar))
00859         {
00860           if (theChar != nbsp)
00861           {
00862             mEndNode = mNode;
00863             mEndOffset = pos;
00864             mEndReason = eText;
00865             mEndReasonNode = mNode;
00866             break;
00867           }
00868           // as we look forwards update our latest found nbsp
00869           mLastNBSPNode = mNode;
00870           mLastNBSPOffset = pos;
00871           // also keep track of earliest nbsp so far
00872           if (!mFirstNBSPNode)
00873           {
00874             mFirstNBSPNode = mNode;
00875             mFirstNBSPOffset = pos;
00876           }
00877         }
00878         end.SetPoint(mNode,pos);
00879       }
00880     }
00881   }
00882 
00883   nsCOMPtr<nsIDOMNode> nextNode;
00884   while (!mEndNode)
00885   {
00886     // we haven't found the end of ws yet.  Keep looking
00887     res = GetNextWSNode(end, blockParent, address_of(nextNode));
00888     NS_ENSURE_SUCCESS(res, res);
00889     if (nextNode)
00890     {
00891       if (IsBlockNode(nextNode))
00892       {
00893         // we encountered a new block.  therefore no more ws.
00894         end.GetPoint(mEndNode, mEndOffset);
00895         mEndReason = eOtherBlock;
00896         mEndReasonNode = nextNode;
00897       }
00898       else if (mHTMLEditor->IsTextNode(nextNode))
00899       {
00900         res = AppendNodeToList(nextNode);
00901         NS_ENSURE_SUCCESS(res, res);
00902         nsCOMPtr<nsITextContent> textNode(do_QueryInterface(nextNode));
00903         if (!textNode) return NS_ERROR_NULL_POINTER;
00904         const nsTextFragment *textFrag = textNode->Text();
00905         PRUint32 len = textNode->TextLength();
00906 
00907         if (len < 1)
00908         {
00909           // Zero length text node. Set end point to it
00910           // so we can get past it!
00911           end.SetPoint(nextNode,0);
00912         }
00913         else
00914         {
00915           PRInt32 pos;
00916           for (pos=0; pos<len; pos++)
00917           {
00918             // sanity bounds check the char position.  bug 136165
00919             if (pos >= textFrag->GetLength())
00920             {
00921               NS_NOTREACHED("looking beyond end of text fragment");
00922               continue;
00923             }
00924             PRUnichar theChar = textFrag->CharAt(pos);
00925             if (!nsCRT::IsAsciiSpace(theChar))
00926             {
00927               if (theChar != nbsp)
00928               {
00929                 mEndNode = nextNode;
00930                 mEndOffset = pos;
00931                 mEndReason = eText;
00932                 mEndReasonNode = nextNode;
00933                 break;
00934               }
00935               // as we look forwards update our latest found nbsp
00936               mLastNBSPNode = nextNode;
00937               mLastNBSPOffset = pos;
00938               // also keep track of earliest nbsp so far
00939               if (!mFirstNBSPNode)
00940               {
00941                 mFirstNBSPNode = nextNode;
00942                 mFirstNBSPOffset = pos;
00943               }
00944             }
00945             end.SetPoint(nextNode,pos+1);
00946           }
00947         }
00948       }
00949       else
00950       {
00951         // we encountered a break or a special node, like <img>, 
00952         // that is not a block and not a break but still 
00953         // serves as a terminator to ws runs.
00954         end.GetPoint(mEndNode, mEndOffset);
00955         if (nsTextEditUtils::IsBreak(nextNode))
00956           mEndReason = eBreak;
00957         else
00958           mEndReason = eSpecial;
00959         mEndReasonNode = nextNode;
00960       }
00961     }
00962     else
00963     {
00964       // no next node means we exhausted blockParent
00965       end.GetPoint(mEndNode, mEndOffset);
00966       mEndReason = eThisBlock;
00967       mEndReasonNode = blockParent;
00968     } 
00969   }
00970 
00971   return NS_OK;
00972 }
00973 
00974 nsresult
00975 nsWSRunObject::GetRuns()
00976 {
00977   ClearRuns();
00978   
00979   // handle some easy cases first
00980   mHTMLEditor->IsPreformatted(mNode, &mPRE);
00981   // if it's preformatedd, or if we are surrounded by text or special, it's all one
00982   // big normal ws run
00983   if ( mPRE || (((mStartReason == eText) || (mStartReason == eSpecial)) &&
00984        ((mEndReason == eText) || (mEndReason == eSpecial) || (mEndReason == eBreak))) )
00985   {
00986     return MakeSingleWSRun(eNormalWS);
00987   }
00988 
00989   // if we are before or after a block (or after a break), and there are no nbsp's,
00990   // then it's all non-rendering ws.
00991   if ( !(mFirstNBSPNode || mLastNBSPNode) &&
00992       ( (mStartReason & eBlock) || (mStartReason == eBreak) || (mEndReason & eBlock) ) )
00993   {
00994     PRInt16 wstype = eNone;
00995     if ((mStartReason & eBlock) || (mStartReason == eBreak))
00996       wstype = eLeadingWS;
00997     if (mEndReason & eBlock) 
00998       wstype |= eTrailingWS;
00999     return MakeSingleWSRun(wstype);
01000   }
01001   
01002   // otherwise a little trickier.  shucks.
01003   mStartRun = new WSFragment();
01004   if (!mStartRun) return NS_ERROR_NULL_POINTER;
01005   mStartRun->mStartNode = mStartNode;
01006   mStartRun->mStartOffset = mStartOffset;
01007   
01008   if ( (mStartReason & eBlock) || (mStartReason == eBreak) )
01009   {
01010     // set up mStartRun
01011     mStartRun->mType = eLeadingWS;
01012     mStartRun->mEndNode = mFirstNBSPNode;
01013     mStartRun->mEndOffset = mFirstNBSPOffset;
01014     mStartRun->mLeftType = mStartReason;
01015     mStartRun->mRightType = eNormalWS;
01016     
01017     // set up next run
01018     WSFragment *normalRun = new WSFragment();
01019     if (!normalRun) return NS_ERROR_NULL_POINTER;
01020     mStartRun->mRight = normalRun;
01021     normalRun->mType = eNormalWS;
01022     normalRun->mStartNode = mFirstNBSPNode;
01023     normalRun->mStartOffset = mFirstNBSPOffset;
01024     normalRun->mLeftType = eLeadingWS;
01025     normalRun->mLeft = mStartRun;
01026     if (mEndReason != eBlock)
01027     {
01028       // then no trailing ws.  this normal run ends the overall ws run.
01029       normalRun->mRightType = mEndReason;
01030       normalRun->mEndNode   = mEndNode;
01031       normalRun->mEndOffset = mEndOffset;
01032       mEndRun = normalRun;
01033     }
01034     else
01035     {
01036       // we might have trailing ws.
01037       // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
01038       // will point to it, even though in general start/end points not
01039       // guaranteed to be in text nodes.
01040       if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
01041       {
01042         // normal ws runs right up to adjacent block (nbsp next to block)
01043         normalRun->mRightType = mEndReason;
01044         normalRun->mEndNode   = mEndNode;
01045         normalRun->mEndOffset = mEndOffset;
01046         mEndRun = normalRun;
01047       }
01048       else
01049       {
01050         normalRun->mEndNode = mLastNBSPNode;
01051         normalRun->mEndOffset = mLastNBSPOffset+1;
01052         normalRun->mRightType = eTrailingWS;
01053         
01054         // set up next run
01055         WSFragment *lastRun = new WSFragment();
01056         if (!lastRun) return NS_ERROR_NULL_POINTER;
01057         lastRun->mType = eTrailingWS;
01058         lastRun->mStartNode = mLastNBSPNode;
01059         lastRun->mStartOffset = mLastNBSPOffset+1;
01060         lastRun->mEndNode = mEndNode;
01061         lastRun->mEndOffset = mEndOffset;
01062         lastRun->mLeftType = eNormalWS;
01063         lastRun->mLeft = normalRun;
01064         lastRun->mRightType = mEndReason;
01065         mEndRun = lastRun;
01066         normalRun->mRight = lastRun;
01067       }
01068     }
01069   }
01070   else // mStartReason is not eBlock or eBreak
01071   {
01072     // set up mStartRun
01073     mStartRun->mType = eNormalWS;
01074     mStartRun->mEndNode = mLastNBSPNode;
01075     mStartRun->mEndOffset = mLastNBSPOffset+1;
01076     mStartRun->mLeftType = mStartReason;
01077 
01078     // we might have trailing ws.
01079     // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
01080     // will point to it, even though in general start/end points not
01081     // guaranteed to be in text nodes.
01082     if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
01083     {
01084       mStartRun->mRightType = mEndReason;
01085       mStartRun->mEndNode   = mEndNode;
01086       mStartRun->mEndOffset = mEndOffset;
01087       mEndRun = mStartRun;
01088     }
01089     else
01090     {
01091       // set up next run
01092       WSFragment *lastRun = new WSFragment();
01093       if (!lastRun) return NS_ERROR_NULL_POINTER;
01094       lastRun->mType = eTrailingWS;
01095       lastRun->mStartNode = mLastNBSPNode;
01096       lastRun->mStartOffset = mLastNBSPOffset+1;
01097       lastRun->mLeftType = eNormalWS;
01098       lastRun->mLeft = mStartRun;
01099       lastRun->mRightType = mEndReason;
01100       mEndRun = lastRun;
01101       mStartRun->mRight = lastRun;
01102       mStartRun->mRightType = eTrailingWS;
01103     }
01104   }
01105   
01106   return NS_OK;
01107 }
01108 
01109 void
01110 nsWSRunObject::ClearRuns()
01111 {
01112   WSFragment *tmp, *run;
01113   run = mStartRun;
01114   while (run)
01115   {
01116     tmp = run->mRight;
01117     delete run;
01118     run = tmp;
01119   }
01120   mStartRun = 0;
01121   mEndRun = 0;
01122 }
01123 
01124 nsresult 
01125 nsWSRunObject::MakeSingleWSRun(PRInt16 aType)
01126 {
01127   mStartRun = new WSFragment();
01128   if (!mStartRun) return NS_ERROR_NULL_POINTER;
01129 
01130   mStartRun->mStartNode   = mStartNode;
01131   mStartRun->mStartOffset = mStartOffset;
01132   mStartRun->mType        = aType;
01133   mStartRun->mEndNode     = mEndNode;
01134   mStartRun->mEndOffset   = mEndOffset;
01135   mStartRun->mLeftType    = mStartReason;
01136   mStartRun->mRightType   = mEndReason;
01137   
01138   mEndRun  = mStartRun;
01139   
01140   return NS_OK;
01141 }
01142 
01143 nsresult 
01144 nsWSRunObject::PrependNodeToList(nsIDOMNode *aNode)
01145 {
01146   if (!aNode) return NS_ERROR_NULL_POINTER;
01147   if (!mNodeArray.InsertObjectAt(aNode, 0))
01148     return NS_ERROR_FAILURE;
01149   return NS_OK;
01150 }
01151 
01152 nsresult 
01153 nsWSRunObject::AppendNodeToList(nsIDOMNode *aNode)
01154 {
01155   if (!aNode) return NS_ERROR_NULL_POINTER;
01156   if (!mNodeArray.AppendObject(aNode))
01157     return NS_ERROR_FAILURE;
01158   return NS_OK;
01159 }
01160 
01161 nsresult 
01162 nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, 
01163                                  nsIDOMNode *aBlockParent, 
01164                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
01165 {
01166   // can't really recycle various getnext/prior routines because we
01167   // have special needs here.  Need to step into inline containers but
01168   // not block containers.
01169   if (!aStartNode || !aBlockParent || !aPriorNode) return NS_ERROR_NULL_POINTER;
01170   
01171   nsresult res = aStartNode->GetPreviousSibling(getter_AddRefs(*aPriorNode));
01172   NS_ENSURE_SUCCESS(res, res);
01173   nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
01174   while (!*aPriorNode)
01175   {
01176     // we have exhausted nodes in parent of aStartNode.
01177     res = curNode->GetParentNode(getter_AddRefs(temp));
01178     NS_ENSURE_SUCCESS(res, res);
01179     if (!temp) return NS_ERROR_NULL_POINTER;
01180     if (temp == aBlockParent)
01181     {
01182       // we have exhausted nodes in the block parent.  The convention here is to return null.
01183       *aPriorNode = nsnull;
01184       return NS_OK;
01185     }
01186     // we have a parent: look for previous sibling
01187     res = temp->GetPreviousSibling(getter_AddRefs(*aPriorNode));
01188     NS_ENSURE_SUCCESS(res, res);
01189     curNode = temp;
01190   }
01191   // we have a prior node.  If it's a block, return it.
01192   if (IsBlockNode(*aPriorNode))
01193     return NS_OK;
01194   // else if it's a container, get deep rightmost child
01195   else if (mHTMLEditor->IsContainer(*aPriorNode))
01196   {
01197     temp = mHTMLEditor->GetRightmostChild(*aPriorNode);
01198     if (temp)
01199       *aPriorNode = temp;
01200     return NS_OK;
01201   }
01202   // else return the node itself
01203   return NS_OK;
01204 }
01205 
01206 nsresult 
01207 nsWSRunObject::GetPreviousWSNode(DOMPoint aPoint,
01208                                  nsIDOMNode *aBlockParent, 
01209                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
01210 {
01211   nsCOMPtr<nsIDOMNode> node;
01212   PRInt32 offset;
01213   aPoint.GetPoint(node, offset);
01214   return GetPreviousWSNode(node,offset,aBlockParent,aPriorNode);
01215 }
01216 
01217 nsresult 
01218 nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode,
01219                                  PRInt16 aOffset, 
01220                                  nsIDOMNode *aBlockParent, 
01221                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
01222 {
01223   // can't really recycle various getnext/prior routines because we
01224   // have special needs here.  Need to step into inline containers but
01225   // not block containers.
01226   if (!aStartNode || !aBlockParent || !aPriorNode)
01227     return NS_ERROR_NULL_POINTER;
01228   *aPriorNode = 0;
01229 
01230   if (mHTMLEditor->IsTextNode(aStartNode))
01231     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
01232   if (!mHTMLEditor->IsContainer(aStartNode))
01233     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
01234   
01235   if (!aOffset)
01236   {
01237     if (aStartNode==aBlockParent)
01238     {
01239       // we are at start of the block.
01240       return NS_OK;
01241     }
01242 
01243     // we are at start of non-block container
01244     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
01245   }
01246 
01247   nsCOMPtr<nsIContent> startContent( do_QueryInterface(aStartNode) );
01248 
01249   nsIContent *priorContent = startContent->GetChildAt(aOffset - 1);
01250   if (!priorContent) 
01251     return NS_ERROR_NULL_POINTER;
01252   *aPriorNode = do_QueryInterface(priorContent);
01253   // we have a prior node.  If it's a block, return it.
01254   if (IsBlockNode(*aPriorNode))
01255     return NS_OK;
01256   // else if it's a container, get deep rightmost child
01257   else if (mHTMLEditor->IsContainer(*aPriorNode))
01258   {
01259     nsCOMPtr<nsIDOMNode> temp;
01260     temp = mHTMLEditor->GetRightmostChild(*aPriorNode);
01261     if (temp)
01262       *aPriorNode = temp;
01263     return NS_OK;
01264   }
01265   // else return the node itself
01266   return NS_OK;
01267 }
01268 
01269 nsresult 
01270 nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode, 
01271                              nsIDOMNode *aBlockParent, 
01272                              nsCOMPtr<nsIDOMNode> *aNextNode)
01273 {
01274   // can't really recycle various getnext/prior routines because we
01275   // have special needs here.  Need to step into inline containers but
01276   // not block containers.
01277   if (!aStartNode || !aBlockParent || !aNextNode)
01278     return NS_ERROR_NULL_POINTER;
01279   
01280   *aNextNode = 0;
01281   nsresult res = aStartNode->GetNextSibling(getter_AddRefs(*aNextNode));
01282   NS_ENSURE_SUCCESS(res, res);
01283   nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
01284   while (!*aNextNode)
01285   {
01286     // we have exhausted nodes in parent of aStartNode.
01287     res = curNode->GetParentNode(getter_AddRefs(temp));
01288     NS_ENSURE_SUCCESS(res, res);
01289     if (!temp) return NS_ERROR_NULL_POINTER;
01290     if (temp == aBlockParent)
01291     {
01292       // we have exhausted nodes in the block parent.  The convention
01293       // here is to return null.
01294       *aNextNode = nsnull;
01295       return NS_OK;
01296     }
01297     // we have a parent: look for next sibling
01298     res = temp->GetNextSibling(getter_AddRefs(*aNextNode));
01299     NS_ENSURE_SUCCESS(res, res);
01300     curNode = temp;
01301   }
01302   // we have a next node.  If it's a block, return it.
01303   if (IsBlockNode(*aNextNode))
01304     return NS_OK;
01305   // else if it's a container, get deep leftmost child
01306   else if (mHTMLEditor->IsContainer(*aNextNode))
01307   {
01308     temp = mHTMLEditor->GetLeftmostChild(*aNextNode);
01309     if (temp)
01310       *aNextNode = temp;
01311     return NS_OK;
01312   }
01313   // else return the node itself
01314   return NS_OK;
01315 }
01316 
01317 nsresult 
01318 nsWSRunObject::GetNextWSNode(DOMPoint aPoint,
01319                              nsIDOMNode *aBlockParent, 
01320                              nsCOMPtr<nsIDOMNode> *aNextNode)
01321 {
01322   nsCOMPtr<nsIDOMNode> node;
01323   PRInt32 offset;
01324   aPoint.GetPoint(node, offset);
01325   return GetNextWSNode(node,offset,aBlockParent,aNextNode);
01326 }
01327 
01328 nsresult 
01329 nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode,
01330                              PRInt16 aOffset, 
01331                              nsIDOMNode *aBlockParent, 
01332                              nsCOMPtr<nsIDOMNode> *aNextNode)
01333 {
01334   // can't really recycle various getnext/prior routines because we have special needs
01335   // here.  Need to step into inline containers but not block containers.
01336   if (!aStartNode || !aBlockParent || !aNextNode)
01337     return NS_ERROR_NULL_POINTER;
01338   *aNextNode = 0;
01339 
01340   if (mHTMLEditor->IsTextNode(aStartNode))
01341     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
01342   if (!mHTMLEditor->IsContainer(aStartNode))
01343     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
01344   
01345   nsCOMPtr<nsIContent> startContent( do_QueryInterface(aStartNode) );
01346   nsIContent *nextContent = startContent->GetChildAt(aOffset);
01347   if (!nextContent)
01348   {
01349     if (aStartNode==aBlockParent)
01350     {
01351       // we are at end of the block.
01352       return NS_OK;
01353     }
01354 
01355     // we are at end of non-block container
01356     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
01357   }
01358   
01359   *aNextNode = do_QueryInterface(nextContent);
01360   // we have a next node.  If it's a block, return it.
01361   if (IsBlockNode(*aNextNode))
01362     return NS_OK;
01363   // else if it's a container, get deep leftmost child
01364   else if (mHTMLEditor->IsContainer(*aNextNode))
01365   {
01366     nsCOMPtr<nsIDOMNode> temp;
01367     temp = mHTMLEditor->GetLeftmostChild(*aNextNode);
01368     if (temp)
01369       *aNextNode = temp;
01370     return NS_OK;
01371   }
01372   // else return the node itself
01373   return NS_OK;
01374 }
01375 
01376 nsresult 
01377 nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
01378 {
01379   // this routine adjust whitespace before *this* and after aEndObject
01380   // in preperation for the two areas to become adjacent after the 
01381   // intervening content is deleted.  It's overly agressive right
01382   // now.  There might be a block boundary remaining between them after
01383   // the deletion, in which case these adjstments are unneeded (though
01384   // I don't think they can ever be harmful?)
01385   
01386   if (!aEndObject)
01387     return NS_ERROR_NULL_POINTER;
01388   nsresult res = NS_OK;
01389   
01390   // get the runs before and after selection
01391   WSFragment *beforeRun, *afterRun;
01392   res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
01393   NS_ENSURE_SUCCESS(res, res);
01394   res = aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, PR_TRUE);
01395   NS_ENSURE_SUCCESS(res, res);
01396   
01397   // trim after run of any leading ws
01398   if (afterRun && (afterRun->mType & eLeadingWS))
01399   {
01400     res = aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset, afterRun->mEndNode, afterRun->mEndOffset,
01401                                   eOutsideUserSelectAll);
01402     NS_ENSURE_SUCCESS(res, res);
01403   }
01404   // adjust normal ws in afterRun if needed
01405   if (afterRun && (afterRun->mType == eNormalWS) && !aEndObject->mPRE)
01406   {
01407     if ( (beforeRun && (beforeRun->mType & eLeadingWS)) ||
01408          (!beforeRun && ((mStartReason & eBlock) || (mStartReason == eBreak))) )
01409     {
01410       // make sure leading char of following ws is an nbsp, so that it will show up
01411       WSPoint point;
01412       aEndObject->GetCharAfter(aEndObject->mNode, aEndObject->mOffset, &point);
01413       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
01414       {
01415         res = aEndObject->ConvertToNBSP(point, eOutsideUserSelectAll);
01416         NS_ENSURE_SUCCESS(res, res);
01417       }
01418     }
01419   }
01420   // trim before run of any trailing ws
01421   if (beforeRun && (beforeRun->mType & eTrailingWS))
01422   {
01423     res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, mNode, mOffset,
01424                       eOutsideUserSelectAll);
01425     NS_ENSURE_SUCCESS(res, res);
01426   }
01427   else if (beforeRun && (beforeRun->mType == eNormalWS) && !mPRE)
01428   {
01429     if ( (afterRun && (afterRun->mType & eTrailingWS)) ||
01430          (afterRun && (afterRun->mType == eNormalWS))   ||
01431          (!afterRun && ((aEndObject->mEndReason & eBlock))) )
01432     {
01433       // make sure trailing char of starting ws is an nbsp, so that it will show up
01434       WSPoint point;
01435       GetCharBefore(mNode, mOffset, &point);
01436       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
01437       {
01438         nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
01439         PRInt32 wsStartOffset, wsEndOffset;
01440         res = GetAsciiWSBounds(eBoth, mNode, mOffset, 
01441                                address_of(wsStartNode), &wsStartOffset, 
01442                                address_of(wsEndNode), &wsEndOffset);
01443         NS_ENSURE_SUCCESS(res, res);
01444         point.mTextNode = do_QueryInterface(wsStartNode);
01445         point.mOffset = wsStartOffset;
01446         res = ConvertToNBSP(point, eOutsideUserSelectAll);
01447         NS_ENSURE_SUCCESS(res, res);
01448       }
01449     }
01450   }
01451   return res;
01452 }
01453 
01454 nsresult 
01455 nsWSRunObject::PrepareToSplitAcrossBlocksPriv()
01456 {
01457   // used to prepare ws to be split across two blocks.  The main issue 
01458   // here is make sure normalWS doesn't end up becoming non-significant
01459   // leading or trailing ws after the split.
01460   nsresult res = NS_OK;
01461   
01462   // get the runs before and after selection
01463   WSFragment *beforeRun, *afterRun;
01464   res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
01465   NS_ENSURE_SUCCESS(res, res);
01466   res = FindRun(mNode, mOffset, &afterRun, PR_TRUE);
01467   
01468   // adjust normal ws in afterRun if needed
01469   if (afterRun && (afterRun->mType == eNormalWS))
01470   {
01471     // make sure leading char of following ws is an nbsp, so that it will show up
01472     WSPoint point;
01473     GetCharAfter(mNode, mOffset, &point);
01474     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
01475     {
01476       res = ConvertToNBSP(point);
01477       NS_ENSURE_SUCCESS(res, res);
01478     }
01479   }
01480 
01481   // adjust normal ws in beforeRun if needed
01482   if (beforeRun && (beforeRun->mType == eNormalWS))
01483   {
01484     // make sure trailing char of starting ws is an nbsp, so that it will show up
01485     WSPoint point;
01486     GetCharBefore(mNode, mOffset, &point);
01487     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
01488     {
01489       nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
01490       PRInt32 wsStartOffset, wsEndOffset;
01491       res = GetAsciiWSBounds(eBoth, mNode, mOffset, 
01492                              address_of(wsStartNode), &wsStartOffset, 
01493                              address_of(wsEndNode), &wsEndOffset);
01494       NS_ENSURE_SUCCESS(res, res);
01495       point.mTextNode = do_QueryInterface(wsStartNode);
01496       point.mOffset = wsStartOffset;
01497       res = ConvertToNBSP(point);
01498       NS_ENSURE_SUCCESS(res, res);
01499     }
01500   }
01501   return res;
01502 }
01503 
01504 nsresult 
01505 nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset, 
01506                            nsIDOMNode *aEndNode, PRInt32 aEndOffset,
01507                            AreaRestriction aAR)
01508 {
01509   // MOOSE: this routine needs to be modified to preserve the integrity of the
01510   // wsFragment info.
01511   if (!aStartNode || !aEndNode)
01512     return NS_ERROR_NULL_POINTER;
01513 
01514   if (aAR == eOutsideUserSelectAll)
01515   {
01516     nsCOMPtr<nsIDOMNode> san = mHTMLEditor->FindUserSelectAllNode(aStartNode);
01517     if (san)
01518       return NS_OK;
01519     
01520     if (aStartNode != aEndNode)
01521     {
01522       san = mHTMLEditor->FindUserSelectAllNode(aEndNode);
01523       if (san)
01524         return NS_OK;
01525     }
01526   }
01527 
01528   if ((aStartNode == aEndNode) && (aStartOffset == aEndOffset))
01529     return NS_OK;  // nothing to delete
01530   
01531   nsresult res = NS_OK;
01532   PRInt32 idx = mNodeArray.IndexOf(aStartNode);
01533   if (idx==-1) idx = 0; // if our strarting point wasn't one of our ws text nodes,
01534                         // then just go through them from the beginning.
01535   nsCOMPtr<nsIDOMNode> node;
01536   nsCOMPtr<nsIDOMCharacterData> textnode;
01537   nsCOMPtr<nsIDOMRange> range;
01538 
01539   if (aStartNode == aEndNode)
01540   {
01541     textnode = do_QueryInterface(aStartNode);
01542     if (textnode)
01543     {
01544       return mHTMLEditor->DeleteText(textnode, (PRUint32)aStartOffset, 
01545                                      (PRUint32)(aEndOffset-aStartOffset));
01546     }
01547   }
01548 
01549   PRInt32 count = mNodeArray.Count();
01550   while (idx < count)
01551   {
01552     node = mNodeArray[idx];
01553     if (!node)
01554       break;  // we ran out of ws nodes; must have been deleting to end
01555     if (node == aStartNode)
01556     {
01557       textnode = do_QueryInterface(node);
01558       PRUint32 len;
01559       textnode->GetLength(&len);
01560       if (aStartOffset<len)
01561       {
01562         res = mHTMLEditor->DeleteText(textnode, (PRUint32)aStartOffset, len-aStartOffset);
01563         NS_ENSURE_SUCCESS(res, res);
01564       }
01565     }
01566     else if (node == aEndNode)
01567     {
01568       if (aEndOffset)
01569       {
01570         textnode = do_QueryInterface(node);
01571         res = mHTMLEditor->DeleteText(textnode, 0, (PRUint32)aEndOffset);
01572         NS_ENSURE_SUCCESS(res, res);
01573       }
01574       break;
01575     }
01576     else
01577     {
01578       if (!range)
01579       {
01580         range = do_CreateInstance("@mozilla.org/content/range;1");
01581         if (!range) return NS_ERROR_OUT_OF_MEMORY;
01582         res = range->SetStart(aStartNode, aStartOffset);
01583         NS_ENSURE_SUCCESS(res, res);
01584         res = range->SetEnd(aEndNode, aEndOffset);
01585         NS_ENSURE_SUCCESS(res, res);
01586       }
01587       PRBool nodeBefore, nodeAfter;
01588       nsCOMPtr<nsIContent> content (do_QueryInterface(node));
01589       res = mHTMLEditor->sRangeHelper->CompareNodeToRange(content, range, &nodeBefore, &nodeAfter);
01590       NS_ENSURE_SUCCESS(res, res);
01591       if (nodeAfter)
01592       {
01593         break;
01594       }
01595       if (!nodeBefore)
01596       {
01597         res = mHTMLEditor->DeleteNode(node);
01598         NS_ENSURE_SUCCESS(res, res);
01599         mNodeArray.RemoveObject(node);
01600         --count;
01601         --idx;
01602       }
01603     }
01604     idx++;
01605   }
01606   return res;
01607 }
01608 
01609 nsresult 
01610 nsWSRunObject::GetCharAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
01611 {
01612   if (!aNode || !outPoint)
01613     return NS_ERROR_NULL_POINTER;
01614 
01615   PRInt32 idx = mNodeArray.IndexOf(aNode);
01616   if (idx == -1) 
01617   {
01618     // use range comparisons to get right ws node
01619     return GetWSPointAfter(aNode, aOffset, outPoint);
01620   }
01621   else
01622   {
01623     // use wspoint version of GetCharAfter()
01624     WSPoint point(aNode,aOffset,0);
01625     return GetCharAfter(point, outPoint);
01626   }
01627   
01628   return NS_ERROR_FAILURE;
01629 }
01630 
01631 nsresult 
01632 nsWSRunObject::GetCharBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
01633 {
01634   if (!aNode || !outPoint)
01635     return NS_ERROR_NULL_POINTER;
01636 
01637   PRInt32 idx = mNodeArray.IndexOf(aNode);
01638   if (idx == -1) 
01639   {
01640     // use range comparisons to get right ws node
01641     return GetWSPointBefore(aNode, aOffset, outPoint);
01642   }
01643   else
01644   {
01645     // use wspoint version of GetCharBefore()
01646     WSPoint point(aNode,aOffset,0);
01647     return GetCharBefore(point, outPoint);
01648   }
01649   
01650   return NS_ERROR_FAILURE;
01651 }
01652 
01653 nsresult 
01654 nsWSRunObject::GetCharAfter(WSPoint &aPoint, WSPoint *outPoint)
01655 {
01656   if (!aPoint.mTextNode || !outPoint)
01657     return NS_ERROR_NULL_POINTER;
01658   
01659   outPoint->mTextNode = nsnull;
01660   outPoint->mOffset = 0;
01661   outPoint->mChar = 0;
01662 
01663   nsCOMPtr<nsIDOMNode> pointTextNode(do_QueryInterface(aPoint.mTextNode));
01664   PRInt32 idx = mNodeArray.IndexOf(pointTextNode);
01665   if (idx == -1) return NS_OK;  // can't find point, but it's not an error
01666   PRInt32 numNodes = mNodeArray.Count();
01667   
01668   if (aPoint.mOffset < aPoint.mTextNode->TextLength())
01669   {
01670     *outPoint = aPoint;
01671     outPoint->mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
01672   }
01673   else if (idx < (PRInt32)(numNodes-1))
01674   {
01675     nsIDOMNode* node = mNodeArray[idx+1];
01676     if (!node) return NS_ERROR_FAILURE;
01677     outPoint->mTextNode = do_QueryInterface(node);
01678     outPoint->mOffset = 0;
01679     outPoint->mChar = GetCharAt(outPoint->mTextNode, 0);
01680   }
01681   return NS_OK;
01682 }
01683 
01684 nsresult 
01685 nsWSRunObject::GetCharBefore(WSPoint &aPoint, WSPoint *outPoint)
01686 {
01687   if (!aPoint.mTextNode || !outPoint)
01688     return NS_ERROR_NULL_POINTER;
01689   
01690   outPoint->mTextNode = nsnull;
01691   outPoint->mOffset = 0;
01692   outPoint->mChar = 0;
01693   
01694   nsresult res = NS_OK;
01695   nsCOMPtr<nsIDOMNode> pointTextNode(do_QueryInterface(aPoint.mTextNode));
01696   PRInt32 idx = mNodeArray.IndexOf(pointTextNode);
01697   if (idx == -1) return NS_OK;  // can't find point, but it's not an error
01698   
01699   if (aPoint.mOffset != 0)
01700   {
01701     *outPoint = aPoint;
01702     outPoint->mOffset--;
01703     outPoint->mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset-1);
01704   }
01705   else if (idx)
01706   {
01707     nsIDOMNode* node = mNodeArray[idx-1];
01708     if (!node) return NS_ERROR_FAILURE;
01709     outPoint->mTextNode = do_QueryInterface(node);
01710 
01711     PRUint32 len = outPoint->mTextNode->TextLength();
01712 
01713     if (len)
01714     {
01715       outPoint->mOffset = len-1;
01716       outPoint->mChar = GetCharAt(outPoint->mTextNode, len-1);
01717     }
01718   }
01719   return NS_OK;
01720 }
01721 
01722 nsresult 
01723 nsWSRunObject::ConvertToNBSP(WSPoint aPoint, AreaRestriction aAR)
01724 {
01725   // MOOSE: this routine needs to be modified to preserve the integrity of the
01726   // wsFragment info.
01727   if (!aPoint.mTextNode)
01728     return NS_ERROR_NULL_POINTER;
01729 
01730   if (aAR == eOutsideUserSelectAll)
01731   {
01732     nsCOMPtr<nsIDOMNode> domnode = do_QueryInterface(aPoint.mTextNode);
01733     if (domnode)
01734     {
01735       nsCOMPtr<nsIDOMNode> san = mHTMLEditor->FindUserSelectAllNode(domnode);
01736       if (san)
01737         return NS_OK;
01738     }
01739   }
01740 
01741   nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(aPoint.mTextNode));
01742   if (!textNode)
01743     return NS_ERROR_NULL_POINTER;
01744   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(textNode));
01745   
01746   // first, insert an nbsp
01747   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
01748   nsAutoString nbspStr(nbsp);
01749   nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, aPoint.mOffset, PR_TRUE);
01750   NS_ENSURE_SUCCESS(res, res);
01751   
01752   // next, find range of ws it will replace
01753   nsCOMPtr<nsIDOMNode> startNode, endNode;
01754   PRInt32 startOffset=0, endOffset=0;
01755   
01756   res = GetAsciiWSBounds(eAfter, node, aPoint.mOffset+1, address_of(startNode), 
01757                          &startOffset, address_of(endNode), &endOffset);
01758   NS_ENSURE_SUCCESS(res, res);
01759   
01760   // finally, delete that replaced ws, if any
01761   if (startNode)
01762   {
01763     res = DeleteChars(startNode, startOffset, endNode, endOffset);
01764   }
01765   
01766   return res;
01767 }
01768 
01769 nsresult
01770 nsWSRunObject::GetAsciiWSBounds(PRInt16 aDir, nsIDOMNode *aNode, PRInt32 aOffset,
01771                                 nsCOMPtr<nsIDOMNode> *outStartNode, PRInt32 *outStartOffset,
01772                                 nsCOMPtr<nsIDOMNode> *outEndNode, PRInt32 *outEndOffset)
01773 {
01774   if (!aNode || !outStartNode || !outEndNode)
01775     return NS_ERROR_NULL_POINTER;
01776 
01777   nsCOMPtr<nsIDOMNode> startNode, endNode;
01778   PRInt32 startOffset=0, endOffset=0;
01779   
01780   nsresult res = NS_OK;
01781   
01782   if (aDir & eAfter)
01783   {
01784     WSPoint point, tmp;
01785     res = GetCharAfter(aNode, aOffset, &point);
01786     if (NS_SUCCEEDED(res) && point.mTextNode)
01787     {  // we found a text node, at least
01788       endNode = do_QueryInterface(point.mTextNode);
01789       endOffset = point.mOffset;
01790       startNode = endNode;
01791       startOffset = endOffset;
01792       
01793       // scan ahead to end of ascii ws
01794       while (nsCRT::IsAsciiSpace(point.mChar))
01795       {
01796         endNode = do_QueryInterface(point.mTextNode);
01797         point.mOffset++;  // endOffset is _after_ ws
01798         endOffset = point.mOffset;
01799         tmp = point;
01800         res = GetCharAfter(tmp, &point);
01801         if (NS_FAILED(res) || !point.mTextNode) break;
01802       }
01803     }
01804   }
01805   
01806   if (aDir & eBefore)
01807   {
01808     WSPoint point, tmp;
01809     res = GetCharBefore(aNode, aOffset, &point);
01810     if (NS_SUCCEEDED(res) && point.mTextNode)
01811     {  // we found a text node, at least
01812       startNode = do_QueryInterface(point.mTextNode);
01813       startOffset = point.mOffset+1;
01814       if (!endNode)
01815       {
01816         endNode = startNode;
01817         endOffset = startOffset;
01818       }
01819       
01820       // scan back to start of ascii ws
01821       while (nsCRT::IsAsciiSpace(point.mChar))
01822       {
01823         startNode = do_QueryInterface(point.mTextNode);
01824         startOffset = point.mOffset;
01825         tmp = point;
01826         res = GetCharBefore(tmp, &point);
01827         if (NS_FAILED(res) || !point.mTextNode) break;
01828       }
01829     }
01830   }  
01831   
01832   *outStartNode = startNode;
01833   *outStartOffset = startOffset;
01834   *outEndNode = endNode;
01835   *outEndOffset = endOffset;
01836 
01837   return NS_OK;
01838 }
01839 
01840 nsresult
01841 nsWSRunObject::FindRun(nsIDOMNode *aNode, PRInt32 aOffset, WSFragment **outRun, PRBool after)
01842 {
01843   // given a dompoint, find the ws run that is before or after it, as caller needs
01844   if (!aNode || !outRun)
01845     return NS_ERROR_NULL_POINTER;
01846     
01847   nsresult res = NS_OK;
01848   WSFragment *run = mStartRun;
01849   while (run)
01850   {
01851     PRInt16 comp = mHTMLEditor->sRangeHelper->ComparePoints(aNode, aOffset, run->mStartNode, run->mStartOffset);
01852     if (comp <= 0)
01853     {
01854       if (after)
01855       {
01856         *outRun = run;
01857         return res;
01858       }
01859       else // before
01860       {
01861         *outRun = nsnull;
01862         return res;
01863       }
01864     }
01865     comp = mHTMLEditor->sRangeHelper->ComparePoints(aNode, aOffset, run->mEndNode, run->mEndOffset);
01866     if (comp < 0)
01867     {
01868       *outRun = run;
01869       return res;
01870     }
01871     else if (comp == 0)
01872     {
01873       if (after)
01874       {
01875         *outRun = run->mRight;
01876         return res;
01877       }
01878       else // before
01879       {
01880         *outRun = run;
01881         return res;
01882       }
01883     }
01884     if (!run->mRight)
01885     {
01886       if (after)
01887       {
01888         *outRun = nsnull;
01889         return res;
01890       }
01891       else // before
01892       {
01893         *outRun = run;
01894         return res;
01895       }
01896     }
01897     run = run->mRight;
01898   }
01899   return res;
01900 }
01901 
01902 PRUnichar 
01903 nsWSRunObject::GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset)
01904 {
01905   // return 0 if we can't get a char, for whatever reason
01906   if (!aTextNode)
01907     return 0;
01908     
01909   const nsTextFragment *textFrag = aTextNode->Text();
01910   
01911   PRUint32 len = textFrag->GetLength();
01912   if (aOffset < 0 || aOffset>=len) 
01913     return 0;
01914     
01915   return textFrag->CharAt(aOffset);
01916 }
01917 
01918 nsresult 
01919 nsWSRunObject::GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
01920 {
01921   // Note: only to be called if aNode is not a ws node.  
01922   
01923   // binary search on wsnodes
01924   PRInt32 numNodes, firstNum, curNum, lastNum;
01925   numNodes = mNodeArray.Count();
01926   
01927   if (!numNodes) 
01928     return NS_OK; // do nothing if there are no nodes to search
01929 
01930   firstNum = 0;
01931   curNum = numNodes/2;
01932   lastNum = numNodes;
01933   PRInt16 cmp=0;
01934   nsCOMPtr<nsIDOMNode>  curNode;
01935   
01936   // begin binary search
01937   // we do this because we need to minimize calls to ComparePoints(),
01938   // which is mongo expensive
01939   while (curNum != lastNum)
01940   {
01941     curNode = mNodeArray[curNum];
01942     cmp = mHTMLEditor->sRangeHelper->ComparePoints(aNode, aOffset, curNode, 0);
01943     if (cmp < 0)
01944       lastNum = curNum;
01945     else
01946       firstNum = curNum + 1;
01947     curNum = (lastNum - firstNum) / 2 + firstNum;
01948     NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
01949   }
01950 
01951   // When the binary search is complete, we always know that the current node
01952   // is the same as the end node, which is always past our range. Therefore,
01953   // we've found the node immediately after the point of interest.
01954   if (curNum == mNodeArray.Count()) {
01955     // they asked for past our range (it's after the last node). GetCharAfter
01956     // will do the work for us when we pass it the last index of the last node.
01957     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNodeArray[curNum-1]));
01958     WSPoint point(textNode, textNode->TextLength(), 0);
01959     return GetCharAfter(point, outPoint);
01960   } else {
01961     // The char after the point of interest is the first character of our range.
01962     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNodeArray[curNum]));
01963     WSPoint point(textNode, 0, 0);
01964     return GetCharAfter(point, outPoint);
01965   }
01966 }
01967 
01968 nsresult 
01969 nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
01970 {
01971   // Note: only to be called if aNode is not a ws node.  
01972   
01973   // binary search on wsnodes
01974   PRInt32 numNodes, firstNum, curNum, lastNum;
01975   numNodes = mNodeArray.Count();
01976   
01977   if (!numNodes) 
01978     return NS_OK; // do nothing if there are no nodes to search
01979   
01980   firstNum = 0;
01981   curNum = numNodes/2;
01982   lastNum = numNodes;
01983   PRInt16 cmp=0;
01984   nsCOMPtr<nsIDOMNode>  curNode;
01985   
01986   // begin binary search
01987   // we do this because we need to minimize calls to ComparePoints(),
01988   // which is mongo expensive
01989   while (curNum != lastNum)
01990   {
01991     curNode = mNodeArray[curNum];
01992     cmp = mHTMLEditor->sRangeHelper->ComparePoints(aNode, aOffset, curNode, 0);
01993     if (cmp < 0)
01994       lastNum = curNum;
01995     else
01996       firstNum = curNum + 1;
01997     curNum = (lastNum - firstNum) / 2 + firstNum;
01998     NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
01999   }
02000 
02001   // When the binary search is complete, we always know that the current node
02002   // is the same as the end node, which is always past our range. Therefore,
02003   // we've found the node immediately after the point of interest.
02004   if (curNum == mNodeArray.Count()) {
02005     // get the point before the end of the last node, we can pass the length
02006     // of the node into GetCharBefore, and it will return the last character.
02007     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNodeArray[curNum - 1]));
02008     WSPoint point(textNode, textNode->TextLength(), 0);
02009     return GetCharBefore(point, outPoint);
02010   } else {
02011     // we can just ask the current node for the point immediately before it,
02012     // it will handle moving to the previous node (if any) and returning the
02013     // appropriate character
02014     nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNodeArray[curNum]));
02015     WSPoint point(textNode, 0, 0);
02016     return GetCharBefore(point, outPoint);
02017   }
02018 }
02019 
02020 nsresult
02021 nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
02022 {    
02023   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. 
02024   // examine what is before and after the trailing nbsp, if any.
02025   if (!aRun) return NS_ERROR_NULL_POINTER;
02026   WSPoint thePoint;
02027   PRBool leftCheck = PR_FALSE;
02028   PRBool spaceNBSP = PR_FALSE;
02029   PRBool rightCheck = PR_FALSE;
02030   
02031   // confirm run is normalWS
02032   if (aRun->mType != eNormalWS) return NS_ERROR_FAILURE;
02033   
02034   // first check for trailing nbsp
02035   nsresult res = GetCharBefore(aRun->mEndNode, aRun->mEndOffset, &thePoint);
02036   if (NS_SUCCEEDED(res) && thePoint.mTextNode && thePoint.mChar == nbsp)
02037   {
02038     // now check that what is to the left of it is compatible with replacing nbsp with space
02039     WSPoint prevPoint;
02040     res = GetCharBefore(thePoint, &prevPoint);
02041     if (NS_SUCCEEDED(res) && prevPoint.mTextNode)
02042     {
02043       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) leftCheck = PR_TRUE;
02044       else spaceNBSP = PR_TRUE;
02045     }
02046     else if (aRun->mLeftType == eText)    leftCheck = PR_TRUE;
02047     else if (aRun->mLeftType == eSpecial) leftCheck = PR_TRUE;
02048     if (leftCheck || spaceNBSP)
02049     {
02050       // now check that what is to the right of it is compatible with replacing nbsp with space
02051       if (aRun->mRightType == eText)    rightCheck = PR_TRUE;
02052       if (aRun->mRightType == eSpecial) rightCheck = PR_TRUE;
02053       if (aRun->mRightType == eBreak)   rightCheck = PR_TRUE;
02054       if (aRun->mRightType & eBlock)
02055       {
02056         // we are at a block boundary.  Insert a <br>.  Why?  Well, first note that
02057         // the br will have no visible effect since it is up against a block boundary.
02058         // |foo<br><p>bar|  renders like |foo<p>bar| and similarly
02059         // |<p>foo<br></p>bar| renders like |<p>foo</p>bar|.  What this <br> addition
02060         // gets us is the ability to convert a trailing nbsp to a space.  Consider:
02061         // |<body>foo. '</body>|, where ' represents selection.  User types space attempting
02062         // to put 2 spaces after the end of their sentence.  We used to do this as:
02063         // |<body>foo. &nbsp</body>|  This caused problems with soft wrapping: the nbsp
02064         // would wrap to the next line, which looked attrocious.  If you try to do:
02065         // |<body>foo.&nbsp </body>| instead, the trailing space is invisible because it 
02066         // is against a block boundary.  If you do: |<body>foo.&nbsp&nbsp</body>| then
02067         // you get an even uglier soft wrapping problem, where foo is on one line until
02068         // you type the final space, and then "foo  " jumps down to the next line.  Ugh.
02069         // The best way I can find out of this is to throw in a harmless <br>
02070         // here, which allows us to do: |<body>foo.&nbsp <br></body>|, which doesn't
02071         // cause foo to jump lines, doesn't cause spaces to show up at the beginning of 
02072         // soft wrapped lines, and lets the user see 2 spaces when they type 2 spaces.
02073         
02074         nsCOMPtr<nsIDOMNode> brNode;
02075         res = mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset, address_of(brNode));
02076         NS_ENSURE_SUCCESS(res, res);
02077         
02078         // refresh thePoint, prevPoint
02079         res = GetCharBefore(aRun->mEndNode, aRun->mEndOffset, &thePoint);
02080         NS_ENSURE_SUCCESS(res, res);
02081         res = GetCharBefore(thePoint, &prevPoint);
02082         NS_ENSURE_SUCCESS(res, res);
02083         rightCheck = PR_TRUE;
02084       }
02085     }
02086     if (leftCheck && rightCheck)
02087     {
02088       // now replace nbsp with space
02089       // first, insert a space
02090       nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
02091       if (!textNode)
02092         return NS_ERROR_NULL_POINTER;
02093       nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02094       nsAutoString spaceStr(PRUnichar(32));
02095       res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset, PR_TRUE);
02096       NS_ENSURE_SUCCESS(res, res);
02097   
02098       // finally, delete that nbsp
02099       nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
02100       res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
02101       NS_ENSURE_SUCCESS(res, res);
02102     }
02103     else if (!mPRE && spaceNBSP && rightCheck)  // don't mess with this preformatted for now.
02104     {
02105       // we have a run of ascii whitespace (which will render as one space)
02106       // followed by an nbsp (which is at the end of the whitespace run).  Let's
02107       // switch their order.  This will insure that if someone types two spaces
02108       // after a sentence, and the editor softwraps at this point, the spaces wont
02109       // be split across lines, which looks ugly and is bad for the moose.
02110       
02111       nsCOMPtr<nsIDOMNode> startNode, endNode, thenode(do_QueryInterface(prevPoint.mTextNode));
02112       PRInt32 startOffset, endOffset;
02113       res = GetAsciiWSBounds(eBoth, thenode, prevPoint.mOffset+1, address_of(startNode), 
02114                            &startOffset, address_of(endNode), &endOffset);
02115       NS_ENSURE_SUCCESS(res, res);
02116       
02117       //  delete that nbsp
02118       nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
02119       res = DeleteChars(delNode, thePoint.mOffset, delNode, thePoint.mOffset+1);
02120       NS_ENSURE_SUCCESS(res, res);
02121       
02122       // finally, insert that nbsp before the ascii ws run
02123       nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02124       nsAutoString nbspStr(nbsp);
02125       nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(startNode));
02126       res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, startOffset, PR_TRUE);
02127       NS_ENSURE_SUCCESS(res, res);
02128     }
02129   }
02130   return NS_OK;
02131 }
02132 
02133 nsresult
02134 nsWSRunObject::CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
02135 {    
02136   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. 
02137   // this routine is called when we about to make this point in the ws abut an inserted break
02138   // or text, so we don't have to worry about what is after it.  What is after it now will 
02139   // end up after the inserted object.   
02140   if (!aRun || !aNode) return NS_ERROR_NULL_POINTER;
02141   WSPoint thePoint;
02142   PRBool canConvert = PR_FALSE;
02143   nsresult res = GetCharBefore(aNode, aOffset, &thePoint);
02144   if (NS_SUCCEEDED(res) && thePoint.mTextNode && thePoint.mChar == nbsp)
02145   {
02146     WSPoint prevPoint;
02147     res = GetCharBefore(thePoint, &prevPoint);
02148     if (NS_SUCCEEDED(res) && prevPoint.mTextNode)
02149     {
02150       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) canConvert = PR_TRUE;
02151     }
02152     else if (aRun->mLeftType == eText)    canConvert = PR_TRUE;
02153     else if (aRun->mLeftType == eSpecial) canConvert = PR_TRUE;
02154   }
02155   if (canConvert)
02156   {
02157     // first, insert a space
02158     nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
02159     if (!textNode)
02160       return NS_ERROR_NULL_POINTER;
02161     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02162     nsAutoString spaceStr(PRUnichar(32));
02163     res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset, PR_TRUE);
02164     NS_ENSURE_SUCCESS(res, res);
02165   
02166     // finally, delete that nbsp
02167     nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
02168     res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
02169     NS_ENSURE_SUCCESS(res, res);
02170   }
02171   return NS_OK;
02172 }
02173 
02174 nsresult
02175 nsWSRunObject::CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
02176 {    
02177   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation    
02178   // this routine is called when we about to make this point in the ws abut an inserted
02179   // text, so we don't have to worry about what is before it.  What is before it now will 
02180   // end up before the inserted text.   
02181   WSPoint thePoint;
02182   PRBool canConvert = PR_FALSE;
02183   nsresult res = GetCharAfter(aNode, aOffset, &thePoint);
02184   if (NS_SUCCEEDED(res) && thePoint.mChar == nbsp)
02185   {
02186     WSPoint nextPoint, tmp=thePoint;
02187     tmp.mOffset++; // we want to be after thePoint
02188     res = GetCharAfter(tmp, &nextPoint);
02189     if (NS_SUCCEEDED(res) && nextPoint.mTextNode)
02190     {
02191       if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) canConvert = PR_TRUE;
02192     }
02193     else if (aRun->mRightType == eText)    canConvert = PR_TRUE;
02194     else if (aRun->mRightType == eSpecial) canConvert = PR_TRUE;
02195     else if (aRun->mRightType == eBreak)   canConvert = PR_TRUE;
02196   }
02197   if (canConvert)
02198   {
02199     // first, insert a space
02200     nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
02201     if (!textNode)
02202       return NS_ERROR_NULL_POINTER;
02203     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
02204     nsAutoString spaceStr(PRUnichar(32));
02205     res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset, PR_TRUE);
02206     NS_ENSURE_SUCCESS(res, res);
02207   
02208     // finally, delete that nbsp
02209     nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
02210     res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
02211     NS_ENSURE_SUCCESS(res, res);
02212   }
02213   return NS_OK;
02214 }
02215 
02216 
02217 nsresult
02218 nsWSRunObject::ScrubBlockBoundaryInner(nsHTMLEditor *aHTMLEd, 
02219                                        nsCOMPtr<nsIDOMNode> *aBlock,
02220                                        BlockBoundary aBoundary)
02221 {
02222   if (!aBlock || !aHTMLEd)
02223     return NS_ERROR_NULL_POINTER;
02224   PRInt32 offset=0;
02225   if (aBoundary == kBlockEnd)
02226   {
02227     PRUint32 uOffset;
02228     aHTMLEd->GetLengthOfDOMNode(*aBlock, uOffset); 
02229     offset = uOffset;
02230   }
02231   nsWSRunObject theWSObj(aHTMLEd, *aBlock, offset);
02232   return theWSObj.Scrub();    
02233 }
02234 
02235 
02236 nsresult
02237 nsWSRunObject::Scrub()
02238 {
02239   WSFragment *run = mStartRun;
02240   while (run)
02241   {
02242     if (run->mType & (eLeadingWS|eTrailingWS) )
02243     {
02244       nsresult res = DeleteChars(run->mStartNode, run->mStartOffset, run->mEndNode, run->mEndOffset);
02245       NS_ENSURE_SUCCESS(res, res);
02246     }
02247     run = run->mRight;
02248   }
02249   return NS_OK;
02250 }
02251