зеркало из https://github.com/mozilla/pjs.git
8072 строки
254 KiB
C++
8072 строки
254 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
*/
|
|
#include "nsICaret.h"
|
|
|
|
|
|
#include "nsHTMLEditor.h"
|
|
#include "nsHTMLEditRules.h"
|
|
#include "nsHTMLEditUtils.h"
|
|
|
|
#include "nsEditorEventListeners.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 "nsICSSLoader.h"
|
|
#include "nsICSSStyleSheet.h"
|
|
#include "nsIHTMLContentContainer.h"
|
|
#include "nsIDocumentObserver.h"
|
|
#include "nsIDocumentStateListener.h"
|
|
|
|
#include "nsIStyleContext.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 "nsAOLCiter.h"
|
|
#include "nsInternetCiter.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "InsertTextTxn.h"
|
|
|
|
// netwerk
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
|
|
// Drag & Drop, Clipboard
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsIClipboard.h"
|
|
#include "nsITransferable.h"
|
|
#include "nsIDragService.h"
|
|
#include "nsIDOMNSUIEvent.h"
|
|
|
|
// Transactionas
|
|
#include "PlaceholderTxn.h"
|
|
#include "nsStyleSheetTxns.h"
|
|
|
|
// Misc
|
|
#include "TextEditorTest.h"
|
|
#include "nsEditorUtils.h"
|
|
#include "nsIPref.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);
|
|
|
|
// Drag & Drop, Clipboard Support
|
|
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
|
static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID);
|
|
static NS_DEFINE_CID(kCDragServiceCID, NS_DRAGSERVICE_CID);
|
|
static NS_DEFINE_CID(kCHTMLFormatConverterCID, NS_HTMLFORMATCONVERTER_CID);
|
|
// private clipboard data flavors for html copy/paste
|
|
#define kHTMLContext "text/_moz_htmlcontext"
|
|
#define kHTMLInfo "text/_moz_htmlinfo"
|
|
|
|
|
|
#if defined(NS_DEBUG) && defined(DEBUG_buster)
|
|
static PRBool gNoisy = PR_FALSE;
|
|
#else
|
|
static const PRBool gNoisy = PR_FALSE;
|
|
#endif
|
|
|
|
// Some utilities to handle annoying overloading of "A" tag for link and named anchor
|
|
static char hrefText[] = "href";
|
|
static char anchorTxt[] = "anchor";
|
|
static char namedanchorText[] = "namedanchor";
|
|
nsIAtom *nsHTMLEditor::gTypingTxnName;
|
|
nsIAtom *nsHTMLEditor::gIMETxnName;
|
|
nsIAtom *nsHTMLEditor::gDeleteTxnName;
|
|
|
|
// some prototypes for rules creation shortcuts
|
|
nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
|
|
nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult);
|
|
|
|
#define IsLink(s) (s.EqualsIgnoreCase(hrefText))
|
|
#define IsNamedAnchor(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText))
|
|
|
|
static PRBool IsLinkNode(nsIDOMNode *aNode)
|
|
{
|
|
if (aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aNode);
|
|
if (anchor)
|
|
{
|
|
nsAutoString tmpText;
|
|
if (NS_SUCCEEDED(anchor->GetHref(tmpText)) && tmpText.GetUnicode() && tmpText.Length() != 0)
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
static PRBool IsNamedAnchorNode(nsIDOMNode *aNode)
|
|
{
|
|
if (aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aNode);
|
|
if (anchor)
|
|
{
|
|
nsAutoString tmpText;
|
|
if (NS_SUCCEEDED(anchor->GetName(tmpText)) && tmpText.GetUnicode() && tmpText.Length() != 0)
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
static PRBool IsListNode(nsIDOMNode *aNode)
|
|
{
|
|
if (aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(aNode);
|
|
if (element)
|
|
{
|
|
nsAutoString tagName;
|
|
if (NS_SUCCEEDED(element->GetTagName(tagName)))
|
|
{
|
|
tagName.ToLowerCase();
|
|
// With only 3 tests, it doesn't
|
|
// seem worth using nsAtoms
|
|
if (tagName.EqualsWithConversion("ol") ||
|
|
tagName.EqualsWithConversion("ul") ||
|
|
tagName.EqualsWithConversion("dl"))
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
static PRBool IsCellNode(nsIDOMNode *aNode)
|
|
{
|
|
if (aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(aNode);
|
|
if (element)
|
|
{
|
|
nsAutoString tagName;
|
|
if (NS_SUCCEEDED(element->GetTagName(tagName)))
|
|
{
|
|
// With only 2 tests, it doesn't
|
|
// seem worth using nsAtoms
|
|
if (tagName.EqualsIgnoreCase("td") ||
|
|
tagName.EqualsIgnoreCase("th"))
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsHTMLEditor::nsHTMLEditor()
|
|
: nsEditor()
|
|
, mIgnoreSpuriousDragEvent(PR_FALSE)
|
|
, mTypeInState(nsnull)
|
|
, mRules(nsnull)
|
|
, mIsComposing(PR_FALSE)
|
|
, mMaxTextLength(-1)
|
|
, mSelectedCellIndex(0)
|
|
{
|
|
// Done in nsEditor
|
|
// NS_INIT_REFCNT();
|
|
mBoldAtom = getter_AddRefs(NS_NewAtom("b"));
|
|
mItalicAtom = getter_AddRefs(NS_NewAtom("i"));
|
|
mUnderlineAtom = getter_AddRefs(NS_NewAtom("u"));
|
|
mFontAtom = getter_AddRefs(NS_NewAtom("font"));
|
|
mLinkAtom = getter_AddRefs(NS_NewAtom("a"));
|
|
|
|
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);
|
|
}
|
|
|
|
nsHTMLEditor::~nsHTMLEditor()
|
|
{
|
|
/* first, delete the transaction manager if there is one.
|
|
this will release any remaining transactions.
|
|
this is important because transactions can hold onto the atoms (gTypingTxnName, ...)
|
|
and to make the optimization (holding refcounted statics) work correctly,
|
|
the editor instance needs to hold the last refcount.
|
|
If you get this wrong, expect to deref a garbage gTypingTxnName pointer if you bring up a second editor.
|
|
*/
|
|
if (mTxnMgr) {
|
|
mTxnMgr = 0;
|
|
}
|
|
nsrefcnt refCount=0;
|
|
if (gTypingTxnName) // we addref'd in the constructor
|
|
{ // want to release it without nulling out the pointer.
|
|
refCount = gTypingTxnName->Release();
|
|
if (0==refCount) {
|
|
gTypingTxnName = nsnull;
|
|
}
|
|
}
|
|
|
|
if (gIMETxnName) // we addref'd in the constructor
|
|
{ // want to release it without nulling out the pointer.
|
|
refCount = gIMETxnName->Release();
|
|
if (0==refCount) {
|
|
gIMETxnName = nsnull;
|
|
}
|
|
}
|
|
|
|
if (gDeleteTxnName) // we addref'd in the constructor
|
|
{ // want to release it without nulling out the pointer.
|
|
refCount = gDeleteTxnName->Release();
|
|
if (0==refCount) {
|
|
gDeleteTxnName = nsnull;
|
|
}
|
|
}
|
|
|
|
// remove the rules as an action listener. Else we get a bad ownership loop later on.
|
|
// it's ok if the rules aren't a listener; we ignore the error.
|
|
nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
|
|
RemoveEditActionListener(mListener);
|
|
|
|
//the autopointers will clear themselves up.
|
|
//but we need to also remove the listeners or we have a leak
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult result = GetSelection(getter_AddRefs(selection));
|
|
// if we don't get the selection, just skip this
|
|
if (NS_SUCCEEDED(result) && selection)
|
|
{
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
nsCOMPtr<nsISelectionListener>listener;
|
|
listener = do_QueryInterface(mTypeInState);
|
|
if (listener) {
|
|
selPriv->RemoveSelectionListener(listener);
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMEventReceiver> erP;
|
|
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_IF_RELEASE(mTypeInState);
|
|
}
|
|
|
|
NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor)
|
|
NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor)
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::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(nsIHTMLEditor))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIHTMLEditor*, 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;
|
|
}
|
|
if (aIID.Equals(NS_GET_IID(nsITableEditor))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsITableEditor*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(NS_GET_IID(nsIEditorStyleSheets))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIEditorStyleSheets*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(NS_GET_IID(nsICSSLoaderObserver))) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsICSSLoaderObserver*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
|
|
return nsEditor::QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::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 result = NS_ERROR_NULL_POINTER;
|
|
// Init the base editor
|
|
result = nsEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
|
|
if (NS_FAILED(result)) { return result; }
|
|
|
|
// disable links
|
|
nsCOMPtr<nsIPresContext> context;
|
|
aPresShell->GetPresContext(getter_AddRefs(context));
|
|
if (!context) return NS_ERROR_NULL_POINTER;
|
|
if (!(mFlags & eEditorPlaintextMask))
|
|
context->SetLinkHandler(0);
|
|
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
result = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
|
|
if (NS_FAILED(result)) { return result; }
|
|
if (!bodyElement) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
// init the type-in state
|
|
mTypeInState = new TypeInState();
|
|
if (!mTypeInState) {return NS_ERROR_NULL_POINTER;}
|
|
NS_ADDREF(mTypeInState);
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) { return result; }
|
|
if (selection)
|
|
{
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
nsCOMPtr<nsISelectionListener>listener;
|
|
listener = do_QueryInterface(mTypeInState);
|
|
if (listener) {
|
|
selPriv->AddSelectionListener(listener);
|
|
}
|
|
}
|
|
|
|
// Set up a DTD XXX XXX
|
|
// HACK: This should have happened in a document specific way
|
|
// in nsEditor::Init(), but we dont' have a way to do that yet
|
|
result = nsComponentManager::CreateInstance(kCTransitionalDTDCID, nsnull,
|
|
NS_GET_IID(nsIDTD), getter_AddRefs(mDTD));
|
|
if (!mDTD) result = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
// Init the rules system
|
|
result = InitRules();
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
EnableUndo(PR_TRUE);
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetDocumentCharacterSet(const PRUnichar* characterSet)
|
|
{
|
|
nsresult result;
|
|
|
|
result = nsEditor::SetDocumentCharacterSet(characterSet);
|
|
|
|
// update META charset tag
|
|
if (NS_SUCCEEDED(result)) {
|
|
nsCOMPtr<nsIDOMDocument>domdoc;
|
|
result = GetDocument(getter_AddRefs(domdoc));
|
|
if (NS_SUCCEEDED(result) && domdoc) {
|
|
nsAutoString newMetaString;
|
|
nsCOMPtr<nsIDOMNodeList>metaList;
|
|
nsCOMPtr<nsIDOMNode>metaNode;
|
|
nsCOMPtr<nsIDOMElement>metaElement;
|
|
PRBool newMetaCharset = PR_TRUE;
|
|
|
|
// get a list of META tags
|
|
result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("meta"), getter_AddRefs(metaList));
|
|
if (NS_SUCCEEDED(result) && metaList) {
|
|
PRUint32 listLength = 0;
|
|
(void) metaList->GetLength(&listLength);
|
|
|
|
for (PRUint32 i = 0; i < listLength; i++) {
|
|
metaList->Item(i, getter_AddRefs(metaNode));
|
|
if (!metaNode) continue;
|
|
metaElement = do_QueryInterface(metaNode);
|
|
if (!metaElement) continue;
|
|
|
|
const NS_ConvertASCIItoUCS2 content("charset=");
|
|
nsString currentValue;
|
|
|
|
if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("http-equiv"), currentValue))) continue;
|
|
|
|
if (kNotFound != currentValue.Find("content-type", PR_TRUE)) {
|
|
if (NS_FAILED(metaElement->GetAttribute(NS_LITERAL_STRING("content"), currentValue))) continue;
|
|
|
|
PRInt32 offset = currentValue.Find(content.GetUnicode(), PR_TRUE);
|
|
if (kNotFound != offset) {
|
|
currentValue.Left(newMetaString, offset); // copy current value before "charset=" (e.g. text/html)
|
|
newMetaString.Append(content);
|
|
newMetaString.Append(characterSet);
|
|
result = nsEditor::SetAttribute(metaElement, NS_ConvertASCIItoUCS2("content"), newMetaString);
|
|
if (NS_SUCCEEDED(result))
|
|
newMetaCharset = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newMetaCharset) {
|
|
nsCOMPtr<nsIDOMNodeList>headList;
|
|
nsCOMPtr<nsIDOMNode>headNode;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
|
|
result = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"),getter_AddRefs(headList));
|
|
if (NS_SUCCEEDED(result) && headList) {
|
|
headList->Item(0, getter_AddRefs(headNode));
|
|
if (headNode) {
|
|
// Create a new meta charset tag
|
|
// can't use |NS_LITERAL_STRING| here until |CreateNode| is fixed to accept readables
|
|
result = CreateNode(NS_ConvertASCIItoUCS2("meta"), headNode, 0, getter_AddRefs(resultNode));
|
|
if (NS_FAILED(result))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Set attributes to the created element
|
|
if (resultNode && nsCRT::strlen(characterSet) > 0) {
|
|
metaElement = do_QueryInterface(resultNode);
|
|
if (metaElement) {
|
|
// not undoable, undo should undo CreateNode
|
|
result = metaElement->SetAttribute(NS_LITERAL_STRING("http-equiv"), NS_LITERAL_STRING("Content-Type"));
|
|
if (NS_SUCCEEDED(result)) {
|
|
newMetaString.AssignWithConversion("text/html;charset=");
|
|
newMetaString.Append(characterSet);
|
|
// not undoable, undo should undo CreateNode
|
|
result = metaElement->SetAttribute(NS_LITERAL_STRING("content"), newMetaString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::PostCreate()
|
|
{
|
|
nsresult result = InstallEventListeners();
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
result = nsEditor::PostCreate();
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InstallEventListeners()
|
|
{
|
|
NS_ASSERTION(mDocWeak, "no document set on this editor");
|
|
if (!mDocWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
nsresult result;
|
|
// get a key listener
|
|
result = NS_NewEditorKeyListener(getter_AddRefs(mKeyListenerP), this);
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// get a mouse listener
|
|
result = NS_NewEditorMouseListener(getter_AddRefs(mMouseListenerP), this);
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// get a text listener
|
|
result = NS_NewEditorTextListener(getter_AddRefs(mTextListenerP),this);
|
|
if (NS_FAILED(result)) {
|
|
#ifdef DEBUG_TAGUE
|
|
printf("nsTextEditor.cpp: failed to get TextEvent Listener\n");
|
|
#endif
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// get a composition listener
|
|
result = NS_NewEditorCompositionListener(getter_AddRefs(mCompositionListenerP),this);
|
|
if (NS_FAILED(result)) {
|
|
#ifdef DEBUG_TAGUE
|
|
printf("nsTextEditor.cpp: failed to get TextEvent Listener\n");
|
|
#endif
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// get a drag listener
|
|
result = NS_NewEditorDragListener(getter_AddRefs(mDragListenerP), this);
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// get a focus listener
|
|
result = NS_NewEditorFocusListener(getter_AddRefs(mFocusListenerP), this);
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMEventReceiver> erP;
|
|
result = GetDOMEventReceiver(getter_AddRefs(erP));
|
|
|
|
//end hack
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// register the event listeners with the DOM event reveiver
|
|
result = erP->AddEventListenerByIID(mKeyListenerP, NS_GET_IID(nsIDOMKeyListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register key listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register mouse listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mFocusListenerP, NS_GET_IID(nsIDOMFocusListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register focus listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mTextListenerP, NS_GET_IID(nsIDOMTextListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register text listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mCompositionListenerP, NS_GET_IID(nsIDOMCompositionListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register composition listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mDragListenerP, NS_GET_IID(nsIDOMDragListener));
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register drag listener");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetFlags(PRUint32 *aFlags)
|
|
{
|
|
if (!mRules || !aFlags) { return NS_ERROR_NULL_POINTER; }
|
|
return mRules->GetFlags(aFlags);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetFlags(PRUint32 aFlags)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NULL_POINTER; }
|
|
return mRules->SetFlags(aFlags);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InitRules()
|
|
{
|
|
// instantiate the rules for this text editor
|
|
// XXX: we should be told which set of rules to instantiate
|
|
nsresult res = NS_ERROR_FAILURE;
|
|
if (mFlags & eEditorPlaintextMask)
|
|
res = NS_NewTextEditRules(getter_AddRefs(mRules));
|
|
else
|
|
res = NS_NewHTMLEditRules(getter_AddRefs(mRules));
|
|
|
|
if (NS_FAILED(res)) return res;
|
|
if (!mRules) return NS_ERROR_UNEXPECTED;
|
|
res = mRules->Init(this, mFlags);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
PRBool nsHTMLEditor::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 nsHTMLEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
|
|
{
|
|
PRUint32 keyCode, character;
|
|
PRBool isShift, ctrlKey, altKey, metaKey;
|
|
nsresult res;
|
|
|
|
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)))
|
|
{
|
|
// this royally blows: because tabs come in from keyDowns instead
|
|
// of keyPress, and because GetCharCode refuses to work for keyDown
|
|
// i have to play games.
|
|
if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB) character = '\t';
|
|
else aKeyEvent->GetCharCode(&character);
|
|
|
|
if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB && !(mFlags&eEditorPlaintextBit))
|
|
{
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
PRInt32 offset;
|
|
nsCOMPtr<nsIDOMNode> node, blockParent;
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
|
|
if (IsBlockNode(node)) blockParent = node;
|
|
else blockParent = GetBlockNodeParent(node);
|
|
|
|
if (blockParent)
|
|
{
|
|
PRBool bHandled = PR_FALSE;
|
|
|
|
if (nsHTMLEditUtils::IsTableElement(blockParent))
|
|
res = TabInTable(isShift, &bHandled);
|
|
else if (nsHTMLEditUtils::IsListItem(blockParent))
|
|
{
|
|
nsAutoString indentstr;
|
|
if (isShift) indentstr.AssignWithConversion("outdent");
|
|
else indentstr.AssignWithConversion("indent");
|
|
res = Indent(indentstr);
|
|
bHandled = PR_TRUE;
|
|
}
|
|
if (NS_FAILED(res)) return res;
|
|
if (bHandled) return res;
|
|
}
|
|
}
|
|
else if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
|
|
|| keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
|
|
{
|
|
nsString empty;
|
|
if (isShift && !(mFlags&eEditorPlaintextBit))
|
|
{
|
|
return TypedText(empty.GetUnicode(), eTypedBR); // only inserts a br node
|
|
}
|
|
else
|
|
{
|
|
return TypedText(empty.GetUnicode(), eTypedBreak); // uses rules to figure out what to insert
|
|
}
|
|
}
|
|
else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
|
|
{
|
|
// pass escape keypresses through as empty strings: needed forime support
|
|
nsString empty;
|
|
return TypedText(empty.GetUnicode(), eTypedText);
|
|
}
|
|
|
|
// if we got here we either fell out of the tab case or have a normal character.
|
|
// Either way, treat as normal character.
|
|
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 nsHTMLEditor::TypedText(const PRUnichar* aString,
|
|
PRInt32 aAction)
|
|
{
|
|
nsAutoPlaceHolderBatch batch(this, gTypingTxnName);
|
|
|
|
switch (aAction)
|
|
{
|
|
case eTypedText:
|
|
{
|
|
return InsertText(aString);
|
|
}
|
|
case eTypedBR:
|
|
{
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
return InsertBR(&brNode); // only inserts a br node
|
|
}
|
|
case eTypedBreak:
|
|
{
|
|
return InsertLineBreak(); // uses rules to figure out what to insert
|
|
}
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled)
|
|
{
|
|
if (!outHandled) return NS_ERROR_NULL_POINTER;
|
|
*outHandled = PR_FALSE;
|
|
|
|
// Find enclosing table cell from the selection (cell may be the selected element)
|
|
nsCOMPtr<nsIDOMElement> cellElement;
|
|
// can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables
|
|
nsresult res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("td"), nsnull, getter_AddRefs(cellElement));
|
|
if (NS_FAILED(res)) return res;
|
|
// Do nothing -- we didn't find a table cell
|
|
if (!cellElement) return NS_OK;
|
|
|
|
// find enclosing table
|
|
nsCOMPtr<nsIDOMNode> tbl = GetEnclosingTable(cellElement);
|
|
if (!tbl) return res;
|
|
|
|
// advance to next cell
|
|
// first create an iterator over the table
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIContent> cTbl = do_QueryInterface(tbl);
|
|
nsCOMPtr<nsIContent> cBlock = do_QueryInterface(cellElement);
|
|
res = iter->Init(cTbl);
|
|
if (NS_FAILED(res)) return res;
|
|
// position iter at block
|
|
res = iter->PositionAt(cBlock);
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsIContent> cNode;
|
|
do
|
|
{
|
|
if (inIsShift) res = iter->Prev();
|
|
else res = iter->Next();
|
|
if (NS_FAILED(res)) break;
|
|
res = iter->CurrentNode(getter_AddRefs(cNode));
|
|
if (NS_FAILED(res)) break;
|
|
node = do_QueryInterface(cNode);
|
|
if (nsHTMLEditUtils::IsTableCell(node) && (GetEnclosingTable(node) == tbl))
|
|
{
|
|
res = CollapseSelectionToDeepestNonTableFirstChild(nsnull, node);
|
|
if (NS_FAILED(res)) return res;
|
|
*outHandled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
} while (iter->IsDone() == NS_ENUMERATOR_FALSE);
|
|
|
|
if (!(*outHandled) && !inIsShift)
|
|
{
|
|
// if we havent handled it yet then we must have run off the end of
|
|
// the table. Insert a new row.
|
|
res = InsertTableRow(1, PR_TRUE);
|
|
if (NS_FAILED(res)) return res;
|
|
*outHandled = PR_TRUE;
|
|
// put selection in right place
|
|
// Use table code to get selection and index to new row...
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsCOMPtr<nsIDOMElement> tblElement;
|
|
nsCOMPtr<nsIDOMElement> cell;
|
|
PRInt32 row;
|
|
res = GetCellContext(getter_AddRefs(selection),
|
|
getter_AddRefs(tblElement),
|
|
getter_AddRefs(cell),
|
|
nsnull, nsnull,
|
|
&row, nsnull);
|
|
if (NS_FAILED(res)) return res;
|
|
// ...so that we can ask for first cell in that row...
|
|
res = GetCellAt(tblElement, row, 0, *(getter_AddRefs(cell)));
|
|
if (NS_FAILED(res)) return res;
|
|
// ...and then set selection there.
|
|
// (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(),
|
|
// but we know cell is an empty new cell, so this works fine)
|
|
node = do_QueryInterface(cell);
|
|
if (node) selection->Collapse(node,0);
|
|
return NS_OK;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
|
|
{
|
|
if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
|
|
*outBRNode = nsnull;
|
|
nsresult res;
|
|
|
|
// we need to insert a br. unfortunately, we may have to split a text node to do it.
|
|
nsCOMPtr<nsIDOMNode> node = *aInOutParent;
|
|
PRInt32 theOffset = *aInOutOffset;
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
|
|
nsAutoString brType; brType.AssignWithConversion("br");
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
if (nodeAsText)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
PRInt32 offset;
|
|
PRUint32 len;
|
|
nodeAsText->GetLength(&len);
|
|
GetNodeLocation(node, &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, &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, &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 nsHTMLEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent = aNode;
|
|
PRInt32 offset = aOffset;
|
|
return CreateBRImpl(&parent, &offset, outBRNode, aSelect);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::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, &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, &selNode, &selOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = selection->Collapse(selNode, selOffset+1);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SetInlineProperty(nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (!aProperty) { return NS_ERROR_NULL_POINTER; }
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
ForceCompositionEnd();
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
return mTypeInState->SetProp(aProperty, *aAttribute, *aValue);
|
|
}
|
|
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kSetTextProperty);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!cancel && !handled)
|
|
{
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
PRInt32 startOffset, endOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
// there are up to three phases here. There are all the nodes
|
|
// reported by the subtree iterator to be processed. And there
|
|
// are potentially a starting textnode and an ending textnode
|
|
// which are only partially contained by the range.
|
|
|
|
// lets handle the nodes reported by the iterator. These nodes
|
|
// are entirely contained in the selection range. We build up
|
|
// a list of them (since doing operations on the document during
|
|
// iteration would perturb the iterator).
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
res = iter->Init(range);
|
|
// init returns an error if no nodes in range.
|
|
// this can easily happen with the subtree
|
|
// iterator if the selection doesn't contain
|
|
// any *whole* nodes.
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
// first check the start parent of the range to see if it needs to
|
|
// be seperately handled (it does if it's a text node, due to how the
|
|
// subtree iterator works - it will not have reported it).
|
|
if (IsTextNode(startNode) && IsEditable(startNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
PRInt32 startOffset;
|
|
PRUint32 textLen;
|
|
range->GetStartOffset(&startOffset);
|
|
nodeAsText->GetLength(&textLen);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, textLen, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// then loop through the list, set the property on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
|
|
// last check the end parent of the range to see if it needs to
|
|
// be seperately handled (it does if it's a text node, due to how the
|
|
// subtree iterator works - it will not have reported it).
|
|
if (IsTextNode(endNode) && IsEditable(endNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(endNode);
|
|
PRInt32 endOffset;
|
|
range->GetEndOffset(&endOffset);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, 0, endOffset, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::SetInlinePropertyOnTextNode( nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset,
|
|
nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (!aTextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// dont need to do anything if no characters actually selected
|
|
if (aStartOffset == aEndOffset) return NS_OK;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp, node = do_QueryInterface(aTextNode);
|
|
|
|
// dont need to do anything if property already set on node
|
|
PRBool bHasProp;
|
|
nsCOMPtr<nsIDOMNode> styleNode;
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, bHasProp, getter_AddRefs(styleNode));
|
|
if (bHasProp) return NS_OK;
|
|
|
|
// do we need to split the text node?
|
|
PRUint32 textLen;
|
|
aTextNode->GetLength(&textLen);
|
|
|
|
if ( (PRUint32)aEndOffset != textLen )
|
|
{
|
|
// we need to split off back of text node
|
|
res = SplitNode(node, aEndOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
node = tmp; // remember left node
|
|
}
|
|
if ( aStartOffset )
|
|
{
|
|
// we need to split off front of text node
|
|
res = SplitNode(node, aStartOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// reparent the node inside inline node with appropriate {attribute,value}
|
|
res = SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue);
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::SetInlinePropertyOnNode( nsIDOMNode *aNode,
|
|
nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (!aNode || !aProperty) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString tag;
|
|
aProperty->ToString(tag);
|
|
tag.ToLowerCase();
|
|
|
|
// dont need to do anything if property already set on node
|
|
PRBool bHasProp;
|
|
nsCOMPtr<nsIDOMNode> styleNode;
|
|
IsTextPropertySetByContent(aNode, aProperty, aAttribute, aValue, bHasProp, getter_AddRefs(styleNode));
|
|
if (bHasProp) return NS_OK;
|
|
|
|
// is it already the right kind of node, but with wrong attribute?
|
|
if (NodeIsType(aNode, aProperty))
|
|
{
|
|
// just set the attribute on it.
|
|
// but first remove any contrary style in it's children.
|
|
res = RemoveStyleInside(aNode, aProperty, aAttribute, PR_TRUE);
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
return SetAttribute(elem, *aAttribute, *aValue);
|
|
}
|
|
|
|
// can it be put inside inline node?
|
|
if (TagCanContain(tag, aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> priorNode, nextNode;
|
|
// is either of it's neighbors the right kind of node?
|
|
GetPriorHTMLSibling(aNode, &priorNode);
|
|
GetNextHTMLSibling(aNode, &nextNode);
|
|
if (priorNode && NodeIsType(priorNode, aProperty) &&
|
|
HasAttrVal(priorNode, aAttribute, aValue) &&
|
|
IsOnlyAttribute(priorNode, aAttribute) )
|
|
{
|
|
// previous sib is already right kind of inline node; slide this over into it
|
|
res = MoveNode(aNode, priorNode, -1);
|
|
}
|
|
else if (nextNode && NodeIsType(nextNode, aProperty) &&
|
|
HasAttrVal(nextNode, aAttribute, aValue) &&
|
|
IsOnlyAttribute(priorNode, aAttribute) )
|
|
{
|
|
// following sib is already right kind of inline node; slide this over into it
|
|
res = MoveNode(aNode, nextNode, 0);
|
|
}
|
|
else
|
|
{
|
|
// ok, chuck it in it's very own container
|
|
res = InsertContainerAbove(aNode, &tmp, tag, aAttribute, aValue);
|
|
}
|
|
if (NS_FAILED(res)) return res;
|
|
return RemoveStyleInside(aNode, aProperty, aAttribute);
|
|
}
|
|
// none of the above? then cycle through the children.
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = aNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (childNodes)
|
|
{
|
|
PRInt32 j;
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
if (childCount)
|
|
{
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// populate the list
|
|
for (j=0 ; j < (PRInt32)childCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(j, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode) && IsEditable(childNode))
|
|
{
|
|
isupports = do_QueryInterface(childNode);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
}
|
|
|
|
// then loop through the list, set the property on each node
|
|
PRUint32 listCount;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < (PRInt32)listCount; j++)
|
|
{
|
|
isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::SplitStyleAboveRange(nsIDOMRange *inRange,
|
|
nsIAtom *aProperty,
|
|
const nsString *aAttribute)
|
|
{
|
|
if (!inRange) return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode, origStartNode;
|
|
PRInt32 startOffset, endOffset, origStartOffset;
|
|
|
|
res = inRange->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
origStartNode = startNode;
|
|
origStartOffset = startOffset;
|
|
PRBool sameNode = (startNode==endNode);
|
|
|
|
// split any matching style nodes above the start of range
|
|
res = SplitStyleAbovePoint(&startNode, &startOffset, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (sameNode && (startNode != origStartNode))
|
|
{
|
|
// our startNode got split. This changes the offset of the end of our range.
|
|
endOffset -= origStartOffset;
|
|
}
|
|
|
|
// second verse, same as the first...
|
|
res = SplitStyleAbovePoint(&endNode, &endOffset, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// reset the range
|
|
res = inRange->SetStart(startNode, startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->SetEnd(endNode, endOffset);
|
|
return res;
|
|
}
|
|
|
|
nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsIDOMNode> *aNode,
|
|
PRInt32 *aOffset,
|
|
nsIAtom *aProperty, // null here means we split all properties
|
|
const nsString *aAttribute,
|
|
nsCOMPtr<nsIDOMNode> *outLeftNode,
|
|
nsCOMPtr<nsIDOMNode> *outRightNode)
|
|
{
|
|
if (!aNode || !*aNode || !aOffset) return NS_ERROR_NULL_POINTER;
|
|
if (outLeftNode) *outLeftNode = nsnull;
|
|
if (outRightNode) *outRightNode = nsnull;
|
|
// split any matching style nodes above the node/offset
|
|
nsCOMPtr<nsIDOMNode> parent, tmp = *aNode;
|
|
PRInt32 offset;
|
|
while (tmp && !nsHTMLEditUtils::IsBody(tmp))
|
|
{
|
|
if ( (aProperty && NodeIsType(tmp, aProperty)) || // node is the correct inline prop
|
|
(aProperty == nsIEditProperty::href && IsLinkNode(tmp)) || // node is href - test if really <a href=...
|
|
(!aProperty && NodeIsProperty(tmp)) ) // or node is any prop, and we asked to split them all
|
|
{
|
|
// found a style node we need to split
|
|
SplitNodeDeep(tmp, *aNode, *aOffset, &offset, PR_FALSE, outLeftNode, outRightNode);
|
|
// reset startNode/startOffset
|
|
tmp->GetParentNode(getter_AddRefs(*aNode));
|
|
*aOffset = offset;
|
|
}
|
|
tmp->GetParentNode(getter_AddRefs(parent));
|
|
tmp = parent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!IsContainer(aNode)) return PR_FALSE;
|
|
if (!IsEditable(aNode)) return PR_FALSE;
|
|
if (!IsInlineNode(aNode)) return PR_FALSE;
|
|
if (NodeIsType(aNode, nsIEditProperty::a)) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode,
|
|
nsIAtom *aProperty, // null here means remove all properties
|
|
const nsString *aAttribute,
|
|
PRBool aChildrenOnly)
|
|
{
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
if (IsTextNode(aNode)) return NS_OK;
|
|
nsresult res = NS_OK;
|
|
|
|
// first process the children
|
|
nsCOMPtr<nsIDOMNode> child, tmp;
|
|
aNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
// cache next sibling since we might remove child
|
|
child->GetNextSibling(getter_AddRefs(tmp));
|
|
res = RemoveStyleInside(child, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
child = tmp;
|
|
}
|
|
|
|
// then process the node itself
|
|
if ( !aChildrenOnly &&
|
|
((aProperty && NodeIsType(aNode, aProperty)) || // node is prop we asked for
|
|
(aProperty == nsIEditProperty::href && IsLinkNode(aNode))) || // but check for link (<a href=...)
|
|
(!aProperty && NodeIsProperty(aNode)) ) // or node is any prop and we asked for that
|
|
{
|
|
// if we weren't passed an attribute, then we want to
|
|
// remove any matching inlinestyles entirely
|
|
if (!aAttribute || aAttribute->IsEmpty())
|
|
{
|
|
res = RemoveContainer(aNode);
|
|
}
|
|
// otherwise we just want to eliminate the attribute
|
|
else
|
|
{
|
|
if (HasAttr(aNode, aAttribute))
|
|
{
|
|
// if this matching attribute is the ONLY one on the node,
|
|
// then remove the whole node. Otherwise just nix the attribute.
|
|
if (IsOnlyAttribute(aNode, aAttribute))
|
|
{
|
|
res = RemoveContainer(aNode);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return NS_ERROR_NULL_POINTER;
|
|
res = RemoveAttribute(elem, *aAttribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode,
|
|
const nsString *aAttribute)
|
|
{
|
|
if (!aNode || !aAttribute) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
|
|
if (!content) return PR_FALSE; // ooops
|
|
|
|
PRInt32 attrCount, i, nameSpaceID;
|
|
nsCOMPtr<nsIAtom> attrName, prefix;
|
|
content->GetAttributeCount(attrCount);
|
|
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
nsAutoString attrString, tmp;
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's the attribute we know about, keep looking
|
|
if (attrString.EqualsIgnoreCase(*aAttribute)) continue;
|
|
// if it's a special _moz... attribute, keep looking
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so return false
|
|
return PR_FALSE;
|
|
}
|
|
// if we made it through all of them without finding a real attribute
|
|
// other than aAttribute, then return PR_TRUE
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool
|
|
nsHTMLEditor::HasMatchingAttributes(nsIDOMNode *aNode1,
|
|
nsIDOMNode *aNode2)
|
|
{
|
|
if (!aNode1 || !aNode2) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content1 = do_QueryInterface(aNode1);
|
|
if (!content1) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content2 = do_QueryInterface(aNode2);
|
|
if (!content2) return PR_FALSE; // ooops
|
|
|
|
PRInt32 attrCount, i, nameSpaceID, realCount1=0, realCount2=0;
|
|
nsCOMPtr<nsIAtom> attrName, prefix;
|
|
nsresult res, res2;
|
|
content1->GetAttributeCount(attrCount);
|
|
nsAutoString attrString, tmp, attrVal1, attrVal2;
|
|
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content1->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's a special _moz... attribute, keep going
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so count it
|
|
realCount1++;
|
|
// and compare it to element2's attributes
|
|
res = content1->GetAttribute(nameSpaceID, attrName, attrVal1);
|
|
res2 = content2->GetAttribute(nameSpaceID, attrName, attrVal2);
|
|
if (res != res2) return PR_FALSE;
|
|
if (!attrVal1.EqualsIgnoreCase(attrVal2)) return PR_FALSE;
|
|
}
|
|
|
|
content2->GetAttributeCount(attrCount);
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content2->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's a special _moz... attribute, keep going
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so count it
|
|
realCount2++;
|
|
}
|
|
|
|
if (realCount1 != realCount2) return PR_FALSE;
|
|
// otherwise, attribute counts match, and we already compared them
|
|
// when going through the first list, so we're done.
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::HasAttr(nsIDOMNode *aNode,
|
|
const nsString *aAttribute)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!aAttribute || aAttribute->IsEmpty()) return PR_TRUE; // everybody has the 'null' attribute
|
|
|
|
// get element
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return PR_FALSE;
|
|
|
|
// get attribute node
|
|
nsCOMPtr<nsIDOMAttr> attNode;
|
|
nsresult res = elem->GetAttributeNode(*aAttribute, getter_AddRefs(attNode));
|
|
if ((NS_FAILED(res)) || !attNode) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
PRBool nsHTMLEditor::HasAttrVal(nsIDOMNode *aNode,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!aAttribute || aAttribute->IsEmpty()) return PR_TRUE; // everybody has the 'null' attribute
|
|
|
|
// get element
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return PR_FALSE;
|
|
|
|
// get attribute node
|
|
nsCOMPtr<nsIDOMAttr> attNode;
|
|
nsresult res = elem->GetAttributeNode(*aAttribute, getter_AddRefs(attNode));
|
|
if ((NS_FAILED(res)) || !attNode) return PR_FALSE;
|
|
|
|
// check if attribute has a value
|
|
PRBool isSet;
|
|
attNode->GetSpecified(&isSet);
|
|
// if no value, and that's what we wanted, then return true
|
|
if (!isSet && (!aValue || aValue->IsEmpty())) return PR_TRUE;
|
|
|
|
// get attribute value
|
|
nsAutoString attrVal;
|
|
attNode->GetValue(attrVal);
|
|
|
|
// do values match?
|
|
if (attrVal.EqualsIgnoreCase(*aValue)) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::PromoteInlineRange(nsIDOMRange *inRange)
|
|
{
|
|
if (!inRange) return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode, parent;
|
|
PRInt32 startOffset, endOffset;
|
|
|
|
res = inRange->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while ( startNode &&
|
|
!nsHTMLEditUtils::IsBody(startNode) &&
|
|
IsAtFrontOfNode(startNode, startOffset) )
|
|
{
|
|
res = GetNodeLocation(startNode, &parent, &startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
startNode = parent;
|
|
}
|
|
if (!startNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
while ( endNode &&
|
|
!nsHTMLEditUtils::IsBody(endNode) &&
|
|
IsAtEndOfNode(endNode, endOffset) )
|
|
{
|
|
res = GetNodeLocation(endNode, &parent, &endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
endNode = parent;
|
|
endOffset++; // we are AFTER this node
|
|
}
|
|
if (!endNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
res = inRange->SetStart(startNode, startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->SetEnd(endNode, endOffset);
|
|
return res;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, PRInt32 aOffset)
|
|
{
|
|
if (!aNode) return PR_FALSE; // oops
|
|
if (!aOffset) return PR_TRUE;
|
|
|
|
if (IsTextNode(aNode))
|
|
{
|
|
return PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> firstNode;
|
|
GetFirstEditableChild(aNode, &firstNode);
|
|
if (!firstNode) return PR_TRUE;
|
|
PRInt32 offset;
|
|
nsEditor::GetChildOffset(firstNode, aNode, offset);
|
|
if (offset < aOffset) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, PRInt32 aOffset)
|
|
{
|
|
if (!aNode) return PR_FALSE; // oops
|
|
PRUint32 len;
|
|
GetLengthOfDOMNode(aNode, len);
|
|
if (aOffset == (PRInt32)len) return PR_TRUE;
|
|
|
|
if (IsTextNode(aNode))
|
|
{
|
|
return PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> lastNode;
|
|
GetLastEditableChild(aNode, &lastNode);
|
|
if (!lastNode) return PR_TRUE;
|
|
PRInt32 offset;
|
|
nsEditor::GetChildOffset(lastNode, aNode, offset);
|
|
if (offset < aOffset) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue,
|
|
PRBool &aFirst,
|
|
PRBool &aAny,
|
|
PRBool &aAll)
|
|
{
|
|
return GetInlinePropertyWithAttrValue( aProperty, aAttribute, aValue, aFirst, aAny, aAll, nsnull);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue,
|
|
PRBool &aFirst,
|
|
PRBool &aAny,
|
|
PRBool &aAll,
|
|
nsString *outValue)
|
|
{
|
|
if (!aProperty)
|
|
return NS_ERROR_NULL_POINTER;
|
|
/*
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("nsTextEditor::GetTextProperty %s\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
*/
|
|
nsresult result;
|
|
aAny=PR_FALSE;
|
|
aAll=PR_TRUE;
|
|
aFirst=PR_FALSE;
|
|
PRBool first=PR_TRUE;
|
|
nsCOMPtr<nsISelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
nsCOMPtr<nsIDOMNode> collapsedNode;
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!enumerator) return NS_ERROR_NULL_POINTER;
|
|
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
result = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
// XXX: should be a while loop, to get each separate range
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(result)) && currentItem)
|
|
{
|
|
PRBool firstNodeInRange = PR_TRUE; // for each range, set a flag
|
|
nsCOMPtr<nsIDOMRange> range(do_QueryInterface(currentItem));
|
|
|
|
if (isCollapsed)
|
|
{
|
|
// efficiency hack. we cache prior results for being collapsed in a given text node.
|
|
// this speeds up typing. Note that other parts of the editor code have to clear out
|
|
// this cache after certain actions.
|
|
range->GetStartContainer(getter_AddRefs(collapsedNode));
|
|
if (!collapsedNode) return NS_ERROR_FAILURE;
|
|
// refresh the cache if we need to
|
|
if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode);
|
|
// cache now current, use it! But override it with typeInState results if any...
|
|
PRBool isSet, theSetting;
|
|
if (aAttribute)
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty, *aAttribute, outValue);
|
|
else
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
aFirst = aAny = aAll = theSetting;
|
|
return NS_OK;
|
|
}
|
|
/*
|
|
if (aProperty == mBoldAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
aFirst = aAny = aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
aFirst = aAny = aAll = mCachedBoldStyle;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
else if (aProperty == mItalicAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
aFirst = aAny = aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
aFirst = aAny = aAll = mCachedItalicStyle;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
else if (aProperty == mUnderlineAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
aFirst = aAny = aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
aFirst = aAny = aAll = mCachedUnderlineStyle;
|
|
}
|
|
return NS_OK;
|
|
} */
|
|
}
|
|
|
|
// either non-collapsed selection or no cached value: do it the hard way
|
|
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;
|
|
|
|
iter->Init(range);
|
|
nsCOMPtr<nsIContent> content;
|
|
nsAutoString firstValue, theValue;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
//if (gNoisy) { printf(" checking node %p\n", content.get()); }
|
|
nsCOMPtr<nsIDOMCharacterData>text;
|
|
text = do_QueryInterface(content);
|
|
PRBool skipNode = PR_FALSE;
|
|
if (text)
|
|
{
|
|
if (!isCollapsed && first && firstNodeInRange)
|
|
{
|
|
firstNodeInRange = PR_FALSE;
|
|
PRInt32 startOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
PRUint32 count;
|
|
text->GetLength(&count);
|
|
if (startOffset==(PRInt32)count)
|
|
{
|
|
//if (gNoisy) { printf(" skipping node %p\n", content.get()); }
|
|
skipNode = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // handle non-text leaf nodes here
|
|
PRBool canContainChildren;
|
|
content->CanContainChildren(canContainChildren);
|
|
if (canContainChildren)
|
|
{
|
|
//if (gNoisy) { printf(" skipping non-leaf node %p\n", content.get()); }
|
|
skipNode = PR_TRUE;
|
|
}
|
|
else {
|
|
//if (gNoisy) { printf(" testing non-text leaf node %p\n", content.get()); }
|
|
}
|
|
}
|
|
if (!skipNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>node;
|
|
node = do_QueryInterface(content);
|
|
if (node)
|
|
{
|
|
PRBool isSet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
if (first)
|
|
{
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, getter_AddRefs(resultNode), &firstValue);
|
|
aFirst = isSet;
|
|
first = PR_FALSE;
|
|
if (outValue) *outValue = firstValue;
|
|
}
|
|
else
|
|
{
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, getter_AddRefs(resultNode), &theValue);
|
|
if (firstValue != theValue)
|
|
aAll = PR_FALSE;
|
|
}
|
|
|
|
if (isSet) {
|
|
aAny = PR_TRUE;
|
|
}
|
|
else {
|
|
aAll = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
result = iter->Next();
|
|
if (NS_FAILED(result))
|
|
break;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
}
|
|
if (!aAny)
|
|
{ // make sure that if none of the selection is set, we don't report all is set
|
|
aAll = PR_FALSE;
|
|
}
|
|
//if (gNoisy) { printf(" returning first=%d any=%d all=%d\n", aFirst, aAny, aAll); }
|
|
return result;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties()
|
|
{
|
|
return RemoveInlinePropertyImpl(nsnull, nsnull);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsString *aAttribute)
|
|
{
|
|
return RemoveInlinePropertyImpl(aProperty, aAttribute);
|
|
}
|
|
|
|
nsresult nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom *aProperty, const nsString *aAttribute)
|
|
{
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
ForceCompositionEnd();
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
|
|
// For links, aProperty uses "href", use "a" instead
|
|
if (aProperty == nsIEditProperty::href)
|
|
aProperty = nsIEditProperty::a;
|
|
|
|
if (aProperty) return mTypeInState->ClearProp(aProperty, *aAttribute);
|
|
else return mTypeInState->ClearAllProps();
|
|
}
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpRemoveTextProperty, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveTextProperty);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!cancel && !handled)
|
|
{
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// remove this style from ancestors of our range endpoints,
|
|
// splitting them as appropriate
|
|
res = SplitStyleAboveRange(range, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
// we're done with this range!
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
iter->Init(range);
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// loop through the list, remove the property on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = RemoveStyleInside(node, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize()
|
|
{
|
|
return RelativeFontChange(1);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize()
|
|
{
|
|
return RelativeFontChange(-1);
|
|
}
|
|
|
|
nsresult nsHTMLEditor::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
|
|
nsHTMLEditor::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
|
|
nsHTMLEditor::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
|
|
nsHTMLEditor::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);
|
|
return CollapseSelectionToDeepestNonTableFirstChild(nsnull, bodyNode);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
if (aSelection)
|
|
{
|
|
selection = aSelection;
|
|
} else {
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_FAILURE;
|
|
}
|
|
nsCOMPtr<nsIDOMNode> node = aNode;
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
|
|
do {
|
|
node->GetFirstChild(getter_AddRefs(child));
|
|
|
|
if (child)
|
|
{
|
|
// Stop if we find a table
|
|
// don't want to go into nested tables
|
|
if (nsHTMLEditUtils::IsTable(child)) break;
|
|
// hey, it'g gotta be a container too!
|
|
if (!IsContainer(child)) break;
|
|
node = child;
|
|
}
|
|
}
|
|
while (child);
|
|
|
|
selection->Collapse(node,0);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DeleteSelection(nsIEditor::EDirection aAction)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
nsresult result;
|
|
|
|
// delete placeholder txns merge.
|
|
nsAutoPlaceHolderBatch batch(this, gDeleteTxnName);
|
|
nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
|
|
|
|
// If it's one of these modes,
|
|
// we have to extend the selection first.
|
|
// This needs to happen inside selection batching,
|
|
// otherwise the deleted text is autocopied to the clipboard.
|
|
if (aAction == eNextWord || aAction == ePreviousWord
|
|
|| aAction == eToBeginningOfLine || aAction == eToEndOfLine)
|
|
{
|
|
if (!mSelConWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsISelectionController> selCont (do_QueryReferent(mSelConWeak));
|
|
if (!selCont)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
|
|
switch (aAction)
|
|
{
|
|
case eNextWord:
|
|
result = selCont->WordMove(PR_TRUE, PR_TRUE);
|
|
// DeleteSelectionImpl doesn't handle these actions
|
|
// because it's inside batching, so don't confuse it:
|
|
aAction = eNone;
|
|
break;
|
|
case ePreviousWord:
|
|
result = selCont->WordMove(PR_FALSE, PR_TRUE);
|
|
aAction = eNone;
|
|
break;
|
|
case eToBeginningOfLine:
|
|
selCont->IntraLineMove(PR_TRUE, PR_FALSE); // try to move to end
|
|
result = selCont->IntraLineMove(PR_FALSE, PR_TRUE); // select to beginning
|
|
aAction = eNone;
|
|
break;
|
|
case eToEndOfLine:
|
|
result = selCont->IntraLineMove(PR_TRUE, PR_TRUE);
|
|
// Bugs 54449/54452: the selection jumps to the wrong place
|
|
// when deleting past a <br> and action is eNext or ePrev,
|
|
// so setting action to eNone makes delete-to-end marginally usable.
|
|
// aAction should really be set to eNext
|
|
aAction = eNone;
|
|
break;
|
|
default: // avoid several compiler warnings
|
|
result = NS_OK;
|
|
break;
|
|
}
|
|
if (NS_FAILED(result))
|
|
{
|
|
#ifdef DEBUG
|
|
printf("Selection controller interface didn't work!\n");
|
|
#endif
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// pre-process
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kDeleteSelection);
|
|
ruleInfo.collapsedAction = aAction;
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!cancel && !handled)
|
|
{
|
|
result = DeleteSelectionImpl(aAction);
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertText(const PRUnichar* aStringToInsert)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
PRInt32 theAction = nsTextEditRules::kInsertText;
|
|
PRInt32 opID = kOpInsertText;
|
|
if (mInIMEMode)
|
|
{
|
|
theAction = nsTextEditRules::kInsertTextIME;
|
|
opID = kOpInsertIMEText;
|
|
}
|
|
nsAutoPlaceHolderBatch batch(this, nsnull);
|
|
nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
nsresult result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsAutoString resultString;
|
|
// XXX can we trust instring to outlive ruleInfo,
|
|
// XXX and ruleInfo not to refer to instring in its dtor?
|
|
nsAutoString instring(aStringToInsert);
|
|
nsTextRulesInfo ruleInfo(theAction);
|
|
ruleInfo.inString = &instring;
|
|
ruleInfo.outString = &resultString;
|
|
ruleInfo.maxLength = mMaxTextLength;
|
|
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!cancel && !handled)
|
|
{
|
|
// we rely on rules code for now - no default implementation
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static nsCOMPtr<nsIDOMNode> GetListParent(nsIDOMNode* aNode)
|
|
{
|
|
if (!aNode) return nsnull;
|
|
nsCOMPtr<nsIDOMNode> parent, tmp;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsList(parent)) return parent;
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
parent = tmp;
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
static nsCOMPtr<nsIDOMNode> GetTableParent(nsIDOMNode* aNode)
|
|
{
|
|
if (!aNode) return nsnull;
|
|
nsCOMPtr<nsIDOMNode> parent, tmp;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsTable(parent)) return parent;
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
parent = tmp;
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsString& aInputString)
|
|
{
|
|
nsAutoString charset;
|
|
return InsertHTMLWithCharset(aInputString, charset);
|
|
}
|
|
|
|
nsresult nsHTMLEditor::InsertHTMLWithContext(const nsString& aInputString, const nsString& aContextStr, const nsString& aInfoStr)
|
|
{
|
|
nsAutoString charset;
|
|
return InsertHTMLWithCharsetAndContext(aInputString, charset, aContextStr, aInfoStr);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertHTMLWithCharset(const nsString& aInputString, const nsString& aCharset)
|
|
{
|
|
return InsertHTMLWithCharsetAndContext(aInputString, aCharset, nsAutoString(), nsAutoString());
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::InsertHTMLWithCharsetAndContext(const nsString& aInputString,
|
|
const nsString& aCharset,
|
|
const nsString& aContextStr,
|
|
const nsString& aInfoStr)
|
|
{
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// First, make sure there are no return chars in the document.
|
|
// Bad things happen if you insert returns (instead of dom newlines, \n)
|
|
// into an editor document.
|
|
nsAutoString inputString (aInputString); // hope this does copy-on-write
|
|
|
|
// Windows linebreaks: Map CRLF to LF:
|
|
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r\n"),
|
|
NS_ConvertASCIItoUCS2("\n"));
|
|
|
|
// Mac linebreaks: Map any remaining CR to LF:
|
|
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r"),
|
|
NS_ConvertASCIItoUCS2("\n"));
|
|
|
|
// force IME commit; set up rules sniffing and batching
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpHTMLPaste, nsIEditor::eNext);
|
|
|
|
// Get selection
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Get the first range in the selection, for context:
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
|
|
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
|
|
if (!nsrange)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
|
|
// create a dom document fragment that represents the structure to paste
|
|
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
|
|
res = nsrange->CreateContextualFragment(inputString,
|
|
getter_AddRefs(docfrag));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
|
|
|
|
res = StripFormattingNodes(fragmentAsNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if we have context info, create a fragment for that too
|
|
nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
|
|
nsCOMPtr<nsIDOMNode> contextLeaf;
|
|
PRInt32 contextDepth = 0;
|
|
if (aContextStr.Length())
|
|
{
|
|
res = nsrange->CreateContextualFragment(aContextStr,
|
|
getter_AddRefs(contextfrag));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> contextAsNode (do_QueryInterface(contextfrag));
|
|
res = StripFormattingNodes(contextAsNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// cache the deepest leaf in the context
|
|
nsCOMPtr<nsIDOMNode> junk, child, tmp = contextAsNode;
|
|
while (tmp)
|
|
{
|
|
contextDepth++;
|
|
contextLeaf = tmp;
|
|
contextLeaf->GetFirstChild(getter_AddRefs(tmp));
|
|
}
|
|
// unite the two trees
|
|
contextLeaf->AppendChild(fragmentAsNode, getter_AddRefs(junk));
|
|
fragmentAsNode = contextAsNode;
|
|
// no longer have fragmentAsNode in tree
|
|
contextDepth--;
|
|
}
|
|
|
|
// get the infoString contents
|
|
nsAutoString numstr1, numstr2;
|
|
PRInt32 rangeStartHint, rangeEndHint;
|
|
if (aInfoStr.Length())
|
|
{
|
|
PRInt32 err, sep;
|
|
sep = aInfoStr.FindChar((PRUnichar)',');
|
|
aInfoStr.Left(numstr1, sep);
|
|
aInfoStr.Mid(numstr2, sep+1, -1);
|
|
rangeStartHint = numstr1.ToInteger(&err) + contextDepth;
|
|
rangeEndHint = numstr2.ToInteger(&err) + contextDepth;
|
|
}
|
|
else
|
|
{
|
|
rangeStartHint = contextDepth;
|
|
rangeEndHint = contextDepth;
|
|
}
|
|
|
|
// make a list of what nodes in docFrag we need to move
|
|
|
|
// First off create a range over the portion of docFrag indicated by
|
|
// the range hints.
|
|
nsCOMPtr<nsIDOMRange> docFragRange;
|
|
docFragRange = do_CreateInstance(kCRangeCID);
|
|
nsCOMPtr<nsIDOMNode> startParent, endParent, tmp;
|
|
PRInt32 endOffset;
|
|
startParent = fragmentAsNode;
|
|
while (rangeStartHint > 0)
|
|
{
|
|
startParent->GetFirstChild(getter_AddRefs(tmp));
|
|
startParent = tmp;
|
|
rangeStartHint--;
|
|
NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
|
|
}
|
|
endParent = fragmentAsNode;
|
|
while (rangeEndHint > 0)
|
|
{
|
|
endParent->GetLastChild(getter_AddRefs(tmp));
|
|
endParent = tmp;
|
|
rangeEndHint--;
|
|
NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE);
|
|
}
|
|
res = GetLengthOfDOMNode(endParent, (PRUint32&)endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
res = docFragRange->SetStart(startParent, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = docFragRange->SetEnd(endParent, endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// now use a subtree iterator over the range to create a list of nodes
|
|
nsTrivialFunctor functor;
|
|
nsDOMSubtreeIterator iter;
|
|
nsCOMPtr<nsISupportsArray> nodeList;
|
|
res = NS_NewISupportsArray(getter_AddRefs(nodeList));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.Init(docFragRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, nodeList);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// are there any table elements in the list?
|
|
|
|
// node and offset for insertion
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
PRInt32 offsetOfNewNode;
|
|
|
|
// check for table cell selection mode
|
|
PRBool cellSelectionMode = PR_FALSE;
|
|
nsCOMPtr<nsIDOMElement> cell;
|
|
res = GetFirstSelectedCell(getter_AddRefs(cell), nsnull);
|
|
if (NS_SUCCEEDED(res) && cell)
|
|
{
|
|
cellSelectionMode = PR_TRUE;
|
|
}
|
|
|
|
if (cellSelectionMode)
|
|
{
|
|
// do we have table content to paste? If so, we want to delete
|
|
// the selected table cells and replace with new table elements;
|
|
// but if not we want to delete _contents_ of cells and replace
|
|
// with non-table elements. Use cellSelectionMode bool to
|
|
// indicate results.
|
|
nsCOMPtr<nsISupports> isupports = nodeList->ElementAt(0);
|
|
nsCOMPtr<nsIDOMNode> firstNode( do_QueryInterface(isupports) );
|
|
if (!nsHTMLEditUtils::IsTableElement(firstNode))
|
|
cellSelectionMode = PR_FALSE;
|
|
}
|
|
|
|
if (!cellSelectionMode)
|
|
{
|
|
res = DeleteSelectionAndPrepareToCreateNode(parentNode, offsetOfNewNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// pasting does not inherit local inline styles
|
|
res = RemoveAllInlineProperties();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// delete whole cells: we will replace with new table content
|
|
if (1)
|
|
{
|
|
// Save current selection since DeleteTableCell perturbs it
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
res = DeleteTableCell(1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// colapse selection to beginning of deleted table content
|
|
selection->CollapseToStart();
|
|
}
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
// The rules code (WillDoAction above) might have changed the selection.
|
|
// refresh our memory...
|
|
res = GetStartNodeAndOffset(selection, &parentNode, &offsetOfNewNode);
|
|
if (!parentNode) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// are we in a text node? If so, split it.
|
|
if (IsTextNode(parentNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
res = SplitNodeDeep(parentNode, parentNode, offsetOfNewNode, &offsetOfNewNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = parentNode->GetParentNode(getter_AddRefs(temp));
|
|
if (NS_FAILED(res)) return res;
|
|
parentNode = temp;
|
|
}
|
|
|
|
// build up list of parents of first node in lst that are either:
|
|
// lists, or tables.
|
|
nsCOMPtr<nsISupports> isup = nodeList->ElementAt(0);
|
|
nsCOMPtr<nsIDOMNode> pNode( do_QueryInterface(isup) );
|
|
nsCOMPtr<nsISupportsArray> listAndTableArray;
|
|
res = NS_NewISupportsArray(getter_AddRefs(listAndTableArray));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
while (pNode)
|
|
{
|
|
if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
|
|
{
|
|
isup = do_QueryInterface(pNode);
|
|
listAndTableArray->AppendElement(isup);
|
|
}
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
pNode->GetParentNode(getter_AddRefs(parent));
|
|
pNode = parent;
|
|
}
|
|
|
|
// remember number of lists and tables above us
|
|
PRUint32 listAndTableParents;
|
|
PRInt32 highWaterMark = -1;
|
|
listAndTableArray->Count(&listAndTableParents);
|
|
|
|
PRUint32 listCount, j;
|
|
if (listAndTableParents)
|
|
{
|
|
// scan insertion list for table elements (other than table).
|
|
nodeList->Count(&listCount);
|
|
for (j=0; j<listCount; j++)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = nodeList->ElementAt(j);
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
|
|
|
|
NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
|
|
if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> theTable = GetTableParent(curNode);
|
|
if (theTable)
|
|
{
|
|
nsCOMPtr<nsISupports> isupTable(do_QueryInterface(theTable));
|
|
PRInt32 indexT = listAndTableArray->IndexOf(isupTable);
|
|
if (indexT >= 0)
|
|
{
|
|
highWaterMark = indexT;
|
|
if ((PRUint32)highWaterMark == listAndTableParents-1) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> theList = GetListParent(curNode);
|
|
if (theList)
|
|
{
|
|
nsCOMPtr<nsISupports> isupList(do_QueryInterface(theList));
|
|
PRInt32 indexL = listAndTableArray->IndexOf(isupList);
|
|
if (indexL >= 0)
|
|
{
|
|
highWaterMark = indexL;
|
|
if ((PRUint32)highWaterMark == listAndTableParents-1) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if we have pieces of tables or lists to be inserted, let's force the paste
|
|
// to deal with table elements right away, so that it doesn't orphan some
|
|
// table or list contents outside the table or list.
|
|
if (highWaterMark >= 0)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = listAndTableArray->ElementAt(highWaterMark);
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
|
|
nsCOMPtr<nsIDOMNode> replaceNode;
|
|
if (nsHTMLEditUtils::IsTable(curNode))
|
|
{
|
|
// look upward from curNode for a piece of this table
|
|
isup = nodeList->ElementAt(0);
|
|
pNode = do_QueryInterface(isup);
|
|
while (pNode)
|
|
{
|
|
if (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tableP = GetTableParent(pNode);
|
|
if (tableP == curNode)
|
|
{
|
|
replaceNode = pNode;
|
|
break;
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
pNode->GetParentNode(getter_AddRefs(parent));
|
|
pNode = parent;
|
|
}
|
|
}
|
|
else // list case
|
|
{
|
|
// look upward from curNode for a piece of this list
|
|
isup = nodeList->ElementAt(0);
|
|
pNode = do_QueryInterface(isup);
|
|
while (pNode)
|
|
{
|
|
if (nsHTMLEditUtils::IsListItem(pNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> listP = GetListParent(pNode);
|
|
if (listP == curNode)
|
|
{
|
|
replaceNode = pNode;
|
|
break;
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
pNode->GetParentNode(getter_AddRefs(parent));
|
|
pNode = parent;
|
|
}
|
|
}
|
|
|
|
if (replaceNode)
|
|
{
|
|
isupports = do_QueryInterface(replaceNode);
|
|
nodeList->ReplaceElementAt(isupports, 0);
|
|
// postprocess list to remove any descendants of this node
|
|
// so that we dont insert them twice.
|
|
do
|
|
{
|
|
isupports = nodeList->ElementAt(1);
|
|
tmp = do_QueryInterface(isupports);
|
|
if (tmp && nsHTMLEditUtils::IsDescendantOf(tmp, replaceNode))
|
|
nodeList->RemoveElementAt(1);
|
|
else
|
|
break;
|
|
} while(tmp);
|
|
}
|
|
}
|
|
|
|
// Loop over the node list and paste the nodes:
|
|
PRBool bDidInsert = PR_FALSE;
|
|
nsCOMPtr<nsIDOMNode> lastInsertNode, insertedContextParent;
|
|
nodeList->Count(&listCount);
|
|
for (j=0; j<listCount; j++)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = nodeList->ElementAt(j);
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
|
|
|
|
NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
|
|
NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
|
|
NS_ENSURE_TRUE(!nsHTMLEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
|
|
|
|
if (insertedContextParent)
|
|
{
|
|
// if we had to insert something higher up in the paste heirarchy, we want to
|
|
// skip any further paste nodes that descend from that. Else we will paste twice.
|
|
if (nsHTMLEditUtils::IsDescendantOf(curNode, insertedContextParent))
|
|
continue;
|
|
}
|
|
|
|
// give the user a hand on table element insertion. if they have
|
|
// a table or table row on the clipboard, and are trying to insert
|
|
// into a table or table row, insert the appropriate children instead.
|
|
if ( (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode))
|
|
&& (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::IsTable(parentNode)) )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
InsertNodeAtPoint(child, parentNode, offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = curNode;
|
|
offsetOfNewNode++;
|
|
}
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// try to insert
|
|
res = InsertNodeAtPoint(curNode, parentNode, offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = curNode;
|
|
}
|
|
|
|
// assume failure means no legal parent in the document heirarchy.
|
|
// try again with the parent of curNode in the paste heirarchy.
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
while (NS_FAILED(res) && curNode)
|
|
{
|
|
curNode->GetParentNode(getter_AddRefs(parent));
|
|
if (parent && !nsHTMLEditUtils::IsBody(parent))
|
|
{
|
|
res = InsertNodeAtPoint(parent, parentNode, offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
insertedContextParent = parent;
|
|
lastInsertNode = parent;
|
|
}
|
|
}
|
|
curNode = parent;
|
|
}
|
|
}
|
|
if (bDidInsert)
|
|
{
|
|
res = GetNodeLocation(lastInsertNode, &parentNode, &offsetOfNewNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
offsetOfNewNode++;
|
|
}
|
|
}
|
|
|
|
// Now collapse the selection to the end of what we just inserted:
|
|
if (lastInsertNode)
|
|
{
|
|
res = GetNodeLocation(lastInsertNode, &parentNode, &offsetOfNewNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
selection->Collapse(parentNode, offsetOfNewNode+1);
|
|
}
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
|
|
if (IsEmptyTextContent(content))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, ignored;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (parent)
|
|
{
|
|
res = parent->RemoveChild(aNode, getter_AddRefs(ignored));
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (!nsHTMLEditUtils::IsPre(aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
aNode->GetLastChild(getter_AddRefs(child));
|
|
|
|
while (child)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
child->GetPreviousSibling(getter_AddRefs(tmp));
|
|
res = StripFormattingNodes(child);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
child = tmp;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// This is mostly like InsertHTMLWithCharset,
|
|
// but we can't use that because it is selection-based and
|
|
// the rules code won't let us edit under the <head> node
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsString &aSourceToInsert)
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
ForceCompositionEnd();
|
|
|
|
// Do not use nsAutoRules -- rules code won't let us insert in <head>
|
|
// Use the head node as a parent and delete/insert directly
|
|
nsCOMPtr<nsIDOMNodeList>nodeList;
|
|
nsAutoString headTag; headTag.AssignWithConversion("head");
|
|
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
|
|
if (!doc) return NS_ERROR_NOT_INITIALIZED;
|
|
res = doc->GetElementsByTagName(headTag, getter_AddRefs(nodeList));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!nodeList) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRUint32 count;
|
|
nodeList->GetLength(&count);
|
|
if (count < 1) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMNode> headNode;
|
|
res = nodeList->Item(0, getter_AddRefs(headNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!headNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// First, make sure there are no return chars in the source.
|
|
// Bad things happen if you insert returns (instead of dom newlines, \n)
|
|
// into an editor document.
|
|
nsAutoString inputString (aSourceToInsert); // hope this does copy-on-write
|
|
|
|
// Windows linebreaks: Map CRLF to LF:
|
|
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r\n"),
|
|
NS_ConvertASCIItoUCS2("\n"));
|
|
|
|
// Mac linebreaks: Map any remaining CR to LF:
|
|
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r"),
|
|
NS_ConvertASCIItoUCS2("\n"));
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Get the first range in the selection, for context:
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
|
|
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
|
|
if (!nsrange)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
|
|
res = nsrange->CreateContextualFragment(inputString,
|
|
getter_AddRefs(docfrag));
|
|
|
|
//XXXX BUG 50965: This is not returning the text between <title> ... </title>
|
|
// Special code is needed in JS to handle title anyway, so it really doesn't matter!
|
|
|
|
if (NS_FAILED(res))
|
|
{
|
|
#ifdef DEBUG
|
|
printf("Couldn't create contextual fragment: error was %d\n", res);
|
|
#endif
|
|
return res;
|
|
}
|
|
if (!docfrag) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
|
|
// First delete all children in head
|
|
do {
|
|
res = headNode->GetFirstChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
if (child)
|
|
{
|
|
res = DeleteNode(child);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
} while (child);
|
|
|
|
// Now insert the new nodes
|
|
PRInt32 offsetOfNewNode = 0;
|
|
nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
|
|
|
|
// Loop over the contents of the fragment and move into the document
|
|
do {
|
|
res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
if (child)
|
|
{
|
|
res = InsertNode(child, headNode, offsetOfNewNode++);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
} while (child);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RebuildDocumentFromSource(const nsString& aSourceString)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
res = GetRootElement(getter_AddRefs(bodyElement));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!bodyElement) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Find where the <body> tag starts.
|
|
// If user mangled that, then abort
|
|
PRInt32 bodyStart = aSourceString.Find(NS_ConvertASCIItoUCS2("<body"), PR_TRUE);
|
|
if (bodyStart == -1) return NS_ERROR_FAILURE;
|
|
|
|
PRInt32 headStart = aSourceString.Find(NS_ConvertASCIItoUCS2("<head"), PR_TRUE);
|
|
if (headStart == -1) return NS_ERROR_FAILURE;
|
|
|
|
// Find the index after "<head>"
|
|
PRInt32 headEnd = aSourceString.Find(NS_ConvertASCIItoUCS2("</head"), PR_TRUE);
|
|
|
|
// We'll be forgiving and assume head ends before body
|
|
if (headEnd == -1) headEnd = bodyStart;
|
|
nsAutoString headString;
|
|
aSourceString.Mid(headString, headStart, (headEnd - headStart));
|
|
|
|
nsAutoString bodyString;
|
|
aSourceString.Mid(bodyString, bodyStart, (aSourceString.Length() - bodyStart - 1));
|
|
|
|
// Time to change the document
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
// Try to replace body contents first
|
|
res = SelectAll();
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
res = InsertHTML(bodyString);
|
|
if (NS_FAILED(res)) return res;
|
|
selection->Collapse(bodyElement, 0);
|
|
|
|
res = ReplaceHeadContentsWithHTML(headString);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Now we must copy attributes user might have edited on the <body> tag
|
|
// because InsertHTML (actually, CreateContextualFragment())
|
|
// will never return a body node in the DOM fragment
|
|
nsAutoString bodyTag;
|
|
// Truncate at the end of the body tag
|
|
PRInt32 bodyTagEnd = bodyString.FindChar((PRUnichar)'>', PR_FALSE, 5);
|
|
if (bodyTagEnd == -1) return NS_ERROR_FAILURE;
|
|
bodyString.Truncate(bodyTagEnd+1);
|
|
|
|
// Kludge of the year: fool the parser by replacing "body" with "div" so we get a node
|
|
bodyString.ToLowerCase();
|
|
bodyString.ReplaceSubstring(NS_ConvertASCIItoUCS2("body"), NS_ConvertASCIItoUCS2("div"));
|
|
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
|
|
if (!nsrange) return NS_ERROR_NO_INTERFACE;
|
|
|
|
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
|
|
res = nsrange->CreateContextualFragment(bodyString, getter_AddRefs(docfrag));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
|
|
if (!fragmentAsNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!child) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Copy all attributes from the div child to current body element
|
|
return CloneAttributes(bodyElement, child);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertLineBreak()
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertBreak, nsIEditor::eNext);
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
// pre-process
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertBreak);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!cancel && !handled)
|
|
{
|
|
// create the new BR node
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsAutoString tag; tag.AssignWithConversion("BR");
|
|
res = DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
|
|
if (!newNode) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// set the selection to the new node
|
|
nsCOMPtr<nsIDOMNode>parent;
|
|
res = newNode->GetParentNode(getter_AddRefs(parent));
|
|
if (!parent) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
PRInt32 offsetInParent=-1; // we use the -1 as a marker to see if we need to compute this or not
|
|
nsCOMPtr<nsIDOMNode>nextNode;
|
|
newNode->GetNextSibling(getter_AddRefs(nextNode));
|
|
if (nextNode)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData>nextTextNode;
|
|
nextTextNode = do_QueryInterface(nextNode);
|
|
if (!nextTextNode) {
|
|
nextNode = do_QueryInterface(newNode);
|
|
}
|
|
else {
|
|
offsetInParent=0;
|
|
}
|
|
}
|
|
else {
|
|
nextNode = do_QueryInterface(newNode);
|
|
}
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (!selection) res = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
if (-1==offsetInParent)
|
|
{
|
|
nextNode->GetParentNode(getter_AddRefs(parent));
|
|
res = GetChildOffset(nextNode, parent, offsetInParent);
|
|
if (NS_SUCCEEDED(res)) {
|
|
// SetInterlinePosition(PR_TRUE) means we want the caret to stick to the content on the "right".
|
|
// We want the caret to stick to whatever is past the break. This is
|
|
// because the break is on the same line we were on, but the next content
|
|
// will be on the following line.
|
|
selPriv->SetInterlinePosition(PR_TRUE);
|
|
res = selection->Collapse(parent, offsetInParent+1); // +1 to insert just after the break
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = selection->Collapse(nextNode, offsetInParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, PRBool aDeleteSelection)
|
|
{
|
|
nsresult res = NS_ERROR_NOT_INITIALIZED;
|
|
|
|
if (!aElement)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
|
|
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (!NS_SUCCEEDED(res) || !selection)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// hand off to the rules system, see if it has anything to say about this
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
ruleInfo.insertElement = aElement;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
if (!handled)
|
|
{
|
|
if (aDeleteSelection)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tempNode;
|
|
PRInt32 tempOffset;
|
|
nsresult result = DeleteSelectionAndPrepareToCreateNode(tempNode,tempOffset);
|
|
if (!NS_SUCCEEDED(result))
|
|
return result;
|
|
}
|
|
|
|
// If deleting, selection will be collapsed.
|
|
// so if not, we collapse it
|
|
if (!aDeleteSelection)
|
|
{
|
|
// Named Anchor is a special case,
|
|
// We collapse to insert element BEFORE the selection
|
|
// For all other tags, we insert AFTER the selection
|
|
if (IsNamedAnchorNode(node))
|
|
{
|
|
selection->CollapseToStart();
|
|
} else {
|
|
selection->CollapseToEnd();
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> parentSelectedNode;
|
|
PRInt32 offsetForInsert;
|
|
res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode));
|
|
// XXX: ERROR_HANDLING bad XPCOM usage
|
|
if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode)
|
|
{
|
|
#ifdef DEBUG_cmanske
|
|
{
|
|
nsAutoString name;
|
|
parentSelectedNode->GetNodeName(name);
|
|
printf("InsertElement: Anchor node of selection: ");
|
|
wprintf(name.GetUnicode());
|
|
printf(" Offset: %d\n", offsetForInsert);
|
|
}
|
|
#endif
|
|
|
|
res = InsertNodeAtPoint(node, parentSelectedNode, offsetForInsert, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// Set caret after element, but check for special case
|
|
// of inserting table-related elements: set in first cell instead
|
|
if (!SetCaretInTableCell(aElement))
|
|
res = SetCaretAfterElement(aElement);
|
|
|
|
}
|
|
}
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode,
|
|
nsIDOMNode *aParent,
|
|
PRInt32 aOffset,
|
|
PRBool aNoEmptyNodes)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
nsAutoString tagName;
|
|
aNode->GetNodeName(tagName);
|
|
tagName.ToLowerCase();
|
|
nsCOMPtr<nsIDOMNode> parent = aParent;
|
|
nsCOMPtr<nsIDOMNode> topChild = aParent;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
PRInt32 offsetOfInsert = aOffset;
|
|
|
|
// Search up the parent chain to find a suitable container
|
|
while (!CanContainTag(parent, tagName))
|
|
{
|
|
// If the current parent is a root (body or table element)
|
|
// then go no further - we can't insert
|
|
if (nsHTMLEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent))
|
|
return NS_ERROR_FAILURE;
|
|
// Get the next parent
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
|
|
topChild = parent;
|
|
parent = tmp;
|
|
}
|
|
if (parent != topChild)
|
|
{
|
|
// we need to split some levels above the original selection parent
|
|
res = SplitNodeDeep(topChild, aParent, aOffset, &offsetOfInsert, aNoEmptyNodes);
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
}
|
|
// Now we can insert the new node
|
|
res = InsertNode(aNode, parent, offsetOfInsert);
|
|
return res;
|
|
}
|
|
|
|
// XXX: error handling in this routine needs to be cleaned up!
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::DeleteSelectionAndCreateNode(const nsString& aTag,
|
|
nsIDOMNode ** aNewNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parentSelectedNode;
|
|
PRInt32 offsetOfNewNode;
|
|
nsresult result = DeleteSelectionAndPrepareToCreateNode(parentSelectedNode,
|
|
offsetOfNewNode);
|
|
if (!NS_SUCCEEDED(result))
|
|
return result;
|
|
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
result = CreateNode(aTag, parentSelectedNode, offsetOfNewNode,
|
|
getter_AddRefs(newNode));
|
|
// XXX: ERROR_HANDLING check result, and make sure aNewNode is set correctly in success/failure cases
|
|
*aNewNode = newNode;
|
|
NS_IF_ADDREF(*aNewNode);
|
|
|
|
// we want the selection to be just after the new node
|
|
nsCOMPtr<nsISelection> selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
result = selection->Collapse(parentSelectedNode, offsetOfNewNode+1);
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SelectElement(nsIDOMElement* aElement)
|
|
{
|
|
nsresult res = NS_ERROR_NULL_POINTER;
|
|
|
|
// Must be sure that element is contained in the document body
|
|
if (IsElementInBody(aElement))
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNode>parent;
|
|
res = aElement->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_SUCCEEDED(res) && parent)
|
|
{
|
|
PRInt32 offsetInParent;
|
|
res = GetChildOffset(aElement, parent, offsetInParent);
|
|
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// Collapse selection to just before desired element,
|
|
res = selection->Collapse(parent, offsetInParent);
|
|
if (NS_SUCCEEDED(res)) {
|
|
// then extend it to just after
|
|
res = selection->Extend(parent, offsetInParent+1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement)
|
|
{
|
|
nsresult res = NS_ERROR_NULL_POINTER;
|
|
|
|
// Be sure the element is contained in the document body
|
|
if (aElement && IsElementInBody(aElement))
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNode>parent;
|
|
res = aElement->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
PRInt32 offsetInParent;
|
|
res = GetChildOffset(aElement, parent, offsetInParent);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// Collapse selection to just after desired element,
|
|
res = selection->Collapse(parent, offsetInParent+1);
|
|
#if 0 //def DEBUG_cmanske
|
|
{
|
|
nsAutoString name;
|
|
parent->GetNodeName(name);
|
|
printf("SetCaretAfterElement: Parent node: ");
|
|
wprintf(name.GetUnicode());
|
|
printf(" Offset: %d\n\nHTML:\n", offsetInParent+1);
|
|
nsAutoString Format("text/html");
|
|
nsAutoString ContentsAs;
|
|
OutputToString(ContentsAs, Format, 2);
|
|
wprintf(ContentsAs.GetUnicode());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetParagraphFormat(const nsString& aParagraphFormat)
|
|
{
|
|
nsAutoString tag; tag.Assign(aParagraphFormat);
|
|
tag.ToLowerCase();
|
|
if (tag.EqualsWithConversion("dd") || tag.EqualsWithConversion("dt"))
|
|
return MakeDefinitionItem(tag);
|
|
else
|
|
return InsertBasicBlock(tag);
|
|
}
|
|
|
|
// XXX: ERROR_HANDLING -- this method needs a little work to ensure all error codes are
|
|
// checked properly, all null pointers are checked, and no memory leaks occur
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetParentBlockTags(nsStringArray *aTagList, PRBool aGetLists)
|
|
{
|
|
if (!aTagList) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
// Find out if the selection is collapsed:
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
if (isCollapsed)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node, blockParent;
|
|
PRInt32 offset;
|
|
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (!node) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMElement> blockParentElem;
|
|
if (aGetLists)
|
|
{
|
|
// Get the "ol", "ul", or "dl" parent element
|
|
res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("list"), node, getter_AddRefs(blockParentElem));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
if (IsBlockNode(node)) blockParent = node;
|
|
else blockParent = GetBlockNodeParent(node);
|
|
blockParentElem = do_QueryInterface(blockParent);
|
|
}
|
|
if (blockParentElem)
|
|
{
|
|
nsAutoString blockParentTag;
|
|
blockParentElem->GetTagName(blockParentTag);
|
|
aTagList->AppendString(blockParentTag);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// else non-collapsed selection
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_NULL_POINTER;
|
|
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
//XXX: should be while loop?
|
|
if (currentItem)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
// scan the range for all the independent block content blockSections
|
|
// and get the block parent of each
|
|
nsISupportsArray *blockSections;
|
|
res = NS_NewISupportsArray(&blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!blockSections) return NS_ERROR_NULL_POINTER;
|
|
res = GetBlockSectionsForRange(range, blockSections);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
nsIDOMRange *subRange;
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
while (subRange)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>startParent;
|
|
res = subRange->GetStartContainer(getter_AddRefs(startParent));
|
|
if (NS_SUCCEEDED(res) && startParent)
|
|
{
|
|
nsCOMPtr<nsIDOMElement> blockParent;
|
|
if (aGetLists)
|
|
{
|
|
// Get the "ol", "ul", or "dl" parent element
|
|
res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("list"), startParent, getter_AddRefs(blockParent));
|
|
}
|
|
else
|
|
{
|
|
blockParent = do_QueryInterface(GetBlockNodeParent(startParent));
|
|
}
|
|
if (NS_SUCCEEDED(res) && blockParent)
|
|
{
|
|
nsAutoString blockParentTag;
|
|
blockParent->GetTagName(blockParentTag);
|
|
PRBool isRoot;
|
|
IsRootTag(blockParentTag, isRoot);
|
|
if ((!isRoot) && (-1==aTagList->IndexOf(blockParentTag))) {
|
|
aTagList->AppendString(blockParentTag);
|
|
}
|
|
}
|
|
}
|
|
NS_RELEASE(subRange);
|
|
if (NS_FAILED(res))
|
|
break; // don't return here, need to release blockSections
|
|
blockSections->RemoveElementAt(0);
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
}
|
|
}
|
|
NS_RELEASE(blockSections);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetParagraphState(PRBool &aMixed, nsString &outFormat)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
|
|
if (!htmlRules) return NS_ERROR_FAILURE;
|
|
|
|
return htmlRules->GetParagraphState(aMixed, outFormat);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetFontFaceState(PRBool &aMixed, nsString &outFace)
|
|
{
|
|
aMixed = PR_TRUE;
|
|
outFace.AssignWithConversion("");
|
|
|
|
nsresult res;
|
|
nsAutoString faceStr; faceStr.AssignWithConversion("face");
|
|
PRBool first, any, all;
|
|
|
|
res = GetInlinePropertyWithAttrValue(nsIEditProperty::font, &faceStr, nsnull, first, any, all, &outFace);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
aMixed = PR_FALSE;
|
|
return res;
|
|
}
|
|
|
|
res = GetInlineProperty(nsIEditProperty::tt, nsnull, nsnull, first, any, all);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
aMixed = PR_FALSE;
|
|
nsIEditProperty::tt->ToString(outFace);
|
|
}
|
|
|
|
if (!any)
|
|
{
|
|
// there was no font face attrs of any kind. We are in normal font.
|
|
outFace.AssignWithConversion("");
|
|
aMixed = PR_FALSE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetFontColorState(PRBool &aMixed, nsString &aOutColor)
|
|
{
|
|
aMixed = PR_TRUE;
|
|
aOutColor.AssignWithConversion("");
|
|
|
|
nsresult res;
|
|
nsAutoString colorStr; colorStr.AssignWithConversion("color");
|
|
PRBool first, any, all;
|
|
|
|
res = GetInlinePropertyWithAttrValue(nsIEditProperty::font, &colorStr, nsnull, first, any, all, &aOutColor);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
aMixed = PR_FALSE;
|
|
return res;
|
|
}
|
|
|
|
if (!any)
|
|
{
|
|
// there was no font color attrs of any kind..
|
|
aOutColor.AssignWithConversion("");
|
|
aMixed = PR_FALSE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetBackgroundColorState(PRBool &aMixed, nsString &aOutColor)
|
|
{
|
|
//TODO: We don't handle "mixed" correctly!
|
|
aMixed = PR_FALSE;
|
|
aOutColor.AssignWithConversion("");
|
|
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
PRInt32 selectedCount;
|
|
nsAutoString tagName;
|
|
nsresult res = GetSelectedOrParentTableElement(*getter_AddRefs(element), tagName, selectedCount);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsAutoString styleName; styleName.AssignWithConversion("bgcolor");
|
|
|
|
while (element)
|
|
{
|
|
// We are in a cell or selected table
|
|
res = element->GetAttribute(styleName, aOutColor);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Done if we have a color explicitly set
|
|
if (aOutColor.Length() > 0)
|
|
return NS_OK;
|
|
|
|
// Once we hit the body, we're done
|
|
if(nsHTMLEditUtils::IsBody(element)) return NS_OK;
|
|
|
|
// No color is set, but we need to report visible color inherited
|
|
// from nested cells/tables, so search up parent chain
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
res = element->GetParentNode(getter_AddRefs(parentNode));
|
|
if (NS_FAILED(res)) return res;
|
|
element = do_QueryInterface(parentNode);
|
|
}
|
|
|
|
// If no table or cell found, get page body
|
|
res = nsEditor::GetRootElement(getter_AddRefs(element));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!element) return NS_ERROR_NULL_POINTER;
|
|
|
|
return element->GetAttribute(styleName, aOutColor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetListState(PRBool &aMixed, PRBool &aOL, PRBool &aUL, PRBool &aDL)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
|
|
if (!htmlRules) return NS_ERROR_FAILURE;
|
|
|
|
return htmlRules->GetListState(aMixed, aOL, aUL, aDL);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetListItemState(PRBool &aMixed, PRBool &aLI, PRBool &aDT, PRBool &aDD)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
|
|
if (!htmlRules) return NS_ERROR_FAILURE;
|
|
|
|
return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetAlignment(PRBool &aMixed, nsIHTMLEditor::EAlignment &aAlign)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
|
|
if (!htmlRules) return NS_ERROR_FAILURE;
|
|
|
|
return htmlRules->GetAlignment(aMixed, aAlign);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetIndentState(PRBool &aCanIndent, PRBool &aCanOutdent)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
|
|
if (!htmlRules) return NS_ERROR_FAILURE;
|
|
|
|
return htmlRules->GetIndentState(aCanIndent, aCanOutdent);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::MakeOrChangeList(const nsString& aListType, PRBool entireList)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpMakeList, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeList);
|
|
ruleInfo.blockType = &aListType;
|
|
ruleInfo.entireList = entireList;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
if (!handled)
|
|
{
|
|
// Find out if the selection is collapsed:
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (!node) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (isCollapsed)
|
|
{
|
|
// have to find a place to put the list
|
|
nsCOMPtr<nsIDOMNode> parent = node;
|
|
nsCOMPtr<nsIDOMNode> topChild = node;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
|
|
while ( !CanContainTag(parent, aListType))
|
|
{
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
topChild = parent;
|
|
parent = tmp;
|
|
}
|
|
|
|
if (parent != node)
|
|
{
|
|
// we need to split up to the child of parent
|
|
res = SplitNodeDeep(topChild, node, offset, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// make a list
|
|
nsCOMPtr<nsIDOMNode> newList;
|
|
res = CreateNode(aListType, parent, offset, getter_AddRefs(newList));
|
|
if (NS_FAILED(res)) return res;
|
|
// make a list item
|
|
nsAutoString tag; tag.AssignWithConversion("li");
|
|
nsCOMPtr<nsIDOMNode> newItem;
|
|
res = CreateNode(tag, newList, 0, getter_AddRefs(newItem));
|
|
if (NS_FAILED(res)) return res;
|
|
res = selection->Collapse(newItem,0);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveList(const nsString& aListType)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpRemoveList, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveList);
|
|
if (aListType.EqualsWithConversion("ol")) ruleInfo.bOrdered = PR_TRUE;
|
|
else ruleInfo.bOrdered = PR_FALSE;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
// no default behavior for this yet. what would it mean?
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::MakeDefinitionItem(const nsString& aItemType)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpMakeDefListItem, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeDefListItem);
|
|
ruleInfo.blockType = &aItemType;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
if (!handled)
|
|
{
|
|
// todo: no default for now. we count on rules to handle it.
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::InsertBasicBlock(const nsString& aBlockType)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpMakeBasicBlock, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeBasicBlock);
|
|
ruleInfo.blockType = &aBlockType;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
if (!handled)
|
|
{
|
|
// Find out if the selection is collapsed:
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (!node) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (isCollapsed)
|
|
{
|
|
// have to find a place to put the block
|
|
nsCOMPtr<nsIDOMNode> parent = node;
|
|
nsCOMPtr<nsIDOMNode> topChild = node;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
|
|
while ( !CanContainTag(parent, aBlockType))
|
|
{
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
topChild = parent;
|
|
parent = tmp;
|
|
}
|
|
|
|
if (parent != node)
|
|
{
|
|
// we need to split up to the child of parent
|
|
res = SplitNodeDeep(topChild, node, offset, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// make a block
|
|
nsCOMPtr<nsIDOMNode> newBlock;
|
|
res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// reposition selection to inside the block
|
|
res = selection->Collapse(newBlock,0);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Indent(const nsString& aIndent)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
PRBool cancel, handled;
|
|
PRInt32 theAction = nsTextEditRules::kIndent;
|
|
PRInt32 opID = kOpIndent;
|
|
if (aIndent.EqualsWithConversion("outdent"))
|
|
{
|
|
theAction = nsTextEditRules::kOutdent;
|
|
opID = kOpOutdent;
|
|
}
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsTextRulesInfo ruleInfo(theAction);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || (NS_FAILED(res))) return res;
|
|
|
|
if (!handled)
|
|
{
|
|
// Do default - insert a blockquote node if selection collapsed
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (!node) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsAutoString inward; inward.AssignWithConversion("indent");
|
|
if (aIndent == inward)
|
|
{
|
|
if (isCollapsed)
|
|
{
|
|
// have to find a place to put the blockquote
|
|
nsCOMPtr<nsIDOMNode> parent = node;
|
|
nsCOMPtr<nsIDOMNode> topChild = node;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString bq(NS_LITERAL_STRING("blockquote"));
|
|
while ( !CanContainTag(parent, bq))
|
|
{
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
topChild = parent;
|
|
parent = tmp;
|
|
}
|
|
|
|
if (parent != node)
|
|
{
|
|
// we need to split up to the child of parent
|
|
res = SplitNodeDeep(topChild, node, offset, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// make a blockquote
|
|
nsCOMPtr<nsIDOMNode> newBQ;
|
|
res = CreateNode(bq, parent, offset, getter_AddRefs(newBQ));
|
|
if (NS_FAILED(res)) return res;
|
|
// put a space in it so layout will draw the list item
|
|
res = selection->Collapse(newBQ,0);
|
|
if (NS_FAILED(res)) return res;
|
|
res = InsertText(NS_LITERAL_STRING(" "));
|
|
if (NS_FAILED(res)) return res;
|
|
// reposition selection to before the space character
|
|
res = GetStartNodeAndOffset(selection, &node, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = selection->Collapse(node,0);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
}
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
//TODO: IMPLEMENT ALIGNMENT!
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Align(const nsString& aAlignType)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpAlign, nsIEditor::eNext);
|
|
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRBool cancel, handled;
|
|
|
|
// Find out if the selection is collapsed:
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kAlign);
|
|
ruleInfo.alignType = &aAlignType;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (cancel || NS_FAILED(res))
|
|
return res;
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetElementOrParentByTagName(const nsString &aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn)
|
|
{
|
|
if (aTagName.Length() == 0 || !aReturn )
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> currentNode;
|
|
|
|
if (aNode)
|
|
currentNode = aNode;
|
|
else
|
|
{
|
|
// If no node supplied, get it from anchor node of current selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode> anchorNode;
|
|
res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
|
|
if(NS_FAILED(res)) return res;
|
|
if (!anchorNode) return NS_ERROR_FAILURE;
|
|
|
|
// Try to get the actual selected node
|
|
PRBool hasChildren = PR_FALSE;
|
|
anchorNode->HasChildNodes(&hasChildren);
|
|
if (hasChildren)
|
|
{
|
|
PRInt32 offset;
|
|
res = selection->GetAnchorOffset(&offset);
|
|
if(NS_FAILED(res)) return res;
|
|
currentNode = nsEditor::GetChildAt(anchorNode, offset);
|
|
}
|
|
// anchor node is probably a text node - just use that
|
|
if (!currentNode)
|
|
currentNode = anchorNode;
|
|
}
|
|
|
|
nsAutoString TagName(aTagName);
|
|
TagName.ToLowerCase();
|
|
PRBool getLink = IsLink(TagName);
|
|
PRBool getNamedAnchor = IsNamedAnchor(TagName);
|
|
if ( getLink || getNamedAnchor)
|
|
{
|
|
TagName.AssignWithConversion("a");
|
|
}
|
|
PRBool findTableCell = aTagName.EqualsIgnoreCase("td");
|
|
PRBool findList = aTagName.EqualsIgnoreCase("list");
|
|
|
|
// default is null - no element found
|
|
*aReturn = nsnull;
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRBool bNodeFound = PR_FALSE;
|
|
|
|
while (PR_TRUE)
|
|
{
|
|
nsAutoString currentTagName;
|
|
// Test if we have a link (an anchor with href set)
|
|
if ( (getLink && IsLinkNode(currentNode)) ||
|
|
(getNamedAnchor && IsNamedAnchorNode(currentNode)) )
|
|
{
|
|
bNodeFound = PR_TRUE;
|
|
break;
|
|
} else {
|
|
if (findList)
|
|
{
|
|
// Match "ol", "ul", or "dl" for lists
|
|
if (IsListNode(currentNode))
|
|
goto NODE_FOUND;
|
|
|
|
} else if (findTableCell)
|
|
{
|
|
// Table cells are another special case:
|
|
// Match either "td" or "th" for them
|
|
if (IsCellNode(currentNode))
|
|
goto NODE_FOUND;
|
|
|
|
} else {
|
|
currentNode->GetNodeName(currentTagName);
|
|
if (currentTagName.EqualsIgnoreCase(TagName))
|
|
{
|
|
NODE_FOUND:
|
|
bNodeFound = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Search up the parent chain
|
|
// We should never fail because of root test below, but lets be safe
|
|
// XXX: ERROR_HANDLING error return code lost
|
|
if (!NS_SUCCEEDED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent)
|
|
break;
|
|
|
|
// Stop searching if parent is a body tag
|
|
nsAutoString parentTagName;
|
|
parent->GetNodeName(parentTagName);
|
|
// Note: Originally used IsRoot to stop at table cells,
|
|
// but that's too messy when you are trying to find the parent table
|
|
//PRBool isRoot;
|
|
//if (!NS_SUCCEEDED(IsRootTag(parentTagName, isRoot)) || isRoot)
|
|
if(parentTagName.EqualsIgnoreCase("body"))
|
|
break;
|
|
|
|
currentNode = parent;
|
|
}
|
|
if (bNodeFound)
|
|
{
|
|
nsCOMPtr<nsIDOMElement> currentElement = do_QueryInterface(currentNode);
|
|
if (currentElement)
|
|
{
|
|
*aReturn = currentElement;
|
|
// Getters must addref
|
|
NS_ADDREF(*aReturn);
|
|
}
|
|
}
|
|
else res = NS_EDITOR_ELEMENT_NOT_FOUND;
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetSelectedElement(const nsString& aTagName, nsIDOMElement** aReturn)
|
|
{
|
|
if (!aReturn )
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// default is null - no element found
|
|
*aReturn = nsnull;
|
|
|
|
// First look for a single element in selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool bNodeFound = PR_FALSE;
|
|
res=NS_ERROR_NOT_INITIALIZED;
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
|
|
nsAutoString domTagName;
|
|
nsAutoString TagName(aTagName);
|
|
TagName.ToLowerCase();
|
|
// Empty string indicates we should match any element tag
|
|
PRBool anyTag = (TagName.IsEmpty());
|
|
PRBool isLinkTag = IsLink(TagName);
|
|
PRBool isNamedAnchorTag = IsNamedAnchor(TagName);
|
|
|
|
nsCOMPtr<nsIDOMElement> selectedElement;
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> startParent;
|
|
PRInt32 startOffset, endOffset;
|
|
res = range->GetStartContainer(getter_AddRefs(startParent));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> endParent;
|
|
res = range->GetEndContainer(getter_AddRefs(endParent));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Optimization for a single selected element
|
|
if (startParent && startParent == endParent && (endOffset-startOffset) == 1)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startParent, startOffset);
|
|
if (NS_FAILED(res)) return NS_OK;
|
|
if (selectedNode)
|
|
{
|
|
selectedNode->GetNodeName(domTagName);
|
|
domTagName.ToLowerCase();
|
|
|
|
// Test for appropriate node type requested
|
|
if (anyTag || (TagName == domTagName) ||
|
|
(isLinkTag && IsLinkNode(selectedNode)) ||
|
|
(isNamedAnchorTag && IsNamedAnchorNode(selectedNode)))
|
|
{
|
|
bNodeFound = PR_TRUE;
|
|
selectedElement = do_QueryInterface(selectedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bNodeFound)
|
|
{
|
|
if (isLinkTag)
|
|
{
|
|
// Link tag is a special case - we return the anchor node
|
|
// found for any selection that is totally within a link,
|
|
// included a collapsed selection (just a caret in a link)
|
|
nsCOMPtr<nsIDOMNode> anchorNode;
|
|
res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
|
|
if (NS_FAILED(res)) return res;
|
|
PRInt32 anchorOffset = -1;
|
|
if (anchorNode)
|
|
selection->GetAnchorOffset(&anchorOffset);
|
|
|
|
nsCOMPtr<nsIDOMNode> focusNode;
|
|
res = selection->GetFocusNode(getter_AddRefs(focusNode));
|
|
if (NS_FAILED(res)) return res;
|
|
PRInt32 focusOffset = -1;
|
|
if (focusNode)
|
|
selection->GetFocusOffset(&focusOffset);
|
|
|
|
// Link node must be the same for both ends of selection
|
|
if (NS_SUCCEEDED(res) && anchorNode)
|
|
{
|
|
#ifdef DEBUG_cmanske
|
|
{
|
|
nsAutoString name;
|
|
anchorNode->GetNodeName(name);
|
|
printf("GetSelectedElement: Anchor node of selection: ");
|
|
wprintf(name.GetUnicode());
|
|
printf(" Offset: %d\n", anchorOffset);
|
|
focusNode->GetNodeName(name);
|
|
printf("Focus node of selection: ");
|
|
wprintf(name.GetUnicode());
|
|
printf(" Offset: %d\n", focusOffset);
|
|
}
|
|
#endif
|
|
nsCOMPtr<nsIDOMElement> parentLinkOfAnchor;
|
|
res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor));
|
|
// XXX: ERROR_HANDLING can parentLinkOfAnchor be null?
|
|
if (NS_SUCCEEDED(res) && parentLinkOfAnchor)
|
|
{
|
|
if (isCollapsed)
|
|
{
|
|
// We have just a caret in the link
|
|
bNodeFound = PR_TRUE;
|
|
} else if(focusNode)
|
|
{ // Link node must be the same for both ends of selection
|
|
nsCOMPtr<nsIDOMElement> parentLinkOfFocus;
|
|
res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("href"), focusNode, getter_AddRefs(parentLinkOfFocus));
|
|
if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor)
|
|
bNodeFound = PR_TRUE;
|
|
}
|
|
|
|
// We found a link node parent
|
|
if (bNodeFound) {
|
|
// GetElementOrParentByTagName addref'd this, so we don't need to do it here
|
|
*aReturn = parentLinkOfAnchor;
|
|
NS_IF_ADDREF(*aReturn);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
else if (anchorOffset >= 0) // Check if link node is the only thing selected
|
|
{
|
|
nsCOMPtr<nsIDOMNode> anchorChild;
|
|
anchorChild = GetChildAt(anchorNode,anchorOffset);
|
|
if (anchorChild && IsLinkNode(anchorChild) &&
|
|
(anchorNode == focusNode) && focusOffset == (anchorOffset+1))
|
|
{
|
|
selectedElement = do_QueryInterface(anchorChild);
|
|
bNodeFound = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isCollapsed) // Don't bother to examine selection if it is collapsed
|
|
{
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
if(!enumerator)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if ((NS_SUCCEEDED(res)) && currentItem)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> currange( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (iter)
|
|
{
|
|
iter->Init(currange);
|
|
// loop through the content iterator for each content node
|
|
nsCOMPtr<nsIContent> content;
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
// Note likely!
|
|
if (NS_FAILED(res))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Query interface to cast nsIContent to nsIDOMNode
|
|
// then get tagType to compare to aTagName
|
|
// Clone node of each desired type and append it to the aDomFrag
|
|
selectedElement = do_QueryInterface(content);
|
|
if (selectedElement)
|
|
{
|
|
// If we already found a node, then we have another element,
|
|
// thus there's not just one element selected
|
|
if (bNodeFound)
|
|
{
|
|
bNodeFound = PR_FALSE;
|
|
break;
|
|
}
|
|
|
|
selectedElement->GetNodeName(domTagName);
|
|
domTagName.ToLowerCase();
|
|
|
|
if (anyTag)
|
|
{
|
|
// Get name of first selected element
|
|
selectedElement->GetTagName(TagName);
|
|
TagName.ToLowerCase();
|
|
anyTag = PR_FALSE;
|
|
}
|
|
|
|
// The "A" tag is a pain,
|
|
// used for both link(href is set) and "Named Anchor"
|
|
nsCOMPtr<nsIDOMNode> selectedNode = do_QueryInterface(selectedElement);
|
|
if ( (isLinkTag && IsLinkNode(selectedNode)) ||
|
|
(isNamedAnchorTag && IsNamedAnchorNode(selectedNode)) )
|
|
{
|
|
bNodeFound = PR_TRUE;
|
|
} else if (TagName == domTagName) { // All other tag names are handled here
|
|
bNodeFound = PR_TRUE;
|
|
}
|
|
if (!bNodeFound)
|
|
{
|
|
// Check if node we have is really part of the selection???
|
|
break;
|
|
}
|
|
}
|
|
iter->Next();
|
|
}
|
|
}
|
|
} else {
|
|
// Should never get here?
|
|
isCollapsed = PR_TRUE;
|
|
printf("isCollapsed was FALSE, but no elements found in selection\n");
|
|
}
|
|
} else {
|
|
printf("Could not create enumerator for GetSelectionProperties\n");
|
|
}
|
|
}
|
|
}
|
|
if (bNodeFound)
|
|
{
|
|
|
|
*aReturn = selectedElement;
|
|
if (selectedElement)
|
|
{
|
|
// Getters must addref
|
|
NS_ADDREF(*aReturn);
|
|
}
|
|
}
|
|
else res = NS_EDITOR_ELEMENT_NOT_FOUND;
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::CreateElementWithDefaults(const nsString& aTagName, nsIDOMElement** aReturn)
|
|
{
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
if (aReturn)
|
|
*aReturn = nsnull;
|
|
|
|
if (aTagName.IsEmpty() || !aReturn)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoString TagName(aTagName);
|
|
TagName.ToLowerCase();
|
|
nsAutoString realTagName;
|
|
|
|
if (IsLink(TagName) || IsNamedAnchor(TagName))
|
|
{
|
|
realTagName.AssignWithConversion("a");
|
|
} else {
|
|
realTagName = TagName;
|
|
}
|
|
//We don't use editor's CreateElement because we don't want to
|
|
// go through the transaction system
|
|
|
|
nsCOMPtr<nsIDOMElement>newElement;
|
|
nsCOMPtr<nsIContent> newContent;
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
|
|
if (!doc) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
//new call to use instead to get proper HTML element, bug# 39919
|
|
res = CreateHTMLContent(realTagName, getter_AddRefs(newContent));
|
|
newElement = do_QueryInterface(newContent);
|
|
if (NS_FAILED(res) || !newElement)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Mark the new element dirty, so it will be formatted
|
|
newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), nsAutoString());
|
|
|
|
// Set default values for new elements
|
|
if (TagName.EqualsWithConversion("hr"))
|
|
{
|
|
// Note that we read the user's attributes for these from prefs (in InsertHLine JS)
|
|
newElement->SetAttribute(NS_LITERAL_STRING("align"),NS_LITERAL_STRING("center"));
|
|
newElement->SetAttribute(NS_LITERAL_STRING("width"),NS_LITERAL_STRING("100%"));
|
|
newElement->SetAttribute(NS_LITERAL_STRING("size"),NS_LITERAL_STRING("2"));
|
|
} else if (TagName.EqualsWithConversion("table"))
|
|
{
|
|
newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2"));
|
|
newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2"));
|
|
newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1"));
|
|
} else if (TagName.EqualsWithConversion("tr"))
|
|
{
|
|
newElement->SetAttribute(NS_LITERAL_STRING("valign"),NS_LITERAL_STRING("top"));
|
|
} else if (TagName.EqualsWithConversion("td"))
|
|
{
|
|
newElement->SetAttribute(NS_LITERAL_STRING("valign"),NS_LITERAL_STRING("top"));
|
|
}
|
|
// ADD OTHER TAGS HERE
|
|
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
*aReturn = newElement;
|
|
// Getters must addref
|
|
NS_ADDREF(*aReturn);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement)
|
|
{
|
|
nsresult res=NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelection> selection;
|
|
|
|
if (!aAnchorElement) return NS_ERROR_NULL_POINTER;
|
|
|
|
|
|
// We must have a real selection
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (!selection)
|
|
{
|
|
res = NS_ERROR_NULL_POINTER;
|
|
}
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res))
|
|
isCollapsed = PR_TRUE;
|
|
|
|
if (isCollapsed)
|
|
{
|
|
printf("InsertLinkAroundSelection called but there is no selection!!!\n");
|
|
res = NS_OK;
|
|
} else {
|
|
// Be sure we were given an anchor element
|
|
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aAnchorElement);
|
|
if (anchor)
|
|
{
|
|
nsAutoString href;
|
|
res = anchor->GetHref(href);
|
|
if (NS_FAILED(res)) return res;
|
|
if (href.GetUnicode() && href.Length() > 0)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsString attribute; attribute.AssignWithConversion("href");
|
|
SetInlineProperty(nsIEditProperty::a, &attribute, &href);
|
|
//TODO: Enumerate through other properties of the anchor tag
|
|
// and set those as well.
|
|
// Optimization: Modify SetTextProperty to set all attributes at once?
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetBackgroundColor(const nsString& aColor)
|
|
{
|
|
NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document");
|
|
|
|
// Find a selected or enclosing table element to set background on
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
PRInt32 selectedCount;
|
|
nsAutoString tagName;
|
|
nsresult res = GetSelectedOrParentTableElement(*getter_AddRefs(element), tagName, selectedCount);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRBool setColor = (aColor.Length() > 0);
|
|
|
|
if (element)
|
|
{
|
|
if (selectedCount > 0)
|
|
{
|
|
// Traverse all selected cells
|
|
nsCOMPtr<nsIDOMElement> cell;
|
|
res = GetFirstSelectedCell(getter_AddRefs(cell), nsnull);
|
|
if (NS_SUCCEEDED(res) && cell)
|
|
{
|
|
while(cell)
|
|
{
|
|
if (setColor)
|
|
res = SetAttribute(cell, NS_ConvertASCIItoUCS2("bgcolor"), aColor);
|
|
else
|
|
res = RemoveAttribute(cell, NS_ConvertASCIItoUCS2("bgcolor"));
|
|
if (NS_FAILED(res)) break;
|
|
|
|
GetNextSelectedCell(getter_AddRefs(cell), nsnull);
|
|
};
|
|
return res;
|
|
}
|
|
}
|
|
// If we failed to find a cell, fall through to use originally-found element
|
|
} else {
|
|
// No table element -- set the background color on the body tag
|
|
res = nsEditor::GetRootElement(getter_AddRefs(element));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!element) return NS_ERROR_NULL_POINTER;
|
|
}
|
|
// Use the editor method that goes through the transaction system
|
|
if (setColor)
|
|
res = SetAttribute(element, NS_ConvertASCIItoUCS2("bgcolor"), aColor);
|
|
else
|
|
res = RemoveAttribute(element, NS_ConvertASCIItoUCS2("bgcolor"));
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsString& aAttribute, const nsString& aValue)
|
|
{
|
|
nsresult res;
|
|
// TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
|
|
|
|
NS_ASSERTION(mDocWeak, "Missing Editor DOM Document");
|
|
|
|
// Set the background color attribute on the body tag
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
|
|
res = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
|
|
if (!bodyElement) res = NS_ERROR_NULL_POINTER;
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// Use the editor method that goes through the transaction system
|
|
res = SetAttribute(bodyElement, aAttribute, aValue);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetDocumentIsEmpty(PRBool *aDocumentIsEmpty)
|
|
{
|
|
if (!aDocumentIsEmpty)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
if (!mRules)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
return mRules->DocumentIsEmpty(aDocumentIsEmpty);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::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
|
|
nsHTMLEditor::SetMaxTextLength(PRInt32 aMaxTextLength)
|
|
{
|
|
mMaxTextLength = aMaxTextLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetMaxTextLength(PRInt32* aMaxTextLength)
|
|
{
|
|
if (!aMaxTextLength)
|
|
return NS_ERROR_INVALID_POINTER;
|
|
*aMaxTextLength = mMaxTextLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark nsIEditorStyleSheets methods
|
|
#pragma mark -
|
|
#endif
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::AddStyleSheet(nsICSSStyleSheet* aSheet)
|
|
{
|
|
AddStyleSheetTxn* txn;
|
|
nsresult rv = CreateTxnForAddStyleSheet(aSheet, &txn);
|
|
if (!txn) rv = NS_ERROR_NULL_POINTER;
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
rv = Do(txn);
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
mLastStyleSheet = do_QueryInterface(aSheet); // save it so we can remove before applying the next one
|
|
}
|
|
}
|
|
// The transaction system (if any) has taken ownwership of txns
|
|
NS_IF_RELEASE(txn);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveStyleSheet(nsICSSStyleSheet* aSheet)
|
|
{
|
|
RemoveStyleSheetTxn* txn;
|
|
nsresult rv = CreateTxnForRemoveStyleSheet(aSheet, &txn);
|
|
if (!txn) rv = NS_ERROR_NULL_POINTER;
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
rv = Do(txn);
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
mLastStyleSheet = nsnull; // forget it
|
|
}
|
|
}
|
|
// The transaction system (if any) has taken ownwership of txns
|
|
NS_IF_RELEASE(txn);
|
|
|
|
return rv;
|
|
}
|
|
|
|
// Do NOT use transaction system for override style sheets
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveOverrideStyleSheet(nsICSSStyleSheet* aSheet)
|
|
{
|
|
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
if (!ps) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
nsCOMPtr<nsIDocument> document;
|
|
nsresult rv = ps->GetDocument(getter_AddRefs(document));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!document) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIStyleSet> styleSet;
|
|
rv = ps->GetStyleSet(getter_AddRefs(styleSet));
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!styleSet) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIStyleSheet> styleSheet = do_QueryInterface(aSheet);
|
|
if (!styleSheet) return NS_ERROR_NULL_POINTER;
|
|
styleSet->RemoveOverrideStyleSheet(styleSheet);
|
|
|
|
// This notifies document observers to rebuild all frames
|
|
// (this doesn't affect style sheet because it is not a doc sheet)
|
|
document->SetStyleSheetDisabledState(styleSheet, PR_FALSE);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ApplyOverrideStyleSheet(const nsString& aURL, nsICSSStyleSheet **aStyleSheet)
|
|
{
|
|
return ApplyDocumentOrOverrideStyleSheet(aURL, PR_TRUE, aStyleSheet);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ApplyStyleSheet(const nsString& aURL, nsICSSStyleSheet **aStyleSheet)
|
|
{
|
|
return ApplyDocumentOrOverrideStyleSheet(aURL, PR_FALSE, aStyleSheet);
|
|
}
|
|
|
|
//Note: Loading a document style sheet is undoable, loading an override sheet is not
|
|
nsresult
|
|
nsHTMLEditor::ApplyDocumentOrOverrideStyleSheet(const nsString& aURL, PRBool aOverride, nsICSSStyleSheet **aStyleSheet)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
nsCOMPtr<nsIURI> uaURL;
|
|
|
|
rv = NS_NewURI(getter_AddRefs(uaURL), aURL);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<nsIDocument> document;
|
|
|
|
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
if (!ps) return NS_ERROR_NOT_INITIALIZED;
|
|
rv = ps->GetDocument(getter_AddRefs(document));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!document) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIHTMLContentContainer> container = do_QueryInterface(document);
|
|
if (!container) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsICSSLoader> cssLoader;
|
|
nsCOMPtr<nsICSSStyleSheet> cssStyleSheet;
|
|
|
|
rv = container->GetCSSLoader(*getter_AddRefs(cssLoader));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!cssLoader) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool complete;
|
|
|
|
if (aOverride)
|
|
{
|
|
// We use null for the callback and data pointer because
|
|
// we MUST ONLY load synchronous local files (no @import)
|
|
rv = cssLoader->LoadAgentSheet(uaURL, *getter_AddRefs(cssStyleSheet), complete,
|
|
nsnull);
|
|
|
|
// Synchronous loads should ALWAYS return completed
|
|
if (!complete || !cssStyleSheet)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIStyleSheet> styleSheet;
|
|
styleSheet = do_QueryInterface(cssStyleSheet);
|
|
nsCOMPtr<nsIStyleSet> styleSet;
|
|
rv = ps->GetStyleSet(getter_AddRefs(styleSet));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!styleSet) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Add the override style sheet
|
|
// (This checks if already exists)
|
|
styleSet->AppendOverrideStyleSheet(styleSheet);
|
|
// Save doc pointer to be able to use nsIStyleSheet::SetEnabled()
|
|
styleSheet->SetOwningDocument(document);
|
|
|
|
// This notifies document observers to rebuild all frames
|
|
// (this doesn't affect style sheet because it is not a doc sheet)
|
|
document->SetStyleSheetDisabledState(styleSheet, PR_FALSE);
|
|
}
|
|
else
|
|
{
|
|
rv = cssLoader->LoadAgentSheet(uaURL, *getter_AddRefs(cssStyleSheet), complete,
|
|
this);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (complete)
|
|
ApplyStyleSheetToPresShellDocument(cssStyleSheet,this);
|
|
|
|
//
|
|
// If not complete, we will be notified later
|
|
// with a call to ApplyStyleSheetToPresShellDocument().
|
|
//
|
|
}
|
|
if (aStyleSheet)
|
|
{
|
|
*aStyleSheet = cssStyleSheet;
|
|
NS_ADDREF(*aStyleSheet);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark nsIEditorMailSupport methods
|
|
#pragma mark -
|
|
#endif
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetBodyStyleContext(nsIStyleContext** aStyleContext)
|
|
{
|
|
nsCOMPtr<nsIDOMElement> body;
|
|
nsresult res = GetRootElement(getter_AddRefs(body));
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(body);
|
|
|
|
nsIFrame *frame;
|
|
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
if (!ps) return NS_ERROR_NOT_INITIALIZED;
|
|
res = ps->GetPrimaryFrameFor(content, &frame);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
return ps->GetStyleContextFor(frame, aStyleContext);
|
|
}
|
|
|
|
//
|
|
// Get the wrap width for the first PRE tag in the document.
|
|
// If no PRE tag, throw an error.
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::GetBodyWrapWidth(PRInt32 *aWrapColumn)
|
|
{
|
|
nsresult res;
|
|
|
|
if (! aWrapColumn)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*aWrapColumn = -1; // default: no wrap
|
|
|
|
nsCOMPtr<nsIStyleContext> styleContext;
|
|
res = GetBodyStyleContext(getter_AddRefs(styleContext));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
const nsStyleText* styleText =
|
|
(const nsStyleText*)styleContext->GetStyleData(eStyleStruct_Text);
|
|
|
|
if (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace)
|
|
*aWrapColumn = 0; // wrap to window width
|
|
else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace)
|
|
{
|
|
const nsStylePosition* stylePosition =
|
|
(const nsStylePosition*)styleContext->GetStyleData(eStyleStruct_Position);
|
|
if (stylePosition->mWidth.GetUnit() == eStyleUnit_Chars)
|
|
*aWrapColumn = stylePosition->mWidth.GetIntValue();
|
|
else {
|
|
#ifdef DEBUG_akkana
|
|
printf("Can't get wrap column: style unit is %d\n",
|
|
stylePosition->mWidth.GetUnit());
|
|
#endif
|
|
*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 first <PRE> tag in this document.
|
|
// (Eventually want to search for more than one in case there are
|
|
// interspersed quoted text blocks.)
|
|
// Alternately: Change the wrap width on the editor style sheet.
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::SetBodyWrapWidth(PRInt32 aWrapColumn)
|
|
{
|
|
nsresult res;
|
|
|
|
// Ought to set a style sheet here ...
|
|
// Probably should keep around an mPlaintextStyleSheet for this purpose.
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
res = GetRootElement(getter_AddRefs(bodyElement));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!bodyElement) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Get the current style for this body element:
|
|
nsAutoString styleName; styleName.AssignWithConversion("style");
|
|
nsAutoString styleValue;
|
|
res = bodyElement->GetAttribute(styleName, styleValue);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// We'll replace styles for these values:
|
|
CutStyle("white-space", styleValue);
|
|
CutStyle("width", styleValue);
|
|
CutStyle("font-family", styleValue);
|
|
|
|
// If we have other style left, trim off any existing semicolons
|
|
// or whitespace, then add a known semicolon-space:
|
|
if (styleValue.Length() > 0)
|
|
{
|
|
styleValue.Trim("; \t", PR_FALSE, PR_TRUE);
|
|
styleValue.AppendWithConversion("; ");
|
|
}
|
|
|
|
// Make sure we have fixed-width font. This should be done for us,
|
|
// but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
|
|
// Only do this if we're wrapping.
|
|
PRUint32 flags = 0;
|
|
GetFlags(&flags);
|
|
if ((flags & eEditorEnableWrapHackMask) && aWrapColumn >= 0)
|
|
styleValue.AppendWithConversion("font-family: -moz-fixed; ");
|
|
|
|
// and now we're ready to set the new whitespace/wrapping style.
|
|
if (aWrapColumn > 0) // Wrap to a fixed column
|
|
{
|
|
styleValue.AppendWithConversion("white-space: -moz-pre-wrap; width: ");
|
|
styleValue.AppendInt(aWrapColumn);
|
|
styleValue.AppendWithConversion("ch;");
|
|
}
|
|
else if (aWrapColumn == 0)
|
|
styleValue.AppendWithConversion("white-space: -moz-pre-wrap;");
|
|
else
|
|
styleValue.AppendWithConversion("white-space: pre;");
|
|
|
|
res = bodyElement->SetAttribute(styleName, styleValue);
|
|
|
|
#ifdef DEBUG_wrapstyle
|
|
char* curstyle = styleValue.ToNewCString();
|
|
printf("Setting style: [%s]\nNow body looks like:\n", curstyle);
|
|
Recycle(curstyle);
|
|
//nsCOMPtr<nsIContent> nodec (do_QueryInterface(bodyElement));
|
|
//if (nodec) nodec->List(stdout);
|
|
//printf("-----\n");
|
|
#endif /* DEBUG_akkana */
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
|
|
{
|
|
if (!aNodeList)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res;
|
|
|
|
res = NS_NewISupportsArray(aNodeList);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!*aNodeList) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
if ((NS_SUCCEEDED(res)))
|
|
{
|
|
// get the root content
|
|
nsCOMPtr<nsIContent> rootContent;
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
nsEditor::GetDocument(getter_AddRefs(domdoc));
|
|
if (!domdoc)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
|
|
if (!doc)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
rootContent = doc->GetRootContent();
|
|
|
|
iter->Init(rootContent);
|
|
|
|
// loop through the content iterator for each content node
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIContent> content;
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res))
|
|
break;
|
|
nsCOMPtr<nsIDOMNode> node (do_QueryInterface(content));
|
|
if (node)
|
|
{
|
|
nsAutoString tagName;
|
|
node->GetNodeName(tagName);
|
|
tagName.ToLowerCase();
|
|
|
|
// See if it's an image or an embed
|
|
if (tagName.EqualsWithConversion("img") || tagName.EqualsWithConversion("embed"))
|
|
(*aNodeList)->AppendElement(node);
|
|
else if (tagName.EqualsWithConversion("a"))
|
|
{
|
|
// XXX Only include links if they're links to file: URLs
|
|
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor (do_QueryInterface(content));
|
|
if (anchor)
|
|
{
|
|
nsAutoString href;
|
|
if (NS_SUCCEEDED(anchor->GetHref(href)))
|
|
if (href.CompareWithConversion("file:", PR_TRUE, 5) == 0)
|
|
(*aNodeList)->AppendElement(node);
|
|
}
|
|
}
|
|
}
|
|
iter->Next();
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark nsIEditor overrides
|
|
#pragma mark -
|
|
#endif
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Undo(PRUint32 aCount)
|
|
{
|
|
ForceCompositionEnd();
|
|
nsresult result = NS_OK;
|
|
|
|
nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
|
|
nsCOMPtr<nsISelection> selection;
|
|
GetSelection(getter_AddRefs(selection));
|
|
PRBool cancel, handled;
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
|
|
if (!cancel && NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::Undo(aCount);
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Redo(PRUint32 aCount)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
|
|
nsCOMPtr<nsISelection> selection;
|
|
GetSelection(getter_AddRefs(selection));
|
|
PRBool cancel, handled;
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
|
|
if (!cancel && NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::Redo(aCount);
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::Cut()
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (!NS_SUCCEEDED(res))
|
|
return res;
|
|
|
|
PRBool isCollapsed;
|
|
if (NS_SUCCEEDED(selection->GetIsCollapsed(&isCollapsed)) && isCollapsed)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
res = Copy();
|
|
if (NS_SUCCEEDED(res))
|
|
res = DeleteSelection(eNone);
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanCut(PRBool &aCanCut)
|
|
{
|
|
aCanCut = PR_FALSE;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
aCanCut = !isCollapsed && IsModifiable();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::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 nsHTMLEditor::CanCopy(PRBool &aCanCopy)
|
|
{
|
|
aCanCopy = PR_FALSE;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
aCanCopy = !isCollapsed;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
|
|
{
|
|
// Create generic Transferable for getting the data
|
|
nsresult rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
|
|
NS_GET_IID(nsITransferable),
|
|
(void**)transferable);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Get the nsITransferable interface for getting the data from the clipboard
|
|
if (transferable)
|
|
{
|
|
// Create the desired DataFlavor for the type of data
|
|
// we want to get out of the transferable
|
|
if ((mFlags & eEditorPlaintextMask) == 0) // This should only happen in html editors, not plaintext
|
|
{
|
|
(*transferable)->AddDataFlavor(kJPEGImageMime);
|
|
(*transferable)->AddDataFlavor(kHTMLMime);
|
|
(*transferable)->AddDataFlavor(kFileMime);
|
|
}
|
|
(*transferable)->AddDataFlavor(kUnicodeMime);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable,
|
|
const nsString& aContextStr,
|
|
const nsString& aInfoStr)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
char* bestFlavor = nsnull;
|
|
nsCOMPtr<nsISupports> genericDataObj;
|
|
PRUint32 len = 0;
|
|
if ( NS_SUCCEEDED(transferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len)) )
|
|
{
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
nsAutoString flavor, stuffToPaste;
|
|
flavor.AssignWithConversion( bestFlavor ); // just so we can use flavor.Equals()
|
|
#ifdef DEBUG_akkana
|
|
printf("Got flavor [%s]\n", bestFlavor);
|
|
#endif
|
|
if (flavor.EqualsWithConversion(kHTMLMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.Assign ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTMLWithContext(stuffToPaste, aContextStr, aInfoStr);
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
}
|
|
else if (flavor.EqualsWithConversion(kUnicodeMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.Assign ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
// pasting does not inherit local inline styles
|
|
RemoveAllInlineProperties();
|
|
rv = InsertText(stuffToPaste.GetUnicode());
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
}
|
|
else if (flavor.EqualsWithConversion(kFileMime))
|
|
{
|
|
nsCOMPtr<nsIFile> fileObj ( do_QueryInterface(genericDataObj) );
|
|
if (fileObj && len > 0)
|
|
{
|
|
nsCOMPtr<nsIFileURL> fileURL;
|
|
rv = nsComponentManager::CreateInstance("@mozilla.org/network/standard-url;1", nsnull,
|
|
NS_GET_IID(nsIURL), getter_AddRefs(fileURL));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
if ( fileURL )
|
|
{
|
|
rv = fileURL->SetFile( fileObj );
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
PRBool insertAsImage = PR_FALSE;
|
|
char *fileextension = nsnull;
|
|
rv = fileURL->GetFileExtension( &fileextension );
|
|
if ( NS_SUCCEEDED(rv) && fileextension )
|
|
{
|
|
if ( (nsCRT::strcasecmp( fileextension, "jpg" ) == 0 )
|
|
|| (nsCRT::strcasecmp( fileextension, "jpeg" ) == 0 )
|
|
|| (nsCRT::strcasecmp( fileextension, "gif" ) == 0 )
|
|
|| (nsCRT::strcasecmp( fileextension, "png" ) == 0 ) )
|
|
{
|
|
insertAsImage = PR_TRUE;
|
|
}
|
|
}
|
|
if (fileextension) nsCRT::free(fileextension);
|
|
|
|
char *urltext = nsnull;
|
|
rv = fileURL->GetSpec( &urltext );
|
|
if ( NS_SUCCEEDED(rv) && urltext && urltext[0] != 0)
|
|
{
|
|
len = strlen(urltext);
|
|
if ( insertAsImage )
|
|
{
|
|
stuffToPaste.AssignWithConversion ( "<IMG src=\"", 10);
|
|
stuffToPaste.AppendWithConversion ( urltext, len );
|
|
stuffToPaste.AppendWithConversion ( "\">" );
|
|
}
|
|
else /* insert as link */
|
|
{
|
|
stuffToPaste.AssignWithConversion ( "<A href=\"" );
|
|
stuffToPaste.AppendWithConversion ( urltext, len );
|
|
stuffToPaste.AppendWithConversion ( "\">" );
|
|
stuffToPaste.AppendWithConversion ( urltext, len );
|
|
stuffToPaste.AppendWithConversion ( "</A>" );
|
|
}
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTML(stuffToPaste);
|
|
}
|
|
if (urltext) nsCRT::free(urltext);
|
|
}
|
|
}
|
|
}
|
|
else if (flavor.EqualsWithConversion(kJPEGImageMime))
|
|
{
|
|
// Insert Image code here
|
|
printf("Don't know how to insert an image yet!\n");
|
|
//nsIImage* image = (nsIImage *)data;
|
|
//NS_RELEASE(image);
|
|
rv = NS_ERROR_NOT_IMPLEMENTED; // for now give error code
|
|
}
|
|
}
|
|
nsCRT::free(bestFlavor);
|
|
|
|
// Try to scroll the selection into view if the paste/drop succeeded
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon)
|
|
selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsIDragService, dragService, "@mozilla.org/widget/dragservice;1", &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIDragSession> dragSession(do_QueryInterface(dragService));
|
|
|
|
if (!dragSession) return NS_OK;
|
|
|
|
// Get the nsITransferable interface for getting the data from the drop
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = PrepareTransferable(getter_AddRefs(trans));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!trans) return NS_OK; // NS_ERROR_FAILURE; SHOULD WE FAIL?
|
|
|
|
PRUint32 numItems = 0;
|
|
rv = dragSession->GetNumDropItems(&numItems);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Combine any deletion and drop insertion into one transaction
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
PRUint32 i;
|
|
PRBool doPlaceCaret = PR_TRUE;
|
|
for (i = 0; i < numItems; ++i)
|
|
{
|
|
rv = dragSession->GetData(trans, i);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!trans) return NS_OK; // NS_ERROR_FAILURE; Should we fail?
|
|
|
|
if ( doPlaceCaret )
|
|
{
|
|
// check if the user pressed the key to force a copy rather than a move
|
|
// if we run into problems here, we'll just assume the user doesn't want a copy
|
|
PRBool userWantsCopy = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent (do_QueryInterface(aDropEvent));
|
|
if (!nsuiEvent) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aDropEvent) );
|
|
if (mouseEvent)
|
|
|
|
#ifdef XP_MAC
|
|
mouseEvent->GetAltKey(&userWantsCopy);
|
|
#else
|
|
mouseEvent->GetCtrlKey(&userWantsCopy);
|
|
#endif
|
|
// Source doc is null if source is *not* the current editor document
|
|
nsCOMPtr<nsIDOMDocument> srcdomdoc;
|
|
rv = dragSession->GetSourceDocument(getter_AddRefs(srcdomdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Current doc is destination
|
|
nsCOMPtr<nsIDOMDocument>destdomdoc;
|
|
rv = GetDocument(getter_AddRefs(destdomdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_FAILURE;
|
|
|
|
PRBool isCollapsed;
|
|
rv = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Parent and offset under the mouse cursor
|
|
nsCOMPtr<nsIDOMNode> newSelectionParent;
|
|
PRInt32 newSelectionOffset = 0;
|
|
rv = nsuiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!newSelectionParent) return NS_ERROR_FAILURE;
|
|
|
|
rv = nsuiEvent->GetRangeOffset(&newSelectionOffset);
|
|
if (NS_FAILED(rv)) return rv;
|
|
/* Creating a range to store insert position because when
|
|
we delete the selection, range gravity will make sure the insertion
|
|
point is in the correct place */
|
|
nsCOMPtr<nsIDOMRange> destinationRange;
|
|
rv = CreateRange(newSelectionParent, newSelectionOffset,newSelectionParent, newSelectionOffset, getter_AddRefs(destinationRange));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if(!destinationRange)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// We never have to delete if selection is already collapsed
|
|
PRBool deleteSelection = PR_FALSE;
|
|
PRBool cursorIsInSelection = PR_FALSE;
|
|
|
|
// Check if mouse is in the selection
|
|
if (!isCollapsed)
|
|
{
|
|
PRInt32 rangeCount;
|
|
rv = selection->GetRangeCount(&rangeCount);
|
|
if (NS_FAILED(rv))
|
|
return rv?rv:NS_ERROR_FAILURE;
|
|
|
|
for (PRInt32 j = 0; j < rangeCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
|
|
rv = selection->GetRangeAt(j, getter_AddRefs(range));
|
|
if (NS_FAILED(rv) || !range)
|
|
continue;//dont bail yet, iterate through them all
|
|
|
|
nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
|
|
if (NS_FAILED(rv) || !nsrange)
|
|
continue;//dont bail yet, iterate through them all
|
|
|
|
rv = nsrange->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
|
|
if(cursorIsInSelection)
|
|
break;
|
|
}
|
|
if (cursorIsInSelection)
|
|
{
|
|
// Dragging within same doc can't drop on itself -- leave!
|
|
// (We shouldn't get here - drag event shouldn't have started if over selection)
|
|
if (srcdomdoc == destdomdoc)
|
|
return NS_OK;
|
|
|
|
// Dragging from another window onto a selection
|
|
// XXX Decision made to NOT do this,
|
|
// note that 4.x does replace if dropped on
|
|
//deleteSelection = PR_TRUE;
|
|
}
|
|
else
|
|
{
|
|
// We are NOT over the selection
|
|
if (srcdomdoc == destdomdoc)
|
|
{
|
|
// Within the same doc: delete if user doesn't want to copy
|
|
deleteSelection = !userWantsCopy;
|
|
}
|
|
else
|
|
{
|
|
// Different source doc: Don't delete
|
|
deleteSelection = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleteSelection)
|
|
{
|
|
rv = DeleteSelection(eNone);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// If we deleted the selection because we dropped from another doc,
|
|
// then we don't have to relocate the caret (insert at the deletion point)
|
|
if (!(deleteSelection && srcdomdoc != destdomdoc))
|
|
{
|
|
// Move the selection to the point under the mouse cursor
|
|
rv = destinationRange->GetStartContainer(getter_AddRefs(newSelectionParent));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if(!newSelectionParent)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
rv = destinationRange->GetStartOffset(&newSelectionOffset);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
selection->Collapse(newSelectionParent, newSelectionOffset);
|
|
}
|
|
// We have to figure out whether to delete and relocate caret only once
|
|
doPlaceCaret = PR_FALSE;
|
|
}
|
|
|
|
rv = InsertFromTransferable(trans, nsAutoString(), nsAutoString());
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanDrag(nsIDOMEvent *aDragEvent, PRBool &aCanDrag)
|
|
{
|
|
/* we really should be checking the XY coordinates of the mouseevent and ensure that
|
|
* that particular point is actually within the selection (not just that there is a selection)
|
|
*/
|
|
aCanDrag = PR_FALSE;
|
|
|
|
// KLUDGE to work around bug 50703
|
|
// After double click and object property editing,
|
|
// we get a spurious drag event
|
|
if (mIgnoreSpuriousDragEvent)
|
|
{
|
|
#ifdef DEBUG_cmanske
|
|
printf(" *** IGNORING SPURIOUS DRAG EVENT!\n");
|
|
#endif
|
|
mIgnoreSpuriousDragEvent = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
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;
|
|
|
|
// if we are collapsed, we have no selection so nothing to drag
|
|
if ( isCollapsed )
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> eventTarget;
|
|
res = aDragEvent->GetOriginalTarget(getter_AddRefs(eventTarget));
|
|
if (NS_FAILED(res)) return res;
|
|
if ( eventTarget )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> eventTargetDomNode = do_QueryInterface(eventTarget);
|
|
if ( eventTargetDomNode )
|
|
{
|
|
PRBool amTargettedCorrectly = PR_FALSE;
|
|
res = selection->ContainsNode(eventTargetDomNode, PR_FALSE, &amTargettedCorrectly);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
aCanDrag = amTargettedCorrectly;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DoDrag(nsIDOMEvent *aDragEvent)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> eventTarget;
|
|
rv = aDragEvent->GetTarget(getter_AddRefs(eventTarget));
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsCOMPtr<nsIDOMNode> domnode = do_QueryInterface(eventTarget);
|
|
|
|
/* get the selection to be dragged */
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* create an array of transferables */
|
|
nsCOMPtr<nsISupportsArray> transferableArray;
|
|
NS_NewISupportsArray(getter_AddRefs(transferableArray));
|
|
if (transferableArray == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
/* get the drag service */
|
|
NS_WITH_SERVICE(nsIDragService, dragService, "@mozilla.org/widget/dragservice;1", &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* create html flavor transferable */
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
|
|
NS_GET_IID(nsITransferable),
|
|
getter_AddRefs(trans));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if ( !trans ) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
rv = GetDocument(getter_AddRefs(domdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
|
|
if (doc)
|
|
{
|
|
nsCOMPtr<nsIDocumentEncoder> docEncoder;
|
|
|
|
docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "text/html");
|
|
NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
|
|
|
|
docEncoder->Init(doc, NS_LITERAL_STRING("text/html"), 0);
|
|
docEncoder->SetSelection(selection);
|
|
|
|
nsAutoString buffer;
|
|
|
|
rv = docEncoder->EncodeToString(buffer);
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
if ( !buffer.IsEmpty() )
|
|
{
|
|
nsCOMPtr<nsIFormatConverter> htmlConverter;
|
|
rv = nsComponentManager::CreateInstance(kCHTMLFormatConverterCID, nsnull, NS_GET_IID(nsIFormatConverter),
|
|
getter_AddRefs(htmlConverter));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!htmlConverter) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsCOMPtr<nsISupportsWString> dataWrapper;
|
|
rv = nsComponentManager::CreateInstance(NS_SUPPORTS_WSTRING_CONTRACTID, nsnull,
|
|
NS_GET_IID(nsISupportsWString), getter_AddRefs(dataWrapper));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if ( !dataWrapper ) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
rv = trans->AddDataFlavor(kHTMLMime);
|
|
if (NS_FAILED(rv)) return rv;
|
|
rv = trans->SetConverter(htmlConverter);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = dataWrapper->SetData( NS_CONST_CAST(PRUnichar*, buffer.GetUnicode()) );
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// QI the data object an |nsISupports| so that when the transferable holds
|
|
// onto it, it will addref the correct interface.
|
|
nsCOMPtr<nsISupports> nsisupportsDataWrapper ( do_QueryInterface(dataWrapper) );
|
|
rv = trans->SetTransferData(kHTMLMime, nsisupportsDataWrapper, buffer.Length() * 2);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* add the transferable to the array */
|
|
rv = transferableArray->AppendElement(trans);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* invoke drag */
|
|
unsigned int flags;
|
|
// in some cases we'll want to cut rather than copy... hmmmmm...
|
|
// if ( wantToCut )
|
|
// flags = nsIDragService.DRAGDROP_ACTION_COPY + nsIDragService.DRAGDROP_ACTION_MOVE;
|
|
// else
|
|
flags = nsIDragService::DRAGDROP_ACTION_COPY + nsIDragService::DRAGDROP_ACTION_MOVE;
|
|
|
|
rv = dragService->InvokeDragSession( domnode, transferableArray, nsnull, flags);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
aDragEvent->PreventBubble();
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
NS_WITH_SERVICE ( nsIClipboard, clipboard, kCClipboardCID, &rv );
|
|
if ( NS_FAILED(rv) )
|
|
return rv;
|
|
|
|
// Get the nsITransferable interface for getting the data from the clipboard
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = PrepareTransferable(getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// Get the Data from the clipboard
|
|
if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
|
|
{
|
|
// also get additional html copy hints, if present
|
|
nsAutoString contextStr, infoStr;
|
|
nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
|
|
PRUint32 contextLen, infoLen;
|
|
nsCOMPtr<nsISupportsWString> textDataObj;
|
|
|
|
nsCOMPtr<nsITransferable> contextTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
|
|
contextTrans->AddDataFlavor(kHTMLContext);
|
|
clipboard->GetData(contextTrans, aSelectionType);
|
|
contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
|
|
|
|
nsCOMPtr<nsITransferable> infoTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
|
|
infoTrans->AddDataFlavor(kHTMLInfo);
|
|
clipboard->GetData(infoTrans, aSelectionType);
|
|
infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
|
|
|
|
if (contextDataObj)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj = do_QueryInterface(contextDataObj);
|
|
textDataObj->ToString ( &text );
|
|
contextStr.Assign ( text, contextLen / 2 );
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
|
|
if (infoDataObj)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj = do_QueryInterface(infoDataObj);
|
|
textDataObj->ToString ( &text );
|
|
infoStr.Assign ( text, infoLen / 2 );
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
rv = InsertFromTransferable(trans, contextStr, infoStr);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanPaste(PRInt32 aSelectionType, PRBool &aCanPaste)
|
|
{
|
|
aCanPaste = PR_FALSE;
|
|
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsIClipboard, clipboard, kCClipboardCID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// the flavors that we can deal with
|
|
char* textEditorFlavors[] = { kUnicodeMime, nsnull };
|
|
char* htmlEditorFlavors[] = { kJPEGImageMime, kHTMLMime, nsnull };
|
|
|
|
nsCOMPtr<nsISupportsArray> flavorsList;
|
|
rv = nsComponentManager::CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, nsnull,
|
|
NS_GET_IID(nsISupportsArray), getter_AddRefs(flavorsList));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 editorFlags;
|
|
GetFlags(&editorFlags);
|
|
|
|
// add the flavors for all editors
|
|
for (char** flavor = textEditorFlavors; *flavor; flavor++)
|
|
{
|
|
nsCOMPtr<nsISupportsString> flavorString;
|
|
nsComponentManager::CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, nsnull,
|
|
NS_GET_IID(nsISupportsString), getter_AddRefs(flavorString));
|
|
if (flavorString)
|
|
{
|
|
flavorString->SetData(*flavor);
|
|
flavorsList->AppendElement(flavorString);
|
|
}
|
|
}
|
|
|
|
// add the HTML-editor only flavors
|
|
if ((editorFlags & eEditorPlaintextMask) == 0)
|
|
{
|
|
for (char** htmlFlavor = htmlEditorFlavors; *htmlFlavor; htmlFlavor++)
|
|
{
|
|
nsCOMPtr<nsISupportsString> flavorString;
|
|
nsComponentManager::CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, nsnull,
|
|
NS_GET_IID(nsISupportsString), getter_AddRefs(flavorString));
|
|
if (flavorString)
|
|
{
|
|
flavorString->SetData(*htmlFlavor);
|
|
flavorsList->AppendElement(flavorString);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool haveFlavors;
|
|
rv = clipboard->HasDataMatchingFlavors(flavorsList, aSelectionType, &haveFlavors);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
aCanPaste = haveFlavors;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// HTML PasteAsQuotation: Paste in a blockquote type=cite
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(PRInt32 aSelectionType)
|
|
{
|
|
if (mFlags & eEditorPlaintextMask)
|
|
return PasteAsPlaintextQuotation(aSelectionType);
|
|
|
|
nsAutoString citation;
|
|
return PasteAsCitedQuotation(citation, aSelectionType);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsString& aCitation,
|
|
PRInt32 aSelectionType)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsAutoString tag; tag.AssignWithConversion("blockquote");
|
|
res = DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!newNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Try to set type=cite. Ignore it if this fails.
|
|
nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
|
|
if (newElement)
|
|
{
|
|
nsAutoString type; type.AssignWithConversion("type");
|
|
nsAutoString cite; cite.AssignWithConversion("cite");
|
|
newElement->SetAttribute(type, cite);
|
|
}
|
|
|
|
// Set the selection to the underneath the node we just inserted:
|
|
res = selection->Collapse(newNode, 0);
|
|
if (NS_FAILED(res))
|
|
{
|
|
#ifdef DEBUG_akkana
|
|
printf("Couldn't collapse");
|
|
#endif
|
|
// XXX: error result: should res be returned here?
|
|
}
|
|
|
|
res = Paste(aSelectionType);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// Paste a plaintext quotation
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(PRInt32 aSelectionType)
|
|
{
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsIClipboard, clipboard, 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_akkana
|
|
printf("Got flavor [%s]\n", flav);
|
|
#endif
|
|
nsAutoString flavor; flavor.AssignWithConversion(flav);
|
|
nsAutoString stuffToPaste;
|
|
if (flavor.EqualsWithConversion(kUnicodeMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.Assign ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertAsPlaintextQuotation(stuffToPaste, 0);
|
|
if (text)
|
|
nsMemory::Free(text);
|
|
}
|
|
}
|
|
nsCRT::free(flav);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsString& aQuotedText,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
if (mFlags & eEditorPlaintextMask)
|
|
return InsertAsPlaintextQuotation(aQuotedText, aNodeInserted);
|
|
|
|
nsAutoString citation;
|
|
nsAutoString charset;
|
|
return InsertAsCitedQuotation(aQuotedText, citation, PR_FALSE,
|
|
charset, aNodeInserted);
|
|
}
|
|
|
|
// text insert.
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertAsPlaintextQuotation(const nsString& aQuotedText,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
// We have the text. Cite it appropriately:
|
|
nsCOMPtr<nsICiter> citer;
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsIPref, prefs, kPrefServiceCID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
char *citationType = 0;
|
|
rv = prefs->CopyCharPref("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;
|
|
|
|
// Let the citer quote it for us:
|
|
nsString quotedStuff;
|
|
rv = citer->GetCiteString(aQuotedText, quotedStuff);
|
|
if (!NS_SUCCEEDED(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.
|
|
quotedStuff.Append(PRUnichar('\n'));
|
|
|
|
nsCOMPtr<nsIDOMNode> preNode;
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
else
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, 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)
|
|
{
|
|
// Wrap the inserted quote in a <pre> so it won't be wrapped:
|
|
nsAutoString tag; tag.AssignWithConversion("pre");
|
|
rv = DeleteSelectionAndCreateNode(tag, getter_AddRefs(preNode));
|
|
|
|
// If this succeeded, then set selection inside the pre
|
|
// so the inserted text will end up there.
|
|
// If it failed, we don't care what the return value was,
|
|
// but we'll fall through and try to insert the text anyway.
|
|
if (NS_SUCCEEDED(rv) && preNode)
|
|
{
|
|
// Add an attribute on the pre node so we'll know it's a quotation.
|
|
// Do this after the insertion, so that
|
|
nsCOMPtr<nsIDOMElement> preElement (do_QueryInterface(preNode));
|
|
if (preElement)
|
|
{
|
|
preElement->SetAttribute(NS_LITERAL_STRING("_moz_quote"),
|
|
NS_LITERAL_STRING("true"));
|
|
// set style to not have unwanted vertical margins
|
|
preElement->SetAttribute(NS_LITERAL_STRING("style"),
|
|
NS_LITERAL_STRING("margin: 0 0 0 0px;"));
|
|
}
|
|
|
|
// and set the selection inside it:
|
|
selection->Collapse(preNode, 0);
|
|
}
|
|
|
|
rv = InsertText(quotedStuff.GetUnicode());
|
|
|
|
if (aNodeInserted && NS_SUCCEEDED(rv))
|
|
{
|
|
*aNodeInserted = preNode;
|
|
NS_IF_ADDREF(*aNodeInserted);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the selection to just after the inserted node:
|
|
if (NS_SUCCEEDED(rv) && preNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
if (NS_SUCCEEDED(GetNodeLocation(preNode, &parent, &offset)) && parent)
|
|
selection->Collapse(parent, offset+1);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertAsCitedQuotation(const nsString& aQuotedText,
|
|
const nsString& aCitation,
|
|
PRBool aInsertHTML,
|
|
const nsString& aCharset,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsresult res = NS_OK;
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
else
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
nsAutoString tag; tag.AssignWithConversion("blockquote");
|
|
res = DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!newNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Try to set type=cite. Ignore it if this fails.
|
|
nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
|
|
if (newElement)
|
|
{
|
|
nsAutoString type; type.AssignWithConversion("type");
|
|
nsAutoString cite; cite.AssignWithConversion("cite");
|
|
newElement->SetAttribute(type, cite);
|
|
|
|
if (aCitation.Length() > 0)
|
|
newElement->SetAttribute(cite, aCitation);
|
|
|
|
// Set the selection inside the blockquote so aQuotedText will go there:
|
|
selection->Collapse(newNode, 0);
|
|
}
|
|
|
|
if (aInsertHTML)
|
|
res = InsertHTMLWithCharset(aQuotedText, aCharset);
|
|
|
|
else
|
|
res = InsertText(aQuotedText.GetUnicode()); // XXX ignore charset
|
|
|
|
if (aNodeInserted)
|
|
{
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
*aNodeInserted = newNode;
|
|
NS_IF_ADDREF(*aNodeInserted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the selection to just after the inserted node:
|
|
if (NS_SUCCEEDED(res) && newNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
if (NS_SUCCEEDED(GetNodeLocation(newNode, &parent, &offset)) && parent)
|
|
selection->Collapse(parent, offset+1);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::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));
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
|
|
// Set the wrap column. If our wrap column is 0,
|
|
// i.e. wrap to body width, then don't set it, let the
|
|
// document encoder use its own default.
|
|
PRInt32 wrapColumn;
|
|
PRUint32 wc =0;
|
|
if (NS_SUCCEEDED(GetBodyWrapWidth(&wrapColumn)))
|
|
{
|
|
if (wrapColumn != 0)
|
|
{
|
|
if (wrapColumn < 0)
|
|
wc = 0;
|
|
else
|
|
wc = (PRUint32)wrapColumn;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMElement> rootElement;
|
|
GetRootElement(getter_AddRefs(rootElement));
|
|
NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
|
|
//is this a body?? do we want to output the whole doc?
|
|
// Set the selection, if appropriate:
|
|
nsCOMPtr<nsISelectionPrivate> selPriv;
|
|
if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
|
|
{
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
selPriv = do_QueryInterface(selection);
|
|
}
|
|
else if (nsHTMLEditUtils::IsBody(rootElement))
|
|
{
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
|
|
formatType.AppendWithConversion(aFormatType);
|
|
rv = nsComponentManager::CreateInstance(formatType,
|
|
nsnull,
|
|
NS_GET_IID(nsIDocumentEncoder),
|
|
getter_AddRefs(encoder));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!mPresShellWeak)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
|
|
if (!ps)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDocument> doc;
|
|
rv = ps->GetDocument(getter_AddRefs(doc));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = encoder->Init(doc, aFormatType, aFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (wc != 0)
|
|
encoder->SetWrapColumn(wc);
|
|
|
|
return encoder->EncodeToString(aOutputString);
|
|
|
|
}
|
|
if (!selection)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
rv = nsComponentManager::CreateInstance(kCRangeCID,
|
|
nsnull,
|
|
NS_GET_IID(nsIDOMRange),
|
|
getter_AddRefs(range));
|
|
if (!range)
|
|
return NS_ERROR_FAILURE;
|
|
rv = nsComponentManager::CreateInstance(kCDOMSelectionCID,
|
|
nsnull,
|
|
NS_GET_IID(nsISelection),
|
|
getter_AddRefs(selection));
|
|
if (selection)
|
|
{
|
|
selPriv = do_QueryInterface(selection);
|
|
//get the independent selection interface
|
|
nsCOMPtr<nsIIndependentSelection> indSel = do_QueryInterface(selection);
|
|
if (indSel)
|
|
{
|
|
nsCOMPtr<nsIPresShell> presShell;
|
|
if (NS_SUCCEEDED(GetPresShell(getter_AddRefs(presShell))) && presShell)
|
|
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);
|
|
}
|
|
if (NS_FAILED(selection->AddRange(range)))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
PRUnichar *tmp;
|
|
char *tmpformattype= ToNewCString(aFormatType);//crap copied string
|
|
if (!tmpformattype)
|
|
return NS_ERROR_NULL_POINTER;
|
|
rv = selPriv->ToStringWithFormat(tmpformattype, aFlags, wc, &tmp);
|
|
if (NS_SUCCEEDED(rv) && tmp)
|
|
{
|
|
nsMemory::Free(tmpformattype);
|
|
aOutputString.Assign(tmp);
|
|
nsMemory::Free(tmp);
|
|
}
|
|
return rv;
|
|
}
|
|
#if 0
|
|
|
|
PRBool cancel, handled;
|
|
nsString resultString;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kOutputText);
|
|
ruleInfo.outString = &resultString;
|
|
ruleInfo.outputFormat = &aFormatType;
|
|
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 = *(ruleInfo.outString);
|
|
}
|
|
else
|
|
{ // default processing
|
|
rv = NS_OK;
|
|
|
|
// special-case for empty document when requesting plain text,
|
|
// to account for the bogus text node
|
|
if (aFormatType.EqualsWithConversion("text/plain"))
|
|
{
|
|
PRBool docEmpty;
|
|
rv = GetDocumentIsEmpty(&docEmpty);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (docEmpty) {
|
|
aOutputString.SetLength(0);
|
|
return NS_OK;
|
|
}
|
|
else if (mFlags & eEditorPlaintextMask)
|
|
aFlags |= nsIDocumentEncoder::OutputPreformatted;
|
|
}
|
|
|
|
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
char* contractid = (char *)nsMemory::Alloc(strlen(NS_DOC_ENCODER_CONTRACTID_BASE) + aFormatType.Length() + 1);
|
|
if (! contractid)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
strcpy(contractid, NS_DOC_ENCODER_CONTRACTID_BASE);
|
|
char* type = aFormatType.ToNewCString();
|
|
strcat(contractid, type);
|
|
nsCRT::free(type);
|
|
rv = nsComponentManager::CreateInstance(contractid,
|
|
nsnull,
|
|
NS_GET_IID(nsIDocumentEncoder),
|
|
getter_AddRefs(encoder));
|
|
|
|
nsCRT::free(contractid);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
rv = GetDocument(getter_AddRefs(domdoc));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
|
|
|
|
rv = encoder->Init(doc, aFormatType, aFlags);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Set the selection, if appropriate:
|
|
if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_SUCCEEDED(rv) && selection)
|
|
encoder->SetSelection(selection);
|
|
}
|
|
|
|
// Set the wrap column. If our wrap column is 0,
|
|
// i.e. wrap to body width, then don't set it, let the
|
|
// document encoder use its own default.
|
|
PRInt32 wrapColumn;
|
|
if (NS_SUCCEEDED(GetBodyWrapWidth(&wrapColumn)))
|
|
{
|
|
if (wrapColumn != 0)
|
|
{
|
|
PRUint32 wc;
|
|
if (wrapColumn < 0)
|
|
wc = 0;
|
|
else
|
|
wc = (PRUint32)wrapColumn;
|
|
if (wrapColumn > 0)
|
|
(void)encoder->SetWrapColumn(wc);
|
|
}
|
|
}
|
|
|
|
rv = encoder->EncodeToString(aOutputString);
|
|
}
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::OutputToStream(nsIOutputStream* aOutputStream,
|
|
const nsString& aFormatType,
|
|
const nsString* aCharset,
|
|
PRUint32 aFlags)
|
|
{
|
|
|
|
nsresult rv;
|
|
|
|
// special-case for empty document when requesting plain text,
|
|
// to account for the bogus text node
|
|
if (aFormatType.EqualsWithConversion("text/plain"))
|
|
{
|
|
PRBool docEmpty;
|
|
rv = GetDocumentIsEmpty(&docEmpty);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (docEmpty)
|
|
return NS_OK; // output nothing
|
|
}
|
|
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
char* contractid = (char *)nsMemory::Alloc(strlen(NS_DOC_ENCODER_CONTRACTID_BASE) + aFormatType.Length() + 1);
|
|
if (! contractid)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
strcpy(contractid, NS_DOC_ENCODER_CONTRACTID_BASE);
|
|
char* type = aFormatType.ToNewCString();
|
|
strcat(contractid, type);
|
|
nsCRT::free(type);
|
|
rv = nsComponentManager::CreateInstance(contractid,
|
|
nsnull,
|
|
NS_GET_IID(nsIDocumentEncoder),
|
|
getter_AddRefs(encoder));
|
|
|
|
nsCRT::free(contractid);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
printf("Couldn't get contractid %s\n", contractid);
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
rv = GetDocument(getter_AddRefs(domdoc));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
|
|
|
|
if (aCharset && aCharset->Length() != 0 && aCharset->EqualsWithConversion("null")==PR_FALSE)
|
|
encoder->SetCharset(*aCharset);
|
|
|
|
rv = encoder->Init(doc, aFormatType, aFlags);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Set the selection, if appropriate:
|
|
if (aFlags & nsIDocumentEncoder::OutputSelectionOnly)
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_SUCCEEDED(rv) && selection)
|
|
encoder->SetSelection(selection);
|
|
}
|
|
|
|
// Set the wrap column. If our wrap column is 0,
|
|
// i.e. wrap to body width, then don't set it, let the
|
|
// document encoder use its own default.
|
|
PRInt32 wrapColumn;
|
|
if (NS_SUCCEEDED(GetBodyWrapWidth(&wrapColumn)))
|
|
{
|
|
if (wrapColumn != 0)
|
|
{
|
|
PRUint32 wc;
|
|
if (wrapColumn < 0)
|
|
wc = 0;
|
|
else
|
|
wc = (PRUint32)wrapColumn;
|
|
if (wrapColumn > 0)
|
|
(void)encoder->SetWrapColumn(wc);
|
|
}
|
|
}
|
|
|
|
return encoder->EncodeToStream(aOutputStream);
|
|
}
|
|
|
|
static nsresult SetSelectionAroundHeadChildren(nsCOMPtr<nsISelection> aSelection, nsWeakPtr aDocWeak)
|
|
{
|
|
nsresult res = NS_OK;
|
|
// Set selection around <head> node
|
|
nsCOMPtr<nsIDOMNodeList>nodeList;
|
|
nsAutoString headTag; headTag.AssignWithConversion("head");
|
|
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(aDocWeak);
|
|
if (!doc) return NS_ERROR_NOT_INITIALIZED;
|
|
res = doc->GetElementsByTagName(headTag, getter_AddRefs(nodeList));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!nodeList) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRUint32 count;
|
|
nodeList->GetLength(&count);
|
|
if (count < 1) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMNode> headNode;
|
|
res = nodeList->Item(0, getter_AddRefs(headNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!headNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Collapse selection to before first child of the head,
|
|
res = aSelection->Collapse(headNode, 0);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// then extend it to just after
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = headNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!childNodes) return NS_ERROR_NULL_POINTER;
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
|
|
return aSelection->Extend(headNode, childCount+1);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetHeadContentsAsHTML(nsString& aOutputString)
|
|
{
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Save current selection
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
|
|
res = SetSelectionAroundHeadChildren(selection, mDocWeak);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
res = OutputToString(aOutputString, NS_ConvertASCIItoUCS2("text/html"),
|
|
nsIDocumentEncoder::OutputSelectionOnly);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// Selection always includes <body></body>,
|
|
// so terminate there
|
|
PRInt32 offset = aOutputString.Find(NS_ConvertASCIItoUCS2("<body"), PR_TRUE);
|
|
if (offset > 0)
|
|
{
|
|
// Ensure the string ends in a newline
|
|
PRUnichar newline ('\n');
|
|
if (aOutputString.CharAt(offset-1) != newline)
|
|
aOutputString.SetCharAt(newline, offset++);
|
|
|
|
aOutputString.Truncate(offset);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed)
|
|
{
|
|
#ifdef DEBUG
|
|
if (!outNumTests || !outNumTestsFailed)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
TextEditorTest *tester = new TextEditorTest();
|
|
if (!tester)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
tester->Run(this, outNumTests, outNumTestsFailed);
|
|
delete tester;
|
|
return NS_OK;
|
|
#else
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark nsIEditorIMESupport overrides
|
|
#pragma mark -
|
|
#endif
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetCompositionString(const nsString& aCompositionString, nsIPrivateTextRangeList* aTextRangeList,nsTextEventReply* aReply)
|
|
{
|
|
NS_ASSERTION(aTextRangeList, "null ptr");
|
|
if(nsnull == aTextRangeList)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsICaret> caretP;
|
|
|
|
// workaround for windows ime bug 23558: we get every ime event twice.
|
|
// for escape keypress, this causes an empty string to be passed
|
|
// twice, which freaks out the editor. This is to detect and aviod that
|
|
// situation:
|
|
if (aCompositionString.IsEmpty() && !mIMETextNode)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
mIMETextRangeList = aTextRangeList;
|
|
nsAutoPlaceHolderBatch batch(this, gIMETxnName);
|
|
|
|
result = InsertText(aCompositionString.GetUnicode());
|
|
|
|
mIMEBufferLength = aCompositionString.Length();
|
|
|
|
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
if (!ps) return NS_ERROR_NOT_INITIALIZED;
|
|
ps->GetCaret(getter_AddRefs(caretP));
|
|
caretP->GetWindowRelativeCoordinates(aReply->mCursorPosition,aReply->mCursorIsCollapsed,selection);
|
|
|
|
// second part of 23558 fix:
|
|
if (aCompositionString.IsEmpty())
|
|
{
|
|
mIMETextNode = nsnull;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetReconversionString(nsReconversionEventReply* aReply)
|
|
{
|
|
nsresult res;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res) || !selection)
|
|
return (res == NS_OK) ? NS_ERROR_FAILURE : res;
|
|
|
|
// get the first range in the selection. Since it is
|
|
// unclear what to do if reconversion happens with a
|
|
// multirange selection, we will ignore any additional ranges.
|
|
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
if (NS_FAILED(res) || !range)
|
|
return (res == NS_OK) ? NS_ERROR_FAILURE : res;
|
|
|
|
nsAutoString textValue;
|
|
res = range->ToString(textValue);
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
|
|
aReply->mReconversionString = (PRUnichar*) nsMemory::Clone(textValue.GetUnicode(),
|
|
(textValue.Length() + 1) * sizeof(PRUnichar));
|
|
if (!aReply->mReconversionString)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
// delete the selection
|
|
res = DeleteSelection(eNone);
|
|
|
|
return res;
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark StyleSheet utils
|
|
#pragma mark -
|
|
#endif
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReplaceStyleSheet(nsICSSStyleSheet *aNewSheet)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
nsAutoEditBatch batchIt(this);
|
|
|
|
if (mLastStyleSheet)
|
|
{
|
|
rv = RemoveStyleSheet(mLastStyleSheet);
|
|
//XXX: rv is ignored here, why?
|
|
}
|
|
|
|
rv = AddStyleSheet(aNewSheet);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify)
|
|
{
|
|
ApplyStyleSheetToPresShellDocument(aSheet, this);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static callback */
|
|
void nsHTMLEditor::ApplyStyleSheetToPresShellDocument(nsICSSStyleSheet* aSheet, void *aData)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
nsHTMLEditor *editor = NS_STATIC_CAST(nsHTMLEditor*, aData);
|
|
if (editor)
|
|
{
|
|
rv = editor->ReplaceStyleSheet(aSheet);
|
|
}
|
|
// XXX: we lose the return value here. Set a flag in the editor?
|
|
}
|
|
|
|
|
|
#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
|
|
nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
|
|
{
|
|
nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection
|
|
if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText)) )
|
|
ClearInlineStylesCache();
|
|
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
|
|
nsHTMLEditor::EndOperation()
|
|
{
|
|
// post processing
|
|
if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText) || (mAction==kOpIgnore)) )
|
|
ClearInlineStylesCache();
|
|
nsresult res = NS_OK;
|
|
if (mRules) res = mRules->AfterEdit(mAction, mDirection);
|
|
nsEditor::EndOperation(); // will clear mAction, mDirection
|
|
return res;
|
|
}
|
|
|
|
|
|
PRBool
|
|
nsHTMLEditor::TagCanContainTag(const nsString &aParentTag, const nsString &aChildTag)
|
|
{
|
|
// COtherDTD gives some unwanted results. We override them here.
|
|
nsAutoString olStr, ulStr, liStr;
|
|
olStr = NS_LITERAL_STRING("ol");
|
|
ulStr = NS_LITERAL_STRING("ul");
|
|
liStr = NS_LITERAL_STRING("li");
|
|
|
|
if ( aParentTag.EqualsIgnoreCase(olStr) ||
|
|
aParentTag.EqualsIgnoreCase(ulStr) )
|
|
{
|
|
// if parent is a list and tag is also a list, say "yes".
|
|
// This is because the editor does sublists illegally for now.
|
|
if (aChildTag.EqualsIgnoreCase(olStr) ||
|
|
aChildTag.EqualsIgnoreCase(ulStr) )
|
|
return PR_TRUE;
|
|
}
|
|
|
|
if ( aParentTag.EqualsIgnoreCase(liStr) )
|
|
{
|
|
// list items cant contain list items
|
|
if (aChildTag.EqualsIgnoreCase(liStr) )
|
|
return PR_FALSE;
|
|
}
|
|
|
|
/*
|
|
// if parent is a pre, and child is not inline, say "no"
|
|
if ( aParentTag.EqualsWithConversion("pre") )
|
|
{
|
|
if (aChildTag.EqualsWithConversion("__moz_text"))
|
|
return PR_TRUE;
|
|
PRInt32 childTagEnum, parentTagEnum;
|
|
nsAutoString non_const_childTag(aChildTag);
|
|
nsAutoString non_const_parentTag(aParentTag);
|
|
nsresult res = mDTD->StringTagToIntTag(non_const_childTag,&childTagEnum);
|
|
if (NS_FAILED(res)) return PR_FALSE;
|
|
res = mDTD->StringTagToIntTag(non_const_parentTag,&parentTagEnum);
|
|
if (NS_FAILED(res)) return PR_FALSE;
|
|
if (!mDTD->IsInlineElement(childTagEnum,parentTagEnum))
|
|
return PR_FALSE;
|
|
}
|
|
*/
|
|
// else fall thru
|
|
return nsEditor::TagCanContainTag(aParentTag, aChildTag);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::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 nsHTMLEditor::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;
|
|
}
|
|
|
|
|
|
// this will NOT find aAttribute unless aAttribute has a non-null value
|
|
// so singleton attributes like <Table border> will not be matched!
|
|
void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode *aNode,
|
|
nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue,
|
|
PRBool &aIsSet,
|
|
nsIDOMNode **aStyleNode,
|
|
nsString *outValue) const
|
|
{
|
|
nsresult result;
|
|
aIsSet = PR_FALSE; // must be initialized to false for code below to work
|
|
nsAutoString propName;
|
|
aProperty->ToString(propName);
|
|
nsCOMPtr<nsIDOMNode>node = aNode;
|
|
|
|
while (node)
|
|
{
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(node);
|
|
if (element)
|
|
{
|
|
nsAutoString tag, value;
|
|
element->GetTagName(tag);
|
|
if (propName.EqualsIgnoreCase(tag))
|
|
{
|
|
PRBool found = PR_FALSE;
|
|
if (aAttribute && 0!=aAttribute->Length())
|
|
{
|
|
element->GetAttribute(*aAttribute, value);
|
|
if (outValue) *outValue = value;
|
|
if (value.Length())
|
|
{
|
|
if (!aValue) {
|
|
found = PR_TRUE;
|
|
}
|
|
else if (aValue->EqualsIgnoreCase(value)) {
|
|
found = PR_TRUE;
|
|
}
|
|
else { // we found the prop with the attribute, but the value doesn't match
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
found = PR_TRUE;
|
|
}
|
|
if (found)
|
|
{
|
|
aIsSet = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode>temp;
|
|
result = node->GetParentNode(getter_AddRefs(temp));
|
|
if (NS_SUCCEEDED(result) && temp) {
|
|
node = do_QueryInterface(temp);
|
|
}
|
|
else {
|
|
node = do_QueryInterface(nsnull);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsHTMLEditor::IsTextStyleSet(nsIStyleContext *aSC,
|
|
nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
PRBool &aIsSet) const
|
|
{
|
|
aIsSet = PR_FALSE;
|
|
if (aSC && aProperty)
|
|
{
|
|
nsStyleFont* font = (nsStyleFont*)aSC->GetStyleData(eStyleStruct_Font);
|
|
if (nsIEditProperty::i==aProperty)
|
|
{
|
|
aIsSet = PRBool(font->mFont.style & NS_FONT_STYLE_ITALIC);
|
|
}
|
|
else if (nsIEditProperty::b==aProperty)
|
|
{ // XXX: check this logic with Peter
|
|
aIsSet = PRBool(font->mFont.weight > NS_FONT_WEIGHT_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#endif
|
|
|
|
//================================================================
|
|
// HTML Editor methods
|
|
//
|
|
// Note: Table Editing methods are implemented in nsTableEditor.cpp
|
|
//
|
|
|
|
|
|
PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement)
|
|
{
|
|
return nsHTMLEditUtils::InBody(aElement, this);
|
|
}
|
|
|
|
PRBool
|
|
nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement)
|
|
{
|
|
PRBool caretIsSet = PR_FALSE;
|
|
|
|
if (aElement && IsElementInBody(aElement))
|
|
{
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
|
|
if (content)
|
|
{
|
|
nsCOMPtr<nsIAtom> atom;
|
|
content->GetTag(*getter_AddRefs(atom));
|
|
if (atom.get() == nsIEditProperty::table ||
|
|
atom.get() == nsIEditProperty::tbody ||
|
|
atom.get() == nsIEditProperty::thead ||
|
|
atom.get() == nsIEditProperty::tfoot ||
|
|
atom.get() == nsIEditProperty::caption ||
|
|
atom.get() == nsIEditProperty::tr ||
|
|
atom.get() == nsIEditProperty::td )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
// This MUST succeed if IsElementInBody was TRUE
|
|
node->GetParentNode(getter_AddRefs(parent));
|
|
nsCOMPtr<nsIDOMNode>firstChild;
|
|
// Find deepest child
|
|
PRBool hasChild;
|
|
while (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
|
|
{
|
|
if (NS_SUCCEEDED(node->GetFirstChild(getter_AddRefs(firstChild))))
|
|
{
|
|
parent = node;
|
|
node = firstChild;
|
|
}
|
|
}
|
|
// Set selection at beginning of deepest node
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_SUCCEEDED(res) && selection && firstChild)
|
|
{
|
|
res = selection->Collapse(firstChild, 0);
|
|
if (NS_SUCCEEDED(res))
|
|
caretIsSet = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return caretIsSet;
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::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;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::IsSubordinateBlock(nsString &aTag, PRBool &aIsTag)
|
|
{
|
|
static char p[] = "p";
|
|
static char h1[] = "h1";
|
|
static char h2[] = "h2";
|
|
static char h3[] = "h3";
|
|
static char h4[] = "h4";
|
|
static char h5[] = "h5";
|
|
static char h6[] = "h6";
|
|
static char address[] = "address";
|
|
static char pre[] = "pre";
|
|
static char li[] = "li";
|
|
static char dt[] = "dt";
|
|
static char dd[] = "dd";
|
|
if (aTag.EqualsIgnoreCase(p) ||
|
|
aTag.EqualsIgnoreCase(h1) ||
|
|
aTag.EqualsIgnoreCase(h2) ||
|
|
aTag.EqualsIgnoreCase(h3) ||
|
|
aTag.EqualsIgnoreCase(h4) ||
|
|
aTag.EqualsIgnoreCase(h5) ||
|
|
aTag.EqualsIgnoreCase(h6) ||
|
|
aTag.EqualsIgnoreCase(address) ||
|
|
aTag.EqualsIgnoreCase(pre) ||
|
|
aTag.EqualsIgnoreCase(li) ||
|
|
aTag.EqualsIgnoreCase(dt) ||
|
|
aTag.EqualsIgnoreCase(dd) )
|
|
{
|
|
aIsTag = PR_TRUE;
|
|
}
|
|
else {
|
|
aIsTag = PR_FALSE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetEnclosingTable: find ancestor who is a table, if any
|
|
//
|
|
nsCOMPtr<nsIDOMNode>
|
|
nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode)
|
|
{
|
|
NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable");
|
|
nsCOMPtr<nsIDOMNode> tbl, tmp, node = aNode;
|
|
|
|
while (!tbl)
|
|
{
|
|
tmp = GetBlockNodeParent(node);
|
|
if (!tmp) break;
|
|
if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp;
|
|
node = tmp;
|
|
}
|
|
return tbl;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::DeleteSelectionAndPrepareToCreateNode(nsCOMPtr<nsIDOMNode> &parentSelectedNode, PRInt32& offsetOfNewNode)
|
|
{
|
|
nsresult result=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsISelection> selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool collapsed;
|
|
result = selection->GetIsCollapsed(&collapsed);
|
|
if (NS_SUCCEEDED(result) && !collapsed)
|
|
{
|
|
result = DeleteSelection(nsIEditor::eNone);
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
// get the new selection
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
#ifdef NS_DEBUG
|
|
nsCOMPtr<nsIDOMNode>testSelectedNode;
|
|
nsresult debugResult = selection->GetAnchorNode(getter_AddRefs(testSelectedNode));
|
|
// no selection is ok.
|
|
// if there is a selection, it must be collapsed
|
|
if (testSelectedNode)
|
|
{
|
|
PRBool testCollapsed;
|
|
debugResult = selection->GetIsCollapsed(&testCollapsed);
|
|
NS_ASSERTION((NS_SUCCEEDED(result)), "couldn't get a selection after deletion");
|
|
NS_ASSERTION(testCollapsed, "selection not reset after deletion");
|
|
}
|
|
#endif
|
|
}
|
|
// split the selected node
|
|
PRInt32 offsetOfSelectedNode;
|
|
result = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode));
|
|
if (NS_SUCCEEDED(result) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetOfSelectedNode)) && parentSelectedNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> selectedNode;
|
|
PRUint32 selectedNodeContentCount=0;
|
|
nsCOMPtr<nsIDOMCharacterData>selectedParentNodeAsText;
|
|
selectedParentNodeAsText = do_QueryInterface(parentSelectedNode);
|
|
|
|
offsetOfNewNode = offsetOfSelectedNode;
|
|
|
|
/* if the selection is a text node, split the text node if necesary
|
|
and compute where to put the new node
|
|
*/
|
|
if (selectedParentNodeAsText)
|
|
{
|
|
PRInt32 indexOfTextNodeInParent;
|
|
selectedNode = do_QueryInterface(parentSelectedNode);
|
|
selectedNode->GetParentNode(getter_AddRefs(parentSelectedNode));
|
|
selectedParentNodeAsText->GetLength(&selectedNodeContentCount);
|
|
GetChildOffset(selectedNode, parentSelectedNode, indexOfTextNodeInParent);
|
|
|
|
if ((offsetOfSelectedNode!=0) && (((PRUint32)offsetOfSelectedNode)!=selectedNodeContentCount))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newSiblingNode;
|
|
result = SplitNode(selectedNode, offsetOfSelectedNode, getter_AddRefs(newSiblingNode));
|
|
// now get the node's offset in it's parent, and insert the new tag there
|
|
if (NS_SUCCEEDED(result)) {
|
|
result = GetChildOffset(selectedNode, parentSelectedNode, offsetOfNewNode);
|
|
}
|
|
}
|
|
else
|
|
{ // determine where to insert the new node
|
|
if (0==offsetOfSelectedNode) {
|
|
offsetOfNewNode = indexOfTextNodeInParent; // insert new node as previous sibling to selection parent
|
|
}
|
|
else { // insert new node as last child
|
|
GetChildOffset(selectedNode, parentSelectedNode, offsetOfNewNode);
|
|
offsetOfNewNode++; // offsets are 0-based, and we need the index of the new node
|
|
}
|
|
}
|
|
}
|
|
// Here's where the new node was inserted
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
printf("InsertLineBreak into an empty document is not yet supported\n");
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#endif
|
|
|
|
void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode) return;
|
|
nsCOMPtr<nsIDOMNode> resultNode;
|
|
mCachedNode = do_QueryInterface(aNode);
|
|
IsTextPropertySetByContent(aNode, mBoldAtom, 0, 0, mCachedBoldStyle, getter_AddRefs(resultNode));
|
|
IsTextPropertySetByContent(aNode, mItalicAtom, 0, 0, mCachedItalicStyle, getter_AddRefs(resultNode));
|
|
IsTextPropertySetByContent(aNode, mUnderlineAtom, 0, 0, mCachedUnderlineStyle, getter_AddRefs(resultNode));
|
|
|
|
}
|
|
|
|
void nsHTMLEditor::ClearInlineStylesCache()
|
|
{
|
|
mCachedNode = nsnull;
|
|
}
|
|
|
|
#ifdef PRE_NODE_IN_BODY
|
|
nsCOMPtr<nsIDOMElement> nsHTMLEditor::FindPreElement()
|
|
{
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
nsEditor::GetDocument(getter_AddRefs(domdoc));
|
|
if (!domdoc)
|
|
return 0;
|
|
|
|
nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
|
|
if (!doc)
|
|
return 0;
|
|
|
|
nsIContent* rootContent = doc->GetRootContent();
|
|
if (!rootContent)
|
|
return 0;
|
|
|
|
nsCOMPtr<nsIDOMNode> rootNode (do_QueryInterface(rootContent));
|
|
if (!rootNode)
|
|
return 0;
|
|
|
|
nsString prestr ("PRE"); // GetFirstNodeOfType requires capitals
|
|
nsCOMPtr<nsIDOMNode> preNode;
|
|
if (!NS_SUCCEEDED(nsEditor::GetFirstNodeOfType(rootNode, prestr,
|
|
getter_AddRefs(preNode))))
|
|
return 0;
|
|
|
|
return do_QueryInterface(preNode);
|
|
}
|
|
#endif /* PRE_NODE_IN_BODY */
|
|
|
|
void nsHTMLEditor::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;
|
|
}
|
|
|
|
/* this method scans the selection for adjacent text nodes
|
|
* and collapses them into a single text node.
|
|
* "adjacent" means literally adjacent siblings of the same parent.
|
|
* Uses nsEditor::JoinNodes so action is undoable.
|
|
* Should be called within the context of a batch transaction.
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange)
|
|
{
|
|
if (!aInRange) return NS_ERROR_NULL_POINTER;
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
nsVoidArray textNodes; // we can't actually do anything during iteration, so store the text nodes in an array
|
|
// don't bother ref counting them because we know we can hold them for the
|
|
// lifetime of this method
|
|
|
|
|
|
// build a list of editable text nodes
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
nsresult result = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
iter->Init(aInRange);
|
|
nsCOMPtr<nsIContent> content;
|
|
result = iter->CurrentNode(getter_AddRefs(content));
|
|
if (!content) return NS_OK;
|
|
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> text = do_QueryInterface(content);
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(content);
|
|
if (text && node && IsEditable(node))
|
|
{
|
|
textNodes.AppendElement((void*)(node.get()));
|
|
}
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
|
|
// now that I have a list of text nodes, collapse adjacent text nodes
|
|
// NOTE: assumption that JoinNodes keeps the righthand node
|
|
nsIDOMNode *leftTextNode = (nsIDOMNode *)(textNodes.ElementAt(0));
|
|
nsIDOMNode *rightTextNode = (nsIDOMNode *)(textNodes.ElementAt(1));
|
|
|
|
while (leftTextNode && rightTextNode)
|
|
{
|
|
// get the prev sibling of the right node, and see if it's leftTextNode
|
|
nsCOMPtr<nsIDOMNode> prevSibOfRightNode;
|
|
result = GetPriorHTMLSibling(rightTextNode, &prevSibOfRightNode);
|
|
if (NS_FAILED(result)) return result;
|
|
if (prevSibOfRightNode && (prevSibOfRightNode.get() == leftTextNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
result = rightTextNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
result = JoinNodes(leftTextNode, rightTextNode, parent);
|
|
if (NS_FAILED(result)) return result;
|
|
}
|
|
|
|
textNodes.RemoveElementAt(0); // remove the leftmost text node from the list
|
|
leftTextNode = (nsIDOMNode *)(textNodes.ElementAt(0));
|
|
rightTextNode = (nsIDOMNode *)(textNodes.ElementAt(1));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetNextElementByTagName(nsIDOMElement *aCurrentElement,
|
|
const nsString *aTagName,
|
|
nsIDOMElement **aReturn)
|
|
{
|
|
nsresult res = NS_OK;
|
|
if (!aCurrentElement || !aTagName || !aReturn)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsIAtom *tagAtom = NS_NewAtom(*aTagName);
|
|
if (!tagAtom) { return NS_ERROR_NULL_POINTER; }
|
|
if (tagAtom==nsIEditProperty::th)
|
|
tagAtom=nsIEditProperty::td;
|
|
|
|
nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(aCurrentElement);
|
|
if (!currentNode)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aReturn = nsnull;
|
|
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
PRBool done = PR_FALSE;
|
|
|
|
do {
|
|
res = GetNextNode(currentNode, PR_TRUE, getter_AddRefs(nextNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!nextNode) break;
|
|
|
|
nsCOMPtr<nsIAtom> atom = GetTag(currentNode);
|
|
|
|
if (tagAtom==atom.get())
|
|
{
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(currentNode);
|
|
if (!element) return NS_ERROR_NULL_POINTER;
|
|
|
|
*aReturn = element;
|
|
NS_ADDREF(*aReturn);
|
|
done = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
currentNode = nextNode;
|
|
} while (!done);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection)
|
|
{
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
nsresult res = GetRootElement(getter_AddRefs(bodyElement));
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
if (!bodyElement) return NS_ERROR_NULL_POINTER;
|
|
res = aSelection->Collapse(bodyElement,0);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#endif
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChange( PRInt32 aSizeChange)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
ForceCompositionEnd();
|
|
|
|
// Get the selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_FAILURE;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
// Is the selection collapsed?
|
|
PRBool bCollapsed;
|
|
res = selection->GetIsCollapsed(&bCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's collapsed set typing state
|
|
if (bCollapsed)
|
|
{
|
|
nsCOMPtr<nsIAtom> atom;
|
|
if (aSizeChange==1) atom = nsIEditProperty::big;
|
|
else atom = nsIEditProperty::small;
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
return mTypeInState->SetProp(atom, nsAutoString(), nsAutoString());
|
|
}
|
|
|
|
// wrap with txn batching, rules sniffing, and selection preservation code
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpSetTextProperty, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
PRInt32 startOffset, endOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
// there are up to three phases here. There are all the nodes
|
|
// reported by the subtree iterator to be processed. And there
|
|
// are potentially a starting textnode and an ending textnode
|
|
// which are only partially contained by the range.
|
|
|
|
// lets handle the nodes reported by the iterator. These nodes
|
|
// are entirely contained in the selection range. We build up
|
|
// a list of them (since doing operations on the document during
|
|
// iteration would perturb the iterator).
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
res = iter->Init(range);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
iter->Next();
|
|
}
|
|
|
|
// now that we have the list, do the font size change on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = RelativeFontChangeOnNode(aSizeChange, node);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
// now check the start and end parents of the range to see if they need to
|
|
// be seperately handled (they do if they are text nodes, due to how the
|
|
// subtree iterator works - it will not have reported them).
|
|
if (IsTextNode(startNode) && IsEditable(startNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
PRInt32 startOffset;
|
|
PRUint32 textLen;
|
|
range->GetStartOffset(&startOffset);
|
|
nodeAsText->GetLength(&textLen);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, textLen);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
if (IsTextNode(endNode) && IsEditable(endNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(endNode);
|
|
PRInt32 endOffset;
|
|
range->GetEndOffset(&endOffset);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, 0, endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChangeOnTextNode( PRInt32 aSizeChange,
|
|
nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
if (!aTextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// dont need to do anything if no characters actually selected
|
|
if (aStartOffset == aEndOffset) return NS_OK;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp, node = do_QueryInterface(aTextNode);
|
|
|
|
// do we need to split the text node?
|
|
PRUint32 textLen;
|
|
aTextNode->GetLength(&textLen);
|
|
|
|
// -1 is a magic value meaning to the end of node
|
|
if (aEndOffset == -1) aEndOffset = textLen;
|
|
|
|
if ( (PRUint32)aEndOffset != textLen )
|
|
{
|
|
// we need to split off back of text node
|
|
res = SplitNode(node, aEndOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
node = tmp; // remember left node
|
|
}
|
|
if ( aStartOffset )
|
|
{
|
|
// we need to split off front of text node
|
|
res = SplitNode(node, aStartOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// reparent the node inside font node with appropriate relative size
|
|
res = InsertContainerAbove(node, &tmp, NS_ConvertASCIItoUCS2(aSizeChange==1 ? "big" : "small"));
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChangeOnNode( PRInt32 aSizeChange,
|
|
nsIDOMNode *aNode)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString tag;
|
|
if (aSizeChange == 1) tag.AssignWithConversion("big");
|
|
else tag.AssignWithConversion("small");
|
|
|
|
// is this node a text node?
|
|
if (IsTextNode(aNode))
|
|
{
|
|
res = InsertContainerAbove(aNode, &tmp, tag);
|
|
return res;
|
|
}
|
|
// is it the opposite of what we want?
|
|
if ( ((aSizeChange == 1) && nsHTMLEditUtils::IsSmall(aNode)) ||
|
|
((aSizeChange == -1) && nsHTMLEditUtils::IsBig(aNode)) )
|
|
{
|
|
// in that case, just remove this node and pull up the children
|
|
res = RemoveContainer(aNode);
|
|
return res;
|
|
}
|
|
// can it be put inside a "big" or "small"?
|
|
if (TagCanContain(tag, aNode))
|
|
{
|
|
// ok, chuck it in.
|
|
res = InsertContainerAbove(aNode, &tmp, tag);
|
|
return res;
|
|
}
|
|
// none of the above? then cycle through the children.
|
|
// MOOSE: we should group the children together if possible
|
|
// into a single "big" or "small". For the moment they are
|
|
// each getting their own.
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = aNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (childNodes)
|
|
{
|
|
PRInt32 j;
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
for (j=0 ; j < (PRInt32)childCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(j, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode))
|
|
{
|
|
res = RelativeFontChangeOnNode(aSizeChange, childNode);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPriorHTMLSibling: returns the previous editable sibling, if there is
|
|
// one within the parent
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode || !inNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
*outNode = nsnull;
|
|
nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
|
|
|
|
while (1)
|
|
{
|
|
res = node->GetPreviousSibling(getter_AddRefs(temp));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!temp) return NS_OK; // return null sibling
|
|
// if it's editable, we're done
|
|
if (IsEditable(temp)) break;
|
|
// otherwise try again
|
|
node = temp;
|
|
}
|
|
*outNode = temp;
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPriorHTMLSibling: returns the previous editable sibling, if there is
|
|
// one within the parent. just like above routine but
|
|
// takes a parent/offset instead of a node.
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode || !inParent) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
*outNode = nsnull;
|
|
if (!inOffset) return NS_OK; // return null sibling if at offset zero
|
|
nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset-1);
|
|
if (IsEditable(node))
|
|
{
|
|
*outNode = node;
|
|
return res;
|
|
}
|
|
// else
|
|
return GetPriorHTMLSibling(node, outNode);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNextHTMLSibling: returns the next editable sibling, if there is
|
|
// one within the parent
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
*outNode = nsnull;
|
|
nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
|
|
|
|
while (1)
|
|
{
|
|
res = node->GetNextSibling(getter_AddRefs(temp));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!temp) return NS_ERROR_FAILURE;
|
|
// if it's editable, we're done
|
|
if (IsEditable(temp)) break;
|
|
// otherwise try again
|
|
node = temp;
|
|
}
|
|
*outNode = temp;
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNextHTMLSibling: returns the next editable sibling, if there is
|
|
// one within the parent. just like above routine but
|
|
// takes a parent/offset instead of a node.
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode || !inParent) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
*outNode = nsnull;
|
|
nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset);
|
|
if (!node) return NS_OK; // return null sibling if no sibling
|
|
if (IsEditable(node))
|
|
{
|
|
*outNode = node;
|
|
return res;
|
|
}
|
|
// else
|
|
return GetPriorHTMLSibling(node, outNode);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPriorHTMLNode: returns the previous editable leaf node, if there is
|
|
// one within the <body>
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = GetPriorNode(inNode, PR_TRUE, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's not in the body, then zero it out
|
|
if (*outNode && !nsHTMLEditUtils::InBody(*outNode, this))
|
|
{
|
|
*outNode = nsnull;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPriorHTMLNode: same as above but takes {parent,offset} instead of node
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = GetPriorNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's not in the body, then zero it out
|
|
if (*outNode && !nsHTMLEditUtils::InBody(*outNode, this))
|
|
{
|
|
*outNode = nsnull;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNextHTMLNode: returns the next editable leaf node, if there is
|
|
// one within the <body>
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = GetNextNode(inNode, PR_TRUE, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's not in the body, then zero it out
|
|
if (*outNode && !nsHTMLEditUtils::InBody(*outNode, this))
|
|
{
|
|
*outNode = nsnull;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNHTMLextNode: same as above but takes {parent,offset} instead of node
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
if (!outNode) return NS_ERROR_NULL_POINTER;
|
|
nsresult res = GetNextNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's not in the body, then zero it out
|
|
if (*outNode && !nsHTMLEditUtils::InBody(*outNode, this))
|
|
{
|
|
*outNode = nsnull;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, PRBool *aOutIsFirst)
|
|
{
|
|
// check parms
|
|
if (!aOutIsFirst || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutIsFirst = PR_FALSE;
|
|
|
|
// find first editable child and compare it to aNode
|
|
nsCOMPtr<nsIDOMNode> parent, firstChild;
|
|
nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!parent) return NS_ERROR_FAILURE;
|
|
res = GetFirstEditableChild(parent, &firstChild);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
*aOutIsFirst = (firstChild.get() == aNode);
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast)
|
|
{
|
|
// check parms
|
|
if (!aOutIsLast || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutIsLast = PR_FALSE;
|
|
|
|
// find last editable child and compare it to aNode
|
|
nsCOMPtr<nsIDOMNode> parent, lastChild;
|
|
nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!parent) return NS_ERROR_FAILURE;
|
|
res = GetLastEditableChild(parent, &lastChild);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
*aOutIsLast = (lastChild.get() == aNode);
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstChild)
|
|
{
|
|
// check parms
|
|
if (!aOutFirstChild || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutFirstChild = nsnull;
|
|
|
|
// find first editable child
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsresult res = aNode->GetFirstChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (child && !IsEditable(child))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = child->GetNextSibling(getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
child = tmp;
|
|
}
|
|
|
|
*aOutFirstChild = child;
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastChild)
|
|
{
|
|
// check parms
|
|
if (!aOutLastChild || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutLastChild = nsnull;
|
|
|
|
// find last editable child
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsresult res = aNode->GetLastChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (child && !IsEditable(child))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = child->GetPreviousSibling(getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
child = tmp;
|
|
}
|
|
|
|
*aOutLastChild = child;
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstLeaf)
|
|
{
|
|
// check parms
|
|
if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutFirstLeaf = nsnull;
|
|
|
|
// find leftmost leaf
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsresult res = GetLeftmostChild(aNode, getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child)))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = GetNextHTMLNode(child, &tmp);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
|
|
// only accept nodes that are descendants of aNode
|
|
if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode))
|
|
child = tmp;
|
|
else
|
|
{
|
|
child = nsnull; // this will abort the loop
|
|
}
|
|
}
|
|
|
|
*aOutFirstLeaf = child;
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastLeaf)
|
|
{
|
|
// check parms
|
|
if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// init out parms
|
|
*aOutLastLeaf = nsnull;
|
|
|
|
// find leftmost leaf
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
nsresult res = GetRightmostChild(aNode, getter_AddRefs(child));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child)))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
res = GetPriorHTMLNode(child, &tmp);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
|
|
// only accept nodes that are descendants of aNode
|
|
if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode))
|
|
child = tmp;
|
|
else
|
|
{
|
|
child = nsnull;
|
|
}
|
|
}
|
|
|
|
*aOutLastLeaf = child;
|
|
return res;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsEmptyNode: figure out if aNode is an empty node.
|
|
// A block can have children and still be considered empty,
|
|
// if the children are empty or non-editable.
|
|
//
|
|
nsresult
|
|
nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode,
|
|
PRBool *outIsEmptyNode,
|
|
PRBool aMozBRDoesntCount,
|
|
PRBool aListOrCellNotEmpty,
|
|
PRBool aSafeToAskFrames)
|
|
{
|
|
if (!aNode || !outIsEmptyNode) return NS_ERROR_NULL_POINTER;
|
|
*outIsEmptyNode = PR_TRUE;
|
|
|
|
// effeciency hack - special case if it's a text node
|
|
if (nsEditor::IsTextNode(aNode))
|
|
{
|
|
PRUint32 length = 0;
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
|
|
nodeAsText = do_QueryInterface(aNode);
|
|
nodeAsText->GetLength(&length);
|
|
if (length) *outIsEmptyNode = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// if it's not a text node (handled above) and it's not a container,
|
|
// then we dont call it empty (it's an <hr>, or <br>, etc).
|
|
// Also, if it's an anchor then dont treat it as empty - even though
|
|
// anchors are containers, named anchors are "empty" but we don't
|
|
// want to treat them as such. Also, don't call ListItems or table
|
|
// cells empty if caller desires.
|
|
if (!IsContainer(aNode) || nsHTMLEditUtils::IsAnchor(aNode) ||
|
|
(aListOrCellNotEmpty && nsHTMLEditUtils::IsListItem(aNode)) ||
|
|
(aListOrCellNotEmpty && nsHTMLEditUtils::IsTableCell(aNode)) )
|
|
{
|
|
*outIsEmptyNode = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
// iterate over node. if no children, or all children are either
|
|
// empty text nodes or non-editable, then node qualifies as empty
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
nsCOMPtr<nsIContent> nodeAsContent = do_QueryInterface(aNode);
|
|
if (!nodeAsContent) return NS_ERROR_FAILURE;
|
|
nsresult res = nsComponentManager::CreateInstance(kCContentIteratorCID,
|
|
nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
res = iter->Init(nodeAsContent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsIContent> content;
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
|
|
// is the node editable and non-empty? if so, return false
|
|
if (nsEditor::IsEditable(node))
|
|
{
|
|
if (nsEditor::IsTextNode(node))
|
|
{
|
|
PRUint32 length = 0;
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
|
|
nodeAsText = do_QueryInterface(node);
|
|
nodeAsText->GetLength(&length);
|
|
if (aSafeToAskFrames)
|
|
{
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
res = GetSelectionController(getter_AddRefs(selCon));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selCon) return NS_ERROR_FAILURE;
|
|
PRBool isVisible = PR_FALSE;
|
|
// ask the selection controller for information about whether any
|
|
// of the data in the node is really rendered. This is really
|
|
// something that frames know about, but we aren't supposed to talk to frames.
|
|
// So we put a call in the selection controller interface, since it's already
|
|
// in bed with frames anyway. (this is a fix for bug 22227, and a
|
|
// partial fix for bug 46209)
|
|
res = selCon->CheckVisibility(node, 0, length, &isVisible);
|
|
if (NS_FAILED(res)) return res;
|
|
if (isVisible)
|
|
{
|
|
*outIsEmptyNode = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else if (length)
|
|
{
|
|
*outIsEmptyNode = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else // an editable, non-text node. we aren't an empty block
|
|
{
|
|
// is it the node we are iterating over?
|
|
if (node.get() == aNode) break;
|
|
// is it a moz-BR and did the caller ask us not to consider those relevant?
|
|
if (!(aMozBRDoesntCount && nsHTMLEditUtils::IsMozBR(node)))
|
|
{
|
|
// is it an empty node of some sort?
|
|
PRBool isEmptyNode;
|
|
res = IsEmptyNode(node, &isEmptyNode, aMozBRDoesntCount, aListOrCellNotEmpty);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!isEmptyNode)
|
|
{
|
|
// otherwise it ain't empty
|
|
*outIsEmptyNode = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|