/* -*- 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; nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr 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 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 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 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 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 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 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 arrayOfNodes; nsCOMPtr content; nsCOMPtr node; nsCOMPtr 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 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 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 tmp, node = do_QueryInterface(aTextNode); // dont need to do anything if property already set on node PRBool bHasProp; nsCOMPtr 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 tmp; nsAutoString tag; aProperty->ToString(tag); tag.ToLowerCase(); // dont need to do anything if property already set on node PRBool bHasProp; nsCOMPtr 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 elem = do_QueryInterface(aNode); return SetAttribute(elem, *aAttribute, *aValue); } // can it be put inside inline node? if (TagCanContain(tag, aNode)) { nsCOMPtr 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 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 arrayOfNodes; nsCOMPtr node; nsCOMPtr 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 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 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 *aNode, PRInt32 *aOffset, nsIAtom *aProperty, // null here means we split all properties const nsAReadableString *aAttribute, nsCOMPtr *outLeftNode, nsCOMPtr *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 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 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 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 (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 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 content = do_QueryInterface(aNode); if (!content) return PR_FALSE; // ooops PRInt32 attrCount, i, nameSpaceID; nsCOMPtr attrName, prefix; content->GetAttributeCount(attrCount); for (i=0; iGetAttributeNameAt(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 (!Compare(attrString,*aAttribute,nsCaseInsensitiveStringComparator())) continue; // if it's a special _moz... attribute, keep looking attrString.Left(tmp,4); if (!Compare(attrString,NS_LITERAL_STRING("_moz"),nsCaseInsensitiveStringComparator())) 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 content1 = do_QueryInterface(aNode1); if (!content1) return PR_FALSE; // ooops nsCOMPtr content2 = do_QueryInterface(aNode2); if (!content2) return PR_FALSE; // ooops PRInt32 attrCount, i, nameSpaceID, realCount1=0, realCount2=0; nsCOMPtr attrName, prefix; nsresult res, res2; content1->GetAttributeCount(attrCount); nsAutoString attrString, tmp, attrVal1, attrVal2; for (i=0; iGetAttributeNameAt(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; iGetAttributeNameAt(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 elem = do_QueryInterface(aNode); if (!elem) return PR_FALSE; // get attribute node nsCOMPtr 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 elem = do_QueryInterface(aNode); if (!elem) return PR_FALSE; // get attribute node nsCOMPtr 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? if (!Compare(attrVal,*aValue,nsCaseInsensitiveStringComparator())) return PR_TRUE; return PR_FALSE; } nsresult nsHTMLEditor::PromoteInlineRange(nsIDOMRange *inRange) { if (!inRange) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr 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 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 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; nsCOMPtrselection; result = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(result)) return result; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr selPriv(do_QueryInterface(selection)); PRBool isCollapsed; selection->GetIsCollapsed(&isCollapsed); nsCOMPtr collapsedNode; nsCOMPtr enumerator; result = selPriv->GetEnumerator(getter_AddRefs(enumerator)); if (NS_FAILED(result)) return result; if (!enumerator) return NS_ERROR_NULL_POINTER; enumerator->First(); nsCOMPtr 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 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 iter; iter = do_CreateInstance(kCContentIteratorCID); if (!iter) return NS_ERROR_NULL_POINTER; iter->Init(range); nsCOMPtr content; nsAutoString firstValue, theValue; iter->CurrentNode(getter_AddRefs(content)); while (NS_ENUMERATOR_FALSE == iter->IsDone()) { //if (gNoisy) { printf(" checking node %p\n", content.get()); } nsCOMPtrtext; 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) { nsCOMPtrnode; node = do_QueryInterface(content); if (node) { PRBool isSet; nsCOMPtrresultNode; 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; nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr 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 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 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 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 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 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 arrayOfNodes; nsCOMPtr content; nsCOMPtr node; nsCOMPtr 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 nsCOMPtrselection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_FAILURE; nsCOMPtr 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 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 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 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 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 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 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 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 arrayOfNodes; nsCOMPtr content; nsCOMPtr node; nsCOMPtr 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 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 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 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 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 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 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; }