diff --git a/mozilla/editor/base/Makefile.in b/mozilla/editor/base/Makefile.in index 8b5b9a35528..1f65cb5ba34 100644 --- a/mozilla/editor/base/Makefile.in +++ b/mozilla/editor/base/Makefile.in @@ -91,6 +91,7 @@ CPPSRCS += nsAOLCiter.cpp \ nsInternetCiter.cpp \ nsTableEditor.cpp \ nsWrapUtils.cpp \ + nsWSRunObject.cpp \ TextEditorTest.cpp \ TypeInState.cpp \ SetDocTitleTxn.cpp \ diff --git a/mozilla/editor/base/makefile.win b/mozilla/editor/base/makefile.win index 4cc4a047994..006b30c4728 100644 --- a/mozilla/editor/base/makefile.win +++ b/mozilla/editor/base/makefile.win @@ -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 \ diff --git a/mozilla/editor/base/nsEditor.h b/mozilla/editor/base/nsEditor.h index d8fc94cd693..6920290e7b9 100644 --- a/mozilla/editor/base/nsEditor.h +++ b/mozilla/editor/base/nsEditor.h @@ -244,6 +244,8 @@ protected: PRUint32 aOffset, PRUint32 aLength); +// NS_IMETHOD DeleteRange(nsIDOMRange *aRange); + NS_IMETHOD CreateTxnForDeleteText(nsIDOMCharacterData *aElement, PRUint32 aOffset, PRUint32 aLength, diff --git a/mozilla/editor/base/nsHTMLEditRules.cpp b/mozilla/editor/base/nsHTMLEditRules.cpp index 19fe76ecfb5..466c49f7d8a 100644 --- a/mozilla/editor/base/nsHTMLEditRules.cpp +++ b/mozilla/editor/base/nsHTMLEditRules.cpp @@ -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 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 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 bodyElement; + nsCOMPtr 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 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 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 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 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 shell; - mEditor->GetPresShell(getter_AddRefs(shell)); - if (shell) - { - nsCOMPtr context; - shell->GetPresContext(getter_AddRefs(context)); - if (context) - { - PRBool bidiEnabled; - context->BidiEnabled(bidiEnabled); - if (bidiEnabled) - { - nsCOMPtr frameSelection; - shell->GetFrameSelection(getter_AddRefs(frameSelection)); - if (frameSelection) - { - nsCOMPtr 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 embeddingLevel = NS_NewAtom("EmbeddingLevel"); - nsCOMPtr baseLevel = NS_NewAtom("BaseLevel"); - frameBefore->GetBidiProperty(context, embeddingLevel, (void**)&levelBefore); - if (frameBefore==frameAfter) - { - frameBefore->GetBidiProperty(context, baseLevel, (void**)¶graphLevel); - levelAfter = paragraphLevel; - } - else - frameAfter->GetBidiProperty(context, embeddingLevel, (void**)&levelAfter); - shell->GetCursorBidiLevel(¤tCursorLevel); - 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, nsCOMPtrnodeAsText; 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 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 node; @@ -1454,8 +1485,16 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection, } // deleting across blocks - nsCOMPtr leftParent = mHTMLEditor->GetBlockNodeParent(priorNode); - nsCOMPtr rightParent = mHTMLEditor->GetBlockNodeParent(startNode); + nsCOMPtr leftParent; + nsCOMPtr 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 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, nsCOMPtrnodeAsText; 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 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 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 leftParent = mHTMLEditor->GetBlockNodeParent(startNode); - nsCOMPtr rightParent = mHTMLEditor->GetBlockNodeParent(nextNode); - + nsCOMPtr leftParent; + nsCOMPtr 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 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. + nsCOMPtrnodeAsText; + 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 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 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 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 leftParent; nsCOMPtr 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 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 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 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 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 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 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 arrayOfNodes; - nsCOMPtr isupports; - PRUint32 nodeCount,j; - nsresult res; - - nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); + // get selection point + nsCOMPtr 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 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 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 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) { diff --git a/mozilla/editor/base/nsHTMLEditRules.h b/mozilla/editor/base/nsHTMLEditRules.h index 077678895bf..a4237c4456c 100644 --- a/mozilla/editor/base/nsHTMLEditRules.h +++ b/mozilla/editor/base/nsHTMLEditRules.h @@ -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 mDocChangeRange; - PRBool mListenerEnabled; - PRBool mReturnInEmptyLIKillsList; - nsCOMPtr mUtilRange; - PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin... - + nsHTMLEditor *mHTMLEditor; + nsCOMPtr mDocChangeRange; + PRBool mListenerEnabled; + PRBool mReturnInEmptyLIKillsList; + nsCOMPtr mUtilRange; + PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin... + }; nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult); diff --git a/mozilla/editor/base/nsHTMLEditor.cpp b/mozilla/editor/base/nsHTMLEditor.cpp index 58e5225c2b1..0c315c9b06b 100644 --- a/mozilla/editor/base/nsHTMLEditor.cpp +++ b/mozilla/editor/base/nsHTMLEditor.cpp @@ -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 *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *outBRNode, EDirection aSelect) +NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *outBRNode, + EDirection aSelect) { if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER; *outBRNode = nsnull; diff --git a/mozilla/editor/base/nsHTMLEditor.h b/mozilla/editor/base/nsHTMLEditor.h index 9ce6a66ed46..3758dd5b818 100644 --- a/mozilla/editor/base/nsHTMLEditor.h +++ b/mozilla/editor/base/nsHTMLEditor.h @@ -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 mRangeHelper; + public: // friends friend class nsHTMLEditRules; friend class nsTextEditRules; +friend class nsWSRunObject; }; diff --git a/mozilla/editor/base/nsSelectionState.h b/mozilla/editor/base/nsSelectionState.h index 3202aba0337..6e81d6a01ac 100644 --- a/mozilla/editor/base/nsSelectionState.h +++ b/mozilla/editor/base/nsSelectionState.h @@ -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 *mNode; + PRInt32 *mOffset; + nsRangeStore mRangeItem; + public: + nsAutoTrackDOMPoint(nsRangeUpdater &aRangeUpdater, nsCOMPtr *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() diff --git a/mozilla/editor/base/nsWSRunObject.cpp b/mozilla/editor/base/nsWSRunObject.cpp index 3ebc8d72d4a..82458a60622 100644 --- a/mozilla/editor/base/nsWSRunObject.cpp +++ b/mozilla/editor/base/nsWSRunObject.cpp @@ -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 *aSplitNode, PRInt32 *aSplitOffset) +nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd, + nsCOMPtr *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 *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 *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 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 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 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 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); diff --git a/mozilla/editor/base/nsWSRunObject.h b/mozilla/editor/base/nsWSRunObject.h index 8cdf161287b..77590a51c05 100644 --- a/mozilla/editor/base/nsWSRunObject.h +++ b/mozilla/editor/base/nsWSRunObject.h @@ -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 *aStartNode, PRInt32 *aStartOffset, nsCOMPtr *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 *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 *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 *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *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 *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 *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 *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 mStartNode; - nsCOMPtr mEndNode; - PRInt16 mStartOffset; - PRInt16 mEndOffset; - PRInt16 mType, mLeftType, mRightType; - WSFragment *mLeft, *mRight; + nsCOMPtr mStartNode; // node where ws run starts + nsCOMPtr 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 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 *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 mNode; - PRInt32 mOffset; - nsCOMPtr mStartNode; - PRInt32 mStartOffset; - PRInt16 mStartReason; - nsCOMPtr mEndNode; - PRInt32 mEndOffset; - PRInt16 mEndReason; - nsCOMPtr mFirstNBSPNode; - PRInt32 mFirstNBSPOffset; - nsCOMPtr mLastNBSPNode; - PRInt32 mLastNBSPOffset; - nsCOMPtr mNodeArray; - WSFragment *mStartRun; - WSFragment *mEndRun; - nsHTMLEditor *mHTMLEditor; // non-owning. + // member variables --------------------------------------------------------- + + nsCOMPtr 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 mStartNode; // node/offet where ws starts + PRInt32 mStartOffset; // ... + PRInt16 mStartReason; // reason why ws starts (eText, eOtherBlock, etc) + + nsCOMPtr mEndNode; // node/offet where ws ends + PRInt32 mEndOffset; // ... + PRInt16 mEndReason; // reason why ws ends (eText, eOtherBlock, etc) + + nsCOMPtr mFirstNBSPNode; // location of first nbsp in ws run, if any + PRInt32 mFirstNBSPOffset; // ... + + nsCOMPtr mLastNBSPNode; // location of last nbsp in ws run, if any + PRInt32 mLastNBSPOffset; // ... + + nsCOMPtr 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 diff --git a/mozilla/editor/libeditor/base/nsEditor.h b/mozilla/editor/libeditor/base/nsEditor.h index d8fc94cd693..6920290e7b9 100644 --- a/mozilla/editor/libeditor/base/nsEditor.h +++ b/mozilla/editor/libeditor/base/nsEditor.h @@ -244,6 +244,8 @@ protected: PRUint32 aOffset, PRUint32 aLength); +// NS_IMETHOD DeleteRange(nsIDOMRange *aRange); + NS_IMETHOD CreateTxnForDeleteText(nsIDOMCharacterData *aElement, PRUint32 aOffset, PRUint32 aLength, diff --git a/mozilla/editor/libeditor/base/nsSelectionState.h b/mozilla/editor/libeditor/base/nsSelectionState.h index 3202aba0337..6e81d6a01ac 100644 --- a/mozilla/editor/libeditor/base/nsSelectionState.h +++ b/mozilla/editor/libeditor/base/nsSelectionState.h @@ -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 *mNode; + PRInt32 *mOffset; + nsRangeStore mRangeItem; + public: + nsAutoTrackDOMPoint(nsRangeUpdater &aRangeUpdater, nsCOMPtr *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() diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp index 19fe76ecfb5..466c49f7d8a 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp @@ -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 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 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 bodyElement; + nsCOMPtr 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 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 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 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 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 shell; - mEditor->GetPresShell(getter_AddRefs(shell)); - if (shell) - { - nsCOMPtr context; - shell->GetPresContext(getter_AddRefs(context)); - if (context) - { - PRBool bidiEnabled; - context->BidiEnabled(bidiEnabled); - if (bidiEnabled) - { - nsCOMPtr frameSelection; - shell->GetFrameSelection(getter_AddRefs(frameSelection)); - if (frameSelection) - { - nsCOMPtr 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 embeddingLevel = NS_NewAtom("EmbeddingLevel"); - nsCOMPtr baseLevel = NS_NewAtom("BaseLevel"); - frameBefore->GetBidiProperty(context, embeddingLevel, (void**)&levelBefore); - if (frameBefore==frameAfter) - { - frameBefore->GetBidiProperty(context, baseLevel, (void**)¶graphLevel); - levelAfter = paragraphLevel; - } - else - frameAfter->GetBidiProperty(context, embeddingLevel, (void**)&levelAfter); - shell->GetCursorBidiLevel(¤tCursorLevel); - 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, nsCOMPtrnodeAsText; 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 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 node; @@ -1454,8 +1485,16 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection, } // deleting across blocks - nsCOMPtr leftParent = mHTMLEditor->GetBlockNodeParent(priorNode); - nsCOMPtr rightParent = mHTMLEditor->GetBlockNodeParent(startNode); + nsCOMPtr leftParent; + nsCOMPtr 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 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, nsCOMPtrnodeAsText; 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 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 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 leftParent = mHTMLEditor->GetBlockNodeParent(startNode); - nsCOMPtr rightParent = mHTMLEditor->GetBlockNodeParent(nextNode); - + nsCOMPtr leftParent; + nsCOMPtr 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 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. + nsCOMPtrnodeAsText; + 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 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 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 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 leftParent; nsCOMPtr 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 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 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 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 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 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 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 arrayOfNodes; - nsCOMPtr isupports; - PRUint32 nodeCount,j; - nsresult res; - - nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); + // get selection point + nsCOMPtr 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 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 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 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) { diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.h b/mozilla/editor/libeditor/html/nsHTMLEditRules.h index 077678895bf..a4237c4456c 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.h @@ -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 mDocChangeRange; - PRBool mListenerEnabled; - PRBool mReturnInEmptyLIKillsList; - nsCOMPtr mUtilRange; - PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin... - + nsHTMLEditor *mHTMLEditor; + nsCOMPtr mDocChangeRange; + PRBool mListenerEnabled; + PRBool mReturnInEmptyLIKillsList; + nsCOMPtr mUtilRange; + PRUint32 mJoinOffset; // need to remember an int across willJoin/didJoin... + }; nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult); diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp index 58e5225c2b1..0c315c9b06b 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.cpp @@ -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 *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *outBRNode, EDirection aSelect) +NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *outBRNode, + EDirection aSelect) { if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER; *outBRNode = nsnull; diff --git a/mozilla/editor/libeditor/html/nsHTMLEditor.h b/mozilla/editor/libeditor/html/nsHTMLEditor.h index 9ce6a66ed46..3758dd5b818 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditor.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditor.h @@ -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 mRangeHelper; + public: // friends friend class nsHTMLEditRules; friend class nsTextEditRules; +friend class nsWSRunObject; }; diff --git a/mozilla/editor/libeditor/html/nsWSRunObject.cpp b/mozilla/editor/libeditor/html/nsWSRunObject.cpp index 3ebc8d72d4a..82458a60622 100644 --- a/mozilla/editor/libeditor/html/nsWSRunObject.cpp +++ b/mozilla/editor/libeditor/html/nsWSRunObject.cpp @@ -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 *aSplitNode, PRInt32 *aSplitOffset) +nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd, + nsCOMPtr *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 *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 *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 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 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 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 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); diff --git a/mozilla/editor/libeditor/html/nsWSRunObject.h b/mozilla/editor/libeditor/html/nsWSRunObject.h index 8cdf161287b..77590a51c05 100644 --- a/mozilla/editor/libeditor/html/nsWSRunObject.h +++ b/mozilla/editor/libeditor/html/nsWSRunObject.h @@ -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 *aStartNode, PRInt32 *aStartOffset, nsCOMPtr *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 *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 *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 *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *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 *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 *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 *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 mStartNode; - nsCOMPtr mEndNode; - PRInt16 mStartOffset; - PRInt16 mEndOffset; - PRInt16 mType, mLeftType, mRightType; - WSFragment *mLeft, *mRight; + nsCOMPtr mStartNode; // node where ws run starts + nsCOMPtr 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 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 *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 mNode; - PRInt32 mOffset; - nsCOMPtr mStartNode; - PRInt32 mStartOffset; - PRInt16 mStartReason; - nsCOMPtr mEndNode; - PRInt32 mEndOffset; - PRInt16 mEndReason; - nsCOMPtr mFirstNBSPNode; - PRInt32 mFirstNBSPOffset; - nsCOMPtr mLastNBSPNode; - PRInt32 mLastNBSPOffset; - nsCOMPtr mNodeArray; - WSFragment *mStartRun; - WSFragment *mEndRun; - nsHTMLEditor *mHTMLEditor; // non-owning. + // member variables --------------------------------------------------------- + + nsCOMPtr 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 mStartNode; // node/offet where ws starts + PRInt32 mStartOffset; // ... + PRInt16 mStartReason; // reason why ws starts (eText, eOtherBlock, etc) + + nsCOMPtr mEndNode; // node/offet where ws ends + PRInt32 mEndOffset; // ... + PRInt16 mEndReason; // reason why ws ends (eText, eOtherBlock, etc) + + nsCOMPtr mFirstNBSPNode; // location of first nbsp in ws run, if any + PRInt32 mFirstNBSPOffset; // ... + + nsCOMPtr mLastNBSPNode; // location of last nbsp in ws run, if any + PRInt32 mLastNBSPOffset; // ... + + nsCOMPtr 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 diff --git a/mozilla/editor/macbuild/editor.mcp b/mozilla/editor/macbuild/editor.mcp index e456e6a9816..ebdb8c2f2ec 100644 Binary files a/mozilla/editor/macbuild/editor.mcp and b/mozilla/editor/macbuild/editor.mcp differ