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
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.
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"
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"
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"
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"
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()
95 , mIgnoreSpuriousDragEvent(PR_FALSE
)
97 , mWrapToWindow(PR_FALSE
)
100 , mInitTriggerCounter(0)
101 , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst
)
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();
122 mRules
->DetachEditor();
125 NS_IMPL_CYCLE_COLLECTION_CLASS(nsPlaintextEditor
)
127 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPlaintextEditor
, nsEditor
)
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
)
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
;
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
);
173 static PRInt32 sNewlineHandlingPref
= -1,
174 sCaretStylePref
= -1;
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",
186 if (sCaretStylePref
== 0)
197 nsPlaintextEditor::GetDefaultEditorPrefs(PRInt32
&aNewlineHandling
,
198 PRInt32
&aCaretStyle
)
200 if (sNewlineHandlingPref
== -1) {
201 nsContentUtils::RegisterPrefCallback("editor.singleLine.pasteNewlines",
202 EditorPrefsChangedCallback
,
204 EditorPrefsChangedCallback("editor.singleLine.pasteNewlines", nsnull
);
205 nsContentUtils::RegisterPrefCallback("layout.selection.caret_style",
206 EditorPrefsChangedCallback
,
208 EditorPrefsChangedCallback("layout.selection.caret_style", nsnull
);
211 aNewlineHandling
= sNewlineHandlingPref
;
212 aCaretStyle
= sCaretStylePref
;
216 nsPlaintextEditor::BeginEditorInit()
218 mInitTriggerCounter
++;
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)
230 if (NS_SUCCEEDED(res
))
237 nsPlaintextEditor::SetDocumentCharacterSet(const nsACString
& characterSet
)
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"),
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
;
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
));
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
);
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
));
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);
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
;
350 PRBool
nsPlaintextEditor::IsModifiable()
352 return !IsReadonly();
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
: {
386 return NS_OK
; // let it be used for focus switching
389 if (nativeKeyEvent
->isShift
|| nativeKeyEvent
->isControl
||
390 nativeKeyEvent
->isAlt
|| nativeKeyEvent
->isMeta
) {
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
) {
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
415 aKeyEvent
->PreventDefault();
416 nsAutoString
str(nativeKeyEvent
->charCode
);
417 return TypedText(str
, eTypedText
);
422 #pragma mark nsIHTMLEditor methods
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
432 NS_IMETHODIMP
nsPlaintextEditor::TypedText(const nsAString
& aString
,
435 nsAutoPlaceHolderBatch
batch(this, nsGkAtoms::TypingTxnName
);
441 return InsertText(aString
);
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
);
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
;
465 nsCOMPtr
<nsIDOMNode
> tmp
;
468 nodeAsText
->GetLength(&len
);
469 GetNodeLocation(node
, address_of(tmp
), &offset
);
470 NS_ENSURE_TRUE(tmp
, NS_ERROR_FAILURE
);
473 // we are already set to go
475 else if (theOffset
== (PRInt32
)len
)
477 // update offset to point AFTER the text node
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
);
489 res
= CreateNode(brType
, tmp
, offset
, getter_AddRefs(brNode
));
490 NS_ENSURE_SUCCESS(res
, res
);
492 *aInOutOffset
= offset
+1;
496 res
= CreateNode(brType
, node
, theOffset
, getter_AddRefs(brNode
));
497 NS_ENSURE_SUCCESS(res
, res
);
502 if (*outBRNode
&& (aSelect
!= eNone
))
504 nsCOMPtr
<nsIDOMNode
> parent
;
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
);
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
);
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
);
549 res
= selection
->GetIsCollapsed(&bCollapsed
);
550 NS_ENSURE_SUCCESS(res
, res
);
553 res
= DeleteSelection(nsIEditor::eNone
);
554 NS_ENSURE_SUCCESS(res
, res
);
556 nsCOMPtr
<nsIDOMNode
> selNode
;
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);
573 nsPlaintextEditor::GetTextSelectionOffsets(nsISelection
*aSelection
,
574 PRUint32
&aOutStartOffset
,
575 PRUint32
&aOutEndOffset
)
577 NS_ASSERTION(aSelection
, "null selection");
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
);
598 PRInt32 nodeCount
= 0; // only needed for the assertions below
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
);
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);
619 textNode
->GetLength(&length
);
620 totalLength
+= length
;
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
;
647 aOutStartOffset
= endOffset
;
648 aOutEndOffset
= startOffset
;
655 nsPlaintextEditor::ExtendSelectionForDelete(nsISelection
*aSelection
,
656 nsIEditor::EDirection
*aAction
)
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
);
675 result
= selCont
->WordExtendForDelete(PR_TRUE
);
676 // DeleteSelectionImpl doesn't handle these actions
677 // because it's inside batching, so don't confuse it:
681 result
= selCont
->WordExtendForDelete(PR_FALSE
);
685 result
= selCont
->CharacterExtendForDelete();
686 // Don't set aAction to eNone (see Bug 502259)
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
693 nsCOMPtr
<nsIDOMNode
> node
;
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
);
703 result
= charData
->GetData(data
);
704 NS_ENSURE_SUCCESS(result
, result
);
707 NS_IS_LOW_SURROGATE(data
[offset
- 1]) &&
708 NS_IS_HIGH_SURROGATE(data
[offset
- 2])) {
709 result
= selCont
->CharacterExtendForBackspace();
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
721 result
= selCont
->IntraLineMove(PR_TRUE
, PR_TRUE
);
724 default: // avoid several compiler warnings
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
);
741 // delete placeholder txns merge.
742 nsAutoPlaceHolderBatch
batch(this, nsGkAtoms::DeleteTxnName
);
743 nsAutoRules
beginRulesSniffing(this, kOpDeleteSelection
, aAction
);
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.
757 result
= selection
->GetIsCollapsed(&bCollapsed
);
758 NS_ENSURE_SUCCESS(result
, result
);
760 (aAction
== eNextWord
|| aAction
== ePreviousWord
||
761 aAction
== eToBeginningOfLine
|| aAction
== eToEndOfLine
))
763 if (mCaretStyle
== 1)
765 result
= selection
->CollapseToStart();
766 NS_ENSURE_SUCCESS(result
, result
);
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
);
786 result
= mRules
->DidDoAction(selection
, &ruleInfo
, 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
;
803 theAction
= nsTextEditRules::kInsertTextIME
;
804 opID
= kOpInsertIMEText
;
806 nsAutoPlaceHolderBatch
batch(this, nsnull
);
807 nsAutoRules
beginRulesSniffing(this, opID
, nsIEditor::eNext
);
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
833 result
= mRules
->DidDoAction(selection
, &ruleInfo
, 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
);
849 nsCOMPtr
<nsISelection
> selection
;
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
));
885 nsCOMPtr
<nsIDOMCharacterData
>nextTextNode
= do_QueryInterface(nextNode
);
887 nextNode
= do_QueryInterface(newNode
); // is this QI needed?
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
913 res
= selection
->Collapse(nextNode
, offsetInParent
);
920 // post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
921 res
= mRules
->DidDoAction(selection
, &ruleInfo
, res
);
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();
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();
988 caretP
->SetCaretDOMSelection(selection
);
991 // second part of 23558 fix:
992 if (aCompositionString
.IsEmpty()) {
993 mIMETextNode
= nsnull
;
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
);
1014 nsPlaintextEditor::GetTextLength(PRInt32
*aCount
)
1016 NS_ASSERTION(aCount
, "null pointer");
1018 // initialize out params
1021 // special-case for empty document, to account for the bogus node
1023 nsresult rv
= GetDocumentIsEmpty(&docEmpty
);
1024 NS_ENSURE_SUCCESS(rv
, rv
);
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
)) {
1043 textNode
->GetLength(&length
);
1044 totalLength
+= length
;
1048 *aCount
= totalLength
;
1053 nsPlaintextEditor::SetMaxTextLength(PRInt32 aMaxTextLength
)
1055 mMaxTextLength
= aMaxTextLength
;
1060 nsPlaintextEditor::GetMaxTextLength(PRInt32
* aMaxTextLength
)
1062 NS_ENSURE_TRUE(aMaxTextLength
, NS_ERROR_INVALID_POINTER
);
1063 *aMaxTextLength
= mMaxTextLength
;
1068 // Get the wrap width
1071 nsPlaintextEditor::GetWrapWidth(PRInt32
*aWrapColumn
)
1073 NS_ENSURE_TRUE( aWrapColumn
, NS_ERROR_NULL_POINTER
);
1075 *aWrapColumn
= mWrapColumn
;
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);
1093 styleValue
.Cut(styleStart
, styleValue
.Length() - styleStart
);
1098 // Change the wrap width on the root of this document.
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())
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.
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",
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;");
1164 styleValue
.AppendLiteral("white-space: pre;");
1166 return rootElement
->SetAttribute(styleName
, styleValue
);
1170 nsPlaintextEditor::SetWrapColumn(PRInt32 aWrapColumn
)
1172 mWrapColumn
= aWrapColumn
;
1177 // Get the newline handling for this editor
1180 nsPlaintextEditor::GetNewlineHandling(PRInt32
*aNewlineHandling
)
1182 NS_ENSURE_ARG_POINTER(aNewlineHandling
);
1184 *aNewlineHandling
= mNewlineHandling
;
1189 // Change the newline handling for this editor
1192 nsPlaintextEditor::SetNewlineHandling(PRInt32 aNewlineHandling
)
1194 mNewlineHandling
= aNewlineHandling
;
1201 #pragma mark nsIEditor overrides
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
);
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
);
1260 nsPlaintextEditor::CanCutOrCopy()
1262 nsCOMPtr
<nsISelection
> selection
;
1263 if (NS_FAILED(GetSelection(getter_AddRefs(selection
))))
1267 selection
->GetIsCollapsed(&isCollapsed
);
1268 return !isCollapsed
;
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
))))
1284 if (!nsCopySupport::FireClipboardEvent(aType
, presShell
, selection
))
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
);
1299 NS_IMETHODIMP
nsPlaintextEditor::CanCut(PRBool
*aCanCut
)
1301 NS_ENSURE_ARG_POINTER(aCanCut
);
1302 *aCanCut
= IsModifiable() && CanCutOrCopy();
1306 NS_IMETHODIMP
nsPlaintextEditor::Copy()
1308 FireClipboardEvent(NS_COPY
);
1312 NS_IMETHODIMP
nsPlaintextEditor::CanCopy(PRBool
*aCanCopy
)
1314 NS_ENSURE_ARG_POINTER(aCanCopy
);
1315 *aCanCopy
= CanCutOrCopy();
1319 // Shared between OutputToString and OutputToStream
1321 nsPlaintextEditor::GetAndInitDocEncoder(const nsAString
& aFormatType
,
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
);
1348 (void) GetWrapWidth(&wc
);
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.
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
);
1382 nsPlaintextEditor::OutputToString(const nsAString
& aFormatType
,
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
; }
1399 { // this case will get triggered by password fields
1400 aOutputString
.Assign(*(ruleInfo
.outString
));
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
);
1416 nsPlaintextEditor::OutputToStream(nsIOutputStream
* aOutputStream
,
1417 const nsAString
& aFormatType
,
1418 const nsACString
& aCharset
,
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"))
1429 rv
= GetDocumentIsEmpty(&docEmpty
);
1430 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
1448 #pragma mark nsIEditorMailSupport overrides
1453 nsPlaintextEditor::InsertTextWithQuotations(const nsAString
&aStringToInsert
)
1455 return InsertText(aStringToInsert
);
1459 nsPlaintextEditor::PasteAsQuotation(PRInt32 aSelectionType
)
1461 // Get Clipboard Service
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
;
1481 char* flav
= nsnull
;
1482 rv
= trans
->GetAnyTransferData(&flav
, getter_AddRefs(genericDataObj
),
1484 if (NS_FAILED(rv
) || !flav
)
1487 printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv
);
1491 #ifdef DEBUG_clipboard
1492 printf("Got flavor [%s]\n", flav
);
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);
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'));
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
1548 rv
= InsertText(quotedStuff
);
1550 // XXX Should set *aNodeInserted to the first node inserted
1551 if (aNodeInserted
&& NS_SUCCEEDED(rv
))
1554 //NS_IF_ADDREF(*aNodeInserted);
1561 nsPlaintextEditor::PasteAsCitedQuotation(const nsAString
& aCitation
,
1562 PRInt32 aSelectionType
)
1564 return NS_ERROR_NOT_IMPLEMENTED
;
1568 nsPlaintextEditor::InsertAsCitedQuotation(const nsAString
& aQuotedText
,
1569 const nsAString
& aCitation
,
1571 nsIDOMNode
**aNodeInserted
)
1573 return InsertAsQuotation(aQuotedText
, aNodeInserted
);
1577 nsPlaintextEditor::SharedOutputString(PRUint32 aFlags
,
1578 PRBool
* aIsCollapsed
,
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
);
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
);
1597 nsPlaintextEditor::Rewrap(PRBool aRespectNewlines
)
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.
1608 printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol
);
1611 nsAutoString current
;
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
);
1623 PRUint32 firstLineOffset
= 0; // XXX need to reset this if there is a selection
1624 rv
= citer
->Rewrap(current
, wrapCol
, firstLineOffset
, aRespectNewlines
,
1626 NS_ENSURE_SUCCESS(rv
, rv
);
1628 if (isCollapsed
) // rewrap the whole document
1631 return InsertTextWithQuotations(wrapped
);
1635 nsPlaintextEditor::StripCites()
1638 printf("nsPlaintextEditor::StripCites()\n");
1641 nsAutoString current
;
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
);
1651 rv
= citer
->StripCites(current
, stripped
);
1652 NS_ENSURE_SUCCESS(rv
, rv
);
1654 if (isCollapsed
) // rewrap the whole document
1657 NS_ENSURE_SUCCESS(rv
, rv
);
1660 return InsertText(stripped
);
1664 nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray
** aNodeList
)
1673 #pragma mark nsEditor overrides
1678 /** All editor operations which alter the doc should be prefaced
1679 * with a call to StartOperation, naming the action and direction */
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
);
1692 /** All editor operations which alter the doc should be followed
1693 * with a call to EndOperation */
1695 nsPlaintextEditor::EndOperation()
1697 // Protect the edit rules object from dying
1698 nsCOMPtr
<nsIEditRules
> kungFuDeathGrip(mRules
);
1701 nsresult res
= NS_OK
;
1702 if (mRules
) res
= mRules
->AfterEdit(mAction
, mDirection
);
1703 nsEditor::EndOperation(); // will clear mAction, mDirection
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
);
1718 if (NS_SUCCEEDED(mRules
->DocumentIsEmpty(&bDocIsEmpty
)) && bDocIsEmpty
)
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();
1742 #pragma mark Random methods
1747 nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement
* aElement
,
1748 const nsAString
& aAttribute
,
1749 const nsAString
& aValue
,
1750 PRBool aSuppressTransaction
)
1752 return nsEditor::SetAttribute(aElement
, aAttribute
, aValue
);
1756 nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement
* aElement
,
1757 const nsAString
& aAttribute
,
1758 PRBool aSuppressTransaction
)
1760 return nsEditor::RemoveAttribute(aElement
, aAttribute
);