changeset 344:62557df25569

make the River menuitem toggle the river view open and closed; fix the Wide Message layout problem (chrome gets messed up in Wide Message mode, particularly when using extensions like Sidebar Tabs)
author alta88 <alta88@gmail.com>
date Fri, 24 Oct 2008 14:13:17 -0700
parents af1ecc9327e0
children 1bbcbc4d95d2
files content/browser.css content/browser.js content/browser.xul content/collections.js content/list.js content/list.xul locale/en-US/browser.dtd modules/service.js
diffstat 8 files changed, 235 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/content/browser.css	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/browser.css	Fri Oct 24 14:13:17 2008 -0700
@@ -61,3 +61,11 @@
 #palette-box toolbarbutton.snowlToolbarMenuButton {
   -moz-box-orient: vertical;
 }
+
+tab[snowl] {
+  background: white;
+}
+
+tab[snowl] > image {
+  list-style-image: url("chrome://snowl/content/icons/snowl-16.png");
+}
--- a/content/browser.js	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/browser.js	Fri Oct 24 14:13:17 2008 -0700
@@ -56,6 +56,11 @@
     return this._version = addon.version;
   },
 
+  get _mainWindow() {
+    delete this._mainWindow;
+    return this._mainWindow = document.getElementById("main-window");
+  },
+
   init: function() {
     let lastVersion = this._prefs.get("lastVersion");
 
@@ -74,10 +79,29 @@
 
     this._prefs.set("lastVersion", this._version);
 
-    // Listen for tab selected, to toggle preferred header view
-    gBrowser.tabContainer.addEventListener("TabSelect",
-        function() { Snowl._toggleHeader("TabSelect"); }, false);
+    // Init tab listeners
+    this._initTabListeners();
+
+    // Init river tab 
+    setTimeout(function() { Snowl._initSnowlRiverTab() }, 100);
+
+  },
 
+  _snowlRiverTab: function() {
+    // Could be null if none else a reference to the tab
+    let gBrowser = document.getElementById("content");
+    let snowlTab = null;
+    let snowlTabOpen = false;
+    
+    for (let index = 0; index < gBrowser.mTabs.length && !snowlTabOpen; index++) {
+      // Get the next tab
+      let currentTab = gBrowser.mTabs[index];
+      if (currentTab.hasAttribute("snowl")) {
+        snowlTabOpen = true;
+        snowlTab = currentTab;
+      }
+    }
+    return snowlTab;
   },
 
   //**************************************************************************//
@@ -104,15 +128,35 @@
       statusbarButton.appendChild(menuPopup);
   },
 
+  onPopupShowing: function(event) {
+    // River view menuitem checkstate is off if its tab is not selected+focused
+    let rivermenuitem = document.getElementById("viewSnowlRiver");
+    let isRiverTab = gBrowser.selectedTab.hasAttribute("snowl");
+    rivermenuitem.setAttribute("checked", isRiverTab);
+
+    // Header checked state
+    let menuitems = document.getElementsByAttribute("name", "snowlHeaderMenuitemGroup");
+    let selectedIndex = this._prefs.get("message.headerView");
+    if (menuitems)
+      menuitems[selectedIndex].setAttribute("checked", true);
+  },
+
+  layoutName: ["classic", "vertical", "widemessage", "widethread", "stacked"],
+
   onLayoutPopupShowing: function(event) {
     let layoutmenu = document.getElementById("snowlLayoutMenu");
     let lchecked = document.getElementById("viewSnowlList").hasAttribute("checked");
     let schecked = document.getElementById("viewSnowlStream").hasAttribute("checked");
     let layoutmenuitems = document.getElementsByAttribute("name", "snowlLayoutMenuitemGroup");
+    let layout = this._mainWindow.getAttribute("snowllayout");
+    let layoutIndex = this.layoutName.indexOf(layout);
 
     if (layoutmenuitems) {
-      for (var i = 0; i < layoutmenuitems.length; i++)
+      for (var i = 0; i < layoutmenuitems.length; i++) {
         layoutmenuitems[i].setAttribute("disabled", !lchecked);
+        if (i == layoutIndex)
+          layoutmenuitems[i].setAttribute("checked", true);
+      }
     }
     document.getElementById("snowlToolbarMenuitem").setAttribute("disabled",
         (!lchecked && !schecked) ? true : false);
@@ -138,7 +182,44 @@
   // Event Handlers
 
   onRiverView: function() {
-    gBrowser.selectedTab = gBrowser.addTab("chrome://snowl/content/river.xul");
+    // Unchecking river menuitem, if current tab is snowl river tab, close it
+    let snowlRiverTab = this._snowlRiverTab();
+    if (gBrowser.selectedTab == snowlRiverTab) {
+      this.closeRiverView(gBrowser.selectedTab);
+      return;
+    }
+
+    // Handle unchecked menuitem
+    if (snowlRiverTab != null) {
+      // Snowl River tab is already open, focus it
+      gBrowser.selectedTab = snowlRiverTab;
+      gBrowser.focus();
+    }
+    else {
+      // River tab not open, create a new one, toggle other views in sidebar 'off'
+//      let lchecked = document.getElementById("viewSnowlList").hasAttribute("checked");
+//      let schecked = document.getElementById("viewSnowlStream").hasAttribute("checked");
+//      if (lchecked)
+//        toggleSidebar('viewSnowlList');
+//      if (schecked)
+//        toggleSidebar('viewSnowlStream');
+
+      gBrowser.selectedTab = gBrowser.addTab("chrome://snowl/content/river.xul");
+      let tabIndex = gBrowser.mTabContainer.selectedIndex;
+      this._mainWindow.setAttribute("snowltabindex", tabIndex);
+      gBrowser.mTabs[tabIndex].setAttribute("snowl", "river");
+    }
+  },
+
+  closeRiverView: function(aTab) {
+    gBrowser.removeTab(aTab);
+    document.getElementById("viewSnowlRiver").setAttribute("checked", false);
+  },
+
+  onTabSelect: function() {
+    // Make sure desired header view showing..
+    this._toggleHeader("TabSelect");
+    // others..
   },
 
   onCheckForNewMessages: function() {
@@ -159,8 +240,22 @@
     SnowlOPML.export(window);
   },
 
+  _initTabListeners: function() {
+    // TabSelect - make sure header state correct
+    gBrowser.tabContainer.addEventListener("TabSelect",
+        function() { Snowl.onTabSelect("TabSelect"); }, false);
+
+    // TabOpen, TabClose, TabMove - make sure snowl River tab index is correct
+    gBrowser.tabContainer.addEventListener("TabOpen",
+        function() { Snowl._resetSnowlRiverTabIndex(); }, false);
+    gBrowser.tabContainer.addEventListener("TabClose",
+        function() { Snowl._resetSnowlRiverTabIndex(); }, false);
+    gBrowser.tabContainer.addEventListener("TabMove",
+        function() { Snowl._resetSnowlRiverTabIndex(); }, false);
+  },
+
   //**************************************************************************//
-  // Buttons
+  // Buttons, menuitems, commands..
 
   // Header toggle
   kNoHeader: 0,
@@ -212,7 +307,6 @@
           "none" : (selectedIndex == 1 ? "brief" : "full"));
     if (menuitems) {
       menuitems[selectedIndex].setAttribute("checked", true);
-//alert("id: checked "+menuitems[selectedIndex].id+":"+menuitems[selectedIndex].getAttribute("checked"));
     }
   },
 
@@ -239,6 +333,29 @@
       menuitem.setAttribute("checked", !toolbar.hidden);
     }
   },
+
+  // Need to init snowl River tab, if exists
+  _initSnowlRiverTab: function() {
+    let tabIndex = parseInt(this._mainWindow.getAttribute("snowltabindex"));
+    if (tabIndex >= 0 && tabIndex <= gBrowser.mTabs.length)
+      gBrowser.mTabs[tabIndex].setAttribute("snowl", "river");
+  },
+
+  // Need to reset snowl River tab index
+  _resetSnowlRiverTabIndex: function () {
+    setTimeout(function() {
+      let snowlRiverTab = Snowl._snowlRiverTab();
+      if (snowlRiverTab) {
+        // River tab exists
+        let newIndex = snowlRiverTab._tPos;
+        Snowl._mainWindow.setAttribute("snowltabindex", newIndex);
+      }
+      else
+        // Tab closed or none, remove it
+        Snowl._mainWindow.removeAttribute("snowltabindex");
+    }, 200)
+  },
+
 };
 
 Cu.import("resource://snowl/modules/Preferences.js", Snowl);
--- a/content/browser.xul	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/browser.xul	Fri Oct 24 14:13:17 2008 -0700
@@ -74,14 +74,22 @@
     <menu id="snowlMenu" class="menu-iconic" label="&snowlMenu.label;"
           image="chrome://snowl/content/icons/snowl-16.png"
           accesskey="&snowlMenu.accesskey;" insertafter="menu_openAddons">
-      <menupopup id="snowlMenuPopup" onpopuphiding="Snowl.onPopupHiding(event)">
+      <menupopup id="snowlMenuPopup"
+                 onpopupshowing="Snowl.onPopupShowing(event)"
+                 onpopuphiding="Snowl.onPopupHiding(event)">
         <menuitem observes="viewSnowlList" label="&listView.label;"
                   accesskey="&listView.accesskey;"/>
         <menuitem observes="viewSnowlStream" label="&streamView.label;"
                   accesskey="&streamView.accesskey;"/>
         <menuseparator/>
-        <menuitem label="&riverView.label;" accesskey="&riverView.accesskey;"
+        <menuitem id="viewSnowlRiver"
+                  label="&riverView.label;"
+                  accesskey="&riverView.accesskey;"
+                  autoCheck="false"
+                  type="checkbox"
+                  persist="checked"
                   oncommand="Snowl.onRiverView()"/>
+        <menuseparator/>
         <menuitem label="&checkForNewMessages.label;"
                   accesskey="&checkForNewMessages.accesskey;"
                   oncommand="Snowl.onCheckForNewMessages()"/>
@@ -97,35 +105,30 @@
                       checked="true"
                       accesskey="&layoutClassic.accesskey;"
                       name="snowlLayoutMenuitemGroup"
-                      persist="checked"
                       oncommand="SnowlMessageView.switchLayout(SnowlMessageView.kClassicLayout)"/>
             <menuitem id="snowlLayoutMenuitemVertical"
                       label="&layoutVertical.label;"
                       type="radio"
                       accesskey="&layoutVertical.accesskey;"
                       name="snowlLayoutMenuitemGroup"
-                      persist="checked"
                       oncommand="SnowlMessageView.switchLayout(SnowlMessageView.kVerticalLayout)"/>
             <menuitem id="snowlLayoutMenuitemWideMessage"
                       label="&layoutWideMessage.label;"
                       type="radio"
                       accesskey="&layoutWideMessage.accesskey;"
                       name="snowlLayoutMenuitemGroup"
-                      persist="checked"
                       oncommand="SnowlMessageView.switchLayout(SnowlMessageView.kWideMessageLayout)"/>
             <menuitem id="snowlLayoutMenuitemWideThread"
                       label="&layoutWideThread.label;"
                       type="radio"
                       accesskey="&layoutWideThread.accesskey;"
                       name="snowlLayoutMenuitemGroup"
-                      persist="checked"
                       oncommand="SnowlMessageView.switchLayout(SnowlMessageView.kWideThreadLayout)"/>
             <menuitem id="snowlLayoutMenuitemStacked"
                       label="&layoutStacked.label;"
                       type="radio"
                       accesskey="&layoutStacked.accesskey;"
                       name="snowlLayoutMenuitemGroup"
-                      persist="checked"
                       oncommand="SnowlMessageView.switchLayout(SnowlMessageView.kStackedLayout)"/>
             <menuseparator/>
             <menuitem id="snowlHeaderMenuitemNone"
@@ -133,7 +136,6 @@
                       type="radio"
                       accesskey="&headerNone.accesskey;"
                       name="snowlHeaderMenuitemGroup"
-                      persist="checked"
                       headerType="Snowl.kNoHeader"
                       oncommand="Snowl._toggleHeader(event)"/>
             <menuitem id="snowlHeaderMenuitemBrief"
@@ -141,7 +143,6 @@
                       type="radio"
                       accesskey="&headerBrief.accesskey;"
                       name="snowlHeaderMenuitemGroup"
-                      persist="checked"
                       headerType="Snowl.kBriefHeader"
                       oncommand="Snowl._toggleHeader(event)"/>
             <menuitem id="snowlHeaderMenuitemFull"
@@ -149,27 +150,22 @@
                       type="radio"
                       accesskey="&headerFull.accesskey;"
                       name="snowlHeaderMenuitemGroup"
-                      persist="checked"
                       headerType="Snowl.kFullHeader"
                       oncommand="Snowl._toggleHeader(event)"/>
             <menuseparator/>
             <menuitem id="snowlToolbarMenuitem"
                       label="&toolbar.label;"
                       type="checkbox"
-                      checked="true"
-                      autoCheck="false"
                       accesskey="&toolbar.accesskey;"
                       name="snowlToolbar"
-                      persist="checked"
                       oncommand="Snowl._toggleToolbar(event)"/>
             <menuitem id="snowlViewToolbarMenuitem"
                       label="&viewtoolbar.label;"
                       type="checkbox"
                       checked="true"
-                      autoCheck="false"
+                      persist="checked"
                       accesskey="&viewtoolbar.accesskey;"
                       name="snowlViewToolbar"
-                      persist="checked"
                       oncommand="Snowl._toggleToolbar(event)"/>
           </menupopup>
         </menu>
--- a/content/collections.js	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/collections.js	Fri Oct 24 14:13:17 2008 -0700
@@ -79,6 +79,11 @@
     this._getCollections();
     this._tree.view = this;
 
+    // Ensure collection selection maintained, if in List sidebar
+    if (document.getElementById("snowlSidebar"))
+      this._tree.view.selection.select(
+          gMessageViewWindow.SnowlMessageView._listCollectionIndex);
+
     // Add a capturing click listener to the tree so we can find out if the user
     // clicked on a row that is already selected (in which case we let them edit
     // the collection name).
@@ -305,7 +310,8 @@
       return;
 
     let collection = this._rows[this._tree.currentIndex];
-    gMessageViewWindow.SnowlMessageView.setCollection(collection);
+    let index = this._tree.currentIndex;
+    gMessageViewWindow.SnowlMessageView.setCollection(collection, index);
   },
 
   onClick: function(aEvent) {
--- a/content/list.js	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/list.js	Fri Oct 24 14:13:17 2008 -0700
@@ -108,6 +108,10 @@
     return this._unreadButton = document.getElementById("snowlUnreadButton");
   },
 
+  // Always maintain selected collection within a session, only for List view
+  // XXX store on document for restore on restart??
+  _listCollectionIndex: null,
+
   // Maps XUL tree column IDs to collection properties.
   _columnProperties: {
     "snowlAuthorCol": "author",
@@ -175,6 +179,10 @@
     let sidebarBox = document.getElementById("sidebar-box");
     this._snowlSidebar.appendChild(sidebarBox);
 
+    // Save position of sidebar/splitter (for wide message layout change)
+    let sidebarSplitter = document.getElementById("sidebar-splitter");
+    this.gSidebarSplitterSiblingID = sidebarSplitter.nextSibling.id;
+
     // Listen for sidebar-box hidden attr change, to toggle properly
     sidebarBox.addEventListener("DOMAttrModified",
         function(aEvent) { 
@@ -182,12 +190,11 @@
             SnowlMessageView._snowlSidebar.hidden = (aEvent.newValue == "true");
         }, false);
 
-    // Restore previous layout view and set menuitem checked
-    let mainWindow = document.getElementById("main-window");
-    let layout = mainWindow.getAttribute("snowlLayout");
+    // Restore previous layout view
+    let layout = Snowl._mainWindow.getAttribute("snowllayout");
     // If error or first time default to 'classic' view
-    let layoutIndex = this.layoutName.indexOf(layout) < 0 ? 
-        0 : this.layoutName.indexOf(layout);
+    let layoutIndex = Snowl.layoutName.indexOf(layout) < 0 ?
+        this.kClassicLayout : Snowl.layoutName.indexOf(layout);
     this.layout(layoutIndex);
   },
 
@@ -288,8 +295,9 @@
     this._rebuildView();
   },
 
-  setCollection: function(collection) {
+  setCollection: function(collection, index) {
     this._collection = collection;
+    this._listCollectionIndex = index;
     this._rebuildView();
   },
 
@@ -302,7 +310,8 @@
     // by reinitializing it instead of merely invalidating the box object
     // (which wouldn't accommodate changes to the number of rows).
     // XXX Is there a better way to do this?
-    this._tree.view = this;
+    // this._tree.view = this; <- doesn't work for all DOM moves..
+    this._tree.boxObject.QueryInterface(Ci.nsITreeBoxObject).view = this;
 
     // Scroll back to the top of the tree.
     this._tree.boxObject.scrollToRow(this._tree.boxObject.getFirstVisibleRow());
@@ -315,7 +324,7 @@
     // Because we've moved the tree, we have to reattach the view to it,
     // or we will get the error: "this._tree.boxObject.invalidate is not
     // a function" when we switch sources.
-    this._tree.view = this;
+    this._tree.boxObject.QueryInterface(Ci.nsITreeBoxObject).view = this;
   },
 
   // Layout views
@@ -325,18 +334,25 @@
   kWideThreadLayout: 3,
   kStackedLayout: 4,
   gCurrentLayout: null,
-  layoutName: ["classic", "vertical", "widemessage", "widethread", "stacked"],
+  gSidebarSplitterSiblingID: null,
 
   layout: function(layout) {
-    let mainWindow = document.getElementById("main-window");
+    if (layout == this.gCurrentLayout)
+      return;
+
     let browser = document.getElementById("browser");
     let appcontent = document.getElementById("appcontent");
     let content = document.getElementById("content");
     let sidebarSplitter = document.getElementById("sidebar-splitter");
+    let snowlSidebar = this._snowlSidebar;
     let snowlThreadContainer = this._snowlViewContainer;
     let snowlThreadSplitter = this._snowlViewSplitter;
 
-    let layoutThreadPaneParent = ["appcontent", "browser", "snowlSidebar", "main-window", "sidebar-box"];
+    let layoutThreadPaneParent = ["appcontent",
+                                  "browser",
+                                  "snowlSidebar",
+                                  "main-window",
+                                  "sidebar-box"];
     // A 'null' is an effective appendChild, code is nice and reusable..
     let layoutThreadPaneInsertBefore = [content, appcontent, null, browser, null];
     // 0=horizontal, 1=vertical for orient arrays..
@@ -350,25 +366,46 @@
         case this.kClassicLayout:
         case this.kVerticalLayout:
         case this.kWideThreadLayout:
-          desiredParent.insertBefore(snowlThreadContainer, layoutThreadPaneInsertBefore[layout]);
-          desiredParent.insertBefore(snowlThreadSplitter, layoutThreadPaneInsertBefore[layout]);
+        case this.kStackedLayout:
+          // Restore sidebar if coming from wide mess
+          if (this.gCurrentLayout == this.kWideMessageLayout) {
+            browser.insertBefore(snowlSidebar,
+                document.getElementById(this.gSidebarSplitterSiblingID));
+            browser.insertBefore(sidebarSplitter,
+                document.getElementById(this.gSidebarSplitterSiblingID));
+          }
+          if (layout == this.kStackedLayout)
+            desiredParent.insertBefore(snowlThreadSplitter,
+                layoutThreadPaneInsertBefore[layout]);
+            desiredParent.insertBefore(snowlThreadContainer,
+                layoutThreadPaneInsertBefore[layout]);
+          if (layout != this.kStackedLayout)
+            desiredParent.insertBefore(snowlThreadSplitter,
+                layoutThreadPaneInsertBefore[layout]);
           break;
-        case this.kStackedLayout:
+
         case this.kWideMessageLayout:
-          desiredParent.insertBefore(snowlThreadSplitter, layoutThreadPaneInsertBefore[layout]);
-          desiredParent.insertBefore(snowlThreadContainer, layoutThreadPaneInsertBefore[layout]);
+          // Move sidebar for wide mess
+          Snowl._mainWindow.insertBefore(snowlSidebar, browser);
+          Snowl._mainWindow.insertBefore(sidebarSplitter, browser);
+
+          desiredParent.insertBefore(snowlThreadSplitter,
+              layoutThreadPaneInsertBefore[layout]);
+          desiredParent.insertBefore(snowlThreadContainer,
+              layoutThreadPaneInsertBefore[layout]);
           break;
       }
     }
 
     // Adjust orient and flex for all layouts
-    browser.orient = sidebarSplitterOrient[layout] ? "vertical" : "horizontal";
-    snowlThreadSplitter.orient = layoutsnowlThreadSplitterOrient[layout] ? "vertical" : "horizontal";
-    sidebarSplitter.orient = sidebarSplitterOrient[layout] ? "vertical" : "horizontal";
+    snowlThreadSplitter.orient = layoutsnowlThreadSplitterOrient[layout] ?
+        "vertical" : "horizontal";
+    sidebarSplitter.orient = sidebarSplitterOrient[layout] ?
+        "vertical" : "horizontal";
     snowlThreadContainer.setAttribute("flex", layoutSnowlBoxFlex[layout]);
 
     // Store the layout
-    mainWindow.setAttribute("snowlLayout", this.layoutName[layout]);
+    Snowl._mainWindow.setAttribute("snowllayout", Snowl.layoutName[layout]);
     this.gCurrentLayout = layout;
   },
 
--- a/content/list.xul	Tue Oct 21 18:05:30 2008 -0700
+++ b/content/list.xul	Fri Oct 24 14:13:17 2008 -0700
@@ -47,8 +47,9 @@
   <script type="application/x-javascript" src="list.js"/>
 
   <window id="main-window"
-          persist="screenX screenY width height sizemode snowlLayout"
-          snowlLayout="classic"/>
+          persist="screenX screenY width height sizemode
+             snowllayout snowltabindex snowlcollectionindex"
+          snowllayout="classic"/>
 
   <hbox id="browser">
     <hbox id="snowlSidebar"
--- a/locale/en-US/browser.dtd	Tue Oct 21 18:05:30 2008 -0700
+++ b/locale/en-US/browser.dtd	Fri Oct 24 14:13:17 2008 -0700
@@ -10,7 +10,7 @@
 
 <!ENTITY listView.label                       "List">
 <!ENTITY listView.accesskey                   "l">
-<!ENTITY riverView.label                      "Open River in New Tab">
+<!ENTITY riverView.label                      "River">
 <!ENTITY riverView.accesskey                  "r">
 <!ENTITY streamView.label                     "Stream">
 <!ENTITY streamView.accesskey                 "t">
--- a/modules/service.js	Tue Oct 21 18:05:30 2008 -0700
+++ b/modules/service.js	Fri Oct 24 14:13:17 2008 -0700
@@ -96,6 +96,14 @@
     return this._converterSvc;
   },
 
+  get _promptSvc() {
+    let promptSvc =
+      Cc["@mozilla.org/embedcomp/prompt-service;1"].
+      getService(Ci.nsIPromptService);
+    this.__defineGetter__("_promptSvc", function() { return promptSvc });
+    return this._promptSvc;
+  },
+
   _log: null,
 
   _init: function() {
@@ -296,8 +304,23 @@
    */
   hasMessage: function(aExternalID) {
     return SnowlDatastore.selectHasMessage(aExternalID);
+  },
+
+  _restartApp: function() {
+    // Notify all windows that an application quit has been requested.
+    var os = Cc["@mozilla.org/observer-service;1"].
+             getService(Ci.nsIObserverService);
+    var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                     createInstance(Ci.nsISupportsPRBool);
+    os.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+    // Something aborted the quit process.
+    if (cancelQuit.data)
+      return;
+
+    Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).
+    quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
   }
-
 };
 
 SnowlService._init();