From d8853e9b1b5f5ecaccaff137b8069df4e5adf2be Mon Sep 17 00:00:00 2001 From: "jfrancis%netscape.com" Date: Mon, 31 Jan 2000 10:30:12 +0000 Subject: [PATCH] make selection sticky across undo/redo (24573); get bold/italic/underline toolbar feedback working (24574); fix for 24856: unable to unbold text; fix for 24572: cant get selection between split lists git-svn-id: svn://10.0.0.236/trunk@59246 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/editor/base/InsertElementTxn.cpp | 2 +- mozilla/editor/base/PlaceholderTxn.cpp | 134 ++++++++---- mozilla/editor/base/PlaceholderTxn.h | 16 +- mozilla/editor/base/nsEditor.cpp | 193 ++++++++++++++++-- mozilla/editor/base/nsEditor.h | 36 +++- mozilla/editor/base/nsEditorShell.cpp | 2 +- mozilla/editor/base/nsEditorUtils.h | 45 ++-- mozilla/editor/base/nsHTMLEditRules.cpp | 4 - mozilla/editor/base/nsHTMLEditor.cpp | 78 ++++++- mozilla/editor/base/nsHTMLEditor.h | 16 +- mozilla/editor/base/nsIAbsorbingTransaction.h | 6 +- mozilla/editor/base/nsInterfaceState.cpp | 53 ++--- mozilla/editor/composer/src/nsEditorShell.cpp | 2 +- .../editor/composer/src/nsInterfaceState.cpp | 53 ++--- .../libeditor/base/InsertElementTxn.cpp | 2 +- .../editor/libeditor/base/PlaceholderTxn.cpp | 134 ++++++++---- .../editor/libeditor/base/PlaceholderTxn.h | 16 +- mozilla/editor/libeditor/base/nsEditor.cpp | 193 ++++++++++++++++-- mozilla/editor/libeditor/base/nsEditor.h | 36 +++- mozilla/editor/libeditor/base/nsEditorUtils.h | 45 ++-- .../libeditor/base/nsIAbsorbingTransaction.h | 6 +- .../editor/libeditor/html/nsHTMLEditRules.cpp | 4 - .../editor/libeditor/html/nsHTMLEditor.cpp | 78 ++++++- mozilla/editor/libeditor/html/nsHTMLEditor.h | 16 +- 24 files changed, 880 insertions(+), 290 deletions(-) diff --git a/mozilla/editor/base/InsertElementTxn.cpp b/mozilla/editor/base/InsertElementTxn.cpp index 22fe60b61a3..fbe7cdc6fad 100644 --- a/mozilla/editor/base/InsertElementTxn.cpp +++ b/mozilla/editor/base/InsertElementTxn.cpp @@ -25,7 +25,7 @@ #include "nsIContent.h" #ifdef NS_DEBUG -static PRBool gNoisy = PR_TRUE; +static PRBool gNoisy = PR_FALSE; #else static const PRBool gNoisy = PR_FALSE; #endif diff --git a/mozilla/editor/base/PlaceholderTxn.cpp b/mozilla/editor/base/PlaceholderTxn.cpp index cd7cbba6131..0c048e54cf2 100644 --- a/mozilla/editor/base/PlaceholderTxn.cpp +++ b/mozilla/editor/base/PlaceholderTxn.cpp @@ -39,7 +39,9 @@ PlaceholderTxn::PlaceholderTxn() : EditAggregateTxn(), mAbsorb(PR_TRUE), mForwarding(nsnull), mIMETextTxn(nsnull), - mCommitted(PR_FALSE) + mCommitted(PR_FALSE), + mStartSel(nsnull), + mEndSel() { SetTransactionDescriptionID( kTransactionID ); /* log description initialized in parent constructor */ @@ -48,6 +50,7 @@ PlaceholderTxn::PlaceholderTxn() : EditAggregateTxn(), PlaceholderTxn::~PlaceholderTxn() { + delete mStartSel; } NS_IMPL_ADDREF_INHERITED(PlaceholderTxn, EditAggregateTxn) @@ -72,15 +75,15 @@ NS_IMETHODIMP PlaceholderTxn::QueryInterface(REFNSIID aIID, void** aInstancePtr) } NS_IMETHODIMP PlaceholderTxn::Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, - nsIDOMNode *aStartNode, PRInt32 aStartOffset) + nsSelectionState *aSelState) { NS_ASSERTION(aPresShellWeak, "bad args"); - if (!aPresShellWeak) return NS_ERROR_NULL_POINTER; + if (!aPresShellWeak || !aSelState) return NS_ERROR_NULL_POINTER; mPresShellWeak = aPresShellWeak; mName = aName; - mStartNode = do_QueryInterface(aStartNode); - mStartOffset = aStartOffset; + mStartSel = aSelState; + return NS_OK; } @@ -92,8 +95,38 @@ NS_IMETHODIMP PlaceholderTxn::Do(void) NS_IMETHODIMP PlaceholderTxn::Undo(void) { - // using this to debug - return EditAggregateTxn::Undo(); + // undo txns + nsresult res = EditAggregateTxn::Undo(); + if (NS_FAILED(res)) return res; + + // now restore selection + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + if (!mStartSel) return NS_ERROR_NULL_POINTER; + res = mStartSel->RestoreSelection(selection); + return res; +} + + +NS_IMETHODIMP PlaceholderTxn::Redo(void) +{ + // redo txns + nsresult res = EditAggregateTxn::Redo(); + if (NS_FAILED(res)) return res; + + // now restore selection + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + res = mEndSel.RestoreSelection(selection); + return res; } @@ -146,6 +179,10 @@ NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransact AppendChild(editTxn); } *aDidMerge = PR_TRUE; +// RememberEndingSelection(); +// efficiency hack: no need to remember selection here, as we haven't yet +// finished the inital batch and we know we will be told when the batch ends. +// we can remeber the selection then. if (gNoisy) { printf("Placeholder txn assimilated %p\n", aTransaction); } } else @@ -155,27 +192,31 @@ NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransact (mName.get() == nsHTMLEditor::gDeleteTxnName)) && !mCommitted ) { - nsCOMPtr plcTxn;// = do_QueryInterface(editTxn); - // cant do_QueryInterface() above due to our broken transaction interfaces. - // instead have to brute it below. ugh. - editTxn->QueryInterface(NS_GET_IID(nsIAbsorbingTransaction), getter_AddRefs(plcTxn)); - if (plcTxn) + // but only if this placeholder started with a collapsed selection + if (mStartSel->IsCollapsed()) { - nsCOMPtr atom; - plcTxn->GetTxnName(getter_AddRefs(atom)); - if (atom && (atom == mName)) + nsCOMPtr plcTxn;// = do_QueryInterface(editTxn); + // cant do_QueryInterface() above due to our broken transaction interfaces. + // instead have to brute it below. ugh. + editTxn->QueryInterface(NS_GET_IID(nsIAbsorbingTransaction), getter_AddRefs(plcTxn)); + if (plcTxn) { - nsCOMPtr otherTxnStartNode; - PRInt32 otherTxnStartOffset; - res = plcTxn->GetStartNodeAndOffset(&otherTxnStartNode, &otherTxnStartOffset); - if (NS_FAILED(res)) return res; - - if ((otherTxnStartNode == mEndNode) && (otherTxnStartOffset == mEndOffset)) + nsCOMPtr atom; + plcTxn->GetTxnName(getter_AddRefs(atom)); + if (atom && (atom == mName)) { - mAbsorb = PR_TRUE; // we need to start absorbing again - plcTxn->ForwardEndBatchTo(this); - AppendChild(editTxn); - *aDidMerge = PR_TRUE; + // check if start selection of next placeholder matches + // end selection of this placeholder + PRBool isSame; + plcTxn->StartSelectionEquals(&mEndSel, &isSame); + if (isSame) + { + mAbsorb = PR_TRUE; // we need to start absorbing again + plcTxn->ForwardEndBatchTo(this); + AppendChild(editTxn); + RememberEndingSelection(); + *aDidMerge = PR_TRUE; + } } } } @@ -189,11 +230,17 @@ NS_IMETHODIMP PlaceholderTxn::GetTxnName(nsIAtom **aName) return GetName(aName); } -NS_IMETHODIMP PlaceholderTxn::GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset) +NS_IMETHODIMP PlaceholderTxn::StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult) { - if (!aTxnStartNode || !aTxnStartOffset) return NS_ERROR_NULL_POINTER; - *aTxnStartNode = mStartNode; - *aTxnStartOffset = mStartOffset; + // determine if starting selection matches the given selection state. + // note that we only care about collapsed selections. + if (!aResult || !aSelState) return NS_ERROR_NULL_POINTER; + if (!mStartSel->IsCollapsed() || !aSelState->IsCollapsed()) + { + *aResult = PR_FALSE; + return NS_OK; + } + *aResult = mStartSel->IsEqual(aSelState); return NS_OK; } @@ -207,21 +254,8 @@ NS_IMETHODIMP PlaceholderTxn::EndPlaceHolderBatch() if (plcTxn) plcTxn->EndPlaceHolderBatch(); } - // if we are a typing or IME or deleting transaction, remember our selection state - if ( (mName.get() == nsHTMLEditor::gTypingTxnName) || - (mName.get() == nsHTMLEditor::gIMETxnName) || - (mName.get() == nsHTMLEditor::gDeleteTxnName) ) - { - nsCOMPtr ps = do_QueryReferent(mPresShellWeak); - if (!ps) return NS_ERROR_NOT_INITIALIZED; - nsCOMPtr selection; - nsresult res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); - if (NS_FAILED(res)) return res; - if (!selection) return NS_ERROR_NULL_POINTER; - res = nsEditor::GetStartNodeAndOffset(selection, &mEndNode, &mEndOffset); - if (NS_FAILED(res)) return res; - } - return NS_OK; + // remember our selection state. + return RememberEndingSelection(); }; NS_IMETHODIMP PlaceholderTxn::ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress) @@ -236,5 +270,17 @@ NS_IMETHODIMP PlaceholderTxn::Commit() return NS_OK; } +NS_IMETHODIMP PlaceholderTxn::RememberEndingSelection() +{ + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + nsresult res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + res = mEndSel.SaveSelection(selection); + + return res; +} diff --git a/mozilla/editor/base/PlaceholderTxn.h b/mozilla/editor/base/PlaceholderTxn.h index 30e9c6f980f..80ed51a88f9 100644 --- a/mozilla/editor/base/PlaceholderTxn.h +++ b/mozilla/editor/base/PlaceholderTxn.h @@ -24,6 +24,7 @@ #define AggregatePlaceholderTxn_h__ #include "EditAggregateTxn.h" +#include "nsEditorUtils.h" #include "nsIAbsorbingTransaction.h" #include "nsIDOMNode.h" #include "nsCOMPtr.h" @@ -67,16 +68,18 @@ public: NS_IMETHOD Do(void); NS_IMETHOD Undo(void); + + NS_IMETHOD Redo(void); NS_IMETHOD Merge(PRBool *aDidMerge, nsITransaction *aTransaction); // ------------ nsIAbsorbingTransaction ----------------------- - NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsIDOMNode *aStartNode, PRInt32 aStartOffset); + NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsSelectionState *aSelState); NS_IMETHOD GetTxnName(nsIAtom **aName); - NS_IMETHOD GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset); + NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult); NS_IMETHOD EndPlaceHolderBatch(); @@ -84,6 +87,8 @@ public: NS_IMETHOD Commit(); + NS_IMETHOD RememberEndingSelection(); + friend class TransactionFactory; enum { kTransactionID = 11260 }; @@ -93,12 +98,15 @@ protected: /** the presentation shell, which we'll need to get the selection */ nsWeakPtr mPresShellWeak; // weak reference to the nsIPresShell PRBool mAbsorb; // do we auto absorb any and all transaction? - nsCOMPtr mStartNode, mEndNode; // selection nodes at beginning and end of operation - PRInt32 mStartOffset, mEndOffset; // selection offsets at beginning and end of operation nsWeakPtr mForwarding; IMETextTxn *mIMETextTxn; // first IME txn in this placeholder - used for IME merging // non-owning for now - cant nsCOMPtr it due to broken transaction interfaces PRBool mCommitted; // do we stop auto absorbing any matching placeholder txns? + // these next two members store the state of the selection in a safe way. + // selection at the start of the txn is stored, as is the selection at the end. + // This is so that Undo() and Redo() can restore the selection properly. + nsSelectionState *mStartSel; // use a pointer because this is constructed before we exist + nsSelectionState mEndSel; }; diff --git a/mozilla/editor/base/nsEditor.cpp b/mozilla/editor/base/nsEditor.cpp index 569bfd85395..124fed3fe73 100644 --- a/mozilla/editor/base/nsEditor.cpp +++ b/mozilla/editor/base/nsEditor.cpp @@ -137,6 +137,169 @@ const PRUnichar nbsp = 160; PRInt32 nsEditor::gInstanceCount = 0; +/*************************************************************************** + * class for recording selection info. stores selection as collection of + * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store + * ranges since dom gravity will possibly change the ranges. + */ +nsSelectionState::nsSelectionState() {} + +nsSelectionState::~nsSelectionState() +{ + // free any items in the array + SelRangeStore *item; + while (item = (SelRangeStore*)mArray.ElementAt(0)) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +nsresult +nsSelectionState::SaveSelection(nsIDOMSelection *aSel) +{ + if (!aSel) return NS_ERROR_NULL_POINTER; + nsresult res = NS_OK; + PRInt32 i,rangeCount, arrayCount = mArray.Count(); + SelRangeStore *item; + aSel->GetRangeCount(&rangeCount); + + // if we need more items in the array, new them + if (arrayCountarrayCount) + { + while (item = (SelRangeStore*)mArray.ElementAt(rangeCount)) + { + delete item; + mArray.RemoveElementAt(rangeCount); + } + } + + // now store the selection ranges + for (i=0; i range; + res = aSel->GetRangeAt(i, getter_AddRefs(range)); + item->StoreRange(range); + } + + return res; +} + +nsresult +nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) +{ + if (!aSel) return NS_ERROR_NULL_POINTER; + nsresult res = NS_OK; + PRInt32 i, arrayCount = mArray.Count(); + SelRangeStore *item; + + // clear ot cur selection + aSel->ClearSelection(); + + // set the selection ranges anew + for (i=0; i range; + item->GetRange(&range); + if (!range) return NS_ERROR_UNEXPECTED; + + res = aSel->AddRange(range); + if(NS_FAILED(res)) return res; + + } + return NS_OK; +} + +PRBool +nsSelectionState::IsCollapsed() +{ + if (1 != mArray.Count()) return PR_FALSE; + SelRangeStore *item; + item = (SelRangeStore*)mArray.ElementAt(0); + if (!item) return PR_FALSE; + nsCOMPtr range; + item->GetRange(&range); + if (!range) return PR_FALSE; + PRBool bIsCollapsed; + range->GetIsCollapsed(&bIsCollapsed); + return bIsCollapsed; +} + +PRBool +nsSelectionState::IsEqual(nsSelectionState *aSelState) +{ + if (!aSelState) return NS_ERROR_NULL_POINTER; + PRInt32 i, myCount = mArray.Count(), itsCount = aSelState->mArray.Count(); + if (myCount != itsCount) return PR_FALSE; + if (myCount < 1) return PR_FALSE; + + SelRangeStore *myItem, *itsItem; + + for (i=0; imArray.ElementAt(0)); + if (!myItem || !itsItem) return PR_FALSE; + + nsCOMPtr myRange, itsRange; + myItem->GetRange(&myRange); + itsItem->GetRange(&itsRange); + if (!myRange || !itsRange) return PR_FALSE; + + PRInt32 compResult; + myRange->CompareEndPoints(nsIDOMRange::START_TO_START, itsRange, &compResult); + if (compResult) return PR_FALSE; + myRange->CompareEndPoints(nsIDOMRange::END_TO_END, itsRange, &compResult); + if (compResult) return PR_FALSE; + } + // if we got here, they are equal + return PR_TRUE; +} + + + +nsresult SelRangeStore::StoreRange(nsIDOMRange *aRange) +{ + if (!aRange) return NS_ERROR_NULL_POINTER; + aRange->GetStartParent(getter_AddRefs(startNode)); + aRange->GetEndParent(getter_AddRefs(endNode)); + aRange->GetStartOffset(&startOffset); + aRange->GetEndOffset(&endOffset); + return NS_OK; +} + +nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) +{ + if (!outRange) return NS_ERROR_NULL_POINTER; + nsresult res = nsComponentManager::CreateInstance(kCRangeCID, + nsnull, + NS_GET_IID(nsIDOMRange), + getter_AddRefs(*outRange)); + if(NS_FAILED(res)) return res; + + res = (*outRange)->SetStart(startNode, startOffset); + if(NS_FAILED(res)) return res; + + res = (*outRange)->SetEnd(endNode, endOffset); + return res; +} + + //class implementations are in order they are declared in nsEditor.h nsEditor::nsEditor() @@ -146,8 +309,7 @@ nsEditor::nsEditor() , mPlaceHolderTxn(nsnull) , mPlaceHolderName(nsnull) , mPlaceHolderBatch(0) -, mTxnStartNode(nsnull) -, mTxnStartOffset(0) +, mSelState(nsnull) , mShouldTxnSetSelection(PR_TRUE) , mBodyElement(nsnull) , mInIMEMode(PR_FALSE) @@ -376,8 +538,9 @@ nsEditor::Do(nsITransaction *aTxn) // save off weak reference to placeholder txn mPlaceHolderTxn = getter_AddRefs( NS_GetWeakReference(plcTxn) ); - plcTxn->Init(mPresShellWeak, mPlaceHolderName, mTxnStartNode, mTxnStartOffset); - + plcTxn->Init(mPresShellWeak, mPlaceHolderName, mSelState); + mSelState = nsnull; // placeholder txn took ownership of this pointer + // finally we QI to an nsITransaction since that's what Do() expects nsCOMPtr theTxn = do_QueryInterface(plcTxn); nsITransaction* txn = theTxn; @@ -588,17 +751,8 @@ nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName) nsCOMPtr selection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; - PRBool collapsed; - res = selection->GetIsCollapsed(&collapsed); - if (NS_FAILED(res)) return res; - if (collapsed) // we cant merge with previous typing/deleting if selection not collapsed - { - // need to remember colapsed selection point to Init the placeholder with later. - // this is because we dont actually make the placeholder until we need it, and we - // might have moved the selection as part of the typing processing by then. - res = GetStartNodeAndOffset(selection, &mTxnStartNode, &mTxnStartOffset); - if (NS_FAILED(res)) return res; - } + mSelState = new nsSelectionState(); + mSelState->SaveSelection(selection); } mPlaceHolderBatch++; @@ -613,8 +767,13 @@ nsEditor::EndPlaceHolderTransaction() { // time to turn off the batch EndUpdateViewBatch(); - mTxnStartNode = nsnull; - mTxnStartOffset = 0; + if (mSelState) + { + // we saved the selection state, but never got to hand it to placeholder + // (else we ould have nulled out this pointer), so destroy it to prevent leaks. + delete mSelState; + mSelState = nsnull; + } if (mPlaceHolderTxn) // we might have never made a placeholder if no action took place { nsCOMPtr plcTxn = do_QueryReferent(mPlaceHolderTxn); diff --git a/mozilla/editor/base/nsEditor.h b/mozilla/editor/base/nsEditor.h index 6b92e5be0e7..d743215c41b 100644 --- a/mozilla/editor/base/nsEditor.h +++ b/mozilla/editor/base/nsEditor.h @@ -46,6 +46,7 @@ #include "nsICSSStyleSheet.h" #include "nsIDTD.h" #include "nsIDOMElement.h" +#include "nsVoidArray.h" class nsIEditActionListener; class nsIDocumentStateListener; @@ -70,6 +71,38 @@ class AddStyleSheetTxn; class RemoveStyleSheetTxn; class nsFileSpec; +/*************************************************************************** + * class for recording selection info. stores selection as collection of + * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store + * ranges since dom gravity will possibly change the ranges. + */ + +// first a helper struct for saving/setting ranges +struct SelRangeStore +{ + nsresult StoreRange(nsIDOMRange *aRange); + nsresult GetRange(nsCOMPtr *outRange); + + nsCOMPtr startNode; + PRInt32 startOffset; + nsCOMPtr endNode; + PRInt32 endOffset; +}; + +class nsSelectionState +{ + public: + + nsSelectionState(); + ~nsSelectionState(); + + nsresult SaveSelection(nsIDOMSelection *aSel); + nsresult RestoreSelection(nsIDOMSelection *aSel); + PRBool IsCollapsed(); + PRBool IsEqual(nsSelectionState *aSelState); + + nsVoidArray mArray; +}; /** implementation of an editor object. it will be the controler/focal point * for the main editor services. i.e. the GUIManager, publishing, transaction @@ -653,8 +686,7 @@ protected: nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes nsIAtom *mPlaceHolderName; // name of placeholder transaction PRInt32 mPlaceHolderBatch; // nesting count for batching - nsCOMPtr mTxnStartNode; // saved selection info to pass to placeholder at init time - PRInt32 mTxnStartOffset; // " " " " + nsSelectionState *mSelState; // saved selection state for placeholder txn batching PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns nsCOMPtr mBodyElement; // cached body node // diff --git a/mozilla/editor/base/nsEditorShell.cpp b/mozilla/editor/base/nsEditorShell.cpp index 074fd94fc80..4ed2b034886 100644 --- a/mozilla/editor/base/nsEditorShell.cpp +++ b/mozilla/editor/base/nsEditorShell.cpp @@ -785,7 +785,7 @@ nsEditorShell::SetTextProperty(const PRUnichar *prop, const PRUnichar *attr, con default: err = NS_ERROR_NOT_IMPLEMENTED; } - + UpdateInterfaceState(); NS_RELEASE(styleAtom); return err; } diff --git a/mozilla/editor/base/nsEditorUtils.h b/mozilla/editor/base/nsEditorUtils.h index be12960f5b3..fd217532224 100644 --- a/mozilla/editor/base/nsEditorUtils.h +++ b/mozilla/editor/base/nsEditorUtils.h @@ -30,31 +30,13 @@ #include "nsIDOMSelection.h" #include "nsIEditor.h" #include "nsIAtom.h" - +#include "nsVoidArray.h" #include "nsEditor.h" -class nsAutoEditBatch -{ - private: - nsCOMPtr mEd; - public: - nsAutoEditBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd)) - { if (mEd) mEd->BeginTransaction(); } - ~nsAutoEditBatch() { if (mEd) mEd->EndTransaction(); } -}; - -class nsAutoEditMayBatch -{ - private: - nsCOMPtr mEd; - PRBool mDidBatch; - public: - nsAutoEditMayBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd)), mDidBatch(PR_FALSE) {} - ~nsAutoEditMayBatch() { if (mEd && mDidBatch) mEd->EndTransaction(); } - - void batch() { if (mEd && !mDidBatch) {mEd->BeginTransaction(); mDidBatch=PR_TRUE;} } -}; - +/*************************************************************************** + * stack based helper class for batching a collection of txns inside a + * placeholder txn. + */ class nsAutoPlaceHolderBatch { private: @@ -66,6 +48,22 @@ class nsAutoPlaceHolderBatch }; +/*************************************************************************** + * stack based helper class for batching a collection of txns. + * Note: I changed this to use placeholder batching so that we get + * proper selection save/restore across undo/redo. + */ +class nsAutoEditBatch : public nsAutoPlaceHolderBatch +{ + public: + nsAutoEditBatch( nsIEditor *aEd) : nsAutoPlaceHolderBatch(aEd,nsnull) {} + ~nsAutoEditBatch() {} +}; + +/*************************************************************************** + * stack based helper class for saving/restoring selection. Note that this + * assumes that the nodes involved are still around afterwards! + */ class nsAutoSelectionReset { private: @@ -138,5 +136,4 @@ class nsAutoTxnsConserveSelection }; - #endif // nsEditorUtils_h__ diff --git a/mozilla/editor/base/nsHTMLEditRules.cpp b/mozilla/editor/base/nsHTMLEditRules.cpp index 6a1509eaefe..92f10b818e5 100644 --- a/mozilla/editor/base/nsHTMLEditRules.cpp +++ b/mozilla/editor/base/nsHTMLEditRules.cpp @@ -2748,10 +2748,6 @@ nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, res = aSelection->Collapse(listparent,offset+1); if (NS_FAILED(res)) return res; - // now attempt to adjust selection to a text node. - // if we fail, then that means we need toinsert a break - res = AdjustSelection(aSelection, nsIEditor::eNext); - if (NS_FAILED(res)) return res; // get the selection location nsCOMPtr selNode; PRInt32 selOffset; diff --git a/mozilla/editor/base/nsHTMLEditor.cpp b/mozilla/editor/base/nsHTMLEditor.cpp index fa54953307e..1d646e56547 100644 --- a/mozilla/editor/base/nsHTMLEditor.cpp +++ b/mozilla/editor/base/nsHTMLEditor.cpp @@ -218,6 +218,11 @@ nsHTMLEditor::nsHTMLEditor() { // Done in nsEditor // NS_INIT_REFCNT(); + mBoldAtom = getter_AddRefs(NS_NewAtom("b")); + mItalicAtom = getter_AddRefs(NS_NewAtom("i")); + mUnderlineAtom = getter_AddRefs(NS_NewAtom("u")); + mFontAtom = getter_AddRefs(NS_NewAtom("font")); + if (!gTypingTxnName) gTypingTxnName = NS_NewAtom("Typing"); else @@ -1053,6 +1058,7 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, PRBool isCollapsed; selection->GetIsCollapsed(&isCollapsed); + nsCOMPtr collapsedNode; nsCOMPtr enumerator; result = selection->GetEnumerator(getter_AddRefs(enumerator)); if (NS_FAILED(result)) return result; @@ -1066,7 +1072,45 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, if ((NS_SUCCEEDED(result)) && currentItem) { PRBool firstNodeInRange = PR_TRUE; // for each range, set a flag - nsCOMPtr range( do_QueryInterface(currentItem) ); + nsCOMPtr range(do_QueryInterface(currentItem)); + + if (isCollapsed) + { + // efficiency hack. we cache prior results for being collapsed in a given text node. + // this speeds up typing. Note that other parts of the editor code have to clear out + // this cache after certain actions. + range->GetStartParent(getter_AddRefs(collapsedNode)); + if (!collapsedNode) return NS_ERROR_FAILURE; + // refresh the cache if we need to + if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode); + // cache now current, use it! But *or* it with typeInState results... + PRBool isSet; + if (aProperty == mBoldAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedBoldStyle || isSet); + return NS_OK; + } + else if (aProperty == mItalicAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedItalicStyle || isSet); + return NS_OK; + } + else if (aProperty == mUnderlineAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedUnderlineStyle || isSet); + return NS_OK; + } + else if (aProperty == mFontAtom.get()) + { +// MOOSE! + return NS_OK; + } + } + + // either non-collapsed selection or no cached value: do it the hard way nsCOMPtr iter; result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, nsIContentIterator::GetIID(), @@ -1138,7 +1182,8 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, iter->CurrentNode(getter_AddRefs(content)); } } - if (PR_FALSE==aAny) { // make sure that if none of the selection is set, we don't report all is set + if (PR_FALSE==aAny) + { // make sure that if none of the selection is set, we don't report all is set aAll = PR_FALSE; } //if (gNoisy) { printf(" returning first=%d any=%d all=%d\n", aFirst, aAny, aAll); } @@ -4450,6 +4495,8 @@ void nsHTMLEditor::ApplyStyleSheetToPresShellDocument(nsICSSStyleSheet* aSheet, NS_IMETHODIMP nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection) { + if (! ((opID==kOpInsertText) || (opID==kOpInsertIMEText)) ) + ClearInlineStylesCache(); if (mRules) return mRules->BeforeEdit(opID, aDirection); return NS_OK; } @@ -4460,6 +4507,8 @@ nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection) NS_IMETHODIMP nsHTMLEditor::EndOperation(PRInt32 opID, nsIEditor::EDirection aDirection) { + if (! ((opID==kOpInsertText) || (opID==kOpInsertIMEText)) ) + ClearInlineStylesCache(); if (mRules) return mRules->AfterEdit(opID, aDirection); return NS_OK; } @@ -5882,6 +5931,22 @@ nsHTMLEditor::DeleteSelectionAndPrepareToCreateNode(nsCOMPtr &parent #pragma mark - #endif +void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode) +{ + if (!aNode) return; + nsCOMPtr resultNode; + mCachedNode = do_QueryInterface(aNode); + IsTextPropertySetByContent(aNode, mBoldAtom, 0, 0, mCachedBoldStyle, getter_AddRefs(resultNode)); + IsTextPropertySetByContent(aNode, mItalicAtom, 0, 0, mCachedItalicStyle, getter_AddRefs(resultNode)); + IsTextPropertySetByContent(aNode, mUnderlineAtom, 0, 0, mCachedUnderlineStyle, getter_AddRefs(resultNode)); + +} + +void nsHTMLEditor::ClearInlineStylesCache() +{ + mCachedNode = nsnull; +} + #ifdef PRE_NODE_IN_BODY nsCOMPtr nsHTMLEditor::FindPreElement() { @@ -6890,7 +6955,7 @@ nsHTMLEditor::RemoveTextPropertiesForNodeWithDifferentParents(nsIDOMNode *aStar } } else - { + { skippedEndNode = PR_TRUE; if (gNoisy) { printf("skipping end node because aProperty not set.\n"); } } @@ -6903,13 +6968,16 @@ nsHTMLEditor::RemoveTextPropertiesForNodeWithDifferentParents(nsIDOMNode *aStar } else { - endNode = GetChildAt(aEndNode, aEndOffset); + // experimental hack: need to rewrite all of this + // MOOSE! + if (aEndOffset<0) aEndOffset=1; + endNode = GetChildAt(aEndNode, aEndOffset-1); parent = do_QueryInterface(aEndNode); if (!endNode || !parent) {return NS_ERROR_NULL_POINTER;} IsTextPropertySetByContent(endNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode)); if (PR_TRUE==textPropertySet) { - result = RemoveTextPropertiesForNode(endNode, parent, aEndOffset, aEndOffset+1, aPropName, aAttribute); + result = RemoveTextPropertiesForNode(endNode, parent, aEndOffset-1, aEndOffset, aPropName, aAttribute); } else { diff --git a/mozilla/editor/base/nsHTMLEditor.h b/mozilla/editor/base/nsHTMLEditor.h index 82d3a5e3218..4dcfa1ea7a1 100644 --- a/mozilla/editor/base/nsHTMLEditor.h +++ b/mozilla/editor/base/nsHTMLEditor.h @@ -306,7 +306,10 @@ protected: PRBool SetCaretInTableCell(nsIDOMElement* aElement); PRBool IsElementInBody(nsIDOMElement* aElement); - + // inline style caching + void CacheInlineStyles(nsIDOMNode *aNode); + void ClearInlineStylesCache(); + // key event helpers NS_IMETHOD TabInTable(PRBool inIsShift, PRBool *outHandled); NS_IMETHOD CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr *outBRNode); @@ -544,6 +547,17 @@ protected: nsCOMPtr mFocusListenerP; PRBool mIsComposing; PRInt32 mMaxTextLength; + + nsCOMPtr mBoldAtom; + nsCOMPtr mItalicAtom; + nsCOMPtr mUnderlineAtom; + nsCOMPtr mFontAtom; + nsCOMPtr mCachedNode; + + PRBool mCachedBoldStyle; + PRBool mCachedItalicStyle; + PRBool mCachedUnderlineStyle; + nsString mCachedFontName; public: static nsIAtom *gTypingTxnName; diff --git a/mozilla/editor/base/nsIAbsorbingTransaction.h b/mozilla/editor/base/nsIAbsorbingTransaction.h index 87baa024617..48c915c2acc 100644 --- a/mozilla/editor/base/nsIAbsorbingTransaction.h +++ b/mozilla/editor/base/nsIAbsorbingTransaction.h @@ -40,6 +40,8 @@ Transaction interface to outside world #include "nsIAtom.h" #include "nsIDOMNode.h" +class nsSelectionState; + /** * A transaction interface mixin - for transactions that can support. * the placeholder absorbtion idiom. @@ -49,13 +51,13 @@ public: static const nsIID& GetIID() { static nsIID iid = NS_IABSORBINGTRANSACTION_IID; return iid; } - NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsIDOMNode *aStartNode, PRInt32 aStartOffset)=0; + NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsSelectionState *aSelState)=0; NS_IMETHOD EndPlaceHolderBatch()=0; NS_IMETHOD GetTxnName(nsIAtom **aName)=0; - NS_IMETHOD GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset)=0; + NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult)=0; NS_IMETHOD ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress)=0; diff --git a/mozilla/editor/base/nsInterfaceState.cpp b/mozilla/editor/base/nsInterfaceState.cpp index 8bd74b2fbd7..054cc7614bc 100644 --- a/mozilla/editor/base/nsInterfaceState.cpp +++ b/mozilla/editor/base/nsInterfaceState.cpp @@ -284,32 +284,14 @@ nsInterfaceState::UpdateFontFace(const char* observerName, const char* attribute PRBool testBoolean; - if (SelectionIsCollapsed()) + rv = mEditor->GetInlineProperty(styleAtom, &faceStr, &thisFace, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + if( !anyOfSelectionHasProp ) { - rv = mEditor->GetTypingStateValue(styleAtom, ioFontString); - if (ioFontString.Length() == 0) - { - // We don't have a font set, so check for "tt" (= "Default Fixed Width") - PRBool stateHasProp = PR_FALSE; - rv = mEditor->GetTypingState(fixedStyleAtom, testBoolean); - testBoolean = stateHasProp; - if (stateHasProp) - thisFace = faceStr; - } - else - testBoolean = PR_TRUE; - } - else - { - rv = mEditor->GetInlineProperty(styleAtom, &faceStr, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - if( !anyOfSelectionHasProp ) - { - // No font face set -- check for "tt" - rv = mEditor->GetInlineProperty(fixedStyleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - testBoolean = anyOfSelectionHasProp; - if (anyOfSelectionHasProp) - thisFace = "tt"; - } + // No font face set -- check for "tt" + rv = mEditor->GetInlineProperty(fixedStyleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + testBoolean = anyOfSelectionHasProp; + if (anyOfSelectionHasProp) + thisFace = "tt"; } // TODO: HANDLE "MIXED" STATE @@ -331,22 +313,13 @@ nsInterfaceState::UpdateTextState(const char* tagName, const char* observerName, nsCOMPtr styleAtom = getter_AddRefs(NS_NewAtom(tagName)); PRBool testBoolean; - if (SelectionIsCollapsed()) - { - PRBool stateHasProp = PR_FALSE; - - rv = mEditor->GetTypingState(styleAtom, stateHasProp); - testBoolean = stateHasProp; - } - else - { - PRBool firstOfSelectionHasProp = PR_FALSE; - PRBool anyOfSelectionHasProp = PR_FALSE; - PRBool allOfSelectionHasProp = PR_FALSE; + PRBool firstOfSelectionHasProp = PR_FALSE; + PRBool anyOfSelectionHasProp = PR_FALSE; + PRBool allOfSelectionHasProp = PR_FALSE; + + rv = mEditor->GetInlineProperty(styleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + testBoolean = allOfSelectionHasProp; // change this to alter the behaviour - rv = mEditor->GetInlineProperty(styleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - testBoolean = allOfSelectionHasProp; // change this to alter the behaviour - } if (NS_FAILED(rv)) return rv; if (testBoolean != ioState) diff --git a/mozilla/editor/composer/src/nsEditorShell.cpp b/mozilla/editor/composer/src/nsEditorShell.cpp index 074fd94fc80..4ed2b034886 100644 --- a/mozilla/editor/composer/src/nsEditorShell.cpp +++ b/mozilla/editor/composer/src/nsEditorShell.cpp @@ -785,7 +785,7 @@ nsEditorShell::SetTextProperty(const PRUnichar *prop, const PRUnichar *attr, con default: err = NS_ERROR_NOT_IMPLEMENTED; } - + UpdateInterfaceState(); NS_RELEASE(styleAtom); return err; } diff --git a/mozilla/editor/composer/src/nsInterfaceState.cpp b/mozilla/editor/composer/src/nsInterfaceState.cpp index 8bd74b2fbd7..054cc7614bc 100644 --- a/mozilla/editor/composer/src/nsInterfaceState.cpp +++ b/mozilla/editor/composer/src/nsInterfaceState.cpp @@ -284,32 +284,14 @@ nsInterfaceState::UpdateFontFace(const char* observerName, const char* attribute PRBool testBoolean; - if (SelectionIsCollapsed()) + rv = mEditor->GetInlineProperty(styleAtom, &faceStr, &thisFace, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + if( !anyOfSelectionHasProp ) { - rv = mEditor->GetTypingStateValue(styleAtom, ioFontString); - if (ioFontString.Length() == 0) - { - // We don't have a font set, so check for "tt" (= "Default Fixed Width") - PRBool stateHasProp = PR_FALSE; - rv = mEditor->GetTypingState(fixedStyleAtom, testBoolean); - testBoolean = stateHasProp; - if (stateHasProp) - thisFace = faceStr; - } - else - testBoolean = PR_TRUE; - } - else - { - rv = mEditor->GetInlineProperty(styleAtom, &faceStr, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - if( !anyOfSelectionHasProp ) - { - // No font face set -- check for "tt" - rv = mEditor->GetInlineProperty(fixedStyleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - testBoolean = anyOfSelectionHasProp; - if (anyOfSelectionHasProp) - thisFace = "tt"; - } + // No font face set -- check for "tt" + rv = mEditor->GetInlineProperty(fixedStyleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + testBoolean = anyOfSelectionHasProp; + if (anyOfSelectionHasProp) + thisFace = "tt"; } // TODO: HANDLE "MIXED" STATE @@ -331,22 +313,13 @@ nsInterfaceState::UpdateTextState(const char* tagName, const char* observerName, nsCOMPtr styleAtom = getter_AddRefs(NS_NewAtom(tagName)); PRBool testBoolean; - if (SelectionIsCollapsed()) - { - PRBool stateHasProp = PR_FALSE; - - rv = mEditor->GetTypingState(styleAtom, stateHasProp); - testBoolean = stateHasProp; - } - else - { - PRBool firstOfSelectionHasProp = PR_FALSE; - PRBool anyOfSelectionHasProp = PR_FALSE; - PRBool allOfSelectionHasProp = PR_FALSE; + PRBool firstOfSelectionHasProp = PR_FALSE; + PRBool anyOfSelectionHasProp = PR_FALSE; + PRBool allOfSelectionHasProp = PR_FALSE; + + rv = mEditor->GetInlineProperty(styleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); + testBoolean = allOfSelectionHasProp; // change this to alter the behaviour - rv = mEditor->GetInlineProperty(styleAtom, nsnull, nsnull, firstOfSelectionHasProp, anyOfSelectionHasProp, allOfSelectionHasProp); - testBoolean = allOfSelectionHasProp; // change this to alter the behaviour - } if (NS_FAILED(rv)) return rv; if (testBoolean != ioState) diff --git a/mozilla/editor/libeditor/base/InsertElementTxn.cpp b/mozilla/editor/libeditor/base/InsertElementTxn.cpp index 22fe60b61a3..fbe7cdc6fad 100644 --- a/mozilla/editor/libeditor/base/InsertElementTxn.cpp +++ b/mozilla/editor/libeditor/base/InsertElementTxn.cpp @@ -25,7 +25,7 @@ #include "nsIContent.h" #ifdef NS_DEBUG -static PRBool gNoisy = PR_TRUE; +static PRBool gNoisy = PR_FALSE; #else static const PRBool gNoisy = PR_FALSE; #endif diff --git a/mozilla/editor/libeditor/base/PlaceholderTxn.cpp b/mozilla/editor/libeditor/base/PlaceholderTxn.cpp index cd7cbba6131..0c048e54cf2 100644 --- a/mozilla/editor/libeditor/base/PlaceholderTxn.cpp +++ b/mozilla/editor/libeditor/base/PlaceholderTxn.cpp @@ -39,7 +39,9 @@ PlaceholderTxn::PlaceholderTxn() : EditAggregateTxn(), mAbsorb(PR_TRUE), mForwarding(nsnull), mIMETextTxn(nsnull), - mCommitted(PR_FALSE) + mCommitted(PR_FALSE), + mStartSel(nsnull), + mEndSel() { SetTransactionDescriptionID( kTransactionID ); /* log description initialized in parent constructor */ @@ -48,6 +50,7 @@ PlaceholderTxn::PlaceholderTxn() : EditAggregateTxn(), PlaceholderTxn::~PlaceholderTxn() { + delete mStartSel; } NS_IMPL_ADDREF_INHERITED(PlaceholderTxn, EditAggregateTxn) @@ -72,15 +75,15 @@ NS_IMETHODIMP PlaceholderTxn::QueryInterface(REFNSIID aIID, void** aInstancePtr) } NS_IMETHODIMP PlaceholderTxn::Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, - nsIDOMNode *aStartNode, PRInt32 aStartOffset) + nsSelectionState *aSelState) { NS_ASSERTION(aPresShellWeak, "bad args"); - if (!aPresShellWeak) return NS_ERROR_NULL_POINTER; + if (!aPresShellWeak || !aSelState) return NS_ERROR_NULL_POINTER; mPresShellWeak = aPresShellWeak; mName = aName; - mStartNode = do_QueryInterface(aStartNode); - mStartOffset = aStartOffset; + mStartSel = aSelState; + return NS_OK; } @@ -92,8 +95,38 @@ NS_IMETHODIMP PlaceholderTxn::Do(void) NS_IMETHODIMP PlaceholderTxn::Undo(void) { - // using this to debug - return EditAggregateTxn::Undo(); + // undo txns + nsresult res = EditAggregateTxn::Undo(); + if (NS_FAILED(res)) return res; + + // now restore selection + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + if (!mStartSel) return NS_ERROR_NULL_POINTER; + res = mStartSel->RestoreSelection(selection); + return res; +} + + +NS_IMETHODIMP PlaceholderTxn::Redo(void) +{ + // redo txns + nsresult res = EditAggregateTxn::Redo(); + if (NS_FAILED(res)) return res; + + // now restore selection + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + res = mEndSel.RestoreSelection(selection); + return res; } @@ -146,6 +179,10 @@ NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransact AppendChild(editTxn); } *aDidMerge = PR_TRUE; +// RememberEndingSelection(); +// efficiency hack: no need to remember selection here, as we haven't yet +// finished the inital batch and we know we will be told when the batch ends. +// we can remeber the selection then. if (gNoisy) { printf("Placeholder txn assimilated %p\n", aTransaction); } } else @@ -155,27 +192,31 @@ NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransact (mName.get() == nsHTMLEditor::gDeleteTxnName)) && !mCommitted ) { - nsCOMPtr plcTxn;// = do_QueryInterface(editTxn); - // cant do_QueryInterface() above due to our broken transaction interfaces. - // instead have to brute it below. ugh. - editTxn->QueryInterface(NS_GET_IID(nsIAbsorbingTransaction), getter_AddRefs(plcTxn)); - if (plcTxn) + // but only if this placeholder started with a collapsed selection + if (mStartSel->IsCollapsed()) { - nsCOMPtr atom; - plcTxn->GetTxnName(getter_AddRefs(atom)); - if (atom && (atom == mName)) + nsCOMPtr plcTxn;// = do_QueryInterface(editTxn); + // cant do_QueryInterface() above due to our broken transaction interfaces. + // instead have to brute it below. ugh. + editTxn->QueryInterface(NS_GET_IID(nsIAbsorbingTransaction), getter_AddRefs(plcTxn)); + if (plcTxn) { - nsCOMPtr otherTxnStartNode; - PRInt32 otherTxnStartOffset; - res = plcTxn->GetStartNodeAndOffset(&otherTxnStartNode, &otherTxnStartOffset); - if (NS_FAILED(res)) return res; - - if ((otherTxnStartNode == mEndNode) && (otherTxnStartOffset == mEndOffset)) + nsCOMPtr atom; + plcTxn->GetTxnName(getter_AddRefs(atom)); + if (atom && (atom == mName)) { - mAbsorb = PR_TRUE; // we need to start absorbing again - plcTxn->ForwardEndBatchTo(this); - AppendChild(editTxn); - *aDidMerge = PR_TRUE; + // check if start selection of next placeholder matches + // end selection of this placeholder + PRBool isSame; + plcTxn->StartSelectionEquals(&mEndSel, &isSame); + if (isSame) + { + mAbsorb = PR_TRUE; // we need to start absorbing again + plcTxn->ForwardEndBatchTo(this); + AppendChild(editTxn); + RememberEndingSelection(); + *aDidMerge = PR_TRUE; + } } } } @@ -189,11 +230,17 @@ NS_IMETHODIMP PlaceholderTxn::GetTxnName(nsIAtom **aName) return GetName(aName); } -NS_IMETHODIMP PlaceholderTxn::GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset) +NS_IMETHODIMP PlaceholderTxn::StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult) { - if (!aTxnStartNode || !aTxnStartOffset) return NS_ERROR_NULL_POINTER; - *aTxnStartNode = mStartNode; - *aTxnStartOffset = mStartOffset; + // determine if starting selection matches the given selection state. + // note that we only care about collapsed selections. + if (!aResult || !aSelState) return NS_ERROR_NULL_POINTER; + if (!mStartSel->IsCollapsed() || !aSelState->IsCollapsed()) + { + *aResult = PR_FALSE; + return NS_OK; + } + *aResult = mStartSel->IsEqual(aSelState); return NS_OK; } @@ -207,21 +254,8 @@ NS_IMETHODIMP PlaceholderTxn::EndPlaceHolderBatch() if (plcTxn) plcTxn->EndPlaceHolderBatch(); } - // if we are a typing or IME or deleting transaction, remember our selection state - if ( (mName.get() == nsHTMLEditor::gTypingTxnName) || - (mName.get() == nsHTMLEditor::gIMETxnName) || - (mName.get() == nsHTMLEditor::gDeleteTxnName) ) - { - nsCOMPtr ps = do_QueryReferent(mPresShellWeak); - if (!ps) return NS_ERROR_NOT_INITIALIZED; - nsCOMPtr selection; - nsresult res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); - if (NS_FAILED(res)) return res; - if (!selection) return NS_ERROR_NULL_POINTER; - res = nsEditor::GetStartNodeAndOffset(selection, &mEndNode, &mEndOffset); - if (NS_FAILED(res)) return res; - } - return NS_OK; + // remember our selection state. + return RememberEndingSelection(); }; NS_IMETHODIMP PlaceholderTxn::ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress) @@ -236,5 +270,17 @@ NS_IMETHODIMP PlaceholderTxn::Commit() return NS_OK; } +NS_IMETHODIMP PlaceholderTxn::RememberEndingSelection() +{ + nsCOMPtr ps = do_QueryReferent(mPresShellWeak); + if (!ps) return NS_ERROR_NOT_INITIALIZED; + nsCOMPtr selection; + nsresult res = ps->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)); + if (NS_FAILED(res)) return res; + if (!selection) return NS_ERROR_NULL_POINTER; + res = mEndSel.SaveSelection(selection); + + return res; +} diff --git a/mozilla/editor/libeditor/base/PlaceholderTxn.h b/mozilla/editor/libeditor/base/PlaceholderTxn.h index 30e9c6f980f..80ed51a88f9 100644 --- a/mozilla/editor/libeditor/base/PlaceholderTxn.h +++ b/mozilla/editor/libeditor/base/PlaceholderTxn.h @@ -24,6 +24,7 @@ #define AggregatePlaceholderTxn_h__ #include "EditAggregateTxn.h" +#include "nsEditorUtils.h" #include "nsIAbsorbingTransaction.h" #include "nsIDOMNode.h" #include "nsCOMPtr.h" @@ -67,16 +68,18 @@ public: NS_IMETHOD Do(void); NS_IMETHOD Undo(void); + + NS_IMETHOD Redo(void); NS_IMETHOD Merge(PRBool *aDidMerge, nsITransaction *aTransaction); // ------------ nsIAbsorbingTransaction ----------------------- - NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsIDOMNode *aStartNode, PRInt32 aStartOffset); + NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsSelectionState *aSelState); NS_IMETHOD GetTxnName(nsIAtom **aName); - NS_IMETHOD GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset); + NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult); NS_IMETHOD EndPlaceHolderBatch(); @@ -84,6 +87,8 @@ public: NS_IMETHOD Commit(); + NS_IMETHOD RememberEndingSelection(); + friend class TransactionFactory; enum { kTransactionID = 11260 }; @@ -93,12 +98,15 @@ protected: /** the presentation shell, which we'll need to get the selection */ nsWeakPtr mPresShellWeak; // weak reference to the nsIPresShell PRBool mAbsorb; // do we auto absorb any and all transaction? - nsCOMPtr mStartNode, mEndNode; // selection nodes at beginning and end of operation - PRInt32 mStartOffset, mEndOffset; // selection offsets at beginning and end of operation nsWeakPtr mForwarding; IMETextTxn *mIMETextTxn; // first IME txn in this placeholder - used for IME merging // non-owning for now - cant nsCOMPtr it due to broken transaction interfaces PRBool mCommitted; // do we stop auto absorbing any matching placeholder txns? + // these next two members store the state of the selection in a safe way. + // selection at the start of the txn is stored, as is the selection at the end. + // This is so that Undo() and Redo() can restore the selection properly. + nsSelectionState *mStartSel; // use a pointer because this is constructed before we exist + nsSelectionState mEndSel; }; diff --git a/mozilla/editor/libeditor/base/nsEditor.cpp b/mozilla/editor/libeditor/base/nsEditor.cpp index 569bfd85395..124fed3fe73 100644 --- a/mozilla/editor/libeditor/base/nsEditor.cpp +++ b/mozilla/editor/libeditor/base/nsEditor.cpp @@ -137,6 +137,169 @@ const PRUnichar nbsp = 160; PRInt32 nsEditor::gInstanceCount = 0; +/*************************************************************************** + * class for recording selection info. stores selection as collection of + * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store + * ranges since dom gravity will possibly change the ranges. + */ +nsSelectionState::nsSelectionState() {} + +nsSelectionState::~nsSelectionState() +{ + // free any items in the array + SelRangeStore *item; + while (item = (SelRangeStore*)mArray.ElementAt(0)) + { + delete item; + mArray.RemoveElementAt(0); + } +} + +nsresult +nsSelectionState::SaveSelection(nsIDOMSelection *aSel) +{ + if (!aSel) return NS_ERROR_NULL_POINTER; + nsresult res = NS_OK; + PRInt32 i,rangeCount, arrayCount = mArray.Count(); + SelRangeStore *item; + aSel->GetRangeCount(&rangeCount); + + // if we need more items in the array, new them + if (arrayCountarrayCount) + { + while (item = (SelRangeStore*)mArray.ElementAt(rangeCount)) + { + delete item; + mArray.RemoveElementAt(rangeCount); + } + } + + // now store the selection ranges + for (i=0; i range; + res = aSel->GetRangeAt(i, getter_AddRefs(range)); + item->StoreRange(range); + } + + return res; +} + +nsresult +nsSelectionState::RestoreSelection(nsIDOMSelection *aSel) +{ + if (!aSel) return NS_ERROR_NULL_POINTER; + nsresult res = NS_OK; + PRInt32 i, arrayCount = mArray.Count(); + SelRangeStore *item; + + // clear ot cur selection + aSel->ClearSelection(); + + // set the selection ranges anew + for (i=0; i range; + item->GetRange(&range); + if (!range) return NS_ERROR_UNEXPECTED; + + res = aSel->AddRange(range); + if(NS_FAILED(res)) return res; + + } + return NS_OK; +} + +PRBool +nsSelectionState::IsCollapsed() +{ + if (1 != mArray.Count()) return PR_FALSE; + SelRangeStore *item; + item = (SelRangeStore*)mArray.ElementAt(0); + if (!item) return PR_FALSE; + nsCOMPtr range; + item->GetRange(&range); + if (!range) return PR_FALSE; + PRBool bIsCollapsed; + range->GetIsCollapsed(&bIsCollapsed); + return bIsCollapsed; +} + +PRBool +nsSelectionState::IsEqual(nsSelectionState *aSelState) +{ + if (!aSelState) return NS_ERROR_NULL_POINTER; + PRInt32 i, myCount = mArray.Count(), itsCount = aSelState->mArray.Count(); + if (myCount != itsCount) return PR_FALSE; + if (myCount < 1) return PR_FALSE; + + SelRangeStore *myItem, *itsItem; + + for (i=0; imArray.ElementAt(0)); + if (!myItem || !itsItem) return PR_FALSE; + + nsCOMPtr myRange, itsRange; + myItem->GetRange(&myRange); + itsItem->GetRange(&itsRange); + if (!myRange || !itsRange) return PR_FALSE; + + PRInt32 compResult; + myRange->CompareEndPoints(nsIDOMRange::START_TO_START, itsRange, &compResult); + if (compResult) return PR_FALSE; + myRange->CompareEndPoints(nsIDOMRange::END_TO_END, itsRange, &compResult); + if (compResult) return PR_FALSE; + } + // if we got here, they are equal + return PR_TRUE; +} + + + +nsresult SelRangeStore::StoreRange(nsIDOMRange *aRange) +{ + if (!aRange) return NS_ERROR_NULL_POINTER; + aRange->GetStartParent(getter_AddRefs(startNode)); + aRange->GetEndParent(getter_AddRefs(endNode)); + aRange->GetStartOffset(&startOffset); + aRange->GetEndOffset(&endOffset); + return NS_OK; +} + +nsresult SelRangeStore::GetRange(nsCOMPtr *outRange) +{ + if (!outRange) return NS_ERROR_NULL_POINTER; + nsresult res = nsComponentManager::CreateInstance(kCRangeCID, + nsnull, + NS_GET_IID(nsIDOMRange), + getter_AddRefs(*outRange)); + if(NS_FAILED(res)) return res; + + res = (*outRange)->SetStart(startNode, startOffset); + if(NS_FAILED(res)) return res; + + res = (*outRange)->SetEnd(endNode, endOffset); + return res; +} + + //class implementations are in order they are declared in nsEditor.h nsEditor::nsEditor() @@ -146,8 +309,7 @@ nsEditor::nsEditor() , mPlaceHolderTxn(nsnull) , mPlaceHolderName(nsnull) , mPlaceHolderBatch(0) -, mTxnStartNode(nsnull) -, mTxnStartOffset(0) +, mSelState(nsnull) , mShouldTxnSetSelection(PR_TRUE) , mBodyElement(nsnull) , mInIMEMode(PR_FALSE) @@ -376,8 +538,9 @@ nsEditor::Do(nsITransaction *aTxn) // save off weak reference to placeholder txn mPlaceHolderTxn = getter_AddRefs( NS_GetWeakReference(plcTxn) ); - plcTxn->Init(mPresShellWeak, mPlaceHolderName, mTxnStartNode, mTxnStartOffset); - + plcTxn->Init(mPresShellWeak, mPlaceHolderName, mSelState); + mSelState = nsnull; // placeholder txn took ownership of this pointer + // finally we QI to an nsITransaction since that's what Do() expects nsCOMPtr theTxn = do_QueryInterface(plcTxn); nsITransaction* txn = theTxn; @@ -588,17 +751,8 @@ nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName) nsCOMPtr selection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; - PRBool collapsed; - res = selection->GetIsCollapsed(&collapsed); - if (NS_FAILED(res)) return res; - if (collapsed) // we cant merge with previous typing/deleting if selection not collapsed - { - // need to remember colapsed selection point to Init the placeholder with later. - // this is because we dont actually make the placeholder until we need it, and we - // might have moved the selection as part of the typing processing by then. - res = GetStartNodeAndOffset(selection, &mTxnStartNode, &mTxnStartOffset); - if (NS_FAILED(res)) return res; - } + mSelState = new nsSelectionState(); + mSelState->SaveSelection(selection); } mPlaceHolderBatch++; @@ -613,8 +767,13 @@ nsEditor::EndPlaceHolderTransaction() { // time to turn off the batch EndUpdateViewBatch(); - mTxnStartNode = nsnull; - mTxnStartOffset = 0; + if (mSelState) + { + // we saved the selection state, but never got to hand it to placeholder + // (else we ould have nulled out this pointer), so destroy it to prevent leaks. + delete mSelState; + mSelState = nsnull; + } if (mPlaceHolderTxn) // we might have never made a placeholder if no action took place { nsCOMPtr plcTxn = do_QueryReferent(mPlaceHolderTxn); diff --git a/mozilla/editor/libeditor/base/nsEditor.h b/mozilla/editor/libeditor/base/nsEditor.h index 6b92e5be0e7..d743215c41b 100644 --- a/mozilla/editor/libeditor/base/nsEditor.h +++ b/mozilla/editor/libeditor/base/nsEditor.h @@ -46,6 +46,7 @@ #include "nsICSSStyleSheet.h" #include "nsIDTD.h" #include "nsIDOMElement.h" +#include "nsVoidArray.h" class nsIEditActionListener; class nsIDocumentStateListener; @@ -70,6 +71,38 @@ class AddStyleSheetTxn; class RemoveStyleSheetTxn; class nsFileSpec; +/*************************************************************************** + * class for recording selection info. stores selection as collection of + * { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store + * ranges since dom gravity will possibly change the ranges. + */ + +// first a helper struct for saving/setting ranges +struct SelRangeStore +{ + nsresult StoreRange(nsIDOMRange *aRange); + nsresult GetRange(nsCOMPtr *outRange); + + nsCOMPtr startNode; + PRInt32 startOffset; + nsCOMPtr endNode; + PRInt32 endOffset; +}; + +class nsSelectionState +{ + public: + + nsSelectionState(); + ~nsSelectionState(); + + nsresult SaveSelection(nsIDOMSelection *aSel); + nsresult RestoreSelection(nsIDOMSelection *aSel); + PRBool IsCollapsed(); + PRBool IsEqual(nsSelectionState *aSelState); + + nsVoidArray mArray; +}; /** implementation of an editor object. it will be the controler/focal point * for the main editor services. i.e. the GUIManager, publishing, transaction @@ -653,8 +686,7 @@ protected: nsWeakPtr mPlaceHolderTxn; // weak reference to placeholder for begin/end batch purposes nsIAtom *mPlaceHolderName; // name of placeholder transaction PRInt32 mPlaceHolderBatch; // nesting count for batching - nsCOMPtr mTxnStartNode; // saved selection info to pass to placeholder at init time - PRInt32 mTxnStartOffset; // " " " " + nsSelectionState *mSelState; // saved selection state for placeholder txn batching PRBool mShouldTxnSetSelection; // turn off for conservative selection adjustment by txns nsCOMPtr mBodyElement; // cached body node // diff --git a/mozilla/editor/libeditor/base/nsEditorUtils.h b/mozilla/editor/libeditor/base/nsEditorUtils.h index be12960f5b3..fd217532224 100644 --- a/mozilla/editor/libeditor/base/nsEditorUtils.h +++ b/mozilla/editor/libeditor/base/nsEditorUtils.h @@ -30,31 +30,13 @@ #include "nsIDOMSelection.h" #include "nsIEditor.h" #include "nsIAtom.h" - +#include "nsVoidArray.h" #include "nsEditor.h" -class nsAutoEditBatch -{ - private: - nsCOMPtr mEd; - public: - nsAutoEditBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd)) - { if (mEd) mEd->BeginTransaction(); } - ~nsAutoEditBatch() { if (mEd) mEd->EndTransaction(); } -}; - -class nsAutoEditMayBatch -{ - private: - nsCOMPtr mEd; - PRBool mDidBatch; - public: - nsAutoEditMayBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd)), mDidBatch(PR_FALSE) {} - ~nsAutoEditMayBatch() { if (mEd && mDidBatch) mEd->EndTransaction(); } - - void batch() { if (mEd && !mDidBatch) {mEd->BeginTransaction(); mDidBatch=PR_TRUE;} } -}; - +/*************************************************************************** + * stack based helper class for batching a collection of txns inside a + * placeholder txn. + */ class nsAutoPlaceHolderBatch { private: @@ -66,6 +48,22 @@ class nsAutoPlaceHolderBatch }; +/*************************************************************************** + * stack based helper class for batching a collection of txns. + * Note: I changed this to use placeholder batching so that we get + * proper selection save/restore across undo/redo. + */ +class nsAutoEditBatch : public nsAutoPlaceHolderBatch +{ + public: + nsAutoEditBatch( nsIEditor *aEd) : nsAutoPlaceHolderBatch(aEd,nsnull) {} + ~nsAutoEditBatch() {} +}; + +/*************************************************************************** + * stack based helper class for saving/restoring selection. Note that this + * assumes that the nodes involved are still around afterwards! + */ class nsAutoSelectionReset { private: @@ -138,5 +136,4 @@ class nsAutoTxnsConserveSelection }; - #endif // nsEditorUtils_h__ diff --git a/mozilla/editor/libeditor/base/nsIAbsorbingTransaction.h b/mozilla/editor/libeditor/base/nsIAbsorbingTransaction.h index 87baa024617..48c915c2acc 100644 --- a/mozilla/editor/libeditor/base/nsIAbsorbingTransaction.h +++ b/mozilla/editor/libeditor/base/nsIAbsorbingTransaction.h @@ -40,6 +40,8 @@ Transaction interface to outside world #include "nsIAtom.h" #include "nsIDOMNode.h" +class nsSelectionState; + /** * A transaction interface mixin - for transactions that can support. * the placeholder absorbtion idiom. @@ -49,13 +51,13 @@ public: static const nsIID& GetIID() { static nsIID iid = NS_IABSORBINGTRANSACTION_IID; return iid; } - NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsIDOMNode *aStartNode, PRInt32 aStartOffset)=0; + NS_IMETHOD Init(nsWeakPtr aPresShellWeak, nsIAtom *aName, nsSelectionState *aSelState)=0; NS_IMETHOD EndPlaceHolderBatch()=0; NS_IMETHOD GetTxnName(nsIAtom **aName)=0; - NS_IMETHOD GetStartNodeAndOffset(nsCOMPtr *aTxnStartNode, PRInt32 *aTxnStartOffset)=0; + NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult)=0; NS_IMETHOD ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress)=0; diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp index 6a1509eaefe..92f10b818e5 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp @@ -2748,10 +2748,6 @@ nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, res = aSelection->Collapse(listparent,offset+1); if (NS_FAILED(res)) return res; - // now attempt to adjust selection to a text node. - // if we fail, then that means we need toinsert a break - res = AdjustSelection(aSelection, nsIEditor::eNext); - if (NS_FAILED(res)) return res; // get the selection location nsCOMPtr selNode; PRInt32 selOffset; diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index fa54953307e..1d646e56547 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -218,6 +218,11 @@ nsHTMLEditor::nsHTMLEditor() { // Done in nsEditor // NS_INIT_REFCNT(); + mBoldAtom = getter_AddRefs(NS_NewAtom("b")); + mItalicAtom = getter_AddRefs(NS_NewAtom("i")); + mUnderlineAtom = getter_AddRefs(NS_NewAtom("u")); + mFontAtom = getter_AddRefs(NS_NewAtom("font")); + if (!gTypingTxnName) gTypingTxnName = NS_NewAtom("Typing"); else @@ -1053,6 +1058,7 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, PRBool isCollapsed; selection->GetIsCollapsed(&isCollapsed); + nsCOMPtr collapsedNode; nsCOMPtr enumerator; result = selection->GetEnumerator(getter_AddRefs(enumerator)); if (NS_FAILED(result)) return result; @@ -1066,7 +1072,45 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, if ((NS_SUCCEEDED(result)) && currentItem) { PRBool firstNodeInRange = PR_TRUE; // for each range, set a flag - nsCOMPtr range( do_QueryInterface(currentItem) ); + nsCOMPtr range(do_QueryInterface(currentItem)); + + if (isCollapsed) + { + // efficiency hack. we cache prior results for being collapsed in a given text node. + // this speeds up typing. Note that other parts of the editor code have to clear out + // this cache after certain actions. + range->GetStartParent(getter_AddRefs(collapsedNode)); + if (!collapsedNode) return NS_ERROR_FAILURE; + // refresh the cache if we need to + if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode); + // cache now current, use it! But *or* it with typeInState results... + PRBool isSet; + if (aProperty == mBoldAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedBoldStyle || isSet); + return NS_OK; + } + else if (aProperty == mItalicAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedItalicStyle || isSet); + return NS_OK; + } + else if (aProperty == mUnderlineAtom.get()) + { + GetTypingState(aProperty, isSet); + aFirst = aAny = aAll = (mCachedUnderlineStyle || isSet); + return NS_OK; + } + else if (aProperty == mFontAtom.get()) + { +// MOOSE! + return NS_OK; + } + } + + // either non-collapsed selection or no cached value: do it the hard way nsCOMPtr iter; result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, nsIContentIterator::GetIID(), @@ -1138,7 +1182,8 @@ NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty, iter->CurrentNode(getter_AddRefs(content)); } } - if (PR_FALSE==aAny) { // make sure that if none of the selection is set, we don't report all is set + if (PR_FALSE==aAny) + { // make sure that if none of the selection is set, we don't report all is set aAll = PR_FALSE; } //if (gNoisy) { printf(" returning first=%d any=%d all=%d\n", aFirst, aAny, aAll); } @@ -4450,6 +4495,8 @@ void nsHTMLEditor::ApplyStyleSheetToPresShellDocument(nsICSSStyleSheet* aSheet, NS_IMETHODIMP nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection) { + if (! ((opID==kOpInsertText) || (opID==kOpInsertIMEText)) ) + ClearInlineStylesCache(); if (mRules) return mRules->BeforeEdit(opID, aDirection); return NS_OK; } @@ -4460,6 +4507,8 @@ nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection) NS_IMETHODIMP nsHTMLEditor::EndOperation(PRInt32 opID, nsIEditor::EDirection aDirection) { + if (! ((opID==kOpInsertText) || (opID==kOpInsertIMEText)) ) + ClearInlineStylesCache(); if (mRules) return mRules->AfterEdit(opID, aDirection); return NS_OK; } @@ -5882,6 +5931,22 @@ nsHTMLEditor::DeleteSelectionAndPrepareToCreateNode(nsCOMPtr &parent #pragma mark - #endif +void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode) +{ + if (!aNode) return; + nsCOMPtr resultNode; + mCachedNode = do_QueryInterface(aNode); + IsTextPropertySetByContent(aNode, mBoldAtom, 0, 0, mCachedBoldStyle, getter_AddRefs(resultNode)); + IsTextPropertySetByContent(aNode, mItalicAtom, 0, 0, mCachedItalicStyle, getter_AddRefs(resultNode)); + IsTextPropertySetByContent(aNode, mUnderlineAtom, 0, 0, mCachedUnderlineStyle, getter_AddRefs(resultNode)); + +} + +void nsHTMLEditor::ClearInlineStylesCache() +{ + mCachedNode = nsnull; +} + #ifdef PRE_NODE_IN_BODY nsCOMPtr nsHTMLEditor::FindPreElement() { @@ -6890,7 +6955,7 @@ nsHTMLEditor::RemoveTextPropertiesForNodeWithDifferentParents(nsIDOMNode *aStar } } else - { + { skippedEndNode = PR_TRUE; if (gNoisy) { printf("skipping end node because aProperty not set.\n"); } } @@ -6903,13 +6968,16 @@ nsHTMLEditor::RemoveTextPropertiesForNodeWithDifferentParents(nsIDOMNode *aStar } else { - endNode = GetChildAt(aEndNode, aEndOffset); + // experimental hack: need to rewrite all of this + // MOOSE! + if (aEndOffset<0) aEndOffset=1; + endNode = GetChildAt(aEndNode, aEndOffset-1); parent = do_QueryInterface(aEndNode); if (!endNode || !parent) {return NS_ERROR_NULL_POINTER;} IsTextPropertySetByContent(endNode, aPropName, aAttribute, nsnull, textPropertySet, getter_AddRefs(resultNode)); if (PR_TRUE==textPropertySet) { - result = RemoveTextPropertiesForNode(endNode, parent, aEndOffset, aEndOffset+1, aPropName, aAttribute); + result = RemoveTextPropertiesForNode(endNode, parent, aEndOffset-1, aEndOffset, aPropName, aAttribute); } else { diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index 82d3a5e3218..4dcfa1ea7a1 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -306,7 +306,10 @@ protected: PRBool SetCaretInTableCell(nsIDOMElement* aElement); PRBool IsElementInBody(nsIDOMElement* aElement); - + // inline style caching + void CacheInlineStyles(nsIDOMNode *aNode); + void ClearInlineStylesCache(); + // key event helpers NS_IMETHOD TabInTable(PRBool inIsShift, PRBool *outHandled); NS_IMETHOD CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr *outBRNode); @@ -544,6 +547,17 @@ protected: nsCOMPtr mFocusListenerP; PRBool mIsComposing; PRInt32 mMaxTextLength; + + nsCOMPtr mBoldAtom; + nsCOMPtr mItalicAtom; + nsCOMPtr mUnderlineAtom; + nsCOMPtr mFontAtom; + nsCOMPtr mCachedNode; + + PRBool mCachedBoldStyle; + PRBool mCachedItalicStyle; + PRBool mCachedUnderlineStyle; + nsString mCachedFontName; public: static nsIAtom *gTypingTxnName;