diff --git a/mozilla/editor/base/nsHTMLEditRules.cpp b/mozilla/editor/base/nsHTMLEditRules.cpp index 87fbc7b100e..bd3164e7411 100644 --- a/mozilla/editor/base/nsHTMLEditRules.cpp +++ b/mozilla/editor/base/nsHTMLEditRules.cpp @@ -72,6 +72,18 @@ nsIAtom *nsHTMLEditRules::sUAtom; nsIAtom *nsHTMLEditRules::sVarAtom; nsIAtom *nsHTMLEditRules::sWbrAtom; +nsIAtom *nsHTMLEditRules::sH1Atom; +nsIAtom *nsHTMLEditRules::sH2Atom; +nsIAtom *nsHTMLEditRules::sH3Atom; +nsIAtom *nsHTMLEditRules::sH4Atom; +nsIAtom *nsHTMLEditRules::sH5Atom; +nsIAtom *nsHTMLEditRules::sH6Atom; +nsIAtom *nsHTMLEditRules::sParagraphAtom; +nsIAtom *nsHTMLEditRules::sListItemAtom; +nsIAtom *nsHTMLEditRules::sBreakAtom; + + + PRInt32 nsHTMLEditRules::sInstanceCount = 0; /******************************************************** @@ -112,6 +124,16 @@ nsHTMLEditRules::nsHTMLEditRules() sUAtom = NS_NewAtom("u"); sVarAtom = NS_NewAtom("var"); sWbrAtom = NS_NewAtom("wbr"); + + sH1Atom = NS_NewAtom("h1"); + sH2Atom = NS_NewAtom("h2"); + sH3Atom = NS_NewAtom("h3"); + sH4Atom = NS_NewAtom("h4"); + sH5Atom = NS_NewAtom("h5"); + sH6Atom = NS_NewAtom("h6"); + sParagraphAtom = NS_NewAtom("p"); + sListItemAtom = NS_NewAtom("li"); + sBreakAtom = NS_NewAtom("br"); } ++sInstanceCount; @@ -148,6 +170,16 @@ nsHTMLEditRules::~nsHTMLEditRules() NS_IF_RELEASE(sUAtom); NS_IF_RELEASE(sVarAtom); NS_IF_RELEASE(sWbrAtom); + + NS_IF_RELEASE(sH1Atom); + NS_IF_RELEASE(sH2Atom); + NS_IF_RELEASE(sH3Atom); + NS_IF_RELEASE(sH4Atom); + NS_IF_RELEASE(sH5Atom); + NS_IF_RELEASE(sH6Atom); + NS_IF_RELEASE(sParagraphAtom); + NS_IF_RELEASE(sListItemAtom); + NS_IF_RELEASE(sBreakAtom); } --sInstanceCount; @@ -173,12 +205,14 @@ nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection, case kInsertText: return WillInsertText(aSelection, aCancel, - info->placeTxn, + info->placeTxn, info->inString, info->outString, info->typeInState); case kInsertBreak: return WillInsertBreak(aSelection, aCancel); + case kDeleteSelection: + return WillDeleteSelection(aSelection, info->dir, aCancel); } return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel); } @@ -187,19 +221,6 @@ NS_IMETHODIMP nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, nsresult aResult) { - if (!aSelection || !aInfo) - return NS_ERROR_NULL_POINTER; - - // my kingdom for dynamic cast - nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); - - switch (info->action) - { - case kInsertText: - return DidInsertText(aSelection, aResult); - case kInsertBreak: - return DidInsertBreak(aSelection, aResult); - } return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); } @@ -221,6 +242,7 @@ nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection, *aCancel = PR_FALSE; // XXX - need to handle strings of length >1 with embedded tabs or spaces + // XXX - what about embedded returns? // is it a tab? if (*inString == "\t" ) @@ -238,139 +260,426 @@ nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection, typeInState); } -nsresult -nsHTMLEditRules::DidInsertText(nsIDOMSelection *aSelection, - nsresult aResult) -{ - // for now, return nsTextEditRules version - return nsTextEditRules::DidInsertText(aSelection, aResult); -} - nsresult nsHTMLEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel) { if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; + + nsresult res; + + // 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::eLTR); + if (NS_FAILED(res)) return res; + } + + //smart splitting rules + nsCOMPtr node; + PRInt32 offset; + PRBool isPRE; + + res = GetStartNodeAndOffset(aSelection, &node, &offset); + if (NS_FAILED(res)) return res; + if (!node) return NS_ERROR_FAILURE; + + res = IsPreformatted(node,&isPRE); + if (NS_FAILED(res)) return res; + + if (isPRE) + { + nsString theString = "\n"; + *aCancel = PR_TRUE; + return mEditor->InsertText(theString); + } + + nsCOMPtr blockParent = GetBlockNodeParent(node); + if (!blockParent) return NS_ERROR_FAILURE; + + // headers: put selection after the header + if (IsHeader(blockParent)) + { + res = ReturnInHeader(aSelection, blockParent, node, offset); + *aCancel = PR_TRUE; + return NS_OK; + } + + // paragraphs: special rules to look for
s + if (IsParagraph(blockParent)) + { + res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel); + return NS_OK; + } + + // list items: special rules to make new list items + if (IsListItem(blockParent)) + { + res = ReturnInListItem(aSelection, blockParent, node, offset); + *aCancel = PR_TRUE; + return NS_OK; + } + + return WillInsert(aSelection, aCancel); } -// XXX: this code is all experimental, and has no effect on the content model yet -// the point here is to collapse adjacent BR's into P's + + nsresult -nsHTMLEditRules::DidInsertBreak(nsIDOMSelection *aSelection, nsresult aResult) +nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::Direction aDir, PRBool *aCancel) { - nsresult result = aResult; // if aResult is an error, we return it. - if (!aSelection) { return NS_ERROR_NULL_POINTER; } - PRBool isCollapsed; - aSelection->GetIsCollapsed(&isCollapsed); - NS_ASSERTION(PR_TRUE==isCollapsed, "selection not collapsed after insert break."); - // if the insert break resulted in consecutive BR tags, - // collapse the two BR tags into a single P - if (NS_SUCCEEDED(result)) + if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } + // initialize out param + *aCancel = PR_FALSE; + + nsresult res = NS_OK; + + PRBool bCollapsed; + res = aSelection->GetIsCollapsed(&bCollapsed); + if (NS_FAILED(res)) return res; + + nsCOMPtr node; + PRInt32 offset; + + res = GetStartNodeAndOffset(aSelection, &node, &offset); + if (NS_FAILED(res)) return res; + if (!node) return NS_ERROR_FAILURE; + + if (bCollapsed) { - nsCOMPtr enumerator; - enumerator = do_QueryInterface(aSelection,&result); - if (enumerator) + // easy case, in a text node: + if (IsTextNode(node)) { - enumerator->First(); - nsISupports *currentItem; - result = enumerator->CurrentItem(¤tItem); - if ((NS_SUCCEEDED(result)) && currentItem) + 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 && (aDir == nsIEditor::eRTL)) { - result = NS_ERROR_UNEXPECTED; - nsCOMPtr range( do_QueryInterface(currentItem) ); - if (range) + nsCOMPtr priorNode; + res = GetPriorNode(node, getter_AddRefs(priorNode)); + if (NS_FAILED(res)) return res; + + // XXX hackery - using this to skip empty text nodes, + // since these are almost always non displayed preservation + // of returns in the original html. but they could + // actually be significant, then we're hosed. FIXME + while (IsEmptyTextNode(priorNode)) { - nsIAtom *brTag = NS_NewAtom("BR"); - nsCOMPtr startNode; - result = range->GetStartParent(getter_AddRefs(startNode)); - if ((NS_SUCCEEDED(result)) && startNode) - { - PRInt32 offset; - range->GetStartOffset(&offset); - nsCOMPtrstartNodeChildren; - result = startNode->GetChildNodes(getter_AddRefs(startNodeChildren)); - if ((NS_SUCCEEDED(result)) && startNodeChildren) - { - nsCOMPtr selectedNode; - result = startNodeChildren->Item(offset, getter_AddRefs(selectedNode)); - if ((NS_SUCCEEDED(result)) && selectedNode) - { - nsCOMPtr prevNode; - result = selectedNode->GetPreviousSibling(getter_AddRefs(prevNode)); - if ((NS_SUCCEEDED(result)) && prevNode) - { - if (PR_TRUE==NodeIsType(prevNode, brTag)) - { // the previous node is a BR, check it's siblings - nsCOMPtr leftNode; - result = prevNode->GetPreviousSibling(getter_AddRefs(leftNode)); - if ((NS_SUCCEEDED(result)) && leftNode) - { - if (PR_TRUE==NodeIsType(leftNode, brTag)) - { // left sibling is also a BR, collapse - printf("1\n"); - } - else - { - if (PR_TRUE==NodeIsType(selectedNode, brTag)) - { // right sibling is also a BR, collapse - printf("2\n"); - } - } - } - } - } - // now check the next node from selectedNode - nsCOMPtr nextNode; - result = selectedNode->GetNextSibling(getter_AddRefs(nextNode)); - if ((NS_SUCCEEDED(result)) && nextNode) - { - if (PR_TRUE==NodeIsType(nextNode, brTag)) - { // the previous node is a BR, check it's siblings - nsCOMPtr rightNode; - result = nextNode->GetNextSibling(getter_AddRefs(rightNode)); - if ((NS_SUCCEEDED(result)) && rightNode) - { - if (PR_TRUE==NodeIsType(rightNode, brTag)) - { // right sibling is also a BR, collapse - printf("3\n"); - } - else - { - if (PR_TRUE==NodeIsType(selectedNode, brTag)) - { // left sibling is also a BR, collapse - printf("4\n"); - } - } - } - } - } - } - } - } - NS_RELEASE(brTag); + res = mEditor->DeleteNode(priorNode); + if (NS_FAILED(res)) return res; + res = GetPriorNode(node, getter_AddRefs(priorNode)); + if (NS_FAILED(res)) return res; } + + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, priorNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(priorNode); + nsCOMPtr rightParent = GetBlockNodeParent(node); + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // join para's, insert break + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // join blocks + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + } + + // at end of text node and deleted? + if ((offset == strLength) && (aDir == nsIEditor::eLTR)) + { + nsCOMPtr nextNode; + res = GetNextNode(node, getter_AddRefs(nextNode)); + if (NS_FAILED(res)) return res; + if (HasSameBlockNodeParent(node, nextNode)) return NS_OK; + + // deleting across blocks + // XXX hackery - using this to skip empty text nodes, + // since these are almost always non displayed preservation + // of returns in the original html. but they could + // actually be significant, then we're hosed. FIXME + while (IsEmptyTextNode(nextNode)) + { + res = mEditor->DeleteNode(nextNode); + if (NS_FAILED(res)) return res; + res = GetNextNode(node, getter_AddRefs(nextNode)); + if (NS_FAILED(res)) return res; + } + + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, nextNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(node); + nsCOMPtr rightParent = GetBlockNodeParent(nextNode); + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // join para's, insert break + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // join blocks + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + + } + + // else do default + return NS_OK; } } - return result; -} + + // else we have a non collapsed selection + // figure out if the enpoints are in nodes that can be merged + nsCOMPtr endNode; + PRInt32 endOffset; + res = GetEndNodeAndOffset(aSelection, &endNode, &endOffset); + if (endNode.get() != node.get()) + { + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, endNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(node); + nsCOMPtr rightParent = 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; + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // first delete the selection + *aCancel = PR_TRUE; + res = mEditor->nsEditor::DeleteSelection(aDir); + if (NS_FAILED(res)) return res; + // then join para's, insert break + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // first delete the selection + *aCancel = PR_TRUE; + res = mEditor->nsEditor::DeleteSelection(aDir); + if (NS_FAILED(res)) return res; + // join blocks + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + } + + return res; +} /******************************************************** * helper methods ********************************************************/ +nsresult +nsHTMLEditRules::GetRightmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result = NS_OK; + nsCOMPtr resultNode(do_QueryInterface(aCurrentNode)); + PRBool hasChildren; + resultNode->HasChildNodes(&hasChildren); + while ((NS_SUCCEEDED(result)) && (PR_TRUE==hasChildren)) + { + nsCOMPtr temp(resultNode); + temp->GetLastChild(getter_AddRefs(resultNode)); + resultNode->HasChildNodes(&hasChildren); + } + + if (NS_SUCCEEDED(result)) { + *aResultNode = resultNode; + NS_ADDREF(*aResultNode); + } + return result; +} + +nsresult +nsHTMLEditRules::GetLeftmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result = NS_OK; + nsCOMPtr resultNode(do_QueryInterface(aCurrentNode)); + PRBool hasChildren; + resultNode->HasChildNodes(&hasChildren); + while ((NS_SUCCEEDED(result)) && (PR_TRUE==hasChildren)) + { + nsCOMPtr temp(resultNode); + temp->GetFirstChild(getter_AddRefs(resultNode)); + resultNode->HasChildNodes(&hasChildren); + } + + if (NS_SUCCEEDED(result)) { + *aResultNode = resultNode; + NS_ADDREF(*aResultNode); + } + return result; +} + +nsresult +nsHTMLEditRules::GetPriorNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result; + *aResultNode = nsnull; + // if aCurrentNode has a left sibling, return that sibling's rightmost child (or itself if it has no children) + result = aCurrentNode->GetPreviousSibling(aResultNode); + if ((NS_SUCCEEDED(result)) && *aResultNode) + { + return GetRightmostChild(*aResultNode, aResultNode); + } + // otherwise, walk up the parent change until there is a child that comes before + // the ancestor of aCurrentNode. Then return that node's rightmost child + + nsCOMPtr parent(do_QueryInterface(aCurrentNode)); + do { + nsCOMPtr node(parent); + result = node->GetParentNode(getter_AddRefs(parent)); + if ((NS_SUCCEEDED(result)) && parent) + { + result = parent->GetPreviousSibling(getter_AddRefs(node)); + if ((NS_SUCCEEDED(result)) && node) + { + return GetRightmostChild(node, aResultNode); + } + } + } while ((NS_SUCCEEDED(result)) && parent); + + return result; +} + +nsresult +nsHTMLEditRules::GetNextNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result; + *aResultNode = nsnull; + // if aCurrentNode has a right sibling, return that sibling's leftmost child (or itself if it has no children) + result = aCurrentNode->GetNextSibling(aResultNode); + if ((NS_SUCCEEDED(result)) && *aResultNode) + { + return GetLeftmostChild(*aResultNode, aResultNode); + } + // otherwise, walk up the parent change until there is a child that comes before + // the ancestor of aCurrentNode. Then return that node's rightmost child + + nsCOMPtr parent(do_QueryInterface(aCurrentNode)); + do { + nsCOMPtr node(parent); + result = node->GetParentNode(getter_AddRefs(parent)); + if ((NS_SUCCEEDED(result)) && parent) + { + result = parent->GetNextSibling(getter_AddRefs(node)); + if ((NS_SUCCEEDED(result)) && node) + { + return GetLeftmostChild(node, aResultNode); + } + } + } while ((NS_SUCCEEDED(result)) && parent); + + return result; +} + + + +/////////////////////////////////////////////////////////////////////////// +// GetTag: digs out the atom for the tag of this node +// +nsCOMPtr +nsHTMLEditRules::GetTag(nsIDOMNode *aNode) +{ + nsCOMPtr atom; + + if (!aNode) + { + NS_NOTREACHED("null node passed to nsHTMLEditRules::GetTag()"); + return atom; + } + + nsCOMPtr content = do_QueryInterface(aNode); + content->GetTag(*getter_AddRefs(atom)); + + return atom; +} + /////////////////////////////////////////////////////////////////////////// // IsBlockNode: true if this node is an html block node // PRBool nsHTMLEditRules::IsBlockNode(nsIDOMNode *aNode) { - nsIAtom* atom = nsnull; - PRBool result; + nsCOMPtr atom; if (!aNode) { @@ -378,54 +687,45 @@ nsHTMLEditRules::IsBlockNode(nsIDOMNode *aNode) return PR_FALSE; } - nsCOMPtr content = do_QueryInterface(aNode); - if (!content) - { - NS_NOTREACHED("could not get content node in IsBlockNode()"); + if (IsTextNode(aNode)) return PR_FALSE; - } - - content->GetTag(atom); + + atom = GetTag(aNode); if (!atom) return PR_TRUE; - if (sAAtom != atom && - sAddressAtom != atom && - sBigAtom != atom && - sBlinkAtom != atom && - sBAtom != atom && - sCiteAtom != atom && - sCodeAtom != atom && - sDfnAtom != atom && - sEmAtom != atom && - sFontAtom != atom && - sIAtom != atom && - sKbdAtom != atom && - sKeygenAtom != atom && - sNobrAtom != atom && - sSAtom != atom && - sSampAtom != atom && - sSmallAtom != atom && - sSpacerAtom != atom && - sSpanAtom != atom && - sStrikeAtom != atom && - sStrongAtom != atom && - sSubAtom != atom && - sSupAtom != atom && - sTtAtom != atom && - sUAtom != atom && - sVarAtom != atom && - sWbrAtom != atom) + if (sAAtom != atom.get() && + sAddressAtom != atom.get() && + sBigAtom != atom.get() && + sBlinkAtom != atom.get() && + sBAtom != atom.get() && + sCiteAtom != atom.get() && + sCodeAtom != atom.get() && + sDfnAtom != atom.get() && + sEmAtom != atom.get() && + sFontAtom != atom.get() && + sIAtom != atom.get() && + sKbdAtom != atom.get() && + sKeygenAtom != atom.get() && + sNobrAtom != atom.get() && + sSAtom != atom.get() && + sSampAtom != atom.get() && + sSmallAtom != atom.get() && + sSpacerAtom != atom.get() && + sSpanAtom != atom.get() && + sStrikeAtom != atom.get() && + sStrongAtom != atom.get() && + sSubAtom != atom.get() && + sSupAtom != atom.get() && + sTtAtom != atom.get() && + sUAtom != atom.get() && + sVarAtom != atom.get() && + sWbrAtom != atom.get()) { - result = PR_TRUE; + return PR_TRUE; } - else - { - result = PR_FALSE; - } - NS_RELEASE(atom); - return result; + return PR_FALSE; } @@ -440,13 +740,16 @@ nsHTMLEditRules::IsInlineNode(nsIDOMNode *aNode) /////////////////////////////////////////////////////////////////////////// // GetBlockNodeParent: returns enclosing block level ancestor, if any -// else returns the node itself +// else returns the node itself. Note that if the +// node itself is a block node, it is returned. nsCOMPtr nsHTMLEditRules::GetBlockNodeParent(nsIDOMNode *aNode) { nsCOMPtr tmp = do_QueryInterface(aNode); nsCOMPtr p; + if (IsBlockNode(aNode)) + return tmp; if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree return tmp; @@ -526,6 +829,127 @@ nsHTMLEditRules::IsTextNode(nsIDOMNode *aNode) } +/////////////////////////////////////////////////////////////////////////// +// IsEmptyTextNode: true if node of dom type text and is empty +// or if it has only char which is whitespace. HACKEROONY! +PRBool +nsHTMLEditRules::IsEmptyTextNode(nsIDOMNode *aNode) +{ + if (!aNode) + { + NS_NOTREACHED("null node passed to IsTextNode()"); + return PR_FALSE; + } + + if (!IsTextNode(aNode)) + return PR_FALSE; + + nsCOMPtr textNode = do_QueryInterface(aNode); + PRUint32 strLength; + textNode->GetLength(&strLength); + if (!strLength) + return PR_TRUE; + + nsString tempString; + textNode->GetData(tempString); + tempString.StripWhitespace(); + if (!tempString.Length()) + return PR_TRUE; + + return PR_FALSE; +} + + + +PRInt32 +nsHTMLEditRules::GetIndexOf(nsIDOMNode *parent, nsIDOMNode *child) +{ + PRInt32 index = 0; + + NS_PRECONDITION(parent, "null parent passed to nsHTMLEditRules::GetIndexOf"); + NS_PRECONDITION(parent, "null child passed to nsHTMLEditRules::GetIndexOf"); + nsCOMPtr content = do_QueryInterface(parent); + nsCOMPtr cChild = do_QueryInterface(child); + NS_PRECONDITION(content, "null content in nsHTMLEditRules::GetIndexOf"); + NS_PRECONDITION(cChild, "null content in nsHTMLEditRules::GetIndexOf"); + + nsresult res = content->IndexOf(cChild, index); + if (NS_FAILED(res)) + { + NS_NOTREACHED("could not find child in parent - nsHTMLEditRules::GetIndexOf"); + } + return index; +} + + +/////////////////////////////////////////////////////////////////////////// +// IsHeader: true if node an html header +// +PRBool +nsHTMLEditRules::IsHeader(nsIDOMNode *node) +{ + NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsHeader"); + nsCOMPtr atom = GetTag(node); + if ( (atom.get() == sH1Atom) || + (atom.get() == sH2Atom) || + (atom.get() == sH3Atom) || + (atom.get() == sH4Atom) || + (atom.get() == sH5Atom) || + (atom.get() == sH6Atom) ) + { + 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"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sParagraphAtom) + { + 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"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sListItemAtom) + { + return PR_TRUE; + } + return PR_FALSE; +} + + +/////////////////////////////////////////////////////////////////////////// +// IsListItem: true if node an html list item +// +PRBool +nsHTMLEditRules::IsBreak(nsIDOMNode *node) +{ + NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBreak"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sBreakAtom) + { + return PR_TRUE; + } + return PR_FALSE; +} + /////////////////////////////////////////////////////////////////////////// // NextNodeInBlock: gets the next/prev node in the block, if any. Next node @@ -576,8 +1000,8 @@ nsHTMLEditRules::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir) /////////////////////////////////////////////////////////////////////////// -// GetStartNode: returns whatever the start parent is of the first range -// in the selection. +// GetStartNodeAndOffset: returns whatever the start parent & offset is of +// the first range in the selection. nsresult nsHTMLEditRules::GetStartNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outStartNode, @@ -610,6 +1034,41 @@ nsHTMLEditRules::GetStartNodeAndOffset(nsIDOMSelection *aSelection, } +/////////////////////////////////////////////////////////////////////////// +// GetEndNodeAndOffset: returns whatever the end parent & offset is of +// the first range in the selection. +nsresult +nsHTMLEditRules::GetEndNodeAndOffset(nsIDOMSelection *aSelection, + nsCOMPtr *outEndNode, + PRInt32 *outEndOffset) +{ + if (!outEndNode || !outEndOffset) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr enumerator; + enumerator = do_QueryInterface(aSelection); + if (!enumerator) + return NS_ERROR_FAILURE; + + enumerator->First(); + nsISupports *currentItem; + if ((NS_FAILED(enumerator->CurrentItem(¤tItem))) || !currentItem) + return NS_ERROR_FAILURE; + + nsCOMPtr range( do_QueryInterface(currentItem) ); + if (!range) + return NS_ERROR_FAILURE; + + if (NS_FAILED(range->GetEndParent(getter_AddRefs(*outEndNode)))) + return NS_ERROR_FAILURE; + + if (NS_FAILED(range->GetEndOffset(outEndOffset))) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + /////////////////////////////////////////////////////////////////////////// // IsPreformatted: checks the style info for the node for the preformatted // text style. @@ -673,6 +1132,7 @@ nsHTMLEditRules::IsNextCharWhitespace(nsIDOMNode *aParentNode, // harder case: next char in next node. nsCOMPtr node = NextNodeInBlock(aParentNode, kIterForward); + nsCOMPtr tmp; while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes @@ -694,7 +1154,8 @@ nsHTMLEditRules::IsNextCharWhitespace(nsIDOMNode *aParentNode, break; } } - node = NextNodeInBlock(aParentNode, kIterForward); + tmp = node; + node = NextNodeInBlock(tmp, kIterForward); } return NS_OK; @@ -728,6 +1189,7 @@ nsHTMLEditRules::IsPrevCharWhitespace(nsIDOMNode *aParentNode, // harder case: prev char in next node nsCOMPtr node = NextNodeInBlock(aParentNode, kIterBackward); + nsCOMPtr tmp; while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes @@ -750,7 +1212,8 @@ nsHTMLEditRules::IsPrevCharWhitespace(nsIDOMNode *aParentNode, } } // otherwise we found a node we want to skip, keep going - node = NextNodeInBlock(aParentNode, kIterBackward); + tmp = node; + node = NextNodeInBlock(tmp, kIterBackward); } return NS_OK; @@ -785,6 +1248,45 @@ nsHTMLEditRules::GetTabAsNBSPsAndSpace(nsString *outString) +/////////////////////////////////////////////////////////////////////////// +// SplitNodeDeep: this plits a node "deeply", splitting children as +// appropriate. The place to split is represented by +// a dom point at {splitPointParent, splitPointOffset}. +// That dom point must be inside aNode, which is the node to +// split. +nsresult +nsHTMLEditRules::SplitNodeDeep(nsIDOMNode *aNode, + nsIDOMNode *aSplitPointParent, + PRInt32 aSplitPointOffset) +{ + if (!aNode || !aSplitPointParent) return NS_ERROR_NULL_POINTER; + nsCOMPtr nodeToSplit = do_QueryInterface(aSplitPointParent); + nsCOMPtr tempNode; + PRInt32 offset = aSplitPointOffset; + + while (nodeToSplit) + { + nsresult res = mEditor->SplitNode(nodeToSplit, offset, getter_AddRefs(tempNode)); + if (NS_FAILED(res)) return res; + + if (nodeToSplit.get() == aNode) // we split all the way up to (and including) aNode; we're done + break; + + tempNode = nodeToSplit; + res = tempNode->GetParentNode(getter_AddRefs(nodeToSplit)); + offset = GetIndexOf(nodeToSplit, tempNode); + } + + if (!nodeToSplit) + { + NS_NOTREACHED("null node obtained in nsHTMLEditRules::SplitNodeDeep()"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + /******************************************************** * main implemntation methods ********************************************************/ @@ -917,5 +1419,167 @@ nsHTMLEditRules::InsertSpace(nsIDOMSelection *aSelection, } +/////////////////////////////////////////////////////////////////////////// +// ReturnInHeader: do the right thing for returns pressed in headers +// +nsresult +nsHTMLEditRules::ReturnInHeader(nsIDOMSelection *aSelection, + nsIDOMNode *aHeader, + nsIDOMNode *aTextNode, + PRInt32 aOffset) +{ + if (!aSelection || !aHeader || !aTextNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr leftNode; + nsCOMPtr textNode = do_QueryInterface(aTextNode); // to hold a ref across the delete call + // split the node + nsresult res = mEditor->SplitNode(aTextNode, aOffset, getter_AddRefs(leftNode)); + if (NS_FAILED(res)) return res; + + // move the right node outside of the header, via deletion/insertion + // delete the right node + res = mEditor->DeleteNode(textNode); + if (NS_FAILED(res)) return res; + + // insert the right node + nsCOMPtr p; + aHeader->GetParentNode(getter_AddRefs(p)); + PRInt32 indx = GetIndexOf(p,aHeader); + res = mEditor->InsertNode(textNode,p,indx+1); + if (NS_FAILED(res)) return res; + + // merge text node with like sibling, if any + nsCOMPtr sibling; + textNode->GetNextSibling(getter_AddRefs(sibling)); + if (sibling) + { + res = mEditor->JoinNodes(textNode,sibling,p); + if (NS_FAILED(res)) return res; + textNode = sibling; // sibling was the node kept by the join; remember it in "textNode" + } + + // position selection before inserted node + res = aSelection->Collapse(textNode,0); + + 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) +{ + if (!aSelection || !aHeader || !aNode || !aCancel) return NS_ERROR_NULL_POINTER; + *aCancel = PR_FALSE; + + nsCOMPtr sibling; + nsresult res = NS_OK; + + // easy case, in a text node: + if (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? + aNode->GetPreviousSibling(getter_AddRefs(sibling)); + if (!sibling) + { + // no previous sib, so + // just fall out to default of inserting a BR + return res; + } + if (IsBreak(sibling)) + { + *aCancel = PR_TRUE; + // get rid of the break + res = mEditor->DeleteNode(sibling); + if (NS_FAILED(res)) return res; + // split the paragraph + res = SplitNodeDeep( aHeader, aNode, aOffset); + 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 = aNode->GetNextSibling(getter_AddRefs(sibling)); + if (!sibling) + { + // no next sib, so + // just fall out to default of inserting a BR + return res; + } + if (IsBreak(sibling)) + { + *aCancel = PR_TRUE; + // get rid of the break + res = mEditor->DeleteNode(sibling); + if (NS_FAILED(res)) return res; + // split the paragraph + res = SplitNodeDeep( aHeader, aNode, aOffset); + 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? + // 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 = SplitNodeDeep( aListItem, aNode, aOffset); + if (NS_FAILED(res)) return res; + res = aSelection->Collapse(aNode,0); + return res; +} + + + + + + + + + + + diff --git a/mozilla/editor/base/nsHTMLEditRules.h b/mozilla/editor/base/nsHTMLEditRules.h index b180b0d1e7d..07877a79cc4 100644 --- a/mozilla/editor/base/nsHTMLEditRules.h +++ b/mozilla/editor/base/nsHTMLEditRules.h @@ -54,30 +54,51 @@ protected: const nsString *inString, nsString *outString, TypeInState typeInState); - nsresult DidInsertText(nsIDOMSelection *aSelection, nsresult aResult); - nsresult WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel); - nsresult DidInsertBreak(nsIDOMSelection *aSelection, nsresult aResult); + nsresult WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::Direction aDir, PRBool *aCancel); nsresult InsertTab(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString); nsresult InsertSpace(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString); + nsresult ReturnInHeader(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset); + nsresult ReturnInParagraph(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset, PRBool *aCancel); + nsresult ReturnInListItem(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset); + // helper methods + static nsresult GetRightmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetLeftmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetPriorNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetNextNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetTabAsNBSPs(nsString *outString); static nsresult GetTabAsNBSPsAndSpace(nsString *outString); + + static nsCOMPtr GetTag(nsIDOMNode *aNode); static PRBool IsInlineNode(nsIDOMNode *aNode); static PRBool IsBlockNode(nsIDOMNode *aNode); static nsCOMPtr GetBlockNodeParent(nsIDOMNode *aNode); static PRBool HasSameBlockNodeParent(nsIDOMNode *aNode1, nsIDOMNode *aNode2); + static PRBool IsTextOrElementNode(nsIDOMNode *aNode); static PRBool IsTextNode(nsIDOMNode *aNode); + static PRBool IsEmptyTextNode(nsIDOMNode *aNode); + + static PRInt32 GetIndexOf(nsIDOMNode *aParent, nsIDOMNode *aChild); + + static PRBool IsHeader(nsIDOMNode *aNode); + static PRBool IsParagraph(nsIDOMNode *aNode); + static PRBool IsListItem(nsIDOMNode *aNode); + static PRBool IsBreak(nsIDOMNode *aNode); + static nsCOMPtr NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir); static nsresult GetStartNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outStartNode, PRInt32 *outStartOffset); + static nsresult GetEndNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outEndNode, PRInt32 *outEndOffset); nsresult IsPreformatted(nsIDOMNode *aNode, PRBool *aResult); nsresult IsNextCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult); nsresult IsPrevCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult); + nsresult SplitNodeDeep(nsIDOMNode *aNode, nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset); // data static nsIAtom *sAAtom; @@ -107,6 +128,17 @@ protected: static nsIAtom *sUAtom; static nsIAtom *sVarAtom; static nsIAtom *sWbrAtom; + + static nsIAtom *sH1Atom; + static nsIAtom *sH2Atom; + static nsIAtom *sH3Atom; + static nsIAtom *sH4Atom; + static nsIAtom *sH5Atom; + static nsIAtom *sH6Atom; + static nsIAtom *sParagraphAtom; + static nsIAtom *sListItemAtom; + static nsIAtom *sBreakAtom; + static PRInt32 sInstanceCount; }; diff --git a/mozilla/editor/base/nsTextEditRules.h b/mozilla/editor/base/nsTextEditRules.h index ab0f6ea2289..08796b0dc86 100644 --- a/mozilla/editor/base/nsTextEditRules.h +++ b/mozilla/editor/base/nsTextEditRules.h @@ -126,7 +126,7 @@ class nsTextRulesInfo : public nsRulesInfo { public: - nsTextRulesInfo(int aAction) : nsRulesInfo(aAction),placeTxn(0),inString(0),outString(0),typeInState() {} + nsTextRulesInfo(int aAction) : nsRulesInfo(aAction),placeTxn(0),inString(0),outString(0),typeInState(),dir(nsIEditor::eLTR) {} virtual ~nsTextRulesInfo() {} // used by kInsertText @@ -134,6 +134,8 @@ class nsTextRulesInfo : public nsRulesInfo const nsString *inString; nsString *outString; TypeInState typeInState; + + nsIEditor::Direction dir; }; #endif //nsTextEditRules_h__ diff --git a/mozilla/editor/base/nsTextEditor.cpp b/mozilla/editor/base/nsTextEditor.cpp index 6150972f14d..6ab01fcfbfe 100644 --- a/mozilla/editor/base/nsTextEditor.cpp +++ b/mozilla/editor/base/nsTextEditor.cpp @@ -746,6 +746,7 @@ NS_IMETHODIMP nsTextEditor::DeleteSelection(nsIEditor::Direction aDir) // pre-process nsEditor::GetSelection(getter_AddRefs(selection)); nsTextRulesInfo ruleInfo(nsTextEditRules::kDeleteSelection); + ruleInfo.dir = aDir; result = mRules->WillDoAction(selection, &ruleInfo, &cancel); if ((PR_FALSE==cancel) && (NS_SUCCEEDED(result))) { diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp index 87fbc7b100e..bd3164e7411 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.cpp @@ -72,6 +72,18 @@ nsIAtom *nsHTMLEditRules::sUAtom; nsIAtom *nsHTMLEditRules::sVarAtom; nsIAtom *nsHTMLEditRules::sWbrAtom; +nsIAtom *nsHTMLEditRules::sH1Atom; +nsIAtom *nsHTMLEditRules::sH2Atom; +nsIAtom *nsHTMLEditRules::sH3Atom; +nsIAtom *nsHTMLEditRules::sH4Atom; +nsIAtom *nsHTMLEditRules::sH5Atom; +nsIAtom *nsHTMLEditRules::sH6Atom; +nsIAtom *nsHTMLEditRules::sParagraphAtom; +nsIAtom *nsHTMLEditRules::sListItemAtom; +nsIAtom *nsHTMLEditRules::sBreakAtom; + + + PRInt32 nsHTMLEditRules::sInstanceCount = 0; /******************************************************** @@ -112,6 +124,16 @@ nsHTMLEditRules::nsHTMLEditRules() sUAtom = NS_NewAtom("u"); sVarAtom = NS_NewAtom("var"); sWbrAtom = NS_NewAtom("wbr"); + + sH1Atom = NS_NewAtom("h1"); + sH2Atom = NS_NewAtom("h2"); + sH3Atom = NS_NewAtom("h3"); + sH4Atom = NS_NewAtom("h4"); + sH5Atom = NS_NewAtom("h5"); + sH6Atom = NS_NewAtom("h6"); + sParagraphAtom = NS_NewAtom("p"); + sListItemAtom = NS_NewAtom("li"); + sBreakAtom = NS_NewAtom("br"); } ++sInstanceCount; @@ -148,6 +170,16 @@ nsHTMLEditRules::~nsHTMLEditRules() NS_IF_RELEASE(sUAtom); NS_IF_RELEASE(sVarAtom); NS_IF_RELEASE(sWbrAtom); + + NS_IF_RELEASE(sH1Atom); + NS_IF_RELEASE(sH2Atom); + NS_IF_RELEASE(sH3Atom); + NS_IF_RELEASE(sH4Atom); + NS_IF_RELEASE(sH5Atom); + NS_IF_RELEASE(sH6Atom); + NS_IF_RELEASE(sParagraphAtom); + NS_IF_RELEASE(sListItemAtom); + NS_IF_RELEASE(sBreakAtom); } --sInstanceCount; @@ -173,12 +205,14 @@ nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection, case kInsertText: return WillInsertText(aSelection, aCancel, - info->placeTxn, + info->placeTxn, info->inString, info->outString, info->typeInState); case kInsertBreak: return WillInsertBreak(aSelection, aCancel); + case kDeleteSelection: + return WillDeleteSelection(aSelection, info->dir, aCancel); } return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel); } @@ -187,19 +221,6 @@ NS_IMETHODIMP nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, nsresult aResult) { - if (!aSelection || !aInfo) - return NS_ERROR_NULL_POINTER; - - // my kingdom for dynamic cast - nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); - - switch (info->action) - { - case kInsertText: - return DidInsertText(aSelection, aResult); - case kInsertBreak: - return DidInsertBreak(aSelection, aResult); - } return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); } @@ -221,6 +242,7 @@ nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection, *aCancel = PR_FALSE; // XXX - need to handle strings of length >1 with embedded tabs or spaces + // XXX - what about embedded returns? // is it a tab? if (*inString == "\t" ) @@ -238,139 +260,426 @@ nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection, typeInState); } -nsresult -nsHTMLEditRules::DidInsertText(nsIDOMSelection *aSelection, - nsresult aResult) -{ - // for now, return nsTextEditRules version - return nsTextEditRules::DidInsertText(aSelection, aResult); -} - nsresult nsHTMLEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel) { if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; + + nsresult res; + + // 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::eLTR); + if (NS_FAILED(res)) return res; + } + + //smart splitting rules + nsCOMPtr node; + PRInt32 offset; + PRBool isPRE; + + res = GetStartNodeAndOffset(aSelection, &node, &offset); + if (NS_FAILED(res)) return res; + if (!node) return NS_ERROR_FAILURE; + + res = IsPreformatted(node,&isPRE); + if (NS_FAILED(res)) return res; + + if (isPRE) + { + nsString theString = "\n"; + *aCancel = PR_TRUE; + return mEditor->InsertText(theString); + } + + nsCOMPtr blockParent = GetBlockNodeParent(node); + if (!blockParent) return NS_ERROR_FAILURE; + + // headers: put selection after the header + if (IsHeader(blockParent)) + { + res = ReturnInHeader(aSelection, blockParent, node, offset); + *aCancel = PR_TRUE; + return NS_OK; + } + + // paragraphs: special rules to look for
s + if (IsParagraph(blockParent)) + { + res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel); + return NS_OK; + } + + // list items: special rules to make new list items + if (IsListItem(blockParent)) + { + res = ReturnInListItem(aSelection, blockParent, node, offset); + *aCancel = PR_TRUE; + return NS_OK; + } + + return WillInsert(aSelection, aCancel); } -// XXX: this code is all experimental, and has no effect on the content model yet -// the point here is to collapse adjacent BR's into P's + + nsresult -nsHTMLEditRules::DidInsertBreak(nsIDOMSelection *aSelection, nsresult aResult) +nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::Direction aDir, PRBool *aCancel) { - nsresult result = aResult; // if aResult is an error, we return it. - if (!aSelection) { return NS_ERROR_NULL_POINTER; } - PRBool isCollapsed; - aSelection->GetIsCollapsed(&isCollapsed); - NS_ASSERTION(PR_TRUE==isCollapsed, "selection not collapsed after insert break."); - // if the insert break resulted in consecutive BR tags, - // collapse the two BR tags into a single P - if (NS_SUCCEEDED(result)) + if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } + // initialize out param + *aCancel = PR_FALSE; + + nsresult res = NS_OK; + + PRBool bCollapsed; + res = aSelection->GetIsCollapsed(&bCollapsed); + if (NS_FAILED(res)) return res; + + nsCOMPtr node; + PRInt32 offset; + + res = GetStartNodeAndOffset(aSelection, &node, &offset); + if (NS_FAILED(res)) return res; + if (!node) return NS_ERROR_FAILURE; + + if (bCollapsed) { - nsCOMPtr enumerator; - enumerator = do_QueryInterface(aSelection,&result); - if (enumerator) + // easy case, in a text node: + if (IsTextNode(node)) { - enumerator->First(); - nsISupports *currentItem; - result = enumerator->CurrentItem(¤tItem); - if ((NS_SUCCEEDED(result)) && currentItem) + 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 && (aDir == nsIEditor::eRTL)) { - result = NS_ERROR_UNEXPECTED; - nsCOMPtr range( do_QueryInterface(currentItem) ); - if (range) + nsCOMPtr priorNode; + res = GetPriorNode(node, getter_AddRefs(priorNode)); + if (NS_FAILED(res)) return res; + + // XXX hackery - using this to skip empty text nodes, + // since these are almost always non displayed preservation + // of returns in the original html. but they could + // actually be significant, then we're hosed. FIXME + while (IsEmptyTextNode(priorNode)) { - nsIAtom *brTag = NS_NewAtom("BR"); - nsCOMPtr startNode; - result = range->GetStartParent(getter_AddRefs(startNode)); - if ((NS_SUCCEEDED(result)) && startNode) - { - PRInt32 offset; - range->GetStartOffset(&offset); - nsCOMPtrstartNodeChildren; - result = startNode->GetChildNodes(getter_AddRefs(startNodeChildren)); - if ((NS_SUCCEEDED(result)) && startNodeChildren) - { - nsCOMPtr selectedNode; - result = startNodeChildren->Item(offset, getter_AddRefs(selectedNode)); - if ((NS_SUCCEEDED(result)) && selectedNode) - { - nsCOMPtr prevNode; - result = selectedNode->GetPreviousSibling(getter_AddRefs(prevNode)); - if ((NS_SUCCEEDED(result)) && prevNode) - { - if (PR_TRUE==NodeIsType(prevNode, brTag)) - { // the previous node is a BR, check it's siblings - nsCOMPtr leftNode; - result = prevNode->GetPreviousSibling(getter_AddRefs(leftNode)); - if ((NS_SUCCEEDED(result)) && leftNode) - { - if (PR_TRUE==NodeIsType(leftNode, brTag)) - { // left sibling is also a BR, collapse - printf("1\n"); - } - else - { - if (PR_TRUE==NodeIsType(selectedNode, brTag)) - { // right sibling is also a BR, collapse - printf("2\n"); - } - } - } - } - } - // now check the next node from selectedNode - nsCOMPtr nextNode; - result = selectedNode->GetNextSibling(getter_AddRefs(nextNode)); - if ((NS_SUCCEEDED(result)) && nextNode) - { - if (PR_TRUE==NodeIsType(nextNode, brTag)) - { // the previous node is a BR, check it's siblings - nsCOMPtr rightNode; - result = nextNode->GetNextSibling(getter_AddRefs(rightNode)); - if ((NS_SUCCEEDED(result)) && rightNode) - { - if (PR_TRUE==NodeIsType(rightNode, brTag)) - { // right sibling is also a BR, collapse - printf("3\n"); - } - else - { - if (PR_TRUE==NodeIsType(selectedNode, brTag)) - { // left sibling is also a BR, collapse - printf("4\n"); - } - } - } - } - } - } - } - } - NS_RELEASE(brTag); + res = mEditor->DeleteNode(priorNode); + if (NS_FAILED(res)) return res; + res = GetPriorNode(node, getter_AddRefs(priorNode)); + if (NS_FAILED(res)) return res; } + + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, priorNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(priorNode); + nsCOMPtr rightParent = GetBlockNodeParent(node); + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // join para's, insert break + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // join blocks + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + } + + // at end of text node and deleted? + if ((offset == strLength) && (aDir == nsIEditor::eLTR)) + { + nsCOMPtr nextNode; + res = GetNextNode(node, getter_AddRefs(nextNode)); + if (NS_FAILED(res)) return res; + if (HasSameBlockNodeParent(node, nextNode)) return NS_OK; + + // deleting across blocks + // XXX hackery - using this to skip empty text nodes, + // since these are almost always non displayed preservation + // of returns in the original html. but they could + // actually be significant, then we're hosed. FIXME + while (IsEmptyTextNode(nextNode)) + { + res = mEditor->DeleteNode(nextNode); + if (NS_FAILED(res)) return res; + res = GetNextNode(node, getter_AddRefs(nextNode)); + if (NS_FAILED(res)) return res; + } + + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, nextNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(node); + nsCOMPtr rightParent = GetBlockNodeParent(nextNode); + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // join para's, insert break + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // join blocks + *aCancel = PR_TRUE; + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + + } + + // else do default + return NS_OK; } } - return result; -} + + // else we have a non collapsed selection + // figure out if the enpoints are in nodes that can be merged + nsCOMPtr endNode; + PRInt32 endOffset; + res = GetEndNodeAndOffset(aSelection, &endNode, &endOffset); + if (endNode.get() != node.get()) + { + // block parents the same? use defaul deletion + if (HasSameBlockNodeParent(node, endNode)) return NS_OK; + + // deleting across blocks + // are the blocks of same type? + nsCOMPtr leftParent = GetBlockNodeParent(node); + nsCOMPtr rightParent = 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; + nsCOMPtr leftAtom = GetTag(leftParent); + nsCOMPtr rightAtom = GetTag(rightParent); + if (leftAtom.get() == rightAtom.get()) + { + nsCOMPtr topParent; + leftParent->GetParentNode(getter_AddRefs(topParent)); + + if (IsParagraph(leftParent)) + { + // first delete the selection + *aCancel = PR_TRUE; + res = mEditor->nsEditor::DeleteSelection(aDir); + if (NS_FAILED(res)) return res; + // then join para's, insert break + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + if (NS_FAILED(res)) return res; + res = mEditor->InsertBreak(); + return res; + } + if (IsListItem(leftParent) || IsHeader(leftParent)) + { + // first delete the selection + *aCancel = PR_TRUE; + res = mEditor->nsEditor::DeleteSelection(aDir); + if (NS_FAILED(res)) return res; + // join blocks + res = mEditor->JoinNodes(leftParent,rightParent,topParent); + return res; + } + } + + // else blocks not same type, bail to default + return NS_OK; + } + + return res; +} /******************************************************** * helper methods ********************************************************/ +nsresult +nsHTMLEditRules::GetRightmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result = NS_OK; + nsCOMPtr resultNode(do_QueryInterface(aCurrentNode)); + PRBool hasChildren; + resultNode->HasChildNodes(&hasChildren); + while ((NS_SUCCEEDED(result)) && (PR_TRUE==hasChildren)) + { + nsCOMPtr temp(resultNode); + temp->GetLastChild(getter_AddRefs(resultNode)); + resultNode->HasChildNodes(&hasChildren); + } + + if (NS_SUCCEEDED(result)) { + *aResultNode = resultNode; + NS_ADDREF(*aResultNode); + } + return result; +} + +nsresult +nsHTMLEditRules::GetLeftmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result = NS_OK; + nsCOMPtr resultNode(do_QueryInterface(aCurrentNode)); + PRBool hasChildren; + resultNode->HasChildNodes(&hasChildren); + while ((NS_SUCCEEDED(result)) && (PR_TRUE==hasChildren)) + { + nsCOMPtr temp(resultNode); + temp->GetFirstChild(getter_AddRefs(resultNode)); + resultNode->HasChildNodes(&hasChildren); + } + + if (NS_SUCCEEDED(result)) { + *aResultNode = resultNode; + NS_ADDREF(*aResultNode); + } + return result; +} + +nsresult +nsHTMLEditRules::GetPriorNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result; + *aResultNode = nsnull; + // if aCurrentNode has a left sibling, return that sibling's rightmost child (or itself if it has no children) + result = aCurrentNode->GetPreviousSibling(aResultNode); + if ((NS_SUCCEEDED(result)) && *aResultNode) + { + return GetRightmostChild(*aResultNode, aResultNode); + } + // otherwise, walk up the parent change until there is a child that comes before + // the ancestor of aCurrentNode. Then return that node's rightmost child + + nsCOMPtr parent(do_QueryInterface(aCurrentNode)); + do { + nsCOMPtr node(parent); + result = node->GetParentNode(getter_AddRefs(parent)); + if ((NS_SUCCEEDED(result)) && parent) + { + result = parent->GetPreviousSibling(getter_AddRefs(node)); + if ((NS_SUCCEEDED(result)) && node) + { + return GetRightmostChild(node, aResultNode); + } + } + } while ((NS_SUCCEEDED(result)) && parent); + + return result; +} + +nsresult +nsHTMLEditRules::GetNextNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode) +{ + nsresult result; + *aResultNode = nsnull; + // if aCurrentNode has a right sibling, return that sibling's leftmost child (or itself if it has no children) + result = aCurrentNode->GetNextSibling(aResultNode); + if ((NS_SUCCEEDED(result)) && *aResultNode) + { + return GetLeftmostChild(*aResultNode, aResultNode); + } + // otherwise, walk up the parent change until there is a child that comes before + // the ancestor of aCurrentNode. Then return that node's rightmost child + + nsCOMPtr parent(do_QueryInterface(aCurrentNode)); + do { + nsCOMPtr node(parent); + result = node->GetParentNode(getter_AddRefs(parent)); + if ((NS_SUCCEEDED(result)) && parent) + { + result = parent->GetNextSibling(getter_AddRefs(node)); + if ((NS_SUCCEEDED(result)) && node) + { + return GetLeftmostChild(node, aResultNode); + } + } + } while ((NS_SUCCEEDED(result)) && parent); + + return result; +} + + + +/////////////////////////////////////////////////////////////////////////// +// GetTag: digs out the atom for the tag of this node +// +nsCOMPtr +nsHTMLEditRules::GetTag(nsIDOMNode *aNode) +{ + nsCOMPtr atom; + + if (!aNode) + { + NS_NOTREACHED("null node passed to nsHTMLEditRules::GetTag()"); + return atom; + } + + nsCOMPtr content = do_QueryInterface(aNode); + content->GetTag(*getter_AddRefs(atom)); + + return atom; +} + /////////////////////////////////////////////////////////////////////////// // IsBlockNode: true if this node is an html block node // PRBool nsHTMLEditRules::IsBlockNode(nsIDOMNode *aNode) { - nsIAtom* atom = nsnull; - PRBool result; + nsCOMPtr atom; if (!aNode) { @@ -378,54 +687,45 @@ nsHTMLEditRules::IsBlockNode(nsIDOMNode *aNode) return PR_FALSE; } - nsCOMPtr content = do_QueryInterface(aNode); - if (!content) - { - NS_NOTREACHED("could not get content node in IsBlockNode()"); + if (IsTextNode(aNode)) return PR_FALSE; - } - - content->GetTag(atom); + + atom = GetTag(aNode); if (!atom) return PR_TRUE; - if (sAAtom != atom && - sAddressAtom != atom && - sBigAtom != atom && - sBlinkAtom != atom && - sBAtom != atom && - sCiteAtom != atom && - sCodeAtom != atom && - sDfnAtom != atom && - sEmAtom != atom && - sFontAtom != atom && - sIAtom != atom && - sKbdAtom != atom && - sKeygenAtom != atom && - sNobrAtom != atom && - sSAtom != atom && - sSampAtom != atom && - sSmallAtom != atom && - sSpacerAtom != atom && - sSpanAtom != atom && - sStrikeAtom != atom && - sStrongAtom != atom && - sSubAtom != atom && - sSupAtom != atom && - sTtAtom != atom && - sUAtom != atom && - sVarAtom != atom && - sWbrAtom != atom) + if (sAAtom != atom.get() && + sAddressAtom != atom.get() && + sBigAtom != atom.get() && + sBlinkAtom != atom.get() && + sBAtom != atom.get() && + sCiteAtom != atom.get() && + sCodeAtom != atom.get() && + sDfnAtom != atom.get() && + sEmAtom != atom.get() && + sFontAtom != atom.get() && + sIAtom != atom.get() && + sKbdAtom != atom.get() && + sKeygenAtom != atom.get() && + sNobrAtom != atom.get() && + sSAtom != atom.get() && + sSampAtom != atom.get() && + sSmallAtom != atom.get() && + sSpacerAtom != atom.get() && + sSpanAtom != atom.get() && + sStrikeAtom != atom.get() && + sStrongAtom != atom.get() && + sSubAtom != atom.get() && + sSupAtom != atom.get() && + sTtAtom != atom.get() && + sUAtom != atom.get() && + sVarAtom != atom.get() && + sWbrAtom != atom.get()) { - result = PR_TRUE; + return PR_TRUE; } - else - { - result = PR_FALSE; - } - NS_RELEASE(atom); - return result; + return PR_FALSE; } @@ -440,13 +740,16 @@ nsHTMLEditRules::IsInlineNode(nsIDOMNode *aNode) /////////////////////////////////////////////////////////////////////////// // GetBlockNodeParent: returns enclosing block level ancestor, if any -// else returns the node itself +// else returns the node itself. Note that if the +// node itself is a block node, it is returned. nsCOMPtr nsHTMLEditRules::GetBlockNodeParent(nsIDOMNode *aNode) { nsCOMPtr tmp = do_QueryInterface(aNode); nsCOMPtr p; + if (IsBlockNode(aNode)) + return tmp; if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree return tmp; @@ -526,6 +829,127 @@ nsHTMLEditRules::IsTextNode(nsIDOMNode *aNode) } +/////////////////////////////////////////////////////////////////////////// +// IsEmptyTextNode: true if node of dom type text and is empty +// or if it has only char which is whitespace. HACKEROONY! +PRBool +nsHTMLEditRules::IsEmptyTextNode(nsIDOMNode *aNode) +{ + if (!aNode) + { + NS_NOTREACHED("null node passed to IsTextNode()"); + return PR_FALSE; + } + + if (!IsTextNode(aNode)) + return PR_FALSE; + + nsCOMPtr textNode = do_QueryInterface(aNode); + PRUint32 strLength; + textNode->GetLength(&strLength); + if (!strLength) + return PR_TRUE; + + nsString tempString; + textNode->GetData(tempString); + tempString.StripWhitespace(); + if (!tempString.Length()) + return PR_TRUE; + + return PR_FALSE; +} + + + +PRInt32 +nsHTMLEditRules::GetIndexOf(nsIDOMNode *parent, nsIDOMNode *child) +{ + PRInt32 index = 0; + + NS_PRECONDITION(parent, "null parent passed to nsHTMLEditRules::GetIndexOf"); + NS_PRECONDITION(parent, "null child passed to nsHTMLEditRules::GetIndexOf"); + nsCOMPtr content = do_QueryInterface(parent); + nsCOMPtr cChild = do_QueryInterface(child); + NS_PRECONDITION(content, "null content in nsHTMLEditRules::GetIndexOf"); + NS_PRECONDITION(cChild, "null content in nsHTMLEditRules::GetIndexOf"); + + nsresult res = content->IndexOf(cChild, index); + if (NS_FAILED(res)) + { + NS_NOTREACHED("could not find child in parent - nsHTMLEditRules::GetIndexOf"); + } + return index; +} + + +/////////////////////////////////////////////////////////////////////////// +// IsHeader: true if node an html header +// +PRBool +nsHTMLEditRules::IsHeader(nsIDOMNode *node) +{ + NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsHeader"); + nsCOMPtr atom = GetTag(node); + if ( (atom.get() == sH1Atom) || + (atom.get() == sH2Atom) || + (atom.get() == sH3Atom) || + (atom.get() == sH4Atom) || + (atom.get() == sH5Atom) || + (atom.get() == sH6Atom) ) + { + 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"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sParagraphAtom) + { + 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"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sListItemAtom) + { + return PR_TRUE; + } + return PR_FALSE; +} + + +/////////////////////////////////////////////////////////////////////////// +// IsListItem: true if node an html list item +// +PRBool +nsHTMLEditRules::IsBreak(nsIDOMNode *node) +{ + NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBreak"); + nsCOMPtr atom = GetTag(node); + if (atom.get() == sBreakAtom) + { + return PR_TRUE; + } + return PR_FALSE; +} + /////////////////////////////////////////////////////////////////////////// // NextNodeInBlock: gets the next/prev node in the block, if any. Next node @@ -576,8 +1000,8 @@ nsHTMLEditRules::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir) /////////////////////////////////////////////////////////////////////////// -// GetStartNode: returns whatever the start parent is of the first range -// in the selection. +// GetStartNodeAndOffset: returns whatever the start parent & offset is of +// the first range in the selection. nsresult nsHTMLEditRules::GetStartNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outStartNode, @@ -610,6 +1034,41 @@ nsHTMLEditRules::GetStartNodeAndOffset(nsIDOMSelection *aSelection, } +/////////////////////////////////////////////////////////////////////////// +// GetEndNodeAndOffset: returns whatever the end parent & offset is of +// the first range in the selection. +nsresult +nsHTMLEditRules::GetEndNodeAndOffset(nsIDOMSelection *aSelection, + nsCOMPtr *outEndNode, + PRInt32 *outEndOffset) +{ + if (!outEndNode || !outEndOffset) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr enumerator; + enumerator = do_QueryInterface(aSelection); + if (!enumerator) + return NS_ERROR_FAILURE; + + enumerator->First(); + nsISupports *currentItem; + if ((NS_FAILED(enumerator->CurrentItem(¤tItem))) || !currentItem) + return NS_ERROR_FAILURE; + + nsCOMPtr range( do_QueryInterface(currentItem) ); + if (!range) + return NS_ERROR_FAILURE; + + if (NS_FAILED(range->GetEndParent(getter_AddRefs(*outEndNode)))) + return NS_ERROR_FAILURE; + + if (NS_FAILED(range->GetEndOffset(outEndOffset))) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + /////////////////////////////////////////////////////////////////////////// // IsPreformatted: checks the style info for the node for the preformatted // text style. @@ -673,6 +1132,7 @@ nsHTMLEditRules::IsNextCharWhitespace(nsIDOMNode *aParentNode, // harder case: next char in next node. nsCOMPtr node = NextNodeInBlock(aParentNode, kIterForward); + nsCOMPtr tmp; while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes @@ -694,7 +1154,8 @@ nsHTMLEditRules::IsNextCharWhitespace(nsIDOMNode *aParentNode, break; } } - node = NextNodeInBlock(aParentNode, kIterForward); + tmp = node; + node = NextNodeInBlock(tmp, kIterForward); } return NS_OK; @@ -728,6 +1189,7 @@ nsHTMLEditRules::IsPrevCharWhitespace(nsIDOMNode *aParentNode, // harder case: prev char in next node nsCOMPtr node = NextNodeInBlock(aParentNode, kIterBackward); + nsCOMPtr tmp; while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes @@ -750,7 +1212,8 @@ nsHTMLEditRules::IsPrevCharWhitespace(nsIDOMNode *aParentNode, } } // otherwise we found a node we want to skip, keep going - node = NextNodeInBlock(aParentNode, kIterBackward); + tmp = node; + node = NextNodeInBlock(tmp, kIterBackward); } return NS_OK; @@ -785,6 +1248,45 @@ nsHTMLEditRules::GetTabAsNBSPsAndSpace(nsString *outString) +/////////////////////////////////////////////////////////////////////////// +// SplitNodeDeep: this plits a node "deeply", splitting children as +// appropriate. The place to split is represented by +// a dom point at {splitPointParent, splitPointOffset}. +// That dom point must be inside aNode, which is the node to +// split. +nsresult +nsHTMLEditRules::SplitNodeDeep(nsIDOMNode *aNode, + nsIDOMNode *aSplitPointParent, + PRInt32 aSplitPointOffset) +{ + if (!aNode || !aSplitPointParent) return NS_ERROR_NULL_POINTER; + nsCOMPtr nodeToSplit = do_QueryInterface(aSplitPointParent); + nsCOMPtr tempNode; + PRInt32 offset = aSplitPointOffset; + + while (nodeToSplit) + { + nsresult res = mEditor->SplitNode(nodeToSplit, offset, getter_AddRefs(tempNode)); + if (NS_FAILED(res)) return res; + + if (nodeToSplit.get() == aNode) // we split all the way up to (and including) aNode; we're done + break; + + tempNode = nodeToSplit; + res = tempNode->GetParentNode(getter_AddRefs(nodeToSplit)); + offset = GetIndexOf(nodeToSplit, tempNode); + } + + if (!nodeToSplit) + { + NS_NOTREACHED("null node obtained in nsHTMLEditRules::SplitNodeDeep()"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + /******************************************************** * main implemntation methods ********************************************************/ @@ -917,5 +1419,167 @@ nsHTMLEditRules::InsertSpace(nsIDOMSelection *aSelection, } +/////////////////////////////////////////////////////////////////////////// +// ReturnInHeader: do the right thing for returns pressed in headers +// +nsresult +nsHTMLEditRules::ReturnInHeader(nsIDOMSelection *aSelection, + nsIDOMNode *aHeader, + nsIDOMNode *aTextNode, + PRInt32 aOffset) +{ + if (!aSelection || !aHeader || !aTextNode) return NS_ERROR_NULL_POINTER; + + nsCOMPtr leftNode; + nsCOMPtr textNode = do_QueryInterface(aTextNode); // to hold a ref across the delete call + // split the node + nsresult res = mEditor->SplitNode(aTextNode, aOffset, getter_AddRefs(leftNode)); + if (NS_FAILED(res)) return res; + + // move the right node outside of the header, via deletion/insertion + // delete the right node + res = mEditor->DeleteNode(textNode); + if (NS_FAILED(res)) return res; + + // insert the right node + nsCOMPtr p; + aHeader->GetParentNode(getter_AddRefs(p)); + PRInt32 indx = GetIndexOf(p,aHeader); + res = mEditor->InsertNode(textNode,p,indx+1); + if (NS_FAILED(res)) return res; + + // merge text node with like sibling, if any + nsCOMPtr sibling; + textNode->GetNextSibling(getter_AddRefs(sibling)); + if (sibling) + { + res = mEditor->JoinNodes(textNode,sibling,p); + if (NS_FAILED(res)) return res; + textNode = sibling; // sibling was the node kept by the join; remember it in "textNode" + } + + // position selection before inserted node + res = aSelection->Collapse(textNode,0); + + 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) +{ + if (!aSelection || !aHeader || !aNode || !aCancel) return NS_ERROR_NULL_POINTER; + *aCancel = PR_FALSE; + + nsCOMPtr sibling; + nsresult res = NS_OK; + + // easy case, in a text node: + if (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? + aNode->GetPreviousSibling(getter_AddRefs(sibling)); + if (!sibling) + { + // no previous sib, so + // just fall out to default of inserting a BR + return res; + } + if (IsBreak(sibling)) + { + *aCancel = PR_TRUE; + // get rid of the break + res = mEditor->DeleteNode(sibling); + if (NS_FAILED(res)) return res; + // split the paragraph + res = SplitNodeDeep( aHeader, aNode, aOffset); + 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 = aNode->GetNextSibling(getter_AddRefs(sibling)); + if (!sibling) + { + // no next sib, so + // just fall out to default of inserting a BR + return res; + } + if (IsBreak(sibling)) + { + *aCancel = PR_TRUE; + // get rid of the break + res = mEditor->DeleteNode(sibling); + if (NS_FAILED(res)) return res; + // split the paragraph + res = SplitNodeDeep( aHeader, aNode, aOffset); + 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? + // 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 = SplitNodeDeep( aListItem, aNode, aOffset); + if (NS_FAILED(res)) return res; + res = aSelection->Collapse(aNode,0); + return res; +} + + + + + + + + + + + diff --git a/mozilla/editor/libeditor/html/nsHTMLEditRules.h b/mozilla/editor/libeditor/html/nsHTMLEditRules.h index b180b0d1e7d..07877a79cc4 100644 --- a/mozilla/editor/libeditor/html/nsHTMLEditRules.h +++ b/mozilla/editor/libeditor/html/nsHTMLEditRules.h @@ -54,30 +54,51 @@ protected: const nsString *inString, nsString *outString, TypeInState typeInState); - nsresult DidInsertText(nsIDOMSelection *aSelection, nsresult aResult); - nsresult WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel); - nsresult DidInsertBreak(nsIDOMSelection *aSelection, nsresult aResult); + nsresult WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::Direction aDir, PRBool *aCancel); nsresult InsertTab(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString); nsresult InsertSpace(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString); + nsresult ReturnInHeader(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset); + nsresult ReturnInParagraph(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset, PRBool *aCancel); + nsresult ReturnInListItem(nsIDOMSelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset); + // helper methods + static nsresult GetRightmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetLeftmostChild(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetPriorNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetNextNode(nsIDOMNode *aCurrentNode, nsIDOMNode **aResultNode); + static nsresult GetTabAsNBSPs(nsString *outString); static nsresult GetTabAsNBSPsAndSpace(nsString *outString); + + static nsCOMPtr GetTag(nsIDOMNode *aNode); static PRBool IsInlineNode(nsIDOMNode *aNode); static PRBool IsBlockNode(nsIDOMNode *aNode); static nsCOMPtr GetBlockNodeParent(nsIDOMNode *aNode); static PRBool HasSameBlockNodeParent(nsIDOMNode *aNode1, nsIDOMNode *aNode2); + static PRBool IsTextOrElementNode(nsIDOMNode *aNode); static PRBool IsTextNode(nsIDOMNode *aNode); + static PRBool IsEmptyTextNode(nsIDOMNode *aNode); + + static PRInt32 GetIndexOf(nsIDOMNode *aParent, nsIDOMNode *aChild); + + static PRBool IsHeader(nsIDOMNode *aNode); + static PRBool IsParagraph(nsIDOMNode *aNode); + static PRBool IsListItem(nsIDOMNode *aNode); + static PRBool IsBreak(nsIDOMNode *aNode); + static nsCOMPtr NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir); static nsresult GetStartNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outStartNode, PRInt32 *outStartOffset); + static nsresult GetEndNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outEndNode, PRInt32 *outEndOffset); nsresult IsPreformatted(nsIDOMNode *aNode, PRBool *aResult); nsresult IsNextCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult); nsresult IsPrevCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult); + nsresult SplitNodeDeep(nsIDOMNode *aNode, nsIDOMNode *aSplitPointParent, PRInt32 aSplitPointOffset); // data static nsIAtom *sAAtom; @@ -107,6 +128,17 @@ protected: static nsIAtom *sUAtom; static nsIAtom *sVarAtom; static nsIAtom *sWbrAtom; + + static nsIAtom *sH1Atom; + static nsIAtom *sH2Atom; + static nsIAtom *sH3Atom; + static nsIAtom *sH4Atom; + static nsIAtom *sH5Atom; + static nsIAtom *sH6Atom; + static nsIAtom *sParagraphAtom; + static nsIAtom *sListItemAtom; + static nsIAtom *sBreakAtom; + static PRInt32 sInstanceCount; }; diff --git a/mozilla/editor/libeditor/text/nsTextEditRules.h b/mozilla/editor/libeditor/text/nsTextEditRules.h index ab0f6ea2289..08796b0dc86 100644 --- a/mozilla/editor/libeditor/text/nsTextEditRules.h +++ b/mozilla/editor/libeditor/text/nsTextEditRules.h @@ -126,7 +126,7 @@ class nsTextRulesInfo : public nsRulesInfo { public: - nsTextRulesInfo(int aAction) : nsRulesInfo(aAction),placeTxn(0),inString(0),outString(0),typeInState() {} + nsTextRulesInfo(int aAction) : nsRulesInfo(aAction),placeTxn(0),inString(0),outString(0),typeInState(),dir(nsIEditor::eLTR) {} virtual ~nsTextRulesInfo() {} // used by kInsertText @@ -134,6 +134,8 @@ class nsTextRulesInfo : public nsRulesInfo const nsString *inString; nsString *outString; TypeInState typeInState; + + nsIEditor::Direction dir; }; #endif //nsTextEditRules_h__