1. IsEditable is much less hacky. It basically says: 1. if the node is a special bogus text node, it's not editable 2. if the node is a text node with only newlines, then it's not editable 3. if the node has no frame associated with it, then it is not editable 4. otherwise, it is editable. As part of this, I had to make IsEditable non-static. This had some cascading effects on static methods that called IsEditable, which likewise had to be made non-static. 2. SetTextProperties now works when the endpoints are leaf nodes (like images), not just text. Charlie, you should put this through some stress cases. git-svn-id: svn://10.0.0.236/trunk@37741 18797224-902f-48f8-a5cc-f745e15eee43
1791 lines
55 KiB
C++
1791 lines
55 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL") you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
#include "nsHTMLEditRules.h"
|
|
#include "nsEditor.h"
|
|
#include "nsTextEditor.h"
|
|
#include "PlaceholderTxn.h"
|
|
#include "InsertTextTxn.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentIterator.h"
|
|
#include "nsIDOMNode.h"
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIDOMSelection.h"
|
|
#include "nsIDOMRange.h"
|
|
#include "nsIDOMCharacterData.h"
|
|
#include "nsIEnumerator.h"
|
|
#include "nsISupportsArray.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsLayoutCID.h"
|
|
|
|
class nsIFrame;
|
|
|
|
//const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
|
|
//const static char* kMOZEditorBogusNodeValue="TRUE";
|
|
const unsigned char nbsp = 160;
|
|
|
|
static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID);
|
|
// static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
|
|
static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
|
|
|
|
enum
|
|
{
|
|
kLonely = 0,
|
|
kPrevSib = 1,
|
|
kNextSib = 2,
|
|
kBothSibs = 3
|
|
};
|
|
|
|
/********************************************************
|
|
* Constructor/Destructor
|
|
********************************************************/
|
|
|
|
nsHTMLEditRules::nsHTMLEditRules()
|
|
{
|
|
mPINSelection = PR_FALSE;
|
|
}
|
|
|
|
nsHTMLEditRules::~nsHTMLEditRules()
|
|
{
|
|
}
|
|
|
|
|
|
/********************************************************
|
|
* Public methods
|
|
********************************************************/
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection,
|
|
nsRulesInfo *aInfo, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aInfo || !aCancel)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*aCancel = PR_FALSE;
|
|
|
|
// my kingdom for dynamic cast
|
|
nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
|
|
|
|
switch (info->action)
|
|
{
|
|
case kInsertText:
|
|
return WillInsertText(aSelection,
|
|
aCancel,
|
|
info->placeTxn,
|
|
info->inString,
|
|
info->outString,
|
|
info->typeInState,
|
|
info->maxLength);
|
|
case kInsertBreak:
|
|
return WillInsertBreak(aSelection, aCancel);
|
|
case kDeleteSelection:
|
|
return WillDeleteSelection(aSelection, info->collapsedAction, aCancel);
|
|
case kMakeList:
|
|
return WillMakeList(aSelection, info->bOrdered, aCancel);
|
|
case kIndent:
|
|
return WillIndent(aSelection, aCancel);
|
|
case kOutdent:
|
|
return WillOutdent(aSelection, aCancel);
|
|
case kAlign:
|
|
return WillAlign(aSelection, info->alignType, aCancel);
|
|
case kMakeHeader:
|
|
return WillMakeHeader(aSelection, aCancel);
|
|
case kMakeAddress:
|
|
return WillMakeAddress(aSelection, aCancel);
|
|
case kMakePRE:
|
|
return WillMakePRE(aSelection, aCancel);
|
|
}
|
|
return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection,
|
|
nsRulesInfo *aInfo, nsresult aResult)
|
|
{
|
|
// my kingdom for dynamic cast
|
|
nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo);
|
|
|
|
switch (info->action)
|
|
{
|
|
case kInsertBreak:
|
|
return NS_OK;
|
|
}
|
|
|
|
return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
|
|
}
|
|
|
|
|
|
/********************************************************
|
|
* Protected rules methods
|
|
********************************************************/
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection,
|
|
PRBool *aCancel,
|
|
PlaceholderTxn **aTxn,
|
|
const nsString *inString,
|
|
nsString *outString,
|
|
TypeInState typeInState,
|
|
PRInt32 aMaxLength)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*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" )
|
|
return InsertTab(aSelection,aCancel,aTxn,outString);
|
|
// is it a space?
|
|
if (*inString == " ")
|
|
return InsertSpace(aSelection,aCancel,aTxn,outString);
|
|
|
|
// otherwise, return nsTextEditRules version
|
|
return nsTextEditRules::WillInsertText(aSelection,
|
|
aCancel,
|
|
aTxn,
|
|
inString,
|
|
outString,
|
|
typeInState,
|
|
aMaxLength);
|
|
}
|
|
|
|
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::eDoNothing);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
//smart splitting rules
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
PRBool isPRE;
|
|
|
|
res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
|
|
res = mEditor->IsPreformatted(node,&isPRE);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (isPRE)
|
|
{
|
|
nsString theString = "\n";
|
|
*aCancel = PR_TRUE;
|
|
return mEditor->InsertText(theString);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> blockParent = mEditor->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 <br>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);
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillDeleteSelection(nsIDOMSelection *aSelection, nsIEditor::ECollapsedSelectionAction aAction, PRBool *aCancel)
|
|
{
|
|
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<nsIDOMNode> node;
|
|
PRInt32 offset;
|
|
|
|
res = mEditor->GetStartNodeAndOffset(aSelection, &node, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
|
|
if (bCollapsed)
|
|
{
|
|
// easy case, in a text node:
|
|
if (mEditor->IsTextNode(node))
|
|
{
|
|
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(node);
|
|
PRUint32 strLength;
|
|
res = textNode->GetLength(&strLength);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// at beginning of text node and backspaced?
|
|
if (!offset && (aAction == nsIEditor::eDeleteLeft))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> priorNode;
|
|
res = mEditor->GetPriorNode(node, PR_TRUE, getter_AddRefs(priorNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if there is no prior node then cancel the deletion
|
|
if (!priorNode)
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// block parents the same? use default deletion
|
|
if (mEditor->HasSameBlockNodeParent(node, priorNode)) return NS_OK;
|
|
|
|
// deleting across blocks
|
|
// are the blocks of same type?
|
|
nsCOMPtr<nsIDOMNode> leftParent = mEditor->GetBlockNodeParent(priorNode);
|
|
nsCOMPtr<nsIDOMNode> rightParent = mEditor->GetBlockNodeParent(node);
|
|
nsCOMPtr<nsIAtom> leftAtom = mEditor->GetTag(leftParent);
|
|
nsCOMPtr<nsIAtom> rightAtom = mEditor->GetTag(rightParent);
|
|
|
|
if (leftAtom.get() == rightAtom.get())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> topParent;
|
|
leftParent->GetParentNode(getter_AddRefs(topParent));
|
|
|
|
if (IsParagraph(leftParent))
|
|
{
|
|
// join para's, insert break
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertBreak();
|
|
return res;
|
|
}
|
|
if (IsListItem(leftParent) || IsHeader(leftParent))
|
|
{
|
|
// join blocks
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// else blocks not same type, bail to default
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
// at end of text node and deleted?
|
|
if ((offset == (PRInt32)strLength)
|
|
&& (aAction == nsIEditor::eDeleteRight))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
res = mEditor->GetNextNode(node, PR_TRUE, getter_AddRefs(nextNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if there is no next node then cancel the deletion
|
|
if (!nextNode)
|
|
{
|
|
*aCancel = PR_TRUE;
|
|
return res;
|
|
}
|
|
|
|
// block parents the same? use default deletion
|
|
if (mEditor->HasSameBlockNodeParent(node, nextNode)) return NS_OK;
|
|
|
|
// deleting across blocks
|
|
// are the blocks of same type?
|
|
nsCOMPtr<nsIDOMNode> leftParent = mEditor->GetBlockNodeParent(node);
|
|
nsCOMPtr<nsIDOMNode> rightParent = mEditor->GetBlockNodeParent(nextNode);
|
|
nsCOMPtr<nsIAtom> leftAtom = mEditor->GetTag(leftParent);
|
|
nsCOMPtr<nsIAtom> rightAtom = mEditor->GetTag(rightParent);
|
|
|
|
if (leftAtom.get() == rightAtom.get())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> topParent;
|
|
leftParent->GetParentNode(getter_AddRefs(topParent));
|
|
|
|
if (IsParagraph(leftParent))
|
|
{
|
|
// join para's, insert break
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertBreak();
|
|
return res;
|
|
}
|
|
if (IsListItem(leftParent) || IsHeader(leftParent))
|
|
{
|
|
// join blocks
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// else blocks not same type, bail to default
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
// else do default
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// else we have a non collapsed selection
|
|
// figure out if the enpoints are in nodes that can be merged
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 endOffset;
|
|
res = mEditor->GetEndNodeAndOffset(aSelection, &endNode, &endOffset);
|
|
if (endNode.get() != node.get())
|
|
{
|
|
// block parents the same? use default deletion
|
|
if (mEditor->HasSameBlockNodeParent(node, endNode)) return NS_OK;
|
|
|
|
// deleting across blocks
|
|
// are the blocks of same type?
|
|
nsCOMPtr<nsIDOMNode> leftParent = mEditor->GetBlockNodeParent(node);
|
|
nsCOMPtr<nsIDOMNode> rightParent = mEditor->GetBlockNodeParent(endNode);
|
|
|
|
// are the blocks siblings?
|
|
nsCOMPtr<nsIDOMNode> leftBlockParent;
|
|
nsCOMPtr<nsIDOMNode> 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<nsIAtom> leftAtom = mEditor->GetTag(leftParent);
|
|
nsCOMPtr<nsIAtom> rightAtom = mEditor->GetTag(rightParent);
|
|
|
|
if (leftAtom.get() == rightAtom.get())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> topParent;
|
|
leftParent->GetParentNode(getter_AddRefs(topParent));
|
|
|
|
if (IsParagraph(leftParent))
|
|
{
|
|
// first delete the selection
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->nsEditor::DeleteSelection(aAction);
|
|
if (NS_FAILED(res)) return res;
|
|
// then join para's, insert break
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
if (NS_FAILED(res)) return res;
|
|
//res = mEditor->InsertBreak();
|
|
// uhh, no, we don't want to have the <br> on a deleted selction across para's
|
|
return res;
|
|
}
|
|
if (IsListItem(leftParent) || IsHeader(leftParent))
|
|
{
|
|
// first delete the selection
|
|
*aCancel = PR_TRUE;
|
|
res = mEditor->nsEditor::DeleteSelection(aAction);
|
|
if (NS_FAILED(res)) return res;
|
|
// join blocks
|
|
res = mEditor->JoinNodeDeep(leftParent,rightParent,aSelection);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
// else blocks not same type, bail to default
|
|
return NS_OK;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeList(nsIDOMSelection *aSelection, PRBool aOrdered, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_TRUE;
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection);
|
|
nsresult res;
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, &arrayOfRanges, kMakeList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kMakeList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if we ended up with any nodes in the list that aren't blocknodes,
|
|
// find their block parent instead and use that.
|
|
|
|
// i started writing this and then the sky fell. there are big questions
|
|
// about what to do here. i may need to switch from thinking about an array of
|
|
// nodes to act on to instead think of an array of ranges to act on.
|
|
|
|
// Next we remove all the <br>'s in the array. This won't prevent <br>'s
|
|
// inside of <p>'s from being significant - those <br>'s are not hit by
|
|
// the subtree iterator, since they are enclosed in a <p>.
|
|
|
|
// We also remove all non-editable nodes. Leave them be.
|
|
|
|
PRUint32 listCount;
|
|
PRInt32 i;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (i=listCount-1; i>=0; i--)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> testNode( do_QueryInterface(isupports ) );
|
|
if (IsBreak(testNode))
|
|
{
|
|
arrayOfNodes->RemoveElementAt(i);
|
|
}
|
|
else if (!mEditor->IsEditable(testNode))
|
|
{
|
|
arrayOfNodes->RemoveElementAt(i);
|
|
}
|
|
}
|
|
|
|
// if there is only one node in the array, and it is a list, div, or blockquote,
|
|
// then look inside of it until we find what we want to make a list out of.
|
|
arrayOfNodes->Count(&listCount);
|
|
if (listCount == 1)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(0));
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
|
|
|
|
while (IsDiv(curNode) || IsOrderedList(curNode) || IsUnorderedList(curNode) || IsBlockquote(curNode))
|
|
{
|
|
// dive as long as there is only one child, and it is a list, div, or blockquote
|
|
PRUint32 numChildren;
|
|
res = mEditor->CountEditableChildren(curNode, numChildren);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (numChildren == 1)
|
|
{
|
|
// keep diving
|
|
nsCOMPtr <nsIDOMNode> tmpNode = nsEditor::GetChildAt(curNode, 0);
|
|
if (IsDiv(tmpNode) || IsOrderedList(tmpNode) || IsUnorderedList(tmpNode) || IsBlockquote(tmpNode))
|
|
{
|
|
// check editablility XXX moose
|
|
curNode = tmpNode;
|
|
}
|
|
else break;
|
|
}
|
|
else break;
|
|
}
|
|
// we've found innermost list/blockquote/div:
|
|
// replace the one node in the array with this node
|
|
isupports = do_QueryInterface(curNode);
|
|
arrayOfNodes->ReplaceElementAt(isupports, 0);
|
|
}
|
|
|
|
// Next we detect all the transitions in the array, where a transition
|
|
// means that adjacent nodes in the array don't have the same parent.
|
|
|
|
nsVoidArray transitionList;
|
|
res = MakeTransitionList(arrayOfNodes, &transitionList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Ok, now go through all the nodes and put then in the list,
|
|
// or whatever is approriate. Wohoo!
|
|
|
|
arrayOfNodes->Count(&listCount);
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curList;
|
|
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (transitionList[i] && // transition node
|
|
((((i+1)<listCount) && transitionList[i+1]) || // and next node is transistion node
|
|
( i+1 >= listCount))) // or there is no next node
|
|
{
|
|
// the parent of this node has no other children on the
|
|
// list of nodes to make a list out of. So if this node
|
|
// is a list, change it's list type if needed instead of
|
|
// reparenting it
|
|
nsCOMPtr<nsIDOMNode> newBlock;
|
|
if (IsUnorderedList(curNode))
|
|
{
|
|
if (aOrdered)
|
|
{
|
|
// make a new ordered list, insert it where the current unordered list is,
|
|
// and move all the children to the new list, and remove the old list
|
|
nsAutoString blockType("ol");
|
|
res = ReplaceContainer(curNode,&newBlock,blockType);
|
|
if (NS_FAILED(res)) return res;
|
|
curList = newBlock;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// do nothing, we are already the right kind of list
|
|
curList = newBlock;
|
|
continue;
|
|
}
|
|
}
|
|
else if (IsOrderedList(curNode))
|
|
{
|
|
if (!aOrdered)
|
|
{
|
|
// make a new unordered list, insert it where the current ordered list is,
|
|
// and move all the children to the new list, and remove the old list
|
|
nsAutoString blockType("ul");
|
|
ReplaceContainer(curNode,&newBlock,blockType);
|
|
if (NS_FAILED(res)) return res;
|
|
curList = newBlock;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// do nothing, we are already the right kind of list
|
|
curList = newBlock;
|
|
continue;
|
|
}
|
|
}
|
|
else if (IsDiv(curNode) || IsBlockquote(curNode))
|
|
{
|
|
// XXX moose
|
|
}
|
|
} // lonely node
|
|
|
|
// need to make a list to put things in if we haven't already,
|
|
// or if this node doesn't go in list we used earlier.
|
|
if (!curList || transitionList[i])
|
|
{
|
|
nsAutoString listType;
|
|
if (aOrdered) listType = "ol";
|
|
else listType = "ul";
|
|
res = mEditor->CreateNode(listType, curParent, offset, getter_AddRefs(curList));
|
|
if (NS_FAILED(res)) return res;
|
|
// curList is now the correct thing to put curNode in
|
|
}
|
|
|
|
// if curNode isn't a list item, we must wrap it in one
|
|
nsCOMPtr<nsIDOMNode> listItem;
|
|
if (!IsListItem(curNode))
|
|
{
|
|
nsAutoString listItemType = "li";
|
|
res = InsertContainerAbove(curNode, &listItem, listItemType);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
listItem = curNode;
|
|
}
|
|
|
|
// tuck the listItem into the end of the active list
|
|
PRUint32 listLen;
|
|
res = mEditor->GetLengthOfDOMNode(curList, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->DeleteNode(listItem);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(listItem, curList, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillIndent(nsIDOMSelection *aSelection, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_TRUE;
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection);
|
|
nsresult res;
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, &arrayOfRanges, kIndent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kIndent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Next we detect all the transitions in the array, where a transition
|
|
// means that adjacent nodes in the array don't have the same parent.
|
|
|
|
nsVoidArray transitionList;
|
|
res = MakeTransitionList(arrayOfNodes, &transitionList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Ok, now go through all the nodes and put them in a blockquote,
|
|
// or whatever is appropriate. Wohoo!
|
|
|
|
PRUint32 listCount;
|
|
PRInt32 i;
|
|
arrayOfNodes->Count(&listCount);
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curQuote;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// need to make a blockquote to put things in if we haven't already,
|
|
// or if this node doesn't go in list we used earlier.
|
|
if (!curQuote || transitionList[i])
|
|
{
|
|
nsAutoString quoteType("blockquote");
|
|
res = mEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote));
|
|
if (NS_FAILED(res)) return res;
|
|
// curQuote is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active blockquote
|
|
PRUint32 listLen;
|
|
res = mEditor->GetLengthOfDOMNode(curQuote, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->DeleteNode(curNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(curNode, curQuote, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillOutdent(nsIDOMSelection *aSelection, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_TRUE;
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection);
|
|
nsresult res = NS_OK;
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, &arrayOfRanges, kOutdent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kOutdent);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Next we detect all the transitions in the array, where a transition
|
|
// means that adjacent nodes in the array don't have the same parent.
|
|
|
|
nsVoidArray transitionList;
|
|
res = MakeTransitionList(arrayOfNodes, &transitionList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Ok, now go through all the nodes and remove a level of blockquoting,
|
|
// or whatever is appropriate. Wohoo!
|
|
|
|
PRUint32 listCount;
|
|
PRInt32 i;
|
|
arrayOfNodes->Count(&listCount);
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (transitionList[i])
|
|
{
|
|
// look for a blockquote somewhere above us and remove it.
|
|
// this is a hack until i think about outdent for real.
|
|
nsCOMPtr<nsIDOMNode> n = curNode;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
while (!IsBody(n))
|
|
{
|
|
if (IsBlockquote(n))
|
|
{
|
|
RemoveContainer(n);
|
|
break;
|
|
}
|
|
n->GetParentNode(getter_AddRefs(tmp));
|
|
n = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillAlign(nsIDOMSelection *aSelection, const nsString *alignType, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_TRUE;
|
|
|
|
nsAutoSelectionReset selectionResetter(aSelection);
|
|
nsresult res = NS_OK;
|
|
|
|
// convert the selection ranges into "promoted" selection ranges:
|
|
// this basically just expands the range to include the immediate
|
|
// block parent, and then further expands to include any ancestors
|
|
// whose children are all in the range
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfRanges;
|
|
res = GetPromotedRanges(aSelection, &arrayOfRanges, kAlign);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// use these ranges to contruct a list of nodes to act on.
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
res = GetNodesForOperation(arrayOfRanges, &arrayOfNodes, kAlign);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Next we detect all the transitions in the array, where a transition
|
|
// means that adjacent nodes in the array don't have the same parent.
|
|
|
|
nsVoidArray transitionList;
|
|
res = MakeTransitionList(arrayOfNodes, &transitionList);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Ok, now go through all the nodes and give them an align attrib or put them in a div,
|
|
// or whatever is appropriate. Wohoo!
|
|
|
|
PRUint32 listCount;
|
|
PRInt32 i;
|
|
arrayOfNodes->Count(&listCount);
|
|
nsCOMPtr<nsIDOMNode> curParent;
|
|
nsCOMPtr<nsIDOMNode> curDiv;
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
// here's where we actually figure out what to do
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(arrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports ) );
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(curNode, &curParent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's a div, don't nest it, just set the alignment
|
|
if (IsDiv(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curNode);
|
|
nsAutoString attr("align");
|
|
res = mEditor->SetAttribute(divElem, attr, *alignType);
|
|
if (NS_FAILED(res)) return res;
|
|
// clear out curDiv so that we don't put nodes after this one into it
|
|
curDiv = 0;
|
|
continue;
|
|
}
|
|
|
|
// need to make a div to put things in if we haven't already,
|
|
// or if this node doesn't go in div we used earlier.
|
|
if (!curDiv || transitionList[i])
|
|
{
|
|
nsAutoString divType("div");
|
|
res = mEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv));
|
|
if (NS_FAILED(res)) return res;
|
|
// set up the alignment on the div
|
|
nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv);
|
|
nsAutoString attr("align");
|
|
res = mEditor->SetAttribute(divElem, attr, *alignType);
|
|
if (NS_FAILED(res)) return res;
|
|
// curDiv is now the correct thing to put curNode in
|
|
}
|
|
|
|
// tuck the node into the end of the active div
|
|
PRUint32 listLen;
|
|
res = mEditor->GetLengthOfDOMNode(curDiv, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->DeleteNode(curNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(curNode, curDiv, listLen);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeHeader(nsIDOMSelection *aSelection, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakeAddress(nsIDOMSelection *aSelection, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditRules::WillMakePRE(nsIDOMSelection *aSelection, PRBool *aCancel)
|
|
{
|
|
if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; }
|
|
// initialize out param
|
|
*aCancel = PR_FALSE;
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/********************************************************
|
|
* helper methods
|
|
********************************************************/
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsHeader: true if node an html header
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsHeader(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsHeader");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if ( (tag == "h1") ||
|
|
(tag == "h2") ||
|
|
(tag == "h3") ||
|
|
(tag == "h4") ||
|
|
(tag == "h5") ||
|
|
(tag == "h6") )
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsParagraph: true if node an html paragraph
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsParagraph(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsParagraph");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "p")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsListItem: true if node an html list item
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsListItem(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsListItem");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "li")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsOrderedList: true if node an html orderd list
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsOrderedList(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsOrderedList");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "ol")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsUnorderedList: true if node an html orderd list
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsUnorderedList(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsUnorderedList");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "ul")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsBreak: true if node an html break node
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsBreak(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBreak");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "br")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsBody: true if node an html body node
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsBody(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBody");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "body")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsBlockquote: true if node an html blockquote node
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsBlockquote(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsBlockquote");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "blockquote")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsDiv: true if node an html div node
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsDiv(nsIDOMNode *node)
|
|
{
|
|
NS_PRECONDITION(node, "null parent passed to nsHTMLEditRules::IsDiv");
|
|
nsAutoString tag;
|
|
nsEditor::GetTagString(node,tag);
|
|
if (tag == "div")
|
|
{
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetTabAsNBSPs: stuff the right number of nbsp's into outString
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetTabAsNBSPs(nsString *outString)
|
|
{
|
|
if (!outString) return NS_ERROR_NULL_POINTER;
|
|
// XXX - this should get the right number from prefs
|
|
*outString += nbsp; *outString += nbsp; *outString += nbsp; *outString += nbsp;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetTabAsNBSPsAndSpace: stuff the right number of nbsp's followed by a
|
|
// space into outString
|
|
nsresult
|
|
nsHTMLEditRules::GetTabAsNBSPsAndSpace(nsString *outString)
|
|
{
|
|
if (!outString) return NS_ERROR_NULL_POINTER;
|
|
// XXX - this should get the right number from prefs
|
|
*outString += nbsp; *outString += nbsp; *outString += nbsp; *outString += ' ';
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsFirstNode: Are we the first edittable node in our parent?
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsFirstNode(nsIDOMNode *aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset, j=0;
|
|
nsEditor::GetNodeLocation(aNode, &parent, &offset);
|
|
if (!offset) // easy case, we are first dom child
|
|
return PR_TRUE;
|
|
|
|
// ok, so there are earlier children. But are they editable???
|
|
nsCOMPtr<nsIDOMNodeList>childList;
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
parent->GetChildNodes(getter_AddRefs(childList));
|
|
while (j < offset)
|
|
{
|
|
childList->Item(j, getter_AddRefs(child));
|
|
if (mEditor->IsEditable(child))
|
|
return PR_FALSE;
|
|
j++;
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// IsLastNode: Are we the first edittable node in our parent?
|
|
//
|
|
PRBool
|
|
nsHTMLEditRules::IsLastNode(nsIDOMNode *aNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset, j;
|
|
PRUint32 numChildren;
|
|
nsEditor::GetNodeLocation(aNode, &parent, &offset);
|
|
nsEditor::GetLengthOfDOMNode(parent, numChildren);
|
|
if (offset+1 == numChildren) // easy case, we are last dom child
|
|
return PR_TRUE;
|
|
|
|
// ok, so there are later children. But are they editable???
|
|
j = offset+1;
|
|
nsCOMPtr<nsIDOMNodeList>childList;
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
parent->GetChildNodes(getter_AddRefs(childList));
|
|
while (j < numChildren)
|
|
{
|
|
childList->Item(j, getter_AddRefs(child));
|
|
if (mEditor->IsEditable(child))
|
|
return PR_FALSE;
|
|
j++;
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPromotedPoint: figure out where a start or end point for a block
|
|
// operation really is
|
|
nsresult
|
|
nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode, PRInt32 aOffset,
|
|
PRInt32 actionID, nsCOMPtr<nsIDOMNode> *outNode, PRInt32 *outOffset)
|
|
{
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> node = aNode;
|
|
nsCOMPtr<nsIDOMNode> parent = aNode;
|
|
PRInt32 offset = aOffset;
|
|
|
|
if (aWhere == kStart)
|
|
{
|
|
// some special casing for text nodes
|
|
if (nsEditor::IsTextNode(aNode))
|
|
{
|
|
nsEditor::GetNodeLocation(aNode, &parent, &offset);
|
|
}
|
|
else
|
|
{
|
|
node = nsEditor::GetChildAt(parent,offset);
|
|
}
|
|
|
|
// finding the real start for this point. look up the tree for as long as we are the
|
|
// first node in the container, and as long as we haven't hit the body node.
|
|
while ((IsFirstNode(node)) && (!IsBody(parent)))
|
|
{
|
|
node = parent;
|
|
res = nsEditor::GetNodeLocation(node, &parent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
*outNode = parent;
|
|
*outOffset = offset;
|
|
return res;
|
|
}
|
|
|
|
if (aWhere == kEnd)
|
|
{
|
|
// some special casing for text nodes
|
|
if (nsEditor::IsTextNode(aNode))
|
|
{
|
|
nsEditor::GetNodeLocation(aNode, &parent, &offset);
|
|
}
|
|
else
|
|
{
|
|
node = nsEditor::GetChildAt(parent,offset);
|
|
}
|
|
offset++; // since this is going to be used for a range _endpoint_, we want to be after the node
|
|
|
|
// finding the real end for this point. look up the tree for as long as we are the
|
|
// last node in the container, and as long as we haven't hit the body node.
|
|
while ((IsLastNode(node)) && (!IsBody(parent)))
|
|
{
|
|
node = parent;
|
|
res = nsEditor::GetNodeLocation(node, &parent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
offset++;
|
|
}
|
|
*outNode = parent;
|
|
*outOffset = offset;
|
|
return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetPromotedRanges: run all the selection range endpoint through
|
|
// GetPromotedPoint()
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetPromotedRanges(nsIDOMSelection *inSelection,
|
|
nsCOMPtr<nsISupportsArray> *outArrayOfRanges,
|
|
PRInt32 inOperationType)
|
|
{
|
|
if (!inSelection || !outArrayOfRanges) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfRanges));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRInt32 rangeCount;
|
|
res = inSelection->GetRangeCount(&rangeCount);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMRange> selectionRange;
|
|
nsCOMPtr<nsIDOMNode> startNode;
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 startOffset, endOffset;
|
|
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange));
|
|
if (NS_FAILED(res)) return res;
|
|
res = selectionRange->GetStartParent(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = selectionRange->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = selectionRange->GetEndParent(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = selectionRange->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// make a new adjusted range to represent the appropriate block content
|
|
// this is tricky. the basic idea is to push out the range endpoints
|
|
// to truly enclose the blocks that we will affect
|
|
|
|
nsCOMPtr<nsIDOMNode> opStartNode;
|
|
nsCOMPtr<nsIDOMNode> opEndNode;
|
|
PRInt32 opStartOffset, opEndOffset;
|
|
nsCOMPtr<nsIDOMRange> opRange;
|
|
|
|
res = GetPromotedPoint( kStart, startNode, startOffset, inOperationType, &opStartNode, &opStartOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = GetPromotedPoint( kEnd, endNode, endOffset, inOperationType, &opEndNode, &opEndOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = selectionRange->Clone(getter_AddRefs(opRange));
|
|
if (NS_FAILED(res)) return res;
|
|
opRange->SetStart(opStartNode, opStartOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
opRange->SetEnd(opEndNode, opEndOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// stuff new opRange into nsISupportsArray
|
|
nsCOMPtr<nsISupports> isupports = do_QueryInterface(opRange);
|
|
(*outArrayOfRanges)->AppendElement(isupports);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GetNodesForOperation: run through the ranges in the array and construct
|
|
// a new array of nodes to be acted on.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::GetNodesForOperation(nsISupportsArray *inArrayOfRanges,
|
|
nsCOMPtr<nsISupportsArray> *outArrayOfNodes,
|
|
PRInt32 inOperationType)
|
|
{
|
|
if (!inArrayOfRanges || !outArrayOfNodes) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_NewISupportsArray(getter_AddRefs(*outArrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRUint32 rangeCount;
|
|
res = inArrayOfRanges->Count(&rangeCount);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRInt32 i;
|
|
nsCOMPtr<nsIDOMRange> opRange;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
|
|
for (i = 0; i < rangeCount; i++)
|
|
{
|
|
isupports = (dont_AddRef)(inArrayOfRanges->ElementAt(i));
|
|
opRange = do_QueryInterface(isupports);
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID,
|
|
nsnull,
|
|
nsIContentIterator::GetIID(),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
res = iter->Init(opRange);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while (NS_COMFALSE == iter->IsDone())
|
|
{
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsIContent> content;
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
node = do_QueryInterface(content);
|
|
if ((NS_SUCCEEDED(res)) && node)
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
(*outArrayOfNodes)->AppendElement(isupports);
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// MakeTransitionList: detect all the transitions in the array, where a
|
|
// transition means that adjacent nodes in the array
|
|
// don't have the same parent.
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::MakeTransitionList(nsISupportsArray *inArrayOfNodes,
|
|
nsVoidArray *inTransitionArray)
|
|
{
|
|
if (!inArrayOfNodes || !inTransitionArray) return NS_ERROR_NULL_POINTER;
|
|
|
|
PRUint32 listCount;
|
|
PRInt32 i;
|
|
inArrayOfNodes->Count(&listCount);
|
|
nsVoidArray transitionList;
|
|
nsCOMPtr<nsIDOMNode> prevElementParent;
|
|
nsCOMPtr<nsIDOMNode> curElementParent;
|
|
|
|
for (i=0; i<listCount; i++)
|
|
{
|
|
nsCOMPtr<nsISupports> isupports = (dont_AddRef)(inArrayOfNodes->ElementAt(i));
|
|
nsCOMPtr<nsIDOMNode> transNode( do_QueryInterface(isupports ) );
|
|
transNode->GetParentNode(getter_AddRefs(curElementParent));
|
|
if (curElementParent != prevElementParent)
|
|
{
|
|
inTransitionArray->InsertElementAt((void*)PR_TRUE,i); // different parents: transition point
|
|
}
|
|
else
|
|
{
|
|
inTransitionArray->InsertElementAt((void*)PR_FALSE,i); // same parents: these nodes grew up together
|
|
}
|
|
prevElementParent = curElementParent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ReplaceContainer: replace inNode with a new node (outNode) which is contructed
|
|
// to be of type aNodeType. Put inNodes children into outNode.
|
|
// Callers responsibility to make sure inNode's children can
|
|
// go in outNode.
|
|
nsresult
|
|
nsHTMLEditRules::ReplaceContainer(nsIDOMNode *inNode,
|
|
nsCOMPtr<nsIDOMNode> *outNode,
|
|
nsString &aNodeType)
|
|
{
|
|
if (!inNode || !outNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
PRBool bHasMoreChildren;
|
|
inNode->HasChildNodes(&bHasMoreChildren);
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
offset = 0;
|
|
while (bHasMoreChildren)
|
|
{
|
|
inNode->GetFirstChild(getter_AddRefs(child));
|
|
res = mEditor->DeleteNode(child);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(child, *outNode, offset);
|
|
if (NS_FAILED(res)) return res;
|
|
inNode->HasChildNodes(&bHasMoreChildren);
|
|
offset++;
|
|
}
|
|
res = mEditor->DeleteNode(inNode);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// RemoveContainer: remove inNode, reparenting it's children into their
|
|
// the parent of inNode
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::RemoveContainer(nsIDOMNode *inNode)
|
|
{
|
|
if (!inNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (IsBody(inNode))
|
|
return NS_ERROR_UNEXPECTED;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
PRBool bHasMoreChildren;
|
|
inNode->HasChildNodes(&bHasMoreChildren);
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
while (bHasMoreChildren)
|
|
{
|
|
inNode->GetLastChild(getter_AddRefs(child));
|
|
res = mEditor->DeleteNode(child);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(child, parent, offset);
|
|
if (NS_FAILED(res)) return res;
|
|
inNode->HasChildNodes(&bHasMoreChildren);
|
|
}
|
|
res = mEditor->DeleteNode(inNode);
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// InsertContainerAbove: insert a new parent for inNode, returned in outNode,
|
|
// which is contructed to be of type aNodeType. outNode becomes
|
|
// a child of inNode's earlier parent.
|
|
// Callers responsibility to make sure inNode's can be child
|
|
// of outNode, and outNode can be child of old parent.
|
|
nsresult
|
|
nsHTMLEditRules::InsertContainerAbove(nsIDOMNode *inNode,
|
|
nsCOMPtr<nsIDOMNode> *outNode,
|
|
nsString &aNodeType)
|
|
{
|
|
if (!inNode || !outNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
res = nsEditor::GetNodeLocation(inNode, &parent, &offset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// make new parent, outNode
|
|
res = mEditor->CreateNode(aNodeType, parent, offset, getter_AddRefs(*outNode));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// put inNode in new parent, outNode
|
|
res = mEditor->DeleteNode(inNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = mEditor->InsertNode(inNode, *outNode, 0);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
|
|
/********************************************************
|
|
* main implementation methods
|
|
********************************************************/
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// InsertTab: top level logic for determining how to insert a tab
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::InsertTab(nsIDOMSelection *aSelection,
|
|
PRBool *aCancel,
|
|
PlaceholderTxn **aTxn,
|
|
nsString *outString)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
PRInt32 offset;
|
|
PRBool isPRE;
|
|
PRBool isNextWhiteSpace;
|
|
PRBool isPrevWhiteSpace;
|
|
|
|
nsresult result = mEditor->GetStartNodeAndOffset(aSelection, &parentNode, &offset);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (!parentNode) return NS_ERROR_FAILURE;
|
|
|
|
result = mEditor->IsPreformatted(parentNode,&isPRE);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (isPRE)
|
|
{
|
|
*outString += '\t';
|
|
return NS_OK;
|
|
}
|
|
|
|
result = mEditor->IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
result = mEditor->IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (isPrevWhiteSpace)
|
|
{
|
|
// prev character is a whitespace; Need to
|
|
// insert nbsp's BEFORE the space
|
|
|
|
// XXX for now put tab in wrong place
|
|
if (isNextWhiteSpace)
|
|
{
|
|
GetTabAsNBSPs(outString);
|
|
return NS_OK;
|
|
}
|
|
GetTabAsNBSPsAndSpace(outString);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (isNextWhiteSpace)
|
|
{
|
|
// character after us is ws. insert nbsps
|
|
GetTabAsNBSPs(outString);
|
|
return NS_OK;
|
|
}
|
|
|
|
// else we are in middle of a word; use n-1 nbsp's plus a space
|
|
GetTabAsNBSPsAndSpace(outString);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// InsertSpace: top level logic for determining how to insert a space
|
|
//
|
|
nsresult
|
|
nsHTMLEditRules::InsertSpace(nsIDOMSelection *aSelection,
|
|
PRBool *aCancel,
|
|
PlaceholderTxn **aTxn,
|
|
nsString *outString)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
PRInt32 offset;
|
|
PRBool isPRE;
|
|
PRBool isNextWhiteSpace;
|
|
PRBool isPrevWhiteSpace;
|
|
|
|
nsresult result = mEditor->GetStartNodeAndOffset(aSelection, &parentNode, &offset);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (!parentNode) return NS_ERROR_FAILURE;
|
|
|
|
result = mEditor->IsPreformatted(parentNode,&isPRE);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (isPRE)
|
|
{
|
|
*outString += " ";
|
|
return NS_OK;
|
|
}
|
|
|
|
result = mEditor->IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
result = mEditor->IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace);
|
|
if (NS_FAILED(result)) return result;
|
|
|
|
if (isPrevWhiteSpace)
|
|
{
|
|
// prev character is a whitespace; Need to
|
|
// insert nbsp BEFORE the space
|
|
|
|
// XXX for now put in wrong place
|
|
if (isNextWhiteSpace)
|
|
{
|
|
*outString += nbsp;
|
|
return NS_OK;
|
|
}
|
|
*outString += nbsp;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (isNextWhiteSpace)
|
|
{
|
|
// character after us is ws. insert nbsp
|
|
*outString += nbsp;
|
|
return NS_OK;
|
|
}
|
|
|
|
// else just a space
|
|
*outString += " ";
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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<nsIDOMNode> leftNode;
|
|
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> p;
|
|
aHeader->GetParentNode(getter_AddRefs(p));
|
|
PRInt32 indx = mEditor->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<nsIDOMNode> sibling;
|
|
textNode->GetNextSibling(getter_AddRefs(sibling));
|
|
if (sibling && mEditor->IsTextNode(sibling) && mEditor->IsEditable(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<nsIDOMNode> sibling;
|
|
nsresult res = NS_OK;
|
|
|
|
// easy case, in a text node:
|
|
if (mEditor->IsTextNode(aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMText> 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 = mEditor->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 = mEditor->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 = mEditor->SplitNodeDeep( aListItem, aNode, aOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = aSelection->Collapse(aNode,0);
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|