Mozilla/mozilla/editor/libeditor/html/nsHTMLDataTransfer.cpp
jfrancis%netscape.com 9a35a75ae3 fixes for bugs:
75618   CR line breaks not recognized when inserting data in a plain 
76258 	IsEditable() needs to be smarter 
72968 	browser crashes when doing REDO after doing some cut/paste i 
55224 	switching from HTML Source mode to Normal causes dataloss 
71355 	style buttons do not work from 2nd cell onwards inside table 
71362 	empty tables inside (otherwise) empty list item disappears w 
74655 	40% of reply time in mailcompose spent converting linefeeds

r=fm; sr=kin


git-svn-id: svn://10.0.0.236/trunk@92547 18797224-902f-48f8-a5cc-f745e15eee43
2001-04-17 10:15:05 +00:00

1615 lines
54 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
*/
#include "nsICaret.h"
#include "nsHTMLEditor.h"
#include "nsHTMLEditRules.h"
#include "nsTextEditUtils.h"
#include "nsHTMLEditUtils.h"
#include "nsEditorEventListeners.h"
#include "nsIDOMText.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
#include "nsIDOMAttr.h"
#include "nsIDocument.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMKeyEvent.h"
#include "nsIDOMKeyListener.h"
#include "nsIDOMMouseListener.h"
#include "nsIDOMMouseEvent.h"
#include "nsISelection.h"
#include "nsISelectionPrivate.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLImageElement.h"
#include "nsISelectionController.h"
#include "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 "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"
// 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 "nsIPref.h"
const PRUnichar nbsp = 160;
// HACK - CID for NS_CTRANSITIONAL_DTD_CID so that we can get at transitional dtd
#define NS_CTRANSITIONAL_DTD_CID \
{ 0x4611d482, 0x960a, 0x11d4, { 0x8e, 0xb0, 0xb6, 0x17, 0x66, 0x1b, 0x6f, 0x7c } }
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);
static NS_DEFINE_IID(kFileWidgetCID, NS_FILEWIDGET_CID);
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
static NS_DEFINE_IID(kCParserIID, NS_IPARSER_IID);
static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
static NS_DEFINE_CID(kCTransitionalDTDCID, NS_CTRANSITIONAL_DTD_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(kCDragServiceCID, NS_DRAGSERVICE_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"
#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::InsertHTML(const nsAReadableString & aInString)
{
nsAutoString charset;
return InsertHTMLWithCharset(aInString, charset);
}
nsresult nsHTMLEditor::InsertHTMLWithContext(const nsAReadableString & aInputString, const nsAReadableString & aContextStr, const nsAReadableString & aInfoStr)
{
nsAutoString charset;
return InsertHTMLWithCharsetAndContext(aInputString, charset, aContextStr, aInfoStr);
}
NS_IMETHODIMP nsHTMLEditor::InsertHTMLWithCharset(const nsAReadableString & aInputString, const nsAReadableString & aCharset)
{
return InsertHTMLWithCharsetAndContext(aInputString, aCharset, nsAutoString(), nsAutoString());
}
nsresult nsHTMLEditor::InsertHTMLWithCharsetAndContext(const nsAReadableString & aInputString,
const nsAReadableString & aCharset,
const nsAReadableString & aContextStr,
const nsAReadableString & aInfoStr)
{
if (!mRules) return NS_ERROR_NOT_INITIALIZED;
/* all this is unneeded: parser handles this for us
// First, make sure there are no return chars in the document.
// Bad things happen if you insert returns (instead of dom newlines, \n)
// into an editor document.
nsAutoString inputString (aInputString); // hope this does copy-on-write
// Windows linebreaks: Map CRLF to LF:
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r\n"),
NS_ConvertASCIItoUCS2("\n"));
// Mac linebreaks: Map any remaining CR to LF:
inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r"),
NS_ConvertASCIItoUCS2("\n"));
*/
// 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
nsCOMPtr<nsISupportsArray> nodeList;
res = CreateListOfNodesToPaste(fragmentAsNode, address_of(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(getter_AddRefs(cell), nsnull);
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.
nsCOMPtr<nsISupports> isupports = dont_AddRef(nodeList->ElementAt(0));
nsCOMPtr<nsIDOMNode> firstNode( do_QueryInterface(isupports) );
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;
// 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 lst that are either:
// lists, or tables.
nsCOMPtr<nsISupports> isup = dont_AddRef(nodeList->ElementAt(0));
nsCOMPtr<nsIDOMNode> pNode( do_QueryInterface(isup) );
nsCOMPtr<nsISupportsArray> listAndTableArray;
res = NS_NewISupportsArray(getter_AddRefs(listAndTableArray));
NS_ENSURE_SUCCESS(res, res);
while (pNode)
{
if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
{
isup = do_QueryInterface(pNode);
listAndTableArray->AppendElement(isup);
}
nsCOMPtr<nsIDOMNode> parent;
pNode->GetParentNode(getter_AddRefs(parent));
pNode = parent;
}
// remember number of lists and tables above us
PRUint32 listAndTableParents;
PRInt32 highWaterMark = -1;
listAndTableArray->Count(&listAndTableParents);
PRUint32 listCount, j;
if (listAndTableParents)
{
// scan insertion list for table elements (other than table).
nodeList->Count(&listCount);
for (j=0; j<listCount; j++)
{
nsCOMPtr<nsISupports> isupports = dont_AddRef(nodeList->ElementAt(j));
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
{
nsCOMPtr<nsIDOMNode> theTable = GetTableParent(curNode);
if (theTable)
{
nsCOMPtr<nsISupports> isupTable(do_QueryInterface(theTable));
PRInt32 indexT = listAndTableArray->IndexOf(isupTable);
if (indexT >= 0)
{
highWaterMark = indexT;
if ((PRUint32)highWaterMark == listAndTableParents-1) break;
}
else
{
break;
}
}
}
if (nsHTMLEditUtils::IsListItem(curNode))
{
nsCOMPtr<nsIDOMNode> theList = GetListParent(curNode);
if (theList)
{
nsCOMPtr<nsISupports> isupList(do_QueryInterface(theList));
PRInt32 indexL = listAndTableArray->IndexOf(isupList);
if (indexL >= 0)
{
highWaterMark = indexL;
if ((PRUint32)highWaterMark == listAndTableParents-1) break;
}
else
{
break;
}
}
}
}
}
// 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)
{
nsCOMPtr<nsISupports> isupports = dont_AddRef(listAndTableArray->ElementAt(highWaterMark));
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
nsCOMPtr<nsIDOMNode> replaceNode, tmp;
if (nsHTMLEditUtils::IsTable(curNode))
{
// look upward from curNode for a piece of this table
isup = dont_AddRef(nodeList->ElementAt(0));
pNode = do_QueryInterface(isup);
while (pNode)
{
if (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))
{
nsCOMPtr<nsIDOMNode> tableP = GetTableParent(pNode);
if (tableP == curNode)
{
replaceNode = pNode;
break;
}
}
nsCOMPtr<nsIDOMNode> parent;
pNode->GetParentNode(getter_AddRefs(parent));
pNode = parent;
}
}
else // list case
{
// look upward from curNode for a piece of this list
isup = dont_AddRef(nodeList->ElementAt(0));
pNode = do_QueryInterface(isup);
while (pNode)
{
if (nsHTMLEditUtils::IsListItem(pNode))
{
nsCOMPtr<nsIDOMNode> listP = GetListParent(pNode);
if (listP == curNode)
{
replaceNode = pNode;
break;
}
}
nsCOMPtr<nsIDOMNode> parent;
pNode->GetParentNode(getter_AddRefs(parent));
pNode = parent;
}
}
if (replaceNode)
{
isupports = do_QueryInterface(replaceNode);
nodeList->ReplaceElementAt(isupports, 0);
// postprocess list to remove any descendants of this node
// so that we dont insert them twice.
do
{
isupports = dont_AddRef(nodeList->ElementAt(1));
tmp = do_QueryInterface(isupports);
if (tmp && nsHTMLEditUtils::IsDescendantOf(tmp, replaceNode))
nodeList->RemoveElementAt(1);
else
break;
} while(tmp);
}
}
// Loop over the node list and paste the nodes:
PRBool bDidInsert = PR_FALSE;
nsCOMPtr<nsIDOMNode> lastInsertNode, insertedContextParent;
nodeList->Count(&listCount);
for (j=0; j<listCount; j++)
{
nsCOMPtr<nsISupports> isupports = dont_AddRef(nodeList->ElementAt(j));
nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(isupports) );
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 (nsHTMLEditUtils::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)
{
InsertNodeAtPoint(child, parentNode, offsetOfNewNode, PR_TRUE);
if (NS_SUCCEEDED(res))
{
bDidInsert = PR_TRUE;
lastInsertNode = curNode;
offsetOfNewNode++;
}
curNode->GetFirstChild(getter_AddRefs(child));
}
}
else
{
// try to insert
res = InsertNodeAtPoint(curNode, 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, 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)
{
res = GetNodeLocation(lastInsertNode, address_of(parentNode), &offsetOfNewNode);
NS_ENSURE_SUCCESS(res, res);
selection->Collapse(parentNode, offsetOfNewNode+1);
}
}
res = mRules->DidDoAction(selection, &ruleInfo, res);
return res;
}
nsresult
nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode)
{
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)
{
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);
NS_ENSURE_SUCCESS(res, res);
child = tmp;
}
}
return res;
}
NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
{
// Create generic Transferable for getting the data
nsresult rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
NS_GET_IID(nsITransferable),
(void**)transferable);
if (NS_FAILED(rv))
return rv;
// Get the nsITransferable interface for getting the data from the clipboard
if (transferable)
{
// 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
{
(*transferable)->AddDataFlavor(kJPEGImageMime);
(*transferable)->AddDataFlavor(kHTMLMime);
(*transferable)->AddDataFlavor(kFileMime);
}
(*transferable)->AddDataFlavor(kUnicodeMime);
}
return NS_OK;
}
NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable,
const nsAReadableString & aContextStr,
const nsAReadableString & 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.EqualsWithConversion(kHTMLMime))
{
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
if (textDataObj && len > 0)
{
PRUnichar* text = nsnull;
textDataObj->ToString ( &text );
stuffToPaste.Assign ( text, len / 2 );
nsAutoEditBatch beginBatching(this);
rv = InsertHTMLWithContext(stuffToPaste, aContextStr, aInfoStr);
if (text)
nsMemory::Free(text);
}
}
else if (flavor.EqualsWithConversion(kUnicodeMime))
{
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
if (textDataObj && len > 0)
{
PRUnichar* text = nsnull;
textDataObj->ToString ( &text );
stuffToPaste.Assign ( text, len / 2 );
nsAutoEditBatch beginBatching(this);
// pasting does not inherit local inline styles
RemoveAllInlineProperties();
rv = InsertText(stuffToPaste);
if (text)
nsMemory::Free(text);
}
}
else if (flavor.EqualsWithConversion(kFileMime))
{
nsCOMPtr<nsIFile> fileObj ( do_QueryInterface(genericDataObj) );
if (fileObj && len > 0)
{
nsCOMPtr<nsIFileURL> fileURL;
rv = nsComponentManager::CreateInstance("@mozilla.org/network/standard-url;1", nsnull,
NS_GET_IID(nsIURL), getter_AddRefs(fileURL));
if (NS_FAILED(rv))
return rv;
if ( fileURL )
{
rv = fileURL->SetFile( fileObj );
if (NS_FAILED(rv))
return rv;
PRBool insertAsImage = PR_FALSE;
char *fileextension = nsnull;
rv = fileURL->GetFileExtension( &fileextension );
if ( NS_SUCCEEDED(rv) && fileextension )
{
if ( (nsCRT::strcasecmp( fileextension, "jpg" ) == 0 )
|| (nsCRT::strcasecmp( fileextension, "jpeg" ) == 0 )
|| (nsCRT::strcasecmp( fileextension, "gif" ) == 0 )
|| (nsCRT::strcasecmp( fileextension, "png" ) == 0 ) )
{
insertAsImage = PR_TRUE;
}
}
if (fileextension) nsCRT::free(fileextension);
char *urltext = nsnull;
rv = fileURL->GetSpec( &urltext );
if ( NS_SUCCEEDED(rv) && urltext && urltext[0] != 0)
{
len = strlen(urltext);
if ( insertAsImage )
{
stuffToPaste.AssignWithConversion ( "<IMG src=\"", 10);
stuffToPaste.AppendWithConversion ( urltext, len );
stuffToPaste.AppendWithConversion ( "\">" );
}
else /* insert as link */
{
stuffToPaste.AssignWithConversion ( "<A href=\"" );
stuffToPaste.AppendWithConversion ( urltext, len );
stuffToPaste.AppendWithConversion ( "\">" );
stuffToPaste.AppendWithConversion ( urltext, len );
stuffToPaste.AppendWithConversion ( "</A>" );
}
nsAutoEditBatch beginBatching(this);
rv = InsertHTML(stuffToPaste);
}
if (urltext) nsCRT::free(urltext);
}
}
}
else if (flavor.EqualsWithConversion(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))
{
nsCOMPtr<nsISelectionController> selCon;
if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon)
selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_FOCUS_REGION);
}
return rv;
}
NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
{
ForceCompositionEnd();
nsresult rv;
NS_WITH_SERVICE(nsIDragService, dragService, "@mozilla.org/widget/dragservice;1", &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDragSession> dragSession(do_QueryInterface(dragService));
if (!dragSession) return NS_OK;
// Get the nsITransferable interface for getting the data from the drop
nsCOMPtr<nsITransferable> trans;
rv = PrepareTransferable(getter_AddRefs(trans));
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?
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?rv:NS_ERROR_FAILURE;
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, nsAutoString(), nsAutoString());
}
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;
res = aDragEvent->GetOriginalTarget(getter_AddRefs(eventTarget));
if (NS_FAILED(res)) return res;
if ( eventTarget )
{
nsCOMPtr<nsIDOMNode> eventTargetDomNode = do_QueryInterface(eventTarget);
if ( eventTargetDomNode )
{
PRBool amTargettedCorrectly = PR_FALSE;
res = selection->ContainsNode(eventTargetDomNode, PR_FALSE, &amTargettedCorrectly);
if (NS_FAILED(res)) return res;
*aCanDrag = amTargettedCorrectly;
}
}
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 */
NS_WITH_SERVICE(nsIDragService, dragService, "@mozilla.org/widget/dragservice;1", &rv);
if (NS_FAILED(rv)) return rv;
/* create html flavor transferable */
nsCOMPtr<nsITransferable> trans;
rv = nsComponentManager::CreateInstance(kCTransferableCID, nsnull,
NS_GET_IID(nsITransferable),
getter_AddRefs(trans));
if (NS_FAILED(rv)) return rv;
if ( !trans ) return NS_ERROR_OUT_OF_MEMORY;
nsCOMPtr<nsIDOMDocument> domdoc;
rv = GetDocument(getter_AddRefs(domdoc));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
if (doc)
{
nsCOMPtr<nsIDocumentEncoder> docEncoder;
docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "text/html");
NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
docEncoder->Init(doc, NS_LITERAL_STRING("text/html"), 0);
docEncoder->SetSelection(selection);
nsAutoString buffer;
rv = docEncoder->EncodeToString(buffer);
if (NS_FAILED(rv))
return rv;
if ( !buffer.IsEmpty() )
{
nsCOMPtr<nsIFormatConverter> htmlConverter;
rv = nsComponentManager::CreateInstance(kCHTMLFormatConverterCID, nsnull, NS_GET_IID(nsIFormatConverter),
getter_AddRefs(htmlConverter));
if (NS_FAILED(rv)) return rv;
if (!htmlConverter) return NS_ERROR_OUT_OF_MEMORY;
nsCOMPtr<nsISupportsWString> dataWrapper;
rv = nsComponentManager::CreateInstance(NS_SUPPORTS_WSTRING_CONTRACTID, nsnull,
NS_GET_IID(nsISupportsWString), getter_AddRefs(dataWrapper));
if (NS_FAILED(rv)) return rv;
if ( !dataWrapper ) return NS_ERROR_OUT_OF_MEMORY;
rv = trans->AddDataFlavor(kHTMLMime);
if (NS_FAILED(rv)) return rv;
rv = trans->SetConverter(htmlConverter);
if (NS_FAILED(rv)) return rv;
rv = dataWrapper->SetData( NS_CONST_CAST(PRUnichar*, buffer.GetUnicode()) );
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> nsisupportsDataWrapper ( do_QueryInterface(dataWrapper) );
rv = trans->SetTransferData(kHTMLMime, nsisupportsDataWrapper, buffer.Length() * 2);
if (NS_FAILED(rv)) return rv;
/* 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...
// if ( wantToCut )
// flags = nsIDragService.DRAGDROP_ACTION_COPY + nsIDragService.DRAGDROP_ACTION_MOVE;
// else
flags = nsIDragService::DRAGDROP_ACTION_COPY + nsIDragService::DRAGDROP_ACTION_MOVE;
rv = dragService->InvokeDragSession( domnode, transferableArray, nsnull, flags);
if (NS_FAILED(rv)) return rv;
aDragEvent->PreventBubble();
}
}
return rv;
}
NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
{
ForceCompositionEnd();
// Get Clipboard Service
nsresult rv;
NS_WITH_SERVICE ( nsIClipboard, clipboard, kCClipboardCID, &rv );
if ( NS_FAILED(rv) )
return rv;
// Get the nsITransferable interface for getting the data from the clipboard
nsCOMPtr<nsITransferable> trans;
rv = PrepareTransferable(getter_AddRefs(trans));
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;
nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
PRUint32 contextLen, infoLen;
nsCOMPtr<nsISupportsWString> 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)
{
PRUnichar* text = nsnull;
textDataObj = do_QueryInterface(contextDataObj);
textDataObj->ToString ( &text );
contextStr.Assign ( text, contextLen / 2 );
if (text)
nsMemory::Free(text);
}
if (infoDataObj)
{
PRUnichar* text = nsnull;
textDataObj = do_QueryInterface(infoDataObj);
textDataObj->ToString ( &text );
infoStr.Assign ( text, infoLen / 2 );
if (text)
nsMemory::Free(text);
}
rv = InsertFromTransferable(trans, contextStr, infoStr);
}
}
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;
NS_WITH_SERVICE(nsIClipboard, clipboard, kCClipboardCID, &rv);
if (NS_FAILED(rv)) return rv;
// the flavors that we can deal with
char* textEditorFlavors[] = { kUnicodeMime, nsnull };
char* htmlEditorFlavors[] = { kJPEGImageMime, kHTMLMime, 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 (char** flavor = textEditorFlavors; *flavor; flavor++)
{
nsCOMPtr<nsISupportsString> flavorString;
nsComponentManager::CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, nsnull,
NS_GET_IID(nsISupportsString), getter_AddRefs(flavorString));
if (flavorString)
{
flavorString->SetData(*flavor);
flavorsList->AppendElement(flavorString);
}
}
// add the HTML-editor only flavors
if ((editorFlags & eEditorPlaintextMask) == 0)
{
for (char** htmlFlavor = htmlEditorFlavors; *htmlFlavor; htmlFlavor++)
{
nsCOMPtr<nsISupportsString> flavorString;
nsComponentManager::CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, nsnull,
NS_GET_IID(nsISupportsString), getter_AddRefs(flavorString));
if (flavorString)
{
flavorString->SetData(*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 nsAReadableString & 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;
nsAutoString tag; tag.AssignWithConversion("blockquote");
res = DeleteSelectionAndCreateNode(tag, 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)
{
nsAutoString type; type.AssignWithConversion("type");
nsAutoString cite; cite.AssignWithConversion("cite");
newElement->SetAttribute(type, 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;
NS_WITH_SERVICE(nsIClipboard, clipboard, 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.EqualsWithConversion(kUnicodeMime))
{
nsCOMPtr<nsISupportsWString> textDataObj ( do_QueryInterface(genericDataObj) );
if (textDataObj && len > 0)
{
PRUnichar* text = nsnull;
textDataObj->ToString ( &text );
stuffToPaste.Assign ( text, len / 2 );
nsAutoEditBatch beginBatching(this);
rv = InsertAsPlaintextQuotation(stuffToPaste, 0);
if (text)
nsMemory::Free(text);
}
}
nsCRT::free(flav);
}
return rv;
}
NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAReadableString & aQuotedText,
nsIDOMNode **aNodeInserted)
{
if (mFlags & eEditorPlaintextMask)
return InsertAsPlaintextQuotation(aQuotedText, 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 nsAReadableString & aQuotedText,
nsIDOMNode **aNodeInserted)
{
nsresult rv;
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; tag.AssignWithConversion("pre");
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"));
// set style to not have unwanted vertical margins
preElement->SetAttribute(NS_LITERAL_STRING("style"),
NS_LITERAL_STRING("margin: 0 0 0 0px;"));
}
// and set the selection inside it:
selection->Collapse(preNode, 0);
}
//rv = InsertText(quotedStuff.GetUnicode());
rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
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 nsAReadableString & aQuotedText,
const nsAReadableString & aCitation,
PRBool aInsertHTML,
const nsAReadableString & aCharset,
nsIDOMNode **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)
{
nsAutoString tag; tag.AssignWithConversion("blockquote");
res = DeleteSelectionAndCreateNode(tag, 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)
{
nsAutoString type; type.AssignWithConversion("type");
nsAutoString cite; cite.AssignWithConversion("cite");
newElement->SetAttribute(type, cite);
if (aCitation.Length() > 0)
newElement->SetAttribute(cite, aCitation);
// Set the selection inside the blockquote so aQuotedText will go there:
selection->Collapse(newNode, 0);
}
if (aInsertHTML)
res = InsertHTMLWithCharset(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;
}
nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(nsIDOMNSRange *aNSRange,
const nsAReadableString & aInputString,
const nsAReadableString & aContextStr,
const nsAReadableString & aInfoStr,
nsCOMPtr<nsIDOMNode> *outFragNode,
PRInt32 *outRangeStartHint,
PRInt32 *outRangeEndHint)
{
if (!outFragNode || !outRangeStartHint || !outRangeEndHint || !aNSRange)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
nsCOMPtr<nsIDOMNode> contextAsNode;
nsresult res = NS_OK;
// if we have context info, create a fragment for that
nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
nsCOMPtr<nsIDOMNode> contextLeaf;
PRInt32 contextDepth = 0;
if (aContextStr.Length())
{
res = aNSRange->CreateContextualFragment(aContextStr, getter_AddRefs(contextfrag));
NS_ENSURE_SUCCESS(res, res);
contextAsNode = do_QueryInterface(contextfrag);
res = StripFormattingNodes(contextAsNode);
NS_ENSURE_SUCCESS(res, res);
// cache the deepest leaf in the context
nsCOMPtr<nsIDOMNode> junk, child, tmp = contextAsNode;
while (tmp)
{
contextDepth++;
contextLeaf = tmp;
contextLeaf->GetFirstChild(getter_AddRefs(tmp));
}
// tweak aNSRange to point inside contextAsNode
nsCOMPtr<nsIDOMRange> range(do_QueryInterface(aNSRange));
if (range)
{
aNSRange->NSDetach();
range->SetStart(contextLeaf,0);
range->SetEnd(contextLeaf,0);
}
}
// create fragment for pasted html
res = aNSRange->CreateContextualFragment(aInputString, getter_AddRefs(docfrag));
NS_ENSURE_SUCCESS(res, res);
*outFragNode = do_QueryInterface(docfrag);
res = StripFormattingNodes(*outFragNode);
NS_ENSURE_SUCCESS(res, res);
if (contextfrag)
{
nsCOMPtr<nsIDOMNode> junk;
// unite the two trees
contextLeaf->AppendChild(*outFragNode, getter_AddRefs(junk));
*outFragNode = contextAsNode;
// no longer have fragmentAsNode in tree
contextDepth--;
}
// get the infoString contents
nsAutoString numstr1, numstr2;
if (aInfoStr.Length())
{
PRInt32 err, sep;
sep = aInfoStr.FindChar((PRUnichar)',');
aInfoStr.Left(numstr1, sep);
aInfoStr.Right(numstr2, sep+1);
*outRangeStartHint = numstr1.ToInteger(&err) + contextDepth;
*outRangeEndHint = numstr2.ToInteger(&err) + contextDepth;
}
else
{
*outRangeStartHint = contextDepth;
*outRangeEndHint = contextDepth;
}
return res;
}
nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode *aFragmentAsNode,
nsCOMPtr<nsISupportsArray> *outNodeList,
PRInt32 aRangeStartHint,
PRInt32 aRangeEndHint)
{
if (!outNodeList || !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 = NS_NewISupportsArray(getter_AddRefs(*outNodeList));
NS_ENSURE_SUCCESS(res, res);
res = iter.Init(docFragRange);
NS_ENSURE_SUCCESS(res, res);
res = iter.AppendList(functor, *outNodeList);
return res;
}