make selection sticky across undo/redo (24573);

This commit is contained in:
jfrancis%netscape.com 2000-01-31 10:30:12 +00:00
Родитель 29efb32bce
Коммит ae816c1a66
24 изменённых файлов: 880 добавлений и 290 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIAbsorbingTransaction> 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<nsIAtom> atom;
plcTxn->GetTxnName(getter_AddRefs(atom));
if (atom && (atom == mName))
nsCOMPtr<nsIAbsorbingTransaction> 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<nsIDOMNode> otherTxnStartNode;
PRInt32 otherTxnStartOffset;
res = plcTxn->GetStartNodeAndOffset(&otherTxnStartNode, &otherTxnStartOffset);
if (NS_FAILED(res)) return res;
if ((otherTxnStartNode == mEndNode) && (otherTxnStartOffset == mEndOffset))
nsCOMPtr<nsIAtom> 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<nsIDOMNode> *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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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;
}

Просмотреть файл

@ -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<nsIDOMNode> *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<nsIDOMNode> 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;
};

Просмотреть файл

@ -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 (arrayCount<rangeCount)
{
PRInt32 count = rangeCount-arrayCount;
for (i=0; i<count; i++)
{
item = new SelRangeStore;
mArray.AppendElement(item);
}
}
// else if we have too many, delete them
else if (rangeCount>arrayCount)
{
while (item = (SelRangeStore*)mArray.ElementAt(rangeCount))
{
delete item;
mArray.RemoveElementAt(rangeCount);
}
}
// now store the selection ranges
for (i=0; i<rangeCount; i++)
{
item = (SelRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> 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<arrayCount; i++)
{
item = (SelRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> 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<nsIDOMRange> 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; i<myCount; i++)
{
myItem = (SelRangeStore*)mArray.ElementAt(0);
itsItem = (SelRangeStore*)(aSelState->mArray.ElementAt(0));
if (!myItem || !itsItem) return PR_FALSE;
nsCOMPtr<nsIDOMRange> 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<nsIDOMRange> *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<nsITransaction> theTxn = do_QueryInterface(plcTxn);
nsITransaction* txn = theTxn;
@ -588,17 +751,8 @@ nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName)
nsCOMPtr<nsIDOMSelection> 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<nsIAbsorbingTransaction> plcTxn = do_QueryReferent(mPlaceHolderTxn);

Просмотреть файл

@ -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<nsIDOMRange> *outRange);
nsCOMPtr<nsIDOMNode> startNode;
PRInt32 startOffset;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMElement> mBodyElement; // cached body node
//

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -30,31 +30,13 @@
#include "nsIDOMSelection.h"
#include "nsIEditor.h"
#include "nsIAtom.h"
#include "nsVoidArray.h"
#include "nsEditor.h"
class nsAutoEditBatch
{
private:
nsCOMPtr<nsIEditor> mEd;
public:
nsAutoEditBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd))
{ if (mEd) mEd->BeginTransaction(); }
~nsAutoEditBatch() { if (mEd) mEd->EndTransaction(); }
};
class nsAutoEditMayBatch
{
private:
nsCOMPtr<nsIEditor> 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__

Просмотреть файл

@ -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<nsIDOMNode> selNode;
PRInt32 selOffset;

Просмотреть файл

@ -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<nsIDOMNode> collapsedNode;
nsCOMPtr<nsIEnumerator> 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<nsIDOMRange> range( do_QueryInterface(currentItem) );
nsCOMPtr<nsIDOMRange> 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<nsIContentIterator> 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<nsIDOMNode> &parent
#pragma mark -
#endif
void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode)
{
if (!aNode) return;
nsCOMPtr<nsIDOMNode> 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<nsIDOMElement> 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
{

Просмотреть файл

@ -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<nsIDOMNode> *outBRNode);
@ -544,6 +547,17 @@ protected:
nsCOMPtr<nsIDOMEventListener> mFocusListenerP;
PRBool mIsComposing;
PRInt32 mMaxTextLength;
nsCOMPtr<nsIAtom> mBoldAtom;
nsCOMPtr<nsIAtom> mItalicAtom;
nsCOMPtr<nsIAtom> mUnderlineAtom;
nsCOMPtr<nsIAtom> mFontAtom;
nsCOMPtr<nsIDOMNode> mCachedNode;
PRBool mCachedBoldStyle;
PRBool mCachedItalicStyle;
PRBool mCachedUnderlineStyle;
nsString mCachedFontName;
public:
static nsIAtom *gTypingTxnName;

Просмотреть файл

@ -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<nsIDOMNode> *aTxnStartNode, PRInt32 *aTxnStartOffset)=0;
NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult)=0;
NS_IMETHOD ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress)=0;

Просмотреть файл

@ -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<nsIAtom> 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)

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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<nsIAtom> 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)

Просмотреть файл

@ -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

Просмотреть файл

@ -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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIAbsorbingTransaction> 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<nsIAtom> atom;
plcTxn->GetTxnName(getter_AddRefs(atom));
if (atom && (atom == mName))
nsCOMPtr<nsIAbsorbingTransaction> 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<nsIDOMNode> otherTxnStartNode;
PRInt32 otherTxnStartOffset;
res = plcTxn->GetStartNodeAndOffset(&otherTxnStartNode, &otherTxnStartOffset);
if (NS_FAILED(res)) return res;
if ((otherTxnStartNode == mEndNode) && (otherTxnStartOffset == mEndOffset))
nsCOMPtr<nsIAtom> 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<nsIDOMNode> *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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMSelection> 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;
}

Просмотреть файл

@ -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<nsIDOMNode> *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<nsIDOMNode> 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;
};

Просмотреть файл

@ -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 (arrayCount<rangeCount)
{
PRInt32 count = rangeCount-arrayCount;
for (i=0; i<count; i++)
{
item = new SelRangeStore;
mArray.AppendElement(item);
}
}
// else if we have too many, delete them
else if (rangeCount>arrayCount)
{
while (item = (SelRangeStore*)mArray.ElementAt(rangeCount))
{
delete item;
mArray.RemoveElementAt(rangeCount);
}
}
// now store the selection ranges
for (i=0; i<rangeCount; i++)
{
item = (SelRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> 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<arrayCount; i++)
{
item = (SelRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> 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<nsIDOMRange> 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; i<myCount; i++)
{
myItem = (SelRangeStore*)mArray.ElementAt(0);
itsItem = (SelRangeStore*)(aSelState->mArray.ElementAt(0));
if (!myItem || !itsItem) return PR_FALSE;
nsCOMPtr<nsIDOMRange> 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<nsIDOMRange> *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<nsITransaction> theTxn = do_QueryInterface(plcTxn);
nsITransaction* txn = theTxn;
@ -588,17 +751,8 @@ nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName)
nsCOMPtr<nsIDOMSelection> 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<nsIAbsorbingTransaction> plcTxn = do_QueryReferent(mPlaceHolderTxn);

Просмотреть файл

@ -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<nsIDOMRange> *outRange);
nsCOMPtr<nsIDOMNode> startNode;
PRInt32 startOffset;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMElement> mBodyElement; // cached body node
//

Просмотреть файл

@ -30,31 +30,13 @@
#include "nsIDOMSelection.h"
#include "nsIEditor.h"
#include "nsIAtom.h"
#include "nsVoidArray.h"
#include "nsEditor.h"
class nsAutoEditBatch
{
private:
nsCOMPtr<nsIEditor> mEd;
public:
nsAutoEditBatch( nsIEditor *aEd) : mEd(do_QueryInterface(aEd))
{ if (mEd) mEd->BeginTransaction(); }
~nsAutoEditBatch() { if (mEd) mEd->EndTransaction(); }
};
class nsAutoEditMayBatch
{
private:
nsCOMPtr<nsIEditor> 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__

Просмотреть файл

@ -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<nsIDOMNode> *aTxnStartNode, PRInt32 *aTxnStartOffset)=0;
NS_IMETHOD StartSelectionEquals(nsSelectionState *aSelState, PRBool *aResult)=0;
NS_IMETHOD ForwardEndBatchTo(nsIAbsorbingTransaction *aForwardingAddress)=0;

Просмотреть файл

@ -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<nsIDOMNode> selNode;
PRInt32 selOffset;

Просмотреть файл

@ -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<nsIDOMNode> collapsedNode;
nsCOMPtr<nsIEnumerator> 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<nsIDOMRange> range( do_QueryInterface(currentItem) );
nsCOMPtr<nsIDOMRange> 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<nsIContentIterator> 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<nsIDOMNode> &parent
#pragma mark -
#endif
void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode)
{
if (!aNode) return;
nsCOMPtr<nsIDOMNode> 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<nsIDOMElement> 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
{

Просмотреть файл

@ -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<nsIDOMNode> *outBRNode);
@ -544,6 +547,17 @@ protected:
nsCOMPtr<nsIDOMEventListener> mFocusListenerP;
PRBool mIsComposing;
PRInt32 mMaxTextLength;
nsCOMPtr<nsIAtom> mBoldAtom;
nsCOMPtr<nsIAtom> mItalicAtom;
nsCOMPtr<nsIAtom> mUnderlineAtom;
nsCOMPtr<nsIAtom> mFontAtom;
nsCOMPtr<nsIDOMNode> mCachedNode;
PRBool mCachedBoldStyle;
PRBool mCachedItalicStyle;
PRBool mCachedUnderlineStyle;
nsString mCachedFontName;
public:
static nsIAtom *gTypingTxnName;