!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 Communicator client code, released
16 * March 31, 1998.
17 *
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998-1999
21 * the Initial Developer. All Rights Reserved.
22 *
23 * Contributor(s):
24 * HÃ¥kan Waara <hwaara@chello.se>
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 var rdfDatasourcePrefix = "@mozilla.org/rdf/datasource;1?name=";
41 var rdfServiceContractID = "@mozilla.org/rdf/rdf-service;1";
42 var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
43 var folderDSContractID = rdfDatasourcePrefix + "mailnewsfolders";
44 var gSearchView;
45 var gSearchSession;
46 var gCurrentFolder;
47
48 var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
49 var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
50 var nsIMsgRDFDataSource = Components.interfaces.nsIMsgRDFDataSource;
51 var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
52
53 var gFolderDatasource;
54 var gFolderPicker;
55 var gStatusBar = null;
56 var gStatusFeedback = new nsMsgStatusFeedback();
57 var gTimelineEnabled = false;
58 var gMessengerBundle = null;
59 var RDF;
60 var gSearchBundle;
61 var gNextMessageViewIndexAfterDelete = -2;
62
63 // Datasource search listener -- made global as it has to be registered
64 // and unregistered in different functions.
65 var gDataSourceSearchListener;
66 var gViewSearchListener;
67
68 var gSearchStopButton;
69 var gMailSession;
70
71 // var MSG_FOLDER_FLAG_VIRTUAL = 0x0020; is defined in commandglue.js
72
73 // Controller object for search results thread pane
74 var nsSearchResultsController =
75 {
supportsCommand
76 supportsCommand: function(command)
77 {
78 switch(command) {
79 case "cmd_delete":
80 case "cmd_shiftDelete":
81 case "button_delete":
82 case "cmd_open":
83 case "file_message_button":
84 case "goto_folder_button":
85 case "saveas_vf_button":
86 case "cmd_selectAll":
87 return true;
88 default:
89 return false;
90 }
91 },
92
93 // this controller only handles commands
94 // that rely on items being selected in
95 // the search results pane.
isCommandEnabled
96 isCommandEnabled: function(command)
97 {
98 var enabled = true;
99
100 switch (command) {
101 case "goto_folder_button":
102 if (GetNumSelectedMessages() != 1)
103 enabled = false;
104 break;
105 case "cmd_delete":
106 case "cmd_shiftDelete":
107 case "button_delete":
108 // this assumes that advanced searches don't cross accounts
109 if (GetNumSelectedMessages() <= 0 || isNewsURI(gSearchView.getURIForViewIndex(0)))
110 enabled = false;
111 break;
112 case "saveas_vf_button":
113 // need someway to see if there are any search criteria...
114 return true;
115 case "cmd_selectAll":
116 return GetDBView() != null;
117 default:
118 if (GetNumSelectedMessages() <= 0)
119 enabled = false;
120 break;
121 }
122
123 return enabled;
124 },
125
doCommand
126 doCommand: function(command)
127 {
128 switch(command) {
129 case "cmd_open":
130 MsgOpenSelectedMessages();
131 return true;
132
133 case "cmd_delete":
134 case "button_delete":
135 MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteMsg);
136 return true;
137 case "cmd_shiftDelete":
138 MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteNoTrash);
139 return true;
140
141 case "goto_folder_button":
142 GoToFolder();
143 return true;
144
145 case "saveas_vf_button":
146 saveAsVirtualFolder();
147 return true;
148
149 case "cmd_selectAll":
150 // move the focus to the search results pane
151 GetThreadTree().focus();
152 GetDBView().doCommand(nsMsgViewCommandType.selectAll)
153 return true;
154
155 default:
156 return false;
157 }
158
159 },
160
onEvent
161 onEvent: function(event)
162 {
163 }
164 }
165
UpdateMailSearch
166 function UpdateMailSearch(caller)
167 {
168 //dump("XXX update mail-search " + caller + "\n");
169 document.commandDispatcher.updateCommands('mail-search');
170 }
171
SetAdvancedSearchStatusText
172 function SetAdvancedSearchStatusText(aNumHits)
173 {
174 var statusMsg;
175 // if there are no hits, it means no matches were found in the search.
176 if (aNumHits == 0)
177 statusMsg = gSearchBundle.getString("searchFailureMessage");
178 else
179 {
180 if (aNumHits == 1)
181 statusMsg = gSearchBundle.getString("searchSuccessMessage");
182 else
183 statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages", [aNumHits]);
184 }
185
186 gStatusFeedback.showStatusString(statusMsg);
187 }
188
189 // nsIMsgSearchNotify object
190 var gSearchNotificationListener =
191 {
onSearchHit
192 onSearchHit: function(header, folder)
193 {
194 // XXX TODO
195 // update status text?
196 },
197
onSearchDone
198 onSearchDone: function(status)
199 {
200 gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
201 gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
202 gStatusFeedback._stopMeteors();
203 SetAdvancedSearchStatusText(gSearchView.QueryInterface(Components.interfaces.nsITreeView).rowCount);
204 },
205
onNewSearch
206 onNewSearch: function()
207 {
208 gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForStopButton"));
209 gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForStopButton.accesskey"));
210 UpdateMailSearch("new-search");
211 gStatusFeedback._startMeteors();
212 gStatusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
213 }
214 }
215
216 // the folderListener object
217 var gFolderListener = {
OnItemAdded
218 OnItemAdded: function(parentItem, item) {},
219
OnItemRemoved
220 OnItemRemoved: function(parentItem, item){},
221
OnItemPropertyChanged
222 OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
223
OnItemIntPropertyChanged
224 OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
225
OnItemBoolPropertyChanged
226 OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
227
OnItemUnicharPropertyChanged
228 OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
OnItemPropertyFlagChanged
229 OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
230
OnItemEvent
231 OnItemEvent: function(folder, event) {
232 var eventType = event.toString();
233
234 if (eventType == "DeleteOrMoveMsgCompleted") {
235 HandleDeleteOrMoveMessageCompleted(folder);
236 }
237 else if (eventType == "DeleteOrMoveMsgFailed") {
238 HandleDeleteOrMoveMessageFailed(folder);
239 }
240 }
241 }
242
HideSearchColumn
243 function HideSearchColumn(id)
244 {
245 var col = document.getElementById(id);
246 if (col) {
247 col.setAttribute("hidden","true");
248 col.setAttribute("ignoreincolumnpicker","true");
249 }
250 }
251
ShowSearchColumn
252 function ShowSearchColumn(id)
253 {
254 var col = document.getElementById(id);
255 if (col) {
256 col.removeAttribute("hidden");
257 col.removeAttribute("ignoreincolumnpicker");
258 }
259 }
260
searchOnLoad
261 function searchOnLoad()
262 {
263 initializeSearchWidgets();
264 initializeSearchWindowWidgets();
265 CreateMessenger();
266
267 gSearchBundle = document.getElementById("bundle_search");
268 gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
269 gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
270 gMessengerBundle = document.getElementById("bundle_messenger");
271 setupDatasource();
272 setupSearchListener();
273
274 if (window.arguments && window.arguments[0])
275 selectFolder(window.arguments[0].folder);
276
277 onMore(null);
278 UpdateMailSearch("onload");
279
280 // hide and remove these columns from the column picker. you can't thread search results
281 HideSearchColumn("threadCol"); // since you can't thread search results
282 HideSearchColumn("totalCol"); // since you can't thread search results
283 HideSearchColumn("unreadCol"); // since you can't thread search results
284 HideSearchColumn("unreadButtonColHeader");
285 HideSearchColumn("statusCol");
286 HideSearchColumn("flaggedCol");
287 HideSearchColumn("idCol");
288 HideSearchColumn("junkStatusCol");
289 HideSearchColumn("accountCol");
290
291 // we want to show the location column for search
292 ShowSearchColumn("locationCol");
293 }
294
searchOnUnload
295 function searchOnUnload()
296 {
297 // unregister listeners
298 gSearchSession.unregisterListener(gViewSearchListener);
299 gSearchSession.unregisterListener(gSearchNotificationListener);
300
301 gMailSession.RemoveFolderListener(gFolderListener);
302
303 if (gSearchView) {
304 gSearchView.close();
305 gSearchView = null;
306 }
307
308 // release this early because msgWindow holds a weak reference
309 msgWindow.rootDocShell = null;
310 }
311
initializeSearchWindowWidgets
312 function initializeSearchWindowWidgets()
313 {
314 gFolderPicker = document.getElementById("searchableFolders");
315 gSearchStopButton = document.getElementById("search-button");
316 gStatusBar = document.getElementById('statusbar-icon');
317 hideMatchAllItem();
318
319 msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
320 .createInstance(nsIMsgWindow);
321 msgWindow.domWindow = window;
322 msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
323 msgWindow.statusFeedback = gStatusFeedback;
324
325 // functionality to enable/disable buttons using nsSearchResultsController
326 // depending of whether items are selected in the search results thread pane.
327 top.controllers.insertControllerAt(0, nsSearchResultsController);
328 }
329
330
onSearchStop
331 function onSearchStop() {
332 gSearchSession.interruptSearch();
333 }
334
onResetSearch
335 function onResetSearch(event) {
336 onReset(event);
337
338 var tree = GetThreadTree();
339 tree.treeBoxObject.view = null;
340 gStatusFeedback.showStatusString("");
341 }
342
selectFolder
343 function selectFolder(folder)
344 {
345 var folderURI;
346
347 // if we can't search messages on this folder, just select the first one
348 if (!folder || !folder.server.canSearchMessages || (folder.flags & MSG_FOLDER_FLAG_VIRTUAL)) {
349 // find first item in our folder picker menu list
350 folderURI = gFolderPicker.firstChild.tree.builderView.getResourceAtIndex(0).Value;
351 } else {
352 folderURI = folder.URI;
353 }
354 updateSearchFolderPicker(folderURI);
355 }
356
updateSearchFolderPicker
357 function updateSearchFolderPicker(folderURI)
358 {
359 SetFolderPicker(folderURI, gFolderPicker.id);
360
361 // use the URI to get the real folder
362 gCurrentFolder =
363 RDF.GetResource(folderURI).QueryInterface(nsIMsgFolder);
364
365 var searchLocalSystem = document.getElementById("checkSearchLocalSystem");
366 if (searchLocalSystem)
367 searchLocalSystem.disabled = gCurrentFolder.server.searchScope == nsMsgSearchScope.offlineMail;
368 setSearchScope(GetScopeForFolder(gCurrentFolder));
369 }
370
updateSearchLocalSystem
371 function updateSearchLocalSystem()
372 {
373 setSearchScope(GetScopeForFolder(gCurrentFolder));
374 }
375
376 function UpdateAfterCustomHeaderChange()
377 {
378 updateSearchAttributes();
379 }
380
onChooseFolder
381 function onChooseFolder(event) {
382 var folderURI = event.id;
383 if (folderURI) {
384 updateSearchFolderPicker(folderURI);
385 }
386 }
387
onEnterInSearchTerm
388 function onEnterInSearchTerm()
389 {
390 // on enter
391 // if not searching, start the search
392 // if searching, stop and then start again
393 if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
394 onSearch();
395 }
396 else {
397 onSearchStop();
398 onSearch();
399 }
400 }
401
onSearch
402 function onSearch()
403 {
404 // set the view. do this on every search, to
405 // allow the tree to reset itself
406 var treeView = gSearchView.QueryInterface(Components.interfaces.nsITreeView);
407 if (treeView)
408 {
409 var tree = GetThreadTree();
410 tree.treeBoxObject.view = treeView;
411 }
412
413 gSearchSession.clearScopes();
414 // tell the search session what the new scope is
415 if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect)
416 gSearchSession.addScopeTerm(GetScopeForFolder(gCurrentFolder),
417 gCurrentFolder);
418
419 var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
420 if (gCurrentFolder && (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
421 {
422 AddSubFolders(gCurrentFolder);
423 }
424 // reflect the search widgets back into the search session
425 saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
426
427 try
428 {
429 gSearchSession.search(msgWindow);
430 }
431 catch(ex)
432 {
433 dump("Search Exception\n");
434 }
435 // refresh the tree after the search starts, because initiating the
436 // search will cause the datasource to clear itself
437 }
438
AddSubFolders
439 function AddSubFolders(folder) {
440 if (folder.hasSubFolders)
441 {
442 var subFolderEnumerator = folder.subFoldersObsolete;
443 var done = false;
444 while (!done)
445 {
446 var next = subFolderEnumerator.currentItem();
447 if (next)
448 {
449 var nextFolder = next.QueryInterface(Components.interfaces.nsIMsgFolder);
450 if (nextFolder && ! (nextFolder.flags & MSG_FOLDER_FLAG_VIRTUAL))
451 {
452 if (!nextFolder.noSelect)
453 gSearchSession.addScopeTerm(GetScopeForFolder(nextFolder), nextFolder);
454 AddSubFolders(nextFolder);
455 }
456 }
457 try
458 {
459 subFolderEnumerator.next();
460 }
461 catch (ex)
462 {
463 done = true;
464 }
465 }
466 }
467 }
468
AddSubFoldersToURI
469 function AddSubFoldersToURI(folder)
470 {
471 var returnString = "";
472 if (folder.hasSubFolders)
473 {
474 var subFolderEnumerator = folder.subFoldersObsolete;
475 var done = false;
476 while (!done)
477 {
478 var next = subFolderEnumerator.currentItem();
479 if (next)
480 {
481 var nextFolder = next.QueryInterface(Components.interfaces.nsIMsgFolder);
482 if (nextFolder && ! (nextFolder.flags & MSG_FOLDER_FLAG_VIRTUAL))
483 {
484 if (!nextFolder.noSelect && !nextFolder.isServer)
485 {
486 if (returnString.length > 0)
487 returnString += '|';
488 returnString += nextFolder.URI;
489 }
490 var subFoldersString = AddSubFoldersToURI(nextFolder);
491 if (subFoldersString.length > 0)
492 {
493 if (returnString.length > 0)
494 returnString += '|';
495 returnString += subFoldersString;
496 }
497
498 }
499 }
500 try
501 {
502 subFolderEnumerator.next();
503 }
504 catch (ex)
505 {
506 done = true;
507 }
508 }
509 }
510 return returnString;
511 }
512
513
GetScopeForFolder
514 function GetScopeForFolder(folder)
515 {
516 var searchLocalSystem = document.getElementById("checkSearchLocalSystem");
517 return searchLocalSystem && searchLocalSystem.checked ? nsMsgSearchScope.offlineMail : folder.server.searchScope;
518 }
519
520 var nsMsgViewSortType = Components.interfaces.nsMsgViewSortType;
521 var nsMsgViewSortOrder = Components.interfaces.nsMsgViewSortOrder;
522 var nsMsgViewFlagsType = Components.interfaces.nsMsgViewFlagsType;
523 var nsMsgViewCommandType = Components.interfaces.nsMsgViewCommandType;
524
goUpdateSearchItems
525 function goUpdateSearchItems(commandset)
526 {
527 for (var i = 0; i < commandset.childNodes.length; i++)
528 {
529 var commandID = commandset.childNodes[i].getAttribute("id");
530 if (commandID)
531 {
532 goUpdateCommand(commandID);
533 }
534 }
535 }
536
nsMsgSearchCommandUpdater
537 function nsMsgSearchCommandUpdater()
538 {}
539
540 nsMsgSearchCommandUpdater.prototype =
541 {
updateCommandStatus
542 updateCommandStatus : function()
543 {
544 // the back end is smart and is only telling us to update command status
545 // when the # of items in the selection has actually changed.
546 document.commandDispatcher.updateCommands('mail-search');
547 },
displayMessageChanged
548 displayMessageChanged : function(aFolder, aSubject, aKeywords)
549 {
550 },
551
updateNextMessageAfterDelete
552 updateNextMessageAfterDelete : function()
553 {
554 SetNextMessageAfterDelete();
555 },
556
QueryInterface
557 QueryInterface : function(iid)
558 {
559 if (iid.equals(Components.interfaces.nsIMsgDBViewCommandUpdater) ||
560 iid.equals(Components.interfaces.nsISupports))
561 return this;
562
563 throw Components.results.NS_NOINTERFACE;
564 }
565 }
566
setupDatasource
567 function setupDatasource() {
568
569 RDF = Components.classes[rdfServiceContractID].getService(Components.interfaces.nsIRDFService);
570 gSearchView = Components.classes["@mozilla.org/messenger/msgdbview;1?type=search"].createInstance(Components.interfaces.nsIMsgDBView);
571 var count = new Object;
572 var cmdupdator = new nsMsgSearchCommandUpdater();
573
574 gSearchView.init(messenger, msgWindow, cmdupdator);
575 gSearchView.open(null, nsMsgViewSortType.byId, nsMsgViewSortOrder.ascending, nsMsgViewFlagsType.kNone, count);
576
577 // the thread pane needs to use the search datasource (to get the
578 // actual list of messages) and the message datasource (to get any
579 // attributes about each message)
580 gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
581
582 gMailSession = Components.classes[mailSessionContractID].getService(Components.interfaces.nsIMsgMailSession);
583 var nsIFolderListener = Components.interfaces.nsIFolderListener;
584 var notifyFlags = nsIFolderListener.event;
585 gMailSession.AddFolderListener(gFolderListener, notifyFlags);
586
587 // the datasource is a listener on the search results
588 gViewSearchListener = gSearchView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
589 gSearchSession.registerListener(gViewSearchListener);
590 }
591
592
setupSearchListener
593 function setupSearchListener()
594 {
595 // Setup the javascript object as a listener on the search results
596 gSearchSession.registerListener(gSearchNotificationListener);
597 }
598
599 // stuff after this is implemented to make the thread pane work
GetFolderDatasource
600 function GetFolderDatasource()
601 {
602 if (!gFolderDatasource)
603 gFolderDatasource = Components.classes[folderDSContractID].getService(Components.interfaces.nsIRDFDataSource);
604 return gFolderDatasource;
605 }
606
607 // used to determine if we should try to load a message
IsThreadAndMessagePaneSplitterCollapsed
608 function IsThreadAndMessagePaneSplitterCollapsed()
609 {
610 return true;
611 }
612
613 // used to toggle functionality for Search/Stop button.
onSearchButton
614 function onSearchButton(event)
615 {
616 if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
617 onSearch();
618 else
619 onSearchStop();
620 }
621
622 // threadPane.js will be needing this, too
GetNumSelectedMessages
623 function GetNumSelectedMessages()
624 {
625 try {
626 return gSearchView.numSelected;
627 }
628 catch (ex) {
629 return 0;
630 }
631 }
632
GetDBView
633 function GetDBView()
634 {
635 return gSearchView;
636 }
637
MsgDeleteSelectedMessages
638 function MsgDeleteSelectedMessages(aCommandType)
639 {
640 // we don't delete news messages, we just return in that case
641 if (isNewsURI(gSearchView.getURIForViewIndex(0)))
642 return;
643
644 // if mail messages delete
645 SetNextMessageAfterDelete();
646 gSearchView.doCommand(aCommandType);
647 }
648
SetNextMessageAfterDelete
649 function SetNextMessageAfterDelete()
650 {
651 gNextMessageViewIndexAfterDelete = gSearchView.msgToSelectAfterDelete;
652 }
653
HandleDeleteOrMoveMessageFailed
654 function HandleDeleteOrMoveMessageFailed(folder)
655 {
656 gNextMessageViewIndexAfterDelete = -2;
657 }
658
HandleDeleteOrMoveMessageCompleted
659 function HandleDeleteOrMoveMessageCompleted(folder)
660 {
661 var treeView = gSearchView.QueryInterface(Components.interfaces.nsITreeView);
662 var treeSelection = treeView.selection;
663 var viewSize = treeView.rowCount;
664
665 if (gNextMessageViewIndexAfterDelete == -2) {
666 // a move or delete can cause our selection can change underneath us.
667 // this can happen when the user
668 // deletes message from the stand alone msg window
669 // or the three pane
670 if (!treeSelection) {
671 // this can happen if you open the search window
672 // and before you do any searches
673 // and you do delete from another mail window
674 return;
675 }
676 else if (treeSelection.count == 0) {
677 // this can happen if you double clicked a message
678 // in the thread pane, and deleted it from the stand alone msg window
679 // see bug #185147
680 treeSelection.clearSelection();
681
682 UpdateMailSearch("delete from another view, 0 rows now selected");
683 }
684 else if (treeSelection.count == 1) {
685 // this can happen if you had two messages selected
686 // in the search results pane, and you deleted one of them from another view
687 // (like the view in the stand alone msg window or the three pane)
688 // since one item is selected, we should load it.
689 var startIndex = {};
690 var endIndex = {};
691 treeSelection.getRangeAt(0, startIndex, endIndex);
692
693 // select the selected item, so we'll load it
694 treeSelection.select(startIndex.value);
695 treeView.selectionChanged();
696
697 EnsureRowInThreadTreeIsVisible(startIndex.value);
698 UpdateMailSearch("delete from another view, 1 row now selected");
699 }
700 else {
701 // this can happen if you have more than 2 messages selected
702 // in the search results pane, and you deleted one of them from another view
703 // (like the view in the stand alone msg window or the three pane)
704 // since multiple messages are still selected, do nothing.
705 }
706 }
707 else {
708 if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None && gNextMessageViewIndexAfterDelete >= viewSize)
709 {
710 if (viewSize > 0)
711 gNextMessageViewIndexAfterDelete = viewSize - 1;
712 else
713 {
714 gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
715
716 // there is nothing to select since viewSize is 0
717 treeSelection.clearSelection();
718
719 UpdateMailSearch("delete from current view, 0 rows left");
720 }
721 }
722
723 // if we are about to set the selection with a new element then DON'T clear
724 // the selection then add the next message to select. This just generates
725 // an extra round of command updating notifications that we are trying to
726 // optimize away.
727 if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
728 {
729 treeSelection.select(gNextMessageViewIndexAfterDelete);
730 // since gNextMessageViewIndexAfterDelete probably has the same value
731 // as the last index we had selected, the tree isn't generating a new
732 // selectionChanged notification for the tree view. So we aren't loading the
733 // next message. to fix this, force the selection changed update.
734 if (treeView)
735 treeView.selectionChanged();
736
737 EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
738
739 // XXX TODO
740 // I think there is a bug in the suppression code above.
741 // what if I have two rows selected, and I hit delete,
742 // and so we load the next row.
743 // what if I have commands that only enable where
744 // exactly one row is selected?
745 UpdateMailSearch("delete from current view, at least one row selected");
746 }
747 }
748
749 // default value after delete/move/copy is over
750 gNextMessageViewIndexAfterDelete = -2;
751
752 // something might have been deleted, so update the status text
753 SetAdvancedSearchStatusText(viewSize);
754 }
755
MoveMessageInSearch
756 function MoveMessageInSearch(destFolder)
757 {
758 try {
759 // get the msg folder we're moving messages into
760 // if the id (uri) is not set, use file-uri which is set for
761 // "File Here"
762 var destUri = destFolder.getAttribute('id');
763 if (destUri.length == 0) {
764 destUri = destFolder.getAttribute('file-uri')
765 }
766
767 var destResource = RDF.GetResource(destUri);
768
769 var destMsgFolder = destResource.QueryInterface(Components.interfaces.nsIMsgFolder);
770
771 // we don't move news messages, we copy them
772 if (isNewsURI(gSearchView.getURIForViewIndex(0))) {
773 gSearchView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, destMsgFolder);
774 }
775 else {
776 SetNextMessageAfterDelete();
777 gSearchView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
778 }
779 }
780 catch (ex) {
781 dump("MsgMoveMessage failed: " + ex + "\n");
782 }
783 }
784
GoToFolder
785 function GoToFolder()
786 {
787 MsgOpenNewWindowForMsgHdr(gSearchView.hdrForFirstSelectedMessage);
788 }
789
BeginDragThreadPane
790 function BeginDragThreadPane(event)
791 {
792 // no search pane dnd yet
793 return false;
794 }
795
saveAsVirtualFolder
796 function saveAsVirtualFolder()
797 {
798 var preselectedURI = gCurrentFolder.URI;
799 searchFolderURIs = preselectedURI;
800
801 var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
802 if (gCurrentFolder && (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
803 {
804 var subFolderURIs = AddSubFoldersToURI(gCurrentFolder);
805 if (subFolderURIs.length > 0)
806 searchFolderURIs += '|' + subFolderURIs;
807 }
808
809 var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
810 "chrome,titlebar,modal,centerscreen",
811 {preselectedURI:preselectedURI,
812 searchTerms:gSearchSession.searchTerms,
813 searchFolderURIs: searchFolderURIs});
814 }