From f1f696cd2a75f65812794eb8c718be482118aac6 Mon Sep 17 00:00:00 2001 From: alta88 Date: Sun, 5 Jul 2009 09:04:45 -0600 Subject: [PATCH] implement 'busy' property and visuals for collections tree; fix inefficient 'mark all read' code. --- content/collections.css | 11 +++++++ content/collections.js | 49 ++++++++++++++++++------------ content/icons/asterisk_orange.png | Bin 0 -> 760 bytes modules/datastore.js | 3 +- modules/service.js | 14 ++++++++- modules/source.js | 12 ++++++-- 6 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 content/icons/asterisk_orange.png diff --git a/content/collections.css b/content/collections.css index 75d0edf..fd44eb5 100644 --- a/content/collections.css +++ b/content/collections.css @@ -72,3 +72,14 @@ #sourcesViewTreeChildren::-moz-tree-image(hasNew) { list-style-image: url("chrome://snowl/content/icons/asterisk_orange.png"); } + +/* Collection loading/refreshing indicator */ +#sourcesViewTreeChildren::-moz-tree-image(isBusy) { +/* XXX: argh, an animated image in the tree will cause a low level race condition + * which increases with number of rows with the image, after the image is no longer + * valid for a row property; no amount of view nulling etc etc could fix this. + * It seems the tree keeps invalidating itself once an anim image is loaded. + list-style-image: url("chrome://global/skin/icons/loading_16.png"); +*/ + list-style-image: url("chrome://snowl/content/icons/arrow_refresh_small.png"); +} diff --git a/content/collections.js b/content/collections.js index fbad2a7..a7615cc 100644 --- a/content/collections.js +++ b/content/collections.js @@ -195,7 +195,7 @@ let CollectionsView = { Observers.add("snowl:source:added", this.onSourceAdded, this); Observers.add("snowl:message:added", this.onMessageAdded, this); Observers.add("snowl:source:removed", this.onSourceRemoved, this); - Observers.add("snowl:messages:changed", this.onMessagesComplete, this); + Observers.add("snowl:messages:completed", this.onMessagesCompleted, this); Observers.add("itemchanged", this.onItemChanged, this); if (this.gListOrRiver == "list") Observers.add("snowl:author:removed", this.onSourceRemoved, this); @@ -206,7 +206,7 @@ let CollectionsView = { Observers.remove("snowl:source:added", this.onSourceAdded, this); Observers.remove("snowl:message:added", this.onMessageAdded, this); Observers.remove("snowl:source:removed", this.onSourceRemoved, this); - Observers.remove("snowl:messages:changed", this.onMessagesComplete, this); + Observers.remove("snowl:messages:completed", this.onMessagesCompleted, this); Observers.remove("itemchanged", this.onItemChanged, this); if (this.gListOrRiver == "list") Observers.remove("snowl:author:removed", this.onSourceRemoved, this); @@ -220,7 +220,7 @@ let CollectionsView = { onSourceAdded: function(aPlaceID) { //this._log.info("onSourceAdded: curIndex:curSelectedIndex = "+ // this._tree.currentIndex+" : "+this._tree.currentSelectedIndex); - // Newly subscribed source has been added to places, elect the inserted row. + // Newly subscribed source has been added to places, select the inserted row. // The effect of selecting here is that onMessageAdded will trigger a view // refresh for each message, so messages pop into the view as added. this._tree.currentSelectedIndex = -1; @@ -244,12 +244,11 @@ let CollectionsView = { } }, - onMessagesComplete: function(aSourceId) { - // Finished downloading all messages. Scroll the collection tree intelligently. -// SnowlUtils.scrollPlacement(this._tree, this._tree.currentIndex); - - // Clear the collections stats cache and invalidate tree to rebuild. - SnowlService._collectionStatsByCollectionID = null; + onMessagesCompleted: function(aSourceId) { + // Source refresh completed, reset busy property. + if (SnowlService._sourcesByID[aSourceId]) + SnowlService._sourcesByID[aSourceId].busy = false; + this._tree.treeBoxObject.invalidate(); }, @@ -538,7 +537,7 @@ this._log.info("onClick: START itemIds - " +this.itemIds.toSource()); this._tree.view.nodeForTreeIndex(this._tree.currentSelectedIndex); // Create places query object from tree item uri let query = new SnowlQuery(selectedSource.uri); - if (query.queryGroupIDColumn != "sources.id") + if (!query.queryTypeSource) return; selectedSources.push(SnowlService.sourcesByID[query.queryID]); @@ -577,14 +576,14 @@ this._log.info("onClick: START itemIds - " +this.itemIds.toSource()); query += "authorID = " + authors.join(" OR authorID = "); } - query = query ? " WHERE ( " + query + " AND" + - " (read = " + MESSAGE_UNREAD + " OR" + - " read = " + MESSAGE_NEW + ") )" : null; + query = query ? "( " + query + " ) AND " : null; } if (query != null) { SnowlDatastore.dbConnection.executeSimpleSQL( - "UPDATE messages SET read = " + MESSAGE_READ + query); + "UPDATE messages SET read = " + MESSAGE_READ + + " WHERE " + query + + " (read = " + MESSAGE_UNREAD + " OR read = " + MESSAGE_NEW + ")"); gMessageViewWindow.SnowlMessageView.onFilter(this.Filters); // Clear the collections stats cache and invalidate tree to rebuild. @@ -594,6 +593,7 @@ this._log.info("onClick: START itemIds - " +this.itemIds.toSource()); }, markCollectionNewState: function() { +//this._log.info("markCollectionNewState: START "); // Mark all selected source/author collection messages as not new (unread) // upon the collection being no longer selected. Note: shift-click on a // collection will leave new state when unselected. @@ -1156,7 +1156,7 @@ PlacesTreeView.prototype.getCellProperties = SnowlTreeViewGetCellProperties; function SnowlTreeViewGetCellProperties(aRow, aColumn, aProperties) { this._getCellProperties(aRow, aColumn, aProperties); - let query, anno, propStr, propArr, prop, collID; + let query, anno, propStr, propArr, prop, collID, source; let node = this._visibleElements[aRow].node; query = new SnowlQuery(node.uri); @@ -1169,12 +1169,15 @@ function SnowlTreeViewGetCellProperties(aRow, aColumn, aProperties) { (query.queryFolder == SnowlPlaces.collectionsSystemID || query.queryFolder == SnowlPlaces.collectionsSourcesID || query.queryFolder == SnowlPlaces.collectionsAuthorsID) ? "all" : null; + source = SnowlService._sourcesByID[query.queryID]; var nodeStats = SnowlService.getCollectionStatsByCollectionID()[collID]; if (nodeStats && nodeStats.u && !node.containerOpen) aProperties.AppendElement(this._getAtomFor("hasUnread")); if (nodeStats && nodeStats.n && !node.containerOpen) aProperties.AppendElement(this._getAtomFor("hasNew")); + if (((source && source.busy) || (nodeStats && nodeStats.busy)) && !node.containerOpen) + aProperties.AppendElement(this._getAtomFor("isBusy")); //if (nodeStats) //SnowlPlaces._log.info("getCellProperties: itemId:title:stats - "+ @@ -1232,10 +1235,16 @@ function SnowlTreeViewGetImageSrc(aRow, aColumn) { // Custom handling for 'new' in collections. let query = new SnowlQuery(node.uri); let collID = query.queryTypeSource ? "s" + query.queryID : - query.queryTypeAuthor ? "a" + query.queryID : null; + query.queryTypeAuthor ? "a" + query.queryID : + (query.queryFolder == SnowlPlaces.collectionsSystemID || + query.queryFolder == SnowlPlaces.collectionsSourcesID || + query.queryFolder == SnowlPlaces.collectionsAuthorsID) ? "all" : null; let nodeStats = SnowlService.getCollectionStatsByCollectionID()[collID]; - if (nodeStats && nodeStats.n) - // Don't set icon, let css handle it for 'new'. + let source = SnowlService._sourcesByID[query.queryID]; + + if ((nodeStats && (nodeStats.n || nodeStats.busy)) || (source && source.busy)) + // Don't set icon, let css handle it for 'new' or 'busy'. "all" collection + // (only) has a busy property so we can set an indicator on a closed container. return ""; var icon = node.icon; @@ -1338,9 +1347,9 @@ PlacesUIUtils.getBestTitle = nodeStats = SnowlService.getCollectionStatsByCollectionID()[collID]; if (nodeStats && collID == "all") - titleStats = " (New:" + nodeStats.n + "/Unread:" + nodeStats.u + "/Total:" + nodeStats.t + ")"; + titleStats = " (New:" + nodeStats.n + " Unread:" + nodeStats.u + " Total:" + nodeStats.t + ")"; else if (nodeStats) - titleStats = " (" + nodeStats.n + "/" + nodeStats.u + "/" + nodeStats.t + ")"; + titleStats = " (" + nodeStats.n + " " + nodeStats.u + " " + nodeStats.t + ")"; title = title + titleStats; } diff --git a/content/icons/asterisk_orange.png b/content/icons/asterisk_orange.png new file mode 100644 index 0000000000000000000000000000000000000000..1ebebde546c1d11df80c3446d0fb123a8d2db6c1 GIT binary patch literal 760 zcmV*uQQn7Ao3L@HcVN_JG5Oz^iELqtfic#8+p{{13 zB!rfdf2k)SDwJS|D58iKOKXwBFIFGcC-AUOfLS9bv(H0S%=6T$O9SC=2fMD%gnJC9? z!Kh(gvt=Cx?1R~s3W9x8mCUENrT{!2(+x==*z->Z(e<;6aicYvPErVhHT_;HRWCrO zX>Ant#Yi-31=*-(@?ZTrNtR{}?1a^q3-ss0eBi>@Qc)edAUbFFjhvR`kHwNKL}lr> zmx4@#VC|1-HQcog=-UoF%!Jv#1q7>sa}xhtBKb#7NeGlV;rH6OFmr?PNPiwMRD=jG zfZ3f5vy;Gqe4sZI=uW3=7L4|E%(QHx)3_-#2chPTcz1*X$TTNe2v#yoR){KnCQhcg z(YBd3W>A0&#{D#mmPqQSlM)|3A^ATXx8dz!8(tOJd5pyIa?we$G^;NU;SSgD5WO`G z1giu)7o5cCBA8D%u7}Z)i7#g)f3PVT=*fasvkLEz*?bEIkm>TthSk)+f{EIdAk!W) z?VGx_0qAlea5(`l58AzP0|-{W*Cx;y=hMVxgG_4#JISQPBo@rv^)uedmP%+LWdH!1u~sTvRX(MC)0C( q-3yOhdA{Gl2X{NXPYNCLME(Md?VydvM~!0u0000 0) { + // Don't set busy on 'all' until we know when the last one is done so it + // can be unset. +// this._collectionStatsByCollectionID["all"].busy = true; + Observers.notify("snowl:messages:completed", "refresh"); + } + // We specify the same refresh time when refreshing sources so that all // new messages have the same received time, which makes messages sorted by // received, then published times look better (more mixed together by @@ -346,7 +358,7 @@ let SnowlService = { /** * Return read, unread, new stats on author, source collections. - * * + * * @returns {object} the t (total), u (unread), n (new) numbers for each * source and author collection. */ diff --git a/modules/source.js b/modules/source.js index 74bfaee..5fc79ff 100644 --- a/modules/source.js +++ b/modules/source.js @@ -49,6 +49,7 @@ Cu.import("resource://snowl/modules/URI.js"); Cu.import("resource://snowl/modules/constants.js"); Cu.import("resource://snowl/modules/datastore.js"); Cu.import("resource://snowl/modules/message.js"); +Cu.import("resource://snowl/modules/service.js"); Cu.import("resource://snowl/modules/utils.js"); // FIXME: make strands.js into a module. @@ -191,6 +192,9 @@ SnowlSource.prototype = { // How often to refresh sources, in milliseconds. refreshInterval: 1000 * 60 * 30, // 30 minutes + // For adding isBusy property to collections tree. + busy: false, + id: null, name: null, @@ -438,10 +442,12 @@ this._log.info("persist placeID:sources.id - " + this.placeID + " : " + this.id) // Update the current flag. this.updateCurrentMessages(currentMessageIDs); - // Notify list and collections views on completion of messages download, list - // also notified of each message addition. if (messagesChanged) - Observers.notify("snowl:messages:changed", this.id); + // Invalidate stats cache on completion of refresh with new messages. + SnowlService._collectionStatsByCollectionID = null; + + // Notify collections view on completion of refresh. + Observers.notify("snowl:messages:completed", this.id); }), unstore: function() {