/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsHTMLEditRules.h" #include "nsEditor.h" #include "nsHTMLEditor.h" #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsIDOMNode.h" #include "nsIDOMText.h" #include "nsIDOMElement.h" #include "nsIDOMNodeList.h" #include "nsIDOMSelection.h" #include "nsIDOMRange.h" #include "nsIDOMCharacterData.h" #include "nsIEnumerator.h" #include "nsIStyleContext.h" #include "nsIPresShell.h" #include "nsLayoutCID.h" #include "nsEditorUtils.h" #include "InsertTextTxn.h" #include "DeleteTextTxn.h" //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE"; //const static char* kMOZEditorBogusNodeValue="TRUE"; const unsigned char nbsp = 160; static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); static NS_DEFINE_IID(kContentIteratorCID, NS_CONTENTITERATOR_CID); static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID); enum { kLonely = 0, kPrevSib = 1, kNextSib = 2, kBothSibs = 3 }; /******************************************************** * Constructor/Destructor ********************************************************/ nsHTMLEditRules::nsHTMLEditRules() : nsTextEditRules() { } nsHTMLEditRules::~nsHTMLEditRules() { // remove ourselves as a listener to edit actions mEditor->RemoveEditActionListener(mListener); } /******************************************************** * Public methods ********************************************************/ NS_IMETHODIMP nsHTMLEditRules::Init(nsHTMLEditor *aEditor, PRUint32 aFlags) { // call through to base class Init first nsresult res = nsTextEditRules::Init(aEditor, aFlags); if (NS_FAILED(res)) return res; // make a listener res = NS_NewEditListener(getter_AddRefs(mListener), mEditor, this); if (NS_FAILED(res)) return res; // add it as a listener to edit actions res = mEditor->AddEditActionListener(mListener); return res; } NS_IMETHODIMP nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, PRBool *aCancel, PRBool *aHandled) { if (!aInfo || !aCancel || !aHandled) return NS_ERROR_NULL_POINTER; #if defined(DEBUG_ftang) printf("nsHTMLEditRules::WillDoAction action = %d\n", aInfo->action); #endif *aCancel = PR_FALSE; *aHandled = PR_FALSE; mDocChangeRange = nsnull; // clear out our accounting of what changed // my kingdom for dynamic cast nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); switch (info->action) { case kInsertText: case kInsertTextIME: return WillInsertText(info->action, aSelection, aCancel, aHandled, info->inString, info->outString, info->typeInState, info->maxLength); case kInsertBreak: return WillInsertBreak(aSelection, aCancel, aHandled); case kDeleteSelection: return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled); case kMakeList: return WillMakeList(aSelection, info->bOrdered, aCancel, aHandled); case kIndent: return WillIndent(aSelection, aCancel, aHandled); case kOutdent: return WillOutdent(aSelection, aCancel, aHandled); case kAlign: return WillAlign(aSelection, info->alignType, aCancel, aHandled); case kMakeBasicBlock: return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled); case kRemoveList: return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled); case kInsertElement: return WillInsert(aSelection, aCancel); } return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); } NS_IMETHODIMP nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, nsresult aResult) { // my kingdom for dynamic cast nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); nsresult res; switch (info->action) { case kUndo: mDocChangeRange = nsnull; // clear out our accounting of what changed res = nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); if (NS_FAILED(res)) return res; return AdjustSelection(aSelection,info->collapsedAction); case kRedo: mDocChangeRange = nsnull; // clear out our accounting of what changed res = nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); if (NS_FAILED(res)) return res; return AdjustSelection(aSelection,info->collapsedAction); } // for other than undo and redo: if (mDocChangeRange) { // expand the "changed doc range" as needed res = PromoteRange(mDocChangeRange, info->action); if (NS_FAILED(res)) return res; // add in any needed
s, and remove any unneeded ones. res = AdjustSpecialBreaks(); if (NS_FAILED(res)) return res; // adjust whitespace for insert text and delete actions if ((aInfo->action == kInsertText) || (aInfo->action == kDeleteSelection)) { res = AdjustWhitespace(); if (NS_FAILED(res)) return res; } // clean up any empty nodes in the selection res = RemoveEmptyNodes(); if (NS_FAILED(res)) return res; } // do default: res = nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); if (NS_FAILED(res)) return res; // adjust selection res = AdjustSelection(aSelection,info->collapsedAction); return res; } /******************************************************** * Protected rules methods ********************************************************/ nsresult nsHTMLEditRules::WillInsert(nsIDOMSelection *aSelection, PRBool *aCancel) { if (!aSelection || !aCancel) return NS_ERROR_NULL_POINTER; nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // this next only works for collapsed selections right now, // because selection is a pain to work with when not collapsed. // (no good way to extend start or end of selection) PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; if (!bCollapsed) return NS_OK; // if we are after a mozBR in the same block, then move selection // to be before it nsCOMPtr selNode, priorNode; PRInt32 selOffset; // get the (collapsed) selection location res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; // get prior node nsCOMPtr block1, block2; if (mEditor->IsBlockNode(selNode)) block1 = selNode; else block1 = mEditor->GetBlockNodeParent(selNode); if (mEditor->IsBlockNode(priorNode)) block2 = priorNode; else block2 = mEditor->GetBlockNodeParent(priorNode); if (block1 != block2) return NS_OK; if (!IsMozBR(priorNode)) return NS_OK; // if we are here then the selection is right after a mozBR // that is in the same block as the selection. We need to move // the selection start to be before the mozBR. res = nsEditor::GetNodeLocation(priorNode, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode,selOffset); if (NS_FAILED(res)) return res; return NS_OK; } nsresult nsHTMLEditRules::WillInsertText(PRInt32 aAction, nsIDOMSelection *aSelection, PRBool *aCancel, PRBool *aHandled, const nsString *inString, nsString *outString, TypeInState typeInState, PRInt32 aMaxLength) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_TRUE; nsresult res; nsCOMPtr selNode; PRInt32 selOffset; char specialChars[] = {'\t',' ',nbsp,'\n',0}; // if the selection isn't collapsed, delete it. PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; if (!bCollapsed) { res = mEditor->DeleteSelection(nsIEditor::eDoNothing); if (NS_FAILED(res)) return res; } res = WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; // get the (collapsed) selection location res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; // split any mailcites in the way if (mFlags & nsIHTMLEditor::eEditorMailMask) { nsCOMPtr citeNode; PRInt32 newOffset; res = GetTopEnclosingMailCite(selNode, &citeNode); if (NS_FAILED(res)) return res; if (citeNode) { res = mEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset); if (NS_FAILED(res)) return res; res = citeNode->GetParentNode(getter_AddRefs(selNode)); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode, newOffset); if (NS_FAILED(res)) return res; } } // identify the block nsCOMPtr blockParent; if (nsEditor::IsBlockNode(selNode)) blockParent = selNode; else blockParent = mEditor->GetBlockNodeParent(selNode); if (!blockParent) return NS_ERROR_FAILURE; // are we not in a textnode? if (!mEditor->IsTextNode(selNode)) { // find a nearby text node if possible nsCOMPtr priorNode, nextNode; res = GetPriorHTMLNode(selNode, selOffset, &priorNode); // are we after a mozBR? if (NS_SUCCEEDED(res) && priorNode && IsMozBR(priorNode) && (blockParent == mEditor->GetBlockNodeParent(priorNode))) { // uhh, lets be before it instead of after it. Now // walk away reeeal slow... res = nsEditor::GetNodeLocation(priorNode, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode, selOffset); if (NS_FAILED(res)) return res; // we know we are still not in a text node, so fall through to rest of this case res = GetPriorHTMLNode(selNode, selOffset, &priorNode); } if (NS_SUCCEEDED(res) && priorNode && mEditor->IsTextNode(priorNode) && (blockParent == mEditor->GetBlockNodeParent(priorNode))) { // put selection at end of prior node PRUint32 strLength; nsCOMPtr textNode = do_QueryInterface(priorNode); res = textNode->GetLength(&strLength); if (NS_FAILED(res)) return res; res = aSelection->Collapse(priorNode, strLength); if (NS_FAILED(res)) return res; } else { res = GetNextHTMLNode(selNode, selOffset, &nextNode); if (NS_SUCCEEDED(res) && nextNode && mEditor->IsTextNode(nextNode) && (blockParent == mEditor->GetBlockNodeParent(nextNode))) { // put selection at begining of next node res = aSelection->Collapse(nextNode, 0); if (NS_FAILED(res)) return res; } } /* // if we are right after a moz br, delete it and make a new moz div PRBool needMozDiv = PR_FALSE; if (priorNode && IsBreak(priorNode) && HasMozAttr(priorNode) && (blockParent == mEditor->GetBlockNodeParent(priorNode))) { res = mEditor->DeleteNode(priorNode); if (NS_FAILED(res)) return res; // but we only need to make a moz div if we weren't in a listitem if (!IsListItem(blockParent)) needMozDiv = PR_TRUE; } // if we are directly in a body or (non-moz) div, create a moz-div. // Also creat one if we detected a prior moz br (see above). res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; if (needMozDiv || IsBody(selNode) || IsNormalDiv(selNode)) { // wrap things up in a moz-div nsCOMPtr mozDiv; res = CreateMozDiv(selNode, selOffset, &mozDiv); if (NS_FAILED(res)) return res; // put selection in it res = aSelection->Collapse(mozDiv, 0); if (NS_FAILED(res)) return res; } */} char nbspStr[2] = {nbsp, 0}; PRBool bCancel; nsString theString(*inString); // copy instring for now if(aAction == kInsertTextIME) { // special case for IME. We need this to : // a) handle null strings, which are meaningful for IME // b) prevent the string from being broken into substrings, // which can happen in non-IME processing below. // I should probably convert runs of spaces and tabs here as well res = DoTextInsertion(aSelection, &bCancel, &theString, typeInState); } else // aAction == kInsertText { while (theString.Length()) { nsString partialString; PRInt32 pos = theString.FindCharInSet(specialChars); // if first char is special, then use just it if (pos == 0) pos = 1; if (pos == -1) pos = theString.Length(); theString.Left(partialString, pos); theString.Cut(0, pos); // is it a solo tab? if (partialString == "\t" ) { res = InsertTab(aSelection,outString); if (NS_FAILED(res)) return res; res = DoTextInsertion(aSelection, &bCancel, outString, typeInState); } // is it a solo space? else if (partialString == " ") { res = InsertSpace(aSelection,outString); if (NS_FAILED(res)) return res; res = DoTextInsertion(aSelection, &bCancel, outString, typeInState); } // is it a solo nbsp? else if (partialString == nbspStr) { res = InsertSpace(aSelection,outString); if (NS_FAILED(res)) return res; res = DoTextInsertion(aSelection, &bCancel, outString, typeInState); } // is it a solo return? else if (partialString == "\n") { res = mEditor->InsertBreak(); } else { res = DoTextInsertion(aSelection, &bCancel, &partialString, typeInState); } if (NS_FAILED(res)) return res; pos = theString.FindCharInSet(specialChars); } } return res; } nsresult nsHTMLEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_FALSE; nsresult res; res = WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; // if the selection isn't collapsed, delete it. PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; if (!bCollapsed) { res = mEditor->DeleteSelection(nsIEditor::eDoNothing); if (NS_FAILED(res)) return res; } // split any mailcites in the way if (mFlags & nsIHTMLEditor::eEditorMailMask) { nsCOMPtr citeNode, selNode; PRInt32 selOffset, newOffset; res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = GetTopEnclosingMailCite(selNode, &citeNode); if (NS_FAILED(res)) return res; if (citeNode) { res = mEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset); if (NS_FAILED(res)) return res; res = citeNode->GetParentNode(getter_AddRefs(selNode)); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode, newOffset); if (NS_FAILED(res)) return res; } } // smart splitting rules nsCOMPtr node; PRInt32 offset; PRBool isPRE; res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset); if (NS_FAILED(res)) return res; if (!node) return NS_ERROR_FAILURE; res = mEditor->IsPreformatted(node,&isPRE); if (NS_FAILED(res)) return res; if (isPRE) { nsString theString = "\n"; *aHandled = PR_TRUE; return mEditor->InsertTextImpl(theString); } // identify the block nsCOMPtr blockParent; if (nsEditor::IsBlockNode(node)) blockParent = node; else blockParent = mEditor->GetBlockNodeParent(node); if (!blockParent) return NS_ERROR_FAILURE; /* // break action depends on type of block PRBool bIsMozDiv = IsMozDiv(blockParent); PRBool bIsNormalDiv = IsNormalDiv(blockParent); // body or normal div: insert a normal br if (IsBody(blockParent) || bIsNormalDiv) { nsCOMPtr brNode; res = mEditor->InsertBR(&brNode); if (NS_FAILED(res)) return res; *aHandled = PR_TRUE; } else if (bIsMozDiv) { // split it PRInt32 newOffset; res = mEditor->SplitNodeDeep( blockParent, node, offset, &newOffset); if (NS_FAILED(res)) return res; nsCOMPtr parent; blockParent->GetParentNode(getter_AddRefs(parent)); nsCOMPtr rightDiv = nsEditor::GetChildAt(parent, newOffset); if (!rightDiv || !IsMozDiv(rightDiv)) return NS_ERROR_FAILURE; // put the trailer br at the end of the lefthand mozdiv nsCOMPtr leftDiv; res = GetPriorHTMLSibling(rightDiv, &leftDiv); if (NS_FAILED(res)) return res; if (!leftDiv || !IsMozDiv(leftDiv)) return NS_ERROR_FAILURE; res = AddTrailerBR(leftDiv); if (NS_FAILED(res)) return res; // put selection at beginning of righthand mozdiv res = aSelection->Collapse(rightDiv, 0); if (NS_FAILED(res)) return res; *aHandled = PR_TRUE; } */ // headers: close (or split) header else if (IsHeader(blockParent)) { res = ReturnInHeader(aSelection, blockParent, node, offset); *aHandled = PR_TRUE; return NS_OK; } // paragraphs: special rules to look for
s else if (IsParagraph(blockParent)) { res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel, aHandled); return NS_OK; } // list items: special rules to make new list items else if (IsListItem(blockParent)) { res = ReturnInListItem(aSelection, blockParent, node, offset); *aHandled = PR_TRUE; return NS_OK; } // its something else (body, div, td, ...): insert a normal br else { nsCOMPtr nearNode; res = GetPriorHTMLNode(node, offset, &nearNode); if (NS_FAILED(res)) return res; if (nearNode && IsBreak(nearNode) && IsMozBR(nearNode)) { // is nearNode also a descendant of same block? nsCOMPtr nearBlock = mEditor->GetBlockNodeParent(nearNode); if (blockParent == nearBlock) { // need to insert special BEFORE the moz BR. Why? Because if we don't // the selectin adjusting code will add an extra BR. doh. res = nsEditor::GetNodeLocation(nearNode, &node, &offset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(node, offset); if (NS_FAILED(res)) return res; } } nsCOMPtr brNode; res = mEditor->CreateBR(node, offset, &brNode); if (NS_FAILED(res)) return res; res = nsEditor::GetNodeLocation(brNode, &node, &offset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(node, offset+1); if (NS_FAILED(res)) return res; *aHandled = PR_TRUE; } return res; } nsresult nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::ESelectionCollapseDirection aAction, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_FALSE; // if there is only bogus content, cancel the operation if (mBogusNode) { *aCancel = PR_TRUE; return NS_OK; } nsresult res = NS_OK; PRBool bCollapsed; res = aSelection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; nsCOMPtr node, selNode; PRInt32 offset, selOffset; res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset); if (NS_FAILED(res)) return res; if (!node) return NS_ERROR_FAILURE; if (bCollapsed) { // easy case, in a text node: if (mEditor->IsTextNode(node)) { nsCOMPtr textNode = do_QueryInterface(node); PRUint32 strLength; res = textNode->GetLength(&strLength); if (NS_FAILED(res)) return res; // at beginning of text node and backspaced? if (!offset && (aAction == nsIEditor::eDeletePrevious)) { nsCOMPtr priorNode; res = GetPriorHTMLNode(node, &priorNode); if (NS_FAILED(res)) return res; // if there is no prior node then cancel the deletion if (!priorNode) { *aCancel = PR_TRUE; return res; } // block parents the same? if (mEditor->HasSameBlockNodeParent(node, priorNode)) { // is prior node not a container? (ie, a br, hr, image...) if (!mEditor->IsContainer(priorNode)) // MOOSE: anchors not handled { // delete the break, and join like nodes if appropriate res = mEditor->DeleteNode(priorNode); if (NS_FAILED(res)) return res; // get new prior node res = GetPriorHTMLNode(node, &priorNode); if (NS_FAILED(res)) return res; // are they in same block? if (mEditor->HasSameBlockNodeParent(node, priorNode)) { // are they same type? if (mEditor->NodesSameType(node, priorNode)) { // if so, join them! nsCOMPtr topParent; priorNode->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(priorNode,node,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } } } // is prior node inline and same type? (This shouldn't happen) if ( mEditor->HasSameBlockNodeParent(node, priorNode) && ( mEditor->IsInlineNode(node) || mEditor->IsTextNode(node) ) && mEditor->NodesSameType(node, priorNode) ) { // if so, join them! nsCOMPtr topParent; priorNode->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(priorNode,node,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } else return NS_OK; // punt to default } // deleting across blocks // are the blocks of same type? nsCOMPtr leftParent = mEditor->GetBlockNodeParent(priorNode); nsCOMPtr rightParent = mEditor->GetBlockNodeParent(node); // if leftParent or rightParent is null, it's because the // corresponding selection endpoint is in the body node. if (!leftParent || !rightParent) return NS_OK; // bail to default if (mEditor->NodesSameType(leftParent, rightParent)) { nsCOMPtr topParent; leftParent->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(leftParent,rightParent,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } // else blocks not same type, bail to default return NS_OK; } // at end of text node and deleted? if ((offset == (PRInt32)strLength) && (aAction == nsIEditor::eDeleteNext)) { nsCOMPtr nextNode; res = GetNextHTMLNode(node, &nextNode); if (NS_FAILED(res)) return res; // if there is no next node, or it's not in the body, then cancel the deletion if (!nextNode || !InBody(nextNode)) { *aCancel = PR_TRUE; return res; } // block parents the same? if (mEditor->HasSameBlockNodeParent(node, nextNode)) { // is next node not a container? (ie, a br, hr, image...) if (!mEditor->IsContainer(nextNode)) // MOOSE: anchors not handled { // delete the break, and join like nodes if appropriate res = mEditor->DeleteNode(nextNode); if (NS_FAILED(res)) return res; // get new next node res = GetNextHTMLNode(node, &nextNode); if (NS_FAILED(res)) return res; // are they in same block? if (mEditor->HasSameBlockNodeParent(node, nextNode)) { // are they same type? if (mEditor->NodesSameType(node, nextNode)) { // if so, join them! nsCOMPtr topParent; nextNode->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(nextNode,node,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } } } // is next node inline and same type? (This shouldn't happen) if ( mEditor->HasSameBlockNodeParent(node, nextNode) && ( mEditor->IsInlineNode(node) || mEditor->IsTextNode(node) ) && mEditor->NodesSameType(node, nextNode) ) { // if so, join them! nsCOMPtr topParent; nextNode->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(node,nextNode,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } else return NS_OK; // punt to default } // deleting across blocks // are the blocks of same type? nsCOMPtr leftParent = mEditor->GetBlockNodeParent(node); nsCOMPtr rightParent = mEditor->GetBlockNodeParent(nextNode); if (mEditor->NodesSameType(leftParent, rightParent)) { nsCOMPtr topParent; leftParent->GetParentNode(getter_AddRefs(topParent)); *aHandled = PR_TRUE; res = JoinNodesSmart(leftParent,rightParent,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } // else blocks not same type, bail to default return NS_OK; } } // else not in text node; we need to find right place to act on else { nsCOMPtr nodeToDelete; if (aAction == nsIEditor::eDeletePrevious) res = GetPriorHTMLNode(node, offset, &nodeToDelete); else if (aAction == nsIEditor::eDeleteNext) res = GetNextHTMLNode(node, offset, &nodeToDelete); else return NS_OK; if (NS_FAILED(res)) return res; if (!nodeToDelete) return NS_ERROR_NULL_POINTER; // if this node is text node, adjust selection if (nsEditor::IsTextNode(nodeToDelete)) { PRUint32 selPoint = 0; nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(nodeToDelete); if (aAction == nsIEditor::eDeletePrevious) nodeAsText->GetLength(&selPoint); res = aSelection->Collapse(nodeToDelete,selPoint); return res; } else { // editable leaf node is not text; delete it. // that's the default behavior res = nsEditor::GetNodeLocation(nodeToDelete, &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 // adjusting code puts it back in. doh if (IsMozBR(nodeToDelete)) { nsCOMPtr brNode; res = GetPriorHTMLNode(nodeToDelete, &brNode); if (IsBreak(brNode)) { // is brNode also a descendant of same block? nsCOMPtr block, brBlock; block = mEditor->GetBlockNodeParent(nodeToDelete); brBlock = mEditor->GetBlockNodeParent(brNode); if (block == brBlock) { // delete both breaks res = mEditor->DeleteNode(brNode); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(nodeToDelete); *aHandled = PR_TRUE; return res; } // else fall through } // else fall through } // adjust selection to be right after it res = aSelection->Collapse(node, offset+1); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(nodeToDelete); *aHandled = PR_TRUE; return res; } } return NS_OK; } // else we have a non collapsed selection // figure out if the enpoints are in nodes that can be merged nsCOMPtr endNode; PRInt32 endOffset; res = mEditor->GetEndNodeAndOffset(aSelection, &endNode, &endOffset); if (NS_FAILED(res)) { return res; } if (endNode.get() != node.get()) { // block parents the same? use default deletion if (mEditor->HasSameBlockNodeParent(node, endNode)) return NS_OK; // deleting across blocks // 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 (IsBody(node)) leftParent = node; else leftParent = mEditor->GetBlockNodeParent(node); if (IsBody(endNode)) rightParent = endNode; else rightParent = mEditor->GetBlockNodeParent(endNode); // are the blocks siblings? nsCOMPtr leftBlockParent; nsCOMPtr rightBlockParent; leftParent->GetParentNode(getter_AddRefs(leftBlockParent)); rightParent->GetParentNode(getter_AddRefs(rightBlockParent)); // bail to default if blocks aren't siblings if (leftBlockParent.get() != rightBlockParent.get()) return NS_OK; if (mEditor->NodesSameType(leftParent, rightParent)) { nsCOMPtr topParent; leftParent->GetParentNode(getter_AddRefs(topParent)); if (IsParagraph(leftParent)) { // first delete the selection *aHandled = PR_TRUE; res = mEditor->DeleteSelectionImpl(aAction); if (NS_FAILED(res)) return res; // then join para's, insert break res = mEditor->JoinNodeDeep(leftParent,rightParent,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } if (IsListItem(leftParent) || IsHeader(leftParent)) { // first delete the selection *aHandled = PR_TRUE; res = mEditor->DeleteSelectionImpl(aAction); if (NS_FAILED(res)) return res; // join blocks res = mEditor->JoinNodeDeep(leftParent,rightParent,&selNode,&selOffset); if (NS_FAILED(res)) return res; // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } } // else blocks not same type, bail to default return NS_OK; } return res; } nsresult nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, PRBool aOrdered, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; *aHandled = PR_FALSE; nsAutoString blockType("ul"); if (aOrdered) blockType = "ol"; nsAutoSelectionReset selectionResetter(aSelection); PRBool outMakeEmpty; res = ShouldMakeEmptyBlock(aSelection, &blockType, &outMakeEmpty); if (NS_FAILED(res)) return res; if (outMakeEmpty) return NS_OK; // ok, we aren't creating a new empty list. Instead we are converting // the set of blocks implied by the selection into a list. // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate // block parent, and then further expands to include any ancestors // whose children are all in the range *aHandled = PR_TRUE; nsCOMPtr arrayOfRanges; res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeList); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeList); if (NS_FAILED(res)) return res; // Remove all non-editable nodes. Leave them be. PRUint32 listCount; PRInt32 i; arrayOfNodes->Count(&listCount); for (i=(PRInt32)listCount-1; i>=0; i--) { nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr testNode( do_QueryInterface(isupports ) ); if (!mEditor->IsEditable(testNode)) { arrayOfNodes->RemoveElementAt(i); } } // if there is only one node in the array, and it is a list, div, or blockquote, // then look inside of it until we find what we want to make a list out of. arrayOfNodes->Count(&listCount); if (listCount == 1) { nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); nsCOMPtr curNode( do_QueryInterface(isupports) ); while (IsDiv(curNode) || IsOrderedList(curNode) || IsUnorderedList(curNode) || IsBlockquote(curNode)) { // dive as long as there is only one child, and it is a list, div, or blockquote PRUint32 numChildren; res = mEditor->CountEditableChildren(curNode, numChildren); if (NS_FAILED(res)) return res; if (numChildren == 1) { // keep diving nsCOMPtr tmpNode = nsEditor::GetChildAt(curNode, 0); if (IsDiv(tmpNode) || IsOrderedList(tmpNode) || IsUnorderedList(tmpNode) || IsBlockquote(tmpNode)) { // check editablility XXX floppy moose curNode = tmpNode; } else break; } else break; } // we've found innermost list/blockquote/div: // replace the one node in the array with this node isupports = do_QueryInterface(curNode); arrayOfNodes->ReplaceElementAt(isupports, 0); } // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. nsVoidArray transitionList; res = MakeTransitionList(arrayOfNodes, &transitionList); if (NS_FAILED(res)) return res; // Ok, now go through all the nodes and put then in the list, // or whatever is approriate. Wohoo! arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curList; nsCOMPtr prevListItem; for (i=0; i<(PRInt32)listCount; i++) { // here's where we actually figure out what to do nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports ) ); PRInt32 offset; res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; if (transitionList[i] && // transition node ((((i+1)<(PRInt32)listCount) && transitionList[i+1]) || // and next node is transistion node ( i+1 >= (PRInt32)listCount))) // or there is no next node { // the parent of this node has no other children on the // list of nodes to make a list out of. So if this node // is a list, change it's list type if needed instead of // reparenting it nsCOMPtr newBlock; if (IsUnorderedList(curNode)) { if (aOrdered) { // make a new ordered list, insert it where the current unordered list is, // and move all the children to the new list, and remove the old list res = ReplaceContainer(curNode,&newBlock,blockType); if (NS_FAILED(res)) return res; curList = newBlock; continue; } else { // do nothing, we are already the right kind of list curList = newBlock; continue; } } else if (IsOrderedList(curNode)) { if (!aOrdered) { // make a new unordered list, insert it where the current ordered list is, // and move all the children to the new list, and remove the old list ReplaceContainer(curNode,&newBlock,blockType); if (NS_FAILED(res)) return res; curList = newBlock; continue; } else { // do nothing, we are already the right kind of list curList = newBlock; continue; } } else if (IsDiv(curNode) || IsBlockquote(curNode)) { // XXX floppy moose } } // lonely node // need to make a list to put things in if we haven't already, // or if this node doesn't go in list we used earlier. if (!curList || transitionList[i]) { nsAutoString listType; if (aOrdered) listType = "ol"; else listType = "ul"; res = mEditor->CreateNode(listType, curParent, offset, getter_AddRefs(curList)); if (NS_FAILED(res)) return res; // curList is now the correct thing to put curNode in prevListItem = 0; } // if curNode is a Break, delete it, and quit remembering prev list item if (IsBreak(curNode)) { res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; prevListItem = 0; continue; } // if curNode isn't a list item, we must wrap it in one nsCOMPtr listItem; if (!IsListItem(curNode)) { if (nsEditor::IsInlineNode(curNode) && prevListItem) { // this is a continuation of some inline nodes that belong together in // the same list item. use prevListItem PRUint32 listItemLen; res = mEditor->GetLengthOfDOMNode(prevListItem, listItemLen); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(curNode, prevListItem, listItemLen); if (NS_FAILED(res)) return res; } else { // don't wrap li around a paragraph. instead replace paragraph with li if (IsParagraph(curNode)) { res = ReplaceContainer(curNode, &listItem, "li"); } else { res = InsertContainerAbove(curNode, &listItem, "li"); } if (NS_FAILED(res)) return res; if (nsEditor::IsInlineNode(curNode)) prevListItem = listItem; } } else { listItem = curNode; } if (listItem) // if we made a new list item, deal with it { // tuck the listItem into the end of the active list PRUint32 listLen; res = mEditor->GetLengthOfDOMNode(curList, listLen); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(listItem); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(listItem, curList, listLen); if (NS_FAILED(res)) return res; } } return res; } nsresult nsHTMLEditRules::WillRemoveList(nsIDOMSelection *aSelection, PRBool aOrdered, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_TRUE; nsAutoString blockType("ul"); if (aOrdered) blockType = "ol"; nsAutoSelectionReset selectionResetter(aSelection); nsCOMPtr arrayOfRanges; nsresult res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeList); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeList); if (NS_FAILED(res)) return res; // Remove all non-editable nodes. Leave them be. PRUint32 listCount; PRInt32 i; arrayOfNodes->Count(&listCount); for (i=listCount-1; i>=0; i--) { nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr testNode( do_QueryInterface(isupports ) ); if (!mEditor->IsEditable(testNode)) { arrayOfNodes->RemoveElementAt(i); } } // Only act on lists or list items in the array nsCOMPtr curParent; for (i=0; i<(PRInt32)listCount; i++) { // here's where we actually figure out what to do nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports ) ); PRInt32 offset; res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; if (IsListItem(curNode)) // unlist this listitem { PRBool bOutOfList; do { res = PopListItem(curNode, &bOutOfList); if (NS_FAILED(res)) return res; } while (!bOutOfList); // keep popping it out until it's not in a list anymore } else if (IsList(curNode)) // node is a list, move list items out { nsCOMPtr child; curNode->GetLastChild(getter_AddRefs(child)); while (child) { if (IsListItem(child)) { PRBool bOutOfList; do { res = PopListItem(child, &bOutOfList); if (NS_FAILED(res)) return res; } while (!bOutOfList); // keep popping it out until it's not in a list anymore } else { // delete any non- list items for now res = mEditor->DeleteNode(child); if (NS_FAILED(res)) return res; } curNode->GetLastChild(getter_AddRefs(child)); } // delete the now-empty list res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; } } return res; } nsresult nsHTMLEditRules::WillMakeBasicBlock(nsIDOMSelection *aSelection, const nsString *aBlockType, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_FALSE; PRBool makeEmpty; nsresult res = ShouldMakeEmptyBlock(aSelection, aBlockType, &makeEmpty); if (NS_FAILED(res)) return res; if (makeEmpty) return res; // just insert a new empty block // else it's not that easy... nsAutoSelectionReset selectionResetter(aSelection); *aHandled = PR_TRUE; nsCOMPtr arrayOfRanges; res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeBasicBlock); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeBasicBlock); if (NS_FAILED(res)) return res; // Ok, now go through all the nodes and make the right kind of blocks, // or whatever is approriate. Wohoo! res = ApplyBlockStyle(arrayOfNodes, aBlockType); return res; } nsresult nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool * aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; *aHandled = PR_TRUE; nsAutoSelectionReset selectionResetter(aSelection); // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate // block parent, and then further expands to include any ancestors // whose children are all in the range nsCOMPtr arrayOfRanges; res = GetPromotedRanges(aSelection, &arrayOfRanges, kIndent); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kIndent); if (NS_FAILED(res)) return res; // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. nsVoidArray transitionList; res = MakeTransitionList(arrayOfNodes, &transitionList); if (NS_FAILED(res)) return res; // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! PRUint32 listCount; PRInt32 i; arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curQuote; nsCOMPtr curList; for (i=0; i<(PRInt32)listCount; i++) { // here's where we actually figure out what to do nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports ) ); PRInt32 offset; res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; // some logic for putting list items into nested lists... if (IsList(curParent)) { if (!curList || transitionList[i]) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); // create a new nested list of correct type res = mEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); if (NS_FAILED(res)) return res; // curList is now the correct thing to put curNode in } // tuck the node into the end of the active list PRUint32 listLen; res = mEditor->GetLengthOfDOMNode(curList, listLen); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(curNode, curList, listLen); if (NS_FAILED(res)) return res; } else // not a list item, use blockquote { // need to make a blockquote to put things in if we haven't already, // or if this node doesn't go in blockquote we used earlier. if (!curQuote || transitionList[i]) { nsAutoString quoteType("blockquote"); if (mEditor->CanContainTag(curParent,quoteType)) { res = mEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote)); if (NS_FAILED(res)) return res; // curQuote is now the correct thing to put curNode in } else { printf("trying to put a blockquote in a bad place\n"); } } // tuck the node into the end of the active blockquote PRUint32 quoteLen; res = mEditor->GetLengthOfDOMNode(curQuote, quoteLen); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(curNode, curQuote, quoteLen); if (NS_FAILED(res)) return res; } } return res; } nsresult nsHTMLEditRules::WillOutdent(nsIDOMSelection *aSelection, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; *aHandled = PR_TRUE; nsAutoSelectionReset selectionResetter(aSelection); nsresult res = NS_OK; // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate // block parent, and then further expands to include any ancestors // whose children are all in the range nsCOMPtr arrayOfRanges; res = GetPromotedRanges(aSelection, &arrayOfRanges, kOutdent); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kOutdent); if (NS_FAILED(res)) return res; // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. nsVoidArray transitionList; res = MakeTransitionList(arrayOfNodes, &transitionList); if (NS_FAILED(res)) return res; // Ok, now go through all the nodes and remove a level of blockquoting, // or whatever is appropriate. Wohoo! PRUint32 listCount; PRInt32 i; arrayOfNodes->Count(&listCount); nsCOMPtr curParent; for (i=0; i<(PRInt32)listCount; i++) { // here's where we actually figure out what to do nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports ) ); PRInt32 offset; res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; if (IsList(curParent)) // move node out of list { if (IsList(curNode)) // just unwrap this sublist { res = RemoveContainer(curNode); if (NS_FAILED(res)) return res; } else // we are moving a list item, but not whole list { PRBool bOutOfList; res = PopListItem(curNode, &bOutOfList); if (NS_FAILED(res)) return res; } } else if (IsList(curNode)) // node is a list, but parent is non-list: move list items out { nsCOMPtr child; curNode->GetLastChild(getter_AddRefs(child)); while (child) { if (IsListItem(child)) { PRBool bOutOfList; res = PopListItem(child, &bOutOfList); if (NS_FAILED(res)) return res; } else { // delete any non- list items for now res = mEditor->DeleteNode(child); if (NS_FAILED(res)) return res; } curNode->GetLastChild(getter_AddRefs(child)); } // delete the now-empty list res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; } else if (transitionList[i]) // not list related - look for enclosing blockquotes and remove { // look for a blockquote somewhere above us and remove it. // this is a hack until i think about outdent for real. nsCOMPtr n = curNode; nsCOMPtr tmp; while (!IsBody(n)) { if (IsBlockquote(n)) { RemoveContainer(n); break; } n->GetParentNode(getter_AddRefs(tmp)); n = tmp; } } } return res; } nsresult nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, const nsString *alignType, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); if (NS_FAILED(res)) return res; // initialize out param // we want to ignore result of WillInsert() *aCancel = PR_FALSE; *aHandled = PR_FALSE; nsAutoSelectionReset selectionResetter(aSelection); PRBool outMakeEmpty; res = ShouldMakeEmptyBlock(aSelection, alignType, &outMakeEmpty); if (NS_FAILED(res)) return res; if (outMakeEmpty) return NS_OK; // convert the selection ranges into "promoted" selection ranges: // this basically just expands the range to include the immediate // block parent, and then further expands to include any ancestors // whose children are all in the range *aHandled = PR_TRUE; nsCOMPtr arrayOfRanges; res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign); if (NS_FAILED(res)) return res; // use these ranges to contruct a list of nodes to act on. nsCOMPtr arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign); if (NS_FAILED(res)) return res; // Next we detect all the transitions in the array, where a transition // means that adjacent nodes in the array don't have the same parent. nsVoidArray transitionList; res = MakeTransitionList(arrayOfNodes, &transitionList); if (NS_FAILED(res)) return res; // Ok, now go through all the nodes and give them an align attrib or put them in a div, // or whatever is appropriate. Wohoo! PRUint32 listCount; PRInt32 i; arrayOfNodes->Count(&listCount); nsCOMPtr curParent; nsCOMPtr curDiv; for (i=0; i<(PRInt32)listCount; i++) { // here's where we actually figure out what to do nsCOMPtr isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); nsCOMPtr curNode( do_QueryInterface(isupports ) ); PRInt32 offset; res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; // if it's a div, don't nest it, just set the alignment if (IsDiv(curNode)) { nsCOMPtr divElem = do_QueryInterface(curNode); nsAutoString attr("align"); res = mEditor->SetAttribute(divElem, attr, *alignType); if (NS_FAILED(res)) return res; // clear out curDiv so that we don't put nodes after this one into it curDiv = 0; continue; } // need to make a div to put things in if we haven't already, // or if this node doesn't go in div we used earlier. if (!curDiv || transitionList[i]) { nsAutoString divType("div"); res = mEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv)); if (NS_FAILED(res)) return res; // set up the alignment on the div nsCOMPtr divElem = do_QueryInterface(curDiv); nsAutoString attr("align"); res = mEditor->SetAttribute(divElem, attr, *alignType); if (NS_FAILED(res)) return res; // curDiv is now the correct thing to put curNode in } // tuck the node into the end of the active div PRUint32 listLen; res = mEditor->GetLengthOfDOMNode(curDiv, listLen); if (NS_FAILED(res)) return res; res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(curNode, curDiv, listLen); if (NS_FAILED(res)) return res; } return res; } /******************************************************** * helper methods ********************************************************/ /////////////////////////////////////////////////////////////////////////// // IsHeader: true if node an html header // PRBool nsHTMLEditRules::IsHeader(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsHeader"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if ( (tag == "h1") || (tag == "h2") || (tag == "h3") || (tag == "h4") || (tag == "h5") || (tag == "h6") ) { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsParagraph: true if node an html paragraph // PRBool nsHTMLEditRules::IsParagraph(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsParagraph"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "p") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsListItem: true if node an html list item // PRBool nsHTMLEditRules::IsListItem(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsListItem"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "li") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsTableCell: true if node an html td or th // PRBool nsHTMLEditRules::IsTableCell(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsTableCell"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "td" || tag == "th") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsList: true if node an html list // PRBool nsHTMLEditRules::IsList(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsList"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if ( (tag == "ol") || (tag == "ul") ) { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsOrderedList: true if node an html orderd list // PRBool nsHTMLEditRules::IsOrderedList(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsOrderedList"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "ol") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsUnorderedList: true if node an html orderd list // PRBool nsHTMLEditRules::IsUnorderedList(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsUnorderedList"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "ul") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsBreak: true if node an html break node // PRBool nsHTMLEditRules::IsBreak(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBreak"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "br") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsBody: true if node an html body node // PRBool nsHTMLEditRules::IsBody(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBody"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "body") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsBlockquote: true if node an html blockquote node // PRBool nsHTMLEditRules::IsBlockquote(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBlockquote"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "blockquote") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsAnchor: true if node an html anchor node // PRBool nsHTMLEditRules::IsAnchor(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsAnchor"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "a") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsDiv: true if node an html div node // PRBool nsHTMLEditRules::IsDiv(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsDiv"); nsAutoString tag; nsEditor::GetTagString(node,tag); tag.ToLowerCase(); if (tag == "div") { return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsNormalDiv: true if node an html div node, without type = _moz // PRBool nsHTMLEditRules::IsNormalDiv(nsIDOMNode *node) { if (IsDiv(node) && !HasMozAttr(node)) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsMozDiv: true if node an html div node with type = _moz // PRBool nsHTMLEditRules::IsMozDiv(nsIDOMNode *node) { if (IsDiv(node) && HasMozAttr(node)) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsMozBR: true if node an html br node with type = _moz // PRBool nsHTMLEditRules::IsMozBR(nsIDOMNode *node) { if (IsBreak(node) && HasMozAttr(node)) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // HasMozAttr: true if node has type attribute = _moz // (used to indicate the div's and br's we use in // mail compose rules) // PRBool nsHTMLEditRules::HasMozAttr(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::HasMozAttr"); nsCOMPtr elem = do_QueryInterface(node); if (elem) { nsAutoString typeAttrName("type"); nsAutoString typeAttrVal; nsresult res = elem->GetAttribute(typeAttrName, typeAttrVal); typeAttrVal.ToLowerCase(); if (NS_SUCCEEDED(res) && (typeAttrVal == "_moz")) return PR_TRUE; } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsMailCite: true if node an html blockquote with type=cite // PRBool nsHTMLEditRules::IsMailCite(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsMailCite"); if (IsBlockquote(node)) { nsCOMPtr bqElem = do_QueryInterface(node); nsAutoString typeAttrName("type"); nsAutoString typeAttrVal; nsresult res = bqElem->GetAttribute(typeAttrName, typeAttrVal); typeAttrVal.ToLowerCase(); if (NS_SUCCEEDED(res)) { if (typeAttrVal.Equals("cite", PR_TRUE, 4)) return PR_TRUE; } } return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // InBody: true if node is a descendant of the body // PRBool nsHTMLEditRules::InBody(nsIDOMNode *node) { NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::InBody"); nsCOMPtr tmp; nsCOMPtr p = do_QueryInterface(node); while (p && !IsBody(p)) { if ( NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree return PR_FALSE; p = tmp; } if (p) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsEmptyBlock: figure out if aNode is (or is inside) an empty block. // A block can have children and still be considered empty, // if the children are empty or non-editable. // nsresult nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, PRBool *outIsEmptyBlock, PRBool aMozBRDoesntCount, PRBool aListItemsNotEmpty) { if (!aNode || !outIsEmptyBlock) return NS_ERROR_NULL_POINTER; *outIsEmptyBlock = PR_TRUE; // nsresult res = NS_OK; nsCOMPtr nodeToTest; if (nsEditor::IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode); // else nsCOMPtr block; // looks like I forgot to finish this. Wonder what I was going to do? if (!nodeToTest) return NS_ERROR_NULL_POINTER; return IsEmptyNode(nodeToTest, outIsEmptyBlock, aMozBRDoesntCount, aListItemsNotEmpty); } /////////////////////////////////////////////////////////////////////////// // IsEmptyNode: figure out if aNode is an empty node. // A block can have children and still be considered empty, // if the children are empty or non-editable. // nsresult nsHTMLEditRules::IsEmptyNode( nsIDOMNode *aNode, PRBool *outIsEmptyNode, PRBool aMozBRDoesntCount, PRBool aListItemsNotEmpty) { if (!aNode || !outIsEmptyNode) return NS_ERROR_NULL_POINTER; *outIsEmptyNode = PR_TRUE; // effeciency hack - special case if it's a text node if (nsEditor::IsTextNode(aNode)) { PRUint32 length = 0; nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(aNode); nodeAsText->GetLength(&length); if (length) *outIsEmptyNode = PR_FALSE; return NS_OK; } // if it's not a text node (handled above) and it's not a container, // then we dont call it empty (it's an
, or
, etc). // Also, if it's an anchor then dont treat it as empty - even though // anchors are containers, named anchors are "empty" but we don't // want to treat them as such. Also, don't call ListItems or table // cells empty if caller desires. if (!mEditor->IsContainer(aNode) || IsAnchor(aNode) || (aListItemsNotEmpty &&IsListItem(aNode)) || (aListItemsNotEmpty &&IsTableCell(aNode)) ) { *outIsEmptyNode = PR_FALSE; return NS_OK; } // iterate over node. if no children, or all children are either // empty text nodes or non-editable, then node qualifies as empty nsCOMPtr iter; nsCOMPtr nodeAsContent = do_QueryInterface(aNode); if (!nodeAsContent) return NS_ERROR_FAILURE; nsresult res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; res = iter->Init(nodeAsContent); if (NS_FAILED(res)) return res; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; // is the node editable and non-empty? if so, return false if (mEditor->IsEditable(node)) { if (nsEditor::IsTextNode(node)) { PRUint32 length = 0; nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(node); nodeAsText->GetLength(&length); if (length) *outIsEmptyNode = PR_FALSE; } else // an editable, non-text node. we aren't an empty block { // is it the node we are iterating over? if (node.get() == aNode) break; // is it a moz-BR and did the caller ask us not to consider those relevant? if (!(aMozBRDoesntCount && IsMozBR(node))) { // otherwise it ain't empty *outIsEmptyNode = PR_FALSE; break; } } } res = iter->Next(); if (NS_FAILED(res)) return res; } return NS_OK; } PRBool nsHTMLEditRules::IsDescendantOf(nsIDOMNode *aNode, nsIDOMNode *aParent) { if (!aNode && !aParent) return PR_FALSE; if (aNode == aParent) return PR_FALSE; nsCOMPtr parent, node = do_QueryInterface(aNode); nsresult res; do { res = node->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(res)) return PR_FALSE; if (parent.get() == aParent) return PR_TRUE; node = parent; } while (parent); return PR_FALSE; } // not needed at moment - leaving around in case we go back to it. #if 0 /////////////////////////////////////////////////////////////////////////// // CreateMozDiv: makes a div with type = _moz // nsresult nsHTMLEditRules::CreateMozDiv(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outDiv) { if (!inParent || !outDiv) return NS_ERROR_NULL_POINTER; nsAutoString divType= "div"; *outDiv = nsnull; nsresult res = mEditor->CreateNode(divType, inParent, inOffset, getter_AddRefs(*outDiv)); if (NS_FAILED(res)) return res; // give it special moz attr nsCOMPtr mozDivElem = do_QueryInterface(*outDiv); res = mEditor->SetAttribute(mozDivElem, "type", "_moz"); if (NS_FAILED(res)) return res; res = AddTrailerBR(*outDiv); return res; } #endif /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLSibling: returns the previous editable sibling, if there is // one within the parent // nsresult nsHTMLEditRules::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; nsCOMPtr temp, node = do_QueryInterface(inNode); while (1) { res = node->GetPreviousSibling(getter_AddRefs(temp)); if (NS_FAILED(res)) return res; if (!temp) return NS_ERROR_FAILURE; // if it's editable, we're done if (mEditor->IsEditable(temp)) break; // otherwise try again node = temp; } *outNode = temp; return res; } /////////////////////////////////////////////////////////////////////////// // GetNextHTMLSibling: returns the next editable sibling, if there is // one within the parent // nsresult nsHTMLEditRules::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; nsCOMPtr temp, node = do_QueryInterface(inNode); while (1) { res = node->GetNextSibling(getter_AddRefs(temp)); if (NS_FAILED(res)) return res; if (!temp) return NS_ERROR_FAILURE; // if it's editable, we're done if (mEditor->IsEditable(temp)) break; // otherwise try again node = temp; } *outNode = temp; return res; } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLNode: returns the previous editable leaf node, if there is // one within the // nsresult nsHTMLEditRules::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = mEditor->GetPriorNode(inNode, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !InBody(*outNode)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node // nsresult nsHTMLEditRules::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = mEditor->GetPriorNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !InBody(*outNode)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetNextHTMLNode: returns the previous editable leaf node, if there is // one within the // nsresult nsHTMLEditRules::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = mEditor->GetNextNode(inNode, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !InBody(*outNode)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetNHTMLextNode: same as above but takes {parent,offset} instead of node // nsresult nsHTMLEditRules::GetNextHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = mEditor->GetNextNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !InBody(*outNode)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetTabAsNBSPs: stuff the right number of nbsp's into outString // nsresult nsHTMLEditRules::GetTabAsNBSPs(nsString *outString) { if (!outString) return NS_ERROR_NULL_POINTER; // XXX - this should get the right number from prefs *outString = (char)nbsp; *outString += nbsp; *outString += nbsp; *outString += nbsp; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetTabAsNBSPsAndSpace: stuff the right number of nbsp's followed by a // space into outString nsresult nsHTMLEditRules::GetTabAsNBSPsAndSpace(nsString *outString) { if (!outString) return NS_ERROR_NULL_POINTER; // XXX - this should get the right number from prefs *outString = (char)nbsp; *outString += nbsp; *outString += nbsp; *outString += ' '; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsFirstNode: Are we the first edittable node in our parent? // PRBool nsHTMLEditRules::IsFirstNode(nsIDOMNode *aNode) { nsCOMPtr parent; PRInt32 offset, j=0; nsEditor::GetNodeLocation(aNode, &parent, &offset); if (!offset) // easy case, we are first dom child return PR_TRUE; // ok, so there are earlier children. But are they editable??? nsCOMPtrchildList; nsCOMPtr child; if (!parent) return PR_TRUE; parent->GetChildNodes(getter_AddRefs(childList)); while (j < offset) { childList->Item(j, getter_AddRefs(child)); if (mEditor->IsEditable(child)) return PR_FALSE; j++; } return PR_TRUE; } /////////////////////////////////////////////////////////////////////////// // IsLastNode: Are we the first edittable node in our parent? // PRBool nsHTMLEditRules::IsLastNode(nsIDOMNode *aNode) { nsCOMPtr parent; PRInt32 offset, j; PRUint32 numChildren; nsEditor::GetNodeLocation(aNode, &parent, &offset); nsEditor::GetLengthOfDOMNode(parent, numChildren); if (offset+1 == (PRInt32)numChildren) // easy case, we are last dom child return PR_TRUE; // ok, so there are later children. But are they editable??? j = offset+1; nsCOMPtrchildList; nsCOMPtr child; parent->GetChildNodes(getter_AddRefs(childList)); while (j < (PRInt32)numChildren) { childList->Item(j, getter_AddRefs(child)); if (mEditor->IsEditable(child)) return PR_FALSE; j++; } return PR_TRUE; } /////////////////////////////////////////////////////////////////////////// // AtStartOfBlock: is node/offset at the start of the editable material in this block? // PRBool nsHTMLEditRules::AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock) { nsCOMPtr nodeAsText = do_QueryInterface(aNode); if (nodeAsText && aOffset) return PR_FALSE; // there are chars in front of us nsCOMPtr priorNode; nsresult res = GetPriorHTMLNode(aNode, aOffset, &priorNode); if (NS_FAILED(res)) return PR_TRUE; if (!priorNode) return PR_TRUE; nsCOMPtr blockParent = mEditor->GetBlockNodeParent(priorNode); if (blockParent && (blockParent.get() == aBlock)) return PR_FALSE; return PR_TRUE; } /////////////////////////////////////////////////////////////////////////// // AtEndOfBlock: is node/offset at the end of the editable material in this block? // PRBool nsHTMLEditRules::AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock) { nsCOMPtr nodeAsText = do_QueryInterface(aNode); if (nodeAsText) { PRUint32 strLength; nodeAsText->GetLength(&strLength); if (strLength > aOffset) return PR_FALSE; // there are chars in after us } nsCOMPtr nextNode; nsresult res = GetNextHTMLNode(aNode, aOffset, &nextNode); if (NS_FAILED(res)) return PR_TRUE; if (!nextNode) return PR_TRUE; nsCOMPtr blockParent = mEditor->GetBlockNodeParent(nextNode); if (blockParent && (blockParent.get() == aBlock)) return PR_FALSE; return PR_TRUE; } /////////////////////////////////////////////////////////////////////////// // GetPromotedPoint: figure out where a start or end point for a block // operation really is nsresult nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt32 aOffset, PRInt32 actionID, nsCOMPtr *outNode, PRInt32 *outOffset) { nsresult res = NS_OK; nsCOMPtr node = aNode; nsCOMPtr parent = aNode; PRInt32 offset = aOffset; // we do one thing for InsertText actions, something else entirely for other actions if (actionID == kInsertText) { PRBool isSpace, isNBSP; nsCOMPtr temp; // for insert text or delete actions, we want to look backwards (or forwards, as appropriate) // for additional whitespace or nbsp's. We may have to act on these later even though // they are outside of the initial selection. Even if they are in another node! if (aWhere == kStart) { do { res = mEditor->IsPrevCharWhitespace(node, offset, &isSpace, &isNBSP, &temp, &offset); if (NS_FAILED(res)) return res; if (isSpace || isNBSP) node = temp; else break; } while (node); *outNode = node; *outOffset = offset; } else if (aWhere == kEnd) { do { res = mEditor->IsNextCharWhitespace(node, offset, &isSpace, &isNBSP, &temp, &offset); if (NS_FAILED(res)) return res; if (isSpace || isNBSP) node = temp; else break; } while (node); *outNode = node; *outOffset = offset; } return res; } // else not kInsertText. In this case we want to see if we should // grab any adjacent inlline nodes and/or parents and other ancestors if (IsBody(aNode)) { // we cant go any higher *outNode = do_QueryInterface(aNode); *outOffset = aOffset; return res; } if (aWhere == kStart) { // some special casing for text nodes if (nsEditor::IsTextNode(aNode)) { res = nsEditor::GetNodeLocation(aNode, &parent, &offset); if (NS_FAILED(res)) return res; } else { node = nsEditor::GetChildAt(parent,offset); if (!node) node = parent; } // if this is an inline node who's block parent is the body, // back up through any prior inline nodes that // aren't across a
from us. if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); // if (IsBody(block)) // { nsCOMPtr prevNode; prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); while (prevNode) { if (IsBreak(prevNode)) break; if (nsEditor::IsBlockNode(prevNode)) break; node = prevNode; prevNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterBackward); } // } // else // { // just grap the whole block // node = block; // } } // finding the real start for this point. look up the tree for as long as we are the // first node in the container, and as long as we haven't hit the body node. nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; while ((IsFirstNode(node)) && (!IsBody(parent))) { node = parent; res = nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; } *outNode = parent; *outOffset = offset; return res; } if (aWhere == kEnd) { // some special casing for text nodes if (nsEditor::IsTextNode(aNode)) { nsEditor::GetNodeLocation(aNode, &parent, &offset); } else { node = nsEditor::GetChildAt(parent,offset); if (!node) node = parent; } if (!node) node = parent; // if this is an inline node who's block parent is the body, // look ahead through any further inline nodes that // aren't across a
from us, and that are enclosed in the same block. if (!nsEditor::IsBlockNode(node)) { nsCOMPtr block = nsEditor::GetBlockNodeParent(node); // if (IsBody(block)) // { nsCOMPtr nextNode; nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); while (nextNode) { if (IsBreak(nextNode)) break; if (nsEditor::IsBlockNode(nextNode)) break; node = nextNode; nextNode = nsEditor::NextNodeInBlock(node, nsEditor::kIterForward); } // } // else // { // just grap the whole block // node = block; // } } // finding the real end for this point. look up the tree for as long as we are the // last node in the container, and as long as we haven't hit the body node. nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; while ((IsLastNode(node)) && (!IsBody(parent))) { node = parent; res = nsEditor::GetNodeLocation(node, &parent, &offset); if (NS_FAILED(res)) return res; } *outNode = parent; offset++; // add one since this in an endpoint - want to be AFTER node. *outOffset = offset; return res; } return res; } /////////////////////////////////////////////////////////////////////////// // GetPromotedRanges: run all the selection range endpoint through // GetPromotedPoint() // nsresult nsHTMLEditRules::GetPromotedRanges(nsIDOMSelection *inSelection, nsCOMPtr *outArrayOfRanges, PRInt32 inOperationType) { if (!inSelection || !outArrayOfRanges) return NS_ERROR_NULL_POINTER; nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfRanges)); if (NS_FAILED(res)) return res; PRInt32 rangeCount; res = inSelection->GetRangeCount(&rangeCount); if (NS_FAILED(res)) return res; PRInt32 i; nsCOMPtr selectionRange; nsCOMPtr opRange; for (i = 0; i < rangeCount; i++) { res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange)); if (NS_FAILED(res)) return res; // clone range so we dont muck with actual selection ranges res = selectionRange->Clone(getter_AddRefs(opRange)); if (NS_FAILED(res)) return res; // make a new adjusted range to represent the appropriate block content. // The basic idea is to push out the range endpoints // to truly enclose the blocks that we will affect. // This call alters opRange. res = PromoteRange(opRange, inOperationType); if (NS_FAILED(res)) return res; // stuff new opRange into nsISupportsArray nsCOMPtr isupports = do_QueryInterface(opRange); (*outArrayOfRanges)->AppendElement(isupports); } return res; } /////////////////////////////////////////////////////////////////////////// // PromoteRange: expand a range to include any parentsfor which all // editable children are already in range. // nsresult nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, PRInt32 inOperationType) { if (!inRange) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr startNode, endNode; PRInt32 startOffset, endOffset; res = inRange->GetStartParent(getter_AddRefs(startNode)); if (NS_FAILED(res)) return res; res = inRange->GetStartOffset(&startOffset); if (NS_FAILED(res)) return res; res = inRange->GetEndParent(getter_AddRefs(endNode)); if (NS_FAILED(res)) return res; res = inRange->GetEndOffset(&endOffset); if (NS_FAILED(res)) return res; // make a new adjusted range to represent the appropriate block content // this is tricky. the basic idea is to push out the range endpoints // to truly enclose the blocks that we will affect nsCOMPtr opStartNode; nsCOMPtr opEndNode; PRInt32 opStartOffset, opEndOffset; nsCOMPtr opRange; res = GetPromotedPoint( kStart, startNode, startOffset, inOperationType, &opStartNode, &opStartOffset); if (NS_FAILED(res)) return res; res = GetPromotedPoint( kEnd, endNode, endOffset, inOperationType, &opEndNode, &opEndOffset); if (NS_FAILED(res)) return res; inRange->SetStart(opStartNode, opStartOffset); if (NS_FAILED(res)) return res; inRange->SetEnd(opEndNode, opEndOffset); return res; } /////////////////////////////////////////////////////////////////////////// // GetNodesForOperation: run through the ranges in the array and construct // a new array of nodes to be acted on. // nsresult nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges, nsCOMPtr *outArrayOfNodes, PRInt32 inOperationType) { if (!inArrayOfRanges || !outArrayOfNodes) return NS_ERROR_NULL_POINTER; nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); if (NS_FAILED(res)) return res; PRUint32 rangeCount; res = inArrayOfRanges->Count(&rangeCount); if (NS_FAILED(res)) return res; PRInt32 i; nsCOMPtr opRange; nsCOMPtr isupports; nsCOMPtr iter; for (i = 0; i < (PRInt32)rangeCount; i++) { isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i)); opRange = do_QueryInterface(isupports); res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; res = iter->Init(opRange); if (NS_FAILED(res)) return res; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; isupports = do_QueryInterface(node); (*outArrayOfNodes)->AppendElement(isupports); res = iter->Next(); if (NS_FAILED(res)) return res; } } return res; } /////////////////////////////////////////////////////////////////////////// // GetChildNodesForOperation: // nsresult nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, nsCOMPtr *outArrayOfNodes) { if (!inNode || !outArrayOfNodes) return NS_ERROR_NULL_POINTER; nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes)); if (NS_FAILED(res)) return res; nsCOMPtr childNodes; res = inNode->GetChildNodes(getter_AddRefs(childNodes)); if (NS_FAILED(res)) return res; PRUint32 childCount; res = childNodes->GetLength(&childCount); if (NS_FAILED(res)) return res; PRUint32 i; nsCOMPtr node; nsCOMPtr isupports; for (i = 0; i < childCount; i++) { res = childNodes->Item( i, getter_AddRefs(node)); if (!node) return NS_ERROR_FAILURE; isupports = do_QueryInterface(node); (*outArrayOfNodes)->AppendElement(isupports); if (NS_FAILED(res)) return res; } return res; } /////////////////////////////////////////////////////////////////////////// // MakeTransitionList: detect all the transitions in the array, where a // transition means that adjacent nodes in the array // don't have the same parent. // nsresult nsHTMLEditRules::MakeTransitionList(nsISupportsArray *inArrayOfNodes, nsVoidArray *inTransitionArray) { if (!inArrayOfNodes || !inTransitionArray) return NS_ERROR_NULL_POINTER; PRUint32 listCount; PRInt32 i; inArrayOfNodes->Count(&listCount); nsVoidArray transitionList; nsCOMPtr prevElementParent; nsCOMPtr curElementParent; for (i=0; i<(PRInt32)listCount; i++) { nsCOMPtr isupports = (dont_AddRef)(inArrayOfNodes->ElementAt(i)); nsCOMPtr transNode( do_QueryInterface(isupports ) ); transNode->GetParentNode(getter_AddRefs(curElementParent)); if (curElementParent != prevElementParent) { // different parents, or seperated by
: transition point inTransitionArray->InsertElementAt((void*)PR_TRUE,i); } else { // same parents: these nodes grew up together inTransitionArray->InsertElementAt((void*)PR_FALSE,i); } prevElementParent = curElementParent; } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // ReplaceContainer: replace inNode with a new node (outNode) which is contructed // to be of type aNodeType. Put inNodes children into outNode. // Callers responsibility to make sure inNode's children can // go in outNode. nsresult nsHTMLEditRules::ReplaceContainer(nsIDOMNode *inNode, nsCOMPtr *outNode, const nsString &aNodeType) { if (!inNode || !outNode) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr parent; PRInt32 offset; res = nsEditor::GetNodeLocation(inNode, &parent, &offset); if (NS_FAILED(res)) return res; res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; PRBool bHasMoreChildren; inNode->HasChildNodes(&bHasMoreChildren); nsCOMPtr child; offset = 0; while (bHasMoreChildren) { inNode->GetLastChild(getter_AddRefs(child)); res = mEditor->DeleteNode(child); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(child, *outNode, 0); if (NS_FAILED(res)) return res; inNode->HasChildNodes(&bHasMoreChildren); } res = mEditor->DeleteNode(inNode); return res; } /////////////////////////////////////////////////////////////////////////// // RemoveContainer: remove inNode, reparenting it's children into their // the parent of inNode // nsresult nsHTMLEditRules::RemoveContainer(nsIDOMNode *inNode) { if (!inNode) return NS_ERROR_NULL_POINTER; if (IsBody(inNode)) return NS_ERROR_UNEXPECTED; nsresult res; nsCOMPtr parent; PRInt32 offset; res = nsEditor::GetNodeLocation(inNode, &parent, &offset); if (NS_FAILED(res)) return res; PRBool bHasMoreChildren; inNode->HasChildNodes(&bHasMoreChildren); nsCOMPtr child; while (bHasMoreChildren) { inNode->GetLastChild(getter_AddRefs(child)); res = mEditor->DeleteNode(child); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(child, parent, offset); if (NS_FAILED(res)) return res; inNode->HasChildNodes(&bHasMoreChildren); } res = mEditor->DeleteNode(inNode); return res; } /////////////////////////////////////////////////////////////////////////// // InsertContainerAbove: insert a new parent for inNode, returned in outNode, // which is contructed to be of type aNodeType. outNode becomes // a child of inNode's earlier parent. // Callers responsibility to make sure inNode's can be child // of outNode, and outNode can be child of old parent. nsresult nsHTMLEditRules::InsertContainerAbove(nsIDOMNode *inNode, nsCOMPtr *outNode, const nsString &aNodeType) { if (!inNode || !outNode) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr parent; PRInt32 offset; res = nsEditor::GetNodeLocation(inNode, &parent, &offset); if (NS_FAILED(res)) return res; // make new parent, outNode res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // put inNode in new parent, outNode res = mEditor->DeleteNode(inNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(inNode, *outNode, 0); if (NS_FAILED(res)) return res; return NS_OK; } /******************************************************** * main implementation methods ********************************************************/ /////////////////////////////////////////////////////////////////////////// // InsertTab: top level logic for determining how to insert a tab // nsresult nsHTMLEditRules::InsertTab(nsIDOMSelection *aSelection, nsString *outString) { nsCOMPtr parentNode; PRInt32 offset; PRBool isPRE, isNextWhiteSpace, isPrevWhiteSpace, isNextNBSP, isPrevNBSP; nsresult res = mEditor->GetStartNodeAndOffset(aSelection, &parentNode, &offset); if (NS_FAILED(res)) return res; if (!parentNode) return NS_ERROR_FAILURE; res = mEditor->IsPreformatted(parentNode, &isPRE); if (NS_FAILED(res)) return res; if (isPRE) { *outString = '\t'; return NS_OK; } res = mEditor->IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace, &isNextNBSP); if (NS_FAILED(res)) return res; res = mEditor->IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace, &isPrevNBSP); if (NS_FAILED(res)) return res; if (isPrevWhiteSpace) { // prev character is a whitespace; Need to // insert nbsp's BEFORE the space // XXX for now put tab in wrong place if (isNextWhiteSpace) { GetTabAsNBSPs(outString); return NS_OK; } GetTabAsNBSPsAndSpace(outString); return NS_OK; } if (isNextWhiteSpace) { // character after us is ws. insert nbsps GetTabAsNBSPs(outString); return NS_OK; } // else we are in middle of a word; use n-1 nbsp's plus a space GetTabAsNBSPsAndSpace(outString); return NS_OK; } /////////////////////////////////////////////////////////////////////////// // InsertSpace: top level logic for determining how to insert a space // nsresult nsHTMLEditRules::InsertSpace(nsIDOMSelection *aSelection, nsString *outString) { nsCOMPtr parentNode; PRInt32 offset; PRBool isPRE, isNextWhiteSpace, isPrevWhiteSpace, isNextNBSP, isPrevNBSP; nsresult res = mEditor->GetStartNodeAndOffset(aSelection, &parentNode, &offset); if (NS_FAILED(res)) return res; if (!parentNode) return NS_ERROR_FAILURE; res = mEditor->IsPreformatted(parentNode,&isPRE); if (NS_FAILED(res)) return res; if (isPRE) { *outString = " "; return NS_OK; } res = mEditor->IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace, &isNextNBSP); if (NS_FAILED(res)) return res; res = mEditor->IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace, &isPrevNBSP); if (NS_FAILED(res)) return res; if (isPrevWhiteSpace) { // prev character is a whitespace; Need to // insert nbsp BEFORE the space // XXX for now put in wrong place if (isNextWhiteSpace) { *outString = (char)nbsp; return NS_OK; } *outString = (char)nbsp; return NS_OK; } if (isNextWhiteSpace) { // character after us is ws. insert nbsp *outString = (char)nbsp; return NS_OK; } // else just a space *outString = " "; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset} // nsresult nsHTMLEditRules::CreateMozBR(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outBRNode) { if (!inParent || !outBRNode) return NS_ERROR_NULL_POINTER; nsresult res = mEditor->CreateBR(inParent, inOffset, outBRNode); if (NS_FAILED(res)) return res; // give it special moz attr nsCOMPtr brElem = do_QueryInterface(*outBRNode); if (brElem) { res = mEditor->SetAttribute(brElem, "type", "_moz"); if (NS_FAILED(res)) return res; } return res; } /////////////////////////////////////////////////////////////////////////// // ReturnInHeader: do the right thing for returns pressed in headers // nsresult nsHTMLEditRules::ReturnInHeader(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aNode, PRInt32 aOffset) { if (!aSelection || !aHeader || !aNode) return NS_ERROR_NULL_POINTER; // remeber where the header is nsCOMPtr headerParent; PRInt32 offset; nsresult res = nsEditor::GetNodeLocation(aHeader, &headerParent, &offset); if (NS_FAILED(res)) return res; // split the header PRInt32 newOffset; res = mEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset); if (NS_FAILED(res)) return res; // if the new (righthand) header node is empty, delete it PRBool isEmpty; res = IsEmptyBlock(aHeader, &isEmpty, PR_TRUE); if (NS_FAILED(res)) return res; if (isEmpty) { res = mEditor->DeleteNode(aHeader); if (NS_FAILED(res)) return res; res = aSelection->Collapse(headerParent,offset+1); return res; } // else unwrap it nsCOMPtr newBlock; res = RemoveContainer(aHeader); return res; } /////////////////////////////////////////////////////////////////////////// // ReturnInParagraph: do the right thing for returns pressed in paragraphs // nsresult nsHTMLEditRules::ReturnInParagraph(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aNode, PRInt32 aOffset, PRBool *aCancel, PRBool *aHandled) { if (!aSelection || !aHeader || !aNode || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } *aCancel = PR_FALSE; *aHandled = PR_FALSE; nsCOMPtr sibling; nsresult res = NS_OK; // easy case, in a text node: if (mEditor->IsTextNode(aNode)) { nsCOMPtr textNode = do_QueryInterface(aNode); PRUint32 strLength; res = textNode->GetLength(&strLength); if (NS_FAILED(res)) return res; // at beginning of text node? if (!aOffset) { // is there a BR prior to it? GetPriorHTMLSibling(aNode, &sibling); if (!sibling) { // no previous sib, so // just fall out to default of inserting a BR return res; } if (IsBreak(sibling)) { PRInt32 newOffset; *aCancel = PR_TRUE; // get rid of the break res = mEditor->DeleteNode(sibling); if (NS_FAILED(res)) return res; // split the paragraph res = mEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset); if (NS_FAILED(res)) return res; // position selection inside textnode res = aSelection->Collapse(aNode,0); } // else just fall out to default of inserting a BR return res; } // at end of text node? if (aOffset == strLength) { // is there a BR after to it? res = GetNextHTMLSibling(aNode, &sibling); if (!sibling) { // no next sib, so // just fall out to default of inserting a BR return res; } if (IsBreak(sibling)) { PRInt32 newOffset; *aCancel = PR_TRUE; // get rid of the break res = mEditor->DeleteNode(sibling); if (NS_FAILED(res)) return res; // split the paragraph res = mEditor->SplitNodeDeep( aHeader, aNode, aOffset, &newOffset); if (NS_FAILED(res)) return res; // position selection inside textnode res = aSelection->Collapse(aNode,0); } // else just fall out to default of inserting a BR return res; } // inside text node // just fall out to default of inserting a BR return res; } // not in a text node. are we next to BR's? // moose XXX return res; } /////////////////////////////////////////////////////////////////////////// // ReturnInListItem: do the right thing for returns pressed in list items // nsresult nsHTMLEditRules::ReturnInListItem(nsIDOMSelection *aSelection, nsIDOMNode *aListItem, nsIDOMNode *aNode, PRInt32 aOffset) { if (!aSelection || !aListItem || !aNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; nsCOMPtr listitem; // sanity check NS_PRECONDITION(PR_TRUE == IsListItem(aListItem), "expected a list item and didnt get one"); // if we are in an empty listitem, then we want to pop up out of the list PRBool isEmpty; res = IsEmptyBlock(aListItem, &isEmpty, PR_TRUE, PR_FALSE); if (NS_FAILED(res)) return res; if (isEmpty) { nsCOMPtr list, listparent; PRInt32 offset, itemOffset; res = nsEditor::GetNodeLocation(aListItem, &list, &itemOffset); if (NS_FAILED(res)) return res; res = nsEditor::GetNodeLocation(list, &listparent, &offset); if (NS_FAILED(res)) return res; // are we the last list item in the list? PRBool bIsLast; res = IsLastEditableChild(aListItem, &bIsLast); if (NS_FAILED(res)) return res; if (!bIsLast) { // we need to split the list! nsCOMPtr tempNode; res = mEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode)); if (NS_FAILED(res)) return res; } // are we in a sublist? if (IsList(listparent)) //in a sublist { // if so, move this list item out of this list and into the grandparent list res = mEditor->DeleteNode(aListItem); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(aListItem,listparent,offset+1); if (NS_FAILED(res)) return res; res = aSelection->Collapse(aListItem,0); } else { // otherwise kill this listitem and set the selection to after the parent list res = mEditor->DeleteNode(aListItem); if (NS_FAILED(res)) return res; res = aSelection->Collapse(listparent,offset+1); if (NS_FAILED(res)) return res; // now attempt to adjust selection to a text node. // if we fail, then that means we need toinsert a break res = AdjustSelection(aSelection, nsIEditor::nsIEditor::eDeleteNext); if (NS_FAILED(res)) return res; // get the selection location nsCOMPtr selNode; PRInt32 selOffset; res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; // are we in a text node? nsCOMPtr textNode = do_QueryInterface(selNode); if (!textNode) { // time to insert a break nsCOMPtr brNode; nsresult res = CreateMozBR(selNode, selOffset, &brNode); if (NS_FAILED(res)) return res; res = nsEditor::GetNodeLocation(brNode, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode,selOffset+1); if (NS_FAILED(res)) return res; } } return res; } // else we want a new list item at the same list level PRInt32 newOffset; res = mEditor->SplitNodeDeep( aListItem, aNode, aOffset, &newOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(aListItem,0); #if 0 // insert a moz-br if new list item is empty PRBool isEmptyNode; nsCOMPtr brNode; res = IsEmptyNode( aListItem, &isEmptyNode); if (NS_FAILED(res)) return res; if (isEmptyNode) { res = CreateMozBR(aListItem, 0, &brNode); if (NS_FAILED(res)) return res; } #endif return res; } /////////////////////////////////////////////////////////////////////////// // ShouldMakeEmptyBlock: determine if a block transformation should make // a new empty block, or instead transform a block // nsresult nsHTMLEditRules::ShouldMakeEmptyBlock(nsIDOMSelection *aSelection, const nsString *blockTag, PRBool *outMakeEmpty) { // a note about strategy: // this routine will be called by the rules code to figure out // if it should do something, or let the nsHTMLEditor default // action happen. The default action is to insert a new block. // Note that if _nothing_ should happen, ie, the selection is // already entireyl inside a block (or blocks) or the correct type, // then you don't want to return true in outMakeEmpty, since the // defualt code will insert a new empty block anyway, rather than // doing nothing. So we have to detect that case and return false. if (!aSelection || !outMakeEmpty) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; // if the selection is collapsed, and // if we in the body, or after a
with // no more inline content before the next block, then we want // a new block. Otherwise we want to trasform a block // xxx possible bug: selection could be not callapsed, but // still empty. it would be nice to have a call for this: IsEmptySelection() PRBool isCollapsed; res = aSelection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) return res; if (isCollapsed) { nsCOMPtr parent; PRInt32 offset; res = nsEditor::GetStartNodeAndOffset(aSelection, &parent, &offset); if (NS_FAILED(res)) return res; // is selection point in the body? if (IsBody(parent)) { *outMakeEmpty = PR_TRUE; return res; } // see if block parent is already right kind of block. // See strategy comment above. nsCOMPtr block; if (!nsEditor::IsBlockNode(parent)) block = nsEditor::GetBlockNodeParent(parent); else block = parent; if (block) { nsAutoString tag; nsEditor::GetTagString(block,tag); if (tag == *blockTag) { *outMakeEmpty = PR_FALSE; return res; } } // are we in a textnode or inline node? if (!nsEditor::IsBlockNode(parent)) { // we must be in a text or inline node - convert existing block *outMakeEmpty = PR_FALSE; return res; } // is it after a
with no inline nodes after it, or a
after it?? if (offset) { nsCOMPtr prevChild, nextChild, tmp; prevChild = nsEditor::GetChildAt(parent, offset-1); while (prevChild && !mEditor->IsEditable(prevChild)) { // search back until we either find an editable node, // or hit the beginning of the block tmp = nsEditor::NextNodeInBlock(prevChild, nsEditor::kIterBackward); prevChild = tmp; } if (prevChild && IsBreak(prevChild)) { nextChild = nsEditor::GetChildAt(parent, offset); while (nextChild && !mEditor->IsEditable(nextChild)) { // search back until we either find an editable node, // or hit the beginning of the block tmp = nsEditor::NextNodeInBlock(nextChild, nsEditor::kIterForward); nextChild = tmp; } if (!nextChild || IsBreak(nextChild) || nsEditor::IsBlockNode(nextChild)) { // we are after a
and not before inline content, // or we are between
s. // make an empty block *outMakeEmpty = PR_FALSE; return res; } } } } // otherwise transform an existing block *outMakeEmpty = PR_FALSE; return res; } /////////////////////////////////////////////////////////////////////////// // ApplyBlockStyle: do whatever it takes to make the list of nodes into // one or more blocks of type blockTag. // nsresult nsHTMLEditRules::ApplyBlockStyle(nsISupportsArray *arrayOfNodes, const nsString *aBlockTag) { // intent of this routine is to be used for converting to/from // headers, paragraphs, pre, and address. Those blocks // that pretty much just contain inline things... if (!arrayOfNodes || !aBlockTag) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; nsCOMPtr curNode, curParent, curBlock, newBlock; PRInt32 offset; PRUint32 listCount; PRBool bNoParent = PR_FALSE; // we special case an empty tag name to mean "remove block parents". // This is used for the "normal" paragraph style in mail-compose if (aBlockTag->IsEmpty()) bNoParent = PR_TRUE; arrayOfNodes->Count(&listCount); PRUint32 i; for (i=0; i isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i)); curNode = do_QueryInterface(isupports); res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; nsAutoString curNodeTag; nsEditor::GetTagString(curNode, curNodeTag); // is it already the right kind of block? if (!bNoParent && curNodeTag == *aBlockTag) { curBlock = 0; // forget any previous block used for previous inline nodes continue; // do nothing to this block } // if curNode is a
 and we are converting to non-pre, we need
    // to process the text inside the 
 so as to convert returns
    // to breaks, and runs of spaces to nbsps.
    // xxx floppy moose
      
    // if curNode is a mozdiv, p, header, address, or pre, replace 
    // it with a new block of correct type.
    // xxx floppy moose: pre cant hold everything the others can
    if (IsMozDiv(curNode)     ||
        (curNodeTag == "pre") || 
        (curNodeTag == "p")   ||
        (curNodeTag == "h1")  ||
        (curNodeTag == "h2")  ||
        (curNodeTag == "h3")  ||
        (curNodeTag == "h4")  ||
        (curNodeTag == "h5")  ||
        (curNodeTag == "h6")  ||
        (curNodeTag == "address"))
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      if (bNoParent)
      {
        nsCOMPtr brNode;
        res = mEditor->CreateBR(curParent, offset+1, &brNode);
        if (NS_FAILED(res)) return res;
        res = RemoveContainer(curNode);
      }
      else
      {
        res = ReplaceContainer(curNode, &newBlock, *aBlockTag);
      }
      if (NS_FAILED(res)) return res;
    }
    else if ((curNodeTag == "table")      || 
             (curNodeTag == "tbody")      ||
             (curNodeTag == "tr")         ||
             (curNodeTag == "td")         ||
             (curNodeTag == "ol")         ||
             (curNodeTag == "ul")         ||
             (curNodeTag == "li")         ||
             (curNodeTag == "blockquote") ||
             (curNodeTag == "div"))  // div's other than mozdivs
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      // recursion time
      nsCOMPtr childArray;
      res = GetChildNodesForOperation(curNode, &childArray);
      if (NS_FAILED(res)) return res;
      res = ApplyBlockStyle(childArray, aBlockTag);
      if (NS_FAILED(res)) return res;
    }
    
    // if the node is a break, we honor it by putting further nodes in a new parent
    else if (curNodeTag == "br")
    {
      curBlock = 0;  // forget any previous block used for previous inline nodes
      if (!bNoParent)
      {
        res = mEditor->DeleteNode(curNode);
        if (NS_FAILED(res)) return res;
      }
    }
        
    
    // if curNode is inline, pull it into curBlock
    // note: it's assumed that consecutive inline nodes in the 
    // arrayOfNodes are actually members of the same block parent.
    // this happens to be true now as a side effect of how
    // arrayOfNodes is contructed, but some additional logic should
    // be added here if that should change
    
    else if (nsEditor::IsInlineNode(curNode) && !bNoParent)
    {
      // if curNode is a non editable, drop it if we are going to 
      if ((*aBlockTag == "pre") && (!mEditor->IsEditable(curNode)))
        continue; // do nothing to this block
      
      // if no curBlock, make one
      if (!curBlock)
      {
        res = mEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
        if (NS_FAILED(res)) return res;
      }
      
      // if curNode is a Break, replace it with a return if we are going to 
      // xxx floppy moose
 
      // this is a continuation of some inline nodes that belong together in
      // the same block item.  use curBlock
      PRUint32 blockLen;
      res = mEditor->GetLengthOfDOMNode(curBlock, blockLen);
      if (NS_FAILED(res)) return res;
      res = mEditor->DeleteNode(curNode);
      if (NS_FAILED(res)) return res;
      res = mEditor->InsertNode(curNode, curBlock, blockLen);
      if (NS_FAILED(res)) return res;
    }
  }
  return res;
}


nsresult 
nsHTMLEditRules::IsFirstEditableChild( nsIDOMNode *aNode, PRBool *aOutIsFirst)
{
  // check parms
  if (!aOutIsFirst || !aNode) return NS_ERROR_NULL_POINTER;
  
  // init out parms
  *aOutIsFirst = PR_FALSE;
  
  // find first editable child and compare it to aNode
  nsCOMPtr parent, firstChild;
  nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
  if (NS_FAILED(res)) return res;
  if (!parent) return NS_ERROR_FAILURE;
  res = GetFirstEditableChild(parent, &firstChild);
  if (NS_FAILED(res)) return res;
  
  *aOutIsFirst = (firstChild.get() == aNode);
  return res;
}


nsresult 
nsHTMLEditRules::IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast)
{
  // check parms
  if (!aOutIsLast || !aNode) return NS_ERROR_NULL_POINTER;
  
  // init out parms
  *aOutIsLast = PR_FALSE;
  
  // find last editable child and compare it to aNode
  nsCOMPtr parent, lastChild;
  nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
  if (NS_FAILED(res)) return res;
  if (!parent) return NS_ERROR_FAILURE;
  res = GetLastEditableChild(parent, &lastChild);
  if (NS_FAILED(res)) return res;
  
  *aOutIsLast = (lastChild.get() == aNode);
  return res;
}


nsresult 
nsHTMLEditRules::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutFirstChild)
{
  // check parms
  if (!aOutFirstChild || !aNode) return NS_ERROR_NULL_POINTER;
  
  // init out parms
  *aOutFirstChild = nsnull;
  
  // find first editable child
  nsCOMPtr child;
  nsresult res = aNode->GetFirstChild(getter_AddRefs(child));
  if (NS_FAILED(res)) return res;
  
  while (child && !mEditor->IsEditable(child))
  {
    nsCOMPtr tmp;
    res = child->GetNextSibling(getter_AddRefs(tmp));
    if (NS_FAILED(res)) return res;
    if (!tmp) return NS_ERROR_FAILURE;
    child = tmp;
  }
  
  *aOutFirstChild = child;
  return res;
}


nsresult 
nsHTMLEditRules::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutLastChild)
{
  // check parms
  if (!aOutLastChild || !aNode) return NS_ERROR_NULL_POINTER;
  
  // init out parms
  *aOutLastChild = nsnull;
  
  // find last editable child
  nsCOMPtr child;
  nsresult res = aNode->GetLastChild(getter_AddRefs(child));
  if (NS_FAILED(res)) return res;
  
  while (child && !mEditor->IsEditable(child))
  {
    nsCOMPtr tmp;
    res = child->GetPreviousSibling(getter_AddRefs(tmp));
    if (NS_FAILED(res)) return res;
    if (!tmp) return NS_ERROR_FAILURE;
    child = tmp;
  }
  
  *aOutLastChild = child;
  return res;
}


///////////////////////////////////////////////////////////////////////////
// JoinNodesSmart:  join two nodes, doing whatever makes sense for their  
//                  children (which often means joining them, too).
//                  aNodeLeft & aNodeRight must be same type of node.
nsresult 
nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, 
                                 nsIDOMNode *aNodeRight, 
                                 nsCOMPtr *aOutMergeParent, 
                                 PRInt32 *aOutMergeOffset)
{
  // check parms
  if (!aNodeLeft ||  
      !aNodeRight || 
      !aOutMergeParent ||
      !aOutMergeOffset) 
    return NS_ERROR_NULL_POINTER;
  
  nsresult res = NS_OK;
  // caller responsible for:
  //   left & right node are same type
  PRInt32 parOffset;
  nsCOMPtr parent, rightParent;
  res = nsEditor::GetNodeLocation(aNodeLeft, &parent, &parOffset);
  if (NS_FAILED(res)) return res;
  aNodeRight->GetParentNode(getter_AddRefs(rightParent));

  // if they don't have the same parent, first move the 'right' node 
  // to after the 'left' one
  if (parent != rightParent)
  {
    res = mEditor->DeleteNode(aNodeRight);
    if (NS_FAILED(res)) return res;
    res = mEditor->InsertNode(aNodeRight, parent, parOffset);
    if (NS_FAILED(res)) return res;
  }
  
  // defaults for outParams
  *aOutMergeParent = aNodeRight;
  res = mEditor->GetLengthOfDOMNode(aNodeLeft, *((PRUint32*)aOutMergeOffset));
  if (NS_FAILED(res)) return res;

  // seperate join rules for differing blocks
  if (IsParagraph(aNodeLeft))
  {
    // for para's, merge deep & add a 
after merging res = mEditor->JoinNodeDeep(aNodeLeft, aNodeRight, aOutMergeParent, aOutMergeOffset); if (NS_FAILED(res)) return res; // now we need to insert a br. nsCOMPtr brNode; res = mEditor->CreateBR(*aOutMergeParent, *aOutMergeOffset, &brNode); return res; } else if (IsList(aNodeLeft) || mEditor->IsTextNode(aNodeLeft)) { // for list's, merge shallow (wouldn't want to combine list items) res = mEditor->JoinNodes(aNodeLeft, aNodeRight, parent); if (NS_FAILED(res)) return res; return res; } else { // remember the last left child, and firt right child nsCOMPtr lastLeft, firstRight; res = GetLastEditableChild(aNodeLeft, &lastLeft); if (NS_FAILED(res)) return res; res = GetFirstEditableChild(aNodeRight, &firstRight); if (NS_FAILED(res)) return res; // for list items, divs, etc, merge smart res = mEditor->JoinNodes(aNodeLeft, aNodeRight, parent); if (NS_FAILED(res)) return res; if (lastLeft && firstRight && mEditor->NodesSameType(lastLeft, firstRight)) { return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset); } } return res; } nsresult nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, nsCOMPtr *aOutCiteNode) { // check parms if (!aNode || !aOutCiteNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; nsCOMPtr node, parentNode; node = do_QueryInterface(aNode); while (node) { if (IsMailCite(node)) *aOutCiteNode = node; if (IsBody(node)) break; res = node->GetParentNode(getter_AddRefs(parentNode)); if (NS_FAILED(res)) return res; node = parentNode; } return res; } nsresult nsHTMLEditRules::AdjustSpecialBreaks() { nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; // make an isupportsArray to hold a list of nodes nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); if (NS_FAILED(res)) return res; // need an iterator res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; // loop over iter and create list of empty containers res = iter->Init(mDocChangeRange); if (NS_FAILED(res)) return res; // gather up a list of empty nodes while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; PRBool bIsEmptyNode; res = IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_FALSE); if (NS_FAILED(res)) return res; if (bIsEmptyNode && (IsListItem(node) || mEditor->IsTableCell(node))) { isupports = do_QueryInterface(node); arrayOfNodes->AppendElement(isupports); } res = iter->Next(); if (NS_FAILED(res)) return res; } // put moz-br's into these empty li's and td's res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; for (j = 0; j < (PRInt32)nodeCount; j++) { isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); nsCOMPtr brNode, theNode( do_QueryInterface(isupports ) ); arrayOfNodes->RemoveElementAt(0); res = CreateMozBR(theNode, 0, &brNode); if (NS_FAILED(res)) return res; } return res; } nsresult nsHTMLEditRules::AdjustWhitespace() { nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; nsresult 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->GetStartParent(getter_AddRefs(startNode)); if (NS_FAILED(res)) return res; res = mDocChangeRange->GetStartOffset(&startOffset); if (NS_FAILED(res)) return res; res = mDocChangeRange->GetEndParent(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; } } // make an isupportsArray to hold a list of nodes res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); if (NS_FAILED(res)) return res; // need an iterator res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; // loop over iter and adjust whitespace in any text nodes we find res = iter->Init(mDocChangeRange); if (NS_FAILED(res)) return res; // gather up a list of text nodes while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; if (nsEditor::IsTextNode(node)) { isupports = do_QueryInterface(node); arrayOfNodes->AppendElement(isupports); } res = iter->Next(); 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 < (PRInt32)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; } nsresult nsHTMLEditRules::AdjustSelection(nsIDOMSelection *aSelection, nsIEditor::ESelectionCollapseDirection aAction) { if (!aSelection) return NS_ERROR_NULL_POINTER; // if the selection isn't collapsed, do nothing. // moose: one thing to do instead ischeck for the case of // only a single break selected, and collapse it. Good thing? Beats me. PRBool bCollapsed; nsresult res = aSelection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; if (!bCollapsed) return res; // get the (collapsed) selection location nsCOMPtr selNode; PRInt32 selOffset; res = mEditor->GetStartNodeAndOffset(aSelection, &selNode, &selOffset); if (NS_FAILED(res)) return res; // are we in a text node? nsCOMPtr textNode = do_QueryInterface(selNode); if (textNode) return NS_OK; // we LIKE it when we are in a text node. that RULZ // do we need to insert a special mozBR? We do if we are: // 1) in a collapsed selection AND // 2) after a normal (non-moz) br AND // 3) that br in the last editable node in it's block AND // 4) that block is same block where selection is nsCOMPtr nearNode; res = GetPriorHTMLNode(selNode, selOffset, &nearNode); if (NS_FAILED(res)) return res; if (nearNode && IsBreak(nearNode) && !IsMozBR(nearNode)) { PRBool bIsLast; res = IsLastEditableChild(nearNode, &bIsLast); if (NS_FAILED(res)) return res; if (bIsLast) { // is nearNode also a descendant of same block? nsCOMPtr block, nearBlock; if (mEditor->IsBlockNode(selNode)) block = selNode; else block = mEditor->GetBlockNodeParent(selNode); nearBlock = mEditor->GetBlockNodeParent(nearNode); if (block == nearBlock) { // need to insert special moz BR. Why? Because if we don't // the user will see no new line for the break. Also, things // like table cells won't grow in height. nsCOMPtr brNode; res = CreateMozBR(selNode, selOffset, &brNode); if (NS_FAILED(res)) return res; res = nsEditor::GetNodeLocation(brNode, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode,selOffset+1); if (NS_FAILED(res)) return res; } } else { // ok, the br inst the last child. But it might be second-to-last // with a mozBR already exiting after it. In this case we have to // move the selection to after the mozBR so it will show up on the // empty line. nsCOMPtr nextNode; res = GetNextHTMLNode(nearNode, &nextNode); if (NS_FAILED(res)) return res; if (IsMozBR(nextNode)) { res = nsEditor::GetNodeLocation(nextNode, &selNode, &selOffset); if (NS_FAILED(res)) return res; res = aSelection->Collapse(selNode,selOffset+1); if (NS_FAILED(res)) return res; } } } // we aren't in a textnode: look for a nearby text node, in the right direction. if (aAction == nsIEditor::eDeletePrevious) res = GetPriorHTMLNode(selNode, selOffset, &nearNode); else res = GetNextHTMLNode(selNode, selOffset, &nearNode); if (NS_FAILED(res)) return res; // if there is no node then punt if (!nearNode) return NS_OK; // is nearNode also a descendant of same block? nsCOMPtr block, nearBlock; if (mEditor->IsBlockNode(selNode)) block = selNode; else block = mEditor->GetBlockNodeParent(selNode); if (mEditor->IsBlockNode(nearNode)) nearBlock = nearNode; else nearBlock = mEditor->GetBlockNodeParent(nearNode); if (block != nearBlock) return NS_OK; // punt - we dont want to jump across a block // is the nearnode a text node? textNode = do_QueryInterface(nearNode); if (textNode) { PRInt32 offset = 0; // put selection in right place: if (aAction == nsIEditor::eDeletePrevious) textNode->GetLength((PRUint32*)&offset); res = aSelection->Collapse(nearNode,offset); if (NS_FAILED(res)) return res; } return res; } nsresult nsHTMLEditRules::RemoveEmptyNodes() { nsCOMPtr iter; nsCOMPtr arrayOfNodes; nsCOMPtr isupports; PRUint32 nodeCount,j; // make an isupportsArray to hold a list of nodes nsresult res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes)); if (NS_FAILED(res)) return res; // need an iterator res = nsComponentManager::CreateInstance(kContentIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; // loop over iter and create list of empty containers do { res = iter->Init(mDocChangeRange); if (NS_FAILED(res)) return res; // gather up a list of empty nodes while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; PRBool bIsEmptyNode; res = IsEmptyNode(node, &bIsEmptyNode, PR_FALSE, PR_TRUE); if (NS_FAILED(res)) return res; if (bIsEmptyNode && !IsBody(node)) { isupports = do_QueryInterface(node); arrayOfNodes->AppendElement(isupports); } res = iter->Next(); if (NS_FAILED(res)) return res; } // now delete the empty nodes res = arrayOfNodes->Count(&nodeCount); if (NS_FAILED(res)) return res; for (j = 0; j < (PRInt32)nodeCount; j++) { isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0)); nsCOMPtr delNode( do_QueryInterface(isupports ) ); arrayOfNodes->RemoveElementAt(0); res = mEditor->DeleteNode(delNode); if (NS_FAILED(res)) return res; } } while (nodeCount); // if we deleted any, loop again // deleting some nodes may make some parents now empty return res; } nsresult nsHTMLEditRules::DoTextNodeWhitespace(nsIDOMCharacterData *aTextNode, PRInt32 aStart, PRInt32 aEnd) { // check parms if (!aTextNode) return NS_ERROR_NULL_POINTER; if (aStart == -1) // -1 means do the whole darn node please { aStart = 0; aTextNode->GetLength((PRUint32*)&aEnd); } if (aStart == aEnd) return NS_OK; nsresult res = NS_OK; PRBool isPRE; nsCOMPtr node = do_QueryInterface(aTextNode); res = mEditor->IsPreformatted(node,&isPRE); if (NS_FAILED(res)) return res; if (isPRE) { // text node has a Preformatted style. All we need to do is strip out any nbsp's // we just put in and replace them with spaces. // moose: write me! return res; } // else we are not preformatted. Need to convert any adjacent spaces to alterating // space/nbsp pairs; need to convert stranded nbsp's to spaces; need to convert // tabs to whitespace; need to convert returns to whitespace. PRInt32 j = 0; nsAutoString tempString; aTextNode->SubstringData(aStart, aEnd, tempString); // identify runs of whitespace PRInt32 runStart = -1, runEnd = -1; do { PRUnichar c = tempString[j]; PRBool isSpace = nsString::IsSpace(c); if (isSpace || c==nbsp) { if (runStart<0) runStart = j; runEnd = j+1; } // translation of below line: // if we have a whitespace run, AND // either we are at the end of it, or the end of the whole string, // THEN process it if (runStart>=0 && (!(isSpace || c==nbsp) || (j==aEnd-1)) ) { // current char is non whitespace, but we have identified an earlier // run of whitespace. convert it if needed. NS_PRECONDITION(runEnd>runStart, "this is what happens when integers turn bad!"); // runStart to runEnd is a run of whitespace nsAutoString runStr, newStr; tempString.Mid(runStr, runStart, runEnd-runStart); res = ConvertWhitespace(runStr, newStr); if (NS_FAILED(res)) return res; if (runStr != newStr) { // delete the original whitespace run EditTxn *txn; // note 1: we are not telling edit listeners about these because they don't care // note 2: we are not wrapping these in a placeholder because we know they already are res = mEditor->CreateTxnForDeleteText(aTextNode, aStart+runStart, runEnd-runStart, (DeleteTextTxn**)&txn); if (NS_FAILED(res)) return res; if (!txn) return NS_ERROR_OUT_OF_MEMORY; res = mEditor->Do(txn); if (NS_FAILED(res)) return res; // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); // insert the new run res = mEditor->CreateTxnForInsertText(newStr, aTextNode, aStart+runStart, (InsertTextTxn**)&txn); if (NS_FAILED(res)) return res; if (!txn) return NS_ERROR_OUT_OF_MEMORY; res = mEditor->Do(txn); // The transaction system (if any) has taken ownwership of txns. NS_IF_RELEASE(txn); } runStart = -1; // reset our run } j++; // next char please! } while (j curParent; nsCOMPtr curNode( do_QueryInterface(aListItem)); PRInt32 offset; nsresult res = nsEditor::GetNodeLocation(curNode, &curParent, &offset); if (NS_FAILED(res)) return res; if (!IsListItem(curNode)) return NS_ERROR_FAILURE; // if it's first or last list item, dont need to split the list // otherwise we do. nsCOMPtr curParPar; PRInt32 parOffset; res = nsEditor::GetNodeLocation(curParent, &curParPar, &parOffset); if (NS_FAILED(res)) return res; PRBool bIsFirstListItem; res = IsFirstEditableChild(curNode, &bIsFirstListItem); if (NS_FAILED(res)) return res; PRBool bIsLastListItem; res = IsLastEditableChild(curNode, &bIsLastListItem); if (NS_FAILED(res)) return res; if (!bIsFirstListItem && !bIsLastListItem) { // split the list nsCOMPtr newBlock; res = mEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock)); if (NS_FAILED(res)) return res; } if (!bIsFirstListItem) parOffset++; res = mEditor->DeleteNode(curNode); if (NS_FAILED(res)) return res; res = mEditor->InsertNode(curNode, curParPar, parOffset); if (NS_FAILED(res)) return res; // unwrap list item contents if they are no longer in a list if (!IsList(curParPar) && IsListItem(curNode)) { nsCOMPtr mozDiv; res = RemoveContainer(curNode); if (NS_FAILED(res)) return res; *aOutOfList = PR_TRUE; } return res; } // not needed for now - leaving around in case we go back to it #if 0 nsresult nsHTMLEditRules::AddTrailerBR(nsIDOMNode *aNode) { // check parms if (!aNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; // comfirm that this node is a type that we add trailer br's to: // td, th, li if (IsListItem(aNode)) // add more types later { PRUint32 count; nsCOMPtr brNode; res = mEditor->GetLengthOfDOMNode(aNode, count); if (NS_FAILED(res)) return res; res = mEditor->CreateBR(aNode, count, &brNode); if (NS_FAILED(res)) return res; // give it special trailer attr nsCOMPtr brElem = do_QueryInterface(brNode); res = mEditor->SetAttribute(brElem, "type", "_moz_trailer"); } else { NS_NOTREACHED("editor error: nsHTMLEditRules::AddTrailerBR() called on bad node type"); } return res; } #endif nsresult nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange) { nsresult res = NS_OK; if (!mDocChangeRange) { mDocChangeRange = do_QueryInterface(aRange); return NS_OK; } else { PRInt32 result; // compare starts of ranges res = mDocChangeRange->CompareEndPoints(nsIDOMRange::START_TO_START, aRange, &result); if (NS_FAILED(res)) return res; if (result < 0) // negative result means aRange start is before mDocChangeRange start { nsCOMPtr startNode; PRInt32 startOffset; res = aRange->GetStartParent(getter_AddRefs(startNode)); if (NS_FAILED(res)) return res; res = aRange->GetStartOffset(&startOffset); if (NS_FAILED(res)) return res; res = mDocChangeRange->SetStart(startNode, startOffset); if (NS_FAILED(res)) return res; } // compare ends of ranges res = mDocChangeRange->CompareEndPoints(nsIDOMRange::END_TO_END, aRange, &result); if (NS_FAILED(res)) return res; if (result > 0) // positive result means aRange end is after mDocChangeRange end { nsCOMPtr endNode; PRInt32 endOffset; res = aRange->GetEndParent(getter_AddRefs(endNode)); if (NS_FAILED(res)) return res; res = aRange->GetEndOffset(&endOffset); if (NS_FAILED(res)) return res; res = mDocChangeRange->SetEnd(endNode, endOffset); if (NS_FAILED(res)) return res; } } return res; } #ifdef XP_MAC #pragma mark - #pragma mark --- nsIEditActionListener methods --- #pragma mark - #endif /* Factory for edit listener object */ nsresult NS_NewEditListener(nsIEditActionListener **aResult, nsHTMLEditor *htmlEditor, nsHTMLEditRules *htmlRules) { if (!aResult) return NS_ERROR_NULL_POINTER; *aResult = new nsHTMLEditListener(htmlEditor, htmlRules); if (!*aResult) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult); return NS_OK; } NS_IMPL_ADDREF(nsHTMLEditListener) NS_IMPL_RELEASE(nsHTMLEditListener) NS_IMPL_QUERY_INTERFACE1(nsHTMLEditListener, nsIEditActionListener) nsHTMLEditListener::nsHTMLEditListener(nsHTMLEditor *htmlEditor, nsHTMLEditRules *htmlRules) : mEditor(htmlEditor), mRules(htmlRules) { NS_INIT_REFCNT(); NS_PRECONDITION(mEditor, "null editor!"); NS_PRECONDITION(mRules, "null edit rules!"); nsCOMPtr bodyElement; nsresult res = mEditor->GetBodyElement(getter_AddRefs(bodyElement)); NS_POSTCONDITION(NS_SUCCEEDED(res), "no body element found for edit listener"); NS_POSTCONDITION(bodyElement, "no body element found for edit listener"); mBody = do_QueryInterface(bodyElement); } nsHTMLEditListener::~nsHTMLEditListener() {} NS_IMETHODIMP nsHTMLEditListener::WillCreateNode(const nsString& aTag, nsIDOMNode *aParent, PRInt32 aPosition) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidCreateNode(const nsString& aTag, nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition, nsresult aResult) { nsCOMPtr range; // assumption that Join keeps the righthand node nsresult res = MakeRangeFromNode(aNode, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aPosition, nsresult aResult) { nsCOMPtr range; // assumption that Join keeps the righthand node nsresult res = MakeRangeFromNode(aNode, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::WillDeleteNode(nsIDOMNode *aChild) { nsCOMPtr range; nsresult res = MakeRangeFromNode(aChild, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::WillSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidSplitNode(nsIDOMNode *aExistingRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode, nsresult aResult) { nsCOMPtr range; nsresult res = MakeRangeFromNode(aNewLeftNode, &range); if (NS_FAILED(res)) return res; if (range) { // now extend range to include right node res = range->SetEndAfter(aExistingRightNode); if (NS_FAILED(res)) return res; res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent, nsresult aResult) { nsCOMPtr range; // assumption that Join keeps the righthand node nsresult res = MakeRangeFromNode(aRightNode, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::WillInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsString &aString) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsString &aString, nsresult aResult) { nsCOMPtr range; PRInt32 length = aString.Length(); nsresult res = MakeRangeFromTextOffsets(aTextNode, aOffset, aOffset+length, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } NS_IMETHODIMP nsHTMLEditListener::WillDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength) { return NS_OK; } NS_IMETHODIMP nsHTMLEditListener::DidDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength, nsresult aResult) { nsCOMPtr range; nsresult res = MakeRangeFromTextOffsets(aTextNode, aOffset, aOffset, &range); if (NS_FAILED(res)) return res; if (range) { res = mRules->UpdateDocChangeRange(range); } return res; } nsresult nsHTMLEditListener::MakeRangeFromNode(nsIDOMNode *inNode, nsCOMPtr *outRange) { if (!inNode || !outRange) return NS_ERROR_NULL_POINTER; *outRange = nsnull; // first check that inNode is still a descendant of the body if (!IsDescendantOfBody(inNode)) return NS_OK; // construct a range to represent start and end of inNode nsresult res = nsComponentManager::CreateInstance(kRangeCID, nsnull, nsIDOMRange::GetIID(), getter_AddRefs(*outRange)); if (NS_FAILED(res)) return res; res = (*outRange)->SelectNode(inNode); return res; } nsresult nsHTMLEditListener::MakeRangeFromTextOffsets(nsIDOMCharacterData *inNode, PRInt32 inStart, PRInt32 inEnd, nsCOMPtr *outRange) { if (!inNode || !outRange) return NS_ERROR_NULL_POINTER; *outRange = nsnull; nsCOMPtr theNode = do_QueryInterface(inNode); // first check that inNode is still a descendant of the body if (!IsDescendantOfBody(theNode)) return NS_OK; // construct a range to represent start and end of text run nsresult res = nsComponentManager::CreateInstance(kRangeCID, nsnull, nsIDOMRange::GetIID(), getter_AddRefs(*outRange)); if (NS_FAILED(res)) return res; res = (*outRange)->SetStart(theNode, inStart); if (NS_FAILED(res)) return res; res = (*outRange)->SetEnd(theNode, inEnd); return res; } PRBool nsHTMLEditListener::IsDescendantOfBody(nsIDOMNode *inNode) { if (!inNode) return PR_FALSE; if (inNode == mBody) return PR_TRUE; nsCOMPtr parent, node = do_QueryInterface(inNode); nsresult res; do { res = node->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(res)) return PR_FALSE; if (parent == mBody) return PR_TRUE; node = parent; } while (parent); return PR_FALSE; }