added PlaceholderTxn. This is an aggregate transaction that sits on the undo stack

and merges in subsequent transactions indiscriminately until it's told to stop.
It also gives the last transaction in its child list a chance to merge the
next transaction.
All this is in support of complex transactions that result in text insertion
being able to collapse into a single undoable event.
Also improved tracking of bogus content node used when document is empty.
This commit is contained in:
buster%netscape.com 1999-03-15 00:57:32 +00:00
parent 422d5a83c5
commit d5932338f7
25 changed files with 476 additions and 230 deletions

View File

@ -167,7 +167,8 @@ NS_IMETHODIMP DeleteRangeTxn::Undo(void)
nsCOMPtr<nsIDOMSelection> selection;
result = mEditor->GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(result)) {
result = selection->Collapse(mStartParent, mStartOffset);
selection->Collapse(mStartParent, mStartOffset);
selection->Extend(mEndParent, mEndOffset);
}
}

View File

@ -153,26 +153,6 @@ NS_IMETHODIMP EditAggregateTxn::AppendChild(EditTxn *aTxn)
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP EditAggregateTxn::SetName(nsIAtom *aName)
{
mName = do_QueryInterface(aName);
return NS_OK;
}
NS_IMETHODIMP EditAggregateTxn::GetName(nsIAtom **aName)
{
if (aName)
{
if (mName)
{
*aName = mName;
NS_ADDREF(*aName);
return NS_OK;
}
}
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP EditAggregateTxn::GetCount(PRInt32 *aCount)
{
if (!aCount) {
@ -205,6 +185,28 @@ NS_IMETHODIMP EditAggregateTxn::GetTxnAt(PRInt32 aIndex, EditTxn **aTxn)
}
NS_IMETHODIMP EditAggregateTxn::SetName(nsIAtom *aName)
{
mName = do_QueryInterface(aName);
return NS_OK;
}
NS_IMETHODIMP EditAggregateTxn::GetName(nsIAtom **aName)
{
if (aName)
{
if (mName)
{
*aName = mName;
NS_ADDREF(*aName);
return NS_OK;
}
}
return NS_ERROR_NULL_POINTER;
}

View File

@ -71,10 +71,10 @@ public:
*/
NS_IMETHOD GetTxnAt(PRInt32 aIndex, EditTxn **aTxn);
/** set the name assigned to this aggregate txn */
/** set the name assigned to this txn */
NS_IMETHOD SetName(nsIAtom *aName);
/** get the name assigned to this aggregate txn */
/** get the name assigned to this txn */
NS_IMETHOD GetName(nsIAtom **aName);
protected:
@ -82,7 +82,6 @@ protected:
//XXX: if this was an nsISupportsArray, it would handle refcounting for us
nsVoidArray * mChildren;
nsCOMPtr<nsIAtom> mName;
};
#endif

View File

@ -20,8 +20,7 @@
#define EditTxn_h__
#include "nsITransaction.h"
class nsIDOMNode;
#include "nsCOMPtr.h"
#define EDIT_TXN_IID \
{/* c5ea31b0-ac48-11d2-86d8-000064657374 */ \
@ -31,6 +30,8 @@ class nsIDOMNode;
/**
* base class for all document editing transactions.
* provides default concrete behavior for all nsITransaction methods.
* EditTxns optionally have a name. This name is for internal purposes only,
* it is never seen by the user or by any external entity.
*/
class EditTxn : public nsITransaction
{

View File

@ -119,19 +119,23 @@ NS_IMETHODIMP InsertTextTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransacti
otherTxn->GetName(getter_AddRefs(txnName));
if (txnName.get()==gInsertTextTxnName)
{ // yep, it's one of ours. By definition, it must contain only
// a single InsertTextTxn
// another aggregate with a single child,
// or a single InsertTextTxn
nsCOMPtr<EditTxn> childTxn;
otherTxn->GetTxnAt(0, getter_AddRefs(childTxn));
nsCOMPtr<InsertTextTxn> otherInsertTxn;
otherInsertTxn = do_QueryInterface(childTxn, &result);
if (otherInsertTxn)
if (childTxn)
{
if (PR_TRUE==IsSequentialInsert(otherInsertTxn))
nsCOMPtr<InsertTextTxn> otherInsertTxn;
otherInsertTxn = do_QueryInterface(childTxn, &result);
if (otherInsertTxn)
{
nsAutoString otherData;
otherInsertTxn->GetData(otherData);
mStringToInsert += otherData;
*aDidMerge = PR_TRUE;
if (PR_TRUE==IsSequentialInsert(otherInsertTxn))
{
nsAutoString otherData;
otherInsertTxn->GetData(otherData);
mStringToInsert += otherData;
*aDidMerge = PR_TRUE;
}
}
}
}

View File

@ -49,6 +49,7 @@ CPPSRCS = \
DeleteTableRowTxn.cpp \
JoinTableCellsTxn.cpp \
InsertTextTxn.cpp \
PlaceholderTxn.cpp \
DeleteTextTxn.cpp \
CreateElementTxn.cpp \
InsertElementTxn.cpp \

View File

@ -0,0 +1,74 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "PlaceholderTxn.h"
#include "nsVoidArray.h"
#ifdef NS_DEBUG
static PRBool gNoisy = PR_TRUE;
#else
static const PRBool gNoisy = PR_FALSE;
#endif
PlaceholderTxn::PlaceholderTxn()
: EditAggregateTxn()
{
mAbsorb=PR_TRUE;
}
PlaceholderTxn::~PlaceholderTxn()
{
}
NS_IMETHODIMP PlaceholderTxn::Do(void)
{
return NS_OK;
}
NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransaction)
{
// set out param default value
if (nsnull!=aDidMerge)
*aDidMerge=PR_FALSE;
nsresult result = NS_OK;
if ((nsnull!=aDidMerge) && (nsnull!=aTransaction))
{
EditTxn *editTxn = (EditTxn*)aTransaction; //XXX: hack, not safe! need nsIEditTransaction!
if (PR_TRUE==mAbsorb)
{ // yep, it's one of ours. Assimilate it.
AppendChild(editTxn);
*aDidMerge = PR_TRUE;
}
else
{ // let our last child txn make the choice
PRInt32 count = mChildren->Count();
if (0<count)
{
EditTxn *lastTxn = (EditTxn*)(mChildren->ElementAt(count-1));
if (lastTxn)
{
lastTxn->Merge(aDidMerge, aTransaction);
}
}
}
}
return result;
}

View File

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#ifndef AggregatePlaceholderTxn_h__
#define AggregatePlaceholderTxn_h__
#include "EditAggregateTxn.h"
#define PLACEHOLDER_TXN_IID \
{/* {0CE9FB00-D9D1-11d2-86DE-000064657374} */ \
0x0CE9FB00, 0xD9D1, 0x11d2, \
{0x86, 0xde, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74} }
/**
* An aggregate transaction that knows how to absorb all subsequent
* transactions with the same name. This transaction does not "Do" anything.
* But it absorbs other transactions via merge, and can undo/redo the
* transactions it has absorbed.
*/
class PlaceholderTxn : public EditAggregateTxn
{
public:
private:
PlaceholderTxn();
public:
virtual ~PlaceholderTxn();
NS_IMETHOD Do(void);
NS_IMETHOD Merge(PRBool *aDidMerge, nsITransaction *aTransaction);
NS_IMETHOD SetAbsorb(PRBool aAbsorb);
friend class TransactionFactory;
protected:
PRBool mAbsorb;
};
inline NS_IMETHODIMP PlaceholderTxn::SetAbsorb(PRBool aAbsorb)
{
mAbsorb = aAbsorb;
return NS_OK;
};
#endif

View File

@ -19,6 +19,7 @@
#include "TransactionFactory.h"
// transactions this factory knows how to build
#include "EditAggregateTxn.h"
#include "PlaceholderTxn.h"
#include "InsertTextTxn.h"
#include "DeleteTextTxn.h"
#include "CreateElementTxn.h"
@ -39,6 +40,7 @@
#include "JoinTableCellsTxn.h"
static NS_DEFINE_IID(kEditAggregateTxnIID, EDIT_AGGREGATE_TXN_IID);
static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID);
static NS_DEFINE_IID(kInsertTextTxnIID, INSERT_TEXT_TXN_IID);
static NS_DEFINE_IID(kDeleteTextTxnIID, DELETE_TEXT_TXN_IID);
static NS_DEFINE_IID(kCreateElementTxnIID, CREATE_ELEMENT_TXN_IID);
@ -91,6 +93,8 @@ TransactionFactory::GetNewTransaction(REFNSIID aTxnType, EditTxn **aResult)
*aResult = new JoinElementTxn();
else if (aTxnType.Equals(kEditAggregateTxnIID))
*aResult = new EditAggregateTxn();
else if (aTxnType.Equals(kPlaceholderTxnIID))
*aResult = new PlaceholderTxn();
else
result = NS_ERROR_NO_INTERFACE;

View File

@ -33,6 +33,7 @@ CPPSRCS = \
ChangeAttributeTxn.cpp \
InsertTextTxn.cpp \
DeleteTextTxn.cpp \
PlaceholderTxn.cpp \
CreateElementTxn.cpp \
InsertElementTxn.cpp \
DeleteElementTxn.cpp \
@ -67,6 +68,7 @@ CPP_OBJS = \
.\$(OBJDIR)\ChangeAttributeTxn.obj \
.\$(OBJDIR)\InsertTextTxn.obj \
.\$(OBJDIR)\DeleteTextTxn.obj \
.\$(OBJDIR)\PlaceholderTxn.obj \
.\$(OBJDIR)\CreateElementTxn.obj \
.\$(OBJDIR)\InsertElementTxn.obj \
.\$(OBJDIR)\DeleteElementTxn.obj \

View File

@ -1184,10 +1184,18 @@ NS_IMETHODIMP nsEditor::DeleteSelectionAndCreateNode(const nsString& aTag, nsIDO
return result;
}
#ifdef NS_DEBUG
PRBool testCollapsed;
nsresult debugResult = selection->IsCollapsed(&testCollapsed);
NS_ASSERTION((NS_SUCCEEDED(result)), "couldn't get a selection after deletion");
NS_ASSERTION(PR_TRUE==testCollapsed, "selection not reset after deletion");;
nsCOMPtr<nsIDOMNode>testSelectedNode;
PRInt32 testOffset;
nsresult debugResult = selection->GetAnchorNodeAndOffset(getter_AddRefs(testSelectedNode), &testOffset);
// no selection is ok.
// if there is a selection, it must be collapsed
if (testSelectedNode)
{
PRBool testCollapsed;
debugResult = selection->IsCollapsed(&testCollapsed);
NS_ASSERTION((NS_SUCCEEDED(result)), "couldn't get a selection after deletion");
NS_ASSERTION(PR_TRUE==testCollapsed, "selection not reset after deletion");
}
#endif
}
// split the text node

View File

@ -18,6 +18,8 @@
#include "nsTextEditRules.h"
#include "nsTextEditor.h"
#include "PlaceholderTxn.h"
#include "InsertTextTxn.h"
#include "nsCOMPtr.h"
#include "nsIDOMNode.h"
#include "nsIDOMElement.h"
@ -31,6 +33,8 @@
const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
const static char* kMOZEditorBogusNodeValue="TRUE";
static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID);
static PRBool NodeIsType(nsIDOMNode *aNode, nsIAtom *aTag)
{
nsCOMPtr<nsIDOMElement>element;
@ -115,45 +119,12 @@ nsTextEditRules::WillInsert(nsIDOMSelection *aSelection, PRBool *aCancel)
*aCancel = PR_FALSE;
// check for the magic content node and delete it if it exists
nsCOMPtr<nsIDOMDocument>doc;
mEditor->GetDocument(getter_AddRefs(doc));
nsCOMPtr<nsIDOMNodeList>nodeList;
nsAutoString bodyTag = "body";
nsresult result = doc->GetElementsByTagName(bodyTag, getter_AddRefs(nodeList));
if ((NS_SUCCEEDED(result)) && nodeList)
if (mBogusNode)
{
PRUint32 count;
nodeList->GetLength(&count);
NS_ASSERTION(1==count, "there is not exactly 1 body in the document!");
nsCOMPtr<nsIDOMNode>bodyNode;
result = nodeList->Item(0, getter_AddRefs(bodyNode));
if ((NS_SUCCEEDED(result)) && bodyNode)
{ // now we've got the body tag.
// iterate the body tag, looking for editable content
// if the magic node is found, delete it
PRBool foundBogusContent=PR_TRUE;
nsCOMPtr<nsIDOMNode>bodyChild; // a child of the body, for iteration
nsCOMPtr<nsIDOMNode>bogusNode; // this will be the magic node
result = bodyNode->GetFirstChild(getter_AddRefs(bodyChild));
while ((NS_SUCCEEDED(result)) && bodyChild)
{
bogusNode = do_QueryInterface(bodyChild);
if (PR_TRUE==IsEditable(bodyChild))
{
foundBogusContent = PR_FALSE;
break;
}
nsCOMPtr<nsIDOMNode>temp;
bodyChild->GetNextSibling(getter_AddRefs(temp));
bodyChild = do_QueryInterface(temp);
}
if (PR_TRUE==foundBogusContent)
{
mEditor->DeleteNode(bogusNode);
// there is no longer any legit selection, so clear it.
aSelection->ClearSelection();
}
}
mEditor->DeleteNode(mBogusNode);
mBogusNode = do_QueryInterface(nsnull);
// there is no longer any legit selection, so clear it.
aSelection->ClearSelection();
}
return NS_OK;
@ -169,13 +140,22 @@ NS_IMETHODIMP
nsTextEditRules::WillInsertText(nsIDOMSelection *aSelection,
const nsString& aInputString,
PRBool *aCancel,
nsString& aOutputString)
nsString& aOutputString,
PlaceholderTxn **aTxn)
{
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
// initialize out param
*aCancel = PR_FALSE;
// by default, we insert what we're told to insert
aOutputString = aInputString;
if (mBogusNode)
{
nsresult result = TransactionFactory::GetNewTransaction(kPlaceholderTxnIID, (EditTxn **)aTxn);
if (NS_FAILED(result)) { return result; }
if (!*aTxn) { return NS_ERROR_NULL_POINTER; }
(*aTxn)->SetName(InsertTextTxn::gInsertTextTxnName);
mEditor->Do(*aTxn);
}
return WillInsert(aSelection, aCancel);
}
@ -315,31 +295,9 @@ nsTextEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, PRBool *aCance
*aCancel = PR_FALSE;
// if there is only bogus content, cancel the operation
nsCOMPtr<nsIDOMNode>node;
PRInt32 offset;
nsresult result = aSelection->GetAnchorNodeAndOffset(getter_AddRefs(node), &offset);
if ((NS_SUCCEEDED(result)) && node)
{
nsCOMPtr<nsIDOMNode>parent;
parent = do_QueryInterface(node);
while (node)
{ //if we find the bogus node, cancel the operation
nsCOMPtr<nsIDOMElement>element;
element = do_QueryInterface(parent);
if (element)
{
nsAutoString att(kMOZEditorBogusNodeAttr);
nsAutoString val;
nsresult result = element->GetAttribute(att, val);
if (val.Equals(kMOZEditorBogusNodeValue)) {
*aCancel = PR_TRUE;
return NS_OK;
}
}
// walk up the content hierarchy
parent->GetParentNode(getter_AddRefs(node));
parent = do_QueryInterface(node);
}
if (mBogusNode) {
*aCancel = PR_TRUE;
return NS_OK;
}
return NS_OK;
}
@ -389,13 +347,13 @@ nsTextEditRules::DidDeleteSelection(nsIDOMSelection *aSelection, nsresult aResul
}
if (PR_TRUE==needsBogusContent)
{
nsCOMPtr<nsIDOMNode>newPNode;
// set mBogusNode to be the newly created <P>
result = mEditor->CreateNode(nsAutoString("P"), bodyNode, 0,
getter_AddRefs(newPNode));
if ((NS_SUCCEEDED(result)) && newPNode)
getter_AddRefs(mBogusNode));
if ((NS_SUCCEEDED(result)) && mBogusNode)
{
nsCOMPtr<nsIDOMNode>newTNode;
result = mEditor->CreateNode(nsIEditor::GetTextNodeTag(), newPNode, 0,
result = mEditor->CreateNode(nsIEditor::GetTextNodeTag(), mBogusNode, 0,
getter_AddRefs(newTNode));
if ((NS_SUCCEEDED(result)) && newTNode)
{
@ -411,7 +369,7 @@ nsTextEditRules::DidDeleteSelection(nsIDOMSelection *aSelection, nsresult aResul
}
// make sure we know the PNode is bogus
nsCOMPtr<nsIDOMElement>newPElement;
newPElement = do_QueryInterface(newPNode);
newPElement = do_QueryInterface(mBogusNode);
if (newPElement)
{
nsAutoString att(kMOZEditorBogusNodeAttr);

View File

@ -21,9 +21,10 @@
#include "nsIEditor.h"
#include "nsCOMPtr.h"
#include "nsIDOMNode.h"
class nsTextEditor;
class PlaceholderTxn;
/** Object that encapsulates HTML text-specific editing rules.
*
@ -52,7 +53,8 @@ public:
NS_IMETHOD WillInsertText(nsIDOMSelection *aSelection,
const nsString& aInputString,
PRBool *aCancel,
nsString& aOutputString);
nsString& aOutputString,
PlaceholderTxn ** aTxn);
NS_IMETHOD DidInsertText(nsIDOMSelection *aSelection, const nsString& aStringToInsert, nsresult aResult);
NS_IMETHOD WillInsert(nsIDOMSelection *aSelection, PRBool *aCancel);
@ -64,7 +66,7 @@ public:
protected:
nsTextEditor *mEditor; // note that we do not refcount the editor
nsCOMPtr<nsIDOMNode> mBogusNode; // magic node acts as placeholder in empty doc
};
#endif //nsTextEditRules_h__

View File

@ -49,6 +49,11 @@
#include "nsIPresShell.h"
#include "nsIStyleContext.h"
// transactions the text editor knows how to build itself
#include "TransactionFactory.h"
#include "PlaceholderTxn.h"
#include "InsertTextTxn.h"
class nsIFrame;
@ -430,22 +435,20 @@ NS_IMETHODIMP nsTextEditor::InsertText(const nsString& aStringToInsert)
nsCOMPtr<nsIDOMSelection> selection;
PRBool cancel= PR_FALSE;
nsresult result = nsEditor::BeginTransaction();
if (NS_FAILED(result)) { return result; }
// pre-process
nsEditor::GetSelection(getter_AddRefs(selection));
nsString stringToInsert;
result = mRules->WillInsertText(selection, aStringToInsert, &cancel, stringToInsert);
nsAutoString stringToInsert;
PlaceholderTxn *placeholderTxn=nsnull;
nsresult result = mRules->WillInsertText(selection, aStringToInsert, &cancel, stringToInsert,
&placeholderTxn);
if ((PR_FALSE==cancel) && (NS_SUCCEEDED(result)))
{
result = nsEditor::InsertText(stringToInsert);
// post-process
result = mRules->DidInsertText(selection, stringToInsert, result);
}
nsresult endTxnResult = nsEditor::EndTransaction(); // don't return this result!
NS_ASSERTION ((NS_SUCCEEDED(endTxnResult)), "bad end transaction result");
if (placeholderTxn)
placeholderTxn->SetAbsorb(PR_FALSE); // this ends the merging of txns into placeholderTxn
// BEGIN HACK!!!
HACKForceRedraw();

View File

@ -167,7 +167,8 @@ NS_IMETHODIMP DeleteRangeTxn::Undo(void)
nsCOMPtr<nsIDOMSelection> selection;
result = mEditor->GetSelection(getter_AddRefs(selection));
if (NS_SUCCEEDED(result)) {
result = selection->Collapse(mStartParent, mStartOffset);
selection->Collapse(mStartParent, mStartOffset);
selection->Extend(mEndParent, mEndOffset);
}
}

View File

@ -153,26 +153,6 @@ NS_IMETHODIMP EditAggregateTxn::AppendChild(EditTxn *aTxn)
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP EditAggregateTxn::SetName(nsIAtom *aName)
{
mName = do_QueryInterface(aName);
return NS_OK;
}
NS_IMETHODIMP EditAggregateTxn::GetName(nsIAtom **aName)
{
if (aName)
{
if (mName)
{
*aName = mName;
NS_ADDREF(*aName);
return NS_OK;
}
}
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP EditAggregateTxn::GetCount(PRInt32 *aCount)
{
if (!aCount) {
@ -205,6 +185,28 @@ NS_IMETHODIMP EditAggregateTxn::GetTxnAt(PRInt32 aIndex, EditTxn **aTxn)
}
NS_IMETHODIMP EditAggregateTxn::SetName(nsIAtom *aName)
{
mName = do_QueryInterface(aName);
return NS_OK;
}
NS_IMETHODIMP EditAggregateTxn::GetName(nsIAtom **aName)
{
if (aName)
{
if (mName)
{
*aName = mName;
NS_ADDREF(*aName);
return NS_OK;
}
}
return NS_ERROR_NULL_POINTER;
}

View File

@ -71,10 +71,10 @@ public:
*/
NS_IMETHOD GetTxnAt(PRInt32 aIndex, EditTxn **aTxn);
/** set the name assigned to this aggregate txn */
/** set the name assigned to this txn */
NS_IMETHOD SetName(nsIAtom *aName);
/** get the name assigned to this aggregate txn */
/** get the name assigned to this txn */
NS_IMETHOD GetName(nsIAtom **aName);
protected:
@ -82,7 +82,6 @@ protected:
//XXX: if this was an nsISupportsArray, it would handle refcounting for us
nsVoidArray * mChildren;
nsCOMPtr<nsIAtom> mName;
};
#endif

View File

@ -20,8 +20,7 @@
#define EditTxn_h__
#include "nsITransaction.h"
class nsIDOMNode;
#include "nsCOMPtr.h"
#define EDIT_TXN_IID \
{/* c5ea31b0-ac48-11d2-86d8-000064657374 */ \
@ -31,6 +30,8 @@ class nsIDOMNode;
/**
* base class for all document editing transactions.
* provides default concrete behavior for all nsITransaction methods.
* EditTxns optionally have a name. This name is for internal purposes only,
* it is never seen by the user or by any external entity.
*/
class EditTxn : public nsITransaction
{

View File

@ -119,19 +119,23 @@ NS_IMETHODIMP InsertTextTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransacti
otherTxn->GetName(getter_AddRefs(txnName));
if (txnName.get()==gInsertTextTxnName)
{ // yep, it's one of ours. By definition, it must contain only
// a single InsertTextTxn
// another aggregate with a single child,
// or a single InsertTextTxn
nsCOMPtr<EditTxn> childTxn;
otherTxn->GetTxnAt(0, getter_AddRefs(childTxn));
nsCOMPtr<InsertTextTxn> otherInsertTxn;
otherInsertTxn = do_QueryInterface(childTxn, &result);
if (otherInsertTxn)
if (childTxn)
{
if (PR_TRUE==IsSequentialInsert(otherInsertTxn))
nsCOMPtr<InsertTextTxn> otherInsertTxn;
otherInsertTxn = do_QueryInterface(childTxn, &result);
if (otherInsertTxn)
{
nsAutoString otherData;
otherInsertTxn->GetData(otherData);
mStringToInsert += otherData;
*aDidMerge = PR_TRUE;
if (PR_TRUE==IsSequentialInsert(otherInsertTxn))
{
nsAutoString otherData;
otherInsertTxn->GetData(otherData);
mStringToInsert += otherData;
*aDidMerge = PR_TRUE;
}
}
}
}

View File

@ -0,0 +1,74 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "PlaceholderTxn.h"
#include "nsVoidArray.h"
#ifdef NS_DEBUG
static PRBool gNoisy = PR_TRUE;
#else
static const PRBool gNoisy = PR_FALSE;
#endif
PlaceholderTxn::PlaceholderTxn()
: EditAggregateTxn()
{
mAbsorb=PR_TRUE;
}
PlaceholderTxn::~PlaceholderTxn()
{
}
NS_IMETHODIMP PlaceholderTxn::Do(void)
{
return NS_OK;
}
NS_IMETHODIMP PlaceholderTxn::Merge(PRBool *aDidMerge, nsITransaction *aTransaction)
{
// set out param default value
if (nsnull!=aDidMerge)
*aDidMerge=PR_FALSE;
nsresult result = NS_OK;
if ((nsnull!=aDidMerge) && (nsnull!=aTransaction))
{
EditTxn *editTxn = (EditTxn*)aTransaction; //XXX: hack, not safe! need nsIEditTransaction!
if (PR_TRUE==mAbsorb)
{ // yep, it's one of ours. Assimilate it.
AppendChild(editTxn);
*aDidMerge = PR_TRUE;
}
else
{ // let our last child txn make the choice
PRInt32 count = mChildren->Count();
if (0<count)
{
EditTxn *lastTxn = (EditTxn*)(mChildren->ElementAt(count-1));
if (lastTxn)
{
lastTxn->Merge(aDidMerge, aTransaction);
}
}
}
}
return result;
}

View File

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#ifndef AggregatePlaceholderTxn_h__
#define AggregatePlaceholderTxn_h__
#include "EditAggregateTxn.h"
#define PLACEHOLDER_TXN_IID \
{/* {0CE9FB00-D9D1-11d2-86DE-000064657374} */ \
0x0CE9FB00, 0xD9D1, 0x11d2, \
{0x86, 0xde, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74} }
/**
* An aggregate transaction that knows how to absorb all subsequent
* transactions with the same name. This transaction does not "Do" anything.
* But it absorbs other transactions via merge, and can undo/redo the
* transactions it has absorbed.
*/
class PlaceholderTxn : public EditAggregateTxn
{
public:
private:
PlaceholderTxn();
public:
virtual ~PlaceholderTxn();
NS_IMETHOD Do(void);
NS_IMETHOD Merge(PRBool *aDidMerge, nsITransaction *aTransaction);
NS_IMETHOD SetAbsorb(PRBool aAbsorb);
friend class TransactionFactory;
protected:
PRBool mAbsorb;
};
inline NS_IMETHODIMP PlaceholderTxn::SetAbsorb(PRBool aAbsorb)
{
mAbsorb = aAbsorb;
return NS_OK;
};
#endif

View File

@ -19,6 +19,7 @@
#include "TransactionFactory.h"
// transactions this factory knows how to build
#include "EditAggregateTxn.h"
#include "PlaceholderTxn.h"
#include "InsertTextTxn.h"
#include "DeleteTextTxn.h"
#include "CreateElementTxn.h"
@ -39,6 +40,7 @@
#include "JoinTableCellsTxn.h"
static NS_DEFINE_IID(kEditAggregateTxnIID, EDIT_AGGREGATE_TXN_IID);
static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID);
static NS_DEFINE_IID(kInsertTextTxnIID, INSERT_TEXT_TXN_IID);
static NS_DEFINE_IID(kDeleteTextTxnIID, DELETE_TEXT_TXN_IID);
static NS_DEFINE_IID(kCreateElementTxnIID, CREATE_ELEMENT_TXN_IID);
@ -91,6 +93,8 @@ TransactionFactory::GetNewTransaction(REFNSIID aTxnType, EditTxn **aResult)
*aResult = new JoinElementTxn();
else if (aTxnType.Equals(kEditAggregateTxnIID))
*aResult = new EditAggregateTxn();
else if (aTxnType.Equals(kPlaceholderTxnIID))
*aResult = new PlaceholderTxn();
else
result = NS_ERROR_NO_INTERFACE;

View File

@ -1184,10 +1184,18 @@ NS_IMETHODIMP nsEditor::DeleteSelectionAndCreateNode(const nsString& aTag, nsIDO
return result;
}
#ifdef NS_DEBUG
PRBool testCollapsed;
nsresult debugResult = selection->IsCollapsed(&testCollapsed);
NS_ASSERTION((NS_SUCCEEDED(result)), "couldn't get a selection after deletion");
NS_ASSERTION(PR_TRUE==testCollapsed, "selection not reset after deletion");;
nsCOMPtr<nsIDOMNode>testSelectedNode;
PRInt32 testOffset;
nsresult debugResult = selection->GetAnchorNodeAndOffset(getter_AddRefs(testSelectedNode), &testOffset);
// no selection is ok.
// if there is a selection, it must be collapsed
if (testSelectedNode)
{
PRBool testCollapsed;
debugResult = selection->IsCollapsed(&testCollapsed);
NS_ASSERTION((NS_SUCCEEDED(result)), "couldn't get a selection after deletion");
NS_ASSERTION(PR_TRUE==testCollapsed, "selection not reset after deletion");
}
#endif
}
// split the text node

View File

@ -18,6 +18,8 @@
#include "nsTextEditRules.h"
#include "nsTextEditor.h"
#include "PlaceholderTxn.h"
#include "InsertTextTxn.h"
#include "nsCOMPtr.h"
#include "nsIDOMNode.h"
#include "nsIDOMElement.h"
@ -31,6 +33,8 @@
const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
const static char* kMOZEditorBogusNodeValue="TRUE";
static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID);
static PRBool NodeIsType(nsIDOMNode *aNode, nsIAtom *aTag)
{
nsCOMPtr<nsIDOMElement>element;
@ -115,45 +119,12 @@ nsTextEditRules::WillInsert(nsIDOMSelection *aSelection, PRBool *aCancel)
*aCancel = PR_FALSE;
// check for the magic content node and delete it if it exists
nsCOMPtr<nsIDOMDocument>doc;
mEditor->GetDocument(getter_AddRefs(doc));
nsCOMPtr<nsIDOMNodeList>nodeList;
nsAutoString bodyTag = "body";
nsresult result = doc->GetElementsByTagName(bodyTag, getter_AddRefs(nodeList));
if ((NS_SUCCEEDED(result)) && nodeList)
if (mBogusNode)
{
PRUint32 count;
nodeList->GetLength(&count);
NS_ASSERTION(1==count, "there is not exactly 1 body in the document!");
nsCOMPtr<nsIDOMNode>bodyNode;
result = nodeList->Item(0, getter_AddRefs(bodyNode));
if ((NS_SUCCEEDED(result)) && bodyNode)
{ // now we've got the body tag.
// iterate the body tag, looking for editable content
// if the magic node is found, delete it
PRBool foundBogusContent=PR_TRUE;
nsCOMPtr<nsIDOMNode>bodyChild; // a child of the body, for iteration
nsCOMPtr<nsIDOMNode>bogusNode; // this will be the magic node
result = bodyNode->GetFirstChild(getter_AddRefs(bodyChild));
while ((NS_SUCCEEDED(result)) && bodyChild)
{
bogusNode = do_QueryInterface(bodyChild);
if (PR_TRUE==IsEditable(bodyChild))
{
foundBogusContent = PR_FALSE;
break;
}
nsCOMPtr<nsIDOMNode>temp;
bodyChild->GetNextSibling(getter_AddRefs(temp));
bodyChild = do_QueryInterface(temp);
}
if (PR_TRUE==foundBogusContent)
{
mEditor->DeleteNode(bogusNode);
// there is no longer any legit selection, so clear it.
aSelection->ClearSelection();
}
}
mEditor->DeleteNode(mBogusNode);
mBogusNode = do_QueryInterface(nsnull);
// there is no longer any legit selection, so clear it.
aSelection->ClearSelection();
}
return NS_OK;
@ -169,13 +140,22 @@ NS_IMETHODIMP
nsTextEditRules::WillInsertText(nsIDOMSelection *aSelection,
const nsString& aInputString,
PRBool *aCancel,
nsString& aOutputString)
nsString& aOutputString,
PlaceholderTxn **aTxn)
{
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
// initialize out param
*aCancel = PR_FALSE;
// by default, we insert what we're told to insert
aOutputString = aInputString;
if (mBogusNode)
{
nsresult result = TransactionFactory::GetNewTransaction(kPlaceholderTxnIID, (EditTxn **)aTxn);
if (NS_FAILED(result)) { return result; }
if (!*aTxn) { return NS_ERROR_NULL_POINTER; }
(*aTxn)->SetName(InsertTextTxn::gInsertTextTxnName);
mEditor->Do(*aTxn);
}
return WillInsert(aSelection, aCancel);
}
@ -315,31 +295,9 @@ nsTextEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, PRBool *aCance
*aCancel = PR_FALSE;
// if there is only bogus content, cancel the operation
nsCOMPtr<nsIDOMNode>node;
PRInt32 offset;
nsresult result = aSelection->GetAnchorNodeAndOffset(getter_AddRefs(node), &offset);
if ((NS_SUCCEEDED(result)) && node)
{
nsCOMPtr<nsIDOMNode>parent;
parent = do_QueryInterface(node);
while (node)
{ //if we find the bogus node, cancel the operation
nsCOMPtr<nsIDOMElement>element;
element = do_QueryInterface(parent);
if (element)
{
nsAutoString att(kMOZEditorBogusNodeAttr);
nsAutoString val;
nsresult result = element->GetAttribute(att, val);
if (val.Equals(kMOZEditorBogusNodeValue)) {
*aCancel = PR_TRUE;
return NS_OK;
}
}
// walk up the content hierarchy
parent->GetParentNode(getter_AddRefs(node));
parent = do_QueryInterface(node);
}
if (mBogusNode) {
*aCancel = PR_TRUE;
return NS_OK;
}
return NS_OK;
}
@ -389,13 +347,13 @@ nsTextEditRules::DidDeleteSelection(nsIDOMSelection *aSelection, nsresult aResul
}
if (PR_TRUE==needsBogusContent)
{
nsCOMPtr<nsIDOMNode>newPNode;
// set mBogusNode to be the newly created <P>
result = mEditor->CreateNode(nsAutoString("P"), bodyNode, 0,
getter_AddRefs(newPNode));
if ((NS_SUCCEEDED(result)) && newPNode)
getter_AddRefs(mBogusNode));
if ((NS_SUCCEEDED(result)) && mBogusNode)
{
nsCOMPtr<nsIDOMNode>newTNode;
result = mEditor->CreateNode(nsIEditor::GetTextNodeTag(), newPNode, 0,
result = mEditor->CreateNode(nsIEditor::GetTextNodeTag(), mBogusNode, 0,
getter_AddRefs(newTNode));
if ((NS_SUCCEEDED(result)) && newTNode)
{
@ -411,7 +369,7 @@ nsTextEditRules::DidDeleteSelection(nsIDOMSelection *aSelection, nsresult aResul
}
// make sure we know the PNode is bogus
nsCOMPtr<nsIDOMElement>newPElement;
newPElement = do_QueryInterface(newPNode);
newPElement = do_QueryInterface(mBogusNode);
if (newPElement)
{
nsAutoString att(kMOZEditorBogusNodeAttr);

View File

@ -21,9 +21,10 @@
#include "nsIEditor.h"
#include "nsCOMPtr.h"
#include "nsIDOMNode.h"
class nsTextEditor;
class PlaceholderTxn;
/** Object that encapsulates HTML text-specific editing rules.
*
@ -52,7 +53,8 @@ public:
NS_IMETHOD WillInsertText(nsIDOMSelection *aSelection,
const nsString& aInputString,
PRBool *aCancel,
nsString& aOutputString);
nsString& aOutputString,
PlaceholderTxn ** aTxn);
NS_IMETHOD DidInsertText(nsIDOMSelection *aSelection, const nsString& aStringToInsert, nsresult aResult);
NS_IMETHOD WillInsert(nsIDOMSelection *aSelection, PRBool *aCancel);
@ -64,7 +66,7 @@ public:
protected:
nsTextEditor *mEditor; // note that we do not refcount the editor
nsCOMPtr<nsIDOMNode> mBogusNode; // magic node acts as placeholder in empty doc
};
#endif //nsTextEditRules_h__