75618 CR line breaks not recognized when inserting data in a plain 76258 IsEditable() needs to be smarter 72968 browser crashes when doing REDO after doing some cut/paste i 55224 switching from HTML Source mode to Normal causes dataloss 71355 style buttons do not work from 2nd cell onwards inside table 71362 empty tables inside (otherwise) empty list item disappears w 74655 40% of reply time in mailcompose spent converting linefeeds r=fm; sr=kin git-svn-id: svn://10.0.0.236/trunk@92547 18797224-902f-48f8-a5cc-f745e15eee43
1545 lines
52 KiB
C++
1545 lines
52 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
*/
|
|
#include "nsICaret.h"
|
|
|
|
#include "nsHTMLEditor.h"
|
|
#include "nsHTMLEditRules.h"
|
|
#include "nsTextEditUtils.h"
|
|
#include "nsHTMLEditUtils.h"
|
|
|
|
#include "nsEditorEventListeners.h"
|
|
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMAttr.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMEventReceiver.h"
|
|
#include "nsIDOMKeyEvent.h"
|
|
#include "nsIDOMKeyListener.h"
|
|
#include "nsIDOMMouseListener.h"
|
|
#include "nsIDOMMouseEvent.h"
|
|
#include "nsISelection.h"
|
|
#include "nsISelectionPrivate.h"
|
|
#include "nsIDOMHTMLAnchorElement.h"
|
|
#include "nsIDOMHTMLImageElement.h"
|
|
#include "nsISelectionController.h"
|
|
|
|
#include "nsICSSLoader.h"
|
|
#include "nsICSSStyleSheet.h"
|
|
#include "nsIHTMLContentContainer.h"
|
|
#include "nsIStyleSet.h"
|
|
#include "nsIDocumentObserver.h"
|
|
#include "nsIDocumentStateListener.h"
|
|
|
|
#include "nsIStyleContext.h"
|
|
#include "TypeInState.h"
|
|
|
|
#include "nsIEnumerator.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentIterator.h"
|
|
#include "nsEditorCID.h"
|
|
#include "nsLayoutCID.h"
|
|
#include "nsIDOMRange.h"
|
|
#include "nsIDOMNSRange.h"
|
|
#include "nsISupportsArray.h"
|
|
#include "nsVoidArray.h"
|
|
#include "nsFileSpec.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIComponentManager.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsIDocumentEncoder.h"
|
|
#include "nsIDOMDocumentFragment.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIParser.h"
|
|
#include "nsParserCIID.h"
|
|
#include "nsIImage.h"
|
|
#include "nsAOLCiter.h"
|
|
#include "nsInternetCiter.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "InsertTextTxn.h"
|
|
|
|
// Transactionas
|
|
#include "PlaceholderTxn.h"
|
|
#include "nsStyleSheetTxns.h"
|
|
|
|
// Misc
|
|
#include "nsEditorUtils.h"
|
|
|
|
static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID);
|
|
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
|
|
static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
|
|
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
|
|
static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID);
|
|
|
|
#if defined(NS_DEBUG) && defined(DEBUG_buster)
|
|
static PRBool gNoisy = PR_FALSE;
|
|
#else
|
|
static const PRBool gNoisy = PR_FALSE;
|
|
#endif
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::SetInlineProperty(nsIAtom *aProperty,
|
|
const nsAReadableString & aAttribute,
|
|
const nsAReadableString & aValue)
|
|
{
|
|
if (!aProperty) { return NS_ERROR_NULL_POINTER; }
|
|
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
|
ForceCompositionEnd();
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
nsString tAttr(aAttribute);//MJUDGE SCC NEED HELP
|
|
nsString tVal(aValue);//MJUDGE SCC NEED HELP
|
|
return mTypeInState->SetProp(aProperty, tAttr, tVal);
|
|
}
|
|
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kSetTextProperty);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!cancel && !handled)
|
|
{
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
PRInt32 startOffset, endOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, endOffset, aProperty, &aAttribute, &aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
// there are up to three phases here. There are all the nodes
|
|
// reported by the subtree iterator to be processed. And there
|
|
// are potentially a starting textnode and an ending textnode
|
|
// which are only partially contained by the range.
|
|
|
|
// lets handle the nodes reported by the iterator. These nodes
|
|
// are entirely contained in the selection range. We build up
|
|
// a list of them (since doing operations on the document during
|
|
// iteration would perturb the iterator).
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
res = iter->Init(range);
|
|
// init returns an error if no nodes in range.
|
|
// this can easily happen with the subtree
|
|
// iterator if the selection doesn't contain
|
|
// any *whole* nodes.
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
// first check the start parent of the range to see if it needs to
|
|
// be seperately handled (it does if it's a text node, due to how the
|
|
// subtree iterator works - it will not have reported it).
|
|
if (IsTextNode(startNode) && IsEditable(startNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
PRInt32 startOffset;
|
|
PRUint32 textLen;
|
|
range->GetStartOffset(&startOffset);
|
|
nodeAsText->GetLength(&textLen);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, startOffset, textLen, aProperty, &aAttribute, &aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// then loop through the list, set the property on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = SetInlinePropertyOnNode(node, aProperty, &aAttribute, &aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
|
|
// last check the end parent of the range to see if it needs to
|
|
// be seperately handled (it does if it's a text node, due to how the
|
|
// subtree iterator works - it will not have reported it).
|
|
if (IsTextNode(endNode) && IsEditable(endNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(endNode);
|
|
PRInt32 endOffset;
|
|
range->GetEndOffset(&endOffset);
|
|
res = SetInlinePropertyOnTextNode(nodeAsText, 0, endOffset, aProperty, &aAttribute, &aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::SetInlinePropertyOnTextNode( nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset,
|
|
nsIAtom *aProperty,
|
|
const nsAReadableString *aAttribute,
|
|
const nsAReadableString *aValue)
|
|
{
|
|
if (!aTextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// dont need to do anything if no characters actually selected
|
|
if (aStartOffset == aEndOffset) return NS_OK;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp, node = do_QueryInterface(aTextNode);
|
|
|
|
// dont need to do anything if property already set on node
|
|
PRBool bHasProp;
|
|
nsCOMPtr<nsIDOMNode> styleNode;
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, bHasProp, getter_AddRefs(styleNode));
|
|
if (bHasProp) return NS_OK;
|
|
|
|
// do we need to split the text node?
|
|
PRUint32 textLen;
|
|
aTextNode->GetLength(&textLen);
|
|
|
|
if ( (PRUint32)aEndOffset != textLen )
|
|
{
|
|
// we need to split off back of text node
|
|
res = SplitNode(node, aEndOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
node = tmp; // remember left node
|
|
}
|
|
if ( aStartOffset )
|
|
{
|
|
// we need to split off front of text node
|
|
res = SplitNode(node, aStartOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// reparent the node inside inline node with appropriate {attribute,value}
|
|
res = SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue);
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::SetInlinePropertyOnNode( nsIDOMNode *aNode,
|
|
nsIAtom *aProperty,
|
|
const nsAReadableString *aAttribute,
|
|
const nsAReadableString *aValue)
|
|
{
|
|
if (!aNode || !aProperty) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString tag;
|
|
aProperty->ToString(tag);
|
|
tag.ToLowerCase();
|
|
|
|
// dont need to do anything if property already set on node
|
|
PRBool bHasProp;
|
|
nsCOMPtr<nsIDOMNode> styleNode;
|
|
IsTextPropertySetByContent(aNode, aProperty, aAttribute, aValue, bHasProp, getter_AddRefs(styleNode));
|
|
if (bHasProp) return NS_OK;
|
|
|
|
// is it already the right kind of node, but with wrong attribute?
|
|
if (NodeIsType(aNode, aProperty))
|
|
{
|
|
// just set the attribute on it.
|
|
// but first remove any contrary style in it's children.
|
|
res = RemoveStyleInside(aNode, aProperty, aAttribute, PR_TRUE);
|
|
if (NS_FAILED(res)) return res;
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
return SetAttribute(elem, *aAttribute, *aValue);
|
|
}
|
|
|
|
// can it be put inside inline node?
|
|
if (TagCanContain(tag, aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> priorNode, nextNode;
|
|
// is either of it's neighbors the right kind of node?
|
|
GetPriorHTMLSibling(aNode, address_of(priorNode));
|
|
GetNextHTMLSibling(aNode, address_of(nextNode));
|
|
if (priorNode && NodeIsType(priorNode, aProperty) &&
|
|
HasAttrVal(priorNode, aAttribute, aValue) &&
|
|
IsOnlyAttribute(priorNode, aAttribute) )
|
|
{
|
|
// previous sib is already right kind of inline node; slide this over into it
|
|
res = MoveNode(aNode, priorNode, -1);
|
|
}
|
|
else if (nextNode && NodeIsType(nextNode, aProperty) &&
|
|
HasAttrVal(nextNode, aAttribute, aValue) &&
|
|
IsOnlyAttribute(priorNode, aAttribute) )
|
|
{
|
|
// following sib is already right kind of inline node; slide this over into it
|
|
res = MoveNode(aNode, nextNode, 0);
|
|
}
|
|
else
|
|
{
|
|
// ok, chuck it in it's very own container
|
|
res = InsertContainerAbove(aNode, address_of(tmp), tag, aAttribute, aValue);
|
|
}
|
|
if (NS_FAILED(res)) return res;
|
|
return RemoveStyleInside(aNode, aProperty, aAttribute);
|
|
}
|
|
// none of the above? then cycle through the children.
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = aNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (childNodes)
|
|
{
|
|
PRInt32 j;
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
if (childCount)
|
|
{
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// populate the list
|
|
for (j=0 ; j < (PRInt32)childCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(j, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode) && IsEditable(childNode))
|
|
{
|
|
isupports = do_QueryInterface(childNode);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
}
|
|
|
|
// then loop through the list, set the property on each node
|
|
PRUint32 listCount;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < (PRInt32)listCount; j++)
|
|
{
|
|
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = SetInlinePropertyOnNode(node, aProperty, aAttribute, aValue);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::SplitStyleAboveRange(nsIDOMRange *inRange,
|
|
nsIAtom *aProperty,
|
|
const nsAReadableString *aAttribute)
|
|
{
|
|
if (!inRange) return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode, origStartNode;
|
|
PRInt32 startOffset, endOffset, origStartOffset;
|
|
|
|
res = inRange->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
origStartNode = startNode;
|
|
origStartOffset = startOffset;
|
|
PRBool sameNode = (startNode==endNode);
|
|
|
|
// split any matching style nodes above the start of range
|
|
res = SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
if (sameNode && (startNode != origStartNode))
|
|
{
|
|
// our startNode got split. This changes the offset of the end of our range.
|
|
endOffset -= origStartOffset;
|
|
}
|
|
|
|
// second verse, same as the first...
|
|
res = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// reset the range
|
|
res = inRange->SetStart(startNode, startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->SetEnd(endNode, endOffset);
|
|
return res;
|
|
}
|
|
|
|
nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsIDOMNode> *aNode,
|
|
PRInt32 *aOffset,
|
|
nsIAtom *aProperty, // null here means we split all properties
|
|
const nsAReadableString *aAttribute,
|
|
nsCOMPtr<nsIDOMNode> *outLeftNode,
|
|
nsCOMPtr<nsIDOMNode> *outRightNode)
|
|
{
|
|
if (!aNode || !*aNode || !aOffset) return NS_ERROR_NULL_POINTER;
|
|
if (outLeftNode) *outLeftNode = nsnull;
|
|
if (outRightNode) *outRightNode = nsnull;
|
|
// split any matching style nodes above the node/offset
|
|
nsCOMPtr<nsIDOMNode> parent, tmp = *aNode;
|
|
PRInt32 offset;
|
|
|
|
while (tmp && !IsBlockNode(tmp))
|
|
{
|
|
if ( (aProperty && NodeIsType(tmp, aProperty)) || // node is the correct inline prop
|
|
(aProperty == nsIEditProperty::href && nsHTMLEditUtils::IsLink(tmp)) || // node is href - test if really <a href=...
|
|
(!aProperty && NodeIsProperty(tmp)) ) // or node is any prop, and we asked to split them all
|
|
{
|
|
// found a style node we need to split
|
|
SplitNodeDeep(tmp, *aNode, *aOffset, &offset, PR_FALSE, outLeftNode, outRightNode);
|
|
// reset startNode/startOffset
|
|
tmp->GetParentNode(getter_AddRefs(*aNode));
|
|
*aOffset = offset;
|
|
}
|
|
tmp->GetParentNode(getter_AddRefs(parent));
|
|
tmp = parent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!IsContainer(aNode)) return PR_FALSE;
|
|
if (!IsEditable(aNode)) return PR_FALSE;
|
|
if (IsBlockNode(aNode)) return PR_FALSE;
|
|
if (NodeIsType(aNode, nsIEditProperty::a)) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode,
|
|
nsIAtom *aProperty, // null here means remove all properties
|
|
const nsAReadableString *aAttribute,
|
|
PRBool aChildrenOnly)
|
|
{
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
if (IsTextNode(aNode)) return NS_OK;
|
|
nsresult res = NS_OK;
|
|
|
|
// first process the children
|
|
nsCOMPtr<nsIDOMNode> child, tmp;
|
|
aNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
// cache next sibling since we might remove child
|
|
child->GetNextSibling(getter_AddRefs(tmp));
|
|
res = RemoveStyleInside(child, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
child = tmp;
|
|
}
|
|
|
|
// then process the node itself
|
|
if ( !aChildrenOnly &&
|
|
((aProperty && NodeIsType(aNode, aProperty)) || // node is prop we asked for
|
|
(aProperty == nsIEditProperty::href && nsHTMLEditUtils::IsLink(aNode))) || // but check for link (<a href=...)
|
|
(!aProperty && NodeIsProperty(aNode)) ) // or node is any prop and we asked for that
|
|
{
|
|
// if we weren't passed an attribute, then we want to
|
|
// remove any matching inlinestyles entirely
|
|
if (!aAttribute || aAttribute->IsEmpty())
|
|
{
|
|
res = RemoveContainer(aNode);
|
|
}
|
|
// otherwise we just want to eliminate the attribute
|
|
else
|
|
{
|
|
if (HasAttr(aNode, aAttribute))
|
|
{
|
|
// if this matching attribute is the ONLY one on the node,
|
|
// then remove the whole node. Otherwise just nix the attribute.
|
|
if (IsOnlyAttribute(aNode, aAttribute))
|
|
{
|
|
res = RemoveContainer(aNode);
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return NS_ERROR_NULL_POINTER;
|
|
res = RemoveAttribute(elem, *aAttribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode,
|
|
const nsAReadableString *aAttribute)
|
|
{
|
|
if (!aNode || !aAttribute) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
|
|
if (!content) return PR_FALSE; // ooops
|
|
|
|
PRInt32 attrCount, i, nameSpaceID;
|
|
nsCOMPtr<nsIAtom> attrName, prefix;
|
|
content->GetAttributeCount(attrCount);
|
|
|
|
nsString tString(*aAttribute);//MJUDGE SCC NEED HELP
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
nsAutoString attrString, tmp;
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's the attribute we know about, keep looking
|
|
if (attrString.EqualsIgnoreCase(tString)) continue;
|
|
// if it's a special _moz... attribute, keep looking
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so return false
|
|
return PR_FALSE;
|
|
}
|
|
// if we made it through all of them without finding a real attribute
|
|
// other than aAttribute, then return PR_TRUE
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool
|
|
nsHTMLEditor::HasMatchingAttributes(nsIDOMNode *aNode1,
|
|
nsIDOMNode *aNode2)
|
|
{
|
|
if (!aNode1 || !aNode2) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content1 = do_QueryInterface(aNode1);
|
|
if (!content1) return PR_FALSE; // ooops
|
|
nsCOMPtr<nsIContent> content2 = do_QueryInterface(aNode2);
|
|
if (!content2) return PR_FALSE; // ooops
|
|
|
|
PRInt32 attrCount, i, nameSpaceID, realCount1=0, realCount2=0;
|
|
nsCOMPtr<nsIAtom> attrName, prefix;
|
|
nsresult res, res2;
|
|
content1->GetAttributeCount(attrCount);
|
|
nsAutoString attrString, tmp, attrVal1, attrVal2;
|
|
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content1->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's a special _moz... attribute, keep going
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so count it
|
|
realCount1++;
|
|
// and compare it to element2's attributes
|
|
res = content1->GetAttribute(nameSpaceID, attrName, attrVal1);
|
|
res2 = content2->GetAttribute(nameSpaceID, attrName, attrVal2);
|
|
if (res != res2) return PR_FALSE;
|
|
if (!attrVal1.EqualsIgnoreCase(attrVal2)) return PR_FALSE;
|
|
}
|
|
|
|
content2->GetAttributeCount(attrCount);
|
|
for (i=0; i<attrCount; i++)
|
|
{
|
|
content2->GetAttributeNameAt(i, nameSpaceID, *getter_AddRefs(attrName),
|
|
*getter_AddRefs(prefix));
|
|
if (!attrName) continue; // ooops
|
|
attrName->ToString(attrString);
|
|
// if it's a special _moz... attribute, keep going
|
|
attrString.Left(tmp,4);
|
|
if (tmp.EqualsWithConversion("_moz")) continue;
|
|
// otherwise, it's another attribute, so count it
|
|
realCount2++;
|
|
}
|
|
|
|
if (realCount1 != realCount2) return PR_FALSE;
|
|
// otherwise, attribute counts match, and we already compared them
|
|
// when going through the first list, so we're done.
|
|
return PR_TRUE;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::HasAttr(nsIDOMNode *aNode,
|
|
const nsAReadableString *aAttribute)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!aAttribute || aAttribute->IsEmpty()) return PR_TRUE; // everybody has the 'null' attribute
|
|
|
|
// get element
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return PR_FALSE;
|
|
|
|
// get attribute node
|
|
nsCOMPtr<nsIDOMAttr> attNode;
|
|
nsresult res = elem->GetAttributeNode(*aAttribute, getter_AddRefs(attNode));
|
|
if ((NS_FAILED(res)) || !attNode) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
|
|
PRBool nsHTMLEditor::HasAttrVal(nsIDOMNode *aNode,
|
|
const nsAReadableString *aAttribute,
|
|
const nsAReadableString *aValue)
|
|
{
|
|
if (!aNode) return PR_FALSE;
|
|
if (!aAttribute || aAttribute->IsEmpty()) return PR_TRUE; // everybody has the 'null' attribute
|
|
|
|
// get element
|
|
nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode);
|
|
if (!elem) return PR_FALSE;
|
|
|
|
// get attribute node
|
|
nsCOMPtr<nsIDOMAttr> attNode;
|
|
nsresult res = elem->GetAttributeNode(*aAttribute, getter_AddRefs(attNode));
|
|
if ((NS_FAILED(res)) || !attNode) return PR_FALSE;
|
|
|
|
// check if attribute has a value
|
|
PRBool isSet;
|
|
attNode->GetSpecified(&isSet);
|
|
// if no value, and that's what we wanted, then return true
|
|
if (!isSet && (!aValue || aValue->IsEmpty())) return PR_TRUE;
|
|
|
|
// get attribute value
|
|
nsAutoString attrVal;
|
|
attNode->GetValue(attrVal);
|
|
|
|
// do values match?
|
|
nsString tString(*aValue);//MJUDGE SCC NEED HELP
|
|
if (attrVal.EqualsIgnoreCase(tString)) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::PromoteInlineRange(nsIDOMRange *inRange)
|
|
{
|
|
if (!inRange) return NS_ERROR_NULL_POINTER;
|
|
nsresult res;
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode, parent;
|
|
PRInt32 startOffset, endOffset;
|
|
|
|
res = inRange->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetStartOffset(&startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->GetEndOffset(&endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
while ( startNode &&
|
|
!nsTextEditUtils::IsBody(startNode) &&
|
|
IsAtFrontOfNode(startNode, startOffset) )
|
|
{
|
|
res = GetNodeLocation(startNode, address_of(parent), &startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
startNode = parent;
|
|
}
|
|
if (!startNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
while ( endNode &&
|
|
!nsTextEditUtils::IsBody(endNode) &&
|
|
IsAtEndOfNode(endNode, endOffset) )
|
|
{
|
|
res = GetNodeLocation(endNode, address_of(parent), &endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
endNode = parent;
|
|
endOffset++; // we are AFTER this node
|
|
}
|
|
if (!endNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
res = inRange->SetStart(startNode, startOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
res = inRange->SetEnd(endNode, endOffset);
|
|
return res;
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, PRInt32 aOffset)
|
|
{
|
|
if (!aNode) return PR_FALSE; // oops
|
|
if (!aOffset) return PR_TRUE;
|
|
|
|
if (IsTextNode(aNode))
|
|
{
|
|
return PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> firstNode;
|
|
GetFirstEditableChild(aNode, address_of(firstNode));
|
|
if (!firstNode) return PR_TRUE;
|
|
PRInt32 offset;
|
|
nsEditor::GetChildOffset(firstNode, aNode, offset);
|
|
if (offset < aOffset) return PR_FALSE;
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
|
|
PRBool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, PRInt32 aOffset)
|
|
{
|
|
if (!aNode) return PR_FALSE; // oops
|
|
PRUint32 len;
|
|
GetLengthOfDOMNode(aNode, len);
|
|
if (aOffset == (PRInt32)len) return PR_TRUE;
|
|
|
|
if (IsTextNode(aNode))
|
|
{
|
|
return PR_FALSE;
|
|
}
|
|
else
|
|
{
|
|
nsCOMPtr<nsIDOMNode> lastNode;
|
|
GetLastEditableChild(aNode, address_of(lastNode));
|
|
if (!lastNode) return PR_TRUE;
|
|
PRInt32 offset;
|
|
nsEditor::GetChildOffset(lastNode, aNode, offset);
|
|
if (offset < aOffset) return PR_TRUE;
|
|
return PR_FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetInlinePropertyBase(nsIAtom *aProperty,
|
|
const nsAReadableString *aAttribute,
|
|
const nsAReadableString *aValue,
|
|
PRBool *aFirst,
|
|
PRBool *aAny,
|
|
PRBool *aAll,
|
|
nsAWritableString *outValue)
|
|
{
|
|
if (!aProperty)
|
|
return NS_ERROR_NULL_POINTER;
|
|
/*
|
|
if (gNoisy)
|
|
{
|
|
nsAutoString propString;
|
|
aProperty->ToString(propString);
|
|
char *propCString = propString.ToNewCString();
|
|
if (gNoisy) { printf("nsTextEditor::GetTextProperty %s\n", propCString); }
|
|
nsCRT::free(propCString);
|
|
}
|
|
*/
|
|
nsresult result;
|
|
*aAny=PR_FALSE;
|
|
*aAll=PR_TRUE;
|
|
*aFirst=PR_FALSE;
|
|
PRBool first=PR_TRUE;
|
|
nsCOMPtr<nsISelection>selection;
|
|
result = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
nsCOMPtr<nsIDOMNode> collapsedNode;
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
result = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(result)) return result;
|
|
if (!enumerator) return NS_ERROR_NULL_POINTER;
|
|
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
result = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
// XXX: should be a while loop, to get each separate range
|
|
// XXX: ERROR_HANDLING can currentItem be null?
|
|
if ((NS_SUCCEEDED(result)) && currentItem)
|
|
{
|
|
PRBool firstNodeInRange = PR_TRUE; // for each range, set a flag
|
|
nsCOMPtr<nsIDOMRange> range(do_QueryInterface(currentItem));
|
|
|
|
if (isCollapsed)
|
|
{
|
|
// efficiency hack. we cache prior results for being collapsed in a given text node.
|
|
// this speeds up typing. Note that other parts of the editor code have to clear out
|
|
// this cache after certain actions.
|
|
range->GetStartContainer(getter_AddRefs(collapsedNode));
|
|
if (!collapsedNode) return NS_ERROR_FAILURE;
|
|
// refresh the cache if we need to
|
|
if (collapsedNode != mCachedNode) CacheInlineStyles(collapsedNode);
|
|
// cache now current, use it! But override it with typeInState results if any...
|
|
PRBool isSet, theSetting;
|
|
if (aAttribute)
|
|
{
|
|
nsString tString(*aAttribute); //MJUDGE SCC NEED HELP
|
|
nsString tOutString;//MJUDGE SCC NEED HELP
|
|
nsString *tPassString=nsnull;
|
|
if (outValue)
|
|
tPassString = &tOutString;
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty, tString, &tOutString);
|
|
if (outValue)
|
|
outValue->Assign(tOutString);
|
|
}
|
|
else
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
*aFirst = *aAny = *aAll = theSetting;
|
|
return NS_OK;
|
|
}
|
|
/*
|
|
if (aProperty == mBoldAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
*aFirst = *aAny = *aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
*aFirst = *aAny = *aAll = mCachedBoldStyle;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
else if (aProperty == mItalicAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
*aFirst = *aAny = *aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
*aFirst = *aAny = *aAll = mCachedItalicStyle;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
else if (aProperty == mUnderlineAtom.get())
|
|
{
|
|
mTypeInState->GetTypingState(isSet, theSetting, aProperty);
|
|
if (isSet)
|
|
{
|
|
*aFirst = *aAny = *aAll = theSetting;
|
|
}
|
|
else
|
|
{
|
|
*aFirst = *aAny = *aAll = mCachedUnderlineStyle;
|
|
}
|
|
return NS_OK;
|
|
} */
|
|
}
|
|
|
|
// either non-collapsed selection or no cached value: do it the hard way
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
iter = do_CreateInstance(kCContentIteratorCID);
|
|
if (!iter) return NS_ERROR_NULL_POINTER;
|
|
|
|
iter->Init(range);
|
|
nsCOMPtr<nsIContent> content;
|
|
nsAutoString firstValue, theValue;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
//if (gNoisy) { printf(" checking node %p\n", content.get()); }
|
|
nsCOMPtr<nsIDOMCharacterData>text;
|
|
text = do_QueryInterface(content);
|
|
|
|
PRBool skipNode = PR_FALSE;
|
|
|
|
// just ignore any non-editable nodes
|
|
if (text && !IsEditable(text))
|
|
{
|
|
skipNode = PR_TRUE;
|
|
}
|
|
else if (text)
|
|
{
|
|
if (!isCollapsed && first && firstNodeInRange)
|
|
{
|
|
firstNodeInRange = PR_FALSE;
|
|
PRInt32 startOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
PRUint32 count;
|
|
text->GetLength(&count);
|
|
if (startOffset==(PRInt32)count)
|
|
{
|
|
//if (gNoisy) { printf(" skipping node %p\n", content.get()); }
|
|
skipNode = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // handle non-text leaf nodes here
|
|
PRBool canContainChildren;
|
|
content->CanContainChildren(canContainChildren);
|
|
if (canContainChildren)
|
|
{
|
|
//if (gNoisy) { printf(" skipping non-leaf node %p\n", content.get()); }
|
|
skipNode = PR_TRUE;
|
|
}
|
|
else {
|
|
//if (gNoisy) { printf(" testing non-text leaf node %p\n", content.get()); }
|
|
}
|
|
}
|
|
if (!skipNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode>node;
|
|
node = do_QueryInterface(content);
|
|
if (node)
|
|
{
|
|
PRBool isSet;
|
|
nsCOMPtr<nsIDOMNode>resultNode;
|
|
if (first)
|
|
{
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, getter_AddRefs(resultNode), &firstValue);
|
|
*aFirst = isSet;
|
|
first = PR_FALSE;
|
|
if (outValue) *outValue = firstValue;
|
|
}
|
|
else
|
|
{
|
|
IsTextPropertySetByContent(node, aProperty, aAttribute, aValue, isSet, getter_AddRefs(resultNode), &theValue);
|
|
if (firstValue != theValue)
|
|
*aAll = PR_FALSE;
|
|
}
|
|
|
|
if (isSet) {
|
|
*aAny = PR_TRUE;
|
|
}
|
|
else {
|
|
*aAll = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
result = iter->Next();
|
|
if (NS_FAILED(result))
|
|
break;
|
|
iter->CurrentNode(getter_AddRefs(content));
|
|
}
|
|
}
|
|
if (!*aAny)
|
|
{ // make sure that if none of the selection is set, we don't report all is set
|
|
*aAll = PR_FALSE;
|
|
}
|
|
//if (gNoisy) { printf(" returning first=%d any=%d all=%d\n", *aFirst, *aAny, *aAll); }
|
|
return result;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty,
|
|
const nsAReadableString &aAttribute,
|
|
const nsAReadableString &aValue,
|
|
PRBool *aFirst,
|
|
PRBool *aAny,
|
|
PRBool *aAll)
|
|
{
|
|
if (!aProperty || !aFirst || !aAny || !aAll)
|
|
return NS_ERROR_NULL_POINTER;
|
|
const nsAReadableString *att = nsnull;
|
|
if (aAttribute.Length())
|
|
att = &aAttribute;
|
|
const nsAReadableString *val = nsnull;
|
|
if (aValue.Length())
|
|
val = &aValue;
|
|
return GetInlinePropertyBase( aProperty, att, val, aFirst, aAny, aAll, nsnull);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty,
|
|
const nsAReadableString &aAttribute,
|
|
const nsAReadableString &aValue,
|
|
PRBool *aFirst,
|
|
PRBool *aAny,
|
|
PRBool *aAll,
|
|
nsAWritableString &outValue)
|
|
{
|
|
if (!aProperty || !aFirst || !aAny || !aAll)
|
|
return NS_ERROR_NULL_POINTER;
|
|
const nsAReadableString *att = nsnull;
|
|
if (aAttribute.Length())
|
|
att = &aAttribute;
|
|
const nsAReadableString *val = nsnull;
|
|
if (aValue.Length())
|
|
val = &aValue;
|
|
return GetInlinePropertyBase( aProperty, att, val, aFirst, aAny, aAll, &outValue);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties()
|
|
{
|
|
return RemoveInlinePropertyImpl(nsnull, nsnull);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAReadableString &aAttribute)
|
|
{
|
|
return RemoveInlinePropertyImpl(aProperty, &aAttribute);
|
|
}
|
|
|
|
nsresult nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom *aProperty, const nsAReadableString *aAttribute)
|
|
{
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
ForceCompositionEnd();
|
|
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
|
|
PRBool isCollapsed;
|
|
selection->GetIsCollapsed(&isCollapsed);
|
|
if (isCollapsed)
|
|
{
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
|
|
// For links, aProperty uses "href", use "a" instead
|
|
if (aProperty == nsIEditProperty::href)
|
|
aProperty = nsIEditProperty::a;
|
|
|
|
if (aProperty) return mTypeInState->ClearProp(aProperty, nsAutoString(*aAttribute));
|
|
else return mTypeInState->ClearAllProps();
|
|
}
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpRemoveTextProperty, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
PRBool cancel, handled;
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveTextProperty);
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (!cancel && !handled)
|
|
{
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// remove this style from ancestors of our range endpoints,
|
|
// splitting them as appropriate
|
|
res = SplitStyleAboveRange(range, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
// we're done with this range!
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
iter->Init(range);
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
res = iter->Next();
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// loop through the list, remove the property on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = RemoveStyleInside(node, aProperty, aAttribute);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
}
|
|
if (!cancel)
|
|
{
|
|
// post-process
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize()
|
|
{
|
|
return RelativeFontChange(1);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize()
|
|
{
|
|
return RelativeFontChange(-1);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChange( PRInt32 aSizeChange)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
ForceCompositionEnd();
|
|
|
|
// Get the selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_FAILURE;
|
|
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
|
|
// Is the selection collapsed?
|
|
PRBool bCollapsed;
|
|
res = selection->GetIsCollapsed(&bCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if it's collapsed set typing state
|
|
if (bCollapsed)
|
|
{
|
|
nsCOMPtr<nsIAtom> atom;
|
|
if (aSizeChange==1) atom = nsIEditProperty::big;
|
|
else atom = nsIEditProperty::small;
|
|
// manipulating text attributes on a collapsed selection only sets state for the next text insertion
|
|
return mTypeInState->SetProp(atom, nsAutoString(), nsAutoString());
|
|
}
|
|
|
|
// wrap with txn batching, rules sniffing, and selection preservation code
|
|
nsAutoEditBatch batchIt(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpSetTextProperty, nsIEditor::eNext);
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
|
|
// get selection range enumerator
|
|
nsCOMPtr<nsIEnumerator> enumerator;
|
|
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!enumerator) return NS_ERROR_FAILURE;
|
|
|
|
// loop thru the ranges in the selection
|
|
enumerator->First();
|
|
nsCOMPtr<nsISupports> currentItem;
|
|
while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
|
|
{
|
|
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!currentItem) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
|
|
|
|
// adjust range to include any ancestors who's children are entirely selected
|
|
res = PromoteInlineRange(range);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// check for easy case: both range endpoints in same text node
|
|
nsCOMPtr<nsIDOMNode> startNode, endNode;
|
|
res = range->GetStartContainer(getter_AddRefs(startNode));
|
|
if (NS_FAILED(res)) return res;
|
|
res = range->GetEndContainer(getter_AddRefs(endNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if ((startNode == endNode) && IsTextNode(startNode))
|
|
{
|
|
PRInt32 startOffset, endOffset;
|
|
range->GetStartOffset(&startOffset);
|
|
range->GetEndOffset(&endOffset);
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else
|
|
{
|
|
// not the easy case. range not contained in single text node.
|
|
// there are up to three phases here. There are all the nodes
|
|
// reported by the subtree iterator to be processed. And there
|
|
// are potentially a starting textnode and an ending textnode
|
|
// which are only partially contained by the range.
|
|
|
|
// lets handle the nodes reported by the iterator. These nodes
|
|
// are entirely contained in the selection range. We build up
|
|
// a list of them (since doing operations on the document during
|
|
// iteration would perturb the iterator).
|
|
|
|
nsCOMPtr<nsIContentIterator> iter;
|
|
res = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull,
|
|
NS_GET_IID(nsIContentIterator),
|
|
getter_AddRefs(iter));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!iter) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsISupportsArray> arrayOfNodes;
|
|
nsCOMPtr<nsIContent> content;
|
|
nsCOMPtr<nsIDOMNode> node;
|
|
nsCOMPtr<nsISupports> isupports;
|
|
|
|
// make a array
|
|
res = NS_NewISupportsArray(getter_AddRefs(arrayOfNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// iterate range and build up array
|
|
res = iter->Init(range);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
while (NS_ENUMERATOR_FALSE == iter->IsDone())
|
|
{
|
|
res = iter->CurrentNode(getter_AddRefs(content));
|
|
if (NS_FAILED(res)) return res;
|
|
node = do_QueryInterface(content);
|
|
if (!node) return NS_ERROR_FAILURE;
|
|
if (IsEditable(node))
|
|
{
|
|
isupports = do_QueryInterface(node);
|
|
arrayOfNodes->AppendElement(isupports);
|
|
}
|
|
iter->Next();
|
|
}
|
|
|
|
// now that we have the list, do the font size change on each node
|
|
PRUint32 listCount;
|
|
PRUint32 j;
|
|
arrayOfNodes->Count(&listCount);
|
|
for (j = 0; j < listCount; j++)
|
|
{
|
|
isupports = dont_AddRef(arrayOfNodes->ElementAt(0));
|
|
node = do_QueryInterface(isupports);
|
|
res = RelativeFontChangeOnNode(aSizeChange, node);
|
|
if (NS_FAILED(res)) return res;
|
|
arrayOfNodes->RemoveElementAt(0);
|
|
}
|
|
}
|
|
// now check the start and end parents of the range to see if they need to
|
|
// be seperately handled (they do if they are text nodes, due to how the
|
|
// subtree iterator works - it will not have reported them).
|
|
if (IsTextNode(startNode) && IsEditable(startNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(startNode);
|
|
PRInt32 startOffset;
|
|
PRUint32 textLen;
|
|
range->GetStartOffset(&startOffset);
|
|
nodeAsText->GetLength(&textLen);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, startOffset, textLen);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
if (IsTextNode(endNode) && IsEditable(endNode))
|
|
{
|
|
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(endNode);
|
|
PRInt32 endOffset;
|
|
range->GetEndOffset(&endOffset);
|
|
res = RelativeFontChangeOnTextNode(aSizeChange, nodeAsText, 0, endOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
enumerator->Next();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChangeOnTextNode( PRInt32 aSizeChange,
|
|
nsIDOMCharacterData *aTextNode,
|
|
PRInt32 aStartOffset,
|
|
PRInt32 aEndOffset)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
if (!aTextNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// dont need to do anything if no characters actually selected
|
|
if (aStartOffset == aEndOffset) return NS_OK;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp, node = do_QueryInterface(aTextNode);
|
|
|
|
// do we need to split the text node?
|
|
PRUint32 textLen;
|
|
aTextNode->GetLength(&textLen);
|
|
|
|
// -1 is a magic value meaning to the end of node
|
|
if (aEndOffset == -1) aEndOffset = textLen;
|
|
|
|
if ( (PRUint32)aEndOffset != textLen )
|
|
{
|
|
// we need to split off back of text node
|
|
res = SplitNode(node, aEndOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
node = tmp; // remember left node
|
|
}
|
|
if ( aStartOffset )
|
|
{
|
|
// we need to split off front of text node
|
|
res = SplitNode(node, aStartOffset, getter_AddRefs(tmp));
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// reparent the node inside font node with appropriate relative size
|
|
res = InsertContainerAbove(node, address_of(tmp), NS_ConvertASCIItoUCS2(aSizeChange==1 ? "big" : "small"));
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::RelativeFontChangeOnNode( PRInt32 aSizeChange,
|
|
nsIDOMNode *aNode)
|
|
{
|
|
// Can only change font size by + or - 1
|
|
if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
if (!aNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
nsAutoString tag;
|
|
if (aSizeChange == 1) tag.AssignWithConversion("big");
|
|
else tag.AssignWithConversion("small");
|
|
|
|
// is this node a text node?
|
|
if (IsTextNode(aNode))
|
|
{
|
|
res = InsertContainerAbove(aNode, address_of(tmp), tag);
|
|
return res;
|
|
}
|
|
// is it the opposite of what we want?
|
|
if ( ((aSizeChange == 1) && nsHTMLEditUtils::IsSmall(aNode)) ||
|
|
((aSizeChange == -1) && nsHTMLEditUtils::IsBig(aNode)) )
|
|
{
|
|
// in that case, just remove this node and pull up the children
|
|
res = RemoveContainer(aNode);
|
|
return res;
|
|
}
|
|
// can it be put inside a "big" or "small"?
|
|
if (TagCanContain(tag, aNode))
|
|
{
|
|
// ok, chuck it in.
|
|
res = InsertContainerAbove(aNode, address_of(tmp), tag);
|
|
return res;
|
|
}
|
|
// none of the above? then cycle through the children.
|
|
// MOOSE: we should group the children together if possible
|
|
// into a single "big" or "small". For the moment they are
|
|
// each getting their own.
|
|
nsCOMPtr<nsIDOMNodeList> childNodes;
|
|
res = aNode->GetChildNodes(getter_AddRefs(childNodes));
|
|
if (NS_FAILED(res)) return res;
|
|
if (childNodes)
|
|
{
|
|
PRInt32 j;
|
|
PRUint32 childCount;
|
|
childNodes->GetLength(&childCount);
|
|
for (j=0 ; j < (PRInt32)childCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> childNode;
|
|
res = childNodes->Item(j, getter_AddRefs(childNode));
|
|
if ((NS_SUCCEEDED(res)) && (childNode))
|
|
{
|
|
res = RelativeFontChangeOnNode(aSizeChange, childNode);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetFontFaceState(PRBool *aMixed, nsAWritableString &outFace)
|
|
{
|
|
if (!aMixed)
|
|
return NS_ERROR_FAILURE;
|
|
*aMixed = PR_TRUE;
|
|
//outFace.AssignWithConversion("");
|
|
outFace.SetLength(0);
|
|
|
|
nsresult res;
|
|
nsAutoString faceStr;
|
|
faceStr.AssignWithConversion("face");
|
|
PRBool first, any, all;
|
|
|
|
|
|
res = GetInlinePropertyBase(nsIEditProperty::font, &faceStr, nsnull, &first, &any, &all, &outFace);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
*aMixed = PR_FALSE;
|
|
return res;
|
|
}
|
|
|
|
res = GetInlinePropertyBase(nsIEditProperty::tt, nsnull, nsnull, &first, &any, &all,nsnull);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
*aMixed = PR_FALSE;
|
|
nsIEditProperty::tt->ToString(outFace);
|
|
}
|
|
|
|
if (!any)
|
|
{
|
|
// there was no font face attrs of any kind. We are in normal font.
|
|
outFace.SetLength(0);
|
|
*aMixed = PR_FALSE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::GetFontColorState(PRBool *aMixed, nsAWritableString &aOutColor)
|
|
{
|
|
if (!aMixed)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*aMixed = PR_TRUE;
|
|
aOutColor.SetLength(0);
|
|
|
|
nsresult res;
|
|
nsAutoString colorStr; colorStr.AssignWithConversion("color");
|
|
PRBool first, any, all;
|
|
|
|
res = GetInlinePropertyBase(nsIEditProperty::font, &colorStr, nsnull, &first, &any, &all, &aOutColor);
|
|
if (NS_FAILED(res)) return res;
|
|
if (any && !all) return res; // mixed
|
|
if (all)
|
|
{
|
|
*aMixed = PR_FALSE;
|
|
return res;
|
|
}
|
|
|
|
if (!any)
|
|
{
|
|
// there was no font color attrs of any kind..
|
|
aOutColor.SetLength(0);
|
|
*aMixed = PR_FALSE;
|
|
}
|
|
return res;
|
|
}
|
|
|