Mozilla/mozilla/editor/libeditor/base/nsSelectionState.cpp
jfrancis%netscape.com dab8e009ec editor fixes for:
180034 editor should respect the select_all style
183836 New list item should not reset inline styles
179384 Merging blocks via forward delete sends selection to front of document
98434 IME does not work correctly at the last characters in the text field  (patch courtesy of Shotaro Kamio)

r=jfrancis,brade,cmanske   sr=kin


git-svn-id: svn://10.0.0.236/trunk@135560 18797224-902f-48f8-a5cc-f745e15eee43
2002-12-22 01:51:14 +00:00

710 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsSelectionState.h"
#include "nsIDOMCharacterData.h"
#include "nsIDOMNode.h"
#include "nsIDOMRange.h"
#include "nsISelection.h"
#include "nsEditor.h"
#include "nsLayoutCID.h"
#include "nsEditorUtils.h"
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
/***************************************************************************
* class for recording selection info. stores selection as collection of
* { {startnode, startoffset} , {endnode, endoffset} } tuples. Cant store
* ranges since dom gravity will possibly change the ranges.
*/
nsSelectionState::nsSelectionState() : mArray(){}
nsSelectionState::~nsSelectionState()
{
MakeEmpty();
}
nsresult
nsSelectionState::SaveSelection(nsISelection *aSel)
{
if (!aSel) return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
PRInt32 i,rangeCount, arrayCount = mArray.Count();
nsRangeStore *item;
aSel->GetRangeCount(&rangeCount);
// if we need more items in the array, new them
if (arrayCount<rangeCount)
{
PRInt32 count = rangeCount-arrayCount;
for (i=0; i<count; i++)
{
item = new nsRangeStore;
mArray.AppendElement(item);
}
}
// else if we have too many, delete them
else if (arrayCount>rangeCount)
{
for (i = arrayCount-1; i >= rangeCount; i--)
{
item = (nsRangeStore*)mArray.ElementAt(i);
delete item;
mArray.RemoveElementAt(i);
}
}
// now store the selection ranges
for (i=0; i<rangeCount; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> range;
res = aSel->GetRangeAt(i, getter_AddRefs(range));
item->StoreRange(range);
}
return res;
}
nsresult
nsSelectionState::RestoreSelection(nsISelection *aSel)
{
if (!aSel) return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
PRInt32 i, arrayCount = mArray.Count();
nsRangeStore *item;
// clear out selection
aSel->RemoveAllRanges();
// set the selection ranges anew
for (i=0; i<arrayCount; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMRange> range;
item->GetRange(address_of(range));
if (!range) return NS_ERROR_UNEXPECTED;
res = aSel->AddRange(range);
if(NS_FAILED(res)) return res;
}
return NS_OK;
}
PRBool
nsSelectionState::IsCollapsed()
{
if (1 != mArray.Count()) return PR_FALSE;
nsRangeStore *item;
item = (nsRangeStore*)mArray.ElementAt(0);
if (!item) return PR_FALSE;
nsCOMPtr<nsIDOMRange> range;
item->GetRange(address_of(range));
if (!range) return PR_FALSE;
PRBool bIsCollapsed;
range->GetCollapsed(&bIsCollapsed);
return bIsCollapsed;
}
PRBool
nsSelectionState::IsEqual(nsSelectionState *aSelState)
{
if (!aSelState) return NS_ERROR_NULL_POINTER;
PRInt32 i, myCount = mArray.Count(), itsCount = aSelState->mArray.Count();
if (myCount != itsCount) return PR_FALSE;
if (myCount < 1) return PR_FALSE;
nsRangeStore *myItem, *itsItem;
for (i=0; i<myCount; i++)
{
myItem = (nsRangeStore*)mArray.ElementAt(i);
itsItem = (nsRangeStore*)(aSelState->mArray.ElementAt(i));
if (!myItem || !itsItem) return PR_FALSE;
nsCOMPtr<nsIDOMRange> myRange, itsRange;
myItem->GetRange(address_of(myRange));
itsItem->GetRange(address_of(itsRange));
if (!myRange || !itsRange) return PR_FALSE;
PRInt32 compResult;
myRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, itsRange, &compResult);
if (compResult) return PR_FALSE;
myRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, itsRange, &compResult);
if (compResult) return PR_FALSE;
}
// if we got here, they are equal
return PR_TRUE;
}
void
nsSelectionState::MakeEmpty()
{
// free any items in the array
nsRangeStore *item;
for (PRInt32 i = mArray.Count()-1; i >= 0; --i)
{
item = (nsRangeStore*)mArray.ElementAt(i);
delete item;
}
mArray.Clear();
}
PRBool
nsSelectionState::IsEmpty()
{
return (mArray.Count() == 0);
}
/***************************************************************************
* nsRangeUpdater: class for updating nsIDOMRanges in response to editor actions.
*/
nsRangeUpdater::nsRangeUpdater() : mArray(), mLock(PR_FALSE) {}
nsRangeUpdater::~nsRangeUpdater()
{
// nothing to do, we don't own the items in our array.
}
void
nsRangeUpdater::RegisterRangeItem(nsRangeStore *aRangeItem)
{
if (!aRangeItem) return;
if (mArray.IndexOf(aRangeItem) != -1)
{
NS_ERROR("tried to register an already registered range");
return; // don't register it again. It would get doubly adjusted.
}
mArray.AppendElement(aRangeItem);
return;
}
void
nsRangeUpdater::DropRangeItem(nsRangeStore *aRangeItem)
{
if (!aRangeItem) return;
mArray.RemoveElement(aRangeItem);
return;
}
nsresult
nsRangeUpdater::RegisterSelectionState(nsSelectionState &aSelState)
{
PRInt32 i, theCount = aSelState.mArray.Count();
if (theCount < 1) return NS_ERROR_FAILURE;
nsRangeStore *item;
for (i=0; i<theCount; i++)
{
item = (nsRangeStore*)aSelState.mArray.ElementAt(i);
RegisterRangeItem(item);
}
return NS_OK;;
}
nsresult
nsRangeUpdater::DropSelectionState(nsSelectionState &aSelState)
{
PRInt32 i, theCount = aSelState.mArray.Count();
if (theCount < 1) return NS_ERROR_FAILURE;
nsRangeStore *item;
for (i=0; i<theCount; i++)
{
item = (nsRangeStore*)aSelState.mArray.ElementAt(i);
DropRangeItem(item);
}
return NS_OK;;
}
// gravity methods:
nsresult
nsRangeUpdater::SelAdjCreateNode(nsIDOMNode *aParent, PRInt32 aPosition)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aParent) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if ((item->startNode.get() == aParent) && (item->startOffset > aPosition))
item->startOffset++;
if ((item->endNode.get() == aParent) && (item->endOffset > aPosition))
item->endOffset++;
}
return NS_OK;
}
nsresult
nsRangeUpdater::SelAdjInsertNode(nsIDOMNode *aParent, PRInt32 aPosition)
{
return SelAdjCreateNode(aParent, aPosition);
}
nsresult
nsRangeUpdater::SelAdjDeleteNode(nsIDOMNode *aNode)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aNode) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset = 0;
nsRangeStore *item;
nsresult res = nsEditor::GetNodeLocation(aNode, address_of(parent), &offset);
NS_ENSURE_SUCCESS(res, res);
// check for range endpoints that are after aNode and in the same parent
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if ((item->startNode.get() == parent) && (item->startOffset > offset))
item->startOffset--;
if ((item->endNode.get() == parent) && (item->endOffset > offset))
item->endOffset--;
}
// check for range endpoints that are in aNode
if (item->startNode == aNode)
{
item->startNode = parent;
item->startOffset = offset;
}
if (item->endNode == aNode)
{
item->endNode = parent;
item->endOffset = offset;
}
// check for range endpoints that are in descendants of aNode
nsCOMPtr<nsIDOMNode> oldStart;
if (nsEditorUtils::IsDescendantOf(item->startNode, aNode))
{
oldStart = item->startNode; // save for efficiency hack below.
item->startNode = parent;
item->startOffset = offset;
}
// avoid having to call IsDescendantOf() for common case of range startnode == range endnode.
if ((item->endNode == oldStart) || nsEditorUtils::IsDescendantOf(item->endNode, aNode))
{
item->endNode = parent;
item->endOffset = offset;
}
return NS_OK;
}
nsresult
nsRangeUpdater::SelAdjSplitNode(nsIDOMNode *aOldRightNode, PRInt32 aOffset, nsIDOMNode *aNewLeftNode)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aOldRightNode || !aNewLeftNode) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset;
nsresult result = nsEditor::GetNodeLocation(aOldRightNode, address_of(parent), &offset);
if (NS_FAILED(result)) return result;
// first part is same as inserting aNewLeftnode
result = SelAdjInsertNode(parent,offset-1);
if (NS_FAILED(result)) return result;
// next step is to check for range enpoints inside aOldRightNode
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if (item->startNode.get() == aOldRightNode)
{
if (item->startOffset > aOffset)
{
item->startOffset -= aOffset;
}
else
{
item->startNode = aNewLeftNode;
}
}
if (item->endNode.get() == aOldRightNode)
{
if (item->endOffset > aOffset)
{
item->endOffset -= aOffset;
}
else
{
item->endNode = aNewLeftNode;
}
}
}
return NS_OK;
}
nsresult
nsRangeUpdater::SelAdjJoinNodes(nsIDOMNode *aLeftNode,
nsIDOMNode *aRightNode,
nsIDOMNode *aParent,
PRInt32 aOffset,
PRInt32 aOldLeftNodeLength)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aLeftNode || !aRightNode || !aParent) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
// adjust endpoints in aParent
if (item->startNode.get() == aParent)
{
if (item->startOffset > aOffset)
{
item->startOffset--;
}
else if (item->startOffset == aOffset)
{
// join keeps right hand node
item->startNode = aRightNode;
item->startOffset = aOldLeftNodeLength;
}
}
if (item->endNode.get() == aParent)
{
if (item->endOffset > aOffset)
{
item->endOffset--;
}
else if (item->endOffset == aOffset)
{
// join keeps right hand node
item->endNode = aRightNode;
item->endOffset = aOldLeftNodeLength;
}
}
// adjust endpoints in aRightNode
if (item->startNode.get() == aRightNode)
item->startOffset += aOldLeftNodeLength;
if (item->endNode.get() == aRightNode)
item->endOffset += aOldLeftNodeLength;
// adjust endpoints in aLeftNode
if (item->startNode.get() == aLeftNode)
item->startNode = aRightNode;
if (item->endNode.get() == aLeftNode)
item->endNode = aRightNode;
}
return NS_OK;
}
nsresult
nsRangeUpdater::SelAdjInsertText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, const nsAString &aString)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aTextNode) return NS_ERROR_NULL_POINTER;
PRInt32 len=aString.Length(), i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aTextNode));
if (!node) return NS_ERROR_NULL_POINTER;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if ((item->startNode.get() == node) && (item->startOffset > aOffset))
item->startOffset += len;
if ((item->endNode.get() == node) && (item->endOffset > aOffset))
item->endOffset += len;
}
return NS_OK;
}
nsresult
nsRangeUpdater::SelAdjDeleteText(nsIDOMCharacterData *aTextNode, PRInt32 aOffset, PRInt32 aLength)
{
if (mLock) return NS_OK; // lock set by Will/DidReplaceParent, etc...
if (!aTextNode) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aTextNode));
if (!node) return NS_ERROR_NULL_POINTER;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if ((item->startNode.get() == node) && (item->startOffset > aOffset))
{
item->startOffset -= aLength;
if (item->startOffset < 0) item->startOffset = 0;
}
if ((item->endNode.get() == node) && (item->endOffset > aOffset))
{
item->endOffset -= aLength;
if (item->endOffset < 0) item->endOffset = 0;
}
}
return NS_OK;
}
nsresult
nsRangeUpdater::WillReplaceContainer()
{
if (mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_TRUE;
return NS_OK;
}
nsresult
nsRangeUpdater::DidReplaceContainer(nsIDOMNode *aOriginalNode, nsIDOMNode *aNewNode)
{
if (!mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_FALSE;
if (!aOriginalNode || !aNewNode) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if (item->startNode.get() == aOriginalNode)
item->startNode = aNewNode;
if (item->endNode.get() == aOriginalNode)
item->endNode = aNewNode;
}
return NS_OK;
}
nsresult
nsRangeUpdater::WillRemoveContainer()
{
if (mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_TRUE;
return NS_OK;
}
nsresult
nsRangeUpdater::DidRemoveContainer(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRUint32 aNodeOrigLen)
{
if (!mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_FALSE;
if (!aNode || !aParent) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
if (item->startNode.get() == aNode)
{
item->startNode = aParent;
item->startOffset += aOffset;
}
else if ((item->startNode.get() == aParent) && (item->startOffset > aOffset))
item->startOffset += (PRInt32)aNodeOrigLen-1;
if (item->endNode.get() == aNode)
{
item->endNode = aParent;
item->endOffset += aOffset;
}
else if ((item->endNode.get() == aParent) && (item->endOffset > aOffset))
item->endOffset += (PRInt32)aNodeOrigLen-1;
}
return NS_OK;
}
nsresult
nsRangeUpdater::WillInsertContainer()
{
if (mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_TRUE;
return NS_OK;
}
nsresult
nsRangeUpdater::DidInsertContainer()
{
if (!mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_FALSE;
return NS_OK;
}
nsresult
nsRangeUpdater::WillMoveNode()
{
if (mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_TRUE;
return NS_OK;
}
nsresult
nsRangeUpdater::DidMoveNode(nsIDOMNode *aOldParent, PRInt32 aOldOffset, nsIDOMNode *aNewParent, PRInt32 aNewOffset)
{
if (!mLock) return NS_ERROR_UNEXPECTED;
mLock = PR_FALSE;
if (!aOldParent || !aNewParent) return NS_ERROR_NULL_POINTER;
PRInt32 i, count = mArray.Count();
if (!count) return NS_OK;
nsRangeStore *item;
for (i=0; i<count; i++)
{
item = (nsRangeStore*)mArray.ElementAt(i);
if (!item) return NS_ERROR_NULL_POINTER;
// like a delete in aOldParent
if ((item->startNode.get() == aOldParent) && (item->startOffset > aOldOffset))
item->startOffset--;
if ((item->endNode.get() == aOldParent) && (item->endOffset > aOldOffset))
item->endOffset--;
// and like an insert in aNewParent
if ((item->startNode.get() == aNewParent) && (item->startOffset > aNewOffset))
item->startOffset++;
if ((item->endNode.get() == aNewParent) && (item->endOffset > aNewOffset))
item->endOffset++;
}
return NS_OK;
}
/***************************************************************************
* helper class for nsSelectionState. nsRangeStore stores range endpoints.
*/
// DEBUG: PRInt32 nsRangeStore::n = 0;
nsRangeStore::nsRangeStore()
{
// DEBUG: n++; printf("range store alloc count=%d\n", n);
}
nsRangeStore::~nsRangeStore()
{
// DEBUG: n--; printf("range store alloc count=%d\n", n);
}
nsresult nsRangeStore::StoreRange(nsIDOMRange *aRange)
{
if (!aRange) return NS_ERROR_NULL_POINTER;
aRange->GetStartContainer(getter_AddRefs(startNode));
aRange->GetEndContainer(getter_AddRefs(endNode));
aRange->GetStartOffset(&startOffset);
aRange->GetEndOffset(&endOffset);
return NS_OK;
}
nsresult nsRangeStore::GetRange(nsCOMPtr<nsIDOMRange> *outRange)
{
if (!outRange) return NS_ERROR_NULL_POINTER;
nsresult res = nsComponentManager::CreateInstance(kCRangeCID,
nsnull,
NS_GET_IID(nsIDOMRange),
getter_AddRefs(*outRange));
if(NS_FAILED(res)) return res;
res = (*outRange)->SetStart(startNode, startOffset);
if(NS_FAILED(res)) return res;
res = (*outRange)->SetEnd(endNode, endOffset);
return res;
}