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;
}