diff --git a/js/Makefile.am b/js/Makefile.am index 2c1bfb640..2cb6be03c 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -23,7 +23,6 @@ nobase_dist_js_DATA = \ ui/dash.js \ ui/dateMenu.js \ ui/dnd.js \ - ui/docDisplay.js \ ui/endSessionDialog.js \ ui/environment.js \ ui/extensionSystem.js \ @@ -64,4 +63,5 @@ nobase_dist_js_DATA = \ ui/workspaceThumbnail.js \ ui/workspacesView.js \ ui/workspaceSwitcherPopup.js \ - ui/xdndHandler.js + ui/xdndHandler.js \ + ui/zeitgeistSearch.js diff --git a/js/misc/docInfo.js b/js/misc/docInfo.js index 8ec375aa9..0b4bc365e 100644 --- a/js/misc/docInfo.js +++ b/js/misc/docInfo.js @@ -1,36 +1,32 @@ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const St = imports.gi.St; -const Shell = imports.gi.Shell; -const Lang = imports.lang; -const Signals = imports.signals; const Search = imports.ui.search; -const THUMBNAIL_ICON_MARGIN = 2; - -function DocInfo(recentInfo) { - this._init(recentInfo); +function ZeitgeistItemInfo(event) { + this._init(event); } -DocInfo.prototype = { - _init : function(recentInfo) { - this.recentInfo = recentInfo; - // We actually used get_modified() instead of get_visited() - // here, as GtkRecentInfo doesn't updated get_visited() - // correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094 - this.timestamp = recentInfo.get_modified(); - this.name = recentInfo.get_display_name(); +ZeitgeistItemInfo.prototype = { + _init : function(event) { + this.event = event; + this.subject = event.subjects[0]; + this.timestamp = event.timestamp; + this.name = this.subject.text; this._lowerName = this.name.toLowerCase(); - this.uri = recentInfo.get_uri(); - this.mimeType = recentInfo.get_mime_type(); + this.uri = this.subject.uri; + this.mimeType = this.subject.mimetype; + this.interpretation = this.subject.interpretation; }, createIcon : function(size) { - return St.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo); + return St.TextureCache.get_default().load_thumbnail(size, this.uri, this.subject.mimetype); + // FIXME: We should consider caching icons }, - launch : function(workspaceIndex) { - Shell.DocSystem.get_default().open(this.recentInfo, workspaceIndex); + launch : function() { + Gio.app_info_launch_default_for_uri(this.uri, + global.create_app_launch_context()); }, matchTerms: function(terms) { @@ -48,93 +44,5 @@ DocInfo.prototype = { } } return mtype; - } + }, }; - -var docManagerInstance = null; - -function getDocManager() { - if (docManagerInstance == null) - docManagerInstance = new DocManager(); - return docManagerInstance; -} - -/** - * DocManager wraps the DocSystem, primarily to expose DocInfo objects. - */ -function DocManager() { - this._init(); -} - -DocManager.prototype = { - _init: function() { - 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._docSystem.get_all(); - this._infosByTimestamp = []; - this._infosByUri = {}; - for (let i = 0; i < docs.length; i++) { - let recentInfo = docs[i]; - - let docInfo = new DocInfo(recentInfo); - this._infosByTimestamp.push(docInfo); - this._infosByUri[docInfo.uri] = docInfo; - } - this.emit('changed'); - }, - - 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); - }, - - _searchDocs: function(items, terms) { - let multiplePrefixMatches = []; - let prefixMatches = []; - let multipleSubtringMatches = []; - let substringMatches = []; - for (let i = 0; i < items.length; i++) { - let item = items[i]; - let mtype = item.matchTerms(terms); - if (mtype == Search.MatchType.MULTIPLE_PREFIX) - multiplePrefixMatches.push(item.uri); - else if (mtype == Search.MatchType.PREFIX) - prefixMatches.push(item.uri); - else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING) - multipleSubtringMatches.push(item.uri); - else if (mtype == Search.MatchType.SUBSTRING) - substringMatches.push(item.uri); - } - return multiplePrefixMatches.concat(prefixMatches.concat(multipleSubtringMatches.concat(substringMatches))); - }, - - initialSearch: function(terms) { - return this._searchDocs(this._infosByTimestamp, terms); - }, - - subsearch: function(previousResults, terms) { - return this._searchDocs(previousResults.map(Lang.bind(this, - function(url) { - return this._infosByUri[url]; - })), terms); - } -}; - -Signals.addSignalMethods(DocManager.prototype); diff --git a/js/ui/docDisplay.js b/js/ui/docDisplay.js deleted file mode 100644 index 2e77f8c77..000000000 --- a/js/ui/docDisplay.js +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ - -const Gettext = imports.gettext.domain('gnome-shell'); -const _ = Gettext.gettext; - -const DocInfo = imports.misc.docInfo; -const Params = imports.misc.params; -const Search = imports.ui.search; - - -function DocSearchProvider() { - this._init(); -} - -DocSearchProvider.prototype = { - __proto__: Search.SearchProvider.prototype, - - _init: function(name) { - Search.SearchProvider.prototype._init.call(this, _("RECENT ITEMS")); - this._docManager = DocInfo.getDocManager(); - }, - - getResultMeta: function(resultId) { - let docInfo = this._docManager.lookupByUri(resultId); - if (!docInfo) - return null; - return { 'id': resultId, - 'name': docInfo.name, - 'icon': docInfo.createIcon(Search.RESULT_ICON_SIZE)}; - }, - - activateResult: function(id, params) { - params = Params.parse(params, { workspace: null, - timestamp: null }); - - let docInfo = this._docManager.lookupByUri(id); - docInfo.launch(params.workspace ? params.workspace.index() : -1); - }, - - getInitialResultSet: function(terms) { - return this._docManager.initialSearch(terms); - }, - - getSubsearchResultSet: function(previousResults, terms) { - return this._docManager.subsearch(previousResults, terms); - }, - - expandSearch: function(terms) { - log('TODO expand docs search'); - } -}; diff --git a/js/ui/overview.js b/js/ui/overview.js index 1aeb83b93..3c5e6e64a 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -15,7 +15,6 @@ const Gdk = imports.gi.Gdk; const AppDisplay = imports.ui.appDisplay; const Dash = imports.ui.dash; const DND = imports.ui.dnd; -const DocDisplay = imports.ui.docDisplay; const Lightbox = imports.ui.lightbox; const Main = imports.ui.main; const MessageTray = imports.ui.messageTray; @@ -24,6 +23,7 @@ const PlaceDisplay = imports.ui.placeDisplay; const Tweener = imports.ui.tweener; const ViewSelector = imports.ui.viewSelector; const WorkspacesView = imports.ui.workspacesView; +const ZeitgeistSearch = imports.ui.zeitgeistSearch; // Time for initial animation going into Overview mode const ANIMATION_TIME = 0.25; @@ -189,7 +189,11 @@ Overview.prototype = { this.viewSelector.addSearchProvider(new AppDisplay.AppSearchProvider()); this.viewSelector.addSearchProvider(new AppDisplay.PrefsSearchProvider()); this.viewSelector.addSearchProvider(new PlaceDisplay.PlaceSearchProvider()); - this.viewSelector.addSearchProvider(new DocDisplay.DocSearchProvider()); + this.viewSelector.addSearchProvider(new ZeitgeistSearch.DocumentsAsyncSearchProvider()); + this.viewSelector.addSearchProvider(new ZeitgeistSearch.VideosAsyncSearchProvider()); + this.viewSelector.addSearchProvider(new ZeitgeistSearch.MusicAsyncSearchProvider()); + this.viewSelector.addSearchProvider(new ZeitgeistSearch.PicturesAsyncSearchProvider()); + this.viewSelector.addSearchProvider(new ZeitgeistSearch.OtherAsyncSearchProvider()); // TODO - recalculate everything when desktop size changes this.dash = new Dash.Dash(); diff --git a/js/ui/zeitgeistSearch.js b/js/ui/zeitgeistSearch.js new file mode 100644 index 000000000..efca78bc0 --- /dev/null +++ b/js/ui/zeitgeistSearch.js @@ -0,0 +1,199 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- + * + * Copyright (C) 2010 Seif Lotfy + * Copyright (C) 2011 Siegfried-Angel Gevatter Pujals + * Copyright (C) 2010-2011 Collabora Ltd. + * Authored by: Seif Lotfy + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +const Lang = imports.lang; +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio +const Semantic = imports.misc.semantic; +const Zeitgeist = imports.misc.zeitgeist; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const DocInfo = imports.misc.docInfo; +const Search = imports.ui.search; + +// FIXME: The subject cache is never being emptied. +let ZeitgeistSubjectCache = {}; + +function ZeitgeistAsyncSearchProvider(title, interpretations) { + this._init(title, interpretations); +} + +ZeitgeistAsyncSearchProvider.prototype = { + __proto__: Search.SearchProvider.prototype, + + _init: function(title, interpretations) { + Search.SearchProvider.prototype._init.call(this, title); + this._buildTemplates(interpretations); + }, + + _buildTemplates: function(interpretations) { + this.templates = []; + for (let i = 0; i < interpretations.length; i++) { + let subject = new Zeitgeist.Subject('', interpretations[i], '', '', '', '', ''); + let event = new Zeitgeist.Event('', '', '', [subject], []); + this.templates.push(event); + } + }, + + _search: function(terms) { + this._search_terms = terms; + Zeitgeist.fullTextSearch(terms[0]+'*', + this.templates, + Lang.bind(this, function(events) { + if (terms == this._search_terms) + this._asyncCallback(events); + })); + }, + + _asyncCancelled: function() { + this._search_terms = null; + }, + + getInitialResultSet: function(terms) { + this._search(terms); + return []; + }, + + getSubsearchResultSet: function(previousResults, terms) { + this.tryCancelAsync(); + return this.getInitialResultSet(terms); + }, + + getResultMeta: function(resultId) { + return { 'id': ZeitgeistSubjectCache[resultId].uri, + 'name': ZeitgeistSubjectCache[resultId].name, + 'icon': ZeitgeistSubjectCache[resultId].createIcon(48) }; + }, + + activateResult: function(resultId) { + Gio.app_info_launch_default_for_uri(resultId, + global.create_app_launch_context()); + }, + + _asyncCallback: function(events) { + let items = []; + for (let i = 0; i < events.length; i++) { + let event = events[i]; + let subject = event.subjects[0]; + let uri = subject.uri.replace('file://', ''); + uri = GLib.uri_unescape_string(uri, ''); + if (GLib.file_test(uri, GLib.FileTest.EXISTS)) { + if (!ZeitgeistSubjectCache.hasOwnProperty(subject.uri)) { + let info = new DocInfo.ZeitgeistItemInfo(event); + ZeitgeistSubjectCache[info.uri] = info; + } + items.push(subject.uri); + } + } + this.addItems(items); + } +}; + +function DocumentsAsyncSearchProvider() { + this._init(); +} + +DocumentsAsyncSearchProvider.prototype = { + __proto__: ZeitgeistAsyncSearchProvider.prototype, + + _init: function() { + let interpretations = [Semantic.NFO_DOCUMENT]; + ZeitgeistAsyncSearchProvider.prototype._init.call(this, _("DOCUMENTS"), interpretations); + } +}; + +function VideosAsyncSearchProvider() { + this._init(); +} + +VideosAsyncSearchProvider.prototype = { + __proto__: ZeitgeistAsyncSearchProvider.prototype, + + _init: function() { + let interpretations = [Semantic.NFO_VIDEO]; + ZeitgeistAsyncSearchProvider.prototype._init.call(this, _("VIDEOS"), interpretations); + } +}; + +function MusicAsyncSearchProvider() { + this._init(); +} + +MusicAsyncSearchProvider.prototype = { + __proto__: ZeitgeistAsyncSearchProvider.prototype, + + _init: function() { + let interpretations = [ + Semantic.NFO_AUDIO, + Semantic.NMM_MUSIC_PIECE]; + ZeitgeistAsyncSearchProvider.prototype._init.call(this, _("MUSIC"), interpretations); + } +}; + +function PicturesAsyncSearchProvider() { + this._init(); +} + +PicturesAsyncSearchProvider.prototype = { + __proto__: ZeitgeistAsyncSearchProvider.prototype, + + _init: function() { + let interpretations = [Semantic.NFO_IMAGE]; + ZeitgeistAsyncSearchProvider.prototype._init.call(this, _("PICTURES"), interpretations); + } +}; + +function OtherAsyncSearchProvider() { + this._init(); +} + +OtherAsyncSearchProvider.prototype = { + __proto__: ZeitgeistAsyncSearchProvider.prototype, + + _init: function() { + let interpretations = [ + '!' + Semantic.NFO_IMAGE, + '!' + Semantic.NFO_DOCUMENT, + '!' + Semantic.NFO_VIDEO, + '!' + Semantic.NFO_AUDIO, + '!' + Semantic.NMM_MUSIC_PIECE]; + ZeitgeistAsyncSearchProvider.prototype._init.call(this, _("OTHER"), interpretations); + }, + + _buildTemplates: function(interpretations) { + // Here we want to get everything matching all of the templates, and + // not just any of them. Therefore we need to AND the interpretations + // instead of OR'ing them; this is done by having an Event with + // different Subjects. + this.templates = []; + let subjects = []; + for (let i = 0; i < interpretations.length; i++) { + let subject = new Zeitgeist.Subject('', interpretations[i], '', '', '', '', ''); + subjects.push(subject); + } + let event = new Zeitgeist.Event('', '', '', subjects, []); + this.templates.push(event); + } +};