home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 January / 01_02.iso / software / netscape62win / mail.xpi / bin / chrome / messenger.jar / content / messenger / SearchDialog.js < prev    next >
Text File  |  2001-08-07  |  19KB  |  613 lines

  1. /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2.  * The contents of this file are subject to the Netscape Public
  3.  * License Version 1.1 (the "License"); you may not use this file
  4.  * except in compliance with the License. You may obtain a copy of
  5.  * the License at http://www.mozilla.org/NPL/
  6.  *
  7.  * Software distributed under the License is distributed on an "AS
  8.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  9.  * implied. See the License for the specific language governing
  10.  * rights and limitations under the License.
  11.  *
  12.  * The Original Code is Mozilla Communicator client code, released
  13.  * March 31, 1998.
  14.  *
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation. Portions created by Netscape are
  17.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Hσkan Waara <hwaara@chello.se>
  22.  */
  23.  
  24. var rdfDatasourcePrefix = "@mozilla.org/rdf/datasource;1?name=";
  25. var rdfServiceContractID    = "@mozilla.org/rdf/rdf-service;1";
  26. var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
  27. var folderDSContractID      = rdfDatasourcePrefix + "mailnewsfolders";
  28. var gSearchView;
  29. var gSearchSession;
  30. var gCurrentFolder;
  31.  
  32. var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
  33. var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
  34. var nsIMsgRDFDataSource = Components.interfaces.nsIMsgRDFDataSource;
  35. var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
  36.  
  37. var gFolderDatasource;
  38. var gFolderPicker;
  39. var gStatusBar = null;
  40. var gStatusFeedback = new nsMsgStatusFeedback;
  41. var gNumOfSearchHits = 0;
  42. var RDF;
  43. var gSearchBundle;
  44. var gNextMessageViewIndexAfterDelete = -1;
  45.  
  46. // Datasource search listener -- made global as it has to be registered
  47. // and unregistered in different functions.
  48. var gDataSourceSearchListener;
  49. var gViewSearchListener;
  50.  
  51. var gSearchStopButton;
  52. var gSearchSessionFolderListener;
  53. var gMailSession;
  54.  
  55. // Controller object for search results thread pane
  56. var nsSearchResultsController =
  57. {
  58.     supportsCommand: function(command)
  59.     {
  60.         switch(command) {
  61.         case "button_delete":
  62.         case "cmd_open":
  63.         case "file_message_button":
  64.             return true;
  65.         default:
  66.             return false;
  67.         }
  68.     },
  69.  
  70.     // this controller only handles commands
  71.     // that rely on items being selected in
  72.     // the threadpane.
  73.     isCommandEnabled: function(command)
  74.     {
  75.         var enabled = true;
  76.         if (GetNumSelectedMessages() <= 0)
  77.           enabled = false;
  78.  
  79.         return enabled;
  80.     },
  81.  
  82.     doCommand: function(command)
  83.     {
  84.         switch(command) {
  85.         case "cmd_open":
  86.             MsgOpenSelectedMessages(gSearchView);
  87.             return true;
  88.  
  89.         case "button_delete":
  90.             MsgDeleteSelectedMessages();
  91.             return true;
  92.  
  93.         default:
  94.             return false;
  95.         }
  96.  
  97.     },
  98.  
  99.     onEvent: function(event)
  100.     {
  101.     }
  102. }
  103.  
  104. // nsIMsgSearchNotify object
  105. var gSearchNotificationListener =
  106. {
  107.     onSearchHit: function(header, folder)
  108.     {
  109.         gNumOfSearchHits++;
  110.     },
  111.  
  112.     onSearchDone: function(status)
  113.     {
  114.         gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
  115.  
  116.         var statusMsg;
  117.         // if there are no hits, it means no matches were found in the search.
  118.         if (gNumOfSearchHits == 0) {
  119.             statusMsg = gSearchBundle.getString("searchFailureMessage");
  120.         }
  121.         else 
  122.         {
  123.             if (gNumOfSearchHits == 1) 
  124.                 statusMsg = gSearchBundle.getString("searchSuccessMessage");
  125.             else
  126.                 statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages", [gNumOfSearchHits]);
  127.  
  128.             gNumOfSearchHits = 0;
  129.         }
  130.  
  131.         gStatusFeedback.showProgress(100);
  132.         gStatusFeedback.showStatusString(statusMsg);
  133.         gStatusBar.setAttribute("mode","normal");
  134.     },
  135.  
  136.     onNewSearch: function()
  137.     {
  138.       gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForStopButton"));
  139.       document.commandDispatcher.updateCommands('mail-search');
  140.       gStatusFeedback.showProgress(0);
  141.       gStatusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
  142.       gStatusBar.setAttribute("mode","undetermined");
  143.     }
  144. }
  145.  
  146. // the folderListener object
  147. var gFolderListener = {
  148.     OnItemAdded: function(parentItem, item, view) {},
  149.  
  150.     OnItemRemoved: function(parentItem, item, view){},
  151.  
  152.     OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
  153.  
  154.     OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
  155.  
  156.     OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
  157.  
  158.     OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
  159.     OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
  160.  
  161.     OnItemEvent: function(folder, event) {
  162.         var eventType = event.GetUnicode();
  163.  
  164.         if (eventType == "DeleteOrMoveMsgCompleted") {
  165.             HandleDeleteOrMoveMessageCompleted(folder);
  166.         }     
  167.         else if (eventType == "DeleteOrMoveMsgFailed") {
  168.             HandleDeleteOrMoveMessageFailed(folder);
  169.         }
  170.  
  171.     }
  172. }
  173.  
  174. function HideSearchColumn(id)
  175. {
  176.   var col = document.getElementById(id);
  177.   if (col) {
  178.     col.setAttribute("hidden","true");
  179.     col.setAttribute("ignoreincolumnpicker","true");
  180.   }
  181. }
  182.  
  183. function ShowSearchColumn(id)
  184. {
  185.   var col = document.getElementById(id);
  186.   if (col) {
  187.     col.removeAttribute("hidden");
  188.     col.removeAttribute("ignoreincolumnpicker");
  189.   }
  190. }
  191.  
  192. function searchOnLoad()
  193. {
  194.   initializeSearchWidgets();
  195.   initializeSearchWindowWidgets();
  196.  
  197.   gSearchBundle = document.getElementById("bundle_search");
  198.   setupDatasource();
  199.   setupSearchListener();
  200.  
  201.   if (window.arguments && window.arguments[0])
  202.       selectFolder(window.arguments[0].folder);
  203.  
  204.   onMore(null);
  205.   document.commandDispatcher.updateCommands('mail-search');
  206.   moveToAlertPosition();
  207.  
  208.   // hide and remove these columns from the column picker.  you can't thread search results
  209.   HideSearchColumn("threadCol"); // since you can't thread search results
  210.   HideSearchColumn("totalCol"); // since you can't thread search results
  211.   HideSearchColumn("unreadCol"); // since you can't thread search results
  212.   HideSearchColumn("unreadButtonColHeader");
  213.   HideSearchColumn("statusCol");
  214.   HideSearchColumn("sizeCol");
  215.   HideSearchColumn("flaggedCol");
  216.   
  217.   // we want to show the location column for search
  218.   ShowSearchColumn("locationCol");
  219. }
  220.  
  221. function searchOnUnload()
  222. {
  223.     // unregister listeners
  224.     gSearchSession.unregisterListener(gViewSearchListener);
  225.     gSearchSession.unregisterListener(gSearchNotificationListener);
  226.  
  227.     gMailSession.RemoveFolderListener(gSearchSessionFolderListener);
  228.     gSearchSession.removeFolderListener(gFolderListener);
  229.     
  230.     if (gSearchView) {
  231.     gSearchView.close();
  232.     gSearchView = null;
  233.     }
  234.  
  235.     // release this early because msgWindow holds a weak reference
  236.     msgWindow.rootDocShell = null;
  237. }
  238.  
  239. function initializeSearchWindowWidgets()
  240. {
  241.     gFolderPicker = document.getElementById("searchableFolders");
  242.     gSearchStopButton = document.getElementById("search-button");
  243.     gStatusBar = document.getElementById('statusbar-icon');
  244.  
  245.     msgWindow = Components.classes[msgWindowContractID].createInstance(nsIMsgWindow);
  246.     msgWindow.statusFeedback = gStatusFeedback;
  247.     msgWindow.SetDOMWindow(window);
  248.  
  249.     // functionality to enable/disable buttons using nsSearchResultsController
  250.     // depending of whether items are selected in the search results thread pane.
  251.     top.controllers.insertControllerAt(0, nsSearchResultsController);
  252. }
  253.  
  254.  
  255. function onSearchStop() {
  256.     gSearchSession.interruptSearch();
  257. }
  258.  
  259. function onReset() {
  260. }
  261.  
  262. function getFirstItemByTag(root, tag)
  263. {
  264.     var node;
  265.     if (root.localName == tag)
  266.         return root;
  267.  
  268.     if (root.childNodes) {
  269.         for (node = root.firstChild; node; node=node.nextSibling) {
  270.             if (node.localName != "template") {
  271.                 result = getFirstItemByTag(node, tag);
  272.                 if (result) return result;
  273.             }
  274.         }
  275.     }
  276.     return null;
  277. }
  278.  
  279. function selectFolder(folder) {
  280.     var folderURI;
  281.     if (!folder) {
  282.         // walk folders to find first item
  283.         var firstItem = getFirstItemByTag(gFolderPicker, "menu");
  284.         folderURI = firstItem.id;
  285.     } else {
  286.         folderURI = folder.URI;
  287.     }
  288.     updateSearchFolderPicker(folderURI);
  289. }
  290.  
  291. function updateSearchFolderPicker(folderURI) {
  292.  
  293.     SetFolderPicker(folderURI, gFolderPicker.id);
  294.  
  295.     // use the URI to get the real folder
  296.     gCurrentFolder =
  297.         RDF.GetResource(folderURI).QueryInterface(nsIMsgFolder);
  298.  
  299.  
  300.     setSearchScope(GetScopeForFolder(gCurrentFolder));
  301.  
  302. }
  303.  
  304. function onChooseFolder(event) {
  305.     var folderURI = event.id;
  306.     if (folderURI) {
  307.         updateSearchFolderPicker(folderURI);
  308.     }
  309. }
  310.  
  311. function onEnterInSearchTerm()
  312. {
  313.   // on enter
  314.   // if not searching, start the search
  315.   // if searching, stop and then start again
  316.   if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) { 
  317.      onSearch(); 
  318.   }
  319.   else {
  320.      onSearchStop();
  321.      onSearch();
  322.   }
  323. }
  324.  
  325. function onSearch()
  326. {
  327.     // set the view.  do this on every search, to
  328.     // allow the outliner to reset itself
  329.     var outlinerView = gSearchView.QueryInterface(Components.interfaces.nsIOutlinerView);
  330.     if (outlinerView)
  331.     {
  332.       var outliner = GetThreadOutliner();
  333.       outliner.boxObject.QueryInterface(Components.interfaces.nsIOutlinerBoxObject).view = outlinerView;
  334.     }
  335.  
  336.     gSearchSession.clearScopes();
  337.     // tell the search session what the new scope is
  338.     if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect)
  339.         gSearchSession.addScopeTerm(GetScopeForFolder(gCurrentFolder),
  340.                                     gCurrentFolder);
  341.  
  342.     var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
  343.     if (gCurrentFolder && (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect))
  344.     {
  345.         AddSubFolders(gCurrentFolder);
  346.     }
  347.     // reflect the search widgets back into the search session
  348.     saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
  349.  
  350.     try
  351.     {
  352.       gSearchSession.search(msgWindow);
  353.     }
  354.     catch(ex)
  355.     {
  356.        dump("Search Exception\n");
  357.     }
  358.     // refresh the tree after the search starts, because initiating the
  359.     // search will cause the datasource to clear itself
  360. }
  361.  
  362. function AddSubFolders(folder) {
  363.   if (folder.hasSubFolders)
  364.   {
  365.     var subFolderEnumerator = folder.GetSubFolders();
  366.     var done = false;
  367.     while (!done)
  368.     {
  369.       var next = subFolderEnumerator.currentItem();
  370.       if (next)
  371.       {
  372.         var nextFolder = next.QueryInterface(Components.interfaces.nsIMsgFolder);
  373.         if (nextFolder)
  374.         {
  375.           if (!nextFolder.noSelect)
  376.             gSearchSession.addScopeTerm(GetScopeForFolder(nextFolder), nextFolder);
  377.           AddSubFolders(nextFolder);
  378.         }
  379.       }
  380.       try
  381.       {
  382.         subFolderEnumerator.next();
  383.        }
  384.        catch (ex)
  385.        {
  386.           done = true;
  387.        }
  388.     }
  389.   }
  390. }
  391.  
  392.  
  393. function GetScopeForFolder(folder) 
  394. {
  395.   return folder.server.searchScope;
  396. }
  397.  
  398. var nsMsgViewSortType = Components.interfaces.nsMsgViewSortType;
  399. var nsMsgViewSortOrder = Components.interfaces.nsMsgViewSortOrder;
  400. var nsMsgViewFlagsType = Components.interfaces.nsMsgViewFlagsType;
  401. var nsMsgViewCommandType = Components.interfaces.nsMsgViewCommandType;
  402.  
  403. function goUpdateSearchItems(commandset)
  404. {
  405.   for (var i = 0; i < commandset.childNodes.length; i++)
  406.   {
  407.     var commandID = commandset.childNodes[i].getAttribute("id");
  408.     if (commandID)
  409.     {
  410.       goUpdateCommand(commandID);
  411.     }
  412.   }
  413. }
  414.  
  415. function nsMsgSearchCommandUpdater()
  416. {}
  417.  
  418. nsMsgSearchCommandUpdater.prototype =
  419. {
  420.   updateCommandStatus : function()
  421.     {
  422.       // the back end is smart and is only telling us to update command status
  423.       // when the # of items in the selection has actually changed.
  424.       document.commandDispatcher.updateCommands('mail-search');
  425.     },
  426.   displayMessageChanged : function(aFolder, aSubject)
  427.   {
  428.   },
  429.  
  430.   QueryInterface : function(iid)
  431.    {
  432.      if(iid.equals(Components.interfaces.nsIMsgDBViewCommandUpdater))
  433.       return this;
  434.  
  435.      throw Components.results.NS_NOINTERFACE;
  436.      return null;
  437.    }
  438. }
  439.  
  440. function setupDatasource() {
  441.  
  442.     RDF = Components.classes[rdfServiceContractID].getService(Components.interfaces.nsIRDFService);
  443.     gSearchView = Components.classes["@mozilla.org/messenger/msgdbview;1?type=search"].createInstance(Components.interfaces.nsIMsgDBView);
  444.     var count = new Object;
  445.     var cmdupdator = new nsMsgSearchCommandUpdater();
  446.  
  447.     gSearchView.init(messenger, msgWindow, cmdupdator);
  448.     gSearchView.open(null, nsMsgViewSortType.byId, nsMsgViewSortOrder.ascending, nsMsgViewFlagsType.kNone, count);
  449.  
  450.     // the thread pane needs to use the search datasource (to get the
  451.     // actual list of messages) and the message datasource (to get any
  452.     // attributes about each message)
  453.     gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
  454.  
  455.     gSearchSessionFolderListener = gSearchSession.QueryInterface(Components.interfaces.nsIFolderListener);
  456.     gMailSession = Components.classes[mailSessionContractID].getService(Components.interfaces.nsIMsgMailSession);
  457.     gMailSession.AddFolderListener(gSearchSessionFolderListener);
  458.  
  459.     // the datasource is a listener on the search results
  460.     gViewSearchListener = gSearchView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
  461.     gSearchSession.registerListener(gViewSearchListener);
  462.     gSearchSession.addFolderListener(gFolderListener);
  463. }
  464.  
  465.  
  466. function setupSearchListener()
  467. {
  468.     // Setup the javascript object as a listener on the search results
  469.     gSearchSession.registerListener(gSearchNotificationListener);
  470. }
  471.  
  472. // stuff after this is implemented to make the thread pane work
  473. function GetFolderDatasource()
  474. {
  475.     if (!gFolderDatasource)
  476.         gFolderDatasource = Components.classes[folderDSContractID].createInstance(Components.interfaces.nsIRDFDataSource);
  477.     return gFolderDatasource;
  478. }
  479.  
  480. // used to determine if we should try to load a message
  481. function IsThreadAndMessagePaneSplitterCollapsed()
  482. {
  483.     return true;
  484. }
  485.  
  486. function setMsgDatasourceWindow(ds, msgwindow)
  487. {
  488.     try {
  489.         var msgDatasource = ds.QueryInterface(nsIMsgRDFDataSource);
  490.         msgDatasource.window = msgwindow;
  491.     } catch (ex) {
  492.         dump("error setting DS on " + ds + ": " + ex + "\n");
  493.     }
  494. }
  495.  
  496. // used to toggle functionality for Search/Stop button.
  497. function onSearchButton(event)
  498. {
  499.     if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
  500.         onSearch();
  501.     else
  502.         onSearchStop();
  503. }
  504.  
  505. // threadPane.js will be needing this, too
  506. function GetNumSelectedMessages()
  507. {
  508.    try {
  509.        return gSearchView.numSelected;
  510.    }
  511.    catch (ex) {
  512.        return 0;
  513.    }
  514. }
  515.  
  516. function GetDBView()
  517. {
  518.     return gSearchView;
  519. }
  520.  
  521. function MsgDeleteSelectedMessages()
  522. {
  523.     // we don't delete news messages, we just return in that case
  524.     if (isNewsURI(gSearchView.getURIForViewIndex(0))) 
  525.         return;
  526.  
  527.     // if mail messages delete
  528.     SetNextMessageAfterDelete();
  529.     gSearchView.doCommand(nsMsgViewCommandType.deleteMsg);
  530. }
  531.  
  532. function SetNextMessageAfterDelete()
  533. {
  534.     dump("setting next msg view index after delete to " + gSearchView.msgToSelectAfterDelete + "\n");
  535.     gNextMessageViewIndexAfterDelete = gSearchView.msgToSelectAfterDelete;
  536. }
  537.  
  538. function HandleDeleteOrMoveMessageFailed(folder)
  539. {
  540.     gNextMessageViewIndexAfterDelete = -1;
  541. }
  542.  
  543.  
  544. function HandleDeleteOrMoveMessageCompleted(folder)
  545. {
  546.         var outlinerView = gSearchView.QueryInterface(Components.interfaces.nsIOutlinerView);
  547.         var outlinerSelection = outlinerView.selection;
  548.         viewSize = outlinerView.rowCount;
  549.  
  550.         if (gNextMessageViewIndexAfterDelete >= viewSize)
  551.         {
  552.             if (viewSize > 0)
  553.                 gNextMessageViewIndexAfterDelete = viewSize - 1;
  554.             else
  555.                 gNextMessageViewIndexAfterDelete = -1;
  556.         }
  557.  
  558.         // if we are about to set the selection with a new element then DON'T clear
  559.         // the selection then add the next message to select. This just generates
  560.         // an extra round of command updating notifications that we are trying to
  561.         // optimize away.
  562.         if (gNextMessageViewIndexAfterDelete != -1) {
  563.             outlinerSelection.select(gNextMessageViewIndexAfterDelete);
  564.             // since gNextMessageViewIndexAfterDelete probably has the same value
  565.             // as the last index we had selected, the outliner isn't generating a new
  566.             // selectionChanged notification for the outliner view. So we aren't loading the 
  567.             // next message. to fix this, force the selection changed update.
  568.             var outlinerView = gSearchView.QueryInterface(Components.interfaces.nsIOutlinerView);
  569.             if (outlinerView)
  570.                 outlinerView.selectionChanged();
  571.  
  572.             EnsureRowInThreadOutlinerIsVisible(gNextMessageViewIndexAfterDelete); 
  573.         }
  574.         else
  575.             outlinerSelection.clearSelection(); /* clear selection in either case  */
  576.  
  577. }
  578.  
  579. function MoveMessageInSearch(destFolder)
  580. {
  581.     try {
  582.         // get the msg folder we're moving messages into
  583.         // if the id (uri) is not set, use file-uri which is set for
  584.         // "File Here"
  585.         var destUri = destFolder.getAttribute('id');
  586.         if (destUri.length == 0) { 
  587.           destUri = destFolder.getAttribute('file-uri')
  588.         }
  589.         
  590.         var destResource = RDF.GetResource(destUri);
  591.  
  592.         var destMsgFolder = destResource.QueryInterface(Components.interfaces.nsIMsgFolder);
  593.  
  594.         // we don't move news messages, we copy them
  595.         if (isNewsURI(gSearchView.getURIForViewIndex(0))) {
  596.           gSearchView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, destMsgFolder);
  597.         }
  598.         else {
  599.             SetNextMessageAfterDelete();
  600.             gSearchView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, destMsgFolder);
  601.         } 
  602.     }
  603.     catch (ex) {
  604.         dump("MsgMoveMessage failed: " + ex + "\n");
  605.     }   
  606. }
  607.  
  608. function BeginDragThreadPane(event)
  609. {
  610.     // no search pane dnd yet
  611.     return false;
  612. }
  613.