fix 497279 r=bienvenu, issues with view | thread |threads with unread
authorAndrew Sutherland <bugmail@asutherland.org>
Thu, 02 Jul 2009 11:00:54 -0700
changeset 2996 818dd2a4bb6cd55dc52d0310744a71f1ccd2e655
parent 2995 d971def57e3c8a6760b1a7744d933c153d155b9a
child 2997 5cf38a1e038e2b26f8b172bfc0a37dcf89bbcf2c
push idunknown
push userunknown
push dateunknown
reviewersbienvenu, issues
bugs497279
fix 497279 r=bienvenu, issues with view | thread |threads with unread
mail/base/content/mail3PaneWindowCommands.js
mailnews/base/src/dbViewWrapper.js
mailnews/base/test/unit/test_viewWrapper_logic.js
mailnews/base/test/unit/test_viewWrapper_realFolder.js
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -404,19 +404,19 @@ var DefaultController =
         if (GetNumSelectedMessages() <= 0) return false;
       case "cmd_expandAllThreads":
       case "cmd_collapseAllThreads":
         return gFolderDisplay.view.showThreaded;
       case "cmd_nextFlaggedMsg":
       case "cmd_previousFlaggedMsg":
         return IsViewNavigationItemEnabled();
       case "cmd_viewAllMsgs":
-      case "cmd_viewUnreadMsgs":
       case "cmd_viewIgnoredThreads":
         return gDBView;
+      case "cmd_viewUnreadMsgs":
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
         return !gFolderDisplay.view.isVirtual;
       case "cmd_stop":
         return true;
       case "cmd_undo":
       case "cmd_redo":
           return SetupUndoRedoCommand(command);
--- a/mailnews/base/src/dbViewWrapper.js
+++ b/mailnews/base/src/dbViewWrapper.js
@@ -78,17 +78,17 @@ var FolderNotificationHelper = {
    */
   _interestedWrappers: {},
 
   /**
    * Array of wrappers that are interested in all folders, used for
    * search results wrappers.
    */
    _curiousWrappers: [],
-   
+
   /**
    * Initialize our listeners.  We currently don't bother cleaning these up
    *  because we are a singleton and if anyone imports us, they probably want
    *  us for as long as their application so shall live.
    */
   _init: function FolderNotificationHelper__init() {
     // register with the session for our folded loaded notifications
     let mailSession =
@@ -161,17 +161,17 @@ var FolderNotificationHelper = {
    * Request notification of every little thing every folder does.
    *
    * @param aViewWrapper - the viewWrapper interested in every notification.
    *                       This will be a search results view of some sort.
    */
   noteCuriosity: function FolderNotificationHelper_noteCuriosity(aViewWrapper) {
     this._curiousWrappers.push(aViewWrapper);
   },
-  
+
   /**
    * Removal helper for use by removeNotifications.
    *
    * @param aTable The table mapping URIs to list of view wrappers.
    * @param aFolder The folder we care about.
    * @param aViewWrapper The view wrapper of interest.
    */
   _removeWrapperFromListener: function(aTable, aFolder, aViewWrapper) {
@@ -933,16 +933,27 @@ DBViewWrapper.prototype = {
     // Make sure the threaded bit is set if group-by-sort is set.  The views
     //  encode 3 states in 2-bits, and we want to avoid that odd-man-out
     //  state.
     if (this.__viewFlags & nsMsgViewFlagsType.kGroupBySort) {
       this.__viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
       this._ensureValidSort();
     }
 
+    // See if the last-used view was one of the special views.  If so, put us in
+    //  that special view mode.  We intentionally do this after restoring the
+    //  view flags because _setSpecialView enforces threading.
+    // The nsMsgDBView is the one who persists this information for us.  In this
+    //  case the nsMsgThreadedDBView superclass of the special views triggers it
+    //  when opened.
+    let viewType = dbFolderInfo.viewType;
+    if ((viewType == nsMsgViewType.eShowThreadsWithUnread) ||
+        (viewType == nsMsgViewType.eShowWatchedThreadsWithUnread))
+      this._setSpecialView(viewType);
+
     // - retrieve virtual folder configuration
     if (aFolder.flags & nsMsgFolderFlags.Virtual) {
       let virtFolder = VirtualFolderHelper.wrapVirtualFolder(aFolder);
       // Filter out the server roots; they only exist for UI reasons.
       this._underlyingFolders =
         [folder for each ([, folder] in Iterator(virtFolder.searchFolders))
                 if (!folder.isServer)];
       this._underlyingData = (this._underlyingFolders.length > 1) ?
@@ -1087,17 +1098,17 @@ DBViewWrapper.prototype = {
    *  loaded.  Assuming we are still interested in the folder, we enter the
    *  folder via _enterFolder.
    */
   _folderLoaded: function DBViewWrapper__folderLoaded(aFolder) {
     if (aFolder == this.displayedFolder) {
       this.folderLoading = false;
       // If _underlyingFolders is null, DBViewWrapper_open probably got
       // an exception trying to open the db, but after reparsing the local
-      // folder, we should have a db, so set up the view based on info 
+      // folder, we should have a db, so set up the view based on info
       // from the db.
       if (this._underlyingFolders == null) {
         this._prepareToLoadView(aFolder.msgDatabase, aFolder);
       }
       this._enterFolder();
       this.listener.onAllMessagesLoaded();
     }
   },
@@ -1263,27 +1274,29 @@ DBViewWrapper.prototype = {
    * Update the view flags to use on the view.  If we are in a view update or
    *  currently don't have a view, we save the view flags for later usage when
    *  the view gets (re)built.  If we have a view, depending on what's happening
    *  we may re-create the view or just set the bits.  The rules/reasons are:
    * - XFVF views can handle the flag changes, just set the flags.
    * - Single-folder threaded/unthreaded can handle a change to/from unthreaded/
    *    threaded, so set it.
    * - Single-folder can _not_ handle a change between grouped and not-grouped,
-   *    so re-generate the view.
+   *    so re-generate the view.  Nor can it handle a change involving
+   *    kUnreadOnly.
    */
   set _viewFlags DBViewWrapper_set__viewFlags(aViewFlags) {
     if (this._viewUpdateDepth || !this.dbView)
       this.__viewFlags = aViewFlags;
     else {
       let oldFlags = this.dbView.viewFlags;
       let changedFlags = oldFlags ^ aViewFlags;
       if ((this.isVirtual && this.isMultiFolder) ||
           (this.isSingleFolder &&
-           !(changedFlags & nsMsgViewFlagsType.kGroupBySort))) {
+           !(changedFlags & (nsMsgViewFlagsType.kGroupBySort |
+                             nsMsgViewFlagsType.kUnreadOnly)))) {
         this.dbView.viewFlags = aViewFlags;
         // ugh, and the single folder case needs us to re-apply his sort...
         if (this.isSingleFolder)
           this.dbView.sort(this.dbView.sortType, this.dbView.sortOrder);
         this.listener.onSortChanged();
       }
       else {
         this.__viewFlags = aViewFlags;
@@ -1704,23 +1717,34 @@ DBViewWrapper.prototype = {
     return Boolean(this._viewFlags & nsMsgViewFlagsType.kUnreadOnly);
   },
   /**
    * Enable/disable showing only unread messages using the view's flag-based
    *  mechanism.  This functionality can also be approximated using a mail
    *  view (or other search) for unread messages.  There also exist special
    *  views for showing messages with unread threads which is different and
    *  has serious limitations because of its nature.
+   * Setting anything to this value clears any active special view because the
+   *  actual UI use case (the "View... Threads..." menu) uses this setter
+   *  intentionally as a mutually exclusive UI choice from the special views.
    */
   set showUnreadOnly(aShowUnreadOnly) {
-    if (this.showUnreadOnly != aShowUnreadOnly) {
+    if (this._specialView || (this.showUnreadOnly != aShowUnreadOnly)) {
+      let viewRebuildRequired = (this._specialView != null);
+      this._specialView = null;
+      if (viewRebuildRequired)
+        this.beginViewUpdate();
+
       if (aShowUnreadOnly)
         this._viewFlags |= nsMsgViewFlagsType.kUnreadOnly;
       else
         this._viewFlags &= ~nsMsgViewFlagsType.kUnreadOnly;
+
+      if (viewRebuildRequired)
+        this.endViewUpdate();
     }
   },
 
   /**
    * Read-only attribute indicating if a 'special view' is in use.  There are
    *  two special views in existence, both of which are concerned about
    *  showing you threads that have any unread messages in them.  They are views
    *  rather than search predicates because the search mechanism is not capable
@@ -1739,16 +1763,19 @@ DBViewWrapper.prototype = {
   _setSpecialView: function DBViewWrapper__setSpecialView(aViewEnum) {
     // special views simply cannot work for virtual folders.  explode.
     if (this.isVirtual)
       throw new Exception("Virtual folders cannot use special views!");
     this.beginViewUpdate();
     // all special views imply a threaded view
     this.showThreaded = true;
     this._specialView = aViewEnum;
+    // We clear the search for paranoia/correctness reasons.  However, the UI
+    //  layer is currently responsible for making sure these are already zeroed
+    //  out.
     this.search.clear();
     this.endViewUpdate();
   },
   /**
    * @return true if the special view that shows threads with unread messages
    *     in them is active.
    */
   get specialViewThreadsWithUnread() {
--- a/mailnews/base/test/unit/test_viewWrapper_logic.js
+++ b/mailnews/base/test/unit/test_viewWrapper_logic.js
@@ -25,16 +25,59 @@ function test_threading_grouping_mutual_
   viewWrapper.showGroupedBySort = true;
   assert_false(viewWrapper.showThreaded,
                "view should not be threaded");
   assert_true(viewWrapper.showGroupedBySort,
               "view should be grouped by sort");
 }
 
 /**
+ * Verify that flipping between the "View... Threads..." menu cases supported by
+ *  |showUnreadOnly| / |specialViewThreadsWithUnread| /
+ *  |specialViewThreadsWithUnread| has them all be properly mutually exclusive.
+ */
+function test_threads_special_views_mutual_exclusion() {
+  let viewWrapper = make_view_wrapper();
+  let folder = make_empty_folder();
+
+  yield async_view_open(viewWrapper, folder);
+  // enter an update that will never conclude. this is fine.
+  viewWrapper.beginViewUpdate();
+
+  // turn on the special view, make sure we think it took
+  viewWrapper.specialViewThreadsWithUnread = true;
+  do_check_true(viewWrapper.specialViewThreadsWithUnread);
+  do_check_false(viewWrapper.specialViewWatchedThreadsWithUnread);
+
+  // hit showUnreadOnly which should already be false, so this makes sure that
+  //  just writing to it forces the special view off.
+  viewWrapper.showUnreadOnly = false;
+  do_check_false(viewWrapper.showUnreadOnly);
+  do_check_false(viewWrapper.specialViewThreadsWithUnread);
+  do_check_false(viewWrapper.specialViewWatchedThreadsWithUnread);
+
+  // turn on the other special view
+  viewWrapper.specialViewWatchedThreadsWithUnread = true;
+  do_check_false(viewWrapper.specialViewThreadsWithUnread);
+  do_check_true(viewWrapper.specialViewWatchedThreadsWithUnread);
+
+  // turn on show unread only mode, make sure special view is cleared
+  viewWrapper.showUnreadOnly = true;
+  do_check_true(viewWrapper.showUnreadOnly);
+  do_check_false(viewWrapper.specialViewThreadsWithUnread);
+  do_check_false(viewWrapper.specialViewWatchedThreadsWithUnread);
+
+  // turn off show unread only mode just to make sure the transition happens
+  viewWrapper.showUnreadOnly = false;
+  do_check_false(viewWrapper.showUnreadOnly);
+  do_check_false(viewWrapper.specialViewThreadsWithUnread);
+  do_check_false(viewWrapper.specialViewWatchedThreadsWithUnread);
+}
+
+/**
  * Do a quick test of primary sorting to make sure we're actually changing the
  *  sort order.  (However, we are not responsible for verifying correctness of
  *  the sort.)
  */
 function test_sort_primary() {
   let viewWrapper = make_view_wrapper();
   // we need to put messages in the folder or the sort logic doesn't actually
   //  save the sort state. (this is the C++ view's fault.)
@@ -199,16 +242,17 @@ function test_view_update_depth_logic() 
   // - depth zeroed on clear
   viewWrapper.beginViewUpdate();
   viewWrapper.close(); // this does little else because there is nothing open
   do_check_eq(viewWrapper._viewUpdateDepth, 0);
 }
 
 var tests = [
   test_threading_grouping_mutual_exclusion,
+  test_threads_special_views_mutual_exclusion,
   test_sort_primary,
   test_sort_secondary_explicit,
   test_sort_secondary_implicit,
   test_mailviews_persistence,
   test_view_update_depth_logic,
 ];
 
 function run_test() {
--- a/mailnews/base/test/unit/test_viewWrapper_realFolder.js
+++ b/mailnews/base/test/unit/test_viewWrapper_realFolder.js
@@ -674,16 +674,35 @@ function test_real_folder_special_views_
 
   // make the second thread visible by marking some message in the middle
   setThreadTwo.slice(5, 6).setRead(false);
   view_expand_all(viewWrapper);
   yield async_view_refresh(viewWrapper);
   verify_messages_in_view([setThreadOne, setThreadTwo], viewWrapper);
 }
 
+/**
+ * Make sure that we restore special views from their persisted state when
+ *  opening the view.
+ */
+function test_real_folder_special_views_persist() {
+  let viewWrapper = make_view_wrapper();
+  let folder = make_empty_folder();
+
+  yield async_view_open(viewWrapper, folder);
+  viewWrapper.beginViewUpdate();
+  viewWrapper.specialViewThreadsWithUnread = true;
+  yield async_view_end_update(viewWrapper);
+  viewWrapper.close();
+
+  yield async_view_open(viewWrapper, folder);
+  assert_true(viewWrapper.specialViewThreadsWithUnread,
+              "We should be in threads-with-unread special view mode.");
+}
+
 var tests = [
   test_real_folder_load,
   test_real_folder_update,
   test_real_folder_load_after_real_folder_load,
   // - threading modes
   test_real_folder_threading_unthreaded,
   test_real_folder_threading_threaded,
   test_real_folder_threading_grouped_by_sort,
@@ -706,15 +725,16 @@ var tests = [
   test_real_folder_quick_search_subject_or_from,
   test_real_folder_quick_search_to_or_cc,
   test_real_folder_quick_search_subject_to_or_cc,
   test_real_folder_quick_search_body,
   // - mail views with quick search
   test_real_folder_mail_view_and_quick_search,
   // - special views
   test_real_folder_special_views_threads_with_unread,
+  test_real_folder_special_views_persist,
   // (we cannot test the watched threads with unread case in local folders)
 ];
 
 function run_test() {
   loadLocalMailAccount();
   async_run_tests(tests);
 }