Bug 574778 - Fix win widget's ConstrainPosition so that it supports full screen windo...
[mozilla-central.git] / editor / libeditor / text / nsPlaintextEditor.cpp
blob2578173058e117cc821af94120e72518b22efd28
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Daniel Glazman <glazman@netscape.com>
24 * Mats Palmgren <matspal@gmail.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 #include "nsPlaintextEditor.h"
42 #include "nsCaret.h"
43 #include "nsTextEditUtils.h"
44 #include "nsTextEditRules.h"
45 #include "nsIEditActionListener.h"
46 #include "nsIDOMNodeList.h"
47 #include "nsIDOMDocument.h"
48 #include "nsIDocument.h"
49 #include "nsIDOMEventTarget.h"
50 #include "nsIDOM3EventTarget.h"
51 #include "nsIDOMKeyEvent.h"
52 #include "nsIDOMMouseListener.h"
53 #include "nsISelection.h"
54 #include "nsISelectionPrivate.h"
55 #include "nsISelectionController.h"
56 #include "nsGUIEvent.h"
57 #include "nsIDOMEventGroup.h"
58 #include "nsCRT.h"
60 #include "nsIEnumerator.h"
61 #include "nsIContent.h"
62 #include "nsIContentIterator.h"
63 #include "nsIDOMRange.h"
64 #include "nsISupportsArray.h"
65 #include "nsIComponentManager.h"
66 #include "nsIServiceManager.h"
67 #include "nsIDocumentEncoder.h"
68 #include "nsIPresShell.h"
69 #include "nsISupportsPrimitives.h"
70 #include "nsReadableUtils.h"
72 // Misc
73 #include "nsEditorUtils.h" // nsAutoEditBatch, nsAutoRules
74 #include "nsIPrefBranch.h"
75 #include "nsIPrefService.h"
76 #include "nsUnicharUtils.h"
77 #include "nsContentCID.h"
78 #include "nsInternetCiter.h"
79 #include "nsEventDispatcher.h"
80 #include "nsGkAtoms.h"
81 #include "nsDebug.h"
83 // Drag & Drop, Clipboard
84 #include "nsIClipboard.h"
85 #include "nsITransferable.h"
86 #include "nsCopySupport.h"
88 #include "mozilla/FunctionTimer.h"
90 // prototype for rules creation shortcut
91 nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
93 nsPlaintextEditor::nsPlaintextEditor()
94 : nsEditor()
95 , mIgnoreSpuriousDragEvent(PR_FALSE)
96 , mRules(nsnull)
97 , mWrapToWindow(PR_FALSE)
98 , mWrapColumn(0)
99 , mMaxTextLength(-1)
100 , mInitTriggerCounter(0)
101 , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst)
102 #ifdef XP_WIN
103 , mCaretStyle(1)
104 #else
105 , mCaretStyle(0)
106 #endif
110 nsPlaintextEditor::~nsPlaintextEditor()
112 // remove the rules as an action listener. Else we get a bad ownership loop later on.
113 // it's ok if the rules aren't a listener; we ignore the error.
114 nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
115 RemoveEditActionListener(mListener);
117 // Remove event listeners. Note that if we had an HTML editor,
118 // it installed its own instead of these
119 RemoveEventListeners();
121 if (mRules)
122 mRules->DetachEditor();
125 NS_IMPL_CYCLE_COLLECTION_CLASS(nsPlaintextEditor)
127 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPlaintextEditor, nsEditor)
128 if (tmp->mRules)
129 tmp->mRules->DetachEditor();
130 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRules)
131 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
133 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPlaintextEditor, nsEditor)
134 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRules)
135 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
137 NS_IMPL_ADDREF_INHERITED(nsPlaintextEditor, nsEditor)
138 NS_IMPL_RELEASE_INHERITED(nsPlaintextEditor, nsEditor)
140 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsPlaintextEditor)
141 NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor)
142 NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
143 NS_INTERFACE_MAP_END_INHERITING(nsEditor)
146 NS_IMETHODIMP nsPlaintextEditor::Init(nsIDOMDocument *aDoc,
147 nsIPresShell *aPresShell, nsIContent *aRoot, nsISelectionController *aSelCon, PRUint32 aFlags)
149 NS_TIME_FUNCTION;
151 NS_PRECONDITION(aDoc && aPresShell, "bad arg");
152 NS_ENSURE_TRUE(aDoc && aPresShell, NS_ERROR_NULL_POINTER);
154 nsresult res = NS_OK, rulesRes = NS_OK;
156 if (1)
158 // block to scope nsAutoEditInitRulesTrigger
159 nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes);
161 // Init the base editor
162 res = nsEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
165 // check the "single line editor newline handling"
166 // and "caret behaviour in selection" prefs
167 GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle);
169 NS_ENSURE_SUCCESS(rulesRes, rulesRes);
170 return res;
173 static PRInt32 sNewlineHandlingPref = -1,
174 sCaretStylePref = -1;
176 static int
177 EditorPrefsChangedCallback(const char *aPrefName, void *)
179 if (nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines") == 0) {
180 sNewlineHandlingPref = nsContentUtils::GetIntPref("editor.singleLine.pasteNewlines",
181 nsIPlaintextEditor::eNewlinesPasteToFirst);
182 } else if (nsCRT::strcmp(aPrefName, "layout.selection.caret_style") == 0) {
183 sCaretStylePref = nsContentUtils::GetIntPref("layout.selection.caret_style",
184 #ifdef XP_WIN
186 if (sCaretStylePref == 0)
187 sCaretStylePref = 1;
188 #else
190 #endif
192 return 0;
195 // static
196 void
197 nsPlaintextEditor::GetDefaultEditorPrefs(PRInt32 &aNewlineHandling,
198 PRInt32 &aCaretStyle)
200 if (sNewlineHandlingPref == -1) {
201 nsContentUtils::RegisterPrefCallback("editor.singleLine.pasteNewlines",
202 EditorPrefsChangedCallback,
203 nsnull);
204 EditorPrefsChangedCallback("editor.singleLine.pasteNewlines", nsnull);
205 nsContentUtils::RegisterPrefCallback("layout.selection.caret_style",
206 EditorPrefsChangedCallback,
207 nsnull);
208 EditorPrefsChangedCallback("layout.selection.caret_style", nsnull);
211 aNewlineHandling = sNewlineHandlingPref;
212 aCaretStyle = sCaretStylePref;
215 void
216 nsPlaintextEditor::BeginEditorInit()
218 mInitTriggerCounter++;
221 nsresult
222 nsPlaintextEditor::EndEditorInit()
224 nsresult res = NS_OK;
225 NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?");
226 mInitTriggerCounter--;
227 if (mInitTriggerCounter == 0)
229 res = InitRules();
230 if (NS_SUCCEEDED(res))
231 EnableUndo(PR_TRUE);
233 return res;
236 NS_IMETHODIMP
237 nsPlaintextEditor::SetDocumentCharacterSet(const nsACString & characterSet)
239 nsresult result;
241 result = nsEditor::SetDocumentCharacterSet(characterSet);
243 // update META charset tag
244 if (NS_SUCCEEDED(result)) {
245 nsCOMPtr<nsIDOMDocument>domdoc;
246 result = GetDocument(getter_AddRefs(domdoc));
247 if (NS_SUCCEEDED(result) && domdoc) {
248 nsCOMPtr<nsIDOMNodeList>metaList;
249 nsCOMPtr<nsIDOMElement>metaElement;
250 PRBool newMetaCharset = PR_TRUE;
252 // get a list of META tags
253 result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("meta"), getter_AddRefs(metaList));
254 if (NS_SUCCEEDED(result) && metaList) {
255 PRUint32 listLength = 0;
256 (void) metaList->GetLength(&listLength);
258 nsCOMPtr<nsIDOMNode>metaNode;
259 for (PRUint32 i = 0; i < listLength; i++) {
260 metaList->Item(i, getter_AddRefs(metaNode));
261 if (!metaNode) continue;
262 metaElement = do_QueryInterface(metaNode);
263 if (!metaElement) continue;
265 nsAutoString currentValue;
266 if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("http-equiv"), currentValue))) continue;
268 if (FindInReadable(NS_LITERAL_STRING("content-type"),
269 currentValue,
270 nsCaseInsensitiveStringComparator())) {
271 NS_NAMED_LITERAL_STRING(content, "content");
272 if (NS_FAILED(metaElement->GetAttribute(content, currentValue))) continue;
274 NS_NAMED_LITERAL_STRING(charsetEquals, "charset=");
275 nsAString::const_iterator originalStart, start, end;
276 originalStart = currentValue.BeginReading(start);
277 currentValue.EndReading(end);
278 if (FindInReadable(charsetEquals, start, end,
279 nsCaseInsensitiveStringComparator())) {
281 // set attribute to <original prefix> charset=text/html
282 result = nsEditor::SetAttribute(metaElement, content,
283 Substring(originalStart, start) +
284 charsetEquals + NS_ConvertASCIItoUTF16(characterSet));
285 if (NS_SUCCEEDED(result))
286 newMetaCharset = PR_FALSE;
287 break;
293 if (newMetaCharset) {
294 nsCOMPtr<nsIDOMNodeList>headList;
295 result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"),getter_AddRefs(headList));
296 if (NS_SUCCEEDED(result) && headList) {
297 nsCOMPtr<nsIDOMNode>headNode;
298 headList->Item(0, getter_AddRefs(headNode));
299 if (headNode) {
300 nsCOMPtr<nsIDOMNode>resultNode;
301 // Create a new meta charset tag
302 result = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode));
303 NS_ENSURE_SUCCESS(result, NS_ERROR_FAILURE);
305 // Set attributes to the created element
306 if (resultNode && !characterSet.IsEmpty()) {
307 metaElement = do_QueryInterface(resultNode);
308 if (metaElement) {
309 // not undoable, undo should undo CreateNode
310 result = metaElement->SetAttribute(NS_LITERAL_STRING("http-equiv"), NS_LITERAL_STRING("Content-Type"));
311 if (NS_SUCCEEDED(result)) {
312 // not undoable, undo should undo CreateNode
313 result = metaElement->SetAttribute(NS_LITERAL_STRING("content"),
314 NS_LITERAL_STRING("text/html;charset=") + NS_ConvertASCIItoUTF16(characterSet));
324 return result;
328 NS_IMETHODIMP nsPlaintextEditor::InitRules()
330 // instantiate the rules for this text editor
331 nsresult res = NS_NewTextEditRules(getter_AddRefs(mRules));
332 NS_ENSURE_SUCCESS(res, res);
333 NS_ENSURE_TRUE(mRules, NS_ERROR_UNEXPECTED);
334 return mRules->Init(this);
338 NS_IMETHODIMP
339 nsPlaintextEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
341 NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
343 nsCOMPtr<nsIDOMDocument> doc;
344 GetDocument(getter_AddRefs(doc));
345 *aIsDocumentEditable = doc ? IsModifiable() : PR_FALSE;
347 return NS_OK;
350 PRBool nsPlaintextEditor::IsModifiable()
352 return !IsReadonly();
355 nsresult
356 nsPlaintextEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
358 // NOTE: When you change this method, you should also change:
359 // * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
360 // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
362 // And also when you add new key handling, you need to change the subclass's
363 // HandleKeyPressEvent()'s switch statement.
365 if (IsReadonly() || IsDisabled()) {
366 // When we're not editable, the events handled on nsEditor.
367 return nsEditor::HandleKeyPressEvent(aKeyEvent);
370 nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
371 NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
372 NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
373 "HandleKeyPressEvent gets non-keypress event");
375 switch (nativeKeyEvent->keyCode) {
376 case nsIDOMKeyEvent::DOM_VK_META:
377 case nsIDOMKeyEvent::DOM_VK_SHIFT:
378 case nsIDOMKeyEvent::DOM_VK_CONTROL:
379 case nsIDOMKeyEvent::DOM_VK_ALT:
380 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
381 case nsIDOMKeyEvent::DOM_VK_DELETE:
382 // These keys are handled on nsEditor
383 return nsEditor::HandleKeyPressEvent(aKeyEvent);
384 case nsIDOMKeyEvent::DOM_VK_TAB: {
385 if (IsTabbable()) {
386 return NS_OK; // let it be used for focus switching
389 if (nativeKeyEvent->isShift || nativeKeyEvent->isControl ||
390 nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
391 return NS_OK;
394 // else we insert the tab straight through
395 aKeyEvent->PreventDefault();
396 return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
398 case nsIDOMKeyEvent::DOM_VK_RETURN:
399 case nsIDOMKeyEvent::DOM_VK_ENTER:
400 if (IsSingleLineEditor() || nativeKeyEvent->isControl ||
401 nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
402 return NS_OK;
404 aKeyEvent->PreventDefault();
405 return TypedText(EmptyString(), eTypedBreak);
408 // NOTE: On some keyboard layout, some characters are inputted with Control
409 // key or Alt key, but at that time, widget sets FALSE to these keys.
410 if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->isControl ||
411 nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
412 // we don't PreventDefault() here or keybindings like control-x won't work
413 return NS_OK;
415 aKeyEvent->PreventDefault();
416 nsAutoString str(nativeKeyEvent->charCode);
417 return TypedText(str, eTypedText);
420 #ifdef XP_MAC
421 #pragma mark -
422 #pragma mark nsIHTMLEditor methods
423 #pragma mark -
424 #endif
426 /* This routine is needed to provide a bottleneck for typing for logging
427 purposes. Can't use HandleKeyPress() (above) for that since it takes
428 a nsIDOMKeyEvent* parameter. So instead we pass enough info through
429 to TypedText() to determine what action to take, but without passing
430 an event.
432 NS_IMETHODIMP nsPlaintextEditor::TypedText(const nsAString& aString,
433 PRInt32 aAction)
435 nsAutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName);
437 switch (aAction)
439 case eTypedText:
441 return InsertText(aString);
443 case eTypedBreak:
445 return InsertLineBreak();
448 return NS_ERROR_FAILURE;
451 NS_IMETHODIMP nsPlaintextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
453 NS_ENSURE_SUCCESS(aInOutParent && *aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER);
454 *outBRNode = nsnull;
455 nsresult res;
457 // we need to insert a br. unfortunately, we may have to split a text node to do it.
458 nsCOMPtr<nsIDOMNode> node = *aInOutParent;
459 PRInt32 theOffset = *aInOutOffset;
460 nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
461 NS_NAMED_LITERAL_STRING(brType, "br");
462 nsCOMPtr<nsIDOMNode> brNode;
463 if (nodeAsText)
465 nsCOMPtr<nsIDOMNode> tmp;
466 PRInt32 offset;
467 PRUint32 len;
468 nodeAsText->GetLength(&len);
469 GetNodeLocation(node, address_of(tmp), &offset);
470 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
471 if (!theOffset)
473 // we are already set to go
475 else if (theOffset == (PRInt32)len)
477 // update offset to point AFTER the text node
478 offset++;
480 else
482 // split the text node
483 res = SplitNode(node, theOffset, getter_AddRefs(tmp));
484 NS_ENSURE_SUCCESS(res, res);
485 res = GetNodeLocation(node, address_of(tmp), &offset);
486 NS_ENSURE_SUCCESS(res, res);
488 // create br
489 res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
490 NS_ENSURE_SUCCESS(res, res);
491 *aInOutParent = tmp;
492 *aInOutOffset = offset+1;
494 else
496 res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode));
497 NS_ENSURE_SUCCESS(res, res);
498 (*aInOutOffset)++;
501 *outBRNode = brNode;
502 if (*outBRNode && (aSelect != eNone))
504 nsCOMPtr<nsIDOMNode> parent;
505 PRInt32 offset;
506 res = GetNodeLocation(*outBRNode, address_of(parent), &offset);
507 NS_ENSURE_SUCCESS(res, res);
509 nsCOMPtr<nsISelection> selection;
510 res = GetSelection(getter_AddRefs(selection));
511 NS_ENSURE_SUCCESS(res, res);
512 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
513 if (aSelect == eNext)
515 // position selection after br
516 selPriv->SetInterlinePosition(PR_TRUE);
517 res = selection->Collapse(parent, offset+1);
519 else if (aSelect == ePrevious)
521 // position selection before br
522 selPriv->SetInterlinePosition(PR_TRUE);
523 res = selection->Collapse(parent, offset);
526 return NS_OK;
530 NS_IMETHODIMP nsPlaintextEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
532 nsCOMPtr<nsIDOMNode> parent = aNode;
533 PRInt32 offset = aOffset;
534 return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
537 NS_IMETHODIMP nsPlaintextEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
539 NS_ENSURE_TRUE(outBRNode, NS_ERROR_NULL_POINTER);
540 *outBRNode = nsnull;
542 // calling it text insertion to trigger moz br treatment by rules
543 nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
545 nsCOMPtr<nsISelection> selection;
546 nsresult res = GetSelection(getter_AddRefs(selection));
547 NS_ENSURE_SUCCESS(res, res);
548 PRBool bCollapsed;
549 res = selection->GetIsCollapsed(&bCollapsed);
550 NS_ENSURE_SUCCESS(res, res);
551 if (!bCollapsed)
553 res = DeleteSelection(nsIEditor::eNone);
554 NS_ENSURE_SUCCESS(res, res);
556 nsCOMPtr<nsIDOMNode> selNode;
557 PRInt32 selOffset;
558 res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
559 NS_ENSURE_SUCCESS(res, res);
561 res = CreateBR(selNode, selOffset, outBRNode);
562 NS_ENSURE_SUCCESS(res, res);
564 // position selection after br
565 res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
566 NS_ENSURE_SUCCESS(res, res);
567 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
568 selPriv->SetInterlinePosition(PR_TRUE);
569 return selection->Collapse(selNode, selOffset+1);
572 nsresult
573 nsPlaintextEditor::GetTextSelectionOffsets(nsISelection *aSelection,
574 PRUint32 &aOutStartOffset,
575 PRUint32 &aOutEndOffset)
577 NS_ASSERTION(aSelection, "null selection");
579 nsresult rv;
580 nsCOMPtr<nsIDOMNode> startNode, endNode;
581 PRInt32 startNodeOffset, endNodeOffset;
582 aSelection->GetAnchorNode(getter_AddRefs(startNode));
583 aSelection->GetAnchorOffset(&startNodeOffset);
584 aSelection->GetFocusNode(getter_AddRefs(endNode));
585 aSelection->GetFocusOffset(&endNodeOffset);
587 nsIDOMElement* rootNode = GetRoot();
588 NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER);
590 PRInt32 startOffset = -1;
591 PRInt32 endOffset = -1;
593 nsCOMPtr<nsIContentIterator> iter =
594 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
595 NS_ENSURE_SUCCESS(rv, rv);
597 #ifdef NS_DEBUG
598 PRInt32 nodeCount = 0; // only needed for the assertions below
599 #endif
600 PRUint32 totalLength = 0;
601 nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootNode);
602 iter->Init(rootContent);
603 for (; !iter->IsDone() && (startOffset == -1 || endOffset == -1); iter->Next()) {
604 nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(iter->GetCurrentNode());
605 nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(currentNode);
606 if (textNode) {
607 // Note that sometimes we have an empty #text-node as start/endNode,
608 // which we regard as not editable because the frame width == 0,
609 // see nsEditor::IsEditable().
610 PRBool editable = IsEditable(currentNode);
611 if (currentNode == startNode) {
612 startOffset = totalLength + (editable ? startNodeOffset : 0);
614 if (currentNode == endNode) {
615 endOffset = totalLength + (editable ? endNodeOffset : 0);
617 if (editable) {
618 PRUint32 length;
619 textNode->GetLength(&length);
620 totalLength += length;
623 #ifdef NS_DEBUG
624 ++nodeCount;
625 #endif
628 if (endOffset == -1) {
629 NS_ASSERTION(endNode == rootNode, "failed to find the end node");
630 NS_ASSERTION(endNodeOffset == nodeCount-1 || endNodeOffset == 0,
631 "invalid end node offset");
632 endOffset = endNodeOffset == 0 ? 0 : totalLength;
634 if (startOffset == -1) {
635 NS_ASSERTION(startNode == rootNode, "failed to find the start node");
636 NS_ASSERTION(startNodeOffset == nodeCount-1 || startNodeOffset == 0,
637 "invalid start node offset");
638 startOffset = startNodeOffset == 0 ? 0 : totalLength;
641 // Make sure aOutStartOffset <= aOutEndOffset.
642 if (startOffset <= endOffset) {
643 aOutStartOffset = startOffset;
644 aOutEndOffset = endOffset;
646 else {
647 aOutStartOffset = endOffset;
648 aOutEndOffset = startOffset;
651 return NS_OK;
654 nsresult
655 nsPlaintextEditor::ExtendSelectionForDelete(nsISelection *aSelection,
656 nsIEditor::EDirection *aAction)
658 nsresult result;
660 PRBool bCollapsed;
661 result = aSelection->GetIsCollapsed(&bCollapsed);
662 NS_ENSURE_SUCCESS(result, result);
664 if (*aAction == eNextWord || *aAction == ePreviousWord
665 || (*aAction == eNext && bCollapsed)
666 || (*aAction == ePrevious && bCollapsed)
667 || *aAction == eToBeginningOfLine || *aAction == eToEndOfLine)
669 nsCOMPtr<nsISelectionController> selCont (do_QueryReferent(mSelConWeak));
670 NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE);
672 switch (*aAction)
674 case eNextWord:
675 result = selCont->WordExtendForDelete(PR_TRUE);
676 // DeleteSelectionImpl doesn't handle these actions
677 // because it's inside batching, so don't confuse it:
678 *aAction = eNone;
679 break;
680 case ePreviousWord:
681 result = selCont->WordExtendForDelete(PR_FALSE);
682 *aAction = eNone;
683 break;
684 case eNext:
685 result = selCont->CharacterExtendForDelete();
686 // Don't set aAction to eNone (see Bug 502259)
687 break;
688 case ePrevious: {
689 // Only extend the selection where the selection is after a UTF-16
690 // surrogate pair. For other cases we don't want to do that, in order
691 // to make sure that pressing backspace will only delete the last
692 // typed character.
693 nsCOMPtr<nsIDOMNode> node;
694 PRInt32 offset;
695 result = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
696 NS_ENSURE_SUCCESS(result, result);
697 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
699 if (IsTextNode(node)) {
700 nsCOMPtr<nsIDOMCharacterData> charData = do_QueryInterface(node);
701 if (charData) {
702 nsAutoString data;
703 result = charData->GetData(data);
704 NS_ENSURE_SUCCESS(result, result);
706 if (offset > 1 &&
707 NS_IS_LOW_SURROGATE(data[offset - 1]) &&
708 NS_IS_HIGH_SURROGATE(data[offset - 2])) {
709 result = selCont->CharacterExtendForBackspace();
713 break;
715 case eToBeginningOfLine:
716 selCont->IntraLineMove(PR_TRUE, PR_FALSE); // try to move to end
717 result = selCont->IntraLineMove(PR_FALSE, PR_TRUE); // select to beginning
718 *aAction = eNone;
719 break;
720 case eToEndOfLine:
721 result = selCont->IntraLineMove(PR_TRUE, PR_TRUE);
722 *aAction = eNext;
723 break;
724 default: // avoid several compiler warnings
725 result = NS_OK;
726 break;
729 return result;
732 NS_IMETHODIMP nsPlaintextEditor::DeleteSelection(nsIEditor::EDirection aAction)
734 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
736 // Protect the edit rules object from dying
737 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
739 nsresult result;
741 // delete placeholder txns merge.
742 nsAutoPlaceHolderBatch batch(this, nsGkAtoms::DeleteTxnName);
743 nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
745 // pre-process
746 nsCOMPtr<nsISelection> selection;
747 result = GetSelection(getter_AddRefs(selection));
748 NS_ENSURE_SUCCESS(result, result);
749 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
751 // If there is an existing selection when an extended delete is requested,
752 // platforms that use "caret-style" caret positioning collapse the
753 // selection to the start and then create a new selection.
754 // Platforms that use "selection-style" caret positioning just delete the
755 // existing selection without extending it.
756 PRBool bCollapsed;
757 result = selection->GetIsCollapsed(&bCollapsed);
758 NS_ENSURE_SUCCESS(result, result);
759 if (!bCollapsed &&
760 (aAction == eNextWord || aAction == ePreviousWord ||
761 aAction == eToBeginningOfLine || aAction == eToEndOfLine))
763 if (mCaretStyle == 1)
765 result = selection->CollapseToStart();
766 NS_ENSURE_SUCCESS(result, result);
768 else
770 aAction = eNone;
774 nsTextRulesInfo ruleInfo(nsTextEditRules::kDeleteSelection);
775 ruleInfo.collapsedAction = aAction;
776 PRBool cancel, handled;
777 result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
778 NS_ENSURE_SUCCESS(result, result);
779 if (!cancel && !handled)
781 result = DeleteSelectionImpl(aAction);
783 if (!cancel)
785 // post-process
786 result = mRules->DidDoAction(selection, &ruleInfo, result);
789 return result;
792 NS_IMETHODIMP nsPlaintextEditor::InsertText(const nsAString &aStringToInsert)
794 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
796 // Protect the edit rules object from dying
797 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
799 PRInt32 theAction = nsTextEditRules::kInsertText;
800 PRInt32 opID = kOpInsertText;
801 if (mInIMEMode)
803 theAction = nsTextEditRules::kInsertTextIME;
804 opID = kOpInsertIMEText;
806 nsAutoPlaceHolderBatch batch(this, nsnull);
807 nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
809 // pre-process
810 nsCOMPtr<nsISelection> selection;
811 nsresult result = GetSelection(getter_AddRefs(selection));
812 NS_ENSURE_SUCCESS(result, result);
813 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
814 nsAutoString resultString;
815 // XXX can we trust instring to outlive ruleInfo,
816 // XXX and ruleInfo not to refer to instring in its dtor?
817 //nsAutoString instring(aStringToInsert);
818 nsTextRulesInfo ruleInfo(theAction);
819 ruleInfo.inString = &aStringToInsert;
820 ruleInfo.outString = &resultString;
821 ruleInfo.maxLength = mMaxTextLength;
823 PRBool cancel, handled;
824 result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
825 NS_ENSURE_SUCCESS(result, result);
826 if (!cancel && !handled)
828 // we rely on rules code for now - no default implementation
830 if (!cancel)
832 // post-process
833 result = mRules->DidDoAction(selection, &ruleInfo, result);
835 return result;
838 NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak()
840 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
842 // Protect the edit rules object from dying
843 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
845 nsAutoEditBatch beginBatching(this);
846 nsAutoRules beginRulesSniffing(this, kOpInsertBreak, nsIEditor::eNext);
848 // pre-process
849 nsCOMPtr<nsISelection> selection;
850 nsresult res;
851 res = GetSelection(getter_AddRefs(selection));
852 NS_ENSURE_SUCCESS(res, res);
853 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
855 // Batching the selection and moving nodes out from under the caret causes
856 // caret turds. Ask the shell to invalidate the caret now to avoid the turds.
857 nsCOMPtr<nsIPresShell> shell;
858 res = GetPresShell(getter_AddRefs(shell));
859 NS_ENSURE_SUCCESS(res, res);
860 shell->MaybeInvalidateCaretPosition();
862 nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
863 PRBool cancel, handled;
864 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
865 NS_ENSURE_SUCCESS(res, res);
866 if (!cancel && !handled)
868 // create the new BR node
869 nsCOMPtr<nsIDOMNode> newNode;
870 res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("br"), getter_AddRefs(newNode));
871 if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
872 if (NS_SUCCEEDED(res))
874 // set the selection to the new node
875 nsCOMPtr<nsIDOMNode>parent;
876 res = newNode->GetParentNode(getter_AddRefs(parent));
877 if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
878 if (NS_SUCCEEDED(res))
880 PRInt32 offsetInParent=-1; // we use the -1 as a marker to see if we need to compute this or not
881 nsCOMPtr<nsIDOMNode>nextNode;
882 newNode->GetNextSibling(getter_AddRefs(nextNode));
883 if (nextNode)
885 nsCOMPtr<nsIDOMCharacterData>nextTextNode = do_QueryInterface(nextNode);
886 if (!nextTextNode) {
887 nextNode = do_QueryInterface(newNode); // is this QI needed?
889 else {
890 offsetInParent=0;
893 else {
894 nextNode = do_QueryInterface(newNode); // is this QI needed?
897 if (-1==offsetInParent)
899 nextNode->GetParentNode(getter_AddRefs(parent));
900 res = GetChildOffset(nextNode, parent, offsetInParent);
901 if (NS_SUCCEEDED(res)) {
902 // SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
903 // We want the caret to stick to whatever is past the break. This is
904 // because the break is on the same line we were on, but the next content
905 // will be on the following line.
906 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
907 selPriv->SetInterlinePosition(PR_TRUE);
908 res = selection->Collapse(parent, offsetInParent+1); // +1 to insert just after the break
911 else
913 res = selection->Collapse(nextNode, offsetInParent);
918 if (!cancel)
920 // post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
921 res = mRules->DidDoAction(selection, &ruleInfo, res);
924 return res;
927 nsresult
928 nsPlaintextEditor::BeginIMEComposition()
930 NS_ENSURE_TRUE(!mInIMEMode, NS_OK);
932 if (IsPasswordEditor()) {
933 NS_ENSURE_TRUE(mRules, NS_ERROR_NULL_POINTER);
934 // Protect the edit rules object from dying
935 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
937 nsTextEditRules *textEditRules =
938 static_cast<nsTextEditRules*>(mRules.get());
939 textEditRules->ResetIMETextPWBuf();
942 return nsEditor::BeginIMEComposition();
945 nsresult
946 nsPlaintextEditor::UpdateIMEComposition(const nsAString& aCompositionString,
947 nsIPrivateTextRangeList* aTextRangeList)
949 if (!aTextRangeList && !aCompositionString.IsEmpty()) {
950 NS_ERROR("aTextRangeList is null but the composition string is not null");
951 return NS_ERROR_NULL_POINTER;
954 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
955 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
957 nsCOMPtr<nsISelection> selection;
958 nsresult rv = GetSelection(getter_AddRefs(selection));
959 NS_ENSURE_SUCCESS(rv, rv);
961 nsRefPtr<nsCaret> caretP = ps->GetCaret();
963 // We should return caret position if it is possible. Because this event
964 // dispatcher always expects to be returned the correct caret position.
965 // But in following cases, we don't need to process the composition string,
966 // so, we only need to return the caret position.
968 // aCompositionString.IsEmpty() && !mIMETextNode:
969 // Workaround for Windows IME bug 23558: We get every IME event twice.
970 // For escape keypress, this causes an empty string to be passed
971 // twice, which freaks out the editor.
973 // aCompositionString.IsEmpty() && !aTextRangeList:
974 // Some Chinese IMEs for Linux are always composition string and text range
975 // list are empty when listing the Chinese characters. In this case,
976 // we don't need to process composition string too. See bug 271815.
978 if (!aCompositionString.IsEmpty() || (mIMETextNode && aTextRangeList)) {
979 mIMETextRangeList = aTextRangeList;
981 SetIsIMEComposing(); // We set mIsIMEComposing properly.
983 rv = InsertText(aCompositionString);
985 mIMEBufferLength = aCompositionString.Length();
987 if (caretP) {
988 caretP->SetCaretDOMSelection(selection);
991 // second part of 23558 fix:
992 if (aCompositionString.IsEmpty()) {
993 mIMETextNode = nsnull;
997 return rv;
1000 NS_IMETHODIMP
1001 nsPlaintextEditor::GetDocumentIsEmpty(PRBool *aDocumentIsEmpty)
1003 NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER);
1005 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
1007 // Protect the edit rules object from dying
1008 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1010 return mRules->DocumentIsEmpty(aDocumentIsEmpty);
1013 NS_IMETHODIMP
1014 nsPlaintextEditor::GetTextLength(PRInt32 *aCount)
1016 NS_ASSERTION(aCount, "null pointer");
1018 // initialize out params
1019 *aCount = 0;
1021 // special-case for empty document, to account for the bogus node
1022 PRBool docEmpty;
1023 nsresult rv = GetDocumentIsEmpty(&docEmpty);
1024 NS_ENSURE_SUCCESS(rv, rv);
1025 if (docEmpty)
1026 return NS_OK;
1028 nsIDOMElement* rootNode = GetRoot();
1029 NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER);
1031 nsCOMPtr<nsIContentIterator> iter =
1032 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
1033 NS_ENSURE_SUCCESS(rv, rv);
1035 PRUint32 totalLength = 0;
1036 nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootNode);
1037 iter->Init(rootContent);
1038 for (; !iter->IsDone(); iter->Next()) {
1039 nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(iter->GetCurrentNode());
1040 nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(currentNode);
1041 if (textNode && IsEditable(currentNode)) {
1042 PRUint32 length;
1043 textNode->GetLength(&length);
1044 totalLength += length;
1048 *aCount = totalLength;
1049 return NS_OK;
1052 NS_IMETHODIMP
1053 nsPlaintextEditor::SetMaxTextLength(PRInt32 aMaxTextLength)
1055 mMaxTextLength = aMaxTextLength;
1056 return NS_OK;
1059 NS_IMETHODIMP
1060 nsPlaintextEditor::GetMaxTextLength(PRInt32* aMaxTextLength)
1062 NS_ENSURE_TRUE(aMaxTextLength, NS_ERROR_INVALID_POINTER);
1063 *aMaxTextLength = mMaxTextLength;
1064 return NS_OK;
1068 // Get the wrap width
1070 NS_IMETHODIMP
1071 nsPlaintextEditor::GetWrapWidth(PRInt32 *aWrapColumn)
1073 NS_ENSURE_TRUE( aWrapColumn, NS_ERROR_NULL_POINTER);
1075 *aWrapColumn = mWrapColumn;
1076 return NS_OK;
1080 // See if the style value includes this attribute, and if it does,
1081 // cut out everything from the attribute to the next semicolon.
1083 static void CutStyle(const char* stylename, nsString& styleValue)
1085 // Find the current wrapping type:
1086 PRInt32 styleStart = styleValue.Find(stylename, PR_TRUE);
1087 if (styleStart >= 0)
1089 PRInt32 styleEnd = styleValue.Find(";", PR_FALSE, styleStart);
1090 if (styleEnd > styleStart)
1091 styleValue.Cut(styleStart, styleEnd - styleStart + 1);
1092 else
1093 styleValue.Cut(styleStart, styleValue.Length() - styleStart);
1098 // Change the wrap width on the root of this document.
1100 NS_IMETHODIMP
1101 nsPlaintextEditor::SetWrapWidth(PRInt32 aWrapColumn)
1103 SetWrapColumn(aWrapColumn);
1105 // Make sure we're a plaintext editor, otherwise we shouldn't
1106 // do the rest of this.
1107 if (!IsPlaintextEditor())
1108 return NS_OK;
1110 // Ought to set a style sheet here ...
1111 // Probably should keep around an mPlaintextStyleSheet for this purpose.
1112 nsIDOMElement *rootElement = GetRoot();
1113 NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
1115 // Get the current style for this root element:
1116 NS_NAMED_LITERAL_STRING(styleName, "style");
1117 nsAutoString styleValue;
1118 nsresult res = rootElement->GetAttribute(styleName, styleValue);
1119 NS_ENSURE_SUCCESS(res, res);
1121 // We'll replace styles for these values:
1122 CutStyle("white-space", styleValue);
1123 CutStyle("width", styleValue);
1124 CutStyle("font-family", styleValue);
1126 // If we have other style left, trim off any existing semicolons
1127 // or whitespace, then add a known semicolon-space:
1128 if (!styleValue.IsEmpty())
1130 styleValue.Trim("; \t", PR_FALSE, PR_TRUE);
1131 styleValue.AppendLiteral("; ");
1134 // Make sure we have fixed-width font. This should be done for us,
1135 // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
1136 // Only do this if we're wrapping.
1137 if (IsWrapHackEnabled() && aWrapColumn >= 0)
1138 styleValue.AppendLiteral("font-family: -moz-fixed; ");
1140 // If "mail.compose.wrap_to_window_width" is set, and we're a mail editor,
1141 // then remember our wrap width (for output purposes) but set the visual
1142 // wrapping to window width.
1143 // We may reset mWrapToWindow here, based on the pref's current value.
1144 if (IsMailEditor())
1146 nsresult rv;
1147 nsCOMPtr<nsIPrefBranch> prefBranch =
1148 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1149 if (NS_SUCCEEDED(rv))
1150 prefBranch->GetBoolPref("mail.compose.wrap_to_window_width",
1151 &mWrapToWindow);
1154 // and now we're ready to set the new whitespace/wrapping style.
1155 if (aWrapColumn > 0 && !mWrapToWindow) // Wrap to a fixed column
1157 styleValue.AppendLiteral("white-space: pre-wrap; width: ");
1158 styleValue.AppendInt(aWrapColumn);
1159 styleValue.AppendLiteral("ch;");
1161 else if (mWrapToWindow || aWrapColumn == 0)
1162 styleValue.AppendLiteral("white-space: pre-wrap;");
1163 else
1164 styleValue.AppendLiteral("white-space: pre;");
1166 return rootElement->SetAttribute(styleName, styleValue);
1169 NS_IMETHODIMP
1170 nsPlaintextEditor::SetWrapColumn(PRInt32 aWrapColumn)
1172 mWrapColumn = aWrapColumn;
1173 return NS_OK;
1177 // Get the newline handling for this editor
1179 NS_IMETHODIMP
1180 nsPlaintextEditor::GetNewlineHandling(PRInt32 *aNewlineHandling)
1182 NS_ENSURE_ARG_POINTER(aNewlineHandling);
1184 *aNewlineHandling = mNewlineHandling;
1185 return NS_OK;
1189 // Change the newline handling for this editor
1191 NS_IMETHODIMP
1192 nsPlaintextEditor::SetNewlineHandling(PRInt32 aNewlineHandling)
1194 mNewlineHandling = aNewlineHandling;
1196 return NS_OK;
1199 #ifdef XP_MAC
1200 #pragma mark -
1201 #pragma mark nsIEditor overrides
1202 #pragma mark -
1203 #endif
1205 NS_IMETHODIMP
1206 nsPlaintextEditor::Undo(PRUint32 aCount)
1208 // Protect the edit rules object from dying
1209 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1211 nsAutoUpdateViewBatch beginViewBatching(this);
1213 ForceCompositionEnd();
1215 nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
1217 nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
1218 nsCOMPtr<nsISelection> selection;
1219 GetSelection(getter_AddRefs(selection));
1220 PRBool cancel, handled;
1221 nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1223 if (!cancel && NS_SUCCEEDED(result))
1225 result = nsEditor::Undo(aCount);
1226 result = mRules->DidDoAction(selection, &ruleInfo, result);
1229 return result;
1232 NS_IMETHODIMP
1233 nsPlaintextEditor::Redo(PRUint32 aCount)
1235 // Protect the edit rules object from dying
1236 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1238 nsAutoUpdateViewBatch beginViewBatching(this);
1240 ForceCompositionEnd();
1242 nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
1244 nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
1245 nsCOMPtr<nsISelection> selection;
1246 GetSelection(getter_AddRefs(selection));
1247 PRBool cancel, handled;
1248 nsresult result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1250 if (!cancel && NS_SUCCEEDED(result))
1252 result = nsEditor::Redo(aCount);
1253 result = mRules->DidDoAction(selection, &ruleInfo, result);
1256 return result;
1259 PRBool
1260 nsPlaintextEditor::CanCutOrCopy()
1262 nsCOMPtr<nsISelection> selection;
1263 if (NS_FAILED(GetSelection(getter_AddRefs(selection))))
1264 return PR_FALSE;
1266 PRBool isCollapsed;
1267 selection->GetIsCollapsed(&isCollapsed);
1268 return !isCollapsed;
1271 PRBool
1272 nsPlaintextEditor::FireClipboardEvent(PRInt32 aType)
1274 if (aType == NS_PASTE)
1275 ForceCompositionEnd();
1277 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShellWeak);
1278 NS_ENSURE_TRUE(presShell, PR_FALSE);
1280 nsCOMPtr<nsISelection> selection;
1281 if (NS_FAILED(GetSelection(getter_AddRefs(selection))))
1282 return PR_FALSE;
1284 if (!nsCopySupport::FireClipboardEvent(aType, presShell, selection))
1285 return PR_FALSE;
1287 // If the event handler caused the editor to be destroyed, return false.
1288 // Otherwise return true to indicate that the event was not cancelled.
1289 return !mDidPreDestroy;
1292 NS_IMETHODIMP nsPlaintextEditor::Cut()
1294 if (FireClipboardEvent(NS_CUT))
1295 return DeleteSelection(eNone);
1296 return NS_OK;
1299 NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool *aCanCut)
1301 NS_ENSURE_ARG_POINTER(aCanCut);
1302 *aCanCut = IsModifiable() && CanCutOrCopy();
1303 return NS_OK;
1306 NS_IMETHODIMP nsPlaintextEditor::Copy()
1308 FireClipboardEvent(NS_COPY);
1309 return NS_OK;
1312 NS_IMETHODIMP nsPlaintextEditor::CanCopy(PRBool *aCanCopy)
1314 NS_ENSURE_ARG_POINTER(aCanCopy);
1315 *aCanCopy = CanCutOrCopy();
1316 return NS_OK;
1319 // Shared between OutputToString and OutputToStream
1320 NS_IMETHODIMP
1321 nsPlaintextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,
1322 PRUint32 aFlags,
1323 const nsACString& aCharset,
1324 nsIDocumentEncoder** encoder)
1326 nsCOMPtr<nsIPresShell> presShell;
1327 nsresult rv = GetPresShell(getter_AddRefs(presShell));
1328 NS_ENSURE_SUCCESS(rv, rv);
1329 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
1331 nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
1332 formatType.AppendWithConversion(aFormatType);
1333 nsCOMPtr<nsIDocumentEncoder> docEncoder (do_CreateInstance(formatType.get(), &rv));
1334 NS_ENSURE_SUCCESS(rv, rv);
1336 nsIDocument *doc = presShell->GetDocument();
1337 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
1338 NS_ASSERTION(domDoc, "Need a document");
1340 rv = docEncoder->Init(domDoc, aFormatType, aFlags);
1341 NS_ENSURE_SUCCESS(rv, rv);
1343 if (!aCharset.IsEmpty()
1344 && !(aCharset.EqualsLiteral("null")))
1345 docEncoder->SetCharset(aCharset);
1347 PRInt32 wc;
1348 (void) GetWrapWidth(&wc);
1349 if (wc >= 0)
1350 (void) docEncoder->SetWrapColumn(wc);
1352 // Set the selection, if appropriate.
1353 // We do this either if the OutputSelectionOnly flag is set,
1354 // in which case we use our existing selection ...
1355 if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
1357 nsCOMPtr<nsISelection> selection;
1358 rv = GetSelection(getter_AddRefs(selection));
1359 if (NS_SUCCEEDED(rv) && selection)
1360 rv = docEncoder->SetSelection(selection);
1361 NS_ENSURE_SUCCESS(rv, rv);
1363 // ... or if the root element is not a body,
1364 // in which case we set the selection to encompass the root.
1365 else
1367 nsIDOMElement *rootElement = GetRoot();
1368 NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
1369 if (!nsTextEditUtils::IsBody(rootElement))
1371 rv = docEncoder->SetContainerNode(rootElement);
1372 NS_ENSURE_SUCCESS(rv, rv);
1376 NS_ADDREF(*encoder = docEncoder);
1377 return rv;
1381 NS_IMETHODIMP
1382 nsPlaintextEditor::OutputToString(const nsAString& aFormatType,
1383 PRUint32 aFlags,
1384 nsAString& aOutputString)
1386 // Protect the edit rules object from dying
1387 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1389 nsString resultString;
1390 nsTextRulesInfo ruleInfo(nsTextEditRules::kOutputText);
1391 ruleInfo.outString = &resultString;
1392 // XXX Struct should store a nsAReadable*
1393 nsAutoString str(aFormatType);
1394 ruleInfo.outputFormat = &str;
1395 PRBool cancel, handled;
1396 nsresult rv = mRules->WillDoAction(nsnull, &ruleInfo, &cancel, &handled);
1397 if (cancel || NS_FAILED(rv)) { return rv; }
1398 if (handled)
1399 { // this case will get triggered by password fields
1400 aOutputString.Assign(*(ruleInfo.outString));
1401 return rv;
1404 nsCAutoString charsetStr;
1405 rv = GetDocumentCharacterSet(charsetStr);
1406 if(NS_FAILED(rv) || charsetStr.IsEmpty())
1407 charsetStr.AssignLiteral("ISO-8859-1");
1409 nsCOMPtr<nsIDocumentEncoder> encoder;
1410 rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder));
1411 NS_ENSURE_SUCCESS(rv, rv);
1412 return encoder->EncodeToString(aOutputString);
1415 NS_IMETHODIMP
1416 nsPlaintextEditor::OutputToStream(nsIOutputStream* aOutputStream,
1417 const nsAString& aFormatType,
1418 const nsACString& aCharset,
1419 PRUint32 aFlags)
1421 nsresult rv;
1423 // special-case for empty document when requesting plain text,
1424 // to account for the bogus text node.
1425 // XXX Should there be a similar test in OutputToString?
1426 if (aFormatType.EqualsLiteral("text/plain"))
1428 PRBool docEmpty;
1429 rv = GetDocumentIsEmpty(&docEmpty);
1430 NS_ENSURE_SUCCESS(rv, rv);
1432 if (docEmpty)
1433 return NS_OK; // output nothing
1436 nsCOMPtr<nsIDocumentEncoder> encoder;
1437 rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset,
1438 getter_AddRefs(encoder));
1440 NS_ENSURE_SUCCESS(rv, rv);
1442 return encoder->EncodeToStream(aOutputStream);
1446 #ifdef XP_MAC
1447 #pragma mark -
1448 #pragma mark nsIEditorMailSupport overrides
1449 #pragma mark -
1450 #endif
1452 NS_IMETHODIMP
1453 nsPlaintextEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
1455 return InsertText(aStringToInsert);
1458 NS_IMETHODIMP
1459 nsPlaintextEditor::PasteAsQuotation(PRInt32 aSelectionType)
1461 // Get Clipboard Service
1462 nsresult rv;
1463 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1464 NS_ENSURE_SUCCESS(rv, rv);
1466 // Create generic Transferable for getting the data
1467 nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
1468 if (NS_SUCCEEDED(rv) && trans)
1470 // We only handle plaintext pastes here
1471 trans->AddDataFlavor(kUnicodeMime);
1473 // Get the Data from the clipboard
1474 clipboard->GetData(trans, aSelectionType);
1476 // Now we ask the transferable for the data
1477 // it still owns the data, we just have a pointer to it.
1478 // If it can't support a "text" output of the data the call will fail
1479 nsCOMPtr<nsISupports> genericDataObj;
1480 PRUint32 len;
1481 char* flav = nsnull;
1482 rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj),
1483 &len);
1484 if (NS_FAILED(rv) || !flav)
1486 #ifdef DEBUG_akkana
1487 printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv);
1488 #endif
1489 return rv;
1491 #ifdef DEBUG_clipboard
1492 printf("Got flavor [%s]\n", flav);
1493 #endif
1494 if (0 == nsCRT::strcmp(flav, kUnicodeMime))
1496 nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
1497 if (textDataObj && len > 0)
1499 nsAutoString stuffToPaste;
1500 textDataObj->GetData ( stuffToPaste );
1501 nsAutoEditBatch beginBatching(this);
1502 rv = InsertAsQuotation(stuffToPaste, 0);
1505 NS_Free(flav);
1508 return rv;
1511 NS_IMETHODIMP
1512 nsPlaintextEditor::InsertAsQuotation(const nsAString& aQuotedText,
1513 nsIDOMNode **aNodeInserted)
1515 // Protect the edit rules object from dying
1516 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1518 // We have the text. Cite it appropriately:
1519 nsCOMPtr<nsICiter> citer = new nsInternetCiter();
1521 // Let the citer quote it for us:
1522 nsString quotedStuff;
1523 nsresult rv = citer->GetCiteString(aQuotedText, quotedStuff);
1524 NS_ENSURE_SUCCESS(rv, rv);
1526 // It's best to put a blank line after the quoted text so that mails
1527 // written without thinking won't be so ugly.
1528 if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != PRUnichar('\n')))
1529 quotedStuff.Append(PRUnichar('\n'));
1531 // get selection
1532 nsCOMPtr<nsISelection> selection;
1533 rv = GetSelection(getter_AddRefs(selection));
1534 NS_ENSURE_SUCCESS(rv, rv);
1535 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1537 nsAutoEditBatch beginBatching(this);
1538 nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
1540 // give rules a chance to handle or cancel
1541 nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
1542 PRBool cancel, handled;
1543 rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1544 NS_ENSURE_SUCCESS(rv, rv);
1545 if (cancel) return NS_OK; // rules canceled the operation
1546 if (!handled)
1548 rv = InsertText(quotedStuff);
1550 // XXX Should set *aNodeInserted to the first node inserted
1551 if (aNodeInserted && NS_SUCCEEDED(rv))
1553 *aNodeInserted = 0;
1554 //NS_IF_ADDREF(*aNodeInserted);
1557 return rv;
1560 NS_IMETHODIMP
1561 nsPlaintextEditor::PasteAsCitedQuotation(const nsAString& aCitation,
1562 PRInt32 aSelectionType)
1564 return NS_ERROR_NOT_IMPLEMENTED;
1567 NS_IMETHODIMP
1568 nsPlaintextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
1569 const nsAString& aCitation,
1570 PRBool aInsertHTML,
1571 nsIDOMNode **aNodeInserted)
1573 return InsertAsQuotation(aQuotedText, aNodeInserted);
1576 nsresult
1577 nsPlaintextEditor::SharedOutputString(PRUint32 aFlags,
1578 PRBool* aIsCollapsed,
1579 nsAString& aResult)
1581 nsCOMPtr<nsISelection> selection;
1582 nsresult rv = GetSelection(getter_AddRefs(selection));
1583 NS_ENSURE_SUCCESS(rv, rv);
1584 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
1586 rv = selection->GetIsCollapsed(aIsCollapsed);
1587 NS_ENSURE_SUCCESS(rv, rv);
1589 if (!*aIsCollapsed)
1590 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1591 // If the selection isn't collapsed, we'll use the whole document.
1593 return OutputToString(NS_LITERAL_STRING("text/plain"), aFlags, aResult);
1596 NS_IMETHODIMP
1597 nsPlaintextEditor::Rewrap(PRBool aRespectNewlines)
1599 PRInt32 wrapCol;
1600 nsresult rv = GetWrapWidth(&wrapCol);
1601 NS_ENSURE_SUCCESS(rv, NS_OK);
1603 // Rewrap makes no sense if there's no wrap column; default to 72.
1604 if (wrapCol <= 0)
1605 wrapCol = 72;
1607 #ifdef DEBUG_akkana
1608 printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol);
1609 #endif
1611 nsAutoString current;
1612 PRBool isCollapsed;
1613 rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted
1614 | nsIDocumentEncoder::OutputLFLineBreak,
1615 &isCollapsed, current);
1616 NS_ENSURE_SUCCESS(rv, rv);
1618 nsCOMPtr<nsICiter> citer = new nsInternetCiter();
1619 NS_ENSURE_SUCCESS(rv, rv);
1620 NS_ENSURE_TRUE(citer, NS_ERROR_UNEXPECTED);
1622 nsString wrapped;
1623 PRUint32 firstLineOffset = 0; // XXX need to reset this if there is a selection
1624 rv = citer->Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines,
1625 wrapped);
1626 NS_ENSURE_SUCCESS(rv, rv);
1628 if (isCollapsed) // rewrap the whole document
1629 SelectAll();
1631 return InsertTextWithQuotations(wrapped);
1634 NS_IMETHODIMP
1635 nsPlaintextEditor::StripCites()
1637 #ifdef DEBUG_akkana
1638 printf("nsPlaintextEditor::StripCites()\n");
1639 #endif
1641 nsAutoString current;
1642 PRBool isCollapsed;
1643 nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted,
1644 &isCollapsed, current);
1645 NS_ENSURE_SUCCESS(rv, rv);
1647 nsCOMPtr<nsICiter> citer = new nsInternetCiter();
1648 NS_ENSURE_TRUE(citer, NS_ERROR_UNEXPECTED);
1650 nsString stripped;
1651 rv = citer->StripCites(current, stripped);
1652 NS_ENSURE_SUCCESS(rv, rv);
1654 if (isCollapsed) // rewrap the whole document
1656 rv = SelectAll();
1657 NS_ENSURE_SUCCESS(rv, rv);
1660 return InsertText(stripped);
1663 NS_IMETHODIMP
1664 nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
1666 *aNodeList = 0;
1667 return NS_OK;
1671 #ifdef XP_MAC
1672 #pragma mark -
1673 #pragma mark nsEditor overrides
1674 #pragma mark -
1675 #endif
1678 /** All editor operations which alter the doc should be prefaced
1679 * with a call to StartOperation, naming the action and direction */
1680 NS_IMETHODIMP
1681 nsPlaintextEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
1683 // Protect the edit rules object from dying
1684 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1686 nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection
1687 if (mRules) return mRules->BeforeEdit(mAction, mDirection);
1688 return NS_OK;
1692 /** All editor operations which alter the doc should be followed
1693 * with a call to EndOperation */
1694 NS_IMETHODIMP
1695 nsPlaintextEditor::EndOperation()
1697 // Protect the edit rules object from dying
1698 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1700 // post processing
1701 nsresult res = NS_OK;
1702 if (mRules) res = mRules->AfterEdit(mAction, mDirection);
1703 nsEditor::EndOperation(); // will clear mAction, mDirection
1704 return res;
1708 NS_IMETHODIMP
1709 nsPlaintextEditor::SelectEntireDocument(nsISelection *aSelection)
1711 if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
1713 // Protect the edit rules object from dying
1714 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1716 // is doc empty?
1717 PRBool bDocIsEmpty;
1718 if (NS_SUCCEEDED(mRules->DocumentIsEmpty(&bDocIsEmpty)) && bDocIsEmpty)
1720 // get root node
1721 nsIDOMElement *rootElement = GetRoot();
1722 NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
1724 // if it's empty don't select entire doc - that would select the bogus node
1725 return aSelection->Collapse(rootElement, 0);
1728 return nsEditor::SelectEntireDocument(aSelection);
1731 already_AddRefed<nsPIDOMEventTarget>
1732 nsPlaintextEditor::GetPIDOMEventTarget()
1734 NS_IF_ADDREF(mEventTarget);
1735 return mEventTarget.get();
1740 #ifdef XP_MAC
1741 #pragma mark -
1742 #pragma mark Random methods
1743 #pragma mark -
1744 #endif
1746 nsresult
1747 nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
1748 const nsAString & aAttribute,
1749 const nsAString & aValue,
1750 PRBool aSuppressTransaction)
1752 return nsEditor::SetAttribute(aElement, aAttribute, aValue);
1755 nsresult
1756 nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
1757 const nsAString & aAttribute,
1758 PRBool aSuppressTransaction)
1760 return nsEditor::RemoveAttribute(aElement, aAttribute);