diff --git a/mozilla/editor/base/nsWSRunObject.cpp b/mozilla/editor/base/nsWSRunObject.cpp new file mode 100644 index 00000000000..a32c4fda127 --- /dev/null +++ b/mozilla/editor/base/nsWSRunObject.cpp @@ -0,0 +1,1819 @@ +/* -*- 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 *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *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 *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 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 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 *aSplitNode, PRInt32 *aSplitOffset) +{ + return NS_OK; +} + +nsresult +nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *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 *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 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 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 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 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 , 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 textNode(do_QueryInterface(mNode)); + const nsTextFragment *textFrag; + res = textNode->GetText(&textFrag); + NS_ENSURE_SUCCESS(res, res); + PRInt32 len; + textNode->GetTextLength(&len); + if (mOffsetCharAt(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 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 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; posCharAt(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 , 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 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 isupports (do_QueryInterface(aNode)); + nsresult res = mNodeArray->AppendElement(isupports); + return res; +} + +nsresult +nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *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 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 *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 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 *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 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 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 *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 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 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 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 node; + nsCOMPtr textnode; + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 isupps; + nsCOMPtr 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 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 isupps; + nsCOMPtr 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 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; +} + + + diff --git a/mozilla/editor/base/nsWSRunObject.h b/mozilla/editor/base/nsWSRunObject.h new file mode 100644 index 00000000000..002ef69f553 --- /dev/null +++ b/mozilla/editor/base/nsWSRunObject.h @@ -0,0 +1,172 @@ +/* -*- 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): + */ + +#ifndef __wsrunobject_h__ +#define __wsrunobject_h__ + +#include "nsCOMPtr.h" +#include "nsISupportsArray.h" +#include "nsIDOMNode.h" +#include "nsISupportsArray.h" +#include "nsITextContent.h" +#include "nsIEditor.h" + +class nsIDOMDocument; +class nsIDOMNode; +class nsHTMLEditor; + +class nsWSRunObject +{ + public: + + typedef enum + { + kForward, + kBackward + } EWSDirection; + + // constructor / destructor + nsWSRunObject(nsHTMLEditor *aEd); + nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset); + ~nsWSRunObject(); + + // public methods + static nsresult PrepareToJoinBlocks(nsHTMLEditor *aEd, nsIDOMNode *aLeftParent, nsIDOMNode *aRightParent); + + nsresult InsertBreak(nsCOMPtr *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *outBRNode, + nsIEditor::EDirection aSelect); + nsresult InsertText(const nsAReadableString& aStringToInsert, + nsCOMPtr *aInOutNode, + PRInt32 *aInOutOffset, + nsIDOMDocument *aDoc); + nsresult DeleteWSBackward(); + nsresult DeleteWSForward(); + nsresult PrepareToDeleteRange(nsWSRunObject* aEndObject); + nsresult PrepareToSplitAcrossBlocks(nsCOMPtr *aSplitNode, PRInt32 *aSplitOffset); + nsresult PriorVisibleNode(nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *outVisNode, + PRInt32 *outVisOffset, + PRInt16 *outType); + nsresult NextVisibleNode (nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *outVisNode, + PRInt32 *outVisOffset, + PRInt16 *outType); + + enum {eNone = 0}; + enum {eLeadingWS = 1}; + enum {eTrailingWS = 1 << 1}; + enum {eNormalWS = 1 << 2}; + enum {eText = 1 << 3}; + enum {eSpecial = 1 << 4}; + enum {eBreak = 1 << 5}; + enum {eOtherBlock = 1 << 6}; + enum {eThisBlock = 1 << 7}; + enum {eBlock = eOtherBlock | eThisBlock}; + + protected: + + struct WSFragment + { + nsCOMPtr mStartNode; + nsCOMPtr mEndNode; + PRInt16 mStartOffset; + PRInt16 mEndOffset; + PRInt16 mType, mLeftType, mRightType; + WSFragment *mLeft, *mRight; + + WSFragment() : mStartNode(0),mEndNode(0),mStartOffset(0), + mEndOffset(0),mType(0),mLeftType(0), + mRightType(0),mLeft(0),mRight(0) {} + }; + + struct WSPoint + { + nsCOMPtr mTextNode; + PRInt16 mOffset; + PRUnichar mChar; + + WSPoint() : mTextNode(0),mOffset(0),mChar(0) {} + WSPoint(nsIDOMNode *aNode, PRInt32 aOffset, PRUnichar aChar) : + mTextNode(do_QueryInterface(aNode)),mOffset(aOffset),mChar(aChar) {} + WSPoint(nsITextContent *aTextNode, PRInt32 aOffset, PRUnichar aChar) : + mTextNode(aTextNode),mOffset(aOffset),mChar(aChar) {} + }; + + // protected methods + nsresult GetWSNodes(); + nsresult GetRuns(); + void ClearRuns(); + nsresult MakeSingleWSRun(PRInt16 aType); + nsresult PrependNodeToList(nsIDOMNode *aNode); + nsresult AppendNodeToList(nsIDOMNode *aNode); + nsresult GetPreviousWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *aPriorNode); + nsresult GetNextWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *aNextNode); + nsresult GetPreviousWSNode(nsIDOMNode *aStartNode, + PRInt16 aOffset, + nsIDOMNode *aBlockParent, + nsCOMPtr *aPriorNode); + nsresult GetNextWSNode(nsIDOMNode *aStartNode, + PRInt16 aOffset, + nsIDOMNode *aBlockParent, + nsCOMPtr *aNextNode); + nsresult DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset, + nsIDOMNode *aEndNode, PRInt32 aEndOffset); + nsresult GetCharAfter(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetCharBefore(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetCharAfter(WSPoint &aPoint, WSPoint *outPoint); + nsresult GetCharBefore(WSPoint &aPoint, WSPoint *outPoint); + nsresult ConvertToNBSP(WSPoint aPoint); + nsresult FindRun(nsIDOMNode *aNode, PRInt32 aOffset, WSFragment **outRun, PRBool after); + PRUnichar GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset); + nsresult GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + + // member variables + nsCOMPtr mNode; + PRInt32 mOffset; + nsCOMPtr mStartNode; + PRInt32 mStartOffset; + PRInt16 mStartReason; + nsCOMPtr mEndNode; + PRInt32 mEndOffset; + PRInt16 mEndReason; + nsCOMPtr mFirstNBSPNode; + PRInt32 mFirstNBSPOffset; + nsCOMPtr mLastNBSPNode; + PRInt32 mLastNBSPOffset; + nsCOMPtr mNodeArray; + WSFragment *mRootRun; + WSFragment *mStartRun; + WSFragment *mEndRun; + nsHTMLEditor *mHTMLEditor; // non-owning. +}; + +#endif + diff --git a/mozilla/editor/libeditor/html/nsWSRunObject.cpp b/mozilla/editor/libeditor/html/nsWSRunObject.cpp new file mode 100644 index 00000000000..a32c4fda127 --- /dev/null +++ b/mozilla/editor/libeditor/html/nsWSRunObject.cpp @@ -0,0 +1,1819 @@ +/* -*- 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 *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *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 *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 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 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 *aSplitNode, PRInt32 *aSplitOffset) +{ + return NS_OK; +} + +nsresult +nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *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 *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 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 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 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 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 , 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 textNode(do_QueryInterface(mNode)); + const nsTextFragment *textFrag; + res = textNode->GetText(&textFrag); + NS_ENSURE_SUCCESS(res, res); + PRInt32 len; + textNode->GetTextLength(&len); + if (mOffsetCharAt(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 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 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; posCharAt(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 , 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 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 isupports (do_QueryInterface(aNode)); + nsresult res = mNodeArray->AppendElement(isupports); + return res; +} + +nsresult +nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *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 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 *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 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 *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 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 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 *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 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 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 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 node; + nsCOMPtr textnode; + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 isupps; + nsCOMPtr 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 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 isupps; + nsCOMPtr 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 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; +} + + + diff --git a/mozilla/editor/libeditor/html/nsWSRunObject.h b/mozilla/editor/libeditor/html/nsWSRunObject.h new file mode 100644 index 00000000000..002ef69f553 --- /dev/null +++ b/mozilla/editor/libeditor/html/nsWSRunObject.h @@ -0,0 +1,172 @@ +/* -*- 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): + */ + +#ifndef __wsrunobject_h__ +#define __wsrunobject_h__ + +#include "nsCOMPtr.h" +#include "nsISupportsArray.h" +#include "nsIDOMNode.h" +#include "nsISupportsArray.h" +#include "nsITextContent.h" +#include "nsIEditor.h" + +class nsIDOMDocument; +class nsIDOMNode; +class nsHTMLEditor; + +class nsWSRunObject +{ + public: + + typedef enum + { + kForward, + kBackward + } EWSDirection; + + // constructor / destructor + nsWSRunObject(nsHTMLEditor *aEd); + nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, PRInt32 aOffset); + ~nsWSRunObject(); + + // public methods + static nsresult PrepareToJoinBlocks(nsHTMLEditor *aEd, nsIDOMNode *aLeftParent, nsIDOMNode *aRightParent); + + nsresult InsertBreak(nsCOMPtr *aInOutParent, + PRInt32 *aInOutOffset, + nsCOMPtr *outBRNode, + nsIEditor::EDirection aSelect); + nsresult InsertText(const nsAReadableString& aStringToInsert, + nsCOMPtr *aInOutNode, + PRInt32 *aInOutOffset, + nsIDOMDocument *aDoc); + nsresult DeleteWSBackward(); + nsresult DeleteWSForward(); + nsresult PrepareToDeleteRange(nsWSRunObject* aEndObject); + nsresult PrepareToSplitAcrossBlocks(nsCOMPtr *aSplitNode, PRInt32 *aSplitOffset); + nsresult PriorVisibleNode(nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *outVisNode, + PRInt32 *outVisOffset, + PRInt16 *outType); + nsresult NextVisibleNode (nsIDOMNode *aNode, + PRInt32 aOffset, + nsCOMPtr *outVisNode, + PRInt32 *outVisOffset, + PRInt16 *outType); + + enum {eNone = 0}; + enum {eLeadingWS = 1}; + enum {eTrailingWS = 1 << 1}; + enum {eNormalWS = 1 << 2}; + enum {eText = 1 << 3}; + enum {eSpecial = 1 << 4}; + enum {eBreak = 1 << 5}; + enum {eOtherBlock = 1 << 6}; + enum {eThisBlock = 1 << 7}; + enum {eBlock = eOtherBlock | eThisBlock}; + + protected: + + struct WSFragment + { + nsCOMPtr mStartNode; + nsCOMPtr mEndNode; + PRInt16 mStartOffset; + PRInt16 mEndOffset; + PRInt16 mType, mLeftType, mRightType; + WSFragment *mLeft, *mRight; + + WSFragment() : mStartNode(0),mEndNode(0),mStartOffset(0), + mEndOffset(0),mType(0),mLeftType(0), + mRightType(0),mLeft(0),mRight(0) {} + }; + + struct WSPoint + { + nsCOMPtr mTextNode; + PRInt16 mOffset; + PRUnichar mChar; + + WSPoint() : mTextNode(0),mOffset(0),mChar(0) {} + WSPoint(nsIDOMNode *aNode, PRInt32 aOffset, PRUnichar aChar) : + mTextNode(do_QueryInterface(aNode)),mOffset(aOffset),mChar(aChar) {} + WSPoint(nsITextContent *aTextNode, PRInt32 aOffset, PRUnichar aChar) : + mTextNode(aTextNode),mOffset(aOffset),mChar(aChar) {} + }; + + // protected methods + nsresult GetWSNodes(); + nsresult GetRuns(); + void ClearRuns(); + nsresult MakeSingleWSRun(PRInt16 aType); + nsresult PrependNodeToList(nsIDOMNode *aNode); + nsresult AppendNodeToList(nsIDOMNode *aNode); + nsresult GetPreviousWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *aPriorNode); + nsresult GetNextWSNode(nsIDOMNode *aStartNode, + nsIDOMNode *aBlockParent, + nsCOMPtr *aNextNode); + nsresult GetPreviousWSNode(nsIDOMNode *aStartNode, + PRInt16 aOffset, + nsIDOMNode *aBlockParent, + nsCOMPtr *aPriorNode); + nsresult GetNextWSNode(nsIDOMNode *aStartNode, + PRInt16 aOffset, + nsIDOMNode *aBlockParent, + nsCOMPtr *aNextNode); + nsresult DeleteChars(nsIDOMNode *aStartNode, PRInt32 aStartOffset, + nsIDOMNode *aEndNode, PRInt32 aEndOffset); + nsresult GetCharAfter(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetCharBefore(WSFragment *run, nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetCharAfter(WSPoint &aPoint, WSPoint *outPoint); + nsresult GetCharBefore(WSPoint &aPoint, WSPoint *outPoint); + nsresult ConvertToNBSP(WSPoint aPoint); + nsresult FindRun(nsIDOMNode *aNode, PRInt32 aOffset, WSFragment **outRun, PRBool after); + PRUnichar GetCharAt(nsITextContent *aTextNode, PRInt32 aOffset); + nsresult GetWSPointAfter(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + nsresult GetWSPointBefore(nsIDOMNode *aNode, PRInt32 aOffset, WSPoint *outPoint); + + // member variables + nsCOMPtr mNode; + PRInt32 mOffset; + nsCOMPtr mStartNode; + PRInt32 mStartOffset; + PRInt16 mStartReason; + nsCOMPtr mEndNode; + PRInt32 mEndOffset; + PRInt16 mEndReason; + nsCOMPtr mFirstNBSPNode; + PRInt32 mFirstNBSPOffset; + nsCOMPtr mLastNBSPNode; + PRInt32 mLastNBSPOffset; + nsCOMPtr mNodeArray; + WSFragment *mRootRun; + WSFragment *mStartRun; + WSFragment *mEndRun; + nsHTMLEditor *mHTMLEditor; // non-owning. +}; + +#endif +