From 949f67469c1d0f680326d16f9ceed8897f33b349 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:43:25 -0500 Subject: [PATCH] Rebase recent documents on top of ShellDocSystem Rather of calling .exists() synchronously for all documents, use ShellDocSystem's async API to only stat docs we're showing. Use the doc opening functionality in ShellDocSystem. Also, more intelligently do redisplay(); don't recreate actors for recent docs if we already have one for that URI. We may need more intelligent caching here; if e.g. just the associated application changes, we should probably reread the info. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- js/misc/docInfo.js | 100 +++++++++++++------------------------------- js/ui/docDisplay.js | 83 ++++++++++++++++++++++++------------ js/ui/widget.js | 3 +- 3 files changed, 87 insertions(+), 99 deletions(-) diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js index 8123e281b..e60d7ca61 100644 --- a/js/misc/docInfo.js +++ b/js/misc/docInfo.js @@ -32,50 +32,7 @@ DocInfo.prototype = { }, launch : function() { - // While using Gio.app_info_launch_default_for_uri() would be - // shorter in terms of lines of code, we are not doing so - // because that would duplicate the work of retrieving the - // mime type. - let needsUri = Gio.file_new_for_uri(this.uri).get_path() == null; - let appInfo = Gio.app_info_get_default_for_type(this.mimeType, needsUri); - - if (appInfo != null) { - appInfo.launch_uris([this.uri], Main.createAppLaunchContext()); - } else { - log("Failed to get default application info for mime type " + this.mimeType + - ". Will try to use the last application that registered the document."); - let appName = this.recentInfo.last_application(); - let [success, appExec, count, time] = this.recentInfo.get_application_info(appName); - if (success) { - log("Will open a document with the following command: " + appExec); - // TODO: Change this once better support for creating - // GAppInfo is added to GtkRecentInfo, as right now - // this relies on the fact that the file uri is - // already a part of appExec, so we don't supply any - // files to appInfo.launch(). - - // The 'command line' passed to - // create_from_command_line is allowed to contain - // '%' macros that are expanded to file - // name / icon name, etc, so we need to escape % as %% - appExec = appExec.replace(/%/g, "%%"); - - let appInfo = Gio.app_info_create_from_commandline(appExec, null, 0, null); - - // The point of passing an app launch context to - // launch() is mostly to get startup notification and - // associated benefits like the app appearing on the - // right desktop; but it doesn't really work for now - // because with the way we create the appInfo we - // aren't reading the application's desktop file, and - // thus don't find the StartupNotify=true in it. So, - // despite passing the app launch context, no startup - // notification occurs. - appInfo.launch([], Main.createAppLaunchContext()); - } else { - log("Failed to get application info for " + this.uri); - } - } + Shell.DocSystem.get_default().open(this.recentInfo); }, exists : function() { @@ -91,50 +48,51 @@ function getDocManager() { return docManagerInstance; } +/** + * DocManager wraps the DocSystem, primarily to expose DocInfo objects + * which conform to the GenericDisplay item API. + */ function DocManager() { this._init(); } DocManager.prototype = { _init: function() { - this._recentManager = Gtk.RecentManager.get_default(); - this._items = {}; - this._recentManager.connect('changed', Lang.bind(this, function(recentManager) { - this._reload(); - this.emit('changed'); - })); + this._docSystem = Shell.DocSystem.get_default(); + this._infosByTimestamp = []; + this._infosByUri = {}; + this._docSystem.connect('changed', Lang.bind(this, this._reload)); this._reload(); }, _reload: function() { - let docs = this._recentManager.get_items(); - let newItems = {}; + let docs = this._docSystem.get_all(); + this._infosByTimestamp = []; + this._infosByUri = {}; for (let i = 0; i < docs.length; i++) { let recentInfo = docs[i]; - if (!recentInfo.exists()) - continue; let docInfo = new DocInfo(recentInfo); - - // we use GtkRecentInfo URI as an item Id - newItems[docInfo.uri] = docInfo; + this._infosByTimestamp.push(docInfo); + this._infosByUri[docInfo.uri] = docInfo; } - let deleted = {}; - for (var uri in this._items) { - if (!(uri in newItems)) - deleted[uri] = this._items[uri]; - } - /* If we'd cached any thumbnail references that no longer exist, - dump them here */ - let texCache = Shell.TextureCache.get_default(); - for (var uri in deleted) { - texCache.evict_recent_thumbnail(this._items[uri].recentInfo); - } - this._items = newItems; + this.emit('changed'); }, - getItems: function() { - return this._items; + getTimestampOrderedInfos: function() { + return this._infosByTimestamp; + }, + + getInfosByUri: function() { + return this._infosByUri; + }, + + lookupByUri: function(uri) { + return this._infosByUri[uri]; + }, + + queueExistenceCheck: function(count) { + return this._docSystem.queue_existence_check(count); } } diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js index 2b67f50ba..6afcf4fa9 100644 --- a/js/ui/docDisplay.js +++ b/js/ui/docDisplay.js @@ -152,7 +152,8 @@ DocDisplay.prototype = { _refreshCache : function() { if (!this._docsStale) return true; - this._allItems = this._docManager.getItems(); + this._allItems = {}; + Lang.copyProperties(this._docManager.getInfosByUri(), this._allItems); this._docsStale = false; return false; }, @@ -275,16 +276,21 @@ DashDocDisplayItem.prototype = { Main.overview.hide(); })); + this.actor._delegate = this; + this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE); let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); iconBox.append(this._icon, Big.BoxPackFlags.NONE); this.actor.append(iconBox, Big.BoxPackFlags.NONE); - let name = new St.Label({ style_class: "dash-recent-docs-item", + let name = new St.Label({ style_class: 'dash-recent-docs-item', text: docInfo.name }); this.actor.append(name, Big.BoxPackFlags.EXPAND); let draggable = DND.makeDraggable(this.actor); - this.actor._delegate = this; + }, + + getUri: function() { + return this._info.uri; }, getDragActorSource: function() { @@ -316,12 +322,14 @@ DashDocDisplay.prototype = { this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); this.actor.connect('allocate', Lang.bind(this, this._allocate)); + this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); + + this._actorsByUri = {}; this._docManager = DocInfo.getDocManager(); - this._docManager.connect('changed', Lang.bind(this, function(mgr) { - this._redisplay(); - })); - this._redisplay(); + this._docManager.connect('changed', Lang.bind(this, this._onDocsChanged)); + this._pendingDocsChange = true; + this._checkDocExistence = false; }, _getPreferredWidth: function(actor, forHeight, alloc) { @@ -355,15 +363,17 @@ DashDocDisplay.prototype = { let firstColumnChildren = Math.ceil(children.length / 2); + let natural = 0; for (let i = 0; i < firstColumnChildren; i++) { let child = children[i]; - let [minSize, naturalSize] = child.get_preferred_height(forWidth); - alloc.natural_size += naturalSize; + let [minSize, naturalSize] = child.get_preferred_height(-1); + natural += naturalSize; if (i > 0 && i < children.length - 1) { - alloc.natural_size += DEFAULT_SPACING; + natural += DEFAULT_SPACING; } } + alloc.natural_size = natural; }, _allocate: function(actor, box, flags) { @@ -418,28 +428,49 @@ DashDocDisplay.prototype = { i++; } - // Everything else didn't fit, just hide it. - for (; i < children.length; i++) { - children[i].hide(); + if (this._checkDocExistence) { + // Now we know how many docs we are displaying, queue a check to see if any of them + // have been deleted. If they are deleted, then we'll get a 'changed' signal; since + // we'll now be displaying items we weren't previously, we'll check again to see + // if they were deleted, and so forth and so on. + // TODO: We should change this to ask for as many as we can fit in the given space: + // https://bugzilla.gnome.org/show_bug.cgi?id=603522#c23 + this._docManager.queueExistenceCheck(i); + this._checkDocExistence = false; } + + let skipPaint = []; + for (; i < children.length; i++) + this.actor.set_skip_paint(children[i], true); + }, + + _onDocsChanged: function() { + this._checkDocExistence = true; + Main.queueDeferredWork(this._workId); }, _redisplay: function() { + // Should be kept alive by the _actorsByUri this.actor.remove_all(); - - let docs = this._docManager.getItems(); - let docUrls = []; - for (let url in docs) { - docUrls.push(url); + let docs = this._docManager.getTimestampOrderedInfos(); + for (let i = 0; i < docs.length; i++) { + let doc = docs[i]; + let display = this._actorsByUri[doc.uri]; + if (display) { + this.actor.add_actor(display.actor); + } else { + let display = new DashDocDisplayItem(doc); + this.actor.add_actor(display.actor); + this._actorsByUri[doc.uri] = display; + } } - docUrls.sort(function (urlA, urlB) { return docs[urlB].timestamp - docs[urlA].timestamp; }); - let textureCache = Shell.TextureCache.get_default(); - - for (let i = 0; i < docUrls.length; i++) { - let url = docUrls[i]; - let docInfo = docs[url]; - let display = new DashDocDisplayItem(docInfo); - this.actor.add_actor(display.actor); + // Any unparented actors must have been deleted + for (let uri in this._actorsByUri) { + let display = this._actorsByUri[uri]; + if (display.actor.get_parent() == null) { + display.actor.destroy(); + delete this._actorsByUri[uri]; + } } this.emit('changed'); } diff --git a/js/ui/widget.js b/js/ui/widget.js index 341833d82..b7e8ca335 100644 --- a/js/ui/widget.js +++ b/js/ui/widget.js @@ -354,8 +354,7 @@ RecentDocsWidget.prototype = { for (i = 0; i < docs.length; i++) { let docInfo = new DocInfo.DocInfo (docs[i]); - if (docInfo.exists()) - items.push(docInfo); + items.push(docInfo); } items.sort(function (a,b) { return b.timestamp - a.timestamp; });