зеркало из https://github.com/mozilla/pjs.git
1820 строки
51 KiB
C++
1820 строки
51 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):
|
|
*/
|
|
|
|
//
|
|
// Define _IMPL_NS_LAYOUT to avoid link errors having to do with
|
|
// nsTextFragment methods, on Win32 platforms.
|
|
//
|
|
#define _IMPL_NS_LAYOUT
|
|
#include "nsTextFragment.h"
|
|
#undef _IMPL_NS_LAYOUT
|
|
|
|
#include "nsWSRunObject.h"
|
|
#include "nsISupportsArray.h"
|
|
#include "nsIDOMNode.h"
|
|
//#include "nsIModule.h"
|
|
#include "nsHTMLEditor.h"
|
|
#include "nsHTMLEditUtils.h"
|
|
#include "nsTextEditUtils.h"
|
|
#include "nsIContent.h"
|
|
#include "nsLayoutCID.h"
|
|
#include "nsIDOMCharacterData.h"
|
|
#include "nsEditorUtils.h"
|
|
|
|
const PRUnichar nbsp = 160;
|
|
|
|
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
|
|
|
|
static PRBool IsBlockNode(nsIDOMNode* node)
|
|
{
|
|
PRBool isBlock (PR_FALSE);
|
|
nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
|
|
return isBlock;
|
|
}
|
|
|
|
static PRBool IsInlineNode(nsIDOMNode* node)
|
|
{
|
|
return !IsBlockNode(node);
|
|
}
|
|
|
|
//- constructor / destructor -----------------------------------------------
|
|
nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd) :
|
|
mNode()
|
|
,mOffset(0)
|
|
,mStartNode()
|
|
,mStartOffset(0)
|
|
,mStartReason()
|
|
,mEndNode()
|
|
,mEndOffset(0)
|
|
,mEndReason()
|
|
,mFirstNBSPNode()
|
|
,mFirstNBSPOffset(0)
|
|
,mLastNBSPNode()
|
|
,mLastNBSPOffset(0)
|
|
,mNodeArray()
|
|
,mRootRun(nsnull)
|
|
,mStartRun(nsnull)
|
|
,mEndRun(nsnull)
|
|
,mHTMLEditor(aEd)
|
|
{
|
|
mNodeArray = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
|
|
}
|
|
|
|
nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset) :
|
|
mNode(aNode)
|
|
,mOffset(aOffset)
|
|
,mStartNode()
|
|
,mStartOffset(0)
|
|
,mEndNode()
|
|
,mEndOffset(0)
|
|
,mEndReason()
|
|
,mFirstNBSPNode()
|
|
,mFirstNBSPOffset(0)
|
|
,mLastNBSPNode()
|
|
,mLastNBSPOffset(0)
|
|
,mNodeArray()
|
|
,mRootRun(nsnull)
|
|
,mStartRun(nsnull)
|
|
,mEndRun(nsnull)
|
|
,mHTMLEditor(aEd)
|
|
{
|
|
mNodeArray = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
|
|
GetWSNodes();
|
|
GetRuns();
|
|
}
|
|
|
|
nsWSRunObject::~nsWSRunObject()
|
|
{
|
|
ClearRuns();
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// public static methods
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd,
|
|
nsIDOMNode *aLeftParent,
|
|
nsIDOMNode *aRightParent)
|
|
{
|
|
if (!aLeftParent || !aRightParent || !aHTMLEd)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
|
|
PRUint32 count;
|
|
aHTMLEd->GetLengthOfDOMNode(aLeftParent, count);
|
|
nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count);
|
|
nsWSRunObject rightWSObj(aHTMLEd, aRightParent, 0);
|
|
|
|
return leftWSObj.PrepareToDeleteRange(&rightWSObj);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// public instance methods
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent,
|
|
PRInt32 *aInOutOffset,
|
|
nsCOMPtr<nsIDOMNode> *outBRNode,
|
|
nsIEditor::EDirection aSelect)
|
|
{
|
|
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
|
// meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
|
|
if (!aInOutParent || !aInOutOffset || !outBRNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
WSFragment *beforeRun, *afterRun;
|
|
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
|
|
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
|
|
|
|
// handle any changes needed to ws run before inserted br
|
|
if (!beforeRun)
|
|
{
|
|
// dont need to do anything. just insert break. ws wont change.
|
|
}
|
|
else if (beforeRun->mType & eLeadingWS)
|
|
{
|
|
// dont need to do anything. just insert break. ws wont change.
|
|
}
|
|
else if (beforeRun->mType == eTrailingWS)
|
|
{
|
|
// need to delete the trailing ws that is before insertion point, because it
|
|
// would become significant after break inserted.
|
|
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (beforeRun->mType == eNormalWS)
|
|
{
|
|
// dont need to do anything. just insert break. ws wont change.
|
|
}
|
|
|
|
// handle any changes needed to ws run after inserted br
|
|
if (!afterRun)
|
|
{
|
|
// dont need to do anything. just insert break. ws wont change.
|
|
}
|
|
else if (afterRun->mType & eTrailingWS)
|
|
{
|
|
// dont need to do anything. just insert break. ws wont change.
|
|
}
|
|
else if (afterRun->mType == eLeadingWS)
|
|
{
|
|
// delete the leading ws that is after insertion point. We don't
|
|
// have to (it would still not be significant after br), but it's
|
|
// just more aesthetically pleasing to.
|
|
res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (afterRun->mType == eNormalWS)
|
|
{
|
|
// need to determine if break at front of non-nbsp run. if so
|
|
// convert run to nbsp.
|
|
WSPoint thePoint;
|
|
res = GetCharAfter(afterRun, *aInOutParent, *aInOutOffset, &thePoint);
|
|
if ( (NS_SUCCEEDED(res)) && (nsCRT::IsAsciiSpace(thePoint.mChar)) )
|
|
{
|
|
WSPoint prevPoint;
|
|
res = GetCharBefore(thePoint, &prevPoint);
|
|
if ( (NS_FAILED(res)) || (!nsCRT::IsAsciiSpace(prevPoint.mChar)) )
|
|
{
|
|
// we are at start of non-nbsps. convert to a single nbsp.
|
|
res = ConvertToNBSP(thePoint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ready, aim, fire!
|
|
return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect);
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::InsertText(const nsAReadableString& aStringToInsert,
|
|
nsCOMPtr<nsIDOMNode> *aInOutParent,
|
|
PRInt32 *aInOutOffset,
|
|
nsIDOMDocument *aDoc)
|
|
{
|
|
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
|
// meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
|
|
|
|
// MOOSE: for now, just getting the ws logic straight. This implementation
|
|
// is very slow. Will need to replace edit rules impl with a more efficient
|
|
// text sink here that does the minimal amount of searching/replacing/copying
|
|
|
|
if (!aInOutParent || !aInOutOffset || !aDoc)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
if (aStringToInsert.IsEmpty()) return res;
|
|
|
|
// string copying sux.
|
|
nsAutoString theString(aStringToInsert);
|
|
|
|
WSFragment *beforeRun, *afterRun;
|
|
res = FindRun(*aInOutParent, *aInOutOffset, &beforeRun, PR_FALSE);
|
|
res = FindRun(*aInOutParent, *aInOutOffset, &afterRun, PR_TRUE);
|
|
|
|
// handle any changes needed to ws run before inserted text
|
|
if (!beforeRun)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
else if (beforeRun->mType & eLeadingWS)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
else if (beforeRun->mType == eTrailingWS)
|
|
{
|
|
// need to delete the trailing ws that is before insertion point, because it
|
|
// would become significant after text inserted.
|
|
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (beforeRun->mType == eNormalWS)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
|
|
// handle any changes needed to ws run after inserted text
|
|
if (!afterRun)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
else if (afterRun->mType & eTrailingWS)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
else if (afterRun->mType == eLeadingWS)
|
|
{
|
|
// delete the leading ws that is after insertion point, because it
|
|
// would become significant after text inserted.
|
|
res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (afterRun->mType == eNormalWS)
|
|
{
|
|
// dont need to do anything. just insert text. ws wont change.
|
|
}
|
|
|
|
// next up, tweak head and tail of string as needed.
|
|
// first the head:
|
|
// there are a variety of circumstances that would require us to convert a
|
|
// leading ws char into an nbsp:
|
|
|
|
if (nsCRT::IsAsciiSpace(theString[0]))
|
|
{
|
|
// we have a leading space
|
|
if (beforeRun)
|
|
{
|
|
if (beforeRun->mType & eLeadingWS)
|
|
{
|
|
theString.SetCharAt(nbsp, 0);
|
|
}
|
|
else if (beforeRun->mType & eNormalWS)
|
|
{
|
|
WSPoint wspoint;
|
|
res = GetCharBefore(beforeRun, *aInOutParent, *aInOutOffset, &wspoint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nsCRT::IsAsciiSpace(wspoint.mChar))
|
|
{
|
|
theString.SetCharAt(nbsp, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((mStartReason & eBlock) || (mStartReason == eBreak))
|
|
{
|
|
theString.SetCharAt(nbsp, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// then the tail
|
|
PRUint32 lastCharIndex = theString.Length()-1;
|
|
|
|
if (nsCRT::IsAsciiSpace(theString[lastCharIndex]))
|
|
{
|
|
// we have a leading space
|
|
if (afterRun)
|
|
{
|
|
if (afterRun->mType & eTrailingWS)
|
|
{
|
|
theString.SetCharAt(nbsp, lastCharIndex);
|
|
}
|
|
else if (afterRun->mType & eNormalWS)
|
|
{
|
|
WSPoint wspoint;
|
|
res = GetCharAfter(afterRun, *aInOutParent, *aInOutOffset, &wspoint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nsCRT::IsAsciiSpace(wspoint.mChar))
|
|
{
|
|
theString.SetCharAt(nbsp, lastCharIndex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((mEndReason & eBlock))
|
|
{
|
|
theString.SetCharAt(nbsp, lastCharIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// next scan string for adjacent ws and convert to nbsp/space combos
|
|
// MOOSE: don't need to convert tabs here since that is done by WillInsertText()
|
|
// before we are called. Eventually, all that logic will be pushed down into
|
|
// here and made more efficient.
|
|
PRInt32 j;
|
|
PRBool prevWS = PR_FALSE;
|
|
for (j=0; j<=lastCharIndex; j++)
|
|
{
|
|
if (nsCRT::IsAsciiSpace(theString[j]))
|
|
{
|
|
if (prevWS)
|
|
{
|
|
theString.SetCharAt(nbsp, j-1); // j-1 cant be negative because prevWS starts out false
|
|
}
|
|
else
|
|
{
|
|
prevWS = PR_TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
prevWS = PR_FALSE;
|
|
}
|
|
}
|
|
|
|
// ready, aim, fire!
|
|
res = mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::DeleteWSBackward()
|
|
{
|
|
nsresult res = NS_OK;
|
|
WSFragment *run = nsnull;
|
|
WSPoint point, tmp;
|
|
res = FindRun(mNode, mOffset, &run, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!run) return NS_ERROR_NULL_POINTER;
|
|
res = GetCharBefore(run, mNode, mOffset, &point);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// callers job to insure that previous char is really ws.
|
|
if (!nsCRT::IsAsciiSpace(point.mChar)) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMNode> startNode;
|
|
PRInt32 startOffset;
|
|
|
|
do
|
|
{
|
|
startNode = do_QueryInterface(point.mTextNode);
|
|
startOffset = point.mOffset;
|
|
res = GetCharBefore(point, &tmp);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
point = tmp;
|
|
} while (nsCRT::IsAsciiSpace(point.mChar));
|
|
|
|
// finally, delete that ws
|
|
return DeleteChars(startNode, startOffset, mNode, mOffset);
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::DeleteWSForward()
|
|
{
|
|
nsresult res = NS_OK;
|
|
WSFragment *run = nsnull;
|
|
WSPoint point, tmp;
|
|
res = FindRun(mNode, mOffset, &run, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!run) return NS_ERROR_NULL_POINTER;
|
|
res = GetCharAfter(run, mNode, mOffset, &point);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
// callers job to insure that next char is really ws.
|
|
if (!nsCRT::IsAsciiSpace(point.mChar)) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 endOffset;
|
|
|
|
do
|
|
{
|
|
endNode = do_QueryInterface(point.mTextNode);
|
|
endOffset = point.mOffset+1;
|
|
res = GetCharAfter(point, &tmp);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
point = tmp;
|
|
} while (nsCRT::IsAsciiSpace(point.mChar));
|
|
|
|
// finally, delete that ws
|
|
return DeleteChars(mNode, mOffset, endNode, endOffset);
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::PrepareToDeleteRange(nsWSRunObject* aEndObject)
|
|
{
|
|
// this routine adjust whitespace before *this* and after aEndObject
|
|
// in preperation for the two areas to become adjacent after the
|
|
// intervening content is deleted. It's overly agressive right
|
|
// now. There might be a block boundary remaining between them after
|
|
// the deletion, in which case these adjstments are unneeded (though
|
|
// I don't think they can ever be harmful?)
|
|
|
|
if (!aEndObject)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsresult res = NS_OK;
|
|
|
|
// get the runs before and after selection
|
|
WSFragment *beforeRun, *afterRun;
|
|
res = FindRun(mNode, mOffset, &beforeRun, PR_FALSE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, PR_TRUE);
|
|
|
|
// trim before run of any trailing ws
|
|
if (beforeRun && (beforeRun->mType == eTrailingWS))
|
|
{
|
|
res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, mNode, mOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// trim after run of any leading ws
|
|
if (afterRun && (afterRun->mType == eLeadingWS))
|
|
{
|
|
res = aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset, afterRun->mEndNode, afterRun->mEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// adjust normal ws in afterRun if needed
|
|
if (afterRun && (afterRun->mType == eNormalWS))
|
|
{
|
|
if ( (beforeRun && (beforeRun->mType == eLeadingWS)) ||
|
|
(!beforeRun && ((mStartReason & eBlock) || (mStartReason == eBreak))) )
|
|
{
|
|
// make sure leading char of following ws is an nbsp, so that it will show up
|
|
WSPoint point;
|
|
aEndObject->GetCharAfter(afterRun, aEndObject->mNode, aEndObject->mOffset, &point);
|
|
if (nsCRT::IsAsciiSpace(point.mChar))
|
|
{
|
|
res = ConvertToNBSP(point);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::PrepareToSplitAcrossBlocks(nsCOMPtr<nsIDOMNode> *aSplitNode, PRInt32 *aSplitOffset)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode,
|
|
PRInt32 aOffset,
|
|
nsCOMPtr<nsIDOMNode> *outVisNode,
|
|
PRInt32 *outVisOffset,
|
|
PRInt16 *outType)
|
|
{
|
|
if (!aNode || !outVisNode || !outVisOffset || !outType)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
WSFragment *run;
|
|
nsresult res = FindRun(aNode, aOffset, &run, PR_FALSE);
|
|
|
|
// is there a visible run there or earlier?
|
|
while (run)
|
|
{
|
|
if (run->mType != eNormalWS)
|
|
{
|
|
run = run->mLeft;
|
|
}
|
|
else
|
|
{
|
|
WSPoint point;
|
|
res = GetCharBefore(run, aNode, aOffset, &point);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (point.mTextNode)
|
|
{
|
|
*outVisNode = do_QueryInterface(point.mTextNode);
|
|
*outVisOffset = point.mOffset;
|
|
if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
|
|
{
|
|
*outType = eNormalWS;
|
|
}
|
|
else if (!point.mChar)
|
|
{
|
|
// MOOSE: not possible?
|
|
*outType = eNone;
|
|
}
|
|
else
|
|
{
|
|
*outType = eText;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
// else if no text node then keep looking. We should eventually fall out of loop
|
|
}
|
|
}
|
|
|
|
// if we get here then nothing in ws data to find. return start reason
|
|
*outVisNode = mStartNode;
|
|
*outVisOffset = mStartOffset;
|
|
*outType = mStartReason;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode,
|
|
PRInt32 aOffset,
|
|
nsCOMPtr<nsIDOMNode> *outVisNode,
|
|
PRInt32 *outVisOffset,
|
|
PRInt16 *outType)
|
|
{
|
|
if (!aNode || !outVisNode || !outVisOffset || !outType)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
WSFragment *run;
|
|
nsresult res = FindRun(aNode, aOffset, &run, PR_TRUE);
|
|
|
|
// is there a visible run there or later?
|
|
while (run)
|
|
{
|
|
if (run->mType != eNormalWS)
|
|
{
|
|
run = run->mRight;
|
|
}
|
|
else
|
|
{
|
|
WSPoint point;
|
|
res = GetCharAfter(run, aNode, aOffset, &point);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (point.mTextNode)
|
|
{
|
|
*outVisNode = do_QueryInterface(point.mTextNode);
|
|
*outVisOffset = point.mOffset;
|
|
if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
|
|
{
|
|
*outType = eNormalWS;
|
|
}
|
|
else if (!point.mChar)
|
|
{
|
|
// MOOSE: not possible?
|
|
*outType = eNone;
|
|
}
|
|
else
|
|
{
|
|
*outType = eText;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
// else if no text node then keep looking. We should eventually fall out of loop
|
|
}
|
|
}
|
|
|
|
// if we get here then nothing in ws data to find. return end reason
|
|
*outVisNode = mEndNode;
|
|
*outVisOffset = mEndOffset;
|
|
*outType = mEndReason;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// protected methods
|
|
//--------------------------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
nsWSRunObject::GetWSNodes()
|
|
{
|
|
// collect up an array of nodes that are contiguous with the insertion point
|
|
// and which contain only whitespace. Stop if you reach non-ws text or a new
|
|
// block boundary.
|
|
nsresult res = NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMNode> blockParent, curStartNode, curEndNode;
|
|
PRInt32 curStartOffset, curEndOffset;
|
|
if (IsBlockNode(mNode)) blockParent = mNode;
|
|
else blockParent = mHTMLEditor->GetBlockNodeParent(mNode);
|
|
|
|
// first look backwards to find preceding ws nodes
|
|
if (mHTMLEditor->IsTextNode(mNode))
|
|
{
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNode));
|
|
const nsTextFragment *textFrag;
|
|
res = textNode->GetText(&textFrag);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
res = PrependNodeToList(mNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (mOffset)
|
|
{
|
|
PRInt32 pos;
|
|
for (pos=mOffset-1; pos>=0; pos--)
|
|
{
|
|
PRUnichar theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar))
|
|
{
|
|
if (theChar != nbsp)
|
|
{
|
|
mStartNode = mNode;
|
|
mStartOffset = pos+1;
|
|
mStartReason = eText;
|
|
break;
|
|
}
|
|
// as we look backwards update our earliest found nbsp
|
|
mFirstNBSPNode = mNode;
|
|
mFirstNBSPOffset = pos;
|
|
// also keep track of latest nbsp so far
|
|
if (!mLastNBSPNode)
|
|
{
|
|
mLastNBSPNode = mNode;
|
|
mLastNBSPOffset = pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!mStartNode)
|
|
{
|
|
// we didnt find beginning of whitespace. remember this text node
|
|
// (and offset 0) as the extent to which we have looked back.
|
|
curStartNode = mNode;
|
|
curStartOffset = 0;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> priorNode;
|
|
while (!mStartNode)
|
|
{
|
|
// we haven't found the start of ws yet. Keep looking
|
|
|
|
// do we have a curStartNode? If not, get one.
|
|
if (!curStartNode)
|
|
{
|
|
res = GetPreviousWSNode(mNode, mOffset, blockParent, address_of(curStartNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
priorNode = curStartNode;
|
|
}
|
|
else
|
|
{
|
|
res = GetPreviousWSNode(curStartNode, blockParent, address_of(priorNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
if (priorNode)
|
|
{
|
|
if (IsBlockNode(priorNode))
|
|
{
|
|
// we encountered a new block. therefore no more ws.
|
|
if (mHTMLEditor->IsTextNode(curStartNode))
|
|
{
|
|
mStartNode = curStartNode;
|
|
mStartOffset = curStartOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curStartNode, address_of(mStartNode), &mStartOffset);
|
|
mStartOffset++;
|
|
}
|
|
mStartReason = eOtherBlock;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(priorNode))
|
|
{
|
|
res = PrependNodeToList(priorNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(priorNode));
|
|
if (!textNode) return NS_ERROR_NULL_POINTER;
|
|
const nsTextFragment *textFrag;
|
|
res = textNode->GetText(&textFrag);
|
|
PRInt32 len;
|
|
res = textNode->GetTextLength(&len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 pos;
|
|
for (pos=len-1; pos>=0; pos--)
|
|
{
|
|
PRUnichar theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar))
|
|
{
|
|
if (theChar != nbsp)
|
|
{
|
|
mStartNode = priorNode;
|
|
mStartOffset = pos+1;
|
|
mStartReason = eText;
|
|
break;
|
|
}
|
|
// as we look backwards update our earliest found nbsp
|
|
mFirstNBSPNode = priorNode;
|
|
mFirstNBSPOffset = pos;
|
|
// also keep track of latest nbsp so far
|
|
if (!mLastNBSPNode)
|
|
{
|
|
mLastNBSPNode = priorNode;
|
|
mLastNBSPOffset = pos;
|
|
}
|
|
}
|
|
}
|
|
if (!mStartNode)
|
|
{
|
|
// we didnt find beginning of whitespace. remember this text node
|
|
// (and offset 0) as the extent to which we have looked back.
|
|
curStartNode = priorNode;
|
|
curStartOffset = 0;
|
|
}
|
|
}
|
|
else if (nsTextEditUtils::IsBreak(priorNode))
|
|
{
|
|
// we encountered a break. therefore no more ws.
|
|
if (mHTMLEditor->IsTextNode(curStartNode))
|
|
{
|
|
mStartNode = curStartNode;
|
|
mStartOffset = curStartOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curStartNode, address_of(mStartNode), &mStartOffset);
|
|
mStartOffset++;
|
|
}
|
|
mStartReason = eBreak;
|
|
}
|
|
else
|
|
{
|
|
// it's a special node, like <img>, that is not a block and not
|
|
// a break but still serves as a terminator to ws runs.
|
|
if (mHTMLEditor->IsTextNode(curStartNode))
|
|
{
|
|
mStartNode = curStartNode;
|
|
mStartOffset = curStartOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curStartNode, address_of(mStartNode), &mStartOffset);
|
|
mStartOffset++;
|
|
}
|
|
mStartReason = eSpecial;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no prior node means we exhausted blockParent
|
|
if (!curStartNode)
|
|
{
|
|
// we never found anything to work with, so start
|
|
// is at beginning of block parent
|
|
mStartNode = blockParent;
|
|
mStartOffset = 0;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(curStartNode))
|
|
{
|
|
mStartNode = curStartNode;
|
|
mStartOffset = curStartOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curStartNode, address_of(mStartNode), &mStartOffset);
|
|
mStartOffset++;
|
|
}
|
|
mStartReason = eThisBlock;
|
|
}
|
|
}
|
|
|
|
// then look ahead to find following ws nodes
|
|
if (mHTMLEditor->IsTextNode(mNode))
|
|
{
|
|
// dont need to put it on list. it already is from code above
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(mNode));
|
|
const nsTextFragment *textFrag;
|
|
res = textNode->GetText(&textFrag);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 len;
|
|
textNode->GetTextLength(&len);
|
|
if (mOffset<len)
|
|
{
|
|
PRInt32 pos;
|
|
for (pos=mOffset; pos<len; pos++)
|
|
{
|
|
PRUnichar theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar))
|
|
{
|
|
if (theChar != nbsp)
|
|
{
|
|
mEndNode = mNode;
|
|
mEndOffset = pos;
|
|
mEndReason = eText;
|
|
break;
|
|
}
|
|
// as we look forwards update our latest found nbsp
|
|
mLastNBSPNode = mNode;
|
|
mLastNBSPOffset = pos;
|
|
// also keep track of earliest nbsp so far
|
|
if (!mFirstNBSPNode)
|
|
{
|
|
mFirstNBSPNode = mNode;
|
|
mFirstNBSPOffset = pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!mEndNode)
|
|
{
|
|
// we didnt find end of whitespace. remember this text node
|
|
// (and offset len) as the extent to which we have looked ahead.
|
|
curEndNode = mNode;
|
|
curEndOffset = len;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> nextNode;
|
|
while (!mEndNode)
|
|
{
|
|
// we haven't found the end of ws yet. Keep looking
|
|
|
|
// do we have a curEndNode? If not, get one.
|
|
if (!curEndNode)
|
|
{
|
|
res = GetNextWSNode(mNode, mOffset, blockParent, address_of(curEndNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nextNode = curEndNode;
|
|
}
|
|
else
|
|
{
|
|
res = GetNextWSNode(curEndNode, blockParent, address_of(nextNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
if (nextNode)
|
|
{
|
|
if (IsBlockNode(nextNode))
|
|
{
|
|
// we encountered a new block. therefore no more ws.
|
|
if (mHTMLEditor->IsTextNode(curEndNode))
|
|
{
|
|
mEndNode = curEndNode;
|
|
mEndOffset = curEndOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curEndNode, address_of(mEndNode), &mEndOffset);
|
|
}
|
|
mEndReason = eOtherBlock;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(nextNode))
|
|
{
|
|
res = AppendNodeToList(nextNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(nextNode));
|
|
if (!textNode) return NS_ERROR_NULL_POINTER;
|
|
const nsTextFragment *textFrag;
|
|
res = textNode->GetText(&textFrag);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 len;
|
|
res = textNode->GetTextLength(&len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
PRInt32 pos;
|
|
for (pos=0; pos<len; pos++)
|
|
{
|
|
PRUnichar theChar = textFrag->CharAt(pos);
|
|
if (!nsCRT::IsAsciiSpace(theChar))
|
|
{
|
|
if (theChar != nbsp)
|
|
{
|
|
mEndNode = nextNode;
|
|
mEndOffset = pos;
|
|
mEndReason = eText;
|
|
break;
|
|
}
|
|
// as we look forwards update our latest found nbsp
|
|
mLastNBSPNode = nextNode;
|
|
mLastNBSPOffset = pos;
|
|
// also keep track of earliest nbsp so far
|
|
if (!mFirstNBSPNode)
|
|
{
|
|
mFirstNBSPNode = nextNode;
|
|
mFirstNBSPOffset = pos;
|
|
}
|
|
}
|
|
}
|
|
if (!mEndNode)
|
|
{
|
|
// we didnt find end of whitespace. remember this text node
|
|
// (and offset len) as the extent to which we have looked ahead.
|
|
curEndNode = nextNode;
|
|
curEndOffset = len;
|
|
}
|
|
}
|
|
else if (nsTextEditUtils::IsBreak(nextNode))
|
|
{
|
|
// we encountered a break. therefore no more ws.
|
|
if (mHTMLEditor->IsTextNode(curEndNode))
|
|
{
|
|
mEndNode = curEndNode;
|
|
mEndOffset = curEndOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curEndNode, address_of(mEndNode), &mEndOffset);
|
|
}
|
|
mEndReason = eBreak;
|
|
}
|
|
else
|
|
{
|
|
// it's a special node, like <img>, that is not a block and not
|
|
// a break but still serves as a terminator to ws runs.
|
|
if (mHTMLEditor->IsTextNode(curEndNode))
|
|
{
|
|
mEndNode = curEndNode;
|
|
mEndOffset = curEndOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curEndNode, address_of(mEndNode), &mEndOffset);
|
|
}
|
|
mEndReason = eSpecial;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no next node means we exhausted blockParent
|
|
if (!curEndNode)
|
|
{
|
|
// we never found anything to work with, so end
|
|
// is at end of block parent
|
|
mEndNode = blockParent;
|
|
PRUint32 count;
|
|
mHTMLEditor->GetLengthOfDOMNode(blockParent, count);
|
|
mEndOffset = count;
|
|
}
|
|
else if (mHTMLEditor->IsTextNode(curEndNode))
|
|
{
|
|
mEndNode = curEndNode;
|
|
mEndOffset = curEndOffset;
|
|
}
|
|
else
|
|
{
|
|
mHTMLEditor->GetNodeLocation(curEndNode, address_of(mEndNode), &mEndOffset);
|
|
}
|
|
mEndReason = eThisBlock;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetRuns()
|
|
{
|
|
ClearRuns();
|
|
|
|
// handle some easy cases first
|
|
|
|
// if we are surrounded by text or special, it's all one
|
|
// big normal ws run
|
|
if ( ((mStartReason == eText) || (mStartReason == eSpecial)) &&
|
|
((mEndReason == eText) || (mEndReason == eSpecial) || (mEndReason == eBreak)) )
|
|
{
|
|
return MakeSingleWSRun(eNormalWS);
|
|
}
|
|
|
|
// if we are before or after a block (or after a break), and there are no nbsp's,
|
|
// then it's all non-rendering ws.
|
|
if ( !(mFirstNBSPNode || mLastNBSPNode) &&
|
|
( (mStartReason & eBlock) || (mStartReason == eBreak) || (mEndReason & eBlock) ) )
|
|
{
|
|
PRInt16 wstype = eNone;
|
|
if ((mStartReason & eBlock) || (mStartReason == eBreak))
|
|
wstype = eLeadingWS;
|
|
if (mEndReason & eBlock)
|
|
wstype |= eTrailingWS;
|
|
return MakeSingleWSRun(wstype);
|
|
}
|
|
|
|
// otherwise a little trickier. shucks.
|
|
mStartRun = new WSFragment();
|
|
if (!mStartRun) return NS_ERROR_NULL_POINTER;
|
|
mStartRun->mStartNode = mStartNode;
|
|
mStartRun->mStartOffset = mStartOffset;
|
|
|
|
if ( (mStartReason & eBlock) || (mStartReason == eBreak) )
|
|
{
|
|
// set up mStartRun
|
|
mStartRun->mType = eLeadingWS;
|
|
mStartRun->mEndNode = mFirstNBSPNode;
|
|
mStartRun->mEndOffset = mFirstNBSPOffset;
|
|
mStartRun->mLeftType = mStartReason;
|
|
mStartRun->mRightType = eNormalWS;
|
|
|
|
// set up next run
|
|
WSFragment *normalRun = new WSFragment();
|
|
if (!normalRun) return NS_ERROR_NULL_POINTER;
|
|
mStartRun->mRight = normalRun;
|
|
normalRun->mType = eNormalWS;
|
|
normalRun->mStartNode = mFirstNBSPNode;
|
|
normalRun->mStartOffset = mFirstNBSPOffset;
|
|
normalRun->mLeftType = eLeadingWS;
|
|
normalRun->mLeft = mStartRun;
|
|
if (mEndReason != eBlock)
|
|
{
|
|
// then no trailing ws. this normal run ends the overall ws run.
|
|
normalRun->mRightType = mEndReason;
|
|
normalRun->mEndNode = mEndNode;
|
|
normalRun->mEndOffset = mEndOffset;
|
|
mEndRun = normalRun;
|
|
}
|
|
else
|
|
{
|
|
// we might have trailing ws.
|
|
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
|
|
{
|
|
// normal ws runs right up to adjacent block (nbsp next to block)
|
|
normalRun->mRightType = mEndReason;
|
|
normalRun->mEndNode = mEndNode;
|
|
normalRun->mEndOffset = mEndOffset;
|
|
mEndRun = normalRun;
|
|
}
|
|
else
|
|
{
|
|
normalRun->mEndNode = mLastNBSPNode;
|
|
normalRun->mEndOffset = mLastNBSPOffset+1;
|
|
|
|
// set up next run
|
|
WSFragment *lastRun = new WSFragment();
|
|
if (!lastRun) return NS_ERROR_NULL_POINTER;
|
|
lastRun->mType = eTrailingWS;
|
|
lastRun->mStartNode = mLastNBSPNode;
|
|
lastRun->mStartOffset = mLastNBSPOffset+1;
|
|
lastRun->mEndNode = mEndNode;
|
|
lastRun->mEndOffset = mEndOffset;
|
|
lastRun->mLeftType = eNormalWS;
|
|
lastRun->mLeft = normalRun;
|
|
lastRun->mRightType = mEndReason;
|
|
mEndRun = lastRun;
|
|
normalRun->mRight = lastRun;
|
|
}
|
|
}
|
|
}
|
|
else // mStartReason is not eBlock or eBreak
|
|
{
|
|
// set up mStartRun
|
|
mStartRun->mType = eNormalWS;
|
|
mStartRun->mEndNode = mLastNBSPNode;
|
|
mStartRun->mEndOffset = mLastNBSPOffset+1;
|
|
mStartRun->mLeftType = mStartReason;
|
|
|
|
// we might have trailing ws.
|
|
if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
|
|
{
|
|
// set up next run
|
|
WSFragment *lastRun = new WSFragment();
|
|
if (!lastRun) return NS_ERROR_NULL_POINTER;
|
|
lastRun->mType = eTrailingWS;
|
|
lastRun->mStartNode = mLastNBSPNode;
|
|
lastRun->mStartOffset = mLastNBSPOffset+1;
|
|
lastRun->mLeftType = eNormalWS;
|
|
lastRun->mLeft = mStartRun;
|
|
lastRun->mRightType = mEndReason;
|
|
mEndRun = lastRun;
|
|
mStartRun->mRight = lastRun;
|
|
}
|
|
else
|
|
{
|
|
mStartRun->mRightType = mEndReason;
|
|
mStartRun->mEndNode = mEndNode;
|
|
mStartRun->mEndOffset = mEndOffset;
|
|
mEndRun = mStartRun;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsWSRunObject::ClearRuns()
|
|
{
|
|
WSFragment *tmp, *run;
|
|
run = mStartRun;
|
|
while (run)
|
|
{
|
|
tmp = run->mRight;
|
|
delete run;
|
|
run = tmp;
|
|
}
|
|
mStartRun = 0;
|
|
mRootRun = 0;
|
|
mEndRun = 0;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::MakeSingleWSRun(PRInt16 aType)
|
|
{
|
|
mStartRun = new WSFragment();
|
|
if (!mStartRun) return NS_ERROR_NULL_POINTER;
|
|
|
|
mStartRun->mStartNode = mStartNode;
|
|
mStartRun->mStartOffset = mStartOffset;
|
|
mStartRun->mType = aType;
|
|
mStartRun->mEndNode = mEndNode;
|
|
mStartRun->mEndOffset = mEndOffset;
|
|
mStartRun->mLeftType = mStartReason;
|
|
mStartRun->mRightType = mEndReason;
|
|
|
|
mRootRun = mStartRun;
|
|
mEndRun = mStartRun;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::PrependNodeToList(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode || !mNodeArray) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISupports> isupports (do_QueryInterface(aNode));
|
|
nsresult res = mNodeArray->InsertElementAt(isupports,0);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::AppendNodeToList(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISupports> isupports (do_QueryInterface(aNode));
|
|
nsresult res = mNodeArray->AppendElement(isupports);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode,
|
|
nsIDOMNode *aBlockParent,
|
|
nsCOMPtr<nsIDOMNode> *aPriorNode)
|
|
{
|
|
// can't really recycle various getnext/prior routines because we have special needs
|
|
// here. Need to step into inline containers but not block containers.
|
|
if (!aStartNode || !aBlockParent || !aPriorNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = aStartNode->GetPreviousSibling(getter_AddRefs(*aPriorNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
|
|
while (!*aPriorNode)
|
|
{
|
|
// we have exhausted nodes in parent of aStartNode.
|
|
res = curNode->GetParentNode(getter_AddRefs(temp));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!temp) return NS_ERROR_NULL_POINTER;
|
|
if (temp == aBlockParent)
|
|
{
|
|
// we have exhausted nodes in the block parent. The convention here is to return null.
|
|
*aPriorNode = nsnull;
|
|
return NS_OK;
|
|
}
|
|
// we have a parent: look for previous sibling
|
|
res = temp->GetPreviousSibling(getter_AddRefs(*aPriorNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curNode = temp;
|
|
}
|
|
// we have a prior node. If it's a block, return it.
|
|
if (IsBlockNode(*aPriorNode))
|
|
return NS_OK;
|
|
// else if it's a container, get deep rightmost child
|
|
else if (mHTMLEditor->IsContainer(*aPriorNode))
|
|
{
|
|
res = mHTMLEditor->GetRightmostChild(*aPriorNode, getter_AddRefs(temp));
|
|
if (temp)
|
|
*aPriorNode = temp;
|
|
return NS_OK;
|
|
}
|
|
// else return the node itself
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode,
|
|
nsIDOMNode *aBlockParent,
|
|
nsCOMPtr<nsIDOMNode> *aNextNode)
|
|
{
|
|
// can't really recycle various getnext/prior routines because we have special needs
|
|
// here. Need to step into inline containers but not block containers.
|
|
if (!aStartNode || !aBlockParent || !aNextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
*aNextNode = 0;
|
|
nsresult res = aStartNode->GetNextSibling(getter_AddRefs(*aNextNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
|
|
while (!*aNextNode)
|
|
{
|
|
// we have exhausted nodes in parent of aStartNode.
|
|
res = curNode->GetParentNode(getter_AddRefs(temp));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!temp) return NS_ERROR_NULL_POINTER;
|
|
if (temp == aBlockParent)
|
|
{
|
|
// we have exhausted nodes in the block parent. The convention here is to return null.
|
|
*aNextNode = nsnull;
|
|
return NS_OK;
|
|
}
|
|
// we have a parent: look for next sibling
|
|
res = temp->GetNextSibling(getter_AddRefs(*aNextNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
curNode = temp;
|
|
}
|
|
// we have a next node. If it's a block, return it.
|
|
if (IsBlockNode(*aNextNode))
|
|
return NS_OK;
|
|
// else if it's a container, get deep leftmost child
|
|
else if (mHTMLEditor->IsContainer(*aNextNode))
|
|
{
|
|
res = mHTMLEditor->GetLeftmostChild(*aNextNode, getter_AddRefs(temp));
|
|
if (temp)
|
|
*aNextNode = temp;
|
|
return NS_OK;
|
|
}
|
|
// else return the node itself
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode,
|
|
PRInt16 aOffset,
|
|
nsIDOMNode *aBlockParent,
|
|
nsCOMPtr<nsIDOMNode> *aPriorNode)
|
|
{
|
|
// can't really recycle various getnext/prior routines because we have special needs
|
|
// here. Need to step into inline containers but not block containers.
|
|
if (!aStartNode || !aBlockParent || !aPriorNode) return NS_ERROR_NULL_POINTER;
|
|
*aPriorNode = 0;
|
|
|
|
if (mHTMLEditor->IsTextNode(aStartNode))
|
|
return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
|
|
if (!mHTMLEditor->IsContainer(aStartNode))
|
|
return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
|
|
|
|
nsCOMPtr<nsIContent> priorContent, startContent( do_QueryInterface(aStartNode) );
|
|
if (!aOffset)
|
|
{
|
|
if (aStartNode==aBlockParent)
|
|
{
|
|
// we are at start of the block.
|
|
return NS_OK;
|
|
}
|
|
else
|
|
{
|
|
// we are at start of non-block container
|
|
return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
|
|
}
|
|
}
|
|
|
|
nsresult res = startContent->ChildAt(aOffset-1, *getter_AddRefs(priorContent));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!priorContent)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*aPriorNode = do_QueryInterface(priorContent);
|
|
// we have a prior node. If it's a block, return it.
|
|
if (IsBlockNode(*aPriorNode))
|
|
return NS_OK;
|
|
// else if it's a container, get deep rightmost child
|
|
else if (mHTMLEditor->IsContainer(*aPriorNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
res = mHTMLEditor->GetRightmostChild(*aPriorNode, getter_AddRefs(temp));
|
|
if (temp)
|
|
*aPriorNode = temp;
|
|
return NS_OK;
|
|
}
|
|
// else return the node itself
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode,
|
|
PRInt16 aOffset,
|
|
nsIDOMNode *aBlockParent,
|
|
nsCOMPtr<nsIDOMNode> *aNextNode)
|
|
{
|
|
// can't really recycle various getnext/prior routines because we have special needs
|
|
// here. Need to step into inline containers but not block containers.
|
|
if (!aStartNode || !aBlockParent || !aNextNode) return NS_ERROR_NULL_POINTER;
|
|
*aNextNode = 0;
|
|
|
|
if (mHTMLEditor->IsTextNode(aStartNode))
|
|
return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
|
|
if (!mHTMLEditor->IsContainer(aStartNode))
|
|
return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
|
|
|
|
nsCOMPtr<nsIContent> nextContent, startContent( do_QueryInterface(aStartNode) );
|
|
nsresult res = startContent->ChildAt(aOffset, *getter_AddRefs(nextContent));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!nextContent)
|
|
{
|
|
if (aStartNode==aBlockParent)
|
|
{
|
|
// we are at end of the block.
|
|
return NS_OK;
|
|
}
|
|
else
|
|
{
|
|
// we are at end of non-block container
|
|
return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
|
|
}
|
|
}
|
|
|
|
*aNextNode = do_QueryInterface(nextContent);
|
|
// we have a next node. If it's a block, return it.
|
|
if (IsBlockNode(*aNextNode))
|
|
return NS_OK;
|
|
// else if it's a container, get deep leftmost child
|
|
else if (mHTMLEditor->IsContainer(*aNextNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
res = mHTMLEditor->GetLeftmostChild(*aNextNode, getter_AddRefs(temp));
|
|
if (temp)
|
|
*aNextNode = temp;
|
|
return NS_OK;
|
|
}
|
|
// else return the node itself
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
|
|
nsIDOMNode *aEndNode, PRInt32 aEndOffset)
|
|
{
|
|
// MOOSE: this routine needs to be modified to preserve the integrity of the
|
|
// wsFragment info.
|
|
if (!aStartNode || !aEndNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsISupports> isupps(do_QueryInterface(aStartNode));
|
|
PRInt32 idx = mNodeArray->IndexOf(isupps);
|
|
if (idx==-1) idx = 0; // if our strarting point wasn't one of our ws text nodes,
|
|
// then just go through them from the beginning.
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsIDOMCharacterData> textnode;
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
|
|
if (aStartNode == aEndNode)
|
|
{
|
|
textnode = do_QueryInterface(aStartNode);
|
|
if (textnode)
|
|
{
|
|
return mHTMLEditor->DeleteText(textnode, (PRUint32)aStartOffset,
|
|
(PRUint32)(aEndOffset-aStartOffset));
|
|
}
|
|
}
|
|
|
|
PRUint32 count;
|
|
mNodeArray->Count(&count);
|
|
while (idx < count)
|
|
{
|
|
isupps = dont_AddRef(mNodeArray->ElementAt(idx));
|
|
node = do_QueryInterface(isupps);
|
|
if (!node)
|
|
break; // we ran out of ws nodes; must have been deleting to end
|
|
if (node == aStartNode)
|
|
{
|
|
textnode = do_QueryInterface(isupps);
|
|
PRUint32 len;
|
|
textnode->GetLength(&len);
|
|
res = mHTMLEditor->DeleteText(textnode, (PRUint32)aStartOffset, len-aStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else if (node == aEndNode)
|
|
{
|
|
textnode = do_QueryInterface(isupps);
|
|
res = mHTMLEditor->DeleteText(textnode, 0, (PRUint32)aEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (!range)
|
|
{
|
|
range = do_CreateInstance(kCRangeCID);
|
|
res = range->SetStart(aStartNode, aStartOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->SetEnd(aEndNode, aEndOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
PRBool nodeBefore, nodeAfter;
|
|
nsCOMPtr<nsIContent> content (do_QueryInterface(node));
|
|
res = mHTMLEditor->mRangeHelper->CompareNodeToRange(content, range, &nodeBefore, &nodeAfter);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (nodeAfter)
|
|
{
|
|
break;
|
|
}
|
|
if (!nodeBefore)
|
|
{
|
|
res = mHTMLEditor->DeleteNode(node);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
mNodeArray->RemoveElement(isupps);
|
|
}
|
|
}
|
|
idx++;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetCharAfter(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
|
|
{
|
|
if (!run || !aNode || !outPoint)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsISupports> isupps(do_QueryInterface(aNode));
|
|
PRInt32 idx = mNodeArray->IndexOf(isupps);
|
|
if (idx == -1)
|
|
{
|
|
// use range comparisons to get right ws node
|
|
return GetWSPointAfter(aNode, aOffset, outPoint);
|
|
}
|
|
else
|
|
{
|
|
// use wspoint version of GetCharAfter()
|
|
WSPoint point(aNode,aOffset,0);
|
|
return GetCharAfter(point, outPoint);
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetCharBefore(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
|
|
{
|
|
if (!run || !aNode || !outPoint)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsISupports> isupps(do_QueryInterface(aNode));
|
|
PRInt32 idx = mNodeArray->IndexOf(isupps);
|
|
if (idx == -1)
|
|
{
|
|
// use range comparisons to get right ws node
|
|
return GetWSPointBefore(aNode, aOffset, outPoint);
|
|
}
|
|
else
|
|
{
|
|
// use wspoint version of GetCharAfter()
|
|
WSPoint point(aNode,aOffset,0);
|
|
return GetCharBefore(point, outPoint);
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetCharAfter(WSPoint &aPoint, WSPoint *outPoint)
|
|
{
|
|
if (!aPoint.mTextNode || !outPoint)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
outPoint->mTextNode = nsnull;
|
|
outPoint->mOffset = 0;
|
|
outPoint->mChar = 0;
|
|
|
|
nsCOMPtr<nsISupports> isupps(do_QueryInterface(aPoint.mTextNode));
|
|
PRInt32 idx = mNodeArray->IndexOf(isupps);
|
|
if (idx == -1) return NS_ERROR_FAILURE;
|
|
|
|
PRInt32 len;
|
|
nsresult res = aPoint.mTextNode->GetTextLength(&len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
if (aPoint.mOffset < len)
|
|
{
|
|
*outPoint = aPoint;
|
|
outPoint->mOffset--;
|
|
outPoint->mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsISupports> isupps = dont_AddRef(mNodeArray->ElementAt(idx+1));
|
|
if (isupps)
|
|
{
|
|
outPoint->mTextNode = do_QueryInterface(isupps);
|
|
outPoint->mOffset = 0;
|
|
outPoint->mChar = GetCharAt(outPoint->mTextNode, 0);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetCharBefore(WSPoint &aPoint, WSPoint *outPoint)
|
|
{
|
|
if (!aPoint.mTextNode || !outPoint)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
outPoint->mTextNode = nsnull;
|
|
outPoint->mOffset = 0;
|
|
outPoint->mChar = 0;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsISupports> isupps(do_QueryInterface(aPoint.mTextNode));
|
|
PRInt32 idx = mNodeArray->IndexOf(isupps);
|
|
if (idx == -1) return NS_ERROR_FAILURE;
|
|
|
|
if (aPoint.mOffset != 0)
|
|
{
|
|
*outPoint = aPoint;
|
|
outPoint->mOffset--;
|
|
outPoint->mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset-1);
|
|
}
|
|
else if (idx)
|
|
{
|
|
nsCOMPtr<nsISupports> isupps = dont_AddRef(mNodeArray->ElementAt(idx-1));
|
|
if (isupps)
|
|
{
|
|
outPoint->mTextNode = do_QueryInterface(isupps);
|
|
PRInt32 len;
|
|
res = outPoint->mTextNode->GetTextLength(&len);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (len)
|
|
{
|
|
outPoint->mOffset = len-1;
|
|
outPoint->mChar = GetCharAt(outPoint->mTextNode, len-1);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::ConvertToNBSP(WSPoint aPoint)
|
|
{
|
|
// MOOSE: this routine needs to be modified to preserve the integrity of the
|
|
// wsFragment info.
|
|
if (!aPoint.mTextNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(aPoint.mTextNode));
|
|
if (!textNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// first, insert an nbsp
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
|
|
nsAutoString nbspStr(nbsp);
|
|
nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, aPoint.mOffset);
|
|
|
|
// next, find range of ws it will replace
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
PRInt32 startOffset, endOffset;
|
|
|
|
startNode = do_QueryInterface(textNode);
|
|
startOffset = ++aPoint.mOffset; // +1 is to put us after nbsp we just inserted
|
|
|
|
WSPoint point = aPoint, tmp;
|
|
res = GetCharAfter(point, &tmp);
|
|
point = tmp;
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
endNode = startNode;
|
|
endOffset = startOffset;
|
|
|
|
do
|
|
{
|
|
endNode = do_QueryInterface(point.mTextNode);
|
|
endOffset = ++point.mOffset; // +1 is to put us after the space
|
|
res = GetCharAfter(point, &tmp);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
point = tmp;
|
|
} while (nsCRT::IsAsciiSpace(point.mChar));
|
|
|
|
// finally, delete that replaced ws
|
|
res = DeleteChars(startNode, startOffset, endNode, endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::FindRun(nsIDOMNode *aNode, PRInt32 aOffset, WSFragment **outRun, PRBool after)
|
|
{
|
|
// given a dompoint, find the ws run that is before or after it, as caller needs
|
|
if (!aNode || !outRun)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
WSFragment *run = mStartRun;
|
|
while (run)
|
|
{
|
|
PRInt16 comp = mHTMLEditor->mRangeHelper->ComparePoints(aNode, aOffset, run->mStartNode, run->mStartOffset);
|
|
if (comp <= 0)
|
|
{
|
|
if (after)
|
|
{
|
|
*outRun = run;
|
|
return res;
|
|
}
|
|
else // before
|
|
{
|
|
*outRun = nsnull;
|
|
return res;
|
|
}
|
|
}
|
|
comp = mHTMLEditor->mRangeHelper->ComparePoints(aNode, aOffset, run->mEndNode, run->mEndOffset);
|
|
if (comp < 0)
|
|
{
|
|
*outRun = run;
|
|
return res;
|
|
}
|
|
else if (comp == 0)
|
|
{
|
|
if (after)
|
|
{
|
|
*outRun = run->mRight;
|
|
return res;
|
|
}
|
|
else // before
|
|
{
|
|
*outRun = run;
|
|
return res;
|
|
}
|
|
}
|
|
if (!run->mRight)
|
|
{
|
|
if (after)
|
|
{
|
|
*outRun = nsnull;
|
|
return res;
|
|
}
|
|
else // before
|
|
{
|
|
*outRun = run;
|
|
return res;
|
|
}
|
|
}
|
|
run = run->mRight;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
PRUnichar
|
|
nsWSRunObject::GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset)
|
|
{
|
|
// return 0 if we can't get a char, for whatever reason
|
|
if (!aTextNode)
|
|
return 0;
|
|
|
|
const nsTextFragment *textFrag;
|
|
nsresult res = aTextNode->GetText(&textFrag);
|
|
NS_ENSURE_SUCCESS(res, 0);
|
|
|
|
PRInt32 len;
|
|
res = aTextNode->GetTextLength(&len);
|
|
NS_ENSURE_SUCCESS(res, 0);
|
|
if (!len)
|
|
return 0;
|
|
if (aOffset>=len)
|
|
return 0;
|
|
|
|
return textFrag->CharAt(aOffset);
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
|
|
{
|
|
// Note: only to be called if aNode is not a ws node.
|
|
|
|
// binary search on wsnodes
|
|
PRUint32 numNodes, curNum, lastNum;
|
|
mNodeArray->Count(&numNodes);
|
|
|
|
if (!numNodes)
|
|
return NS_OK; // do nothing if there are no nodes to search
|
|
|
|
curNum = numNodes/2;
|
|
lastNum = numNodes;
|
|
PRInt16 cmp=0;
|
|
nsCOMPtr<nsISupports> isupps;
|
|
nsCOMPtr<nsIDOMNode> curNode;
|
|
|
|
// begin binary search
|
|
// we do this because we need to minimize calls to ComparePoints(),
|
|
// which is mongo expensive
|
|
while (curNum != lastNum)
|
|
{
|
|
PRUint32 savedCur = curNum;
|
|
isupps = dont_AddRef(mNodeArray->ElementAt(curNum));
|
|
curNode = do_QueryInterface(isupps);
|
|
cmp = mHTMLEditor->mRangeHelper->ComparePoints(aNode, aOffset, curNode, 0);
|
|
if (cmp < 0)
|
|
{
|
|
if (lastNum > curNum)
|
|
curNum = curNum/2;
|
|
else
|
|
curNum = (curNum+lastNum)/2;
|
|
}
|
|
else
|
|
{
|
|
if (lastNum > curNum)
|
|
curNum = (curNum+lastNum)/2;
|
|
else
|
|
curNum = (curNum+numNodes)/2;
|
|
}
|
|
lastNum = savedCur;
|
|
}
|
|
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(curNode));
|
|
|
|
if (cmp < 0)
|
|
{
|
|
WSPoint point(textNode,0,0);
|
|
return GetCharAfter(point, outPoint);
|
|
}
|
|
else
|
|
{
|
|
PRInt32 len;
|
|
textNode->GetTextLength(&len);
|
|
WSPoint point(textNode,len,0);
|
|
return GetCharAfter(point, outPoint);
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult
|
|
nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint)
|
|
{
|
|
// Note: only to be called if aNode is not a ws node.
|
|
|
|
// binary search on wsnodes
|
|
PRUint32 numNodes, curNum, lastNum;
|
|
mNodeArray->Count(&numNodes);
|
|
|
|
if (!numNodes)
|
|
return NS_OK; // do nothing if there are no nodes to search
|
|
|
|
curNum = numNodes/2;
|
|
lastNum = numNodes;
|
|
PRInt16 cmp=0;
|
|
nsCOMPtr<nsISupports> isupps;
|
|
nsCOMPtr<nsIDOMNode> curNode;
|
|
|
|
// begin binary search
|
|
// we do this because we need to minimize calls to ComparePoints(),
|
|
// which is mongo expensive
|
|
while (curNum != lastNum)
|
|
{
|
|
PRUint32 savedCur = curNum;
|
|
isupps = dont_AddRef(mNodeArray->ElementAt(curNum));
|
|
curNode = do_QueryInterface(isupps);
|
|
cmp = mHTMLEditor->mRangeHelper->ComparePoints(aNode, aOffset, curNode, 0);
|
|
if (cmp < 0)
|
|
{
|
|
if (lastNum > curNum)
|
|
curNum = curNum/2;
|
|
else
|
|
curNum = (curNum+lastNum)/2;
|
|
}
|
|
else
|
|
{
|
|
if (lastNum > curNum)
|
|
curNum = (curNum+lastNum)/2;
|
|
else
|
|
curNum = (curNum+numNodes)/2;
|
|
}
|
|
lastNum = savedCur;
|
|
}
|
|
|
|
nsCOMPtr<nsITextContent> textNode(do_QueryInterface(curNode));
|
|
|
|
if (cmp > 0)
|
|
{
|
|
PRInt32 len;
|
|
textNode->GetTextLength(&len);
|
|
WSPoint point(textNode,len,0);
|
|
return GetCharBefore(point, outPoint);
|
|
}
|
|
else
|
|
{
|
|
WSPoint point(textNode,0,0);
|
|
return GetCharBefore(point, outPoint);
|
|
}
|
|
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
|
|
|