bug 65557 and cast of thousands: making the editor behave more intelligently wrt html whitespace rules. Improved ws handling for:

This commit is contained in:
jfrancis%netscape.com 2001-05-11 12:43:22 +00:00
parent d1a70597de
commit 56ac4a4d19
19 changed files with 1415 additions and 634 deletions

View File

@ -91,6 +91,7 @@ CPPSRCS += nsAOLCiter.cpp \
nsInternetCiter.cpp \
nsTableEditor.cpp \
nsWrapUtils.cpp \
nsWSRunObject.cpp \
TextEditorTest.cpp \
TypeInState.cpp \
SetDocTitleTxn.cpp \

View File

@ -126,6 +126,7 @@ CPPSRCS = $(CPPSRCS) \
nsInternetCiter.cpp \
nsTableEditor.cpp \
nsWrapUtils.cpp \
nsWSRunObject.cpp \
TextEditorTest.cpp \
TypeInState.cpp \
SetDocTitleTxn.cpp \
@ -146,6 +147,7 @@ CPP_OBJS = $(CPP_OBJS) \
.\$(OBJDIR)\nsInternetCiter.obj \
.\$(OBJDIR)\nsTableEditor.obj \
.\$(OBJDIR)\nsWrapUtils.obj \
.\$(OBJDIR)\nsWSRunObject.obj \
.\$(OBJDIR)\TextEditorTest.obj \
.\$(OBJDIR)\TypeInState.obj \
.\$(OBJDIR)\SetDocTitleTxn.obj \

View File

@ -244,6 +244,8 @@ protected:
PRUint32 aOffset,
PRUint32 aLength);
// NS_IMETHOD DeleteRange(nsIDOMRange *aRange);
NS_IMETHOD CreateTxnForDeleteText(nsIDOMCharacterData *aElement,
PRUint32 aOffset,
PRUint32 aLength,

View File

@ -56,6 +56,7 @@
#endif // IBMBIDI
#include "nsEditorUtils.h"
#include "nsWSRunObject.h"
#include "InsertTextTxn.h"
#include "DeleteTextTxn.h"
@ -380,20 +381,11 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection
if (NS_FAILED(res)) return res;
}
// adjust whitespace for insert text and delete actions
if ((action == nsEditor::kOpInsertText) ||
(action == nsEditor::kOpInsertIMEText) ||
(action == nsEditor::kOpDeleteSelection))
{
res = AdjustWhitespace(selection);
if (NS_FAILED(res)) return res;
}
// replace newlines that are preformatted
// replace newlines with breaks.
// MOOSE: This is buttUgly. A better way to
// organize the action enum is in order.
if ((action == nsEditor::kOpInsertText) ||
(action == nsEditor::kOpInsertIMEText) ||
if (// (action == nsEditor::kOpInsertText) ||
// (action == nsEditor::kOpInsertIMEText) ||
(action == nsHTMLEditor::kOpInsertElement) ||
(action == nsHTMLEditor::kOpInsertQuotation) ||
(action == nsEditor::kOpInsertNode))
@ -404,6 +396,13 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection
// clean up any empty nodes in the selection
res = RemoveEmptyNodes();
if (NS_FAILED(res)) return res;
// attempt to transform any uneeded nbsp's into spaces after doing deletions
if (action == nsEditor::kOpDeleteSelection)
{
res = AdjustWhitespace(selection);
if (NS_FAILED(res)) return res;
}
// adjust selection for insert text, html paste, and delete actions
if ((action == nsEditor::kOpInsertText) ||
@ -941,6 +940,8 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
nsCOMPtr<nsIDOMNode> selNode;
PRInt32 selOffset;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
// if the selection isn't collapsed, delete it.
PRBool bCollapsed;
res = aSelection->GetIsCollapsed(&bCollapsed);
@ -1006,7 +1007,7 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
// for efficiency, break out the pre case seperately. This is because
// its a lot cheaper to search the input string for only newlines than
// it is to search for both tabs and newlines.
if (isPRE)
if (isPRE || bPlaintext)
{
char newlineChar = '\n';
while (unicodeBuf && (pos != -1) && (pos < (PRInt32)(*inString).Length()))
@ -1068,21 +1069,29 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
subStr.Subsume((PRUnichar*)&unicodeBuf[oldPos], PR_FALSE, subStrLen);
nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset);
// is it a tab?
if (subStr.EqualsWithConversion("\t"))
{
res = mHTMLEditor->InsertTextImpl(tabString, address_of(curNode), &curOffset, doc);
// res = mHTMLEditor->InsertTextImpl(tabString, address_of(curNode), &curOffset, doc);
res = wsObj.InsertText(tabString, address_of(curNode), &curOffset, doc);
if (NS_FAILED(res)) return res;
pos++;
}
// is it a return?
else if (subStr.EqualsWithConversion("\n"))
{
res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
// res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
if (NS_FAILED(res)) return res;
pos++;
}
else
{
res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
// res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
if (NS_FAILED(res)) return res;
}
if (NS_FAILED(res)) return res;
}
@ -1121,6 +1130,7 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
*aCancel = PR_FALSE;
*aHandled = PR_FALSE;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
// if the selection isn't collapsed, delete it.
PRBool bCollapsed;
@ -1147,7 +1157,6 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
PRInt32 selOffset, newOffset;
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
res = GetTopEnclosingMailCite(selNode, address_of(citeNode), bPlaintext);
if (NS_FAILED(res)) return res;
if (citeNode)
@ -1212,7 +1221,15 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
else
{
nsCOMPtr<nsIDOMNode> brNode;
res = mHTMLEditor->CreateBR(node, offset, address_of(brNode));
if (bPlaintext)
{
res = mHTMLEditor->CreateBR(node, offset, address_of(brNode));
}
else
{
nsWSRunObject wsObj(mHTMLEditor, node, offset);
res = wsObj.InsertBreak(address_of(node), &offset, address_of(brNode), nsIEditor::eNone);
}
if (NS_FAILED(res)) return res;
res = nsEditor::GetNodeLocation(brNode, address_of(node), &offset);
if (NS_FAILED(res)) return res;
@ -1266,6 +1283,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
nsresult res = NS_OK;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
PRBool bCollapsed;
res = aSelection->GetIsCollapsed(&bCollapsed);
@ -1289,9 +1307,81 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (NS_FAILED(res)) return res;
if (!startNode) return NS_ERROR_FAILURE;
// get the root element
nsCOMPtr<nsIDOMElement> bodyElement;
nsCOMPtr<nsIDOMNode> bodyNode;
res = mHTMLEditor->GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_UNEXPECTED;
bodyNode = do_QueryInterface(bodyElement);
if (bCollapsed)
{
// easy case, in a text node:
// if we are inside an empty block, delete it.
// Note: do NOT delete table elements this way.
nsCOMPtr<nsIDOMNode> block;
if (IsBlockNode(startNode))
block = startNode;
else
block = mHTMLEditor->GetBlockNodeParent(startNode);
PRBool bIsEmptyNode;
if (block != bodyNode)
{
res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
if (NS_FAILED(res)) return res;
if (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(startNode))
{
// adjust selection to be right after it
nsCOMPtr<nsIDOMNode> blockParent;
PRInt32 offset;
res = nsEditor::GetNodeLocation(block, address_of(blockParent), &offset);
if (NS_FAILED(res)) return res;
if (!blockParent || offset < 0) return NS_ERROR_FAILURE;
res = aSelection->Collapse(blockParent, offset+1);
if (NS_FAILED(res)) return res;
res = mHTMLEditor->DeleteNode(block);
*aHandled = PR_TRUE;
return res;
}
}
if (!bPlaintext)
{
// gather up ws data here. We may be next to non-significant ws.
nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset);
nsCOMPtr<nsIDOMNode> visNode;
PRInt32 visOffset;
PRInt16 wsType;
if (aAction == nsIEditor::ePrevious)
{
res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
// note that visOffset is _after_ what we are about to delete.
}
else if (aAction == nsIEditor::eNext)
{
res = wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
// note that visOffset is _before_ what we are about to delete.
}
if (NS_SUCCEEDED(res))
{
if (wsType==nsWSRunObject::eNormalWS)
{
// we found some visible ws to delete. Let ws code handle it.
if (aAction == nsIEditor::ePrevious)
res = wsObj.DeleteWSBackward();
else if (aAction == nsIEditor::eNext)
res = wsObj.DeleteWSForward();
*aHandled = PR_TRUE;
return res;
}
else if (visNode)
{
// reposition startNode and startOffset so that we skip over any non-significant ws
startNode = visNode;
startOffset = visOffset;
}
}
}
// in a text node:
if (mHTMLEditor->IsTextNode(startNode))
{
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
@ -1299,83 +1389,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
res = textNode->GetLength(&strLength);
if (NS_FAILED(res)) return res;
#ifdef IBMBIDI // Test for distance between caret and text that will be deleted
nsCOMPtr<nsIPresShell> shell;
mEditor->GetPresShell(getter_AddRefs(shell));
if (shell)
{
nsCOMPtr<nsIPresContext> context;
shell->GetPresContext(getter_AddRefs(context));
if (context)
{
PRBool bidiEnabled;
context->BidiEnabled(bidiEnabled);
if (bidiEnabled)
{
nsCOMPtr<nsIFrameSelection> frameSelection;
shell->GetFrameSelection(getter_AddRefs(frameSelection));
if (frameSelection)
{
nsCOMPtr<nsIContent> content = do_QueryInterface(startNode);
if (content)
{
nsIFrame *primaryFrame;
nsIFrame *frameBefore;
nsIFrame *frameAfter;
PRInt32 frameOffset;
shell->GetPrimaryFrameFor(content, &primaryFrame);
if (primaryFrame)
{
res = primaryFrame->GetChildFrameContainingOffset(startOffset, PR_FALSE, &frameOffset, &frameBefore);
if (NS_SUCCEEDED(res) && frameBefore)
{
PRInt32 start, end;
frameBefore->GetOffsets(start, end);
if (startOffset == end)
{
res = primaryFrame->GetChildFrameContainingOffset(startOffset, PR_TRUE, &frameOffset, &frameAfter);
if (NS_SUCCEEDED(res) && frameAfter)
{
PRUint8 currentCursorLevel;
long levelBefore;
long levelAfter;
long paragraphLevel;
long levelOfDeletion;
nsCOMPtr<nsIAtom> embeddingLevel = NS_NewAtom("EmbeddingLevel");
nsCOMPtr<nsIAtom> baseLevel = NS_NewAtom("BaseLevel");
frameBefore->GetBidiProperty(context, embeddingLevel, (void**)&levelBefore);
if (frameBefore==frameAfter)
{
frameBefore->GetBidiProperty(context, baseLevel, (void**)&paragraphLevel);
levelAfter = paragraphLevel;
}
else
frameAfter->GetBidiProperty(context, embeddingLevel, (void**)&levelAfter);
shell->GetCursorBidiLevel(&currentCursorLevel);
if (levelBefore==levelAfter && (levelAfter & 1) == (currentCursorLevel & 1))
shell->SetCursorBidiLevel(levelBefore);
else
{
levelOfDeletion = (nsIEditor::eNext==aAction) ? levelAfter : levelBefore;
shell->SetCursorBidiLevel(levelOfDeletion);
if ((currentCursorLevel/* & 1*/) != (levelOfDeletion/* & 1*/))
{
*aCancel = PR_TRUE;
return NS_OK;
}
}
}
}
}
}
}
}
}
}
}
#endif // IBMBIDI
// at beginning of text node and backspaced?
if (!startOffset && (aAction == nsIEditor::ePrevious))
{
@ -1384,7 +1397,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (NS_FAILED(res)) return res;
// if there is no prior node then cancel the deletion
if (!priorNode)
if (!priorNode || !nsTextEditUtils::InBody(priorNode, mHTMLEditor))
{
*aCancel = PR_TRUE;
return res;
@ -1401,7 +1414,17 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(priorNode);
nodeAsText->GetLength((PRUint32*)&offset);
NS_ENSURE_TRUE(offset, NS_ERROR_FAILURE);
res = aSelection->Collapse(priorNode,offset);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = offset-1;
PRInt32 eo = offset;
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(priorNode), &so,
address_of(priorNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
@ -1409,6 +1432,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// is prior node not a container? (ie, a br, hr, image...)
else if (!mHTMLEditor->IsContainer(priorNode)) // MOOSE: anchors not handled
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, priorNode);
if (NS_FAILED(res)) return res;
}
// delete the break, and join like nodes if appropriate
res = mHTMLEditor->DeleteNode(priorNode);
if (NS_FAILED(res)) return res;
@ -1424,8 +1452,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (mHTMLEditor->IsTextNode(priorNode))
{
// if so, join them!
nsCOMPtr<nsIDOMNode> topParent;
priorNode->GetParentNode(getter_AddRefs(topParent));
res = JoinNodesSmart(priorNode,startNode,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
// fix up selection
@ -1436,6 +1462,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
else if ( IsInlineNode(priorNode) )
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, priorNode);
if (NS_FAILED(res)) return res;
}
// remember where we are
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
@ -1454,8 +1485,16 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
// deleting across blocks
nsCOMPtr<nsIDOMNode> leftParent = mHTMLEditor->GetBlockNodeParent(priorNode);
nsCOMPtr<nsIDOMNode> rightParent = mHTMLEditor->GetBlockNodeParent(startNode);
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
if (IsBlockNode(priorNode))
leftParent = priorNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(priorNode);
if (IsBlockNode(startNode))
rightParent = startNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(startNode);
// if leftParent or rightParent is null, it's because the
// corresponding selection endpoint is in the body node.
@ -1472,9 +1511,13 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
if (mHTMLEditor->NodesSameType(leftParent, rightParent))
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (!bPlaintext)
{
// adjust whitespace at block boundaries
res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor,leftParent,rightParent);
if (NS_FAILED(res)) return res;
}
// join the nodes
*aHandled = PR_TRUE;
res = JoinNodesSmart(leftParent,rightParent,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
@ -1513,6 +1556,15 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(nextNode);
res = aSelection->Collapse(nextNode,0);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = 0;
PRInt32 eo = 1;
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(nextNode), &so,
address_of(nextNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
@ -1520,6 +1572,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// is next node not a container? (ie, a br, hr, image...)
else if (!mHTMLEditor->IsContainer(nextNode)) // MOOSE: anchors not handled
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nextNode);
if (NS_FAILED(res)) return res;
}
// delete the break, and join like nodes if appropriate
res = mHTMLEditor->DeleteNode(nextNode);
if (NS_FAILED(res)) return res;
@ -1535,8 +1592,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if ( mHTMLEditor->IsTextNode(nextNode) )
{
// if so, join them!
nsCOMPtr<nsIDOMNode> topParent;
nextNode->GetParentNode(getter_AddRefs(topParent));
res = JoinNodesSmart(startNode,nextNode,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
// fix up selection
@ -1547,9 +1602,14 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
else if ( IsInlineNode(nextNode) )
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nextNode);
if (NS_FAILED(res)) return res;
}
// remember where we are
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
// remember where we are
res = mHTMLEditor->GetNodeLocation(nextNode, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// delete it
@ -1565,9 +1625,17 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
// deleting across blocks
nsCOMPtr<nsIDOMNode> leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
nsCOMPtr<nsIDOMNode> rightParent = mHTMLEditor->GetBlockNodeParent(nextNode);
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
if (IsBlockNode(startNode))
leftParent = startNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
if (IsBlockNode(nextNode))
rightParent = nextNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(nextNode);
// if leftParent or rightParent is null, it's because the
// corresponding selection endpoint is in the body node.
if (!leftParent || !rightParent)
@ -1583,9 +1651,13 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
if (mHTMLEditor->NodesSameType(leftParent, rightParent))
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (!bPlaintext)
{
// adjust whitespace at block boundaries
res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor,leftParent,rightParent);
if (NS_FAILED(res)) return res;
}
// join the nodes
*aHandled = PR_TRUE;
res = JoinNodesSmart(leftParent,rightParent,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
@ -1596,8 +1668,28 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// else blocks not same type, bail to default
return NS_OK;
}
// else in middle of text node. default will do right thing.
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(startNode);
if (!nodeAsText) return NS_ERROR_NULL_POINTER;
res = aSelection->Collapse(startNode,startOffset);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = startOffset;
PRInt32 eo = startOffset;
if (aAction == nsIEditor::ePrevious)
so--; // we know so not zero - that case handled above
else
eo++; // we know eo not at end of text node - that case handled above
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(startNode), &so,
address_of(startNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
}
// else not in text node; we need to find right place to act on
else
@ -1658,7 +1750,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
*aCancel = PR_TRUE;
return res;
}
// if this node is text node, adjust selection
if (nsEditor::IsTextNode(nodeToDelete))
{
@ -1674,10 +1766,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
{
// editable leaf node is not text; delete it.
// that's the default behavior
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
res = nsEditor::GetNodeLocation(nodeToDelete, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// EXCEPTION: if it's a mozBR, we have to check and see if
// there is a br in front of it. If so, we must delete both.
// else you get this: deletion code deletes mozBR, then selection
@ -1695,16 +1783,28 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (block == brBlock)
{
// delete both breaks
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, brNode);
if (NS_FAILED(res)) return res;
}
res = mHTMLEditor->DeleteNode(brNode);
if (NS_FAILED(res)) return res;
res = mHTMLEditor->DeleteNode(nodeToDelete);
*aHandled = PR_TRUE;
return res;
// fall through to delete other br
}
// else fall through
}
// else fall through
}
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nodeToDelete);
if (NS_FAILED(res)) return res;
}
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
res = nsEditor::GetNodeLocation(nodeToDelete, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// adjust selection to be right after it
res = aSelection->Collapse(node, offset+1);
if (NS_FAILED(res)) return res;
@ -1722,9 +1822,15 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMNode> endNode;
PRInt32 endOffset;
res = mHTMLEditor->GetEndNodeAndOffset(aSelection, address_of(endNode), &endOffset);
if (NS_FAILED(res))
{
return res;
if (NS_FAILED(res)) return res;
// adjust surrounding whitespace in preperation to delete selection
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(startNode), &startOffset,
address_of(endNode), &endOffset);
if (NS_FAILED(res)) return res;
}
if (endNode.get() != startNode.get())
{
@ -1735,20 +1841,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
// XXX: Fix for bug #10815: Crash deleting selected text and table.
// Make sure leftParent and rightParent are never NULL. This
// can happen if we call GetBlockNodeParent() and the node we
// pass in is a body node.
//
// Should we be calling IsBlockNode() instead of IsBody() here?
if (nsTextEditUtils::IsBody(startNode))
if (IsBlockNode(startNode))
leftParent = startNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
if (nsTextEditUtils::IsBody(endNode))
if (IsBlockNode(endNode))
rightParent = endNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(endNode);
@ -1763,9 +1860,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if ( (leftBlockParent.get() == rightBlockParent.get())
&& (mHTMLEditor->NodesSameType(leftParent, rightParent)) )
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (nsHTMLEditUtils::IsParagraph(leftParent))
{
// first delete the selection
@ -2780,25 +2874,20 @@ nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocume
res = mEditor->DeleteNode(rightNode);
if (NS_FAILED(res)) return res;
}
// register a rangeStore item that points at the new heirarchy.
// This is so we can know where to put the selection after we call
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
// so I have to use the range tracking system to find the right spot to put selection.
nsRangeStore *rangeItem = new nsRangeStore();
if (!rangeItem) return NS_ERROR_NULL_POINTER;
rangeItem->startNode = newSelParent;
rangeItem->endNode = newSelParent;
rangeItem->startOffset = 0;
rangeItem->endOffset = 0;
mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItem);
// remove the style on this new heirarchy
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
if (NS_FAILED(res)) return res;
PRInt32 newSelOffset = 0;
{
// track the point at the new heirarchy.
// This is so we can know where to put the selection after we call
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
// so I have to use the range tracking system to find the right spot to put selection.
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(newSelParent), &newSelOffset);
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
if (NS_FAILED(res)) return res;
}
// reset our node offset values to the resulting new sel point
mHTMLEditor->mRangeUpdater.DropRangeItem(rangeItem);
node = rangeItem->startNode;
offset = rangeItem->startOffset;
delete rangeItem;
node = newSelParent;
offset = newSelOffset;
}
// we own item now (TakeClearProperty hands ownership to us)
delete item;
@ -4210,9 +4299,14 @@ nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection,
nsresult res = nsEditor::GetNodeLocation(aHeader, address_of(headerParent), &offset);
if (NS_FAILED(res)) return res;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the header
PRInt32 newOffset;
res = mHTMLEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// if the leftand heading is empty, put a mozbr in it
@ -4306,8 +4400,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
{
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(sibling);
@ -4344,8 +4442,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
{
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep(aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep(aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(sibling);
@ -4383,8 +4485,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
// else remove sibling br and split para
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(nearNode);
@ -4468,9 +4574,14 @@ nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection,
return res;
}
// else we want a new list item at the same list level
// else we want a new list item at the same list level.
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// now split list item
PRInt32 newOffset;
res = mHTMLEditor->SplitNodeDeep( aListItem, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// hack: until I can change the damaged doc range code back to being
// extra inclusive, I have to manually detect certain list items that
@ -4944,64 +5055,19 @@ nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames)
return res;
}
nsresult
nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
{
nsCOMPtr<nsISupportsArray> arrayOfNodes;
nsCOMPtr<nsISupports> isupports;
PRUint32 nodeCount,j;
nsresult res;
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
// get selection point
nsCOMPtr<nsIDOMNode> selNode;
PRInt32 selOffset;
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
// special case for mDocChangeRange entirely in one text node.
// This is an efficiency hack for normal typing in the editor.
nsCOMPtr<nsIDOMNode> startNode, endNode;
PRInt32 startOffset, endOffset;
res = mDocChangeRange->GetStartContainer(getter_AddRefs(startNode));
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetStartOffset(&startOffset);
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetEndContainer(getter_AddRefs(endNode));
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetEndOffset(&endOffset);
if (NS_FAILED(res)) return res;
if (startNode == endNode)
{
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
if (nodeAsText)
{
res = DoTextNodeWhitespace(nodeAsText, startOffset, endOffset);
return res;
}
}
// gather up a list of text nodes
nsEditableTextFunctor functor(mHTMLEditor);
nsDOMIterator iter;
res = iter.Init(mDocChangeRange);
if (NS_FAILED(res)) return res;
res = iter.MakeList(functor, address_of(arrayOfNodes));
if (NS_FAILED(res)) return res;
// now adjust whitespace on node we found
res = arrayOfNodes->Count(&nodeCount);
if (NS_FAILED(res)) return res;
for (j = 0; j < nodeCount; j++)
{
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
nsCOMPtr<nsIDOMCharacterData> textNode( do_QueryInterface(isupports ) );
arrayOfNodes->RemoveElementAt(0);
res = DoTextNodeWhitespace(textNode, -1, -1);
if (NS_FAILED(res)) return res;
}
return res;
// ask whitespace object to tweak nbsp's
return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
}
nsresult
nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction)
{
@ -5103,6 +5169,12 @@ nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection
res = aSelection->Collapse(selNode,selOffset);
if (NS_FAILED(res)) return res;
}
else if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
{
// selection between br and mozbr. make it stick to mozbr
// so that it will be on blank line.
selPriv->SetInterlinePosition(PR_TRUE);
}
}
}
}
@ -5892,6 +5964,20 @@ nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode,
return res;
}
NS_IMETHODIMP
nsHTMLEditRules::WillDeleteRange(nsIDOMRange *aRange)
{
if (!mListenerEnabled) return NS_OK;
// get the (collapsed) selection location
return UpdateDocChangeRange(aRange);
}
NS_IMETHODIMP
nsHTMLEditRules::DidDeleteRange(nsIDOMRange *aRange)
{
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection)
{

View File

@ -77,6 +77,8 @@ public:
NS_IMETHOD DidInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAReadableString &aString, nsresult aResult);
NS_IMETHOD WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength);
NS_IMETHOD DidDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength, nsresult aResult);
NS_IMETHOD WillDeleteRange(nsIDOMRange *aRange);
NS_IMETHOD DidDeleteRange(nsIDOMRange *aRange);
NS_IMETHOD WillDeleteSelection(nsISelection *aSelection);
NS_IMETHOD DidDeleteSelection(nsISelection *aSelection);
@ -179,13 +181,13 @@ protected:
// data members
protected:
nsHTMLEditor *mHTMLEditor;
nsCOMPtr<nsIDOMRange> mDocChangeRange;
PRBool mListenerEnabled;
PRBool mReturnInEmptyLIKillsList;
nsCOMPtr<nsIDOMRange> mUtilRange;
PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin...
nsHTMLEditor *mHTMLEditor;
nsCOMPtr<nsIDOMRange> mDocChangeRange;
PRBool mListenerEnabled;
PRBool mReturnInEmptyLIKillsList;
nsCOMPtr<nsIDOMRange> mUtilRange;
PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin...
};
nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult);

View File

@ -64,6 +64,7 @@
#include "nsIContentIterator.h"
#include "nsEditorCID.h"
#include "nsLayoutCID.h"
#include "nsContentCID.h"
#include "nsIDOMRange.h"
#include "nsIDOMNSRange.h"
#include "nsISupportsArray.h"
@ -114,6 +115,7 @@ static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID);
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
static NS_DEFINE_IID(kRangeUtilsCID, NS_RANGEUTILS_CID);
static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID);
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
@ -232,6 +234,10 @@ NS_IMETHODIMP nsHTMLEditor::Init(nsIDOMDocument *aDoc,
nsresult result = NS_OK, rulesRes = NS_OK;
// make a range util object for comparing dom points
mRangeHelper = do_CreateInstance(kRangeUtilsCID);
if (!mRangeHelper) return NS_ERROR_NULL_POINTER;
// Init mEditProperty
result = NS_NewEditProperty(getter_AddRefs(mEditProperty));
if (NS_FAILED(result)) { return result; }
@ -1267,7 +1273,10 @@ NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled)
return res;
}
NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent,
PRInt32 *aInOutOffset,
nsCOMPtr<nsIDOMNode> *outBRNode,
EDirection aSelect)
{
if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
*outBRNode = nsnull;

View File

@ -38,6 +38,7 @@
#include "nsICSSLoader.h"
#include "nsICSSLoaderObserver.h"
#include "nsITableLayout.h"
#include "nsIRangeUtils.h"
#include "nsEditRules.h"
@ -679,11 +680,14 @@ protected:
// Used by GetFirstSelectedCell and GetNextSelectedCell
PRInt32 mSelectedCellIndex;
nsCOMPtr<nsIRangeUtils> mRangeHelper;
public:
// friends
friend class nsHTMLEditRules;
friend class nsTextEditRules;
friend class nsWSRunObject;
};

View File

@ -118,6 +118,41 @@ class nsRangeUpdater
};
/***************************************************************************
* helper class for using nsSelectionState. stack based class for doing
* preservation of dom points across editor actions
*/
class nsAutoTrackDOMPoint
{
private:
nsRangeUpdater &mRU;
nsCOMPtr<nsIDOMNode> *mNode;
PRInt32 *mOffset;
nsRangeStore mRangeItem;
public:
nsAutoTrackDOMPoint(nsRangeUpdater &aRangeUpdater, nsCOMPtr<nsIDOMNode> *aNode, PRInt32 *aOffset) :
mRU(aRangeUpdater)
,mNode(aNode)
,mOffset(aOffset)
{
mRangeItem.startNode = *mNode;
mRangeItem.endNode = *mNode;
mRangeItem.startOffset = *mOffset;
mRangeItem.endOffset = *mOffset;
mRU.RegisterRangeItem(&mRangeItem);
}
~nsAutoTrackDOMPoint()
{
mRU.DropRangeItem(&mRangeItem);
*mNode = mRangeItem.startNode;
*mOffset = mRangeItem.startOffset;
}
};
/***************************************************************************
* another helper class for nsSelectionState. stack based class for doing
* Will/DidReplaceContainer()

View File

@ -119,7 +119,6 @@ nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd,
if (!aLeftParent || !aRightParent || !aHTMLEd)
return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
PRUint32 count;
aHTMLEd->GetLengthOfDOMNode(aLeftParent, count);
nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count);
@ -168,9 +167,19 @@ nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor *aHTMLEd,
}
nsresult
nsWSRunObject::PrepareToSplitAcrossBlocks(nsCOMPtr<nsIDOMNode> *aSplitNode, PRInt32 *aSplitOffset)
nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aSplitNode,
PRInt32 *aSplitOffset)
{
return NS_OK;
if (!aSplitNode || !aSplitOffset || !*aSplitNode || !aHTMLEd)
return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset);
nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset);
return wsObj.PrepareToSplitAcrossBlocksPriv();
}
//--------------------------------------------------------------------------------------------
@ -193,29 +202,6 @@ nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
// handle any changes needed to ws run before inserted br
if (!beforeRun)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after break inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run after inserted br
if (!afterRun)
{
@ -252,6 +238,29 @@ nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
}
}
// handle any changes needed to ws run before inserted br
if (!beforeRun)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after break inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// ready, aim, fire!
return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect);
}
@ -282,29 +291,6 @@ nsWSRunObject::InsertText(const nsAReadableString& aStringToInsert,
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
// handle any changes needed to ws run before inserted text
if (!beforeRun)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after text inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run after inserted text
if (!afterRun)
{
@ -328,6 +314,29 @@ nsWSRunObject::InsertText(const nsAReadableString& aStringToInsert,
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run before inserted text
if (!beforeRun)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after text inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// next up, tweak head and tail of string as needed.
// first the head:
// there are a variety of circumstances that would require us to convert a
@ -446,7 +455,7 @@ nsWSRunObject::DeleteWSBackward()
NS_ENSURE_SUCCESS(res, res);
// finally, delete that ws
return DeleteChars(startNode, startOffset, mNode, mOffset);
return DeleteChars(startNode, startOffset, endNode, endOffset);
}
else if (point.mChar == nbsp)
{
@ -489,7 +498,7 @@ nsWSRunObject::DeleteWSForward()
NS_ENSURE_SUCCESS(res, res);
// finally, delete that ws
return DeleteChars(startNode, startOffset, mNode, mOffset);
return DeleteChars(startNode, startOffset, endNode, endOffset);
}
else if (point.mChar == nbsp)
{
@ -515,6 +524,8 @@ nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode,
PRInt32 *outVisOffset,
PRInt16 *outType)
{
// Find first visible thing before the point. position outVisNode/outVisOffset
// just _after_ that thing. If we don't find anything return start of ws.
if (!aNode || !outVisNode || !outVisOffset || !outType)
return NS_ERROR_NULL_POINTER;
@ -536,7 +547,7 @@ nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode,
if (point.mTextNode)
{
*outVisNode = do_QueryInterface(point.mTextNode);
*outVisOffset = point.mOffset;
*outVisOffset = point.mOffset+1;
if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
{
*outType = eNormalWS;
@ -571,6 +582,8 @@ nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode,
PRInt32 *outVisOffset,
PRInt16 *outType)
{
// Find first visible thing before the point. position outVisNode/outVisOffset
// just _before_ that thing. If we don't find anything return end of ws.
if (!aNode || !outVisNode || !outVisOffset || !outType)
return NS_ERROR_NULL_POINTER;
@ -619,6 +632,27 @@ nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode,
return NS_OK;
}
nsresult
nsWSRunObject::AdjustWhitespace()
{
// this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
// replacing them with regualr ascii space if possible. Keeping things simple
// for now and just trying to fix up the trailing ws in the run.
if (!mLastNBSPNode) return NS_OK; // nothing to do!
nsresult res = NS_OK;
WSFragment *curRun = mStartRun;
while (curRun)
{
// look for normal ws run
if (curRun->mType == eNormalWS)
{
res = CheckTrailingNBSPOfRun(curRun);
break;
}
curRun = curRun->mRight;
}
return res;
}
//--------------------------------------------------------------------------------------------
@ -1061,6 +1095,9 @@ nsWSRunObject::GetRuns()
else
{
// we might have trailing ws.
// it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
// will point to it, even though in general start/end points not
// guaranteed to be in text nodes.
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
{
// normal ws runs right up to adjacent block (nbsp next to block)
@ -1099,6 +1136,9 @@ nsWSRunObject::GetRuns()
mStartRun->mLeftType = mStartReason;
// we might have trailing ws.
// it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
// will point to it, even though in general start/end points not
// guaranteed to be in text nodes.
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
{
// set up next run
@ -1388,6 +1428,7 @@ nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
NS_ENSURE_SUCCESS(res, res);
res = aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, PR_TRUE);
NS_ENSURE_SUCCESS(res, res);
// trim after run of any leading ws
if (afterRun && (afterRun->mType == eLeadingWS))
@ -1444,6 +1485,56 @@ nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
return res;
}
nsresult
nsWSRunObject::PrepareToSplitAcrossBlocksPriv()
{
// used to prepare ws to be split across two blocks. The main issue
// here is make sure normalWS doesn't end up becoming non-significant
// leading or trailing ws after the split.
nsresult res = NS_OK;
// get the runs before and after selection
WSFragment *beforeRun, *afterRun;
res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
NS_ENSURE_SUCCESS(res, res);
res = FindRun(mNode, mOffset, &afterRun, PR_TRUE);
// adjust normal ws in afterRun if needed
if (afterRun && (afterRun->mType == eNormalWS))
{
// make sure leading char of following ws is an nbsp, so that it will show up
WSPoint point;
GetCharAfter(mNode, mOffset, &point);
if (nsCRT::IsAsciiSpace(point.mChar))
{
res = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(res, res);
}
}
// adjust normal ws in beforeRun if needed
if (beforeRun && (beforeRun->mType == eNormalWS))
{
// make sure trailing char of starting ws is an nbsp, so that it will show up
WSPoint point;
GetCharBefore(mNode, mOffset, &point);
if (nsCRT::IsAsciiSpace(point.mChar))
{
nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
PRInt32 wsStartOffset, wsEndOffset;
res = GetAsciiWSBounds(eBoth, mNode, mOffset,
address_of(wsStartNode), &wsStartOffset,
address_of(wsEndNode), &wsEndOffset);
NS_ENSURE_SUCCESS(res, res);
point.mTextNode = do_QueryInterface(wsStartNode);
point.mOffset = wsStartOffset;
res = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(res, res);
}
}
return res;
}
nsresult
nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
nsIDOMNode *aEndNode, PRInt32 aEndOffset)
@ -1704,14 +1795,13 @@ nsWSRunObject::GetAsciiWSBounds(PRInt16 aDir, nsIDOMNode *aNode, PRInt32 aOffset
nsCOMPtr<nsIDOMNode> startNode, endNode;
PRInt32 startOffset, endOffset;
WSPoint point, tmp;
nsresult res = NS_OK;
if (aDir & eAfter)
{
WSPoint point, tmp;
res = GetCharAfter(aNode, aOffset, &point);
NS_ENSURE_SUCCESS(res, res);
if (point.mTextNode)
if (NS_SUCCEEDED(res) && point.mTextNode)
{ // we found a text node, at least
endNode = do_QueryInterface(point.mTextNode);
endOffset = point.mOffset;
@ -1733,9 +1823,9 @@ nsWSRunObject::GetAsciiWSBounds(PRInt16 aDir, nsIDOMNode *aNode, PRInt32 aOffset
if (aDir & eBefore)
{
WSPoint point, tmp;
res = GetCharBefore(aNode, aOffset, &point);
NS_ENSURE_SUCCESS(res, res);
if (point.mTextNode)
if (NS_SUCCEEDED(res) && point.mTextNode)
{ // we found a text node, at least
startNode = do_QueryInterface(point.mTextNode);
startOffset = point.mOffset+1;
@ -1973,10 +2063,68 @@ nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *out
return NS_ERROR_FAILURE;
}
nsresult
nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation.
// examine what is before and after the trailing nbsp, if any.
if (!aRun) return NS_ERROR_NULL_POINTER;
WSPoint thePoint;
PRBool leftCheck = PR_FALSE;
PRBool rightCheck = PR_FALSE;
// confirm run is normalWS
if (aRun->mType != eNormalWS) return NS_ERROR_FAILURE;
// first check for trailing nbsp
nsresult res = GetCharBefore(aRun->mEndNode, aRun->mEndOffset, &thePoint);
if (NS_SUCCEEDED(res) && thePoint.mChar == nbsp)
{
// now check that what is to the left of it is compatible with replacing nbsp with space
WSPoint prevPoint;
res = GetCharBefore(thePoint, &prevPoint);
if (NS_SUCCEEDED(res))
{
if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) leftCheck = PR_TRUE;
}
else if (aRun->mLeftType == eText) leftCheck = PR_TRUE;
else if (aRun->mLeftType == eSpecial) leftCheck = PR_TRUE;
if (leftCheck)
{
// now check that what is to the right of it is compatible with replacing nbsp with space
if (aRun->mRightType == eText) rightCheck = PR_TRUE;
if (aRun->mRightType == eSpecial) rightCheck = PR_TRUE;
if (aRun->mRightType == eBreak) rightCheck = PR_TRUE;
}
if (leftCheck && rightCheck)
{
// now replace nbsp with space
// first, insert a space
nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
if (!textNode)
return NS_ERROR_NULL_POINTER;
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
nsAutoString spaceStr(PRUnichar(32));
res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset);
NS_ENSURE_SUCCESS(res, res);
// finally, delete that nbsp
nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
NS_ENSURE_SUCCESS(res, res);
}
}
return NS_OK;
}
nsresult
nsWSRunObject::CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation.
// this routine is called when we about to make this point in the ws abut an inserted break
// or text, so we don't have to worry about what is after it. What is after it now will
// end up after the inserted object.
if (!aRun || !aNode) return NS_ERROR_NULL_POINTER;
WSPoint thePoint;
PRBool canConvert = PR_FALSE;
nsresult res = GetCharBefore(aNode, aOffset, &thePoint);
@ -2014,6 +2162,9 @@ nsresult
nsWSRunObject::CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
// this routine is called when we about to make this point in the ws abut an inserted
// text, so we don't have to worry about what is before it. What is before it now will
// end up before the inserted text.
WSPoint thePoint;
PRBool canConvert = PR_FALSE;
nsresult res = GetCharAfter(aNode, aOffset, &thePoint);

View File

@ -34,6 +34,24 @@ class nsIDOMDocument;
class nsIDOMNode;
class nsHTMLEditor;
// class nsWSRunObject represents the entire whitespace situation
// around a given point. It collects up a list of nodes that contain
// whitespace and categorizes in up to 3 different WSFragments (detailed
// below). Each WSFragment is a collection of whitespace that is
// either all insignificant, or that is significant. A WSFragment could
// consist of insignificant whitespace because it is after a block
// boundary or after a break. Or it could be insignificant because it
// is before a block. Or it could be significant because it is
// surrounded by text, or starts and ends with nbsps, etc.
// Throughout I refer to LeadingWS, NormalWS, TrailingWS. LeadingWS & TrailingWS
// are runs of ascii ws that are insignificant (do not render) because they
// are adjacent to block boundaries, or after a break. NormalWS is ws that
// does cause soem rendering. Note that not all the ws in a NormalWS run need
// render. For example, two ascii spaces surrounded by text on both sides
// will only render as one space (in non-preformatted stlye html), yet both
// spaces count as NormalWS. Together, they render as the one visible space.
class nsWSRunObject
{
public:
@ -44,77 +62,147 @@ class nsWSRunObject
kBackward
} EWSDirection;
// constructor / destructor
// constructor / destructor -----------------------------------------------
nsWSRunObject(nsHTMLEditor *aEd);
nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset);
~nsWSRunObject();
// public methods
// public methods ---------------------------------------------------------
// PrepareToJoinBlocks fixes up ws at the end of aLeftParent and the
// beginning of aRightParent in preperation for them to be joined.
// example of fixup: trailingws in aLeftParent needs to be removed.
static nsresult PrepareToJoinBlocks(nsHTMLEditor *aEd,
nsIDOMNode *aLeftParent,
nsIDOMNode *aRightParent);
// PrepareToDeleteRange fixes up ws before {aStartNode,aStartOffset}
// and after {aEndNode,aEndOffset} in preperation for content
// in that range to be deleted. Note that the nodes and offsets
// are adjusted in response to any dom changes we make while
// adjusting ws.
// example of fixup: trailingws before {aStartNode,aStartOffset}
// needs to be removed.
static nsresult PrepareToDeleteRange(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aStartNode,
PRInt32 *aStartOffset,
nsCOMPtr<nsIDOMNode> *aEndNode,
PRInt32 *aEndOffset);
// PrepareToDeleteNode fixes up ws before and after aNode in preperation
// for aNode to be deleted.
// example of fixup: trailingws before aNode needs to be removed.
static nsresult PrepareToDeleteNode(nsHTMLEditor *aHTMLEd,
nsIDOMNode *aNode);
static nsresult PrepareToSplitAcrossBlocks(nsCOMPtr<nsIDOMNode> *aSplitNode,
// PrepareToSplitAcrossBlocks fixes up ws before and after
// {aSplitNode,aSplitOffset} in preperation for a block
// parent to be split. Note that the aSplitNode and aSplitOffset
// are adjusted in response to any dom changes we make while
// adjusting ws.
// example of fixup: normalws before {aSplitNode,aSplitOffset}
// needs to end with nbsp.
static nsresult PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aSplitNode,
PRInt32 *aSplitOffset);
// InsertBreak inserts a br node at {aInOutParent,aInOutOffset}
// and makes any needed adjustments to ws around that point.
// example of fixup: normalws after {aInOutParent,aInOutOffset}
// needs to begin with nbsp.
nsresult InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
PRInt32 *aInOutOffset,
nsCOMPtr<nsIDOMNode> *outBRNode,
nsIEditor::EDirection aSelect);
// InsertText inserts a string at {aInOutParent,aInOutOffset}
// and makes any needed adjustments to ws around that point.
// example of fixup: trailingws before {aInOutParent,aInOutOffset}
// needs to be removed.
nsresult InsertText(const nsAReadableString& aStringToInsert,
nsCOMPtr<nsIDOMNode> *aInOutNode,
PRInt32 *aInOutOffset,
nsIDOMDocument *aDoc);
// DeleteWSBackward deletes a single visible piece of ws before
// the ws point (the point to create the wsRunObject, passed to
// its constructor). It makes any needed conversion to adjacent
// ws to retain its significance.
nsresult DeleteWSBackward();
// DeleteWSForward deletes a single visible piece of ws after
// the ws point (the point to create the wsRunObject, passed to
// its constructor). It makes any needed conversion to adjacent
// ws to retain its significance.
nsresult DeleteWSForward();
// PriorVisibleNode returns the first piece of visible thing
// before {aNode,aOffset}. If there is no visible ws qualifying
// it returns what is before the ws run. Note that
// {outVisNode,outVisOffset} is set to just AFTER the visible
// object.
nsresult PriorVisibleNode(nsIDOMNode *aNode,
PRInt32 aOffset,
nsCOMPtr<nsIDOMNode> *outVisNode,
PRInt32 *outVisOffset,
PRInt16 *outType);
// NextVisibleNode returns the first piece of visible thing
// after {aNode,aOffset}. If there is no visible ws qualifying
// it returns what is after the ws run. Note that
// {outVisNode,outVisOffset} is set to just BEFORE the visible
// object.
nsresult NextVisibleNode (nsIDOMNode *aNode,
PRInt32 aOffset,
nsCOMPtr<nsIDOMNode> *outVisNode,
PRInt32 *outVisOffset,
PRInt16 *outType);
// AdjustWhitespace examines the ws object for nbsp's that can
// be safely converted to regular ascii space and converts them.
nsresult AdjustWhitespace();
// public enums ---------------------------------------------------------
enum {eNone = 0};
enum {eLeadingWS = 1};
enum {eTrailingWS = 1 << 1};
enum {eNormalWS = 1 << 2};
enum {eText = 1 << 3};
enum {eSpecial = 1 << 4};
enum {eBreak = 1 << 5};
enum {eOtherBlock = 1 << 6};
enum {eThisBlock = 1 << 7};
enum {eBlock = eOtherBlock | eThisBlock};
enum {eLeadingWS = 1}; // leading insignificant ws, ie, after block or br
enum {eTrailingWS = 1 << 1}; // trailing insignificant ws, ie, before block
enum {eNormalWS = 1 << 2}; // normal significant ws, ie, after text, image, ...
enum {eText = 1 << 3}; // indicates regular (non-ws) text
enum {eSpecial = 1 << 4}; // indicates an inline non-container, like image
enum {eBreak = 1 << 5}; // indicates a br node
enum {eOtherBlock = 1 << 6}; // indicates a block other than one ws run is in
enum {eThisBlock = 1 << 7}; // indicates the block ws run is in
enum {eBlock = eOtherBlock | eThisBlock}; // block found
enum {eBefore = 1};
enum {eAfter = 1 << 1};
enum {eBoth = eBefore | eAfter};
protected:
// WSFragment struct ---------------------------------------------------------
// WSFragment represents a single run of ws (all leadingws, or all normalws,
// or all trailingws, or all leading+trailingws). Note that this single run may
// still span multiple nodes.
struct WSFragment
{
nsCOMPtr<nsIDOMNode> mStartNode;
nsCOMPtr<nsIDOMNode> mEndNode;
PRInt16 mStartOffset;
PRInt16 mEndOffset;
PRInt16 mType, mLeftType, mRightType;
WSFragment *mLeft, *mRight;
nsCOMPtr<nsIDOMNode> mStartNode; // node where ws run starts
nsCOMPtr<nsIDOMNode> mEndNode; // node where ws run ends
PRInt16 mStartOffset; // offset where ws run starts
PRInt16 mEndOffset; // offset where ws run ends
PRInt16 mType, mLeftType, mRightType; // type of ws, and what is to left and right of it
WSFragment *mLeft, *mRight; // other ws runs to left or right. may be null.
WSFragment() : mStartNode(0),mEndNode(0),mStartOffset(0),
mEndOffset(0),mType(0),mLeftType(0),
mRightType(0),mLeft(0),mRight(0) {}
};
// WSPoint struct ------------------------------------------------------------
// A WSPoint struct represents a unique location within the ws run. It is
// always within a textnode that is one of the nodes stored in the list
// in the wsRunObject. For convenience, the character at that point is also
// stored in the struct.
struct WSPoint
{
nsCOMPtr<nsITextContent> mTextNode;
@ -128,7 +216,8 @@ class nsWSRunObject
mTextNode(aTextNode),mOffset(aOffset),mChar(aChar) {}
};
// protected methods
// protected methods ---------------------------------------------------------
// tons of utility methods.
nsresult GetWSNodes();
nsresult GetRuns();
void ClearRuns();
@ -150,6 +239,7 @@ class nsWSRunObject
nsIDOMNode *aBlockParent,
nsCOMPtr<nsIDOMNode> *aNextNode);
nsresult PrepareToDeleteRangePriv(nsWSRunObject* aEndObject);
nsresult PrepareToSplitAcrossBlocksPriv();
nsresult DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
nsIDOMNode *aEndNode, PRInt32 aEndOffset);
nsresult GetCharAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
@ -164,26 +254,36 @@ class nsWSRunObject
PRUnichar GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset);
nsresult GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
nsresult GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
nsresult CheckTrailingNBSPOfRun(WSFragment *aRun);
nsresult CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset);
nsresult CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset);
// member variables
nsCOMPtr<nsIDOMNode> mNode;
PRInt32 mOffset;
nsCOMPtr<nsIDOMNode> mStartNode;
PRInt32 mStartOffset;
PRInt16 mStartReason;
nsCOMPtr<nsIDOMNode> mEndNode;
PRInt32 mEndOffset;
PRInt16 mEndReason;
nsCOMPtr<nsIDOMNode> mFirstNBSPNode;
PRInt32 mFirstNBSPOffset;
nsCOMPtr<nsIDOMNode> mLastNBSPNode;
PRInt32 mLastNBSPOffset;
nsCOMPtr<nsISupportsArray> mNodeArray;
WSFragment *mStartRun;
WSFragment *mEndRun;
nsHTMLEditor *mHTMLEditor; // non-owning.
// member variables ---------------------------------------------------------
nsCOMPtr<nsIDOMNode> mNode; // the node passed to our constructor
PRInt32 mOffset; // the offset passed to our contructor
// together, the above represent the point at which we are building up ws info.
nsCOMPtr<nsIDOMNode> mStartNode; // node/offet where ws starts
PRInt32 mStartOffset; // ...
PRInt16 mStartReason; // reason why ws starts (eText, eOtherBlock, etc)
nsCOMPtr<nsIDOMNode> mEndNode; // node/offet where ws ends
PRInt32 mEndOffset; // ...
PRInt16 mEndReason; // reason why ws ends (eText, eOtherBlock, etc)
nsCOMPtr<nsIDOMNode> mFirstNBSPNode; // location of first nbsp in ws run, if any
PRInt32 mFirstNBSPOffset; // ...
nsCOMPtr<nsIDOMNode> mLastNBSPNode; // location of last nbsp in ws run, if any
PRInt32 mLastNBSPOffset; // ...
nsCOMPtr<nsISupportsArray> mNodeArray;//the list of nodes containing ws in this run
WSFragment *mStartRun; // the first WSFragment in the run
WSFragment *mEndRun; // the last WSFragment in the run, may be same as first
nsHTMLEditor *mHTMLEditor; // non-owning.
};
#endif

View File

@ -244,6 +244,8 @@ protected:
PRUint32 aOffset,
PRUint32 aLength);
// NS_IMETHOD DeleteRange(nsIDOMRange *aRange);
NS_IMETHOD CreateTxnForDeleteText(nsIDOMCharacterData *aElement,
PRUint32 aOffset,
PRUint32 aLength,

View File

@ -118,6 +118,41 @@ class nsRangeUpdater
};
/***************************************************************************
* helper class for using nsSelectionState. stack based class for doing
* preservation of dom points across editor actions
*/
class nsAutoTrackDOMPoint
{
private:
nsRangeUpdater &mRU;
nsCOMPtr<nsIDOMNode> *mNode;
PRInt32 *mOffset;
nsRangeStore mRangeItem;
public:
nsAutoTrackDOMPoint(nsRangeUpdater &aRangeUpdater, nsCOMPtr<nsIDOMNode> *aNode, PRInt32 *aOffset) :
mRU(aRangeUpdater)
,mNode(aNode)
,mOffset(aOffset)
{
mRangeItem.startNode = *mNode;
mRangeItem.endNode = *mNode;
mRangeItem.startOffset = *mOffset;
mRangeItem.endOffset = *mOffset;
mRU.RegisterRangeItem(&mRangeItem);
}
~nsAutoTrackDOMPoint()
{
mRU.DropRangeItem(&mRangeItem);
*mNode = mRangeItem.startNode;
*mOffset = mRangeItem.startOffset;
}
};
/***************************************************************************
* another helper class for nsSelectionState. stack based class for doing
* Will/DidReplaceContainer()

View File

@ -56,6 +56,7 @@
#endif // IBMBIDI
#include "nsEditorUtils.h"
#include "nsWSRunObject.h"
#include "InsertTextTxn.h"
#include "DeleteTextTxn.h"
@ -380,20 +381,11 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection
if (NS_FAILED(res)) return res;
}
// adjust whitespace for insert text and delete actions
if ((action == nsEditor::kOpInsertText) ||
(action == nsEditor::kOpInsertIMEText) ||
(action == nsEditor::kOpDeleteSelection))
{
res = AdjustWhitespace(selection);
if (NS_FAILED(res)) return res;
}
// replace newlines that are preformatted
// replace newlines with breaks.
// MOOSE: This is buttUgly. A better way to
// organize the action enum is in order.
if ((action == nsEditor::kOpInsertText) ||
(action == nsEditor::kOpInsertIMEText) ||
if (// (action == nsEditor::kOpInsertText) ||
// (action == nsEditor::kOpInsertIMEText) ||
(action == nsHTMLEditor::kOpInsertElement) ||
(action == nsHTMLEditor::kOpInsertQuotation) ||
(action == nsEditor::kOpInsertNode))
@ -404,6 +396,13 @@ nsHTMLEditRules::AfterEditInner(PRInt32 action, nsIEditor::EDirection aDirection
// clean up any empty nodes in the selection
res = RemoveEmptyNodes();
if (NS_FAILED(res)) return res;
// attempt to transform any uneeded nbsp's into spaces after doing deletions
if (action == nsEditor::kOpDeleteSelection)
{
res = AdjustWhitespace(selection);
if (NS_FAILED(res)) return res;
}
// adjust selection for insert text, html paste, and delete actions
if ((action == nsEditor::kOpInsertText) ||
@ -941,6 +940,8 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
nsCOMPtr<nsIDOMNode> selNode;
PRInt32 selOffset;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
// if the selection isn't collapsed, delete it.
PRBool bCollapsed;
res = aSelection->GetIsCollapsed(&bCollapsed);
@ -1006,7 +1007,7 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
// for efficiency, break out the pre case seperately. This is because
// its a lot cheaper to search the input string for only newlines than
// it is to search for both tabs and newlines.
if (isPRE)
if (isPRE || bPlaintext)
{
char newlineChar = '\n';
while (unicodeBuf && (pos != -1) && (pos < (PRInt32)(*inString).Length()))
@ -1068,21 +1069,29 @@ nsHTMLEditRules::WillInsertText(PRInt32 aAction,
subStr.Subsume((PRUnichar*)&unicodeBuf[oldPos], PR_FALSE, subStrLen);
nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset);
// is it a tab?
if (subStr.EqualsWithConversion("\t"))
{
res = mHTMLEditor->InsertTextImpl(tabString, address_of(curNode), &curOffset, doc);
// res = mHTMLEditor->InsertTextImpl(tabString, address_of(curNode), &curOffset, doc);
res = wsObj.InsertText(tabString, address_of(curNode), &curOffset, doc);
if (NS_FAILED(res)) return res;
pos++;
}
// is it a return?
else if (subStr.EqualsWithConversion("\n"))
{
res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
// res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
if (NS_FAILED(res)) return res;
pos++;
}
else
{
res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
// res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
if (NS_FAILED(res)) return res;
}
if (NS_FAILED(res)) return res;
}
@ -1121,6 +1130,7 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
*aCancel = PR_FALSE;
*aHandled = PR_FALSE;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
// if the selection isn't collapsed, delete it.
PRBool bCollapsed;
@ -1147,7 +1157,6 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
PRInt32 selOffset, newOffset;
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
res = GetTopEnclosingMailCite(selNode, address_of(citeNode), bPlaintext);
if (NS_FAILED(res)) return res;
if (citeNode)
@ -1212,7 +1221,15 @@ nsHTMLEditRules::WillInsertBreak(nsISelection *aSelection, PRBool *aCancel, PRBo
else
{
nsCOMPtr<nsIDOMNode> brNode;
res = mHTMLEditor->CreateBR(node, offset, address_of(brNode));
if (bPlaintext)
{
res = mHTMLEditor->CreateBR(node, offset, address_of(brNode));
}
else
{
nsWSRunObject wsObj(mHTMLEditor, node, offset);
res = wsObj.InsertBreak(address_of(node), &offset, address_of(brNode), nsIEditor::eNone);
}
if (NS_FAILED(res)) return res;
res = nsEditor::GetNodeLocation(brNode, address_of(node), &offset);
if (NS_FAILED(res)) return res;
@ -1266,6 +1283,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
nsresult res = NS_OK;
PRBool bPlaintext = mFlags & nsIPlaintextEditor::eEditorPlaintextMask;
PRBool bCollapsed;
res = aSelection->GetIsCollapsed(&bCollapsed);
@ -1289,9 +1307,81 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (NS_FAILED(res)) return res;
if (!startNode) return NS_ERROR_FAILURE;
// get the root element
nsCOMPtr<nsIDOMElement> bodyElement;
nsCOMPtr<nsIDOMNode> bodyNode;
res = mHTMLEditor->GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_UNEXPECTED;
bodyNode = do_QueryInterface(bodyElement);
if (bCollapsed)
{
// easy case, in a text node:
// if we are inside an empty block, delete it.
// Note: do NOT delete table elements this way.
nsCOMPtr<nsIDOMNode> block;
if (IsBlockNode(startNode))
block = startNode;
else
block = mHTMLEditor->GetBlockNodeParent(startNode);
PRBool bIsEmptyNode;
if (block != bodyNode)
{
res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, PR_TRUE, PR_FALSE);
if (NS_FAILED(res)) return res;
if (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(startNode))
{
// adjust selection to be right after it
nsCOMPtr<nsIDOMNode> blockParent;
PRInt32 offset;
res = nsEditor::GetNodeLocation(block, address_of(blockParent), &offset);
if (NS_FAILED(res)) return res;
if (!blockParent || offset < 0) return NS_ERROR_FAILURE;
res = aSelection->Collapse(blockParent, offset+1);
if (NS_FAILED(res)) return res;
res = mHTMLEditor->DeleteNode(block);
*aHandled = PR_TRUE;
return res;
}
}
if (!bPlaintext)
{
// gather up ws data here. We may be next to non-significant ws.
nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset);
nsCOMPtr<nsIDOMNode> visNode;
PRInt32 visOffset;
PRInt16 wsType;
if (aAction == nsIEditor::ePrevious)
{
res = wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
// note that visOffset is _after_ what we are about to delete.
}
else if (aAction == nsIEditor::eNext)
{
res = wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), &visOffset, &wsType);
// note that visOffset is _before_ what we are about to delete.
}
if (NS_SUCCEEDED(res))
{
if (wsType==nsWSRunObject::eNormalWS)
{
// we found some visible ws to delete. Let ws code handle it.
if (aAction == nsIEditor::ePrevious)
res = wsObj.DeleteWSBackward();
else if (aAction == nsIEditor::eNext)
res = wsObj.DeleteWSForward();
*aHandled = PR_TRUE;
return res;
}
else if (visNode)
{
// reposition startNode and startOffset so that we skip over any non-significant ws
startNode = visNode;
startOffset = visOffset;
}
}
}
// in a text node:
if (mHTMLEditor->IsTextNode(startNode))
{
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
@ -1299,83 +1389,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
res = textNode->GetLength(&strLength);
if (NS_FAILED(res)) return res;
#ifdef IBMBIDI // Test for distance between caret and text that will be deleted
nsCOMPtr<nsIPresShell> shell;
mEditor->GetPresShell(getter_AddRefs(shell));
if (shell)
{
nsCOMPtr<nsIPresContext> context;
shell->GetPresContext(getter_AddRefs(context));
if (context)
{
PRBool bidiEnabled;
context->BidiEnabled(bidiEnabled);
if (bidiEnabled)
{
nsCOMPtr<nsIFrameSelection> frameSelection;
shell->GetFrameSelection(getter_AddRefs(frameSelection));
if (frameSelection)
{
nsCOMPtr<nsIContent> content = do_QueryInterface(startNode);
if (content)
{
nsIFrame *primaryFrame;
nsIFrame *frameBefore;
nsIFrame *frameAfter;
PRInt32 frameOffset;
shell->GetPrimaryFrameFor(content, &primaryFrame);
if (primaryFrame)
{
res = primaryFrame->GetChildFrameContainingOffset(startOffset, PR_FALSE, &frameOffset, &frameBefore);
if (NS_SUCCEEDED(res) && frameBefore)
{
PRInt32 start, end;
frameBefore->GetOffsets(start, end);
if (startOffset == end)
{
res = primaryFrame->GetChildFrameContainingOffset(startOffset, PR_TRUE, &frameOffset, &frameAfter);
if (NS_SUCCEEDED(res) && frameAfter)
{
PRUint8 currentCursorLevel;
long levelBefore;
long levelAfter;
long paragraphLevel;
long levelOfDeletion;
nsCOMPtr<nsIAtom> embeddingLevel = NS_NewAtom("EmbeddingLevel");
nsCOMPtr<nsIAtom> baseLevel = NS_NewAtom("BaseLevel");
frameBefore->GetBidiProperty(context, embeddingLevel, (void**)&levelBefore);
if (frameBefore==frameAfter)
{
frameBefore->GetBidiProperty(context, baseLevel, (void**)&paragraphLevel);
levelAfter = paragraphLevel;
}
else
frameAfter->GetBidiProperty(context, embeddingLevel, (void**)&levelAfter);
shell->GetCursorBidiLevel(&currentCursorLevel);
if (levelBefore==levelAfter && (levelAfter & 1) == (currentCursorLevel & 1))
shell->SetCursorBidiLevel(levelBefore);
else
{
levelOfDeletion = (nsIEditor::eNext==aAction) ? levelAfter : levelBefore;
shell->SetCursorBidiLevel(levelOfDeletion);
if ((currentCursorLevel/* & 1*/) != (levelOfDeletion/* & 1*/))
{
*aCancel = PR_TRUE;
return NS_OK;
}
}
}
}
}
}
}
}
}
}
}
#endif // IBMBIDI
// at beginning of text node and backspaced?
if (!startOffset && (aAction == nsIEditor::ePrevious))
{
@ -1384,7 +1397,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (NS_FAILED(res)) return res;
// if there is no prior node then cancel the deletion
if (!priorNode)
if (!priorNode || !nsTextEditUtils::InBody(priorNode, mHTMLEditor))
{
*aCancel = PR_TRUE;
return res;
@ -1401,7 +1414,17 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(priorNode);
nodeAsText->GetLength((PRUint32*)&offset);
NS_ENSURE_TRUE(offset, NS_ERROR_FAILURE);
res = aSelection->Collapse(priorNode,offset);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = offset-1;
PRInt32 eo = offset;
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(priorNode), &so,
address_of(priorNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
@ -1409,6 +1432,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// is prior node not a container? (ie, a br, hr, image...)
else if (!mHTMLEditor->IsContainer(priorNode)) // MOOSE: anchors not handled
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, priorNode);
if (NS_FAILED(res)) return res;
}
// delete the break, and join like nodes if appropriate
res = mHTMLEditor->DeleteNode(priorNode);
if (NS_FAILED(res)) return res;
@ -1424,8 +1452,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (mHTMLEditor->IsTextNode(priorNode))
{
// if so, join them!
nsCOMPtr<nsIDOMNode> topParent;
priorNode->GetParentNode(getter_AddRefs(topParent));
res = JoinNodesSmart(priorNode,startNode,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
// fix up selection
@ -1436,6 +1462,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
else if ( IsInlineNode(priorNode) )
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, priorNode);
if (NS_FAILED(res)) return res;
}
// remember where we are
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
@ -1454,8 +1485,16 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
// deleting across blocks
nsCOMPtr<nsIDOMNode> leftParent = mHTMLEditor->GetBlockNodeParent(priorNode);
nsCOMPtr<nsIDOMNode> rightParent = mHTMLEditor->GetBlockNodeParent(startNode);
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
if (IsBlockNode(priorNode))
leftParent = priorNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(priorNode);
if (IsBlockNode(startNode))
rightParent = startNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(startNode);
// if leftParent or rightParent is null, it's because the
// corresponding selection endpoint is in the body node.
@ -1472,9 +1511,13 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
if (mHTMLEditor->NodesSameType(leftParent, rightParent))
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (!bPlaintext)
{
// adjust whitespace at block boundaries
res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor,leftParent,rightParent);
if (NS_FAILED(res)) return res;
}
// join the nodes
*aHandled = PR_TRUE;
res = JoinNodesSmart(leftParent,rightParent,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
@ -1513,6 +1556,15 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(nextNode);
res = aSelection->Collapse(nextNode,0);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = 0;
PRInt32 eo = 1;
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(nextNode), &so,
address_of(nextNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
@ -1520,6 +1572,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// is next node not a container? (ie, a br, hr, image...)
else if (!mHTMLEditor->IsContainer(nextNode)) // MOOSE: anchors not handled
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nextNode);
if (NS_FAILED(res)) return res;
}
// delete the break, and join like nodes if appropriate
res = mHTMLEditor->DeleteNode(nextNode);
if (NS_FAILED(res)) return res;
@ -1535,8 +1592,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if ( mHTMLEditor->IsTextNode(nextNode) )
{
// if so, join them!
nsCOMPtr<nsIDOMNode> topParent;
nextNode->GetParentNode(getter_AddRefs(topParent));
res = JoinNodesSmart(startNode,nextNode,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
// fix up selection
@ -1547,9 +1602,14 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
else if ( IsInlineNode(nextNode) )
{
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nextNode);
if (NS_FAILED(res)) return res;
}
// remember where we are
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
// remember where we are
res = mHTMLEditor->GetNodeLocation(nextNode, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// delete it
@ -1565,9 +1625,17 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
}
// deleting across blocks
nsCOMPtr<nsIDOMNode> leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
nsCOMPtr<nsIDOMNode> rightParent = mHTMLEditor->GetBlockNodeParent(nextNode);
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
if (IsBlockNode(startNode))
leftParent = startNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
if (IsBlockNode(nextNode))
rightParent = nextNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(nextNode);
// if leftParent or rightParent is null, it's because the
// corresponding selection endpoint is in the body node.
if (!leftParent || !rightParent)
@ -1583,9 +1651,13 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
if (mHTMLEditor->NodesSameType(leftParent, rightParent))
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (!bPlaintext)
{
// adjust whitespace at block boundaries
res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor,leftParent,rightParent);
if (NS_FAILED(res)) return res;
}
// join the nodes
*aHandled = PR_TRUE;
res = JoinNodesSmart(leftParent,rightParent,address_of(selNode),&selOffset);
if (NS_FAILED(res)) return res;
@ -1596,8 +1668,28 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// else blocks not same type, bail to default
return NS_OK;
}
// else in middle of text node. default will do right thing.
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(startNode);
if (!nodeAsText) return NS_ERROR_NULL_POINTER;
res = aSelection->Collapse(startNode,startOffset);
if (NS_FAILED(res)) return res;
if (!bPlaintext)
{
PRInt32 so = startOffset;
PRInt32 eo = startOffset;
if (aAction == nsIEditor::ePrevious)
so--; // we know so not zero - that case handled above
else
eo++; // we know eo not at end of text node - that case handled above
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(startNode), &so,
address_of(startNode), &eo);
}
// just return without setting handled to true.
// default code will take care of actual deletion
return res;
}
// else not in text node; we need to find right place to act on
else
@ -1658,7 +1750,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
*aCancel = PR_TRUE;
return res;
}
// if this node is text node, adjust selection
if (nsEditor::IsTextNode(nodeToDelete))
{
@ -1674,10 +1766,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
{
// editable leaf node is not text; delete it.
// that's the default behavior
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
res = nsEditor::GetNodeLocation(nodeToDelete, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// EXCEPTION: if it's a mozBR, we have to check and see if
// there is a br in front of it. If so, we must delete both.
// else you get this: deletion code deletes mozBR, then selection
@ -1695,16 +1783,28 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if (block == brBlock)
{
// delete both breaks
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, brNode);
if (NS_FAILED(res)) return res;
}
res = mHTMLEditor->DeleteNode(brNode);
if (NS_FAILED(res)) return res;
res = mHTMLEditor->DeleteNode(nodeToDelete);
*aHandled = PR_TRUE;
return res;
// fall through to delete other br
}
// else fall through
}
// else fall through
}
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, nodeToDelete);
if (NS_FAILED(res)) return res;
}
PRInt32 offset;
nsCOMPtr<nsIDOMNode> node;
res = nsEditor::GetNodeLocation(nodeToDelete, address_of(node), &offset);
if (NS_FAILED(res)) return res;
// adjust selection to be right after it
res = aSelection->Collapse(node, offset+1);
if (NS_FAILED(res)) return res;
@ -1722,9 +1822,15 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
nsCOMPtr<nsIDOMNode> endNode;
PRInt32 endOffset;
res = mHTMLEditor->GetEndNodeAndOffset(aSelection, address_of(endNode), &endOffset);
if (NS_FAILED(res))
{
return res;
if (NS_FAILED(res)) return res;
// adjust surrounding whitespace in preperation to delete selection
if (!bPlaintext)
{
res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
address_of(startNode), &startOffset,
address_of(endNode), &endOffset);
if (NS_FAILED(res)) return res;
}
if (endNode.get() != startNode.get())
{
@ -1735,20 +1841,11 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
// are the blocks of same type?
nsCOMPtr<nsIDOMNode> leftParent;
nsCOMPtr<nsIDOMNode> rightParent;
// XXX: Fix for bug #10815: Crash deleting selected text and table.
// Make sure leftParent and rightParent are never NULL. This
// can happen if we call GetBlockNodeParent() and the node we
// pass in is a body node.
//
// Should we be calling IsBlockNode() instead of IsBody() here?
if (nsTextEditUtils::IsBody(startNode))
if (IsBlockNode(startNode))
leftParent = startNode;
else
leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
if (nsTextEditUtils::IsBody(endNode))
if (IsBlockNode(endNode))
rightParent = endNode;
else
rightParent = mHTMLEditor->GetBlockNodeParent(endNode);
@ -1763,9 +1860,6 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
if ( (leftBlockParent.get() == rightBlockParent.get())
&& (mHTMLEditor->NodesSameType(leftParent, rightParent)) )
{
nsCOMPtr<nsIDOMNode> topParent;
leftParent->GetParentNode(getter_AddRefs(topParent));
if (nsHTMLEditUtils::IsParagraph(leftParent))
{
// first delete the selection
@ -2780,25 +2874,20 @@ nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocume
res = mEditor->DeleteNode(rightNode);
if (NS_FAILED(res)) return res;
}
// register a rangeStore item that points at the new heirarchy.
// This is so we can know where to put the selection after we call
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
// so I have to use the range tracking system to find the right spot to put selection.
nsRangeStore *rangeItem = new nsRangeStore();
if (!rangeItem) return NS_ERROR_NULL_POINTER;
rangeItem->startNode = newSelParent;
rangeItem->endNode = newSelParent;
rangeItem->startOffset = 0;
rangeItem->endOffset = 0;
mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItem);
// remove the style on this new heirarchy
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
if (NS_FAILED(res)) return res;
PRInt32 newSelOffset = 0;
{
// track the point at the new heirarchy.
// This is so we can know where to put the selection after we call
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
// so I have to use the range tracking system to find the right spot to put selection.
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(newSelParent), &newSelOffset);
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
if (NS_FAILED(res)) return res;
}
// reset our node offset values to the resulting new sel point
mHTMLEditor->mRangeUpdater.DropRangeItem(rangeItem);
node = rangeItem->startNode;
offset = rangeItem->startOffset;
delete rangeItem;
node = newSelParent;
offset = newSelOffset;
}
// we own item now (TakeClearProperty hands ownership to us)
delete item;
@ -4210,9 +4299,14 @@ nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection,
nsresult res = nsEditor::GetNodeLocation(aHeader, address_of(headerParent), &offset);
if (NS_FAILED(res)) return res;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the header
PRInt32 newOffset;
res = mHTMLEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// if the leftand heading is empty, put a mozbr in it
@ -4306,8 +4400,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
{
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(sibling);
@ -4344,8 +4442,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
{
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep(aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep(aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(sibling);
@ -4383,8 +4485,12 @@ nsHTMLEditRules::ReturnInParagraph(nsISelection *aSelection,
// else remove sibling br and split para
PRInt32 newOffset;
*aCancel = PR_TRUE;
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// split the paragraph
res = mHTMLEditor->SplitNodeDeep( aPara, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aPara, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// get rid of the break
res = mHTMLEditor->DeleteNode(nearNode);
@ -4468,9 +4574,14 @@ nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection,
return res;
}
// else we want a new list item at the same list level
// else we want a new list item at the same list level.
// get ws code to adjust any ws
nsCOMPtr<nsIDOMNode> selNode = aNode;
res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
if (NS_FAILED(res)) return res;
// now split list item
PRInt32 newOffset;
res = mHTMLEditor->SplitNodeDeep( aListItem, aNode, aOffset, &newOffset);
res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset);
if (NS_FAILED(res)) return res;
// hack: until I can change the damaged doc range code back to being
// extra inclusive, I have to manually detect certain list items that
@ -4944,64 +5055,19 @@ nsHTMLEditRules::AdjustSpecialBreaks(PRBool aSafeToAskFrames)
return res;
}
nsresult
nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
{
nsCOMPtr<nsISupportsArray> arrayOfNodes;
nsCOMPtr<nsISupports> isupports;
PRUint32 nodeCount,j;
nsresult res;
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
// get selection point
nsCOMPtr<nsIDOMNode> selNode;
PRInt32 selOffset;
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, address_of(selNode), &selOffset);
if (NS_FAILED(res)) return res;
// special case for mDocChangeRange entirely in one text node.
// This is an efficiency hack for normal typing in the editor.
nsCOMPtr<nsIDOMNode> startNode, endNode;
PRInt32 startOffset, endOffset;
res = mDocChangeRange->GetStartContainer(getter_AddRefs(startNode));
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetStartOffset(&startOffset);
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetEndContainer(getter_AddRefs(endNode));
if (NS_FAILED(res)) return res;
res = mDocChangeRange->GetEndOffset(&endOffset);
if (NS_FAILED(res)) return res;
if (startNode == endNode)
{
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
if (nodeAsText)
{
res = DoTextNodeWhitespace(nodeAsText, startOffset, endOffset);
return res;
}
}
// gather up a list of text nodes
nsEditableTextFunctor functor(mHTMLEditor);
nsDOMIterator iter;
res = iter.Init(mDocChangeRange);
if (NS_FAILED(res)) return res;
res = iter.MakeList(functor, address_of(arrayOfNodes));
if (NS_FAILED(res)) return res;
// now adjust whitespace on node we found
res = arrayOfNodes->Count(&nodeCount);
if (NS_FAILED(res)) return res;
for (j = 0; j < nodeCount; j++)
{
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
nsCOMPtr<nsIDOMCharacterData> textNode( do_QueryInterface(isupports ) );
arrayOfNodes->RemoveElementAt(0);
res = DoTextNodeWhitespace(textNode, -1, -1);
if (NS_FAILED(res)) return res;
}
return res;
// ask whitespace object to tweak nbsp's
return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
}
nsresult
nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction)
{
@ -5103,6 +5169,12 @@ nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection
res = aSelection->Collapse(selNode,selOffset);
if (NS_FAILED(res)) return res;
}
else if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
{
// selection between br and mozbr. make it stick to mozbr
// so that it will be on blank line.
selPriv->SetInterlinePosition(PR_TRUE);
}
}
}
}
@ -5892,6 +5964,20 @@ nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode,
return res;
}
NS_IMETHODIMP
nsHTMLEditRules::WillDeleteRange(nsIDOMRange *aRange)
{
if (!mListenerEnabled) return NS_OK;
// get the (collapsed) selection location
return UpdateDocChangeRange(aRange);
}
NS_IMETHODIMP
nsHTMLEditRules::DidDeleteRange(nsIDOMRange *aRange)
{
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection)
{

View File

@ -77,6 +77,8 @@ public:
NS_IMETHOD DidInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAReadableString &aString, nsresult aResult);
NS_IMETHOD WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength);
NS_IMETHOD DidDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength, nsresult aResult);
NS_IMETHOD WillDeleteRange(nsIDOMRange *aRange);
NS_IMETHOD DidDeleteRange(nsIDOMRange *aRange);
NS_IMETHOD WillDeleteSelection(nsISelection *aSelection);
NS_IMETHOD DidDeleteSelection(nsISelection *aSelection);
@ -179,13 +181,13 @@ protected:
// data members
protected:
nsHTMLEditor *mHTMLEditor;
nsCOMPtr<nsIDOMRange> mDocChangeRange;
PRBool mListenerEnabled;
PRBool mReturnInEmptyLIKillsList;
nsCOMPtr<nsIDOMRange> mUtilRange;
PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin...
nsHTMLEditor *mHTMLEditor;
nsCOMPtr<nsIDOMRange> mDocChangeRange;
PRBool mListenerEnabled;
PRBool mReturnInEmptyLIKillsList;
nsCOMPtr<nsIDOMRange> mUtilRange;
PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin...
};
nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult);

View File

@ -64,6 +64,7 @@
#include "nsIContentIterator.h"
#include "nsEditorCID.h"
#include "nsLayoutCID.h"
#include "nsContentCID.h"
#include "nsIDOMRange.h"
#include "nsIDOMNSRange.h"
#include "nsISupportsArray.h"
@ -114,6 +115,7 @@ static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID);
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
static NS_DEFINE_IID(kRangeUtilsCID, NS_RANGEUTILS_CID);
static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID);
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
@ -232,6 +234,10 @@ NS_IMETHODIMP nsHTMLEditor::Init(nsIDOMDocument *aDoc,
nsresult result = NS_OK, rulesRes = NS_OK;
// make a range util object for comparing dom points
mRangeHelper = do_CreateInstance(kRangeUtilsCID);
if (!mRangeHelper) return NS_ERROR_NULL_POINTER;
// Init mEditProperty
result = NS_NewEditProperty(getter_AddRefs(mEditProperty));
if (NS_FAILED(result)) { return result; }
@ -1267,7 +1273,10 @@ NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled)
return res;
}
NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent,
PRInt32 *aInOutOffset,
nsCOMPtr<nsIDOMNode> *outBRNode,
EDirection aSelect)
{
if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
*outBRNode = nsnull;

View File

@ -38,6 +38,7 @@
#include "nsICSSLoader.h"
#include "nsICSSLoaderObserver.h"
#include "nsITableLayout.h"
#include "nsIRangeUtils.h"
#include "nsEditRules.h"
@ -679,11 +680,14 @@ protected:
// Used by GetFirstSelectedCell and GetNextSelectedCell
PRInt32 mSelectedCellIndex;
nsCOMPtr<nsIRangeUtils> mRangeHelper;
public:
// friends
friend class nsHTMLEditRules;
friend class nsTextEditRules;
friend class nsWSRunObject;
};

View File

@ -119,7 +119,6 @@ nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd,
if (!aLeftParent || !aRightParent || !aHTMLEd)
return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
PRUint32 count;
aHTMLEd->GetLengthOfDOMNode(aLeftParent, count);
nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count);
@ -168,9 +167,19 @@ nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor *aHTMLEd,
}
nsresult
nsWSRunObject::PrepareToSplitAcrossBlocks(nsCOMPtr<nsIDOMNode> *aSplitNode, PRInt32 *aSplitOffset)
nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aSplitNode,
PRInt32 *aSplitOffset)
{
return NS_OK;
if (!aSplitNode || !aSplitOffset || !*aSplitNode || !aHTMLEd)
return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset);
nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset);
return wsObj.PrepareToSplitAcrossBlocksPriv();
}
//--------------------------------------------------------------------------------------------
@ -193,29 +202,6 @@ nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
// handle any changes needed to ws run before inserted br
if (!beforeRun)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after break inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run after inserted br
if (!afterRun)
{
@ -252,6 +238,29 @@ nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
}
}
// handle any changes needed to ws run before inserted br
if (!beforeRun)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert break. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after break inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// ready, aim, fire!
return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect);
}
@ -282,29 +291,6 @@ nsWSRunObject::InsertText(const nsAReadableString& aStringToInsert,
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
// handle any changes needed to ws run before inserted text
if (!beforeRun)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after text inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run after inserted text
if (!afterRun)
{
@ -328,6 +314,29 @@ nsWSRunObject::InsertText(const nsAReadableString& aStringToInsert,
NS_ENSURE_SUCCESS(res, res);
}
// handle any changes needed to ws run before inserted text
if (!beforeRun)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType & eLeadingWS)
{
// dont need to do anything. just insert text. ws wont change.
}
else if (beforeRun->mType == eTrailingWS)
{
// need to delete the trailing ws that is before insertion point, because it
// would become significant after text inserted.
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
else if (beforeRun->mType == eNormalWS)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
NS_ENSURE_SUCCESS(res, res);
}
// next up, tweak head and tail of string as needed.
// first the head:
// there are a variety of circumstances that would require us to convert a
@ -446,7 +455,7 @@ nsWSRunObject::DeleteWSBackward()
NS_ENSURE_SUCCESS(res, res);
// finally, delete that ws
return DeleteChars(startNode, startOffset, mNode, mOffset);
return DeleteChars(startNode, startOffset, endNode, endOffset);
}
else if (point.mChar == nbsp)
{
@ -489,7 +498,7 @@ nsWSRunObject::DeleteWSForward()
NS_ENSURE_SUCCESS(res, res);
// finally, delete that ws
return DeleteChars(startNode, startOffset, mNode, mOffset);
return DeleteChars(startNode, startOffset, endNode, endOffset);
}
else if (point.mChar == nbsp)
{
@ -515,6 +524,8 @@ nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode,
PRInt32 *outVisOffset,
PRInt16 *outType)
{
// Find first visible thing before the point. position outVisNode/outVisOffset
// just _after_ that thing. If we don't find anything return start of ws.
if (!aNode || !outVisNode || !outVisOffset || !outType)
return NS_ERROR_NULL_POINTER;
@ -536,7 +547,7 @@ nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode,
if (point.mTextNode)
{
*outVisNode = do_QueryInterface(point.mTextNode);
*outVisOffset = point.mOffset;
*outVisOffset = point.mOffset+1;
if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
{
*outType = eNormalWS;
@ -571,6 +582,8 @@ nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode,
PRInt32 *outVisOffset,
PRInt16 *outType)
{
// Find first visible thing before the point. position outVisNode/outVisOffset
// just _before_ that thing. If we don't find anything return end of ws.
if (!aNode || !outVisNode || !outVisOffset || !outType)
return NS_ERROR_NULL_POINTER;
@ -619,6 +632,27 @@ nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode,
return NS_OK;
}
nsresult
nsWSRunObject::AdjustWhitespace()
{
// this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
// replacing them with regualr ascii space if possible. Keeping things simple
// for now and just trying to fix up the trailing ws in the run.
if (!mLastNBSPNode) return NS_OK; // nothing to do!
nsresult res = NS_OK;
WSFragment *curRun = mStartRun;
while (curRun)
{
// look for normal ws run
if (curRun->mType == eNormalWS)
{
res = CheckTrailingNBSPOfRun(curRun);
break;
}
curRun = curRun->mRight;
}
return res;
}
//--------------------------------------------------------------------------------------------
@ -1061,6 +1095,9 @@ nsWSRunObject::GetRuns()
else
{
// we might have trailing ws.
// it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
// will point to it, even though in general start/end points not
// guaranteed to be in text nodes.
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
{
// normal ws runs right up to adjacent block (nbsp next to block)
@ -1099,6 +1136,9 @@ nsWSRunObject::GetRuns()
mStartRun->mLeftType = mStartReason;
// we might have trailing ws.
// it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
// will point to it, even though in general start/end points not
// guaranteed to be in text nodes.
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
{
// set up next run
@ -1388,6 +1428,7 @@ nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
NS_ENSURE_SUCCESS(res, res);
res = aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, PR_TRUE);
NS_ENSURE_SUCCESS(res, res);
// trim after run of any leading ws
if (afterRun && (afterRun->mType == eLeadingWS))
@ -1444,6 +1485,56 @@ nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
return res;
}
nsresult
nsWSRunObject::PrepareToSplitAcrossBlocksPriv()
{
// used to prepare ws to be split across two blocks. The main issue
// here is make sure normalWS doesn't end up becoming non-significant
// leading or trailing ws after the split.
nsresult res = NS_OK;
// get the runs before and after selection
WSFragment *beforeRun, *afterRun;
res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
NS_ENSURE_SUCCESS(res, res);
res = FindRun(mNode, mOffset, &afterRun, PR_TRUE);
// adjust normal ws in afterRun if needed
if (afterRun && (afterRun->mType == eNormalWS))
{
// make sure leading char of following ws is an nbsp, so that it will show up
WSPoint point;
GetCharAfter(mNode, mOffset, &point);
if (nsCRT::IsAsciiSpace(point.mChar))
{
res = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(res, res);
}
}
// adjust normal ws in beforeRun if needed
if (beforeRun && (beforeRun->mType == eNormalWS))
{
// make sure trailing char of starting ws is an nbsp, so that it will show up
WSPoint point;
GetCharBefore(mNode, mOffset, &point);
if (nsCRT::IsAsciiSpace(point.mChar))
{
nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
PRInt32 wsStartOffset, wsEndOffset;
res = GetAsciiWSBounds(eBoth, mNode, mOffset,
address_of(wsStartNode), &wsStartOffset,
address_of(wsEndNode), &wsEndOffset);
NS_ENSURE_SUCCESS(res, res);
point.mTextNode = do_QueryInterface(wsStartNode);
point.mOffset = wsStartOffset;
res = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(res, res);
}
}
return res;
}
nsresult
nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
nsIDOMNode *aEndNode, PRInt32 aEndOffset)
@ -1704,14 +1795,13 @@ nsWSRunObject::GetAsciiWSBounds(PRInt16 aDir, nsIDOMNode *aNode, PRInt32 aOffset
nsCOMPtr<nsIDOMNode> startNode, endNode;
PRInt32 startOffset, endOffset;
WSPoint point, tmp;
nsresult res = NS_OK;
if (aDir & eAfter)
{
WSPoint point, tmp;
res = GetCharAfter(aNode, aOffset, &point);
NS_ENSURE_SUCCESS(res, res);
if (point.mTextNode)
if (NS_SUCCEEDED(res) && point.mTextNode)
{ // we found a text node, at least
endNode = do_QueryInterface(point.mTextNode);
endOffset = point.mOffset;
@ -1733,9 +1823,9 @@ nsWSRunObject::GetAsciiWSBounds(PRInt16 aDir, nsIDOMNode *aNode, PRInt32 aOffset
if (aDir & eBefore)
{
WSPoint point, tmp;
res = GetCharBefore(aNode, aOffset, &point);
NS_ENSURE_SUCCESS(res, res);
if (point.mTextNode)
if (NS_SUCCEEDED(res) && point.mTextNode)
{ // we found a text node, at least
startNode = do_QueryInterface(point.mTextNode);
startOffset = point.mOffset+1;
@ -1973,10 +2063,68 @@ nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *out
return NS_ERROR_FAILURE;
}
nsresult
nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation.
// examine what is before and after the trailing nbsp, if any.
if (!aRun) return NS_ERROR_NULL_POINTER;
WSPoint thePoint;
PRBool leftCheck = PR_FALSE;
PRBool rightCheck = PR_FALSE;
// confirm run is normalWS
if (aRun->mType != eNormalWS) return NS_ERROR_FAILURE;
// first check for trailing nbsp
nsresult res = GetCharBefore(aRun->mEndNode, aRun->mEndOffset, &thePoint);
if (NS_SUCCEEDED(res) && thePoint.mChar == nbsp)
{
// now check that what is to the left of it is compatible with replacing nbsp with space
WSPoint prevPoint;
res = GetCharBefore(thePoint, &prevPoint);
if (NS_SUCCEEDED(res))
{
if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) leftCheck = PR_TRUE;
}
else if (aRun->mLeftType == eText) leftCheck = PR_TRUE;
else if (aRun->mLeftType == eSpecial) leftCheck = PR_TRUE;
if (leftCheck)
{
// now check that what is to the right of it is compatible with replacing nbsp with space
if (aRun->mRightType == eText) rightCheck = PR_TRUE;
if (aRun->mRightType == eSpecial) rightCheck = PR_TRUE;
if (aRun->mRightType == eBreak) rightCheck = PR_TRUE;
}
if (leftCheck && rightCheck)
{
// now replace nbsp with space
// first, insert a space
nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
if (!textNode)
return NS_ERROR_NULL_POINTER;
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
nsAutoString spaceStr(PRUnichar(32));
res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset);
NS_ENSURE_SUCCESS(res, res);
// finally, delete that nbsp
nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
NS_ENSURE_SUCCESS(res, res);
}
}
return NS_OK;
}
nsresult
nsWSRunObject::CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation.
// this routine is called when we about to make this point in the ws abut an inserted break
// or text, so we don't have to worry about what is after it. What is after it now will
// end up after the inserted object.
if (!aRun || !aNode) return NS_ERROR_NULL_POINTER;
WSPoint thePoint;
PRBool canConvert = PR_FALSE;
nsresult res = GetCharBefore(aNode, aOffset, &thePoint);
@ -2014,6 +2162,9 @@ nsresult
nsWSRunObject::CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset)
{
// try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
// this routine is called when we about to make this point in the ws abut an inserted
// text, so we don't have to worry about what is before it. What is before it now will
// end up before the inserted text.
WSPoint thePoint;
PRBool canConvert = PR_FALSE;
nsresult res = GetCharAfter(aNode, aOffset, &thePoint);

View File

@ -34,6 +34,24 @@ class nsIDOMDocument;
class nsIDOMNode;
class nsHTMLEditor;
// class nsWSRunObject represents the entire whitespace situation
// around a given point. It collects up a list of nodes that contain
// whitespace and categorizes in up to 3 different WSFragments (detailed
// below). Each WSFragment is a collection of whitespace that is
// either all insignificant, or that is significant. A WSFragment could
// consist of insignificant whitespace because it is after a block
// boundary or after a break. Or it could be insignificant because it
// is before a block. Or it could be significant because it is
// surrounded by text, or starts and ends with nbsps, etc.
// Throughout I refer to LeadingWS, NormalWS, TrailingWS. LeadingWS & TrailingWS
// are runs of ascii ws that are insignificant (do not render) because they
// are adjacent to block boundaries, or after a break. NormalWS is ws that
// does cause soem rendering. Note that not all the ws in a NormalWS run need
// render. For example, two ascii spaces surrounded by text on both sides
// will only render as one space (in non-preformatted stlye html), yet both
// spaces count as NormalWS. Together, they render as the one visible space.
class nsWSRunObject
{
public:
@ -44,77 +62,147 @@ class nsWSRunObject
kBackward
} EWSDirection;
// constructor / destructor
// constructor / destructor -----------------------------------------------
nsWSRunObject(nsHTMLEditor *aEd);
nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset);
~nsWSRunObject();
// public methods
// public methods ---------------------------------------------------------
// PrepareToJoinBlocks fixes up ws at the end of aLeftParent and the
// beginning of aRightParent in preperation for them to be joined.
// example of fixup: trailingws in aLeftParent needs to be removed.
static nsresult PrepareToJoinBlocks(nsHTMLEditor *aEd,
nsIDOMNode *aLeftParent,
nsIDOMNode *aRightParent);
// PrepareToDeleteRange fixes up ws before {aStartNode,aStartOffset}
// and after {aEndNode,aEndOffset} in preperation for content
// in that range to be deleted. Note that the nodes and offsets
// are adjusted in response to any dom changes we make while
// adjusting ws.
// example of fixup: trailingws before {aStartNode,aStartOffset}
// needs to be removed.
static nsresult PrepareToDeleteRange(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aStartNode,
PRInt32 *aStartOffset,
nsCOMPtr<nsIDOMNode> *aEndNode,
PRInt32 *aEndOffset);
// PrepareToDeleteNode fixes up ws before and after aNode in preperation
// for aNode to be deleted.
// example of fixup: trailingws before aNode needs to be removed.
static nsresult PrepareToDeleteNode(nsHTMLEditor *aHTMLEd,
nsIDOMNode *aNode);
static nsresult PrepareToSplitAcrossBlocks(nsCOMPtr<nsIDOMNode> *aSplitNode,
// PrepareToSplitAcrossBlocks fixes up ws before and after
// {aSplitNode,aSplitOffset} in preperation for a block
// parent to be split. Note that the aSplitNode and aSplitOffset
// are adjusted in response to any dom changes we make while
// adjusting ws.
// example of fixup: normalws before {aSplitNode,aSplitOffset}
// needs to end with nbsp.
static nsresult PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd,
nsCOMPtr<nsIDOMNode> *aSplitNode,
PRInt32 *aSplitOffset);
// InsertBreak inserts a br node at {aInOutParent,aInOutOffset}
// and makes any needed adjustments to ws around that point.
// example of fixup: normalws after {aInOutParent,aInOutOffset}
// needs to begin with nbsp.
nsresult InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
PRInt32 *aInOutOffset,
nsCOMPtr<nsIDOMNode> *outBRNode,
nsIEditor::EDirection aSelect);
// InsertText inserts a string at {aInOutParent,aInOutOffset}
// and makes any needed adjustments to ws around that point.
// example of fixup: trailingws before {aInOutParent,aInOutOffset}
// needs to be removed.
nsresult InsertText(const nsAReadableString& aStringToInsert,
nsCOMPtr<nsIDOMNode> *aInOutNode,
PRInt32 *aInOutOffset,
nsIDOMDocument *aDoc);
// DeleteWSBackward deletes a single visible piece of ws before
// the ws point (the point to create the wsRunObject, passed to
// its constructor). It makes any needed conversion to adjacent
// ws to retain its significance.
nsresult DeleteWSBackward();
// DeleteWSForward deletes a single visible piece of ws after
// the ws point (the point to create the wsRunObject, passed to
// its constructor). It makes any needed conversion to adjacent
// ws to retain its significance.
nsresult DeleteWSForward();
// PriorVisibleNode returns the first piece of visible thing
// before {aNode,aOffset}. If there is no visible ws qualifying
// it returns what is before the ws run. Note that
// {outVisNode,outVisOffset} is set to just AFTER the visible
// object.
nsresult PriorVisibleNode(nsIDOMNode *aNode,
PRInt32 aOffset,
nsCOMPtr<nsIDOMNode> *outVisNode,
PRInt32 *outVisOffset,
PRInt16 *outType);
// NextVisibleNode returns the first piece of visible thing
// after {aNode,aOffset}. If there is no visible ws qualifying
// it returns what is after the ws run. Note that
// {outVisNode,outVisOffset} is set to just BEFORE the visible
// object.
nsresult NextVisibleNode (nsIDOMNode *aNode,
PRInt32 aOffset,
nsCOMPtr<nsIDOMNode> *outVisNode,
PRInt32 *outVisOffset,
PRInt16 *outType);
// AdjustWhitespace examines the ws object for nbsp's that can
// be safely converted to regular ascii space and converts them.
nsresult AdjustWhitespace();
// public enums ---------------------------------------------------------
enum {eNone = 0};
enum {eLeadingWS = 1};
enum {eTrailingWS = 1 << 1};
enum {eNormalWS = 1 << 2};
enum {eText = 1 << 3};
enum {eSpecial = 1 << 4};
enum {eBreak = 1 << 5};
enum {eOtherBlock = 1 << 6};
enum {eThisBlock = 1 << 7};
enum {eBlock = eOtherBlock | eThisBlock};
enum {eLeadingWS = 1}; // leading insignificant ws, ie, after block or br
enum {eTrailingWS = 1 << 1}; // trailing insignificant ws, ie, before block
enum {eNormalWS = 1 << 2}; // normal significant ws, ie, after text, image, ...
enum {eText = 1 << 3}; // indicates regular (non-ws) text
enum {eSpecial = 1 << 4}; // indicates an inline non-container, like image
enum {eBreak = 1 << 5}; // indicates a br node
enum {eOtherBlock = 1 << 6}; // indicates a block other than one ws run is in
enum {eThisBlock = 1 << 7}; // indicates the block ws run is in
enum {eBlock = eOtherBlock | eThisBlock}; // block found
enum {eBefore = 1};
enum {eAfter = 1 << 1};
enum {eBoth = eBefore | eAfter};
protected:
// WSFragment struct ---------------------------------------------------------
// WSFragment represents a single run of ws (all leadingws, or all normalws,
// or all trailingws, or all leading+trailingws). Note that this single run may
// still span multiple nodes.
struct WSFragment
{
nsCOMPtr<nsIDOMNode> mStartNode;
nsCOMPtr<nsIDOMNode> mEndNode;
PRInt16 mStartOffset;
PRInt16 mEndOffset;
PRInt16 mType, mLeftType, mRightType;
WSFragment *mLeft, *mRight;
nsCOMPtr<nsIDOMNode> mStartNode; // node where ws run starts
nsCOMPtr<nsIDOMNode> mEndNode; // node where ws run ends
PRInt16 mStartOffset; // offset where ws run starts
PRInt16 mEndOffset; // offset where ws run ends
PRInt16 mType, mLeftType, mRightType; // type of ws, and what is to left and right of it
WSFragment *mLeft, *mRight; // other ws runs to left or right. may be null.
WSFragment() : mStartNode(0),mEndNode(0),mStartOffset(0),
mEndOffset(0),mType(0),mLeftType(0),
mRightType(0),mLeft(0),mRight(0) {}
};
// WSPoint struct ------------------------------------------------------------
// A WSPoint struct represents a unique location within the ws run. It is
// always within a textnode that is one of the nodes stored in the list
// in the wsRunObject. For convenience, the character at that point is also
// stored in the struct.
struct WSPoint
{
nsCOMPtr<nsITextContent> mTextNode;
@ -128,7 +216,8 @@ class nsWSRunObject
mTextNode(aTextNode),mOffset(aOffset),mChar(aChar) {}
};
// protected methods
// protected methods ---------------------------------------------------------
// tons of utility methods.
nsresult GetWSNodes();
nsresult GetRuns();
void ClearRuns();
@ -150,6 +239,7 @@ class nsWSRunObject
nsIDOMNode *aBlockParent,
nsCOMPtr<nsIDOMNode> *aNextNode);
nsresult PrepareToDeleteRangePriv(nsWSRunObject* aEndObject);
nsresult PrepareToSplitAcrossBlocksPriv();
nsresult DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
nsIDOMNode *aEndNode, PRInt32 aEndOffset);
nsresult GetCharAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
@ -164,26 +254,36 @@ class nsWSRunObject
PRUnichar GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset);
nsresult GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
nsresult GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint);
nsresult CheckTrailingNBSPOfRun(WSFragment *aRun);
nsresult CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset);
nsresult CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, PRInt32 aOffset);
// member variables
nsCOMPtr<nsIDOMNode> mNode;
PRInt32 mOffset;
nsCOMPtr<nsIDOMNode> mStartNode;
PRInt32 mStartOffset;
PRInt16 mStartReason;
nsCOMPtr<nsIDOMNode> mEndNode;
PRInt32 mEndOffset;
PRInt16 mEndReason;
nsCOMPtr<nsIDOMNode> mFirstNBSPNode;
PRInt32 mFirstNBSPOffset;
nsCOMPtr<nsIDOMNode> mLastNBSPNode;
PRInt32 mLastNBSPOffset;
nsCOMPtr<nsISupportsArray> mNodeArray;
WSFragment *mStartRun;
WSFragment *mEndRun;
nsHTMLEditor *mHTMLEditor; // non-owning.
// member variables ---------------------------------------------------------
nsCOMPtr<nsIDOMNode> mNode; // the node passed to our constructor
PRInt32 mOffset; // the offset passed to our contructor
// together, the above represent the point at which we are building up ws info.
nsCOMPtr<nsIDOMNode> mStartNode; // node/offet where ws starts
PRInt32 mStartOffset; // ...
PRInt16 mStartReason; // reason why ws starts (eText, eOtherBlock, etc)
nsCOMPtr<nsIDOMNode> mEndNode; // node/offet where ws ends
PRInt32 mEndOffset; // ...
PRInt16 mEndReason; // reason why ws ends (eText, eOtherBlock, etc)
nsCOMPtr<nsIDOMNode> mFirstNBSPNode; // location of first nbsp in ws run, if any
PRInt32 mFirstNBSPOffset; // ...
nsCOMPtr<nsIDOMNode> mLastNBSPNode; // location of last nbsp in ws run, if any
PRInt32 mLastNBSPOffset; // ...
nsCOMPtr<nsISupportsArray> mNodeArray;//the list of nodes containing ws in this run
WSFragment *mStartRun; // the first WSFragment in the run
WSFragment *mEndRun; // the last WSFragment in the run, may be same as first
nsHTMLEditor *mHTMLEditor; // non-owning.
};
#endif

Binary file not shown.