Compare commits

...

13 Commits

Author SHA1 Message Date
Seif Lotfy
07c39959d9 Use the correct ID for app that is being launched 2011-04-04 17:02:54 -04:00
Federico Mena Quintero
23d1d18619 Convert stringified timestamps from D-Bus to integers
We already had the symmetrical code to convert numerical timestamps to strings before
sending out D-Bus requests, anyway.

Signed-off-by: Federico Mena Quintero <federico@gnome.org>
2011-03-31 13:07:44 -04:00
Federico Mena Quintero
23bf5db80c Merge remote branch 'origin/master' into zeitgeist 2011-03-29 17:00:54 -04:00
Seif Lotfy
550e9973b7 Add basic jumplists in the application icons
We add recently-used items relative to the app in question, and
also frequently-used items, to the app icon's popup menu.
2011-03-28 18:35:55 -04:00
Federico Mena Quintero
e4e6b26e15 Merge remote branch origin/master into zeitgeist 2011-03-25 18:28:35 -04:00
Federico Mena Quintero
9551e1c2f5 Merge remote branch 'origin/master' into zeitgeist 2011-03-14 18:35:33 -04:00
Federico Mena Quintero
a70ea12be8 Merge remote branch 'origin/master' into zeitgeist 2011-03-09 18:50:07 -05:00
Seif Lotfy
fb5a3a53fa Don't refresh all the search providers in O(n^2) fashion; just refresh the one that changed
This modification reduces the faulty clearing and redrawing of the
results.  The old code refreshed the view for every async
search provider by iterating through all result sets (one result set per
provider) and refreshing all providers. In this case there were 40
redraws. This patch adds a new method _clearDisplayForProvider that
allows us to refresh only providers with changed results.
2011-03-04 16:42:15 -05:00
Federico Mena Quintero
30eac56691 Merge master into zeitgeist 2011-03-03 21:51:15 -05:00
Federico Mena Quintero
cb6f3a0836 Add all of Zeitgeist's enum values for ResultType and StorageState
These are copied from the Python code.  It's unfortunate
that we can't introspect enum values from D-Bus.

Signed-off-by: Federico Mena Quintero <federico@gnome.org>
2011-03-03 21:40:44 -05:00
Siegfried-Angel Gevatter Pujals
8e579024b1 Add support for Zeitgeist search providers
https://bugzilla.gnome.org/show_bug.cgi?id=640659

Co-authored-by: Seif Lotfy <seif@lotfy.com>
Co-authored-by: Federico Mena Quintero <federico@gnome.org>
2011-03-03 21:40:44 -05:00
Siegfried-Angel Gevatter Pujals
41c70293df Add JavaScript bindings for Zeitgeist
Co-authored-by: Seif Lotfy <seif@lotfy.com>
Co-authored-by: Federico Mena Quintero <federico@gnome.org>
2011-03-03 21:40:44 -05:00
Seif Lotfy
4b015903bc Add support for asynchronous search providers
https://bugzilla.gnome.org/show_bug.cgi?id=640659
2011-03-03 21:40:33 -05:00
12 changed files with 701 additions and 190 deletions

View File

@ -10,7 +10,9 @@ nobase_dist_js_DATA = \
misc/history.js \
misc/modemManager.js \
misc/params.js \
misc/semantic.js \
misc/util.js \
misc/zeitgeist.js \
perf/core.js \
ui/altTab.js \
ui/appDisplay.js \
@ -22,7 +24,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 +65,5 @@ nobase_dist_js_DATA = \
ui/workspaceThumbnail.js \
ui/workspacesView.js \
ui/workspaceSwitcherPopup.js \
ui/xdndHandler.js
ui/xdndHandler.js \
ui/zeitgeistSearch.js

View File

@ -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);

43
js/misc/semantic.js Normal file
View File

@ -0,0 +1,43 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
*
* Semantic-desktop interpretations for various data types
*
* Authors: Federico Mena Quintero <federico@gnome.org>
*
* 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 NFO_AUDIO = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio";
const NFO_DOCUMENT = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Document";
const NFO_HTML_DOCUMENT = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#HtmlDocument";
const NFO_IMAGE = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image";
const NFO_MEDIA = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Media";
const NFO_MIND_MAP = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#MindMap";
const NFO_PAGINATED_TEXT_DOCUMENT = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#PaginatedTextDocument";
const NFO_PLAIN_TEXT_DOCUMENT = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#PlainTextDocument";
const NFO_PRESENTATION = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Presentation";
const NFO_RASTER_IMAGE = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#RasterImage";
const NFO_SOURCE_CODE = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#SourceCode";
const NFO_SPREADSHEET = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Spreadsheet";
const NFO_TEXT_DOCUMENT = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#TextDocument";
const NFO_VECTOR_IMAGE = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#VectorImage";
const NFO_VIDEO = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Video";
const NMM_CURSOR = "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#Cursor";
const NMM_ICON = "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#Icon";
const NMM_MOVIE = "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#Movie";
const NMM_MUSIC_PIECE = "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#MusicPiece";
const NMM_TV_SHOW = "http://www.semanticdesktop.org/ontologies/2009/02/19/nmm#TVShow";

264
js/misc/zeitgeist.js Normal file
View File

@ -0,0 +1,264 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
*
* Copyright (C) 2010 Seif Lotfy <seif@lotfy.com>
* Copyright (C) 2011 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
* Copyright (C) 2010-2011 Collabora Ltd.
* Authored by: Seif Lotfy <seif@lotfy.com>
*
* 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 DBus = imports.dbus;
const SIG_EVENT = '(asaasay)';
const MAX_TIMESTAMP = 9999999999999;
// Number of results given by fullTextSearch; 100 is probably enough.
// Note: We can't currently increase this number to anything above 132, due to
// https://bugs.launchpad.net/zeitgeist-extensions/+bug/716503
const MAX_RESULTS = 100;
const ResultType = {
// http://zeitgeist-project.com/docs/0.6/datamodel.html#resulttype
// It's unfortunate to have to define these by hand; maybe if D-Bus had a way to introspect enums...
MOST_RECENT_EVENTS : 0,
LEAST_RECENT_EVENTS : 1,
MOST_RECENT_SUBJECTS : 2,
LEAST_RECENT_SUBJECTS : 3,
MOST_POPULAR_SUBJECTS : 4,
LEAST_POPULAR_SUBJECTS : 5,
MOST_POPULAR_ACTOR : 6,
LEAST_POPULAR_ACTOR : 7,
MOST_RECENT_ACTOR : 8,
LEAST_RECENT_ACTOR : 9,
MOST_RECENT_ORIGIN : 10,
LEAST_RECENT_ORIGIN : 11,
MOST_POPULAR_ORIGIN : 12,
LEAST_POPULAR_ORIGIN : 13,
OLDEST_ACTOR : 14,
MOST_RECENT_SUBJECT_INTERPRETATION : 15,
LEAST_RECENT_SUBJECT_INTERPRETATION : 16,
MOST_POPULAR_SUBJECT_INTERPRETATION : 17,
LEAST_POPULAR_SUBJECT_INTERPRETATION : 18,
MOST_RECENT_MIME_TYPE : 19,
LEAST_RECENT_MIME_TYPE : 20,
MOST_POPULAR_MIME_TYPE : 21,
LEAST_POPULAR_MIME_TYPE : 22
};
const StorageState = {
// http://zeitgeist-project.com/docs/0.6/datamodel.html#storagestate
// As with ResultType, it would be nice if we could introspect enums through D-Bus
NOT_AVAILABLE : 0,
AVAILABLE : 1,
ANY : 2
};
/* Zeitgeist Subjects (files, people, etc.) */
function Subject(uri, interpretation, manifestation, origin, mimetype, text, storage) {
this._init(uri, interpretation, manifestation, origin, mimetype, text, storage);
};
Subject.prototype = {
_init: function(uri, interpretation, manifestation, origin, mimetype, text, storage) {
this.uri = uri;
this.interpretation = interpretation;
this.manifestation = manifestation;
this.origin = origin;
this.mimetype = mimetype;
this.text = text;
this.storage = storage;
},
};
Subject.fromPlain = function(rawSubject) {
return new Subject(rawSubject[0], // uri
rawSubject[1], // interpretation
rawSubject[2], // manifestation
rawSubject[3], // origin
rawSubject[4], // mimetype
rawSubject[5], // text
rawSubject[6]); // storage
};
Subject.toPlain = function(subject) {
let rawSubject = [];
rawSubject[0] = subject.uri;
rawSubject[1] = subject.interpretation;
rawSubject[2] = subject.manifestation
rawSubject[3] = subject.origin;
rawSubject[4] = subject.mimetype;
rawSubject[5] = subject.text;
rawSubject[6] = subject.storage;
return rawSubject;
};
/* Zeitgeist Events */
function Event(interpretation, manifestation, actor, subjects, payload) {
this._init(interpretation, manifestation, actor, subjects, payload);
};
Event.prototype = {
_init: function(interpretation, manifestation, actor, subjects, payload) {
this.id = 0;
this.timestamp = 0;
this.actor = actor;
this.interpretation = interpretation;
this.manifestation = manifestation;
this.actor = actor;
this.payload = payload;
this.subjects = subjects;
},
};
Event.fromPlain = function(rawEvent) {
let subjects = rawEvent[1].map(Subject.fromPlain);
let event = new Event(rawEvent[0][2], // interpretation
rawEvent[0][3], // manifestation
rawEvent[0][4], // actor
subjects, // subjects
rawEvent[2]);// payload
event.id = rawEvent[0][0]; // id
event.timestamp = parseInt(rawEvent[0][1], 10); // timestamp - it comes as a string over d-bus (yuck)
return event;
};
Event.toPlain = function(event) {
let rawEvent = [];
rawEvent[0] = [];
rawEvent[0][0] = event.id.toString();
rawEvent[0][1] = event.timestamp.toString();
rawEvent[0][2] = event.interpretation;
rawEvent[0][3] = event.manifestation;
rawEvent[0][4] = event.actor;
rawEvent[1] = event.subjects.map(Subject.toPlain);
rawEvent[2] = event.payload;
return rawEvent;
};
// Zeitgeist D-Bus interface definitions. Note that most of these are
// incomplete, and only cover the methods/properties/signals that
// we're currently using.
/* Zeitgeist D-Bus Interface */
const LOG_NAME = 'org.gnome.zeitgeist.Engine';
const LOG_PATH = '/org/gnome/zeitgeist/log/activity';
const LogIface = {
name: 'org.gnome.zeitgeist.Log',
methods: [
{ name: 'GetEvents',
inSignature: 'au',
outSignature: 'a'+SIG_EVENT },
{ name: 'FindRelatedUris',
inSignature: 'au',
outSignature: '(xx)a(' + SIG_EVENT + ')a'+ SIG_EVENT + 'uuu' },
{ name: 'FindEventIds',
inSignature: '(xx)a' + SIG_EVENT + 'uuu',
outSignature: 'au' },
{ name: 'FindEvents',
inSignature: '(xx)a' + SIG_EVENT + 'uuu',
outSignature: 'a' + SIG_EVENT },
{ name: 'InsertEvents',
inSignature: 'a' + SIG_EVENT,
outSignature: 'au' },
{ name: 'DeleteEvents',
inSignature: 'au',
outSignature: '(xx)' },
{ name: 'DeleteLog',
inSignature: '',
outSignature: '' },
{ name: 'Quit',
inSignature: '',
outSignature: '' },
// FIXME: Add missing DBus Methods
// - InstallMonitor
// - RemoveMonitor
],
properties: [
{ name: 'Get',
inSignature: 'ss',
outSignature: 'v',
access: 'read' },
{ name: 'Set',
inSignature: 'ssv',
outSignature: '',
access: 'read' },
{ name: 'GetAll',
inSignature: 's',
outSignature: 'a{sv}',
access: 'read' },
]
};
const Log = DBus.makeProxyClass(LogIface);
const _log = new Log(DBus.session, LOG_NAME, LOG_PATH);
function findEvents(timeRange, eventTemplates, storageState, numEvents, resultType, callback) {
function handler(results, error) {
if (error != null)
log("Error querying Zeitgeist for events: "+error);
else
callback(results.map(Event.fromPlain));
}
_log.FindEventsRemote(timeRange, eventTemplates.map(Event.toPlain),
storageState, numEvents, resultType, handler);
}
/* Zeitgeist Full-Text-Search Interface */
const INDEX_NAME = 'org.gnome.zeitgeist.Engine';
const INDEX_PATH = '/org/gnome/zeitgeist/index/activity';
const IndexIface = {
name: 'org.gnome.zeitgeist.Index',
methods: [
{ name: 'Search',
inSignature: 's(xx)a'+SIG_EVENT+'uuu',
outSignature: 'a'+SIG_EVENT+'u' },
],
};
const Index = DBus.makeProxyClass(IndexIface);
const _index = new Index(DBus.session, INDEX_NAME, INDEX_PATH);
/**
* fullTextSearch:
*
* Asynchronously search Zeitgeist's index for events relating to the query.
*
* @param query The query string, using asterisks for wildcards. Wildcards must
* be used at the start and/or end of a string to get relevant information.
* @param eventTemplates Zeitgeist event templates, see
* http://zeitgeist-project.com/docs/0.6/datamodel.html#event for more
* information
* @param callback The callback, takes a list containing Zeitgeist.Event
* objects
*/
function fullTextSearch(query, eventTemplates, callback) {
function handler(results, error) {
if (error != null)
log("Error searching with Zeitgeist FTS: "+error);
else
callback(results[0].map(Event.fromPlain));
}
_index.SearchRemote(query, [0, MAX_TIMESTAMP],
eventTemplates.map(Event.toPlain),
0, // offset into the search results
MAX_RESULTS,
ResultType.MOST_POPULAR_SUBJECTS, handler);
}

View File

@ -2,6 +2,7 @@
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
@ -13,6 +14,7 @@ const _ = Gettext.gettext;
const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd;
const DocInfo = imports.misc.docInfo;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
@ -21,6 +23,7 @@ const Search = imports.ui.search;
const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace;
const Params = imports.misc.params;
const Zeitgeist = imports.misc.zeitgeist;
const MENU_POPUP_TIMEOUT = 600;
const SCROLL_TIME = 0.1;
@ -582,6 +585,7 @@ AppIconMenu.prototype = {
this.blockSourceEvents = true;
this._source = source;
this._eventTemplate = new Zeitgeist.Event('', '', "application://" + this._source.app.get_id(), [], []);
this.connect('activate', Lang.bind(this, this._onActivate));
this.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
@ -628,6 +632,63 @@ AppIconMenu.prototype = {
this._toggleFavoriteMenuItem = this._appendMenuItem(isFavorite ? _("Remove from Favorites")
: _("Add to Favorites"));
Zeitgeist.findEvents([new Date().getTime() - 86400000*90, Zeitgeist.MAX_TIMESTAMP],
[this._eventTemplate],
Zeitgeist.StorageState.ANY,
100,
Zeitgeist.ResultType.MOST_RECENT_SUBJECTS,
Lang.bind(this, this._appendJumplist));
},
_appendJumplist: function (events) {
let fetchedUris = [];
let hasJumplist = false;
function appendEvents(events2, count, type) {
if (count == null) {
count = 3;
}
if (type == null) {
type = "emblem-favorite";
}
let j = 0;
if (events.length > 0) {
for (let i in events) {
let uri = events[i].subjects[0].uri.replace('file://', '');
uri = uri.replace(/\%20/g, ' '); // FIXME: properly unescape, or get the display name otherwise
if (fetchedUris.indexOf(uri) == -1 &&
(GLib.file_test(uri, GLib.FileTest.EXISTS) || this._source.app.get_id() == "tomboy.desktop")) {
if (!hasJumplist) {
this._appendSeparator();
hasJumplist = true;
}
this._appendJumplistItem(events[i], type);
fetchedUris.push(uri);
j++;
if (j >= count)
break;
}
}
}
}
appendEvents.call(this, events, 4, "document-open-recent");
Zeitgeist.findEvents([new Date().getTime() - 86400000*90, Zeitgeist.MAX_TIMESTAMP],
[this._eventTemplate],
Zeitgeist.StorageState.ANY,
100,
Zeitgeist.ResultType.MOST_POPULAR_SUBJECTS,
Lang.bind(this, appendEvents));
},
_appendJumplistItem: function (event, type) {
let info = new DocInfo.ZeitgeistItemInfo(event);
let item = new PopupMenu.PopupImageMenuItem(info.name, type);
this.addMenuItem(item);
item.connect('activate', Lang.bind(this, function () {
let app = new Gio.DesktopAppInfo.new(this._source.app.get_id());
app.launch_uris([info.uri], null);
}));
},
_appendSeparator: function () {

View File

@ -1,50 +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,
'createIcon': function(size) {
return docInfo.createIcon(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);
}
};

View File

@ -26,11 +26,13 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
'const Gtk = imports.gi.Gtk; ' +
'const Mainloop = imports.mainloop; ' +
'const Meta = imports.gi.Meta; ' +
'const Semantic = imports.misc.semantic' +
'const Shell = imports.gi.Shell; ' +
'const Tp = imports.gi.TelepathyGLib; ' +
'const Main = imports.ui.main; ' +
'const Lang = imports.lang; ' +
'const Tweener = imports.ui.tweener; ' +
'const Zeitgeist = imports.misc.zeitgeist; ' +
/* Utility functions...we should probably be able to use these
* in the shell core code too. */
'const stage = global.stage; ' +

View File

@ -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;
@ -25,6 +24,7 @@ const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector;
const WorkspacesView = imports.ui.workspacesView;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
const ZeitgeistSearch = imports.ui.zeitgeistSearch;
// Time for initial animation going into Overview mode
const ANIMATION_TIME = 0.25;
@ -192,7 +192,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();

View File

@ -115,6 +115,43 @@ function SearchProvider(title) {
SearchProvider.prototype = {
_init: function(title) {
this.title = title;
this.searchSystem = null;
this.searchAsync = false;
},
_asyncCancelled: function() {
},
startAsync: function() {
this.searchAsync = true;
},
tryCancelAsync: function() {
if (!this.searchAsync)
return;
this._asyncCancelled();
this.searchAsync = false;
},
/**
* addItems:
* @items: an array of result identifier strings representing
* items which match the last given search terms.
*
* This should be used for something that requires a bit more
* logic; it's designed to be an asyncronous way to add a result
* to the current search.
*/
addItems: function( items) {
if (!this.searchSystem)
throw new Error('Search provider not registered');
if (!items.length)
return;
this.tryCancelAsync();
this.searchSystem.addProviderItems(this, items);
},
/**
@ -212,6 +249,7 @@ SearchProvider.prototype = {
};
Signals.addSignalMethods(SearchProvider.prototype);
function OpenSearchSystem() {
this._init();
}
@ -324,6 +362,7 @@ SearchSystem.prototype = {
},
registerProvider: function (provider) {
provider.searchSystem = this;
this._providers.push(provider);
},
@ -340,30 +379,50 @@ SearchSystem.prototype = {
this._previousResults = [];
},
addProviderItems: function(provider, items) {
let index = this._providers.indexOf(provider);
let [provider2, results] = this._previousResults[index];
if (provider !== provider2)
return;
results.push.apply(results, items);
this.emit('results-updated', this._previousResults);
},
updateSearch: function(searchString) {
searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
if (searchString == '')
return [];
return;
let terms = searchString.split(/\s+/);
let isSubSearch = terms.length == this._previousTerms.length;
if (isSubSearch) {
for (let i = 0; i < terms.length; i++) {
if (terms[i].indexOf(this._previousTerms[i]) != 0) {
isSubSearch = false;
break;
this.updateSearchResults(terms);
},
updateSearchResults: function(terms) {
let isSubSearch = false;
if (terms) {
isSubSearch = terms.length == this._previousTerms.length;
if (isSubSearch) {
for (let i = 0; i < terms.length; i++) {
if (terms[i].indexOf(this._previousTerms[i]) != 0) {
isSubSearch = false;
break;
}
}
}
} else {
terms = this._previousTerms;
}
let results = [];
if (isSubSearch) {
for (let i = 0; i < this._previousResults.length; i++) {
for (let i = 0; i < this._providers.length; i++) {
let [provider, previousResults] = this._previousResults[i];
provider.tryCancelAsync();
try {
let providerResults = provider.getSubsearchResultSet(previousResults, terms);
if (providerResults.length > 0)
results.push([provider, providerResults]);
results.push([provider, providerResults]);
} catch (error) {
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
}
@ -371,10 +430,10 @@ SearchSystem.prototype = {
} else {
for (let i = 0; i < this._providers.length; i++) {
let provider = this._providers[i];
provider.tryCancelAsync();
try {
let providerResults = provider.getInitialResultSet(terms);
if (providerResults.length > 0)
results.push([provider, providerResults]);
results.push([provider, providerResults]);
} catch (error) {
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
}
@ -383,8 +442,7 @@ SearchSystem.prototype = {
this._previousTerms = terms;
this._previousResults = results;
return results;
}
this.emit('results-updated', results);
},
};
Signals.addSignalMethods(SearchSystem.prototype);

View File

@ -182,6 +182,7 @@ function SearchResults(searchSystem, openSearchSystem) {
SearchResults.prototype = {
_init: function(searchSystem, openSearchSystem) {
this._searchSystem = searchSystem;
this._searchSystem.connect('results-updated', Lang.bind(this, this._updateResults));
this._openSearchSystem = openSearchSystem;
this.actor = new St.BoxLayout({ name: 'searchResults',
@ -216,9 +217,11 @@ SearchResults.prototype = {
this._selectedProvider = -1;
this._providers = this._searchSystem.getProviders();
this._providerMeta = [];
for (let i = 0; i < this._providers.length; i++)
this._providerMetaResults = {};
for (let i = 0; i < this._providers.length; i++) {
this.createProviderMeta(this._providers[i]);
this._providerMetaResults[this.providers[i].title] = [];
}
this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' });
this.actor.add(this._searchProvidersBox);
@ -296,6 +299,12 @@ SearchResults.prototype = {
meta.actor.hide();
}
},
_clearDisplayForProvider: function(index) {
let meta = this._providerMeta[index];
meta.resultDisplay.clear();
meta.actor.hide();
},
reset: function() {
this._searchSystem.reset();
@ -311,15 +320,15 @@ SearchResults.prototype = {
this._statusText.show();
},
doSearch: function (searchString) {
this._searchSystem.updateSearch(searchString);
},
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
updateSearch: function (searchString) {
let results = this._searchSystem.updateSearch(searchString);
this._clearDisplay();
_updateResults: function(searchSystem, results) {
if (results.length == 0) {
this._statusText.set_text(_("No matching results."));
this._statusText.show();
@ -329,14 +338,22 @@ SearchResults.prototype = {
this._statusText.hide();
}
let terms = this._searchSystem.getTerms();
let terms = searchSystem.getTerms();
this._openSearchSystem.setSearchTerms(terms);
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
let meta = this._metaForProvider(provider);
meta.actor.show();
meta.resultDisplay.renderResults(providerResults, terms);
if (providerResults.length == 0)
this._clearDisplayForProvider(i)
else {
if (this._providerMetaResults[provider.title] != providerResults) {
this._providerMetaResults[provider.title] = providerResults;
this._clearDisplayForProvider(i);
let meta = this._metaForProvider(provider);
meta.actor.show();
meta.resultDisplay.renderResults(providerResults, terms);
}
}
}
if (this._selectedOpenSearchButton == -1)

View File

@ -290,7 +290,7 @@ SearchTab.prototype = {
_doSearch: function () {
this._searchTimeoutId = 0;
let text = this._text.get_text().replace(/^\s+/g, '').replace(/\s+$/g, '');
this._searchResults.updateSearch(text);
this._searchResults.doSearch(text);
return false;
}

202
js/ui/zeitgeistSearch.js Normal file
View File

@ -0,0 +1,202 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
*
* Copyright (C) 2010 Seif Lotfy <seif@lotfy.com>
* Copyright (C) 2011 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
* Copyright (C) 2010-2011 Collabora Ltd.
* Authored by: Seif Lotfy <seif@lotfy.com>
*
* 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,
'createIcon': function (size) {
return ZeitgeistSubjectCache[resultId].createIcon(size);
},
};
},
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);
}
};