1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "mozilla/Util.h"
39 :
40 : #include "nsPlaintextEditor.h"
41 :
42 : #include "nsIDOMDocument.h"
43 : #include "nsIDocument.h"
44 : #include "nsIContent.h"
45 : #include "nsIFormControl.h"
46 : #include "nsIDOMEventTarget.h"
47 : #include "nsIDOMNSEvent.h"
48 : #include "nsIDOMMouseEvent.h"
49 : #include "nsIDOMDragEvent.h"
50 : #include "nsISelection.h"
51 : #include "nsCRT.h"
52 : #include "nsServiceManagerUtils.h"
53 :
54 : #include "nsIDOMRange.h"
55 : #include "nsIDOMDOMStringList.h"
56 : #include "nsIDocumentEncoder.h"
57 : #include "nsISupportsPrimitives.h"
58 :
59 : // Drag & Drop, Clipboard
60 : #include "nsIClipboard.h"
61 : #include "nsITransferable.h"
62 : #include "nsIDragService.h"
63 : #include "nsIDOMUIEvent.h"
64 : #include "nsCopySupport.h"
65 : #include "nsITransferable.h"
66 :
67 : // Misc
68 : #include "nsEditorUtils.h"
69 : #include "nsContentCID.h"
70 : #include "nsISelectionPrivate.h"
71 : #include "nsFrameSelection.h"
72 : #include "nsEventDispatcher.h"
73 : #include "nsContentUtils.h"
74 :
75 : using namespace mozilla;
76 :
77 0 : NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable)
78 : {
79 : // Create generic Transferable for getting the data
80 0 : nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
81 0 : NS_ENSURE_SUCCESS(rv, rv);
82 :
83 : // Get the nsITransferable interface for getting the data from the clipboard
84 0 : if (transferable) {
85 0 : (*transferable)->AddDataFlavor(kUnicodeMime);
86 0 : (*transferable)->AddDataFlavor(kMozTextInternal);
87 : };
88 0 : return NS_OK;
89 : }
90 :
91 0 : nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert,
92 : nsIDOMNode *aDestinationNode,
93 : PRInt32 aDestOffset,
94 : bool aDoDeleteSelection)
95 : {
96 0 : if (aDestinationNode)
97 : {
98 : nsresult res;
99 0 : nsCOMPtr<nsISelection>selection;
100 0 : res = GetSelection(getter_AddRefs(selection));
101 0 : NS_ENSURE_SUCCESS(res, res);
102 :
103 0 : nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
104 0 : PRInt32 targetOffset = aDestOffset;
105 :
106 0 : if (aDoDeleteSelection)
107 : {
108 : // Use an auto tracker so that our drop point is correctly
109 : // positioned after the delete.
110 0 : nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
111 0 : res = DeleteSelection(eNone);
112 0 : NS_ENSURE_SUCCESS(res, res);
113 : }
114 :
115 0 : res = selection->Collapse(targetNode, targetOffset);
116 0 : NS_ENSURE_SUCCESS(res, res);
117 : }
118 :
119 0 : return InsertText(aStringToInsert);
120 : }
121 :
122 0 : NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable,
123 : nsIDOMNode *aDestinationNode,
124 : PRInt32 aDestOffset,
125 : bool aDoDeleteSelection)
126 : {
127 0 : FireTrustedInputEvent trusted(this);
128 :
129 0 : nsresult rv = NS_OK;
130 0 : char* bestFlavor = nsnull;
131 0 : nsCOMPtr<nsISupports> genericDataObj;
132 0 : PRUint32 len = 0;
133 0 : if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len))
134 0 : && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
135 0 : 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)))
136 : {
137 0 : nsAutoTxnsConserveSelection dontSpazMySelection(this);
138 0 : nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
139 0 : if (textDataObj && len > 0)
140 : {
141 0 : nsAutoString stuffToPaste;
142 0 : textDataObj->GetData(stuffToPaste);
143 0 : NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
144 :
145 : // Sanitize possible carriage returns in the string to be inserted
146 0 : nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
147 :
148 0 : nsAutoEditBatch beginBatching(this);
149 0 : rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
150 : }
151 : }
152 0 : NS_Free(bestFlavor);
153 :
154 : // Try to scroll the selection into view if the paste/drop succeeded
155 :
156 0 : if (NS_SUCCEEDED(rv))
157 0 : ScrollSelectionIntoView(false);
158 :
159 0 : return rv;
160 : }
161 :
162 0 : nsresult nsPlaintextEditor::InsertFromDataTransfer(nsIDOMDataTransfer *aDataTransfer,
163 : PRInt32 aIndex,
164 : nsIDOMDocument *aSourceDoc,
165 : nsIDOMNode *aDestinationNode,
166 : PRInt32 aDestOffset,
167 : bool aDoDeleteSelection)
168 : {
169 0 : nsCOMPtr<nsIVariant> data;
170 0 : aDataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex,
171 0 : getter_AddRefs(data));
172 0 : if (data) {
173 0 : nsAutoString insertText;
174 0 : data->GetAsAString(insertText);
175 0 : nsContentUtils::PlatformToDOMLineBreaks(insertText);
176 :
177 0 : nsAutoEditBatch beginBatching(this);
178 0 : return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
179 : }
180 :
181 0 : return NS_OK;
182 : }
183 :
184 0 : nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
185 : {
186 0 : ForceCompositionEnd();
187 :
188 0 : nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
189 0 : NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
190 :
191 0 : nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
192 0 : nsresult rv = dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
193 0 : NS_ENSURE_SUCCESS(rv, rv);
194 :
195 : // Current doc is destination
196 0 : nsCOMPtr<nsIDOMDocument> destdomdoc;
197 0 : rv = GetDocument(getter_AddRefs(destdomdoc));
198 0 : NS_ENSURE_SUCCESS(rv, rv);
199 :
200 0 : PRUint32 numItems = 0;
201 0 : rv = dataTransfer->GetMozItemCount(&numItems);
202 0 : NS_ENSURE_SUCCESS(rv, rv);
203 0 : if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop?
204 :
205 : // Combine any deletion and drop insertion into one transaction
206 0 : nsAutoEditBatch beginBatching(this);
207 :
208 0 : bool deleteSelection = false;
209 :
210 : // We have to figure out whether to delete and relocate caret only once
211 : // Parent and offset are under the mouse cursor
212 0 : nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
213 0 : NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
214 :
215 0 : nsCOMPtr<nsIDOMNode> newSelectionParent;
216 0 : rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
217 0 : NS_ENSURE_SUCCESS(rv, rv);
218 0 : NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
219 :
220 : PRInt32 newSelectionOffset;
221 0 : rv = uiEvent->GetRangeOffset(&newSelectionOffset);
222 0 : NS_ENSURE_SUCCESS(rv, rv);
223 :
224 0 : nsCOMPtr<nsISelection> selection;
225 0 : rv = GetSelection(getter_AddRefs(selection));
226 0 : NS_ENSURE_SUCCESS(rv, rv);
227 0 : NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
228 :
229 : bool isCollapsed;
230 0 : rv = selection->GetIsCollapsed(&isCollapsed);
231 0 : NS_ENSURE_SUCCESS(rv, rv);
232 :
233 0 : nsCOMPtr<nsIDOMNode> sourceNode;
234 0 : dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
235 :
236 0 : nsCOMPtr<nsIDOMDocument> srcdomdoc;
237 0 : if (sourceNode) {
238 0 : sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
239 0 : NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
240 : }
241 :
242 : // Only the nsHTMLEditor::FindUserSelectAllNode returns a node.
243 0 : nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
244 0 : if (userSelectNode)
245 : {
246 : // The drop is happening over a "-moz-user-select: all"
247 : // subtree so make sure the content we insert goes before
248 : // the root of the subtree.
249 : //
250 : // XXX: Note that inserting before the subtree matches the
251 : // current behavior when dropping on top of an image.
252 : // The decision for dropping before or after the
253 : // subtree should really be done based on coordinates.
254 :
255 : rv = GetNodeLocation(userSelectNode, address_of(newSelectionParent),
256 0 : &newSelectionOffset);
257 :
258 0 : NS_ENSURE_SUCCESS(rv, rv);
259 0 : NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
260 : }
261 :
262 : // Check if mouse is in the selection
263 : // if so, jump through some hoops to determine if mouse is over selection (bail)
264 : // and whether user wants to copy selection or delete it
265 0 : if (!isCollapsed)
266 : {
267 : // We never have to delete if selection is already collapsed
268 0 : bool cursorIsInSelection = false;
269 :
270 : PRInt32 rangeCount;
271 0 : rv = selection->GetRangeCount(&rangeCount);
272 0 : NS_ENSURE_SUCCESS(rv, rv);
273 :
274 0 : for (PRInt32 j = 0; j < rangeCount; j++)
275 : {
276 0 : nsCOMPtr<nsIDOMRange> range;
277 0 : rv = selection->GetRangeAt(j, getter_AddRefs(range));
278 0 : if (NS_FAILED(rv) || !range)
279 0 : continue; // don't bail yet, iterate through them all
280 :
281 0 : rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
282 0 : if (cursorIsInSelection)
283 : break;
284 : }
285 :
286 0 : if (cursorIsInSelection)
287 : {
288 : // Dragging within same doc can't drop on itself -- leave!
289 0 : if (srcdomdoc == destdomdoc)
290 0 : return NS_OK;
291 :
292 : // Dragging from another window onto a selection
293 : // XXX Decision made to NOT do this,
294 : // note that 4.x does replace if dropped on
295 : //deleteSelection = true;
296 : }
297 : else
298 : {
299 : // We are NOT over the selection
300 0 : if (srcdomdoc == destdomdoc)
301 : {
302 : // Within the same doc: delete if user doesn't want to copy
303 : PRUint32 dropEffect;
304 0 : dataTransfer->GetDropEffectInt(&dropEffect);
305 0 : deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
306 : }
307 : else
308 : {
309 : // Different source doc: Don't delete
310 0 : deleteSelection = false;
311 : }
312 : }
313 : }
314 :
315 0 : if (IsPlaintextEditor()) {
316 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
317 0 : while (content) {
318 0 : nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
319 0 : if (formControl && !formControl->AllowDrop()) {
320 : // Don't allow dropping into a form control that doesn't allow being
321 : // dropped into.
322 0 : return NS_OK;
323 : }
324 0 : content = content->GetParent();
325 : }
326 : }
327 :
328 0 : for (PRUint32 i = 0; i < numItems; ++i) {
329 : InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
330 0 : newSelectionOffset, deleteSelection);
331 : }
332 :
333 0 : if (NS_SUCCEEDED(rv))
334 0 : ScrollSelectionIntoView(false);
335 :
336 0 : return rv;
337 : }
338 :
339 0 : NS_IMETHODIMP nsPlaintextEditor::Paste(PRInt32 aSelectionType)
340 : {
341 0 : if (!FireClipboardEvent(NS_PASTE))
342 0 : return NS_OK;
343 :
344 : // Get Clipboard Service
345 : nsresult rv;
346 0 : nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
347 0 : if ( NS_FAILED(rv) )
348 0 : return rv;
349 :
350 : // Get the nsITransferable interface for getting the data from the clipboard
351 0 : nsCOMPtr<nsITransferable> trans;
352 0 : rv = PrepareTransferable(getter_AddRefs(trans));
353 0 : if (NS_SUCCEEDED(rv) && trans)
354 : {
355 : // Get the Data from the clipboard
356 0 : if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
357 : {
358 : // handle transferable hooks
359 0 : nsCOMPtr<nsIDOMDocument> domdoc;
360 0 : GetDocument(getter_AddRefs(domdoc));
361 0 : if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, trans))
362 0 : return NS_OK;
363 :
364 0 : rv = InsertTextFromTransferable(trans, nsnull, nsnull, true);
365 : }
366 : }
367 :
368 0 : return rv;
369 : }
370 :
371 0 : NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable)
372 : {
373 0 : if (!FireClipboardEvent(NS_PASTE))
374 0 : return NS_OK;
375 :
376 0 : if (!IsModifiable())
377 0 : return NS_OK;
378 :
379 : // handle transferable hooks
380 0 : nsCOMPtr<nsIDOMDocument> domdoc;
381 0 : GetDocument(getter_AddRefs(domdoc));
382 0 : if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, aTransferable))
383 0 : return NS_OK;
384 :
385 0 : return InsertTextFromTransferable(aTransferable, nsnull, nsnull, true);
386 : }
387 :
388 0 : NS_IMETHODIMP nsPlaintextEditor::CanPaste(PRInt32 aSelectionType, bool *aCanPaste)
389 : {
390 0 : NS_ENSURE_ARG_POINTER(aCanPaste);
391 0 : *aCanPaste = false;
392 :
393 : // can't paste if readonly
394 0 : if (!IsModifiable())
395 0 : return NS_OK;
396 :
397 : nsresult rv;
398 0 : nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
399 0 : NS_ENSURE_SUCCESS(rv, rv);
400 :
401 : // the flavors that we can deal with
402 0 : const char* textEditorFlavors[] = { kUnicodeMime };
403 :
404 : bool haveFlavors;
405 0 : rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
406 : ArrayLength(textEditorFlavors),
407 0 : aSelectionType, &haveFlavors);
408 0 : NS_ENSURE_SUCCESS(rv, rv);
409 :
410 0 : *aCanPaste = haveFlavors;
411 0 : return NS_OK;
412 : }
413 :
414 :
415 0 : NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
416 : {
417 0 : NS_ENSURE_ARG_POINTER(aCanPaste);
418 :
419 : // can't paste if readonly
420 0 : if (!IsModifiable()) {
421 0 : *aCanPaste = false;
422 0 : return NS_OK;
423 : }
424 :
425 : // If |aTransferable| is null, assume that a paste will succeed.
426 0 : if (!aTransferable) {
427 0 : *aCanPaste = true;
428 0 : return NS_OK;
429 : }
430 :
431 0 : nsCOMPtr<nsISupports> data;
432 : PRUint32 dataLen;
433 : nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
434 0 : getter_AddRefs(data),
435 0 : &dataLen);
436 0 : if (NS_SUCCEEDED(rv) && data)
437 0 : *aCanPaste = true;
438 : else
439 0 : *aCanPaste = false;
440 :
441 0 : return NS_OK;
442 : }
|