diff --git a/editor/base/nsHTMLEditRules.cpp b/editor/base/nsHTMLEditRules.cpp index d1d35abed273..9a4e8ec74389 100644 --- a/editor/base/nsHTMLEditRules.cpp +++ b/editor/base/nsHTMLEditRules.cpp @@ -98,12 +98,17 @@ nsHTMLEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags) bodyNode = do_QueryInterface(bodyElem); if (bodyNode) { + // temporarily turn off rules sniffing + nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); res = nsComponentManager::CreateInstance(kRangeCID, nsnull, NS_GET_IID(nsIDOMRange), getter_AddRefs(mDocChangeRange)); if (NS_FAILED(res)) return res; if (!mDocChangeRange) return NS_ERROR_NULL_POINTER; mDocChangeRange->SelectNode(bodyNode); - AdjustSpecialBreaks(); + res = ReplaceNewlines(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = AdjustSpecialBreaks(); + if (NS_FAILED(res)) return res; } // turn on undo mEditor->EnableUndo(PR_TRUE); @@ -180,6 +185,13 @@ nsHTMLEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection) res = AdjustWhitespace(selection); if (NS_FAILED(res)) return res; } + // replace newlines that are preformatted + if ((action == nsEditor::kOpInsertText) || + (action == nsEditor::kOpInsertIMEText) || + (action == nsEditor::kOpInsertNode)) + { + res = ReplaceNewlines(mDocChangeRange); + } // clean up any empty nodes in the selection res = RemoveEmptyNodes(); if (NS_FAILED(res)) return res; @@ -1577,8 +1589,27 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, PRBool outMakeEmpty; res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty); if (NS_FAILED(res)) return res; - if (outMakeEmpty) return NS_OK; - + if (outMakeEmpty) + { + PRInt32 offset; + nsCOMPtr brNode, parent, theDiv; + nsAutoString divType("div"); + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv)); + if (NS_FAILED(res)) return res; + // set up the alignment on the div + nsCOMPtr divElem = do_QueryInterface(theDiv); + nsAutoString attr("align"); + res = mEditor->SetAttribute(divElem, attr, *alignType); + if (NS_FAILED(res)) return res; + *aHandled = PR_TRUE; + // put in a moz-br so that it won't get deleted + res = CreateMozBR(theDiv, 0, &brNode); + if (NS_FAILED(res)) return res; + res = aSelection->Collapse(theDiv, 0); + return res; + } // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate diff --git a/editor/base/nsHTMLEditor.cpp b/editor/base/nsHTMLEditor.cpp index 340c7486fd96..82a791e85162 100644 --- a/editor/base/nsHTMLEditor.cpp +++ b/editor/base/nsHTMLEditor.cpp @@ -4269,6 +4269,38 @@ nsHTMLEditor::CanContainTag(nsIDOMNode* aParent, const nsString &aTag) } +NS_IMETHODIMP +nsHTMLEditor::SelectEntireDocument(nsIDOMSelection *aSelection) +{ + nsresult res; + if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } + + // get body node + nsCOMPtrbodyElement; + res = GetBodyElement(getter_AddRefs(bodyElement)); + if (NS_FAILED(res)) return res; + nsCOMPtrbodyNode = do_QueryInterface(bodyElement); + if (!bodyNode) return NS_ERROR_FAILURE; + + // is doc empty? + PRBool bDocIsEmpty; + res = mRules->DocumentIsEmpty(&bDocIsEmpty); + if (NS_FAILED(res)) return res; + + if (bDocIsEmpty) + { + // if its empty dont select entire doc - that would select the bogus node + return aSelection->Collapse(bodyNode, 0); + } + else + { + return nsEditor::SelectEntireDocument(aSelection); + } + return res; +} + + + #ifdef XP_MAC #pragma mark - #pragma mark --- Random methods --- diff --git a/editor/base/nsHTMLEditor.h b/editor/base/nsHTMLEditor.h index da9f8d795227..feeb95bab509 100644 --- a/editor/base/nsHTMLEditor.h +++ b/editor/base/nsHTMLEditor.h @@ -241,11 +241,6 @@ public: NS_IMETHOD DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed); - /* ------------ nsICSSLoaderObserver -------------- */ - NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify); - - /* ------------ nsEditor overrides ---------------- */ - /** All editor operations which alter the doc should be prefaced * with a call to StartOperation, naming the action and direction */ NS_IMETHOD StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection); @@ -257,6 +252,12 @@ public: /** returns PR_TRUE if aParent can contain a child of type aTag */ PRBool CanContainTag(nsIDOMNode* aParent, const nsString &aTag); + /** make the given selection span the entire document */ + NS_IMETHOD SelectEntireDocument(nsIDOMSelection *aSelection); + + /* ------------ nsICSSLoaderObserver -------------- */ + NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify); + /* ------------ Utility Routines, not part of public API -------------- */ NS_IMETHOD GetBodyStyleContext(nsIStyleContext** aStyleContext); diff --git a/editor/base/nsTextEditRules.cpp b/editor/base/nsTextEditRules.cpp index 07f281ab1e03..9360d8034f67 100644 --- a/editor/base/nsTextEditRules.cpp +++ b/editor/base/nsTextEditRules.cpp @@ -37,8 +37,10 @@ #include "nsLayoutCID.h" #include "nsIEditProperty.h" #include "nsEditorUtils.h" +#include "EditTxn.h" -static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_CID(kContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \ @@ -82,6 +84,24 @@ nsTextEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags) mEditor->GetSelection(getter_AddRefs(selection)); NS_ASSERTION(selection, "editor cannot get selection"); nsresult res = CreateBogusNodeIfNeeded(selection); // this method handles null selection, which should never happen anyway + + // create a range that is the entire body contents + if (NS_FAILED(res)) return res; + nsCOMPtr bodyElement; + res = mEditor->GetBodyElement(getter_AddRefs(bodyElement)); + if (NS_FAILED(res)) return res; + if (!bodyElement) return NS_ERROR_NULL_POINTER; + nsCOMPtrbodyNode = do_QueryInterface(bodyElement); + if (!bodyNode) return NS_ERROR_FAILURE; + nsCOMPtr wholeDoc; + res = nsComponentManager::CreateInstance(kRangeCID, nsnull, NS_GET_IID(nsIDOMRange), + getter_AddRefs(wholeDoc)); + if (NS_FAILED(res)) return res; + res = wholeDoc->SelectNode(bodyNode); + if (NS_FAILED(res)) return res; + + // replace newlines in that range with breaks + res = ReplaceNewlines(wholeDoc); return res; } @@ -1264,6 +1284,101 @@ nsTextEditRules::DidOutputText(nsIDOMSelection *aSelection, nsresult aResult) } +nsresult +nsTextEditRules::ReplaceNewlines(nsIDOMRange *aRange) +{ + if (!aRange) return NS_ERROR_NULL_POINTER; + + // convert any newlines in editable, preformatted text nodes + // into normal breaks. this is because layout wont give us a place + // to put the cursor on empty lines otherwise. + + nsCOMPtr iter; + nsCOMPtr isupports; + PRUint32 nodeCount,j; + nsCOMPtr arrayOfNodes; + + // make an isupportsArray to hold a list of nodes + nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + if (NS_FAILED(res)) return res; + + // need an iterator + res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(iter)); + if (NS_FAILED(res)) return res; + res = iter->Init(aRange); + if (NS_FAILED(res)) return res; + + // gather up a list of editable preformatted text nodes + while (NS_ENUMERATOR_FALSE == iter->IsDone()) + { + nsCOMPtr node; + nsCOMPtr content; + res = iter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return res; + node = do_QueryInterface(content); + if (!node) return NS_ERROR_FAILURE; + + if (mEditor->IsTextNode(node) && mEditor->IsEditable(node)) + { + PRBool isPRE; + res = mEditor->IsPreformatted(node, &isPRE); + if (NS_FAILED(res)) return res; + if (isPRE) + { + isupports = do_QueryInterface(node); + arrayOfNodes->AppendElement(isupports); + } + } + res = iter->Next(); + if (NS_FAILED(res)) return res; + } + + // replace newlines with breaks. have to do this left to right, + // since inserting the break can split the text node, and the + // original node becomes the righthand node. + char newlineChar[] = {'\n',0}; + res = arrayOfNodes->Count(&nodeCount); + if (NS_FAILED(res)) return res; + for (j = 0; j < nodeCount; j++) + { + isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); + nsCOMPtr brNode, theNode( do_QueryInterface(isupports) ); + nsCOMPtr textNode( do_QueryInterface(theNode) ); + arrayOfNodes->RemoveElementAt(0); + // find the newline + PRInt32 offset; + nsAutoString tempString; + do + { + textNode->GetData(tempString); + offset = tempString.FindCharInSet(newlineChar); + if (offset == -1) break; // done with this node + + // delete the newline + EditTxn *txn; + // note 1: we are not telling edit listeners about these because they don't care + // note 2: we are not wrapping these in a placeholder because we know they already are, + // or, failing that, undo is disabled + res = mEditor->CreateTxnForDeleteText(textNode, offset, 1, (DeleteTextTxn**)&txn); + if (NS_FAILED(res)) return res; + if (!txn) return NS_ERROR_OUT_OF_MEMORY; + res = mEditor->Do(txn); + if (NS_FAILED(res)) return res; + // The transaction system (if any) has taken ownwership of txn + NS_IF_RELEASE(txn); + + // insert a break + res = mEditor->CreateBR(textNode, offset, &brNode); + if (NS_FAILED(res)) return res; + } while (1); // break used to exit while loop + } + return res; +} + + nsresult nsTextEditRules::CreateBogusNodeIfNeeded(nsIDOMSelection *aSelection) { diff --git a/editor/base/nsTextEditRules.h b/editor/base/nsTextEditRules.h index db89294a7e2d..55a94df5aab0 100644 --- a/editor/base/nsTextEditRules.h +++ b/editor/base/nsTextEditRules.h @@ -182,6 +182,9 @@ protected: const nsString &aValue, nsIDOMSelection *aSelection); + /** replaces newllines with breaks, if needed. acts on doc portion in aRange */ + nsresult ReplaceNewlines(nsIDOMRange *aRange); + /** creates a bogus text node if the document has no editable content */ nsresult CreateBogusNodeIfNeeded(nsIDOMSelection *aSelection); diff --git a/editor/libeditor/html/nsHTMLEditRules.cpp b/editor/libeditor/html/nsHTMLEditRules.cpp index d1d35abed273..9a4e8ec74389 100644 --- a/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/editor/libeditor/html/nsHTMLEditRules.cpp @@ -98,12 +98,17 @@ nsHTMLEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags) bodyNode = do_QueryInterface(bodyElem); if (bodyNode) { + // temporarily turn off rules sniffing + nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); res = nsComponentManager::CreateInstance(kRangeCID, nsnull, NS_GET_IID(nsIDOMRange), getter_AddRefs(mDocChangeRange)); if (NS_FAILED(res)) return res; if (!mDocChangeRange) return NS_ERROR_NULL_POINTER; mDocChangeRange->SelectNode(bodyNode); - AdjustSpecialBreaks(); + res = ReplaceNewlines(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = AdjustSpecialBreaks(); + if (NS_FAILED(res)) return res; } // turn on undo mEditor->EnableUndo(PR_TRUE); @@ -180,6 +185,13 @@ nsHTMLEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection) res = AdjustWhitespace(selection); if (NS_FAILED(res)) return res; } + // replace newlines that are preformatted + if ((action == nsEditor::kOpInsertText) || + (action == nsEditor::kOpInsertIMEText) || + (action == nsEditor::kOpInsertNode)) + { + res = ReplaceNewlines(mDocChangeRange); + } // clean up any empty nodes in the selection res = RemoveEmptyNodes(); if (NS_FAILED(res)) return res; @@ -1577,8 +1589,27 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, PRBool outMakeEmpty; res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty); if (NS_FAILED(res)) return res; - if (outMakeEmpty) return NS_OK; - + if (outMakeEmpty) + { + PRInt32 offset; + nsCOMPtr brNode, parent, theDiv; + nsAutoString divType("div"); + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv)); + if (NS_FAILED(res)) return res; + // set up the alignment on the div + nsCOMPtr divElem = do_QueryInterface(theDiv); + nsAutoString attr("align"); + res = mEditor->SetAttribute(divElem, attr, *alignType); + if (NS_FAILED(res)) return res; + *aHandled = PR_TRUE; + // put in a moz-br so that it won't get deleted + res = CreateMozBR(theDiv, 0, &brNode); + if (NS_FAILED(res)) return res; + res = aSelection->Collapse(theDiv, 0); + return res; + } // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate diff --git a/editor/libeditor/html/nsHTMLEditor.cpp b/editor/libeditor/html/nsHTMLEditor.cpp index 340c7486fd96..82a791e85162 100644 --- a/editor/libeditor/html/nsHTMLEditor.cpp +++ b/editor/libeditor/html/nsHTMLEditor.cpp @@ -4269,6 +4269,38 @@ nsHTMLEditor::CanContainTag(nsIDOMNode* aParent, const nsString &aTag) } +NS_IMETHODIMP +nsHTMLEditor::SelectEntireDocument(nsIDOMSelection *aSelection) +{ + nsresult res; + if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } + + // get body node + nsCOMPtrbodyElement; + res = GetBodyElement(getter_AddRefs(bodyElement)); + if (NS_FAILED(res)) return res; + nsCOMPtrbodyNode = do_QueryInterface(bodyElement); + if (!bodyNode) return NS_ERROR_FAILURE; + + // is doc empty? + PRBool bDocIsEmpty; + res = mRules->DocumentIsEmpty(&bDocIsEmpty); + if (NS_FAILED(res)) return res; + + if (bDocIsEmpty) + { + // if its empty dont select entire doc - that would select the bogus node + return aSelection->Collapse(bodyNode, 0); + } + else + { + return nsEditor::SelectEntireDocument(aSelection); + } + return res; +} + + + #ifdef XP_MAC #pragma mark - #pragma mark --- Random methods --- diff --git a/editor/libeditor/html/nsHTMLEditor.h b/editor/libeditor/html/nsHTMLEditor.h index da9f8d795227..feeb95bab509 100644 --- a/editor/libeditor/html/nsHTMLEditor.h +++ b/editor/libeditor/html/nsHTMLEditor.h @@ -241,11 +241,6 @@ public: NS_IMETHOD DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed); - /* ------------ nsICSSLoaderObserver -------------- */ - NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify); - - /* ------------ nsEditor overrides ---------------- */ - /** All editor operations which alter the doc should be prefaced * with a call to StartOperation, naming the action and direction */ NS_IMETHOD StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection); @@ -257,6 +252,12 @@ public: /** returns PR_TRUE if aParent can contain a child of type aTag */ PRBool CanContainTag(nsIDOMNode* aParent, const nsString &aTag); + /** make the given selection span the entire document */ + NS_IMETHOD SelectEntireDocument(nsIDOMSelection *aSelection); + + /* ------------ nsICSSLoaderObserver -------------- */ + NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify); + /* ------------ Utility Routines, not part of public API -------------- */ NS_IMETHOD GetBodyStyleContext(nsIStyleContext** aStyleContext); diff --git a/editor/libeditor/text/nsTextEditRules.cpp b/editor/libeditor/text/nsTextEditRules.cpp index 07f281ab1e03..9360d8034f67 100644 --- a/editor/libeditor/text/nsTextEditRules.cpp +++ b/editor/libeditor/text/nsTextEditRules.cpp @@ -37,8 +37,10 @@ #include "nsLayoutCID.h" #include "nsIEditProperty.h" #include "nsEditorUtils.h" +#include "EditTxn.h" -static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_CID(kContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \ @@ -82,6 +84,24 @@ nsTextEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags) mEditor->GetSelection(getter_AddRefs(selection)); NS_ASSERTION(selection, "editor cannot get selection"); nsresult res = CreateBogusNodeIfNeeded(selection); // this method handles null selection, which should never happen anyway + + // create a range that is the entire body contents + if (NS_FAILED(res)) return res; + nsCOMPtr bodyElement; + res = mEditor->GetBodyElement(getter_AddRefs(bodyElement)); + if (NS_FAILED(res)) return res; + if (!bodyElement) return NS_ERROR_NULL_POINTER; + nsCOMPtrbodyNode = do_QueryInterface(bodyElement); + if (!bodyNode) return NS_ERROR_FAILURE; + nsCOMPtr wholeDoc; + res = nsComponentManager::CreateInstance(kRangeCID, nsnull, NS_GET_IID(nsIDOMRange), + getter_AddRefs(wholeDoc)); + if (NS_FAILED(res)) return res; + res = wholeDoc->SelectNode(bodyNode); + if (NS_FAILED(res)) return res; + + // replace newlines in that range with breaks + res = ReplaceNewlines(wholeDoc); return res; } @@ -1264,6 +1284,101 @@ nsTextEditRules::DidOutputText(nsIDOMSelection *aSelection, nsresult aResult) } +nsresult +nsTextEditRules::ReplaceNewlines(nsIDOMRange *aRange) +{ + if (!aRange) return NS_ERROR_NULL_POINTER; + + // convert any newlines in editable, preformatted text nodes + // into normal breaks. this is because layout wont give us a place + // to put the cursor on empty lines otherwise. + + nsCOMPtr iter; + nsCOMPtr isupports; + PRUint32 nodeCount,j; + nsCOMPtr arrayOfNodes; + + // make an isupportsArray to hold a list of nodes + nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + if (NS_FAILED(res)) return res; + + // need an iterator + res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(iter)); + if (NS_FAILED(res)) return res; + res = iter->Init(aRange); + if (NS_FAILED(res)) return res; + + // gather up a list of editable preformatted text nodes + while (NS_ENUMERATOR_FALSE == iter->IsDone()) + { + nsCOMPtr node; + nsCOMPtr content; + res = iter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return res; + node = do_QueryInterface(content); + if (!node) return NS_ERROR_FAILURE; + + if (mEditor->IsTextNode(node) && mEditor->IsEditable(node)) + { + PRBool isPRE; + res = mEditor->IsPreformatted(node, &isPRE); + if (NS_FAILED(res)) return res; + if (isPRE) + { + isupports = do_QueryInterface(node); + arrayOfNodes->AppendElement(isupports); + } + } + res = iter->Next(); + if (NS_FAILED(res)) return res; + } + + // replace newlines with breaks. have to do this left to right, + // since inserting the break can split the text node, and the + // original node becomes the righthand node. + char newlineChar[] = {'\n',0}; + res = arrayOfNodes->Count(&nodeCount); + if (NS_FAILED(res)) return res; + for (j = 0; j < nodeCount; j++) + { + isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); + nsCOMPtr brNode, theNode( do_QueryInterface(isupports) ); + nsCOMPtr textNode( do_QueryInterface(theNode) ); + arrayOfNodes->RemoveElementAt(0); + // find the newline + PRInt32 offset; + nsAutoString tempString; + do + { + textNode->GetData(tempString); + offset = tempString.FindCharInSet(newlineChar); + if (offset == -1) break; // done with this node + + // delete the newline + EditTxn *txn; + // note 1: we are not telling edit listeners about these because they don't care + // note 2: we are not wrapping these in a placeholder because we know they already are, + // or, failing that, undo is disabled + res = mEditor->CreateTxnForDeleteText(textNode, offset, 1, (DeleteTextTxn**)&txn); + if (NS_FAILED(res)) return res; + if (!txn) return NS_ERROR_OUT_OF_MEMORY; + res = mEditor->Do(txn); + if (NS_FAILED(res)) return res; + // The transaction system (if any) has taken ownwership of txn + NS_IF_RELEASE(txn); + + // insert a break + res = mEditor->CreateBR(textNode, offset, &brNode); + if (NS_FAILED(res)) return res; + } while (1); // break used to exit while loop + } + return res; +} + + nsresult nsTextEditRules::CreateBogusNodeIfNeeded(nsIDOMSelection *aSelection) { diff --git a/editor/libeditor/text/nsTextEditRules.h b/editor/libeditor/text/nsTextEditRules.h index db89294a7e2d..55a94df5aab0 100644 --- a/editor/libeditor/text/nsTextEditRules.h +++ b/editor/libeditor/text/nsTextEditRules.h @@ -182,6 +182,9 @@ protected: const nsString &aValue, nsIDOMSelection *aSelection); + /** replaces newllines with breaks, if needed. acts on doc portion in aRange */ + nsresult ReplaceNewlines(nsIDOMRange *aRange); + /** creates a bogus text node if the document has no editable content */ nsresult CreateBogusNodeIfNeeded(nsIDOMSelection *aSelection);