/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL") you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsHTMLEditRules.h" #include "nsEditor.h" #include "nsTextEditor.h" #include "PlaceholderTxn.h" #include "InsertTextTxn.h" #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsIDOMNode.h" #include "nsIDOMText.h" #include "nsIDOMElement.h" #include "nsIDOMNodeList.h" #include "nsIDOMSelection.h" #include "nsIDOMRange.h" #include "nsIDOMCharacterData.h" #include "nsIEnumerator.h" #include "nsIStyleContext.h" #include "nsIPresShell.h" #include "nsLayoutCID.h" class nsIFrame; const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE"; const static char* kMOZEditorBogusNodeValue="TRUE"; const unsigned char nbsp = nbsp; static NS_DEFINE_IID(kPlaceholderTxnIID, PLACEHOLDER_TXN_IID); static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); nsIAtom *nsHTMLEditRules::sAAtom; nsIAtom *nsHTMLEditRules::sAddressAtom; nsIAtom *nsHTMLEditRules::sBigAtom; nsIAtom *nsHTMLEditRules::sBlinkAtom; nsIAtom *nsHTMLEditRules::sBAtom; nsIAtom *nsHTMLEditRules::sCiteAtom; nsIAtom *nsHTMLEditRules::sCodeAtom; nsIAtom *nsHTMLEditRules::sDfnAtom; nsIAtom *nsHTMLEditRules::sEmAtom; nsIAtom *nsHTMLEditRules::sFontAtom; nsIAtom *nsHTMLEditRules::sIAtom; nsIAtom *nsHTMLEditRules::sKbdAtom; nsIAtom *nsHTMLEditRules::sKeygenAtom; nsIAtom *nsHTMLEditRules::sNobrAtom; nsIAtom *nsHTMLEditRules::sSAtom; nsIAtom *nsHTMLEditRules::sSampAtom; nsIAtom *nsHTMLEditRules::sSmallAtom; nsIAtom *nsHTMLEditRules::sSpacerAtom; nsIAtom *nsHTMLEditRules::sSpanAtom; nsIAtom *nsHTMLEditRules::sStrikeAtom; nsIAtom *nsHTMLEditRules::sStrongAtom; nsIAtom *nsHTMLEditRules::sSubAtom; nsIAtom *nsHTMLEditRules::sSupAtom; nsIAtom *nsHTMLEditRules::sTtAtom; nsIAtom *nsHTMLEditRules::sUAtom; nsIAtom *nsHTMLEditRules::sVarAtom; nsIAtom *nsHTMLEditRules::sWbrAtom; PRInt32 nsHTMLEditRules::sInstanceCount = 0; /******************************************************** * Constructor/Destructor ********************************************************/ nsHTMLEditRules::nsHTMLEditRules() { // XXX: this sux. We are dependant on layout's private conception of tag atom names. if (sInstanceCount <= 0) { // inline tags sAAtom = NS_NewAtom("a"); sAddressAtom = NS_NewAtom("address"); sBigAtom = NS_NewAtom("big"); sBlinkAtom = NS_NewAtom("blink"); sBAtom = NS_NewAtom("b"); sCiteAtom = NS_NewAtom("cite"); sCodeAtom = NS_NewAtom("code"); sDfnAtom = NS_NewAtom("dfn"); sEmAtom = NS_NewAtom("em"); sFontAtom = NS_NewAtom("font"); sIAtom = NS_NewAtom("i"); sKbdAtom = NS_NewAtom("kbd"); sKeygenAtom = NS_NewAtom("keygen"); sNobrAtom = NS_NewAtom("nobr"); sSAtom = NS_NewAtom("s"); sSampAtom = NS_NewAtom("samp"); sSmallAtom = NS_NewAtom("small"); sSpacerAtom = NS_NewAtom("spacer"); sSpanAtom = NS_NewAtom("span"); sStrikeAtom = NS_NewAtom("strike"); sStrongAtom = NS_NewAtom("strong"); sSubAtom = NS_NewAtom("sub"); sSupAtom = NS_NewAtom("sup"); sTtAtom = NS_NewAtom("tt"); sUAtom = NS_NewAtom("u"); sVarAtom = NS_NewAtom("var"); sWbrAtom = NS_NewAtom("wbr"); } ++sInstanceCount; } nsHTMLEditRules::~nsHTMLEditRules() { if (sInstanceCount <= 1) { NS_IF_RELEASE(sAAtom); NS_IF_RELEASE(sAddressAtom); NS_IF_RELEASE(sBigAtom); NS_IF_RELEASE(sBlinkAtom); NS_IF_RELEASE(sBAtom); NS_IF_RELEASE(sCiteAtom); NS_IF_RELEASE(sCodeAtom); NS_IF_RELEASE(sDfnAtom); NS_IF_RELEASE(sEmAtom); NS_IF_RELEASE(sFontAtom); NS_IF_RELEASE(sIAtom); NS_IF_RELEASE(sKbdAtom); NS_IF_RELEASE(sKeygenAtom); NS_IF_RELEASE(sNobrAtom); NS_IF_RELEASE(sSAtom); NS_IF_RELEASE(sSampAtom); NS_IF_RELEASE(sSmallAtom); NS_IF_RELEASE(sSpacerAtom); NS_IF_RELEASE(sSpanAtom); NS_IF_RELEASE(sStrikeAtom); NS_IF_RELEASE(sStrongAtom); NS_IF_RELEASE(sSubAtom); NS_IF_RELEASE(sSupAtom); NS_IF_RELEASE(sTtAtom); NS_IF_RELEASE(sUAtom); NS_IF_RELEASE(sVarAtom); NS_IF_RELEASE(sWbrAtom); } --sInstanceCount; } /******************************************************** * Public methods ********************************************************/ NS_IMETHODIMP nsHTMLEditRules::WillDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, PRBool *aCancel) { if (!aSelection || !aInfo) return NS_ERROR_NULL_POINTER; // my kingdom for dynamic cast nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); switch (info->action) { case kInsertText: return WillInsertText(aSelection, aCancel, info->placeTxn, info->inString, info->outString, info->typeInState); case kInsertBreak: return WillInsertBreak(aSelection, aCancel); } return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel); } NS_IMETHODIMP nsHTMLEditRules::DidDoAction(nsIDOMSelection *aSelection, nsRulesInfo *aInfo, nsresult aResult) { if (!aSelection || !aInfo) return NS_ERROR_NULL_POINTER; // my kingdom for dynamic cast nsTextRulesInfo *info = NS_STATIC_CAST(nsTextRulesInfo*, aInfo); switch (info->action) { case kInsertText: return DidInsertText(aSelection, aResult); case kInsertBreak: return DidInsertBreak(aSelection, aResult); } return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); } /******************************************************** * Protected rules methods ********************************************************/ nsresult nsHTMLEditRules::WillInsertText(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, const nsString *inString, nsString *outString, TypeInState typeInState) { if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; // XXX - need to handle strings of length >1 with embedded tabs or spaces // is it a tab? if (*inString == "\t" ) return InsertTab(aSelection,aCancel,aTxn,outString); // is it a space? if (*inString == " ") return InsertSpace(aSelection,aCancel,aTxn,outString); // otherwise, return nsTextEditRules version return nsTextEditRules::WillInsertText(aSelection, aCancel, aTxn, inString, outString, typeInState); } nsresult nsHTMLEditRules::DidInsertText(nsIDOMSelection *aSelection, nsresult aResult) { // for now, return nsTextEditRules version return nsTextEditRules::DidInsertText(aSelection, aResult); } nsresult nsHTMLEditRules::WillInsertBreak(nsIDOMSelection *aSelection, PRBool *aCancel) { if (!aSelection || !aCancel) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = PR_FALSE; return WillInsert(aSelection, aCancel); } // XXX: this code is all experimental, and has no effect on the content model yet // the point here is to collapse adjacent BR's into P's nsresult nsHTMLEditRules::DidInsertBreak(nsIDOMSelection *aSelection, nsresult aResult) { nsresult result = aResult; // if aResult is an error, we return it. if (!aSelection) { return NS_ERROR_NULL_POINTER; } PRBool isCollapsed; aSelection->GetIsCollapsed(&isCollapsed); NS_ASSERTION(PR_TRUE==isCollapsed, "selection not collapsed after insert break."); // if the insert break resulted in consecutive BR tags, // collapse the two BR tags into a single P if (NS_SUCCEEDED(result)) { nsCOMPtr enumerator; enumerator = do_QueryInterface(aSelection,&result); if (enumerator) { enumerator->First(); nsISupports *currentItem; result = enumerator->CurrentItem(¤tItem); if ((NS_SUCCEEDED(result)) && currentItem) { result = NS_ERROR_UNEXPECTED; nsCOMPtr range( do_QueryInterface(currentItem) ); if (range) { nsIAtom *brTag = NS_NewAtom("BR"); nsCOMPtr startNode; result = range->GetStartParent(getter_AddRefs(startNode)); if ((NS_SUCCEEDED(result)) && startNode) { PRInt32 offset; range->GetStartOffset(&offset); nsCOMPtrstartNodeChildren; result = startNode->GetChildNodes(getter_AddRefs(startNodeChildren)); if ((NS_SUCCEEDED(result)) && startNodeChildren) { nsCOMPtr selectedNode; result = startNodeChildren->Item(offset, getter_AddRefs(selectedNode)); if ((NS_SUCCEEDED(result)) && selectedNode) { nsCOMPtr prevNode; result = selectedNode->GetPreviousSibling(getter_AddRefs(prevNode)); if ((NS_SUCCEEDED(result)) && prevNode) { if (PR_TRUE==NodeIsType(prevNode, brTag)) { // the previous node is a BR, check it's siblings nsCOMPtr leftNode; result = prevNode->GetPreviousSibling(getter_AddRefs(leftNode)); if ((NS_SUCCEEDED(result)) && leftNode) { if (PR_TRUE==NodeIsType(leftNode, brTag)) { // left sibling is also a BR, collapse printf("1\n"); } else { if (PR_TRUE==NodeIsType(selectedNode, brTag)) { // right sibling is also a BR, collapse printf("2\n"); } } } } } // now check the next node from selectedNode nsCOMPtr nextNode; result = selectedNode->GetNextSibling(getter_AddRefs(nextNode)); if ((NS_SUCCEEDED(result)) && nextNode) { if (PR_TRUE==NodeIsType(nextNode, brTag)) { // the previous node is a BR, check it's siblings nsCOMPtr rightNode; result = nextNode->GetNextSibling(getter_AddRefs(rightNode)); if ((NS_SUCCEEDED(result)) && rightNode) { if (PR_TRUE==NodeIsType(rightNode, brTag)) { // right sibling is also a BR, collapse printf("3\n"); } else { if (PR_TRUE==NodeIsType(selectedNode, brTag)) { // left sibling is also a BR, collapse printf("4\n"); } } } } } } } } NS_RELEASE(brTag); } } } } return result; } /******************************************************** * helper methods ********************************************************/ /////////////////////////////////////////////////////////////////////////// // IsBlockNode: true if this node is an html block node // PRBool nsHTMLEditRules::IsBlockNode(nsIDOMNode *aNode) { nsIAtom* atom = nsnull; PRBool result; if (!aNode) { NS_NOTREACHED("null node passed to IsBlockNode()"); return PR_FALSE; } nsCOMPtr content = do_QueryInterface(aNode); if (!content) { NS_NOTREACHED("could not get content node in IsBlockNode()"); return PR_FALSE; } content->GetTag(atom); if (!atom) return PR_TRUE; if (sAAtom != atom && sAddressAtom != atom && sBigAtom != atom && sBlinkAtom != atom && sBAtom != atom && sCiteAtom != atom && sCodeAtom != atom && sDfnAtom != atom && sEmAtom != atom && sFontAtom != atom && sIAtom != atom && sKbdAtom != atom && sKeygenAtom != atom && sNobrAtom != atom && sSAtom != atom && sSampAtom != atom && sSmallAtom != atom && sSpacerAtom != atom && sSpanAtom != atom && sStrikeAtom != atom && sStrongAtom != atom && sSubAtom != atom && sSupAtom != atom && sTtAtom != atom && sUAtom != atom && sVarAtom != atom && sWbrAtom != atom) { result = PR_TRUE; } else { result = PR_FALSE; } NS_RELEASE(atom); return result; } /////////////////////////////////////////////////////////////////////////// // IsInlineNode: true if this node is an html inline node // PRBool nsHTMLEditRules::IsInlineNode(nsIDOMNode *aNode) { return !IsBlockNode(aNode); } /////////////////////////////////////////////////////////////////////////// // GetBlockNodeParent: returns enclosing block level ancestor, if any // else returns the node itself nsCOMPtr nsHTMLEditRules::GetBlockNodeParent(nsIDOMNode *aNode) { nsCOMPtr tmp = do_QueryInterface(aNode); nsCOMPtr p; if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree return tmp; while (p && !IsBlockNode(p)) { if (NS_FAILED(p->GetParentNode(getter_AddRefs(tmp)))) // no parent, ran off top of tree return p; p = tmp; } return p; } /////////////////////////////////////////////////////////////////////////// // HasSameBlockNodeParent: true if nodes have same block level ancestor // PRBool nsHTMLEditRules::HasSameBlockNodeParent(nsIDOMNode *aNode1, nsIDOMNode *aNode2) { if (!aNode1 || !aNode2) { NS_NOTREACHED("null node passed to HasSameBlockNodeParent()"); return PR_FALSE; } if (aNode1 == aNode2) return PR_TRUE; nsCOMPtr p1 = GetBlockNodeParent(aNode1); nsCOMPtr p2 = GetBlockNodeParent(aNode2); return (p1 == p2); } /////////////////////////////////////////////////////////////////////////// // IsTextOrElementNode: true if node of dom type element or text // PRBool nsHTMLEditRules::IsTextOrElementNode(nsIDOMNode *aNode) { if (!aNode) { NS_NOTREACHED("null node passed to IsTextOrElementNode()"); return PR_FALSE; } PRUint16 nodeType; aNode->GetNodeType(&nodeType); if ((nodeType == nsIDOMNode::ELEMENT_NODE) || (nodeType == nsIDOMNode::TEXT_NODE)) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // IsTextNode: true if node of dom type text // PRBool nsHTMLEditRules::IsTextNode(nsIDOMNode *aNode) { if (!aNode) { NS_NOTREACHED("null node passed to IsTextNode()"); return PR_FALSE; } PRUint16 nodeType; aNode->GetNodeType(&nodeType); if (nodeType == nsIDOMNode::TEXT_NODE) return PR_TRUE; return PR_FALSE; } /////////////////////////////////////////////////////////////////////////// // NextNodeInBlock: gets the next/prev node in the block, if any. Next node // must be an element or text node, others are ignored nsCOMPtr nsHTMLEditRules::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir) { nsCOMPtr nullNode; nsCOMPtr content; nsCOMPtr blockContent; nsCOMPtr node; nsCOMPtr blockParent; if (!aNode) return nullNode; nsCOMPtr iter; if (NS_FAILED(nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, nsIContentIterator::GetIID(), getter_AddRefs(iter)))) return nullNode; // much gnashing of teeth as we twit back and forth between content and domnode types content = do_QueryInterface(aNode); blockParent = GetBlockNodeParent(aNode); if (!blockParent) return nullNode; blockContent = do_QueryInterface(blockParent); if (!blockContent) return nullNode; if (NS_FAILED(iter->Init(blockContent))) return nullNode; if (NS_FAILED(iter->PositionAt(content))) return nullNode; while (NS_COMFALSE == iter->IsDone()) { if (NS_FAILED(iter->CurrentNode(getter_AddRefs(content)))) return nullNode; // ignore nodes that aren't elements or text, or that are the block parent node = do_QueryInterface(content); if (node && IsTextOrElementNode(node) && (node != blockParent)) return node; if (aDir == kIterForward) iter->Next(); else iter->Prev(); } return nullNode; } /////////////////////////////////////////////////////////////////////////// // GetStartNode: returns whatever the start parent is of the first range // in the selection. nsresult nsHTMLEditRules::GetStartNodeAndOffset(nsIDOMSelection *aSelection, nsCOMPtr *outStartNode, PRInt32 *outStartOffset) { if (!outStartNode || !outStartOffset) return NS_ERROR_NULL_POINTER; nsCOMPtr enumerator; enumerator = do_QueryInterface(aSelection); if (!enumerator) return NS_ERROR_FAILURE; enumerator->First(); nsISupports *currentItem; if ((NS_FAILED(enumerator->CurrentItem(¤tItem))) || !currentItem) return NS_ERROR_FAILURE; nsCOMPtr range( do_QueryInterface(currentItem) ); if (!range) return NS_ERROR_FAILURE; if (NS_FAILED(range->GetStartParent(getter_AddRefs(*outStartNode)))) return NS_ERROR_FAILURE; if (NS_FAILED(range->GetStartOffset(outStartOffset))) return NS_ERROR_FAILURE; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsPreformatted: checks the style info for the node for the preformatted // text style. nsresult nsHTMLEditRules::IsPreformatted(nsIDOMNode *aNode, PRBool *aResult) { nsIPresShell* shell = nsnull; nsresult result; nsCOMPtr content = do_QueryInterface(aNode); nsIFrame *frame; nsCOMPtr styleContext; const nsStyleText* styleText; PRBool bPreformatted; if (!aResult || !content) return NS_ERROR_NULL_POINTER; result = mEditor->GetPresShell(&shell); if (NS_FAILED(result)) return result; result = shell->GetPrimaryFrameFor(content, &frame); if (NS_FAILED(result)) return result; result = shell->GetStyleContextFor(frame, getter_AddRefs(styleContext)); if (NS_FAILED(result)) return result; styleText = (const nsStyleText*)styleContext->GetStyleData(eStyleStruct_Text); bPreformatted = (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) || (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace); *aResult = bPreformatted; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsNextCharWhitespace: checks the adjacent content in the same block // to see if following selection is whitespace nsresult nsHTMLEditRules::IsNextCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult) { if (!aResult) return NS_ERROR_NULL_POINTER; *aResult = PR_FALSE; nsString tempString; PRUint32 strLength; nsCOMPtr textNode = do_QueryInterface(aParentNode); if (textNode) { textNode->GetLength(&strLength); if (aOffset < strLength) { // easy case: next char is in same node textNode->SubstringData(aOffset,aOffset+1,tempString); *aResult = nsString::IsSpace(tempString.First()); return NS_OK; } } // harder case: next char in next node. nsCOMPtr node = NextNodeInBlock(aParentNode, kIterForward); while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes { if (IsTextNode(node)) { textNode = do_QueryInterface(node); textNode->GetLength(&strLength); if (strLength) { textNode->SubstringData(0,1,tempString); *aResult = nsString::IsSpace(tempString.First()); return NS_OK; } // else it's an empty text node, skip it. } else // node is an image or some other thingy that doesn't count as whitespace { break; } } node = NextNodeInBlock(aParentNode, kIterForward); } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsPrevCharWhitespace: checks the adjacent content in the same block // to see if following selection is whitespace nsresult nsHTMLEditRules::IsPrevCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *aResult) { if (!aResult) return NS_ERROR_NULL_POINTER; *aResult = PR_FALSE; nsString tempString; PRUint32 strLength; nsCOMPtr textNode = do_QueryInterface(aParentNode); if (textNode) { if (aOffset > 0) { // easy case: prev char is in same node textNode->SubstringData(aOffset-1,aOffset,tempString); *aResult = nsString::IsSpace(tempString.First()); return NS_OK; } } // harder case: prev char in next node nsCOMPtr node = NextNodeInBlock(aParentNode, kIterBackward); while (node) { if (!IsInlineNode(node)) // skip over bold, italic, link, ect nodes { if (IsTextNode(node)) { textNode = do_QueryInterface(node); textNode->GetLength(&strLength); if (strLength) { textNode->SubstringData(strLength-1,strLength,tempString); *aResult = nsString::IsSpace(tempString.First()); return NS_OK; } // else it's an empty text node, skip it. } else // node is an image or some other thingy that doesn't count as whitespace { break; } } // otherwise we found a node we want to skip, keep going node = NextNodeInBlock(aParentNode, kIterBackward); } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetTabAsNBSPs: stuff the right number of nbsp's into outString // nsresult nsHTMLEditRules::GetTabAsNBSPs(nsString *outString) { if (!outString) return NS_ERROR_NULL_POINTER; // XXX - this should get the right number from prefs *outString += nbsp; *outString += nbsp; *outString += nbsp; *outString += nbsp; return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetTabAsNBSPsAndSpace: stuff the right number of nbsp's followed by a // space into outString nsresult nsHTMLEditRules::GetTabAsNBSPsAndSpace(nsString *outString) { if (!outString) return NS_ERROR_NULL_POINTER; // XXX - this should get the right number from prefs *outString += nbsp; *outString += nbsp; *outString += nbsp; *outString += ' '; return NS_OK; } /******************************************************** * main implemntation methods ********************************************************/ /////////////////////////////////////////////////////////////////////////// // InsertTab: top level logic for determining how to insert a tab // nsresult nsHTMLEditRules::InsertTab(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString) { nsCOMPtr parentNode; PRInt32 offset; PRBool isPRE; PRBool isNextWhiteSpace; PRBool isPrevWhiteSpace; nsresult result = GetStartNodeAndOffset(aSelection, &parentNode, &offset); if (NS_FAILED(result)) return result; if (!parentNode) return NS_ERROR_FAILURE; result = IsPreformatted(parentNode,&isPRE); if (NS_FAILED(result)) return result; if (isPRE) { *outString += '\t'; return NS_OK; } result = IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace); if (NS_FAILED(result)) return result; result = IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace); if (NS_FAILED(result)) return result; if (isPrevWhiteSpace) { // prev character is a whitespace; Need to // insert nbsp's BEFORE the space // XXX for now put tab in wrong place if (isNextWhiteSpace) { GetTabAsNBSPs(outString); return NS_OK; } GetTabAsNBSPsAndSpace(outString); return NS_OK; } if (isNextWhiteSpace) { // character after us is ws. insert nbsps GetTabAsNBSPs(outString); return NS_OK; } // else we are in middle of a word; use n-1 nbsp's plus a space GetTabAsNBSPsAndSpace(outString); return NS_OK; } /////////////////////////////////////////////////////////////////////////// // InsertSpace: top level logic for determining how to insert a space // nsresult nsHTMLEditRules::InsertSpace(nsIDOMSelection *aSelection, PRBool *aCancel, PlaceholderTxn **aTxn, nsString *outString) { nsCOMPtr parentNode; PRInt32 offset; PRBool isPRE; PRBool isNextWhiteSpace; PRBool isPrevWhiteSpace; nsresult result = GetStartNodeAndOffset(aSelection, &parentNode, &offset); if (NS_FAILED(result)) return result; if (!parentNode) return NS_ERROR_FAILURE; result = IsPreformatted(parentNode,&isPRE); if (NS_FAILED(result)) return result; if (isPRE) { *outString += " "; return NS_OK; } result = IsNextCharWhitespace(parentNode, offset, &isNextWhiteSpace); if (NS_FAILED(result)) return result; result = IsPrevCharWhitespace(parentNode, offset, &isPrevWhiteSpace); if (NS_FAILED(result)) return result; if (isPrevWhiteSpace) { // prev character is a whitespace; Need to // insert nbsp BEFORE the space // XXX for now put in wrong place if (isNextWhiteSpace) { *outString += nbsp; return NS_OK; } *outString += nbsp; return NS_OK; } if (isNextWhiteSpace) { // character after us is ws. insert nbsp *outString += nbsp; return NS_OK; } // else just a space *outString += " "; return NS_OK; }