pjs/editor/base/nsHTMLEditor.cpp

2749 строки
87 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.0 (the "NPL") you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsTextEditor.h"
#include "nsHTMLEditor.h"
#include "nsHTMLEditRules.h"
#include "nsEditorEventListeners.h"
#include "nsInsertHTMLTxn.h"
#include "nsIDOMText.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
#include "nsIDocument.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMKeyListener.h"
#include "nsIDOMMouseListener.h"
#include "nsIDOMSelection.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLImageElement.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 "nsIPref.h"
#include "nsIDOMDocumentFragment.h"
#include "nsIPresShell.h"
#include "prprf.h"
const unsigned char nbsp = 160;
#ifdef ENABLE_JS_EDITOR_LOG
#include "nsJSEditorLog.h"
#endif // ENABLE_JS_EDITOR_LOG
static NS_DEFINE_CID(kEditorCID, NS_EDITOR_CID);
static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_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);
#ifdef NS_DEBUG
static PRBool gNoisy = PR_FALSE;
#else
static const PRBool gNoisy = PR_FALSE;
#endif
nsHTMLEditor::nsHTMLEditor()
{
// Done in nsEditor
// NS_INIT_REFCNT();
}
nsHTMLEditor::~nsHTMLEditor()
{
//the autopointers will clear themselves up.
}
// Adds appropriate AddRef, Release, and QueryInterface methods for derived class
//NS_IMPL_ISUPPORTS_INHERITED(nsHTMLEditor, nsTextEditor, nsIHTMLEditor)
//NS_IMPL_ADDREF_INHERITED(Class, Super)
NS_IMETHODIMP_(nsrefcnt) nsHTMLEditor::AddRef(void)
{
return nsTextEditor::AddRef();
}
//NS_IMPL_RELEASE_INHERITED(Class, Super)
NS_IMETHODIMP_(nsrefcnt) nsHTMLEditor::Release(void)
{
return nsTextEditor::Release();
}
//NS_IMPL_QUERY_INTERFACE_INHERITED(Class, Super, AdditionalInterface)
NS_IMETHODIMP nsHTMLEditor::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
if (!aInstancePtr) return NS_ERROR_NULL_POINTER;
if (aIID.Equals(nsIHTMLEditor::GetIID())) {
*aInstancePtr = NS_STATIC_CAST(nsIHTMLEditor*, this);
NS_ADDREF_THIS();
return NS_OK;
}
return nsTextEditor::QueryInterface(aIID, aInstancePtr);
}
NS_IMETHODIMP nsHTMLEditor::Init(nsIDOMDocument *aDoc,
nsIPresShell *aPresShell)
{
NS_PRECONDITION(nsnull!=aDoc && nsnull!=aPresShell, "bad arg");
nsresult res=NS_ERROR_NULL_POINTER;
if ((nsnull!=aDoc) && (nsnull!=aPresShell))
{
return nsTextEditor::Init(aDoc, aPresShell);
}
return res;
}
void nsHTMLEditor::InitRules()
{
// instantiate the rules for this text editor
// XXX: we should be told which set of rules to instantiate
mRules = new nsHTMLEditRules();
mRules->Init(this);
}
NS_IMETHODIMP nsHTMLEditor::SetTextProperty(nsIAtom *aProperty,
const nsString *aAttribute,
const nsString *aValue)
{
return nsTextEditor::SetTextProperty(aProperty, aAttribute, aValue);
}
NS_IMETHODIMP nsHTMLEditor::GetTextProperty(nsIAtom *aProperty,
const nsString *aAttribute,
const nsString *aValue,
PRBool &aFirst, PRBool &aAny, PRBool &aAll)
{
return nsTextEditor::GetTextProperty(aProperty, aAttribute, aValue, aFirst, aAny, aAll);
}
NS_IMETHODIMP nsHTMLEditor::RemoveTextProperty(nsIAtom *aProperty, const nsString *aAttribute)
{
return nsTextEditor::RemoveTextProperty(aProperty, aAttribute);
}
NS_IMETHODIMP nsHTMLEditor::DeleteSelection(nsIEditor::ECollapsedSelectionAction aAction)
{
return nsTextEditor::DeleteSelection(aAction);
}
NS_IMETHODIMP nsHTMLEditor::InsertText(const nsString& aStringToInsert)
{
return nsTextEditor::InsertText(aStringToInsert);
}
NS_IMETHODIMP nsHTMLEditor::SetBackgroundColor(const nsString& aColor)
{
// nsresult result;
NS_ASSERTION(mDoc, "Missing Editor DOM Document");
// TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
// For initial testing, just set the background on the BODY tag (the document's background)
// Do this only if setting a table or cell background
// It will be called in nsTextEditor::SetBackgroundColor for the page background
#if 0 //def ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->SetBackgroundColor(aColor);
#endif // ENABLE_JS_EDITOR_LOG
NS_ASSERTION(mDoc, "Missing Editor DOM Document");
// TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
// For initial testing, just set the background on the BODY tag (the document's background)
return nsTextEditor::SetBackgroundColor(aColor);
}
NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsString& aAttribute, const nsString& aValue)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->SetBodyAttribute(aAttribute, aValue);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res;
// TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
NS_ASSERTION(mDoc, "Missing Editor DOM Document");
// Set the background color attribute on the body tag
nsCOMPtr<nsIDOMElement> bodyElement;
res = nsEditor::GetBodyElement(getter_AddRefs(bodyElement));
if (NS_SUCCEEDED(res) && bodyElement)
{
// Use the editor's method which goes through the transaction system
SetAttribute(bodyElement, aAttribute, aValue);
}
return res;
}
NS_IMETHODIMP nsHTMLEditor::InsertBreak()
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertBreak();
#endif // ENABLE_JS_EDITOR_LOG
nsresult res;
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
nsCOMPtr<nsIDOMSelection> selection;
PRBool cancel= PR_FALSE;
nsAutoEditBatch beginBatching(this);
// pre-process
nsEditor::GetSelection(getter_AddRefs(selection));
nsTextRulesInfo ruleInfo(nsHTMLEditRules::kInsertBreak);
res = mRules->WillDoAction(selection, &ruleInfo, &cancel);
if ((PR_FALSE==cancel) && (NS_SUCCEEDED(res)))
{
// create the new BR node
nsCOMPtr<nsIDOMNode> newNode;
nsAutoString tag("BR");
res = nsEditor::DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
if (NS_SUCCEEDED(res) && newNode)
{
// set the selection to the new node
nsCOMPtr<nsIDOMNode>parent;
res = newNode->GetParentNode(getter_AddRefs(parent));
if (NS_SUCCEEDED(res) && parent)
{
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 = nsEditor::GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(res))
{
if (-1==offsetInParent)
{
nextNode->GetParentNode(getter_AddRefs(parent));
res = GetChildOffset(nextNode, parent, offsetInParent);
if (NS_SUCCEEDED(res)) {
selection->Collapse(parent, offsetInParent+1, SELECTION_NORMAL); // +1 to insert just after the break
}
}
else
{
selection->Collapse(nextNode, offsetInParent, SELECTION_NORMAL);
}
}
}
}
// post-process, always called if WillInsertBreak didn't return cancel==PR_TRUE
res = mRules->DidDoAction(selection, &ruleInfo, res);
}
// XXXX: Horrible hack! We are doing this because
// of an error in Gecko which is not rendering the
// document after a change via the DOM - gpk 2/13/99
// BEGIN HACK!!!
// HACKForceRedraw();
// END HACK
return res;
}
NS_IMETHODIMP nsHTMLEditor::GetParagraphFormat(nsString& aParagraphFormat)
{
nsresult res = NS_ERROR_NOT_INITIALIZED;
return res;
}
NS_IMETHODIMP nsHTMLEditor::SetParagraphFormat(const nsString& aParagraphFormat)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->SetParagraphFormat(aParagraphFormat);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res = NS_ERROR_NOT_INITIALIZED;
//Kinda sad to waste memory just to force lower case
nsAutoString tag = aParagraphFormat;
tag.ToLowerCase();
if (tag == "normal" || tag == "p") {
res = RemoveParagraphStyle();
} else if (tag == "li") {
res = InsertList("ul");
} else {
res = ReplaceBlockParent(tag);
}
return res;
}
// Methods shared with the base editor.
// Note: We could call each of these via nsTextEditor -- is that better?
NS_IMETHODIMP nsHTMLEditor::EnableUndo(PRBool aEnable)
{
return nsTextEditor::EnableUndo(aEnable);
}
NS_IMETHODIMP nsHTMLEditor::Undo(PRUint32 aCount)
{
return nsTextEditor::Undo(aCount);
}
NS_IMETHODIMP nsHTMLEditor::CanUndo(PRBool &aIsEnabled, PRBool &aCanUndo)
{
return nsTextEditor::CanUndo(aIsEnabled, aCanUndo);
}
NS_IMETHODIMP nsHTMLEditor::Redo(PRUint32 aCount)
{
return nsTextEditor::Redo(aCount);
}
NS_IMETHODIMP nsHTMLEditor::CanRedo(PRBool &aIsEnabled, PRBool &aCanRedo)
{
return nsTextEditor::CanRedo(aIsEnabled, aCanRedo);
}
NS_IMETHODIMP nsHTMLEditor::BeginTransaction()
{
return nsTextEditor::BeginTransaction();
}
NS_IMETHODIMP nsHTMLEditor::EndTransaction()
{
return nsTextEditor::EndTransaction();
}
NS_IMETHODIMP nsHTMLEditor::MoveSelectionUp(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::MoveSelectionUp(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::MoveSelectionDown(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::MoveSelectionDown(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::MoveSelectionNext(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::MoveSelectionNext(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::MoveSelectionPrevious(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::MoveSelectionPrevious(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::SelectNext(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::SelectNext(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::SelectPrevious(nsIAtom *aIncrement, PRBool aExtendSelection)
{
return nsTextEditor::SelectPrevious(aIncrement, aExtendSelection);
}
NS_IMETHODIMP nsHTMLEditor::SelectAll()
{
return nsTextEditor::SelectAll();
}
NS_IMETHODIMP nsHTMLEditor::BeginningOfDocument()
{
return nsEditor::BeginningOfDocument();
}
NS_IMETHODIMP nsHTMLEditor::EndOfDocument()
{
return nsEditor::EndOfDocument();
}
NS_IMETHODIMP nsHTMLEditor::ScrollUp(nsIAtom *aIncrement)
{
return nsTextEditor::ScrollUp(aIncrement);
}
NS_IMETHODIMP nsHTMLEditor::ScrollDown(nsIAtom *aIncrement)
{
return nsTextEditor::ScrollDown(aIncrement);
}
NS_IMETHODIMP nsHTMLEditor::ScrollIntoView(PRBool aScrollToBegin)
{
return nsTextEditor::ScrollIntoView(aScrollToBegin);
}
NS_IMETHODIMP nsHTMLEditor::Save()
{
return nsTextEditor::Save();
}
NS_IMETHODIMP nsHTMLEditor::SaveAs(PRBool aSavingCopy)
{
return nsTextEditor::SaveAs(aSavingCopy);
}
NS_IMETHODIMP nsHTMLEditor::Cut()
{
return nsTextEditor::Cut();
}
NS_IMETHODIMP nsHTMLEditor::Copy()
{
return nsTextEditor::Copy();
}
NS_IMETHODIMP nsHTMLEditor::Paste()
{
return nsTextEditor::Paste();
}
//
// HTML PasteAsQuotation: Paste in a blockquote type=cite
//
NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation()
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->PasteAsQuotation();
#endif // ENABLE_JS_EDITOR_LOG
nsAutoString citation("");
return PasteAsCitedQuotation(citation);
}
NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsString& aCitation)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->PasteAsCitedQuotation(aCitation);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIDOMNode> newNode;
nsAutoString tag("blockquote");
nsresult res = nsEditor::DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
if (NS_FAILED(res) || !newNode)
return res;
// 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:
nsCOMPtr<nsIDOMSelection> selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
{
#ifdef DEBUG_akkana
printf("Can't get selection!");
#endif
}
res = selection->Collapse(newNode, 0, SELECTION_NORMAL);
if (NS_FAILED(res))
{
#ifdef DEBUG_akkana
printf("Couldn't collapse");
#endif
}
res = Paste();
return res;
}
NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsString& aQuotedText)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertAsQuotation(aQuotedText);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoString citation ("");
return InsertAsCitedQuotation(aQuotedText, citation);
}
NS_IMETHODIMP nsHTMLEditor::InsertAsCitedQuotation(const nsString& aQuotedText,
const nsString& aCitation)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertAsCitedQuotation(aQuotedText, aCitation);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIDOMNode> newNode;
nsAutoString tag("blockquote");
nsresult res = nsEditor::DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
if (NS_FAILED(res) || !newNode)
return res;
// 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);
}
res = InsertHTML(aQuotedText);
return res;
}
NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsString& aInputString)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertHTML(aInputString);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoEditBatch beginBatching(this);
nsresult res;
nsCOMPtr<nsIDOMNode> parentNode;
PRInt32 offsetOfNewNode;
res = DeleteSelectionAndPrepareToCreateNode(parentNode, offsetOfNewNode);
nsCOMPtr<nsIDOMSelection>selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res))
return res;
// Get the first range in the selection, for context:
nsCOMPtr<nsIDOMRange> range;
res = selection->GetRangeAt(0, SELECTION_NORMAL, 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)
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));
#define INSERT_FRAGMENT_DIRECTLY 1
#ifdef INSERT_FRAGMENT_DIRECTLY
// Make a collapsed range pointing to right after the current selection,
// and let range gravity keep track of where it is so that we can
// set the selection back there after the insert.
// Unfortunately this doesn't work right yet.
nsCOMPtr<nsIDOMRange> saverange;
res = selection->GetRangeAt(0, SELECTION_NORMAL, getter_AddRefs(saverange));
// Insert the node:
res = InsertNode(fragmentAsNode, parentNode, offsetOfNewNode);
// Now collapse the selection to the beginning of what we just inserted;
// would be better to set it to the end.
if (saverange)
{
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset;
if (NS_SUCCEEDED(saverange->GetEndParent(getter_AddRefs(parent))))
if (NS_SUCCEEDED(saverange->GetEndOffset(&offset)))
selection->Collapse(parent, offset, SELECTION_NORMAL);
}
else
selection->Collapse(parentNode, 0/*offsetOfNewNode*/, SELECTION_NORMAL);
#else /* INSERT_FRAGMENT_DIRECTLY */
// 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)
{
res = InsertNode(child, parentNode, offsetOfNewNode++);
if (NS_FAILED(res))
break;
nsCOMPtr<nsIDOMNode> nextSib;
if (NS_FAILED(child->GetNextSibling(getter_AddRefs(nextSib))))
/*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);
#endif /* INSERT_FRAGMENT_DIRECTLY */
return res;
}
NS_IMETHODIMP nsHTMLEditor::OutputToString(nsString& aOutputString,
const nsString& aFormatType,
PRUint32 aFlags)
{
return nsTextEditor::OutputToString(aOutputString, aFormatType, aFlags);
}
NS_IMETHODIMP nsHTMLEditor::OutputToStream(nsIOutputStream* aOutputStream,
const nsString& aFormatType,
const nsString* aCharset,
PRUint32 aFlags)
{
return nsTextEditor::OutputToStream(aOutputStream, aFormatType,
aCharset, aFlags);
}
NS_IMETHODIMP
nsHTMLEditor::CopyAttributes(nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode)
{
return nsTextEditor::CopyAttributes(aDestNode, aSourceNode);
}
NS_IMETHODIMP
nsHTMLEditor::ApplyStyleSheet(const nsString& aURL)
{
return nsTextEditor::ApplyStyleSheet(aURL);
}
//================================================================
// HTML Editor methods
//
// Note: Table Editing methods are implemented in EditTable.cpp
//
// get the paragraph style(s) for the selection
NS_IMETHODIMP
nsHTMLEditor::GetParagraphStyle(nsStringArray *aTagList)
{
if (gNoisy) { printf("---------- nsHTMLEditor::GetParagraphStyle ----------\n"); }
if (!aTagList) { return NS_ERROR_NULL_POINTER; }
nsresult res;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if ((NS_SUCCEEDED(res)) && selection)
{
nsCOMPtr<nsIEnumerator> enumerator;
res = selection->GetEnumerator(SELECTION_NORMAL, getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res) && enumerator)
{
enumerator->First();
nsISupports *currentItem;
res = enumerator->CurrentItem(&currentItem);
if ((NS_SUCCEEDED(res)) && (nsnull!=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_SUCCEEDED(res)) && blockSections)
{
res = GetBlockSectionsForRange(range, blockSections);
if (NS_SUCCEEDED(res))
{
nsIDOMRange *subRange;
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
while (subRange && (NS_SUCCEEDED(res)))
{
nsCOMPtr<nsIDOMNode>startParent;
res = subRange->GetStartParent(getter_AddRefs(startParent));
if (NS_SUCCEEDED(res) && startParent)
{
nsCOMPtr<nsIDOMElement> blockParent;
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);
blockSections->RemoveElementAt(0);
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
}
}
NS_RELEASE(blockSections);
}
}
}
}
return res;
}
// use this when block parents are to be added regardless of current state
NS_IMETHODIMP
nsHTMLEditor::AddBlockParent(nsString& aParentTag)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->AddBlockParent(aParentTag);
#endif // ENABLE_JS_EDITOR_LOG
if (gNoisy)
{
char *tag = aParentTag.ToNewCString();
printf("---------- nsHTMLEditor::AddBlockParent %s ----------\n", tag);
delete [] tag;
}
nsresult res=NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if ((NS_SUCCEEDED(res)) && selection)
{
// 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();
nsISupports *currentItem;
res = enumerator->CurrentItem(&currentItem);
if ((NS_SUCCEEDED(res)) && (nsnull!=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)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->ReplaceBlockParent(aParentTag);
#endif // ENABLE_JS_EDITOR_LOG
if (gNoisy)
{
char *tag = aParentTag.ToNewCString();
printf("---------- nsHTMLEditor::ReplaceBlockParent %s ----------\n", tag);
delete [] tag;
}
nsresult res=NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if ((NS_SUCCEEDED(res)) && selection)
{
nsAutoSelectionReset selectionResetter(selection);
// set the block parent for all selected ranges
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIEnumerator> enumerator;
res = selection->GetEnumerator(SELECTION_NORMAL, getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res) && enumerator)
{
enumerator->First();
nsISupports *currentItem;
res = enumerator->CurrentItem(&currentItem);
if ((NS_SUCCEEDED(res)) && (nsnull!=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::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);
delete [] 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;
nsTextEditor::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 = nsTextEditor::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 = nsTextEditor::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));
nsCOMPtr<nsIDOMNode>previousAncestor = do_QueryInterface(aNode);
while (NS_SUCCEEDED(res) && 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));
}
// now, previousAncestor is the node we are operating on
nsCOMPtr<nsIDOMNode>leftNode, rightNode;
res = GetBlockSection(previousAncestor,
getter_AddRefs(leftNode),
getter_AddRefs(rightNode));
if ((NS_SUCCEEDED(res)) && leftNode && rightNode)
{
// 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 = nsTextEditor::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_SUCCEEDED(res)) && grandParent)
{
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 = nsTextEditor::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 = nsTextEditor::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 = nsTextEditor::DeleteNode(childNode);
if (NS_SUCCEEDED(res)) {
res = nsTextEditor::InsertNode(childNode, grandParent, offsetInParent);
}
}
}
if (gNoisy) { printf("removing the old block parent %p\n", blockParentNode.get()); }
res = nsTextEditor::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_SUCCEEDED(res)) && blockSections)
{
res = GetBlockSectionsForRange(aRange, blockSections);
if (NS_SUCCEEDED(res))
{
nsIDOMRange *subRange;
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
while (subRange && (NS_SUCCEEDED(res)))
{
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);
blockSections->RemoveElementAt(0);
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
}
}
NS_RELEASE(blockSections);
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::RemoveParagraphStyle()
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->RemoveParagraphStyle();
#endif // ENABLE_JS_EDITOR_LOG
if (gNoisy) {
printf("---------- nsHTMLEditor::RemoveParagraphStyle ----------\n");
}
nsresult res=NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if ((NS_SUCCEEDED(res)) && selection)
{
nsAutoSelectionReset selectionResetter(selection);
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIEnumerator> enumerator;
res = selection->GetEnumerator(SELECTION_NORMAL, getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res) && enumerator)
{
enumerator->First();
nsISupports *currentItem;
res = enumerator->CurrentItem(&currentItem);
if ((NS_SUCCEEDED(res)) && (nsnull!=currentItem))
{
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
res = RemoveParagraphStyleFromRange(range);
}
}
}
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_SUCCEEDED(res)) && blockSections)
{
res = GetBlockSectionsForRange(aRange, blockSections);
if (NS_SUCCEEDED(res))
{
nsIDOMRange *subRange;
subRange = (nsIDOMRange *)(blockSections->ElementAt(0));
while (subRange && (NS_SUCCEEDED(res)))
{
res = RemoveParagraphStyleFromBlockContent(subRange);
NS_RELEASE(subRange);
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 = nsTextEditor::GetBlockParent(startParent, getter_AddRefs(blockParentElement));
while ((NS_SUCCEEDED(res)) && 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));
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 = nsTextEditor::DeleteNode(childNode);
if (NS_SUCCEEDED(res)) {
res = nsTextEditor::InsertNode(childNode, grandParent, offsetInParent);
}
}
}
if (NS_SUCCEEDED(res)) {
res = nsTextEditor::DeleteNode(blockParentElement);
}
}
}
res = nsTextEditor::GetBlockParent(startParent, getter_AddRefs(blockParentElement));
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::RemoveParent(const nsString &aParentTag)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->RemoveParent(aParentTag);
#endif // ENABLE_JS_EDITOR_LOG
if (gNoisy) {
printf("---------- nsHTMLEditor::RemoveParent ----------\n");
}
nsresult res=NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if ((NS_SUCCEEDED(res)) && selection)
{
nsAutoSelectionReset selectionResetter(selection);
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIEnumerator> enumerator;
res = selection->GetEnumerator(SELECTION_NORMAL, getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res) && enumerator)
{
enumerator->First();
nsISupports *currentItem;
res = enumerator->CurrentItem(&currentItem);
if ((NS_SUCCEEDED(res)) && (nsnull!=currentItem))
{
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
res = RemoveParentFromRange(aParentTag, range);
}
}
}
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_SUCCEEDED(res)) && blockSections)
{
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);
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_SUCCEEDED(res)) && startParent)
{
nsCOMPtr<nsIDOMNode>parentNode;
nsCOMPtr<nsIDOMElement>parentElement;
res = startParent->GetParentNode(getter_AddRefs(parentNode));
while ((NS_SUCCEEDED(res)) && 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_SUCCEEDED(res)) && (childNodes))
{
nsCOMPtr<nsIDOMNode>grandParent;
parentElement->GetParentNode(getter_AddRefs(grandParent));
PRInt32 offsetInParent;
res = GetChildOffset(parentElement, 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 = nsTextEditor::DeleteNode(childNode);
if (NS_SUCCEEDED(res)) {
res = nsTextEditor::InsertNode(childNode, grandParent, offsetInParent);
}
}
}
if (NS_SUCCEEDED(res)) {
res = nsTextEditor::DeleteNode(parentElement);
}
}
break;
}
else if (PR_TRUE==isRoot) { // hit a local root node, terminate loop
break;
}
res = parentElement->GetParentNode(getter_AddRefs(parentNode));
}
}
return res;
}
// TODO: Implement "outdent"
NS_IMETHODIMP
nsHTMLEditor::Indent(const nsString& aIndent)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->Indent(aIndent);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res;
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
PRBool cancel= PR_FALSE;
nsAutoEditBatch beginBatching(this);
// pre-process
nsCOMPtr<nsIDOMSelection> selection;
nsEditor::GetSelection(getter_AddRefs(selection));
nsTextRulesInfo ruleInfo(nsHTMLEditRules::kIndent);
if (aIndent == "outdent")
ruleInfo.action = nsHTMLEditRules::kOutdent;
res = mRules->WillDoAction(selection, &ruleInfo, &cancel);
if (cancel || (NS_FAILED(res))) return res;
// Do default - insert a blockquote node if selection collapsed
nsCOMPtr<nsIDOMNode> node;
PRInt32 offset;
PRBool isCollapsed;
res = selection->GetIsCollapsed(SELECTION_NORMAL, &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);
if (NS_FAILED(res)) return res;
// topChild already went to the right on the split
// so we don't need to add one to offset when figuring
// out where to plop list
offset = GetIndexOf(parent,topChild);
}
// 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, SELECTION_NORMAL);
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, SELECTION_NORMAL);
if (NS_FAILED(res)) return res;
}
}
return res;
}
//TODO: IMPLEMENT ALIGNMENT!
NS_IMETHODIMP
nsHTMLEditor::Align(const nsString& aAlignType)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->Align(aAlignType);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIDOMNode> node;
PRBool cancel= PR_FALSE;
// Find out if the selection is collapsed:
nsCOMPtr<nsIDOMSelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection) return res;
nsTextRulesInfo ruleInfo(nsHTMLEditRules::kAlign);
ruleInfo.alignType = &aAlignType;
res = mRules->WillDoAction(selection, &ruleInfo, &cancel);
return res;
}
NS_IMETHODIMP
nsHTMLEditor::InsertList(const nsString& aListType)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertList(aListType);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res;
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
nsCOMPtr<nsIDOMSelection> selection;
PRBool cancel= PR_FALSE;
nsAutoEditBatch beginBatching(this);
// pre-process
nsEditor::GetSelection(getter_AddRefs(selection));
nsTextRulesInfo ruleInfo(nsHTMLEditRules::kMakeList);
if (aListType == "ol") ruleInfo.bOrdered = PR_TRUE;
else ruleInfo.bOrdered = PR_FALSE;
res = mRules->WillDoAction(selection, &ruleInfo, &cancel);
if (cancel || (NS_FAILED(res))) return res;
// Find out if the selection is collapsed:
if (NS_FAILED(res) || !selection) return res;
PRBool isCollapsed;
res = selection->GetIsCollapsed(SELECTION_NORMAL, &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);
if (NS_FAILED(res)) return res;
// topChild already went to the right on the split
// so we don't need to add one to offset when figuring
// out where to plop list
offset = GetIndexOf(parent,topChild);
}
// 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, SELECTION_NORMAL);
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, SELECTION_NORMAL);
if (NS_FAILED(res)) return res;
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::InsertLink(nsString& aURL)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertLink(aURL);
#endif // ENABLE_JS_EDITOR_LOG
nsAutoEditBatch beginBatching(this);
// Find out if the selection is collapsed:
nsCOMPtr<nsIDOMSelection> selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
{
#ifdef DEBUG_akkana
printf("Can't get selection!");
#endif
return res;
}
PRBool isCollapsed;
res = selection->GetIsCollapsed(SELECTION_NORMAL, &isCollapsed);
if (NS_FAILED(res))
isCollapsed = PR_TRUE;
// Temporary: we need to save the contents of the selection,
// then insert them back in as the child of the newly created
// anchor node in order to put the link around the selection.
// This will require copying the selection into a document fragment,
// then splicing the document fragment back into the tree after the
// new anchor node has been put in place. As a temporary solution,
// Copy/Paste does this for us in the text case
// (and eventually in all cases).
if (!isCollapsed)
(void)Copy();
nsCOMPtr<nsIDOMNode> newNode;
nsAutoString tag("A");
res = nsEditor::DeleteSelectionAndCreateNode(tag, getter_AddRefs(newNode));
if (NS_FAILED(res) || !newNode)
return res;
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor (do_QueryInterface(newNode));
if (!anchor)
{
#ifdef DEBUG_akkana
printf("Not an anchor element\n");
#endif
return NS_NOINTERFACE;
}
res = anchor->SetHref(aURL);
if (NS_FAILED(res))
{
#ifdef DEBUG_akkana
printf("SetHref failed");
#endif
return res;
}
// Set the selection to the node we just inserted:
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
{
#ifdef DEBUG_akkana
printf("Can't get selection!");
#endif
return res;
}
res = selection->Collapse(newNode, 0, SELECTION_NORMAL);
if (NS_FAILED(res))
{
#ifdef DEBUG_akkana
printf("Couldn't collapse");
#endif
return res;
}
// If we weren't collapsed, paste the old selection back in under the link:
if (!isCollapsed)
(void)Paste();
// Otherwise (we were collapsed) insert some bogus text in
// so the link will be visible
else
{
nsString link("[***]");
(void) InsertText(link); // ignore return value -- we don't care
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::InsertImage(nsString& aURL,
nsString& aWidth, nsString& aHeight,
nsString& aHspace, nsString& aVspace,
nsString& aBorder,
nsString& aAlt,
nsString& aAlignment)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertImage(aURL, aWidth, aHeight, aHspace, aVspace, aBorder, aAlt, aAlignment);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res;
nsCOMPtr<nsIDOMNode> newNode;
nsCOMPtr<nsIDOMDocument>doc;
res = GetDocument(getter_AddRefs(doc));
if (NS_SUCCEEDED(res))
{
nsAutoString tag("IMG");
nsCOMPtr<nsIDOMElement>newElement;
res = doc->CreateElement(tag, getter_AddRefs(newElement));
if (NS_SUCCEEDED(res) && newElement)
{
newNode = do_QueryInterface(newElement);
nsCOMPtr<nsIDOMHTMLImageElement> image (do_QueryInterface(newNode));
// Set all the attributes now, before we insert into the tree:
if (image)
{
if (NS_SUCCEEDED(res = image->SetSrc(aURL)))
if (NS_SUCCEEDED(res = image->SetWidth(aWidth)))
if (NS_SUCCEEDED(res = image->SetHeight(aHeight)))
if (NS_SUCCEEDED(res = image->SetAlt(aAlt)))
if (NS_SUCCEEDED(res = image->SetBorder(aBorder)))
if (NS_SUCCEEDED(res = image->SetAlign(aAlignment)))
if (NS_SUCCEEDED(res = image->SetHspace(aHspace)))
if (NS_SUCCEEDED(res = image->SetVspace(aVspace)))
;
}
}
}
// If any of these failed, then don't insert the new node into the tree
if (NS_FAILED(res))
{
#ifdef DEBUG_akkana
printf("Some failure creating the new image node\n");
#endif
return res;
}
//
// Now we're ready to insert the new image node:
// Starting now, don't return without ending the transaction!
//
nsAutoEditBatch beginBatching(this);
nsCOMPtr<nsIDOMNode> parentNode;
PRInt32 offsetOfNewNode;
res = DeleteSelectionAndPrepareToCreateNode(parentNode,
offsetOfNewNode);
if (NS_SUCCEEDED(res))
{
// and insert it into the right place in the tree:
res = InsertNode(newNode, parentNode, offsetOfNewNode);
}
return res;
}
// This should replace InsertLink and InsertImage once it is working
NS_IMETHODIMP
nsHTMLEditor::GetSelectedElement(const nsString& aTagName, nsIDOMElement** aReturn)
{
if (!aReturn )
return NS_ERROR_NULL_POINTER;
*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 = nsEditor::GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
return res;
PRBool isCollapsed;
selection->GetIsCollapsed(SELECTION_NORMAL, &isCollapsed);
nsCOMPtr<nsIDOMElement> selectedElement;
PRBool bNodeFound = PR_FALSE;
// Don't bother to examine selection if it is collapsed
if (!isCollapsed)
{
nsCOMPtr<nsIEnumerator> enumerator;
res = selection->GetEnumerator(SELECTION_NORMAL,getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res) && enumerator)
{
enumerator->First();
nsISupports *currentItem;
res = enumerator->CurrentItem(&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));
if ((NS_SUCCEEDED(res)) && iter)
{
iter->Init(range);
// loop through the content iterator for each content node
nsCOMPtr<nsIContent> content;
res = iter->CurrentNode(getter_AddRefs(content));
//PRBool bOtherNodeTypeFound = PR_FALSE;
while (NS_COMFALSE == iter->IsDone())
{
// 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"
if (TagName == "href" || (TagName == "anchor"))
{
// We could use GetAttribute, but might as well use anchor element directly
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(selectedElement);
if (anchor)
{
nsString tmpText;
if( TagName == "href")
{
if (NS_SUCCEEDED(anchor->GetHref(tmpText)) && tmpText.GetUnicode() && tmpText.Length() != 0)
bNodeFound = PR_TRUE;
} else if (TagName == "anchor")
{
if (NS_SUCCEEDED(anchor->GetName(tmpText)) && tmpText.GetUnicode() && tmpText.Length() != 0)
bNodeFound = PR_TRUE;
}
#if 0
// Not sure if this kind of logic should be here or in JavaScript
} else if (TagName == "href")
{
// Check for a single image is inside a link
// It is usually the immediate parent, but lets be sure
// by walking up the parents until we find an "A" tag
nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(selectedElement);
if (image)
{
nsCOMPtr<nsIDOMNode> parent;
nsCOMPtr<nsIDOMNode> current = do_QueryInterface(selectedElement);
PRBool notDone = PR_TRUE;
do {
res = current->GetParentNode(getter_AddRefs(parent));
notDone = NS_SUCCEEDED(res) && parent != nsnull;
if(notDone)
{
nsString tmpText;
/*nsCOMPtr<nsIDOMHTMLAnchorElement>*/
anchor = do_QueryInterface(parent);
if (anchor && NS_SUCCEEDED(anchor->GetHref(tmpText)) && tmpText.GetUnicode() && tmpText.Length() != 0)
{
nsCOMPtr<nsIDOMElement> link = do_QueryInterface(parent);
if (link)
{
*aReturn =link;
// Getters must addref
NS_ADDREF(*aReturn);
}
return NS_OK;
}
}
current = parent;
} while (notDone);
}
#endif
}
} 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;
// 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;
PRBool isHREF = (TagName == "href");
PRBool isAnchor = (TagName == "anchor");
if (isHREF || isAnchor)
{
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;
res = mDoc->CreateElement(realTagName, getter_AddRefs(newElement));
if (NS_FAILED(res) || !newElement)
return NS_ERROR_FAILURE;
// Set default values for new elements
if (TagName.Equals("hr"))
{
// TODO: Get the text of the selection and build a suggested Name
// Replace spaces with "_"
} else if (TagName.Equals("hr"))
{
// Hard coded defaults in case there's no prefs
nsAutoString align("center");
nsAutoString width("100%");
nsAutoString height("2");
PRBool bNoShade = PR_FALSE;
if (mPrefs)
{
char buf[16];
PRInt32 iAlign;
// Currently using 0=left, 1=center, and 2=right
if( NS_SUCCEEDED(mPrefs->GetIntPref("editor.hrule.align", &iAlign)))
{
switch (iAlign) {
case 0:
align = "left";
break;
case 2:
align = "right";
break;
}
}
PRInt32 iHeight;
PRUint32 count;
if( NS_SUCCEEDED(mPrefs->GetIntPref("editor.hrule.height", &iHeight)))
{
count = PR_snprintf(buf, 16, "%d", iHeight);
if (count > 0)
{
height = buf;
}
}
PRInt32 iWidth;
PRBool bPercent;
if( NS_SUCCEEDED(mPrefs->GetIntPref("editor.hrule.width", &iWidth)) &&
NS_SUCCEEDED(mPrefs->GetBoolPref("editor.hrule.width_percent", &bPercent)))
{
count = PR_snprintf(buf, 16, "%d", iWidth);
if (count > 0)
{
width = buf;
if (bPercent)
width.Append("%");
}
}
PRBool bShading;
if (NS_SUCCEEDED(mPrefs->GetBoolPref("editor.hrule.shading", &bShading)))
{
bNoShade = !bShading;
}
}
newElement->SetAttribute("align", align);
newElement->SetAttribute("height", height);
newElement->SetAttribute("width", width);
if (bNoShade)
newElement->SetAttribute("noshade", "");
} else if (TagName.Equals("table"))
{
newElement->SetAttribute("cellpadding","2");
newElement->SetAttribute("cellspacing","2");
newElement->SetAttribute("width","50%");
newElement->SetAttribute("border","1");
} else if (TagName.Equals("tr"))
{
newElement->SetAttribute("valign","top");
} else if (TagName.Equals("td"))
{
newElement->SetAttribute("valign","top");
// 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;
nsString space;
// Set contents to the &nbsp 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 = mDoc->CreateTextNode(space, getter_AddRefs(newTextNode));
if (NS_SUCCEEDED(result) && newTextNode)
{
nsCOMPtr<nsIDOMNode>resultNode;
result = newCellNode->AppendChild(newTextNode, getter_AddRefs(resultNode));
}
}
}
// ADD OTHER DEFAULT ATTRIBUTES HERE
if (NS_SUCCEEDED(res))
{
*aReturn = newElement;
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::CanContainElement(nsIDOMNode* aParent, nsIDOMElement* aElement)
{
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::InsertElement(nsIDOMElement* aElement, PRBool aDeleteSelection)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertElement(aElement, aDeleteSelection);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res = NS_ERROR_NOT_INITIALIZED;
if (!aElement)
return NS_ERROR_NULL_POINTER;
nsAutoEditBatch beginBatching(this);
// For most elements, set caret after inserting
PRBool setCaretAfterElement = PR_TRUE;
nsCOMPtr<nsIDOMNode> parentSelectedNode;
PRInt32 splitPointOffset;
nsCOMPtr<nsIDOMSelection>selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if (!NS_SUCCEEDED(res) || !selection)
return NS_ERROR_FAILURE;
// Clear current selection.
// Should put caret at anchor point?
if (!aDeleteSelection)
{
PRBool collapseAfter = PR_TRUE;
// Named Anchor is a special case,
// We collapse to insert element BEFORE the selection
// For all other tags, we insert AFTER the selection
nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aElement);
if (anchor)
{
nsAutoString name;
if (NS_SUCCEEDED(anchor->GetName(name)) && name.GetUnicode() && name.Length() != 0)
collapseAfter = PR_FALSE;
}
if (collapseAfter)
{
// Default behavior is to collapse to the end of the selection
selection->ClearSelection(SELECTION_NORMAL);
} else {
// Collapse to the start of the selection,
// We must explore the first range and find
// its parent and starting offset of selection
// TODO: Move this logic to a new method nsIDOMSelection::CollapseToStart()???
nsCOMPtr<nsIDOMRange> firstRange;
res = selection->GetRangeAt(0, SELECTION_NORMAL, getter_AddRefs(firstRange));
if (NS_SUCCEEDED(res) && firstRange)
{
nsCOMPtr<nsIDOMNode> parent;
res = firstRange->GetCommonParent(getter_AddRefs(parent));
if (NS_SUCCEEDED(res) && parent)
{
PRInt32 startOffset;
firstRange->GetStartOffset(&startOffset);
selection->Collapse(parent, startOffset, SELECTION_NORMAL);
} else {
// Very unlikely, but collapse to the end if we failed above
selection->ClearSelection(SELECTION_NORMAL);
}
}
}
}
res = DeleteSelectionAndPrepareToCreateNode(parentSelectedNode, splitPointOffset);
if (NS_SUCCEEDED(res))
{
nsCOMPtr<nsIDOMNode> newNode = do_QueryInterface(aElement);
PRBool isInline;
res = IsNodeInline(newNode, isInline);
if( NS_SUCCEEDED(res) && isInline)
{
// The simple case of an inline node
// This will split any inline nodes at the caret
// and insert between them
res = InsertNode(aElement, parentSelectedNode, splitPointOffset);
} else {
// Inserting a BLOCK element
// Get the first block parent of the paragraph/container
// which we can split to create insert location
// (Current parent may already be a suitable block to split)
nsCOMPtr<nsIDOMNode> parentNodeOfInsert = parentSelectedNode;
nsCOMPtr<nsIDOMNode> topNodeToSplit = parentSelectedNode;
nsCOMPtr<nsIDOMElement> bodyElement;
// We need to find the offset at each level we will split
// to use when we insert the new element
PRInt32 offsetToInsertAt = splitPointOffset;
res = nsEditor::GetBodyElement(getter_AddRefs(bodyElement));
if (NS_SUCCEEDED(res) && bodyElement &&
// If text node is direct child of body, we can't insert a block node
(parentSelectedNode != bodyElement))
{
nsCOMPtr<nsIDOMNode> bodyNode = do_QueryInterface(bodyElement);
PRBool isInline = PR_TRUE;
while (isInline)
{
// Get parent of the top node to split
res = topNodeToSplit->GetParentNode(getter_AddRefs(parentNodeOfInsert));
// If Inline, we loop around to get the next parent level
if (NS_SUCCEEDED(res) && parentNodeOfInsert &&
NS_SUCCEEDED(IsNodeInline(topNodeToSplit, isInline)) &&
NS_SUCCEEDED(GetChildOffset(topNodeToSplit, parentNodeOfInsert, offsetToInsertAt)))
{
#ifdef DEBUG_cmanske
nsAutoString nodeName;
topNodeToSplit->GetNodeName(nodeName);
printf("Top Node to split: ");
wprintf(nodeName.GetUnicode());
parentNodeOfInsert->GetNodeName(nodeName);
printf(" Parent of this node: ");
wprintf(nodeName.GetUnicode());
printf("\n");
#endif
// The new offset to insert at is just after the topmost node we will split
offsetToInsertAt++;
} else {
// Major failure if we get here
return NS_ERROR_FAILURE;
}
}
if (bodyNode != topNodeToSplit)
{
// We have the node to split and its parent.
// TODO: Implement "CanContainElement" to be sure
// we are allowed to insert aElement under parentNodeOfInsert
// If we can't we should call special methods for various combintations
// if (!CanContain(aElement, parentNodeOfInsert)
// return NS_ERROR_FAILURE;
// Split nodes from the selection parent ("bottom node") and all intervening parents
// up to the topmost node, which may be = to bottom node)
// (This redistribute children as each level is split.)
res = SplitNodeDeep(topNodeToSplit, parentSelectedNode, splitPointOffset);
if (NS_SUCCEEDED(res))
{
//Insert the block element between the 2 "topmost" containers
res = InsertNode(aElement, parentNodeOfInsert, offsetToInsertAt);
// Check for special case of inserting a table
PRBool caretIsSet;
res = SetCaretInTableCell(aElement, &caretIsSet);
if (NS_SUCCEEDED(res) && caretIsSet)
setCaretAfterElement = PR_FALSE;
}
} else {
// If here, we must have an inline node directly under the body
// so we can't insert a block tag
// TODO: Take each inline node resulting from DeleteSelectionAndPrepareToCreateNode
// and insert them each into a default paragraph.
// Then insert new block node between them.
return NS_ERROR_FAILURE;
}
}
}
}
if (setCaretAfterElement && NS_SUCCEEDED(res))
SetCaretAfterElement(aElement);
return res;
}
NS_IMETHODIMP
nsHTMLEditor::SaveHLineSettings(nsIDOMElement* aElement)
{
nsresult res=NS_ERROR_NOT_INITIALIZED;
if (!aElement || !mPrefs)
return res;
nsAutoString align, width, height, noshade;
res = NS_ERROR_UNEXPECTED;
if (NS_SUCCEEDED(aElement->GetAttribute("align", align)) &&
NS_SUCCEEDED(aElement->GetAttribute("height", height)) &&
NS_SUCCEEDED(aElement->GetAttribute("width", width)) &&
NS_SUCCEEDED(aElement->GetAttribute("noshade", noshade)))
{
PRInt32 iAlign = 0;
if (align == "center")
iAlign = 1;
else if (align == "right")
iAlign = 2;
mPrefs->SetIntPref("editor.hrule.align", iAlign);
PRInt32 errorCode;
PRInt32 iHeight = height.ToInteger(&errorCode);
if (errorCode == NS_OK && iHeight > 0)
mPrefs->SetIntPref("editor.hrule.height", iHeight);
PRInt32 iWidth = width.ToInteger(&errorCode);
if (errorCode == NS_OK && iWidth > 0) {
mPrefs->SetIntPref("editor.hrule.width", iWidth);
mPrefs->SetBoolPref("editor.hrule.width_percent", (width.Find("%") > 0));
}
mPrefs->SetBoolPref("editor.hrule.shading", (noshade == ""));
res = NS_OK;
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->InsertLinkAroundSelection(aAnchorElement);
#endif // ENABLE_JS_EDITOR_LOG
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;
// We must have a real selection
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res) || !selection)
goto DELETE_ANCHOR;
PRBool isCollapsed;
res = selection->GetIsCollapsed(SELECTION_NORMAL, &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;
if (NS_SUCCEEDED(anchor->GetHref(href)) && href.GetUnicode() && href.Length() > 0)
{
nsAutoEditBatch beginBatching(this);
const nsString attribute("href");
SetTextProperty(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;
}
PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement)
{
if ( aElement )
{
nsIDOMElement* bodyElement = nsnull;
nsresult res = nsEditor::GetBodyElement(&bodyElement);
if (NS_SUCCEEDED(res) && bodyElement)
{
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;
}
NS_IMETHODIMP
nsHTMLEditor::SelectElement(nsIDOMElement* aElement)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->SelectElement(aElement);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res = NS_ERROR_NULL_POINTER;
// Must be sure that element is contained in the document body
if (IsElementInBody(aElement))
{
nsCOMPtr<nsIDOMSelection> selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(res) && selection)
{
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,
selection->Collapse(parent, offsetInParent, SELECTION_NORMAL);
// then extend it to just after
selection->Extend(parent, offsetInParent+1, SELECTION_NORMAL);
}
}
}
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement, PRBool* caretIsSet)
{
nsresult res = NS_ERROR_NULL_POINTER;
if (caretIsSet)
*caretIsSet = PR_FALSE;
if (aElement && IsElementInBody(aElement))
{
res = NS_OK;
nsAutoString tagName;
aElement->GetNodeName(tagName);
tagName.ToLowerCase();
if (tagName == "td" || tagName == "tr" ||
tagName == "th" || tagName == "td" ||
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;
}
}
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 &nbsp
nsCOMPtr<nsIDOMCharacterData>textNode = do_QueryInterface(node);
nsString text;
char 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
}
}
// Set selection at beginning of deepest node
// Should we set
nsCOMPtr<nsIDOMSelection> selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(res) && selection)
{
res = selection->Collapse(parent, offset, SELECTION_NORMAL);
if (NS_SUCCEEDED(res) && caretIsSet)
*caretIsSet = PR_TRUE;
}
}
}
return res;
}
NS_IMETHODIMP
nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement)
{
#ifdef ENABLE_JS_EDITOR_LOG
nsAutoJSEditorLogLock logLock(mJSEditorLog);
if (mJSEditorLog)
mJSEditorLog->SetCaretAfterElement(aElement);
#endif // ENABLE_JS_EDITOR_LOG
nsresult res = NS_ERROR_NULL_POINTER;
// Be sure the element is contained in the document body
if (aElement && IsElementInBody(aElement))
{
nsCOMPtr<nsIDOMSelection> selection;
res = nsEditor::GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(res) && selection)
{
nsCOMPtr<nsIDOMNode>parent;
res = aElement->GetParentNode(getter_AddRefs(parent));
if (NS_SUCCEEDED(res) && parent)
{
PRInt32 offsetInParent;
res = GetChildOffset(aElement, parent, offsetInParent);
// New collapsed selection will be just after the new element
offsetInParent++;
if (NS_SUCCEEDED(res))
{
// Collapse selection to just after desired element,
selection->Collapse(parent, offsetInParent+1, SELECTION_NORMAL);
}
}
}
}
return res;
}
PRBool
nsHTMLEditor::CanContainTag(nsIDOMNode* aParent, const nsString &aTag)
{
if (!aParent) return PR_FALSE;
static nsAutoString ulTag = "ul";
static nsAutoString olTag = "ol";
static nsAutoString liTag = "li";
static nsAutoString bodyTag = "body";
static nsAutoString tdTag = "td";
static nsAutoString thTag = "th";
static nsAutoString bqTag = "blockquote";
nsCOMPtr<nsIAtom> pTagAtom = GetTag(aParent);
nsAutoString pTag;
pTagAtom->ToString(pTag);
// flesh this out...
// for now, only lists and blockquotes are using this funct
if (aTag.EqualsIgnoreCase(ulTag) ||
aTag.EqualsIgnoreCase(olTag) )
{
if (pTag.EqualsIgnoreCase(bodyTag) ||
pTag.EqualsIgnoreCase(tdTag) ||
pTag.EqualsIgnoreCase(thTag) ||
pTag.EqualsIgnoreCase(ulTag) ||
pTag.EqualsIgnoreCase(olTag) ||
pTag.EqualsIgnoreCase(liTag) ||
pTag.EqualsIgnoreCase(bqTag) )
{
return PR_TRUE;
}
}
else if (aTag.EqualsIgnoreCase(bqTag) )
{
if (pTag.EqualsIgnoreCase(bodyTag) ||
pTag.EqualsIgnoreCase(tdTag) ||
pTag.EqualsIgnoreCase(thTag) ||
pTag.EqualsIgnoreCase(liTag) ||
pTag.EqualsIgnoreCase(bqTag) )
{
return PR_TRUE;
}
}
return PR_FALSE;
}
NS_IMETHODIMP
nsHTMLEditor::IsRootTag(nsString &aTag, PRBool &aIsTag)
{
static nsAutoString bodyTag = "body";
static nsAutoString tdTag = "td";
static nsAutoString thTag = "th";
static nsAutoString 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 nsAutoString p = "p";
static nsAutoString h1 = "h1";
static nsAutoString h2 = "h2";
static nsAutoString h3 = "h3";
static nsAutoString h4 = "h4";
static nsAutoString h5 = "h5";
static nsAutoString h6 = "h6";
static nsAutoString address = "address";
static nsAutoString pre = "pre";
static nsAutoString li = "li";
static nsAutoString dt = "dt";
static nsAutoString 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;
}
NS_IMETHODIMP nsHTMLEditor::BeginComposition(void)
{
return nsTextEditor::BeginComposition();
}
NS_IMETHODIMP nsHTMLEditor::EndComposition(void)
{
return nsTextEditor::EndComposition();
}
NS_IMETHODIMP nsHTMLEditor::SetCompositionString(const nsString& aCompositionString, nsIDOMTextRangeList* aTextRangeList)
{
return nsTextEditor::SetCompositionString(aCompositionString,aTextRangeList);
}
NS_IMETHODIMP
nsHTMLEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed)
{
#ifdef DEBUG
if (!outNumTests || !outNumTestsFailed)
return NS_ERROR_NULL_POINTER;
// first, run the text editor tests (is this appropriate?)
nsresult rv = nsTextEditor::DebugUnitTests(outNumTests, outNumTestsFailed);
if (NS_FAILED(rv))
return rv;
// now run our tests
*outNumTests += 0;
*outNumTestsFailed += 0;
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
NS_IMETHODIMP
nsHTMLEditor::StartLogging(nsIFileSpec *aLogFile)
{
return nsTextEditor::StartLogging(aLogFile);
}
NS_IMETHODIMP
nsHTMLEditor::StopLogging()
{
return nsTextEditor::StopLogging();
}