/* -*- 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 mListener = do_QueryInterface(mRules); RemoveEditActionListener(mListener); nsCOMPtr 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)) { nsCOMPtrdomdoc; result = GetDocument(getter_AddRefs(domdoc)); if (NS_SUCCEEDED(result) && domdoc) { nsAutoString newMetaString; nsCOMPtrmetaList; nsCOMPtrmetaNode; nsCOMPtrmetaElement; 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) { nsCOMPtrheadList; nsCOMPtrheadNode; nsCOMPtrresultNode; 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 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 *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *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 node = *aInOutParent; PRInt32 theOffset = *aInOutOffset; nsCOMPtr nodeAsText = do_QueryInterface(node); nsAutoString brType; brType.AssignWithConversion("br"); nsCOMPtr brNode; if (nodeAsText) { nsCOMPtr 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 selection; nsCOMPtr parent; PRInt32 offset; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; nsCOMPtr 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 *outBRNode, EDirection aSelect) { nsCOMPtr parent = aNode; PRInt32 offset = aOffset; return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); } NS_IMETHODIMP nsPlaintextEditor::InsertBR(nsCOMPtr *outBRNode) { PRBool bCollapsed; nsCOMPtr 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 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 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 startNode, endNode, parentNode; PRInt32 startOffset, endOffset; aSelection->GetAnchorNode(getter_AddRefs(startNode)); aSelection->GetAnchorOffset(&startOffset); aSelection->GetFocusNode(getter_AddRefs(endNode)); aSelection->GetFocusOffset(&endOffset); nsCOMPtr enumerator; nsCOMPtr selection(aSelection); nsCOMPtr 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 currentItem; nsresult findParentResult = enumerator->CurrentItem(getter_AddRefs(currentItem)); if ((NS_SUCCEEDED(findParentResult)) && (currentItem)) { nsCOMPtr 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 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; nsCOMPtrtextNode; nsCOMPtrblockParentContent = do_QueryInterface(aInCommonParentNode); iter->Init(blockParentContent); // loop through the content iterator for each content node nsCOMPtr content; result = iter->CurrentNode(getter_AddRefs(content)); while (NS_ENUMERATOR_FALSE == iter->IsDone()) { textNode = do_QueryInterface(content); if (textNode) { nsCOMPtrcurrentNode = 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 end"); return result; } nsresult nsPlaintextEditor::GetDOMEventReceiver(nsIDOMEventReceiver **aEventReceiver) { if (!aEventReceiver) return NS_ERROR_NULL_POINTER; *aEventReceiver = 0; nsCOMPtr 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 content = do_QueryInterface(rootElement); if (content) { nsCOMPtr 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 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 bodyElement; nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(res)) return res; if (!bodyElement) return NS_ERROR_NULL_POINTER; nsCOMPtr bodyNode = do_QueryInterface(bodyElement); nsCOMPtr 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 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 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
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 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 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 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 nsCOMPtrparent; 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 nsCOMPtrnextNode; newNode->GetNextSibling(getter_AddRefs(nextNode)); if (nextNode) { nsCOMPtrnextTextNode; 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 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 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 nsCOMPtrbodyNode = do_QueryInterface(bodyElement); if (!bodyNode) { return NS_ERROR_NULL_POINTER; } PRInt32 numBodyChildren=0; nsCOMPtrlastChild; 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 body; nsresult res = GetRootElement(getter_AddRefs(body)); if (NS_FAILED(res)) return res; nsCOMPtr content = do_QueryInterface(body); nsIFrame *frame; if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr 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 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 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 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 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 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 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 ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; return ps->DoCopy(); } NS_IMETHODIMP nsPlaintextEditor::CanCopy(PRBool &aCanCopy) { aCanCopy = PR_FALSE; nsCOMPtr 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 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 docEncoder (do_CreateInstance(formatType, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 range (do_CreateInstance(kCRangeCID, &rv)); if (NS_FAILED(rv)) return rv; if (!range) return NS_ERROR_FAILURE; nsCOMPtr selection (do_CreateInstance(kCDOMSelectionCID, &rv)); if (NS_FAILED(rv)) return rv; if (!selection) return NS_ERROR_FAILURE; // get the independent selection interface nsCOMPtr indSel = do_QueryInterface(selection); if (indSel) indSel->SetPresShell(presShell); nsCOMPtr 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 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 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 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 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 ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; ps->GetCaret(getter_AddRefs(caretP)); caretP->GetWindowRelativeCoordinates(aReply->mCursorPosition,aReply->mCursorIsCollapsed,selection); // second part of 23558 fix: if (aCompositionString.IsEmpty()) { mIMETextNode = nsnull; } return result; } NS_IMETHODIMP nsPlaintextEditor::GetReconversionString(nsReconversionEventReply* aReply) { nsresult res; nsCOMPtr 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 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 nsCOMPtrbodyElement; res = GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(res)) return res; nsCOMPtrbodyNode = 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 ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; if ((nsnull!=aNode)) { // get the content interface nsCOMPtr 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