From 84db5fa6249c3d36dc1cca9211a3aad2db11cc71 Mon Sep 17 00:00:00 2001 From: "jfrancis%netscape.com" Date: Sat, 26 Aug 2000 04:03:50 +0000 Subject: [PATCH] fixes bugs 46782,50161,48643,49266,49265,46395; groundwork for bugs 47931,49952; r=fm git-svn-id: svn://10.0.0.236/trunk@77257 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/editor/base/TypeInState.cpp | 57 +- mozilla/editor/base/TypeInState.h | 17 +- mozilla/editor/base/nsEditor.cpp | 336 ++++++-- mozilla/editor/base/nsEditor.h | 53 +- mozilla/editor/base/nsEditorUtils.cpp | 155 +++- mozilla/editor/base/nsEditorUtils.h | 46 + mozilla/editor/base/nsHTMLEditRules.cpp | 784 ++++++++++-------- mozilla/editor/base/nsHTMLEditRules.h | 7 +- mozilla/editor/base/nsHTMLEditUtils.cpp | 10 +- mozilla/editor/base/nsHTMLEditUtils.h | 3 + mozilla/editor/base/nsHTMLEditor.cpp | 83 +- mozilla/editor/base/nsHTMLEditor.h | 11 +- mozilla/editor/base/nsTextEditRules.cpp | 2 +- mozilla/editor/libeditor/base/nsEditor.cpp | 336 ++++++-- mozilla/editor/libeditor/base/nsEditor.h | 53 +- .../editor/libeditor/base/nsEditorUtils.cpp | 155 +++- mozilla/editor/libeditor/base/nsEditorUtils.h | 46 + mozilla/editor/libeditor/html/TypeInState.cpp | 57 +- mozilla/editor/libeditor/html/TypeInState.h | 17 +- .../editor/libeditor/html/nsHTMLEditRules.cpp | 784 ++++++++++-------- .../editor/libeditor/html/nsHTMLEditRules.h | 7 +- .../editor/libeditor/html/nsHTMLEditUtils.cpp | 10 +- .../editor/libeditor/html/nsHTMLEditUtils.h | 3 + .../editor/libeditor/html/nsHTMLEditor.cpp | 83 +- mozilla/editor/libeditor/html/nsHTMLEditor.h | 11 +- .../editor/libeditor/text/nsTextEditRules.cpp | 2 +- 26 files changed, 2084 insertions(+), 1044 deletions(-) diff --git a/mozilla/editor/base/TypeInState.cpp b/mozilla/editor/base/TypeInState.cpp index e6167cfdaeb..89ed469a6a1 100644 --- a/mozilla/editor/base/TypeInState.cpp +++ b/mozilla/editor/base/TypeInState.cpp @@ -122,14 +122,14 @@ nsresult TypeInState::SetProp(nsIAtom *aProp, const nsString &aAttr, const nsStr } // if it's already set we are done - if (IsPropSet(aProp,aAttr,aValue)) return NS_OK; + if (IsPropSet(aProp,aAttr,nsnull)) return NS_OK; // make a new propitem PropItem *item = new PropItem(aProp,aAttr,aValue); if (!item) return NS_ERROR_OUT_OF_MEMORY; // remove it from the list of cleared properties, if we have a match - RemovePropFromClearedList(aProp,aAttr,aValue); + RemovePropFromClearedList(aProp,aAttr); // add it to the list of set properties mSetArray.AppendElement((void*)item); @@ -141,30 +141,25 @@ nsresult TypeInState::SetProp(nsIAtom *aProp, const nsString &aAttr, const nsStr nsresult TypeInState::ClearAllProps() { // null prop means "all" props - return ClearProp(nsnull,nsAutoString(),nsAutoString()); + return ClearProp(nsnull,nsAutoString()); } nsresult TypeInState::ClearProp(nsIAtom *aProp) { - return ClearProp(aProp,nsAutoString(),nsAutoString()); + return ClearProp(aProp,nsAutoString()); } nsresult TypeInState::ClearProp(nsIAtom *aProp, const nsString &aAttr) -{ - return ClearProp(aProp,aAttr,nsAutoString()); -} - -nsresult TypeInState::ClearProp(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue) { // if it's already cleared we are done - if (IsPropCleared(aProp,aAttr,aValue)) return NS_OK; + if (IsPropCleared(aProp,aAttr)) return NS_OK; // make a new propitem - PropItem *item = new PropItem(aProp,aAttr,aValue); + PropItem *item = new PropItem(aProp,aAttr,nsAutoString()); if (!item) return NS_ERROR_OUT_OF_MEMORY; // remove it from the list of set properties, if we have a match - RemovePropFromSetList(aProp,aAttr,aValue); + RemovePropFromSetList(aProp,aAttr); // add it to the list of cleared properties mClearedArray.AppendElement((void*)item); @@ -222,7 +217,7 @@ nsresult TypeInState::TakeRelativeFontSize(PRInt32 *outRelSize) nsresult TypeInState::GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp) { - return GetTypingState(isSet, theSetting, aProp, nsAutoString(), nsAutoString()); + return GetTypingState(isSet, theSetting, aProp, nsAutoString(), nsnull); } nsresult TypeInState::GetTypingState(PRBool &isSet, @@ -230,7 +225,7 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, nsIAtom *aProp, const nsString &aAttr) { - return GetTypingState(isSet, theSetting, aProp, aAttr, nsAutoString()); + return GetTypingState(isSet, theSetting, aProp, aAttr, nsnull); } @@ -238,14 +233,14 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue) + nsString *aValue) { if (IsPropSet(aProp, aAttr, aValue)) { isSet = PR_TRUE; theSetting = PR_TRUE; } - else if (IsPropCleared(aProp, aAttr, aValue)) + else if (IsPropCleared(aProp, aAttr)) { isSet = PR_TRUE; theSetting = PR_FALSE; @@ -264,8 +259,7 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, *******************************************************************/ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 index; PropItem *item; @@ -282,7 +276,7 @@ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, if (item) delete item; } } - else if (FindPropInList(aProp, aAttr, aValue, mSetArray, index)) + else if (FindPropInList(aProp, aAttr, nsnull, mSetArray, index)) { item = (PropItem*)mSetArray.ElementAt(index); mSetArray.RemoveElementAt(index); @@ -293,11 +287,10 @@ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, nsresult TypeInState::RemovePropFromClearedList(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 index; - if (FindPropInList(aProp, aAttr, aValue, mClearedArray, index)) + if (FindPropInList(aProp, aAttr, nsnull, mClearedArray, index)) { PropItem *item = (PropItem*)mClearedArray.ElementAt(index); mClearedArray.RemoveElementAt(index); @@ -309,16 +302,16 @@ nsresult TypeInState::RemovePropFromClearedList(nsIAtom *aProp, PRBool TypeInState::IsPropSet(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue) + nsString* outValue) { PRInt32 i; - return IsPropSet(aProp, aAttr, aValue, i); + return IsPropSet(aProp, aAttr, outValue, i); } PRBool TypeInState::IsPropSet(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, + nsString *outValue, PRInt32 &outIndex) { // linear search. list should be short. @@ -329,6 +322,7 @@ PRBool TypeInState::IsPropSet(nsIAtom *aProp, if ( (item->tag == aProp) && (item->attr == aAttr) ) { + if (outValue) *outValue = item->value; outIndex = i; return PR_TRUE; } @@ -338,22 +332,20 @@ PRBool TypeInState::IsPropSet(nsIAtom *aProp, PRBool TypeInState::IsPropCleared(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 i; - return IsPropCleared(aProp, aAttr, aValue, i); + return IsPropCleared(aProp, aAttr, i); } PRBool TypeInState::IsPropCleared(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, PRInt32 &outIndex) { - if (FindPropInList(aProp, aAttr, aValue, mClearedArray, outIndex)) + if (FindPropInList(aProp, aAttr, nsnull, mClearedArray, outIndex)) return PR_TRUE; - if (FindPropInList(0, nsAutoString(), nsAutoString(), mClearedArray, outIndex)) + if (FindPropInList(0, nsAutoString(), nsnull, mClearedArray, outIndex)) { // special case for all props cleared outIndex = -1; @@ -364,7 +356,7 @@ PRBool TypeInState::IsPropCleared(nsIAtom *aProp, PRBool TypeInState::FindPropInList(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, + nsString *outValue, nsVoidArray &aList, PRInt32 &outIndex) { @@ -376,6 +368,7 @@ PRBool TypeInState::FindPropInList(nsIAtom *aProp, if ( (item->tag == aProp) && (item->attr == aAttr) ) { + if (outValue) *outValue = item->value; outIndex = i; return PR_TRUE; } diff --git a/mozilla/editor/base/TypeInState.h b/mozilla/editor/base/TypeInState.h index eed5f33b7b6..3d3bd7b7a6d 100644 --- a/mozilla/editor/base/TypeInState.h +++ b/mozilla/editor/base/TypeInState.h @@ -57,7 +57,6 @@ public: nsresult ClearAllProps(); nsresult ClearProp(nsIAtom *aProp); nsresult ClearProp(nsIAtom *aProp, const nsString &aAttr); - nsresult ClearProp(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); //************************************************************************** // TakeClearProperty: hands back next poroperty item on the clear list. @@ -78,17 +77,17 @@ public: nsresult GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, const nsString &aAttr); nsresult GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, - const nsString &aAttr, const nsString &aValue); + const nsString &aAttr, nsString* outValue); protected: - nsresult RemovePropFromSetList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - nsresult RemovePropFromClearedList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, PRInt32 &outIndex); - PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, PRInt32 &outIndex); - PRBool FindPropInList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, nsVoidArray &aList, PRInt32 &outIndex); + nsresult RemovePropFromSetList(nsIAtom *aProp, const nsString &aAttr); + nsresult RemovePropFromClearedList(nsIAtom *aProp, const nsString &aAttr); + PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, nsString* outValue); + PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, nsString* outValue, PRInt32 &outIndex); + PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr); + PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, PRInt32 &outIndex); + PRBool FindPropInList(nsIAtom *aProp, const nsString &aAttr, nsString *outValue, nsVoidArray &aList, PRInt32 &outIndex); nsVoidArray mSetArray; nsVoidArray mClearedArray; diff --git a/mozilla/editor/base/nsEditor.cpp b/mozilla/editor/base/nsEditor.cpp index 354a8c2b113..23dda5472e1 100644 --- a/mozilla/editor/base/nsEditor.cpp +++ b/mozilla/editor/base/nsEditor.cpp @@ -152,17 +152,11 @@ PRInt32 nsEditor::gInstanceCount = 0; * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store * ranges since dom gravity will possibly change the ranges. */ -nsSelectionState::nsSelectionState() : mArray(), mLock(PR_FALSE) {} +nsSelectionState::nsSelectionState() : mArray(){} nsSelectionState::~nsSelectionState() { - // free any items in the array - SelRangeStore *item; - while ((item = (SelRangeStore*)mArray.ElementAt(0))) - { - delete item; - mArray.RemoveElementAt(0); - } + MakeEmpty(); } nsresult @@ -171,7 +165,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) if (!aSel) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; PRInt32 i,rangeCount, arrayCount = mArray.Count(); - SelRangeStore *item; + nsRangeStore *item; aSel->GetRangeCount(&rangeCount); // if we need more items in the array, new them @@ -180,7 +174,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) PRInt32 count = rangeCount-arrayCount; for (i=0; iarrayCount) { - while ((item = (SelRangeStore*)mArray.ElementAt(rangeCount))) + while ((item = (nsRangeStore*)mArray.ElementAt(rangeCount))) { delete item; mArray.RemoveElementAt(rangeCount); @@ -198,7 +192,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) // now store the selection ranges for (i=0; i range; res = aSel->GetRangeAt(i, getter_AddRefs(range)); @@ -214,7 +208,7 @@ nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) if (!aSel) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; PRInt32 i, arrayCount = mArray.Count(); - SelRangeStore *item; + nsRangeStore *item; // clear out selection aSel->ClearSelection(); @@ -222,7 +216,7 @@ nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) // set the selection ranges anew for (i=0; i range; item->GetRange(&range); @@ -239,8 +233,8 @@ PRBool nsSelectionState::IsCollapsed() { if (1 != mArray.Count()) return PR_FALSE; - SelRangeStore *item; - item = (SelRangeStore*)mArray.ElementAt(0); + nsRangeStore *item; + item = (nsRangeStore*)mArray.ElementAt(0); if (!item) return PR_FALSE; nsCOMPtr range; item->GetRange(&range); @@ -258,12 +252,12 @@ nsSelectionState::IsEqual(nsSelectionState *aSelState) if (myCount != itsCount) return PR_FALSE; if (myCount < 1) return PR_FALSE; - SelRangeStore *myItem, *itsItem; + nsRangeStore *myItem, *itsItem; for (i=0; imArray.ElementAt(0)); + myItem = (nsRangeStore*)mArray.ElementAt(0); + itsItem = (nsRangeStore*)(aSelState->mArray.ElementAt(0)); if (!myItem || !itsItem) return PR_FALSE; nsCOMPtr myRange, itsRange; @@ -281,22 +275,137 @@ nsSelectionState::IsEqual(nsSelectionState *aSelState) return PR_TRUE; } -// notification routines used to update the saved selection state in response to -// document editing. +void +nsSelectionState::MakeEmpty() +{ + // free any items in the array + nsRangeStore *item; + while ((item = (nsRangeStore*)mArray.ElementAt(0))) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +PRBool +nsSelectionState::IsEmpty() +{ + return (mArray.Count() == 0); +} + +/*************************************************************************** + * nsRangeUpdater: class for updating nsIDOMRanges in response to editor actions. + */ + +nsRangeUpdater::nsRangeUpdater() : mArray(), mLock(PR_FALSE) {} + +nsRangeUpdater::~nsRangeUpdater() +{ + // free any items in the array + nsRangeStore *item; + while ((item = (nsRangeStore*)mArray.ElementAt(0))) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +void* +nsRangeUpdater::RegisterRange(nsIDOMRange *aRange) +{ + nsRangeStore *item = new nsRangeStore; + if (!item) return nsnull; + item->StoreRange(aRange); + mArray.AppendElement(item); + return item; +} + +nsCOMPtr +nsRangeUpdater::ReclaimRange(void *aCookie) +{ + nsRangeStore *item = NS_STATIC_CAST(nsRangeStore*,aCookie); + if (!item) return nsnull; + nsCOMPtr outRange; + item->GetRange(&outRange); + mArray.RemoveElement(aCookie); + delete item; + return outRange; +} + +void +nsRangeUpdater::DropRange(void *aCookie) +{ + nsRangeStore *item = NS_STATIC_CAST(nsRangeStore*,aCookie); + if (!item) return; + mArray.RemoveElement(aCookie); + delete item; +} + +void +nsRangeUpdater::RegisterRangeItem(nsRangeStore *aRangeItem) +{ + if (!aRangeItem) return; + mArray.AppendElement(aRangeItem); + return; +} + +void +nsRangeUpdater::DropRangeItem(nsRangeStore *aRangeItem) +{ + if (!aRangeItem) return; + mArray.RemoveElement(aRangeItem); + return; +} + +nsresult +nsRangeUpdater::RegisterSelectionState(nsSelectionState &aSelState) +{ + PRInt32 i, theCount = aSelState.mArray.Count(); + if (theCount < 1) return NS_ERROR_FAILURE; + + nsRangeStore *item; + + for (i=0; istartNode.get() == aParent) && (item->startOffset > aPosition)) @@ -308,25 +417,25 @@ nsSelectionState::SelAdjCreateNode(nsIDOMNode *aParent, PRInt32 aPosition) } nsresult -nsSelectionState::SelAdjInsertNode(nsIDOMNode *aParent, PRInt32 aPosition) +nsRangeUpdater::SelAdjInsertNode(nsIDOMNode *aParent, PRInt32 aPosition) { return SelAdjCreateNode(aParent, aPosition); } nsresult -nsSelectionState::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) +nsRangeUpdater::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) { if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... if (!aNode) return NS_ERROR_NULL_POINTER; PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aParent) && (item->startOffset > aOffset)) @@ -341,7 +450,7 @@ nsSelectionState::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt nsresult -nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode) +nsRangeUpdater::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode) { if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... if (!aOldRightNode || !aNewLeftNode) return NS_ERROR_NULL_POINTER; @@ -358,11 +467,11 @@ nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, ns if (NS_FAILED(result)) return result; // next step is to check for range enpoints inside aOldRightNode - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aOldRightNode) @@ -393,7 +502,7 @@ nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, ns nsresult -nsSelectionState::SelAdjJoinNodes(nsIDOMNode *aLeftNode, +nsRangeUpdater::SelAdjJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent, PRInt32 aOffset, @@ -404,11 +513,11 @@ nsSelectionState::SelAdjJoinNodes(nsIDOMNode *aLeftNode, PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aOriginalNode) @@ -501,7 +610,7 @@ nsSelectionState::DidReplaceContainer(nsIDOMNode *aOriginalNode, nsIDOMNode *aNe nsresult -nsSelectionState::WillRemoveContainer() +nsRangeUpdater::WillRemoveContainer() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -510,7 +619,7 @@ nsSelectionState::WillRemoveContainer() nsresult -nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) +nsRangeUpdater::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -519,11 +628,11 @@ nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRI PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aNode) @@ -546,7 +655,7 @@ nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRI nsresult -nsSelectionState::WillInsertContainer() +nsRangeUpdater::WillInsertContainer() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -555,7 +664,7 @@ nsSelectionState::WillInsertContainer() nsresult -nsSelectionState::DidInsertContainer() +nsRangeUpdater::DidInsertContainer() { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -564,7 +673,7 @@ nsSelectionState::DidInsertContainer() nsresult -nsSelectionState::WillMoveNode() +nsRangeUpdater::WillMoveNode() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -573,7 +682,7 @@ nsSelectionState::WillMoveNode() nsresult -nsSelectionState::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) +nsRangeUpdater::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -582,11 +691,11 @@ nsSelectionState::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOM PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; iGetStartContainer(getter_AddRefs(startNode)); @@ -620,7 +740,7 @@ nsresult SelRangeStore::StoreRange(nsIDOMRange *aRange) return NS_OK; } -nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) +nsresult nsRangeStore::GetRange(nsCOMPtr *outRange) { if (!outRange) return NS_ERROR_NULL_POINTER; nsresult res = nsComponentManager::CreateInstance(kCRangeCID, @@ -644,22 +764,22 @@ nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) class nsAutoReplaceContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mOriginalNode; nsIDOMNode *mNewNode; public: - nsAutoReplaceContainerSelNotify(nsSelectionState *aSelState, nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode) : - mSel(aSelState) + nsAutoReplaceContainerSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode) : + mRU(aRangeUpdater) ,mOriginalNode(aOriginalNode) ,mNewNode(aNewNode) { - if (mSel) mSel->WillReplaceContainer(); + mRU.WillReplaceContainer(); } ~nsAutoReplaceContainerSelNotify() { - if (mSel) mSel->DidReplaceContainer(mOriginalNode, mNewNode); + mRU.DidReplaceContainer(mOriginalNode, mNewNode); } }; @@ -672,30 +792,30 @@ class nsAutoReplaceContainerSelNotify class nsAutoRemoveContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mNode; nsIDOMNode *mParent; PRInt32 mOffset; PRUint32 mNodeOrigLen; public: - nsAutoRemoveContainerSelNotify(nsSelectionState *aSelState, + nsAutoRemoveContainerSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) : - mSel(aSelState) + mRU(aRangeUpdater) ,mNode(aNode) ,mParent(aParent) ,mOffset(aOffset) ,mNodeOrigLen(aNodeOrigLen) { - if (mSel) mSel->WillRemoveContainer(); + mRU.WillRemoveContainer(); } ~nsAutoRemoveContainerSelNotify() { - if (mSel) mSel->DidRemoveContainer(mNode, mParent, mOffset, mNodeOrigLen); + mRU.DidRemoveContainer(mNode, mParent, mOffset, mNodeOrigLen); } }; @@ -707,18 +827,18 @@ class nsAutoRemoveContainerSelNotify class nsAutoInsertContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; public: - nsAutoInsertContainerSelNotify(nsSelectionState *aSelState) : - mSel(aSelState) + nsAutoInsertContainerSelNotify(nsRangeUpdater &aRangeUpdater) : + mRU(aRangeUpdater) { - if (mSel) mSel->WillInsertContainer(); + mRU.WillInsertContainer(); } ~nsAutoInsertContainerSelNotify() { - if (mSel) mSel->DidInsertContainer(); + mRU.DidInsertContainer(); } }; @@ -731,30 +851,30 @@ class nsAutoInsertContainerSelNotify class nsAutoMoveNodeSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mOldParent; nsIDOMNode *mNewParent; PRInt32 mOldOffset; PRInt32 mNewOffset; public: - nsAutoMoveNodeSelNotify(nsSelectionState *aSelState, + nsAutoMoveNodeSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) : - mSel(aSelState) + mRU(aRangeUpdater) ,mOldParent(aOldParent) ,mNewParent(aNewParent) ,mOldOffset(aOldOffset) ,mNewOffset(aNewOffset) { - if (mSel) mSel->WillMoveNode(); + mRU.WillMoveNode(); } ~nsAutoMoveNodeSelNotify() { - if (mSel) mSel->DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset); + mRU.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset); } }; @@ -774,7 +894,8 @@ nsEditor::nsEditor() , mPlaceHolderName(nsnull) , mPlaceHolderBatch(0) , mSelState(nsnull) -, mSavedSel(nsnull) +, mSavedSel() +, mRangeUpdater() , mShouldTxnSetSelection(PR_TRUE) , mBodyElement(nsnull) , mInIMEMode(PR_FALSE) @@ -1595,7 +1716,7 @@ NS_IMETHODIMP nsEditor::CreateNode(const nsString& aTag, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjCreateNode(aParent, aPosition); + mRangeUpdater.SelAdjCreateNode(aParent, aPosition); if (mActionListeners) { @@ -1637,7 +1758,7 @@ NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjInsertNode(aParent, aPosition); + mRangeUpdater.SelAdjInsertNode(aParent, aPosition); if (mActionListeners) { @@ -1686,7 +1807,7 @@ nsEditor::SplitNode(nsIDOMNode * aNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); + mRangeUpdater.SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); if (mActionListeners) { @@ -1743,7 +1864,7 @@ nsEditor::JoinNodes(nsIDOMNode * aLeftNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (PRInt32)oldLeftNodeLen); + mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (PRInt32)oldLeftNodeLen); if (mActionListeners) { @@ -1789,7 +1910,7 @@ NS_IMETHODIMP nsEditor::DeleteNode(nsIDOMNode * aElement) // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjDeleteNode(aElement, parent, offset); + mRangeUpdater.SelAdjDeleteNode(aElement, parent, offset); if (mActionListeners) { @@ -1856,8 +1977,8 @@ nsEditor::ReplaceContainer(nsIDOMNode *inNode, // notify our internal selection state listener // (Note: A nsAutoSelectionReset object must be created - // before calling this to initialize mSavedSel) - nsAutoReplaceContainerSelNotify selStateNotify(mSavedSel, inNode, *outNode); + // before calling this to initialize mRangeUpdater) + nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, inNode, *outNode); nsCOMPtr child; PRBool bHasMoreChildren; @@ -1908,7 +2029,7 @@ nsEditor::RemoveContainer(nsIDOMNode *inNode) nodeList->GetLength(&nodeOrigLen); // notify our internal selection state listener - nsAutoRemoveContainerSelNotify selNotify(mSavedSel, inNode, parent, offset, nodeOrigLen); + nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, inNode, parent, offset, nodeOrigLen); nsCOMPtr child; while (bHasMoreChildren) @@ -1970,7 +2091,7 @@ nsEditor::InsertContainerAbove( nsIDOMNode *inNode, } // notify our internal selection state listener - nsAutoInsertContainerSelNotify selNotify(mSavedSel); + nsAutoInsertContainerSelNotify selNotify(mRangeUpdater); // put inNode in new parent, outNode res = DeleteNode(inNode); @@ -2009,7 +2130,7 @@ nsEditor::MoveNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) if ((aParent == oldParent.get()) && (oldOffset == aOffset)) return NS_OK; // notify our internal selection state listener - nsAutoMoveNodeSelNotify selNotify(mSavedSel, oldParent, oldOffset, aParent, aOffset); + nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset, aParent, aOffset); // need to adjust aOffset if we are moving aNode further along in it's current parent if ((aParent == oldParent.get()) && (oldOffset < aOffset)) @@ -2250,6 +2371,44 @@ nsEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed) return NS_OK; } +#ifdef XP_MAC +#pragma mark - +#pragma mark support for selection preservation +#pragma mark - +#endif + +PRBool +nsEditor::ArePreservingSelection() +{ + if (mSavedSel.IsEmpty()) return PR_FALSE; + return PR_TRUE; +} + +nsresult +nsEditor::PreserveSelectionAcrossActions(nsIDOMSelection *aSel) +{ + mSavedSel.SaveSelection(aSel); + mRangeUpdater.RegisterSelectionState(mSavedSel); + return NS_OK; +} + +nsresult +nsEditor::RestorePreservedSelection(nsIDOMSelection *aSel) +{ + if (mSavedSel.IsEmpty()) return NS_ERROR_FAILURE; + mSavedSel.RestoreSelection(aSel); + StopPreservingSelection(); + return NS_OK; +} + +void +nsEditor::StopPreservingSelection() +{ + mRangeUpdater.DropSelectionState(mSavedSel); + mSavedSel.MakeEmpty(); +} + + #ifdef XP_MAC #pragma mark - #pragma mark nsIEditorIMESupport @@ -4985,6 +5144,7 @@ nsEditor::SplitNodeDeep(nsIDOMNode *aNode, nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset, PRInt32 *outOffset, + PRBool aNoEmptyContainers, nsCOMPtr *outLeftNode, nsCOMPtr *outRightNode) { @@ -5006,12 +5166,12 @@ nsEditor::SplitNodeDeep(nsIDOMNode *aNode, // this nsEditor routine. nsCOMPtr nodeAsText = do_QueryInterface(nodeToSplit); - PRUint32 textLen=0; - if (nodeAsText) - nodeAsText->GetLength(&textLen); + PRUint32 len; PRBool bDoSplit = PR_FALSE; + res = GetLengthOfDOMNode(nodeToSplit, len); + if (NS_FAILED(res)) return res; - if (!nodeAsText || (offset && (offset != (PRInt32)textLen))) + if (!(aNoEmptyContainers || nodeAsText) || (offset && (offset != (PRInt32)len))) { bDoSplit = PR_TRUE; res = SplitNode(nodeToSplit, offset, getter_AddRefs(tempNode)); diff --git a/mozilla/editor/base/nsEditor.h b/mozilla/editor/base/nsEditor.h index 8b24c510043..59ce695d7b6 100644 --- a/mozilla/editor/base/nsEditor.h +++ b/mozilla/editor/base/nsEditor.h @@ -80,8 +80,10 @@ class nsISelectionController; */ // first a helper struct for saving/setting ranges -struct SelRangeStore +struct nsRangeStore { + nsRangeStore(); + ~nsRangeStore(); nsresult StoreRange(nsIDOMRange *aRange); nsresult GetRange(nsCOMPtr *outRange); @@ -89,6 +91,7 @@ struct SelRangeStore PRInt32 startOffset; nsCOMPtr endNode; PRInt32 endOffset; + // DEBUG: static PRInt32 n; }; class nsSelectionState @@ -104,6 +107,26 @@ class nsSelectionState PRBool IsEqual(nsSelectionState *aSelState); void MakeEmpty(); PRBool IsEmpty(); + protected: + nsVoidArray mArray; + + friend class nsRangeUpdater; +}; + +class nsRangeUpdater +{ + public: + + nsRangeUpdater(); + ~nsRangeUpdater(); + + void* RegisterRange(nsIDOMRange *aRange); + nsCOMPtr ReclaimRange(void *aCookie); + void DropRange(void *aCookie); + void RegisterRangeItem(nsRangeStore *aRangeItem); + void DropRangeItem(nsRangeStore *aRangeItem); + nsresult RegisterSelectionState(nsSelectionState &aSelState); + nsresult DropSelectionState(nsSelectionState &aSelState); // editor selection gravity routines. Note that we can't always depend on // DOM Range gravity to do what we want to the "real" selection. For instance, @@ -131,7 +154,7 @@ class nsSelectionState nsresult DidInsertContainer(); nsresult WillMoveNode(); nsresult DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset); - + protected: nsVoidArray mArray; PRBool mLock; }; @@ -483,6 +506,14 @@ public: * with a call to EndOperation */ NS_IMETHOD EndOperation(); + /** routines for managing the preservation of selection across + * various editor actions */ + PRBool ArePreservingSelection(); + nsresult PreserveSelectionAcrossActions(nsIDOMSelection *aSel); + nsresult RestorePreservedSelection(nsIDOMSelection *aSel); + void StopPreservingSelection(); + + /** return the string that represents text nodes in the content tree */ static nsresult GetTextNodeTag(nsString& aOutString); @@ -725,6 +756,7 @@ public: nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset, PRInt32 *outOffset, + PRBool aNoEmptyContainers = PR_FALSE, nsCOMPtr *outLeftNode = 0, nsCOMPtr *outRightNode = 0); nsresult JoinNodeDeep(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsCOMPtr *aOutJoinNode, PRInt32 *outOffset); @@ -748,15 +780,16 @@ protected: nsCOMPtr mTxnMgr; nsCOMPtr mEditProperty; nsCOMPtr mLastStyleSheet; // is owning this dangerous? - nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes - nsIAtom *mPlaceHolderName; // name of placeholder transaction - PRInt32 mPlaceHolderBatch; // nesting count for batching - nsSelectionState *mSelState; // saved selection state for placeholder txn batching - nsSelectionState *mSavedSel; // cached selection for nsAutoSelectionReset - PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns + nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes + nsIAtom *mPlaceHolderName; // name of placeholder transaction + PRInt32 mPlaceHolderBatch; // nesting count for batching + nsSelectionState *mSelState; // saved selection state for placeholder txn batching + nsSelectionState mSavedSel; // cached selection for nsAutoSelectionReset + nsRangeUpdater mRangeUpdater; // utility class object for maintaining preserved ranges + PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns nsCOMPtr mBodyElement; // cached body node - PRInt32 mAction; // the current editor action - EDirection mDirection; // the current direction of editor action + PRInt32 mAction; // the current editor action + EDirection mDirection; // the current direction of editor action // data necessary to build IME transactions PRBool mInIMEMode; // are we inside an IME composition? diff --git a/mozilla/editor/base/nsEditorUtils.cpp b/mozilla/editor/base/nsEditorUtils.cpp index 982337fb017..00f80836790 100644 --- a/mozilla/editor/base/nsEditorUtils.cpp +++ b/mozilla/editor/base/nsEditorUtils.cpp @@ -23,6 +23,9 @@ #include "nsEditorUtils.h" #include "nsIDOMDocument.h" +#include "nsIDOMRange.h" +#include "nsIContent.h" +#include "nsLayoutCID.h" /****************************************************************************** @@ -34,24 +37,162 @@ mSel(nsnull) ,mEd(nsnull) { if (!aSel || !aEd) return; // not much we can do, bail. - if (aEd->mSavedSel) return; // we already have initted mSavedSel, so this must be nested call. + if (aEd->ArePreservingSelection()) return; // we already have initted mSavedSel, so this must be nested call. mSel = do_QueryInterface(aSel); mEd = aEd; if (mSel) { - mEd->mSavedSel = new nsSelectionState(); - mEd->mSavedSel->SaveSelection(mSel); + mEd->PreserveSelectionAcrossActions(mSel); } } nsAutoSelectionReset::~nsAutoSelectionReset() { - if (mSel && mEd->mSavedSel) // mSel will be null if this was nested call + if (mSel && mEd->ArePreservingSelection()) // mSel will be null if this was nested call { - mEd->mSavedSel->RestoreSelection(mSel); - delete mEd->mSavedSel; - mEd->mSavedSel = nsnull; + mEd->RestorePreservedSelection(mSel); } } +void +nsAutoSelectionReset::Abort() +{ + mEd->StopPreservingSelection(); +} + + +/****************************************************************************** + * some helper classes for iterating the dom tree + *****************************************************************************/ + +static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); +static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID); + +nsDOMIterator::nsDOMIterator() : +mIter(nsnull) +{ +} + +nsDOMIterator::~nsDOMIterator() +{ +} + +nsresult +nsDOMIterator::Init(nsIDOMRange* aRange) +{ + nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + return mIter->Init(aRange); +} + +nsresult +nsDOMIterator::Init(nsIDOMNode* aNode) +{ + nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + nsCOMPtr content = do_QueryInterface(aNode); + return mIter->Init(content); +} + +void +nsDOMIterator::ForEach(nsDomIterFunctor& functor) const +{ + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // iterate through dom + while (NS_ENUMERATOR_FALSE == mIter->IsDone()) + { + res = mIter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return; + node = do_QueryInterface(content); + if (!node) return; + functor(node); + res = mIter->Next(); + if (NS_FAILED(res)) return; + } +} + +nsresult +nsDOMIterator::MakeList(nsBoolDomIterFunctor& functor, + nsCOMPtr *outArrayOfNodes) const +{ + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // make a array + res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); + if (NS_FAILED(res)) return res; + + return AppendList(functor, *outArrayOfNodes); +} + +nsresult +nsDOMIterator::AppendList(nsBoolDomIterFunctor& functor, + nsCOMPtr arrayOfNodes) const +{ + if (!arrayOfNodes) return NS_ERROR_NULL_POINTER; + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // iterate through dom and build list + while (NS_ENUMERATOR_FALSE == mIter->IsDone()) + { + res = mIter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return res; + node = do_QueryInterface(content); + if (!node) return NS_ERROR_NULL_POINTER; + if (functor(node)) + { + isupports = do_QueryInterface(node); + arrayOfNodes->AppendElement(isupports); + } + res = mIter->Next(); + if (NS_FAILED(res)) return res; + } + return NS_OK; +} + +nsDOMSubtreeIterator::nsDOMSubtreeIterator() +{ +} + +nsDOMSubtreeIterator::~nsDOMSubtreeIterator() +{ +} + +nsresult +nsDOMSubtreeIterator::Init(nsIDOMRange* aRange) +{ + nsresult res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + return mIter->Init(aRange); +} + +nsresult +nsDOMSubtreeIterator::Init(nsIDOMNode* aNode) +{ + nsresult res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + nsCOMPtr content = do_QueryInterface(aNode); + return mIter->Init(content); +} diff --git a/mozilla/editor/base/nsEditorUtils.h b/mozilla/editor/base/nsEditorUtils.h index d5fc7a2a69d..9932e49a1c3 100644 --- a/mozilla/editor/base/nsEditorUtils.h +++ b/mozilla/editor/base/nsEditorUtils.h @@ -32,6 +32,7 @@ #include "nsIAtom.h" #include "nsVoidArray.h" #include "nsEditor.h" +#include "nsIContentIterator.h" /*************************************************************************** * stack based helper class for batching a collection of txns inside a @@ -77,6 +78,9 @@ class nsAutoSelectionReset /** destructor restores mSel to its former state */ ~nsAutoSelectionReset(); + + /** Abort: cancel selection saver */ + void Abort(); }; /*************************************************************************** @@ -139,5 +143,47 @@ class nsAutoTxnsConserveSelection PRBool mOldState; }; +/****************************************************************************** + * some helper classes for iterating the dom tree + *****************************************************************************/ + +class nsDomIterFunctor +{ + public: + virtual void* operator()(nsIDOMNode* aNode)=0; +}; + +class nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode)=0; +}; + +class nsDOMIterator +{ + public: + nsDOMIterator(); + virtual ~nsDOMIterator(); + + nsresult Init(nsIDOMRange* aRange); + nsresult Init(nsIDOMNode* aNode); + void ForEach(nsDomIterFunctor& functor) const; + nsresult MakeList(nsBoolDomIterFunctor& functor, + nsCOMPtr *outArrayOfNodes) const; + nsresult AppendList(nsBoolDomIterFunctor& functor, + nsCOMPtr arrayOfNodes) const; + protected: + nsCOMPtr mIter; +}; + +class nsDOMSubtreeIterator : public nsDOMIterator +{ + public: + nsDOMSubtreeIterator(); + virtual ~nsDOMSubtreeIterator(); + + nsresult Init(nsIDOMRange* aRange); + nsresult Init(nsIDOMNode* aNode); +}; #endif // nsEditorUtils_h__ diff --git a/mozilla/editor/base/nsHTMLEditRules.cpp b/mozilla/editor/base/nsHTMLEditRules.cpp index 258f1811360..7868bf8cdd9 100644 --- a/mozilla/editor/base/nsHTMLEditRules.cpp +++ b/mozilla/editor/base/nsHTMLEditRules.cpp @@ -51,7 +51,6 @@ //const static char* kMOZEditorBogusNodeValue="TRUE"; const static PRUnichar nbsp = 160; -static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID); static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID); @@ -64,6 +63,82 @@ enum kBothSibs = 3 }; +/******************************************************** + * first some helpful funcotrs we will use + ********************************************************/ + + +class nsTrivialFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers + { + return PR_TRUE; + } +}; + +class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers + { + if (nsHTMLEditUtils::IsTableCell(aNode)) return PR_TRUE; + if (nsHTMLEditUtils::IsListItem(aNode)) return PR_TRUE; + return PR_FALSE; + } +}; + +class nsBRNodeFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all td's & th's iterator covers + { + if (nsHTMLEditUtils::IsBreak(aNode)) return PR_TRUE; + return PR_FALSE; + } +}; + +class nsEmptyFunctor : public nsBoolDomIterFunctor +{ + public: + nsEmptyFunctor(nsHTMLEditor* editor) : mEditor(editor) {} + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of empty li's and td's + { + PRBool bIsEmptyNode; + nsresult res = mEditor->IsEmptyNode(aNode, &bIsEmptyNode, PR_FALSE, PR_FALSE); + if (NS_FAILED(res)) return PR_FALSE; + if (bIsEmptyNode + && (nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCellOrCaption(aNode))) + { + return PR_TRUE; + } + return PR_FALSE; + } + protected: + nsHTMLEditor* mEditor; +}; + +class nsEditableTextFunctor : public nsBoolDomIterFunctor +{ + public: + nsEditableTextFunctor(nsHTMLEditor* editor) : mEditor(editor) {} + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of empty li's and td's + { + if (nsEditor::IsTextNode(aNode) && mEditor->IsEditable(aNode)) + { + return PR_TRUE; + } + return PR_FALSE; + } + protected: + nsHTMLEditor* mEditor; +}; + + +/******************************************************** + * routine for making new rules instance + ********************************************************/ + nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult) { @@ -361,6 +436,9 @@ nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, switch (info->action) { case kMakeBasicBlock: + case kIndent: + case kOutdent: + case kAlign: return DidMakeBasicBlock(aSelection, aInfo, aResult); } @@ -613,13 +691,31 @@ nsHTMLEditRules::GetParagraphState(PRBool &aMixed, nsString &outFormat) nsresult res = GetParagraphFormatNodes(&arrayOfNodes, PR_TRUE); if (NS_FAILED(res)) return res; - // loop through the nodes in selection and examine their paragraph format + // we might have an empty node list. if so, find selection parent + // and put that on the list PRUint32 listCount; - PRInt32 i; arrayOfNodes->Count(&listCount); + nsCOMPtr isupports; + if (!listCount) + { + nsCOMPtr selNode; + PRInt32 selOffset; + nsCOMPtrselection; + nsresult res = mEditor->GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + res = mEditor->GetStartNodeAndOffset(selection, &selNode, &selOffset); + if (NS_FAILED(res)) return res; + isupports = do_QueryInterface(selNode); + if (!isupports) return NS_ERROR_NULL_POINTER; + arrayOfNodes->AppendElement(isupports); + listCount = 1; + } + + // loop through the nodes in selection and examine their paragraph format + PRInt32 i; for (i=(PRInt32)listCount-1; i>=0; i--) { - nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); + isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports) ); nsAutoString format; @@ -1331,7 +1427,8 @@ nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, if (NS_FAILED(res)) return res; } - if (nsHTMLEditUtils::IsBreak(maybeBreak) && nsEditor::IsBlockNode(maybeBlock)) + if (maybeBreak && maybeBlock && + nsHTMLEditUtils::IsBreak(maybeBreak) && nsEditor::IsBlockNode(maybeBlock)) nodeToDelete = maybeBreak; else if (aAction == nsIEditor::ePrevious) res = mEditor->GetPriorHTMLNode(node, offset, &nodeToDelete); @@ -1524,10 +1621,27 @@ nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, else itemType.AssignWithConversion("li"); - PRBool outMakeEmpty; - res = ShouldMakeEmptyBlock(aSelection, aListType, &outMakeEmpty); + // ok, we aren't creating a new empty list. Instead we are converting + // the set of blocks implied by the selection into a list. + + // convert the selection ranges into "promoted" selection ranges: + // this basically just expands the range to include the immediate + // block parent, and then further expands to include any ancestors + // whose children are all in the range + + *aHandled = PR_TRUE; + + nsAutoSelectionReset selectionResetter(aSelection, mEditor); + + nsCOMPtr arrayOfNodes; + res = GetListActionNodes(&arrayOfNodes); if (NS_FAILED(res)) return res; - if (outMakeEmpty) + + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + + // if no nodes, we make empty list. + if (!listCount) { nsCOMPtr parent, theList, theListItem; PRInt32 offset; @@ -1545,30 +1659,13 @@ nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, if (NS_FAILED(res)) return res; // put selection in new list item res = aSelection->Collapse(theListItem,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = PR_TRUE; return res; } - - // ok, we aren't creating a new empty list. Instead we are converting - // the set of blocks implied by the selection into a list. - - // convert the selection ranges into "promoted" selection ranges: - // this basically just expands the range to include the immediate - // block parent, and then further expands to include any ancestors - // whose children are all in the range - - *aHandled = PR_TRUE; - nsAutoSelectionReset selectionResetter(aSelection, mEditor); - - nsCOMPtr arrayOfNodes; - res = GetListActionNodes(&arrayOfNodes); - if (NS_FAILED(res)) return res; - // if there is only one node in the array, and it is a list, div, or blockquote, // then look inside of it until we find what we want to make a list out of. - PRUint32 listCount; - arrayOfNodes->Count(&listCount); if (listCount == 1) { nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); @@ -1915,14 +2012,6 @@ nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; - - PRBool makeEmpty; - res = ShouldMakeEmptyBlock(aSelection, aBlockType, &makeEmpty); - if (NS_FAILED(res)) return res; - - if (makeEmpty) return res; // just insert a new empty block - - // else it's not that easy... nsAutoSelectionReset selectionResetter(aSelection, mEditor); nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); *aHandled = PR_TRUE; @@ -1936,13 +2025,40 @@ nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeBasicBlock); if (NS_FAILED(res)) return res; - // Ok, now go through all the nodes and make the right kind of blocks, - // or whatever is approriate. Wohoo! - // Note: blockquote is handled a little differently - if (aBlockType->EqualsWithConversion("blockquote")) - res = MakeBlockquote(arrayOfNodes); + // if no nodes, we make empty block. + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) + { + nsCOMPtr parent, theBlock; + PRInt32 offset; + + // get selection location + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + + // make sure we can put a block here + res = SplitAsNeeded(aBlockType, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock)); + if (NS_FAILED(res)) return res; + // put selection in new block + res = aSelection->Collapse(theBlock,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. + *aHandled = PR_TRUE; + return res; + } else - res = ApplyBlockStyle(arrayOfNodes, aBlockType); + { + // Ok, now go through all the nodes and make the right kind of blocks, + // or whatever is approriate. Wohoo! + // Note: blockquote is handled a little differently + if (aBlockType->EqualsWithConversion("blockquote")) + res = MakeBlockquote(arrayOfNodes); + else + res = ApplyBlockStyle(arrayOfNodes, aBlockType); + return res; + } return res; } @@ -1994,6 +2110,31 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kIndent); if (NS_FAILED(res)) return res; + // if no nodes, we make empty block. + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) + { + nsCOMPtr parent, theBlock; + PRInt32 offset; + nsAutoString quoteType; quoteType.AssignWithConversion("blockquote"); + + // get selection location + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + + // make sure we can put a block here + res = SplitAsNeeded("eType, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); + if (NS_FAILED(res)) return res; + // put selection in new block + res = aSelection->Collapse(theBlock,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. + *aHandled = PR_TRUE; + return res; + } + // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. @@ -2004,9 +2145,7 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! - PRUint32 listCount; PRInt32 i; - arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curQuote; nsCOMPtr curList; @@ -2045,7 +2184,7 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool { // need to make a blockquote to put things in if we haven't already, // or if this node doesn't go in blockquote we used earlier. - if (!curQuote || transitionList[i]) + if (!curQuote) // || transitionList[i]) { nsAutoString quoteType; quoteType.AssignWithConversion("blockquote"); res = SplitAsNeeded("eType, &curParent, &offset); @@ -2352,11 +2491,26 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, *aCancel = PR_FALSE; *aHandled = PR_FALSE; + nsAutoSelectionReset selectionResetter(aSelection, mEditor); - PRBool outMakeEmpty; - res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty); + // convert the selection ranges into "promoted" selection ranges: + // this basically just expands the range to include the immediate + // block parent, and then further expands to include any ancestors + // whose children are all in the range + *aHandled = PR_TRUE; + + nsCOMPtr arrayOfRanges; + res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign); if (NS_FAILED(res)) return res; - if (outMakeEmpty) + + // use these ranges to contruct a list of nodes to act on. + nsCOMPtr arrayOfNodes; + res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign); + if (NS_FAILED(res)) return res; + + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) { PRInt32 offset; nsCOMPtr brNode, parent, theDiv, sib; @@ -2369,7 +2523,7 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, // creating extra lines, if possible. res = mEditor->GetNextHTMLNode(parent, offset, &brNode); if (NS_FAILED(res)) return res; - if (nsHTMLEditUtils::IsBreak(brNode)) + if (brNode && nsHTMLEditUtils::IsBreak(brNode)) { // making use of html structure... if next node after where // we are putting our div is not a block, then the br we @@ -2394,26 +2548,10 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, res = CreateMozBR(theDiv, 0, &brNode); if (NS_FAILED(res)) return res; res = aSelection->Collapse(theDiv, 0); + selectionResetter.Abort(); // dont reset our selection in this case. return res; } - nsAutoSelectionReset selectionResetter(aSelection, mEditor); - - // convert the selection ranges into "promoted" selection ranges: - // this basically just expands the range to include the immediate - // block parent, and then further expands to include any ancestors - // whose children are all in the range - *aHandled = PR_TRUE; - - nsCOMPtr arrayOfRanges; - res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign); - if (NS_FAILED(res)) return res; - - // use these ranges to contruct a list of nodes to act on. - nsCOMPtr arrayOfNodes; - res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign); - if (NS_FAILED(res)) return res; - // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. @@ -2424,9 +2562,7 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, // Ok, now go through all the nodes and give them an align attrib or put them in a div, // or whatever is appropriate. Wohoo! - PRUint32 listCount; PRInt32 i; - arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curDiv; for (i=0; i<(PRInt32)listCount; i++) @@ -2450,11 +2586,12 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, continue; } - // if it's a table element (but not a table), forget any "current" div, and - // instead put divs inside any th's and td's inside the table element - if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode)) + // if it's a table element (but not a table) or a list item, forget any "current" div, and + // instead put divs inside the appropriate block (td, li, etc) + if ( (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode)) + || nsHTMLEditUtils::IsListItem(curNode) ) { - res = AlignTableElement(curNode, alignType); + res = AlignInnerBlocks(curNode, alignType); if (NS_FAILED(res)) return res; // clear out curDiv so that we don't put nodes after this one into it curDiv = 0; @@ -2491,56 +2628,35 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, /////////////////////////////////////////////////////////////////////////// -// AlignTableElement: take a table element and align it's contents -// +// AlignInnerBlocks: align inside table cells or list items +// nsresult -nsHTMLEditRules::AlignTableElement(nsIDOMNode *aNode, const nsString *alignType) +nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsString *alignType) { if (!aNode || !alignType) return NS_ERROR_NULL_POINTER; nsresult res; - nsCOMPtr iter; - res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); - if (NS_FAILED(res)) return res; - if (!iter) return NS_ERROR_FAILURE; - + + // gather list of table cells or list items nsCOMPtr arrayOfNodes; - nsCOMPtr content; - nsCOMPtr node; - nsCOMPtr isupports; - - // make a array - res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + nsTableCellAndListItemFunctor functor; + nsDOMIterator iter; + res = iter.Init(aNode); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &arrayOfNodes); if (NS_FAILED(res)) return res; - - // iterate node and build up array of td's and th's - content = do_QueryInterface(aNode); - iter->Init(content); - while (NS_ENUMERATOR_FALSE == iter->IsDone()) - { - res = iter->CurrentNode(getter_AddRefs(content)); - if (NS_FAILED(res)) return res; - node = do_QueryInterface(content); - if (!node) return NS_ERROR_FAILURE; - if (nsHTMLEditUtils::IsTableCell(node)) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - res = iter->Next(); - if (NS_FAILED(res)) return res; - } // now that we have the list, align their contents as requested PRUint32 listCount; PRUint32 j; + nsCOMPtr node; + nsCOMPtr isupports; + arrayOfNodes->Count(&listCount); for (j = 0; j < listCount; j++) { isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); node = do_QueryInterface(isupports); - res = AlignTableCellContents(node, alignType); + res = AlignBlockContents(node, alignType); if (NS_FAILED(res)) return res; arrayOfNodes->RemoveElementAt(0); } @@ -2550,10 +2666,10 @@ nsHTMLEditRules::AlignTableElement(nsIDOMNode *aNode, const nsString *alignType) /////////////////////////////////////////////////////////////////////////// -// AlignTableElement: take a table cell and align it's contents +// AlignBlockContents: align contents of a block element // nsresult -nsHTMLEditRules::AlignTableCellContents(nsIDOMNode *aNode, const nsString *alignType) +nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsString *alignType) { if (!aNode || !alignType) return NS_ERROR_NULL_POINTER; nsresult res; @@ -2578,7 +2694,7 @@ nsHTMLEditRules::AlignTableCellContents(nsIDOMNode *aNode, const nsString *align } else { - // else we need to put in a div, set the alignment, and toss in al the children + // else we need to put in a div, set the alignment, and toss in all the children nsAutoString divType; divType.AssignWithConversion("div"); res = mEditor->CreateNode(divType, aNode, 0, getter_AddRefs(divNode)); if (NS_FAILED(res)) return res; @@ -2862,16 +2978,20 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); - nsCOMPtr prevNode; - prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); - while (prevNode) + nsCOMPtr prevNode, prevNodeBlock; + res = mEditor->GetPriorHTMLNode(node, &prevNode); + + while (prevNode && NS_SUCCEEDED(res)) { + prevNodeBlock = nsEditor::GetBlockNodeParent(prevNode); + if (prevNodeBlock != block) + break; if (nsHTMLEditUtils::IsBreak(prevNode)) break; if (nsEditor::IsBlockNode(prevNode)) break; node = prevNode; - prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); + res = mEditor->GetPriorHTMLNode(node, &prevNode); } } @@ -2883,6 +3003,10 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (NS_FAILED(res)) return res; while ((IsFirstNode(node)) && (!nsHTMLEditUtils::IsBody(parent))) { + // special case for outdent: don't keep looking up + // if we have found a blockquote element to act on + if ((actionID == kOutdent) && nsHTMLEditUtils::IsBlockquote(parent)) + break; node = parent; res = nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; @@ -2915,10 +3039,14 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); - nsCOMPtr nextNode; - nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); - while (nextNode) + nsCOMPtr nextNode, nextNodeBlock; + res = mEditor->GetNextHTMLNode(node, &nextNode); + + while (nextNode && NS_SUCCEEDED(res)) { + nextNodeBlock = nsEditor::GetBlockNodeParent(nextNode); + if (nextNodeBlock != block) + break; if (nsHTMLEditUtils::IsBreak(nextNode)) { node = nextNode; @@ -2927,7 +3055,7 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (nsEditor::IsBlockNode(nextNode)) break; node = nextNode; - nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); + res = mEditor->GetNextHTMLNode(node, &nextNode); } } @@ -3053,6 +3181,7 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, { if (!inArrayOfRanges || !outArrayOfNodes) return NS_ERROR_NULL_POINTER; + // make a array nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); if (NS_FAILED(res)) return res; @@ -3063,38 +3192,62 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, PRInt32 i; nsCOMPtr opRange; nsCOMPtr isupports; - nsCOMPtr iter; + // bust up any inlines that cross our range endpoints, + // but only if we are allowed to touch content. + + if (!aDontTouchContent) + { + nsVoidArray rangeItemArray; + // first register ranges for special editor gravity + for (i = 0; i < (PRInt32)rangeCount; i++) + { + isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i)); + opRange = do_QueryInterface(isupports); + nsRangeStore *item = new nsRangeStore(); + if (!item) return NS_ERROR_NULL_POINTER; + item->StoreRange(opRange); + mEditor->mRangeUpdater.RegisterRangeItem(item); + rangeItemArray.AppendElement((void*)item); + inArrayOfRanges->RemoveElementAt(0); + } + // now bust up inlines + for (i = (PRInt32)rangeCount-1; i >= 0; i--) + { + nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(i); + res = BustUpInlinesAtRangeEndpoints(*item); + if (NS_FAILED(res)) return res; + } + // then unregister the ranges + for (i = 0; i < (PRInt32)rangeCount; i++) + { + nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(0); + if (!item) return NS_ERROR_NULL_POINTER; + mEditor->mRangeUpdater.DropRangeItem(item); + res = item->GetRange(&opRange); + if (NS_FAILED(res)) return res; + nsCOMPtr isupports = do_QueryInterface(opRange); + inArrayOfRanges->AppendElement(isupports); + } + } + // gather up a list of all the nodes for (i = 0; i < (PRInt32)rangeCount; i++) { isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i)); opRange = do_QueryInterface(isupports); - res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, - nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); - if (NS_FAILED(res)) return res; - res = iter->Init(opRange); - if (NS_FAILED(res)) return res; - 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; - isupports = do_QueryInterface(node); - (*outArrayOfNodes)->AppendElement(isupports); - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - } - // post process the list to break up inline containers that contain br's. - // but only for operations that might care, like making lists or para's... + nsTrivialFunctor functor; + nsDOMSubtreeIterator iter; + res = iter.Init(opRange); + if (NS_FAILED(res)) return res; + res = iter.AppendList(functor, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + + // certain operations should not act on li's and td's, but rather inside + // them. alter the list as needed if ( (inOperationType == kMakeBasicBlock) || - (inOperationType == kMakeList) ) + (inOperationType == kAlign) ) { PRUint32 listCount; (*outArrayOfNodes)->Count(&listCount); @@ -3102,7 +3255,52 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, { nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); nsCOMPtr node( do_QueryInterface(isupports) ); - if (!aDontTouchContent && mEditor->IsInlineNode(node) && mEditor->IsContainer(node)) + if ( (nsHTMLEditUtils::IsTableElement(node) && !nsHTMLEditUtils::IsTable(node)) + || (nsHTMLEditUtils::IsListItem(node))) + { + (*outArrayOfNodes)->RemoveElementAt(i); + res = GetInnerContent(node, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + } + } + // indent/outdent already do something special for list items, but + // we still need to make sure we dont act on table elements + else if ( (inOperationType == kOutdent) || + (inOperationType == kIndent) ) + { + PRUint32 listCount; + (*outArrayOfNodes)->Count(&listCount); + for (i=(PRInt32)listCount-1; i>=0; i--) + { + nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); + nsCOMPtr node( do_QueryInterface(isupports) ); + if ( (nsHTMLEditUtils::IsTableElement(node) && !nsHTMLEditUtils::IsTable(node)) ) + { + (*outArrayOfNodes)->RemoveElementAt(i); + res = GetInnerContent(node, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + } + } + + + // post process the list to break up inline containers that contain br's. + // but only for operations that might care, like making lists or para's... + if ( (inOperationType == kMakeBasicBlock) || + (inOperationType == kMakeList) || + (inOperationType == kAlign) || + (inOperationType == kIndent) || + (inOperationType == kOutdent) ) + { + PRUint32 listCount; + (*outArrayOfNodes)->Count(&listCount); + for (i=(PRInt32)listCount-1; i>=0; i--) + { + nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); + nsCOMPtr node( do_QueryInterface(isupports) ); + if (!aDontTouchContent && mEditor->IsInlineNode(node) + && mEditor->IsContainer(node) && !mEditor->IsTextNode(node)) { nsCOMPtr arrayOfInlines; res = BustUpInlinesAtBRs(node, &arrayOfInlines); @@ -3285,6 +3483,49 @@ nsHTMLEditRules::GetParagraphFormatNodes(nsCOMPtr *outArrayOfN } +/////////////////////////////////////////////////////////////////////////// +// BustUpInlinesAtRangeEndpoints: +// +nsresult +nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) +{ + nsresult res = NS_OK; + PRBool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); + + nsCOMPtr endInline = GetHighestInlineParent(item.endNode); + + // if we have inline parents above range endpoints, split them + if (endInline && !isCollapsed) + { + nsCOMPtr resultEndNode; + PRInt32 resultEndOffset; + item.endNode->GetParentNode(getter_AddRefs(resultEndNode)); + res = mEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset, + &resultEndOffset, PR_TRUE); + if (NS_FAILED(res)) return res; + // reset range + item.endNode = resultEndNode; item.endOffset = resultEndOffset; + } + + nsCOMPtr startInline = GetHighestInlineParent(item.startNode); + + if (startInline) + { + nsCOMPtr resultStartNode; + PRInt32 resultStartOffset; + item.startNode->GetParentNode(getter_AddRefs(resultStartNode)); + res = mEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset, + &resultStartOffset, PR_TRUE); + if (NS_FAILED(res)) return res; + // reset range + item.startNode = resultStartNode; item.startOffset = resultStartOffset; + } + + return res; +} + + + /////////////////////////////////////////////////////////////////////////// // BustUpInlinesAtBRs: // @@ -3300,38 +3541,15 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, // first step is to build up a list of all the break nodes inside // the inline container. nsCOMPtr arrayOfBreaks; - res = NS_NewISupportsArray(getter_AddRefs(arrayOfBreaks)); + nsBRNodeFunctor functor; + nsDOMIterator iter; + res = iter.Init(inNode); if (NS_FAILED(res)) return res; - - nsCOMPtr iter; - nsCOMPtr cNode = do_QueryInterface(inNode); - res = nsComponentManager::CreateInstance(kContentIteratorCID, - nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); + res = iter.MakeList(functor, &arrayOfBreaks); if (NS_FAILED(res)) return res; - res = iter->Init(cNode); - if (NS_FAILED(res)) return res; - nsCOMPtr isupports; - - 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 (nsHTMLEditUtils::IsBreak(node)) - { - isupports = do_QueryInterface(node); - arrayOfBreaks->AppendElement(isupports); - } - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // if there aren't and breaks, just put inNode itself in the array + // if there aren't any breaks, just put inNode itself in the array + nsCOMPtr isupports; PRUint32 listCount; arrayOfBreaks->Count(&listCount); if (!listCount) @@ -3361,7 +3579,7 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, res = nsEditor::GetNodeLocation(breakNode, &splitParentNode, &splitOffset); if (NS_FAILED(res)) return res; res = mEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset, - &resultOffset, &leftNode, &rightNode); + &resultOffset, PR_FALSE, &leftNode, &rightNode); if (NS_FAILED(res)) return res; // put left node in node list if (leftNode) @@ -3394,6 +3612,20 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, } +nsCOMPtr +nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode) +{ + if (!aNode) return nsnull; + if (nsEditor::IsBlockNode(aNode)) return nsnull; + nsCOMPtr inlineNode, node=aNode; + + while (node && mEditor->IsInlineNode(node)) + { + inlineNode = node; + inlineNode->GetParentNode(getter_AddRefs(node)); + } + return inlineNode; +} /////////////////////////////////////////////////////////////////////////// // MakeTransitionList: detect all the transitions in the array, where a @@ -3773,124 +4005,6 @@ nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, } -/////////////////////////////////////////////////////////////////////////// -// ShouldMakeEmptyBlock: determine if a block transformation should make -// a new empty block, or instead transform a block -// -nsresult -nsHTMLEditRules::ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, - const nsString *blockTag, - PRBool *outMakeEmpty) -{ - // a note about strategy: - // this routine will be called by the rules code to figure out - // if it should do something, or let the nsHTMLEditor default - // action happen. The default action is to insert a new block. - // Note that if _nothing_ should happen, ie, the selection is - // already entireyl inside a block (or blocks) or the correct type, - // then you don't want to return true in outMakeEmpty, since the - // default code will insert a new empty block anyway, rather than - // doing nothing. So we have to detect that case and return false. - - if (!aSelection || !outMakeEmpty) return NS_ERROR_NULL_POINTER; - nsresult res = NS_OK; - - // if the selection is collapsed, and - // if we in the body, or after a
with - // no more inline content before the next block, - // or in an empty block (empty td, li, etc), then we want - // a new block. Otherwise we want to trasform a block - - // xxx possible bug: selection could be not callapsed, but - // still empty. it would be nice to have a call for this: IsEmptySelection() - - PRBool isCollapsed; - res = aSelection->GetIsCollapsed(&isCollapsed); - if (NS_FAILED(res)) return res; - if (isCollapsed) - { - nsCOMPtr parent; - PRInt32 offset; - res = nsEditor::GetStartNodeAndOffset(aSelection, &parent, &offset); - if (NS_FAILED(res)) return res; - - // is selection point in the body? or a cell? - if (nsHTMLEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) - { - *outMakeEmpty = PR_TRUE; - return res; - } - - // see if block parent is already right kind of block. - // See strategy comment above. - nsCOMPtr block; - if (!nsEditor::IsBlockNode(parent)) - block = nsEditor::GetBlockNodeParent(parent); - else - block = parent; - if (block) - { - nsAutoString tag; - nsEditor::GetTagString(block,tag); - if (tag == *blockTag) - { - *outMakeEmpty = PR_FALSE; - return res; - } - } - - // is it after a
with no inline nodes after it, or a
after it?? - if (offset) - { - nsCOMPtr prevChild, nextChild, tmp; - prevChild = nsEditor::GetChildAt(parent, offset-1); - while (prevChild && !mEditor->IsEditable(prevChild)) - { - // search back until we either find an editable node, - // or hit the beginning of the block - tmp = nsEditor::NextNodeInBlock(prevChild, nsEditor::kIterBackward); - prevChild = tmp; - } - - if (prevChild && nsHTMLEditUtils::IsBreak(prevChild)) - { - nextChild = nsEditor::GetChildAt(parent, offset); - while (nextChild && !mEditor->IsEditable(nextChild)) - { - // search back until we either find an editable node, - // or hit the beginning of the block - tmp = nsEditor::NextNodeInBlock(nextChild, nsEditor::kIterForward); - nextChild = tmp; - } - if (!nextChild || nsHTMLEditUtils::IsBreak(nextChild) - || nsEditor::IsBlockNode(nextChild)) - { - // we are after a
and not before inline content, - // or we are between
s. - // make an empty block - *outMakeEmpty = PR_TRUE; - return res; - } - } - } - - // are we in a textnode or inline node? - if (!nsEditor::IsBlockNode(parent)) - { - // we must be in a text or inline node - convert existing block - *outMakeEmpty = PR_FALSE; - return res; - } - - } - // otherwise transform an existing block - *outMakeEmpty = PR_FALSE; - return res; -} - - - - /////////////////////////////////////////////////////////////////////////// // MakeBlockquote: put the list of nodes into one or more blockquotes. // @@ -4166,7 +4280,7 @@ nsHTMLEditRules::AddTerminatingBR(nsIDOMNode *aBlock) { if (!aBlock) return NS_ERROR_NULL_POINTER; nsCOMPtr last; - nsresult res = mEditor->GetLastEditableChild(aBlock, &last); + nsresult res = mEditor->GetLastEditableLeaf(aBlock, &last); if (last && nsHTMLEditUtils::IsBreak(last)) { if (nsHTMLEditUtils::IsMozBR(last)) @@ -4306,50 +4420,18 @@ nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, nsCOMPtr nsresult nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames) { - nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; - // make an isupportsArray to hold a list of nodes - nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + // gather list of empty nodes + nsEmptyFunctor functor(mEditor); + nsDOMIterator iter; + nsresult res = iter.Init(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &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; - - // loop over iter and create list of empty containers - res = iter->Init(mDocChangeRange); - if (NS_FAILED(res)) return res; - - // gather up a list of empty 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; - - PRBool bIsEmptyNode; - res = mEditor->IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_FALSE, aSafeToAskFrames); - if (NS_FAILED(res)) return res; - if (bIsEmptyNode - && (nsHTMLEditUtils::IsListItem(node) || nsHTMLEditUtils::IsTableCellOrCaption(node))) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // put moz-br's into these empty li's and td's res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; @@ -4369,7 +4451,6 @@ nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames) nsresult nsHTMLEditRules::AdjustWhitespace(nsIDOMSelection *aSelection) { - nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; @@ -4400,41 +4481,14 @@ nsHTMLEditRules::AdjustWhitespace(nsIDOMSelection *aSelection) } } - // make an isupportsArray to hold a list of nodes - 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; - - // loop over iter and adjust whitespace in any text nodes we find - res = iter->Init(mDocChangeRange); - if (NS_FAILED(res)) return res; - // gather up a list of 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; + nsEditableTextFunctor functor(mEditor); + nsDOMIterator iter; + res = iter.Init(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &arrayOfNodes); + if (NS_FAILED(res)) return res; - if (nsEditor::IsTextNode(node) && mEditor->IsEditable(node)) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // now adjust whitespace on node we found res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; @@ -4902,7 +4956,7 @@ nsHTMLEditRules::DoTextNodeWhitespace(nsIDOMCharacterData *aTextNode, PRInt32 aS runStart = -1; // reset our run } j++; // next char please! - } while (j *outArrayOfNodes, PRBool aDontTouchContent=PR_FALSE); nsresult GetDefinitionListItemTypes(nsIDOMNode *aNode, PRBool &aDT, PRBool &aDD); nsresult GetParagraphFormatNodes(nsCOMPtr *outArrayOfNodes, PRBool aDontTouchContent=PR_FALSE); + nsresult BustUpInlinesAtRangeEndpoints(nsRangeStore &inRange); nsresult BustUpInlinesAtBRs(nsIDOMNode *inNode, nsCOMPtr *outArrayOfNodes); + nsCOMPtr GetHighestInlineParent(nsIDOMNode* aNode); nsresult MakeTransitionList(nsISupportsArray *inArrayOfNodes, nsVoidArray *inTransitionArray); - nsresult ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, const nsString *blockTag, PRBool *outMakeEmpty); nsresult ApplyBlockStyle(nsISupportsArray *arrayOfNodes, const nsString *aBlockTag); nsresult MakeBlockquote(nsISupportsArray *arrayOfNodes); nsresult SplitAsNeeded(const nsString *aTag, nsCOMPtr *inOutParent, PRInt32 *inOutOffset); diff --git a/mozilla/editor/base/nsHTMLEditUtils.cpp b/mozilla/editor/base/nsHTMLEditUtils.cpp index f8f722b4b11..8fa1230f08e 100644 --- a/mozilla/editor/base/nsHTMLEditUtils.cpp +++ b/mozilla/editor/base/nsHTMLEditUtils.cpp @@ -42,6 +42,7 @@ nsHTMLEditUtils::IsBody(nsIDOMNode *node) { nsAutoString tag; nsEditor::GetTagString(node,tag); + tag.ToLowerCase(); if (tag.EqualsWithConversion("body")) { return PR_TRUE; @@ -573,4 +574,11 @@ nsHTMLEditUtils::IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent) } - +PRBool +nsHTMLEditUtils::IsLeafNode(nsIDOMNode *aNode) +{ + if (!aNode) return PR_FALSE; + PRBool hasChildren = PR_FALSE; + aNode->HasChildNodes(&hasChildren); + return !hasChildren; +} \ No newline at end of file diff --git a/mozilla/editor/base/nsHTMLEditUtils.h b/mozilla/editor/base/nsHTMLEditUtils.h index b05ea6df602..4904f271157 100644 --- a/mozilla/editor/base/nsHTMLEditUtils.h +++ b/mozilla/editor/base/nsHTMLEditUtils.h @@ -63,6 +63,9 @@ public: static PRBool IsMozDiv(nsIDOMNode *aNode); static PRBool IsMailCite(nsIDOMNode *aNode); static PRBool IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent); + + static PRBool IsLeafNode(nsIDOMNode *aNode); + }; #endif /* nsHTMLEditUtils_h__ */ diff --git a/mozilla/editor/base/nsHTMLEditor.cpp b/mozilla/editor/base/nsHTMLEditor.cpp index 56652547668..ff2a2f557f8 100644 --- a/mozilla/editor/base/nsHTMLEditor.cpp +++ b/mozilla/editor/base/nsHTMLEditor.cpp @@ -1777,6 +1777,16 @@ NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty, if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode); // cache now current, use it! But override it with typeInState results if any... PRBool isSet, theSetting; + if (aAttribute) + mTypeInState->GetTypingState(isSet, theSetting, aProperty, *aAttribute, outValue); + else + mTypeInState->GetTypingState(isSet, theSetting, aProperty); + if (isSet) + { + aFirst = aAny = aAll = theSetting; + return NS_OK; + } + /* if (aProperty == mBoldAtom.get()) { mTypeInState->GetTypingState(isSet, theSetting, aProperty); @@ -1815,7 +1825,7 @@ NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty, aFirst = aAny = aAll = mCachedUnderlineStyle; } return NS_OK; - } + } */ } // either non-collapsed selection or no cached value: do it the hard way @@ -7102,7 +7112,7 @@ nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr< /////////////////////////////////////////////////////////////////////////// -// GetNextHTMLNode: returns the previous editable leaf node, if there is +// GetNextHTMLNode: returns the next editable leaf node, if there is // one within the // nsresult @@ -7239,6 +7249,75 @@ nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOu return res; } +nsresult +nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf) +{ + // check parms + if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER; + + // init out parms + *aOutFirstLeaf = nsnull; + + // find leftmost leaf + nsCOMPtr child; + nsresult res = GetLeftmostChild(aNode, getter_AddRefs(child)); + if (NS_FAILED(res)) return res; + + while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) + { + nsCOMPtr tmp; + res = GetNextHTMLNode(child, &tmp); + if (NS_FAILED(res)) return res; + if (!tmp) return NS_ERROR_FAILURE; + + // only accept nodes that are descendants of aNode + if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) + child = tmp; + else + { + child = nsnull; // this will abort the loop + } + } + + *aOutFirstLeaf = child; + return res; +} + + +nsresult +nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf) +{ + // check parms + if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER; + + // init out parms + *aOutLastLeaf = nsnull; + + // find leftmost leaf + nsCOMPtr child; + nsresult res = GetRightmostChild(aNode, getter_AddRefs(child)); + if (NS_FAILED(res)) return res; + + while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) + { + nsCOMPtr tmp; + res = GetPriorHTMLNode(child, &tmp); + if (NS_FAILED(res)) return res; + if (!tmp) return NS_ERROR_FAILURE; + + // only accept nodes that are descendants of aNode + if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) + child = tmp; + else + { + child = nsnull; + } + } + + *aOutLastLeaf = child; + return res; +} + /////////////////////////////////////////////////////////////////////////// // IsEmptyNode: figure out if aNode is an empty node. // A block can have children and still be considered empty, diff --git a/mozilla/editor/base/nsHTMLEditor.h b/mozilla/editor/base/nsHTMLEditor.h index a08a1084b04..d6592c4a35f 100644 --- a/mozilla/editor/base/nsHTMLEditor.h +++ b/mozilla/editor/base/nsHTMLEditor.h @@ -332,6 +332,11 @@ public: // aSelection is optional -- if null, we get current seletion nsresult CollapseSelectionToDeepestNonTableFirstChild(nsIDOMSelection *aSelection, nsIDOMNode *aNode); + nsresult IsEmptyNode(nsIDOMNode *aNode, PRBool *outIsEmptyBlock, + PRBool aMozBRDoesntCount = PR_FALSE, + PRBool aListOrCellNotEmpty = PR_FALSE, + PRBool aSafeToAskFrames = PR_FALSE); + protected: NS_IMETHOD InitRules(); @@ -563,10 +568,8 @@ protected: nsresult IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast); nsresult GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutFirstChild); nsresult GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutLastChild); - nsresult IsEmptyNode(nsIDOMNode *aNode, PRBool *outIsEmptyBlock, - PRBool aMozBRDoesntCount = PR_FALSE, - PRBool aListOrCellNotEmpty = PR_FALSE, - PRBool aSafeToAskFrames = PR_FALSE); + nsresult GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf); + nsresult GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf); nsresult GetDOMEventReceiver(nsIDOMEventReceiver **aEventReceiver); diff --git a/mozilla/editor/base/nsTextEditRules.cpp b/mozilla/editor/base/nsTextEditRules.cpp index 2fc89018387..517342246b3 100644 --- a/mozilla/editor/base/nsTextEditRules.cpp +++ b/mozilla/editor/base/nsTextEditRules.cpp @@ -477,7 +477,7 @@ nsTextEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel, P printf("It's a moz quote -- splitting\n"); nsCOMPtr outLeftNode; nsCOMPtr outRightNode; - res = mEditor->SplitNodeDeep(preNode, selNode, selOffset, &newOffset, &outLeftNode, &outRightNode); + res = mEditor->SplitNodeDeep(preNode, selNode, selOffset, &newOffset, PR_TRUE, &outLeftNode, &outRightNode); if (NS_FAILED(res)) return res; PRBool bIsEmptyNode; diff --git a/mozilla/editor/libeditor/base/nsEditor.cpp b/mozilla/editor/libeditor/base/nsEditor.cpp index 354a8c2b113..23dda5472e1 100644 --- a/mozilla/editor/libeditor/base/nsEditor.cpp +++ b/mozilla/editor/libeditor/base/nsEditor.cpp @@ -152,17 +152,11 @@ PRInt32 nsEditor::gInstanceCount = 0; * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store * ranges since dom gravity will possibly change the ranges. */ -nsSelectionState::nsSelectionState() : mArray(), mLock(PR_FALSE) {} +nsSelectionState::nsSelectionState() : mArray(){} nsSelectionState::~nsSelectionState() { - // free any items in the array - SelRangeStore *item; - while ((item = (SelRangeStore*)mArray.ElementAt(0))) - { - delete item; - mArray.RemoveElementAt(0); - } + MakeEmpty(); } nsresult @@ -171,7 +165,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) if (!aSel) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; PRInt32 i,rangeCount, arrayCount = mArray.Count(); - SelRangeStore *item; + nsRangeStore *item; aSel->GetRangeCount(&rangeCount); // if we need more items in the array, new them @@ -180,7 +174,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) PRInt32 count = rangeCount-arrayCount; for (i=0; iarrayCount) { - while ((item = (SelRangeStore*)mArray.ElementAt(rangeCount))) + while ((item = (nsRangeStore*)mArray.ElementAt(rangeCount))) { delete item; mArray.RemoveElementAt(rangeCount); @@ -198,7 +192,7 @@ nsSelectionState::SaveSelection(nsIDOMSelection *aSel) // now store the selection ranges for (i=0; i range; res = aSel->GetRangeAt(i, getter_AddRefs(range)); @@ -214,7 +208,7 @@ nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) if (!aSel) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; PRInt32 i, arrayCount = mArray.Count(); - SelRangeStore *item; + nsRangeStore *item; // clear out selection aSel->ClearSelection(); @@ -222,7 +216,7 @@ nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) // set the selection ranges anew for (i=0; i range; item->GetRange(&range); @@ -239,8 +233,8 @@ PRBool nsSelectionState::IsCollapsed() { if (1 != mArray.Count()) return PR_FALSE; - SelRangeStore *item; - item = (SelRangeStore*)mArray.ElementAt(0); + nsRangeStore *item; + item = (nsRangeStore*)mArray.ElementAt(0); if (!item) return PR_FALSE; nsCOMPtr range; item->GetRange(&range); @@ -258,12 +252,12 @@ nsSelectionState::IsEqual(nsSelectionState *aSelState) if (myCount != itsCount) return PR_FALSE; if (myCount < 1) return PR_FALSE; - SelRangeStore *myItem, *itsItem; + nsRangeStore *myItem, *itsItem; for (i=0; imArray.ElementAt(0)); + myItem = (nsRangeStore*)mArray.ElementAt(0); + itsItem = (nsRangeStore*)(aSelState->mArray.ElementAt(0)); if (!myItem || !itsItem) return PR_FALSE; nsCOMPtr myRange, itsRange; @@ -281,22 +275,137 @@ nsSelectionState::IsEqual(nsSelectionState *aSelState) return PR_TRUE; } -// notification routines used to update the saved selection state in response to -// document editing. +void +nsSelectionState::MakeEmpty() +{ + // free any items in the array + nsRangeStore *item; + while ((item = (nsRangeStore*)mArray.ElementAt(0))) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +PRBool +nsSelectionState::IsEmpty() +{ + return (mArray.Count() == 0); +} + +/*************************************************************************** + * nsRangeUpdater: class for updating nsIDOMRanges in response to editor actions. + */ + +nsRangeUpdater::nsRangeUpdater() : mArray(), mLock(PR_FALSE) {} + +nsRangeUpdater::~nsRangeUpdater() +{ + // free any items in the array + nsRangeStore *item; + while ((item = (nsRangeStore*)mArray.ElementAt(0))) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +void* +nsRangeUpdater::RegisterRange(nsIDOMRange *aRange) +{ + nsRangeStore *item = new nsRangeStore; + if (!item) return nsnull; + item->StoreRange(aRange); + mArray.AppendElement(item); + return item; +} + +nsCOMPtr +nsRangeUpdater::ReclaimRange(void *aCookie) +{ + nsRangeStore *item = NS_STATIC_CAST(nsRangeStore*,aCookie); + if (!item) return nsnull; + nsCOMPtr outRange; + item->GetRange(&outRange); + mArray.RemoveElement(aCookie); + delete item; + return outRange; +} + +void +nsRangeUpdater::DropRange(void *aCookie) +{ + nsRangeStore *item = NS_STATIC_CAST(nsRangeStore*,aCookie); + if (!item) return; + mArray.RemoveElement(aCookie); + delete item; +} + +void +nsRangeUpdater::RegisterRangeItem(nsRangeStore *aRangeItem) +{ + if (!aRangeItem) return; + mArray.AppendElement(aRangeItem); + return; +} + +void +nsRangeUpdater::DropRangeItem(nsRangeStore *aRangeItem) +{ + if (!aRangeItem) return; + mArray.RemoveElement(aRangeItem); + return; +} + +nsresult +nsRangeUpdater::RegisterSelectionState(nsSelectionState &aSelState) +{ + PRInt32 i, theCount = aSelState.mArray.Count(); + if (theCount < 1) return NS_ERROR_FAILURE; + + nsRangeStore *item; + + for (i=0; istartNode.get() == aParent) && (item->startOffset > aPosition)) @@ -308,25 +417,25 @@ nsSelectionState::SelAdjCreateNode(nsIDOMNode *aParent, PRInt32 aPosition) } nsresult -nsSelectionState::SelAdjInsertNode(nsIDOMNode *aParent, PRInt32 aPosition) +nsRangeUpdater::SelAdjInsertNode(nsIDOMNode *aParent, PRInt32 aPosition) { return SelAdjCreateNode(aParent, aPosition); } nsresult -nsSelectionState::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) +nsRangeUpdater::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) { if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... if (!aNode) return NS_ERROR_NULL_POINTER; PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aParent) && (item->startOffset > aOffset)) @@ -341,7 +450,7 @@ nsSelectionState::SelAdjDeleteNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt nsresult -nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode) +nsRangeUpdater::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode) { if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc... if (!aOldRightNode || !aNewLeftNode) return NS_ERROR_NULL_POINTER; @@ -358,11 +467,11 @@ nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, ns if (NS_FAILED(result)) return result; // next step is to check for range enpoints inside aOldRightNode - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aOldRightNode) @@ -393,7 +502,7 @@ nsSelectionState::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, ns nsresult -nsSelectionState::SelAdjJoinNodes(nsIDOMNode *aLeftNode, +nsRangeUpdater::SelAdjJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent, PRInt32 aOffset, @@ -404,11 +513,11 @@ nsSelectionState::SelAdjJoinNodes(nsIDOMNode *aLeftNode, PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aOriginalNode) @@ -501,7 +610,7 @@ nsSelectionState::DidReplaceContainer(nsIDOMNode *aOriginalNode, nsIDOMNode *aNe nsresult -nsSelectionState::WillRemoveContainer() +nsRangeUpdater::WillRemoveContainer() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -510,7 +619,7 @@ nsSelectionState::WillRemoveContainer() nsresult -nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) +nsRangeUpdater::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -519,11 +628,11 @@ nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRI PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; istartNode.get() == aNode) @@ -546,7 +655,7 @@ nsSelectionState::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRI nsresult -nsSelectionState::WillInsertContainer() +nsRangeUpdater::WillInsertContainer() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -555,7 +664,7 @@ nsSelectionState::WillInsertContainer() nsresult -nsSelectionState::DidInsertContainer() +nsRangeUpdater::DidInsertContainer() { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -564,7 +673,7 @@ nsSelectionState::DidInsertContainer() nsresult -nsSelectionState::WillMoveNode() +nsRangeUpdater::WillMoveNode() { if (mLock) return NS_ERROR_UNEXPECTED; mLock = PR_TRUE; @@ -573,7 +682,7 @@ nsSelectionState::WillMoveNode() nsresult -nsSelectionState::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) +nsRangeUpdater::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) { if (!mLock) return NS_ERROR_UNEXPECTED; mLock = PR_FALSE; @@ -582,11 +691,11 @@ nsSelectionState::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOM PRInt32 i, count = mArray.Count(); if (!count) return NS_OK; - SelRangeStore *item; + nsRangeStore *item; for (i=0; iGetStartContainer(getter_AddRefs(startNode)); @@ -620,7 +740,7 @@ nsresult SelRangeStore::StoreRange(nsIDOMRange *aRange) return NS_OK; } -nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) +nsresult nsRangeStore::GetRange(nsCOMPtr *outRange) { if (!outRange) return NS_ERROR_NULL_POINTER; nsresult res = nsComponentManager::CreateInstance(kCRangeCID, @@ -644,22 +764,22 @@ nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) class nsAutoReplaceContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mOriginalNode; nsIDOMNode *mNewNode; public: - nsAutoReplaceContainerSelNotify(nsSelectionState *aSelState, nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode) : - mSel(aSelState) + nsAutoReplaceContainerSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode) : + mRU(aRangeUpdater) ,mOriginalNode(aOriginalNode) ,mNewNode(aNewNode) { - if (mSel) mSel->WillReplaceContainer(); + mRU.WillReplaceContainer(); } ~nsAutoReplaceContainerSelNotify() { - if (mSel) mSel->DidReplaceContainer(mOriginalNode, mNewNode); + mRU.DidReplaceContainer(mOriginalNode, mNewNode); } }; @@ -672,30 +792,30 @@ class nsAutoReplaceContainerSelNotify class nsAutoRemoveContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mNode; nsIDOMNode *mParent; PRInt32 mOffset; PRUint32 mNodeOrigLen; public: - nsAutoRemoveContainerSelNotify(nsSelectionState *aSelState, + nsAutoRemoveContainerSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen) : - mSel(aSelState) + mRU(aRangeUpdater) ,mNode(aNode) ,mParent(aParent) ,mOffset(aOffset) ,mNodeOrigLen(aNodeOrigLen) { - if (mSel) mSel->WillRemoveContainer(); + mRU.WillRemoveContainer(); } ~nsAutoRemoveContainerSelNotify() { - if (mSel) mSel->DidRemoveContainer(mNode, mParent, mOffset, mNodeOrigLen); + mRU.DidRemoveContainer(mNode, mParent, mOffset, mNodeOrigLen); } }; @@ -707,18 +827,18 @@ class nsAutoRemoveContainerSelNotify class nsAutoInsertContainerSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; public: - nsAutoInsertContainerSelNotify(nsSelectionState *aSelState) : - mSel(aSelState) + nsAutoInsertContainerSelNotify(nsRangeUpdater &aRangeUpdater) : + mRU(aRangeUpdater) { - if (mSel) mSel->WillInsertContainer(); + mRU.WillInsertContainer(); } ~nsAutoInsertContainerSelNotify() { - if (mSel) mSel->DidInsertContainer(); + mRU.DidInsertContainer(); } }; @@ -731,30 +851,30 @@ class nsAutoInsertContainerSelNotify class nsAutoMoveNodeSelNotify { private: - nsSelectionState *mSel; + nsRangeUpdater &mRU; nsIDOMNode *mOldParent; nsIDOMNode *mNewParent; PRInt32 mOldOffset; PRInt32 mNewOffset; public: - nsAutoMoveNodeSelNotify(nsSelectionState *aSelState, + nsAutoMoveNodeSelNotify(nsRangeUpdater &aRangeUpdater, nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset) : - mSel(aSelState) + mRU(aRangeUpdater) ,mOldParent(aOldParent) ,mNewParent(aNewParent) ,mOldOffset(aOldOffset) ,mNewOffset(aNewOffset) { - if (mSel) mSel->WillMoveNode(); + mRU.WillMoveNode(); } ~nsAutoMoveNodeSelNotify() { - if (mSel) mSel->DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset); + mRU.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset); } }; @@ -774,7 +894,8 @@ nsEditor::nsEditor() , mPlaceHolderName(nsnull) , mPlaceHolderBatch(0) , mSelState(nsnull) -, mSavedSel(nsnull) +, mSavedSel() +, mRangeUpdater() , mShouldTxnSetSelection(PR_TRUE) , mBodyElement(nsnull) , mInIMEMode(PR_FALSE) @@ -1595,7 +1716,7 @@ NS_IMETHODIMP nsEditor::CreateNode(const nsString& aTag, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjCreateNode(aParent, aPosition); + mRangeUpdater.SelAdjCreateNode(aParent, aPosition); if (mActionListeners) { @@ -1637,7 +1758,7 @@ NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjInsertNode(aParent, aPosition); + mRangeUpdater.SelAdjInsertNode(aParent, aPosition); if (mActionListeners) { @@ -1686,7 +1807,7 @@ nsEditor::SplitNode(nsIDOMNode * aNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); + mRangeUpdater.SelAdjSplitNode(aNode, aOffset, *aNewLeftNode); if (mActionListeners) { @@ -1743,7 +1864,7 @@ nsEditor::JoinNodes(nsIDOMNode * aLeftNode, // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (PRInt32)oldLeftNodeLen); + mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (PRInt32)oldLeftNodeLen); if (mActionListeners) { @@ -1789,7 +1910,7 @@ NS_IMETHODIMP nsEditor::DeleteNode(nsIDOMNode * aElement) // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); - if (mSavedSel) mSavedSel->SelAdjDeleteNode(aElement, parent, offset); + mRangeUpdater.SelAdjDeleteNode(aElement, parent, offset); if (mActionListeners) { @@ -1856,8 +1977,8 @@ nsEditor::ReplaceContainer(nsIDOMNode *inNode, // notify our internal selection state listener // (Note: A nsAutoSelectionReset object must be created - // before calling this to initialize mSavedSel) - nsAutoReplaceContainerSelNotify selStateNotify(mSavedSel, inNode, *outNode); + // before calling this to initialize mRangeUpdater) + nsAutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, inNode, *outNode); nsCOMPtr child; PRBool bHasMoreChildren; @@ -1908,7 +2029,7 @@ nsEditor::RemoveContainer(nsIDOMNode *inNode) nodeList->GetLength(&nodeOrigLen); // notify our internal selection state listener - nsAutoRemoveContainerSelNotify selNotify(mSavedSel, inNode, parent, offset, nodeOrigLen); + nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, inNode, parent, offset, nodeOrigLen); nsCOMPtr child; while (bHasMoreChildren) @@ -1970,7 +2091,7 @@ nsEditor::InsertContainerAbove( nsIDOMNode *inNode, } // notify our internal selection state listener - nsAutoInsertContainerSelNotify selNotify(mSavedSel); + nsAutoInsertContainerSelNotify selNotify(mRangeUpdater); // put inNode in new parent, outNode res = DeleteNode(inNode); @@ -2009,7 +2130,7 @@ nsEditor::MoveNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset) if ((aParent == oldParent.get()) && (oldOffset == aOffset)) return NS_OK; // notify our internal selection state listener - nsAutoMoveNodeSelNotify selNotify(mSavedSel, oldParent, oldOffset, aParent, aOffset); + nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset, aParent, aOffset); // need to adjust aOffset if we are moving aNode further along in it's current parent if ((aParent == oldParent.get()) && (oldOffset < aOffset)) @@ -2250,6 +2371,44 @@ nsEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed) return NS_OK; } +#ifdef XP_MAC +#pragma mark - +#pragma mark support for selection preservation +#pragma mark - +#endif + +PRBool +nsEditor::ArePreservingSelection() +{ + if (mSavedSel.IsEmpty()) return PR_FALSE; + return PR_TRUE; +} + +nsresult +nsEditor::PreserveSelectionAcrossActions(nsIDOMSelection *aSel) +{ + mSavedSel.SaveSelection(aSel); + mRangeUpdater.RegisterSelectionState(mSavedSel); + return NS_OK; +} + +nsresult +nsEditor::RestorePreservedSelection(nsIDOMSelection *aSel) +{ + if (mSavedSel.IsEmpty()) return NS_ERROR_FAILURE; + mSavedSel.RestoreSelection(aSel); + StopPreservingSelection(); + return NS_OK; +} + +void +nsEditor::StopPreservingSelection() +{ + mRangeUpdater.DropSelectionState(mSavedSel); + mSavedSel.MakeEmpty(); +} + + #ifdef XP_MAC #pragma mark - #pragma mark nsIEditorIMESupport @@ -4985,6 +5144,7 @@ nsEditor::SplitNodeDeep(nsIDOMNode *aNode, nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset, PRInt32 *outOffset, + PRBool aNoEmptyContainers, nsCOMPtr *outLeftNode, nsCOMPtr *outRightNode) { @@ -5006,12 +5166,12 @@ nsEditor::SplitNodeDeep(nsIDOMNode *aNode, // this nsEditor routine. nsCOMPtr nodeAsText = do_QueryInterface(nodeToSplit); - PRUint32 textLen=0; - if (nodeAsText) - nodeAsText->GetLength(&textLen); + PRUint32 len; PRBool bDoSplit = PR_FALSE; + res = GetLengthOfDOMNode(nodeToSplit, len); + if (NS_FAILED(res)) return res; - if (!nodeAsText || (offset && (offset != (PRInt32)textLen))) + if (!(aNoEmptyContainers || nodeAsText) || (offset && (offset != (PRInt32)len))) { bDoSplit = PR_TRUE; res = SplitNode(nodeToSplit, offset, getter_AddRefs(tempNode)); diff --git a/mozilla/editor/libeditor/base/nsEditor.h b/mozilla/editor/libeditor/base/nsEditor.h index 8b24c510043..59ce695d7b6 100644 --- a/mozilla/editor/libeditor/base/nsEditor.h +++ b/mozilla/editor/libeditor/base/nsEditor.h @@ -80,8 +80,10 @@ class nsISelectionController; */ // first a helper struct for saving/setting ranges -struct SelRangeStore +struct nsRangeStore { + nsRangeStore(); + ~nsRangeStore(); nsresult StoreRange(nsIDOMRange *aRange); nsresult GetRange(nsCOMPtr *outRange); @@ -89,6 +91,7 @@ struct SelRangeStore PRInt32 startOffset; nsCOMPtr endNode; PRInt32 endOffset; + // DEBUG: static PRInt32 n; }; class nsSelectionState @@ -104,6 +107,26 @@ class nsSelectionState PRBool IsEqual(nsSelectionState *aSelState); void MakeEmpty(); PRBool IsEmpty(); + protected: + nsVoidArray mArray; + + friend class nsRangeUpdater; +}; + +class nsRangeUpdater +{ + public: + + nsRangeUpdater(); + ~nsRangeUpdater(); + + void* RegisterRange(nsIDOMRange *aRange); + nsCOMPtr ReclaimRange(void *aCookie); + void DropRange(void *aCookie); + void RegisterRangeItem(nsRangeStore *aRangeItem); + void DropRangeItem(nsRangeStore *aRangeItem); + nsresult RegisterSelectionState(nsSelectionState &aSelState); + nsresult DropSelectionState(nsSelectionState &aSelState); // editor selection gravity routines. Note that we can't always depend on // DOM Range gravity to do what we want to the "real" selection. For instance, @@ -131,7 +154,7 @@ class nsSelectionState nsresult DidInsertContainer(); nsresult WillMoveNode(); nsresult DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset); - + protected: nsVoidArray mArray; PRBool mLock; }; @@ -483,6 +506,14 @@ public: * with a call to EndOperation */ NS_IMETHOD EndOperation(); + /** routines for managing the preservation of selection across + * various editor actions */ + PRBool ArePreservingSelection(); + nsresult PreserveSelectionAcrossActions(nsIDOMSelection *aSel); + nsresult RestorePreservedSelection(nsIDOMSelection *aSel); + void StopPreservingSelection(); + + /** return the string that represents text nodes in the content tree */ static nsresult GetTextNodeTag(nsString& aOutString); @@ -725,6 +756,7 @@ public: nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset, PRInt32 *outOffset, + PRBool aNoEmptyContainers = PR_FALSE, nsCOMPtr *outLeftNode = 0, nsCOMPtr *outRightNode = 0); nsresult JoinNodeDeep(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsCOMPtr *aOutJoinNode, PRInt32 *outOffset); @@ -748,15 +780,16 @@ protected: nsCOMPtr mTxnMgr; nsCOMPtr mEditProperty; nsCOMPtr mLastStyleSheet; // is owning this dangerous? - nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes - nsIAtom *mPlaceHolderName; // name of placeholder transaction - PRInt32 mPlaceHolderBatch; // nesting count for batching - nsSelectionState *mSelState; // saved selection state for placeholder txn batching - nsSelectionState *mSavedSel; // cached selection for nsAutoSelectionReset - PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns + nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes + nsIAtom *mPlaceHolderName; // name of placeholder transaction + PRInt32 mPlaceHolderBatch; // nesting count for batching + nsSelectionState *mSelState; // saved selection state for placeholder txn batching + nsSelectionState mSavedSel; // cached selection for nsAutoSelectionReset + nsRangeUpdater mRangeUpdater; // utility class object for maintaining preserved ranges + PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns nsCOMPtr mBodyElement; // cached body node - PRInt32 mAction; // the current editor action - EDirection mDirection; // the current direction of editor action + PRInt32 mAction; // the current editor action + EDirection mDirection; // the current direction of editor action // data necessary to build IME transactions PRBool mInIMEMode; // are we inside an IME composition? diff --git a/mozilla/editor/libeditor/base/nsEditorUtils.cpp b/mozilla/editor/libeditor/base/nsEditorUtils.cpp index 982337fb017..00f80836790 100644 --- a/mozilla/editor/libeditor/base/nsEditorUtils.cpp +++ b/mozilla/editor/libeditor/base/nsEditorUtils.cpp @@ -23,6 +23,9 @@ #include "nsEditorUtils.h" #include "nsIDOMDocument.h" +#include "nsIDOMRange.h" +#include "nsIContent.h" +#include "nsLayoutCID.h" /****************************************************************************** @@ -34,24 +37,162 @@ mSel(nsnull) ,mEd(nsnull) { if (!aSel || !aEd) return; // not much we can do, bail. - if (aEd->mSavedSel) return; // we already have initted mSavedSel, so this must be nested call. + if (aEd->ArePreservingSelection()) return; // we already have initted mSavedSel, so this must be nested call. mSel = do_QueryInterface(aSel); mEd = aEd; if (mSel) { - mEd->mSavedSel = new nsSelectionState(); - mEd->mSavedSel->SaveSelection(mSel); + mEd->PreserveSelectionAcrossActions(mSel); } } nsAutoSelectionReset::~nsAutoSelectionReset() { - if (mSel && mEd->mSavedSel) // mSel will be null if this was nested call + if (mSel && mEd->ArePreservingSelection()) // mSel will be null if this was nested call { - mEd->mSavedSel->RestoreSelection(mSel); - delete mEd->mSavedSel; - mEd->mSavedSel = nsnull; + mEd->RestorePreservedSelection(mSel); } } +void +nsAutoSelectionReset::Abort() +{ + mEd->StopPreservingSelection(); +} + + +/****************************************************************************** + * some helper classes for iterating the dom tree + *****************************************************************************/ + +static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); +static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID); + +nsDOMIterator::nsDOMIterator() : +mIter(nsnull) +{ +} + +nsDOMIterator::~nsDOMIterator() +{ +} + +nsresult +nsDOMIterator::Init(nsIDOMRange* aRange) +{ + nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + return mIter->Init(aRange); +} + +nsresult +nsDOMIterator::Init(nsIDOMNode* aNode) +{ + nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + nsCOMPtr content = do_QueryInterface(aNode); + return mIter->Init(content); +} + +void +nsDOMIterator::ForEach(nsDomIterFunctor& functor) const +{ + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // iterate through dom + while (NS_ENUMERATOR_FALSE == mIter->IsDone()) + { + res = mIter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return; + node = do_QueryInterface(content); + if (!node) return; + functor(node); + res = mIter->Next(); + if (NS_FAILED(res)) return; + } +} + +nsresult +nsDOMIterator::MakeList(nsBoolDomIterFunctor& functor, + nsCOMPtr *outArrayOfNodes) const +{ + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // make a array + res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); + if (NS_FAILED(res)) return res; + + return AppendList(functor, *outArrayOfNodes); +} + +nsresult +nsDOMIterator::AppendList(nsBoolDomIterFunctor& functor, + nsCOMPtr arrayOfNodes) const +{ + if (!arrayOfNodes) return NS_ERROR_NULL_POINTER; + nsCOMPtr content; + nsCOMPtr node; + nsCOMPtr isupports; + nsresult res; + + // iterate through dom and build list + while (NS_ENUMERATOR_FALSE == mIter->IsDone()) + { + res = mIter->CurrentNode(getter_AddRefs(content)); + if (NS_FAILED(res)) return res; + node = do_QueryInterface(content); + if (!node) return NS_ERROR_NULL_POINTER; + if (functor(node)) + { + isupports = do_QueryInterface(node); + arrayOfNodes->AppendElement(isupports); + } + res = mIter->Next(); + if (NS_FAILED(res)) return res; + } + return NS_OK; +} + +nsDOMSubtreeIterator::nsDOMSubtreeIterator() +{ +} + +nsDOMSubtreeIterator::~nsDOMSubtreeIterator() +{ +} + +nsresult +nsDOMSubtreeIterator::Init(nsIDOMRange* aRange) +{ + nsresult res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + return mIter->Init(aRange); +} + +nsresult +nsDOMSubtreeIterator::Init(nsIDOMNode* aNode) +{ + nsresult res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, + nsnull, + NS_GET_IID(nsIContentIterator), + getter_AddRefs(mIter)); + if (NS_FAILED(res)) return res; + nsCOMPtr content = do_QueryInterface(aNode); + return mIter->Init(content); +} diff --git a/mozilla/editor/libeditor/base/nsEditorUtils.h b/mozilla/editor/libeditor/base/nsEditorUtils.h index d5fc7a2a69d..9932e49a1c3 100644 --- a/mozilla/editor/libeditor/base/nsEditorUtils.h +++ b/mozilla/editor/libeditor/base/nsEditorUtils.h @@ -32,6 +32,7 @@ #include "nsIAtom.h" #include "nsVoidArray.h" #include "nsEditor.h" +#include "nsIContentIterator.h" /*************************************************************************** * stack based helper class for batching a collection of txns inside a @@ -77,6 +78,9 @@ class nsAutoSelectionReset /** destructor restores mSel to its former state */ ~nsAutoSelectionReset(); + + /** Abort: cancel selection saver */ + void Abort(); }; /*************************************************************************** @@ -139,5 +143,47 @@ class nsAutoTxnsConserveSelection PRBool mOldState; }; +/****************************************************************************** + * some helper classes for iterating the dom tree + *****************************************************************************/ + +class nsDomIterFunctor +{ + public: + virtual void* operator()(nsIDOMNode* aNode)=0; +}; + +class nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode)=0; +}; + +class nsDOMIterator +{ + public: + nsDOMIterator(); + virtual ~nsDOMIterator(); + + nsresult Init(nsIDOMRange* aRange); + nsresult Init(nsIDOMNode* aNode); + void ForEach(nsDomIterFunctor& functor) const; + nsresult MakeList(nsBoolDomIterFunctor& functor, + nsCOMPtr *outArrayOfNodes) const; + nsresult AppendList(nsBoolDomIterFunctor& functor, + nsCOMPtr arrayOfNodes) const; + protected: + nsCOMPtr mIter; +}; + +class nsDOMSubtreeIterator : public nsDOMIterator +{ + public: + nsDOMSubtreeIterator(); + virtual ~nsDOMSubtreeIterator(); + + nsresult Init(nsIDOMRange* aRange); + nsresult Init(nsIDOMNode* aNode); +}; #endif // nsEditorUtils_h__ diff --git a/mozilla/editor/libeditor/html/TypeInState.cpp b/mozilla/editor/libeditor/html/TypeInState.cpp index e6167cfdaeb..89ed469a6a1 100644 --- a/mozilla/editor/libeditor/html/TypeInState.cpp +++ b/mozilla/editor/libeditor/html/TypeInState.cpp @@ -122,14 +122,14 @@ nsresult TypeInState::SetProp(nsIAtom *aProp, const nsString &aAttr, const nsStr } // if it's already set we are done - if (IsPropSet(aProp,aAttr,aValue)) return NS_OK; + if (IsPropSet(aProp,aAttr,nsnull)) return NS_OK; // make a new propitem PropItem *item = new PropItem(aProp,aAttr,aValue); if (!item) return NS_ERROR_OUT_OF_MEMORY; // remove it from the list of cleared properties, if we have a match - RemovePropFromClearedList(aProp,aAttr,aValue); + RemovePropFromClearedList(aProp,aAttr); // add it to the list of set properties mSetArray.AppendElement((void*)item); @@ -141,30 +141,25 @@ nsresult TypeInState::SetProp(nsIAtom *aProp, const nsString &aAttr, const nsStr nsresult TypeInState::ClearAllProps() { // null prop means "all" props - return ClearProp(nsnull,nsAutoString(),nsAutoString()); + return ClearProp(nsnull,nsAutoString()); } nsresult TypeInState::ClearProp(nsIAtom *aProp) { - return ClearProp(aProp,nsAutoString(),nsAutoString()); + return ClearProp(aProp,nsAutoString()); } nsresult TypeInState::ClearProp(nsIAtom *aProp, const nsString &aAttr) -{ - return ClearProp(aProp,aAttr,nsAutoString()); -} - -nsresult TypeInState::ClearProp(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue) { // if it's already cleared we are done - if (IsPropCleared(aProp,aAttr,aValue)) return NS_OK; + if (IsPropCleared(aProp,aAttr)) return NS_OK; // make a new propitem - PropItem *item = new PropItem(aProp,aAttr,aValue); + PropItem *item = new PropItem(aProp,aAttr,nsAutoString()); if (!item) return NS_ERROR_OUT_OF_MEMORY; // remove it from the list of set properties, if we have a match - RemovePropFromSetList(aProp,aAttr,aValue); + RemovePropFromSetList(aProp,aAttr); // add it to the list of cleared properties mClearedArray.AppendElement((void*)item); @@ -222,7 +217,7 @@ nsresult TypeInState::TakeRelativeFontSize(PRInt32 *outRelSize) nsresult TypeInState::GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp) { - return GetTypingState(isSet, theSetting, aProp, nsAutoString(), nsAutoString()); + return GetTypingState(isSet, theSetting, aProp, nsAutoString(), nsnull); } nsresult TypeInState::GetTypingState(PRBool &isSet, @@ -230,7 +225,7 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, nsIAtom *aProp, const nsString &aAttr) { - return GetTypingState(isSet, theSetting, aProp, aAttr, nsAutoString()); + return GetTypingState(isSet, theSetting, aProp, aAttr, nsnull); } @@ -238,14 +233,14 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue) + nsString *aValue) { if (IsPropSet(aProp, aAttr, aValue)) { isSet = PR_TRUE; theSetting = PR_TRUE; } - else if (IsPropCleared(aProp, aAttr, aValue)) + else if (IsPropCleared(aProp, aAttr)) { isSet = PR_TRUE; theSetting = PR_FALSE; @@ -264,8 +259,7 @@ nsresult TypeInState::GetTypingState(PRBool &isSet, *******************************************************************/ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 index; PropItem *item; @@ -282,7 +276,7 @@ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, if (item) delete item; } } - else if (FindPropInList(aProp, aAttr, aValue, mSetArray, index)) + else if (FindPropInList(aProp, aAttr, nsnull, mSetArray, index)) { item = (PropItem*)mSetArray.ElementAt(index); mSetArray.RemoveElementAt(index); @@ -293,11 +287,10 @@ nsresult TypeInState::RemovePropFromSetList(nsIAtom *aProp, nsresult TypeInState::RemovePropFromClearedList(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 index; - if (FindPropInList(aProp, aAttr, aValue, mClearedArray, index)) + if (FindPropInList(aProp, aAttr, nsnull, mClearedArray, index)) { PropItem *item = (PropItem*)mClearedArray.ElementAt(index); mClearedArray.RemoveElementAt(index); @@ -309,16 +302,16 @@ nsresult TypeInState::RemovePropFromClearedList(nsIAtom *aProp, PRBool TypeInState::IsPropSet(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue) + nsString* outValue) { PRInt32 i; - return IsPropSet(aProp, aAttr, aValue, i); + return IsPropSet(aProp, aAttr, outValue, i); } PRBool TypeInState::IsPropSet(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, + nsString *outValue, PRInt32 &outIndex) { // linear search. list should be short. @@ -329,6 +322,7 @@ PRBool TypeInState::IsPropSet(nsIAtom *aProp, if ( (item->tag == aProp) && (item->attr == aAttr) ) { + if (outValue) *outValue = item->value; outIndex = i; return PR_TRUE; } @@ -338,22 +332,20 @@ PRBool TypeInState::IsPropSet(nsIAtom *aProp, PRBool TypeInState::IsPropCleared(nsIAtom *aProp, - const nsString &aAttr, - const nsString &aValue) + const nsString &aAttr) { PRInt32 i; - return IsPropCleared(aProp, aAttr, aValue, i); + return IsPropCleared(aProp, aAttr, i); } PRBool TypeInState::IsPropCleared(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, PRInt32 &outIndex) { - if (FindPropInList(aProp, aAttr, aValue, mClearedArray, outIndex)) + if (FindPropInList(aProp, aAttr, nsnull, mClearedArray, outIndex)) return PR_TRUE; - if (FindPropInList(0, nsAutoString(), nsAutoString(), mClearedArray, outIndex)) + if (FindPropInList(0, nsAutoString(), nsnull, mClearedArray, outIndex)) { // special case for all props cleared outIndex = -1; @@ -364,7 +356,7 @@ PRBool TypeInState::IsPropCleared(nsIAtom *aProp, PRBool TypeInState::FindPropInList(nsIAtom *aProp, const nsString &aAttr, - const nsString &aValue, + nsString *outValue, nsVoidArray &aList, PRInt32 &outIndex) { @@ -376,6 +368,7 @@ PRBool TypeInState::FindPropInList(nsIAtom *aProp, if ( (item->tag == aProp) && (item->attr == aAttr) ) { + if (outValue) *outValue = item->value; outIndex = i; return PR_TRUE; } diff --git a/mozilla/editor/libeditor/html/TypeInState.h b/mozilla/editor/libeditor/html/TypeInState.h index eed5f33b7b6..3d3bd7b7a6d 100644 --- a/mozilla/editor/libeditor/html/TypeInState.h +++ b/mozilla/editor/libeditor/html/TypeInState.h @@ -57,7 +57,6 @@ public: nsresult ClearAllProps(); nsresult ClearProp(nsIAtom *aProp); nsresult ClearProp(nsIAtom *aProp, const nsString &aAttr); - nsresult ClearProp(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); //************************************************************************** // TakeClearProperty: hands back next poroperty item on the clear list. @@ -78,17 +77,17 @@ public: nsresult GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, const nsString &aAttr); nsresult GetTypingState(PRBool &isSet, PRBool &theSetting, nsIAtom *aProp, - const nsString &aAttr, const nsString &aValue); + const nsString &aAttr, nsString* outValue); protected: - nsresult RemovePropFromSetList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - nsresult RemovePropFromClearedList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, PRInt32 &outIndex); - PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue); - PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, PRInt32 &outIndex); - PRBool FindPropInList(nsIAtom *aProp, const nsString &aAttr, const nsString &aValue, nsVoidArray &aList, PRInt32 &outIndex); + nsresult RemovePropFromSetList(nsIAtom *aProp, const nsString &aAttr); + nsresult RemovePropFromClearedList(nsIAtom *aProp, const nsString &aAttr); + PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, nsString* outValue); + PRBool IsPropSet(nsIAtom *aProp, const nsString &aAttr, nsString* outValue, PRInt32 &outIndex); + PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr); + PRBool IsPropCleared(nsIAtom *aProp, const nsString &aAttr, PRInt32 &outIndex); + PRBool FindPropInList(nsIAtom *aProp, const nsString &aAttr, nsString *outValue, nsVoidArray &aList, PRInt32 &outIndex); nsVoidArray mSetArray; nsVoidArray mClearedArray; diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp index 258f1811360..7868bf8cdd9 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp @@ -51,7 +51,6 @@ //const static char* kMOZEditorBogusNodeValue="TRUE"; const static PRUnichar nbsp = 160; -static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID); static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID); @@ -64,6 +63,82 @@ enum kBothSibs = 3 }; +/******************************************************** + * first some helpful funcotrs we will use + ********************************************************/ + + +class nsTrivialFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers + { + return PR_TRUE; + } +}; + +class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers + { + if (nsHTMLEditUtils::IsTableCell(aNode)) return PR_TRUE; + if (nsHTMLEditUtils::IsListItem(aNode)) return PR_TRUE; + return PR_FALSE; + } +}; + +class nsBRNodeFunctor : public nsBoolDomIterFunctor +{ + public: + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of all td's & th's iterator covers + { + if (nsHTMLEditUtils::IsBreak(aNode)) return PR_TRUE; + return PR_FALSE; + } +}; + +class nsEmptyFunctor : public nsBoolDomIterFunctor +{ + public: + nsEmptyFunctor(nsHTMLEditor* editor) : mEditor(editor) {} + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of empty li's and td's + { + PRBool bIsEmptyNode; + nsresult res = mEditor->IsEmptyNode(aNode, &bIsEmptyNode, PR_FALSE, PR_FALSE); + if (NS_FAILED(res)) return PR_FALSE; + if (bIsEmptyNode + && (nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCellOrCaption(aNode))) + { + return PR_TRUE; + } + return PR_FALSE; + } + protected: + nsHTMLEditor* mEditor; +}; + +class nsEditableTextFunctor : public nsBoolDomIterFunctor +{ + public: + nsEditableTextFunctor(nsHTMLEditor* editor) : mEditor(editor) {} + virtual PRBool operator()(nsIDOMNode* aNode) // used to build list of empty li's and td's + { + if (nsEditor::IsTextNode(aNode) && mEditor->IsEditable(aNode)) + { + return PR_TRUE; + } + return PR_FALSE; + } + protected: + nsHTMLEditor* mEditor; +}; + + +/******************************************************** + * routine for making new rules instance + ********************************************************/ + nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult) { @@ -361,6 +436,9 @@ nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, switch (info->action) { case kMakeBasicBlock: + case kIndent: + case kOutdent: + case kAlign: return DidMakeBasicBlock(aSelection, aInfo, aResult); } @@ -613,13 +691,31 @@ nsHTMLEditRules::GetParagraphState(PRBool &aMixed, nsString &outFormat) nsresult res = GetParagraphFormatNodes(&arrayOfNodes, PR_TRUE); if (NS_FAILED(res)) return res; - // loop through the nodes in selection and examine their paragraph format + // we might have an empty node list. if so, find selection parent + // and put that on the list PRUint32 listCount; - PRInt32 i; arrayOfNodes->Count(&listCount); + nsCOMPtr isupports; + if (!listCount) + { + nsCOMPtr selNode; + PRInt32 selOffset; + nsCOMPtrselection; + nsresult res = mEditor->GetSelection(getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + res = mEditor->GetStartNodeAndOffset(selection, &selNode, &selOffset); + if (NS_FAILED(res)) return res; + isupports = do_QueryInterface(selNode); + if (!isupports) return NS_ERROR_NULL_POINTER; + arrayOfNodes->AppendElement(isupports); + listCount = 1; + } + + // loop through the nodes in selection and examine their paragraph format + PRInt32 i; for (i=(PRInt32)listCount-1; i>=0; i--) { - nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); + isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports) ); nsAutoString format; @@ -1331,7 +1427,8 @@ nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, if (NS_FAILED(res)) return res; } - if (nsHTMLEditUtils::IsBreak(maybeBreak) && nsEditor::IsBlockNode(maybeBlock)) + if (maybeBreak && maybeBlock && + nsHTMLEditUtils::IsBreak(maybeBreak) && nsEditor::IsBlockNode(maybeBlock)) nodeToDelete = maybeBreak; else if (aAction == nsIEditor::ePrevious) res = mEditor->GetPriorHTMLNode(node, offset, &nodeToDelete); @@ -1524,10 +1621,27 @@ nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, else itemType.AssignWithConversion("li"); - PRBool outMakeEmpty; - res = ShouldMakeEmptyBlock(aSelection, aListType, &outMakeEmpty); + // ok, we aren't creating a new empty list. Instead we are converting + // the set of blocks implied by the selection into a list. + + // convert the selection ranges into "promoted" selection ranges: + // this basically just expands the range to include the immediate + // block parent, and then further expands to include any ancestors + // whose children are all in the range + + *aHandled = PR_TRUE; + + nsAutoSelectionReset selectionResetter(aSelection, mEditor); + + nsCOMPtr arrayOfNodes; + res = GetListActionNodes(&arrayOfNodes); if (NS_FAILED(res)) return res; - if (outMakeEmpty) + + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + + // if no nodes, we make empty list. + if (!listCount) { nsCOMPtr parent, theList, theListItem; PRInt32 offset; @@ -1545,30 +1659,13 @@ nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, if (NS_FAILED(res)) return res; // put selection in new list item res = aSelection->Collapse(theListItem,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = PR_TRUE; return res; } - - // ok, we aren't creating a new empty list. Instead we are converting - // the set of blocks implied by the selection into a list. - - // convert the selection ranges into "promoted" selection ranges: - // this basically just expands the range to include the immediate - // block parent, and then further expands to include any ancestors - // whose children are all in the range - - *aHandled = PR_TRUE; - nsAutoSelectionReset selectionResetter(aSelection, mEditor); - - nsCOMPtr arrayOfNodes; - res = GetListActionNodes(&arrayOfNodes); - if (NS_FAILED(res)) return res; - // if there is only one node in the array, and it is a list, div, or blockquote, // then look inside of it until we find what we want to make a list out of. - PRUint32 listCount; - arrayOfNodes->Count(&listCount); if (listCount == 1) { nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); @@ -1915,14 +2012,6 @@ nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; - - PRBool makeEmpty; - res = ShouldMakeEmptyBlock(aSelection, aBlockType, &makeEmpty); - if (NS_FAILED(res)) return res; - - if (makeEmpty) return res; // just insert a new empty block - - // else it's not that easy... nsAutoSelectionReset selectionResetter(aSelection, mEditor); nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); *aHandled = PR_TRUE; @@ -1936,13 +2025,40 @@ nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeBasicBlock); if (NS_FAILED(res)) return res; - // Ok, now go through all the nodes and make the right kind of blocks, - // or whatever is approriate. Wohoo! - // Note: blockquote is handled a little differently - if (aBlockType->EqualsWithConversion("blockquote")) - res = MakeBlockquote(arrayOfNodes); + // if no nodes, we make empty block. + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) + { + nsCOMPtr parent, theBlock; + PRInt32 offset; + + // get selection location + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + + // make sure we can put a block here + res = SplitAsNeeded(aBlockType, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock)); + if (NS_FAILED(res)) return res; + // put selection in new block + res = aSelection->Collapse(theBlock,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. + *aHandled = PR_TRUE; + return res; + } else - res = ApplyBlockStyle(arrayOfNodes, aBlockType); + { + // Ok, now go through all the nodes and make the right kind of blocks, + // or whatever is approriate. Wohoo! + // Note: blockquote is handled a little differently + if (aBlockType->EqualsWithConversion("blockquote")) + res = MakeBlockquote(arrayOfNodes); + else + res = ApplyBlockStyle(arrayOfNodes, aBlockType); + return res; + } return res; } @@ -1994,6 +2110,31 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kIndent); if (NS_FAILED(res)) return res; + // if no nodes, we make empty block. + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) + { + nsCOMPtr parent, theBlock; + PRInt32 offset; + nsAutoString quoteType; quoteType.AssignWithConversion("blockquote"); + + // get selection location + res = mEditor->GetStartNodeAndOffset(aSelection, &parent, &offset); + if (NS_FAILED(res)) return res; + + // make sure we can put a block here + res = SplitAsNeeded("eType, &parent, &offset); + if (NS_FAILED(res)) return res; + res = mEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); + if (NS_FAILED(res)) return res; + // put selection in new block + res = aSelection->Collapse(theBlock,0); + selectionResetter.Abort(); // to prevent selection reseter from overriding us. + *aHandled = PR_TRUE; + return res; + } + // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. @@ -2004,9 +2145,7 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! - PRUint32 listCount; PRInt32 i; - arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curQuote; nsCOMPtr curList; @@ -2045,7 +2184,7 @@ nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool { // need to make a blockquote to put things in if we haven't already, // or if this node doesn't go in blockquote we used earlier. - if (!curQuote || transitionList[i]) + if (!curQuote) // || transitionList[i]) { nsAutoString quoteType; quoteType.AssignWithConversion("blockquote"); res = SplitAsNeeded("eType, &curParent, &offset); @@ -2352,11 +2491,26 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, *aCancel = PR_FALSE; *aHandled = PR_FALSE; + nsAutoSelectionReset selectionResetter(aSelection, mEditor); - PRBool outMakeEmpty; - res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty); + // convert the selection ranges into "promoted" selection ranges: + // this basically just expands the range to include the immediate + // block parent, and then further expands to include any ancestors + // whose children are all in the range + *aHandled = PR_TRUE; + + nsCOMPtr arrayOfRanges; + res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign); if (NS_FAILED(res)) return res; - if (outMakeEmpty) + + // use these ranges to contruct a list of nodes to act on. + nsCOMPtr arrayOfNodes; + res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign); + if (NS_FAILED(res)) return res; + + PRUint32 listCount; + arrayOfNodes->Count(&listCount); + if (!listCount) { PRInt32 offset; nsCOMPtr brNode, parent, theDiv, sib; @@ -2369,7 +2523,7 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, // creating extra lines, if possible. res = mEditor->GetNextHTMLNode(parent, offset, &brNode); if (NS_FAILED(res)) return res; - if (nsHTMLEditUtils::IsBreak(brNode)) + if (brNode && nsHTMLEditUtils::IsBreak(brNode)) { // making use of html structure... if next node after where // we are putting our div is not a block, then the br we @@ -2394,26 +2548,10 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, res = CreateMozBR(theDiv, 0, &brNode); if (NS_FAILED(res)) return res; res = aSelection->Collapse(theDiv, 0); + selectionResetter.Abort(); // dont reset our selection in this case. return res; } - nsAutoSelectionReset selectionResetter(aSelection, mEditor); - - // convert the selection ranges into "promoted" selection ranges: - // this basically just expands the range to include the immediate - // block parent, and then further expands to include any ancestors - // whose children are all in the range - *aHandled = PR_TRUE; - - nsCOMPtr arrayOfRanges; - res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign); - if (NS_FAILED(res)) return res; - - // use these ranges to contruct a list of nodes to act on. - nsCOMPtr arrayOfNodes; - res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign); - if (NS_FAILED(res)) return res; - // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. @@ -2424,9 +2562,7 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, // Ok, now go through all the nodes and give them an align attrib or put them in a div, // or whatever is appropriate. Wohoo! - PRUint32 listCount; PRInt32 i; - arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curDiv; for (i=0; i<(PRInt32)listCount; i++) @@ -2450,11 +2586,12 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, continue; } - // if it's a table element (but not a table), forget any "current" div, and - // instead put divs inside any th's and td's inside the table element - if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode)) + // if it's a table element (but not a table) or a list item, forget any "current" div, and + // instead put divs inside the appropriate block (td, li, etc) + if ( (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode)) + || nsHTMLEditUtils::IsListItem(curNode) ) { - res = AlignTableElement(curNode, alignType); + res = AlignInnerBlocks(curNode, alignType); if (NS_FAILED(res)) return res; // clear out curDiv so that we don't put nodes after this one into it curDiv = 0; @@ -2491,56 +2628,35 @@ nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, /////////////////////////////////////////////////////////////////////////// -// AlignTableElement: take a table element and align it's contents -// +// AlignInnerBlocks: align inside table cells or list items +// nsresult -nsHTMLEditRules::AlignTableElement(nsIDOMNode *aNode, const nsString *alignType) +nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsString *alignType) { if (!aNode || !alignType) return NS_ERROR_NULL_POINTER; nsresult res; - nsCOMPtr iter; - res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); - if (NS_FAILED(res)) return res; - if (!iter) return NS_ERROR_FAILURE; - + + // gather list of table cells or list items nsCOMPtr arrayOfNodes; - nsCOMPtr content; - nsCOMPtr node; - nsCOMPtr isupports; - - // make a array - res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + nsTableCellAndListItemFunctor functor; + nsDOMIterator iter; + res = iter.Init(aNode); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &arrayOfNodes); if (NS_FAILED(res)) return res; - - // iterate node and build up array of td's and th's - content = do_QueryInterface(aNode); - iter->Init(content); - while (NS_ENUMERATOR_FALSE == iter->IsDone()) - { - res = iter->CurrentNode(getter_AddRefs(content)); - if (NS_FAILED(res)) return res; - node = do_QueryInterface(content); - if (!node) return NS_ERROR_FAILURE; - if (nsHTMLEditUtils::IsTableCell(node)) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - res = iter->Next(); - if (NS_FAILED(res)) return res; - } // now that we have the list, align their contents as requested PRUint32 listCount; PRUint32 j; + nsCOMPtr node; + nsCOMPtr isupports; + arrayOfNodes->Count(&listCount); for (j = 0; j < listCount; j++) { isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); node = do_QueryInterface(isupports); - res = AlignTableCellContents(node, alignType); + res = AlignBlockContents(node, alignType); if (NS_FAILED(res)) return res; arrayOfNodes->RemoveElementAt(0); } @@ -2550,10 +2666,10 @@ nsHTMLEditRules::AlignTableElement(nsIDOMNode *aNode, const nsString *alignType) /////////////////////////////////////////////////////////////////////////// -// AlignTableElement: take a table cell and align it's contents +// AlignBlockContents: align contents of a block element // nsresult -nsHTMLEditRules::AlignTableCellContents(nsIDOMNode *aNode, const nsString *alignType) +nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsString *alignType) { if (!aNode || !alignType) return NS_ERROR_NULL_POINTER; nsresult res; @@ -2578,7 +2694,7 @@ nsHTMLEditRules::AlignTableCellContents(nsIDOMNode *aNode, const nsString *align } else { - // else we need to put in a div, set the alignment, and toss in al the children + // else we need to put in a div, set the alignment, and toss in all the children nsAutoString divType; divType.AssignWithConversion("div"); res = mEditor->CreateNode(divType, aNode, 0, getter_AddRefs(divNode)); if (NS_FAILED(res)) return res; @@ -2862,16 +2978,20 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); - nsCOMPtr prevNode; - prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); - while (prevNode) + nsCOMPtr prevNode, prevNodeBlock; + res = mEditor->GetPriorHTMLNode(node, &prevNode); + + while (prevNode && NS_SUCCEEDED(res)) { + prevNodeBlock = nsEditor::GetBlockNodeParent(prevNode); + if (prevNodeBlock != block) + break; if (nsHTMLEditUtils::IsBreak(prevNode)) break; if (nsEditor::IsBlockNode(prevNode)) break; node = prevNode; - prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); + res = mEditor->GetPriorHTMLNode(node, &prevNode); } } @@ -2883,6 +3003,10 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (NS_FAILED(res)) return res; while ((IsFirstNode(node)) && (!nsHTMLEditUtils::IsBody(parent))) { + // special case for outdent: don't keep looking up + // if we have found a blockquote element to act on + if ((actionID == kOutdent) && nsHTMLEditUtils::IsBlockquote(parent)) + break; node = parent; res = nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; @@ -2915,10 +3039,14 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); - nsCOMPtr nextNode; - nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); - while (nextNode) + nsCOMPtr nextNode, nextNodeBlock; + res = mEditor->GetNextHTMLNode(node, &nextNode); + + while (nextNode && NS_SUCCEEDED(res)) { + nextNodeBlock = nsEditor::GetBlockNodeParent(nextNode); + if (nextNodeBlock != block) + break; if (nsHTMLEditUtils::IsBreak(nextNode)) { node = nextNode; @@ -2927,7 +3055,7 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt if (nsEditor::IsBlockNode(nextNode)) break; node = nextNode; - nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); + res = mEditor->GetNextHTMLNode(node, &nextNode); } } @@ -3053,6 +3181,7 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, { if (!inArrayOfRanges || !outArrayOfNodes) return NS_ERROR_NULL_POINTER; + // make a array nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); if (NS_FAILED(res)) return res; @@ -3063,38 +3192,62 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, PRInt32 i; nsCOMPtr opRange; nsCOMPtr isupports; - nsCOMPtr iter; + // bust up any inlines that cross our range endpoints, + // but only if we are allowed to touch content. + + if (!aDontTouchContent) + { + nsVoidArray rangeItemArray; + // first register ranges for special editor gravity + for (i = 0; i < (PRInt32)rangeCount; i++) + { + isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i)); + opRange = do_QueryInterface(isupports); + nsRangeStore *item = new nsRangeStore(); + if (!item) return NS_ERROR_NULL_POINTER; + item->StoreRange(opRange); + mEditor->mRangeUpdater.RegisterRangeItem(item); + rangeItemArray.AppendElement((void*)item); + inArrayOfRanges->RemoveElementAt(0); + } + // now bust up inlines + for (i = (PRInt32)rangeCount-1; i >= 0; i--) + { + nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(i); + res = BustUpInlinesAtRangeEndpoints(*item); + if (NS_FAILED(res)) return res; + } + // then unregister the ranges + for (i = 0; i < (PRInt32)rangeCount; i++) + { + nsRangeStore *item = (nsRangeStore*)rangeItemArray.ElementAt(0); + if (!item) return NS_ERROR_NULL_POINTER; + mEditor->mRangeUpdater.DropRangeItem(item); + res = item->GetRange(&opRange); + if (NS_FAILED(res)) return res; + nsCOMPtr isupports = do_QueryInterface(opRange); + inArrayOfRanges->AppendElement(isupports); + } + } + // gather up a list of all the nodes for (i = 0; i < (PRInt32)rangeCount; i++) { isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i)); opRange = do_QueryInterface(isupports); - res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, - nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); - if (NS_FAILED(res)) return res; - res = iter->Init(opRange); - if (NS_FAILED(res)) return res; - 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; - isupports = do_QueryInterface(node); - (*outArrayOfNodes)->AppendElement(isupports); - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - } - // post process the list to break up inline containers that contain br's. - // but only for operations that might care, like making lists or para's... + nsTrivialFunctor functor; + nsDOMSubtreeIterator iter; + res = iter.Init(opRange); + if (NS_FAILED(res)) return res; + res = iter.AppendList(functor, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + + // certain operations should not act on li's and td's, but rather inside + // them. alter the list as needed if ( (inOperationType == kMakeBasicBlock) || - (inOperationType == kMakeList) ) + (inOperationType == kAlign) ) { PRUint32 listCount; (*outArrayOfNodes)->Count(&listCount); @@ -3102,7 +3255,52 @@ nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, { nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); nsCOMPtr node( do_QueryInterface(isupports) ); - if (!aDontTouchContent && mEditor->IsInlineNode(node) && mEditor->IsContainer(node)) + if ( (nsHTMLEditUtils::IsTableElement(node) && !nsHTMLEditUtils::IsTable(node)) + || (nsHTMLEditUtils::IsListItem(node))) + { + (*outArrayOfNodes)->RemoveElementAt(i); + res = GetInnerContent(node, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + } + } + // indent/outdent already do something special for list items, but + // we still need to make sure we dont act on table elements + else if ( (inOperationType == kOutdent) || + (inOperationType == kIndent) ) + { + PRUint32 listCount; + (*outArrayOfNodes)->Count(&listCount); + for (i=(PRInt32)listCount-1; i>=0; i--) + { + nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); + nsCOMPtr node( do_QueryInterface(isupports) ); + if ( (nsHTMLEditUtils::IsTableElement(node) && !nsHTMLEditUtils::IsTable(node)) ) + { + (*outArrayOfNodes)->RemoveElementAt(i); + res = GetInnerContent(node, *outArrayOfNodes); + if (NS_FAILED(res)) return res; + } + } + } + + + // post process the list to break up inline containers that contain br's. + // but only for operations that might care, like making lists or para's... + if ( (inOperationType == kMakeBasicBlock) || + (inOperationType == kMakeList) || + (inOperationType == kAlign) || + (inOperationType == kIndent) || + (inOperationType == kOutdent) ) + { + PRUint32 listCount; + (*outArrayOfNodes)->Count(&listCount); + for (i=(PRInt32)listCount-1; i>=0; i--) + { + nsCOMPtr isupports = (dont_AddRef)((*outArrayOfNodes)->ElementAt(i)); + nsCOMPtr node( do_QueryInterface(isupports) ); + if (!aDontTouchContent && mEditor->IsInlineNode(node) + && mEditor->IsContainer(node) && !mEditor->IsTextNode(node)) { nsCOMPtr arrayOfInlines; res = BustUpInlinesAtBRs(node, &arrayOfInlines); @@ -3285,6 +3483,49 @@ nsHTMLEditRules::GetParagraphFormatNodes(nsCOMPtr *outArrayOfN } +/////////////////////////////////////////////////////////////////////////// +// BustUpInlinesAtRangeEndpoints: +// +nsresult +nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) +{ + nsresult res = NS_OK; + PRBool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); + + nsCOMPtr endInline = GetHighestInlineParent(item.endNode); + + // if we have inline parents above range endpoints, split them + if (endInline && !isCollapsed) + { + nsCOMPtr resultEndNode; + PRInt32 resultEndOffset; + item.endNode->GetParentNode(getter_AddRefs(resultEndNode)); + res = mEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset, + &resultEndOffset, PR_TRUE); + if (NS_FAILED(res)) return res; + // reset range + item.endNode = resultEndNode; item.endOffset = resultEndOffset; + } + + nsCOMPtr startInline = GetHighestInlineParent(item.startNode); + + if (startInline) + { + nsCOMPtr resultStartNode; + PRInt32 resultStartOffset; + item.startNode->GetParentNode(getter_AddRefs(resultStartNode)); + res = mEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset, + &resultStartOffset, PR_TRUE); + if (NS_FAILED(res)) return res; + // reset range + item.startNode = resultStartNode; item.startOffset = resultStartOffset; + } + + return res; +} + + + /////////////////////////////////////////////////////////////////////////// // BustUpInlinesAtBRs: // @@ -3300,38 +3541,15 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, // first step is to build up a list of all the break nodes inside // the inline container. nsCOMPtr arrayOfBreaks; - res = NS_NewISupportsArray(getter_AddRefs(arrayOfBreaks)); + nsBRNodeFunctor functor; + nsDOMIterator iter; + res = iter.Init(inNode); if (NS_FAILED(res)) return res; - - nsCOMPtr iter; - nsCOMPtr cNode = do_QueryInterface(inNode); - res = nsComponentManager::CreateInstance(kContentIteratorCID, - nsnull, - NS_GET_IID(nsIContentIterator), - getter_AddRefs(iter)); + res = iter.MakeList(functor, &arrayOfBreaks); if (NS_FAILED(res)) return res; - res = iter->Init(cNode); - if (NS_FAILED(res)) return res; - nsCOMPtr isupports; - - 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 (nsHTMLEditUtils::IsBreak(node)) - { - isupports = do_QueryInterface(node); - arrayOfBreaks->AppendElement(isupports); - } - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // if there aren't and breaks, just put inNode itself in the array + // if there aren't any breaks, just put inNode itself in the array + nsCOMPtr isupports; PRUint32 listCount; arrayOfBreaks->Count(&listCount); if (!listCount) @@ -3361,7 +3579,7 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, res = nsEditor::GetNodeLocation(breakNode, &splitParentNode, &splitOffset); if (NS_FAILED(res)) return res; res = mEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset, - &resultOffset, &leftNode, &rightNode); + &resultOffset, PR_FALSE, &leftNode, &rightNode); if (NS_FAILED(res)) return res; // put left node in node list if (leftNode) @@ -3394,6 +3612,20 @@ nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, } +nsCOMPtr +nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode) +{ + if (!aNode) return nsnull; + if (nsEditor::IsBlockNode(aNode)) return nsnull; + nsCOMPtr inlineNode, node=aNode; + + while (node && mEditor->IsInlineNode(node)) + { + inlineNode = node; + inlineNode->GetParentNode(getter_AddRefs(node)); + } + return inlineNode; +} /////////////////////////////////////////////////////////////////////////// // MakeTransitionList: detect all the transitions in the array, where a @@ -3773,124 +4005,6 @@ nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, } -/////////////////////////////////////////////////////////////////////////// -// ShouldMakeEmptyBlock: determine if a block transformation should make -// a new empty block, or instead transform a block -// -nsresult -nsHTMLEditRules::ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, - const nsString *blockTag, - PRBool *outMakeEmpty) -{ - // a note about strategy: - // this routine will be called by the rules code to figure out - // if it should do something, or let the nsHTMLEditor default - // action happen. The default action is to insert a new block. - // Note that if _nothing_ should happen, ie, the selection is - // already entireyl inside a block (or blocks) or the correct type, - // then you don't want to return true in outMakeEmpty, since the - // default code will insert a new empty block anyway, rather than - // doing nothing. So we have to detect that case and return false. - - if (!aSelection || !outMakeEmpty) return NS_ERROR_NULL_POINTER; - nsresult res = NS_OK; - - // if the selection is collapsed, and - // if we in the body, or after a
with - // no more inline content before the next block, - // or in an empty block (empty td, li, etc), then we want - // a new block. Otherwise we want to trasform a block - - // xxx possible bug: selection could be not callapsed, but - // still empty. it would be nice to have a call for this: IsEmptySelection() - - PRBool isCollapsed; - res = aSelection->GetIsCollapsed(&isCollapsed); - if (NS_FAILED(res)) return res; - if (isCollapsed) - { - nsCOMPtr parent; - PRInt32 offset; - res = nsEditor::GetStartNodeAndOffset(aSelection, &parent, &offset); - if (NS_FAILED(res)) return res; - - // is selection point in the body? or a cell? - if (nsHTMLEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) - { - *outMakeEmpty = PR_TRUE; - return res; - } - - // see if block parent is already right kind of block. - // See strategy comment above. - nsCOMPtr block; - if (!nsEditor::IsBlockNode(parent)) - block = nsEditor::GetBlockNodeParent(parent); - else - block = parent; - if (block) - { - nsAutoString tag; - nsEditor::GetTagString(block,tag); - if (tag == *blockTag) - { - *outMakeEmpty = PR_FALSE; - return res; - } - } - - // is it after a
with no inline nodes after it, or a
after it?? - if (offset) - { - nsCOMPtr prevChild, nextChild, tmp; - prevChild = nsEditor::GetChildAt(parent, offset-1); - while (prevChild && !mEditor->IsEditable(prevChild)) - { - // search back until we either find an editable node, - // or hit the beginning of the block - tmp = nsEditor::NextNodeInBlock(prevChild, nsEditor::kIterBackward); - prevChild = tmp; - } - - if (prevChild && nsHTMLEditUtils::IsBreak(prevChild)) - { - nextChild = nsEditor::GetChildAt(parent, offset); - while (nextChild && !mEditor->IsEditable(nextChild)) - { - // search back until we either find an editable node, - // or hit the beginning of the block - tmp = nsEditor::NextNodeInBlock(nextChild, nsEditor::kIterForward); - nextChild = tmp; - } - if (!nextChild || nsHTMLEditUtils::IsBreak(nextChild) - || nsEditor::IsBlockNode(nextChild)) - { - // we are after a
and not before inline content, - // or we are between
s. - // make an empty block - *outMakeEmpty = PR_TRUE; - return res; - } - } - } - - // are we in a textnode or inline node? - if (!nsEditor::IsBlockNode(parent)) - { - // we must be in a text or inline node - convert existing block - *outMakeEmpty = PR_FALSE; - return res; - } - - } - // otherwise transform an existing block - *outMakeEmpty = PR_FALSE; - return res; -} - - - - /////////////////////////////////////////////////////////////////////////// // MakeBlockquote: put the list of nodes into one or more blockquotes. // @@ -4166,7 +4280,7 @@ nsHTMLEditRules::AddTerminatingBR(nsIDOMNode *aBlock) { if (!aBlock) return NS_ERROR_NULL_POINTER; nsCOMPtr last; - nsresult res = mEditor->GetLastEditableChild(aBlock, &last); + nsresult res = mEditor->GetLastEditableLeaf(aBlock, &last); if (last && nsHTMLEditUtils::IsBreak(last)) { if (nsHTMLEditUtils::IsMozBR(last)) @@ -4306,50 +4420,18 @@ nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, nsCOMPtr nsresult nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames) { - nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; - // make an isupportsArray to hold a list of nodes - nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); + // gather list of empty nodes + nsEmptyFunctor functor(mEditor); + nsDOMIterator iter; + nsresult res = iter.Init(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &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; - - // loop over iter and create list of empty containers - res = iter->Init(mDocChangeRange); - if (NS_FAILED(res)) return res; - - // gather up a list of empty 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; - - PRBool bIsEmptyNode; - res = mEditor->IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_FALSE, aSafeToAskFrames); - if (NS_FAILED(res)) return res; - if (bIsEmptyNode - && (nsHTMLEditUtils::IsListItem(node) || nsHTMLEditUtils::IsTableCellOrCaption(node))) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // put moz-br's into these empty li's and td's res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; @@ -4369,7 +4451,6 @@ nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames) nsresult nsHTMLEditRules::AdjustWhitespace(nsIDOMSelection *aSelection) { - nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; @@ -4400,41 +4481,14 @@ nsHTMLEditRules::AdjustWhitespace(nsIDOMSelection *aSelection) } } - // make an isupportsArray to hold a list of nodes - 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; - - // loop over iter and adjust whitespace in any text nodes we find - res = iter->Init(mDocChangeRange); - if (NS_FAILED(res)) return res; - // gather up a list of 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; + nsEditableTextFunctor functor(mEditor); + nsDOMIterator iter; + res = iter.Init(mDocChangeRange); + if (NS_FAILED(res)) return res; + res = iter.MakeList(functor, &arrayOfNodes); + if (NS_FAILED(res)) return res; - if (nsEditor::IsTextNode(node) && mEditor->IsEditable(node)) - { - isupports = do_QueryInterface(node); - arrayOfNodes->AppendElement(isupports); - } - - res = iter->Next(); - if (NS_FAILED(res)) return res; - } - // now adjust whitespace on node we found res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; @@ -4902,7 +4956,7 @@ nsHTMLEditRules::DoTextNodeWhitespace(nsIDOMCharacterData *aTextNode, PRInt32 aS runStart = -1; // reset our run } j++; // next char please! - } while (j *outArrayOfNodes, PRBool aDontTouchContent=PR_FALSE); nsresult GetDefinitionListItemTypes(nsIDOMNode *aNode, PRBool &aDT, PRBool &aDD); nsresult GetParagraphFormatNodes(nsCOMPtr *outArrayOfNodes, PRBool aDontTouchContent=PR_FALSE); + nsresult BustUpInlinesAtRangeEndpoints(nsRangeStore &inRange); nsresult BustUpInlinesAtBRs(nsIDOMNode *inNode, nsCOMPtr *outArrayOfNodes); + nsCOMPtr GetHighestInlineParent(nsIDOMNode* aNode); nsresult MakeTransitionList(nsISupportsArray *inArrayOfNodes, nsVoidArray *inTransitionArray); - nsresult ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, const nsString *blockTag, PRBool *outMakeEmpty); nsresult ApplyBlockStyle(nsISupportsArray *arrayOfNodes, const nsString *aBlockTag); nsresult MakeBlockquote(nsISupportsArray *arrayOfNodes); nsresult SplitAsNeeded(const nsString *aTag, nsCOMPtr *inOutParent, PRInt32 *inOutOffset); diff --git a/mozilla/editor/libeditor/html/nsHTMLEditUtils.cpp b/mozilla/editor/libeditor/html/nsHTMLEditUtils.cpp index f8f722b4b11..8fa1230f08e 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditUtils.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditUtils.cpp @@ -42,6 +42,7 @@ nsHTMLEditUtils::IsBody(nsIDOMNode *node) { nsAutoString tag; nsEditor::GetTagString(node,tag); + tag.ToLowerCase(); if (tag.EqualsWithConversion("body")) { return PR_TRUE; @@ -573,4 +574,11 @@ nsHTMLEditUtils::IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent) } - +PRBool +nsHTMLEditUtils::IsLeafNode(nsIDOMNode *aNode) +{ + if (!aNode) return PR_FALSE; + PRBool hasChildren = PR_FALSE; + aNode->HasChildNodes(&hasChildren); + return !hasChildren; +} \ No newline at end of file diff --git a/mozilla/editor/libeditor/html/nsHTMLEditUtils.h b/mozilla/editor/libeditor/html/nsHTMLEditUtils.h index b05ea6df602..4904f271157 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditUtils.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditUtils.h @@ -63,6 +63,9 @@ public: static PRBool IsMozDiv(nsIDOMNode *aNode); static PRBool IsMailCite(nsIDOMNode *aNode); static PRBool IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent); + + static PRBool IsLeafNode(nsIDOMNode *aNode); + }; #endif /* nsHTMLEditUtils_h__ */ diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index 56652547668..ff2a2f557f8 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -1777,6 +1777,16 @@ NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty, if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode); // cache now current, use it! But override it with typeInState results if any... PRBool isSet, theSetting; + if (aAttribute) + mTypeInState->GetTypingState(isSet, theSetting, aProperty, *aAttribute, outValue); + else + mTypeInState->GetTypingState(isSet, theSetting, aProperty); + if (isSet) + { + aFirst = aAny = aAll = theSetting; + return NS_OK; + } + /* if (aProperty == mBoldAtom.get()) { mTypeInState->GetTypingState(isSet, theSetting, aProperty); @@ -1815,7 +1825,7 @@ NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty, aFirst = aAny = aAll = mCachedUnderlineStyle; } return NS_OK; - } + } */ } // either non-collapsed selection or no cached value: do it the hard way @@ -7102,7 +7112,7 @@ nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr< /////////////////////////////////////////////////////////////////////////// -// GetNextHTMLNode: returns the previous editable leaf node, if there is +// GetNextHTMLNode: returns the next editable leaf node, if there is // one within the // nsresult @@ -7239,6 +7249,75 @@ nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOu return res; } +nsresult +nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf) +{ + // check parms + if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER; + + // init out parms + *aOutFirstLeaf = nsnull; + + // find leftmost leaf + nsCOMPtr child; + nsresult res = GetLeftmostChild(aNode, getter_AddRefs(child)); + if (NS_FAILED(res)) return res; + + while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) + { + nsCOMPtr tmp; + res = GetNextHTMLNode(child, &tmp); + if (NS_FAILED(res)) return res; + if (!tmp) return NS_ERROR_FAILURE; + + // only accept nodes that are descendants of aNode + if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) + child = tmp; + else + { + child = nsnull; // this will abort the loop + } + } + + *aOutFirstLeaf = child; + return res; +} + + +nsresult +nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf) +{ + // check parms + if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER; + + // init out parms + *aOutLastLeaf = nsnull; + + // find leftmost leaf + nsCOMPtr child; + nsresult res = GetRightmostChild(aNode, getter_AddRefs(child)); + if (NS_FAILED(res)) return res; + + while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) + { + nsCOMPtr tmp; + res = GetPriorHTMLNode(child, &tmp); + if (NS_FAILED(res)) return res; + if (!tmp) return NS_ERROR_FAILURE; + + // only accept nodes that are descendants of aNode + if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) + child = tmp; + else + { + child = nsnull; + } + } + + *aOutLastLeaf = child; + return res; +} + /////////////////////////////////////////////////////////////////////////// // IsEmptyNode: figure out if aNode is an empty node. // A block can have children and still be considered empty, diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index a08a1084b04..d6592c4a35f 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -332,6 +332,11 @@ public: // aSelection is optional -- if null, we get current seletion nsresult CollapseSelectionToDeepestNonTableFirstChild(nsIDOMSelection *aSelection, nsIDOMNode *aNode); + nsresult IsEmptyNode(nsIDOMNode *aNode, PRBool *outIsEmptyBlock, + PRBool aMozBRDoesntCount = PR_FALSE, + PRBool aListOrCellNotEmpty = PR_FALSE, + PRBool aSafeToAskFrames = PR_FALSE); + protected: NS_IMETHOD InitRules(); @@ -563,10 +568,8 @@ protected: nsresult IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast); nsresult GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutFirstChild); nsresult GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutLastChild); - nsresult IsEmptyNode(nsIDOMNode *aNode, PRBool *outIsEmptyBlock, - PRBool aMozBRDoesntCount = PR_FALSE, - PRBool aListOrCellNotEmpty = PR_FALSE, - PRBool aSafeToAskFrames = PR_FALSE); + nsresult GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf); + nsresult GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf); nsresult GetDOMEventReceiver(nsIDOMEventReceiver **aEventReceiver); diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.cpp b/mozilla/editor/libeditor/text/nsTextEditRules.cpp index 2fc89018387..517342246b3 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.cpp +++ b/mozilla/editor/libeditor/text/nsTextEditRules.cpp @@ -477,7 +477,7 @@ nsTextEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel, P printf("It's a moz quote -- splitting\n"); nsCOMPtr outLeftNode; nsCOMPtr outRightNode; - res = mEditor->SplitNodeDeep(preNode, selNode, selOffset, &newOffset, &outLeftNode, &outRightNode); + res = mEditor->SplitNodeDeep(preNode, selNode, selOffset, &newOffset, PR_TRUE, &outLeftNode, &outRightNode); if (NS_FAILED(res)) return res; PRBool bIsEmptyNode;