changeset 355:e9d7087abad1

implement tree (list and collection) contextmenu foundation, change XXX Utils to DateUtils, some click handling fixes.
author alta88
date Sun, 02 Nov 2008 10:20:12 -0700
parents 3af68614eb52
children 8d90feea857c
files content/collections.js content/collections.xul content/list-sidebar.js content/list.js content/list.xul content/message/message.js content/river.js content/stream.js locale/en-US/collections.dtd locale/en-US/list.dtd modules/collection.js modules/feed.js modules/message.js modules/service.js modules/source.js modules/twitter.js modules/utils.js
diffstat 17 files changed, 269 insertions(+), 104 deletions(-) [+]
line wrap: on
line diff
--- a/content/collections.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/collections.js	Sun Nov 02 10:20:12 2008 -0700
@@ -86,14 +86,7 @@
 
     // 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).
-    // FIXME: disable this for names that can't be changed.
-    this._tree.addEventListener("mousedown", function(aEvent) { CollectionsView.onClick(aEvent) }, true);
+      this._tree.view.selection.select(SnowlUtils.gListViewCollectionIndex);
   },
 
 
@@ -314,25 +307,21 @@
   },
 
   onSelect: function(aEvent) {
-    if (this._tree.currentIndex == -1)
+    if (this._tree.currentIndex == -1 || SnowlUtils.gRightMouseButtonDown)
       return;
 
     let collection = this._rows[this._tree.currentIndex];
-    let index = this._tree.currentIndex;
-    gMessageViewWindow.SnowlMessageView.setCollection(collection, index);
+    SnowlUtils.gListViewCollectionIndex = this._tree.currentIndex;
+    gMessageViewWindow.SnowlMessageView.setCollection(collection);
   },
 
-  onClick: function(aEvent) {
-this._log.info("on click");
-//this._log.info(Log4Moz.enumerateProperties(aEvent).join("\n"));
-//this._log.info(aEvent.target.nodeName);
+  onCollectionsTreeMouseDown: function(aEvent) {
+    SnowlUtils.onTreeMouseDown(aEvent, this._tree);
+  },
 
-  let row = {}, col = {}, child = {};
-  this._tree.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, row, col, child);
-  if (this._tree.view.selection.isSelected(row.value))
-this._log.info(row.value + " is selected");
-else
-this._log.info(row.value + " is not selected");
+  onTreeContextPopupHidden: function() {
+    if (!SnowlUtils.gSelectOnRtClick)
+      SnowlUtils.RestoreSelectionWithoutContentLoad(this._tree);
   },
 
   unsubscribe: function() {
--- a/content/collections.xul	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/collections.xul	Sun Nov 02 10:20:12 2008 -0700
@@ -45,13 +45,24 @@
   <script type="application/x-javascript" src="chrome://snowl/content/collections.js"/>
 
   <vbox id="collectionsViewBox">
-    <tree id="sourcesView" flex="1" context="sourcesContextMenu" editable="true"
+
+    <!-- Collection context -->
+    <popup id="snowlCollectionContext"
+           onpopuphidden="CollectionsView.onTreeContextPopupHidden(event)">
+      <menuitem id="snowlCollectionRefreshMenuitem"
+                label="&refreshAll.label;"
+                accesskey="&refreshAll.accesskey;"
+                oncommand="SnowlService.refreshAllSources();"/>
+    </popup>
+
+    <tree id="sourcesView" flex="1" editable="true"
           onselect="CollectionsView.onSelect(event)">
       <treecols>
         <treecol id="nameCol" label="&nameCol.label;" primary="true" flex="1"/>
       </treecols>
 
-      <treechildren flex="1"/>
+      <treechildren flex="1" context="snowlCollectionContext"
+                    onmousedown="CollectionsView.onCollectionsTreeMouseDown(event)"/>
     </tree>
 
     <toolbar id="snowlToolbar"/>
--- a/content/list-sidebar.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/list-sidebar.js	Sun Nov 02 10:20:12 2008 -0700
@@ -39,6 +39,9 @@
 const Cr = Components.results;
 const Cu = Components.utils;
 
+//modules that are Snowl-specific
+Cu.import("resource://snowl/modules/utils.js");
+
 let gBrowserWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).
                      getInterface(Ci.nsIWebNavigation).
                      QueryInterface(Ci.nsIDocShellTreeItem).
--- a/content/list.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/list.js	Sun Nov 02 10:20:12 2008 -0700
@@ -108,10 +108,6 @@
     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",
@@ -137,7 +133,7 @@
       case "snowlSubjectCol":
         return this._collection.messages[aRow].subject;
       case "snowlTimestampCol":
-        return SnowlUtils._formatDate(this._collection.messages[aRow].timestamp);
+        return SnowlDateUtils._formatDate(this._collection.messages[aRow].timestamp);
       default:
         return null;
     }
@@ -190,9 +186,8 @@
             SnowlMessageView._snowlSidebar.hidden = (aEvent.newValue == "true");
         }, false);
 
-    // Restore previous layout view
+    // Restore previous layout, if error or first time default to 'classic' view
     let layout = Snowl._mainWindow.getAttribute("snowllayout");
-    // If error or first time default to 'classic' view
     let layoutIndex = Snowl.layoutName.indexOf(layout) < 0 ?
         this.kClassicLayout : Snowl.layoutName.indexOf(layout);
     this.layout(layoutIndex);
@@ -295,9 +290,8 @@
     this._rebuildView();
   },
 
-  setCollection: function(collection, index) {
+  setCollection: function(collection) {
     this._collection = collection;
-    this._listCollectionIndex = index;
     this._rebuildView();
   },
 
@@ -410,7 +404,8 @@
   },
 
   onSelect: function(aEvent) {
-    if (this._tree.currentIndex == -1)
+this._log.info("onSelect - start: event.target.id = "+aEvent.target.id);
+    if (this._tree.currentIndex == -1 || SnowlUtils.gRightMouseButtonDown)
       return;
 
     // When we support opening multiple links in the background,
@@ -424,6 +419,7 @@
     let url = "chrome://snowl/content/message/message.xul?id=" + message.id;
     window.loadURI(url, null, null, false);
 
+    SnowlUtils.gListViewListIndex = row;
     this._setRead(true);
   },
 
@@ -440,6 +436,8 @@
       this._toggleRead(true);
     else if (aEvent.charCode == " ".charCodeAt(0))
       this._onSpacePress(aEvent);
+    else if (aEvent.keyCode == "13")
+      this._openListMessage(aEvent);
   },
 
   // Based on SpaceHit in mailWindowOverlay.js
@@ -527,13 +525,15 @@
   },
 
   onClickColumnHeader: function(aEvent) {
+    // Only for left click, button = 0..
+    if (aEvent.button != 0)
+      return;
+
     let column = aEvent.target;
     let property = this._columnProperties[column.id];
     let sortResource = this._tree.getAttribute("sortResource");
     let sortDirection = this._tree.getAttribute("sortDirection");
 
-    // FIXME: don't sort if the user right- or middle-clicked the header.
-
     // Determine the sort order.  If the user clicked on the header for
     // the current sort column, we sort in the reverse of the current order.
     // Otherwise we sort in ascending order.
@@ -566,7 +566,21 @@
     this._collection.sortProperties = [property];
     this._collection.sortOrder = order;
     this._collection.sort();
-  }
+  },
+
+  _openListMessage: function(event) {
+alert("openlistmessage");
+  },
+
+  onListTreeMouseDown: function(aEvent) {
+    SnowlUtils.onTreeMouseDown(aEvent, this._tree);
+  },
+
+  onTreeContextPopupHidden: function(aEvent) {
+    if (!SnowlUtils.gSelectOnRtClick)
+      SnowlUtils.RestoreSelectionWithoutContentLoad(this._tree);
+  },
+
 };
 
 window.addEventListener("load", function() { SnowlMessageView.init() }, false);
--- a/content/list.xul	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/list.xul	Sun Nov 02 10:20:12 2008 -0700
@@ -49,7 +49,21 @@
   <window id="main-window"
           persist="screenX screenY width height sizemode
              snowllayout snowltabindex snowlcollectionindex"
-          snowllayout="classic"/>
+          snowllayout="classic">
+
+    <!-- Apparently Popup can not be child of mainPopupSet, otherwise popup hide
+         click is sent as select event. This effect is not bad but could not be
+         recreated in the collections sidebar tree context popup. Quirky xul here. -->
+    <popup id="snowlListContext"
+           onpopuphidden="SnowlMessageView.onTreeContextPopupHidden(event)">
+      <menuitem id="snowlOpenListMessageMenuitem"
+                disabled="true"
+                label="&openListMessage.label;"
+                accesskey="&openListMessage.accesskey;"
+                oncommand="SnowlMessageView._openListMessage(event)"/>
+    </popup>  
+
+  </window>
 
   <hbox id="browser">
     <hbox id="snowlSidebar"
@@ -112,7 +126,8 @@
                    onclick="SnowlMessageView.onClickColumnHeader(event)"/>
         </treecols>
 
-        <treechildren flex="1"/>
+        <treechildren flex="1" context="snowlListContext"
+                      onmousedown="SnowlMessageView.onListTreeMouseDown(event)"/>
       </tree>
     </vbox>
 
--- a/content/message/message.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/message/message.js	Sun Nov 02 10:20:12 2008 -0700
@@ -87,13 +87,13 @@
 document.getElementById("briefAuthor").value = message.author;
 document.getElementById("briefSubject").value = message.subject;
 document.getElementById("briefSubject").setAttribute("href", message.link);
-document.getElementById("briefTimestamp").value = SnowlUtils._formatDate(message.timestamp);
+document.getElementById("briefTimestamp").value = SnowlDateUtils._formatDate(message.timestamp);
 
 // Full headers
 document.getElementById("author").value = message.author;
 document.getElementById("subject").value = message.subject;
 document.documentElement.setAttribute("title", message.subject);
-document.getElementById("timestamp").value = SnowlUtils._formatDate(message.timestamp);
+document.getElementById("timestamp").value = SnowlDateUtils._formatDate(message.timestamp);
 document.getElementById("link").href = message.link;
 document.getElementById("link").value = message.link;
 
--- a/content/river.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/river.js	Sun Nov 02 10:20:12 2008 -0700
@@ -123,13 +123,13 @@
 
     switch (this._periodMenu.selectedItem.value) {
       case "today":
-        return SnowlUtils.jsToJulianDate(SnowlUtils.today);
+        return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.today);
       case "yesterday":
-        return SnowlUtils.jsToJulianDate(SnowlUtils.yesterday);
+        return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.yesterday);
       case "last7days":
-        return SnowlUtils.jsToJulianDate(SnowlUtils.sixDaysAgo.epoch);
+        return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.sixDaysAgo.epoch);
       case "last4weeks":
-        return SnowlUtils.jsToJulianDate(SnowlUtils.twentySevenDaysAgo.epoch);
+        return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.twentySevenDaysAgo.epoch);
       case "all":
       default:
         return 0;
@@ -147,7 +147,7 @@
       // messages received in the future from these categories, but since that
       // situation is exceptional, it's probably better to show those.
       case "yesterday":
-        return SnowlUtils.jsToJulianDate(SnowlUtils.today);
+        return SnowlDateUtils.jsToJulianDate(SnowlDateUtils.today);
       case "today":
       case "last7days":
       case "last4weeks":
@@ -284,7 +284,7 @@
   _setMidnightTimout: function() {
     let t = this;
     let now = new Date();
-    let msUntilMidnight = SnowlUtils.tomorrow - now;
+    let msUntilMidnight = SnowlDateUtils.tomorrow - now;
     this._log.info("setting midnight timeout for " + new Date(now.getTime() + msUntilMidnight));
     window.setTimeout(function() { t.onMidnight() }, msUntilMidnight);
   },
@@ -650,43 +650,43 @@
   _groups: {
     today: [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Evening", epoch: SnowlUtils.evening(SnowlUtils.today) },
-      { name: "Afternoon", epoch: SnowlUtils.afternoon(SnowlUtils.today) },
-      { name: "Morning", epoch: SnowlUtils.morning(SnowlUtils.today) },
-      { name: "Wee Hours", epoch: SnowlUtils.today },
+      { name: "Evening", epoch: SnowlDateUtils.evening(SnowlDateUtils.today) },
+      { name: "Afternoon", epoch: SnowlDateUtils.afternoon(SnowlDateUtils.today) },
+      { name: "Morning", epoch: SnowlDateUtils.morning(SnowlDateUtils.today) },
+      { name: "Wee Hours", epoch: SnowlDateUtils.today },
       { name: "Older", epoch: 0 }
     ],
     yesterday: [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Evening", epoch: SnowlUtils.evening(SnowlUtils.yesterday) },
-      { name: "Afternoon", epoch: SnowlUtils.afternoon(SnowlUtils.yesterday) },
-      { name: "Morning", epoch: SnowlUtils.morning(SnowlUtils.yesterday) },
-      { name: "Wee Hours", epoch: SnowlUtils.yesterday },
+      { name: "Evening", epoch: SnowlDateUtils.evening(SnowlDateUtils.yesterday) },
+      { name: "Afternoon", epoch: SnowlDateUtils.afternoon(SnowlDateUtils.yesterday) },
+      { name: "Morning", epoch: SnowlDateUtils.morning(SnowlDateUtils.yesterday) },
+      { name: "Wee Hours", epoch: SnowlDateUtils.yesterday },
       { name: "Older", epoch: 0 }
     ],
     last7days: [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Today", epoch: SnowlUtils.today },
-      { name: "Yesterday", epoch: SnowlUtils.yesterday },
-      { name: SnowlUtils.twoDaysAgo.name, epoch: SnowlUtils.twoDaysAgo.epoch },
-      { name: SnowlUtils.threeDaysAgo.name, epoch: SnowlUtils.threeDaysAgo.epoch },
-      { name: SnowlUtils.fourDaysAgo.name, epoch: SnowlUtils.fourDaysAgo.epoch },
-      { name: SnowlUtils.fiveDaysAgo.name, epoch: SnowlUtils.fiveDaysAgo.epoch },
-      { name: SnowlUtils.sixDaysAgo.name, epoch: SnowlUtils.sixDaysAgo.epoch },
+      { name: "Today", epoch: SnowlDateUtils.today },
+      { name: "Yesterday", epoch: SnowlDateUtils.yesterday },
+      { name: SnowlDateUtils.twoDaysAgo.name, epoch: SnowlDateUtils.twoDaysAgo.epoch },
+      { name: SnowlDateUtils.threeDaysAgo.name, epoch: SnowlDateUtils.threeDaysAgo.epoch },
+      { name: SnowlDateUtils.fourDaysAgo.name, epoch: SnowlDateUtils.fourDaysAgo.epoch },
+      { name: SnowlDateUtils.fiveDaysAgo.name, epoch: SnowlDateUtils.fiveDaysAgo.epoch },
+      { name: SnowlDateUtils.sixDaysAgo.name, epoch: SnowlDateUtils.sixDaysAgo.epoch },
       { name: "Older", epoch: 0 }
     ],
     last4weeks: [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Week One", epoch: SnowlUtils.tomorrow - (SnowlUtils.msInDay * 7) },
-      { name: "Week Two", epoch: SnowlUtils.tomorrow - (SnowlUtils.msInDay * 14) },
-      { name: "Week Three", epoch: SnowlUtils.tomorrow - (SnowlUtils.msInDay * 21) },
-      { name: "Week Four", epoch: SnowlUtils.tomorrow - (SnowlUtils.msInDay * 28) },
+      { name: "Week One", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 7) },
+      { name: "Week Two", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 14) },
+      { name: "Week Three", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 21) },
+      { name: "Week Four", epoch: SnowlDateUtils.tomorrow - (SnowlDateUtils.msInDay * 28) },
       { name: "Older", epoch: 0 }
     ],
     all: [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Today", epoch: SnowlUtils.today },
-      { name: "Yesterday", epoch: SnowlUtils.yesterday },
+      { name: "Today", epoch: SnowlDateUtils.today },
+      { name: "Yesterday", epoch: SnowlDateUtils.yesterday },
       { name: "Older", epoch: 0 }
     ]
   },
@@ -783,7 +783,7 @@
         bylineBox.appendChild(this._document.createTextNode(message.source.name));
 
       //// Timestamp
-      //let lastUpdated = SnowlUtils._formatDate(message.timestamp);
+      //let lastUpdated = SnowlDateUtils._formatDate(message.timestamp);
       //if (lastUpdated) {
       //  let timestamp = this._document.createElementNS(HTML_NS, "span");
       //  timestamp.className = "timestamp";
--- a/content/stream.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/content/stream.js	Sun Nov 02 10:20:12 2008 -0700
@@ -183,7 +183,7 @@
   _setMidnightTimout: function() {
     let t = this;
     let now = new Date();
-    let msUntilMidnight = SnowlUtils.tomorrow - now;
+    let msUntilMidnight = SnowlDateUtils.tomorrow - now;
     this._log.info("setting midnight timeout for " + new Date(now.getTime() + msUntilMidnight));
     window.setTimeout(function() { t.onMidnight() }, msUntilMidnight);
   },
@@ -351,13 +351,13 @@
 
     let groups = [
       { name: "The Future", epoch: Number.MAX_VALUE },
-      { name: "Today", epoch: SnowlUtils.today },
-      { name: "Yesterday", epoch: SnowlUtils.yesterday },
-      { name: SnowlUtils.twoDaysAgo.name, epoch: SnowlUtils.twoDaysAgo.epoch },
-      { name: SnowlUtils.threeDaysAgo.name, epoch: SnowlUtils.threeDaysAgo.epoch },
-      { name: SnowlUtils.fourDaysAgo.name, epoch: SnowlUtils.fourDaysAgo.epoch },
-      { name: SnowlUtils.fiveDaysAgo.name, epoch: SnowlUtils.fiveDaysAgo.epoch },
-      { name: SnowlUtils.sixDaysAgo.name, epoch: SnowlUtils.sixDaysAgo.epoch },
+      { name: "Today", epoch: SnowlDateUtils.today },
+      { name: "Yesterday", epoch: SnowlDateUtils.yesterday },
+      { name: SnowlDateUtils.twoDaysAgo.name, epoch: SnowlDateUtils.twoDaysAgo.epoch },
+      { name: SnowlDateUtils.threeDaysAgo.name, epoch: SnowlDateUtils.threeDaysAgo.epoch },
+      { name: SnowlDateUtils.fourDaysAgo.name, epoch: SnowlDateUtils.fourDaysAgo.epoch },
+      { name: SnowlDateUtils.fiveDaysAgo.name, epoch: SnowlDateUtils.fiveDaysAgo.epoch },
+      { name: SnowlDateUtils.sixDaysAgo.name, epoch: SnowlDateUtils.sixDaysAgo.epoch },
       { name: "Older", epoch: 0 }
     ];
 
@@ -445,7 +445,7 @@
     // by time received.  Instead, we're going to group by time period
     // received (this morning, yesterday, last week, etc.) to give users
     // useful chronographic info.
-    //let lastUpdated = SnowlUtils._formatDate(message.timestamp);
+    //let lastUpdated = SnowlDateUtils._formatDate(message.timestamp);
     //if (lastUpdated) {
     //  let timestamp = this._document.createElementNS(XUL_NS, "description");
     //  timestamp.className = "timestamp";
--- a/locale/en-US/collections.dtd	Wed Oct 29 18:36:16 2008 -0600
+++ b/locale/en-US/collections.dtd	Sun Nov 02 10:20:12 2008 -0700
@@ -1,1 +1,5 @@
-<!ENTITY nameCol.label                "Name">
+<!ENTITY nameCol.label                      "Name">
+
+<!-- Collections contextmenu -->
+<!ENTITY refreshAll.label                   "Refresh All">
+<!ENTITY refreshAll.accesskey               "A">
--- a/locale/en-US/list.dtd	Wed Oct 29 18:36:16 2008 -0600
+++ b/locale/en-US/list.dtd	Sun Nov 02 10:20:12 2008 -0700
@@ -7,3 +7,6 @@
 <!ENTITY authorCol.label              "Author">
 <!ENTITY subjectCol.label             "Subject">
 <!ENTITY timestampCol.label           "Date">
+
+<!ENTITY openListMessage.label        "Open Message">
+<!ENTITY openListMessage.accesskey    "O">
--- a/modules/collection.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/collection.js	Sun Nov 02 10:20:12 2008 -0700
@@ -254,10 +254,10 @@
                                        statement.row.subject,
                                        statement.row.author,
                                        statement.row.link,
-                                       SnowlUtils.julianToJSDate(statement.row.timestamp),
+                                       SnowlDateUtils.julianToJSDate(statement.row.timestamp),
                                        (statement.row.read ? true : false),
                                        statement.row.authorIcon,
-                                       SnowlUtils.julianToJSDate(statement.row.received));
+                                       SnowlDateUtils.julianToJSDate(statement.row.received));
         this._messages.push(message);
         this._messageIndex[message.id] = message;
       }
--- a/modules/feed.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/feed.js	Sun Nov 02 10:20:12 2008 -0700
@@ -468,8 +468,8 @@
                                    aExternalID,
                                    aSubject,
                                    aAuthorID,
-                                   SnowlUtils.jsToJulianDate(aTimestamp),
-                                   SnowlUtils.jsToJulianDate(aReceived),
+                                   SnowlDateUtils.jsToJulianDate(aTimestamp),
+                                   SnowlDateUtils.jsToJulianDate(aReceived),
                                    aLink ? aLink.spec : null);
 
     return messageID;
--- a/modules/message.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/message.js	Sun Nov 02 10:20:12 2008 -0700
@@ -86,10 +86,10 @@
                                  statement.row.subject,
                                  statement.row.author,
                                  statement.row.link,
-                                 SnowlUtils.julianToJSDate(statement.row.timestamp),
+                                 SnowlDateUtils.julianToJSDate(statement.row.timestamp),
                                  (statement.row.read ? true : false),
                                  statement.row.authorIcon,
-                                 SnowlUtils.julianToJSDate(statement.row.received));
+                                 SnowlDateUtils.julianToJSDate(statement.row.received));
     }
   }
   finally {
--- a/modules/service.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/service.js	Sun Nov 02 10:20:12 2008 -0700
@@ -243,7 +243,7 @@
                                      row.name,
                                      URI.get(row.machineURI),
                                      URI.get(row.humanURI),
-                                     SnowlUtils.julianToJSDate(row.lastRefreshed),
+                                     SnowlDateUtils.julianToJSDate(row.lastRefreshed),
                                      row.importance));
       }
     }
--- a/modules/source.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/source.js	Sun Nov 02 10:20:12 2008 -0700
@@ -81,7 +81,7 @@
                              this._getStatement.row.name,
                              URI.get(this._getStatement.row.machineURI),
                              URI.get(this._getStatement.row.humanURI),
-                             SnowlUtils.julianToJSDate(this._getStatement.row.lastRefreshed),
+                             SnowlDateUtils.julianToJSDate(this._getStatement.row.lastRefreshed),
                              this._getStatement.row.importance);
   }
   finally {
@@ -134,7 +134,7 @@
     let stmt = SnowlDatastore.createStatement("UPDATE sources " +
                                               "SET lastRefreshed = :lastRefreshed " +
                                               "WHERE id = :id");
-    stmt.params.lastRefreshed = SnowlUtils.jsToJulianDate(this._lastRefreshed);
+    stmt.params.lastRefreshed = SnowlDateUtils.jsToJulianDate(this._lastRefreshed);
     stmt.params.id = this.id;
     stmt.execute();
   },
--- a/modules/twitter.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/twitter.js	Sun Nov 02 10:20:12 2008 -0700
@@ -474,8 +474,8 @@
                                    aExternalID,
                                    aSubject,
                                    aAuthorID,
-                                   SnowlUtils.jsToJulianDate(aTimestamp),
-                                   SnowlUtils.jsToJulianDate(aReceived),
+                                   SnowlDateUtils.jsToJulianDate(aTimestamp),
+                                   SnowlDateUtils.jsToJulianDate(aReceived),
                                    aLink ? aLink.spec : null);
 
     return messageID;
--- a/modules/utils.js	Wed Oct 29 18:36:16 2008 -0600
+++ b/modules/utils.js	Sun Nov 02 10:20:12 2008 -0700
@@ -34,16 +34,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
-const EXPORTED_SYMBOLS = ["SnowlUtils"];
+const EXPORTED_SYMBOLS = ["SnowlDateUtils", "SnowlUtils"];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
-// FIXME: rename this to reflect the fact that it's date-specific rather than
-// being a generic set of utilities.
-let SnowlUtils = {
+//modules that are generic
+Cu.import("resource://snowl/modules/log4moz.js");
+
+let SnowlDateUtils = {
   get msInHour() 1000 * 60 * 60,
 
   get msInDay() this.msInHour * 24,
@@ -132,32 +133,32 @@
   },
 
   twoDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 2)) },
-    get name() { return SnowlUtils.days[this.epoch.getDay()] }
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 2)) },
+    get name() { return SnowlDateUtils.days[this.epoch.getDay()] }
   },
 
   threeDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 3)) },
-    get name() { return SnowlUtils.days[this.epoch.getDay()] }
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 3)) },
+    get name() { return SnowlDateUtils.days[this.epoch.getDay()] }
   },
 
   fourDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 4)) },
-    get name() { return SnowlUtils.days[this.epoch.getDay()] }
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 4)) },
+    get name() { return SnowlDateUtils.days[this.epoch.getDay()] }
   },
 
   fiveDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 5)) },
-    get name() { return SnowlUtils.days[this.epoch.getDay()] }
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 5)) },
+    get name() { return SnowlDateUtils.days[this.epoch.getDay()] }
   },
 
   sixDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 6)) },
-    get name() { return SnowlUtils.days[this.epoch.getDay()] }
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 6)) },
+    get name() { return SnowlDateUtils.days[this.epoch.getDay()] }
   },
 
   twentySevenDaysAgo: {
-    get epoch() { return new Date(SnowlUtils.today - (SnowlUtils.msInDay * 27)) },
+    get epoch() { return new Date(SnowlDateUtils.today - (SnowlDateUtils.msInDay * 27)) },
     get name() { return "Twenty Seven Days Ago" }
   },
 
@@ -237,6 +238,131 @@
                                       date.getHours(),
                                       date.getMinutes(),
                                       date.getSeconds());
+  },
+};
+
+let SnowlUtils = {
+  get _log() {
+    let log = Log4Moz.Service.getLogger("Snowl.Utils");
+    this.__defineGetter__("_log", function() { return log });
+    return this._log;
+  },
+
+  // Always maintain selected listitem within a session
+  // XXX store on document for restore on restart??
+  gListViewListIndex: null,
+  gListViewCollectionIndex: null,
+
+  // From Tb: Detect right mouse click and change the highlight to the row
+  // where the click happened without loading the message headers in
+  // the Folder or Thread Pane.
+  gRightMouseButtonDown: false,
+  gSelectOnRtClick: false,
+  onTreeMouseDown: function(aEvent, tree) {
+    if (aEvent.button == 2 && !this.gSelectOnRtClick) {
+      this.gRightMouseButtonDown = true;
+      this.ChangeSelectionWithoutContentLoad(aEvent, aEvent.target.parentNode);
+    }
+    else {
+      // 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).
+      // FIXME: disable this for names that can't be changed.
+      // this._tree.addEventListener("mousedown", function(aEvent) { 
+      //     CollectionsView.onClick(aEvent) }, true);
+      let row = {}, col = {}, child = {};
+      tree.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, row, col, child);
+      if (tree.view.selection.isSelected(row.value))
+this._log.info("row: "+ row.value + " is selected");
+      else {
+this._log.info("row: "+ row.value + " is not selected");
+      }
+    this.gRightMouseButtonDown = false;
+    }
+  },
+
+  // From Tb: Function to change the highlighted row to where the mouse was
+  // clicked without loading the contents of the selected row.
+  // It will also keep the outline/dotted line in the original row.
+  ChangeSelectionWithoutContentLoad: function(aEvent, tree) {
+this._log.info("change selection right click: tree.id = "+tree.id);
+    let treeBoxObj = tree.treeBoxObject;
+    let treeSelection = treeBoxObj.view.selection;
+
+    let row = treeBoxObj.getRowAt(aEvent.clientX, aEvent.clientY);
+
+    // Make sure that row.value is valid so that it doesn't mess up
+    // the call to ensureRowIsVisible().
+    if((row >= 0) && !treeSelection.isSelected(row)) {
+      let saveCurrentIndex = treeSelection.currentIndex;
+      treeSelection.selectEventsSuppressed = true;
+      treeSelection.select(row);
+      treeSelection.currentIndex = saveCurrentIndex;
+      treeBoxObj.ensureRowIsVisible(row);
+      // This causes onSelect to fire, not necessary here
+//      treeSelection.selectEventsSuppressed = false;
+
+      // Keep track of which row in the tree is currently selected.
+      if(tree.id == "snowlView")
+        this.gListViewListIndex = row;
+      if(tree.id == "sourcesView")
+        this.gListViewCollectionIndex = row;
+    }
+    // This will not stop the onSelect event, need to test in the handler..
+//    aEvent.stopPropagation();
+  },
+
+  // From Tb: Function to change the highlighted row back to the row that
+  // is currently outline/dotted without loading the contents of either rows.
+  // This is triggered when the context menu for a given row is hidden/closed
+  // (onpopuphidden for the context <popup>).
+  RestoreSelectionWithoutContentLoad: function(tree) {
+    // If a delete or move command had been issued, then we should
+    // reset gRightMouseButtonDown and gListDeleteOrMoveOccurred
+    // and return (see bug 142065).
+    // TODO: once contextmenu has these options..
+//    if(this.gListDeleteOrMoveOccurred) {
+//      this.gRightMouseButtonDown = false;
+//      this.gListDeleteOrMoveOccurred = false;
+//      return;
+//    }
+this._log.info("restore selection onpopuphidden: tree.id = "+tree.id);
+
+    let treeSelection = tree.view.selection;
+
+    // Make sure that currentIndex is valid so that we don't try to restore
+    // a selection of an invalid row.
+    if((!treeSelection.isSelected(treeSelection.currentIndex)) &&
+        (treeSelection.currentIndex >= 0)) {
+      treeSelection.selectEventsSuppressed = true;
+      treeSelection.select(treeSelection.currentIndex);
+      treeSelection.selectEventsSuppressed = false;
+
+      // Keep track of which row in the tree is currently selected.
+      // This is currently only needed when deleting messages.
+      // TODO: once contextmenu has these options..
+//      if(tree.id == "snowlView")
+//        this.gListViewListIndex = treeSelection.currentIndex;
+//      if(tree.id == "sourcesView")
+//        this.gListViewCollectionIndex = treeSelection.currentIndex;
+    }
+    else if(treeSelection.currentIndex < 0)
+      // Clear the selection in the case of when a folder has just been
+      // loaded where the message pane does not have a message loaded yet.
+      // When right-clicking a message in this case and dismissing the
+      // popup menu (by either executing a menu command or clicking
+      // somewhere else),  the selection needs to be cleared.
+      // However, if the 'Delete Message' or 'Move To' menu item has been
+      // selected, DO NOT clear the selection, else it will prevent the
+      // tree view from refreshing.
+      treeSelection.clearSelection();
+
+    // Need to reset gRightMouseButtonDown to false here because
+    // TreeOnMouseDown() is only called on a mousedown, not on a key down.
+    // So resetting it here allows the loading of messages in the messagepane
+    // when navigating via the keyboard or the toolbar buttons *after*
+    // the context menu has been dismissed.
+    this.gRightMouseButtonDown = false;
   }
 
 };