Back to index

lightning-sunbird  0.9+nobinonly
nsPlaintextEditor.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  *   Daniel Glazman <glazman@netscape.com>
00024  *   Mats Palmgren <mats.palmgren@bredband.net>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 
00041 #include "nsPlaintextEditor.h"
00042 #include "nsICaret.h"
00043 #include "nsTextEditUtils.h"
00044 #include "nsTextEditRules.h"
00045 #include "nsEditorEventListeners.h"
00046 #include "nsIEditActionListener.h"
00047 #include "nsIDOMNodeList.h"
00048 #include "nsIDOMDocument.h"
00049 #include "nsIDocument.h"
00050 #include "nsIDOMEventReceiver.h" 
00051 #include "nsIDOM3EventTarget.h" 
00052 #include "nsIDOMKeyEvent.h"
00053 #include "nsIDOMMouseListener.h"
00054 #include "nsISelection.h"
00055 #include "nsISelectionPrivate.h"
00056 #include "nsISelectionController.h"
00057 #include "nsGUIEvent.h"
00058 #include "nsIDOMEventGroup.h"
00059 #include "nsCRT.h"
00060 
00061 #include "nsIEnumerator.h"
00062 #include "nsIContent.h"
00063 #include "nsIContentIterator.h"
00064 #include "nsIDOMRange.h"
00065 #include "nsISupportsArray.h"
00066 #include "nsIComponentManager.h"
00067 #include "nsIServiceManager.h"
00068 #include "nsIDocumentEncoder.h"
00069 #include "nsIPresShell.h"
00070 #include "nsISupportsPrimitives.h"
00071 #include "nsReadableUtils.h"
00072 
00073 // Misc
00074 #include "nsEditorUtils.h"  // nsAutoEditBatch, nsAutoRules
00075 #include "nsIPrefBranch.h"
00076 #include "nsIPrefService.h"
00077 #include "nsUnicharUtils.h"
00078 
00079 #include "nsAOLCiter.h"
00080 #include "nsInternetCiter.h"
00081 
00082 // Drag & Drop, Clipboard
00083 #include "nsIClipboard.h"
00084 #include "nsITransferable.h"
00085 
00086 // prototype for rules creation shortcut
00087 nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
00088 
00089 nsPlaintextEditor::nsPlaintextEditor()
00090 : nsEditor()
00091 , mIgnoreSpuriousDragEvent(PR_FALSE)
00092 , mRules(nsnull)
00093 , mWrapToWindow(PR_FALSE)
00094 , mWrapColumn(0)
00095 , mMaxTextLength(-1)
00096 , mInitTriggerCounter(0)
00097 {
00098 } 
00099 
00100 nsPlaintextEditor::~nsPlaintextEditor()
00101 {
00102   // remove the rules as an action listener.  Else we get a bad ownership loop later on.
00103   // it's ok if the rules aren't a listener; we ignore the error.
00104   nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
00105   RemoveEditActionListener(mListener);
00106   
00107   // Remove event listeners. Note that if we had an HTML editor,
00108   //  it installed its own instead of these
00109   RemoveEventListeners();
00110 }
00111 
00112 NS_IMPL_ADDREF_INHERITED(nsPlaintextEditor, nsEditor)
00113 NS_IMPL_RELEASE_INHERITED(nsPlaintextEditor, nsEditor)
00114 
00115 NS_INTERFACE_MAP_BEGIN(nsPlaintextEditor)
00116   NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor)
00117   NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
00118 NS_INTERFACE_MAP_END_INHERITING(nsEditor)
00119 
00120 
00121 NS_IMETHODIMP nsPlaintextEditor::Init(nsIDOMDocument *aDoc, 
00122                                  nsIPresShell   *aPresShell, nsIContent *aRoot, nsISelectionController *aSelCon, PRUint32 aFlags)
00123 {
00124   NS_PRECONDITION(aDoc && aPresShell, "bad arg");
00125   if (!aDoc || !aPresShell)
00126     return NS_ERROR_NULL_POINTER;
00127   
00128   nsresult res = NS_OK, rulesRes = NS_OK;
00129   
00130   if (1)
00131   {
00132     // block to scope nsAutoEditInitRulesTrigger
00133     nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes);
00134   
00135     // Init the base editor
00136     res = nsEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
00137   }
00138   
00139   if (NS_FAILED(rulesRes)) return rulesRes;
00140   return res;
00141 }
00142 
00143 void 
00144 nsPlaintextEditor::BeginEditorInit()
00145 {
00146   mInitTriggerCounter++;
00147 }
00148 
00149 nsresult 
00150 nsPlaintextEditor::EndEditorInit()
00151 {
00152   nsresult res = NS_OK;
00153   NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?");
00154   mInitTriggerCounter--;
00155   if (mInitTriggerCounter == 0)
00156   {
00157     res = InitRules();
00158     if (NS_SUCCEEDED(res)) 
00159       EnableUndo(PR_TRUE);
00160   }
00161   return res;
00162 }
00163 
00164 NS_IMETHODIMP 
00165 nsPlaintextEditor::SetDocumentCharacterSet(const nsACString & characterSet) 
00166 { 
00167   nsresult result; 
00168 
00169   result = nsEditor::SetDocumentCharacterSet(characterSet); 
00170 
00171   // update META charset tag 
00172   if (NS_SUCCEEDED(result)) { 
00173     nsCOMPtr<nsIDOMDocument>domdoc; 
00174     result = GetDocument(getter_AddRefs(domdoc)); 
00175     if (NS_SUCCEEDED(result) && domdoc) { 
00176       nsCOMPtr<nsIDOMNodeList>metaList; 
00177       nsCOMPtr<nsIDOMElement>metaElement; 
00178       PRBool newMetaCharset = PR_TRUE; 
00179 
00180       // get a list of META tags 
00181       result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("meta"), getter_AddRefs(metaList)); 
00182       if (NS_SUCCEEDED(result) && metaList) { 
00183         PRUint32 listLength = 0; 
00184         (void) metaList->GetLength(&listLength); 
00185 
00186         nsCOMPtr<nsIDOMNode>metaNode; 
00187         for (PRUint32 i = 0; i < listLength; i++) { 
00188           metaList->Item(i, getter_AddRefs(metaNode)); 
00189           if (!metaNode) continue; 
00190           metaElement = do_QueryInterface(metaNode); 
00191           if (!metaElement) continue; 
00192 
00193           nsAutoString currentValue; 
00194           if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("http-equiv"), currentValue))) continue; 
00195 
00196           if (FindInReadable(NS_LITERAL_STRING("content-type"),
00197                              currentValue,
00198                              nsCaseInsensitiveStringComparator())) { 
00199             NS_NAMED_LITERAL_STRING(content, "content");
00200             if (NS_FAILED(metaElement->GetAttribute(content, currentValue))) continue; 
00201 
00202             NS_NAMED_LITERAL_STRING(charsetEquals, "charset=");
00203             nsAString::const_iterator originalStart, start, end;
00204             originalStart = currentValue.BeginReading(start);
00205             currentValue.EndReading(end);
00206             if (FindInReadable(charsetEquals, start, end,
00207                                nsCaseInsensitiveStringComparator())) {
00208 
00209               // set attribute to <original prefix> charset=text/html
00210               result = nsEditor::SetAttribute(metaElement, content,
00211                                               Substring(originalStart, start) +
00212                                               charsetEquals + NS_ConvertASCIItoUCS2(characterSet)); 
00213               if (NS_SUCCEEDED(result)) 
00214                 newMetaCharset = PR_FALSE; 
00215               break; 
00216             } 
00217           } 
00218         } 
00219       } 
00220 
00221       if (newMetaCharset) { 
00222         nsCOMPtr<nsIDOMNodeList>headList; 
00223         result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"),getter_AddRefs(headList)); 
00224         if (NS_SUCCEEDED(result) && headList) { 
00225           nsCOMPtr<nsIDOMNode>headNode; 
00226           headList->Item(0, getter_AddRefs(headNode)); 
00227           if (headNode) { 
00228             nsCOMPtr<nsIDOMNode>resultNode; 
00229             // Create a new meta charset tag 
00230             result = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode)); 
00231             if (NS_FAILED(result)) 
00232               return NS_ERROR_FAILURE; 
00233 
00234             // Set attributes to the created element 
00235             if (resultNode && !characterSet.IsEmpty()) { 
00236               metaElement = do_QueryInterface(resultNode); 
00237               if (metaElement) { 
00238                 // not undoable, undo should undo CreateNode 
00239                 result = metaElement->SetAttribute(NS_LITERAL_STRING("http-equiv"), NS_LITERAL_STRING("Content-Type")); 
00240                 if (NS_SUCCEEDED(result)) { 
00241                   // not undoable, undo should undo CreateNode 
00242                   result = metaElement->SetAttribute(NS_LITERAL_STRING("content"),
00243                                                      NS_LITERAL_STRING("text/html;charset=") + NS_ConvertASCIItoUCS2(characterSet)); 
00244                 } 
00245               } 
00246             } 
00247           } 
00248         } 
00249       } 
00250     } 
00251   } 
00252 
00253   return result; 
00254 } 
00255 
00256 nsresult
00257 nsPlaintextEditor::CreateEventListeners()
00258 {
00259   nsresult rv = NS_OK;
00260 
00261   if (!mMouseListenerP) {
00262     // get a mouse listener
00263     rv |= NS_NewEditorMouseListener(getter_AddRefs(mMouseListenerP), this);
00264   }
00265 
00266   if (!mKeyListenerP) {
00267     // get a key listener
00268     rv |= NS_NewEditorKeyListener(getter_AddRefs(mKeyListenerP), this);
00269   }
00270 
00271   if (!mTextListenerP) {
00272     // get a text listener
00273     rv |= NS_NewEditorTextListener(getter_AddRefs(mTextListenerP), this);
00274   }
00275 
00276   if (!mCompositionListenerP) {
00277     // get a composition listener
00278     rv |=
00279       NS_NewEditorCompositionListener(getter_AddRefs(mCompositionListenerP),
00280                                       this);
00281   }
00282 
00283   if (!mDragListenerP) {
00284     // get a drag listener
00285     nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShellWeak);
00286     rv |= NS_NewEditorDragListener(getter_AddRefs(mDragListenerP), presShell,
00287                                    this);
00288   }
00289 
00290   if (!mFocusListenerP) {
00291     // get a focus listener
00292     rv |= NS_NewEditorFocusListener(getter_AddRefs(mFocusListenerP), this);
00293   }
00294 
00295   return rv;
00296 }
00297 
00298 NS_IMETHODIMP 
00299 nsPlaintextEditor::GetFlags(PRUint32 *aFlags)
00300 {
00301   if (!mRules || !aFlags) { return NS_ERROR_NULL_POINTER; }
00302   return mRules->GetFlags(aFlags);
00303 }
00304 
00305 
00306 NS_IMETHODIMP 
00307 nsPlaintextEditor::SetFlags(PRUint32 aFlags)
00308 {
00309   if (!mRules) { return NS_ERROR_NULL_POINTER; }
00310   return mRules->SetFlags(aFlags);
00311 }
00312 
00313 
00314 NS_IMETHODIMP nsPlaintextEditor::InitRules()
00315 {
00316   // instantiate the rules for this text editor
00317   nsresult res = NS_NewTextEditRules(getter_AddRefs(mRules));
00318   if (NS_FAILED(res)) return res;
00319   if (!mRules) return NS_ERROR_UNEXPECTED;
00320   return mRules->Init(this, mFlags);
00321 }
00322 
00323 
00324 NS_IMETHODIMP
00325 nsPlaintextEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
00326 {
00327   NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
00328 
00329   nsCOMPtr<nsIDOMDocument> doc;
00330   GetDocument(getter_AddRefs(doc));
00331   *aIsDocumentEditable = doc ? IsModifiable() : PR_FALSE;
00332 
00333   return NS_OK;
00334 }
00335 
00336 PRBool nsPlaintextEditor::IsModifiable()
00337 {
00338   PRUint32 flags;
00339   if (NS_SUCCEEDED(GetFlags(&flags)))
00340     return ((flags & eEditorReadonlyMask) == 0);
00341 
00342   return PR_FALSE;
00343 }
00344 
00345 
00346 #ifdef XP_MAC
00347 #pragma mark -
00348 #pragma mark  nsIHTMLEditor methods 
00349 #pragma mark -
00350 #endif
00351 
00352 NS_IMETHODIMP nsPlaintextEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
00353 {
00354   PRUint32 keyCode, character;
00355   PRBool   ctrlKey, altKey, metaKey;
00356 
00357   if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
00358 
00359   if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) && 
00360       NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) &&
00361       NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) &&
00362       NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey)))
00363   {
00364     aKeyEvent->GetCharCode(&character);
00365     if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
00366      || keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
00367     {
00368       nsString empty;
00369       return TypedText(empty, eTypedBreak);
00370     }
00371     else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
00372     {
00373       // pass escape keypresses through as empty strings: needed for ime support
00374       nsString empty;
00375       return TypedText(empty, eTypedText);
00376     }
00377     
00378     if (character && !altKey && !ctrlKey && !metaKey)
00379     {
00380       aKeyEvent->PreventDefault();
00381       nsAutoString key(character);
00382       return TypedText(key, eTypedText);
00383     }
00384   }
00385   return NS_ERROR_FAILURE;
00386 }
00387 
00388 /* This routine is needed to provide a bottleneck for typing for logging
00389    purposes.  Can't use HandleKeyPress() (above) for that since it takes
00390    a nsIDOMKeyEvent* parameter.  So instead we pass enough info through
00391    to TypedText() to determine what action to take, but without passing
00392    an event.
00393    */
00394 NS_IMETHODIMP nsPlaintextEditor::TypedText(const nsAString& aString,
00395                                       PRInt32 aAction)
00396 {
00397   nsAutoPlaceHolderBatch batch(this, gTypingTxnName);
00398 
00399   switch (aAction)
00400   {
00401     case eTypedText:
00402       {
00403         return InsertText(aString);
00404       }
00405     case eTypedBreak:
00406       {
00407         return InsertLineBreak();
00408       } 
00409   } 
00410   return NS_ERROR_FAILURE; 
00411 }
00412 
00413 NS_IMETHODIMP nsPlaintextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
00414 {
00415   if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
00416   *outBRNode = nsnull;
00417   nsresult res;
00418   
00419   // we need to insert a br.  unfortunately, we may have to split a text node to do it.
00420   nsCOMPtr<nsIDOMNode> node = *aInOutParent;
00421   PRInt32 theOffset = *aInOutOffset;
00422   nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
00423   NS_NAMED_LITERAL_STRING(brType, "br");
00424   nsCOMPtr<nsIDOMNode> brNode;
00425   if (nodeAsText)  
00426   {
00427     nsCOMPtr<nsIDOMNode> tmp;
00428     PRInt32 offset;
00429     PRUint32 len;
00430     nodeAsText->GetLength(&len);
00431     GetNodeLocation(node, address_of(tmp), &offset);
00432     if (!tmp) return NS_ERROR_FAILURE;
00433     if (!theOffset)
00434     {
00435       // we are already set to go
00436     }
00437     else if (theOffset == (PRInt32)len)
00438     {
00439       // update offset to point AFTER the text node
00440       offset++;
00441     }
00442     else
00443     {
00444       // split the text node
00445       res = SplitNode(node, theOffset, getter_AddRefs(tmp));
00446       if (NS_FAILED(res)) return res;
00447       res = GetNodeLocation(node, address_of(tmp), &offset);
00448       if (NS_FAILED(res)) return res;
00449     }
00450     // create br
00451     res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
00452     if (NS_FAILED(res)) return res;
00453     *aInOutParent = tmp;
00454     *aInOutOffset = offset+1;
00455   }
00456   else
00457   {
00458     res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode));
00459     if (NS_FAILED(res)) return res;
00460     (*aInOutOffset)++;
00461   }
00462 
00463   *outBRNode = brNode;
00464   if (*outBRNode && (aSelect != eNone))
00465   {
00466     nsCOMPtr<nsIDOMNode> parent;
00467     PRInt32 offset;
00468     res = GetNodeLocation(*outBRNode, address_of(parent), &offset);
00469     if (NS_FAILED(res)) return res;
00470 
00471     nsCOMPtr<nsISelection> selection;
00472     res = GetSelection(getter_AddRefs(selection));
00473     if (NS_FAILED(res)) return res;
00474     nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00475     if (aSelect == eNext)
00476     {
00477       // position selection after br
00478       selPriv->SetInterlinePosition(PR_TRUE);
00479       res = selection->Collapse(parent, offset+1);
00480     }
00481     else if (aSelect == ePrevious)
00482     {
00483       // position selection before br
00484       selPriv->SetInterlinePosition(PR_TRUE);
00485       res = selection->Collapse(parent, offset);
00486     }
00487   }
00488   return NS_OK;
00489 }
00490 
00491 
00492 NS_IMETHODIMP nsPlaintextEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
00493 {
00494   nsCOMPtr<nsIDOMNode> parent = aNode;
00495   PRInt32 offset = aOffset;
00496   return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
00497 }
00498 
00499 NS_IMETHODIMP nsPlaintextEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
00500 {
00501   if (!outBRNode) return NS_ERROR_NULL_POINTER;
00502   *outBRNode = nsnull;
00503 
00504   // calling it text insertion to trigger moz br treatment by rules
00505   nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
00506 
00507   nsCOMPtr<nsISelection> selection;
00508   nsresult res = GetSelection(getter_AddRefs(selection));
00509   if (NS_FAILED(res)) return res;
00510   PRBool bCollapsed;
00511   res = selection->GetIsCollapsed(&bCollapsed);
00512   if (NS_FAILED(res)) return res;
00513   if (!bCollapsed)
00514   {
00515     res = DeleteSelection(nsIEditor::eNone);
00516     if (NS_FAILED(res)) return res;
00517   }
00518   nsCOMPtr<nsIDOMNode> selNode;
00519   PRInt32 selOffset;
00520   res = GetStartNodeAndOffset(selection, address_of(selNode), &selOffset);
00521   if (NS_FAILED(res)) return res;
00522   
00523   res = CreateBR(selNode, selOffset, outBRNode);
00524   if (NS_FAILED(res)) return res;
00525     
00526   // position selection after br
00527   res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
00528   if (NS_FAILED(res)) return res;
00529   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00530   selPriv->SetInterlinePosition(PR_TRUE);
00531   return selection->Collapse(selNode, selOffset+1);
00532 }
00533 
00534 nsresult
00535 nsPlaintextEditor::GetTextSelectionOffsets(nsISelection *aSelection,
00536                                            PRUint32 &aOutStartOffset, 
00537                                            PRUint32 &aOutEndOffset)
00538 {
00539   NS_ASSERTION(aSelection, "null selection");
00540 
00541   nsresult rv;
00542   nsCOMPtr<nsIDOMNode> startNode, endNode;
00543   PRInt32 startNodeOffset, endNodeOffset;
00544   aSelection->GetAnchorNode(getter_AddRefs(startNode));
00545   aSelection->GetAnchorOffset(&startNodeOffset);
00546   aSelection->GetFocusNode(getter_AddRefs(endNode));
00547   aSelection->GetFocusOffset(&endNodeOffset);
00548 
00549   nsIDOMElement* rootNode = GetRoot();
00550   NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER);
00551 
00552   PRInt32 startOffset = -1;
00553   PRInt32 endOffset = -1;
00554 
00555   nsCOMPtr<nsIContentIterator> iter =
00556     do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
00557   NS_ENSURE_SUCCESS(rv, rv);
00558     
00559 #ifdef NS_DEBUG
00560   PRInt32 nodeCount = 0; // only needed for the assertions below
00561 #endif
00562   PRUint32 totalLength = 0;
00563   nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootNode);
00564   iter->Init(rootContent);
00565   for (; !iter->IsDone() && (startOffset == -1 || endOffset == -1); iter->Next()) {
00566     nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(iter->GetCurrentNode());
00567     nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(currentNode);
00568     if (textNode) {
00569       // Note that sometimes we have an empty #text-node as start/endNode,
00570       // which we regard as not editable because the frame width == 0,
00571       // see nsEditor::IsEditable().
00572       PRBool editable = IsEditable(currentNode);
00573       if (currentNode == startNode) {
00574         startOffset = totalLength + (editable ? startNodeOffset : 0);
00575       }
00576       if (currentNode == endNode) {
00577         endOffset = totalLength + (editable ? endNodeOffset : 0);
00578       }
00579       if (editable) {
00580         PRUint32 length;
00581         textNode->GetLength(&length);
00582         totalLength += length;
00583       }
00584     }
00585 #ifdef NS_DEBUG
00586     ++nodeCount;
00587 #endif
00588   }
00589 
00590   if (endOffset == -1) {
00591     NS_ASSERTION(endNode == rootNode, "failed to find the end node");
00592     NS_ASSERTION(endNodeOffset == nodeCount-1 || endNodeOffset == 0,
00593                  "invalid end node offset");
00594     endOffset = endNodeOffset == 0 ? 0 : totalLength;
00595   }
00596   if (startOffset == -1) {
00597     NS_ASSERTION(startNode == rootNode, "failed to find the start node");
00598     NS_ASSERTION(startNodeOffset == nodeCount-1 || startNodeOffset == 0,
00599                  "invalid start node offset");
00600     startOffset = startNodeOffset == 0 ? 0 : totalLength;
00601   }
00602 
00603   // Make sure aOutStartOffset <= aOutEndOffset.
00604   if (startOffset <= endOffset) {
00605     aOutStartOffset = startOffset;
00606     aOutEndOffset = endOffset;
00607   }
00608   else {
00609     aOutStartOffset = endOffset;
00610     aOutEndOffset = startOffset;
00611   }
00612 
00613   return NS_OK;
00614 }
00615 
00616 NS_IMETHODIMP nsPlaintextEditor::DeleteSelection(nsIEditor::EDirection aAction)
00617 {
00618   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
00619 
00620   nsresult result;
00621 
00622   // delete placeholder txns merge.
00623   nsAutoPlaceHolderBatch batch(this, gDeleteTxnName);
00624   nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
00625 
00626   // If it's one of these modes,
00627   // we have to extend the selection first.
00628   // This needs to happen inside selection batching,
00629   // otherwise the deleted text is autocopied to the clipboard.
00630   if (aAction == eNextWord || aAction == ePreviousWord
00631       || aAction == eToBeginningOfLine || aAction == eToEndOfLine)
00632   {
00633     if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
00634     nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
00635     if (!ps) return NS_ERROR_NOT_INITIALIZED;
00636 
00637     PRUint8 caretBidiLevel;
00638     result = ps->GetCaretBidiLevel(&caretBidiLevel);
00639     if (NS_FAILED(result)) return result;
00640     
00641     nsCOMPtr<nsISelectionController> selCont (do_QueryReferent(mSelConWeak));
00642     if (!selCont)
00643       return NS_ERROR_NO_INTERFACE;
00644 
00645     switch (aAction)
00646     {
00647         // if caret has odd Bidi level, i.e. text is right-to-left,
00648         // reverse the effect of ePreviousWord and eNextWord
00649         case eNextWord:
00650           result = (caretBidiLevel & 1) ?
00651                    selCont->WordMove(PR_FALSE, PR_TRUE) :
00652                    selCont->WordMove(PR_TRUE, PR_TRUE);
00653           // DeleteSelectionImpl doesn't handle these actions
00654           // because it's inside batching, so don't confuse it:
00655           aAction = eNone;
00656           break;
00657         case ePreviousWord:
00658           result = (caretBidiLevel & 1) ?
00659                    selCont->WordMove(PR_TRUE, PR_TRUE) :
00660                    selCont->WordMove(PR_FALSE, PR_TRUE);
00661           aAction = eNone;
00662           break;
00663         case eToBeginningOfLine:
00664           selCont->IntraLineMove(PR_TRUE, PR_FALSE);          // try to move to end
00665           result = selCont->IntraLineMove(PR_FALSE, PR_TRUE); // select to beginning
00666           aAction = eNone;
00667           break;
00668         case eToEndOfLine:
00669           result = selCont->IntraLineMove(PR_TRUE, PR_TRUE);
00670           aAction = eNext;
00671           break;
00672         default:       // avoid several compiler warnings
00673           result = NS_OK;
00674           break;
00675     }
00676     NS_ENSURE_SUCCESS(result, result);
00677   }
00678 
00679   // pre-process
00680   nsCOMPtr<nsISelection> selection;
00681   result = GetSelection(getter_AddRefs(selection));
00682   if (NS_FAILED(result)) return result;
00683   if (!selection) return NS_ERROR_NULL_POINTER;
00684 
00685   nsTextRulesInfo ruleInfo(nsTextEditRules::kDeleteSelection);
00686   ruleInfo.collapsedAction = aAction;
00687   PRBool cancel, handled;
00688   result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
00689   if (NS_FAILED(result)) return result;
00690   if (!cancel && !handled)
00691   {
00692     result = DeleteSelectionImpl(aAction);
00693   }
00694   if (!cancel)
00695   {
00696     // post-process 
00697     result = mRules->DidDoAction(selection, &ruleInfo, result);
00698   }
00699 
00700   return result;
00701 }
00702 
00703 NS_IMETHODIMP nsPlaintextEditor::InsertText(const nsAString &aStringToInsert)
00704 {
00705   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
00706 
00707   PRInt32 theAction = nsTextEditRules::kInsertText;
00708   PRInt32 opID = kOpInsertText;
00709   if (mInIMEMode) 
00710   {
00711     theAction = nsTextEditRules::kInsertTextIME;
00712     opID = kOpInsertIMEText;
00713   }
00714   nsAutoPlaceHolderBatch batch(this, nsnull); 
00715   nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
00716 
00717   // pre-process
00718   nsCOMPtr<nsISelection> selection;
00719   nsresult result = GetSelection(getter_AddRefs(selection));
00720   if (NS_FAILED(result)) return result;
00721   if (!selection) return NS_ERROR_NULL_POINTER;
00722   nsAutoString resultString;
00723   // XXX can we trust instring to outlive ruleInfo,
00724   // XXX and ruleInfo not to refer to instring in its dtor?
00725   //nsAutoString instring(aStringToInsert);
00726   nsTextRulesInfo ruleInfo(theAction);
00727   ruleInfo.inString = &aStringToInsert;
00728   ruleInfo.outString = &resultString;
00729   ruleInfo.maxLength = mMaxTextLength;
00730 
00731   PRBool cancel, handled;
00732   result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
00733   if (NS_FAILED(result)) return result;
00734   if (!cancel && !handled)
00735   {
00736     // we rely on rules code for now - no default implementation
00737   }
00738   if (!cancel)
00739   {
00740     // post-process 
00741     result = mRules->DidDoAction(selection, &ruleInfo, result);
00742   }
00743   return result;
00744 }
00745 
00746 NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak()
00747 {
00748   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
00749 
00750   nsAutoEditBatch beginBatching(this);
00751   nsAutoRules beginRulesSniffing(this, kOpInsertBreak, nsIEditor::eNext);
00752 
00753   // pre-process
00754   nsCOMPtr<nsISelection> selection;
00755   nsresult res;
00756   res = GetSelection(getter_AddRefs(selection));
00757   if (NS_FAILED(res)) return res;
00758   if (!selection) return NS_ERROR_NULL_POINTER;
00759 
00760   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
00761   PRBool cancel, handled;
00762   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
00763   if (NS_FAILED(res)) return res;
00764   if (!cancel && !handled)
00765   {
00766     // create the new BR node
00767     nsCOMPtr<nsIDOMNode> newNode;
00768     res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("br"), getter_AddRefs(newNode));
00769     if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
00770     if (NS_SUCCEEDED(res))
00771     {
00772       // set the selection to the new node
00773       nsCOMPtr<nsIDOMNode>parent;
00774       res = newNode->GetParentNode(getter_AddRefs(parent));
00775       if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
00776       if (NS_SUCCEEDED(res))
00777       {
00778         PRInt32 offsetInParent=-1;  // we use the -1 as a marker to see if we need to compute this or not
00779         nsCOMPtr<nsIDOMNode>nextNode;
00780         newNode->GetNextSibling(getter_AddRefs(nextNode));
00781         if (nextNode)
00782         {
00783           nsCOMPtr<nsIDOMCharacterData>nextTextNode = do_QueryInterface(nextNode);
00784           if (!nextTextNode) {
00785             nextNode = do_QueryInterface(newNode); // is this QI needed?
00786           }
00787           else { 
00788             offsetInParent=0; 
00789           }
00790         }
00791         else {
00792           nextNode = do_QueryInterface(newNode); // is this QI needed?
00793         }
00794 
00795         if (-1==offsetInParent) 
00796         {
00797           nextNode->GetParentNode(getter_AddRefs(parent));
00798           res = GetChildOffset(nextNode, parent, offsetInParent);
00799           if (NS_SUCCEEDED(res)) {
00800             // SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
00801             // We want the caret to stick to whatever is past the break.  This is
00802             // because the break is on the same line we were on, but the next content
00803             // will be on the following line.
00804             nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00805             selPriv->SetInterlinePosition(PR_TRUE);
00806             res = selection->Collapse(parent, offsetInParent+1);  // +1 to insert just after the break
00807           }
00808         }
00809         else
00810         {
00811           res = selection->Collapse(nextNode, offsetInParent);
00812         }
00813       }
00814     }
00815   }
00816   if (!cancel)
00817   {
00818     // post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
00819     res = mRules->DidDoAction(selection, &ruleInfo, res);
00820   }
00821 
00822   return res;
00823 }
00824 
00825 NS_IMETHODIMP
00826 nsPlaintextEditor::BeginComposition(nsTextEventReply* aReply)
00827 {
00828   NS_ENSURE_TRUE(!mInIMEMode, NS_OK);
00829 
00830   if(mFlags & nsIPlaintextEditor::eEditorPasswordMask)  {
00831     if (mRules) {
00832       nsIEditRules *p = mRules.get();
00833       nsTextEditRules *textEditRules = NS_STATIC_CAST(nsTextEditRules *, p);
00834       textEditRules->ResetIMETextPWBuf();
00835     }
00836     else  {
00837       return NS_ERROR_NULL_POINTER;
00838     }
00839   }
00840 
00841   return nsEditor::BeginComposition(aReply);
00842 }
00843 
00844 NS_IMETHODIMP
00845 nsPlaintextEditor::GetDocumentIsEmpty(PRBool *aDocumentIsEmpty)
00846 {
00847   if (!aDocumentIsEmpty)
00848     return NS_ERROR_NULL_POINTER;
00849   
00850   if (!mRules)
00851     return NS_ERROR_NOT_INITIALIZED;
00852   
00853   return mRules->DocumentIsEmpty(aDocumentIsEmpty);
00854 }
00855 
00856 NS_IMETHODIMP
00857 nsPlaintextEditor::GetTextLength(PRInt32 *aCount)
00858 {
00859   NS_ASSERTION(aCount, "null pointer");
00860 
00861   // initialize out params
00862   *aCount = 0;
00863   
00864   // special-case for empty document, to account for the bogus node
00865   PRBool docEmpty;
00866   nsresult rv = GetDocumentIsEmpty(&docEmpty);
00867   NS_ENSURE_SUCCESS(rv, rv);
00868   if (docEmpty)
00869     return NS_OK;
00870 
00871   nsIDOMElement* rootNode = GetRoot();
00872   NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER);
00873 
00874   nsCOMPtr<nsIContentIterator> iter =
00875     do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
00876   NS_ENSURE_SUCCESS(rv, rv);
00877 
00878   PRUint32 totalLength = 0;
00879   nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootNode);
00880   iter->Init(rootContent);
00881   for (; !iter->IsDone(); iter->Next()) {
00882     nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(iter->GetCurrentNode());
00883     nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(currentNode);
00884     if (textNode && IsEditable(currentNode)) {
00885       PRUint32 length;
00886       textNode->GetLength(&length);
00887       totalLength += length;
00888     }
00889   }
00890 
00891   *aCount = totalLength;
00892   return NS_OK;
00893 }
00894 
00895 NS_IMETHODIMP
00896 nsPlaintextEditor::SetMaxTextLength(PRInt32 aMaxTextLength)
00897 {
00898   mMaxTextLength = aMaxTextLength;
00899   return NS_OK;
00900 }
00901 
00902 NS_IMETHODIMP
00903 nsPlaintextEditor::GetMaxTextLength(PRInt32* aMaxTextLength)
00904 {
00905   if (!aMaxTextLength)
00906     return NS_ERROR_INVALID_POINTER;
00907   *aMaxTextLength = mMaxTextLength;
00908   return NS_OK;
00909 }
00910 
00911 //
00912 // Get the wrap width
00913 //
00914 NS_IMETHODIMP 
00915 nsPlaintextEditor::GetWrapWidth(PRInt32 *aWrapColumn)
00916 {
00917   if (! aWrapColumn)
00918     return NS_ERROR_NULL_POINTER;
00919 
00920   *aWrapColumn = mWrapColumn;
00921   return NS_OK;
00922 }
00923 
00924 //
00925 // See if the style value includes this attribute, and if it does,
00926 // cut out everything from the attribute to the next semicolon.
00927 //
00928 static void CutStyle(const char* stylename, nsString& styleValue)
00929 {
00930   // Find the current wrapping type:
00931   PRInt32 styleStart = styleValue.Find(stylename, PR_TRUE);
00932   if (styleStart >= 0)
00933   {
00934     PRInt32 styleEnd = styleValue.Find(";", PR_FALSE, styleStart);
00935     if (styleEnd > styleStart)
00936       styleValue.Cut(styleStart, styleEnd - styleStart + 1);
00937     else
00938       styleValue.Cut(styleStart, styleValue.Length() - styleStart);
00939   }
00940 }
00941 
00942 //
00943 // Change the wrap width on the root of this document.
00944 // 
00945 NS_IMETHODIMP 
00946 nsPlaintextEditor::SetWrapWidth(PRInt32 aWrapColumn)
00947 {
00948   mWrapColumn = aWrapColumn;
00949 
00950   // Make sure we're a plaintext editor, otherwise we shouldn't
00951   // do the rest of this.
00952   PRUint32 flags = 0;
00953   GetFlags(&flags);
00954   if (!(flags & eEditorPlaintextMask))
00955     return NS_OK;
00956 
00957   // Ought to set a style sheet here ...
00958   // Probably should keep around an mPlaintextStyleSheet for this purpose.
00959   nsIDOMElement *rootElement = GetRoot();
00960   if (!rootElement)
00961     return NS_ERROR_NULL_POINTER;
00962 
00963   // Get the current style for this root element:
00964   NS_NAMED_LITERAL_STRING(styleName, "style");
00965   nsAutoString styleValue;
00966   nsresult res = rootElement->GetAttribute(styleName, styleValue);
00967   if (NS_FAILED(res)) return res;
00968 
00969   // We'll replace styles for these values:
00970   CutStyle("white-space", styleValue);
00971   CutStyle("width", styleValue);
00972   CutStyle("font-family", styleValue);
00973 
00974   // If we have other style left, trim off any existing semicolons
00975   // or whitespace, then add a known semicolon-space:
00976   if (!styleValue.IsEmpty())
00977   {
00978     styleValue.Trim("; \t", PR_FALSE, PR_TRUE);
00979     styleValue.AppendLiteral("; ");
00980   }
00981 
00982   // Make sure we have fixed-width font.  This should be done for us,
00983   // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
00984   // Only do this if we're wrapping.
00985   if ((flags & eEditorEnableWrapHackMask) && aWrapColumn >= 0)
00986     styleValue.AppendLiteral("font-family: -moz-fixed; ");
00987 
00988   // If "mail.compose.wrap_to_window_width" is set, and we're a mail editor,
00989   // then remember our wrap width (for output purposes) but set the visual
00990   // wrapping to window width.
00991   // We may reset mWrapToWindow here, based on the pref's current value.
00992   if (flags & eEditorMailMask)
00993   {
00994     nsresult rv;
00995     nsCOMPtr<nsIPrefBranch> prefBranch =
00996       do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
00997     if (NS_SUCCEEDED(rv))
00998       prefBranch->GetBoolPref("mail.compose.wrap_to_window_width",
00999                               &mWrapToWindow);
01000   }
01001 
01002   // and now we're ready to set the new whitespace/wrapping style.
01003   if (aWrapColumn > 0 && !mWrapToWindow)        // Wrap to a fixed column
01004   {
01005     styleValue.AppendLiteral("white-space: -moz-pre-wrap; width: ");
01006     styleValue.AppendInt(aWrapColumn);
01007     styleValue.AppendLiteral("ch;");
01008   }
01009   else if (mWrapToWindow || aWrapColumn == 0)
01010     styleValue.AppendLiteral("white-space: -moz-pre-wrap;");
01011   else
01012     styleValue.AppendLiteral("white-space: pre;");
01013 
01014   return rootElement->SetAttribute(styleName, styleValue);
01015 }
01016 
01017 
01018 
01019 #ifdef XP_MAC
01020 #pragma mark -
01021 #pragma mark  nsIEditor overrides 
01022 #pragma mark -
01023 #endif
01024 
01025 NS_IMETHODIMP 
01026 nsPlaintextEditor::Undo(PRUint32 aCount)
01027 {
01028   nsAutoUpdateViewBatch beginViewBatching(this);
01029 
01030   ForceCompositionEnd();
01031 
01032   nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
01033 
01034   nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
01035   nsCOMPtr<nsISelection> selection;
01036   GetSelection(getter_AddRefs(selection));
01037   PRBool cancel, handled;
01038   nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
01039   
01040   if (!cancel && NS_SUCCEEDED(result))
01041   {
01042     result = nsEditor::Undo(aCount);
01043     result = mRules->DidDoAction(selection, &ruleInfo, result);
01044   } 
01045    
01046   return result;
01047 }
01048 
01049 NS_IMETHODIMP 
01050 nsPlaintextEditor::Redo(PRUint32 aCount)
01051 {
01052   nsAutoUpdateViewBatch beginViewBatching(this);
01053 
01054   ForceCompositionEnd();
01055 
01056   nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
01057 
01058   nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
01059   nsCOMPtr<nsISelection> selection;
01060   GetSelection(getter_AddRefs(selection));
01061   PRBool cancel, handled;
01062   nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
01063   
01064   if (!cancel && NS_SUCCEEDED(result))
01065   {
01066     result = nsEditor::Redo(aCount);
01067     result = mRules->DidDoAction(selection, &ruleInfo, result);
01068   } 
01069    
01070   return result;
01071 }
01072 
01073 NS_IMETHODIMP nsPlaintextEditor::Cut()
01074 {
01075   nsCOMPtr<nsISelection> selection;
01076   nsresult res = GetSelection(getter_AddRefs(selection));
01077   if (NS_FAILED(res))
01078     return res;
01079 
01080   PRBool isCollapsed;
01081   if (NS_SUCCEEDED(selection->GetIsCollapsed(&isCollapsed)) && isCollapsed)
01082     return NS_OK;  // just return ok so no JS error is thrown
01083 
01084   res = Copy();
01085   if (NS_SUCCEEDED(res))
01086     res = DeleteSelection(eNone);
01087   return res;
01088 }
01089 
01090 NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool *aCanCut)
01091 {
01092   nsresult res = CanCopy(aCanCut);
01093   if (NS_FAILED(res)) return res;
01094     
01095   *aCanCut = *aCanCut && IsModifiable();
01096   return NS_OK;
01097 }
01098 
01099 NS_IMETHODIMP nsPlaintextEditor::Copy()
01100 {
01101   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
01102   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
01103   if (!ps) return NS_ERROR_NOT_INITIALIZED;
01104   return ps->DoCopy();
01105 }
01106 
01107 NS_IMETHODIMP nsPlaintextEditor::CanCopy(PRBool *aCanCopy)
01108 {
01109   if (!aCanCopy)
01110     return NS_ERROR_NULL_POINTER;
01111   *aCanCopy = PR_FALSE;
01112   
01113   nsCOMPtr<nsISelection> selection;
01114   nsresult res = GetSelection(getter_AddRefs(selection));
01115   if (NS_FAILED(res)) return res;
01116     
01117   PRBool isCollapsed;
01118   res = selection->GetIsCollapsed(&isCollapsed);
01119   if (NS_FAILED(res)) return res;
01120 
01121   *aCanCopy = !isCollapsed;
01122   return NS_OK;
01123 }
01124 
01125 
01126 // Shared between OutputToString and OutputToStream
01127 NS_IMETHODIMP
01128 nsPlaintextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,
01129                                         PRUint32 aFlags,
01130                                         const nsACString& aCharset,
01131                                         nsIDocumentEncoder** encoder)
01132 {
01133   nsCOMPtr<nsIPresShell> presShell;
01134   nsresult rv = GetPresShell(getter_AddRefs(presShell));
01135   if (NS_FAILED(rv)) return rv;
01136   if (!presShell) return NS_ERROR_FAILURE;
01137 
01138   nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
01139   formatType.AppendWithConversion(aFormatType);
01140   nsCOMPtr<nsIDocumentEncoder> docEncoder (do_CreateInstance(formatType.get(), &rv));
01141   NS_ENSURE_SUCCESS(rv, rv);
01142 
01143   nsIDocument *doc = presShell->GetDocument();
01144   rv = docEncoder->Init(doc, aFormatType, aFlags);
01145   NS_ENSURE_SUCCESS(rv, rv);
01146 
01147   if (!aCharset.IsEmpty()
01148     && !(aCharset.EqualsLiteral("null")))
01149     docEncoder->SetCharset(aCharset);
01150 
01151   PRInt32 wc;
01152   (void) GetWrapWidth(&wc);
01153   if (wc >= 0)
01154     (void) docEncoder->SetWrapColumn(wc);
01155 
01156   // Set the selection, if appropriate.
01157   // We do this either if the OutputSelectionOnly flag is set,
01158   // in which case we use our existing selection ...
01159   if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
01160   {
01161     nsCOMPtr<nsISelection> selection;
01162     rv = GetSelection(getter_AddRefs(selection));
01163     if (NS_SUCCEEDED(rv) && selection)
01164       rv = docEncoder->SetSelection(selection);
01165     NS_ENSURE_SUCCESS(rv, rv);
01166   }
01167   // ... or if the root element is not a body,
01168   // in which case we set the selection to encompass the root.
01169   else
01170   {
01171     nsIDOMElement *rootElement = GetRoot();
01172     NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
01173     if (!nsTextEditUtils::IsBody(rootElement))
01174     {
01175       nsCOMPtr<nsIDOMRange> range (do_CreateInstance("@mozilla.org/content/range;1", &rv));
01176       NS_ENSURE_SUCCESS(rv, rv);
01177 
01178       rv = range->SelectNodeContents(rootElement);
01179       NS_ENSURE_SUCCESS(rv, rv);
01180 
01181       rv = docEncoder->SetRange(range);
01182       NS_ENSURE_SUCCESS(rv, rv);
01183     }
01184   }
01185 
01186   NS_ADDREF(*encoder = docEncoder);
01187   return rv;
01188 }
01189 
01190 
01191 NS_IMETHODIMP 
01192 nsPlaintextEditor::OutputToString(const nsAString& aFormatType,
01193                                   PRUint32 aFlags,
01194                                   nsAString& aOutputString)
01195 {
01196   nsString resultString;
01197   nsTextRulesInfo ruleInfo(nsTextEditRules::kOutputText);
01198   ruleInfo.outString = &resultString;
01199   // XXX Struct should store a nsAReadable*
01200   nsAutoString str(aFormatType);
01201   ruleInfo.outputFormat = &str;
01202   PRBool cancel, handled;
01203   nsresult rv = mRules->WillDoAction(nsnull, &ruleInfo, &cancel, &handled);
01204   if (cancel || NS_FAILED(rv)) { return rv; }
01205   if (handled)
01206   { // this case will get triggered by password fields
01207     aOutputString.Assign(*(ruleInfo.outString));
01208     return rv;
01209   }
01210 
01211   nsCAutoString charsetStr;
01212   rv = GetDocumentCharacterSet(charsetStr);
01213   if(NS_FAILED(rv) || charsetStr.IsEmpty())
01214     charsetStr.AssignLiteral("ISO-8859-1");
01215 
01216   nsCOMPtr<nsIDocumentEncoder> encoder;
01217   rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder));
01218   if (NS_FAILED(rv))
01219     return rv;
01220   return encoder->EncodeToString(aOutputString);
01221 }
01222 
01223 NS_IMETHODIMP
01224 nsPlaintextEditor::OutputToStream(nsIOutputStream* aOutputStream,
01225                              const nsAString& aFormatType,
01226                              const nsACString& aCharset,
01227                              PRUint32 aFlags)
01228 {
01229   nsresult rv;
01230 
01231   // special-case for empty document when requesting plain text,
01232   // to account for the bogus text node.
01233   // XXX Should there be a similar test in OutputToString?
01234   if (aFormatType.EqualsLiteral("text/plain"))
01235   {
01236     PRBool docEmpty;
01237     rv = GetDocumentIsEmpty(&docEmpty);
01238     if (NS_FAILED(rv)) return rv;
01239     
01240     if (docEmpty)
01241        return NS_OK;    // output nothing
01242   }
01243 
01244   nsCOMPtr<nsIDocumentEncoder> encoder;
01245   rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset,
01246                             getter_AddRefs(encoder));
01247 
01248   if (NS_FAILED(rv))
01249     return rv;
01250 
01251   return encoder->EncodeToStream(aOutputStream);
01252 }
01253 
01254 
01255 #ifdef XP_MAC
01256 #pragma mark -
01257 #pragma mark  nsIEditorMailSupport overrides 
01258 #pragma mark -
01259 #endif
01260 
01261 NS_IMETHODIMP
01262 nsPlaintextEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
01263 {
01264   return InsertText(aStringToInsert);
01265 }
01266 
01267 NS_IMETHODIMP
01268 nsPlaintextEditor::PasteAsQuotation(PRInt32 aSelectionType)
01269 {
01270   // Get Clipboard Service
01271   nsresult rv;
01272   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
01273   if (NS_FAILED(rv)) return rv;
01274 
01275   // Create generic Transferable for getting the data
01276   nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
01277   if (NS_SUCCEEDED(rv) && trans)
01278   {
01279     // We only handle plaintext pastes here
01280     trans->AddDataFlavor(kUnicodeMime);
01281 
01282     // Get the Data from the clipboard
01283     clipboard->GetData(trans, aSelectionType);
01284 
01285     // Now we ask the transferable for the data
01286     // it still owns the data, we just have a pointer to it.
01287     // If it can't support a "text" output of the data the call will fail
01288     nsCOMPtr<nsISupports> genericDataObj;
01289     PRUint32 len;
01290     char* flav = nsnull;
01291     rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj),
01292                                    &len);
01293     if (NS_FAILED(rv) || !flav)
01294     {
01295 #ifdef DEBUG_akkana
01296       printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv);
01297 #endif
01298       return rv;
01299     }
01300 #ifdef DEBUG_clipboard
01301     printf("Got flavor [%s]\n", flav);
01302 #endif
01303     if (0 == nsCRT::strcmp(flav, kUnicodeMime))
01304     {
01305       nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
01306       if (textDataObj && len > 0)
01307       {
01308         nsAutoString stuffToPaste;
01309         textDataObj->GetData ( stuffToPaste );
01310         nsAutoEditBatch beginBatching(this);
01311         rv = InsertAsQuotation(stuffToPaste, 0);
01312       }
01313     }
01314     NS_Free(flav);
01315   }
01316 
01317   return rv;
01318 }
01319 
01320 // Utility routine to make a new citer.  This addrefs, of course.
01321 static nsICiter* MakeACiter()
01322 {
01323   // Make a citer of an appropriate type
01324   nsICiter* citer = 0;
01325   nsresult rv;
01326   nsCOMPtr<nsIPrefBranch> prefBranch =
01327     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
01328   if (NS_FAILED(rv)) return 0;
01329 
01330   char *citationType = 0;
01331   rv = prefBranch->GetCharPref("mail.compose.citationType", &citationType);
01332                           
01333   if (NS_SUCCEEDED(rv) && citationType[0] && !strncmp(citationType, "aol", 3))
01334     citer = new nsAOLCiter;
01335   else
01336     citer = new nsInternetCiter;
01337 
01338   if (citationType)
01339     PL_strfree(citationType);
01340 
01341   if (citer)
01342     NS_ADDREF(citer);
01343   return citer;
01344 }
01345 
01346 NS_IMETHODIMP
01347 nsPlaintextEditor::InsertAsQuotation(const nsAString& aQuotedText,
01348                                      nsIDOMNode **aNodeInserted)
01349 {
01350   // We have the text.  Cite it appropriately:
01351   nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
01352 
01353   // Let the citer quote it for us:
01354   nsString quotedStuff;
01355   nsresult rv = citer->GetCiteString(aQuotedText, quotedStuff);
01356   if (NS_FAILED(rv))
01357     return rv;
01358 
01359   // It's best to put a blank line after the quoted text so that mails
01360   // written without thinking won't be so ugly.
01361   if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != PRUnichar('\n')))
01362     quotedStuff.Append(PRUnichar('\n'));
01363 
01364   // get selection
01365   nsCOMPtr<nsISelection> selection;
01366   rv = GetSelection(getter_AddRefs(selection));
01367   if (NS_FAILED(rv)) return rv;
01368   if (!selection) return NS_ERROR_NULL_POINTER;
01369 
01370   nsAutoEditBatch beginBatching(this);
01371   nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
01372 
01373   // give rules a chance to handle or cancel
01374   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
01375   PRBool cancel, handled;
01376   rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
01377   if (NS_FAILED(rv)) return rv;
01378   if (cancel) return NS_OK; // rules canceled the operation
01379   if (!handled)
01380   {
01381     rv = InsertText(quotedStuff);
01382 
01383     // XXX Should set *aNodeInserted to the first node inserted
01384     if (aNodeInserted && NS_SUCCEEDED(rv))
01385     {
01386       *aNodeInserted = 0;
01387       //NS_IF_ADDREF(*aNodeInserted);
01388     }
01389   }
01390   return rv;
01391 }
01392 
01393 NS_IMETHODIMP
01394 nsPlaintextEditor::PasteAsCitedQuotation(const nsAString& aCitation,
01395                                          PRInt32 aSelectionType)
01396 {
01397   return NS_ERROR_NOT_IMPLEMENTED;
01398 }
01399 
01400 NS_IMETHODIMP
01401 nsPlaintextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
01402                                           const nsAString& aCitation,
01403                                           PRBool aInsertHTML,
01404                                           nsIDOMNode **aNodeInserted)
01405 {
01406   return InsertAsQuotation(aQuotedText, aNodeInserted);
01407 }
01408 
01409 nsresult
01410 nsPlaintextEditor::SharedOutputString(PRUint32 aFlags,
01411                                       PRBool* aIsCollapsed,
01412                                       nsAString& aResult)
01413 {
01414   nsCOMPtr<nsISelection> selection;
01415   nsresult rv = GetSelection(getter_AddRefs(selection));
01416   NS_ENSURE_SUCCESS(rv, rv);
01417   if (!selection)
01418     return NS_ERROR_NOT_INITIALIZED;
01419 
01420   rv = selection->GetIsCollapsed(aIsCollapsed);
01421   NS_ENSURE_SUCCESS(rv, rv);
01422 
01423   if (!*aIsCollapsed)
01424     aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
01425   // If the selection isn't collapsed, we'll use the whole document.
01426 
01427   return OutputToString(NS_LITERAL_STRING("text/plain"), aFlags, aResult);
01428 }
01429 
01430 NS_IMETHODIMP
01431 nsPlaintextEditor::Rewrap(PRBool aRespectNewlines)
01432 {
01433   PRInt32 wrapCol;
01434   nsresult rv = GetWrapWidth(&wrapCol);
01435   if (NS_FAILED(rv))
01436     return NS_OK;
01437 
01438   // Rewrap makes no sense if there's no wrap column; default to 72.
01439   if (wrapCol <= 0)
01440     wrapCol = 72;
01441 
01442 #ifdef DEBUG_akkana
01443   printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol);
01444 #endif
01445 
01446   nsAutoString current;
01447   PRBool isCollapsed;
01448   rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted
01449                           | nsIDocumentEncoder::OutputLFLineBreak,
01450                           &isCollapsed, current);
01451   if (NS_FAILED(rv)) return rv;
01452 
01453   nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
01454   if (NS_FAILED(rv)) return rv;
01455   if (!citer) return NS_ERROR_UNEXPECTED;
01456 
01457   nsString wrapped;
01458   PRUint32 firstLineOffset = 0;   // XXX need to reset this if there is a selection
01459   rv = citer->Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines,
01460                      wrapped);
01461   if (NS_FAILED(rv)) return rv;
01462 
01463   if (isCollapsed)    // rewrap the whole document
01464     SelectAll();
01465 
01466   return InsertTextWithQuotations(wrapped);
01467 }
01468 
01469 NS_IMETHODIMP    
01470 nsPlaintextEditor::StripCites()
01471 {
01472 #ifdef DEBUG_akkana
01473   printf("nsPlaintextEditor::StripCites()\n");
01474 #endif
01475 
01476   nsAutoString current;
01477   PRBool isCollapsed;
01478   nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted,
01479                                    &isCollapsed, current);
01480   if (NS_FAILED(rv)) return rv;
01481 
01482   nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
01483   if (!citer) return NS_ERROR_UNEXPECTED;
01484 
01485   nsString stripped;
01486   rv = citer->StripCites(current, stripped);
01487   if (NS_FAILED(rv)) return rv;
01488 
01489   if (isCollapsed)    // rewrap the whole document
01490   {
01491     rv = SelectAll();
01492     if (NS_FAILED(rv)) return rv;
01493   }
01494 
01495   return InsertText(stripped);
01496 }
01497 
01498 NS_IMETHODIMP
01499 nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
01500 {
01501   *aNodeList = 0;
01502   return NS_OK;
01503 }
01504 
01505 
01506 #ifdef XP_MAC
01507 #pragma mark -
01508 #pragma mark  nsIEditorIMESupport overrides 
01509 #pragma mark -
01510 #endif
01511 
01512 NS_IMETHODIMP
01513 nsPlaintextEditor::SetCompositionString(const nsAString& aCompositionString, nsIPrivateTextRangeList* aTextRangeList,nsTextEventReply* aReply)
01514 {
01515   if (!aTextRangeList && !aCompositionString.IsEmpty())
01516   {
01517     NS_ERROR("aTextRangeList is null but the composition string is not null");
01518     return NS_ERROR_NULL_POINTER;
01519   }
01520 
01521   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
01522   if (!ps) 
01523     return NS_ERROR_NOT_INITIALIZED;
01524 
01525   nsCOMPtr<nsISelection> selection;
01526   nsresult result = GetSelection(getter_AddRefs(selection));
01527   if (NS_FAILED(result)) return result;
01528 
01529   nsCOMPtr<nsICaret>  caretP;
01530   ps->GetCaret(getter_AddRefs(caretP));
01531 
01532   // We should return caret position if it is possible. Because this event
01533   // dispatcher always expects to be returned the correct caret position.
01534   // But in following cases, we don't need to process the composition string,
01535   // so, we only need to return the caret position.
01536 
01537   // aCompositionString.IsEmpty() && !mIMETextNode:
01538   //   Workaround for Windows IME bug 23558: We get every IME event twice.
01539   //   For escape keypress, this causes an empty string to be passed
01540   //   twice, which freaks out the editor.
01541 
01542   // aCompositionString.IsEmpty() && !aTextRangeList:
01543   //   Some Chinese IMEs for Linux are always composition string and text range
01544   //   list are empty when listing the Chinese characters. In this case,
01545   //   we don't need to process composition string too. See bug 271815.
01546 
01547   if (!aCompositionString.IsEmpty() || (mIMETextNode && aTextRangeList))
01548   {
01549     mIMETextRangeList = aTextRangeList;
01550 
01551     // XXX_kin: BEGIN HACK! HACK! HACK!
01552     // XXX_kin:
01553     // XXX_kin: This is lame! The IME stuff needs caret coordinates
01554     // XXX_kin: synchronously, but the editor could be using async
01555     // XXX_kin: updates (reflows and paints) for performance reasons.
01556     // XXX_kin: In order to give IME what it needs, we have to temporarily
01557     // XXX_kin: switch to sync updating during this call so that the
01558     // XXX_kin: nsAutoPlaceHolderBatch can force sync reflows, paints,
01559     // XXX_kin: and selection scrolling, so that we get back accurate
01560     // XXX_kin: caret coordinates.
01561 
01562     PRUint32 flags = 0;
01563     PRBool restoreFlags = PR_FALSE;
01564 
01565     if (NS_SUCCEEDED(GetFlags(&flags)) &&
01566         (flags & nsIPlaintextEditor::eEditorUseAsyncUpdatesMask))
01567     {
01568       if (NS_SUCCEEDED(SetFlags(
01569           flags & (~nsIPlaintextEditor::eEditorUseAsyncUpdatesMask))))
01570         restoreFlags = PR_TRUE;
01571     }
01572 
01573     // XXX_kin: END HACK! HACK! HACK!
01574 
01575     // we need the nsAutoPlaceHolderBatch destructor called before hitting
01576     // GetCaretCoordinates so the states in Frame system sync with content
01577     // therefore, we put the nsAutoPlaceHolderBatch into a inner block
01578     {
01579       nsAutoPlaceHolderBatch batch(this, gIMETxnName);
01580 
01581       SetIsIMEComposing(); // We set mIsIMEComposing properly.
01582 
01583       result = InsertText(aCompositionString);
01584 
01585       mIMEBufferLength = aCompositionString.Length();
01586 
01587       if (caretP)
01588         caretP->SetCaretDOMSelection(selection);
01589 
01590       // second part of 23558 fix:
01591       if (aCompositionString.IsEmpty())
01592         mIMETextNode = nsnull;
01593     }
01594 
01595     // XXX_kin: BEGIN HACK! HACK! HACK!
01596     // XXX_kin:
01597     // XXX_kin: Restore the previous set of flags!
01598 
01599     if (restoreFlags)
01600       SetFlags(flags);
01601 
01602     // XXX_kin: END HACK! HACK! HACK!
01603   }
01604 
01605   if (caretP)
01606   {
01607     result = caretP->GetCaretCoordinates(nsICaret::eIMECoordinates,
01608                                          selection,
01609                                          &(aReply->mCursorPosition),
01610                                          &(aReply->mCursorIsCollapsed),
01611                                          nsnull);
01612     NS_ASSERTION(NS_SUCCEEDED(result), "cannot get caret position");
01613   }
01614 
01615   return result;
01616 }
01617 
01618 NS_IMETHODIMP 
01619 nsPlaintextEditor::GetReconversionString(nsReconversionEventReply* aReply)
01620 {
01621   nsCOMPtr<nsISelection> selection;
01622   nsresult res = GetSelection(getter_AddRefs(selection));
01623   if (NS_FAILED(res)) return res;
01624   if (!selection) return NS_ERROR_FAILURE;
01625 
01626   // XXX get the first range in the selection.  Since it is
01627   // unclear what to do if reconversion happens with a 
01628   // multirange selection, we will ignore any additional ranges.
01629   
01630   nsCOMPtr<nsIDOMRange> range;
01631   res = selection->GetRangeAt(0, getter_AddRefs(range));
01632   if (NS_FAILED(res)) return res;
01633   if (!range) return NS_ERROR_FAILURE;
01634   
01635   nsAutoString textValue;
01636   res = range->ToString(textValue);
01637   if (NS_FAILED(res))
01638     return res;
01639   
01640   aReply->mReconversionString = (PRUnichar*) nsMemory::Clone(textValue.get(),
01641                                                                 (textValue.Length() + 1) * sizeof(PRUnichar));
01642   if (!aReply->mReconversionString)
01643     return NS_ERROR_OUT_OF_MEMORY;
01644 
01645   if (textValue.IsEmpty())
01646     return NS_OK;
01647 
01648   // delete the selection
01649   return DeleteSelection(eNone);
01650 }
01651 
01652 #ifdef XP_MAC
01653 #pragma mark -
01654 #pragma mark  nsEditor overrides 
01655 #pragma mark -
01656 #endif
01657 
01658 
01661 NS_IMETHODIMP
01662 nsPlaintextEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
01663 {
01664   nsEditor::StartOperation(opID, aDirection);  // will set mAction, mDirection
01665   if (mRules) return mRules->BeforeEdit(mAction, mDirection);
01666   return NS_OK;
01667 }
01668 
01669 
01672 NS_IMETHODIMP
01673 nsPlaintextEditor::EndOperation()
01674 {
01675   // post processing
01676   nsresult res = NS_OK;
01677   if (mRules) res = mRules->AfterEdit(mAction, mDirection);
01678   nsEditor::EndOperation();  // will clear mAction, mDirection
01679   return res;
01680 }  
01681 
01682 
01683 NS_IMETHODIMP 
01684 nsPlaintextEditor::SelectEntireDocument(nsISelection *aSelection)
01685 {
01686   if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
01687 
01688   // is doc empty?
01689   PRBool bDocIsEmpty;
01690   if (NS_SUCCEEDED(mRules->DocumentIsEmpty(&bDocIsEmpty)) && bDocIsEmpty)
01691   {
01692     // get root node
01693     nsIDOMElement *rootElement = GetRoot();
01694     if (!rootElement)
01695       return NS_ERROR_FAILURE;
01696 
01697     // if it's empty don't select entire doc - that would select the bogus node
01698     return aSelection->Collapse(rootElement, 0);
01699   }
01700 
01701   return nsEditor::SelectEntireDocument(aSelection);
01702 }
01703 
01704 
01705 
01706 #ifdef XP_MAC
01707 #pragma mark -
01708 #pragma mark  Random methods 
01709 #pragma mark -
01710 #endif
01711 
01712 
01713 NS_IMETHODIMP nsPlaintextEditor::GetLayoutObject(nsIDOMNode *aNode, nsISupports **aLayoutObject)
01714 {
01715   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
01716   if (!ps) return NS_ERROR_NOT_INITIALIZED;
01717 
01718   nsresult result = NS_ERROR_NULL_POINTER;
01719   if (aNode)
01720   { // get the content interface
01721     nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aNode) );
01722     if (nodeAsContent)
01723     { // get the frame from the content interface
01724       //Note: frames are not ref counted, so don't use an nsCOMPtr
01725       *aLayoutObject = nsnull;
01726       result = ps->GetLayoutObjectFor(nodeAsContent, aLayoutObject);
01727     }
01728   }
01729 
01730   return result;
01731 }
01732 
01733 #ifdef XP_MAC
01734 #pragma mark -
01735 #endif
01736 
01737 nsresult
01738 nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
01739                                             const nsAString & aAttribute,
01740                                             const nsAString & aValue,
01741                                             PRBool aSuppressTransaction)
01742 {
01743   return nsEditor::SetAttribute(aElement, aAttribute, aValue);
01744 }
01745 
01746 nsresult
01747 nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
01748                                                const nsAString & aAttribute,
01749                                                PRBool aSuppressTransaction)
01750 {
01751   return nsEditor::RemoveAttribute(aElement, aAttribute);
01752 }