Back to index

lightning-sunbird  0.9+nobinonly
nsTextEditRules.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 "nsTextEditRules.h"
00039 
00040 #include "nsEditor.h"
00041 #include "nsTextEditUtils.h"
00042 #include "nsCRT.h"
00043 
00044 #include "nsCOMPtr.h"
00045 #include "nsIServiceManager.h"
00046 #include "nsIDOMNode.h"
00047 #include "nsIDOMElement.h"
00048 #include "nsIDOMText.h"
00049 #include "nsIDOMNodeList.h"
00050 #include "nsISelection.h"
00051 #include "nsISelectionPrivate.h"
00052 #include "nsISelectionController.h"
00053 #include "nsIDOMRange.h"
00054 #include "nsIDOMNSRange.h"
00055 #include "nsIDOMCharacterData.h"
00056 #include "nsIContent.h"
00057 #include "nsIContentIterator.h"
00058 #include "nsEditorUtils.h"
00059 #include "EditTxn.h"
00060 #include "nsIPrefBranch.h"
00061 #include "nsIPrefService.h"
00062 #include "nsUnicharUtils.h"
00063 #include "nsIWordBreakerFactory.h"
00064 #include "nsLWBrkCIID.h"
00065 #include "DeleteTextTxn.h"
00066 #include "nsAutoPtr.h"
00067 
00068 // for IBMBIDI
00069 #include "nsIPresShell.h"
00070 
00071 #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
00072   if ((mFlags & nsIPlaintextEditor::eEditorReadonlyMask) || (mFlags & nsIPlaintextEditor::eEditorDisabledMask)) \
00073   {                     \
00074     *aCancel = PR_TRUE; \
00075     return NS_OK;       \
00076   };
00077 
00078 
00079 nsresult
00080 NS_NewTextEditRules(nsIEditRules** aInstancePtrResult)
00081 {
00082   nsTextEditRules * rules = new nsTextEditRules();
00083   if (rules)
00084     return rules->QueryInterface(NS_GET_IID(nsIEditRules), (void**) aInstancePtrResult);
00085   return NS_ERROR_OUT_OF_MEMORY;
00086 }
00087 
00088 
00089 /********************************************************
00090  *  Constructor/Destructor 
00091  ********************************************************/
00092 
00093 nsTextEditRules::nsTextEditRules()
00094 : mEditor(nsnull)
00095 , mPasswordText()
00096 , mPasswordIMEText()
00097 , mPasswordIMEIndex(0)
00098 , mFlags(0) // initialized to 0 ("no flags set").  Real initial value is given in Init()
00099 , mActionNesting(0)
00100 , mLockRulesSniffing(PR_FALSE)
00101 , mDidExplicitlySetInterline(PR_FALSE)
00102 , mTheAction(0)
00103 {
00104 }
00105 
00106 nsTextEditRules::~nsTextEditRules()
00107 {
00108    // do NOT delete mEditor here.  We do not hold a ref count to mEditor.  mEditor owns our lifespan.
00109 }
00110 
00111 /********************************************************
00112  *  XPCOM Cruft
00113  ********************************************************/
00114 
00115 NS_IMPL_ISUPPORTS1(nsTextEditRules, nsIEditRules)
00116 
00117 
00118 /********************************************************
00119  *  Public methods 
00120  ********************************************************/
00121 
00122 NS_IMETHODIMP
00123 nsTextEditRules::Init(nsPlaintextEditor *aEditor, PRUint32 aFlags)
00124 {
00125   if (!aEditor) { return NS_ERROR_NULL_POINTER; }
00126 
00127   mEditor = aEditor;  // we hold a non-refcounted reference back to our editor
00128   // call SetFlags only aftet mEditor has been initialized!
00129   SetFlags(aFlags);
00130   nsCOMPtr<nsISelection> selection;
00131   mEditor->GetSelection(getter_AddRefs(selection));
00132   NS_ASSERTION(selection, "editor cannot get selection");
00133 
00134   // Cache our body node, if available.
00135   GetBody();
00136 
00137   // Put in a magic br if needed. This method handles null selection,
00138   // which should never happen anyway
00139   nsresult res = CreateBogusNodeIfNeeded(selection);
00140   if (NS_FAILED(res)) return res;
00141 
00142   if (mFlags & nsIPlaintextEditor::eEditorPlaintextMask)
00143   {
00144     // ensure trailing br node
00145     res = CreateTrailingBRIfNeeded();
00146     if (NS_FAILED(res)) return res;
00147   }
00148 
00149   if (mBody)
00150   {
00151     // create a range that is the entire body contents
00152     nsCOMPtr<nsIDOMRange> wholeDoc =
00153       do_CreateInstance("@mozilla.org/content/range;1");
00154     if (!wholeDoc) return NS_ERROR_NULL_POINTER;
00155     wholeDoc->SetStart(mBody,0);
00156     nsCOMPtr<nsIDOMNodeList> list;
00157     res = mBody->GetChildNodes(getter_AddRefs(list));
00158     if (NS_FAILED(res)) return res;
00159     if (!list) return NS_ERROR_FAILURE;
00160 
00161     PRUint32 listCount;
00162     res = list->GetLength(&listCount);
00163     if (NS_FAILED(res)) return res;
00164 
00165     res = wholeDoc->SetEnd(mBody, listCount);
00166     if (NS_FAILED(res)) return res;
00167 
00168     // replace newlines in that range with breaks
00169     res = ReplaceNewlines(wholeDoc);
00170   }
00171 
00172   return res;
00173 }
00174 
00175 NS_IMETHODIMP
00176 nsTextEditRules::GetFlags(PRUint32 *aFlags)
00177 {
00178   if (!aFlags) { return NS_ERROR_NULL_POINTER; }
00179   *aFlags = mFlags;
00180   return NS_OK;
00181 }
00182 
00183 NS_IMETHODIMP
00184 nsTextEditRules::SetFlags(PRUint32 aFlags)
00185 {
00186   if (mFlags == aFlags) return NS_OK;
00187   
00188   // XXX - this won't work if body element already has
00189   // a style attribute on it, don't know why.
00190   // SetFlags() is really meant to only be called once
00191   // and at editor init time.  
00192 
00193   mFlags = aFlags;
00194   return NS_OK;
00195 }
00196 
00197 NS_IMETHODIMP
00198 nsTextEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection)
00199 {
00200   if (mLockRulesSniffing) return NS_OK;
00201   
00202   nsAutoLockRulesSniffing lockIt(this);
00203   mDidExplicitlySetInterline = PR_FALSE;
00204   
00205   // get the selection and cache the position before editing
00206   nsCOMPtr<nsISelection> selection;
00207   nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
00208   if (NS_FAILED(res)) 
00209     return res;
00210 
00211   selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode));
00212   selection->GetAnchorOffset(&mCachedSelectionOffset);
00213 
00214   if (!mActionNesting)
00215   {
00216     // let rules remember the top level action
00217     mTheAction = action;
00218   }
00219   mActionNesting++;
00220   return NS_OK;
00221 }
00222 
00223 
00224 NS_IMETHODIMP
00225 nsTextEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
00226 {
00227   if (mLockRulesSniffing) return NS_OK;
00228   
00229   nsAutoLockRulesSniffing lockIt(this);
00230   
00231   NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
00232   nsresult res = NS_OK;
00233   if (!--mActionNesting)
00234   {
00235     nsCOMPtr<nsISelection>selection;
00236     res = mEditor->GetSelection(getter_AddRefs(selection));
00237     if (NS_FAILED(res)) return res;
00238   
00239     res = mEditor->HandleInlineSpellCheck(action, selection,
00240                                           mCachedSelectionNode, mCachedSelectionOffset,
00241                                           nsnull, 0, nsnull, 0);
00242     if (NS_FAILED(res)) 
00243       return res;
00244 
00245     // detect empty doc
00246     res = CreateBogusNodeIfNeeded(selection);
00247     if (NS_FAILED(res)) 
00248       return res;
00249     
00250     // insure trailing br node
00251     res = CreateTrailingBRIfNeeded();
00252     if (NS_FAILED(res)) 
00253       return res;
00254     
00255     /* After inserting text the cursor Bidi level must be set to the level of the inserted text.
00256      * This is difficult, because we cannot know what the level is until after the Bidi algorithm
00257      * is applied to the whole paragraph.
00258      *
00259      * So we set the cursor Bidi level to UNDEFINED here, and the caret code will set it correctly later
00260      */
00261     if (action == nsEditor::kOpInsertText
00262         || action == nsEditor::kOpInsertIMEText) {
00263       nsCOMPtr<nsIPresShell> shell;
00264       mEditor->GetPresShell(getter_AddRefs(shell));
00265       if (shell) {
00266         shell->UndefineCaretBidiLevel();
00267       }
00268     }
00269   }
00270   return res;
00271 }
00272 
00273 
00274 NS_IMETHODIMP 
00275 nsTextEditRules::WillDoAction(nsISelection *aSelection, 
00276                               nsRulesInfo *aInfo, 
00277                               PRBool *aCancel, 
00278                               PRBool *aHandled)
00279 {
00280   // null selection is legal
00281   if (!aInfo || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
00282 #if defined(DEBUG_ftang)
00283   printf("nsTextEditRules::WillDoAction action= %d", aInfo->action);
00284 #endif
00285 
00286   *aCancel = PR_FALSE;
00287   *aHandled = PR_FALSE;
00288 
00289   // my kingdom for dynamic cast
00290   nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
00291     
00292   switch (info->action)
00293   {
00294     case kInsertBreak:
00295       return WillInsertBreak(aSelection, aCancel, aHandled);
00296     case kInsertText:
00297     case kInsertTextIME:
00298       return WillInsertText(info->action,
00299                             aSelection, 
00300                             aCancel,
00301                             aHandled, 
00302                             info->inString,
00303                             info->outString,
00304                             info->maxLength);
00305     case kDeleteSelection:
00306       return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
00307     case kUndo:
00308       return WillUndo(aSelection, aCancel, aHandled);
00309     case kRedo:
00310       return WillRedo(aSelection, aCancel, aHandled);
00311     case kSetTextProperty:
00312       return WillSetTextProperty(aSelection, aCancel, aHandled);
00313     case kRemoveTextProperty:
00314       return WillRemoveTextProperty(aSelection, aCancel, aHandled);
00315     case kOutputText:
00316       return WillOutputText(aSelection, 
00317                             info->outputFormat,
00318                             info->outString,                            
00319                             aCancel,
00320                             aHandled);
00321     case kInsertElement:  // i had thought this would be html rules only.  but we put pre elements
00322                           // into plaintext mail when doing quoting for reply!  doh!
00323       return WillInsert(aSelection, aCancel);
00324   }
00325   return NS_ERROR_FAILURE;
00326 }
00327   
00328 NS_IMETHODIMP 
00329 nsTextEditRules::DidDoAction(nsISelection *aSelection,
00330                              nsRulesInfo *aInfo, nsresult aResult)
00331 {
00332   // don't let any txns in here move the selection around behind our back.
00333   // Note that this won't prevent explicit selection setting from working.
00334   nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
00335 
00336   if (!aSelection || !aInfo) 
00337     return NS_ERROR_NULL_POINTER;
00338     
00339   // my kingdom for dynamic cast
00340   nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
00341 
00342   switch (info->action)
00343   {
00344    case kInsertBreak:
00345      return DidInsertBreak(aSelection, aResult);
00346     case kInsertText:
00347     case kInsertTextIME:
00348       return DidInsertText(aSelection, aResult);
00349     case kDeleteSelection:
00350       return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
00351     case kUndo:
00352       return DidUndo(aSelection, aResult);
00353     case kRedo:
00354       return DidRedo(aSelection, aResult);
00355     case kSetTextProperty:
00356       return DidSetTextProperty(aSelection, aResult);
00357     case kRemoveTextProperty:
00358       return DidRemoveTextProperty(aSelection, aResult);
00359     case kOutputText:
00360       return DidOutputText(aSelection, aResult);
00361   }
00362   // Don't fail on transactions we don't handle here!
00363   return NS_OK;
00364 }
00365 
00366 
00367 NS_IMETHODIMP
00368 nsTextEditRules::DocumentIsEmpty(PRBool *aDocumentIsEmpty)
00369 {
00370   if (!aDocumentIsEmpty)
00371     return NS_ERROR_NULL_POINTER;
00372   
00373   *aDocumentIsEmpty = (mBogusNode != nsnull);
00374   return NS_OK;
00375 }
00376 
00377 /********************************************************
00378  *  Protected methods 
00379  ********************************************************/
00380 
00381 
00382 nsresult
00383 nsTextEditRules::WillInsert(nsISelection *aSelection, PRBool *aCancel)
00384 {
00385   if (!aSelection || !aCancel)
00386     return NS_ERROR_NULL_POINTER;
00387   
00388   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
00389 
00390   // initialize out param
00391   *aCancel = PR_FALSE;
00392   
00393   // check for the magic content node and delete it if it exists
00394   if (mBogusNode)
00395   {
00396     mEditor->DeleteNode(mBogusNode);
00397     mBogusNode = nsnull;
00398   }
00399 
00400   return NS_OK;
00401 }
00402 
00403 nsresult
00404 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
00405 {
00406   return NS_OK;
00407 }
00408 
00409 nsresult
00410 nsTextEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
00411 {
00412   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
00413   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
00414   *aHandled = PR_FALSE;
00415   if (mFlags & nsIPlaintextEditor::eEditorSingleLineMask) {
00416     *aCancel = PR_TRUE;
00417   }
00418   else 
00419   {
00420     *aCancel = PR_FALSE;
00421 
00422     // if the selection isn't collapsed, delete it.
00423     PRBool bCollapsed;
00424     nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
00425     if (NS_FAILED(res)) return res;
00426     if (!bCollapsed)
00427     {
00428       res = mEditor->DeleteSelection(nsIEditor::eNone);
00429       if (NS_FAILED(res)) return res;
00430     }
00431 
00432     res = WillInsert(aSelection, aCancel);
00433     if (NS_FAILED(res)) return res;
00434     // initialize out param
00435     // we want to ignore result of WillInsert()
00436     *aCancel = PR_FALSE;
00437   
00438   }
00439   return NS_OK;
00440 }
00441 
00442 nsresult
00443 nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
00444 {
00445   // we only need to execute the stuff below if we are a plaintext editor.
00446   // html editors have a different mechanism for putting in mozBR's
00447   // (because there are a bunch more places you have to worry about it in html) 
00448   if (!nsIPlaintextEditor::eEditorPlaintextMask & mFlags) return NS_OK;
00449 
00450   // if we are at the end of the document, we need to insert 
00451   // a special mozBR following the normal br, and then set the
00452   // selection to stick to the mozBR.
00453   PRInt32 selOffset;
00454   nsCOMPtr<nsIDOMNode> selNode;
00455   nsresult res;
00456   res = mEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
00457   if (NS_FAILED(res)) return res;
00458   // confirm we are at end of document
00459   if (selOffset == 0) return NS_OK;  // cant be after a br if we are at offset 0
00460   nsIDOMElement *rootElem = mEditor->GetRoot();
00461 
00462   nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem);
00463   if (!root) return NS_ERROR_NULL_POINTER;
00464   if (selNode != root) return NS_OK; // must be inside text node or somewhere other than end of root
00465 
00466   nsCOMPtr<nsIDOMNode> temp = mEditor->GetChildAt(selNode, selOffset);
00467   if (temp) return NS_OK; // can't be at end if there is a node after us.
00468 
00469   nsCOMPtr<nsIDOMNode> nearNode = mEditor->GetChildAt(selNode, selOffset-1);
00470   if (nearNode && nsTextEditUtils::IsBreak(nearNode) && !nsTextEditUtils::IsMozBR(nearNode))
00471   {
00472     nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
00473     // need to insert special moz BR. Why?  Because if we don't
00474     // the user will see no new line for the break.  Also, things
00475     // like table cells won't grow in height.
00476     nsCOMPtr<nsIDOMNode> brNode;
00477     res = CreateMozBR(selNode, selOffset, address_of(brNode));
00478     if (NS_FAILED(res)) return res;
00479 
00480     res = nsEditor::GetNodeLocation(brNode, address_of(selNode), &selOffset);
00481     if (NS_FAILED(res)) return res;
00482     selPrivate->SetInterlinePosition(PR_TRUE);
00483     res = aSelection->Collapse(selNode, selOffset);
00484     if (NS_FAILED(res)) return res;
00485   }
00486   return res;
00487 }
00488 
00489 
00490 nsresult
00491 nsTextEditRules::WillInsertText(PRInt32          aAction,
00492                                 nsISelection *aSelection, 
00493                                 PRBool          *aCancel,
00494                                 PRBool          *aHandled,
00495                                 const nsAString *inString,
00496                                 nsAString *outString,
00497                                 PRInt32          aMaxLength)
00498 {  
00499   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
00500 
00501   if (inString->IsEmpty() && (aAction != kInsertTextIME))
00502   {
00503     // HACK: this is a fix for bug 19395
00504     // I can't outlaw all empty insertions
00505     // because IME transaction depend on them
00506     // There is more work to do to make the 
00507     // world safe for IME.
00508     *aCancel = PR_TRUE;
00509     *aHandled = PR_FALSE;
00510     return NS_OK;
00511   }
00512   
00513   // initialize out param
00514   *aCancel = PR_FALSE;
00515   *aHandled = PR_TRUE;
00516 
00517   // handle docs with a max length
00518   // NOTE, this function copies inString into outString for us.
00519   nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, aMaxLength);
00520   if (NS_FAILED(res)) return res;
00521   
00522   PRUint32 start = 0;
00523   PRUint32 end = 0;  
00524 
00525   // handle password field docs
00526   if (mFlags & nsIPlaintextEditor::eEditorPasswordMask)
00527   {
00528     res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
00529     NS_ASSERTION((NS_SUCCEEDED(res)), "getTextSelectionOffsets failed!");
00530     if (NS_FAILED(res)) return res;
00531   }
00532 
00533   // if the selection isn't collapsed, delete it.
00534   PRBool bCollapsed;
00535   res = aSelection->GetIsCollapsed(&bCollapsed);
00536   if (NS_FAILED(res)) return res;
00537   if (!bCollapsed)
00538   {
00539     res = mEditor->DeleteSelection(nsIEditor::eNone);
00540     if (NS_FAILED(res)) return res;
00541   }
00542 
00543   res = WillInsert(aSelection, aCancel);
00544   if (NS_FAILED(res)) return res;
00545   // initialize out param
00546   // we want to ignore result of WillInsert()
00547   *aCancel = PR_FALSE;
00548   
00549   // handle password field data
00550   // this has the side effect of changing all the characters in aOutString
00551   // to the replacement character
00552   if (mFlags & nsIPlaintextEditor::eEditorPasswordMask)
00553   {
00554     if (aAction == kInsertTextIME)  {
00555       res = RemoveIMETextFromPWBuf(start, outString);
00556       if (NS_FAILED(res)) return res;
00557     }
00558   }
00559 
00560   // People have lots of different ideas about what text fields
00561   // should do with multiline pastes.  See bugs 21032, 23485, 23485, 50935.
00562   // The four possible options are:
00563   // 0. paste newlines intact
00564   // 1. paste up to the first newline
00565   // 2. replace newlines with spaces
00566   // 3. strip newlines
00567   // 4. replace with commas
00568   // So find out what we're expected to do:
00569   enum {
00570     ePasteIntact = 0, ePasteFirstLine = 1,
00571     eReplaceWithSpaces = 2, eStripNewlines = 3, 
00572     eReplaceWithCommas = 4
00573   };
00574   PRInt32 singleLineNewlineBehavior = 1;
00575   nsCOMPtr<nsIPrefBranch> prefBranch =
00576     do_GetService(NS_PREFSERVICE_CONTRACTID, &res);
00577   if (NS_SUCCEEDED(res) && prefBranch)
00578     res = prefBranch->GetIntPref("editor.singleLine.pasteNewlines",
00579                                  &singleLineNewlineBehavior);
00580 
00581   if (nsIPlaintextEditor::eEditorSingleLineMask & mFlags)
00582   {
00583     nsAutoString tString(*outString);
00584 
00585     if (singleLineNewlineBehavior == eReplaceWithSpaces)
00586     {
00587       //nsAString destString;
00588       //NormalizeCRLF(outString,destString);
00589 
00590       tString.ReplaceChar(CRLF, ' ');
00591     }
00592     else if (singleLineNewlineBehavior == eStripNewlines)
00593       tString.StripChars(CRLF);
00594     else if (singleLineNewlineBehavior == ePasteFirstLine)
00595     {
00596       PRInt32 firstCRLF = tString.FindCharInSet(CRLF);
00597 
00598       // we get first *non-empty* line.
00599       PRInt32 offset = 0;
00600       while (firstCRLF == offset)
00601       {
00602         offset++;
00603         firstCRLF = tString.FindCharInSet(CRLF, offset);
00604       }
00605       if (firstCRLF > 0)
00606         tString.Truncate(firstCRLF);
00607       if (offset > 0)
00608         tString.Cut(0, offset);
00609     }
00610     else if (singleLineNewlineBehavior == eReplaceWithCommas)
00611     {
00612       tString.Trim(CRLF, PR_TRUE, PR_TRUE);
00613       tString.ReplaceChar(CRLF, ',');
00614     }
00615     else // even if we're pasting newlines, don't paste leading/trailing ones
00616       tString.Trim(CRLF, PR_TRUE, PR_TRUE);
00617 
00618     outString->Assign(tString);
00619   }
00620 
00621   if (mFlags & nsIPlaintextEditor::eEditorPasswordMask)
00622   {
00623     res = EchoInsertionToPWBuff(start, end, outString);
00624     if (NS_FAILED(res)) return res;
00625   }
00626 
00627   // get the (collapsed) selection location
00628   nsCOMPtr<nsIDOMNode> selNode;
00629   PRInt32 selOffset;
00630   res = mEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
00631   if (NS_FAILED(res)) return res;
00632 
00633   // don't put text in places that can't have it
00634   if (!mEditor->IsTextNode(selNode) && !mEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text")))
00635     return NS_ERROR_FAILURE;
00636 
00637   // we need to get the doc
00638   nsCOMPtr<nsIDOMDocument>doc;
00639   res = mEditor->GetDocument(getter_AddRefs(doc));
00640   if (NS_FAILED(res)) return res;
00641   if (!doc) return NS_ERROR_NULL_POINTER;
00642     
00643   if (aAction == kInsertTextIME) 
00644   { 
00645     res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc);
00646     if (NS_FAILED(res)) return res;
00647   }
00648   else // aAction == kInsertText
00649   {
00650     // find where we are
00651     nsCOMPtr<nsIDOMNode> curNode = selNode;
00652     PRInt32 curOffset = selOffset;
00653 
00654     // is our text going to be PREformatted?  
00655     // We remember this so that we know how to handle tabs.
00656     PRBool isPRE;
00657     res = mEditor->IsPreformatted(selNode, &isPRE);
00658     if (NS_FAILED(res)) return res;    
00659 
00660     // don't spaz my selection in subtransactions
00661     nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
00662     nsString tString(*outString);
00663     const PRUnichar *unicodeBuf = tString.get();
00664     nsCOMPtr<nsIDOMNode> unused;
00665     PRInt32 pos = 0;
00666 
00667     // for efficiency, break out the pre case separately.  This is because
00668     // it's a lot cheaper to search the input string for only newlines than
00669     // it is to search for both tabs and newlines.
00670     if (isPRE)
00671     {
00672       while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
00673       {
00674         PRInt32 oldPos = pos;
00675         PRInt32 subStrLen;
00676         pos = tString.FindChar(nsCRT::LF, oldPos);
00677         
00678         if (pos != -1) 
00679         {
00680           subStrLen = pos - oldPos;
00681           // if first char is newline, then use just it
00682           if (subStrLen == 0)
00683             subStrLen = 1;
00684         }
00685         else
00686         {
00687           subStrLen = tString.Length() - oldPos;
00688           pos = tString.Length();
00689         }
00690 
00691         nsDependentSubstring subStr(tString, oldPos, subStrLen);
00692         
00693         // is it a return?
00694         if (subStr.EqualsLiteral(LFSTR))
00695         {
00696           if (nsIPlaintextEditor::eEditorSingleLineMask & mFlags)
00697           {
00698             NS_ASSERTION((singleLineNewlineBehavior == ePasteIntact),
00699                   "Newline improperly getting into single-line edit field!");
00700             res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
00701           }
00702           else
00703           {
00704             res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
00705 
00706             // If the newline is the last character in the string, and the BR we
00707             // just inserted is the last node in the content tree, we need to add
00708             // a mozBR so that a blank line is created.
00709 
00710             if (NS_SUCCEEDED(res) && curNode && pos == (PRInt32)(tString.Length() - 1))
00711             {
00712               nsCOMPtr<nsIDOMNode> nextChild = mEditor->GetChildAt(curNode, curOffset);
00713 
00714               if (!nextChild)
00715               {
00716                 // We must be at the end since there isn't a nextChild.
00717                 //
00718                 // curNode and curOffset should be set to the position after
00719                 // the BR we added above, so just create a mozBR at that position.
00720                 //
00721                 // Note that we don't update curOffset after we've created/inserted
00722                 // the mozBR since we never want the selection to be placed after it.
00723 
00724                 res = CreateMozBR(curNode, curOffset, address_of(unused));
00725               }
00726             }
00727           }
00728           pos++;
00729         }
00730         else
00731         {
00732           res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
00733         }
00734         if (NS_FAILED(res)) return res;
00735       }
00736     }
00737     else
00738     {
00739       char specialChars[] = {TAB, nsCRT::LF, 0};
00740       while (unicodeBuf && (pos != -1) && ((PRUint32)pos < tString.Length()))
00741       {
00742         PRInt32 oldPos = pos;
00743         PRInt32 subStrLen;
00744         pos = tString.FindCharInSet(specialChars, oldPos);
00745         
00746         if (pos != -1) 
00747         {
00748           subStrLen = pos - oldPos;
00749           // if first char is newline, then use just it
00750           if (subStrLen == 0)
00751             subStrLen = 1;
00752         }
00753         else
00754         {
00755           subStrLen = tString.Length() - oldPos;
00756           pos = tString.Length();
00757         }
00758 
00759         nsDependentSubstring subStr(tString, oldPos, subStrLen);
00760         
00761         // is it a tab?
00762         if (subStr.EqualsLiteral("\t"))
00763         {
00764           res = mEditor->InsertTextImpl(NS_LITERAL_STRING("    "), address_of(curNode), &curOffset, doc);
00765           pos++;
00766         }
00767         // is it a return?
00768         else if (subStr.EqualsLiteral(LFSTR))
00769         {
00770           res = mEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
00771           pos++;
00772         }
00773         else
00774         {
00775           res = mEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
00776         }
00777         if (NS_FAILED(res)) return res;
00778       }
00779     }
00780     outString->Assign(tString);
00781 
00782     if (curNode) 
00783     {
00784       aSelection->Collapse(curNode, curOffset);
00785       
00786       // Make the caret attach to the inserted text, unless this text ends with a LF, 
00787       // in which case make the caret attach to the next line.
00788       PRBool endsWithLF = !tString.IsEmpty() && tString.get()[tString.Length() - 1] == nsCRT::LF;
00789       nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
00790       selPrivate->SetInterlinePosition(endsWithLF);
00791     }
00792   }
00793   return res;
00794 }
00795 
00796 nsresult
00797 nsTextEditRules::DidInsertText(nsISelection *aSelection, 
00798                                nsresult aResult)
00799 {
00800   return DidInsert(aSelection, aResult);
00801 }
00802 
00803 
00804 
00805 nsresult
00806 nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
00807 {
00808   if (!aSelection || !aCancel || !aHandled) 
00809     { return NS_ERROR_NULL_POINTER; }
00810 
00811   // XXX: should probably return a success value other than NS_OK that means "not allowed"
00812   if (nsIPlaintextEditor::eEditorPlaintextMask & mFlags) {
00813     *aCancel = PR_TRUE;
00814   }
00815   return NS_OK;
00816 }
00817 
00818 nsresult
00819 nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult)
00820 {
00821   return NS_OK;
00822 }
00823 
00824 nsresult
00825 nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
00826 {
00827   if (!aSelection || !aCancel || !aHandled) 
00828     { return NS_ERROR_NULL_POINTER; }
00829 
00830   // XXX: should probably return a success value other than NS_OK that means "not allowed"
00831   if (nsIPlaintextEditor::eEditorPlaintextMask & mFlags) {
00832     *aCancel = PR_TRUE;
00833   }
00834   return NS_OK;
00835 }
00836 
00837 nsresult
00838 nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult)
00839 {
00840   return NS_OK;
00841 }
00842 
00843 nsresult
00844 nsTextEditRules::WillDeleteSelection(nsISelection *aSelection, 
00845                                      nsIEditor::EDirection aCollapsedAction, 
00846                                      PRBool *aCancel,
00847                                      PRBool *aHandled)
00848 {
00849   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
00850   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
00851 
00852   // initialize out param
00853   *aCancel = PR_FALSE;
00854   *aHandled = PR_FALSE;
00855   
00856   // if there is only bogus content, cancel the operation
00857   if (mBogusNode) {
00858     *aCancel = PR_TRUE;
00859     return NS_OK;
00860   }
00861 
00862   nsresult res = NS_OK;
00863 
00864   if (mFlags & nsIPlaintextEditor::eEditorPasswordMask)
00865   {
00866     // manage the password buffer
00867     PRUint32 start, end;
00868     mEditor->GetTextSelectionOffsets(aSelection, start, end);
00869     NS_ENSURE_SUCCESS(res, res);
00870     if (end == start)
00871     { // collapsed selection
00872       if (nsIEditor::ePrevious==aCollapsedAction && 0<start) { // del back
00873         mPasswordText.Cut(start-1, 1);
00874       }
00875       else if (nsIEditor::eNext==aCollapsedAction) {      // del forward
00876         mPasswordText.Cut(start, 1);
00877       }
00878       // otherwise nothing to do for this collapsed selection
00879     }
00880     else {  // extended selection
00881       mPasswordText.Cut(start, end-start);
00882     }
00883   }
00884   else
00885   {
00886     nsCOMPtr<nsIDOMNode> startNode;
00887     PRInt32 startOffset;
00888     res = mEditor->GetStartNodeAndOffset(aSelection, address_of(startNode), &startOffset);
00889     if (NS_FAILED(res)) return res;
00890     if (!startNode) return NS_ERROR_FAILURE;
00891     
00892     PRBool bCollapsed;
00893     res = aSelection->GetIsCollapsed(&bCollapsed);
00894     if (NS_FAILED(res)) return res;
00895   
00896     if (bCollapsed)
00897     {
00898       // Test for distance between caret and text that will be deleted
00899       res = CheckBidiLevelForDeletion(startNode, startOffset, aCollapsedAction, aCancel);
00900       if (NS_FAILED(res)) return res;
00901       if (*aCancel) return NS_OK;
00902 
00903       nsCOMPtr<nsIDOMText> textNode;
00904       PRUint32 strLength;
00905       
00906       // destroy any empty text nodes in our path
00907       if (mEditor->IsTextNode(startNode))
00908       {
00909         textNode = do_QueryInterface(startNode);
00910         res = textNode->GetLength(&strLength);
00911         if (NS_FAILED(res)) return res;
00912         // if it has a length and we aren't at the edge, we are done
00913         if (strLength && !( ((aCollapsedAction == nsIEditor::ePrevious) && startOffset) ||
00914                             ((aCollapsedAction == nsIEditor::eNext) && startOffset==PRInt32(strLength)) ) )
00915           return NS_OK;
00916         
00917         // remember where we are
00918         nsCOMPtr<nsIDOMNode> selNode = startNode;
00919         res = nsEditor::GetNodeLocation(selNode, address_of(startNode), &startOffset);
00920         if (NS_FAILED(res)) return res;
00921 
00922         // delete this text node if empty
00923         if (!strLength)
00924         {
00925           // delete empty text node
00926           res = mEditor->DeleteNode(selNode);
00927           if (NS_FAILED(res)) return res;
00928         }
00929         else
00930         {
00931           // if text node isn't empty, but we are at end of it, remeber that we are after it
00932           if (aCollapsedAction == nsIEditor::eNext)
00933             startOffset++;
00934         }
00935       }
00936 
00937       // find next node (we know we are in container here)
00938       nsCOMPtr<nsIContent> child, content(do_QueryInterface(startNode));
00939       if (!content) return NS_ERROR_NULL_POINTER;
00940       if (aCollapsedAction == nsIEditor::ePrevious)
00941         --startOffset;
00942       child = content->GetChildAt(startOffset);
00943 
00944       nsCOMPtr<nsIDOMNode> nextNode = do_QueryInterface(child);
00945       
00946       // scan for next node, deleting empty text nodes on way
00947       while (nextNode && mEditor->IsTextNode(nextNode))
00948       {
00949         textNode = do_QueryInterface(nextNode);
00950         if (!textNode) break;// found a br, stop there
00951 
00952         res = textNode->GetLength(&strLength);
00953         if (NS_FAILED(res)) return res;
00954         if (strLength) break;  // found a non-empty text node
00955         
00956         // delete empty text node
00957         res = mEditor->DeleteNode(nextNode);
00958         if (NS_FAILED(res)) return res;
00959         
00960         // find next node
00961         if (aCollapsedAction == nsIEditor::ePrevious)
00962           --startOffset;
00963           // don't need to increment startOffset for nsIEditor::eNext
00964         child = content->GetChildAt(startOffset);
00965 
00966         nextNode = do_QueryInterface(child);
00967       }
00968       // fix for bugzilla #125161: if we are about to forward delete a <BR>,
00969       // make sure it is not last node in editfield.  If it is, cancel deletion.
00970       if (nextNode && (aCollapsedAction == nsIEditor::eNext) && nsTextEditUtils::IsBreak(nextNode))
00971       {
00972         if (!GetBody()) return NS_ERROR_NULL_POINTER;
00973         nsCOMPtr<nsIDOMNode> lastChild;
00974         res = mBody->GetLastChild(getter_AddRefs(lastChild));
00975         if (lastChild == nextNode)
00976         {
00977           *aCancel = PR_TRUE;
00978           return NS_OK;
00979         }
00980       }
00981     }
00982   }
00983 
00984   return res;
00985 }
00986 
00987 nsresult
00988 nsTextEditRules::DidDeleteSelection(nsISelection *aSelection, 
00989                                     nsIEditor::EDirection aCollapsedAction, 
00990                                     nsresult aResult)
00991 {
00992   nsCOMPtr<nsIDOMNode> startNode;
00993   PRInt32 startOffset;
00994   nsresult res = mEditor->GetStartNodeAndOffset(aSelection, address_of(startNode), &startOffset);
00995   if (NS_FAILED(res)) return res;
00996   if (!startNode) return NS_ERROR_FAILURE;
00997   
00998   // delete empty text nodes at selection
00999   if (mEditor->IsTextNode(startNode))
01000   {
01001     nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
01002     PRUint32 strLength;
01003     res = textNode->GetLength(&strLength);
01004     if (NS_FAILED(res)) return res;
01005     
01006     // are we in an empty text node?
01007     if (!strLength)
01008     {
01009       res = mEditor->DeleteNode(startNode);
01010       if (NS_FAILED(res)) return res;
01011     }
01012   }
01013   if (!mDidExplicitlySetInterline)
01014   {
01015     // We prevent the caret from sticking on the left of prior BR
01016     // (i.e. the end of previous line) after this deletion.  Bug 92124
01017     nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(aSelection);
01018     if (selPriv) res = selPriv->SetInterlinePosition(PR_TRUE);
01019   }
01020   return res;
01021 }
01022 
01023 nsresult
01024 nsTextEditRules::WillUndo(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
01025 {
01026   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
01027   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
01028   // initialize out param
01029   *aCancel = PR_FALSE;
01030   *aHandled = PR_FALSE;
01031   return NS_OK;
01032 }
01033 
01034 /* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo.
01035  * if it has, set our state so we remember it.
01036  * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care.
01037  * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here.
01038  */
01039 nsresult
01040 nsTextEditRules:: DidUndo(nsISelection *aSelection, nsresult aResult)
01041 {
01042   nsresult res = aResult;  // if aResult is an error, we return it.
01043   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
01044   if (NS_SUCCEEDED(res)) 
01045   {
01046     if (mBogusNode) {
01047       mBogusNode = nsnull;
01048     }
01049     else
01050     {
01051       nsIDOMElement *theRoot = mEditor->GetRoot();
01052       if (!theRoot) return NS_ERROR_FAILURE;
01053       nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
01054       if (node && mEditor->IsMozEditorBogusNode(node))
01055         mBogusNode = node;
01056     }
01057   }
01058   return res;
01059 }
01060 
01061 nsresult
01062 nsTextEditRules::WillRedo(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
01063 {
01064   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
01065   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
01066   // initialize out param
01067   *aCancel = PR_FALSE;
01068   *aHandled = PR_FALSE;
01069   return NS_OK;
01070 }
01071 
01072 nsresult
01073 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
01074 {
01075   nsresult res = aResult;  // if aResult is an error, we return it.
01076   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
01077   if (NS_SUCCEEDED(res)) 
01078   {
01079     if (mBogusNode) {
01080       mBogusNode = nsnull;
01081     }
01082     else
01083     {
01084       nsIDOMElement *theRoot = mEditor->GetRoot();
01085       if (!theRoot) return NS_ERROR_FAILURE;
01086       
01087       nsCOMPtr<nsIDOMNodeList> nodeList;
01088       res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("div"),
01089                                           getter_AddRefs(nodeList));
01090       if (NS_FAILED(res)) return res;
01091       if (nodeList)
01092       {
01093         PRUint32 len;
01094         nodeList->GetLength(&len);
01095         
01096         if (len != 1) return NS_OK;  // only in the case of one div could there be the bogus node
01097         nsCOMPtr<nsIDOMNode> node;
01098         nodeList->Item(0, getter_AddRefs(node));
01099         if (!node) return NS_ERROR_NULL_POINTER;
01100         if (mEditor->IsMozEditorBogusNode(node))
01101           mBogusNode = node;
01102       }
01103     }
01104   }
01105   return res;
01106 }
01107 
01108 nsresult
01109 nsTextEditRules::WillOutputText(nsISelection *aSelection, 
01110                                 const nsAString  *aOutputFormat,
01111                                 nsAString *aOutString,                                
01112                                 PRBool   *aCancel,
01113                                 PRBool   *aHandled)
01114 {
01115   // null selection ok
01116   if (!aOutString || !aOutputFormat || !aCancel || !aHandled) 
01117     { return NS_ERROR_NULL_POINTER; }
01118 
01119   // initialize out param
01120   *aCancel = PR_FALSE;
01121   *aHandled = PR_FALSE;
01122 
01123   nsAutoString outputFormat(*aOutputFormat);
01124   ToLowerCase(outputFormat);
01125   if (outputFormat.EqualsLiteral("text/plain"))
01126   { // only use these rules for plain text output
01127     if (mFlags & nsIPlaintextEditor::eEditorPasswordMask)
01128     {
01129       *aOutString = mPasswordText;
01130       *aHandled = PR_TRUE;
01131     }
01132     else if (mBogusNode)
01133     { // this means there's no content, so output null string
01134       aOutString->Truncate();
01135       *aHandled = PR_TRUE;
01136     }
01137   }
01138   return NS_OK;
01139 }
01140 
01141 nsresult
01142 nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult)
01143 {
01144   return NS_OK;
01145 }
01146 
01147 nsresult
01148 nsTextEditRules::ReplaceNewlines(nsIDOMRange *aRange)
01149 {
01150   if (!aRange) return NS_ERROR_NULL_POINTER;
01151   
01152   // convert any newlines in editable, preformatted text nodes 
01153   // into normal breaks.  this is because layout wont give us a place 
01154   // to put the cursor on empty lines otherwise.
01155 
01156   nsresult res;
01157   nsCOMPtr<nsIContentIterator> iter =
01158        do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
01159   if (NS_FAILED(res)) return res;
01160 
01161   res = iter->Init(aRange);
01162   if (NS_FAILED(res)) return res;
01163   
01164   nsCOMArray<nsIDOMCharacterData> arrayOfNodes;
01165   
01166   // gather up a list of editable preformatted text nodes
01167   while (!iter->IsDone())
01168   {
01169     nsCOMPtr<nsIDOMNode> node = do_QueryInterface(iter->GetCurrentNode());
01170     if (!node)
01171       return NS_ERROR_FAILURE;
01172 
01173     if (mEditor->IsTextNode(node) && mEditor->IsEditable(node))
01174     {
01175       PRBool isPRE;
01176       res = mEditor->IsPreformatted(node, &isPRE);
01177       if (NS_FAILED(res)) return res;
01178       if (isPRE)
01179       {
01180         nsCOMPtr<nsIDOMCharacterData> data = do_QueryInterface(node);
01181         arrayOfNodes.AppendObject(data);
01182       }
01183     }
01184     iter->Next();
01185   }
01186   
01187   // replace newlines with breaks.  have to do this left to right,
01188   // since inserting the break can split the text node, and the
01189   // original node becomes the righthand node.
01190   PRInt32 j, nodeCount = arrayOfNodes.Count();
01191   for (j = 0; j < nodeCount; j++)
01192   {
01193     nsCOMPtr<nsIDOMNode> brNode;
01194     nsCOMPtr<nsIDOMCharacterData> textNode = arrayOfNodes[0];
01195     arrayOfNodes.RemoveObjectAt(0);
01196     // find the newline
01197     PRInt32 offset;
01198     nsAutoString tempString;
01199     do 
01200     {
01201       textNode->GetData(tempString);
01202       offset = tempString.FindChar(nsCRT::LF);
01203       if (offset == -1) break; // done with this node
01204       
01205       // delete the newline
01206       nsRefPtr<DeleteTextTxn> txn;
01207       // note 1: we are not telling edit listeners about these because they don't care
01208       // note 2: we are not wrapping these in a placeholder because we know they already are,
01209       //         or, failing that, undo is disabled
01210       res = mEditor->CreateTxnForDeleteText(textNode, offset, 1,
01211                                             getter_AddRefs(txn));
01212       if (NS_FAILED(res))  return res; 
01213       if (!txn)  return NS_ERROR_OUT_OF_MEMORY;
01214       res = mEditor->DoTransaction(txn); 
01215       if (NS_FAILED(res))  return res; 
01216       
01217       // insert a break
01218       res = mEditor->CreateBR(textNode, offset, address_of(brNode));
01219       if (NS_FAILED(res)) return res;
01220     } while (1);  // break used to exit while loop
01221   }
01222   return res;
01223 }
01224 
01225 nsresult
01226 nsTextEditRules::CreateTrailingBRIfNeeded()
01227 {
01228   // but only if we aren't a single line edit field
01229   if (mFlags & nsIPlaintextEditor::eEditorSingleLineMask)
01230     return NS_OK;
01231   if (!GetBody()) return NS_ERROR_NULL_POINTER;
01232   nsCOMPtr<nsIDOMNode> lastChild;
01233   nsresult res = mBody->GetLastChild(getter_AddRefs(lastChild));
01234   // assuming CreateBogusNodeIfNeeded() has been called first
01235   if (NS_FAILED(res)) return res;  
01236   if (!lastChild) return NS_ERROR_NULL_POINTER;
01237 
01238   if (!nsTextEditUtils::IsBreak(lastChild))
01239   {
01240     nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
01241     PRUint32 rootLen;
01242     res = mEditor->GetLengthOfDOMNode(mBody, rootLen);
01243     if (NS_FAILED(res)) return res; 
01244     nsCOMPtr<nsIDOMNode> unused;
01245     res = CreateMozBR(mBody, rootLen, address_of(unused));
01246   }
01247   return res;
01248 }
01249 
01250 nsresult
01251 nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection)
01252 {
01253   if (!aSelection) { return NS_ERROR_NULL_POINTER; }
01254   if (!mEditor) { return NS_ERROR_NULL_POINTER; }
01255   if (mBogusNode) return NS_OK;  // let's not create more than one, ok?
01256 
01257   // tell rules system to not do any post-processing
01258   nsAutoRules beginRulesSniffing(mEditor, nsEditor::kOpIgnore, nsIEditor::eNone);
01259   
01260   if (!GetBody())
01261   {
01262     // we don't even have a body yet, don't insert any bogus nodes at
01263     // this point.
01264 
01265     return NS_OK;
01266   }
01267 
01268   // now we've got the body tag.
01269   // iterate the body tag, looking for editable content
01270   // if no editable content is found, insert the bogus node
01271   PRBool needsBogusContent=PR_TRUE;
01272   nsCOMPtr<nsIDOMNode> bodyChild;
01273   nsresult res = mBody->GetFirstChild(getter_AddRefs(bodyChild));        
01274   while ((NS_SUCCEEDED(res)) && bodyChild)
01275   { 
01276     if (mEditor->IsMozEditorBogusNode(bodyChild) || mEditor->IsEditable(bodyChild))
01277     {
01278       needsBogusContent = PR_FALSE;
01279       break;
01280     }
01281     nsCOMPtr<nsIDOMNode>temp;
01282     bodyChild->GetNextSibling(getter_AddRefs(temp));
01283     bodyChild = do_QueryInterface(temp);
01284   }
01285   if (needsBogusContent)
01286   {
01287     // create a br
01288     nsCOMPtr<nsIContent> newContent;
01289     res = mEditor->CreateHTMLContent(NS_LITERAL_STRING("br"), getter_AddRefs(newContent));
01290     if (NS_FAILED(res)) return res;
01291     nsCOMPtr<nsIDOMElement>brElement = do_QueryInterface(newContent);
01292 
01293     // set mBogusNode to be the newly created <br>
01294     mBogusNode = brElement;
01295     if (!mBogusNode) return NS_ERROR_NULL_POINTER;
01296 
01297     // give it a special attribute
01298     brElement->SetAttribute( kMOZEditorBogusNodeAttr,
01299                              kMOZEditorBogusNodeValue );
01300     
01301     // put the node in the document
01302     res = mEditor->InsertNode(mBogusNode, mBody, 0);
01303     if (NS_FAILED(res)) return res;
01304 
01305     // set selection
01306     aSelection->Collapse(mBody, 0);
01307   }
01308   return res;
01309 }
01310 
01311 
01312 nsresult
01313 nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection, 
01314                                            const nsAString  *aInString,
01315                                            nsAString  *aOutString,
01316                                            PRInt32          aMaxLength)
01317 {
01318   if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
01319   
01320   nsresult res = NS_OK;
01321   *aOutString = *aInString;
01322   
01323   if ((-1 != aMaxLength) && (mFlags & nsIPlaintextEditor::eEditorPlaintextMask)
01324       && !mEditor->IsIMEComposing() )
01325   {
01326     // Get the current text length.
01327     // Get the length of inString.
01328     // Get the length of the selection.
01329     //   If selection is collapsed, it is length 0.
01330     //   Subtract the length of the selection from the len(doc) 
01331     //   since we'll delete the selection on insert.
01332     //   This is resultingDocLength.
01333     // Get old length of IME composing string
01334     //   which will be replaced by new one.
01335     // If (resultingDocLength) is at or over max, cancel the insert
01336     // If (resultingDocLength) + (length of input) > max, 
01337     //    set aOutString to subset of inString so length = max
01338     PRInt32 docLength;
01339     res = mEditor->GetTextLength(&docLength);
01340     if (NS_FAILED(res)) { return res; }
01341 
01342     PRUint32 start, end;
01343     res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
01344     if (NS_FAILED(res)) { return res; }
01345 
01346     PRInt32 oldCompStrLength;
01347     res = mEditor->GetIMEBufferLength(&oldCompStrLength);
01348     if (NS_FAILED(res)) { return res; }
01349 
01350     const PRInt32 selectionLength = end - start;
01351     const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength;
01352     if (resultingDocLength >= aMaxLength)
01353     {
01354       aOutString->Truncate();
01355     }
01356     else
01357     {
01358       PRInt32 inCount = aOutString->Length();
01359       if (inCount + resultingDocLength > aMaxLength)
01360       {
01361         aOutString->Truncate(aMaxLength - resultingDocLength);
01362       }
01363     }
01364   }
01365   return res;
01366 }
01367 
01368 nsresult
01369 nsTextEditRules::ResetIMETextPWBuf()
01370 {
01371   mPasswordIMEText.Truncate();
01372   return NS_OK;
01373 }
01374 
01375 nsresult
01376 nsTextEditRules::RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString)
01377 {
01378   if (!aIMEString) {
01379     return NS_ERROR_NULL_POINTER;
01380   }
01381 
01382   // initialize PasswordIME
01383   if (mPasswordIMEText.IsEmpty()) {
01384     mPasswordIMEIndex = aStart;
01385   }
01386   else {
01387     // manage the password buffer
01388     mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length());
01389     aStart = mPasswordIMEIndex;
01390   }
01391 
01392   mPasswordIMEText.Assign(*aIMEString);
01393   return NS_OK;
01394 }
01395 
01396 nsresult
01397 nsTextEditRules::EchoInsertionToPWBuff(PRInt32 aStart, PRInt32 aEnd, nsAString *aOutString)
01398 {
01399   if (!aOutString) {return NS_ERROR_NULL_POINTER;}
01400 
01401   // manage the password buffer
01402   mPasswordText.Insert(*aOutString, aStart);
01403 
01404   // change the output to '*' only
01405   PRInt32 length = aOutString->Length();
01406   PRInt32 i;
01407   aOutString->Truncate();
01408   for (i=0; i<length; i++)
01409   {
01410     aOutString->Append(PRUnichar('*'));
01411   }
01412 
01413   return NS_OK;
01414 }
01415 
01416 
01418 // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset}
01419 //                       
01420 nsresult 
01421 nsTextEditRules::CreateMozBR(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outBRNode)
01422 {
01423   if (!inParent || !outBRNode) return NS_ERROR_NULL_POINTER;
01424 
01425   nsresult res = mEditor->CreateBR(inParent, inOffset, outBRNode);
01426   if (NS_FAILED(res)) return res;
01427 
01428   // give it special moz attr
01429   nsCOMPtr<nsIDOMElement> brElem = do_QueryInterface(*outBRNode);
01430   if (brElem)
01431   {
01432     res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz"));
01433     if (NS_FAILED(res)) return res;
01434   }
01435   return res;
01436 }
01437 
01438 nsIDOMNode *
01439 nsTextEditRules::GetBody()
01440 {
01441   if (!mBody)
01442   {
01443     // remember our body node
01444     mBody = mEditor->GetRoot();
01445   }
01446 
01447   return mBody;
01448 }