Final part of bug 489588 - hook processes from activity manager into the status bar. r=bievenu,ui-review=clarkbw
authorMark Banner <bugzilla@standard8.plus.com>
Fri, 24 Apr 2009 23:18:43 +0100
changeset 2476 1a60aac99d5bb4d154711ffa3e29e01bbc2f0b71
parent 2475 eaae6740e85e9da0b2720cbb2dec521b0d3881ad
child 2477 b11b7bed1b182ef5e265b50b7a190cb5128a655a
push idunknown
push userunknown
push dateunknown
reviewersbievenu
bugs489588
Final part of bug 489588 - hook processes from activity manager into the status bar. r=bievenu,ui-review=clarkbw
mail/base/content/SearchDialog.js
mail/base/content/mailWindow.js
mail/base/content/subscribe.js
--- a/mail/base/content/SearchDialog.js
+++ b/mail/base/content/SearchDialog.js
@@ -44,18 +44,17 @@ var gCurrentFolder;
 
 var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
 var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
 var nsIMsgRDFDataSource = Components.interfaces.nsIMsgRDFDataSource;
 var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
 
 var gFolderDatasource;
 var gFolderPicker;
-var gStatusBar = null;
-var gStatusFeedback = new nsMsgStatusFeedback();
+var gStatusFeedback;
 var gTimelineEnabled = false;
 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.
@@ -307,23 +306,24 @@ function searchOnUnload()
     // 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 = Components.classes["@mozilla.org/messenger/msgwindow;1"]
                           .createInstance(nsIMsgWindow);
     msgWindow.domWindow = window;
     msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
+
+    gStatusFeedback = new nsMsgStatusFeedback();
     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);
 }
 
 
--- a/mail/base/content/mailWindow.js
+++ b/mail/base/content/mailWindow.js
@@ -156,16 +156,17 @@ function InitMsgWindow()
 
 function nsMsgStatusFeedback()
 {
   this._statusText = document.getElementById("statusText");
   this._progressBar = document.getElementById("statusbar-icon"); 
   this._progressBarContainer = document.getElementById("statusbar-progresspanel");
   this._throbber = document.getElementById("navigator-throbber");
   this._stopCmd = document.getElementById("cmd_stop");
+  this._activeProcesses = new Array();
 }
 
 nsMsgStatusFeedback.prototype =
 {
   // Document elements.
   _statusText: null,
   _progressBar: null,
   _progressBarContainer: null,
@@ -175,16 +176,18 @@ nsMsgStatusFeedback.prototype =
   // Member variables.
   _startTimeoutID: null,
   _stopTimeoutID: null,
   // How many start meteors have been requested.
   _startRequests: 0,
   _meteorsSpinning: false,
   _defaultStatusText: null,
   _progressBarVisible: false,
+  _activeProcesses: null,
+  _statusFeedbackProgress: -1,
 
   // nsIXULBrowserWindow implementation.
   setJSStatus: function(status) {
     if (status.length > 0)
       this.showStatusString(status);
   },
 
   setJSDefaultStatus: function(status) {
@@ -197,16 +200,17 @@ nsMsgStatusFeedback.prototype =
   setOverLink: function(link, context) {
     this._statusText.label = link;
   },
 
   QueryInterface: function(iid) {
     if (iid.equals(Components.interfaces.nsIMsgStatusFeedback) ||
         iid.equals(Components.interfaces.nsIXULBrowserWindow) ||
         iid.equals(Components.interfaces.nsIActivityMgrListener) ||
+        iid.equals(Components.interfaces.nsIActivityListener) ||
         iid.equals(Components.interfaces.nsISupportsWeakReference) ||
         iid.equals(Components.interfaces.nsISupports))
       return this;
     throw Components.results.NS_NOINTERFACE;
   },
 
   // nsIMsgStatusFeedback implementation.
   showStatusString: function(statusText) {
@@ -216,39 +220,34 @@ nsMsgStatusFeedback.prototype =
       this._defaultStatusText = "";
     this._statusText.label = statusText;
   },
 
   _startMeteors: function() {
     this._meteorsSpinning = true;
     this._startTimeoutID = null;
 
-    if (!this._progressBarVisible) {
-      this._progressBarContainer.removeAttribute('collapsed');
-      this._progressBarVisible = true;
-    }
-
     // Turn progress meter on.
-    this._progressBar.setAttribute("mode", "undetermined");
+    this.updateProgress();
 
     // Start the throbber.
     if (this._throbber)
       this._throbber.setAttribute("busy", true);
 
     // Turn on stop button and menu.
     if (this._stopCmd)
       this._stopCmd.removeAttribute("disabled");
   },
 
   startMeteors: function() {
     this._startRequests++;
     // If we don't already have a start meteor timeout pending
     // and the meteors aren't spinning, then kick off a start.
     if (!this._startTimeoutID && !this._meteorsSpinning &&
-        window.MsgStatusFeedback)
+        "MsgStatusFeedback" in window)
       this._startTimeoutID =
         setTimeout('window.MsgStatusFeedback._startMeteors();', 500);
 
     // Since we are going to start up the throbber no sense in processing
     // a stop timeout...
     if (this._stopTimeoutID) {
       clearTimeout(this._stopTimeoutID);
       this._stopTimeoutID = null;
@@ -257,69 +256,159 @@ nsMsgStatusFeedback.prototype =
 
   _stopMeteors: function() {
     this.showStatusString(defaultStatus);
 
     // stop the throbber
     if (this._throbber)
       this._throbber.setAttribute("busy", false);
 
-    // Turn progress meter off.
-    /// XXX is this valid?
-    this._progressBar.setAttribute("mode", "normal");
-    this._progressBar.value = 0;  // be sure to clear the progress bar
-    this._progressBar.label = "";
-
-    if (this._progressBarVisible) {
-      this._progressBarContainer.collapsed = true;
-      this._progressBarVisible = false;
-    }
-
     if (this._stopCmd)
       this._stopCmd.setAttribute("disabled", "true");
 
     this._meteorsSpinning = false;
     this._stopTimeoutID = null;
+
+    // Turn progress meter off.
+    this._statusFeedbackProgress = -1;
+    this.updateProgress();
   },
 
   stopMeteors: function() {
     if (this._startRequests > 0)
       this._startRequests--;
 
     // If we are going to be starting the meteors, cancel the start.
     if (this._startRequests == 0 && this._startTimeoutID) {
       clearTimeout(this._startTimeoutID);
       this._startTimeoutID = null;
     }
 
     // If we have no more pending starts and we don't have a stop timeout
     // already in progress AND the meteors are currently running then fire a
     // stop timeout to shut them down.
     if (this._startRequests == 0 && !this._stopTimeoutID &&
-        this._meteorsSpinning && window.MsgStatusFeedback) {
+        this._meteorsSpinning && "MsgStatusFeedback" in window) {
       this._stopTimeoutID =
         setTimeout('window.MsgStatusFeedback._stopMeteors();', 500);
     }
   },
 
   showProgress: function(percentage) {
-    if (percentage >= 0) {
-      this._progressBar.setAttribute("mode", "normal");
-      this._progressBar.value = percentage;
-      this._progressBar.label = Math.round(percentage) + "%";
+    this._statusFeedbackProgress = percentage;
+    this.updateProgress();
+  },
+
+  updateProgress: function() {
+    if (this._meteorsSpinning) {
+      // In this function, we expect that the maximum for each progress is 100,
+      // i.e. we are dealing with percentages. Hence we can combine several
+      // processes running at the same time.
+      let currentProgress = 0;
+      let progressCount = 0;
+
+      // For each activity that is in progress, get its status.
+      this._activeProcesses.forEach(function (element) {
+          if (element.state ==
+              Components.interfaces.nsIActivityProcess.STATE_INPROGRESS &&
+              element.percentComplete != -1) {
+            currentProgress += element.percentComplete;
+            ++progressCount;
+          }
+        });
+
+      // Add the generic progress that's fed to the status feedback object if
+      // we've got one.
+      if (this._statusFeedbackProgress != -1) {
+        currentProgress += this._statusFeedbackProgress;
+        ++progressCount;
+      }
+
+      let percentage = 0;
+      if (progressCount) {
+        percentage = currentProgress / progressCount;
+      }
+
+      if (!percentage)
+        this._progressBar.setAttribute("mode", "undetermined");
+      else {
+        this._progressBar.setAttribute("mode", "determined");
+        this._progressBar.value = percentage;
+        this._progressBar.label = Math.round(percentage) + "%";
+      }
+      if (!this._progressBarVisible) {
+        this._progressBarContainer.removeAttribute('collapsed');
+        this._progressBarVisible = true;
+      }
+    }
+    else {
+      // Stop the bar spinning as we're not doing anything now.
+      this._progressBar.setAttribute("mode", "determined");
+      this._progressBar.value = 0;
+      this._progressBar.label = ""; 
+
+      if (this._progressBarVisible) {
+        this._progressBarContainer.collapsed = true;
+        this._progressBarVisible = false;
+      }
     }
   },
 
+  // nsIActivityMgrListener
   onAddedActivity: function(aID, aActivity) {
     if (aActivity instanceof Components.interfaces.nsIActivityEvent) {
       this.showStatusString(aActivity.displayText);
     }
+    else if (aActivity instanceof Components.interfaces.nsIActivityProcess) {
+      this._activeProcesses.push(aActivity);
+      aActivity.addListener(this);
+      this.startMeteors();
+    }
   },
 
   onRemovedActivity: function(aID) {
+    this._activeProcesses =
+      this._activeProcesses.filter(function (element) {
+        if (element.id == aID) {
+          element.removeListener(this);
+          this.stopMeteors();
+          return false;
+        }
+        return true;
+      }, this);
+  },
+
+  // nsIActivityListener
+  onStateChanged: function(aActivity, aOldState) {
+  },
+
+  onProgressChanged: function(aActivity, aStatusText, aWorkUnitsCompleted,
+                              aTotalWorkUnits) {
+    let index = this._activeProcesses.indexOf(aActivity);
+
+    // Iterate through the list trying to find the first active process, but
+    // only go as far as our process.
+    for (var i = 0; i < index; ++i) {
+      if (this._activeProcesses[i].status ==
+          Components.interfaces.nsIActivityProcess.STATE_INPROGRESS)
+        break;
+    }
+
+    // If the found activity was the same as our activity, update the status
+    // text.
+    if (i == index)
+      // Use the display text if we haven't got any status text. I'm assuming
+      // that the status text will be generally what we want to see on the
+      // status bar.
+      this.showStatusString(aStatusText ? aStatusText : aActivity.displayText);
+
+    this.updateProgress();
+  },
+
+  onHandlerChanged: function(aActivity) {
   }
 }
 
 
 function nsMsgWindowCommands()
 {
 }
 
--- a/mail/base/content/subscribe.js
+++ b/mail/base/content/subscribe.js
@@ -38,21 +38,20 @@
  * ***** END LICENSE BLOCK ***** */
 
 var gSubscribeTree = null;
 var gSearchTree;
 var okCallback = null;
 var gChangeTable = {};
 var gServerURI = null;
 var gSubscribableServer = null;
-var gStatusBar = null;
 var gNameField = null;
 var gNameFieldLabel = null;
 var gFolderDelimiter = ".";
-var gStatusFeedback = new nsMsgStatusFeedback;
+var gStatusFeedback;
 var gMessengerBundle = null;
 var gSubscribeDeck = null;
 var gSearchView = null;
 var gSearchTreeBoxObject = null;
 // the rdf service
 var RDF = Components.classes['@mozilla.org/rdf/rdf-service;1'].getService(Components.interfaces.nsIRDFService);
 var subscribeDS = RDF.GetDataSource("rdf:subscribe");
 
@@ -149,17 +148,16 @@ var MySubscribeListener = {
         document.getElementById("refreshButton").disabled = false;
         document.getElementById("currentListTab").disabled = false;
         document.getElementById("newGroupsTab").disabled = false;
   }
 };
 
 function SetUpTree(forceToServer, getOnlyNew)
 {
-  gStatusBar = document.getElementById('statusbar-icon');
   if (!gServerURI)
     return;
 
   var server = GetMsgFolderFromUri(gServerURI, true).server;
   try
   {
     CleanUpSearchView();
     gSubscribableServer = server.QueryInterface(Components.interfaces.nsISubscribableServer);
@@ -232,16 +230,17 @@ function SubscribeOnLoad()
   gNameField = document.getElementById("namefield");
   gNameFieldLabel = document.getElementById("namefieldlabel");
 
   gSubscribeDeck = document.getElementById("subscribedeck");
 
   msgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
                         .createInstance(Components.interfaces.nsIMsgWindow);
   msgWindow.domWindow = window;
+  gStatusFeedback = new nsMsgStatusFeedback
   msgWindow.statusFeedback = gStatusFeedback;
   msgWindow.rootDocShell.allowAuth = true;
   msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
 
   // look in arguments[0] for parameters
   if (window.arguments && window.arguments[0]) {
     if ( window.arguments[0].okCallback ) {
       top.okCallback = window.arguments[0].okCallback;