!import
1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 * disttsc@bart.nl
24 * jarrod.k.gray@rose-hulman.edu
25 * Jan Varga <varga@ku.sk>
26 * Markus Hossner <markushossner@gmx.de>
27 *
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
39 *
40 * ***** END LICENSE BLOCK ***** */
41
42 // cache these services
43 var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService().QueryInterface(Components.interfaces.nsIRDFService);
44 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService().QueryInterface(Components.interfaces.nsIDragService);
45 var nsIDragService = Components.interfaces.nsIDragService;
46
debugDump
47 function debugDump(msg)
48 {
49 // uncomment for noise
50 // dump(msg+"\n");
51 }
52
CanDropOnFolderTree
53 function CanDropOnFolderTree(index, orientation)
54 {
55 var dragSession = null;
56 var dragFolder = false;
57
58 dragSession = dragService.getCurrentSession();
59 if (! dragSession)
60 return false;
61
62 var flavorSupported = dragSession.isDataFlavorSupported("text/x-moz-message") || dragSession.isDataFlavorSupported("text/x-moz-folder");
63
64 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
65 if (! trans)
66 return false;
67
68 trans.addDataFlavor("text/x-moz-message");
69 trans.addDataFlavor("text/x-moz-folder");
70 trans.addDataFlavor("text/x-moz-newsfolder");
71 trans.addDataFlavor("text/x-moz-url");
72
73 var folderTree = GetFolderTree();
74 var targetResource = GetFolderResource(folderTree, index);
75 var targetUri = targetResource.Value;
76 var targetFolder = targetResource.QueryInterface(Components.interfaces.nsIMsgFolder);
77 var targetServer = targetFolder.server;
78 var sourceServer;
79 var sourceResource;
80
81 for (var i = 0; i < dragSession.numDropItems; i++)
82 {
83 dragSession.getData (trans, i);
84 var dataObj = new Object();
85 var dataFlavor = new Object();
86 var len = new Object();
87 try
88 {
89 trans.getAnyTransferData (dataFlavor, dataObj, len );
90 }
91 catch (ex)
92 {
93 continue; //no data so continue;
94 }
95 if (dataObj)
96 dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
97 if (! dataObj)
98 continue;
99
100 // pull the URL out of the data object
101 var sourceUri = dataObj.data.substring(0, len.value);
102 if (! sourceUri)
103 continue;
104 if (dataFlavor.value == "text/x-moz-message")
105 {
106 sourceResource = null;
107
108 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
109 return false;
110
111 var isServer = GetFolderAttribute(folderTree, targetResource, "IsServer");
112 if (isServer == "true")
113 {
114 debugDump("***isServer == true\n");
115 return false;
116 }
117 // canFileMessages checks no select, and acl, for imap.
118 var canFileMessages = GetFolderAttribute(folderTree, targetResource, "CanFileMessages");
119 if (canFileMessages != "true")
120 {
121 debugDump("***canFileMessages == false\n");
122 return false;
123 }
124 var hdr = messenger.msgHdrFromURI(sourceUri);
125 if (hdr.folder == targetFolder)
126 return false;
127 break;
128 } else if (dataFlavor.value == "text/x-moz-folder") {
129 // we should only get here if we are dragging and dropping folders
130 dragFolder = true;
131 sourceResource = RDF.GetResource(sourceUri);
132 var sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder);
133 sourceServer = sourceFolder.server;
134
135 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
136 return false;
137
138 if (targetUri == sourceUri)
139 return false;
140
141 //don't allow immediate child to be dropped to it's parent
142 if (targetFolder.URI == sourceFolder.parent.URI)
143 {
144 debugDump(targetFolder.URI + "\n");
145 debugDump(sourceFolder.parent.URI + "\n");
146 return false;
147 }
148
149 // don't allow dragging of virtual folders across accounts
150 if ((sourceFolder.flags & MSG_FOLDER_FLAG_VIRTUAL) && sourceServer != targetServer)
151 return false;
152
153 var isAncestor = sourceFolder.isAncestorOf(targetFolder);
154 // don't allow parent to be dropped on its ancestors
155 if (isAncestor)
156 return false;
157 }
158 else if (dataFlavor.value == "text/x-moz-newsfolder")
159 {
160 sourceResource = RDF.GetResource(sourceUri);
161 var sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder);
162
163 // don't allow dragging on to element
164 if (orientation == Components.interfaces.nsITreeView.DROP_ON)
165 return false;
166
167 // don't allow dragging news folder (newsgroup) to other account
168 if (targetFolder.rootFolder != sourceFolder.rootFolder)
169 return false;
170
171 // don't allow dragging news folder (newsgroup) to server folder
172 if (targetFolder.isServer)
173 return false;
174
175 // don't allow dragging news folder (newsgroup) to before/after itself
176 index += orientation;
177 if (index < folderTree.view.rowCount) {
178 targetResource = GetFolderResource(folderTree, index);
179
180 if (targetResource == sourceResource)
181 return false;
182 }
183
184 return true;
185 }
186 else if (dataFlavor.value == "text/x-moz-url")
187 {
188 // eventually check to make sure this is an http url before doing anything else...
189 var uri = Components.classes["@mozilla.org/network/standard-url;1"].
190 createInstance(Components.interfaces.nsIURI);
191 var url = sourceUri.split("\n")[0];
192 uri.spec = url;
193
194 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
195 return false;
196
197 if ( (uri.schemeIs("http") || uri.schemeIs("https")) && targetServer && targetServer.type == 'rss')
198 return true;
199 }
200 }
201
202 if (dragFolder)
203 {
204 //first check these conditions then proceed further
205 debugDump("***isFolderFlavor == true \n");
206
207 // no copy for folder drag within a server
208 if (dragSession.dragAction == nsIDragService.DRAGDROP_ACTION_COPY && sourceServer == targetServer)
209 return false;
210
211 var canCreateSubfolders = GetFolderAttribute(folderTree, targetResource, "CanCreateSubfolders");
212 // if cannot create subfolders then a folder cannot be dropped here
213 if (canCreateSubfolders == "false")
214 {
215 debugDump("***canCreateSubfolders == false \n");
216 return false;
217 }
218
219 var serverType = GetFolderAttribute(folderTree, targetResource, "ServerType");
220
221 // if we've got a folder that can't be renamed
222 // allow us to drop it if we plan on dropping it on "Local Folders"
223 // (but not within the same server, to prevent renaming folders on "Local Folders" that
224 // should not be renamed)
225 var srcCanRename = GetFolderAttribute(folderTree, sourceResource, "CanRename");
226 if (srcCanRename == "false") {
227 if (sourceServer == targetServer)
228 return false;
229 if (serverType != "none")
230 return false;
231 }
232 }
233
234 //message or folder
235 if (flavorSupported)
236 {
237 dragSession.canDrop = true;
238 return true;
239 }
240
241 return false;
242 }
243
DropOnFolderTree
244 function DropOnFolderTree(row, orientation)
245 {
246 var folderTree = GetFolderTree();
247 var targetResource = GetFolderResource(folderTree, row);
248 var targetFolder = targetResource.QueryInterface(Components.interfaces.nsIMsgFolder);
249 var targetServer = targetFolder.server;
250
251 var targetUri = targetResource.Value;
252 debugDump("***targetUri = " + targetUri + "\n");
253
254 var dragSession = dragService.getCurrentSession();
255 if (! dragSession )
256 return false;
257
258 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
259 trans.addDataFlavor("text/x-moz-message");
260 trans.addDataFlavor("text/x-moz-folder");
261 trans.addDataFlavor("text/x-moz-newsfolder");
262 trans.addDataFlavor("text/x-moz-url");
263
264 var list = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
265
266 var dropMessage;
267 var sourceUri;
268 var sourceResource;
269 var sourceFolder;
270 var sourceServer;
271
272 for (var i = 0; i < dragSession.numDropItems; i++)
273 {
274 dragSession.getData (trans, i);
275 var dataObj = new Object();
276 var flavor = new Object();
277 var len = new Object();
278 trans.getAnyTransferData(flavor, dataObj, len);
279 if (dataObj)
280 dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
281 if (! dataObj)
282 continue;
283
284 // pull the URL out of the data object
285 sourceUri = dataObj.data.substring(0, len.value);
286 if (! sourceUri)
287 continue;
288
289 debugDump(" Node #" + i + ": drop " + sourceUri + " to " + targetUri + "\n");
290
291 // only do this for the first object, either they are all messages or they are all folders
292 if (i == 0)
293 {
294 if (flavor.value == "text/x-moz-folder")
295 {
296 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
297 return false;
298
299 sourceResource = RDF.GetResource(sourceUri);
300 sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder);
301 dropMessage = false; // we are dropping a folder
302 }
303 else if (flavor.value == "text/x-moz-newsfolder")
304 {
305 if (orientation == Components.interfaces.nsITreeView.DROP_ON)
306 return false;
307
308 sourceResource = RDF.GetResource(sourceUri);
309 sourceFolder = sourceResource.QueryInterface(Components.interfaces.nsIMsgFolder);
310 dropMessage = false; // we are dropping a news folder (newsgroup)
311 }
312 else if (flavor.value == "text/x-moz-message")
313 {
314 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
315 return false;
316
317 dropMessage = true;
318 }
319 else if (flavor.value == "text/x-moz-url")
320 {
321 if (orientation != Components.interfaces.nsITreeView.DROP_ON)
322 return false;
323
324 var uri = Components.classes["@mozilla.org/network/standard-url;1"].
325 createInstance(Components.interfaces.nsIURI);
326 var url = sourceUri.split("\n")[0];
327 uri.spec = url;
328
329 if ( (uri.schemeIs("http") || uri.schemeIs("https")) && targetServer && targetServer.type == 'rss')
330 {
331 var rssService = Components.classes["@mozilla.org/newsblog-feed-downloader;1"].getService().
332 QueryInterface(Components.interfaces.nsINewsBlogFeedDownloader);
333 if (rssService)
334 rssService.subscribeToFeed(url, targetFolder, msgWindow);
335 return true;
336 }
337 else
338 return false;
339 }
340 }
341 else {
342 if (!dropMessage)
343 dump("drag and drop of multiple folders isn't supported\n");
344 }
345
346 if (dropMessage) {
347 // from the message uri, get the appropriate messenger service
348 // and then from that service, get the msgDbHdr
349 list.AppendElement(messenger.msgHdrFromURI(sourceUri));
350 }
351 else {
352 // Prevent dropping of a node before, after, or on itself
353 if (sourceResource == targetResource)
354 continue;
355
356 list.AppendElement(sourceResource);
357 }
358 }
359
360 if (list.Count() < 1)
361 return false;
362
363 var isSourceNews = false;
364 isSourceNews = isNewsURI(sourceUri);
365
366 if (dropMessage) {
367 var sourceMsgHdr = list.GetElementAt(0).QueryInterface(Components.interfaces.nsIMsgDBHdr);
368 sourceFolder = sourceMsgHdr.folder;
369 sourceResource = sourceFolder.QueryInterface(Components.interfaces.nsIRDFResource);
370 sourceServer = sourceFolder.server;
371
372 try {
373 if (isSourceNews) {
374 // news to pop or imap is always a copy
375 messenger.copyMessages(GetFolderDatasource(), sourceResource, targetResource, list, false);
376 }
377 else if (dragSession.dragAction == nsIDragService.DRAGDROP_ACTION_COPY ||
378 dragSession.dragAction == nsIDragService.DRAGDROP_ACTION_MOVE) {
379 var isMove = (dragSession.dragAction == nsIDragService.DRAGDROP_ACTION_MOVE);
380 pref.setCharPref("mail.last_msg_movecopy_target_uri", targetFolder.URI);
381 pref.setBoolPref("mail.last_msg_movecopy_was_move", isMove);
382 messenger.copyMessages(GetFolderDatasource(), sourceResource, targetResource, list, isMove);
383 }
384 }
385 catch (ex) {
386 dump("failed to copy messages: " + ex + "\n");
387 }
388 }
389 else
390 {
391 var folderTree = GetFolderTree();
392
393 if (sourceFolder.server.type == "nntp")
394 { // dragging a news folder (newsgroup)
395 var newsFolder = targetFolder.rootFolder
396 .QueryInterface(Components.interfaces.nsIMsgNewsFolder);
397 newsFolder.moveFolder(sourceFolder, targetFolder, orientation);
398 SelectFolder(sourceFolder.URI);
399 }
400 else
401 { // dragging a normal folder
402 sourceServer = sourceFolder.server;
403 try
404 {
405 messenger.copyFolders(GetFolderDatasource(), targetResource, list, (sourceServer == targetServer));
406 }
407 catch(ex)
408 {
409 dump ("Exception : CopyFolders " + ex + "\n");
410 }
411 }
412 }
413 return true;
414 }
415
BeginDragFolderTree
416 function BeginDragFolderTree(event)
417 {
418 debugDump("BeginDragFolderTree\n");
419
420 if (event.originalTarget.localName != "treechildren")
421 return false;
422
423 var folderTree = GetFolderTree();
424 var row = folderTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
425 if (row == -1)
426 return false;
427
428 var folderResource = GetFolderResource(folderTree, row);
429
430 if (GetFolderAttribute(folderTree, folderResource, "IsServer") == "true")
431 {
432 debugDump("***IsServer == true\n");
433 return false;
434 }
435
436 var flavor;
437 if (GetFolderAttribute(folderTree, folderResource, "ServerType") == "nntp")
438 { // news folder (newsgroup)
439 flavor = "text/x-moz-newsfolder";
440 }
441 else
442 { // normal folder
443 flavor = "text/x-moz-folder";
444 }
445
446 var selectedFolders = GetSelectedFolders();
447 return BeginDragTree(event, folderTree, selectedFolders, flavor);
448 }
449
BeginDragThreadPane
450 function BeginDragThreadPane(event)
451 {
452 debugDump("BeginDragThreadPane\n");
453
454 var threadTree = GetThreadTree();
455 var selectedMessages = GetSelectedMessages();
456 if (!selectedMessages)
457 return false;
458
459 //A message can be dragged from one window and dropped on another window
460 //therefore setNextMessageAfterDelete() here
461 //no major disadvantage even if it is a copy operation
462
463 SetNextMessageAfterDelete();
464 return BeginDragTree(event, threadTree, selectedMessages, "text/x-moz-message");
465 }
466
BeginDragTree
467 function BeginDragTree(event, tree, selArray, flavor)
468 {
469 var dragStarted = false;
470
471 var transArray = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
472 if ( !transArray )
473 return false;
474
475 // let's build the drag region
476 var region = null;
477 try {
478 region = Components.classes["@mozilla.org/gfx/region;1"].createInstance(Components.interfaces.nsIScriptableRegion);
479 region.init();
480 var obo = tree.treeBoxObject;
481 var bo = obo.treeBody.boxObject;
482 var sel= tree.view.selection;
483
484 var rowX = bo.x;
485 var rowY = bo.y;
486 var rowHeight = obo.rowHeight;
487 var rowWidth = bo.width;
488
489 //add a rectangle for each visible selected row
490 for (var i = obo.getFirstVisibleRow(); i <= obo.getLastVisibleRow(); i ++)
491 {
492 if (sel.isSelected(i))
493 region.unionRect(rowX, rowY, rowWidth, rowHeight);
494 rowY = rowY + rowHeight;
495 }
496
497 //and finally, clip the result to be sure we don't spill over...
498 if(!region.isEmpty())
499 region.intersectRect(bo.x, bo.y, bo.width, bo.height);
500 } catch(ex) {
501 dump("Error while building selection region: " + ex + "\n");
502 region = null;
503 }
504
505 var count = selArray.length;
506 debugDump("selArray.length = " + count + "\n");
507 for (i = 0; i < count; ++i ) {
508 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
509 if (!trans)
510 return false;
511
512 var genTextData = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
513 if (!genTextData)
514 return false;
515
516 trans.addDataFlavor(flavor);
517
518 // get id (url)
519 var id = selArray[i];
520 genTextData.data = id;
521 debugDump(" ID #" + i + " = " + id + "\n");
522
523 trans.setTransferData ( flavor, genTextData, id.length * 2 ); // doublebyte byte data
524
525 // put it into the transferable as an |nsISupports|
526 var genTrans = trans.QueryInterface(Components.interfaces.nsISupports);
527 transArray.AppendElement(genTrans);
528 }
529
530 dragService.invokeDragSessionWithImage(event.target, transArray, region,
531 nsIDragService.DRAGDROP_ACTION_COPY +
532 nsIDragService.DRAGDROP_ACTION_MOVE,
533 null, 0, 0, event);
534
535 dragStarted = true;
536
537 return(!dragStarted); // don't propagate the event if a drag has begun
538 }