180034 editor should respect the select_all style 183836 New list item should not reset inline styles 179384 Merging blocks via forward delete sends selection to front of document 98434 IME does not work correctly at the last characters in the text field (patch courtesy of Shotaro Kamio) r=jfrancis,brade,cmanske sr=kin git-svn-id: svn://10.0.0.236/trunk@135560 18797224-902f-48f8-a5cc-f745e15eee43
2455 lines
81 KiB
C++
2455 lines
81 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* 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 the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the NPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the NPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
#include "nsICaret.h"
|
|
|
|
|
|
#include "nsHTMLEditor.h"
|
|
#include "nsHTMLEditRules.h"
|
|
#include "nsTextEditUtils.h"
|
|
#include "nsHTMLEditUtils.h"
|
|
#include "nsWSRunObject.h"
|
|
|
|
#include "nsEditorEventListeners.h"
|
|
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMAttr.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDOMEventReceiver.h"
|
|
#include "nsIDOMNSEvent.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 "nsIFileChannel.h"
|
|
#include "nsIFrameSelection.h" // For TABLESELECTION_ defines
|
|
#include "nsIIndependentSelection.h" //domselections answer to frameselection
|
|
|
|
|
|
#include "nsICSSLoader.h"
|
|
#include "nsICSSStyleSheet.h"
|
|
#include "nsIHTMLContentContainer.h"
|
|
#include "nsIStyleSet.h"
|
|
#include "nsIDocumentObserver.h"
|
|
#include "nsIDocumentStateListener.h"
|
|
|
|
#include "nsIStyleContext.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 "nsCOMArray.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 "nsXPCOM.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsLinebreakConverter.h"
|
|
#include "nsIHTMLFragmentContentSink.h"
|
|
|
|
// netwerk
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
|
|
// Drag & Drop, Clipboard
|
|
#include "nsWidgetsCID.h"
|
|
#include "nsIClipboard.h"
|
|
#include "nsITransferable.h"
|
|
#include "nsIDragService.h"
|
|
#include "nsIDOMNSUIEvent.h"
|
|
|
|
// Misc
|
|
#include "TextEditorTest.h"
|
|
#include "nsEditorUtils.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsIPrefService.h"
|
|
const PRUnichar nbsp = 160;
|
|
|
|
static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID);
|
|
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
|
|
|
|
// Drag & Drop, Clipboard Support
|
|
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
|
|
static NS_DEFINE_CID(kCTransferableCID, NS_TRANSFERABLE_CID);
|
|
static NS_DEFINE_CID(kCHTMLFormatConverterCID, NS_HTMLFORMATCONVERTER_CID);
|
|
// private clipboard data flavors for html copy/paste
|
|
#define kHTMLContext "text/_moz_htmlcontext"
|
|
#define kHTMLInfo "text/_moz_htmlinfo"
|
|
|
|
// some little helpers
|
|
static PRInt32 FindPositiveIntegerAfterString(const char *aLeadingString, nsCString &aCStr);
|
|
static nsresult RemoveFragComments(nsCString &theStr);
|
|
static void RemoveBodyAndHead(nsIDOMNode *aNode);
|
|
|
|
#if defined(NS_DEBUG) && defined(DEBUG_buster)
|
|
static PRBool gNoisy = PR_FALSE;
|
|
#else
|
|
static const PRBool gNoisy = PR_FALSE;
|
|
#endif
|
|
static nsCOMPtr<nsIDOMNode> GetListParent(nsIDOMNode* aNode)
|
|
{
|
|
if (!aNode) return nsnull;
|
|
nsCOMPtr<nsIDOMNode> parent, tmp;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsList(parent)) return parent;
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
parent = tmp;
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
static nsCOMPtr<nsIDOMNode> GetTableParent(nsIDOMNode* aNode)
|
|
{
|
|
if (!aNode) return nsnull;
|
|
nsCOMPtr<nsIDOMNode> parent, tmp;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
while (parent)
|
|
{
|
|
if (nsHTMLEditUtils::IsTable(parent)) return parent;
|
|
parent->GetParentNode(getter_AddRefs(tmp));
|
|
parent = tmp;
|
|
}
|
|
return nsnull;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInString)
|
|
{
|
|
nsAutoString charset;
|
|
return LoadHTMLWithCharset(aInString, charset);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::LoadHTMLWithCharset(const nsAString & aInputString, const nsAString & aCharset)
|
|
{
|
|
nsresult res = NS_OK;
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// force IME commit; set up rules sniffing and batching
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpLoadHTML, nsIEditor::eNext);
|
|
|
|
// Get selection
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kLoadHTML);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Delete Selection, but only if it isn't collapsed, see bug #106269
|
|
if (!isCollapsed)
|
|
{
|
|
res = DeleteSelection(eNone);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// Get the first range in the selection, for context:
|
|
nsCOMPtr<nsIDOMRange> range, clone;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!range)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
|
|
if (!nsrange)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
|
|
// create fragment for pasted html
|
|
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
|
|
{
|
|
res = nsrange->CreateContextualFragment(aInputString, getter_AddRefs(docfrag));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// put the fragment into the document
|
|
nsCOMPtr<nsIDOMNode> parent, junk;
|
|
res = range->GetStartContainer(getter_AddRefs(parent));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
if (!parent)
|
|
return NS_ERROR_NULL_POINTER;
|
|
PRInt32 childOffset;
|
|
res = range->GetStartOffset(&childOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
nsCOMPtr<nsIDOMNode> nodeToInsert;
|
|
docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
|
|
while (nodeToInsert)
|
|
{
|
|
res = InsertNode(nodeToInsert, parent, childOffset++);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
|
|
}
|
|
}
|
|
|
|
return mRules->DidDoAction(selection, &ruleInfo, res);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsAString & aInString)
|
|
{
|
|
nsAutoString charset;
|
|
return InsertHTMLWithCharset(aInString, charset);
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString,
|
|
const nsAString & aContextStr,
|
|
const nsAString & aInfoStr)
|
|
{
|
|
nsAutoString charset;
|
|
return InsertHTMLWithCharsetAndContext(aInputString, charset,
|
|
aContextStr, aInfoStr);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertHTMLWithCharset(const nsAString & aInputString,
|
|
const nsAString & aCharset)
|
|
{
|
|
return InsertHTMLWithCharsetAndContext(aInputString, aCharset,
|
|
nsAutoString(), nsAutoString());
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::InsertHTMLWithCharsetAndContext(const nsAString & aInputString,
|
|
const nsAString & aCharset,
|
|
const nsAString & aContextStr,
|
|
const nsAString & aInfoStr)
|
|
{
|
|
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// force IME commit; set up rules sniffing and batching
|
|
ForceCompositionEnd();
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpHTMLPaste, nsIEditor::eNext);
|
|
|
|
// Get selection
|
|
nsresult res;
|
|
nsCOMPtr<nsISelection>selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// Get the first range in the selection, for context:
|
|
nsCOMPtr<nsIDOMRange> range, clone;
|
|
res = selection->GetRangeAt(0, getter_AddRefs(range));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = range->CloneRange(getter_AddRefs(clone));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(clone));
|
|
if (!nsrange)
|
|
return NS_ERROR_NO_INTERFACE;
|
|
|
|
// create a dom document fragment that represents the structure to paste
|
|
nsCOMPtr<nsIDOMNode> fragmentAsNode;
|
|
PRInt32 rangeStartHint, rangeEndHint;
|
|
res = CreateDOMFragmentFromPaste(nsrange, aInputString, aContextStr, aInfoStr,
|
|
address_of(fragmentAsNode),
|
|
&rangeStartHint, &rangeEndHint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// make a list of what nodes in docFrag we need to move
|
|
nsCOMArray<nsIDOMNode> nodeList;
|
|
res = CreateListOfNodesToPaste(fragmentAsNode, nodeList, rangeStartHint, rangeEndHint);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// are there any table elements in the list?
|
|
// node and offset for insertion
|
|
nsCOMPtr<nsIDOMNode> parentNode;
|
|
PRInt32 offsetOfNewNode;
|
|
|
|
// check for table cell selection mode
|
|
PRBool cellSelectionMode = PR_FALSE;
|
|
nsCOMPtr<nsIDOMElement> cell;
|
|
res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
|
|
if (NS_SUCCEEDED(res) && cell)
|
|
{
|
|
cellSelectionMode = PR_TRUE;
|
|
}
|
|
|
|
if (cellSelectionMode)
|
|
{
|
|
// do we have table content to paste? If so, we want to delete
|
|
// the selected table cells and replace with new table elements;
|
|
// but if not we want to delete _contents_ of cells and replace
|
|
// with non-table elements. Use cellSelectionMode bool to
|
|
// indicate results.
|
|
nsIDOMNode* firstNode = nodeList[0];
|
|
if (!nsHTMLEditUtils::IsTableElement(firstNode))
|
|
cellSelectionMode = PR_FALSE;
|
|
}
|
|
|
|
if (!cellSelectionMode)
|
|
{
|
|
res = DeleteSelectionAndPrepareToCreateNode(parentNode, offsetOfNewNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// pasting does not inherit local inline styles
|
|
res = RemoveAllInlineProperties();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
else
|
|
{
|
|
// delete whole cells: we will replace with new table content
|
|
if (1)
|
|
{
|
|
// Save current selection since DeleteTableCell perturbs it
|
|
nsAutoSelectionReset selectionResetter(selection, this);
|
|
res = DeleteTableCell(1);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
// colapse selection to beginning of deleted table content
|
|
selection->CollapseToStart();
|
|
}
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
// The rules code (WillDoAction above) might have changed the selection.
|
|
// refresh our memory...
|
|
res = GetStartNodeAndOffset(selection, address_of(parentNode), &offsetOfNewNode);
|
|
if (!parentNode) res = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if there are any invisible br's after our insertion point, remove them.
|
|
// this is because if there is a br at end of what we paste, it will make
|
|
// the invisible br visible.
|
|
nsWSRunObject wsObj(this, parentNode, offsetOfNewNode);
|
|
if (nsTextEditUtils::IsBreak(wsObj.mEndReasonNode) &&
|
|
!IsVisBreak(wsObj.mEndReasonNode) )
|
|
{
|
|
res = DeleteNode(wsObj.mEndReasonNode);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// remeber if we are in a link.
|
|
PRBool bStartedInLink = IsInLink(parentNode);
|
|
|
|
// are we in a text node? If so, split it.
|
|
if (IsTextNode(parentNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
res = SplitNodeDeep(parentNode, parentNode, offsetOfNewNode, &offsetOfNewNode);
|
|
if (NS_FAILED(res)) return res;
|
|
res = parentNode->GetParentNode(getter_AddRefs(temp));
|
|
if (NS_FAILED(res)) return res;
|
|
parentNode = temp;
|
|
}
|
|
|
|
// build up list of parents of first node in list that are either
|
|
// lists or tables. First examine front of paste node list.
|
|
nsCOMArray<nsIDOMNode> startListAndTableArray;
|
|
res = GetListAndTableParents(PR_FALSE, nodeList, startListAndTableArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// remember number of lists and tables above us
|
|
PRInt32 highWaterMark = -1;
|
|
if (startListAndTableArray.Count() > 0)
|
|
{
|
|
res = DiscoverPartialListsAndTables(nodeList, startListAndTableArray, &highWaterMark);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// if we have pieces of tables or lists to be inserted, let's force the paste
|
|
// to deal with table elements right away, so that it doesn't orphan some
|
|
// table or list contents outside the table or list.
|
|
if (highWaterMark >= 0)
|
|
{
|
|
res = ReplaceOrphanedStructure(PR_FALSE, nodeList, startListAndTableArray, highWaterMark);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// Now go through the same process again for the end of the paste node list.
|
|
nsCOMArray<nsIDOMNode> endListAndTableArray;
|
|
res = GetListAndTableParents(PR_TRUE, nodeList, endListAndTableArray);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
highWaterMark = -1;
|
|
|
|
// remember number of lists and tables above us
|
|
if (endListAndTableArray.Count() > 0)
|
|
{
|
|
res = DiscoverPartialListsAndTables(nodeList, endListAndTableArray, &highWaterMark);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// don't orphan partial list or table structure
|
|
if (highWaterMark >= 0)
|
|
{
|
|
res = ReplaceOrphanedStructure(PR_TRUE, nodeList, endListAndTableArray, highWaterMark);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
}
|
|
|
|
// Loop over the node list and paste the nodes:
|
|
PRBool bDidInsert = PR_FALSE;
|
|
nsCOMPtr<nsIDOMNode> parentBlock, lastInsertNode, insertedContextParent;
|
|
PRInt32 listCount = nodeList.Count();
|
|
PRInt32 j;
|
|
if (IsBlockNode(parentNode))
|
|
parentBlock = parentNode;
|
|
else
|
|
parentBlock = GetBlockNodeParent(parentNode);
|
|
|
|
for (j=0; j<listCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = nodeList[j];
|
|
|
|
nsString namestr;
|
|
curNode->GetNodeName(namestr);
|
|
|
|
NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
|
|
NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
|
|
NS_ENSURE_TRUE(!nsTextEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
|
|
|
|
if (insertedContextParent)
|
|
{
|
|
// if we had to insert something higher up in the paste heirarchy, we want to
|
|
// skip any further paste nodes that descend from that. Else we will paste twice.
|
|
if (nsEditorUtils::IsDescendantOf(curNode, insertedContextParent))
|
|
continue;
|
|
}
|
|
|
|
// give the user a hand on table element insertion. if they have
|
|
// a table or table row on the clipboard, and are trying to insert
|
|
// into a table or table row, insert the appropriate children instead.
|
|
if ( (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode))
|
|
&& (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::IsTable(parentNode)) )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = child;
|
|
offsetOfNewNode++;
|
|
}
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
}
|
|
// give the user a hand on list insertion. if they have
|
|
// a list on the clipboard, and are trying to insert
|
|
// into a list or list item, insert the appropriate children instead,
|
|
// ie, merge the lists instead of pasting in a sublist.
|
|
else if (nsHTMLEditUtils::IsList(curNode) &&
|
|
(nsHTMLEditUtils::IsList(parentNode) || nsHTMLEditUtils::IsListItem(parentNode)) )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child, tmp;
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
if (nsHTMLEditUtils::IsListItem(child) || nsHTMLEditUtils::IsList(child))
|
|
{
|
|
// check if we are pasting into empty list item. If so
|
|
// delete it and paste into parent list instead.
|
|
if (nsHTMLEditUtils::IsListItem(parentNode))
|
|
{
|
|
PRBool isEmpty;
|
|
res = IsEmptyNode(parentNode, &isEmpty, PR_TRUE);
|
|
if ((NS_SUCCEEDED(res)) && isEmpty)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> listNode;
|
|
PRInt32 newOffset;
|
|
GetNodeLocation(parentNode, address_of(listNode), &newOffset);
|
|
if (listNode)
|
|
{
|
|
DeleteNode(parentNode);
|
|
parentNode = listNode;
|
|
offsetOfNewNode = newOffset;
|
|
}
|
|
}
|
|
}
|
|
res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = child;
|
|
offsetOfNewNode++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curNode->RemoveChild(child, getter_AddRefs(tmp));
|
|
}
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
|
|
}
|
|
// check for pre's going into pre's.
|
|
else if (nsHTMLEditUtils::IsPre(parentBlock) && nsHTMLEditUtils::IsPre(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child, tmp;
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = child;
|
|
offsetOfNewNode++;
|
|
}
|
|
curNode->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// try to insert
|
|
res = InsertNodeAtPoint(curNode, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
lastInsertNode = curNode;
|
|
}
|
|
|
|
// assume failure means no legal parent in the document heirarchy.
|
|
// try again with the parent of curNode in the paste heirarchy.
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
while (NS_FAILED(res) && curNode)
|
|
{
|
|
curNode->GetParentNode(getter_AddRefs(parent));
|
|
if (parent && !nsTextEditUtils::IsBody(parent))
|
|
{
|
|
res = InsertNodeAtPoint(parent, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
bDidInsert = PR_TRUE;
|
|
insertedContextParent = parent;
|
|
lastInsertNode = parent;
|
|
}
|
|
}
|
|
curNode = parent;
|
|
}
|
|
}
|
|
if (bDidInsert)
|
|
{
|
|
res = GetNodeLocation(lastInsertNode, address_of(parentNode), &offsetOfNewNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
offsetOfNewNode++;
|
|
}
|
|
}
|
|
|
|
// Now collapse the selection to the end of what we just inserted:
|
|
if (lastInsertNode)
|
|
{
|
|
// set selection to the end of what we just pasted.
|
|
nsCOMPtr<nsIDOMNode> selNode, tmp, visNode, highTable;
|
|
PRInt32 selOffset;
|
|
|
|
// but dont cross tables
|
|
if (!nsHTMLEditUtils::IsTable(lastInsertNode))
|
|
{
|
|
res = GetLastEditableLeaf(lastInsertNode, address_of(selNode));
|
|
if (NS_FAILED(res)) return res;
|
|
tmp = selNode;
|
|
while (tmp && (tmp != lastInsertNode))
|
|
{
|
|
if (nsHTMLEditUtils::IsTable(tmp))
|
|
highTable = tmp;
|
|
nsCOMPtr<nsIDOMNode> parent = tmp;
|
|
tmp->GetParentNode(getter_AddRefs(parent));
|
|
tmp = parent;
|
|
}
|
|
if (highTable)
|
|
selNode = highTable;
|
|
}
|
|
if (!selNode)
|
|
selNode = lastInsertNode;
|
|
if (IsTextNode(selNode) || (IsContainer(selNode) && !nsHTMLEditUtils::IsTable(selNode)))
|
|
{
|
|
res = GetLengthOfDOMNode(selNode, (PRUint32&)selOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
else // we need to find a container for selection. Look up.
|
|
{
|
|
tmp = selNode;
|
|
res = GetNodeLocation(tmp, address_of(selNode), &selOffset);
|
|
++selOffset; // want to be *after* last leaf node in paste
|
|
if (NS_FAILED(res)) return res;
|
|
}
|
|
|
|
// make sure we dont end up with selection collapsed after an invisible break node
|
|
nsWSRunObject wsRunObj(this, selNode, selOffset);
|
|
PRInt32 outVisOffset=0;
|
|
PRInt16 visType=0;
|
|
res = wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), &outVisOffset, &visType);
|
|
if (NS_FAILED(res)) return res;
|
|
if (visType == nsWSRunObject::eBreak)
|
|
{
|
|
// we are after a break. Is it visible? Despite the name,
|
|
// PriorVisibleNode does not make that determination for breaks.
|
|
// It also may not return the break in visNode. We have to pull it
|
|
// out of the nsWSRunObject's state.
|
|
if (!IsVisBreak(wsRunObj.mStartReasonNode))
|
|
{
|
|
// dont leave selection past an invisible break;
|
|
// reset {selNode,selOffset} to point before break
|
|
res = GetNodeLocation(wsRunObj.mStartReasonNode, address_of(selNode), &selOffset);
|
|
// we want to be inside any inline style prior to break
|
|
nsWSRunObject wsRunObj(this, selNode, selOffset);
|
|
res = wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), &outVisOffset, &visType);
|
|
if (NS_FAILED(res)) return res;
|
|
if (visType == nsWSRunObject::eText ||
|
|
visType == nsWSRunObject::eNormalWS)
|
|
{
|
|
selNode = visNode;
|
|
selOffset = outVisOffset; // PriorVisibleNode already set offset to _after_ the text or ws
|
|
}
|
|
else if (visType == nsWSRunObject::eSpecial)
|
|
{
|
|
// prior visible thing is an image or some other non-text thingy.
|
|
// We want to be right after it.
|
|
res = GetNodeLocation(wsRunObj.mStartReasonNode, address_of(selNode), &selOffset);
|
|
++selOffset;
|
|
}
|
|
}
|
|
}
|
|
selection->Collapse(selNode, selOffset);
|
|
|
|
// if we just pasted a link, discontinue link style
|
|
nsCOMPtr<nsIDOMNode> link;
|
|
if (!bStartedInLink && IsInLink(selNode, address_of(link)))
|
|
{
|
|
// so, if we just pasted a link, I split it. Why do that instead of just
|
|
// nudging selection point beyond it? Because it might have ended in a BR
|
|
// that is not visible. If so, the code above just placed selection
|
|
// inside that. So I split it instead.
|
|
nsCOMPtr<nsIDOMNode> leftLink;
|
|
PRInt32 linkOffset;
|
|
res = SplitNodeDeep(link, selNode, selOffset, &linkOffset, PR_TRUE, address_of(leftLink));
|
|
if (NS_FAILED(res)) return res;
|
|
res = GetNodeLocation(leftLink, address_of(selNode), &selOffset);
|
|
if (NS_FAILED(res)) return res;
|
|
selection->Collapse(selNode, selOffset+1);
|
|
}
|
|
}
|
|
}
|
|
|
|
res = mRules->DidDoAction(selection, &ruleInfo, res);
|
|
return res;
|
|
}
|
|
|
|
|
|
PRBool
|
|
nsHTMLEditor::IsInLink(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *outLink)
|
|
{
|
|
if (!aNode)
|
|
return PR_FALSE;
|
|
if (outLink)
|
|
*outLink = nsnull;
|
|
nsCOMPtr<nsIDOMNode> tmp, node = aNode;
|
|
while (node)
|
|
{
|
|
if (nsHTMLEditUtils::IsLink(node))
|
|
{
|
|
if (outLink)
|
|
*outLink = node;
|
|
return PR_TRUE;
|
|
}
|
|
tmp = node;
|
|
tmp->GetParentNode(getter_AddRefs(node));
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode, PRBool aListOnly)
|
|
{
|
|
NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsresult res = NS_OK;
|
|
nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
|
|
if (IsEmptyTextContent(content))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent, ignored;
|
|
aNode->GetParentNode(getter_AddRefs(parent));
|
|
if (parent)
|
|
{
|
|
if (!aListOnly || nsHTMLEditUtils::IsList(parent))
|
|
res = parent->RemoveChild(aNode, getter_AddRefs(ignored));
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (!nsHTMLEditUtils::IsPre(aNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> child;
|
|
aNode->GetLastChild(getter_AddRefs(child));
|
|
|
|
while (child)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> tmp;
|
|
child->GetPreviousSibling(getter_AddRefs(tmp));
|
|
res = StripFormattingNodes(child, aListOnly);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
child = tmp;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PrepareHTMLTransferable(nsITransferable **aTransferable,
|
|
PRBool aHavePrivFlavor)
|
|
{
|
|
// Create generic Transferable for getting the data
|
|
nsresult rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
|
|
NS_GET_IID(nsITransferable),
|
|
(void**)aTransferable);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Get the nsITransferable interface for getting the data from the clipboard
|
|
if (aTransferable)
|
|
{
|
|
// Create the desired DataFlavor for the type of data
|
|
// we want to get out of the transferable
|
|
if ((mFlags & eEditorPlaintextMask) == 0) // This should only happen in html editors, not plaintext
|
|
{
|
|
if (!aHavePrivFlavor)
|
|
{
|
|
(*aTransferable)->AddDataFlavor(kNativeHTMLMime);
|
|
}
|
|
(*aTransferable)->AddDataFlavor(kHTMLMime);
|
|
(*aTransferable)->AddDataFlavor(kFileMime);
|
|
//(*transferable)->AddDataFlavor(kJPEGImageMime);
|
|
}
|
|
(*aTransferable)->AddDataFlavor(kUnicodeMime);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
PRInt32
|
|
FindPositiveIntegerAfterString(const char *aLeadingString, nsCString &aCStr)
|
|
{
|
|
// first obtain offsets from cfhtml str
|
|
PRInt32 numFront = aCStr.Find(aLeadingString);
|
|
if (numFront == -1)
|
|
return -1;
|
|
numFront += nsCRT::strlen(aLeadingString);
|
|
|
|
PRInt32 numBack = aCStr.FindCharInSet(CRLF, numFront);
|
|
if (numBack == -1)
|
|
return -1;
|
|
|
|
nsCAutoString numStr(Substring(aCStr, numFront, numBack-numFront));
|
|
PRInt32 errorCode;
|
|
return numStr.ToInteger(&errorCode);
|
|
}
|
|
|
|
nsresult
|
|
RemoveFragComments(nsCString & aStr)
|
|
{
|
|
// remove the StartFragment/EndFragment comments from the str, if present
|
|
PRInt32 startCommentIndx = aStr.Find("<!--StartFragment");
|
|
if (startCommentIndx >= 0)
|
|
{
|
|
PRInt32 startCommentEnd = aStr.Find("-->", PR_FALSE, startCommentIndx);
|
|
if (startCommentEnd > startCommentIndx)
|
|
aStr.Cut(startCommentIndx, (startCommentEnd+3)-startCommentIndx);
|
|
}
|
|
PRInt32 endCommentIndx = aStr.Find("<!--EndFragment");
|
|
if (endCommentIndx >= 0)
|
|
{
|
|
PRInt32 endCommentEnd = aStr.Find("-->", PR_FALSE, endCommentIndx);
|
|
if (endCommentEnd > endCommentIndx)
|
|
aStr.Cut(endCommentIndx, (endCommentEnd+3)-endCommentIndx);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, PRUnichar **aStuffToPaste, PRUnichar **aCfcontext)
|
|
{
|
|
// first obtain offsets from cfhtml str
|
|
PRInt32 startHTML = FindPositiveIntegerAfterString("StartHTML:", aCfhtml);
|
|
PRInt32 endHTML = FindPositiveIntegerAfterString("EndHTML:", aCfhtml);
|
|
PRInt32 startFragment = FindPositiveIntegerAfterString("StartFragment:", aCfhtml);
|
|
PRInt32 endFragment = FindPositiveIntegerAfterString("EndFragment:", aCfhtml);
|
|
|
|
if ((startHTML<0) || (endHTML<0) || (startFragment<0) || (endFragment<0))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// create context string
|
|
nsCAutoString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) +
|
|
Substring(aCfhtml, endFragment, endHTML - endFragment));
|
|
|
|
// create fragment string
|
|
nsCAutoString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment));
|
|
|
|
// remove the StartFragment/EndFragment comments from the fragment, if present
|
|
RemoveFragComments(fragmentUTF8);
|
|
|
|
// remove the StartFragment/EndFragment comments from the context, if present
|
|
RemoveFragComments(contextUTF8);
|
|
|
|
// convert both strings to usc2
|
|
const nsAFlatString& fragUcs2Str = NS_ConvertUTF8toUCS2(fragmentUTF8);
|
|
const nsAFlatString& cntxtUcs2Str = NS_ConvertUTF8toUCS2(contextUTF8);
|
|
|
|
// translate platform linebreaks for fragment
|
|
PRUnichar* newStr = 0;
|
|
PRInt32 oldLengthInChars = fragUcs2Str.Length() + 1; // +1 to include null terminator
|
|
PRInt32 newLengthInChars = 0;
|
|
*aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(),
|
|
nsLinebreakConverter::eLinebreakAny,
|
|
nsLinebreakConverter::eLinebreakContent,
|
|
oldLengthInChars, &newLengthInChars);
|
|
if (!aStuffToPaste)
|
|
{
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// translate platform linebreaks for context
|
|
newStr = 0;
|
|
oldLengthInChars = cntxtUcs2Str.Length() + 1; // +1 to include null terminator
|
|
newLengthInChars = 0;
|
|
*aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(),
|
|
nsLinebreakConverter::eLinebreakAny,
|
|
nsLinebreakConverter::eLinebreakContent,
|
|
oldLengthInChars, &newLengthInChars);
|
|
// it's ok for context to be empty. frag might be whole doc and contain all it's context.
|
|
|
|
// we're done!
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable,
|
|
const nsAString & aContextStr,
|
|
const nsAString & aInfoStr)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
char* bestFlavor = nsnull;
|
|
nsCOMPtr<nsISupports> genericDataObj;
|
|
PRUint32 len = 0;
|
|
if ( NS_SUCCEEDED(transferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len)) )
|
|
{
|
|
nsAutoTxnsConserveSelection dontSpazMySelection(this);
|
|
nsAutoString flavor, stuffToPaste;
|
|
flavor.AssignWithConversion(bestFlavor); // just so we can use flavor.Equals()
|
|
#ifdef DEBUG_clipboard
|
|
printf("Got flavor [%s]\n", bestFlavor);
|
|
#endif
|
|
|
|
if (flavor.Equals(NS_LITERAL_STRING(kNativeHTMLMime)))
|
|
{
|
|
// note cf_html uses utf8, hence use length = len, not len/2 as in flavors below
|
|
nsCOMPtr<nsISupportsCString> textDataObj(do_QueryInterface(genericDataObj));
|
|
if (textDataObj && len > 0)
|
|
{
|
|
nsCAutoString cfhtml;
|
|
textDataObj->GetData(cfhtml);
|
|
NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!");
|
|
nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
|
|
|
|
rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
|
|
if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTMLWithContext(cffragment, cfcontext, cfselection);
|
|
}
|
|
}
|
|
}
|
|
else if (flavor.Equals(NS_LITERAL_STRING(kHTMLMime)))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
|
|
if (textDataObj && len > 0)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
|
|
stuffToPaste.Assign(text.get(), len / 2);
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTMLWithContext(stuffToPaste, aContextStr, aInfoStr);
|
|
}
|
|
}
|
|
else if (flavor.Equals(NS_LITERAL_STRING(kUnicodeMime)))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
|
|
if (textDataObj && len > 0)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
|
|
stuffToPaste.Assign(text.get(), len / 2);
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertText(stuffToPaste);
|
|
}
|
|
}
|
|
else if (flavor.Equals(NS_LITERAL_STRING(kFileMime)))
|
|
{
|
|
nsCOMPtr<nsIFile> fileObj(do_QueryInterface(genericDataObj));
|
|
if (fileObj && len > 0)
|
|
{
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = NS_NewFileURI(getter_AddRefs(uri), fileObj);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsCOMPtr<nsIURL> fileURL(do_QueryInterface(uri));
|
|
if (fileURL)
|
|
{
|
|
PRBool insertAsImage = PR_FALSE;
|
|
nsCAutoString fileextension;
|
|
rv = fileURL->GetFileExtension(fileextension);
|
|
if (NS_SUCCEEDED(rv) && !fileextension.IsEmpty())
|
|
{
|
|
if ( (nsCRT::strcasecmp(fileextension.get(), "jpg") == 0 )
|
|
|| (nsCRT::strcasecmp(fileextension.get(), "jpeg") == 0 )
|
|
|| (nsCRT::strcasecmp(fileextension.get(), "gif") == 0 )
|
|
|| (nsCRT::strcasecmp(fileextension.get(), "png") == 0 ) )
|
|
{
|
|
insertAsImage = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
nsCAutoString urltext;
|
|
rv = fileURL->GetSpec(urltext);
|
|
if (NS_SUCCEEDED(rv) && !urltext.IsEmpty())
|
|
{
|
|
if (insertAsImage)
|
|
{
|
|
stuffToPaste.Assign(NS_LITERAL_STRING("<IMG src=\""));
|
|
stuffToPaste.Append(NS_ConvertUTF8toUCS2(urltext));
|
|
stuffToPaste.Append(NS_LITERAL_STRING("\" alt=\"\" >"));
|
|
}
|
|
else /* insert as link */
|
|
{
|
|
stuffToPaste.Assign(NS_LITERAL_STRING("<A href=\""));
|
|
stuffToPaste.Append(NS_ConvertUTF8toUCS2(urltext));
|
|
stuffToPaste.Append(NS_LITERAL_STRING("\">"));
|
|
stuffToPaste.Append(NS_ConvertUTF8toUCS2(urltext));
|
|
stuffToPaste.Append(NS_LITERAL_STRING("</A>"));
|
|
}
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertHTML(stuffToPaste);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (flavor.Equals(NS_LITERAL_STRING(kJPEGImageMime)))
|
|
{
|
|
// Insert Image code here
|
|
printf("Don't know how to insert an image yet!\n");
|
|
//nsIImage* image = (nsIImage *)data;
|
|
//NS_RELEASE(image);
|
|
rv = NS_ERROR_NOT_IMPLEMENTED; // for now give error code
|
|
}
|
|
}
|
|
nsCRT::free(bestFlavor);
|
|
|
|
// Try to scroll the selection into view if the paste/drop succeeded
|
|
if (NS_SUCCEEDED(rv))
|
|
ScrollSelectionIntoView(PR_FALSE);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIDragService> dragService =
|
|
do_GetService("@mozilla.org/widget/dragservice;1", &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIDragSession> dragSession;
|
|
dragService->GetCurrentSession(getter_AddRefs(dragSession));
|
|
if (!dragSession) return NS_OK;
|
|
|
|
// find out if we have our internal html flavor on the clipboard. We don't want to mess
|
|
// around with cfhtml if we do.
|
|
PRBool bHavePrivateHTMLFlavor = PR_FALSE;
|
|
rv = dragSession->IsDataFlavorSupported(kHTMLContext, &bHavePrivateHTMLFlavor);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Get the nsITransferable interface for getting the data from the drop
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!trans) return NS_OK; // NS_ERROR_FAILURE; SHOULD WE FAIL?
|
|
|
|
PRUint32 numItems = 0;
|
|
rv = dragSession->GetNumDropItems(&numItems);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Combine any deletion and drop insertion into one transaction
|
|
nsAutoEditBatch beginBatching(this);
|
|
|
|
PRUint32 i;
|
|
PRBool doPlaceCaret = PR_TRUE;
|
|
for (i = 0; i < numItems; ++i)
|
|
{
|
|
rv = dragSession->GetData(trans, i);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!trans) return NS_OK; // NS_ERROR_FAILURE; Should we fail?
|
|
|
|
// get additional html copy hints, if present
|
|
nsAutoString contextStr, infoStr;
|
|
nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
|
|
PRUint32 contextLen, infoLen;
|
|
nsCOMPtr<nsISupportsString> textDataObj;
|
|
|
|
nsCOMPtr<nsITransferable> contextTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
|
|
contextTrans->AddDataFlavor(kHTMLContext);
|
|
dragSession->GetData(contextTrans, i);
|
|
contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
|
|
|
|
nsCOMPtr<nsITransferable> infoTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
|
|
infoTrans->AddDataFlavor(kHTMLInfo);
|
|
dragSession->GetData(infoTrans, i);
|
|
infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
|
|
|
|
if (contextDataObj)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj = do_QueryInterface(contextDataObj);
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
|
|
contextStr.Assign(text.get(), contextLen / 2);
|
|
}
|
|
|
|
if (infoDataObj)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj = do_QueryInterface(infoDataObj);
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
|
|
infoStr.Assign(text.get(), infoLen / 2);
|
|
}
|
|
|
|
if (doPlaceCaret)
|
|
{
|
|
// check if the user pressed the key to force a copy rather than a move
|
|
// if we run into problems here, we'll just assume the user doesn't want a copy
|
|
PRBool userWantsCopy = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent(do_QueryInterface(aDropEvent));
|
|
if (!nsuiEvent) return NS_ERROR_FAILURE;
|
|
|
|
nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aDropEvent));
|
|
if (mouseEvent)
|
|
|
|
#ifdef XP_MAC
|
|
mouseEvent->GetAltKey(&userWantsCopy);
|
|
#else
|
|
mouseEvent->GetCtrlKey(&userWantsCopy);
|
|
#endif
|
|
// Source doc is null if source is *not* the current editor document
|
|
nsCOMPtr<nsIDOMDocument> srcdomdoc;
|
|
rv = dragSession->GetSourceDocument(getter_AddRefs(srcdomdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Current doc is destination
|
|
nsCOMPtr<nsIDOMDocument>destdomdoc;
|
|
rv = GetDocument(getter_AddRefs(destdomdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_FAILURE;
|
|
|
|
PRBool isCollapsed;
|
|
rv = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Parent and offset under the mouse cursor
|
|
nsCOMPtr<nsIDOMNode> newSelectionParent;
|
|
PRInt32 newSelectionOffset = 0;
|
|
rv = nsuiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!newSelectionParent) return NS_ERROR_FAILURE;
|
|
|
|
rv = nsuiEvent->GetRangeOffset(&newSelectionOffset);
|
|
if (NS_FAILED(rv)) return rv;
|
|
/* Creating a range to store insert position because when
|
|
we delete the selection, range gravity will make sure the insertion
|
|
point is in the correct place */
|
|
nsCOMPtr<nsIDOMRange> destinationRange;
|
|
rv = CreateRange(newSelectionParent, newSelectionOffset,newSelectionParent, newSelectionOffset, getter_AddRefs(destinationRange));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if(!destinationRange)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// We never have to delete if selection is already collapsed
|
|
PRBool deleteSelection = PR_FALSE;
|
|
PRBool cursorIsInSelection = PR_FALSE;
|
|
|
|
// Check if mouse is in the selection
|
|
if (!isCollapsed)
|
|
{
|
|
PRInt32 rangeCount;
|
|
rv = selection->GetRangeCount(&rangeCount);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
for (PRInt32 j = 0; j < rangeCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMRange> range;
|
|
|
|
rv = selection->GetRangeAt(j, getter_AddRefs(range));
|
|
if (NS_FAILED(rv) || !range)
|
|
continue;//dont bail yet, iterate through them all
|
|
|
|
nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
|
|
if (NS_FAILED(rv) || !nsrange)
|
|
continue;//dont bail yet, iterate through them all
|
|
|
|
rv = nsrange->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
|
|
if(cursorIsInSelection)
|
|
break;
|
|
}
|
|
if (cursorIsInSelection)
|
|
{
|
|
// Dragging within same doc can't drop on itself -- leave!
|
|
// (We shouldn't get here - drag event shouldn't have started if over selection)
|
|
if (srcdomdoc == destdomdoc)
|
|
return NS_OK;
|
|
|
|
// Dragging from another window onto a selection
|
|
// XXX Decision made to NOT do this,
|
|
// note that 4.x does replace if dropped on
|
|
//deleteSelection = PR_TRUE;
|
|
}
|
|
else
|
|
{
|
|
// We are NOT over the selection
|
|
if (srcdomdoc == destdomdoc)
|
|
{
|
|
// Within the same doc: delete if user doesn't want to copy
|
|
deleteSelection = !userWantsCopy;
|
|
}
|
|
else
|
|
{
|
|
// Different source doc: Don't delete
|
|
deleteSelection = PR_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleteSelection)
|
|
{
|
|
rv = DeleteSelection(eNone);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// If we deleted the selection because we dropped from another doc,
|
|
// then we don't have to relocate the caret (insert at the deletion point)
|
|
if (!(deleteSelection && srcdomdoc != destdomdoc))
|
|
{
|
|
// Move the selection to the point under the mouse cursor
|
|
rv = destinationRange->GetStartContainer(getter_AddRefs(newSelectionParent));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if(!newSelectionParent)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
rv = destinationRange->GetStartOffset(&newSelectionOffset);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
selection->Collapse(newSelectionParent, newSelectionOffset);
|
|
}
|
|
// We have to figure out whether to delete and relocate caret only once
|
|
doPlaceCaret = PR_FALSE;
|
|
}
|
|
|
|
rv = InsertFromTransferable(trans, contextStr, infoStr);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanDrag(nsIDOMEvent *aDragEvent, PRBool *aCanDrag)
|
|
{
|
|
if (!aCanDrag)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
/* we really should be checking the XY coordinates of the mouseevent and ensure that
|
|
* that particular point is actually within the selection (not just that there is a selection)
|
|
*/
|
|
*aCanDrag = PR_FALSE;
|
|
|
|
// KLUDGE to work around bug 50703
|
|
// After double click and object property editing,
|
|
// we get a spurious drag event
|
|
if (mIgnoreSpuriousDragEvent)
|
|
{
|
|
#ifdef DEBUG_cmanske
|
|
printf(" *** IGNORING SPURIOUS DRAG EVENT!\n");
|
|
#endif
|
|
mIgnoreSpuriousDragEvent = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
PRBool isCollapsed;
|
|
res = selection->GetIsCollapsed(&isCollapsed);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
// if we are collapsed, we have no selection so nothing to drag
|
|
if (isCollapsed)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> eventTarget;
|
|
|
|
nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aDragEvent));
|
|
|
|
if (nsevent)
|
|
{
|
|
res = nsevent->GetOriginalTarget(getter_AddRefs(eventTarget));
|
|
if (NS_FAILED(res))
|
|
{
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (eventTarget)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> eventTargetDomNode = do_QueryInterface(eventTarget);
|
|
if (eventTargetDomNode)
|
|
{
|
|
PRBool isTargetedCorrectly = PR_FALSE;
|
|
res = selection->ContainsNode(eventTargetDomNode, PR_FALSE, &isTargetedCorrectly);
|
|
if (NS_FAILED(res)) return res;
|
|
|
|
*aCanDrag = isTargetedCorrectly;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::DoDrag(nsIDOMEvent *aDragEvent)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIDOMEventTarget> eventTarget;
|
|
rv = aDragEvent->GetTarget(getter_AddRefs(eventTarget));
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsCOMPtr<nsIDOMNode> domnode = do_QueryInterface(eventTarget);
|
|
|
|
/* get the selection to be dragged */
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* create an array of transferables */
|
|
nsCOMPtr<nsISupportsArray> transferableArray;
|
|
NS_NewISupportsArray(getter_AddRefs(transferableArray));
|
|
if (transferableArray == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
/* get the drag service */
|
|
nsCOMPtr<nsIDragService> dragService =
|
|
do_GetService("@mozilla.org/widget/dragservice;1", &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* create html flavor transferable */
|
|
nsCOMPtr<nsITransferable> trans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
|
|
|
|
nsCOMPtr<nsIDOMDocument> domdoc;
|
|
rv = GetDocument(getter_AddRefs(domdoc));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
|
|
if (doc)
|
|
{
|
|
// find out if we're a plaintext control or not
|
|
PRUint32 editorFlags = 0;
|
|
rv = GetFlags(&editorFlags);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRBool bIsPlainTextControl = ((editorFlags & eEditorPlaintextMask) != 0);
|
|
|
|
// get correct mimeType and document encoder flags set
|
|
nsAutoString mimeType;
|
|
PRUint32 docEncoderFlags = 0;
|
|
if (bIsPlainTextControl)
|
|
{
|
|
docEncoderFlags |= nsIDocumentEncoder::OutputBodyOnly | nsIDocumentEncoder::OutputPreformatted;
|
|
mimeType = NS_LITERAL_STRING(kUnicodeMime);
|
|
}
|
|
else
|
|
mimeType = NS_LITERAL_STRING(kHTMLMime);
|
|
|
|
// set up docEncoder
|
|
nsCOMPtr<nsIDocumentEncoder> docEncoder = do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID);
|
|
NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
|
|
|
|
rv = docEncoder->Init(doc, mimeType, docEncoderFlags);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = docEncoder->SetSelection(selection);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// grab a string
|
|
nsAutoString buffer, parents, info;
|
|
|
|
if (!bIsPlainTextControl)
|
|
{
|
|
// encode the selection as html with contextual info
|
|
rv = docEncoder->EncodeToStringWithContext(buffer, parents, info);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
else
|
|
{
|
|
// encode the selection
|
|
rv = docEncoder->EncodeToString(buffer);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// if we have an empty string, we're done; otherwise continue
|
|
if (!buffer.IsEmpty())
|
|
{
|
|
nsCOMPtr<nsISupportsString> dataWrapper, contextWrapper, infoWrapper;
|
|
|
|
dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
|
|
NS_ENSURE_TRUE(dataWrapper, NS_ERROR_FAILURE);
|
|
rv = dataWrapper->SetData(buffer);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (bIsPlainTextControl)
|
|
{
|
|
// Add the unicode flavor to the transferable
|
|
rv = trans->AddDataFlavor(kUnicodeMime);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// QI the data object an |nsISupports| so that when the transferable holds
|
|
// onto it, it will addref the correct interface.
|
|
nsCOMPtr<nsISupports> genericDataObj(do_QueryInterface(dataWrapper));
|
|
rv = trans->SetTransferData(kUnicodeMime, genericDataObj, buffer.Length() * 2);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
else
|
|
{
|
|
contextWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
|
|
NS_ENSURE_TRUE(contextWrapper, NS_ERROR_FAILURE);
|
|
infoWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
|
|
NS_ENSURE_TRUE(infoWrapper, NS_ERROR_FAILURE);
|
|
|
|
contextWrapper->SetData(parents);
|
|
infoWrapper->SetData(info);
|
|
|
|
rv = trans->AddDataFlavor(kHTMLMime);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIFormatConverter> htmlConverter = do_CreateInstance(kCHTMLFormatConverterCID);
|
|
NS_ENSURE_TRUE(htmlConverter, NS_ERROR_FAILURE);
|
|
|
|
rv = trans->SetConverter(htmlConverter);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsISupports> genericDataObj(do_QueryInterface(dataWrapper));
|
|
rv = trans->SetTransferData(kHTMLMime, genericDataObj, buffer.Length() * 2);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (parents.Length())
|
|
{
|
|
// Add the htmlcontext DataFlavor to the transferable
|
|
trans->AddDataFlavor(kHTMLContext);
|
|
genericDataObj = do_QueryInterface(contextWrapper);
|
|
trans->SetTransferData(kHTMLContext, genericDataObj, parents.Length()*2);
|
|
}
|
|
if (info.Length())
|
|
{
|
|
// Add the htmlinfo DataFlavor to the transferable
|
|
trans->AddDataFlavor(kHTMLInfo);
|
|
genericDataObj = do_QueryInterface(infoWrapper);
|
|
trans->SetTransferData(kHTMLInfo, genericDataObj, info.Length()*2);
|
|
}
|
|
}
|
|
|
|
/* add the transferable to the array */
|
|
rv = transferableArray->AppendElement(trans);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
/* invoke drag */
|
|
unsigned int flags;
|
|
// in some cases we'll want to cut rather than copy... hmmmmm...
|
|
flags = nsIDragService::DRAGDROP_ACTION_COPY + nsIDragService::DRAGDROP_ACTION_MOVE;
|
|
|
|
rv = dragService->InvokeDragSession(domnode, transferableArray, nsnull, flags);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aDragEvent));
|
|
|
|
if (nsevent)
|
|
{
|
|
nsevent->PreventBubble();
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
PRBool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard)
|
|
{
|
|
// check the clipboard for our special kHTMLContext flavor. If that is there, we know
|
|
// we have our own internal html format on clipboard.
|
|
|
|
if (!aClipboard) return PR_FALSE;
|
|
PRBool bHavePrivateHTMLFlavor = PR_FALSE;
|
|
nsCOMPtr<nsISupportsArray> flavArray;
|
|
|
|
nsresult res = NS_NewISupportsArray(getter_AddRefs(flavArray));
|
|
if (NS_FAILED(res)) return PR_FALSE;
|
|
|
|
nsCOMPtr<nsISupportsCString> contextString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
|
if (!contextString) return PR_FALSE;
|
|
|
|
contextString->SetData(NS_LITERAL_CSTRING(kHTMLContext));
|
|
|
|
flavArray->AppendElement(contextString);
|
|
|
|
if (NS_SUCCEEDED(aClipboard->HasDataMatchingFlavors (flavArray, nsIClipboard::kGlobalClipboard, &bHavePrivateHTMLFlavor )))
|
|
return bHavePrivateHTMLFlavor;
|
|
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// find out if we have our internal html flavor on the clipboard. We don't want to mess
|
|
// around with cfhtml if we do.
|
|
PRBool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard);
|
|
|
|
// Get the nsITransferable interface for getting the data from the clipboard
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor);
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// Get the Data from the clipboard
|
|
if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
|
|
{
|
|
// also get additional html copy hints, if present
|
|
nsAutoString contextStr, infoStr;
|
|
|
|
// also get additional html copy hints, if present
|
|
if (bHavePrivateHTMLFlavor)
|
|
{
|
|
nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
|
|
PRUint32 contextLen, infoLen;
|
|
nsCOMPtr<nsISupportsString> textDataObj;
|
|
|
|
nsCOMPtr<nsITransferable> contextTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
|
|
contextTrans->AddDataFlavor(kHTMLContext);
|
|
clipboard->GetData(contextTrans, aSelectionType);
|
|
contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
|
|
|
|
nsCOMPtr<nsITransferable> infoTrans = do_CreateInstance(kCTransferableCID);
|
|
NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
|
|
infoTrans->AddDataFlavor(kHTMLInfo);
|
|
clipboard->GetData(infoTrans, aSelectionType);
|
|
infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
|
|
|
|
if (contextDataObj)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj = do_QueryInterface(contextDataObj);
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
|
|
contextStr.Assign(text.get(), contextLen / 2);
|
|
}
|
|
|
|
if (infoDataObj)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj = do_QueryInterface(infoDataObj);
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
|
|
infoStr.Assign(text.get(), infoLen / 2);
|
|
}
|
|
}
|
|
rv = InsertFromTransferable(trans, contextStr, infoStr);
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
//
|
|
// HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(PRInt32 aSelectionType)
|
|
{
|
|
ForceCompositionEnd();
|
|
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// Get the nsITransferable interface for getting the data from the clipboard.
|
|
// use nsPlaintextEditor::PrepareTransferable() to force unicode plaintext data.
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = nsPlaintextEditor::PrepareTransferable(getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// Get the Data from the clipboard
|
|
if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
|
|
{
|
|
rv = InsertFromTransferable(trans, nsString(), nsString());
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::CanPaste(PRInt32 aSelectionType, PRBool *aCanPaste)
|
|
{
|
|
if (!aCanPaste)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*aCanPaste = PR_FALSE;
|
|
|
|
// can't paste if readonly
|
|
if (!IsModifiable())
|
|
return NS_OK;
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// the flavors that we can deal with
|
|
const char* const textEditorFlavors[] = { kUnicodeMime, nsnull };
|
|
const char* const htmlEditorFlavors[] = { kHTMLMime, kJPEGImageMime, nsnull };
|
|
|
|
nsCOMPtr<nsISupportsArray> flavorsList;
|
|
rv = nsComponentManager::CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, nsnull,
|
|
NS_GET_IID(nsISupportsArray), getter_AddRefs(flavorsList));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 editorFlags;
|
|
GetFlags(&editorFlags);
|
|
|
|
// add the flavors for all editors
|
|
for (const char* const* flavor = textEditorFlavors; *flavor; flavor++)
|
|
{
|
|
nsCOMPtr<nsISupportsCString> flavorString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
|
if (flavorString)
|
|
{
|
|
flavorString->SetData(nsDependentCString(*flavor));
|
|
flavorsList->AppendElement(flavorString);
|
|
}
|
|
}
|
|
|
|
// add the HTML-editor only flavors
|
|
if ((editorFlags & eEditorPlaintextMask) == 0)
|
|
{
|
|
for (const char* const* htmlFlavor = htmlEditorFlavors;
|
|
*htmlFlavor;
|
|
htmlFlavor++)
|
|
{
|
|
nsCOMPtr<nsISupportsCString> flavorString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
|
if (flavorString)
|
|
{
|
|
flavorString->SetData(nsDependentCString(*htmlFlavor));
|
|
flavorsList->AppendElement(flavorString);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRBool haveFlavors;
|
|
rv = clipboard->HasDataMatchingFlavors(flavorsList, aSelectionType, &haveFlavors);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
*aCanPaste = haveFlavors;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//
|
|
// HTML PasteAsQuotation: Paste in a blockquote type=cite
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(PRInt32 aSelectionType)
|
|
{
|
|
if (mFlags & eEditorPlaintextMask)
|
|
return PasteAsPlaintextQuotation(aSelectionType);
|
|
|
|
nsAutoString citation;
|
|
return PasteAsCitedQuotation(citation, aSelectionType);
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation,
|
|
PRInt32 aSelectionType)
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
nsresult res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!newNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Try to set type=cite. Ignore it if this fails.
|
|
nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
|
|
if (newElement)
|
|
{
|
|
newElement->SetAttribute(NS_LITERAL_STRING("type"), NS_LITERAL_STRING("cite"));
|
|
}
|
|
|
|
// Set the selection to the underneath the node we just inserted:
|
|
res = selection->Collapse(newNode, 0);
|
|
if (NS_FAILED(res))
|
|
{
|
|
#ifdef DEBUG_akkana
|
|
printf("Couldn't collapse");
|
|
#endif
|
|
// XXX: error result: should res be returned here?
|
|
}
|
|
|
|
res = Paste(aSelectionType);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
//
|
|
// Paste a plaintext quotation
|
|
//
|
|
NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(PRInt32 aSelectionType)
|
|
{
|
|
// Get Clipboard Service
|
|
nsresult rv;
|
|
nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Create generic Transferable for getting the data
|
|
nsCOMPtr<nsITransferable> trans;
|
|
rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
|
|
NS_GET_IID(nsITransferable),
|
|
(void**) getter_AddRefs(trans));
|
|
if (NS_SUCCEEDED(rv) && trans)
|
|
{
|
|
// We only handle plaintext pastes here
|
|
trans->AddDataFlavor(kUnicodeMime);
|
|
|
|
// Get the Data from the clipboard
|
|
clipboard->GetData(trans, aSelectionType);
|
|
|
|
// Now we ask the transferable for the data
|
|
// it still owns the data, we just have a pointer to it.
|
|
// If it can't support a "text" output of the data the call will fail
|
|
nsCOMPtr<nsISupports> genericDataObj;
|
|
PRUint32 len = 0;
|
|
char* flav = 0;
|
|
rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj),
|
|
&len);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
#ifdef DEBUG_akkana
|
|
printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv);
|
|
#endif
|
|
return rv;
|
|
}
|
|
#ifdef DEBUG_clipboard
|
|
printf("Got flavor [%s]\n", flav);
|
|
#endif
|
|
nsAutoString flavor; flavor.AssignWithConversion(flav);
|
|
nsAutoString stuffToPaste;
|
|
if (flavor.Equals(NS_LITERAL_STRING(kUnicodeMime)))
|
|
{
|
|
nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
|
|
if (textDataObj && len > 0)
|
|
{
|
|
nsAutoString text;
|
|
textDataObj->GetData(text);
|
|
NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
|
|
stuffToPaste.Assign(text.get(), len / 2);
|
|
nsAutoEditBatch beginBatching(this);
|
|
rv = InsertAsPlaintextQuotation(stuffToPaste, PR_TRUE, 0);
|
|
}
|
|
}
|
|
nsCRT::free(flav);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
|
|
{
|
|
if (mWrapToWindow)
|
|
return InsertText(aStringToInsert);
|
|
|
|
// The whole operation should be undoable in one transaction:
|
|
BeginTransaction();
|
|
|
|
// We're going to loop over the string, collecting up a "hunk"
|
|
// that's all the same type (quoted or not),
|
|
// Whenever the quotedness changes (or we reach the string's end)
|
|
// we will insert the hunk all at once, quoted or non.
|
|
|
|
static const PRUnichar cite('>');
|
|
PRBool curHunkIsQuoted = (aStringToInsert.First() == cite);
|
|
|
|
nsAString::const_iterator hunkStart, strEnd;
|
|
aStringToInsert.BeginReading(hunkStart);
|
|
aStringToInsert.EndReading(strEnd);
|
|
|
|
// In the loop below, we only look for DOM newlines (\n),
|
|
// because we don't have a FindChars method that can look
|
|
// for both \r and \n. \r is illegal in the dom anyway,
|
|
// but in debug builds, let's take the time to verify that
|
|
// there aren't any there:
|
|
#ifdef DEBUG
|
|
nsAString::const_iterator dbgStart (hunkStart);
|
|
if (FindCharInReadable('\r', dbgStart, strEnd))
|
|
NS_ASSERTION(PR_FALSE,
|
|
"Return characters in DOM! InsertTextWithQuotations may be wrong");
|
|
#endif /* DEBUG */
|
|
|
|
// Loop over lines:
|
|
nsresult rv = NS_OK;
|
|
nsAString::const_iterator lineStart (hunkStart);
|
|
while (1) // we will break from inside when we run out of newlines
|
|
{
|
|
// Search for the end of this line (dom newlines, see above):
|
|
PRBool found = FindCharInReadable('\n', lineStart, strEnd);
|
|
PRBool quoted = PR_FALSE;
|
|
if (found)
|
|
{
|
|
// if there's another newline, lineStart now points there.
|
|
// Loop over any consecutive newline chars:
|
|
nsAString::const_iterator firstNewline (lineStart);
|
|
while (*lineStart == '\n')
|
|
++lineStart;
|
|
quoted = (*lineStart == cite);
|
|
if (quoted == curHunkIsQuoted)
|
|
continue;
|
|
// else we're changing state, so we need to insert
|
|
// from curHunk to lineStart then loop around.
|
|
|
|
// But if the current hunk is quoted, then we want to make sure
|
|
// that any extra newlines on the end do not get included in
|
|
// the quoted section: blank lines flaking a quoted section
|
|
// should be considered unquoted, so that if the user clicks
|
|
// there and starts typing, the new text will be outside of
|
|
// the quoted block.
|
|
if (curHunkIsQuoted)
|
|
lineStart = firstNewline;
|
|
}
|
|
|
|
// If no newline found, lineStart is now strEnd and we can finish up,
|
|
// inserting from curHunk to lineStart then returning.
|
|
const nsAString &curHunk = Substring(hunkStart, lineStart);
|
|
nsCOMPtr<nsIDOMNode> dummyNode;
|
|
#ifdef DEBUG_akkana_verbose
|
|
printf("==== Inserting text as %squoted: ---\n%s---\n",
|
|
curHunkIsQuoted ? "" : "non-",
|
|
NS_LossyConvertUCS2toASCII(curHunk).get());
|
|
#endif
|
|
if (curHunkIsQuoted)
|
|
rv = InsertAsPlaintextQuotation(curHunk, PR_FALSE,
|
|
getter_AddRefs(dummyNode));
|
|
else
|
|
rv = InsertText(curHunk);
|
|
|
|
if (!found)
|
|
break;
|
|
|
|
curHunkIsQuoted = quoted;
|
|
hunkStart = lineStart;
|
|
}
|
|
|
|
EndTransaction();
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAString & aQuotedText,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
if (mFlags & eEditorPlaintextMask)
|
|
return InsertAsPlaintextQuotation(aQuotedText, PR_TRUE, aNodeInserted);
|
|
|
|
nsAutoString citation;
|
|
nsAutoString charset;
|
|
return InsertAsCitedQuotation(aQuotedText, citation, PR_FALSE,
|
|
charset, aNodeInserted);
|
|
}
|
|
|
|
// Insert plaintext as a quotation, with cite marks (e.g. "> ").
|
|
// This differs from its corresponding method in nsPlaintextEditor
|
|
// in that here, quoted material is enclosed in a <pre> tag
|
|
// in order to preserve the original line wrapping.
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString & aQuotedText,
|
|
PRBool aAddCites,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
if (mWrapToWindow)
|
|
return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
|
|
|
|
nsresult rv;
|
|
|
|
// The quotesPreformatted pref is a temporary measure. See bug 69638.
|
|
// Eventually we'll pick one way or the other.
|
|
PRBool quotesInPre;
|
|
nsCOMPtr<nsIPrefBranch> prefBranch =
|
|
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
|
|
if (NS_SUCCEEDED(rv) && prefBranch)
|
|
prefBranch->GetBoolPref("editor.quotesPreformatted", "esInPre);
|
|
|
|
nsCOMPtr<nsIDOMNode> preNode;
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
rv = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
else
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
// Wrap the inserted quote in a <pre> so it won't be wrapped:
|
|
nsAutoString tag;
|
|
if (quotesInPre)
|
|
tag.Assign(NS_LITERAL_STRING("pre"));
|
|
else
|
|
tag.Assign(NS_LITERAL_STRING("span"));
|
|
|
|
rv = DeleteSelectionAndCreateNode(tag, getter_AddRefs(preNode));
|
|
|
|
// If this succeeded, then set selection inside the pre
|
|
// so the inserted text will end up there.
|
|
// If it failed, we don't care what the return value was,
|
|
// but we'll fall through and try to insert the text anyway.
|
|
if (NS_SUCCEEDED(rv) && preNode)
|
|
{
|
|
// Add an attribute on the pre node so we'll know it's a quotation.
|
|
// Do this after the insertion, so that
|
|
nsCOMPtr<nsIDOMElement> preElement (do_QueryInterface(preNode));
|
|
if (preElement)
|
|
{
|
|
preElement->SetAttribute(NS_LITERAL_STRING("_moz_quote"),
|
|
NS_LITERAL_STRING("true"));
|
|
if (quotesInPre)
|
|
{
|
|
// set style to not have unwanted vertical margins
|
|
preElement->SetAttribute(NS_LITERAL_STRING("style"),
|
|
NS_LITERAL_STRING("margin: 0 0 0 0px;"));
|
|
}
|
|
else
|
|
{
|
|
// turn off wrapping on spans
|
|
preElement->SetAttribute(NS_LITERAL_STRING("style"),
|
|
NS_LITERAL_STRING("white-space: pre;"));
|
|
}
|
|
}
|
|
|
|
// and set the selection inside it:
|
|
selection->Collapse(preNode, 0);
|
|
}
|
|
|
|
if (aAddCites)
|
|
rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
|
|
else
|
|
rv = nsPlaintextEditor::InsertText(aQuotedText);
|
|
// Note that if !aAddCites, aNodeInserted isn't set.
|
|
// That's okay because the routines that use aAddCites
|
|
// don't need to know the inserted node.
|
|
|
|
if (aNodeInserted && NS_SUCCEEDED(rv))
|
|
{
|
|
*aNodeInserted = preNode;
|
|
NS_IF_ADDREF(*aNodeInserted);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the selection to just after the inserted node:
|
|
if (NS_SUCCEEDED(rv) && preNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
if (NS_SUCCEEDED(GetNodeLocation(preNode, address_of(parent), &offset)) && parent)
|
|
selection->Collapse(parent, offset+1);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsHTMLEditor::InsertAsCitedQuotation(const nsAString & aQuotedText,
|
|
const nsAString & aCitation,
|
|
PRBool aInsertHTML,
|
|
const nsAString & aCharset,
|
|
nsIDOMNode **aNodeInserted)
|
|
{
|
|
// Don't let anyone insert html into a "plaintext" editor:
|
|
if (mFlags & eEditorPlaintextMask)
|
|
{
|
|
NS_ASSERTION(!aInsertHTML, "InsertAsCitedQuotation: trying to insert html into plaintext editor");
|
|
return InsertAsPlaintextQuotation(aQuotedText, PR_TRUE, aNodeInserted);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
nsresult res = NS_OK;
|
|
|
|
// get selection
|
|
nsCOMPtr<nsISelection> selection;
|
|
res = GetSelection(getter_AddRefs(selection));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!selection) return NS_ERROR_NULL_POINTER;
|
|
else
|
|
{
|
|
nsAutoEditBatch beginBatching(this);
|
|
nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
|
|
|
|
// give rules a chance to handle or cancel
|
|
nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
|
|
PRBool cancel, handled;
|
|
res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
|
if (NS_FAILED(res)) return res;
|
|
if (cancel) return NS_OK; // rules canceled the operation
|
|
if (!handled)
|
|
{
|
|
res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
|
|
if (NS_FAILED(res)) return res;
|
|
if (!newNode) return NS_ERROR_NULL_POINTER;
|
|
|
|
// Try to set type=cite. Ignore it if this fails.
|
|
nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
|
|
if (newElement)
|
|
{
|
|
NS_NAMED_LITERAL_STRING(citestr, "cite");
|
|
newElement->SetAttribute(NS_LITERAL_STRING("type"), citestr);
|
|
|
|
if (aCitation.Length() > 0)
|
|
newElement->SetAttribute(citestr, aCitation);
|
|
|
|
// Set the selection inside the blockquote so aQuotedText will go there:
|
|
selection->Collapse(newNode, 0);
|
|
}
|
|
|
|
if (aInsertHTML)
|
|
res = LoadHTMLWithCharset(aQuotedText, aCharset);
|
|
|
|
else
|
|
res = InsertText(aQuotedText); // XXX ignore charset
|
|
|
|
if (aNodeInserted)
|
|
{
|
|
if (NS_SUCCEEDED(res))
|
|
{
|
|
*aNodeInserted = newNode;
|
|
NS_IF_ADDREF(*aNodeInserted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the selection to just after the inserted node:
|
|
if (NS_SUCCEEDED(res) && newNode)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
PRInt32 offset;
|
|
if (NS_SUCCEEDED(GetNodeLocation(newNode, address_of(parent), &offset)) && parent)
|
|
selection->Collapse(parent, offset+1);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
void RemoveBodyAndHead(nsIDOMNode *aNode)
|
|
{
|
|
if (!aNode)
|
|
return;
|
|
|
|
nsCOMPtr<nsIDOMNode> tmp, child, body, head;
|
|
// find the body and head nodes if any.
|
|
// look only at immediate children of aNode.
|
|
aNode->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
if (nsTextEditUtils::IsBody(child))
|
|
{
|
|
body = child;
|
|
}
|
|
else if (nsEditor::NodeIsType(child, NS_LITERAL_STRING("head")))
|
|
{
|
|
head = child;
|
|
}
|
|
child->GetNextSibling(getter_AddRefs(tmp));
|
|
child = tmp;
|
|
}
|
|
if (head)
|
|
{
|
|
aNode->RemoveChild(head, getter_AddRefs(tmp));
|
|
}
|
|
if (body)
|
|
{
|
|
body->GetFirstChild(getter_AddRefs(child));
|
|
while (child)
|
|
{
|
|
aNode->InsertBefore(child, body, getter_AddRefs(tmp));
|
|
body->GetFirstChild(getter_AddRefs(child));
|
|
}
|
|
aNode->RemoveChild(body, getter_AddRefs(tmp));
|
|
}
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(nsIDOMNSRange *aNSRange,
|
|
const nsAString & aInputString,
|
|
const nsAString & aContextStr,
|
|
const nsAString & aInfoStr,
|
|
nsCOMPtr<nsIDOMNode> *outFragNode,
|
|
PRInt32 *outRangeStartHint,
|
|
PRInt32 *outRangeEndHint)
|
|
{
|
|
if (!outFragNode || !outRangeStartHint || !outRangeEndHint || !aNSRange)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
|
|
nsCOMPtr<nsIDOMNode> contextAsNode, tmp;
|
|
nsresult res = NS_OK;
|
|
|
|
// if we have context info, create a fragment for that
|
|
nsCOMPtr<nsIDOMNode> contextLeaf, junk;
|
|
PRInt32 contextDepth = 0;
|
|
if (aContextStr.Length())
|
|
{
|
|
res = ParseFragment(aContextStr, address_of(contextAsNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
|
|
|
|
res = StripFormattingNodes(contextAsNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
RemoveBodyAndHead(contextAsNode);
|
|
|
|
// cache the deepest leaf in the context
|
|
tmp = contextAsNode;
|
|
while (tmp)
|
|
{
|
|
contextDepth++;
|
|
contextLeaf = tmp;
|
|
contextLeaf->GetFirstChild(getter_AddRefs(tmp));
|
|
}
|
|
}
|
|
|
|
|
|
// create fragment for pasted html
|
|
res = ParseFragment(aInputString, outFragNode);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(*outFragNode, NS_ERROR_FAILURE);
|
|
|
|
RemoveBodyAndHead(*outFragNode);
|
|
|
|
if (contextAsNode)
|
|
{
|
|
// unite the two trees
|
|
contextLeaf->AppendChild(*outFragNode, getter_AddRefs(junk));
|
|
*outFragNode = contextAsNode;
|
|
// no longer have fragmentAsNode in tree
|
|
contextDepth--;
|
|
}
|
|
|
|
res = StripFormattingNodes(*outFragNode, PR_TRUE);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// get the infoString contents
|
|
nsAutoString numstr1, numstr2;
|
|
if (aInfoStr.Length())
|
|
{
|
|
PRInt32 err, sep;
|
|
sep = aInfoStr.FindChar((PRUnichar)',');
|
|
numstr1 = Substring(aInfoStr, 0, sep);
|
|
numstr2 = Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1));
|
|
*outRangeStartHint = numstr1.ToInteger(&err) + contextDepth;
|
|
*outRangeEndHint = numstr2.ToInteger(&err) + contextDepth;
|
|
}
|
|
else
|
|
{
|
|
*outRangeStartHint = contextDepth;
|
|
*outRangeEndHint = contextDepth;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult nsHTMLEditor::ParseFragment(const nsAString &aFragStr, nsCOMPtr<nsIDOMNode> *outNode)
|
|
{
|
|
// create the parser to do the conversion.
|
|
nsCOMPtr<nsIParser> parser;
|
|
nsresult res = nsComponentManager::CreateInstance(kCParserCID, nsnull, NS_GET_IID(nsIParser),
|
|
getter_AddRefs(parser));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE);
|
|
|
|
// create the html fragment sink
|
|
nsCOMPtr<nsIContentSink> sink;
|
|
sink = do_CreateInstance(NS_HTMLFRAGMENTSINK2_CONTRACTID);
|
|
NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
|
|
nsCOMPtr<nsIHTMLFragmentContentSink> fragSink(do_QueryInterface(sink));
|
|
NS_ENSURE_TRUE(fragSink, NS_ERROR_FAILURE);
|
|
|
|
// parse the fragment
|
|
parser->SetContentSink(sink);
|
|
parser->Parse(aFragStr, 0, NS_LITERAL_CSTRING("text/html"), PR_FALSE, PR_TRUE, eDTDMode_fragment);
|
|
|
|
// get the fragment node
|
|
nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
|
|
res = fragSink->GetFragment(getter_AddRefs(contextfrag));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
*outNode = do_QueryInterface(contextfrag);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode *aFragmentAsNode,
|
|
nsCOMArray<nsIDOMNode>& outNodeList,
|
|
PRInt32 aRangeStartHint,
|
|
PRInt32 aRangeEndHint)
|
|
{
|
|
if (!aFragmentAsNode)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
// First off create a range over the portion of docFrag indicated by
|
|
// the range hints.
|
|
nsCOMPtr<nsIDOMRange> docFragRange;
|
|
docFragRange = do_CreateInstance(kCRangeCID);
|
|
nsCOMPtr<nsIDOMNode> startParent, endParent, tmp;
|
|
PRInt32 endOffset;
|
|
startParent = aFragmentAsNode;
|
|
while (aRangeStartHint > 0)
|
|
{
|
|
startParent->GetFirstChild(getter_AddRefs(tmp));
|
|
startParent = tmp;
|
|
aRangeStartHint--;
|
|
NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
|
|
}
|
|
endParent = aFragmentAsNode;
|
|
while (aRangeEndHint > 0)
|
|
{
|
|
endParent->GetLastChild(getter_AddRefs(tmp));
|
|
endParent = tmp;
|
|
aRangeEndHint--;
|
|
NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE);
|
|
}
|
|
nsresult res = GetLengthOfDOMNode(endParent, (PRUint32&)endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
res = docFragRange->SetStart(startParent, 0);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = docFragRange->SetEnd(endParent, endOffset);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// now use a subtree iterator over the range to create a list of nodes
|
|
nsTrivialFunctor functor;
|
|
nsDOMSubtreeIterator iter;
|
|
res = iter.Init(docFragRange);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
res = iter.AppendList(functor, outNodeList);
|
|
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::GetListAndTableParents(PRBool aEnd,
|
|
nsCOMArray<nsIDOMNode>& aListOfNodes,
|
|
nsCOMArray<nsIDOMNode>& outArray)
|
|
{
|
|
PRInt32 listCount = aListOfNodes.Count();
|
|
if (listCount <= 0)
|
|
return NS_ERROR_FAILURE; // no empty lists, please
|
|
|
|
// build up list of parents of first (or last) node in list
|
|
// that are either lists, or tables.
|
|
PRInt32 idx = 0;
|
|
if (aEnd) idx = listCount-1;
|
|
|
|
nsCOMPtr<nsIDOMNode> pNode = aListOfNodes[idx];
|
|
while (pNode)
|
|
{
|
|
if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
|
|
{
|
|
if (!outArray.AppendObject(pNode))
|
|
{
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
pNode->GetParentNode(getter_AddRefs(parent));
|
|
pNode = parent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::DiscoverPartialListsAndTables(nsCOMArray<nsIDOMNode>& aPasteNodes,
|
|
nsCOMArray<nsIDOMNode>& aListsAndTables,
|
|
PRInt32 *outHighWaterMark)
|
|
{
|
|
NS_ENSURE_TRUE(outHighWaterMark, NS_ERROR_NULL_POINTER);
|
|
|
|
*outHighWaterMark = -1;
|
|
PRInt32 listAndTableParents = aListsAndTables.Count();
|
|
|
|
// scan insertion list for table elements (other than table).
|
|
PRInt32 listCount = aPasteNodes.Count();
|
|
PRInt32 j;
|
|
for (j=0; j<listCount; j++)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = aPasteNodes[j];
|
|
|
|
NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
|
|
if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> theTable = GetTableParent(curNode);
|
|
if (theTable)
|
|
{
|
|
PRInt32 indexT = aListsAndTables.IndexOf(theTable);
|
|
if (indexT >= 0)
|
|
{
|
|
*outHighWaterMark = indexT;
|
|
if (*outHighWaterMark == listAndTableParents-1) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (nsHTMLEditUtils::IsListItem(curNode))
|
|
{
|
|
nsCOMPtr<nsIDOMNode> theList = GetListParent(curNode);
|
|
if (theList)
|
|
{
|
|
PRInt32 indexL = aListsAndTables.IndexOf(theList);
|
|
if (indexL >= 0)
|
|
{
|
|
*outHighWaterMark = indexL;
|
|
if (*outHighWaterMark == listAndTableParents-1) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::ScanForListAndTableStructure( PRBool aEnd,
|
|
nsCOMArray<nsIDOMNode>& aNodes,
|
|
nsIDOMNode *aListOrTable,
|
|
nsCOMPtr<nsIDOMNode> *outReplaceNode)
|
|
{
|
|
NS_ENSURE_TRUE(aListOrTable, NS_ERROR_NULL_POINTER);
|
|
NS_ENSURE_TRUE(outReplaceNode, NS_ERROR_NULL_POINTER);
|
|
|
|
*outReplaceNode = 0;
|
|
|
|
// look upward from first/last paste node for a piece of this list/table
|
|
PRInt32 listCount = aNodes.Count(), idx = 0;
|
|
if (aEnd) idx = listCount-1;
|
|
PRBool bList = nsHTMLEditUtils::IsList(aListOrTable);
|
|
|
|
nsCOMPtr<nsIDOMNode> pNode = aNodes[idx];
|
|
nsCOMPtr<nsIDOMNode> originalNode = pNode;
|
|
while (pNode)
|
|
{
|
|
if ( (bList && nsHTMLEditUtils::IsListItem(pNode)) ||
|
|
(!bList && (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))) )
|
|
{
|
|
nsCOMPtr<nsIDOMNode> structureNode;
|
|
if (bList) structureNode = GetListParent(pNode);
|
|
else structureNode = GetTableParent(pNode);
|
|
if (structureNode == aListOrTable)
|
|
{
|
|
if (bList)
|
|
*outReplaceNode = structureNode;
|
|
else
|
|
*outReplaceNode = pNode;
|
|
break;
|
|
}
|
|
}
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
pNode->GetParentNode(getter_AddRefs(parent));
|
|
pNode = parent;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHTMLEditor::ReplaceOrphanedStructure(PRBool aEnd,
|
|
nsCOMArray<nsIDOMNode>& aNodeArray,
|
|
nsCOMArray<nsIDOMNode>& aListAndTableArray,
|
|
PRInt32 aHighWaterMark)
|
|
{
|
|
nsCOMPtr<nsIDOMNode> curNode = aListAndTableArray[aHighWaterMark];
|
|
NS_ENSURE_TRUE(curNode, NS_ERROR_NULL_POINTER);
|
|
|
|
nsCOMPtr<nsIDOMNode> replaceNode, originalNode;
|
|
|
|
// find substructure of list or table that must be included in paste.
|
|
nsresult res = ScanForListAndTableStructure(aEnd, aNodeArray,
|
|
curNode, address_of(replaceNode));
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// if we found substructure, paste it instead of it's descendants
|
|
if (replaceNode)
|
|
{
|
|
// postprocess list to remove any descendants of this node
|
|
// so that we dont insert them twice.
|
|
nsCOMPtr<nsIDOMNode> endpoint;
|
|
do
|
|
{
|
|
endpoint = GetArrayEndpoint(aEnd, aNodeArray);
|
|
if (!endpoint) break;
|
|
if (nsEditorUtils::IsDescendantOf(endpoint, replaceNode))
|
|
aNodeArray.RemoveObject(endpoint);
|
|
else
|
|
break;
|
|
} while(endpoint);
|
|
|
|
// now replace the removed nodes with the structural parent
|
|
if (aEnd) aNodeArray.AppendObject(replaceNode);
|
|
else aNodeArray.InsertObjectAt(replaceNode, 0);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIDOMNode* nsHTMLEditor::GetArrayEndpoint(PRBool aEnd,
|
|
nsCOMArray<nsIDOMNode>& aNodeArray)
|
|
{
|
|
if (aEnd)
|
|
{
|
|
PRInt32 listCount = aNodeArray.Count();
|
|
if (listCount <= 0) return nsnull;
|
|
else return aNodeArray[listCount-1];
|
|
}
|
|
else
|
|
{
|
|
return aNodeArray[0];
|
|
}
|
|
}
|