pjs/editor/base/nsPlaintextEditor.cpp

1811 строки
54 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
*/
#include "nsPlaintextEditor.h"
#include "nsICaret.h"
#include "nsHTMLEditUtils.h"
#include "nsTextEditRules.h"
#include "nsEditorEventListeners.h"
#include "nsIEditActionListener.h"
#include "nsIDOMText.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
#include "nsIDOMAttr.h"
#include "nsIDocument.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMKeyEvent.h"
#include "nsIDOMKeyListener.h"
#include "nsIDOMMouseListener.h"
#include "nsIDOMMouseEvent.h"
#include "nsISelection.h"
#include "nsISelectionPrivate.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLImageElement.h"
#include "nsISelectionController.h"
#include "nsIIndependentSelection.h" //domselections answer to frameselection
#include "nsIDocumentObserver.h"
#include "nsIDocumentStateListener.h"
#include "nsIEnumerator.h"
#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsEditorCID.h"
#include "nsLayoutCID.h"
#include "nsIDOMRange.h"
#include "nsIDOMNSRange.h"
#include "nsISupportsArray.h"
#include "nsVoidArray.h"
#include "nsFileSpec.h"
#include "nsIFile.h"
#include "nsIURL.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsWidgetsCID.h"
#include "nsIDocumentEncoder.h"
#include "nsIDOMDocumentFragment.h"
#include "nsIPresShell.h"
#include "nsIPresContext.h"
#include "nsIParser.h"
#include "nsParserCIID.h"
#include "nsIImage.h"
#include "nsISupportsPrimitives.h"
#include "InsertTextTxn.h"
// netwerk
#include "nsIURI.h"
#include "nsNetUtil.h"
// Transactionas
#include "PlaceholderTxn.h"
// Misc
#include "TextEditorTest.h"
#include "nsEditorUtils.h"
#include "nsIPref.h"
#include "nsStyleConsts.h"
#include "nsIStyleContext.h"
const PRUnichar nbsp = 160;
// HACK - CID for NS_CTRANSITIONAL_DTD_CID so that we can get at transitional dtd
#define NS_CTRANSITIONAL_DTD_CID \
{ 0x4611d482, 0x960a, 0x11d4, { 0x8e, 0xb0, 0xb6, 0x17, 0x66, 0x1b, 0x6f, 0x7c } }
static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID);
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID);
static NS_DEFINE_IID(kFileWidgetCID, NS_FILEWIDGET_CID);
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
static NS_DEFINE_IID(kCParserIID, NS_IPARSER_IID);
static NS_DEFINE_IID(kCParserCID, NS_PARSER_IID);
static NS_DEFINE_CID(kCTransitionalDTDCID, NS_CTRANSITIONAL_DTD_CID);
#if defined(NS_DEBUG) && defined(DEBUG_buster)
static PRBool gNoisy = PR_FALSE;
#else
static const PRBool gNoisy = PR_FALSE;
#endif
nsIAtom *nsPlaintextEditor::gTypingTxnName;
nsIAtom *nsPlaintextEditor::gIMETxnName;
nsIAtom *nsPlaintextEditor::gDeleteTxnName;
// prototype for rules creation shortcut
nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
nsPlaintextEditor::nsPlaintextEditor()
: nsEditor()
, mIgnoreSpuriousDragEvent(PR_FALSE)
, mRules(nsnull)
, mIsComposing(PR_FALSE)
, mMaxTextLength(-1)
, mInitTriggerCounter(0)
{
// Done in nsEditor
// NS_INIT_REFCNT();
if (!gTypingTxnName)
gTypingTxnName = NS_NewAtom("Typing");
else
NS_ADDREF(gTypingTxnName);
if (!gIMETxnName)
gIMETxnName = NS_NewAtom("IME");
else
NS_ADDREF(gIMETxnName);
if (!gDeleteTxnName)
gDeleteTxnName = NS_NewAtom("Deleting");
else
NS_ADDREF(gDeleteTxnName);
}
nsPlaintextEditor::~nsPlaintextEditor()
{
/* first, delete the transaction manager if there is one.
this will release any remaining transactions.
this is important because transactions can hold onto the atoms (gTypingTxnName, ...)
and to make the optimization (holding refcounted statics) work correctly,
the editor instance needs to hold the last refcount.
If you get this wrong, expect to deref a garbage gTypingTxnName pointer if you bring up a second editor.
*/
if (mTxnMgr) {
mTxnMgr = 0;
}
nsrefcnt refCount=0;
if (gTypingTxnName) // we addref'd in the constructor
{ // want to release it without nulling out the pointer.
refCount = gTypingTxnName->Release();
if (0==refCount) {
gTypingTxnName = nsnull;
}
}
if (gIMETxnName) // we addref'd in the constructor
{ // want to release it without nulling out the pointer.
refCount = gIMETxnName->Release();
if (0==refCount) {
gIMETxnName = nsnull;
}
}
if (gDeleteTxnName) // we addref'd in the constructor
{ // want to release it without nulling out the pointer.
refCount = gDeleteTxnName->Release();
if (0==refCount) {
gDeleteTxnName = nsnull;
}
}
// remove the rules as an action listener. Else we get a bad ownership loop later on.
// it's ok if the rules aren't a listener; we ignore the error.
nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
RemoveEditActionListener(mListener);
nsCOMPtr<nsIDOMEventReceiver> erP;
nsresult result = GetDOMEventReceiver(getter_AddRefs(erP));
if (NS_SUCCEEDED(result) && erP)
{
if (mKeyListenerP) {
erP->RemoveEventListenerByIID(mKeyListenerP, NS_GET_IID(nsIDOMKeyListener));
}
if (mMouseListenerP) {
erP->RemoveEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener));
}
if (mTextListenerP) {
erP->RemoveEventListenerByIID(mTextListenerP, NS_GET_IID(nsIDOMTextListener));
}
if (mCompositionListenerP) {
erP->RemoveEventListenerByIID(mCompositionListenerP, NS_GET_IID(nsIDOMCompositionListener));
}
if (mFocusListenerP) {
erP->RemoveEventListenerByIID(mFocusListenerP, NS_GET_IID(nsIDOMFocusListener));
}
if (mDragListenerP) {
erP->RemoveEventListenerByIID(mDragListenerP, NS_GET_IID(nsIDOMDragListener));
}
}
else
NS_NOTREACHED("~nsTextEditor");
}
NS_IMPL_ADDREF_INHERITED(nsPlaintextEditor, nsEditor)
NS_IMPL_RELEASE_INHERITED(nsPlaintextEditor, nsEditor)
NS_IMETHODIMP nsPlaintextEditor::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
if (!aInstancePtr)
return NS_ERROR_NULL_POINTER;
*aInstancePtr = nsnull;
if (aIID.Equals(NS_GET_IID(nsIPlaintextEditor))) {
*aInstancePtr = NS_STATIC_CAST(nsIPlaintextEditor*, this);
NS_ADDREF_THIS();
return NS_OK;
}
return nsEditor::QueryInterface(aIID, aInstancePtr);
}
NS_IMETHODIMP nsPlaintextEditor::Init(nsIDOMDocument *aDoc,
nsIPresShell *aPresShell, nsIContent *aRoot, nsISelectionController *aSelCon, PRUint32 aFlags)
{
NS_PRECONDITION(aDoc && aPresShell, "bad arg");
if (!aDoc || !aPresShell)
return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK, rulesRes = NS_OK;
if (1)
{
// block to scope nsAutoEditInitRulesTrigger
nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes);
// Init the base editor
res = nsEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
}
if (NS_FAILED(rulesRes)) return rulesRes;
return res;
}
void
nsPlaintextEditor::BeginEditorInit()
{
mInitTriggerCounter++;
}
nsresult
nsPlaintextEditor::EndEditorInit()
{
nsresult res = NS_OK;
NS_PRECONDITION(mInitTriggerCounter > 0, "ended editor init before we began?");
mInitTriggerCounter--;
if (mInitTriggerCounter == 0)
{
res = InitRules();
if (NS_SUCCEEDED(res))
EnableUndo(PR_TRUE);
}
return res;
}
NS_IMETHODIMP
nsPlaintextEditor::SetDocumentCharacterSet(const PRUnichar* characterSet)
{
nsresult result;
result = nsEditor::SetDocumentCharacterSet(characterSet);
// update META charset tag
if (NS_SUCCEEDED(result)) {
nsCOMPtr<nsIDOMDocument>domdoc;
result = GetDocument(getter_AddRefs(domdoc));
if (NS_SUCCEEDED(result) && domdoc) {
nsAutoString newMetaString;
nsCOMPtr<nsIDOMNodeList>metaList;
nsCOMPtr<nsIDOMNode>metaNode;
nsCOMPtr<nsIDOMElement>metaElement;
PRBool newMetaCharset = PR_TRUE;
// get a list of META tags
result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("meta"), getter_AddRefs(metaList));
if (NS_SUCCEEDED(result) && metaList) {
PRUint32 listLength = 0;
(void) metaList->GetLength(&listLength);
for (PRUint32 i = 0; i < listLength; i++) {
metaList->Item(i, getter_AddRefs(metaNode));
if (!metaNode) continue;
metaElement = do_QueryInterface(metaNode);
if (!metaElement) continue;
const NS_ConvertASCIItoUCS2 content("charset=");
nsString currentValue;
if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("http-equiv"), currentValue))) continue;
if (kNotFound != currentValue.Find("content-type", PR_TRUE)) {
if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("content"), currentValue))) continue;
PRInt32 offset = currentValue.Find(content.GetUnicode(), PR_TRUE);
if (kNotFound != offset) {
currentValue.Left(newMetaString, offset); // copy current value before "charset=" (e.g. text/html)
newMetaString.Append(content);
newMetaString.Append(characterSet);
result = nsEditor::SetAttribute(metaElement, NS_ConvertASCIItoUCS2("content"), newMetaString);
if (NS_SUCCEEDED(result))
newMetaCharset = PR_FALSE;
break;
}
}
}
}
if (newMetaCharset) {
nsCOMPtr<nsIDOMNodeList>headList;
nsCOMPtr<nsIDOMNode>headNode;
nsCOMPtr<nsIDOMNode>resultNode;
result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"),getter_AddRefs(headList));
if (NS_SUCCEEDED(result) && headList) {
headList->Item(0, getter_AddRefs(headNode));
if (headNode) {
// Create a new meta charset tag
// can't use |NS_LITERAL_STRING| here until |CreateNode| is fixed to accept readables
result = CreateNode(NS_ConvertASCIItoUCS2("meta"), headNode, 0, getter_AddRefs(resultNode));
if (NS_FAILED(result))
return NS_ERROR_FAILURE;
// Set attributes to the created element
if (resultNode && nsCRT::strlen(characterSet) > 0) {
metaElement = do_QueryInterface(resultNode);
if (metaElement) {
// not undoable, undo should undo CreateNode
result = metaElement->SetAttribute(NS_LITERAL_STRING("http-equiv"), NS_LITERAL_STRING("Content-Type"));
if (NS_SUCCEEDED(result)) {
newMetaString.AssignWithConversion("text/html;charset=");
newMetaString.Append(characterSet);
// not undoable, undo should undo CreateNode
result = metaElement->SetAttribute(NS_LITERAL_STRING("content"), newMetaString);
}
}
}
}
}
}
}
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::PostCreate()
{
nsresult result = InstallEventListeners();
if (NS_FAILED(result)) return result;
result = nsEditor::PostCreate();
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::InstallEventListeners()
{
NS_ASSERTION(mDocWeak, "no document set on this editor");
if (!mDocWeak) return NS_ERROR_NOT_INITIALIZED;
nsresult result;
// get a key listener
result = NS_NewEditorKeyListener(getter_AddRefs(mKeyListenerP), this);
if (NS_FAILED(result)) {
HandleEventListenerError();
return result;
}
// get a mouse listener
result = NS_NewEditorMouseListener(getter_AddRefs(mMouseListenerP), this);
if (NS_FAILED(result)) {
HandleEventListenerError();
return result;
}
// get a text listener
result = NS_NewEditorTextListener(getter_AddRefs(mTextListenerP),this);
if (NS_FAILED(result)) {
#ifdef DEBUG_TAGUE
printf("nsTextEditor.cpp: failed to get TextEvent Listener\n");
#endif
HandleEventListenerError();
return result;
}
// get a composition listener
result = NS_NewEditorCompositionListener(getter_AddRefs(mCompositionListenerP),this);
if (NS_FAILED(result)) {
#ifdef DEBUG_TAGUE
printf("nsTextEditor.cpp: failed to get TextEvent Listener\n");
#endif
HandleEventListenerError();
return result;
}
// get a drag listener
result = NS_NewEditorDragListener(getter_AddRefs(mDragListenerP), this);
if (NS_FAILED(result)) {
HandleEventListenerError();
return result;
}
// get a focus listener
result = NS_NewEditorFocusListener(getter_AddRefs(mFocusListenerP), this);
if (NS_FAILED(result)) {
HandleEventListenerError();
return result;
}
nsCOMPtr<nsIDOMEventReceiver> erP;
result = GetDOMEventReceiver(getter_AddRefs(erP));
//end hack
if (NS_FAILED(result)) {
HandleEventListenerError();
return result;
}
// register the event listeners with the DOM event reveiver
result = erP->AddEventListenerByIID(mKeyListenerP, NS_GET_IID(nsIDOMKeyListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register key listener");
if (NS_SUCCEEDED(result))
{
result = erP->AddEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register mouse listener");
if (NS_SUCCEEDED(result))
{
result = erP->AddEventListenerByIID(mFocusListenerP, NS_GET_IID(nsIDOMFocusListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register focus listener");
if (NS_SUCCEEDED(result))
{
result = erP->AddEventListenerByIID(mTextListenerP, NS_GET_IID(nsIDOMTextListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register text listener");
if (NS_SUCCEEDED(result))
{
result = erP->AddEventListenerByIID(mCompositionListenerP, NS_GET_IID(nsIDOMCompositionListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register composition listener");
if (NS_SUCCEEDED(result))
{
result = erP->AddEventListenerByIID(mDragListenerP, NS_GET_IID(nsIDOMDragListener));
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register drag listener");
}
}
}
}
}
if (NS_FAILED(result)) {
HandleEventListenerError();
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::GetFlags(PRUint32 *aFlags)
{
if (!mRules || !aFlags) { return NS_ERROR_NULL_POINTER; }
return mRules->GetFlags(aFlags);
}
NS_IMETHODIMP
nsPlaintextEditor::SetFlags(PRUint32 aFlags)
{
if (!mRules) { return NS_ERROR_NULL_POINTER; }
return mRules->SetFlags(aFlags);
}
NS_IMETHODIMP nsPlaintextEditor::InitRules()
{
// instantiate the rules for this text editor
nsresult res = NS_ERROR_FAILURE;
res = NS_NewTextEditRules(getter_AddRefs(mRules));
if (NS_FAILED(res)) return res;
if (!mRules) return NS_ERROR_UNEXPECTED;
res = mRules->Init(this, mFlags);
return res;
}
PRBool nsPlaintextEditor::IsModifiable()
{
PRUint32 flags;
if (NS_SUCCEEDED(GetFlags(&flags)))
return ((flags & eEditorReadonlyMask) == 0);
else
return PR_FALSE;
}
#ifdef XP_MAC
#pragma mark -
#pragma mark nsIHTMLEditor methods
#pragma mark -
#endif
NS_IMETHODIMP nsPlaintextEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
{
PRUint32 keyCode, character;
PRBool isShift, ctrlKey, altKey, metaKey;
if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) &&
NS_SUCCEEDED(aKeyEvent->GetShiftKey(&isShift)) &&
NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) &&
NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) &&
NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey)))
{
aKeyEvent->GetCharCode(&character);
if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
|| keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
{
nsString empty;
return TypedText(empty.GetUnicode(), eTypedBreak);
}
else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
{
// pass escape keypresses through as empty strings: needed for ime support
nsString empty;
return TypedText(empty.GetUnicode(), eTypedText);
}
if (character && !altKey && !ctrlKey && !isShift && !metaKey)
{
nsAutoString key(character);
return TypedText(key.GetUnicode(), eTypedText);
}
}
return NS_ERROR_FAILURE;
}
/* This routine is needed to provide a bottleneck for typing for logging
purposes. Can't use EditorKeyPress() (above) for that since it takes
a nsIDOMUIEvent* parameter. So instead we pass enough info through
to TypedText() to determine what action to take, but without passing
an event.
*/
NS_IMETHODIMP nsPlaintextEditor::TypedText(const PRUnichar* aString,
PRInt32 aAction)
{
nsAutoPlaceHolderBatch batch(this, gTypingTxnName);
switch (aAction)
{
case eTypedText:
{
return InsertText(aString);
}
case eTypedBreak:
{
return InsertLineBreak();
}
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP nsPlaintextEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
{
if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
*outBRNode = nsnull;
nsresult res;
// we need to insert a br. unfortunately, we may have to split a text node to do it.
nsCOMPtr<nsIDOMNode> node = *aInOutParent;
PRInt32 theOffset = *aInOutOffset;
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
nsAutoString brType; brType.AssignWithConversion("br");
nsCOMPtr<nsIDOMNode> brNode;
if (nodeAsText)
{
nsCOMPtr<nsIDOMNode> tmp;
PRInt32 offset;
PRUint32 len;
nodeAsText->GetLength(&len);
GetNodeLocation(node, address_of(tmp), &offset);
if (!tmp) return NS_ERROR_FAILURE;
if (!theOffset)
{
// we are already set to go
}
else if (theOffset == (PRInt32)len)
{
// update offset to point AFTER the text node
offset++;
}
else
{
// split the text node
res = SplitNode(node, theOffset, getter_AddRefs(tmp));
if (NS_FAILED(res)) return res;
res = GetNodeLocation(node, address_of(tmp), &offset);
if (NS_FAILED(res)) return res;
}
// create br
res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
if (NS_FAILED(res)) return res;
*aInOutParent = tmp;
*aInOutOffset = offset+1;
}
else
{
res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode));
if (NS_FAILED(res)) return res;
(*aInOutOffset)++;
}
*outBRNode = brNode;
if (*outBRNode && (aSelect != eNone))
{
nsCOMPtr<nsISelection> selection;
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
res = GetNodeLocation(*outBRNode, address_of(parent), &offset);
if (NS_FAILED(res)) return res;
if (aSelect == eNext)
{
// position selection after br
selPriv->SetInterlinePosition(PR_TRUE);
res = selection->Collapse(parent, offset+1);
}
else if (aSelect == ePrevious)
{
// position selection before br
selPriv->SetInterlinePosition(PR_TRUE);
res = selection->Collapse(parent, offset);
}
}
return NS_OK;
}
NS_IMETHODIMP nsPlaintextEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
{
nsCOMPtr<nsIDOMNode> parent = aNode;
PRInt32 offset = aOffset;
return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
}
NS_IMETHODIMP nsPlaintextEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
{
PRBool bCollapsed;
nsCOMPtr<nsISelection> selection;
if (!outBRNode) return NS_ERROR_NULL_POINTER;
*outBRNode = nsnull;
// calling it text insertion to trigger moz br treatment by rules
nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
res = selection->GetIsCollapsed(&bCollapsed);
if (NS_FAILED(res)) return res;
if (!bCollapsed)
{
res = DeleteSelection(nsIEditor::eNone);
if (NS_FAILED(res)) return res;
}
nsCOMPtr<nsIDOMNode> selNode;
PRInt32 selOffset;
res = GetStartNodeAndOffset(selection, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
res = CreateBR(selNode, selOffset, outBRNode);
if (NS_FAILED(res)) return res;
// position selection after br
res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
selPriv->SetInterlinePosition(PR_TRUE);
res = selection->Collapse(selNode, selOffset+1);
return res;
}
nsresult nsPlaintextEditor::GetTextSelectionOffsets(nsISelection *aSelection,
PRInt32 &aOutStartOffset,
PRInt32 &aOutEndOffset)
{
if(!aSelection) { return NS_ERROR_NULL_POINTER; }
nsresult result;
// initialize out params
aOutStartOffset = 0; // default to first char in selection
aOutEndOffset = -1; // default to total length of text in selection
nsCOMPtr<nsIDOMNode> startNode, endNode, parentNode;
PRInt32 startOffset, endOffset;
aSelection->GetAnchorNode(getter_AddRefs(startNode));
aSelection->GetAnchorOffset(&startOffset);
aSelection->GetFocusNode(getter_AddRefs(endNode));
aSelection->GetFocusOffset(&endOffset);
nsCOMPtr<nsIEnumerator> enumerator;
nsCOMPtr<nsISelection> selection(aSelection);
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
result = selPriv->GetEnumerator(getter_AddRefs(enumerator));
if (NS_FAILED(result)) return result;
if (!enumerator) return NS_ERROR_NULL_POINTER;
// don't use "result" in this block
enumerator->First();
nsCOMPtr<nsISupports> currentItem;
nsresult findParentResult = enumerator->CurrentItem(getter_AddRefs(currentItem));
if ((NS_SUCCEEDED(findParentResult)) && (currentItem))
{
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
range->GetCommonAncestorContainer(getter_AddRefs(parentNode));
}
else
{
parentNode = do_QueryInterface(startNode);
}
return GetAbsoluteOffsetsForPoints(startNode, startOffset,
endNode, endOffset,
parentNode,
aOutStartOffset, aOutEndOffset);
}
nsresult
nsPlaintextEditor::GetAbsoluteOffsetsForPoints(nsIDOMNode *aInStartNode,
PRInt32 aInStartOffset,
nsIDOMNode *aInEndNode,
PRInt32 aInEndOffset,
nsIDOMNode *aInCommonParentNode,
PRInt32 &aOutStartOffset,
PRInt32 &aOutEndOffset)
{
if(!aInStartNode || !aInEndNode || !aInCommonParentNode)
return NS_ERROR_NULL_POINTER;
nsresult result;
// initialize out params
aOutStartOffset = 0; // default to first char in selection
aOutEndOffset = -1; // default to total length of text in selection
nsCOMPtr<nsIContentIterator> iter;
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
NS_GET_IID(nsIContentIterator),
getter_AddRefs(iter));
if (NS_FAILED(result)) return result;
if (!iter) return NS_ERROR_NULL_POINTER;
PRUint32 totalLength=0;
nsCOMPtr<nsIDOMCharacterData>textNode;
nsCOMPtr<nsIContent>blockParentContent = do_QueryInterface(aInCommonParentNode);
iter->Init(blockParentContent);
// loop through the content iterator for each content node
nsCOMPtr<nsIContent> content;
result = iter->CurrentNode(getter_AddRefs(content));
while (NS_ENUMERATOR_FALSE == iter->IsDone())
{
textNode = do_QueryInterface(content);
if (textNode)
{
nsCOMPtr<nsIDOMNode>currentNode = do_QueryInterface(textNode);
if (!currentNode) {return NS_ERROR_NO_INTERFACE;}
if (IsEditable(currentNode))
{
if (currentNode.get() == aInStartNode)
{
aOutStartOffset = totalLength + aInStartOffset;
}
if (currentNode.get() == aInEndNode)
{
aOutEndOffset = totalLength + aInEndOffset;
break;
}
PRUint32 length;
textNode->GetLength(&length);
totalLength += length;
}
}
iter->Next();
iter->CurrentNode(getter_AddRefs(content));
}
if (-1==aOutEndOffset) {
aOutEndOffset = totalLength;
}
// guarantee that aOutStartOffset <= aOutEndOffset
if (aOutEndOffset<aOutStartOffset)
{
PRInt32 temp;
temp = aOutStartOffset;
aOutStartOffset= aOutEndOffset;
aOutEndOffset = temp;
}
NS_POSTCONDITION(aOutStartOffset <= aOutEndOffset, "start > end");
return result;
}
nsresult
nsPlaintextEditor::GetDOMEventReceiver(nsIDOMEventReceiver **aEventReceiver)
{
if (!aEventReceiver)
return NS_ERROR_NULL_POINTER;
*aEventReceiver = 0;
nsCOMPtr<nsIDOMElement> rootElement;
nsresult result = GetRootElement(getter_AddRefs(rootElement));
if (NS_FAILED(result))
return result;
if (!rootElement)
return NS_ERROR_FAILURE;
// Now hack to make sure we are not anonymous content.
// If we are grab the parent of root element for our observer.
nsCOMPtr<nsIContent> content = do_QueryInterface(rootElement);
if (content)
{
nsCOMPtr<nsIContent> parent;
if (NS_SUCCEEDED(content->GetParent(*getter_AddRefs(parent))) && parent)
{
PRInt32 index;
if (NS_FAILED(parent->IndexOf(content, index)) || index < 0 )
{
rootElement = do_QueryInterface(parent); //this will put listener on the form element basically
result = rootElement->QueryInterface(NS_GET_IID(nsIDOMEventReceiver), (void **)aEventReceiver);
}
else
rootElement = 0; // Let the event receiver work on the document instead of the root element
}
}
else
rootElement = 0;
if (!rootElement && mDocWeak)
{
// Don't use getDocument here, because we have no way of knowing if
// Init() was ever called. So we need to get the document ourselves,
// if it exists.
nsCOMPtr<nsIDOMDocument> domdoc = do_QueryReferent(mDocWeak);
if (!domdoc)
return NS_ERROR_FAILURE;
result = domdoc->QueryInterface(NS_GET_IID(nsIDOMEventReceiver), (void **)aEventReceiver);
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::CollapseSelectionToStart()
{
nsCOMPtr<nsIDOMElement> bodyElement;
nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> bodyNode = do_QueryInterface(bodyElement);
nsCOMPtr<nsISelection> selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
return selection->Collapse(bodyNode,0);
}
NS_IMETHODIMP nsPlaintextEditor::DeleteSelection(nsIEditor::EDirection aAction)
{
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
nsCOMPtr<nsISelection> selection;
PRBool cancel, handled;
nsresult result;
// delete placeholder txns merge.
nsAutoPlaceHolderBatch batch(this, gDeleteTxnName);
nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
// If it's one of these modes,
// we have to extend the selection first.
// This needs to happen inside selection batching,
// otherwise the deleted text is autocopied to the clipboard.
if (aAction == eNextWord || aAction == ePreviousWord
|| aAction == eToBeginningOfLine || aAction == eToEndOfLine)
{
if (!mSelConWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsISelectionController> selCont (do_QueryReferent(mSelConWeak));
if (!selCont)
return NS_ERROR_NO_INTERFACE;
switch (aAction)
{
case eNextWord:
result = selCont->WordMove(PR_TRUE, PR_TRUE);
// DeleteSelectionImpl doesn't handle these actions
// because it's inside batching, so don't confuse it:
aAction = eNone;
break;
case ePreviousWord:
result = selCont->WordMove(PR_FALSE, PR_TRUE);
aAction = eNone;
break;
case eToBeginningOfLine:
selCont->IntraLineMove(PR_TRUE, PR_FALSE); // try to move to end
result = selCont->IntraLineMove(PR_FALSE, PR_TRUE); // select to beginning
aAction = eNone;
break;
case eToEndOfLine:
result = selCont->IntraLineMove(PR_TRUE, PR_TRUE);
// Bugs 54449/54452: the selection jumps to the wrong place
// when deleting past a <br> and action is eNext or ePrev,
// so setting action to eNone makes delete-to-end marginally usable.
// aAction should really be set to eNext
aAction = eNone;
break;
default: // avoid several compiler warnings
result = NS_OK;
break;
}
if (NS_FAILED(result))
{
#ifdef DEBUG
printf("Selection controller interface didn't work!\n");
#endif
return result;
}
}
// pre-process
result = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(result)) return result;
if (!selection) return NS_ERROR_NULL_POINTER;
nsTextRulesInfo ruleInfo(nsTextEditRules::kDeleteSelection);
ruleInfo.collapsedAction = aAction;
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (NS_FAILED(result)) return result;
if (!cancel && !handled)
{
result = DeleteSelectionImpl(aAction);
}
if (!cancel)
{
// post-process
result = mRules->DidDoAction(selection, &ruleInfo, result);
}
return result;
}
NS_IMETHODIMP nsPlaintextEditor::InsertText(const PRUnichar* aStringToInsert)
{
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
nsCOMPtr<nsISelection> selection;
PRBool cancel, handled;
PRInt32 theAction = nsTextEditRules::kInsertText;
PRInt32 opID = kOpInsertText;
if (mInIMEMode)
{
theAction = nsTextEditRules::kInsertTextIME;
opID = kOpInsertIMEText;
}
nsAutoPlaceHolderBatch batch(this, nsnull);
nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
// pre-process
nsresult result = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(result)) return result;
if (!selection) return NS_ERROR_NULL_POINTER;
nsAutoString resultString;
// XXX can we trust instring to outlive ruleInfo,
// XXX and ruleInfo not to refer to instring in its dtor?
nsAutoString instring(aStringToInsert);
nsTextRulesInfo ruleInfo(theAction);
ruleInfo.inString = &instring;
ruleInfo.outString = &resultString;
ruleInfo.maxLength = mMaxTextLength;
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (NS_FAILED(result)) return result;
if (!cancel && !handled)
{
// we rely on rules code for now - no default implementation
}
if (!cancel)
{
// post-process
result = mRules->DidDoAction(selection, &ruleInfo, result);
}
return result;
}
NS_IMETHODIMP nsPlaintextEditor::InsertLineBreak()
{
nsresult res;
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
nsAutoEditBatch beginBatching(this);
nsAutoRules beginRulesSniffing(this, kOpInsertBreak, nsIEditor::eNext);
nsCOMPtr<nsISelection> selection;
PRBool cancel, handled;
// pre-process
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (NS_FAILED(res)) return res;
if (!cancel && !handled)
{
// create the new BR node
nsCOMPtr<nsIDOMNode> newNode;
nsAutoString tag; tag.AssignWithConversion("BR");
res = DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
if (NS_SUCCEEDED(res))
{
// set the selection to the new node
nsCOMPtr<nsIDOMNode>parent;
res = newNode->GetParentNode(getter_AddRefs(parent));
if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
if (NS_SUCCEEDED(res))
{
PRInt32 offsetInParent=-1; // we use the -1 as a marker to see if we need to compute this or not
nsCOMPtr<nsIDOMNode>nextNode;
newNode->GetNextSibling(getter_AddRefs(nextNode));
if (nextNode)
{
nsCOMPtr<nsIDOMCharacterData>nextTextNode;
nextTextNode = do_QueryInterface(nextNode);
if (!nextTextNode) {
nextNode = do_QueryInterface(newNode);
}
else {
offsetInParent=0;
}
}
else {
nextNode = do_QueryInterface(newNode);
}
res = GetSelection(getter_AddRefs(selection));
if (!selection) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
if (NS_SUCCEEDED(res))
{
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
if (-1==offsetInParent)
{
nextNode->GetParentNode(getter_AddRefs(parent));
res = GetChildOffset(nextNode, parent, offsetInParent);
if (NS_SUCCEEDED(res)) {
// SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
// We want the caret to stick to whatever is past the break. This is
// because the break is on the same line we were on, but the next content
// will be on the following line.
selPriv->SetInterlinePosition(PR_TRUE);
res = selection->Collapse(parent, offsetInParent+1); // +1 to insert just after the break
}
}
else
{
res = selection->Collapse(nextNode, offsetInParent);
}
}
}
}
}
if (!cancel)
{
// post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
res = mRules->DidDoAction(selection, &ruleInfo, res);
}
return res;
}
NS_IMETHODIMP
nsPlaintextEditor::GetDocumentIsEmpty(PRBool *aDocumentIsEmpty)
{
if (!aDocumentIsEmpty)
return NS_ERROR_NULL_POINTER;
if (!mRules)
return NS_ERROR_NOT_INITIALIZED;
return mRules->DocumentIsEmpty(aDocumentIsEmpty);
}
NS_IMETHODIMP
nsPlaintextEditor::GetTextLength(PRInt32 *aCount)
{
if (!aCount) { return NS_ERROR_NULL_POINTER; }
nsresult result;
// initialize out params
*aCount = 0;
// special-case for empty document, to account for the bogus text node
PRBool docEmpty;
result = GetDocumentIsEmpty(&docEmpty);
if (NS_FAILED(result)) return result;
if (docEmpty)
{
*aCount = 0;
return NS_OK;
}
// get the body node
nsCOMPtr<nsIDOMElement> bodyElement;
result = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(result)) { return result; }
if (!bodyElement) { return NS_ERROR_NULL_POINTER; }
// get the offsets of the first and last children of the body node
nsCOMPtr<nsIDOMNode>bodyNode = do_QueryInterface(bodyElement);
if (!bodyNode) { return NS_ERROR_NULL_POINTER; }
PRInt32 numBodyChildren=0;
nsCOMPtr<nsIDOMNode>lastChild;
result = bodyNode->GetLastChild(getter_AddRefs(lastChild));
if (NS_FAILED(result)) { return result; }
if (!lastChild) { return NS_ERROR_NULL_POINTER; }
result = GetChildOffset(lastChild, bodyNode, numBodyChildren);
if (NS_FAILED(result)) { return result; }
// count
PRInt32 start, end;
result = GetAbsoluteOffsetsForPoints(bodyNode, 0,
bodyNode, numBodyChildren,
bodyNode, start, end);
if (NS_SUCCEEDED(result))
{
NS_ASSERTION(0==start, "GetAbsoluteOffsetsForPoints failed to set start correctly.");
NS_ASSERTION(0<=end, "GetAbsoluteOffsetsForPoints failed to set end correctly.");
if (0<=end) {
*aCount = end;
}
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::SetMaxTextLength(PRInt32 aMaxTextLength)
{
mMaxTextLength = aMaxTextLength;
return NS_OK;
}
NS_IMETHODIMP
nsPlaintextEditor::GetMaxTextLength(PRInt32* aMaxTextLength)
{
if (!aMaxTextLength)
return NS_ERROR_INVALID_POINTER;
*aMaxTextLength = mMaxTextLength;
return NS_OK;
}
NS_IMETHODIMP
nsPlaintextEditor::GetBodyStyleContext(nsIStyleContext** aStyleContext)
{
nsCOMPtr<nsIDOMElement> body;
nsresult res = GetRootElement(getter_AddRefs(body));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIContent> content = do_QueryInterface(body);
nsIFrame *frame;
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
res = ps->GetPrimaryFrameFor(content, &frame);
if (NS_FAILED(res)) return res;
return ps->GetStyleContextFor(frame, aStyleContext);
}
//
// Get the wrap width for the root of the document.
//
NS_IMETHODIMP
nsPlaintextEditor::GetWrapWidth(PRInt32 *aWrapColumn)
{
nsresult res;
if (! aWrapColumn)
return NS_ERROR_NULL_POINTER;
*aWrapColumn = -1; // default: no wrap
nsCOMPtr<nsIStyleContext> styleContext;
res = GetBodyStyleContext(getter_AddRefs(styleContext));
if (NS_FAILED(res)) return res;
const nsStyleText* styleText =
(const nsStyleText*)styleContext->GetStyleData(eStyleStruct_Text);
if (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace)
{
*aWrapColumn = 0; // wrap to window width
}
else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace)
{
const nsStylePosition* stylePosition =
(const nsStylePosition*)styleContext->GetStyleData(eStyleStruct_Position);
if (stylePosition->mWidth.GetUnit() == eStyleUnit_Chars)
*aWrapColumn = stylePosition->mWidth.GetIntValue();
else
{
*aWrapColumn = -1;
return NS_ERROR_UNEXPECTED;
}
}
else
{
*aWrapColumn = -1;
}
return NS_OK;
}
//
// See if the style value includes this attribute, and if it does,
// cut out everything from the attribute to the next semicolon.
//
static void CutStyle(const char* stylename, nsString& styleValue)
{
// Find the current wrapping type:
PRInt32 styleStart = styleValue.Find(stylename, PR_TRUE);
if (styleStart >= 0)
{
PRInt32 styleEnd = styleValue.Find(";", PR_FALSE, styleStart);
if (styleEnd > styleStart)
styleValue.Cut(styleStart, styleEnd - styleStart + 1);
else
styleValue.Cut(styleStart, styleValue.Length() - styleStart);
}
}
//
// Change the wrap width on the root of this document.
//
NS_IMETHODIMP
nsPlaintextEditor::SetWrapWidth(PRInt32 aWrapColumn)
{
nsresult res;
// Ought to set a style sheet here ...
// Probably should keep around an mPlaintextStyleSheet for this purpose.
nsCOMPtr<nsIDOMElement> bodyElement;
res = GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
// Get the current style for this body element:
nsAutoString styleName; styleName.AssignWithConversion("style");
nsAutoString styleValue;
res = bodyElement->GetAttribute(styleName, styleValue);
if (NS_FAILED(res)) return res;
// We'll replace styles for these values:
CutStyle("white-space", styleValue);
CutStyle("width", styleValue);
CutStyle("font-family", styleValue);
// If we have other style left, trim off any existing semicolons
// or whitespace, then add a known semicolon-space:
if (styleValue.Length() > 0)
{
styleValue.Trim("; \t", PR_FALSE, PR_TRUE);
styleValue.AppendWithConversion("; ");
}
// Make sure we have fixed-width font. This should be done for us,
// but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
// Only do this if we're wrapping.
PRUint32 flags = 0;
GetFlags(&flags);
if ((flags & eEditorEnableWrapHackMask) && aWrapColumn >= 0)
styleValue.AppendWithConversion("font-family: -moz-fixed; ");
// and now we're ready to set the new whitespace/wrapping style.
if (aWrapColumn > 0) // Wrap to a fixed column
{
styleValue.AppendWithConversion("white-space: -moz-pre-wrap; width: ");
styleValue.AppendInt(aWrapColumn);
styleValue.AppendWithConversion("ch;");
}
else if (aWrapColumn == 0)
styleValue.AppendWithConversion("white-space: -moz-pre-wrap;");
else
styleValue.AppendWithConversion("white-space: pre;");
res = bodyElement->SetAttribute(styleName, styleValue);
return res;
}
#ifdef XP_MAC
#pragma mark -
#pragma mark nsIEditor overrides
#pragma mark -
#endif
NS_IMETHODIMP
nsPlaintextEditor::Undo(PRUint32 aCount)
{
ForceCompositionEnd();
nsresult result = NS_OK;
nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
nsCOMPtr<nsISelection> selection;
GetSelection(getter_AddRefs(selection));
PRBool cancel, handled;
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (!cancel && NS_SUCCEEDED(result))
{
result = nsEditor::Undo(aCount);
result = mRules->DidDoAction(selection, &ruleInfo, result);
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::Redo(PRUint32 aCount)
{
nsresult result = NS_OK;
nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
nsCOMPtr<nsISelection> selection;
GetSelection(getter_AddRefs(selection));
PRBool cancel, handled;
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
if (!cancel && NS_SUCCEEDED(result))
{
result = nsEditor::Redo(aCount);
result = mRules->DidDoAction(selection, &ruleInfo, result);
}
return result;
}
NS_IMETHODIMP nsPlaintextEditor::Cut()
{
nsCOMPtr<nsISelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (!NS_SUCCEEDED(res))
return res;
PRBool isCollapsed;
if (NS_SUCCEEDED(selection->GetIsCollapsed(&isCollapsed)) && isCollapsed)
return NS_ERROR_NOT_AVAILABLE;
res = Copy();
if (NS_SUCCEEDED(res))
res = DeleteSelection(eNone);
return res;
}
NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool &aCanCut)
{
aCanCut = PR_FALSE;
nsCOMPtr<nsISelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
PRBool isCollapsed;
res = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(res)) return res;
aCanCut = !isCollapsed && IsModifiable();
return NS_OK;
}
NS_IMETHODIMP nsPlaintextEditor::Copy()
{
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
return ps->DoCopy();
}
NS_IMETHODIMP nsPlaintextEditor::CanCopy(PRBool &aCanCopy)
{
aCanCopy = PR_FALSE;
nsCOMPtr<nsISelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
PRBool isCollapsed;
res = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(res)) return res;
aCanCopy = !isCollapsed;
return NS_OK;
}
// Shared between OutputToString and OutputToStream
NS_IMETHODIMP
nsPlaintextEditor::GetAndInitDocEncoder(const nsAReadableString& aFormatType,
PRUint32 aFlags,
const nsAReadableString* aCharset,
nsIDocumentEncoder** encoder)
{
nsCOMPtr<nsIPresShell> presShell;
nsresult rv = GetPresShell(getter_AddRefs(presShell));
if (NS_FAILED(rv)) return rv;
if (!presShell) return NS_ERROR_FAILURE;
nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
formatType.AppendWithConversion(aFormatType);
nsCOMPtr<nsIDocumentEncoder> docEncoder (do_CreateInstance(formatType, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocument> doc;
rv = presShell->GetDocument(getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
rv = docEncoder->Init(doc, aFormatType, aFlags);
NS_ENSURE_SUCCESS(rv, rv);
if (aCharset && aCharset->Length() != 0
&& !(aCharset->Equals(NS_LITERAL_STRING("null"))))
docEncoder->SetCharset(*aCharset);
PRInt32 wc;
(void) GetWrapWidth(&wc);
if (wc >= 0)
(void) docEncoder->SetWrapColumn(wc);
// Set the selection, if appropriate.
// We do this either if the OutputSelectionOnly flag is set,
// in which case we use our existing selection ...
if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
{
nsCOMPtr<nsISelection> selection;
rv = GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(rv) && selection)
rv = docEncoder->SetSelection(selection);
NS_ENSURE_SUCCESS(rv, rv);
}
// ... or if the root element is not a body,
// in which case we set the selection to encompass the root.
else
{
nsCOMPtr<nsIDOMElement> rootElement;
GetRootElement(getter_AddRefs(rootElement));
NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
if (!nsHTMLEditUtils::IsBody(rootElement))
{
// XXX Why does this use range rather than selection collapse/extend?
nsCOMPtr<nsIDOMRange> range (do_CreateInstance(kCRangeCID, &rv));
if (NS_FAILED(rv)) return rv;
if (!range) return NS_ERROR_FAILURE;
nsCOMPtr<nsISelection> selection (do_CreateInstance(kCDOMSelectionCID,
&rv));
if (NS_FAILED(rv)) return rv;
if (!selection) return NS_ERROR_FAILURE;
// get the independent selection interface
nsCOMPtr<nsIIndependentSelection> indSel = do_QueryInterface(selection);
if (indSel)
indSel->SetPresShell(presShell);
nsCOMPtr<nsIContent> content(do_QueryInterface(rootElement));
if (content)
{
range->SetStart(rootElement,0);
PRInt32 children;
if (NS_SUCCEEDED(content->ChildCount(children)))
range->SetEnd(rootElement,children);
// XXX else, should we return the error code?
if (NS_FAILED(selection->AddRange(range)))
return NS_ERROR_FAILURE;
}
rv = docEncoder->SetSelection(selection);
NS_ENSURE_SUCCESS(rv, rv);
}
}
NS_ADDREF(*encoder = docEncoder);
return rv;
}
NS_IMETHODIMP
nsPlaintextEditor::OutputToString(nsAWritableString& aOutputString,
const nsAReadableString& aFormatType,
PRUint32 aFlags)
{
PRBool cancel, handled;
nsString resultString;
nsTextRulesInfo ruleInfo(nsTextEditRules::kOutputText);
ruleInfo.outString = &resultString;
// XXX Struct should store a nsAReadable*
nsAutoString str(aFormatType);
ruleInfo.outputFormat = &str;
nsresult rv = mRules->WillDoAction(nsnull, &ruleInfo, &cancel, &handled);
if (cancel || NS_FAILED(rv)) { return rv; }
if (handled)
{ // this case will get triggered by password fields
aOutputString.Assign(*(ruleInfo.outString));
return rv;
}
nsCOMPtr<nsIDocumentEncoder> encoder;
rv = GetAndInitDocEncoder(aFormatType, aFlags, 0, getter_AddRefs(encoder));
if (NS_FAILED(rv))
return rv;
rv = encoder->EncodeToString(aOutputString);
return rv;
}
NS_IMETHODIMP
nsPlaintextEditor::OutputToStream(nsIOutputStream* aOutputStream,
const nsAReadableString& aFormatType,
const nsAReadableString* aCharset,
PRUint32 aFlags)
{
nsresult rv;
// special-case for empty document when requesting plain text,
// to account for the bogus text node.
// XXX Should there be a similar test in OutputToString?
if (aFormatType == NS_LITERAL_STRING("text/plain"))
{
PRBool docEmpty;
rv = GetDocumentIsEmpty(&docEmpty);
if (NS_FAILED(rv)) return rv;
if (docEmpty)
return NS_OK; // output nothing
}
nsCOMPtr<nsIDocumentEncoder> encoder;
rv = GetAndInitDocEncoder(aFormatType, aFlags, aCharset,
getter_AddRefs(encoder));
if (NS_FAILED(rv))
return rv;
return encoder->EncodeToStream(aOutputStream);
}
#ifdef XP_MAC
#pragma mark -
#pragma mark nsIEditorIMESupport overrides
#pragma mark -
#endif
NS_IMETHODIMP
nsPlaintextEditor::SetCompositionString(const nsString& aCompositionString, nsIPrivateTextRangeList* aTextRangeList,nsTextEventReply* aReply)
{
NS_ASSERTION(aTextRangeList, "null ptr");
if(nsnull == aTextRangeList)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsICaret> caretP;
// workaround for windows ime bug 23558: we get every ime event twice.
// for escape keypress, this causes an empty string to be passed
// twice, which freaks out the editor. This is to detect and aviod that
// situation:
if (aCompositionString.IsEmpty() && !mIMETextNode)
{
return NS_OK;
}
nsCOMPtr<nsISelection> selection;
nsresult result = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(result)) return result;
mIMETextRangeList = aTextRangeList;
nsAutoPlaceHolderBatch batch(this, gIMETxnName);
result = InsertText(aCompositionString.GetUnicode());
mIMEBufferLength = aCompositionString.Length();
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
ps->GetCaret(getter_AddRefs(caretP));
caretP->SetCaretDOMSelection(selection);
caretP->GetCaretCoordinates(nsICaret::eTopLevelWindowCoordinates, selection,
&(aReply->mCursorPosition), &(aReply->mCursorIsCollapsed));
// second part of 23558 fix:
if (aCompositionString.IsEmpty())
{
mIMETextNode = nsnull;
}
return result;
}
NS_IMETHODIMP
nsPlaintextEditor::GetReconversionString(nsReconversionEventReply* aReply)
{
nsresult res;
nsCOMPtr<nsISelection> selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
return (res == NS_OK) ? NS_ERROR_FAILURE : res;
// get the first range in the selection. Since it is
// unclear what to do if reconversion happens with a
// multirange selection, we will ignore any additional ranges.
nsCOMPtr<nsIDOMRange> range;
res = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(res) || !range)
return (res == NS_OK) ? NS_ERROR_FAILURE : res;
nsAutoString textValue;
res = range->ToString(textValue);
if (NS_FAILED(res))
return res;
aReply->mReconversionString = (PRUnichar*) nsMemory::Clone(textValue.GetUnicode(),
(textValue.Length() + 1) * sizeof(PRUnichar));
if (!aReply->mReconversionString)
return NS_ERROR_OUT_OF_MEMORY;
// delete the selection
res = DeleteSelection(eNone);
return res;
}
#ifdef XP_MAC
#pragma mark -
#pragma mark nsEditor overrides
#pragma mark -
#endif
/** All editor operations which alter the doc should be prefaced
* with a call to StartOperation, naming the action and direction */
NS_IMETHODIMP
nsPlaintextEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
{
nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection
if (mRules) return mRules->BeforeEdit(mAction, mDirection);
return NS_OK;
}
/** All editor operations which alter the doc should be followed
* with a call to EndOperation */
NS_IMETHODIMP
nsPlaintextEditor::EndOperation()
{
// post processing
nsresult res = NS_OK;
if (mRules) res = mRules->AfterEdit(mAction, mDirection);
nsEditor::EndOperation(); // will clear mAction, mDirection
return res;
}
NS_IMETHODIMP
nsPlaintextEditor::SelectEntireDocument(nsISelection *aSelection)
{
nsresult res;
if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
// get body node
nsCOMPtr<nsIDOMElement>bodyElement;
res = GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode>bodyNode = do_QueryInterface(bodyElement);
if (!bodyNode) return NS_ERROR_FAILURE;
// is doc empty?
PRBool bDocIsEmpty;
res = mRules->DocumentIsEmpty(&bDocIsEmpty);
if (NS_FAILED(res)) return res;
if (bDocIsEmpty)
{
// if its empty dont select entire doc - that would select the bogus node
return aSelection->Collapse(bodyNode, 0);
}
else
{
return nsEditor::SelectEntireDocument(aSelection);
}
return res;
}
#ifdef XP_MAC
#pragma mark -
#pragma mark Random methods
#pragma mark -
#endif
NS_IMETHODIMP nsPlaintextEditor::GetLayoutObject(nsIDOMNode *aNode, nsISupports **aLayoutObject)
{
nsresult result = NS_ERROR_FAILURE; // we return an error unless we get the index
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
if ((nsnull!=aNode))
{ // get the content interface
nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aNode) );
if (nodeAsContent)
{ // get the frame from the content interface
//Note: frames are not ref counted, so don't use an nsCOMPtr
*aLayoutObject = nsnull;
result = ps->GetLayoutObjectFor(nodeAsContent, aLayoutObject);
}
}
else {
result = NS_ERROR_NULL_POINTER;
}
return result;
}
#ifdef XP_MAC
#pragma mark -
#endif
NS_IMETHODIMP
nsPlaintextEditor::IsRootTag(nsString &aTag, PRBool &aIsTag)
{
static char bodyTag[] = "body";
static char tdTag[] = "td";
static char thTag[] = "th";
static char captionTag[] = "caption";
if (aTag.EqualsIgnoreCase(bodyTag) ||
aTag.EqualsIgnoreCase(tdTag) ||
aTag.EqualsIgnoreCase(thTag) ||
aTag.EqualsIgnoreCase(captionTag) )
{
aIsTag = PR_TRUE;
}
else {
aIsTag = PR_FALSE;
}
return NS_OK;
}
#ifdef XP_MAC
#pragma mark -
#endif
void nsPlaintextEditor::HandleEventListenerError()
{
if (gNoisy) { printf("failed to add event listener\n"); }
// null out the nsCOMPtrs
mKeyListenerP = nsnull;
mMouseListenerP = nsnull;
mTextListenerP = nsnull;
mDragListenerP = nsnull;
mCompositionListenerP = nsnull;
mFocusListenerP = nsnull;
}
#ifdef XP_MAC
#pragma mark -
#endif