180034 editor should respect the select_all style 183836 New list item should not reset inline styles 179384 Merging blocks via forward delete sends selection to front of document 98434 IME does not work correctly at the last characters in the text field (patch courtesy of Shotaro Kamio) r=jfrancis,brade,cmanske sr=kin git-svn-id: svn://10.0.0.236/trunk@135560 18797224-902f-48f8-a5cc-f745e15eee43
2199 lines
66 KiB
C++
2199 lines
66 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* 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 the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Daniel Glazman <glazman@netscape.com>
|
|
*
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the NPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the NPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
#include "nsPlaintextEditor.h"
|
|
#include "nsICaret.h"
|
|
#include "nsTextEditUtils.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 "nsIDOM3EventTarget.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 "nsGUIEvent.h"
|
|
#include "nsIDOMEventGroup.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 "nsIURL.h"
|
|
#include "nsIComponentManager.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsIDocumentEncoder.h"
|
|
#include "nsIDOMDocumentFragment.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIImage.h"
|
|
#include "nsXPCOM.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
|
|
// netwerk
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
|
|
// Misc
|
|
#include "nsEditorUtils.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsUnicharUtils.h"
|
|
|
|
#include "nsAOLCiter.h"
|
|
#include "nsInternetCiter.h"
|
|
|
|
// Drag & Drop, Clipboard
|
|
//#include "nsWidgetsCID.h"
|
|
#include "nsIClipboard.h"
|
|
#include "nsITransferable.h"
|
|
//#include "nsIDragService.h"
|
|
//#include "nsIDOMNSUIEvent.h"
|
|
|
|
const PRUnichar nbsp = 160;
|
|
|
|
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
|
|
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
|
|
static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID);
|
|
// Drag & Drop, Clipboard Support
|
|
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
|
static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID);
|
|
|
|
#if defined(NS_DEBUG) && defined(DEBUG_buster)
|
|
static PRBool gNoisy = PR_FALSE;
|
|
#else
|
|
static const PRBool gNoisy = PR_FALSE;
|
|
#endif
|
|
|
|
// prototype for rules creation shortcut
|
|
nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
|
|
|
|
nsPlaintextEditor::nsPlaintextEditor()
|
|
: nsEditor()
|
|
, mIgnoreSpuriousDragEvent(PR_FALSE)
|
|
, mRules(nsnull)
|
|
, mWrapToWindow(PR_FALSE)
|
|
, mWrapColumn(0)
|
|
, mMaxTextLength(-1)
|
|
, mInitTriggerCounter(0)
|
|
{
|
|
// Done in nsEditor
|
|
// NS_INIT_ISUPPORTS();
|
|
}
|
|
|
|
nsPlaintextEditor::~nsPlaintextEditor()
|
|
{
|
|
// 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);
|
|
|
|
// Remove event listeners. Note that if we had an HTML editor,
|
|
// it installed its own instead of these
|
|
nsCOMPtr<nsIDOMEventReceiver> erP;
|
|
nsresult result = GetDOMEventReceiver(getter_AddRefs(erP));
|
|
if (NS_SUCCEEDED(result) && erP)
|
|
{
|
|
nsCOMPtr<nsIDOM3EventTarget> dom3Targ(do_QueryInterface(erP));
|
|
nsCOMPtr<nsIDOMEventGroup> sysGroup;
|
|
if (NS_SUCCEEDED(erP->GetSystemEventGroup(getter_AddRefs(sysGroup)))) {
|
|
result = dom3Targ->RemoveGroupedEventListener(NS_LITERAL_STRING("keypress"), mKeyListenerP, PR_FALSE, sysGroup);
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (aIID.Equals(NS_GET_IID(nsIEditor))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIEditor*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(NS_GET_IID(nsIEditorMailSupport))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIEditorMailSupport*, 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 nsAString & 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) {
|
|
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;
|
|
|
|
nsAutoString currentValue;
|
|
if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("http-equiv"), currentValue))) continue;
|
|
|
|
if (FindInReadable(NS_LITERAL_STRING("content-type"),
|
|
currentValue,
|
|
nsCaseInsensitiveStringComparator())) {
|
|
NS_NAMED_LITERAL_STRING(content, "content");
|
|
if (NS_FAILED(metaElement->GetAttribute(content, currentValue))) continue;
|
|
|
|
NS_NAMED_LITERAL_STRING(charsetEquals, "charset=");
|
|
nsAString::const_iterator originalStart, start, end;
|
|
originalStart = currentValue.BeginReading(start);
|
|
currentValue.EndReading(end);
|
|
if (FindInReadable(charsetEquals, start, end,
|
|
nsCaseInsensitiveStringComparator())) {
|
|
|
|
// set attribute to <original prefix> charset=text/html
|
|
result = nsEditor::SetAttribute(metaElement, content,
|
|
Substring(originalStart, start) +
|
|
charsetEquals + characterSet);
|
|
if (NS_SUCCEEDED(result))
|
|
newMetaCharset = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newMetaCharset) {
|
|
nsCOMPtr<nsIDOMNodeList>headList;
|
|
result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"),getter_AddRefs(headList));
|
|
if (NS_SUCCEEDED(result) && headList) {
|
|
nsCOMPtr<nsIDOMNode>headNode;
|
|
headList->Item(0, getter_AddRefs(headNode));
|
|
if (headNode) {
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
// Create a new meta charset tag
|
|
result = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode));
|
|
if (NS_FAILED(result))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Set attributes to the created element
|
|
if (resultNode && characterSet.Length() > 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)) {
|
|
// not undoable, undo should undo CreateNode
|
|
result = metaElement->SetAttribute(NS_LITERAL_STRING("content"),
|
|
NS_LITERAL_STRING("text/html;charset=") + characterSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
nsCOMPtr<nsIDOM3EventTarget> dom3Targ(do_QueryInterface(erP));
|
|
nsCOMPtr<nsIDOMEventGroup> sysGroup;
|
|
if (NS_SUCCEEDED(erP->GetSystemEventGroup(getter_AddRefs(sysGroup)))) {
|
|
result = dom3Targ->AddGroupedEventListener(NS_LITERAL_STRING("keypress"), mKeyListenerP, PR_FALSE, sysGroup);
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register key listener in system group");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
|
|
|
|
nsCOMPtr<nsIDOMDocument> doc;
|
|
GetDocument(getter_AddRefs(doc));
|
|
*aIsDocumentEditable = doc ? IsModifiable() : PR_FALSE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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 ctrlKey, altKey, metaKey;
|
|
|
|
if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
|
|
|
|
if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) &&
|
|
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, eTypedBreak);
|
|
}
|
|
else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
|
|
{
|
|
// pass escape keypresses through as empty strings: needed for ime support
|
|
nsString empty;
|
|
return TypedText(empty, eTypedText);
|
|
}
|
|
|
|
if (character && !altKey && !ctrlKey && !metaKey)
|
|
{
|
|
aKeyEvent->PreventDefault();
|
|
nsAutoString key(character);
|
|
return TypedText(key, 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 nsAString& 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);
|
|
NS_NAMED_LITERAL_STRING(brType, "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);
|
|
aAction = eNext;
|
|
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 nsAString &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 = &aStringToInsert;
|
|
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;
|
|
res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("BR"), 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::BeginComposition(nsTextEventReply* aReply)
|
|
{
|
|
if(mFlags & nsIPlaintextEditor::eEditorPasswordMask) {
|
|
if (mRules) {
|
|
nsIEditRules *p = mRules.get();
|
|
nsTextEditRules *textEditRules = NS_STATIC_CAST(nsTextEditRules *, p);
|
|
textEditRules->ResetIMETextPWBuf();
|
|
}
|
|
else {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
}
|
|
|
|
return nsEditor::BeginComposition(aReply);
|
|
}
|
|
|
|
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
|
|
//
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::GetWrapWidth(PRInt32 *aWrapColumn)
|
|
{
|
|
if (! aWrapColumn)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*aWrapColumn = mWrapColumn;
|
|
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;
|
|
|
|
mWrapColumn = aWrapColumn;
|
|
|
|
// Make sure we're a plaintext editor, otherwise we shouldn't
|
|
// do the rest of this.
|
|
PRUint32 flags = 0;
|
|
GetFlags(&flags);
|
|
if (!(flags & eEditorPlaintextMask))
|
|
return NS_OK;
|
|
|
|
// 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:
|
|
NS_NAMED_LITERAL_STRING(styleName, "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.Append(NS_LITERAL_STRING("; "));
|
|
}
|
|
|
|
// 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.
|
|
if ((flags & eEditorEnableWrapHackMask) && aWrapColumn >= 0)
|
|
styleValue.Append(NS_LITERAL_STRING("font-family: -moz-fixed; "));
|
|
|
|
// If "mail.compose.wrap_to_window_width" is set, and we're a mail editor,
|
|
// then remember our wrap width (for output purposes) but set the visual
|
|
// wrapping to window width.
|
|
// We may reset mWrapToWindow here, based on the pref's current value.
|
|
if (flags & eEditorMailMask)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPrefBranch> prefBranch =
|
|
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
|
|
if (NS_SUCCEEDED(rv))
|
|
prefBranch->GetBoolPref("mail.compose.wrap_to_window_width",
|
|
&mWrapToWindow);
|
|
}
|
|
|
|
// and now we're ready to set the new whitespace/wrapping style.
|
|
if (aWrapColumn > 0 && !mWrapToWindow) // Wrap to a fixed column
|
|
{
|
|
styleValue.Append(NS_LITERAL_STRING("white-space: -moz-pre-wrap; width: "));
|
|
styleValue.AppendInt(aWrapColumn);
|
|
styleValue.Append(NS_LITERAL_STRING("ch;"));
|
|
}
|
|
else if (mWrapToWindow || aWrapColumn == 0)
|
|
styleValue.Append(NS_LITERAL_STRING("white-space: -moz-pre-wrap;"));
|
|
else
|
|
styleValue.Append(NS_LITERAL_STRING("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)
|
|
{
|
|
nsAutoUpdateViewBatch beginViewBatching(this);
|
|
|
|
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;
|
|
|
|
nsAutoUpdateViewBatch beginViewBatching(this);
|
|
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_FAILED(res))
|
|
return res;
|
|
|
|
PRBool isCollapsed;
|
|
if (NS_SUCCEEDED(selection->GetIsCollapsed(&isCollapsed)) && isCollapsed)
|
|
return NS_OK; // just return ok so no JS error is thrown
|
|
|
|
res = Copy();
|
|
if (NS_SUCCEEDED(res))
|
|
res = DeleteSelection(eNone);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsPlaintextEditor::CanCut(PRBool *aCanCut)
|
|
{
|
|
if (!aCanCut)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*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)
|
|
{
|
|
if (!aCanCopy)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*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 nsAString& aFormatType,
|
|
PRUint32 aFlags,
|
|
const nsAString& 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.get(), &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.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 (!nsTextEditUtils::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(const nsAString& aFormatType,
|
|
PRUint32 aFlags,
|
|
nsAString& aOutputString)
|
|
{
|
|
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;
|
|
}
|
|
|
|
nsAutoString charsetStr;
|
|
rv = GetDocumentCharacterSet(charsetStr);
|
|
if(NS_FAILED(rv) || charsetStr.IsEmpty())
|
|
charsetStr = NS_LITERAL_STRING("ISO-8859-1");
|
|
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
rv = GetAndInitDocEncoder(aFormatType, aFlags, charsetStr, getter_AddRefs(encoder));
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
rv = encoder->EncodeToString(aOutputString);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::OutputToStream(nsIOutputStream* aOutputStream,
|
|
const nsAString& aFormatType,
|
|
const nsAString& 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 nsIEditorMailSupport overrides
|
|
#pragma mark -
|
|
#endif
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
|
|
{
|
|
return InsertText(aStringToInsert);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::PasteAsQuotation(PRInt32 aSelectionType)
|
|
{
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Create generic Transferable for getting the data
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
|
|
NS_GET_IID(nsITransferable),
|
|
(void**) getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// We only handle plaintext pastes here
|
|
trans->AddDataFlavor(kUnicodeMime);
|
|
|
|
// Get the Data from the clipboard
|
|
clipboard->GetData(trans, aSelectionType);
|
|
|
|
// Now we ask the transferable for the data
|
|
// it still owns the data, we just have a pointer to it.
|
|
// If it can't support a "text" output of the data the call will fail
|
|
nsCOMPtr<nsISupports> genericDataObj;
|
|
PRUint32 len = 0;
|
|
char* flav = 0;
|
|
rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj),
|
|
&len);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
#ifdef DEBUG_akkana
|
|
printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv);
|
|
#endif
|
|
return rv;
|
|
}
|
|
#ifdef DEBUG_clipboard
|
|
printf("Got flavor [%s]\n", flav);
|
|
#endif
|
|
nsAutoString flavor; flavor.AssignWithConversion(flav);
|
|
nsAutoString stuffToPaste;
|
|
if (flavor.Equals(NS_LITERAL_STRING(kUnicodeMime)))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.Assign ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertAsQuotation(stuffToPaste, 0);
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
}
|
|
nsCRT::free(flav);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// Utility routine to make a new citer. This addrefs, of course.
|
|
static nsICiter* MakeACiter()
|
|
{
|
|
// Make a citer of an appropriate type
|
|
nsICiter* citer = 0;
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPrefBranch> prefBranch =
|
|
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv)) return 0;
|
|
|
|
char *citationType = 0;
|
|
rv = prefBranch->GetCharPref("mail.compose.citationType", &citationType);
|
|
|
|
if (NS_SUCCEEDED(rv) && citationType[0])
|
|
{
|
|
if (!strncmp(citationType, "aol", 3))
|
|
citer = new nsAOLCiter;
|
|
else
|
|
citer = new nsInternetCiter;
|
|
PL_strfree(citationType);
|
|
}
|
|
else
|
|
citer = new nsInternetCiter;
|
|
|
|
if (citer)
|
|
NS_ADDREF(citer);
|
|
return citer;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::InsertAsQuotation(const nsAString& aQuotedText,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
// We have the text. Cite it appropriately:
|
|
nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
|
|
|
|
// Let the citer quote it for us:
|
|
nsString quotedStuff;
|
|
nsresult rv = citer->GetCiteString(aQuotedText, quotedStuff);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// It's best to put a blank line after the quoted text so that mails
|
|
// written without thinking won't be so ugly.
|
|
if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != PRUnichar('\n')))
|
|
quotedStuff.Append(PRUnichar('\n'));
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
rv = InsertText(quotedStuff);
|
|
|
|
// XXX Should set *aNodeInserted to the first node inserted
|
|
if (aNodeInserted && NS_SUCCEEDED(rv))
|
|
{
|
|
*aNodeInserted = 0;
|
|
//NS_IF_ADDREF(*aNodeInserted);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::PasteAsCitedQuotation(const nsAString& aCitation,
|
|
PRInt32 aSelectionType)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
|
|
const nsAString& aCitation,
|
|
PRBool aInsertHTML,
|
|
const nsAString& aCharset,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
return InsertAsQuotation(aQuotedText, aNodeInserted);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::Rewrap(PRBool aRespectNewlines)
|
|
{
|
|
PRInt32 wrapCol;
|
|
nsresult rv = GetWrapWidth(&wrapCol);
|
|
if (NS_FAILED(rv))
|
|
return NS_OK;
|
|
|
|
// Rewrap makes no sense if there's no wrap column; default to 72.
|
|
if (wrapCol <= 0)
|
|
wrapCol = 72;
|
|
|
|
#ifdef DEBUG_akkana
|
|
printf("nsPlaintextEditor::Rewrap to %ld columns\n", (long)wrapCol);
|
|
#endif
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (!selection)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
PRBool isCollapsed;
|
|
rv = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Variables we'll need either way
|
|
NS_NAMED_LITERAL_STRING(format, "text/plain");
|
|
nsAutoString current;
|
|
nsString wrapped;
|
|
|
|
if (isCollapsed) // rewrap the whole document
|
|
{
|
|
rv = OutputToString(format,
|
|
nsIDocumentEncoder::OutputFormatted
|
|
| nsIDocumentEncoder::OutputLFLineBreak,
|
|
current);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!citer) return NS_ERROR_UNEXPECTED;
|
|
|
|
rv = citer->Rewrap(current, wrapCol, 0, aRespectNewlines, wrapped);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = SelectAll();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return InsertTextWithQuotations(wrapped);
|
|
}
|
|
else // rewrap only the selection
|
|
{
|
|
rv = OutputToString(format,
|
|
nsIDocumentEncoder::OutputFormatted
|
|
| nsIDocumentEncoder::OutputLFLineBreak
|
|
| nsIDocumentEncoder::OutputSelectionOnly,
|
|
current);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!citer) return NS_ERROR_UNEXPECTED;
|
|
|
|
PRUint32 firstLineOffset = 0; // XXX need to get this
|
|
rv = citer->Rewrap(current, wrapCol, firstLineOffset, aRespectNewlines,
|
|
wrapped);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return InsertTextWithQuotations(wrapped);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::StripCites()
|
|
{
|
|
#ifdef DEBUG_akkana
|
|
printf("nsPlaintextEditor::StripCites()\n");
|
|
#endif
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (!selection)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
PRBool isCollapsed;
|
|
rv = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Variables we'll need either way
|
|
NS_NAMED_LITERAL_STRING(format, "text/plain");
|
|
nsAutoString current;
|
|
nsString stripped;
|
|
|
|
if (isCollapsed) // rewrap the whole document
|
|
{
|
|
rv = OutputToString(format, nsIDocumentEncoder::OutputFormatted, current);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!citer) return NS_ERROR_UNEXPECTED;
|
|
|
|
rv = citer->StripCites(current, stripped);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = SelectAll();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return InsertText(stripped);
|
|
}
|
|
else // rewrap only the selection
|
|
{
|
|
rv = OutputToString(format,
|
|
nsIDocumentEncoder::OutputFormatted
|
|
| nsIDocumentEncoder::OutputSelectionOnly,
|
|
current);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsICiter> citer = dont_AddRef(MakeACiter());
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!citer) return NS_ERROR_UNEXPECTED;
|
|
|
|
rv = citer->StripCites(current, stripped);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return InsertText(stripped);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
|
|
{
|
|
*aNodeList = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark nsIEditorIMESupport overrides
|
|
#pragma mark -
|
|
#endif
|
|
|
|
NS_IMETHODIMP
|
|
nsPlaintextEditor::SetCompositionString(const nsAString& 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;
|
|
|
|
if (!mPresShellWeak)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
if (!ps)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// XXX_kin: BEGIN HACK! HACK! HACK!
|
|
// XXX_kin:
|
|
// XXX_kin: This is lame! The IME stuff needs caret coordinates
|
|
// XXX_kin: synchronously, but the editor could be using async
|
|
// XXX_kin: updates (reflows and paints) for performance reasons.
|
|
// XXX_kin: In order to give IME what it needs, we have to temporarily
|
|
// XXX_kin: switch to sync updating during this call so that the
|
|
// XXX_kin: nsAutoPlaceHolderBatch can force sync reflows, paints,
|
|
// XXX_kin: and selection scrolling, so that we get back accurate
|
|
// XXX_kin: caret coordinates.
|
|
|
|
PRUint32 flags = 0;
|
|
PRBool restoreFlags = PR_FALSE;
|
|
|
|
if (NS_SUCCEEDED(GetFlags(&flags)) &&
|
|
(flags & nsIPlaintextEditor::eEditorUseAsyncUpdatesMask))
|
|
{
|
|
if (NS_SUCCEEDED(SetFlags(flags & (~nsIPlaintextEditor::eEditorUseAsyncUpdatesMask))))
|
|
restoreFlags = PR_TRUE;
|
|
}
|
|
|
|
// XXX_kin: END HACK! HACK! HACK!
|
|
|
|
// we need the nsAutoPlaceHolderBatch destructor called before hitting
|
|
// GetCaretCoordinates so the states in Frame system sync with content
|
|
// therefore, we put the nsAutoPlaceHolderBatch into a inner block
|
|
{
|
|
nsAutoPlaceHolderBatch batch(this, gIMETxnName);
|
|
|
|
SetIsIMEComposing(); // We set mIsIMEComposing properly.
|
|
|
|
result = InsertText(aCompositionString);
|
|
|
|
mIMEBufferLength = aCompositionString.Length();
|
|
|
|
ps->GetCaret(getter_AddRefs(caretP));
|
|
caretP->SetCaretDOMSelection(selection);
|
|
|
|
// second part of 23558 fix:
|
|
if (aCompositionString.IsEmpty())
|
|
{
|
|
mIMETextNode = nsnull;
|
|
}
|
|
}
|
|
|
|
// XXX_kin: BEGIN HACK! HACK! HACK!
|
|
// XXX_kin:
|
|
// XXX_kin: Restore the previous set of flags!
|
|
|
|
if (restoreFlags)
|
|
SetFlags(flags);
|
|
|
|
// XXX_kin: END HACK! HACK! HACK!
|
|
|
|
result = caretP->GetCaretCoordinates(nsICaret::eIMECoordinates, selection,
|
|
&(aReply->mCursorPosition), &(aReply->mCursorIsCollapsed), nsnull);
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "cannot get caret position");
|
|
|
|
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.get(),
|
|
(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
|
|
|
|
nsresult
|
|
nsPlaintextEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
|
|
const nsAString & aAttribute,
|
|
const nsAString & aValue,
|
|
PRBool aSuppressTransaction)
|
|
{
|
|
return nsEditor::SetAttribute(aElement, aAttribute, aValue);
|
|
}
|
|
|
|
nsresult
|
|
nsPlaintextEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
|
|
const nsAString & aAttribute,
|
|
PRBool aSuppressTransaction)
|
|
{
|
|
return nsEditor::RemoveAttribute(aElement, aAttribute);
|
|
}
|