Revision control

Copy as Markdown

Other Tools

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
var gDBView;
var gSearchSession;
var gMsgFolderSelected;
var nsIMsgFolder = Ci.nsIMsgFolder;
var nsIMsgWindow = Ci.nsIMsgWindow;
var nsMsgSearchScope = Ci.nsMsgSearchScope;
var gFolderPicker;
var gStatusBar = null;
var gStatusFeedback = new nsMsgStatusFeedback();
var gMessengerBundle = null;
var RDF;
var gSearchBundle;
var gNextMessageViewIndexAfterDelete = -2;
// Datasource search listener -- made global as it has to be registered
// and unregistered in different functions.
var gDataSourceSearchListener;
var gViewSearchListener;
var gSearchStopButton;
// Controller object for search results thread pane
var nsSearchResultsController =
{
supportsCommand: function(command)
{
switch(command) {
case "cmd_openMessage":
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
case "file_message_button":
case "goto_folder_button":
case "saveas_vf_button":
case "cmd_selectAll":
case "cmd_markAsRead":
case "cmd_markAsUnread":
case "cmd_markAsFlagged":
return true;
default:
return false;
}
},
// this controller only handles commands
// that rely on items being selected in
// the search results pane.
isCommandEnabled: function(command)
{
var enabled = true;
switch (command) {
case "goto_folder_button":
if (GetNumSelectedMessages() != 1)
enabled = false;
break;
case "cmd_delete":
case "cmd_shiftDelete":
case "button_delete":
// this assumes that advanced searches don't cross accounts
if (GetNumSelectedMessages() <= 0)
enabled = false;
break;
case "saveas_vf_button":
// need someway to see if there are any search criteria...
return true;
case "cmd_selectAll":
return GetDBView() != null;
default:
if (GetNumSelectedMessages() <= 0)
enabled = false;
break;
}
return enabled;
},
doCommand: function(command)
{
switch(command) {
case "cmd_openMessage":
MsgOpenSelectedMessages();
return true;
case "cmd_delete":
case "button_delete":
MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteMsg);
return true;
case "cmd_shiftDelete":
MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteNoTrash);
return true;
case "goto_folder_button":
GoToFolder();
return true;
case "saveas_vf_button":
saveAsVirtualFolder();
return true;
case "cmd_selectAll":
// move the focus to the search results pane
GetThreadTree().focus();
GetDBView().doCommand(nsMsgViewCommandType.selectAll)
return true;
case "cmd_markAsRead":
MsgMarkMsgAsRead(true);
return true;
case "cmd_markAsUnread":
MsgMarkMsgAsRead(false);
return true;
case "cmd_markAsFlagged":
MsgMarkAsFlagged();
return true;
default:
return false;
}
},
onEvent: function(event)
{
}
}
function UpdateMailSearch(caller)
{
//dump("XXX update mail-search " + caller + "\n");
document.commandDispatcher.updateCommands('mail-search');
}
function SetAdvancedSearchStatusText(aNumHits)
{
var statusMsg;
// if there are no hits, it means no matches were found in the search.
if (aNumHits == 0)
{
statusMsg = gSearchBundle.getString("noMatchesFound");
}
else
{
statusMsg = PluralForm.get(aNumHits,
gSearchBundle.getString("matchesFound"));
statusMsg = statusMsg.replace("#1", aNumHits);
}
gStatusFeedback.showStatusString(statusMsg);
}
// nsIMsgSearchNotify object
var gSearchNotificationListener =
{
onSearchHit: function(header, folder)
{
// XXX TODO
// update status text?
},
onSearchDone: function(status)
{
gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
gStatusFeedback._stopMeteors();
SetAdvancedSearchStatusText(gDBView.QueryInterface(Ci.nsITreeView).rowCount);
},
onNewSearch: function()
{
gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForStopButton"));
gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForStopButton.accesskey"));
UpdateMailSearch("new-search");
gStatusFeedback._startMeteors();
gStatusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
}
}
// the folderListener object
var gFolderListener = {
onFolderAdded: function(parentFolder, child) {},
onMessageAdded: function(parentFolder, msg) {},
onFolderRemoved: function(parentFolder, child) {},
onMessageRemoved: function(parentFolder, msg) {},
onFolderPropertyChanged: function(item, property, oldValue, newValue) {},
onFolderIntPropertyChanged: function(item, property, oldValue, newValue) {},
onFolderBoolPropertyChanged: function(item, property, oldValue, newValue) {},
onFolderUnicharPropertyChanged: function(item, property, oldValue, newValue){},
onFolderPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
onFolderEvent: function(folder, event) {
if (event == "DeleteOrMoveMsgCompleted") {
HandleDeleteOrMoveMessageCompleted(folder);
}
else if (event == "DeleteOrMoveMsgFailed") {
HandleDeleteOrMoveMessageFailed(folder);
}
}
}
function HideSearchColumn(id)
{
var col = document.getElementById(id);
if (col) {
col.setAttribute("hidden","true");
col.setAttribute("ignoreincolumnpicker","true");
}
}
function ShowSearchColumn(id)
{
var col = document.getElementById(id);
if (col) {
col.removeAttribute("hidden");
col.removeAttribute("ignoreincolumnpicker");
}
}
function searchOnLoad()
{
initializeSearchWidgets();
initializeSearchWindowWidgets();
messenger = Cc["@mozilla.org/messenger;1"]
.createInstance(Ci.nsIMessenger);
gSearchBundle = document.getElementById("bundle_search");
gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
gMessengerBundle = document.getElementById("bundle_messenger");
setupDatasource();
setupSearchListener();
if (window.arguments && window.arguments[0])
selectFolder(window.arguments[0].folder);
onMore(null);
UpdateMailSearch("onload");
// hide and remove these columns from the column picker. you can't thread search results
HideSearchColumn("threadCol"); // since you can't thread search results
HideSearchColumn("totalCol"); // since you can't thread search results
HideSearchColumn("unreadCol"); // since you can't thread search results
HideSearchColumn("unreadButtonColHeader");
HideSearchColumn("idCol");
HideSearchColumn("junkStatusCol");
HideSearchColumn("accountCol");
// we want to show the location column for search
ShowSearchColumn("locationCol");
}
function searchOnUnload()
{
// unregister listeners
gSearchSession.unregisterListener(gViewSearchListener);
gSearchSession.unregisterListener(gSearchNotificationListener);
MailServices.mailSession.RemoveFolderListener(gFolderListener);
if (gDBView)
{
gDBView.close();
gDBView = null;
}
top.controllers.removeController(nsSearchResultsController);
// release this early because msgWindow holds a weak reference
msgWindow.rootDocShell = null;
}
function initializeSearchWindowWidgets()
{
gFolderPicker = document.getElementById("searchableFolders");
gSearchStopButton = document.getElementById("search-button");
gStatusBar = document.getElementById('statusbar-icon');
hideMatchAllItem();
msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
.createInstance(nsIMsgWindow);
msgWindow.domWindow = window;
msgWindow.rootDocShell.allowAuth = true;
msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
msgWindow.statusFeedback = gStatusFeedback;
// functionality to enable/disable buttons using nsSearchResultsController
// depending of whether items are selected in the search results thread pane.
top.controllers.insertControllerAt(0, nsSearchResultsController);
}
function onSearchStop() {
gSearchSession.interruptSearch();
}
function onResetSearch(event) {
onReset(event);
var tree = GetThreadTree();
tree.treeBoxObject.view = null;
gStatusFeedback.showStatusString("");
}
function selectFolder(folder)
{
var folderURI;
// if we can't search messages on this folder, just select the first one
if (!folder || !folder.server.canSearchMessages ||
(folder.flags & Ci.nsMsgFolderFlags.Virtual)) {
// find first item in our folder picker menu list
folderURI = gFolderPicker.firstChild.tree.builderView.getResourceAtIndex(0).Value;
} else {
folderURI = folder.URI;
}
updateSearchFolderPicker(folderURI);
}
function updateSearchFolderPicker(folderURI)
{
SetFolderPicker(folderURI, gFolderPicker.id);
// use the URI to get the real folder
gMsgFolderSelected = MailUtils.getFolderForURI(folderURI);
var searchSubFolders = document.getElementById("checkSearchSubFolders");
if (searchSubFolders)
searchSubFolders.disabled = !gMsgFolderSelected.hasSubFolders;
var searchLocalSystem = document.getElementById("menuSearchLocalSystem");
if (searchLocalSystem)
searchLocalSystem.disabled = gMsgFolderSelected.server.searchScope == nsMsgSearchScope.offlineMail;
setSearchScope(GetScopeForFolder(gMsgFolderSelected));
}
function updateSearchLocalSystem()
{
setSearchScope(GetScopeForFolder(gMsgFolderSelected));
}
function UpdateAfterCustomHeaderChange()
{
updateSearchAttributes();
}
function onChooseFolder(event) {
var folderURI = event.id;
if (folderURI) {
updateSearchFolderPicker(folderURI);
}
}
function onEnterInSearchTerm()
{
// on enter
// if not searching, start the search
// if searching, stop and then start again
if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
onSearch();
}
else {
onSearchStop();
onSearch();
}
}
function onSearch()
{
// set the view. do this on every search, to
// allow the tree to reset itself
var treeView = gDBView.QueryInterface(Ci.nsITreeView);
if (treeView)
{
var tree = GetThreadTree();
tree.treeBoxObject.view = treeView;
}
gSearchSession.clearScopes();
// tell the search session what the new scope is
if (!gMsgFolderSelected.isServer && !gMsgFolderSelected.noSelect)
gSearchSession.addScopeTerm(GetScopeForFolder(gMsgFolderSelected),
gMsgFolderSelected);
var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
if (gMsgFolderSelected && (searchSubfolders || gMsgFolderSelected.isServer || gMsgFolderSelected.noSelect))
{
AddSubFolders(gMsgFolderSelected);
}
// reflect the search widgets back into the search session
gSearchSession.searchTerms = saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
try
{
gSearchSession.search(msgWindow);
}
catch(ex)
{
dump("Search Exception\n");
}
// refresh the tree after the search starts, because initiating the
// search will cause the datasource to clear itself
}
function AddSubFolders(folder) {
for (let nextFolder of folder.subFolders) {
if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual))
{
if (!nextFolder.noSelect)
gSearchSession.addScopeTerm(GetScopeForFolder(nextFolder), nextFolder);
AddSubFolders(nextFolder);
}
}
}
function AddSubFoldersToURI(folder)
{
var returnString = "";
for (let nextFolder of folder.subFolders) {
{
if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual))
{
if (!nextFolder.noSelect && !nextFolder.isServer)
{
if (returnString.length > 0)
returnString += '|';
returnString += nextFolder.URI;
}
var subFoldersString = AddSubFoldersToURI(nextFolder);
if (subFoldersString.length > 0)
{
if (returnString.length > 0)
returnString += '|';
returnString += subFoldersString;
}
}
}
return returnString;
}
function GetScopeForFolder(folder)
{
var searchLocalSystem = document.getElementById("menuSearchLocalSystem");
return searchLocalSystem && searchLocalSystem.value == "local" ?
nsMsgSearchScope.offlineMail :
folder.server.searchScope;
}
var nsMsgViewSortType = Ci.nsMsgViewSortType;
var nsMsgViewSortOrder = Ci.nsMsgViewSortOrder;
var nsMsgViewFlagsType = Ci.nsMsgViewFlagsType;
var nsMsgViewCommandType = Ci.nsMsgViewCommandType;
function goUpdateSearchItems(commandset)
{
for (var i = 0; i < commandset.childNodes.length; i++)
{
var commandID = commandset.childNodes[i].getAttribute("id");
if (commandID)
{
goUpdateCommand(commandID);
}
}
}
function nsMsgSearchCommandUpdater()
{}
nsMsgSearchCommandUpdater.prototype =
{
updateCommandStatus : function()
{
// the back end is smart and is only telling us to update command status
// when the # of items in the selection has actually changed.
document.commandDispatcher.updateCommands('mail-search');
},
displayMessageChanged : function(aFolder, aSubject, aKeywords)
{
},
updateNextMessageAfterDelete : function()
{
SetNextMessageAfterDelete();
},
summarizeSelection: function() {return false},
QueryInterface : function(iid)
{
if (iid.equals(Ci.nsIMsgDBViewCommandUpdater) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_NOINTERFACE;
}
}
function setupDatasource() {
gDBView = Cc["@mozilla.org/messenger/msgdbview;1?type=search"]
.createInstance(Ci.nsIMsgDBView);
var count = new Object;
var cmdupdator = new nsMsgSearchCommandUpdater();
gDBView.init(messenger, msgWindow, cmdupdator);
gDBView.open(null, nsMsgViewSortType.byId, nsMsgViewSortOrder.ascending, nsMsgViewFlagsType.kNone, count);
// the thread pane needs to use the search datasource (to get the
// actual list of messages) and the message datasource (to get any
// attributes about each message)
gSearchSession = Cc[searchSessionContractID].createInstance(Ci.nsIMsgSearchSession);
var nsIFolderListener = Ci.nsIFolderListener;
var notifyFlags = nsIFolderListener.event;
MailServices.mailSession.AddFolderListener(gFolderListener, notifyFlags);
// the datasource is a listener on the search results
gViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
gSearchSession.registerListener(gViewSearchListener);
}
function setupSearchListener()
{
// Setup the javascript object as a listener on the search results
gSearchSession.registerListener(gSearchNotificationListener);
}
// used to toggle functionality for Search/Stop button.
function onSearchButton(event)
{
if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
onSearch();
else
onSearchStop();
}
// Stuff after this is implemented to make the thread pane work.
function GetNumSelectedMessages()
{
try {
return gDBView.numSelected;
}
catch (ex) {
return 0;
}
}
function GetDBView()
{
return gDBView;
}
function MsgDeleteSelectedMessages(aCommandType)
{
SetNextMessageAfterDelete();
gDBView.doCommand(aCommandType);
}
function SetNextMessageAfterDelete()
{
gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
}
function HandleDeleteOrMoveMessageFailed(folder)
{
gDBView.onDeleteCompleted(false);
gNextMessageViewIndexAfterDelete = -2;
}
function HandleDeleteOrMoveMessageCompleted(folder)
{
gDBView.onDeleteCompleted(true);
var treeView = gDBView.QueryInterface(Ci.nsITreeView);
var treeSelection = treeView.selection;
var viewSize = treeView.rowCount;
if (gNextMessageViewIndexAfterDelete == -2) {
// a move or delete can cause our selection can change underneath us.
// this can happen when the user
// deletes message from the stand alone msg window
// or the three pane
if (!treeSelection) {
// this can happen if you open the search window
// and before you do any searches
// and you do delete from another mail window
return;
}
else if (treeSelection.count == 0) {
// this can happen if you double clicked a message
// in the thread pane, and deleted it from the stand alone msg window
// see bug #185147
treeSelection.clearSelection();
UpdateMailSearch("delete from another view, 0 rows now selected");
}
else if (treeSelection.count == 1) {
// this can happen if you had two messages selected
// in the search results pane, and you deleted one of them from another view
// (like the view in the stand alone msg window or the three pane)
// since one item is selected, we should load it.
var startIndex = {};
var endIndex = {};
treeSelection.getRangeAt(0, startIndex, endIndex);
// select the selected item, so we'll load it
treeSelection.select(startIndex.value);
treeView.selectionChanged();
EnsureRowInThreadTreeIsVisible(startIndex.value);
UpdateMailSearch("delete from another view, 1 row now selected");
}
else {
// this can happen if you have more than 2 messages selected
// in the search results pane, and you deleted one of them from another view
// (like the view in the stand alone msg window or the three pane)
// since multiple messages are still selected, do nothing.
}
}
else {
if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None && gNextMessageViewIndexAfterDelete >= viewSize)
{
if (viewSize > 0)
gNextMessageViewIndexAfterDelete = viewSize - 1;
else
{
gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
// there is nothing to select since viewSize is 0
treeSelection.clearSelection();
UpdateMailSearch("delete from current view, 0 rows left");
}
}
// if we are about to set the selection with a new element then DON'T clear
// the selection then add the next message to select. This just generates
// an extra round of command updating notifications that we are trying to
// optimize away.
if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
{
treeSelection.select(gNextMessageViewIndexAfterDelete);
// since gNextMessageViewIndexAfterDelete probably has the same value
// as the last index we had selected, the tree isn't generating a new
// selectionChanged notification for the tree view. So we aren't loading the
// next message. to fix this, force the selection changed update.
if (treeView)
treeView.selectionChanged();
EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
// XXX TODO
// I think there is a bug in the suppression code above.
// what if I have two rows selected, and I hit delete,
// and so we load the next row.
// what if I have commands that only enable where
// exactly one row is selected?
UpdateMailSearch("delete from current view, at least one row selected");
}
}
// default value after delete/move/copy is over
gNextMessageViewIndexAfterDelete = -2;
// something might have been deleted, so update the status text
SetAdvancedSearchStatusText(viewSize);
}
function MoveMessageInSearch(destFolder)
{
if (destFolder._folder)
{
try {
SetNextMessageAfterDelete();
gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages,
destFolder._folder);
}
catch (ex) {
dump("MoveMessageInSearch failed: " + ex + "\n");
}
}
}
function GoToFolder()
{
var hdr = gDBView.hdrForFirstSelectedMessage;
MsgOpenNewWindowForFolder(hdr.folder.URI, hdr.messageKey);
}
function saveAsVirtualFolder()
{
let searchFolderURIs = window.arguments[0].folder.URI;
var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
if (gMsgFolderSelected && (searchSubfolders || gMsgFolderSelected.isServer || gMsgFolderSelected.noSelect))
{
var subFolderURIs = AddSubFoldersToURI(gMsgFolderSelected);
if (subFolderURIs.length > 0)
searchFolderURIs += '|' + subFolderURIs;
}
var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
"chrome,titlebar,modal,centerscreen",
{folder:window.arguments[0].folder,
searchTerms:gSearchSession.searchTerms,
searchFolderURIs: searchFolderURIs});
}
function OnTagsChange()
{
// Dummy, called by RemoveAllMessageTags and ToggleMessageTag
}