Mozilla/mozilla/editor/libeditor/txtsvc/nsTextServicesDocument.cpp
anthonyd%netscape.com f4c17f81ff fix for bug 48624 (hidden selection issues with plain text widgets)
fix for bug 45662 (updating range impl to dom spec)
fix for some edge case selection issues invloving multiple windows, no bug #
but saari knows about this.
r: sfraser, jfrancis, mjudge, saari
a: beppe


git-svn-id: svn://10.0.0.236/trunk@77040 18797224-902f-48f8-a5cc-f745e15eee43
2000-08-24 03:54:30 +00:00

4463 lines
96 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.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*/
#include "nsLayoutCID.h"
#include "nsIAtom.h"
#include "nsIContent.h"
#include "nsIContentIterator.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMRange.h"
#include "nsIDOMSelection.h"
#include "nsIHTMLEditor.h"
#include "nsTextServicesDocument.h"
#include "nsIDOMElement.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMHTMLDocument.h"
#define LOCK_DOC(doc)
#define UNLOCK_DOC(doc)
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_CID(kCDOMRangeCID, NS_RANGE_CID);
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kITextServicesDocumentIID, NS_ITEXTSERVICESDOCUMENT_IID);
class OffsetEntry
{
public:
OffsetEntry(nsIDOMNode *aNode, PRInt32 aOffset, PRInt32 aLength)
: mNode(aNode), mNodeOffset(0), mStrOffset(aOffset), mLength(aLength),
mIsInsertedText(PR_FALSE), mIsValid(PR_TRUE)
{
if (mStrOffset < 1)
mStrOffset = 0;
if (mLength < 1)
mLength = 0;
}
virtual ~OffsetEntry()
{
mNode = 0;
mNodeOffset = 0;
mStrOffset = 0;
mLength = 0;
mIsValid = PR_FALSE;
}
nsIDOMNode *mNode;
PRInt32 mNodeOffset;
PRInt32 mStrOffset;
PRInt32 mLength;
PRBool mIsInsertedText;
PRBool mIsValid;
};
// XXX: Should change these pointers to
// nsCOMPtr<nsIAtom> pointers!
nsIAtom *nsTextServicesDocument::sAAtom;
nsIAtom *nsTextServicesDocument::sAddressAtom;
nsIAtom *nsTextServicesDocument::sBigAtom;
nsIAtom *nsTextServicesDocument::sBlinkAtom;
nsIAtom *nsTextServicesDocument::sBAtom;
nsIAtom *nsTextServicesDocument::sCiteAtom;
nsIAtom *nsTextServicesDocument::sCodeAtom;
nsIAtom *nsTextServicesDocument::sDfnAtom;
nsIAtom *nsTextServicesDocument::sEmAtom;
nsIAtom *nsTextServicesDocument::sFontAtom;
nsIAtom *nsTextServicesDocument::sIAtom;
nsIAtom *nsTextServicesDocument::sKbdAtom;
nsIAtom *nsTextServicesDocument::sKeygenAtom;
nsIAtom *nsTextServicesDocument::sNobrAtom;
nsIAtom *nsTextServicesDocument::sSAtom;
nsIAtom *nsTextServicesDocument::sSampAtom;
nsIAtom *nsTextServicesDocument::sSmallAtom;
nsIAtom *nsTextServicesDocument::sSpacerAtom;
nsIAtom *nsTextServicesDocument::sSpanAtom;
nsIAtom *nsTextServicesDocument::sStrikeAtom;
nsIAtom *nsTextServicesDocument::sStrongAtom;
nsIAtom *nsTextServicesDocument::sSubAtom;
nsIAtom *nsTextServicesDocument::sSupAtom;
nsIAtom *nsTextServicesDocument::sTtAtom;
nsIAtom *nsTextServicesDocument::sUAtom;
nsIAtom *nsTextServicesDocument::sVarAtom;
nsIAtom *nsTextServicesDocument::sWbrAtom;
PRInt32 nsTextServicesDocument::sInstanceCount;
nsTextServicesDocument::nsTextServicesDocument()
{
mRefCnt = 0;
mSelStartIndex = -1;
mSelStartOffset = -1;
mSelEndIndex = -1;
mSelEndOffset = -1;
mIteratorStatus = eIsDone;
if (sInstanceCount <= 0)
{
sAAtom = NS_NewAtom("a");
sAddressAtom = NS_NewAtom("address");
sBigAtom = NS_NewAtom("big");
sBlinkAtom = NS_NewAtom("blink");
sBAtom = NS_NewAtom("b");
sCiteAtom = NS_NewAtom("cite");
sCodeAtom = NS_NewAtom("code");
sDfnAtom = NS_NewAtom("dfn");
sEmAtom = NS_NewAtom("em");
sFontAtom = NS_NewAtom("font");
sIAtom = NS_NewAtom("i");
sKbdAtom = NS_NewAtom("kbd");
sKeygenAtom = NS_NewAtom("keygen");
sNobrAtom = NS_NewAtom("nobr");
sSAtom = NS_NewAtom("s");
sSampAtom = NS_NewAtom("samp");
sSmallAtom = NS_NewAtom("small");
sSpacerAtom = NS_NewAtom("spacer");
sSpanAtom = NS_NewAtom("span");
sStrikeAtom = NS_NewAtom("strike");
sStrongAtom = NS_NewAtom("strong");
sSubAtom = NS_NewAtom("sub");
sSupAtom = NS_NewAtom("sup");
sTtAtom = NS_NewAtom("tt");
sUAtom = NS_NewAtom("u");
sVarAtom = NS_NewAtom("var");
sWbrAtom = NS_NewAtom("wbr");
}
++sInstanceCount;
}
nsTextServicesDocument::~nsTextServicesDocument()
{
if (mEditor && mNotifier)
mEditor->RemoveEditActionListener(mNotifier);
ClearOffsetTable();
if (sInstanceCount <= 1)
{
NS_IF_RELEASE(sAAtom);
NS_IF_RELEASE(sAddressAtom);
NS_IF_RELEASE(sBigAtom);
NS_IF_RELEASE(sBlinkAtom);
NS_IF_RELEASE(sBAtom);
NS_IF_RELEASE(sCiteAtom);
NS_IF_RELEASE(sCodeAtom);
NS_IF_RELEASE(sDfnAtom);
NS_IF_RELEASE(sEmAtom);
NS_IF_RELEASE(sFontAtom);
NS_IF_RELEASE(sIAtom);
NS_IF_RELEASE(sKbdAtom);
NS_IF_RELEASE(sKeygenAtom);
NS_IF_RELEASE(sNobrAtom);
NS_IF_RELEASE(sSAtom);
NS_IF_RELEASE(sSampAtom);
NS_IF_RELEASE(sSmallAtom);
NS_IF_RELEASE(sSpacerAtom);
NS_IF_RELEASE(sSpanAtom);
NS_IF_RELEASE(sStrikeAtom);
NS_IF_RELEASE(sStrongAtom);
NS_IF_RELEASE(sSubAtom);
NS_IF_RELEASE(sSupAtom);
NS_IF_RELEASE(sTtAtom);
NS_IF_RELEASE(sUAtom);
NS_IF_RELEASE(sVarAtom);
NS_IF_RELEASE(sWbrAtom);
}
--sInstanceCount;
}
#define DEBUG_TEXT_SERVICES__DOCUMENT_REFCNT 1
#ifdef DEBUG_TEXT_SERVICES__DOCUMENT_REFCNT
nsrefcnt nsTextServicesDocument::AddRef(void)
{
return ++mRefCnt;
}
nsrefcnt nsTextServicesDocument::Release(void)
{
NS_PRECONDITION(0 != mRefCnt, "dup release");
if (--mRefCnt == 0) {
NS_DELETEXPCOM(this);
return 0;
}
return mRefCnt;
}
#else
NS_IMPL_ADDREF(nsTextServicesDocument)
NS_IMPL_RELEASE(nsTextServicesDocument)
#endif
NS_IMETHODIMP
nsTextServicesDocument::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
if (nsnull == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kISupportsIID)) {
*aInstancePtr = (void*)(nsISupports*)this;
NS_ADDREF_THIS();
return NS_OK;
}
if (aIID.Equals(kITextServicesDocumentIID)) {
*aInstancePtr = (void*)(nsITextServicesDocument*)this;
NS_ADDREF_THIS();
return NS_OK;
}
*aInstancePtr = 0;
return NS_NOINTERFACE;
}
NS_IMETHODIMP
nsTextServicesDocument::InitWithDocument(nsIDOMDocument *aDOMDocument, nsIPresShell *aPresShell)
{
nsresult result = NS_OK;
if (!aDOMDocument || !aPresShell)
return NS_ERROR_NULL_POINTER;
NS_ASSERTION(!mSelCon, "mSelCon already initialized!");
if (mSelCon)
return NS_ERROR_FAILURE;
NS_ASSERTION(!mDOMDocument, "mDOMDocument already initialized!");
if (mDOMDocument)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
mSelCon = do_QueryInterface(aPresShell);
mDOMDocument = do_QueryInterface(aDOMDocument);
result = CreateDocumentContentIterator(getter_AddRefs(mIterator));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eIsDone;
result = FirstBlock();
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::InitWithEditor(nsIEditor *aEditor)
{
nsresult result = NS_OK;
nsCOMPtr<nsISelectionController> selCon;
nsCOMPtr<nsIDOMDocument> doc;
if (!aEditor)
return NS_ERROR_NULL_POINTER;
LOCK_DOC(this);
// Check to see if we already have an mSelCon. If we do, it
// better be the same one the editor uses!
result = aEditor->GetSelectionController(getter_AddRefs(selCon));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!selCon || (mSelCon && selCon != mSelCon))
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
if (!mSelCon)
mSelCon = selCon;
// Check to see if we already have an mDOMDocument. If we do, it
// better be the same one the editor uses!
result = aEditor->GetDocument(getter_AddRefs(doc));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!doc || (mDOMDocument && doc != mDOMDocument))
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
if (!mDOMDocument)
{
mDOMDocument = doc;
result = CreateDocumentContentIterator(getter_AddRefs(mIterator));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eIsDone;
result = FirstBlock();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
}
mEditor = do_QueryInterface(aEditor);
nsTSDNotifier *notifier = new nsTSDNotifier(this);
if (!notifier)
{
UNLOCK_DOC(this);
return NS_ERROR_OUT_OF_MEMORY;
}
mNotifier = do_QueryInterface(notifier);
result = mEditor->AddEditActionListener(mNotifier);
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::CanEdit(PRBool *aCanEdit)
{
if (!aCanEdit)
return NS_ERROR_NULL_POINTER;
*aCanEdit = (mEditor) ? PR_TRUE : PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsTextServicesDocument::GetCurrentTextBlock(nsString *aStr)
{
nsresult result;
if (!aStr)
return NS_ERROR_NULL_POINTER;
aStr->SetLength(0);
if (!mIterator)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
result = CreateOffsetTable(aStr);
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::FirstBlock()
{
nsresult result;
LOCK_DOC(this);
mIteratorStatus = nsTextServicesDocument::eIsDone;
if (!mIterator)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
// Position the iterator on the first text node
// we run across:
result = mIterator->First();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
nsCOMPtr<nsIContent> content;
while (NS_ENUMERATOR_FALSE == mIterator->IsDone())
{
result = mIterator->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
mIteratorStatus = nsTextServicesDocument::eValid;
break;
}
result = mIterator->Next();
if (NS_FAILED(result))
return result;
}
// Keep track of prev and next blocks, just in case
// the text service blows away the current block.
if (mIteratorStatus == nsTextServicesDocument::eValid)
{
mPrevTextBlock = nsCOMPtr<nsIContent>();
result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
}
else
{
// There's no text block in the document!
mPrevTextBlock = nsCOMPtr<nsIContent>();
mNextTextBlock = nsCOMPtr<nsIContent>();
}
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::LastBlock()
{
nsresult result;
LOCK_DOC(this);
mIteratorStatus = nsTextServicesDocument::eIsDone;
if (!mIterator)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
// Position the iterator on the last text node
// in the tree, then walk backwards over adjacent
// text nodes until we hit a block boundary:
result = mIterator->Last();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIContent> last;
while (NS_ENUMERATOR_FALSE == mIterator->IsDone())
{
result = mIterator->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
break;
}
result = mIterator->Prev();
if (NS_FAILED(result))
return result;
}
// Keep track of prev and next blocks, just in case
// the text service blows away the current block.
if (mIteratorStatus == nsTextServicesDocument::eValid)
{
result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
mNextTextBlock = nsCOMPtr<nsIContent>();
}
else
{
// There's no text block in the document!
mPrevTextBlock = nsCOMPtr<nsIContent>();
mNextTextBlock = nsCOMPtr<nsIContent>();
}
UNLOCK_DOC(this);
return result;
}
// XXX: CODE BLOAT ALERT. FirstSelectedBlock() and LastSelectedBlock()
// are almost identical! Later, when we have time, we should try
// and combine them into one method.
NS_IMETHODIMP
nsTextServicesDocument::FirstSelectedBlock(TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength)
{
nsresult result = NS_OK;
if (!aSelStatus || !aSelOffset || !aSelLength)
return NS_ERROR_NULL_POINTER;
LOCK_DOC(this);
mIteratorStatus = nsTextServicesDocument::eIsDone;
*aSelStatus = nsITextServicesDocument::eBlockNotFound;
*aSelOffset = *aSelLength = -1;
if (!mSelCon || !mIterator)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMSelection> selection;
PRBool isCollapsed = PR_FALSE;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = selection->GetIsCollapsed( &isCollapsed);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
nsCOMPtr<nsIContentIterator> iter;
nsCOMPtr<nsIDOMRange> range;
nsCOMPtr<nsIDOMNode> parent;
nsCOMPtr<nsIContent> content;
PRInt32 i, rangeCount, offset;
if (isCollapsed)
{
// We have a caret. Check if the caret is in a text node.
// If it is, make the text node's block the current block.
// If the caret isn't in a text node, search backwards in
// the document, till we find a text node.
result = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!range)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartContainer(getter_AddRefs(parent));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!parent)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartOffset(&offset);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(parent))
{
// The caret is in a text node. Find the beginning
// of the text block containing this text node and
// return.
content = do_QueryInterface(parent);
if (!content)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (*aSelStatus == nsITextServicesDocument::eBlockContains)
result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_FALSE);
}
else
{
// The caret isn't in a text node. Create an iterator
// based on a range that extends from the beginning of
// the document, to the current caret position, then
// walk backwards till you find a text node, then find
// the beginning of the block.
result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_TRUE, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->Last();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!content)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
if (IsTextNode(content))
break;
content = nsCOMPtr<nsIContent>();
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
if (!content)
{
UNLOCK_DOC(this);
return NS_OK;
}
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
}
UNLOCK_DOC(this);
return result;
}
// If we get here, we have an uncollapsed selection!
// Look through each range in the selection till you
// find the first text node. If you find one, find the
// beginning of it's text block, and make it the current
// block.
result = selection->GetRangeCount(&rangeCount);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
NS_ASSERTION(rangeCount > 0, "Unexpected range count!");
if (rangeCount <= 0)
{
UNLOCK_DOC(this);
return NS_OK;
}
// XXX: We may need to add some code here to make sure
// the ranges are sorted in document appearance order!
for (i = 0; i < rangeCount; i++)
{
// Get the i'th range from the selection.
result = selection->GetRangeAt(i, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Create an iterator for the range.
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->First();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Now walk through the range till we find a text node.
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
// We found a text node, so position the document's
// iterator at the beginning of the block, then get
// the selection in terms of the string offset.
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
UNLOCK_DOC(this);
return result;
}
result = iter->Next();
if (NS_FAILED(result))
return result;
}
}
// If we get here, we didn't find any text node in the selection!
// Create a range that extends from the beginning of the selection,
// to the beginning of the document, then iterate backwards through
// it till you find a text node!
result = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!range)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartContainer(getter_AddRefs(parent));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!parent)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartOffset(&offset);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_TRUE, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = range->GetCollapsed(&isCollapsed);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (isCollapsed)
{
// If we get here, the range is collapsed because there is nothing before
// the current selection! Just return NS_OK;
UNLOCK_DOC(this);
return NS_OK;
}
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->Last();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
// We found a text node! Adjust the document's iterator to point
// to the beginning of it's text block, then get the current selection.
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
UNLOCK_DOC(this);
return result;
}
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
// If we get here, we didn't find any block before or inside
// the selection! Just return OK.
UNLOCK_DOC(this);
return NS_OK;
}
// XXX: CODE BLOAT ALERT. FirstSelectedBlock() and LastSelectedBlock()
// are almost identical! Later, when we have time, we should try
// and combine them into one method.
NS_IMETHODIMP
nsTextServicesDocument::LastSelectedBlock(TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength)
{
nsresult result = NS_OK;
if (!aSelStatus || !aSelOffset || !aSelLength)
return NS_ERROR_NULL_POINTER;
LOCK_DOC(this);
mIteratorStatus = nsTextServicesDocument::eIsDone;
*aSelStatus = nsITextServicesDocument::eBlockNotFound;
*aSelOffset = *aSelLength = -1;
if (!mSelCon || !mIterator)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMSelection> selection;
PRBool isCollapsed = PR_FALSE;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
nsCOMPtr<nsIContentIterator> iter;
nsCOMPtr<nsIDOMRange> range;
nsCOMPtr<nsIDOMNode> parent;
nsCOMPtr<nsIContent> content;
PRInt32 i, rangeCount, offset;
if (isCollapsed)
{
// We have a caret. Check if the caret is in a text node.
// If it is, make the text node's block the current block.
// If the caret isn't in a text node, search forwards in
// the document, till we find a text node.
result = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!range)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartContainer(getter_AddRefs(parent));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!parent)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetStartOffset(&offset);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(parent))
{
// The caret is in a text node. Find the beginning
// of the text block containing this text node and
// return.
content = do_QueryInterface(parent);
if (!content)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (*aSelStatus == nsITextServicesDocument::eBlockContains)
result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_FALSE);
}
else
{
// The caret isn't in a text node. Create an iterator
// based on a range that extends from the current caret
// position to the end of the document, then walk forwards
// till you find a text node, then find the beginning of it's block.
result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_FALSE, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->First();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!content)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
if (IsTextNode(content))
break;
content = nsCOMPtr<nsIContent>();
result = iter->Next();
if (NS_FAILED(result))
return result;
}
if (!content)
{
UNLOCK_DOC(this);
return NS_OK;
}
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
}
UNLOCK_DOC(this);
return result;
}
// If we get here, we have an uncollapsed selection!
// Look backwards through each range in the selection till you
// find the first text node. If you find one, find the
// beginning of it's text block, and make it the current
// block.
result = selection->GetRangeCount(&rangeCount);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
NS_ASSERTION(rangeCount > 0, "Unexpected range count!");
if (rangeCount <= 0)
{
UNLOCK_DOC(this);
return NS_OK;
}
// XXX: We may need to add some code here to make sure
// the ranges are sorted in document appearance order!
for (i = rangeCount - 1; i >= 0; i--)
{
// Get the i'th range from the selection.
result = selection->GetRangeAt(i, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Create an iterator for the range.
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->Last();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Now walk through the range till we find a text node.
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
// We found a text node, so position the document's
// iterator at the beginning of the block, then get
// the selection in terms of the string offset.
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
UNLOCK_DOC(this);
return result;
}
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
}
// If we get here, we didn't find any text node in the selection!
// Create a range that extends from the end of the selection,
// to the end of the document, then iterate forwards through
// it till you find a text node!
result = selection->GetRangeAt(rangeCount - 1, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!range)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetEndContainer(getter_AddRefs(parent));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!parent)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = range->GetEndOffset(&offset);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = CreateDocumentContentRootToNodeOffsetRange(parent, offset, PR_FALSE, getter_AddRefs(range));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = range->GetCollapsed(&isCollapsed);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (isCollapsed)
{
// If we get here, the range is collapsed because there is nothing after
// the current selection! Just return NS_OK;
UNLOCK_DOC(this);
return NS_OK;
}
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = iter->First();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (IsTextNode(content))
{
// We found a text node! Adjust the document's iterator to point
// to the beginning of it's text block, then get the current selection.
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
mIteratorStatus = nsTextServicesDocument::eValid;
result = CreateOffsetTable();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
result = GetSelection(aSelStatus, aSelOffset, aSelLength);
UNLOCK_DOC(this);
return result;
}
result = iter->Next();
if (NS_FAILED(result))
return result;
}
// If we get here, we didn't find any block before or inside
// the selection! Just return OK.
UNLOCK_DOC(this);
return NS_OK;
}
NS_IMETHODIMP
nsTextServicesDocument::PrevBlock()
{
nsresult result = NS_OK;
if (!mIterator)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
if (mIteratorStatus == nsTextServicesDocument::eIsDone)
return NS_OK;
switch (mIteratorStatus)
{
case nsTextServicesDocument::eValid:
case nsTextServicesDocument::eNext:
result = FirstTextNodeInPrevBlock(mIterator);
if (NS_FAILED(result))
{
mIteratorStatus = nsTextServicesDocument::eIsDone;
UNLOCK_DOC(this);
return result;
}
if (mIterator->IsDone() != NS_ENUMERATOR_FALSE)
{
mIteratorStatus = nsTextServicesDocument::eIsDone;
UNLOCK_DOC(this);
return NS_OK;
}
mIteratorStatus = nsTextServicesDocument::eValid;
break;
case nsTextServicesDocument::ePrev:
// The iterator already points to the previous
// block, so don't do anything.
mIteratorStatus = nsTextServicesDocument::eValid;
break;
default:
mIteratorStatus = nsTextServicesDocument::eIsDone;
break;
}
// Keep track of prev and next blocks, just in case
// the text service blows away the current block.
if (mIteratorStatus == nsTextServicesDocument::eValid)
{
result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
}
else
{
// We must be done!
mPrevTextBlock = nsCOMPtr<nsIContent>();
mNextTextBlock = nsCOMPtr<nsIContent>();
}
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::NextBlock()
{
nsresult result = NS_OK;
if (!mIterator)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
if (mIteratorStatus == nsTextServicesDocument::eIsDone)
return NS_OK;
switch (mIteratorStatus)
{
case nsTextServicesDocument::eValid:
// Advance the iterator to the next text block.
result = FirstTextNodeInNextBlock(mIterator);
if (NS_FAILED(result))
{
mIteratorStatus = nsTextServicesDocument::eIsDone;
UNLOCK_DOC(this);
return result;
}
if (mIterator->IsDone() != NS_ENUMERATOR_FALSE)
{
mIteratorStatus = nsTextServicesDocument::eIsDone;
UNLOCK_DOC(this);
return NS_OK;
}
mIteratorStatus = nsTextServicesDocument::eValid;
break;
case nsTextServicesDocument::eNext:
// The iterator already points to the next block,
// so don't do anything to it!
mIteratorStatus = nsTextServicesDocument::eValid;
break;
case nsTextServicesDocument::ePrev:
// If the iterator is pointing to the previous block,
// we know that there is no next text block! Just
// fall through to the default case!
default:
mIteratorStatus = nsTextServicesDocument::eIsDone;
break;
}
// Keep track of prev and next blocks, just in case
// the text service blows away the current block.
if (mIteratorStatus == nsTextServicesDocument::eValid)
{
result = GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock));
result = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock));
}
else
{
// We must be done.
mPrevTextBlock = nsCOMPtr<nsIContent>();
mNextTextBlock = nsCOMPtr<nsIContent>();
}
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::IsDone(PRBool *aIsDone)
{
if (!aIsDone)
return NS_ERROR_NULL_POINTER;
*aIsDone = PR_FALSE;
if (!mIterator)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
*aIsDone = (mIteratorStatus == nsTextServicesDocument::eIsDone) ? PR_TRUE : PR_FALSE;
UNLOCK_DOC(this);
return NS_OK;
}
NS_IMETHODIMP
nsTextServicesDocument::SetSelection(PRInt32 aOffset, PRInt32 aLength)
{
nsresult result;
if (!mSelCon || aOffset < 0 || aLength < 0)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
result = SetSelectionInternal(aOffset, aLength, PR_TRUE);
UNLOCK_DOC(this);
//**** KDEBUG ****
// printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
//**** KDEBUG ****
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::ScrollSelectionIntoView()
{
nsresult result;
if (!mSelCon)
return NS_ERROR_FAILURE;
LOCK_DOC(this);
result = mSelCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION);
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::DeleteSelection()
{
nsresult result = NS_OK;
// We don't allow deletion during a collapsed selection!
NS_ASSERTION(mEditor, "DeleteSelection called without an editor present!");
NS_ASSERTION(SelectionIsValid(), "DeleteSelection called without a valid selection!");
if (!mEditor || !SelectionIsValid())
return NS_ERROR_FAILURE;
if (SelectionIsCollapsed())
return NS_OK;
LOCK_DOC(this);
//**** KDEBUG ****
// printf("\n---- Before Delete\n");
// printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
// PrintOffsetTable();
//**** KDEBUG ****
PRInt32 i, selLength;
OffsetEntry *entry, *newEntry;
for (i = mSelStartIndex; i <= mSelEndIndex; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (i == mSelStartIndex)
{
// Calculate the length of the selection. Note that the
// selection length can be zero if the start of the selection
// is at the very end of a text node entry.
if (entry->mIsInsertedText)
{
// Inserted text offset entries have no width when
// talking in terms of string offsets! If the beginning
// of the selection is in an inserted text offset entry,
// the caret is always at the end of the entry!
selLength = 0;
}
else
selLength = entry->mLength - (mSelStartOffset - entry->mStrOffset);
if (selLength > 0 && mSelStartOffset > entry->mStrOffset)
{
// Selection doesn't start at the beginning of the
// text node entry. We need to split this entry into
// two pieces, the piece before the selection, and
// the piece inside the selection.
result = SplitOffsetEntry(i, selLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Adjust selection indexes to account for new entry:
++mSelStartIndex;
++mSelEndIndex;
++i;
entry = (OffsetEntry *)mOffsetTable[i];
}
if (selLength > 0 && mSelStartIndex < mSelEndIndex)
{
// The entire entry is contained in the selection. Mark the
// entry invalid.
entry->mIsValid = PR_FALSE;
}
}
//**** KDEBUG ****
// printf("\n---- Middle Delete\n");
// printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
// PrintOffsetTable();
//**** KDEBUG ****
if (i == mSelEndIndex)
{
if (entry->mIsInsertedText)
{
// Inserted text offset entries have no width when
// talking in terms of string offsets! If the end
// of the selection is in an inserted text offset entry,
// the selection includes the entire entry!
entry->mIsValid = PR_FALSE;
}
else
{
// Calculate the length of the selection. Note that the
// selection length can be zero if the end of the selection
// is at the very beginning of a text node entry.
selLength = mSelEndOffset - entry->mStrOffset;
if (selLength > 0 && mSelEndOffset < entry->mStrOffset + entry->mLength)
{
// mStrOffset is guaranteed to be inside the selection, even
// when mSelStartIndex == mSelEndIndex.
result = SplitOffsetEntry(i, entry->mLength - selLength);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// Update the entry fields:
newEntry = (OffsetEntry *)mOffsetTable[i+1];
newEntry->mNodeOffset = entry->mNodeOffset;
}
if (selLength > 0 && mSelEndOffset == entry->mStrOffset + entry->mLength)
{
// The entire entry is contained in the selection. Mark the
// entry invalid.
entry->mIsValid = PR_FALSE;
}
}
}
if (i != mSelStartIndex && i != mSelEndIndex)
{
// The entire entry is contained in the selection. Mark the
// entry invalid.
entry->mIsValid = PR_FALSE;
}
}
// Make sure mIterator always points to something valid!
AdjustContentIterator();
// Now delete the actual content!
result = mEditor->DeleteSelection(nsIEditor::ePrevious);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
entry = 0;
// Move the caret to the end of the first valid entry.
// Start with mSelStartIndex since it may still be valid.
for (i = mSelStartIndex; !entry && i >= 0; i--)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry->mIsValid)
entry = 0;
else
{
mSelStartIndex = mSelEndIndex = i;
mSelStartOffset = mSelEndOffset = entry->mStrOffset + entry->mLength;
}
}
// If we still don't have a valid entry, move the caret
// to the next valid entry after the selection:
for (i = mSelEndIndex; !entry && i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry->mIsValid)
entry = 0;
else
{
mSelStartIndex = mSelEndIndex = i;
mSelStartOffset = mSelEndOffset = entry->mStrOffset;
}
}
if (entry)
result = SetSelection(mSelStartOffset, 0);
else
{
// Uuughh we have no valid offset entry to place our
// caret ... just mark the selection invalid.
mSelStartIndex = mSelEndIndex = -1;
mSelStartOffset = mSelEndOffset = -1;
}
// Now remove any invalid entries from the offset table.
result = RemoveInvalidOffsetEntries();
//**** KDEBUG ****
// printf("\n---- After Delete\n");
// printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
// PrintOffsetTable();
//**** KDEBUG ****
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::InsertText(const nsString *aText)
{
nsresult result = NS_OK;
NS_ASSERTION(mEditor, "InsertText called without an editor present!");
if (!mEditor)
return NS_ERROR_FAILURE;
if (!aText)
return NS_ERROR_NULL_POINTER;
LOCK_DOC(this);
result = mEditor->BeginTransaction();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
// We have to call DeleteSelection(), just in case there is
// a selection, to make sure that our offset table is kept in sync!
result = DeleteSelection();
if (NS_FAILED(result))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return result;
}
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor, &result);
if (htmlEditor)
result = htmlEditor->InsertText(*aText);
if (NS_FAILED(result))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return result;
}
if (SelectionIsValid())
{
//**** KDEBUG ****
// printf("\n---- Before Insert\n");
// printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
// PrintOffsetTable();
//**** KDEBUG ****
PRInt32 i, strLength = aText->Length();
nsCOMPtr<nsIDOMSelection> selection;
OffsetEntry *itEntry;
OffsetEntry *entry = (OffsetEntry *)mOffsetTable[mSelStartIndex];
void *node = entry->mNode;
NS_ASSERTION((entry->mIsValid), "Invalid insertion point!");
if (entry->mStrOffset == mSelStartOffset)
{
if (entry->mIsInsertedText)
{
// If the caret is in an inserted text offset entry,
// we simply insert the text at the end of the entry.
entry->mLength += strLength;
}
else
{
// Insert an inserted text offset entry before the current
// entry!
itEntry = new OffsetEntry(entry->mNode, entry->mStrOffset, strLength);
if (!itEntry)
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_OUT_OF_MEMORY;
}
itEntry->mIsInsertedText = PR_TRUE;
if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
}
}
else if ((entry->mStrOffset + entry->mLength) == mSelStartOffset)
{
// We are inserting text at the end of the current offset entry.
// Look at the next valid entry in the table. If it's an inserted
// text entry, add to it's length and adjust it's node offset. If
// it isn't, add a new inserted text entry.
i = mSelStartIndex + 1;
itEntry = 0;
if (mOffsetTable.Count() > i)
{
itEntry = (OffsetEntry *)mOffsetTable[i];
if (!itEntry)
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
// Check if the entry is a match. If it isn't, set
// iEntry to zero.
if (!itEntry->mIsInsertedText || itEntry->mStrOffset != mSelStartOffset)
itEntry = 0;
}
if (!itEntry)
{
// We didn't find an inserted text offset entry, so
// create one.
itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, 0);
if (!itEntry)
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_OUT_OF_MEMORY;
}
itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength;
itEntry->mIsInsertedText = PR_TRUE;
if (!mOffsetTable.InsertElementAt(itEntry, i))
{
delete itEntry;
return NS_ERROR_FAILURE;
}
}
// We have a valid inserted text offset entry. Update it's
// length, adjust the selection indexes, and make sure the
// caret is properly placed!
itEntry->mLength += strLength;
mSelStartIndex = mSelEndIndex = i;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return result;
}
result = selection->Collapse(itEntry->mNode, itEntry->mNodeOffset + itEntry->mLength);
if (NS_FAILED(result))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return result;
}
}
else if ((entry->mStrOffset + entry->mLength) > mSelStartOffset)
{
// We are inserting text into the middle of the current offset entry.
// split the current entry into two parts, then insert an inserted text
// entry between them!
i = entry->mLength - (mSelStartOffset - entry->mStrOffset);
result = SplitOffsetEntry(mSelStartIndex, i);
if (NS_FAILED(result))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return result;
}
itEntry = new OffsetEntry(entry->mNode, mSelStartOffset, strLength);
if (!itEntry)
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_OUT_OF_MEMORY;
}
itEntry->mIsInsertedText = PR_TRUE;
itEntry->mNodeOffset = entry->mNodeOffset + entry->mLength;
if (!mOffsetTable.InsertElementAt(itEntry, mSelStartIndex + 1))
{
mEditor->EndTransaction();
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
mSelEndIndex = ++mSelStartIndex;
}
// We've just finished inserting an inserted text offset entry.
// update all entries with the same mNode pointer that follow
// it in the table!
for (i = mSelStartIndex + 1; i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (entry->mNode == node)
{
if (entry->mIsValid)
entry->mNodeOffset += strLength;
}
else
break;
}
//**** KDEBUG ****
// printf("\n---- After Insert\n");
// printf("Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
// PrintOffsetTable();
//**** KDEBUG ****
}
result = mEditor->EndTransaction();
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
UNLOCK_DOC(this);
return result;
}
NS_IMETHODIMP
nsTextServicesDocument::SetDisplayStyle(TSDDisplayStyle aStyle)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
nsTextServicesDocument::InsertNode(nsIDOMNode *aNode,
nsIDOMNode *aParent,
PRInt32 aPosition)
{
//**** KDEBUG ****
// printf("** InsertNode: 0x%.8x 0x%.8x %d\n", aNode, aParent, aPosition);
// fflush(stdout);
//**** KDEBUG ****
NS_ASSERTION(0, "InsertNode called, offset tables might be out of sync.");
return NS_OK;
}
nsresult
nsTextServicesDocument::DeleteNode(nsIDOMNode *aChild)
{
//**** KDEBUG ****
// printf("** DeleteNode: 0x%.8x\n", aChild);
// fflush(stdout);
//**** KDEBUG ****
LOCK_DOC(this);
PRInt32 nodeIndex, tcount;
PRBool hasEntry;
OffsetEntry *entry;
nsresult result = NodeHasOffsetEntry(aChild, &hasEntry, &nodeIndex);
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (!hasEntry)
{
// It's okay if the node isn't in the offset table, the
// editor could be cleaning house.
UNLOCK_DOC(this);
return NS_OK;
}
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIDOMNode> node;
result = mIterator->CurrentNode(getter_AddRefs(content));
if (content)
{
node = do_QueryInterface(content);
if (node && node.get() == aChild)
{
// The iterator points to the node that is about
// to be deleted. Move it to the next valid text node
// listed in the offset table!
// XXX
}
}
tcount = mOffsetTable.Count();
while (nodeIndex < tcount)
{
entry = (OffsetEntry *)mOffsetTable[nodeIndex];
if (!entry)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
if (entry->mNode == aChild)
{
entry->mIsValid = PR_FALSE;
}
nodeIndex++;
}
UNLOCK_DOC(this);
NS_ASSERTION(0, "DeleteNode called, offset tables might be out of sync.");
return NS_OK;
}
nsresult
nsTextServicesDocument::SplitNode(nsIDOMNode *aExistingRightNode,
PRInt32 aOffset,
nsIDOMNode *aNewLeftNode)
{
//**** KDEBUG ****
// printf("** SplitNode: 0x%.8x %d 0x%.8x\n", aExistingRightNode, aOffset, aNewLeftNode);
// fflush(stdout);
//**** KDEBUG ****
NS_ASSERTION(0, "SplitNode called, offset tables might be out of sync.");
return NS_OK;
}
nsresult
nsTextServicesDocument::JoinNodes(nsIDOMNode *aLeftNode,
nsIDOMNode *aRightNode,
nsIDOMNode *aParent)
{
PRInt32 i;
PRUint16 type;
nsresult result;
//**** KDEBUG ****
// printf("** JoinNodes: 0x%.8x 0x%.8x 0x%.8x\n", aLeftNode, aRightNode, aParent);
// fflush(stdout);
//**** KDEBUG ****
// Make sure that both nodes are text nodes!
result = aLeftNode->GetNodeType(&type);
if (NS_FAILED(result))
return PR_FALSE;
if (nsIDOMNode::TEXT_NODE != type)
{
NS_ASSERTION(0, "JoinNode called with a non-text left node!");
return NS_ERROR_FAILURE;
}
result = aRightNode->GetNodeType(&type);
if (NS_FAILED(result))
return PR_FALSE;
if (nsIDOMNode::TEXT_NODE != type)
{
NS_ASSERTION(0, "JoinNode called with a non-text right node!");
return NS_ERROR_FAILURE;
}
// Note: The editor merges the contents of the left node into the
// contents of the right.
PRInt32 leftIndex, rightIndex;
PRBool leftHasEntry, rightHasEntry;
result = NodeHasOffsetEntry(aLeftNode, &leftHasEntry, &leftIndex);
if (NS_FAILED(result))
return result;
if (!leftHasEntry)
{
// XXX: Not sure if we should be throwing an error here!
NS_ASSERTION(0, "JoinNode called with node not listed in offset table.");
return NS_ERROR_FAILURE;
}
result = NodeHasOffsetEntry(aRightNode, &rightHasEntry, &rightIndex);
if (NS_FAILED(result))
return result;
if (!rightHasEntry)
{
// XXX: Not sure if we should be throwing an error here!
NS_ASSERTION(0, "JoinNode called with node not listed in offset table.");
return NS_ERROR_FAILURE;
}
NS_ASSERTION(leftIndex < rightIndex, "Indexes out of order.");
if (leftIndex > rightIndex)
{
// Don't know how to handle this situation.
return NS_ERROR_FAILURE;
}
LOCK_DOC(this);
OffsetEntry *entry = (OffsetEntry *)mOffsetTable[leftIndex];
NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for leftIndex.");
entry = (OffsetEntry *)mOffsetTable[rightIndex];
NS_ASSERTION(entry->mNodeOffset == 0, "Unexpected offset value for rightIndex.");
// Run through the table and change all entries referring to
// the left node so that they now refer to the right node:
PRInt32 nodeLength = 0;
for (i = leftIndex; i < rightIndex; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (entry->mNode == aLeftNode)
{
if (entry->mIsValid)
{
entry->mNode = aRightNode;
nodeLength += entry->mLength;
}
}
else
break;
}
// Run through the table and adjust the node offsets
// for all entries referring to the right node.
for (i = rightIndex; i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (entry->mNode == aRightNode)
{
if (entry->mIsValid)
entry->mNodeOffset += nodeLength;
}
else
break;
}
// Now check to see if the iterator is pointing to the
// left node. If it is, make it point to the right node!
nsCOMPtr<nsIContent> currentContent;
nsCOMPtr<nsIContent> leftContent = do_QueryInterface(aLeftNode);
nsCOMPtr<nsIContent> rightContent = do_QueryInterface(aRightNode);
if (!leftContent || !rightContent)
{
UNLOCK_DOC(this);
return NS_ERROR_FAILURE;
}
result = mIterator->CurrentNode(getter_AddRefs(currentContent));
if (NS_FAILED(result))
{
UNLOCK_DOC(this);
return result;
}
if (currentContent == leftContent)
result = mIterator->PositionAt(rightContent);
UNLOCK_DOC(this);
return NS_OK;
}
nsresult
nsTextServicesDocument::CreateContentIterator(nsIDOMRange *aRange, nsIContentIterator **aIterator)
{
nsresult result;
if (!aRange || !aIterator)
return NS_ERROR_NULL_POINTER;
*aIterator = 0;
result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull,
NS_GET_IID(nsIContentIterator),
(void **)aIterator);
if (NS_FAILED(result))
return result;
if (!*aIterator)
return NS_ERROR_NULL_POINTER;
result = (*aIterator)->Init(aRange);
if (NS_FAILED(result))
{
NS_RELEASE((*aIterator));
*aIterator = 0;
return result;
}
return NS_OK;
}
nsresult
nsTextServicesDocument::GetDocumentContentRootNode(nsIDOMNode **aNode)
{
nsresult result;
if (!aNode)
return NS_ERROR_NULL_POINTER;
*aNode = 0;
if (!mDOMDocument)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDOMDocument);
if (htmlDoc)
{
// For HTML documents, the content root node is the body.
nsCOMPtr<nsIDOMHTMLElement> bodyElement;
result = htmlDoc->GetBody(getter_AddRefs(bodyElement));
if (NS_FAILED(result))
return result;
if (!bodyElement)
return NS_ERROR_FAILURE;
result = bodyElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode);
}
else
{
// For non-HTML documents, the content root node will be the document element.
nsCOMPtr<nsIDOMElement> docElement;
result = mDOMDocument->GetDocumentElement(getter_AddRefs(docElement));
if (NS_FAILED(result))
return result;
if (!docElement)
return NS_ERROR_FAILURE;
result = docElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode);
}
return result;
}
nsresult
nsTextServicesDocument::CreateDocumentContentRange(nsIDOMRange **aRange)
{
nsresult result;
if (!aRange)
return NS_ERROR_NULL_POINTER;
*aRange = 0;
nsCOMPtr<nsIDOMNode>node;
result = GetDocumentContentRootNode(getter_AddRefs(node));
if (NS_FAILED(result))
return result;
if (!node)
return NS_ERROR_NULL_POINTER;
result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull,
NS_GET_IID(nsIDOMRange),
(void **)aRange);
if (NS_FAILED(result))
return result;
if (!*aRange)
return NS_ERROR_NULL_POINTER;
result = (*aRange)->SelectNodeContents(node);
if (NS_FAILED(result))
{
NS_RELEASE((*aRange));
*aRange = 0;
return result;
}
return NS_OK;
}
nsresult
nsTextServicesDocument::CreateDocumentContentRootToNodeOffsetRange(nsIDOMNode *aParent, PRInt32 aOffset, PRBool aToStart, nsIDOMRange **aRange)
{
nsresult result;
if (!aParent || !aRange)
return NS_ERROR_NULL_POINTER;
*aRange = 0;
NS_ASSERTION(aOffset >= 0, "Invalid offset!");
if (aOffset < 0)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIDOMNode> bodyNode;
result = GetDocumentContentRootNode(getter_AddRefs(bodyNode));
if (NS_FAILED(result))
return result;
if (!bodyNode)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> startNode;
nsCOMPtr<nsIDOMNode> endNode;
PRInt32 startOffset, endOffset;
if (aToStart)
{
// The range should begin at the start of the document
// and extend up until (aParent, aOffset).
startNode = bodyNode;
startOffset = 0;
endNode = do_QueryInterface(aParent);
endOffset = aOffset;
}
else
{
// The range should begin at (aParent, aOffset) and
// extend to the end of the document.
nsCOMPtr<nsIDOMNodeList> nodeList;
PRUint32 nodeListLength;
startNode = do_QueryInterface(aParent);
startOffset = aOffset;
endNode = bodyNode;
endOffset = 0;
result = bodyNode->GetChildNodes(getter_AddRefs(nodeList));
if (NS_FAILED(result))
return NS_ERROR_FAILURE;
if (nodeList)
{
result = nodeList->GetLength(&nodeListLength);
if (NS_FAILED(result))
return NS_ERROR_FAILURE;
endOffset = (PRInt32)nodeListLength;
}
}
result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull,
NS_GET_IID(nsIDOMRange),
(void **)aRange);
if (NS_FAILED(result))
return result;
if (!*aRange)
return NS_ERROR_NULL_POINTER;
result = (*aRange)->SetStart(startNode, startOffset);
if (NS_SUCCEEDED(result))
result = (*aRange)->SetEnd(endNode, endOffset);
if (NS_FAILED(result))
{
NS_RELEASE((*aRange));
*aRange = 0;
}
return result;
}
nsresult
nsTextServicesDocument::CreateDocumentContentIterator(nsIContentIterator **aIterator)
{
nsresult result;
if (!aIterator)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMRange> range;
result = CreateDocumentContentRange(getter_AddRefs(range));
if (NS_FAILED(result))
return result;
result = CreateContentIterator(range, aIterator);
return result;
}
nsresult
nsTextServicesDocument::AdjustContentIterator()
{
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIDOMNode> node;
nsresult result;
PRInt32 i;
if (!mIterator)
return NS_ERROR_FAILURE;
result = mIterator->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (!content)
return NS_ERROR_FAILURE;
node = do_QueryInterface(content);
if (!node)
return NS_ERROR_FAILURE;
nsIDOMNode *nodePtr = node.get();
PRInt32 tcount = mOffsetTable.Count();
nsIDOMNode *prevValidNode = 0;
nsIDOMNode *nextValidNode = 0;
PRBool foundEntry = PR_FALSE;
OffsetEntry *entry;
for (i = 0; i < tcount && !nextValidNode; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry)
return NS_ERROR_FAILURE;
if (entry->mNode == nodePtr)
{
if (entry->mIsValid)
{
// The iterator is still pointing to something valid!
// Do nothing!
return NS_OK;
}
else
{
// We found an invalid entry that points to
// the current iterator node. Stop looking for
// a previous valid node!
foundEntry = PR_TRUE;
}
}
if (entry->mIsValid)
{
if (!foundEntry)
prevValidNode = entry->mNode;
else
nextValidNode = entry->mNode;
}
}
content = nsCOMPtr<nsIContent>();
if (prevValidNode)
content = do_QueryInterface(prevValidNode);
else if (nextValidNode)
content = do_QueryInterface(nextValidNode);
if (content)
{
result = mIterator->PositionAt(content);
if (NS_FAILED(result))
mIteratorStatus = eIsDone;
else
mIteratorStatus = eValid;
return result;
}
// If we get here, there aren't any valid entries
// in the offset table! Try to position the iterator
// on the next text block first, then previous if
// one doesn't exist!
if (mNextTextBlock)
{
result = mIterator->PositionAt(mNextTextBlock);
if (NS_FAILED(result))
{
mIteratorStatus = eIsDone;
return result;
}
mIteratorStatus = eNext;
}
else if (mPrevTextBlock)
{
result = mIterator->PositionAt(mPrevTextBlock);
if (NS_FAILED(result))
{
mIteratorStatus = eIsDone;
return result;
}
mIteratorStatus = ePrev;
}
else
mIteratorStatus = eIsDone;
return NS_OK;
}
PRBool
nsTextServicesDocument::IsBlockNode(nsIContent *aContent)
{
nsCOMPtr<nsIAtom> atom;
aContent->GetTag(*getter_AddRefs(atom));
if (!atom)
return PR_TRUE;
nsIAtom *atomPtr = atom.get();
return (sAAtom != atomPtr &&
sAddressAtom != atomPtr &&
sBigAtom != atomPtr &&
sBlinkAtom != atomPtr &&
sBAtom != atomPtr &&
sCiteAtom != atomPtr &&
sCodeAtom != atomPtr &&
sDfnAtom != atomPtr &&
sEmAtom != atomPtr &&
sFontAtom != atomPtr &&
sIAtom != atomPtr &&
sKbdAtom != atomPtr &&
sKeygenAtom != atomPtr &&
sNobrAtom != atomPtr &&
sSAtom != atomPtr &&
sSampAtom != atomPtr &&
sSmallAtom != atomPtr &&
sSpacerAtom != atomPtr &&
sSpanAtom != atomPtr &&
sStrikeAtom != atomPtr &&
sStrongAtom != atomPtr &&
sSubAtom != atomPtr &&
sSupAtom != atomPtr &&
sTtAtom != atomPtr &&
sUAtom != atomPtr &&
sVarAtom != atomPtr &&
sWbrAtom != atomPtr);
}
PRBool
nsTextServicesDocument::HasSameBlockNodeParent(nsIContent *aContent1, nsIContent *aContent2)
{
nsCOMPtr<nsIContent> p1;
nsCOMPtr<nsIContent> p2;
nsresult result;
result = aContent1->GetParent(*getter_AddRefs(p1));
if (NS_FAILED(result))
return PR_FALSE;
result = aContent2->GetParent(*getter_AddRefs(p2));
if (NS_FAILED(result))
return PR_FALSE;
// Quick test:
if (p1 == p2)
return PR_TRUE;
// Walk up the parent hierarchy looking for closest block boundary node:
nsCOMPtr<nsIContent> tmp;
while (p1 && !IsBlockNode(p1))
{
result = p1->GetParent(*getter_AddRefs(tmp));
if (NS_FAILED(result))
return PR_FALSE;
p1 = tmp;
}
while (p2 && !IsBlockNode(p2))
{
result = p2->GetParent(*getter_AddRefs(tmp));
if (NS_FAILED(result))
return PR_FALSE;
p2 = tmp;
}
return p1 == p2;
}
PRBool
nsTextServicesDocument::IsTextNode(nsIContent *aContent)
{
if (!aContent)
return PR_FALSE;
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aContent);
return IsTextNode(node);
}
PRBool
nsTextServicesDocument::IsTextNode(nsIDOMNode *aNode)
{
if (!aNode)
return PR_FALSE;
PRUint16 type;
nsresult result = aNode->GetNodeType(&type);
if (NS_FAILED(result))
return PR_FALSE;
return nsIDOMNode::TEXT_NODE == type;
}
nsresult
nsTextServicesDocument::SetSelectionInternal(PRInt32 aOffset, PRInt32 aLength, PRBool aDoUpdate)
{
nsresult result = NS_OK;
if (!mSelCon || aOffset < 0 || aLength < 0)
return NS_ERROR_FAILURE;
nsIDOMNode *sNode = 0, *eNode = 0;
PRInt32 i, sOffset = 0, eOffset = 0;
OffsetEntry *entry;
// Find start of selection in node offset terms:
for (i = 0; !sNode && i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (entry->mIsValid)
{
if (entry->mIsInsertedText)
{
// Caret can only be placed at the end of an
// inserted text offset entry, if the offsets
// match exactly!
if (entry->mStrOffset == aOffset)
{
sNode = entry->mNode;
sOffset = entry->mNodeOffset + entry->mLength;
}
}
else if (aOffset >= entry->mStrOffset && aOffset <= entry->mStrOffset + entry->mLength)
{
sNode = entry->mNode;
sOffset = entry->mNodeOffset + aOffset - entry->mStrOffset;
}
if (sNode)
{
mSelStartIndex = i;
mSelStartOffset = aOffset;
}
}
}
if (!sNode)
return NS_ERROR_FAILURE;
// XXX: If we ever get a SetSelection() method in nsIEditor, we should
// use it.
nsCOMPtr<nsIDOMSelection> selection;
if (aDoUpdate)
{
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
return result;
result = selection->Collapse(sNode, sOffset);
if (NS_FAILED(result))
return result;
}
if (aLength <= 0)
{
// We have a collapsed selection. (Caret)
mSelEndIndex = mSelStartIndex;
mSelEndOffset = mSelStartOffset;
//**** KDEBUG ****
// printf("\n* Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
//**** KDEBUG ****
return NS_OK;
}
// Find the end of the selection in node offset terms:
PRInt32 endOffset = aOffset + aLength;
for (i = mOffsetTable.Count() - 1; !eNode && i >= 0; i--)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (entry->mIsValid)
{
if (entry->mIsInsertedText)
{
if (entry->mStrOffset == eOffset)
{
// If the selection ends on an inserted text offset entry,
// the selection includes the entire entry!
eNode = entry->mNode;
eOffset = entry->mNodeOffset + entry->mLength;
}
}
else if (endOffset >= entry->mStrOffset && endOffset <= entry->mStrOffset + entry->mLength)
{
eNode = entry->mNode;
eOffset = entry->mNodeOffset + endOffset - entry->mStrOffset;
}
if (eNode)
{
mSelEndIndex = i;
mSelEndOffset = endOffset;
}
}
}
if (aDoUpdate && eNode)
{
result = selection->Extend(eNode, eOffset);
if (NS_FAILED(result))
return result;
}
//**** KDEBUG ****
// printf("\n * Sel: (%2d, %4d) (%2d, %4d)\n", mSelStartIndex, mSelStartOffset, mSelEndIndex, mSelEndOffset);
//**** KDEBUG ****
return result;
}
nsresult
nsTextServicesDocument::GetSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength)
{
nsresult result;
if (!aSelStatus || !aSelOffset || !aSelLength)
return NS_ERROR_NULL_POINTER;
*aSelStatus = nsITextServicesDocument::eBlockNotFound;
*aSelOffset = -1;
*aSelLength = -1;
if (!mDOMDocument || !mSelCon)
return NS_ERROR_FAILURE;
if (mIteratorStatus == nsTextServicesDocument::eIsDone)
return NS_OK;
nsCOMPtr<nsIDOMSelection> selection;
PRBool isCollapsed;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
return result;
if (!selection)
return NS_ERROR_FAILURE;
result = selection->GetIsCollapsed(&isCollapsed);
if (NS_FAILED(result))
return result;
// XXX: If we expose this method publicly, we need to
// add LOCK_DOC/UNLOCK_DOC calls!
// LOCK_DOC(this);
if (isCollapsed)
result = GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength);
else
result = GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength);
// UNLOCK_DOC(this);
return result;
}
nsresult
nsTextServicesDocument::GetCollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength)
{
nsresult result;
nsCOMPtr<nsIDOMSelection> selection;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
return result;
if (!selection)
return NS_ERROR_FAILURE;
// The calling function should have done the GetIsCollapsed()
// check already. Just assume it's collapsed!
nsCOMPtr<nsIDOMRange> range;
nsCOMPtr<nsIContent> content;
OffsetEntry *entry;
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset, tableCount, i;
PRInt32 e1s1, e2s1;
OffsetEntry *eStart, *eEnd;
PRInt32 eStartOffset, eEndOffset;
*aSelStatus = nsITextServicesDocument::eBlockOutside;
*aSelOffset = *aSelLength = -1;
tableCount = mOffsetTable.Count();
if (tableCount == 0)
return NS_OK;
// Get pointers to the first and last offset entries
// in the table.
eStart = (OffsetEntry *)mOffsetTable[0];
if (tableCount > 1)
eEnd = (OffsetEntry *)mOffsetTable[tableCount - 1];
else
eEnd = eStart;
eStartOffset = eStart->mNodeOffset;
eEndOffset = eEnd->mNodeOffset + eEnd->mLength;
result = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(result))
return result;
result = range->GetStartContainer(getter_AddRefs(parent));
if (NS_FAILED(result))
return result;
result = range->GetStartOffset(&offset);
if (NS_FAILED(result))
return result;
result = ComparePoints(eStart->mNode, eStartOffset, parent, offset, &e1s1);
if (NS_FAILED(result))
return result;
result = ComparePoints(eEnd->mNode, eEndOffset, parent, offset, &e2s1);
if (NS_FAILED(result))
return result;
if (e1s1 > 0 || e2s1 < 0)
{
// We're done if the caret is outside the
// current text block.
return NS_OK;
}
if (IsTextNode(parent))
{
// Good news, the caret is in a text node. Look
// through the offset table for the entry that
// matches it's parent and offset.
for (i = 0; i < tableCount; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry)
return NS_ERROR_FAILURE;
if (entry->mNode == parent.get() &&
entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength))
{
*aSelStatus = nsITextServicesDocument::eBlockContains;
*aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset);
*aSelLength = 0;
return NS_OK;
}
}
// If we get here, we didn't find a text node entry
// in our offset table that matched.
return NS_ERROR_FAILURE;
}
// The caret is in our text block, but it's positioned in some
// non-text node (ex. <b>). Create a range based on the start
// and end of the text block, then create an iterator based on
// this range, with it's initial position set to the closest
// child of this non-text node. Then look for the closest text
// node.
nsCOMPtr<nsIDOMNode> node, saveNode;
nsCOMPtr<nsIDOMNodeList> children;
nsCOMPtr<nsIContentIterator> iter;
PRBool hasChildren;
result = CreateRange(eStart->mNode, eStartOffset, eEnd->mNode, eEndOffset, getter_AddRefs(range));
if (NS_FAILED(result))
return result;
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
return result;
result = parent->HasChildNodes(&hasChildren);
if (NS_FAILED(result))
return result;
if (hasChildren)
{
// XXX: We need to make sure that all of parent's
// children are in the text block.
// If the parent has children, position the iterator
// on the child that is to the left of the offset.
PRUint32 childIndex = (PRUint32)offset;
result = parent->GetChildNodes(getter_AddRefs(children));
if (NS_FAILED(result))
return result;
if (!children)
return NS_ERROR_FAILURE;
if (childIndex > 0)
{
PRUint32 numChildren;
result = children->GetLength(&numChildren);
if (NS_FAILED(result))
return result;
NS_ASSERTION(childIndex <= numChildren, "Invalid selection offset!");
if (childIndex > numChildren)
childIndex = numChildren;
childIndex -= 1;
}
result = children->Item(childIndex, getter_AddRefs(saveNode));
if (NS_FAILED(result))
return result;
content = do_QueryInterface(saveNode);
if (!content)
return NS_ERROR_FAILURE;
result = iter->PositionAt(content);
if (NS_FAILED(result))
return result;
}
else
{
// The parent has no children, so position the iterator
// on the parent.
content = do_QueryInterface(parent);
if (!content)
return NS_ERROR_FAILURE;
result = iter->PositionAt(content);
if (NS_FAILED(result))
return result;
saveNode = parent;
}
// Now iterate to the left, towards the beginning of
// the text block, to find the first text node you
// come across.
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (IsTextNode(content))
{
node = do_QueryInterface(content);
if (!node)
return NS_ERROR_FAILURE;
break;
}
node = nsCOMPtr<nsIDOMNode>();
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
if (node)
{
// We found a node, now set the offset to the end
// of the text node.
nsString str;
result = node->GetNodeValue(str);
if (NS_FAILED(result))
return result;
offset = str.Length();
}
else
{
// We should never really get here, but I'm paranoid.
// We didn't find a text node above, so iterate to
// the right, towards the end of the text block, looking
// for a text node.
content = do_QueryInterface(saveNode);
result = iter->PositionAt(content);
if (NS_FAILED(result))
return result;
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (IsTextNode(content))
{
node = do_QueryInterface(content);
if (!node)
return NS_ERROR_FAILURE;
break;
}
node = nsCOMPtr<nsIDOMNode>();
result = iter->Next();
if (NS_FAILED(result))
return result;
}
if (!node)
return NS_ERROR_FAILURE;
// We found a text node, so set the offset to
// the begining of the node.
offset = 0;
}
for (i = 0; i < tableCount; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry)
return NS_ERROR_FAILURE;
if (entry->mNode == node.get() &&
entry->mNodeOffset <= offset && offset <= (entry->mNodeOffset + entry->mLength))
{
*aSelStatus = nsITextServicesDocument::eBlockContains;
*aSelOffset = entry->mStrOffset + (offset - entry->mNodeOffset);
*aSelLength = 0;
// Now move the caret so that it is actually in the text node.
// We do this to keep things in sync.
//
// In most cases, the user shouldn't see any movement in the caret
// on screen.
result = SetSelectionInternal(*aSelOffset, *aSelLength, PR_TRUE);
return result;
}
}
return NS_ERROR_FAILURE;
}
nsresult
nsTextServicesDocument::GetUncollapsedSelection(nsITextServicesDocument::TSDBlockSelectionStatus *aSelStatus, PRInt32 *aSelOffset, PRInt32 *aSelLength)
{
nsresult result;
nsCOMPtr<nsIDOMSelection> selection;
nsCOMPtr<nsIDOMRange> range;
OffsetEntry *entry;
result = mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
if (NS_FAILED(result))
return result;
if (!selection)
return NS_ERROR_FAILURE;
// It is assumed that the calling function has made sure that the
// selection is not collapsed, and that the input params to this
// method are initialized to some defaults.
nsCOMPtr<nsIDOMNode> startParent, endParent;
PRInt32 startOffset, endOffset;
PRInt32 rangeCount, tableCount, i;
PRInt32 e1s1, e1s2, e2s1, e2s2;
OffsetEntry *eStart, *eEnd;
PRInt32 eStartOffset, eEndOffset;
tableCount = mOffsetTable.Count();
// Get pointers to the first and last offset entries
// in the table.
eStart = (OffsetEntry *)mOffsetTable[0];
if (tableCount > 1)
eEnd = (OffsetEntry *)mOffsetTable[tableCount - 1];
else
eEnd = eStart;
eStartOffset = eStart->mNodeOffset;
eEndOffset = eEnd->mNodeOffset + eEnd->mLength;
result = selection->GetRangeCount(&rangeCount);
if (NS_FAILED(result))
return result;
// Find the first range in the selection that intersects
// the current text block.
for (i = 0; i < rangeCount; i++)
{
result = selection->GetRangeAt(i, getter_AddRefs(range));
if (NS_FAILED(result))
return result;
result = GetRangeEndPoints(range,
getter_AddRefs(startParent), &startOffset,
getter_AddRefs(endParent), &endOffset);
if (NS_FAILED(result))
return result;
result = ComparePoints(eStart->mNode, eStartOffset, endParent, endOffset, &e1s2);
if (NS_FAILED(result))
return result;
result = ComparePoints(eEnd->mNode, eEndOffset, startParent, startOffset, &e2s1);
if (NS_FAILED(result))
return result;
// Break out of the loop if the text block intersects the current range.
if (e1s2 <= 0 && e2s1 >= 0)
break;
}
// We're done if we didn't find an intersecting range.
if (rangeCount < 1 || e1s2 > 0 || e2s1 < 0)
{
*aSelStatus = nsITextServicesDocument::eBlockOutside;
*aSelOffset = *aSelLength = -1;
return NS_OK;
}
// Now that we have an intersecting range, find out more info:
result = ComparePoints(eStart->mNode, eStartOffset, startParent, startOffset, &e1s1);
if (NS_FAILED(result))
return result;
result = ComparePoints(eEnd->mNode, eEndOffset, endParent, endOffset, &e2s2);
if (NS_FAILED(result))
return result;
if (rangeCount > 1)
{
// There are multiple selection ranges, we only deal
// with the first one that intersects the current,
// text block, so mark this a as a partial.
*aSelStatus = nsITextServicesDocument::eBlockPartial;
}
else if (e1s1 > 0 && e2s2 < 0)
{
// The range extends beyond the start and
// end of the current text block.
*aSelStatus = nsITextServicesDocument::eBlockInside;
}
else if (e1s1 <= 0 && e2s2 >= 0)
{
// The current text block contains the entire
// range.
*aSelStatus = nsITextServicesDocument::eBlockContains;
}
else
{
// The range partially intersects the block.
*aSelStatus = nsITextServicesDocument::eBlockPartial;
}
// Now create a range based on the intersection of the
// text block and range:
nsCOMPtr<nsIDOMNode> p1, p2;
PRInt32 o1, o2;
// The start of the range will be the rightmost
// start node.
if (e1s1 >= 0)
{
p1 = do_QueryInterface(eStart->mNode);
o1 = eStartOffset;
}
else
{
p1 = startParent;
o1 = startOffset;
}
// The end of the range will be the leftmost
// end node.
if (e2s2 <= 0)
{
p2 = do_QueryInterface(eEnd->mNode);
o2 = eEndOffset;
}
else
{
p2 = endParent;
o2 = endOffset;
}
result = CreateRange(p1, o1, p2, o2, getter_AddRefs(range));
if (NS_FAILED(result))
return result;
// Now iterate over this range to figure out the selection's
// block offset and length.
nsCOMPtr<nsIContentIterator> iter;
result = CreateContentIterator(range, getter_AddRefs(iter));
if (NS_FAILED(result))
return result;
// Find the first text node in the range.
PRBool found;
nsCOMPtr<nsIContent> content;
result = iter->First();
if (NS_FAILED(result))
return result;
if (! IsTextNode(p1))
{
found = PR_FALSE;
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (!content)
return NS_ERROR_FAILURE;
if (IsTextNode(content))
{
p1 = do_QueryInterface(content);
if (!p1)
return NS_ERROR_FAILURE;
o1 = 0;
found = PR_TRUE;
break;
}
result = iter->Next();
if (NS_FAILED(result))
return result;
}
if (!found)
return NS_ERROR_FAILURE;
}
// Find the last text node in the range.
result = iter->Last();
if (NS_FAILED(result))
return result;
if (! IsTextNode(p2))
{
found = PR_FALSE;
while (iter->IsDone() == NS_ENUMERATOR_FALSE)
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (!content)
return NS_ERROR_FAILURE;
if (IsTextNode(content))
{
p2 = do_QueryInterface(content);
if (!p2)
return NS_ERROR_FAILURE;
nsString str;
result = p2->GetNodeValue(str);
if (NS_FAILED(result))
return result;
o2 = str.Length();
found = PR_TRUE;
break;
}
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
if (!found)
return NS_ERROR_FAILURE;
}
found = PR_FALSE;
*aSelLength = 0;
for (i = 0; i < tableCount; i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry)
return NS_ERROR_FAILURE;
if (!found)
{
if (entry->mNode == p1.get() &&
entry->mNodeOffset <= o1 && o1 <= (entry->mNodeOffset + entry->mLength))
{
*aSelOffset = entry->mStrOffset + (o1 - entry->mNodeOffset);
if (p1 == p2 &&
entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength))
{
// The start and end of the range are in the same offset
// entry. Calculate the length of the range then we're done.
*aSelLength = o2 - o1;
break;
}
else
{
// Add the length of the sub string in this offset entry
// that follows the start of the range.
*aSelLength = entry->mLength - (o1 - entry->mNodeOffset);
}
found = PR_TRUE;
}
}
else // found
{
if (entry->mNode == p2.get() &&
entry->mNodeOffset <= o2 && o2 <= (entry->mNodeOffset + entry->mLength))
{
// We found the end of the range. Calculate the length of the
// sub string that is before the end of the range, then we're done.
*aSelLength += o2 - entry->mNodeOffset;
break;
}
else
{
// The entire entry must be in the range.
*aSelLength += entry->mLength;
}
}
}
return result;
}
PRBool
nsTextServicesDocument::SelectionIsCollapsed()
{
return(mSelStartIndex == mSelEndIndex && mSelStartOffset == mSelEndOffset);
}
PRBool
nsTextServicesDocument::SelectionIsValid()
{
return(mSelStartIndex >= 0);
}
nsresult
nsTextServicesDocument::ComparePoints(nsIDOMNode* aParent1, PRInt32 aOffset1,
nsIDOMNode* aParent2, PRInt32 aOffset2,
PRInt32 *aResult)
{
nsresult result;
// Compare two node/offset pairs.
//
// Return -1 if node1 < node2
// Return 0 if node1 == node2
// Return 1 if node1 > node2
*aResult = 0;
if (aParent1 == aParent2 && aOffset1 == aOffset2)
return NS_OK;
nsCOMPtr<nsIDOMRange> range;
result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull,
NS_GET_IID(nsIDOMRange),
getter_AddRefs(range));
if (NS_FAILED(result))
return result;
if (!range)
return NS_ERROR_FAILURE;
result = range->SetStart(aParent1, aOffset1);
if (NS_FAILED(result))
return result;
result = range->SetEnd(aParent2, aOffset2);
if (NS_SUCCEEDED(result))
*aResult = -1;
else
*aResult = 1;
return NS_OK;
}
nsresult
nsTextServicesDocument::GetRangeEndPoints(nsIDOMRange *aRange,
nsIDOMNode **aStartParent, PRInt32 *aStartOffset,
nsIDOMNode **aEndParent, PRInt32 *aEndOffset)
{
nsresult result;
if (!aRange || !aStartParent || !aStartOffset || !aEndParent || !aEndOffset)
return NS_ERROR_NULL_POINTER;
result = aRange->GetStartContainer(aStartParent);
if (NS_FAILED(result))
return result;
if (!aStartParent)
return NS_ERROR_FAILURE;
result = aRange->GetStartOffset(aStartOffset);
if (NS_FAILED(result))
return result;
result = aRange->GetEndContainer(aEndParent);
if (NS_FAILED(result))
return result;
if (!aEndParent)
return NS_ERROR_FAILURE;
result = aRange->GetEndOffset(aEndOffset);
return result;
}
nsresult
nsTextServicesDocument::CreateRange(nsIDOMNode *aStartParent, PRInt32 aStartOffset,
nsIDOMNode *aEndParent, PRInt32 aEndOffset,
nsIDOMRange **aRange)
{
nsresult result;
result = nsComponentManager::CreateInstance(kCDOMRangeCID, nsnull,
NS_GET_IID(nsIDOMRange),
(void **)aRange);
if (NS_FAILED(result))
return result;
if (!*aRange)
return NS_ERROR_NULL_POINTER;
result = (*aRange)->SetStart(aStartParent, aStartOffset);
if (NS_SUCCEEDED(result))
result = (*aRange)->SetEnd(aEndParent, aEndOffset);
if (NS_FAILED(result))
{
NS_RELEASE((*aRange));
*aRange = 0;
}
return result;
}
nsresult
nsTextServicesDocument::FirstTextNodeInCurrentBlock(nsIContentIterator *iter)
{
nsresult result;
if (!iter)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIContent> last;
// Walk backwards over adjacent text nodes until
// we hit a block boundary:
while (NS_ENUMERATOR_FALSE == iter->IsDone())
{
result = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (IsTextNode(content))
{
if (!last || HasSameBlockNodeParent(content, last))
last = content;
else
{
// We're done, the current text node is in a
// different block.
break;
}
}
else if (last && IsBlockNode(content))
break;
result = iter->Prev();
if (NS_FAILED(result))
return result;
}
if (last)
result = iter->PositionAt(last);
// XXX: What should we return if last is null?
return NS_OK;
}
nsresult
nsTextServicesDocument::FirstTextNodeInPrevBlock(nsIContentIterator *aIterator)
{
nsCOMPtr<nsIContent> content;
nsresult result;
if (!aIterator)
return NS_ERROR_NULL_POINTER;
// XXX: What if mIterator is not currently on a text node?
// Make sure mIterator is pointing to the first text node in the
// current block:
result = FirstTextNodeInCurrentBlock(aIterator);
if (NS_FAILED(result))
return NS_ERROR_FAILURE;
// Point mIterator to the first node before the first text node:
result = aIterator->Prev();
if (NS_FAILED(result))
return result;
// Now find the first text node of the next block:
return FirstTextNodeInCurrentBlock(aIterator);
}
nsresult
nsTextServicesDocument::FirstTextNodeInNextBlock(nsIContentIterator *aIterator)
{
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIContent> prev;
PRBool crossedBlockBoundary = PR_FALSE;
nsresult result;
if (!aIterator)
return NS_ERROR_NULL_POINTER;
while (NS_ENUMERATOR_FALSE == aIterator->IsDone())
{
result = aIterator->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (!content)
return NS_ERROR_FAILURE;
if (IsTextNode(content))
{
if (!crossedBlockBoundary && (!prev || HasSameBlockNodeParent(prev, content)))
prev = content;
else
break;
}
else if (IsBlockNode(content))
crossedBlockBoundary = PR_TRUE;
result = aIterator->Next();
if (NS_FAILED(result))
return result;
}
return NS_OK;
}
nsresult
nsTextServicesDocument::GetFirstTextNodeInPrevBlock(nsIContent **aContent)
{
nsCOMPtr<nsIContent> content;
nsresult result;
if (!aContent)
return NS_ERROR_NULL_POINTER;
*aContent = 0;
// Save the iterator's current content node so we can restore
// it when we are done:
mIterator->CurrentNode(getter_AddRefs(content));
result = FirstTextNodeInPrevBlock(mIterator);
if (NS_FAILED(result))
{
// Try to restore the iterator before returning.
mIterator->PositionAt(content);
return result;
}
if (mIterator->IsDone() == NS_ENUMERATOR_FALSE)
{
result = mIterator->CurrentNode(aContent);
if (NS_FAILED(result))
{
// Try to restore the iterator before returning.
mIterator->PositionAt(content);
return result;
}
}
// Restore the iterator:
result = mIterator->PositionAt(content);
return result;
}
nsresult
nsTextServicesDocument::GetFirstTextNodeInNextBlock(nsIContent **aContent)
{
nsCOMPtr<nsIContent> content;
nsresult result;
if (!aContent)
return NS_ERROR_NULL_POINTER;
*aContent = 0;
// Save the iterator's current content node so we can restore
// it when we are done:
mIterator->CurrentNode(getter_AddRefs(content));
result = FirstTextNodeInNextBlock(mIterator);
if (NS_FAILED(result))
{
// Try to restore the iterator before returning.
mIterator->PositionAt(content);
return result;
}
if (mIterator->IsDone() == NS_ENUMERATOR_FALSE)
{
result = mIterator->CurrentNode(aContent);
if (NS_FAILED(result))
{
// Try to restore the iterator before returning.
mIterator->PositionAt(content);
return result;
}
}
// Restore the iterator:
result = mIterator->PositionAt(content);
return result;
}
nsresult
nsTextServicesDocument::CreateOffsetTable(nsString *aStr)
{
nsresult result = NS_OK;
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsIContent> first;
nsCOMPtr<nsIContent> prev;
if (!mIterator)
return NS_ERROR_NULL_POINTER;
ClearOffsetTable();
if (aStr)
aStr->SetLength(0);
if (mIteratorStatus == nsTextServicesDocument::eIsDone)
return NS_OK;
// The text service could have added text nodes to the beginning
// of the current block and called this method again. Make sure
// we really are at the beginning of the current block:
result = FirstTextNodeInCurrentBlock(mIterator);
if (NS_FAILED(result))
return result;
PRInt32 offset = 0;
while (NS_ENUMERATOR_FALSE == mIterator->IsDone())
{
result = mIterator->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(result))
return result;
if (!content)
return NS_ERROR_FAILURE;
if (IsTextNode(content))
{
if (!prev || HasSameBlockNodeParent(prev, content))
{
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(content);
if (node)
{
nsString str;
result = node->GetNodeValue(str);
if (NS_FAILED(result))
return result;
// Add an entry for this text node into the offset table:
OffsetEntry *entry = new OffsetEntry(node, offset, str.Length());
if (!entry)
return NS_ERROR_OUT_OF_MEMORY;
mOffsetTable.AppendElement((void *)entry);
offset += str.Length();
if (aStr)
{
// Append the text node's string to the output string:
if (!first)
*aStr = str;
else
*aStr += str;
}
}
prev = content;
if (!first)
first = content;
}
else
break;
}
else if (IsBlockNode(content))
break;
result = mIterator->Next();
if (NS_FAILED(result))
return result;
}
if (first)
{
// Always leave the iterator pointing at the first
// text node of the current block!
mIterator->PositionAt(first);
}
else
{
// If we never ran across a text node, the iterator
// might have been pointing to something invalid to
// begin with.
mIteratorStatus = nsTextServicesDocument::eIsDone;
}
return result;
}
nsresult
nsTextServicesDocument::RemoveInvalidOffsetEntries()
{
OffsetEntry *entry;
PRInt32 i = 0;
while (i < mOffsetTable.Count())
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry->mIsValid)
{
if (!mOffsetTable.RemoveElementAt(i))
return NS_ERROR_FAILURE;
if (mSelStartIndex >= 0 && mSelStartIndex >= i)
{
// We are deleting an entry that comes before
// mSelStartIndex, decrement mSelStartIndex so
// that it points to the correct entry!
NS_ASSERTION(i != mSelStartIndex, "Invalid selection index.");
--mSelStartIndex;
--mSelEndIndex;
}
}
else
i++;
}
return NS_OK;
}
nsresult
nsTextServicesDocument::ClearOffsetTable()
{
PRInt32 i;
for (i = 0; i < mOffsetTable.Count(); i++)
{
OffsetEntry *entry = (OffsetEntry *)mOffsetTable[i];
if (entry)
delete entry;
}
mOffsetTable.Clear();
return NS_OK;
}
nsresult
nsTextServicesDocument::SplitOffsetEntry(PRInt32 aTableIndex, PRInt32 aNewEntryLength)
{
OffsetEntry *entry = (OffsetEntry *)mOffsetTable[aTableIndex];
NS_ASSERTION((aNewEntryLength > 0), "aNewEntryLength <= 0");
NS_ASSERTION((aNewEntryLength < entry->mLength), "aNewEntryLength >= mLength");
if (aNewEntryLength < 1 || aNewEntryLength >= entry->mLength)
return NS_ERROR_FAILURE;
PRInt32 oldLength = entry->mLength - aNewEntryLength;
OffsetEntry *newEntry = new OffsetEntry(entry->mNode,
entry->mStrOffset + oldLength,
aNewEntryLength);
if (!newEntry)
return NS_ERROR_OUT_OF_MEMORY;
if (!mOffsetTable.InsertElementAt(newEntry, aTableIndex + 1))
{
delete newEntry;
return NS_ERROR_FAILURE;
}
// Adjust entry fields:
entry->mLength = oldLength;
newEntry->mNodeOffset = entry->mNodeOffset + oldLength;
return NS_OK;
}
nsresult
nsTextServicesDocument::NodeHasOffsetEntry(nsIDOMNode *aNode, PRBool *aHasEntry, PRInt32 *aEntryIndex)
{
OffsetEntry *entry;
PRInt32 i;
if (!aNode || !aHasEntry || !aEntryIndex)
return NS_ERROR_NULL_POINTER;
for (i = 0; i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
if (!entry)
return NS_ERROR_FAILURE;
if (entry->mNode == aNode)
{
*aHasEntry = PR_TRUE;
*aEntryIndex = i;
return NS_OK;
}
}
*aHasEntry = PR_FALSE;
*aEntryIndex = -1;
return NS_OK;
}
void
nsTextServicesDocument::PrintOffsetTable()
{
OffsetEntry *entry;
PRInt32 i;
for (i = 0; i < mOffsetTable.Count(); i++)
{
entry = (OffsetEntry *)mOffsetTable[i];
printf("ENTRY %4d: 0x%.8x %c %c %4d %4d %4d\n",
i, (PRInt32)entry->mNode, entry->mIsValid ? 'V' : 'N',
entry->mIsInsertedText ? 'I' : 'B',
entry->mNodeOffset, entry->mStrOffset, entry->mLength);
}
fflush(stdout);
}
void
nsTextServicesDocument::PrintContentNode(nsIContent *aContent)
{
nsString tmpStr, str;
char tmpBuf[256];
nsIAtom *atom = 0;
nsresult result;
aContent->GetTag(atom);
atom->ToString(tmpStr);
tmpStr.ToCString(tmpBuf, 256);
printf("%s", tmpBuf);
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aContent);
if (node)
{
PRUint16 type;
result = node->GetNodeType(&type);
if (NS_FAILED(result))
return;
if (nsIDOMNode::TEXT_NODE == type)
{
result = node->GetNodeValue(str);
if (NS_FAILED(result))
return;
str.ToCString(tmpBuf, 256);
printf(": \"%s\"", tmpBuf);
}
}
printf("\n");
fflush(stdout);
}