зеркало из https://github.com/mozilla/gecko-dev.git
7169 строки
236 KiB
C++
7169 строки
236 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):
|
|
*/
|
|
#include "nsICaret.h"
|
|
|
|
|
|
#include "nsHTMLEditor.h"
|
|
#include "nsHTMLEditRules.h"
|
|
|
|
#include "nsEditorEventListeners.h"
|
|
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMEventReceiver.h"
|
|
#include "nsIDOMKeyEvent.h"
|
|
#include "nsIDOMKeyListener.h"
|
|
#include "nsIDOMMouseListener.h"
|
|
#include "nsIDOMSelection.h"
|
|
#include "nsIDOMHTMLAnchorElement.h"
|
|
#include "nsIDOMHTMLImageElement.h"
|
|
|
|
#include "nsISelectionController.h"
|
|
|
|
#include "nsICSSLoader.h"
|
|
#include "nsICSSStyleSheet.h"
|
|
#include "nsIHTMLContentContainer.h"
|
|
#include "nsIStyleSet.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 "nsIComponentManager.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsIDocumentEncoder.h"
|
|
#include "nsIDOMDocumentFragment.h"
|
|
#include "nsIPresShell.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"
|
|
|
|
// Transactionas
|
|
#include "PlaceholderTxn.h"
|
|
#include "nsStyleSheetTxns.h"
|
|
|
|
// Misc
|
|
#include "TextEditorTest.h"
|
|
#include "nsEditorUtils.h"
|
|
#include "nsIPref.h"
|
|
|
|
const PRUnichar nbsp = 160;
|
|
|
|
// HACK - CID for NavDTD until we can get at dtd via the document
|
|
// {a6cf9107-15b3-11d2-932e-00805f8add32}
|
|
#define NS_CNAVDTD_CID \
|
|
{ 0xa6cf9107, 0x15b3, 0x11d2, { 0x93, 0x2e, 0x0, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
|
|
static NS_DEFINE_CID(kCNavDTDCID, NS_CNAVDTD_CID);
|
|
|
|
static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID);
|
|
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
|
|
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
|
|
static NS_DEFINE_IID(kFileWidgetCID, NS_FILEWIDGET_CID);
|
|
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
|
|
|
|
// Drag & Drop, Clipboard Support
|
|
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
|
static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID);
|
|
|
|
#if defined(NS_DEBUG) && defined(DEBUG_buster)
|
|
static PRBool gNoisy = PR_FALSE;
|
|
#else
|
|
static const PRBool gNoisy = PR_FALSE;
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
|
|
#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.Equals("ol") ||
|
|
tagName.Equals("ul") ||
|
|
tagName.Equals("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)))
|
|
{
|
|
tagName.ToLowerCase();
|
|
// With only 3 tests, it doesn't
|
|
// seem worth using nsAtoms
|
|
if (tagName.Equals("td") ||
|
|
tagName.Equals("th"))
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsHTMLEditor::nsHTMLEditor()
|
|
: nsEditor()
|
|
, mTypeInState(nsnull)
|
|
, mRules(nsnull)
|
|
, mIsComposing(PR_FALSE)
|
|
, mMaxTextLength(-1)
|
|
{
|
|
// Done in nsEditor
|
|
// NS_INIT_REFCNT();
|
|
if (!gTypingTxnName)
|
|
gTypingTxnName = NS_NewAtom("Typing");
|
|
else
|
|
NS_ADDREF(gTypingTxnName);
|
|
if (!gIMETxnName)
|
|
gIMETxnName = NS_NewAtom("IME");
|
|
else
|
|
NS_ADDREF(gIMETxnName);
|
|
if (!gDeleteTxnName)
|
|
gDeleteTxnName = NS_NewAtom("Deleting");
|
|
else
|
|
NS_ADDREF(gDeleteTxnName);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
//the autopointers will clear themselves up.
|
|
//but we need to also remove the listeners or we have a leak
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
nsresult result = GetSelection(getter_AddRefs(selection));
|
|
// if we don't get the selection, just skip this
|
|
if (NS_SUCCEEDED(result) && selection)
|
|
{
|
|
nsCOMPtr<nsIDOMSelectionListener>listener;
|
|
listener = do_QueryInterface(mTypeInState);
|
|
if (listener) {
|
|
selection->RemoveSelectionListener(listener);
|
|
}
|
|
}
|
|
// 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.
|
|
if (mDocWeak)
|
|
{
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
|
|
if (doc)
|
|
{
|
|
nsCOMPtr<nsIDOMEventReceiver> erP = do_QueryInterface(doc, &result);
|
|
if (NS_SUCCEEDED(result) && erP)
|
|
{
|
|
if (mKeyListenerP) {
|
|
erP->RemoveEventListenerByIID(mKeyListenerP, nsIDOMKeyListener::GetIID());
|
|
}
|
|
if (mMouseListenerP) {
|
|
erP->RemoveEventListenerByIID(mMouseListenerP, nsIDOMMouseListener::GetIID());
|
|
}
|
|
if (mTextListenerP) {
|
|
erP->RemoveEventListenerByIID(mTextListenerP, nsIDOMTextListener::GetIID());
|
|
}
|
|
if (mCompositionListenerP) {
|
|
erP->RemoveEventListenerByIID(mCompositionListenerP, nsIDOMCompositionListener::GetIID());
|
|
}
|
|
if (mFocusListenerP) {
|
|
erP->RemoveEventListenerByIID(mFocusListenerP, nsIDOMFocusListener::GetIID());
|
|
}
|
|
if (mDragListenerP) {
|
|
erP->RemoveEventListenerByIID(mDragListenerP, nsIDOMDragListener::GetIID());
|
|
}
|
|
}
|
|
else
|
|
NS_NOTREACHED("~nsTextEditor");
|
|
}
|
|
}
|
|
|
|
// deleting a null pointer is safe
|
|
delete mRules;
|
|
|
|
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(nsIHTMLEditor::GetIID())) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIHTMLEditor*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(nsIEditorMailSupport::GetIID())) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIEditorMailSupport*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(nsITableEditor::GetIID())) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsITableEditor*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(nsIEditorStyleSheets::GetIID())) {
|
|
*aInstancePtr = NS_STATIC_CAST(nsIEditorStyleSheets*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
if (aIID.Equals(nsICSSLoaderObserver::GetIID())) {
|
|
*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, 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, aFlags);
|
|
if (NS_FAILED(result)) { return result; }
|
|
|
|
nsCOMPtr<nsIDOMElement> bodyElement;
|
|
result = nsEditor::GetBodyElement(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<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) { return result; }
|
|
if (selection)
|
|
{
|
|
nsCOMPtr<nsIDOMSelectionListener>listener;
|
|
listener = do_QueryInterface(mTypeInState);
|
|
if (listener) {
|
|
selection->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(kCNavDTDCID, nsnull,
|
|
nsIDTD::GetIID(), getter_AddRefs(mDTD));
|
|
if (!mDTD) result = NS_ERROR_FAILURE;
|
|
|
|
// Init the rules system
|
|
// XXX: ERROR CHECKING should InitRules return an error, and then we could check it here?
|
|
InitRules();
|
|
|
|
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) && nsnull != domdoc) {
|
|
const nsString tag("meta");
|
|
nsString newValue;
|
|
nsCOMPtr<nsIDOMNodeList>nodeList;
|
|
nsCOMPtr<nsIDOMNode>node;
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
PRBool newMetaCharset = PR_TRUE;
|
|
|
|
// get a list of META tags
|
|
result = domdoc->GetElementsByTagName(tag, getter_AddRefs(nodeList));
|
|
if (NS_SUCCEEDED(result) && nsnull != nodeList) {
|
|
PRUint32 listLength = 0;
|
|
(void) nodeList->GetLength(&listLength);
|
|
|
|
for (PRUint32 i = 0; i < listLength; i++) {
|
|
nodeList->Item(i, getter_AddRefs(node));
|
|
if (nsnull == node) continue;
|
|
element = do_QueryInterface(node);
|
|
if (nsnull == element) continue;
|
|
|
|
const nsString content("charset=");
|
|
nsString currentValue;
|
|
|
|
if (NS_FAILED(element->GetAttribute("http-equiv", currentValue))) continue;
|
|
|
|
if (kNotFound != currentValue.Find("content-type", PR_TRUE)) {
|
|
if (NS_FAILED(element->GetAttribute("content", currentValue))) continue;
|
|
|
|
PRInt32 offset = currentValue.Find(content.GetUnicode(), PR_TRUE);
|
|
if (kNotFound != offset) {
|
|
newValue.SetString(currentValue, offset); // copy current value before "charset=" (e.g. text/html)
|
|
newValue.Append(content);
|
|
newValue.Append(characterSet);
|
|
result = element->SetAttribute("content", newValue);
|
|
newMetaCharset = PR_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (newMetaCharset) {
|
|
;// TODO: create a new tag
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// get the DOM event receiver
|
|
nsCOMPtr<nsIDOMEventReceiver> erP;
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
|
|
if (!doc) return NS_ERROR_NOT_INITIALIZED;
|
|
result = doc->QueryInterface(nsIDOMEventReceiver::GetIID(), getter_AddRefs(erP));
|
|
if (NS_FAILED(result)) {
|
|
HandleEventListenerError();
|
|
return result;
|
|
}
|
|
|
|
// register the event listeners with the DOM event reveiver
|
|
result = erP->AddEventListenerByIID(mKeyListenerP, nsIDOMKeyListener::GetIID());
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register key listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mMouseListenerP, nsIDOMMouseListener::GetIID());
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register mouse listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mFocusListenerP, nsIDOMFocusListener::GetIID());
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register focus listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mTextListenerP, nsIDOMTextListener::GetIID());
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register text listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mCompositionListenerP, nsIDOMCompositionListener::GetIID());
|
|
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register composition listener");
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = erP->AddEventListenerByIID(mDragListenerP, nsIDOMDragListener::GetIID());
|
|
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);
|
|
}
|
|
|
|
|
|
void nsHTMLEditor::InitRules()
|
|
{
|
|
// instantiate the rules for this text editor
|
|
// XXX: we should be told which set of rules to instantiate
|
|
if (mFlags & eEditorPlaintextMask)
|
|
mRules = new nsTextEditRules();
|
|
else
|
|
mRules = new nsHTMLEditRules();
|
|
|
|
mRules->Init(this, mFlags);
|
|
}
|
|
|
|
|
|
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::EditorKeyPress(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))
|
|
{
|
|
PRBool bHandled = PR_FALSE;
|
|
res = TabInTable(isShift, &bHandled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (bHandled) return res;
|
|
}
|
|
else if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
|
|
|| keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
|
|
{
|
|
nsAutoString empty;
|
|
if (isShift && !(mFlags&eEditorPlaintextBit))
|
|
{
|
|
return TypedText(empty, eTypedBR); // only inserts a br node
|
|
}
|
|
else
|
|
{
|
|
return TypedText(empty, eTypedBreak); // uses rules to figure out what to insert
|
|
}
|
|
}
|
|
|
|
// 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, 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 nsString& 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 InsertBreak(); // 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 selection start
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 selOffset;
|
|
res = GetStartNodeAndOffset(selection, &parent, &selOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!parent) return res;
|
|
nsCOMPtr<nsIDOMNode> selNode = GetChildAt(parent, selOffset);
|
|
if (!selNode) selNode = parent;
|
|
|
|
// is it in a table block of some kind?
|
|
nsCOMPtr<nsIDOMNode> block;
|
|
if (IsBlockNode(selNode)) block = selNode;
|
|
else block = GetBlockNodeParent(selNode);
|
|
if (!block) return res;
|
|
|
|
if (IsTableElement(block))
|
|
{
|
|
// find enclosing table
|
|
nsCOMPtr<nsIDOMNode> tbl;
|
|
if (IsTable(block)) tbl = block;
|
|
else tbl = GetEnclosingTable(block);
|
|
if (!tbl) return res;
|
|
// advance to next cell
|
|
// first create an iterator over the table
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
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(block);
|
|
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)) return res;
|
|
res = iter->CurrentNode(getter_AddRefs(cNode));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(cNode);
|
|
if (IsTableCell(node) && (GetEnclosingTable(node) == tbl))
|
|
{
|
|
selection->Collapse(node, 0);
|
|
*outHandled = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
} while (iter->IsDone() == NS_ENUMERATOR_FALSE);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode)
|
|
{
|
|
if (!aNode || !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<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
|
|
nsAutoString brType("br");
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
if (nodeAsText)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
PRInt32 offset;
|
|
PRUint32 len;
|
|
nodeAsText->GetLength(&len);
|
|
GetNodeLocation(aNode, &tmp, &offset);
|
|
if (!tmp) return NS_ERROR_FAILURE;
|
|
if (!aOffset)
|
|
{
|
|
// we are already set to go
|
|
}
|
|
else if (aOffset == (PRInt32)len)
|
|
{
|
|
// update offset to point AFTER the text node
|
|
offset++;
|
|
}
|
|
else
|
|
{
|
|
// split the text node
|
|
res = SplitNode(aNode, aOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
res = GetNodeLocation(aNode, &tmp, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
// create br
|
|
res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
res = CreateNode(brType, aNode, aOffset, getter_AddRefs(brNode));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
*outBRNode = brNode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
|
|
{
|
|
PRBool bCollapsed;
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
|
|
if (!outBRNode) return NS_ERROR_NULL_POINTER;
|
|
*outBRNode = nsnull;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
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 in righthand text node
|
|
// moose
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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();
|
|
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("---------- start nsTextEditor::SetTextProperty %s ----------\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
|
|
nsresult result=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kSetTextProperty);
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!cancel && !handled)
|
|
{
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (PR_TRUE==isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
SetTypeInStateForProperty(*mTypeInState, aProperty, aAttribute, aValue);
|
|
}
|
|
else
|
|
{
|
|
// set the text property for all selected ranges
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = selection->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));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!currentItem) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIDOMNode>commonParent;
|
|
result = range->GetCommonParent(getter_AddRefs(commonParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!commonParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRInt32 startOffset, endOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
nsCOMPtr<nsIDOMNode> startParent; nsCOMPtr<nsIDOMNode> endParent;
|
|
range->GetStartParent(getter_AddRefs(startParent));
|
|
range->GetEndParent(getter_AddRefs(endParent));
|
|
PRBool startIsText = IsTextNode(startParent);
|
|
PRBool endIsText = IsTextNode(endParent);
|
|
if ((PR_TRUE==startIsText) && (PR_TRUE==endIsText) &&
|
|
(startParent.get()==endParent.get()))
|
|
{ // the range is entirely contained within a single text node
|
|
// commonParent==aStartParent, so get the "real" parent of the selection
|
|
startParent->GetParentNode(getter_AddRefs(commonParent));
|
|
result = SetTextPropertiesForNode(startParent, commonParent,
|
|
startOffset, endOffset,
|
|
aProperty, aAttribute, aValue);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> startGrandParent;
|
|
result = startParent->GetParentNode(getter_AddRefs(startGrandParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!startGrandParent) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNode> endGrandParent;
|
|
result = endParent->GetParentNode(getter_AddRefs(endGrandParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!endGrandParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool canCollapseStyleNode = PR_FALSE;
|
|
if ((PR_TRUE==startIsText) && (PR_TRUE==endIsText) &&
|
|
endGrandParent.get()==startGrandParent.get())
|
|
{
|
|
result = IntermediateNodesAreInline(range, startParent, startOffset,
|
|
endParent, endOffset,
|
|
canCollapseStyleNode);
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (PR_TRUE==canCollapseStyleNode)
|
|
{ // the range is between 2 nodes that have a common (immediate) grandparent,
|
|
// and any intermediate nodes are just inline style nodes
|
|
result = SetTextPropertiesForNodesWithSameParent(startParent,startOffset,
|
|
endParent, endOffset,
|
|
commonParent,
|
|
aProperty, aAttribute, aValue);
|
|
}
|
|
else
|
|
{ // the range is between 2 nodes that have no simple relationship
|
|
result = SetTextPropertiesForNodeWithDifferentParents(range,
|
|
startParent,startOffset,
|
|
endParent, endOffset,
|
|
commonParent,
|
|
aProperty, aAttribute, aValue);
|
|
}
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{ // compute a range for the selection
|
|
// don't want to actually do anything with selection, because
|
|
// we are still iterating through it. Just want to create and remember
|
|
// an nsIDOMRange, and later add the range to the selection after clearing it.
|
|
// XXX: I'm blocked here because nsIDOMSelection doesn't provide a mechanism
|
|
// for setting a compound selection yet.
|
|
}
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("---------- end nsTextEditor::SetTextProperty %s ----------\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue,
|
|
PRBool &aFirst, PRBool &aAny, PRBool &aAll)
|
|
{
|
|
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<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = selection->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) );
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
iter->Init(range);
|
|
nsCOMPtr<nsIContent> content;
|
|
result = 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 (PR_FALSE==isCollapsed && PR_TRUE==first && PR_TRUE==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 (PR_TRUE==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 (PR_FALSE==skipNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>node;
|
|
node = do_QueryInterface(content);
|
|
if (node)
|
|
{
|
|
PRBool isSet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==first)
|
|
{
|
|
aFirst = isSet;
|
|
first = PR_FALSE;
|
|
}
|
|
if (PR_TRUE==isSet) {
|
|
aAny = PR_TRUE;
|
|
}
|
|
else {
|
|
aAll = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
}
|
|
if (PR_FALSE==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::RemoveInlineProperty(nsIAtom *aProperty, const nsString *aAttribute)
|
|
{
|
|
if (!aProperty) { return NS_ERROR_NULL_POINTER; }
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
ForceCompositionEnd();
|
|
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpRemoveTextProperty, nsIEditor::eNext);
|
|
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("---------- start nsTextEditor::RemoveInlineProperty %s ----------\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
|
|
nsresult result;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveTextProperty);
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!cancel && !handled)
|
|
{
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (PR_TRUE==isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
SetTypeInStateForProperty(*mTypeInState, aProperty, aAttribute, nsnull);
|
|
}
|
|
else
|
|
{
|
|
// removing text properties can really shuffle text nodes around
|
|
// so we need to keep some extra state to restore a reasonable selection
|
|
// after we're done
|
|
nsCOMPtr<nsIDOMNode> parentForSelection; // selection's block parent
|
|
PRInt32 rangeStartOffset, rangeEndOffset;
|
|
GetTextSelectionOffsetsForRange(selection, getter_AddRefs(parentForSelection),
|
|
rangeStartOffset, rangeEndOffset);
|
|
nsCOMPtr<nsIDOMNode> startParent, endParent;
|
|
PRInt32 startOffset, endOffset;
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = selection->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));
|
|
if (NS_FAILED(result)) return result;
|
|
//XXX: should be a while loop to get all ranged in selection
|
|
if (currentItem)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIDOMNode>commonParent;
|
|
result = range->GetCommonParent(getter_AddRefs(commonParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!commonParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
result = range->GetStartParent(getter_AddRefs(startParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!startParent) return NS_ERROR_NULL_POINTER;
|
|
result = range->GetEndParent(getter_AddRefs(endParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!endParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
if (startParent.get()==endParent.get())
|
|
{ // the range is entirely contained within a single text node
|
|
// commonParent==aStartParent, so get the "real" parent of the selection
|
|
startParent->GetParentNode(getter_AddRefs(commonParent));
|
|
result = RemoveTextPropertiesForNode(startParent, commonParent,
|
|
startOffset, endOffset,
|
|
aProperty, nsnull);
|
|
}
|
|
else
|
|
{
|
|
result = RemoveTextPropertiesForNodeWithDifferentParents(startParent,startOffset,
|
|
endParent, endOffset,
|
|
commonParent,
|
|
aProperty, nsnull);
|
|
}
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
{ // compute a range for the selection
|
|
// don't want to actually do anything with selection, because
|
|
// we are still iterating through it. Just want to create and remember
|
|
// an nsIDOMRange, and later add the range to the selection after clearing it.
|
|
// XXX: I'm blocked here because nsIDOMSelection doesn't provide a mechanism
|
|
// for setting a compound selection yet.
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = CollapseAdjacentTextNodes(selection); // we may have created wasteful consecutive text nodes. collapse them.
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// XXX: ERROR_HANDLING should get a result and validate it
|
|
ResetTextSelectionForRange(parentForSelection, rangeStartOffset, rangeEndOffset, selection);
|
|
}
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("---------- end nsTextEditor::RemoveInlineProperty %s ----------\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize()
|
|
{
|
|
//TODO: Write this!
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize()
|
|
{
|
|
//TODO: Write this!
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetTypingState(nsIAtom *aProperty, PRBool &aSet)
|
|
{
|
|
if (!aProperty)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
if (!mTypeInState)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
PRUint32 styleEnum;
|
|
mTypeInState->GetEnumForName(aProperty, styleEnum);
|
|
if (styleEnum == NS_TYPEINSTATE_UNKNOWN)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
mTypeInState->GetProp(styleEnum, aSet);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetTypingStateValue(nsIAtom *aProperty, nsString &aValue)
|
|
{
|
|
if (!aProperty)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
if (!mTypeInState)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
PRUint32 styleEnum;
|
|
mTypeInState->GetEnumForName(aProperty, styleEnum);
|
|
if (styleEnum == NS_TYPEINSTATE_UNKNOWN)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
mTypeInState->GetPropValue(styleEnum, aValue);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DeleteSelection(nsIEditor::EDirection aAction)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
PRBool cancel, handled;
|
|
|
|
// If it's one of these modes,
|
|
// we have to extend the selection first.
|
|
// This can't happen inside selection batching --
|
|
// selection refuses to move if batching is on.
|
|
if (aAction == eNextWord || aAction == ePreviousWord
|
|
|| aAction == eToBeginningOfLine || aAction == eToEndOfLine)
|
|
{
|
|
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
|
|
nsCOMPtr<nsISelectionController> selCont (do_QueryInterface(ps));
|
|
if (!selCont)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
|
|
nsresult result;
|
|
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:
|
|
result = selCont->IntraLineMove(PR_FALSE, PR_TRUE);
|
|
aAction = eNone;
|
|
break;
|
|
case eToEndOfLine:
|
|
result = selCont->IntraLineMove(PR_TRUE, PR_TRUE);
|
|
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;
|
|
}
|
|
}
|
|
|
|
// delete placeholder txns merge.
|
|
nsAutoPlaceHolderBatch batch(this, gDeleteTxnName);
|
|
nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
|
|
|
|
// pre-process
|
|
nsresult 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 nsString& aStringToInsert)
|
|
{
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIDOMSelection> 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;
|
|
nsTextRulesInfo ruleInfo(theAction);
|
|
ruleInfo.inString = &aStringToInsert;
|
|
ruleInfo.outString = &resultString;
|
|
ruleInfo.typeInState = *mTypeInState;
|
|
ruleInfo.maxLength = mMaxTextLength;
|
|
|
|
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!cancel && !handled)
|
|
{
|
|
result = InsertTextImpl(resultString);
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
result = mRules->DidDoAction(selection, &ruleInfo, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsString& aInputString)
|
|
{
|
|
nsAutoString charset;
|
|
return InsertHTMLWithCharset(aInputString, charset);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertHTMLWithCharset(const nsString& aInputString,
|
|
const nsString& aCharset)
|
|
{
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
|
|
nsresult res;
|
|
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
PRInt32 offsetOfNewNode;
|
|
res = DeleteSelectionAndPrepareToCreateNode(parentNode, offsetOfNewNode);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
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(nsHTMLEditRules::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)
|
|
{
|
|
// 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(aInputString,
|
|
getter_AddRefs(docfrag));
|
|
if (NS_FAILED(res))
|
|
{
|
|
#ifdef DEBUG
|
|
printf("Couldn't create contextual fragment: error was %d\n", res);
|
|
#endif
|
|
return res;
|
|
}
|
|
|
|
#if defined(DEBUG_akkana_verbose)
|
|
printf("============ Fragment dump :===========\n");
|
|
|
|
nsCOMPtr<nsIContent> fragc (do_QueryInterface(docfrag));
|
|
if (!fragc)
|
|
printf("Couldn't get fragment is nsIContent\n");
|
|
else
|
|
fragc->List(stdout);
|
|
#endif
|
|
|
|
// Insert the contents of the document fragment:
|
|
nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
|
|
|
|
// Loop over the contents of the fragment:
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
|
|
if (NS_FAILED(res))
|
|
{
|
|
printf("GetFirstChild failed!\n");
|
|
return res;
|
|
}
|
|
while (child)
|
|
{
|
|
#if defined(DEBUG_akkana_verbose)
|
|
printf("About to try to insert this node:\n");
|
|
nsCOMPtr<nsIContent> nodec (do_QueryInterface(child));
|
|
if (nodec) nodec->List(stdout);
|
|
printf("-----\n");
|
|
#endif
|
|
// Get the next sibling before inserting the node;
|
|
// when we insert the node, it moves into the main doc tree
|
|
// so we'll no longer be able to get the siblings in the doc frag.
|
|
nsCOMPtr<nsIDOMNode> nextSib;
|
|
child->GetNextSibling(getter_AddRefs(nextSib));
|
|
// Ignore the return value, we'll check child when we loop around again.
|
|
|
|
// Now we can insert the node.
|
|
res = InsertNode(child, parentNode, offsetOfNewNode++);
|
|
if (NS_FAILED(res))
|
|
break;
|
|
child = nextSib;
|
|
}
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
|
|
// Now collapse the selection to the end of what we just inserted:
|
|
selection->Collapse(parentNode, offsetOfNewNode);
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertBreak()
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertBreak, nsIEditor::eNext);
|
|
nsCOMPtr<nsIDOMSelection> 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(nsHTMLEditRules::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("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))
|
|
{
|
|
if (-1==offsetInParent)
|
|
{
|
|
nextNode->GetParentNode(getter_AddRefs(parent));
|
|
res = GetChildOffset(nextNode, parent, offsetInParent);
|
|
if (NS_SUCCEEDED(res)) {
|
|
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;
|
|
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
|
|
nsCOMPtr<nsIDOMSelection>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(nsHTMLEditRules::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
|
|
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
|
|
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
|
|
nsAutoString tagName;
|
|
aElement->GetNodeName(tagName);
|
|
tagName.ToLowerCase();
|
|
nsCOMPtr<nsIDOMNode> parent = parentSelectedNode;
|
|
nsCOMPtr<nsIDOMNode> topChild = parentSelectedNode;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString parentTagName;
|
|
PRBool isRoot;
|
|
|
|
// Search up the parent chain to find a suitable container
|
|
while (!CanContainTag(parent, tagName))
|
|
{
|
|
// If the current parent is a root (body or table cell)
|
|
// then go no further - we can't insert
|
|
parent->GetNodeName(parentTagName);
|
|
res = IsRootTag(parentTagName, isRoot);
|
|
if (!NS_SUCCEEDED(res) || isRoot)
|
|
return NS_ERROR_FAILURE;
|
|
// Get the next parent
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
if (!tmp)
|
|
return NS_ERROR_FAILURE;
|
|
topChild = parent;
|
|
parent = tmp;
|
|
}
|
|
#ifdef DEBUG_cmanske
|
|
{
|
|
nsAutoString name;
|
|
parent->GetNodeName(name);
|
|
printf("Parent node to insert under: ");
|
|
wprintf(name.GetUnicode());
|
|
printf("\n");
|
|
topChild->GetNodeName(name);
|
|
printf("TopChild to split: ");
|
|
wprintf(name.GetUnicode());
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
if (parent != topChild)
|
|
{
|
|
// we need to split some levels above the original selection parent
|
|
res = SplitNodeDeep(topChild, parentSelectedNode, offsetForInsert, &offsetForInsert);
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
}
|
|
// Now we can insert the new node
|
|
res = InsertNode(aElement, parent, offsetForInsert);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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<nsIDOMSelection> 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<nsIDOMSelection> 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<nsIDOMSelection> 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)
|
|
{
|
|
//Kinda sad to waste memory just to force lower case
|
|
nsAutoString tag = aParagraphFormat;
|
|
tag.ToLowerCase();
|
|
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<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// 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("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 = selection->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->GetStartParent(getter_AddRefs(startParent));
|
|
if (NS_SUCCEEDED(res) && startParent)
|
|
{
|
|
nsCOMPtr<nsIDOMElement> blockParent;
|
|
if (aGetLists)
|
|
{
|
|
// Get the "ol", "ul", or "dl" parent element
|
|
res = GetElementOrParentByTagName("list", startParent, getter_AddRefs(blockParent));
|
|
} else {
|
|
res = GetBlockParent(startParent, getter_AddRefs(blockParent));
|
|
}
|
|
if (NS_SUCCEEDED(res) && blockParent)
|
|
{
|
|
nsAutoString blockParentTag;
|
|
blockParent->GetTagName(blockParentTag);
|
|
PRBool isRoot;
|
|
IsRootTag(blockParentTag, isRoot);
|
|
if ((PR_FALSE==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;
|
|
}
|
|
|
|
|
|
// get the paragraph style(s) for the selection
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetParagraphTags(nsStringArray *aTagList)
|
|
{
|
|
#if 0
|
|
if (gNoisy) { printf("---------- nsHTMLEditor::GetPargraphTags ----------\n"); }
|
|
#endif
|
|
return GetParentBlockTags(aTagList, PR_FALSE);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetListTags(nsStringArray *aTagList)
|
|
{
|
|
return GetParentBlockTags(aTagList, PR_TRUE);
|
|
}
|
|
|
|
/*
|
|
// use this when block parents are to be added regardless of current state
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::AddBlockParent(nsString& aParentTag)
|
|
{
|
|
if (gNoisy)
|
|
{
|
|
char *tag = aParentTag.ToNewCString();
|
|
printf("---------- nsHTMLEditor::AddBlockParent %s ----------\n", tag);
|
|
nsCRT::free(tag);
|
|
}
|
|
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// set the block parent for all selected ranges
|
|
nsAutoSelectionReset selectionResetter(selection);
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
enumerator = do_QueryInterface(selection);
|
|
if (enumerator)
|
|
{
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(res)) && (currentItem))
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
// scan the range for all the independent block content blockSections
|
|
// and apply the transformation to them
|
|
res = ReParentContentOfRange(range, aParentTag, eInsertParent);
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(res))
|
|
{ // set the selection
|
|
// XXX: can't do anything until I can create ranges
|
|
}
|
|
if (gNoisy) {printf("Finished nsHTMLEditor::AddBlockParent with this content:\n"); DebugDumpContent(); } // DEBUG
|
|
|
|
return res;
|
|
}
|
|
|
|
// use this when a paragraph type is being transformed from one type to another
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReplaceBlockParent(nsString& aParentTag)
|
|
{
|
|
if (gNoisy)
|
|
{
|
|
char *tag = aParentTag.ToNewCString();
|
|
printf("---------- nsHTMLEditor::ReplaceBlockParent %s ----------\n", tag);
|
|
nsCRT::free(tag);
|
|
}
|
|
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoSelectionReset selectionResetter(selection);
|
|
// set the block parent for all selected ranges
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selection->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));
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(res)) && (currentItem))
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
// scan the range for all the independent block content blockSections
|
|
// and apply the transformation to them
|
|
res = ReParentContentOfRange(range, aParentTag, eReplaceParent);
|
|
}
|
|
if (gNoisy) {printf("Finished nsHTMLEditor::AddBlockParent with this content:\n"); DebugDumpContent(); } // DEBUG
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParagraphStyle()
|
|
{
|
|
if (gNoisy) {
|
|
printf("---------- nsHTMLEditor::RemoveParagraphStyle ----------\n");
|
|
}
|
|
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoSelectionReset selectionResetter(selection);
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selection->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));
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(res)) && (currentItem))
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
res = RemoveParagraphStyleFromRange(range);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParent(const nsString &aParentTag)
|
|
{
|
|
if (gNoisy) {
|
|
printf("---------- nsHTMLEditor::RemoveParent ----------\n");
|
|
}
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoSelectionReset selectionResetter(selection);
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selection->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));
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(res)) && (currentItem))
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
res = RemoveParentFromRange(aParentTag, range);
|
|
}
|
|
return res;
|
|
}
|
|
*/
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::MakeOrChangeList(const nsString& aListType)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIDOMSelection> 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(nsHTMLEditRules::kMakeList);
|
|
if (aListType == "ol") ruleInfo.bOrdered = PR_TRUE;
|
|
else ruleInfo.bOrdered = PR_FALSE;
|
|
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("li");
|
|
nsCOMPtr<nsIDOMNode> newItem;
|
|
res = CreateNode(tag, newList, 0, getter_AddRefs(newItem));
|
|
if (NS_FAILED(res)) return res;
|
|
// put a space in it so layout will draw the list item
|
|
// XXX - revisit when layout is fixed
|
|
res = selection->Collapse(newItem,0);
|
|
if (NS_FAILED(res)) return res;
|
|
#if 0
|
|
nsAutoString theText(" ");
|
|
res = InsertText(theText);
|
|
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;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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<nsIDOMSelection> 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(nsHTMLEditRules::kRemoveList);
|
|
if (aListType == "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;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertBasicBlock(const nsString& aBlockType)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
nsCOMPtr<nsIDOMSelection> 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(nsHTMLEditRules::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;
|
|
|
|
// xxx
|
|
// i'm def'ing this out to see if auto br insertion is working for this case
|
|
#if 0
|
|
// put a space in it so layout will draw it
|
|
res = selection->Collapse(newBlock,0);
|
|
if (NS_FAILED(res)) return res;
|
|
nsAutoString theText(nbsp);
|
|
res = InsertText(theText);
|
|
if (NS_FAILED(res)) return res;
|
|
#endif
|
|
// 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;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Indent(const nsString& aIndent)
|
|
{
|
|
nsresult res;
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
|
|
PRBool cancel, handled;
|
|
PRInt32 theAction = nsHTMLEditRules::kIndent;
|
|
PRInt32 opID = kOpIndent;
|
|
if (aIndent == "outdent")
|
|
{
|
|
theAction = nsHTMLEditRules::kOutdent;
|
|
opID = kOpOutdent;
|
|
}
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
|
|
|
|
// pre-process
|
|
nsCOMPtr<nsIDOMSelection> 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("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("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;
|
|
nsAutoString theText(" ");
|
|
res = InsertText(theText);
|
|
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<nsIDOMSelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsTextRulesInfo ruleInfo(nsHTMLEditRules::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;
|
|
// If no node supplied, use anchor node of current selection
|
|
if (!aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
//TODO: Do I need to release the node?
|
|
if(NS_FAILED(selection->GetAnchorNode(&aNode)))
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsAutoString TagName = aTagName;
|
|
TagName.ToLowerCase();
|
|
PRBool getLink = IsLink(TagName);
|
|
PRBool getNamedAnchor = IsNamedAnchor(TagName);
|
|
if ( getLink || getNamedAnchor)
|
|
{
|
|
TagName = "a";
|
|
}
|
|
PRBool findTableCell = aTagName.EqualsIgnoreCase("td");
|
|
PRBool findList = aTagName.EqualsIgnoreCase("list");
|
|
|
|
// default is null - no element found
|
|
*aReturn = nsnull;
|
|
|
|
nsCOMPtr<nsIDOMNode> currentNode = aNode;
|
|
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);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetSelectedElement(const nsString& aTagName, nsIDOMElement** aReturn)
|
|
{
|
|
if (!aReturn )
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// default is null - no element found
|
|
*aReturn = nsnull;
|
|
|
|
nsAutoString TagName = aTagName;
|
|
TagName.ToLowerCase();
|
|
// Empty string indicates we should match any element tag
|
|
PRBool anyTag = (TagName == "");
|
|
|
|
//Note that this doesn't need to go through the transaction system
|
|
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
//PRBool first=PR_TRUE;
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
nsCOMPtr<nsIDOMElement> selectedElement;
|
|
PRBool bNodeFound = PR_FALSE;
|
|
|
|
if (IsLink(TagName))
|
|
{
|
|
// 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("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("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;
|
|
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 (!bNodeFound && !isCollapsed) // Don't bother to examine selection if it is collapsed
|
|
{
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selection->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> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
getter_AddRefs(iter));
|
|
// XXX: ERROR_HANDLING XPCOM usage
|
|
if ((NS_SUCCEEDED(res)) && iter)
|
|
{
|
|
iter->Init(range);
|
|
// 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,
|
|
// so don't return an element
|
|
if (bNodeFound)
|
|
{
|
|
//bNodeFound;
|
|
break;
|
|
}
|
|
|
|
nsAutoString domTagName;
|
|
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 ( (IsLink(TagName) && IsLinkNode(selectedNode)) ||
|
|
(IsNamedAnchor(TagName) && 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);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::CreateElementWithDefaults(const nsString& aTagName, nsIDOMElement** aReturn)
|
|
{
|
|
nsresult res=NS_ERROR_NOT_INITIALIZED;
|
|
if (aReturn)
|
|
*aReturn = nsnull;
|
|
|
|
if (aTagName == "" || !aReturn)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsAutoString TagName = aTagName;
|
|
TagName.ToLowerCase();
|
|
nsAutoString realTagName;
|
|
|
|
if (IsLink(TagName) || IsNamedAnchor(TagName))
|
|
{
|
|
realTagName = "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<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
|
|
if (!doc) return NS_ERROR_NOT_INITIALIZED;
|
|
res = doc->CreateElement(realTagName, getter_AddRefs(newElement));
|
|
if (NS_FAILED(res) || !newElement)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// Set default values for new elements
|
|
if (TagName.Equals("hr"))
|
|
{
|
|
// Note that we read the user's attributes for these from prefs (in InsertHLine JS)
|
|
newElement->SetAttribute("align","center");
|
|
newElement->SetAttribute("width","100%");
|
|
newElement->SetAttribute("size","2");
|
|
} else if (TagName.Equals("table"))
|
|
{
|
|
newElement->SetAttribute("cellpadding","2");
|
|
newElement->SetAttribute("cellspacing","2");
|
|
newElement->SetAttribute("border","1");
|
|
} else if (TagName.Equals("tr"))
|
|
{
|
|
newElement->SetAttribute("valign","top");
|
|
} else if (TagName.Equals("td"))
|
|
{
|
|
newElement->SetAttribute("valign","top");
|
|
|
|
// I'm def'ing this out to see if auto br insertion is working here
|
|
#if 0
|
|
// Insert the default space in a cell so border displays
|
|
nsCOMPtr<nsIDOMNode> newCellNode = do_QueryInterface(newElement);
|
|
if (newCellNode)
|
|
{
|
|
// TODO: This should probably be in the RULES code or
|
|
// preference based for "should we add the nbsp"
|
|
nsCOMPtr<nsIDOMText>newTextNode;
|
|
nsAutoString space;
|
|
// Set contents to the   character by concatanating the char code
|
|
space += nbsp;
|
|
// If we fail here, we return NS_OK anyway, since we have an OK cell node
|
|
nsresult result = doc->CreateTextNode(space, getter_AddRefs(newTextNode));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!newTextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
result = newCellNode->AppendChild(newTextNode, getter_AddRefs(resultNode));
|
|
}
|
|
#endif
|
|
}
|
|
// 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<nsIDOMSelection> selection;
|
|
|
|
// DON'T RETURN EXCEPT AT THE END -- WE NEED TO RELEASE THE aAnchorElement
|
|
if (!aAnchorElement)
|
|
goto DELETE_ANCHOR; // DON'T USE GOTO IN C++!
|
|
|
|
// We must have a real selection
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (!selection)
|
|
{
|
|
res = NS_ERROR_NULL_POINTER;
|
|
}
|
|
if (NS_FAILED(res) || !selection)
|
|
goto DELETE_ANCHOR;
|
|
|
|
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;
|
|
// XXX: ERROR_HANDLING return code lost
|
|
if (NS_SUCCEEDED(anchor->GetHref(href)) && href.GetUnicode() && href.Length() > 0)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
const nsString attribute("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?
|
|
}
|
|
}
|
|
}
|
|
DELETE_ANCHOR:
|
|
// We don't insert the element created in CreateElementWithDefaults
|
|
// into the document like we do in InsertElement,
|
|
// so shouldn't we have to do this here?
|
|
// It crashes in JavaScript if we do this!
|
|
//NS_RELEASE(aAnchorElement);
|
|
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
|
|
// TODO: Handle case of > 1 table cell or row selected
|
|
nsCOMPtr<nsIDOMElement> element;
|
|
PRBool isSelected;
|
|
nsAutoString tagName;
|
|
nsresult res = GetSelectedOrParentTableElement(element, tagName, isSelected);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!element)
|
|
{
|
|
// No table element -- set the background color on the body tag
|
|
res = nsEditor::GetBodyElement(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
|
|
return SetAttribute(element, "bgcolor", aColor);
|
|
}
|
|
|
|
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::GetBodyElement(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::MoveSelectionUp(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::MoveSelectionDown(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::MoveSelectionNext(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::MoveSelectionPrevious(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SelectNext(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SelectPrevious(nsIAtom *aIncrement, PRBool aExtendSelection)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::ScrollUp(nsIAtom *aIncrement)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::ScrollDown(nsIAtom *aIncrement)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::ScrollIntoView(PRBool aScrollToBegin)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
*/
|
|
|
|
|
|
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::GetDocumentLength(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::GetBodyElement(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;
|
|
printf ("count = %d\n", *aCount);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SetMaxTextLength(PRInt32 aMaxTextLength)
|
|
{
|
|
mMaxTextLength = aMaxTextLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetMaxTextLength(PRInt32& aMaxTextLength)
|
|
{
|
|
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;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ApplyOverrideStyleSheet(const nsString& aURL)
|
|
{
|
|
return ApplyDocumentOrOverrideStyleSheet(aURL, PR_TRUE);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ApplyStyleSheet(const nsString& aURL)
|
|
{
|
|
return ApplyDocumentOrOverrideStyleSheet(aURL, PR_FALSE);
|
|
}
|
|
|
|
//Note: Loading a document style sheet is undoable, loading an override sheet is not
|
|
nsresult
|
|
nsHTMLEditor::ApplyDocumentOrOverrideStyleSheet(const nsString& aURL, PRBool aOverride)
|
|
{
|
|
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_SUCCEEDED(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_SUCCEEDED(rv)) {
|
|
PRBool complete;
|
|
|
|
if (!cssLoader)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
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 = do_QueryInterface(cssStyleSheet);
|
|
nsCOMPtr<nsIStyleSet> styleSet;
|
|
rv = ps->GetStyleSet(getter_AddRefs(styleSet));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (!styleSet)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// Add the override style sheet
|
|
// (This checks if already exists
|
|
// If yes, it and reads it does)
|
|
styleSet->AppendOverrideStyleSheet(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);
|
|
}
|
|
}
|
|
else {
|
|
rv = cssLoader->LoadAgentSheet(uaURL, *getter_AddRefs(cssStyleSheet), complete,
|
|
this);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (complete) {
|
|
if (cssStyleSheet) {
|
|
ApplyStyleSheetToPresShellDocument(cssStyleSheet,this);
|
|
}
|
|
else
|
|
rv = NS_ERROR_NULL_POINTER;
|
|
}
|
|
//
|
|
// If not complete, we will be notified later
|
|
// with a call to ApplyStyleSheetToPresShellDocument().
|
|
//
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 = GetBodyElement(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;
|
|
|
|
nsCOMPtr<nsIStyleContext> styleContext;
|
|
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;
|
|
}
|
|
|
|
//
|
|
// 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 = GetBodyElement(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 ("style");
|
|
nsAutoString styleValue;
|
|
res = bodyElement->GetAttribute(styleName, styleValue);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Find the current wrapping type:
|
|
PRInt32 whitespaceStart=-1, whitespaceEnd=-1, widthStart=-1, widthEnd=-1;
|
|
whitespaceStart = styleValue.Find("white-space", PR_TRUE);
|
|
if (whitespaceStart >= 0)
|
|
whitespaceEnd = styleValue.Find(";", PR_FALSE, whitespaceStart);
|
|
if (whitespaceStart >= 0)
|
|
{
|
|
if (whitespaceEnd > whitespaceStart)
|
|
styleValue.Cut(whitespaceStart, whitespaceEnd - whitespaceStart + 1);
|
|
else
|
|
styleValue.Cut(whitespaceStart, styleValue.Length() - whitespaceStart);
|
|
}
|
|
widthStart = styleValue.Find("width", PR_TRUE);
|
|
if (widthStart >= 0)
|
|
widthEnd = styleValue.Find(";", PR_FALSE, widthStart);
|
|
if (widthStart >= 0)
|
|
{
|
|
if (widthEnd > widthStart)
|
|
styleValue.Cut(widthStart, widthEnd - widthStart + 1);
|
|
else
|
|
styleValue.Cut(widthStart, styleValue.Length() - widthStart);
|
|
}
|
|
|
|
// If we have other style left, trim off any existing semicolons
|
|
// or whitespace, then add a known semicolon-space:
|
|
if (styleValue.Length() > 0)
|
|
{
|
|
styleValue.Trim("; \t", PR_FALSE, PR_TRUE);
|
|
styleValue.Append("; ");
|
|
}
|
|
|
|
// and now we're ready to set the new whitespace/wrapping style.
|
|
if (aWrapColumn > 0) // Wrap to a fixed column
|
|
{
|
|
styleValue.Append("white-space: -moz-pre-wrap; width: ");
|
|
styleValue.Append(aWrapColumn);
|
|
styleValue.Append("ch;");
|
|
}
|
|
else if (aWrapColumn == 0)
|
|
styleValue.Append("white-space: pre wrap"); // XXX does this work?
|
|
else
|
|
styleValue.Append("white-space: pre");
|
|
|
|
#ifdef DEBUG_akkana
|
|
char* curstyle = styleValue.ToNewCString();
|
|
printf("Setting style: [%s]\n", curstyle);
|
|
nsCRT::free(curstyle);
|
|
#endif /* DEBUG_akkana */
|
|
|
|
return bodyElement->SetAttribute(styleName, styleValue);
|
|
}
|
|
|
|
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,
|
|
nsIContentIterator::GetIID(),
|
|
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 == "img" || tagName == "embed")
|
|
(*aNodeList)->AppendElement(node);
|
|
else if (tagName == "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.Compare("file:", PR_TRUE, 5) == 0)
|
|
(*aNodeList)->AppendElement(node);
|
|
}
|
|
}
|
|
}
|
|
iter->Next();
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#pragma mark --- nsIEditor overrides ---
|
|
#pragma mark -
|
|
#endif
|
|
|
|
//
|
|
// Figure out what formatting needs to go with this node, and insert it.
|
|
//
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertFormattingForNode(nsIDOMNode* aNode)
|
|
{
|
|
nsresult res;
|
|
|
|
// Don't insert any formatting unless it's an element node
|
|
PRUint16 nodeType;
|
|
res = aNode->GetNodeType(&nodeType);
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
if (nodeType != nsIDOMNode::ELEMENT_NODE)
|
|
return NS_OK;
|
|
|
|
// Insert formatting only for block nodes
|
|
// (would it be better to insert for any non-inline node?)
|
|
PRBool block;
|
|
res = IsNodeBlock(aNode, block);
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
if (!block)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
res = aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(res))
|
|
return res;
|
|
PRInt32 offset = GetIndexOf(parent, aNode);
|
|
|
|
nsString namestr;
|
|
aNode->GetNodeName(namestr);
|
|
|
|
// Don't insert formatting for pre or br if we're a plaintext editor.
|
|
// The newlines get considered to be part of the text, yet the
|
|
// edit rules also insert breaks, so we end up with too much
|
|
// vertical whitespace any time the user hits return.
|
|
// This, of course, makes the html look lousy, but we're
|
|
// expecting that no one will look at the html from a plaintext editor.
|
|
if ((mFlags & nsHTMLEditor::eEditorPlaintextMask)
|
|
&& (namestr.Equals("pre", PR_TRUE) || namestr.Equals("br", PR_TRUE)))
|
|
return NS_OK;
|
|
|
|
#ifdef DEBUG_akkana
|
|
//DumpContentTree();
|
|
char* nodename = namestr.ToNewCString();
|
|
printf("Inserting formatting for node <%s> at offset %d\n",
|
|
nodename, offset);
|
|
Recycle(nodename);
|
|
#endif /* DEBUG_akkana */
|
|
|
|
//
|
|
// XXX We don't yet have a real formatter. As a cheap stopgap,
|
|
// XXX just insert a newline before and after each newly inserted tag.
|
|
//
|
|
|
|
nsAutoString str ("\n");
|
|
|
|
// After the close tag
|
|
//res = InsertNoneditableTextNode(parent, offset+1, str);
|
|
|
|
// Before the open tag
|
|
res = InsertNoneditableTextNode(parent, offset, str);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Undo(PRUint32 aCount)
|
|
{
|
|
ForceCompositionEnd();
|
|
nsresult result = NS_OK;
|
|
|
|
BeginUpdateViewBatch();
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kUndo);
|
|
nsCOMPtr<nsIDOMSelection> 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);
|
|
}
|
|
|
|
EndUpdateViewBatch();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::Redo(PRUint32 aCount)
|
|
{
|
|
nsresult result = NS_OK;
|
|
|
|
BeginUpdateViewBatch();
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRedo);
|
|
nsCOMPtr<nsIDOMSelection> 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);
|
|
}
|
|
|
|
EndUpdateViewBatch();
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::Cut()
|
|
{
|
|
nsCOMPtr<nsIDOMSelection> 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<nsIDOMSelection> 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()
|
|
{
|
|
//printf("nsEditor::Copy\n");
|
|
|
|
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<nsIDOMSelection> 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::Paste()
|
|
{
|
|
ForceCompositionEnd();
|
|
nsAutoString stuffToPaste;
|
|
|
|
// 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,
|
|
nsITransferable::GetIID(),
|
|
(void**) getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
// Get the nsITransferable interface for getting the data from the clipboard
|
|
if (trans)
|
|
{
|
|
// 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
|
|
{
|
|
trans->AddDataFlavor(kJPEGImageMime);
|
|
trans->AddDataFlavor(kHTMLMime);
|
|
}
|
|
trans->AddDataFlavor(kUnicodeMime);
|
|
trans->AddDataFlavor(kTextMime);
|
|
|
|
// Get the Data from the clipboard
|
|
if (NS_SUCCEEDED(clipboard->GetData(trans)))
|
|
{
|
|
char* bestFlavor = nsnull;
|
|
nsCOMPtr<nsISupports> genericDataObj;
|
|
PRUint32 len = 0;
|
|
if ( NS_SUCCEEDED(trans->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len)) )
|
|
{
|
|
nsAutoString flavor ( bestFlavor ); // just so we can use flavor.Equals()
|
|
#ifdef DEBUG_akkana
|
|
printf("Got flavor [%s]\n", bestFlavor);
|
|
#endif
|
|
if (flavor.Equals(kHTMLMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.SetString ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTML(stuffToPaste);
|
|
}
|
|
}
|
|
else if (flavor.Equals(kTextMime))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
char* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.SetString ( text, len );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertText(stuffToPaste);
|
|
}
|
|
}
|
|
else if (flavor.Equals(kUnicodeMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.SetString ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertText(stuffToPaste);
|
|
}
|
|
}
|
|
else if (flavor.Equals(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanPaste(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[] = { kTextMime, kUnicodeMime, nsnull };
|
|
char* htmlEditorFlavors[] = { kJPEGImageMime, kHTMLMime, nsnull };
|
|
|
|
nsCOMPtr<nsISupportsArray> flavorsList;
|
|
rv = nsComponentManager::CreateInstance(NS_SUPPORTSARRAY_PROGID, 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_PROGID, 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** flavor = htmlEditorFlavors; *flavor; flavor++)
|
|
{
|
|
nsCOMPtr<nsISupportsString> flavorString;
|
|
nsComponentManager::CreateInstance(NS_SUPPORTS_STRING_PROGID, nsnull,
|
|
NS_GET_IID(nsISupportsString), getter_AddRefs(flavorString));
|
|
if (flavorString)
|
|
{
|
|
flavorString->SetData(*flavor);
|
|
flavorsList->AppendElement(flavorString);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool haveFlavors;
|
|
rv = clipboard->HasDataMatchingFlavors(flavorsList, &haveFlavors);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
aCanPaste = haveFlavors;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// HTML PasteAsQuotation: Paste in a blockquote type=cite
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation()
|
|
{
|
|
if (mFlags & eEditorPlaintextMask)
|
|
return PasteAsPlaintextQuotation();
|
|
|
|
nsAutoString citation("");
|
|
return PasteAsCitedQuotation(citation);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsString& aCitation)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsIDOMSelection> 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(nsHTMLEditRules::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("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");
|
|
nsAutoString cite ("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();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// Paste a plaintext quotation
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation()
|
|
{
|
|
// 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,
|
|
nsITransferable::GetIID(),
|
|
(void**) getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// We only handle plaintext pastes here
|
|
trans->AddDataFlavor(kUnicodeMime);
|
|
trans->AddDataFlavor(kTextMime);
|
|
|
|
// Get the Data from the clipboard
|
|
clipboard->GetData(trans);
|
|
|
|
// 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(flav);
|
|
nsAutoString stuffToPaste;
|
|
if (flavor.Equals(kTextMime))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
char* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.SetString ( text, len );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertAsPlaintextQuotation(stuffToPaste, 0);
|
|
}
|
|
}
|
|
else if (flavor.Equals(kUnicodeMime))
|
|
{
|
|
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
|
|
if (textDataObj && len > 0)
|
|
{
|
|
PRUnichar* text = nsnull;
|
|
textDataObj->ToString ( &text );
|
|
stuffToPaste.SetString ( text, len / 2 );
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertAsPlaintextQuotation(stuffToPaste, 0);
|
|
}
|
|
}
|
|
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, charset, aNodeInserted);
|
|
}
|
|
|
|
// text insert.
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertAsPlaintextQuotation(const nsString& aQuotedText,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
// Now 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;
|
|
|
|
// Insert blank lines after the quoted text:
|
|
quotedStuff += "\n\n";
|
|
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsHTMLEditRules::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:
|
|
nsCOMPtr<nsIDOMNode> preNode;
|
|
nsAutoString tag("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)
|
|
{
|
|
selection->Collapse(preNode, 0);
|
|
}
|
|
|
|
rv = InsertText(quotedStuff);
|
|
if (aNodeInserted)
|
|
{
|
|
if (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,
|
|
const nsString& aCharset,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsIDOMSelection> 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(nsHTMLEditRules::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("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");
|
|
nsAutoString cite ("cite");
|
|
newElement->SetAttribute(type, cite);
|
|
|
|
if (aCitation.Length() > 0)
|
|
newElement->SetAttribute(cite, aCitation);
|
|
|
|
// Set the selection inside the blockquote so aQuotedText will go there:
|
|
if (NS_SUCCEEDED(res))
|
|
selection->Collapse(newNode, 0);
|
|
}
|
|
|
|
res = InsertHTMLWithCharset(aQuotedText, aCharset);
|
|
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(nsString& aOutputString,
|
|
const nsString& aFormatType,
|
|
PRUint32 aFlags)
|
|
{
|
|
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 == "text/plain")
|
|
{
|
|
PRBool docEmpty;
|
|
rv = GetDocumentIsEmpty(&docEmpty);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (docEmpty) {
|
|
aOutputString = "";
|
|
return NS_OK;
|
|
}
|
|
else if (mFlags & eEditorPlaintextMask)
|
|
aFlags |= nsIDocumentEncoder::OutputPreformatted;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
char* progid = (char *)nsAllocator::Alloc(strlen(NS_DOC_ENCODER_PROGID_BASE) + aFormatType.Length() + 1);
|
|
if (! progid)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
strcpy(progid, NS_DOC_ENCODER_PROGID_BASE);
|
|
char* type = aFormatType.ToNewCString();
|
|
strcat(progid, type);
|
|
nsCRT::free(type);
|
|
rv = nsComponentManager::CreateInstance(progid,
|
|
nsnull,
|
|
nsIDocumentEncoder::GetIID(),
|
|
getter_AddRefs(encoder));
|
|
|
|
nsCRT::free(progid);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
printf("Couldn't get progid %s\n", progid);
|
|
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<nsIDOMSelection> 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);
|
|
}
|
|
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 == "text/plain")
|
|
{
|
|
PRBool docEmpty;
|
|
rv = GetDocumentIsEmpty(&docEmpty);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (docEmpty)
|
|
return NS_OK; // output nothing
|
|
}
|
|
|
|
nsCOMPtr<nsIDocumentEncoder> encoder;
|
|
char* progid = (char *)nsAllocator::Alloc(strlen(NS_DOC_ENCODER_PROGID_BASE) + aFormatType.Length() + 1);
|
|
if (! progid)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
strcpy(progid, NS_DOC_ENCODER_PROGID_BASE);
|
|
char* type = aFormatType.ToNewCString();
|
|
strcat(progid, type);
|
|
nsCRT::free(type);
|
|
rv = nsComponentManager::CreateInstance(progid,
|
|
nsnull,
|
|
nsIDocumentEncoder::GetIID(),
|
|
getter_AddRefs(encoder));
|
|
|
|
nsCRT::free(progid);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
printf("Couldn't get progid %s\n", progid);
|
|
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->Equals("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<nsIDOMSelection> 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);
|
|
}
|
|
#if 0
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetTextNearNode(nsIDOMNode *aNode, aNode, PRInt32 aMaxChars, nsString& aOutputString)
|
|
{
|
|
if (!aNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// Create a temporary selection object and
|
|
// based on the suppled
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
nsresult rv = NS_NewRange(getter_AddRsfs(range))
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
if (!range)
|
|
return NS_ERROR_NULL_POINTER;
|
|
range.SetStart(aNode,0);
|
|
range.SetEnd(aNode,
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv) && selection)
|
|
encoder->SetSelection(selection);
|
|
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
|
|
nsCOMPtr<nsIDOMSelection> selection;
|
|
nsresult result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
mIMETextRangeList = aTextRangeList;
|
|
nsAutoPlaceHolderBatch batch(this, gIMETxnName);
|
|
|
|
result = InsertText(aCompositionString);
|
|
|
|
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);
|
|
|
|
return result;
|
|
}
|
|
|
|
#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)
|
|
{
|
|
if (mRules) return mRules->BeforeEdit(opID, aDirection);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/** All editor operations which alter the doc should be followed
|
|
* with a call to EndOperation, naming the action and direction */
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::EndOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
|
|
{
|
|
if (mRules) return mRules->AfterEdit(opID, aDirection);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
PRBool
|
|
nsHTMLEditor::CanContainTag(nsIDOMNode* aParent, const nsString &aTag)
|
|
{
|
|
// CNavDTD gives some unwanted results. We override them here.
|
|
// if parent is a list and tag is text, say "no".
|
|
if (IsListNode(aParent) && (aTag == "__moz_text"))
|
|
return PR_FALSE;
|
|
// else fall thru
|
|
return nsEditor::CanContainTag(aParent, aTag);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SelectEntireDocument(nsIDOMSelection *aSelection)
|
|
{
|
|
nsresult res;
|
|
if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
// get body node
|
|
nsCOMPtr<nsIDOMElement>bodyElement;
|
|
res = GetBodyElement(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;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetTypeInStateForProperty(TypeInState &aTypeInState,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (!aPropName) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
PRUint32 propEnum;
|
|
aTypeInState.GetEnumForName(aPropName, propEnum);
|
|
if (nsIEditProperty::b==aPropName || nsIEditProperty::i==aPropName || nsIEditProperty::u==aPropName)
|
|
{
|
|
if (PR_TRUE==aTypeInState.IsSet(propEnum))
|
|
{ // toggle currently set boldness
|
|
aTypeInState.UnSet(propEnum);
|
|
}
|
|
else
|
|
{ // get the current style and set boldness to the opposite of the current state
|
|
PRBool any = PR_FALSE;
|
|
PRBool all = PR_FALSE;
|
|
PRBool first = PR_FALSE;
|
|
GetInlineProperty(aPropName, aAttribute, nsnull, first, any, all); // operates on current selection
|
|
aTypeInState.SetProp(propEnum, !any);
|
|
}
|
|
}
|
|
else if (nsIEditProperty::font==aPropName)
|
|
{
|
|
if (!aAttribute) { return NS_ERROR_NULL_POINTER; }
|
|
nsIAtom *attribute = NS_NewAtom(*aAttribute);
|
|
if (!attribute) { return NS_ERROR_NULL_POINTER; }
|
|
PRUint32 attrEnum;
|
|
aTypeInState.GetEnumForName(attribute, attrEnum);
|
|
if (nsIEditProperty::color==attribute || nsIEditProperty::face==attribute || nsIEditProperty::size==attribute)
|
|
{
|
|
if (PR_TRUE==aTypeInState.IsSet(attrEnum))
|
|
{
|
|
if (nsnull==aValue) {
|
|
aTypeInState.UnSet(attrEnum);
|
|
}
|
|
else { // we're just changing the value of color
|
|
aTypeInState.SetPropValue(attrEnum, *aValue);
|
|
}
|
|
}
|
|
else
|
|
{ // get the current style and set font color if it's needed
|
|
if (!aValue) { return NS_ERROR_NULL_POINTER; }
|
|
PRBool any = PR_FALSE;
|
|
PRBool all = PR_FALSE;
|
|
PRBool first = PR_FALSE;
|
|
GetInlineProperty(aPropName, aAttribute, aValue, first, any, all); // operates on current selection
|
|
if (PR_FALSE==all) {
|
|
aTypeInState.SetPropValue(attrEnum, *aValue);
|
|
}
|
|
}
|
|
}
|
|
else { return NS_ERROR_FAILURE; }
|
|
}
|
|
else { return NS_ERROR_FAILURE; }
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// 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) const
|
|
{
|
|
nsresult result;
|
|
aIsSet = PR_FALSE; // must be initialized to false for code below to work
|
|
nsAutoString propName;
|
|
aProperty->ToString(propName);
|
|
nsCOMPtr<nsIDOMNode>parent;
|
|
result = aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return;
|
|
while (parent)
|
|
{
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(parent);
|
|
if (element)
|
|
{
|
|
nsAutoString tag;
|
|
element->GetTagName(tag);
|
|
if (propName.EqualsIgnoreCase(tag))
|
|
{
|
|
PRBool found = PR_FALSE;
|
|
if (aAttribute && 0!=aAttribute->Length())
|
|
{
|
|
nsAutoString value;
|
|
element->GetAttribute(*aAttribute, value);
|
|
if (0!=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 (PR_TRUE==found)
|
|
{
|
|
aIsSet = PR_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode>temp;
|
|
result = parent->GetParentNode(getter_AddRefs(temp));
|
|
if (NS_SUCCEEDED(result) && temp) {
|
|
parent = do_QueryInterface(temp);
|
|
}
|
|
else {
|
|
parent = 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::GetTextSelectionOffsets(nsIDOMSelection *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;
|
|
result = aSelection->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->GetCommonParent(getter_AddRefs(parentNode));
|
|
}
|
|
else
|
|
{
|
|
parentNode = do_QueryInterface(startNode);
|
|
}
|
|
|
|
|
|
return GetAbsoluteOffsetsForPoints(startNode, startOffset,
|
|
endNode, endOffset,
|
|
parentNode,
|
|
aOutStartOffset, aOutEndOffset);
|
|
}
|
|
|
|
// this is a complete ripoff from nsTextEditor::GetTextSelectionOffsetsForRange
|
|
// the two should use common code, or even just be one method
|
|
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,
|
|
nsIContentIterator::GetIID(),
|
|
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 (PR_TRUE==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;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetTextSelectionOffsetsForRange(nsIDOMSelection *aSelection,
|
|
nsIDOMNode **aParent,
|
|
PRInt32 &aOutStartOffset,
|
|
PRInt32 &aEndOffset)
|
|
{
|
|
if (!aSelection) { return NS_ERROR_NULL_POINTER; }
|
|
aOutStartOffset = aEndOffset = 0;
|
|
nsresult result;
|
|
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRInt32 startOffset, endOffset;
|
|
aSelection->GetAnchorNode(getter_AddRefs(startNode));
|
|
aSelection->GetAnchorOffset(&startOffset);
|
|
aSelection->GetFocusNode(getter_AddRefs(endNode));
|
|
aSelection->GetFocusOffset(&endOffset);
|
|
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = aSelection->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));
|
|
if ((NS_SUCCEEDED(result)) && currentItem)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
range->GetCommonParent(aParent);
|
|
}
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
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(*aParent);
|
|
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.get() == startNode.get())
|
|
{
|
|
aOutStartOffset = totalLength + startOffset;
|
|
}
|
|
if (currentNode.get() == endNode.get())
|
|
{
|
|
aEndOffset = totalLength + endOffset;
|
|
break;
|
|
}
|
|
PRUint32 length;
|
|
textNode->GetLength(&length);
|
|
totalLength += length;
|
|
}
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void nsHTMLEditor::ResetTextSelectionForRange(nsIDOMNode *aParent,
|
|
PRInt32 aOutStartOffset,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMSelection *aSelection)
|
|
{
|
|
if (!aParent || !aSelection) { return; } // XXX: should return an error
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRInt32 startOffset=0, endOffset=0;
|
|
|
|
nsresult result;
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return;
|
|
if (!iter) return;
|
|
PRBool setStart = PR_FALSE;
|
|
PRUint32 totalLength=0;
|
|
nsCOMPtr<nsIDOMCharacterData>textNode;
|
|
nsCOMPtr<nsIContent>blockParentContent = do_QueryInterface(aParent);
|
|
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)
|
|
{
|
|
PRUint32 length;
|
|
textNode->GetLength(&length);
|
|
if ((PR_FALSE==setStart) && aOutStartOffset<=(PRInt32)(totalLength+length))
|
|
{
|
|
setStart = PR_TRUE;
|
|
startNode = do_QueryInterface(textNode);
|
|
startOffset = aOutStartOffset-totalLength;
|
|
}
|
|
if (aEndOffset<=(PRInt32)(totalLength+length))
|
|
{
|
|
endNode = do_QueryInterface(textNode);
|
|
endOffset = aEndOffset-totalLength;
|
|
break;
|
|
}
|
|
totalLength += length;
|
|
}
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
aSelection->Collapse(startNode, startOffset);
|
|
aSelection->Extend(endNode, endOffset);
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#endif
|
|
|
|
//================================================================
|
|
// HTML Editor methods
|
|
//
|
|
// Note: Table Editing methods are implemented in EditTable.cpp
|
|
//
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReParentContentOfNode(nsIDOMNode *aNode,
|
|
nsString &aParentTag,
|
|
BlockTransformationType aTransformation)
|
|
{
|
|
if (!aNode) { return NS_ERROR_NULL_POINTER; }
|
|
if (gNoisy)
|
|
{
|
|
char *tag = aParentTag.ToNewCString();
|
|
printf("---------- ReParentContentOfNode(%p,%s,%d) -----------\n", aNode, tag, aTransformation);
|
|
nsCRT::free(tag);
|
|
}
|
|
// find the current block parent, or just use aNode if it is a block node
|
|
nsCOMPtr<nsIDOMElement>blockParentElement;
|
|
nsCOMPtr<nsIDOMNode>nodeToReParent; // this is the node we'll operate on, by default it's aNode
|
|
nsresult res = aNode->QueryInterface(nsIDOMNode::GetIID(), getter_AddRefs(nodeToReParent));
|
|
PRBool nodeIsInline;
|
|
PRBool nodeIsBlock=PR_FALSE;
|
|
IsNodeInline(aNode, nodeIsInline);
|
|
if (PR_FALSE==nodeIsInline)
|
|
{
|
|
nsresult QIResult;
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
|
|
QIResult = aNode->QueryInterface(nsIDOMCharacterData::GetIID(), getter_AddRefs(nodeAsText));
|
|
if (NS_FAILED(QIResult) || !nodeAsText) {
|
|
nodeIsBlock=PR_TRUE;
|
|
}
|
|
}
|
|
// if aNode is the block parent, then the node to reparent is one of its children
|
|
if (PR_TRUE==nodeIsBlock)
|
|
{
|
|
res = aNode->QueryInterface(nsIDOMNode::GetIID(), getter_AddRefs(blockParentElement));
|
|
if (NS_SUCCEEDED(res) && blockParentElement) {
|
|
res = aNode->GetFirstChild(getter_AddRefs(nodeToReParent));
|
|
}
|
|
}
|
|
else { // we just need to get the block parent of aNode
|
|
res = GetBlockParent(aNode, getter_AddRefs(blockParentElement));
|
|
}
|
|
// at this point, we must have a good res, a node to reparent, and a block parent
|
|
if (!nodeToReParent) { return NS_ERROR_UNEXPECTED;}
|
|
if (!blockParentElement) { return NS_ERROR_NULL_POINTER;}
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newParentNode;
|
|
nsCOMPtr<nsIDOMNode> blockParentNode = do_QueryInterface(blockParentElement);
|
|
// we need to treat nodes directly inside the body differently
|
|
nsAutoString parentTag;
|
|
blockParentElement->GetTagName(parentTag);
|
|
PRBool isRoot;
|
|
IsRootTag(parentTag, isRoot);
|
|
if (PR_TRUE==isRoot)
|
|
{
|
|
// if nodeToReParent is a text node, we have <ROOT>Text.
|
|
// re-parent Text into a new <aTag> at the offset of Text in <ROOT>
|
|
// so we end up with <ROOT><aTag>Text
|
|
// ignore aTransformation, replaces act like inserts
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(nodeToReParent);
|
|
if (nodeAsText)
|
|
{
|
|
res = ReParentBlockContent(nodeToReParent, aParentTag, blockParentNode, parentTag,
|
|
aTransformation, getter_AddRefs(newParentNode));
|
|
}
|
|
else
|
|
{ // this is the case of an insertion point between 2 non-text objects
|
|
// XXX: how to you know it's an insertion point???
|
|
PRInt32 offsetInParent=0;
|
|
res = GetChildOffset(nodeToReParent, blockParentNode, offsetInParent);
|
|
NS_ASSERTION((NS_SUCCEEDED(res)), "bad res from GetChildOffset");
|
|
// otherwise, just create the block parent at the selection
|
|
res = CreateNode(aParentTag, blockParentNode, offsetInParent,
|
|
getter_AddRefs(newParentNode));
|
|
// XXX: need to move some of the children of blockParentNode into the newParentNode?
|
|
// XXX: need to create a bogus text node inside this new block?
|
|
// that means, I need to generalize bogus node handling
|
|
}
|
|
}
|
|
else
|
|
{ // the block parent is not a ROOT,
|
|
// for the selected block content, transform blockParentNode
|
|
if (((eReplaceParent==aTransformation) && (PR_FALSE==parentTag.EqualsIgnoreCase(aParentTag))) ||
|
|
(eInsertParent==aTransformation))
|
|
{
|
|
if (gNoisy) { DebugDumpContent(); } // DEBUG
|
|
res = ReParentBlockContent(nodeToReParent, aParentTag, blockParentNode, parentTag,
|
|
aTransformation, getter_AddRefs(newParentNode));
|
|
if ((NS_SUCCEEDED(res)) && (newParentNode) && (eReplaceParent==aTransformation))
|
|
{
|
|
PRBool hasChildren;
|
|
blockParentNode->HasChildNodes(&hasChildren);
|
|
if (PR_FALSE==hasChildren)
|
|
{
|
|
res = nsEditor::DeleteNode(blockParentNode);
|
|
if (gNoisy)
|
|
{
|
|
printf("deleted old block parent node %p\n", blockParentNode.get());
|
|
DebugDumpContent(); // DEBUG
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else { // otherwise, it's a no-op
|
|
if (gNoisy) { printf("AddBlockParent is a no-op for this collapsed selection.\n"); }
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReParentBlockContent(nsIDOMNode *aNode,
|
|
nsString &aParentTag,
|
|
nsIDOMNode *aBlockParentNode,
|
|
nsString &aBlockParentTag,
|
|
BlockTransformationType aTransformation,
|
|
nsIDOMNode **aNewParentNode)
|
|
{
|
|
if (!aNode || !aBlockParentNode || !aNewParentNode) { return NS_ERROR_NULL_POINTER; }
|
|
nsCOMPtr<nsIDOMNode> blockParentNode = do_QueryInterface(aBlockParentNode);
|
|
PRBool removeBlockParent = PR_FALSE;
|
|
PRBool removeBreakBefore = PR_FALSE;
|
|
PRBool removeBreakAfter = PR_FALSE;
|
|
nsCOMPtr<nsIDOMNode>ancestor;
|
|
nsresult res = aNode->GetParentNode(getter_AddRefs(ancestor));
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIDOMNode>previousAncestor = do_QueryInterface(aNode);
|
|
while (ancestor)
|
|
{
|
|
nsCOMPtr<nsIDOMElement>ancestorElement = do_QueryInterface(ancestor);
|
|
nsAutoString ancestorTag;
|
|
ancestorElement->GetTagName(ancestorTag);
|
|
if (ancestorTag.EqualsIgnoreCase(aBlockParentTag))
|
|
{
|
|
break; // previousAncestor will contain the node to operate on
|
|
}
|
|
previousAncestor = do_QueryInterface(ancestor);
|
|
res = ancestorElement->GetParentNode(getter_AddRefs(ancestor));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
// now, previousAncestor is the node we are operating on
|
|
nsCOMPtr<nsIDOMNode>leftNode, rightNode;
|
|
res = GetBlockSection(previousAncestor,
|
|
getter_AddRefs(leftNode),
|
|
getter_AddRefs(rightNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!leftNode || !rightNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// determine some state for managing <BR>s around the new block
|
|
PRBool isSubordinateBlock = PR_FALSE; // if true, the content is already in a subordinate block
|
|
PRBool isRootBlock = PR_FALSE; // if true, the content is in a root block
|
|
nsCOMPtr<nsIDOMElement>blockParentElement = do_QueryInterface(blockParentNode);
|
|
if (blockParentElement)
|
|
{
|
|
nsAutoString blockParentTag;
|
|
blockParentElement->GetTagName(blockParentTag);
|
|
IsSubordinateBlock(blockParentTag, isSubordinateBlock);
|
|
IsRootTag(blockParentTag, isRootBlock);
|
|
}
|
|
|
|
if (PR_TRUE==isRootBlock)
|
|
{ // we're creating a block element where a block element did not previously exist
|
|
removeBreakBefore = PR_TRUE;
|
|
removeBreakAfter = PR_TRUE;
|
|
}
|
|
|
|
// apply the transformation
|
|
PRInt32 offsetInParent=0;
|
|
if (eInsertParent==aTransformation || PR_TRUE==isRootBlock)
|
|
{
|
|
res = GetChildOffset(leftNode, blockParentNode, offsetInParent);
|
|
NS_ASSERTION((NS_SUCCEEDED(res)), "bad res from GetChildOffset");
|
|
res = CreateNode(aParentTag, blockParentNode, offsetInParent, aNewParentNode);
|
|
if (gNoisy) { printf("created a node in blockParentNode at offset %d\n", offsetInParent); }
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> grandParent;
|
|
res = blockParentNode->GetParentNode(getter_AddRefs(grandParent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!grandParent) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNode>firstChildNode, lastChildNode;
|
|
blockParentNode->GetFirstChild(getter_AddRefs(firstChildNode));
|
|
blockParentNode->GetLastChild(getter_AddRefs(lastChildNode));
|
|
if (firstChildNode==leftNode && lastChildNode==rightNode)
|
|
{
|
|
res = GetChildOffset(blockParentNode, grandParent, offsetInParent);
|
|
NS_ASSERTION((NS_SUCCEEDED(res)), "bad res from GetChildOffset");
|
|
res = CreateNode(aParentTag, grandParent, offsetInParent, aNewParentNode);
|
|
if (gNoisy) { printf("created a node in grandParent at offset %d\n", offsetInParent); }
|
|
}
|
|
else
|
|
{
|
|
// We're in the case where the content of blockParentNode is separated by <BR>'s,
|
|
// creating multiple block content ranges.
|
|
// Split blockParentNode around the blockContent
|
|
if (gNoisy) { printf("splitting a node because of <BR>s\n"); }
|
|
nsCOMPtr<nsIDOMNode> newLeftNode;
|
|
if (firstChildNode!=leftNode)
|
|
{
|
|
res = GetChildOffset(leftNode, blockParentNode, offsetInParent);
|
|
if (gNoisy) { printf("splitting left at %d\n", offsetInParent); }
|
|
res = SplitNode(blockParentNode, offsetInParent, getter_AddRefs(newLeftNode));
|
|
// after this split, blockParentNode still contains leftNode and rightNode
|
|
}
|
|
if (lastChildNode!=rightNode)
|
|
{
|
|
res = GetChildOffset(rightNode, blockParentNode, offsetInParent);
|
|
offsetInParent++;
|
|
if (gNoisy) { printf("splitting right at %d\n", offsetInParent); }
|
|
res = SplitNode(blockParentNode, offsetInParent, getter_AddRefs(newLeftNode));
|
|
blockParentNode = do_QueryInterface(newLeftNode);
|
|
}
|
|
res = GetChildOffset(leftNode, blockParentNode, offsetInParent);
|
|
NS_ASSERTION((NS_SUCCEEDED(res)), "bad res from GetChildOffset");
|
|
res = CreateNode(aParentTag, blockParentNode, offsetInParent, aNewParentNode);
|
|
if (gNoisy) { printf("created a node in blockParentNode at offset %d\n", offsetInParent); }
|
|
// what we need to do here is remove the existing block parent when we're all done.
|
|
removeBlockParent = PR_TRUE;
|
|
}
|
|
}
|
|
if ((NS_SUCCEEDED(res)) && *aNewParentNode)
|
|
{ // move all the children/contents of blockParentNode to aNewParentNode
|
|
nsCOMPtr<nsIDOMNode>childNode = do_QueryInterface(rightNode);
|
|
nsCOMPtr<nsIDOMNode>previousSiblingNode;
|
|
while (NS_SUCCEEDED(res) && childNode)
|
|
{
|
|
childNode->GetPreviousSibling(getter_AddRefs(previousSiblingNode));
|
|
// explicitly delete of childNode from it's current parent
|
|
// can't just rely on DOM semantics of InsertNode doing the delete implicitly, doesn't undo!
|
|
res = nsEditor::DeleteNode(childNode);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
res = nsEditor::InsertNode(childNode, *aNewParentNode, 0);
|
|
if (gNoisy)
|
|
{
|
|
printf("re-parented sibling node %p\n", childNode.get());
|
|
}
|
|
}
|
|
if (childNode==leftNode || rightNode==leftNode) {
|
|
break;
|
|
}
|
|
childNode = do_QueryInterface(previousSiblingNode);
|
|
} // end while loop
|
|
}
|
|
// clean up the surrounding content to maintain vertical whitespace
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
// if the prior node is a <BR> and we did something to change vertical whitespacing, delete the <BR>
|
|
nsCOMPtr<nsIDOMNode> brNode;
|
|
res = GetPriorNode(leftNode, PR_TRUE, getter_AddRefs(brNode));
|
|
if (NS_SUCCEEDED(res) && brNode)
|
|
{
|
|
nsCOMPtr<nsIContent> brContent = do_QueryInterface(brNode);
|
|
if (brContent)
|
|
{
|
|
nsCOMPtr<nsIAtom> brContentTag;
|
|
brContent->GetTag(*getter_AddRefs(brContentTag));
|
|
if (nsIEditProperty::br==brContentTag.get()) {
|
|
res = DeleteNode(brNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the next node is a <BR> and we did something to change vertical whitespacing, delete the <BR>
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
res = GetNextNode(rightNode, PR_TRUE, getter_AddRefs(brNode));
|
|
if (NS_SUCCEEDED(res) && brNode)
|
|
{
|
|
nsCOMPtr<nsIContent> brContent = do_QueryInterface(brNode);
|
|
if (brContent)
|
|
{
|
|
nsCOMPtr<nsIAtom> brContentTag;
|
|
brContent->GetTag(*getter_AddRefs(brContentTag));
|
|
if (nsIEditProperty::br==brContentTag.get()) {
|
|
res = DeleteNode(brNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((NS_SUCCEEDED(res)) && (PR_TRUE==removeBlockParent))
|
|
{ // we determined we need to remove the previous block parent. Do it!
|
|
// go through list backwards so deletes don't interfere with the iteration
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = blockParentNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if ((NS_SUCCEEDED(res)) && (childNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
blockParentNode->GetParentNode(getter_AddRefs(grandParent));
|
|
//PRInt32 offsetInParent;
|
|
res = GetChildOffset(blockParentNode, grandParent, offsetInParent);
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
PRInt32 i=childCount-1;
|
|
for ( ; ((NS_SUCCEEDED(res)) && (0<=i)); i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(i, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode))
|
|
{
|
|
res = DeleteNode(childNode);
|
|
if (NS_SUCCEEDED(res)) {
|
|
res = InsertNode(childNode, grandParent, offsetInParent);
|
|
}
|
|
}
|
|
}
|
|
if (gNoisy) { printf("removing the old block parent %p\n", blockParentNode.get()); }
|
|
res = DeleteNode(blockParentNode);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::ReParentContentOfRange(nsIDOMRange *aRange,
|
|
nsString &aParentTag,
|
|
BlockTransformationType aTranformation)
|
|
{
|
|
if (!aRange) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res;
|
|
nsISupportsArray *blockSections;
|
|
res = NS_NewISupportsArray(&blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!blockSections) return NS_ERROR_NULL_POINTER;
|
|
|
|
res = GetBlockSectionsForRange(aRange, blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// no embedded returns allowed below here in this method, or you'll get a space leak
|
|
nsIDOMRange *subRange;
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
while (subRange)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>startParent;
|
|
res = subRange->GetStartParent(getter_AddRefs(startParent));
|
|
if (NS_SUCCEEDED(res) && startParent)
|
|
{
|
|
if (gNoisy) { printf("ReParentContentOfRange calling ReParentContentOfNode\n"); }
|
|
res = ReParentContentOfNode(startParent, aParentTag, aTranformation);
|
|
}
|
|
NS_RELEASE(subRange);
|
|
if (NS_FAILED(res))
|
|
{ // be sure to break after free of subRange
|
|
break;
|
|
}
|
|
blockSections->RemoveElementAt(0);
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
}
|
|
NS_RELEASE(blockSections);
|
|
|
|
return res;
|
|
}
|
|
*/
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParagraphStyleFromRange(nsIDOMRange *aRange)
|
|
{
|
|
if (!aRange) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res;
|
|
nsISupportsArray *blockSections;
|
|
res = NS_NewISupportsArray(&blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!blockSections) return NS_ERROR_NULL_POINTER;
|
|
|
|
res = GetBlockSectionsForRange(aRange, blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsIDOMRange *subRange;
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
while (subRange)
|
|
{
|
|
res = RemoveParagraphStyleFromBlockContent(subRange);
|
|
NS_RELEASE(subRange);
|
|
if (NS_FAILED(res))
|
|
{ // be sure to break after subrange is released
|
|
break;
|
|
}
|
|
blockSections->RemoveElementAt(0);
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
}
|
|
NS_RELEASE(blockSections);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParagraphStyleFromBlockContent(nsIDOMRange *aRange)
|
|
{
|
|
if (!aRange) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode>startParent;
|
|
aRange->GetStartParent(getter_AddRefs(startParent));
|
|
nsCOMPtr<nsIDOMElement>blockParentElement;
|
|
res = GetBlockParent(startParent, getter_AddRefs(blockParentElement));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (blockParentElement)
|
|
{
|
|
nsAutoString blockParentTag;
|
|
blockParentElement->GetTagName(blockParentTag);
|
|
PRBool isSubordinateBlock;
|
|
IsSubordinateBlock(blockParentTag, isSubordinateBlock);
|
|
if (PR_FALSE==isSubordinateBlock) {
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// go through list backwards so deletes don't interfere with the iteration
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = blockParentElement->GetChildNodes(getter_AddRefs(childNodes));
|
|
if ((NS_SUCCEEDED(res)) && (childNodes))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
blockParentElement->GetParentNode(getter_AddRefs(grandParent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!grandParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRInt32 offsetInParent;
|
|
res = GetChildOffset(blockParentElement, grandParent, offsetInParent);
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
PRInt32 i=childCount-1;
|
|
for ( ; ((NS_SUCCEEDED(res)) && (0<=i)); i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(i, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode))
|
|
{
|
|
res = DeleteNode(childNode);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
res = InsertNode(childNode, grandParent, offsetInParent);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
res = DeleteNode(blockParentElement);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
}
|
|
res = GetBlockParent(startParent, getter_AddRefs(blockParentElement));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParentFromRange(const nsString &aParentTag, nsIDOMRange *aRange)
|
|
{
|
|
if (!aRange) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res;
|
|
nsISupportsArray *blockSections;
|
|
res = NS_NewISupportsArray(&blockSections);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!blockSections) return NS_ERROR_NULL_POINTER;
|
|
res = GetBlockSectionsForRange(aRange, blockSections);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
nsIDOMRange *subRange;
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
while (subRange && (NS_SUCCEEDED(res)))
|
|
{
|
|
res = RemoveParentFromBlockContent(aParentTag, subRange);
|
|
NS_RELEASE(subRange);
|
|
if (NS_FAILED(res))
|
|
{
|
|
break;
|
|
}
|
|
blockSections->RemoveElementAt(0);
|
|
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
|
|
}
|
|
}
|
|
NS_RELEASE(blockSections);
|
|
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveParentFromBlockContent(const nsString &aParentTag, nsIDOMRange *aRange)
|
|
{
|
|
if (!aRange) { return NS_ERROR_NULL_POINTER; }
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode>startParent;
|
|
res = aRange->GetStartParent(getter_AddRefs(startParent));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!startParent) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode>parentNode;
|
|
nsCOMPtr<nsIDOMElement>parentElement;
|
|
res = startParent->GetParentNode(getter_AddRefs(parentNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (parentNode)
|
|
{
|
|
parentElement = do_QueryInterface(parentNode);
|
|
nsAutoString parentTag;
|
|
parentElement->GetTagName(parentTag);
|
|
PRBool isRoot;
|
|
IsRootTag(parentTag, isRoot);
|
|
if (aParentTag.EqualsIgnoreCase(parentTag))
|
|
{
|
|
// go through list backwards so deletes don't interfere with the iteration
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = parentElement->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!childNodes) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
parentElement->GetParentNode(getter_AddRefs(grandParent));
|
|
PRInt32 offsetInParent;
|
|
res = GetChildOffset(parentElement, grandParent, offsetInParent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
PRInt32 i=childCount-1;
|
|
for ( ; ((NS_SUCCEEDED(res)) && (0<=i)); i--)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(i, getter_AddRefs(childNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!childNode) return NS_ERROR_NULL_POINTER;
|
|
res = DeleteNode(childNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = InsertNode(childNode, grandParent, offsetInParent);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
res = DeleteNode(parentElement);
|
|
if (NS_FAILED(res)) { return res; }
|
|
|
|
break;
|
|
}
|
|
else if (PR_TRUE==isRoot) { // hit a local root node, terminate loop
|
|
break;
|
|
}
|
|
res = parentElement->GetParentNode(getter_AddRefs(parentNode));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement)
|
|
{
|
|
if ( aElement )
|
|
{
|
|
nsIDOMElement* bodyElement = nsnull;
|
|
nsresult res = nsEditor::GetBodyElement(&bodyElement);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!bodyElement) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
nsCOMPtr<nsIDOMNode> currentElement = do_QueryInterface(aElement);
|
|
if (currentElement)
|
|
{
|
|
do {
|
|
currentElement->GetParentNode(getter_AddRefs(parent));
|
|
if (parent)
|
|
{
|
|
if (parent == bodyElement)
|
|
return PR_TRUE;
|
|
|
|
currentElement = parent;
|
|
}
|
|
} while(parent);
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
PRBool
|
|
nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement)
|
|
{
|
|
PRBool caretIsSet = PR_FALSE;
|
|
|
|
if (aElement && IsElementInBody(aElement))
|
|
{
|
|
nsresult res = NS_OK;
|
|
nsAutoString tagName;
|
|
aElement->GetNodeName(tagName);
|
|
tagName.ToLowerCase();
|
|
if (tagName == "table" || tagName == "tr" ||
|
|
tagName == "td" || tagName == "th" ||
|
|
tagName == "thead" || tagName == "tfoot" ||
|
|
tagName == "tbody" || tagName == "caption")
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
# if 0
|
|
// I've ifdef'd this out because it isn't finished and I'm not sure what the intent is.
|
|
PRInt32 offset = 0;
|
|
nsCOMPtr<nsIDOMNode>lastChild;
|
|
res = parent->GetLastChild(getter_AddRefs(lastChild));
|
|
if (NS_SUCCEEDED(res) && lastChild && node != lastChild)
|
|
{
|
|
if (node == lastChild)
|
|
{
|
|
// Check if node is text and has more than just a  
|
|
nsCOMPtr<nsIDOMCharacterData>textNode = do_QueryInterface(node);
|
|
nsAutoString text;
|
|
PRUnichar nbspStr[2] = {nbsp, 0};
|
|
if (textNode && textNode->GetData(text))
|
|
{
|
|
// Set selection relative to the text node
|
|
parent = node;
|
|
PRInt32 len = text.Length();
|
|
if (len > 1 || text != nbspStr)
|
|
{
|
|
offset = len;
|
|
}
|
|
}
|
|
} else {
|
|
// We have > 1 node, so set to end of content
|
|
}
|
|
}
|
|
#endif
|
|
// Set selection at beginning of deepest node
|
|
// Should we set
|
|
nsCOMPtr<nsIDOMSelection> 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 (PR_TRUE==aTag.EqualsIgnoreCase(bodyTag) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(tdTag) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(thTag) ||
|
|
PR_TRUE==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 (PR_TRUE==aTag.EqualsIgnoreCase(p) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h1) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h2) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h3) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h4) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h5) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(h6) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(address) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(pre) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(li) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(dt) ||
|
|
PR_TRUE==aTag.EqualsIgnoreCase(dd) )
|
|
{
|
|
aIsTag = PR_TRUE;
|
|
}
|
|
else {
|
|
aIsTag = PR_FALSE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsTable: true if node an html table
|
|
//
|
|
PRBool
|
|
nsHTMLEditor::IsTable(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null node passed to nsHTMLEditor::IsTable");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "table")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsTableCell: true if node an html td
|
|
//
|
|
PRBool
|
|
nsHTMLEditor::IsTableCell(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null node passed to nsHTMLEditor::IsTableCell");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "td")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsTableElement: true if node an html table, td, tr, ...
|
|
//
|
|
PRBool
|
|
nsHTMLEditor::IsTableElement(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null node passed to nsHTMLEditor::IsTableElement");
|
|
nsAutoString tagName;
|
|
nsEditor::GetTagString(node,tagName);
|
|
if (tagName == "table" || tagName == "tr" ||
|
|
tagName == "td" || tagName == "th" ||
|
|
tagName == "thead" || tagName == "tfoot" ||
|
|
tagName == "tbody" || tagName == "caption")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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 (IsTable(tmp)) tbl = tmp;
|
|
node = tmp;
|
|
}
|
|
return tbl;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::DeleteSelectionAndPrepareToCreateNode(nsCOMPtr<nsIDOMNode> &parentSelectedNode, PRInt32& offsetOfNewNode)
|
|
{
|
|
nsresult result=NS_ERROR_NOT_INITIALIZED;
|
|
nsCOMPtr<nsIDOMSelection> 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(PR_TRUE==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
|
|
}
|
|
}
|
|
}
|
|
|
|
// I dont know what is up with this, but there is no reason to split
|
|
// any node we happen to be inserting into. The code below (ifdef'd out)
|
|
// breaks InsertBreak().
|
|
|
|
|
|
#if 0
|
|
|
|
/* if the selection is not a text node, split the parent node if necesary
|
|
and compute where to put the new node
|
|
*/
|
|
else
|
|
{ // it's an interior node
|
|
nsCOMPtr<nsIDOMNodeList>parentChildList;
|
|
parentSelectedNode->GetChildNodes(getter_AddRefs(parentChildList));
|
|
if ((NS_SUCCEEDED(result)) && parentChildList)
|
|
{
|
|
result = parentChildList->Item(offsetOfSelectedNode, getter_AddRefs(selectedNode));
|
|
if ((NS_SUCCEEDED(result)) && selectedNode)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData>selectedNodeAsText;
|
|
selectedNodeAsText = do_QueryInterface(selectedNode);
|
|
nsCOMPtr<nsIDOMNodeList>childList;
|
|
//CM: I added "result ="
|
|
result = selectedNode->GetChildNodes(getter_AddRefs(childList));
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (childList)
|
|
{
|
|
childList->GetLength(&selectedNodeContentCount);
|
|
}
|
|
else
|
|
{
|
|
// This is the case where the collapsed selection offset
|
|
// points to an inline node with no children
|
|
// This must also be where the new node should be inserted
|
|
// and there is no splitting necessary
|
|
offsetOfNewNode = offsetOfSelectedNode;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
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 = 0; // insert new node as first child
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Here's where the new node was inserted
|
|
}
|
|
else {
|
|
printf("InsertBreak into an empty document is not yet supported\n");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
#ifdef XP_MAC
|
|
#pragma mark -
|
|
#endif
|
|
|
|
#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;
|
|
}
|
|
|
|
|
|
TypeInState * nsHTMLEditor::GetTypeInState()
|
|
{
|
|
if (mTypeInState) {
|
|
NS_ADDREF(mTypeInState);
|
|
}
|
|
return mTypeInState;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetTextPropertiesForNode(nsIDOMNode *aNode,
|
|
nsIDOMNode *aParent,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (gNoisy) { printf("nsTextEditor::SetTextPropertyForNode\n"); }
|
|
nsresult result=NS_OK;
|
|
PRBool textPropertySet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
IsTextPropertySetByContent(aNode, aPropName, aAttribute, aValue, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_FALSE==textPropertySet)
|
|
{
|
|
if (aValue && 0!=aValue->Length())
|
|
{
|
|
result = RemoveTextPropertiesForNode(aNode, aParent, aStartOffset, aEndOffset, aPropName, aAttribute);
|
|
}
|
|
nsAutoString tag;
|
|
aPropName->ToString(tag);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>newStyleNode;
|
|
result = nsEditor::CreateNode(tag, aParent, 0, getter_AddRefs(newStyleNode));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!newStyleNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsChar;
|
|
nodeAsChar = do_QueryInterface(aNode);
|
|
if (nodeAsChar)
|
|
{
|
|
result = MoveContentOfNodeIntoNewParent(aNode, newStyleNode, aStartOffset, aEndOffset);
|
|
}
|
|
else
|
|
{ // handle non-text selection
|
|
nsCOMPtr<nsIDOMNode> parent; // used just to make the code easier to understand
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
parent = do_QueryInterface(aNode);
|
|
child = GetChildAt(parent, aStartOffset);
|
|
// XXX: need to loop for aStartOffset!=aEndOffset-1?
|
|
PRInt32 offsetInParent = aStartOffset; // remember where aNode was in aParent
|
|
if (NS_SUCCEEDED(result))
|
|
{ // remove child from parent
|
|
result = nsEditor::DeleteNode(child);
|
|
if (NS_SUCCEEDED(result))
|
|
{ // put child into the newStyleNode
|
|
result = nsEditor::InsertNode(child, newStyleNode, 0);
|
|
if (NS_SUCCEEDED(result))
|
|
{ // put newStyleNode in parent where child was
|
|
result = nsEditor::InsertNode(newStyleNode, parent, offsetInParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (aAttribute && 0!=aAttribute->Length())
|
|
{
|
|
nsCOMPtr<nsIDOMElement> newStyleElement;
|
|
newStyleElement = do_QueryInterface(newStyleNode);
|
|
nsAutoString value;
|
|
if (aValue) {
|
|
value = *aValue;
|
|
}
|
|
// XXX should be a call to editor to change attribute!
|
|
result = newStyleElement->SetAttribute(*aAttribute, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (gNoisy) { printf("SetTextPropertyForNode returning %d, dumping content...\n", result);}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::MoveContentOfNodeIntoNewParent(nsIDOMNode *aNode,
|
|
nsIDOMNode *aNewParentNode,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset)
|
|
{
|
|
if (!aNode || !aNewParentNode) { return NS_ERROR_NULL_POINTER; }
|
|
if (gNoisy) { printf("nsTextEditor::MoveContentOfNodeIntoNewParent\n"); }
|
|
nsresult result=NS_OK;
|
|
|
|
PRUint32 count;
|
|
result = GetLengthOfDOMNode(aNode, count);
|
|
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>newChildNode; // this will be the child node we move into the new style node
|
|
// split the node at the start offset unless the split would create an empty node
|
|
if (aStartOffset!=0)
|
|
{
|
|
result = nsEditor::SplitNode(aNode, aStartOffset, getter_AddRefs(newChildNode));
|
|
if (gNoisy) { printf("* split created left node %p\n", newChildNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (aEndOffset!=(PRInt32)count)
|
|
{
|
|
result = nsEditor::SplitNode(aNode, aEndOffset-aStartOffset, getter_AddRefs(newChildNode));
|
|
if (gNoisy) { printf("* split created left node %p\n", newChildNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
else
|
|
{
|
|
newChildNode = do_QueryInterface(aNode);
|
|
if (gNoisy) { printf("* second split not required, new text node set to aNode = %p\n", newChildNode.get());}
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// move aNewParentNode into the right location
|
|
|
|
// optimization: if all we're doing is changing a value for an existing attribute for the
|
|
// entire selection, then just twiddle the existing style node
|
|
PRBool done = PR_FALSE; // set to true in optimized case if we can really do the optimization
|
|
/*
|
|
if (aAttribute && aValue && (0==aStartOffset) && (aEndOffset==(PRInt32)count))
|
|
{
|
|
// ??? can we really compute this?
|
|
}
|
|
*/
|
|
if (PR_FALSE==done)
|
|
{
|
|
// if we've ended up with an empty text node, just delete it and we're done
|
|
nsCOMPtr<nsIDOMCharacterData>newChildNodeAsChar;
|
|
newChildNodeAsChar = do_QueryInterface(newChildNode);
|
|
PRUint32 newChildNodeLength;
|
|
if (newChildNodeAsChar)
|
|
{
|
|
newChildNodeAsChar->GetLength(&newChildNodeLength);
|
|
if (0==newChildNodeLength)
|
|
{
|
|
result = nsEditor::DeleteNode(newChildNode);
|
|
done = PR_TRUE;
|
|
// XXX: need to set selection here
|
|
}
|
|
}
|
|
// move the new child node into the new parent
|
|
if (PR_FALSE==done)
|
|
{
|
|
// first, move the new parent into the correct location
|
|
PRInt32 offsetInParent;
|
|
nsCOMPtr<nsIDOMNode>parentNode;
|
|
result = aNode->GetParentNode(getter_AddRefs(parentNode));
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::DeleteNode(aNewParentNode);
|
|
if (NS_SUCCEEDED(result))
|
|
{ // must get child offset AFTER delete of aNewParentNode!
|
|
result = GetChildOffset(aNode, parentNode, offsetInParent);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::InsertNode(aNewParentNode, parentNode, offsetInParent);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// then move the new child into the new parent node
|
|
result = nsEditor::DeleteNode(newChildNode);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::InsertNode(newChildNode, aNewParentNode, 0);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// set the selection
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
selection->Collapse(newChildNode, 0);
|
|
PRInt32 endOffset = aEndOffset-aStartOffset;
|
|
selection->Extend(newChildNode, endOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* this should only get called if the only intervening nodes are inline style nodes */
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetTextPropertiesForNodesWithSameParent(nsIDOMNode *aStartNode,
|
|
PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMNode *aParent,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (gNoisy) { printf("---------- start nsTextEditor::SetTextPropertiesForNodesWithSameParent ----------\n"); }
|
|
nsresult result=NS_OK;
|
|
PRBool textPropertySet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
IsTextPropertySetByContent(aStartNode, aPropName, aAttribute, aValue, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_FALSE==textPropertySet)
|
|
{
|
|
result = RemoveTextPropertiesForNodeWithDifferentParents(aStartNode, aStartOffset,
|
|
aEndNode, aEndOffset,
|
|
aParent, aPropName, aAttribute);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
nsAutoString tag;
|
|
aPropName->ToString(tag);
|
|
// create the new style node, which will be the new parent for the selected nodes
|
|
nsCOMPtr<nsIDOMNode>newStyleNode;
|
|
nsCOMPtr<nsIDOMDocument>doc;
|
|
result = GetDocument(getter_AddRefs(doc));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!doc) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMElement>newElement;
|
|
result = doc->CreateElement(tag, getter_AddRefs(newElement));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!newElement) return NS_ERROR_NULL_POINTER;
|
|
|
|
newStyleNode = do_QueryInterface(newElement);
|
|
if (!newStyleNode) return NS_ERROR_NULL_POINTER;
|
|
result = MoveContiguousContentIntoNewParent(aStartNode, aStartOffset, aEndNode, aEndOffset, aParent, newStyleNode);
|
|
if (NS_SUCCEEDED(result) && aAttribute && 0!=aAttribute->Length())
|
|
{
|
|
nsCOMPtr<nsIDOMElement> newStyleElement;
|
|
newStyleElement = do_QueryInterface(newStyleNode);
|
|
nsAutoString value;
|
|
if (aValue) {
|
|
value = *aValue;
|
|
}
|
|
result = newStyleElement->SetAttribute(*aAttribute, value);
|
|
}
|
|
}
|
|
if (gNoisy) { printf("---------- end nsTextEditor::SetTextPropertiesForNodesWithSameParent ----------\n"); }
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::MoveContiguousContentIntoNewParent(nsIDOMNode *aStartNode,
|
|
PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMNode *aGrandParentNode,
|
|
nsIDOMNode *aNewParentNode)
|
|
{
|
|
if (!aStartNode || !aEndNode || !aNewParentNode) { return NS_ERROR_NULL_POINTER; }
|
|
if (gNoisy) { printf("--- start nsTextEditor::MoveContiguousContentIntoNewParent ---\n"); }
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
|
|
nsresult result = NS_OK;
|
|
nsCOMPtr<nsIDOMNode>startNode, endNode;
|
|
PRInt32 startOffset = aStartOffset; // this will be the left edge of what we change
|
|
PRInt32 endOffset = aEndOffset; // this will be the right edge of what we change
|
|
nsCOMPtr<nsIDOMNode>newLeftNode; // this will be the middle text node
|
|
if (IsTextNode(aStartNode))
|
|
{
|
|
startOffset = 0;
|
|
if (gNoisy) { printf("aStartNode is a text node.\n"); }
|
|
startNode = do_QueryInterface(aStartNode);
|
|
if (0!=aStartOffset)
|
|
{
|
|
result = nsEditor::SplitNode(aStartNode, aStartOffset, getter_AddRefs(newLeftNode));
|
|
if (gNoisy) { printf("split aStartNode, newLeftNode = %p\n", newLeftNode.get()); }
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
else {
|
|
if (gNoisy) { printf("did not split aStartNode\n"); }
|
|
}
|
|
}
|
|
else {
|
|
startNode = GetChildAt(aStartNode, aStartOffset);
|
|
if (gNoisy) { printf("aStartNode is not a text node, got startNode = %p.\n", startNode.get()); }
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>newRightNode; // this will be the middle text node
|
|
if (IsTextNode(aEndNode))
|
|
{
|
|
if (gNoisy) { printf("aEndNode is a text node.\n"); }
|
|
endNode = do_QueryInterface(aEndNode);
|
|
PRUint32 count;
|
|
GetLengthOfDOMNode(aEndNode, count);
|
|
if ((PRInt32)count!=aEndOffset)
|
|
{
|
|
result = nsEditor::SplitNode(aEndNode, aEndOffset, getter_AddRefs(newRightNode));
|
|
if (gNoisy) { printf("split aEndNode, newRightNode = %p\n", newRightNode.get()); }
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
else {
|
|
newRightNode = do_QueryInterface(aEndNode);
|
|
if (gNoisy) { printf("did not split aEndNode\n"); }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
endNode = GetChildAt(aEndNode, aEndOffset-1);
|
|
newRightNode = do_QueryInterface(endNode);
|
|
if (gNoisy) { printf("aEndNode is not a text node, got endNode = %p.\n", endNode.get()); }
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
PRInt32 offsetInParent;
|
|
result = GetChildOffset(startNode, aGrandParentNode, offsetInParent);
|
|
/*
|
|
if (newLeftNode) {
|
|
result = GetChildOffset(newLeftNode, aGrandParentNode, offsetInParent);
|
|
}
|
|
else {
|
|
offsetInParent = 0; // relies on +1 below in call to CreateNode
|
|
}
|
|
*/
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// insert aNewParentNode into aGrandParentNode
|
|
result = nsEditor::InsertNode(aNewParentNode, aGrandParentNode, offsetInParent);
|
|
if (gNoisy) printf("just after InsertNode 1\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{ // move the right half of the start node into the new parent node
|
|
nsCOMPtr<nsIDOMNode>intermediateNode;
|
|
result = startNode->GetNextSibling(getter_AddRefs(intermediateNode));
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::DeleteNode(startNode);
|
|
if (gNoisy) printf("just after DeleteNode 1\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
PRInt32 childIndex=0;
|
|
result = nsEditor::InsertNode(startNode, aNewParentNode, childIndex);
|
|
if (gNoisy) printf("just after InsertNode 2\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
childIndex++;
|
|
if (NS_SUCCEEDED(result))
|
|
{ // move all the intermediate nodes into the new parent node
|
|
nsCOMPtr<nsIDOMNode>nextSibling;
|
|
while (intermediateNode.get() != endNode.get())
|
|
{
|
|
if (!intermediateNode)
|
|
result = NS_ERROR_NULL_POINTER;
|
|
if (NS_FAILED(result)) {
|
|
break;
|
|
}
|
|
// get the next sibling before moving the current child!!!
|
|
intermediateNode->GetNextSibling(getter_AddRefs(nextSibling));
|
|
result = nsEditor::DeleteNode(intermediateNode);
|
|
if (gNoisy) printf("just after DeleteNode 3\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::InsertNode(intermediateNode, aNewParentNode, childIndex);
|
|
if (gNoisy) printf("just after InsertNode 4\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
childIndex++;
|
|
}
|
|
intermediateNode = do_QueryInterface(nextSibling);
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{ // move the left half of the end node into the new parent node
|
|
result = nsEditor::DeleteNode(newRightNode);
|
|
if (gNoisy) printf("just after DeleteNode 5\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::InsertNode(newRightNode, aNewParentNode, childIndex);
|
|
if (gNoisy) printf("just after InsertNode 5\n");
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
// now set the selection
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
selection->Collapse(startNode, startOffset);
|
|
selection->Extend(newRightNode, endOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (gNoisy) { printf("--- end nsTextEditor::MoveContiguousContentIntoNewParent ---\n"); }
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::IsLeafThatTakesInlineStyle(const nsString *aTag,
|
|
PRBool &aResult)
|
|
{
|
|
if (!aTag) { return NS_ERROR_NULL_POINTER; }
|
|
|
|
aResult = (PRBool)((PR_FALSE==aTag->EqualsIgnoreCase("br")) &&
|
|
(PR_FALSE==aTag->EqualsIgnoreCase("hr")) );
|
|
return NS_OK;
|
|
}
|
|
|
|
/* this wraps every selected text node in a new inline style node if needed
|
|
the text nodes are treated as being unique -- each needs it's own style node
|
|
if the style is not already present.
|
|
each action has immediate effect on the content tree and resolved style, so
|
|
doing outermost text nodes first removes the need for interior style nodes in some cases.
|
|
XXX: need to code test to see if new style node is needed
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::SetTextPropertiesForNodeWithDifferentParents(nsIDOMRange *aRange,
|
|
nsIDOMNode *aStartNode,
|
|
PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMNode *aParent,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute,
|
|
const nsString *aValue)
|
|
{
|
|
if (gNoisy) { printf("start nsTextEditor::SetTextPropertiesForNodeWithDifferentParents\n"); }
|
|
nsresult result=NS_OK;
|
|
PRUint32 count;
|
|
if (!aRange || !aStartNode || !aEndNode || !aParent || !aPropName)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
PRInt32 startOffset, endOffset;
|
|
// create a style node for the text in the start parent
|
|
nsCOMPtr<nsIDOMNode>parent;
|
|
|
|
result = RemoveTextPropertiesForNodeWithDifferentParents(aStartNode,aStartOffset,
|
|
aEndNode, aEndOffset,
|
|
aParent, aPropName, aAttribute);
|
|
if (NS_FAILED(result)) { return result; }
|
|
|
|
// RemoveTextProperties... might have changed selection endpoints, get new ones
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) { return result; }
|
|
if (!selection) { return NS_ERROR_NULL_POINTER; }
|
|
selection->GetAnchorOffset(&aStartOffset);
|
|
selection->GetFocusOffset(&aEndOffset);
|
|
|
|
// create new parent nodes for all the content between the start and end nodes
|
|
nsCOMPtr<nsIContentIterator>iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(), getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
// find our starting point
|
|
PRBool startIsText = IsTextNode(aStartNode);
|
|
nsCOMPtr<nsIContent>startContent;
|
|
if (PR_TRUE==startIsText) {
|
|
startContent = do_QueryInterface(aStartNode);
|
|
}
|
|
else {
|
|
nsCOMPtr<nsIDOMNode>node = GetChildAt(aStartNode, aStartOffset);
|
|
startContent = do_QueryInterface(node);
|
|
}
|
|
|
|
// find our ending point
|
|
PRBool endIsText = IsTextNode(aEndNode);
|
|
nsCOMPtr<nsIContent>endContent;
|
|
if (PR_TRUE==endIsText) {
|
|
endContent = do_QueryInterface(aEndNode);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode>theEndNode;
|
|
if (aEndOffset>0)
|
|
{
|
|
theEndNode = GetChildAt(aEndNode, aEndOffset-1);
|
|
}
|
|
else {
|
|
// XXX: we need to find the previous node and set the selection correctly
|
|
NS_ASSERTION(0, "unexpected selection");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
endContent = do_QueryInterface(theEndNode);
|
|
}
|
|
|
|
if (!startContent || !endContent) { return NS_ERROR_NULL_POINTER; }
|
|
// iterate over the nodes between the starting and ending points
|
|
iter->Init(aRange);
|
|
nsCOMPtr<nsIContent> content;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
nsAutoString tag;
|
|
aPropName->ToString(tag);
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
if ((content.get() != startContent.get()) &&
|
|
(content.get() != endContent.get()))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>node;
|
|
node = do_QueryInterface(content);
|
|
if (IsEditable(node))
|
|
{
|
|
PRBool canContainChildren;
|
|
content->CanContainChildren(canContainChildren);
|
|
if (PR_FALSE==canContainChildren)
|
|
{
|
|
nsEditor::GetTagString(node,tag);
|
|
PRBool processLeaf;
|
|
IsLeafThatTakesInlineStyle(&tag, processLeaf);
|
|
if (processLeaf) // skip leaf tags that don't take inline style
|
|
{
|
|
// only want to wrap the node in a new style node if it doesn't already have that style
|
|
if (gNoisy) { printf("node %p is an editable leaf.\n", node.get()); }
|
|
PRBool textPropertySet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
IsTextPropertySetByContent(node, aPropName, aAttribute, aValue, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_FALSE==textPropertySet)
|
|
{
|
|
if (gNoisy) { printf("property not set\n"); }
|
|
node->GetParentNode(getter_AddRefs(parent));
|
|
if (!parent) { return NS_ERROR_NULL_POINTER; }
|
|
nsCOMPtr<nsIContent>parentContent;
|
|
parentContent = do_QueryInterface(parent);
|
|
nsCOMPtr<nsIDOMNode>parentNode = do_QueryInterface(parent);
|
|
if (PR_TRUE==IsTextNode(node))
|
|
{
|
|
startOffset = 0;
|
|
result = GetLengthOfDOMNode(node, (PRUint32&)endOffset);
|
|
}
|
|
else
|
|
{
|
|
parentContent->IndexOf(content, startOffset);
|
|
endOffset = startOffset+1;
|
|
}
|
|
if (gNoisy) { printf("start/end = %d %d\n", startOffset, endOffset); }
|
|
if (NS_SUCCEEDED(result)) {
|
|
result = SetTextPropertiesForNode(node, parentNode, startOffset, endOffset, aPropName, aAttribute, aValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// XXX: shouldn't there be an else here for non-text leaf nodes?
|
|
}
|
|
// note we don't check the result, we just rely on iter->IsDone
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
// handle endpoints
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// create a style node for the text in the start parent
|
|
nsCOMPtr<nsIDOMNode>startNode = do_QueryInterface(startContent);
|
|
result = startNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsChar;
|
|
nodeAsChar = do_QueryInterface(startNode);
|
|
if (nodeAsChar)
|
|
{
|
|
nodeAsChar->GetLength(&count);
|
|
if (gNoisy) { printf("processing start node %p.\n", nodeAsChar.get()); }
|
|
if (aStartOffset!=(PRInt32)count)
|
|
{
|
|
result = SetTextPropertiesForNode(startNode, parent, aStartOffset, count, aPropName, aAttribute, aValue);
|
|
startOffset = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
result = parent->GetParentNode(getter_AddRefs(grandParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!grandParent) return NS_ERROR_NULL_POINTER;
|
|
if (gNoisy) { printf("processing start node %p.\n", parent.get()); }
|
|
result = SetTextPropertiesForNode(parent, grandParent, aStartOffset, aStartOffset+1, aPropName, aAttribute, aValue);
|
|
startNode = do_QueryInterface(parent);
|
|
startOffset = aStartOffset;
|
|
}
|
|
|
|
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// create a style node for the text in the end parent
|
|
nsCOMPtr<nsIDOMNode>endNode = do_QueryInterface(endContent);
|
|
result = endNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) {
|
|
return result;
|
|
}
|
|
nodeAsChar = do_QueryInterface(endNode);
|
|
if (nodeAsChar)
|
|
{
|
|
nodeAsChar->GetLength(&count);
|
|
if (gNoisy) { printf("processing end node %p.\n", nodeAsChar.get()); }
|
|
if (0!=aEndOffset)
|
|
{
|
|
result = SetTextPropertiesForNode(endNode, parent, 0, aEndOffset, aPropName, aAttribute, aValue);
|
|
// SetTextPropertiesForNode kindly computed the proper selection focus node and offset for us,
|
|
// remember them here
|
|
selection->GetFocusOffset(&endOffset);
|
|
selection->GetFocusNode(getter_AddRefs(endNode));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NS_ASSERTION(0!=aEndOffset, "unexpected selection end offset");
|
|
if (0==aEndOffset) { return NS_ERROR_NOT_IMPLEMENTED; }
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
result = parent->GetParentNode(getter_AddRefs(grandParent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!grandParent) return NS_ERROR_NULL_POINTER;
|
|
if (gNoisy) { printf("processing end node %p.\n", parent.get()); }
|
|
result = SetTextPropertiesForNode(parent, grandParent, aEndOffset-1, aEndOffset, aPropName, aAttribute, aValue);
|
|
endNode = do_QueryInterface(parent);
|
|
endOffset = 0;
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
|
|
selection->Collapse(startNode, startOffset);
|
|
selection->Extend(endNode, aEndOffset);
|
|
}
|
|
}
|
|
}
|
|
if (gNoisy) { printf("end nsTextEditor::SetTextPropertiesForNodeWithDifferentParents\n"); }
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveTextPropertiesForNode(nsIDOMNode *aNode,
|
|
nsIDOMNode *aParent,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute)
|
|
{
|
|
if (gNoisy) { printf("nsTextEditor::RemoveTextPropertyForNode\n"); }
|
|
nsresult result=NS_OK;
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsChar;
|
|
nodeAsChar = do_QueryInterface(aNode);
|
|
PRBool textPropertySet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
IsTextPropertySetByContent(aNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==textPropertySet)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>parent; // initially set to first interior parent node to process
|
|
nsCOMPtr<nsIDOMNode>newMiddleNode; // this will be the middle node after any required splits
|
|
nsCOMPtr<nsIDOMNode>newLeftNode; // this will be the leftmost node,
|
|
// the node being split will be rightmost
|
|
PRUint32 count;
|
|
// if aNode is a text node, treat is specially
|
|
if (nodeAsChar)
|
|
{
|
|
nodeAsChar->GetLength(&count);
|
|
// split the node, and all parent nodes up to the style node
|
|
// then promote the selected content to the parent of the style node
|
|
if (0!=aStartOffset) {
|
|
if (gNoisy) { printf("* splitting text node %p at %d\n", aNode, aStartOffset);}
|
|
result = nsEditor::SplitNode(aNode, aStartOffset, getter_AddRefs(newLeftNode));
|
|
if (gNoisy) { printf("* split created left node %p\n", newLeftNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if ((PRInt32)count!=aEndOffset) {
|
|
if (gNoisy) { printf("* splitting text node (right node) %p at %d\n", aNode, aEndOffset-aStartOffset);}
|
|
result = nsEditor::SplitNode(aNode, aEndOffset-aStartOffset, getter_AddRefs(newMiddleNode));
|
|
if (gNoisy) { printf("* split created middle node %p\n", newMiddleNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
else {
|
|
if (gNoisy) { printf("* no need to split text node, middle to aNode\n");}
|
|
newMiddleNode = do_QueryInterface(aNode);
|
|
}
|
|
NS_ASSERTION(newMiddleNode, "no middle node created");
|
|
// now that the text node is split, split parent nodes until we get to the style node
|
|
parent = do_QueryInterface(aParent); // we know this has to succeed, no need to check
|
|
}
|
|
}
|
|
else {
|
|
newMiddleNode = do_QueryInterface(aNode);
|
|
parent = do_QueryInterface(aParent);
|
|
}
|
|
if (NS_SUCCEEDED(result) && newMiddleNode)
|
|
{
|
|
// split every ancestor until we find the node that is giving us the style we want to remove
|
|
// then split the style node and promote the selected content to the style node's parent
|
|
while (NS_SUCCEEDED(result) && parent)
|
|
{
|
|
if (gNoisy) { printf("* looking at parent %p\n", parent.get());}
|
|
// get the tag from parent and see if we're done
|
|
nsCOMPtr<nsIDOMNode>temp;
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(parent);
|
|
if (element)
|
|
{
|
|
nsAutoString tag;
|
|
result = element->GetTagName(tag);
|
|
if (gNoisy) { printf("* parent has tag %s\n", tag.ToNewCString()); } // XXX leak!
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
const PRUnichar *unicodeString;
|
|
aPropName->GetUnicode(&unicodeString);
|
|
if (PR_FALSE==tag.EqualsIgnoreCase(unicodeString))
|
|
{
|
|
PRInt32 offsetInParent;
|
|
result = GetChildOffset(newMiddleNode, parent, offsetInParent);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (0!=offsetInParent) {
|
|
if (gNoisy) { printf("* splitting parent %p at offset %d\n", parent.get(), offsetInParent);}
|
|
result = nsEditor::SplitNode(parent, offsetInParent, getter_AddRefs(newLeftNode));
|
|
if (gNoisy) { printf("* split created left node %p sibling of parent\n", newLeftNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNodeList>childNodes;
|
|
result = parent->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_SUCCEEDED(result) && childNodes)
|
|
{
|
|
childNodes->GetLength(&count);
|
|
NS_ASSERTION(count>0, "bad child count in newly split node");
|
|
if ((PRInt32)count!=1)
|
|
{
|
|
if (gNoisy) { printf("* splitting parent %p at offset %d\n", parent.get(), 1);}
|
|
result = nsEditor::SplitNode(parent, 1, getter_AddRefs(newMiddleNode));
|
|
if (gNoisy) { printf("* split created middle node %p sibling of parent\n", newMiddleNode.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
else {
|
|
if (gNoisy) { printf("* no need to split parent, newMiddleNode=parent\n");}
|
|
newMiddleNode = do_QueryInterface(parent);
|
|
}
|
|
NS_ASSERTION(newMiddleNode, "no middle node created");
|
|
parent->GetParentNode(getter_AddRefs(temp));
|
|
parent = do_QueryInterface(temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// else we've found the style tag (referred to by "parent")
|
|
// newMiddleNode is the node that is an ancestor to the selection
|
|
else
|
|
{
|
|
if (gNoisy) { printf("* this is the style node\n");}
|
|
PRInt32 offsetInParent;
|
|
result = GetChildOffset(newMiddleNode, parent, offsetInParent);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNodeList>childNodes;
|
|
result = parent->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_SUCCEEDED(result) && childNodes)
|
|
{
|
|
childNodes->GetLength(&count);
|
|
// if there are siblings to the right, split parent at offsetInParent+1
|
|
if ((PRInt32)count!=offsetInParent+1)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>newRightNode;
|
|
//nsCOMPtr<nsIDOMNode>temp;
|
|
if (gNoisy) { printf("* splitting parent %p at offset %d for right side\n", parent.get(), offsetInParent+1);}
|
|
result = nsEditor::SplitNode(parent, offsetInParent+1, getter_AddRefs(temp));
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
newRightNode = do_QueryInterface(parent);
|
|
parent = do_QueryInterface(temp);
|
|
if (gNoisy) { printf("* split created right node %p sibling of parent %p\n", newRightNode.get(), parent.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(result) && 0!=offsetInParent) {
|
|
if (gNoisy) { printf("* splitting parent %p at offset %d for left side\n", parent.get(), offsetInParent);}
|
|
result = nsEditor::SplitNode(parent, offsetInParent, getter_AddRefs(newLeftNode));
|
|
if (gNoisy) { printf("* split created left node %p sibling of parent %p\n", newLeftNode.get(), parent.get());}
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
if (NS_SUCCEEDED(result))
|
|
{ // promote the selection to the grandparent
|
|
// first, determine the child's position in it's parent
|
|
PRInt32 childPositionInParent;
|
|
GetChildOffset(newMiddleNode, parent, childPositionInParent);
|
|
// compare childPositionInParent to the number of children in parent
|
|
//PRUint32 count=0;
|
|
//nsCOMPtr<nsIDOMNodeList>childNodes;
|
|
result = parent->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_SUCCEEDED(result) && childNodes) {
|
|
childNodes->GetLength(&count);
|
|
}
|
|
PRBool insertAfter = PR_FALSE;
|
|
// if they're equal, we'll insert newMiddleNode in grandParent after the parent
|
|
if ((PRInt32)count==childPositionInParent) {
|
|
insertAfter = PR_TRUE;
|
|
}
|
|
// now that we know where to put newMiddleNode, do it.
|
|
nsCOMPtr<nsIDOMNode>grandParent;
|
|
result = parent->GetParentNode(getter_AddRefs(grandParent));
|
|
if (NS_SUCCEEDED(result) && grandParent)
|
|
{
|
|
if (gNoisy) { printf("* deleting middle node %p\n", newMiddleNode.get());}
|
|
result = nsEditor::DeleteNode(newMiddleNode);
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
PRInt32 position;
|
|
result = GetChildOffset(parent, grandParent, position);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
if (PR_TRUE==insertAfter)
|
|
{
|
|
if (gNoisy) {printf("insertAfter=PR_TRUE, incr. position\n"); }
|
|
position++;
|
|
}
|
|
if (gNoisy) {
|
|
printf("* inserting node %p in grandparent %p at offset %d\n",
|
|
newMiddleNode.get(), grandParent.get(), position);
|
|
}
|
|
result = nsEditor::InsertNode(newMiddleNode, grandParent, position);
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
PRBool hasChildren=PR_TRUE;
|
|
parent->HasChildNodes(&hasChildren);
|
|
if (PR_FALSE==hasChildren) {
|
|
if (gNoisy) { printf("* deleting empty style node %p\n", parent.get());}
|
|
result = nsEditor::DeleteNode(parent);
|
|
if (gNoisy) {DebugDumpContent(); } // DEBUG
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* this should only get called if the only intervening nodes are inline style nodes */
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveTextPropertiesForNodesWithSameParent(nsIDOMNode *aStartNode,
|
|
PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMNode *aParent,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute)
|
|
{
|
|
NS_ASSERTION(0, "obsolete");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::RemoveTextPropertiesForNodeWithDifferentParents(nsIDOMNode *aStartNode,
|
|
PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode,
|
|
PRInt32 aEndOffset,
|
|
nsIDOMNode *aParent,
|
|
nsIAtom *aPropName,
|
|
const nsString *aAttribute)
|
|
{
|
|
if (gNoisy) { printf("----- start nsTextEditor::RemoveTextPropertiesForNodeWithDifferentParents-----\n"); }
|
|
nsresult result=NS_OK;
|
|
if (!aStartNode || !aEndNode || !aParent || !aPropName)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
PRInt32 rangeStartOffset = aStartOffset; // used to construct a range for the nodes between
|
|
PRInt32 rangeEndOffset = aEndOffset; // aStartNode and aEndNode after we've processed those endpoints
|
|
|
|
// temporary state variables
|
|
PRBool textPropertySet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
|
|
// delete the style node for the text in the start parent
|
|
nsCOMPtr<nsIDOMNode>startNode = do_QueryInterface(aStartNode); // use computed endpoint based on end points passed in
|
|
nsCOMPtr<nsIDOMNode>endNode = do_QueryInterface(aEndNode); // use computed endpoint based on end points passed in
|
|
nsCOMPtr<nsIDOMNode>parent; // the parent of the node we're operating on
|
|
PRBool skippedStartNode = PR_FALSE; // we skip the start node if aProp is not set on it
|
|
PRBool skippedEndNode = PR_FALSE; // we skip the end node if aProp is not set on it
|
|
PRUint32 count;
|
|
nsCOMPtr<nsIDOMCharacterData>nodeAsChar;
|
|
nodeAsChar = do_QueryInterface(startNode);
|
|
if (nodeAsChar)
|
|
{
|
|
result = aStartNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) { return result; }
|
|
nodeAsChar->GetLength(&count);
|
|
if ((PRUint32)aStartOffset!=count)
|
|
{ // only do this if at least one child is selected
|
|
IsTextPropertySetByContent(aStartNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==textPropertySet)
|
|
{
|
|
result = RemoveTextPropertiesForNode(aStartNode, parent, aStartOffset, count, aPropName, aAttribute);
|
|
if (0!=aStartOffset) {
|
|
rangeStartOffset = 0; // we split aStartNode at aStartOffset and it is the right node now
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skippedStartNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping start node because property not set\n"); }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skippedStartNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping start node because aStartOffset==count\n"); }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
startNode = GetChildAt(aStartNode, aStartOffset);
|
|
parent = do_QueryInterface(aStartNode);
|
|
if (!startNode || !parent) {return NS_ERROR_NULL_POINTER;}
|
|
IsTextPropertySetByContent(startNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==textPropertySet)
|
|
{
|
|
result = RemoveTextPropertiesForNode(startNode, parent, aStartOffset, aStartOffset+1, aPropName, aAttribute);
|
|
}
|
|
else
|
|
{
|
|
skippedStartNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping start node because property not set\n"); }
|
|
}
|
|
}
|
|
|
|
// delete the style node for the text in the end parent
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nodeAsChar = do_QueryInterface(endNode);
|
|
if (nodeAsChar)
|
|
{
|
|
result = endNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
nodeAsChar->GetLength(&count);
|
|
if (aEndOffset!=0)
|
|
{ // only do this if at least one child is selected
|
|
IsTextPropertySetByContent(endNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==textPropertySet)
|
|
{
|
|
result = RemoveTextPropertiesForNode(endNode, parent, 0, aEndOffset, aPropName, aAttribute);
|
|
if (0!=aEndOffset && (((PRInt32)count)!=aEndOffset)) {
|
|
rangeEndOffset = 0; // we split endNode at aEndOffset and it is the right node now
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skippedEndNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping end node because aProperty not set.\n"); }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
skippedEndNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping end node because aEndOffset==0\n"); }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
endNode = GetChildAt(aEndNode, aEndOffset);
|
|
parent = do_QueryInterface(aEndNode);
|
|
if (!endNode || !parent) {return NS_ERROR_NULL_POINTER;}
|
|
IsTextPropertySetByContent(endNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode));
|
|
if (PR_TRUE==textPropertySet)
|
|
{
|
|
result = RemoveTextPropertiesForNode(endNode, parent, aEndOffset, aEndOffset+1, aPropName, aAttribute);
|
|
}
|
|
else
|
|
{
|
|
skippedEndNode = PR_TRUE;
|
|
if (gNoisy) { printf("skipping end node because property not set\n"); }
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove aPropName style nodes for all the content between the start and end nodes
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
// build our own range now, because the endpoints may have shifted during shipping
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
result = nsComponentManager::CreateInstance(kCRangeCID,
|
|
nsnull,
|
|
nsIDOMRange::GetIID(),
|
|
getter_AddRefs(range));
|
|
if (NS_FAILED(result)) { return result; }
|
|
if (!range) { return NS_ERROR_NULL_POINTER; }
|
|
// compute the start node
|
|
if (PR_TRUE==skippedStartNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>tempNode = do_QueryInterface(startNode);
|
|
nsEditor::GetNextNode(startNode, PR_TRUE, getter_AddRefs(tempNode));
|
|
startNode = do_QueryInterface(tempNode);
|
|
}
|
|
range->SetStart(startNode, rangeStartOffset);
|
|
range->SetEnd(endNode, rangeEndOffset);
|
|
if (gNoisy)
|
|
{
|
|
printf("created range [(%p,%d), (%p,%d)]\n",
|
|
startNode.get(), rangeStartOffset,
|
|
endNode.get(), rangeEndOffset);
|
|
}
|
|
|
|
nsVoidArray nodeList;
|
|
nsCOMPtr<nsIContentIterator>iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(), getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIContent>startContent;
|
|
startContent = do_QueryInterface(startNode);
|
|
nsCOMPtr<nsIContent>endContent;
|
|
endContent = do_QueryInterface(endNode);
|
|
if (startContent && endContent)
|
|
{
|
|
iter->Init(range);
|
|
nsCOMPtr<nsIContent> content;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
nsAutoString propName; // the property we are removing
|
|
aPropName->ToString(propName);
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
if ((content.get() != startContent.get()) &&
|
|
(content.get() != endContent.get()))
|
|
{
|
|
nsCOMPtr<nsIDOMElement>element;
|
|
element = do_QueryInterface(content);
|
|
if (element)
|
|
{
|
|
nsString tag;
|
|
element->GetTagName(tag);
|
|
if (propName.EqualsIgnoreCase(tag))
|
|
{
|
|
if (-1==nodeList.IndexOf(content.get())) {
|
|
nodeList.AppendElement((void *)(content.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// note we don't check the result, we just rely on iter->IsDone
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
}
|
|
|
|
// now delete all the style nodes we found
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsIContent *contentPtr;
|
|
contentPtr = (nsIContent*)(nodeList.ElementAt(0));
|
|
while (NS_SUCCEEDED(result) && contentPtr)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>styleNode;
|
|
styleNode = do_QueryInterface(contentPtr);
|
|
// promote the children of styleNode
|
|
nsCOMPtr<nsIDOMNode>parentNode;
|
|
result = styleNode->GetParentNode(getter_AddRefs(parentNode));
|
|
if (NS_SUCCEEDED(result) && parentNode)
|
|
{
|
|
PRInt32 position;
|
|
result = GetChildOffset(styleNode, parentNode, position);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
nsCOMPtr<nsIDOMNode>previousSiblingNode;
|
|
nsCOMPtr<nsIDOMNode>childNode;
|
|
result = styleNode->GetLastChild(getter_AddRefs(childNode));
|
|
while (NS_SUCCEEDED(result) && childNode)
|
|
{
|
|
childNode->GetPreviousSibling(getter_AddRefs(previousSiblingNode));
|
|
// explicitly delete of childNode from styleNode
|
|
// can't just rely on DOM semantics of InsertNode doing the delete implicitly, doesn't undo!
|
|
result = nsEditor::DeleteNode(childNode);
|
|
if (NS_SUCCEEDED(result))
|
|
{
|
|
result = nsEditor::InsertNode(childNode, parentNode, position);
|
|
if (gNoisy)
|
|
{
|
|
printf("deleted next sibling node %p\n", childNode.get());
|
|
}
|
|
}
|
|
childNode = do_QueryInterface(previousSiblingNode);
|
|
} // end while loop
|
|
// delete styleNode
|
|
result = nsEditor::DeleteNode(styleNode);
|
|
if (gNoisy)
|
|
{
|
|
printf("deleted style node %p\n", styleNode.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
// get next content ptr
|
|
nodeList.RemoveElementAt(0);
|
|
contentPtr = (nsIContent*)(nodeList.ElementAt(0));
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMSelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// set the sel. start point. if we skipped the start node, just use it
|
|
if (PR_TRUE==skippedStartNode)
|
|
startNode = do_QueryInterface(aStartNode);
|
|
result = selection->Collapse(startNode, rangeStartOffset);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
// set the sel. end point. if we skipped the end node, just use it
|
|
if (PR_TRUE==skippedEndNode)
|
|
endNode = do_QueryInterface(aEndNode);
|
|
result = selection->Extend(endNode, rangeEndOffset);
|
|
if (NS_FAILED(result)) return result;
|
|
if (gNoisy) { printf("RTPFNWDP set selection.\n"); }
|
|
}
|
|
if (gNoisy)
|
|
{
|
|
printf("----- end nsTextEditor::RemoveTextPropertiesForNodeWithDifferentParents, dumping content...-----\n");
|
|
DebugDumpContent();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* 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.
|
|
* In all cases, the content of the right text node is moved into
|
|
* the left node, and the right node is deleted.
|
|
* Uses nsEditor::JoinNodes so action is undoable.
|
|
* Should be called within the context of a batch transaction.
|
|
*/
|
|
/*
|
|
XXX: TODO: use a helper function of next/prev sibling that only deals with editable content
|
|
*/
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMSelection *aInSelection)
|
|
{
|
|
if (gNoisy)
|
|
{
|
|
printf("---------- start nsTextEditor::CollapseAdjacentTextNodes ----------\n");
|
|
DebugDumpContent();
|
|
}
|
|
if (!aInSelection) return NS_ERROR_NULL_POINTER;
|
|
|
|
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
|
|
|
|
PRBool isCollapsed;
|
|
aInSelection->GetIsCollapsed(&isCollapsed);
|
|
if (PR_TRUE==isCollapsed) { return NS_OK; } // no need to scan collapsed selection
|
|
|
|
// store info about selection endpoints so we can re-establish selection after collapsing text nodes
|
|
// XXX: won't work for multiple selections (this will create a single selection from anchor to focus)
|
|
nsCOMPtr<nsIDOMNode> parentForSelection; // selection's block parent
|
|
PRInt32 rangeStartOffset, rangeEndOffset; // selection offsets
|
|
nsresult result = GetTextSelectionOffsetsForRange(aInSelection, getter_AddRefs(parentForSelection),
|
|
rangeStartOffset, rangeEndOffset);
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parentForSelection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// first, do the endpoints of the selection, so we don't rely on the iteration to include them
|
|
nsCOMPtr<nsIDOMNode>anchor, focus;
|
|
nsCOMPtr<nsIDOMCharacterData>anchorText, focusText;
|
|
aInSelection->GetAnchorNode(getter_AddRefs(anchor));
|
|
anchorText = do_QueryInterface(anchor);
|
|
if (anchorText)
|
|
{
|
|
textNodes.AppendElement((void*)(anchorText.get()));
|
|
}
|
|
aInSelection->GetFocusNode(getter_AddRefs(focus));
|
|
focusText = do_QueryInterface(focus);
|
|
|
|
// for all the ranges in the selection, build a list of text nodes
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = aInSelection->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));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!currentItem) return NS_OK; // there are no ranges in the selection, so nothing to do
|
|
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
iter->Init(range);
|
|
nsCOMPtr<nsIContent> content;
|
|
result = iter->CurrentNode(getter_AddRefs(content));
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> text = do_QueryInterface(content);
|
|
if (text && anchorText.get() != text.get() && focusText.get() != text.get())
|
|
{
|
|
textNodes.AppendElement((void*)(text.get()));
|
|
}
|
|
iter->Next();
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
enumerator->Next();
|
|
enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
}
|
|
// now add the focus to the list, if it's a text node
|
|
if (focusText)
|
|
{
|
|
textNodes.AppendElement((void*)(focusText.get()));
|
|
}
|
|
|
|
|
|
// now that I have a list of text nodes, collapse adjacent text nodes
|
|
PRInt32 count = textNodes.Count();
|
|
const PRInt32 initialCount = count;
|
|
while (1<count)
|
|
{
|
|
// get the leftmost 2 elements
|
|
nsIDOMCharacterData *leftTextNode = (nsIDOMCharacterData *)(textNodes.ElementAt(0));
|
|
nsIDOMCharacterData *rightTextNode = (nsIDOMCharacterData *)(textNodes.ElementAt(1));
|
|
|
|
// special case: check prev sibling of the absolute leftmost text node
|
|
if (initialCount==count)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> prevSiblingOfLeftTextNode;
|
|
result = leftTextNode->GetPreviousSibling(getter_AddRefs(prevSiblingOfLeftTextNode));
|
|
if (NS_FAILED(result)) return result;
|
|
if (prevSiblingOfLeftTextNode)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData>prevSiblingAsText = do_QueryInterface(prevSiblingOfLeftTextNode);
|
|
if (prevSiblingAsText)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
result = leftTextNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
result = JoinNodes(prevSiblingOfLeftTextNode, leftTextNode, parent);
|
|
if (NS_FAILED(result)) return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the prev sibling of the right node, and see if it's leftTextNode
|
|
nsCOMPtr<nsIDOMNode> prevSiblingOfRightTextNode;
|
|
result = rightTextNode->GetPreviousSibling(getter_AddRefs(prevSiblingOfRightTextNode));
|
|
if (NS_FAILED(result)) return result;
|
|
if (prevSiblingOfRightTextNode)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData>prevSiblingAsText = do_QueryInterface(prevSiblingOfRightTextNode);
|
|
if (prevSiblingAsText)
|
|
{
|
|
if (prevSiblingAsText.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// special case: check next sibling of the rightmost text node
|
|
if (2==count)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> nextSiblingOfRightTextNode;
|
|
result = rightTextNode->GetNextSibling(getter_AddRefs(nextSiblingOfRightTextNode));
|
|
if (NS_FAILED(result)) return result;
|
|
if (nextSiblingOfRightTextNode)
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData>nextSiblingAsText = do_QueryInterface(nextSiblingOfRightTextNode);
|
|
if (nextSiblingAsText)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
result = rightTextNode->GetParentNode(getter_AddRefs(parent));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!parent) return NS_ERROR_NULL_POINTER;
|
|
result = JoinNodes(rightTextNode, nextSiblingOfRightTextNode, parent);
|
|
if (NS_FAILED(result)) return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the rightmost remaining text node from the array and work our way towards the beginning
|
|
textNodes.RemoveElementAt(0); // remove the leftmost text node from the list
|
|
count --;
|
|
}
|
|
|
|
ResetTextSelectionForRange(parentForSelection, rangeStartOffset, rangeEndOffset, aInSelection);
|
|
|
|
if (gNoisy)
|
|
{
|
|
printf("---------- end nsTextEditor::CollapseAdjacentTextNodes ----------\n");
|
|
DebugDumpContent();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|