2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2009-11-29 22:45:30 +00:00
|
|
|
|
2011-01-17 21:38:47 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
const Lang = imports.lang;
|
2009-11-29 22:45:30 +00:00
|
|
|
const Signals = imports.signals;
|
2011-01-17 21:38:47 +00:00
|
|
|
const Shell = imports.gi.Shell;
|
2011-01-17 22:05:30 +00:00
|
|
|
const Util = imports.misc.util;
|
2011-01-17 21:38:47 +00:00
|
|
|
|
|
|
|
const FileUtils = imports.misc.fileUtils;
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
|
|
|
const DISABLED_OPEN_SEARCH_PROVIDERS_KEY = 'disabled-open-search-providers';
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
// Not currently referenced by the search API, but
|
|
|
|
// this enumeration can be useful for provider
|
|
|
|
// implementations.
|
|
|
|
const MatchType = {
|
|
|
|
NONE: 0,
|
2010-06-06 23:09:15 +00:00
|
|
|
SUBSTRING: 1,
|
2012-05-02 19:31:01 +00:00
|
|
|
PREFIX: 2
|
2009-11-29 22:45:30 +00:00
|
|
|
};
|
|
|
|
|
2011-11-20 16:07:14 +00:00
|
|
|
const SearchResultDisplay = new Lang.Class({
|
|
|
|
Name: 'SearchResultDisplay',
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
_init: function(provider) {
|
|
|
|
this.provider = provider;
|
|
|
|
this.actor = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* renderResults:
|
|
|
|
* @results: List of identifier strings
|
|
|
|
* @terms: List of search term strings
|
|
|
|
*
|
|
|
|
* Display the given search matches which resulted
|
|
|
|
* from the given terms. It's expected that not
|
|
|
|
* all results will fit in the space for the container
|
|
|
|
* actor; in this case, show as many as makes sense
|
|
|
|
* for your result type.
|
|
|
|
*
|
|
|
|
* The terms are useful for search match highlighting.
|
|
|
|
*/
|
|
|
|
renderResults: function(results, terms) {
|
2010-05-13 19:46:04 +00:00
|
|
|
throw new Error('Not implemented');
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clear:
|
2011-11-14 03:33:58 +00:00
|
|
|
* Remove all results from this display.
|
2009-11-29 22:45:30 +00:00
|
|
|
*/
|
|
|
|
clear: function() {
|
|
|
|
this.actor.get_children().forEach(function (actor) { actor.destroy(); });
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* getVisibleResultCount:
|
|
|
|
*
|
|
|
|
* Returns: The number of actors visible.
|
|
|
|
*/
|
|
|
|
getVisibleResultCount: function() {
|
2010-05-13 19:46:04 +00:00
|
|
|
throw new Error('Not implemented');
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* SearchProvider:
|
|
|
|
*
|
|
|
|
* Subclass this object to add a new result type
|
|
|
|
* to the search system, then call registerProvider()
|
|
|
|
* in SearchSystem with an instance.
|
2012-05-02 19:54:25 +00:00
|
|
|
* Search is asynchronous and uses the
|
2012-02-21 18:53:25 +00:00
|
|
|
* getInitialResultSet()/getSubsearchResultSet() methods.
|
2009-11-29 22:45:30 +00:00
|
|
|
*/
|
2011-11-20 16:07:14 +00:00
|
|
|
const SearchProvider = new Lang.Class({
|
|
|
|
Name: 'SearchProvider',
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
_init: function(title) {
|
|
|
|
this.title = title;
|
2011-07-24 10:29:36 +00:00
|
|
|
this.searchSystem = null;
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* getInitialResultSet:
|
2010-06-06 23:09:15 +00:00
|
|
|
* @terms: Array of search terms, treated as logical AND
|
2009-11-29 22:45:30 +00:00
|
|
|
*
|
|
|
|
* Called when the user first begins a search (most likely
|
|
|
|
* therefore a single term of length one or two), or when
|
|
|
|
* a new term is added.
|
|
|
|
*
|
2012-05-02 19:54:25 +00:00
|
|
|
* Should "return" an array of result identifier strings representing
|
2009-11-29 22:45:30 +00:00
|
|
|
* items which match the given search terms. This
|
|
|
|
* is expected to be a substring match on the metadata for a given
|
|
|
|
* item. Ordering of returned results is up to the discretion of the provider,
|
|
|
|
* but you should follow these heruistics:
|
|
|
|
*
|
2010-06-06 23:09:15 +00:00
|
|
|
* * Put items where the term matches multiple criteria (e.g. name and
|
|
|
|
* description) before single matches
|
2009-11-29 22:45:30 +00:00
|
|
|
* * Put items which match on a prefix before non-prefix substring matches
|
|
|
|
*
|
2012-05-02 19:54:25 +00:00
|
|
|
* We say "return" above, but in order to make the query asynchronous, use
|
|
|
|
* this.searchSystem.pushResults();. The return value should be ignored.
|
|
|
|
*
|
2009-11-29 22:45:30 +00:00
|
|
|
* This function should be fast; do not perform unindexed full-text searches
|
|
|
|
* or network queries.
|
|
|
|
*/
|
|
|
|
getInitialResultSet: function(terms) {
|
2010-05-13 19:46:04 +00:00
|
|
|
throw new Error('Not implemented');
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* getSubsearchResultSet:
|
|
|
|
* @previousResults: Array of item identifiers
|
|
|
|
* @newTerms: Updated search terms
|
|
|
|
*
|
|
|
|
* Called when a search is performed which is a "subsearch" of
|
|
|
|
* the previous search; i.e. when every search term has exactly
|
|
|
|
* one corresponding term in the previous search which is a prefix
|
|
|
|
* of the new term.
|
|
|
|
*
|
|
|
|
* This allows search providers to only search through the previous
|
|
|
|
* result set, rather than possibly performing a full re-query.
|
2012-02-21 18:53:25 +00:00
|
|
|
*
|
2012-05-02 19:54:25 +00:00
|
|
|
* Similar to getInitialResultSet, the return value for this will
|
|
|
|
* be ignored; use this.searchSystem.pushResults();.
|
2012-02-21 18:53:25 +00:00
|
|
|
*/
|
2012-05-02 19:54:25 +00:00
|
|
|
getSubsearchResultSet: function(previousResults, newTerms) {
|
2012-02-21 18:53:25 +00:00
|
|
|
throw new Error('Not implemented');
|
|
|
|
},
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
/**
|
2012-02-17 15:39:27 +00:00
|
|
|
* getResultMetas:
|
|
|
|
* @ids: Result identifier strings
|
2009-11-29 22:45:30 +00:00
|
|
|
*
|
2012-05-02 19:54:25 +00:00
|
|
|
* Call callback with array of objects with 'id', 'name', (both strings) and
|
2012-02-17 15:39:27 +00:00
|
|
|
* 'createIcon' (function(size) returning a Clutter.Texture) properties
|
|
|
|
* with the same number of members as @ids
|
2009-11-29 22:45:30 +00:00
|
|
|
*/
|
2012-05-02 19:54:25 +00:00
|
|
|
getResultMetas: function(ids, callback) {
|
2012-02-21 18:53:25 +00:00
|
|
|
throw new Error('Not implemented');
|
|
|
|
},
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
/**
|
|
|
|
* createResultContainer:
|
|
|
|
*
|
|
|
|
* Search providers may optionally override this to render their
|
|
|
|
* results in a custom fashion. The default implementation
|
|
|
|
* will create a vertical list.
|
|
|
|
*
|
|
|
|
* Returns: An instance of SearchResultDisplay.
|
|
|
|
*/
|
|
|
|
createResultContainerActor: function() {
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* createResultActor:
|
|
|
|
* @resultMeta: Object with result metadata
|
|
|
|
* @terms: Array of search terms, should be used for highlighting
|
|
|
|
*
|
|
|
|
* Search providers may optionally override this to render a
|
|
|
|
* particular serch result in a custom fashion. The default
|
|
|
|
* implementation will show the icon next to the name.
|
|
|
|
*
|
|
|
|
* The actor should be an instance of St.Widget, with the style class
|
2010-11-18 09:23:44 +00:00
|
|
|
* 'search-result-content'.
|
2009-11-29 22:45:30 +00:00
|
|
|
*/
|
|
|
|
createResultActor: function(resultMeta, terms) {
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* activateResult:
|
|
|
|
* @id: Result identifier string
|
|
|
|
*
|
|
|
|
* Called when the user chooses a given result.
|
|
|
|
*/
|
|
|
|
activateResult: function(id) {
|
2010-05-13 19:46:04 +00:00
|
|
|
throw new Error('Not implemented');
|
2009-11-29 22:45:30 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2009-11-29 22:45:30 +00:00
|
|
|
Signals.addSignalMethods(SearchProvider.prototype);
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const OpenSearchSystem = new Lang.Class({
|
|
|
|
Name: 'OpenSearchSystem',
|
2011-01-17 21:38:47 +00:00
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
this._providers = [];
|
|
|
|
global.settings.connect('changed::' + DISABLED_OPEN_SEARCH_PROVIDERS_KEY, Lang.bind(this, this._refresh));
|
|
|
|
this._refresh();
|
|
|
|
},
|
|
|
|
|
|
|
|
getProviders: function() {
|
|
|
|
let res = [];
|
|
|
|
for (let i = 0; i < this._providers.length; i++)
|
|
|
|
res.push({ id: i, name: this._providers[i].name });
|
|
|
|
|
|
|
|
return res;
|
|
|
|
},
|
|
|
|
|
|
|
|
setSearchTerms: function(terms) {
|
|
|
|
this._terms = terms;
|
|
|
|
},
|
|
|
|
|
|
|
|
_checkSupportedProviderLanguage: function(provider) {
|
|
|
|
if (provider.url.search(/{language}/) == -1)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
let langs = GLib.get_language_names();
|
|
|
|
|
|
|
|
langs.push('en');
|
|
|
|
let lang = null;
|
|
|
|
for (let i = 0; i < langs.length; i++) {
|
|
|
|
for (let k = 0; k < provider.langs.length; k++) {
|
|
|
|
if (langs[i] == provider.langs[k])
|
|
|
|
lang = langs[i];
|
|
|
|
}
|
|
|
|
if (lang)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
provider.lang = lang;
|
|
|
|
return lang != null;
|
|
|
|
},
|
|
|
|
|
2011-01-30 21:09:58 +00:00
|
|
|
activateResult: function(id, params) {
|
2011-01-17 21:38:47 +00:00
|
|
|
let searchTerms = this._terms.join(' ');
|
|
|
|
|
|
|
|
let url = this._providers[id].url.replace('{searchTerms}', encodeURIComponent(searchTerms));
|
|
|
|
if (url.match('{language}'))
|
|
|
|
url = url.replace('{language}', this._providers[id].lang);
|
|
|
|
|
|
|
|
try {
|
|
|
|
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
|
|
|
|
} catch (e) {
|
|
|
|
// TODO: remove this after glib will be removed from moduleset
|
|
|
|
// In the default jhbuild, gio is in our prefix but gvfs is not
|
2011-01-17 22:05:30 +00:00
|
|
|
Util.spawn(['gvfs-open', url])
|
2011-01-17 21:38:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Main.overview.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
_addProvider: function(fileName) {
|
2012-02-06 13:39:22 +00:00
|
|
|
let path = global.datadir + '/open-search-providers/' + fileName;
|
2011-03-31 20:13:07 +00:00
|
|
|
let source = Shell.get_file_contents_utf8_sync(path);
|
2011-04-27 11:04:58 +00:00
|
|
|
let [success, name, url, langs, icon_uri] = Shell.parse_search_provider(source);
|
2011-03-31 20:13:07 +00:00
|
|
|
let provider ={ name: name,
|
|
|
|
url: url,
|
|
|
|
id: this._providers.length,
|
|
|
|
icon_uri: icon_uri,
|
|
|
|
langs: langs };
|
|
|
|
if (this._checkSupportedProviderLanguage(provider)) {
|
|
|
|
this._providers.push(provider);
|
|
|
|
this.emit('changed');
|
|
|
|
}
|
2011-01-17 21:38:47 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_refresh: function() {
|
|
|
|
this._providers = [];
|
|
|
|
let names = global.settings.get_strv(DISABLED_OPEN_SEARCH_PROVIDERS_KEY);
|
2012-02-06 13:39:22 +00:00
|
|
|
let file = Gio.file_new_for_path(global.datadir + '/open-search-providers');
|
2011-01-17 21:38:47 +00:00
|
|
|
FileUtils.listDirAsync(file, Lang.bind(this, function(files) {
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
let enabled = true;
|
|
|
|
let name = files[i].get_name();
|
|
|
|
for (let k = 0; k < names.length; k++)
|
|
|
|
if (names[k] == name)
|
|
|
|
enabled = false;
|
|
|
|
if (enabled)
|
|
|
|
this._addProvider(name);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2011-01-17 21:38:47 +00:00
|
|
|
Signals.addSignalMethods(OpenSearchSystem.prototype);
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const SearchSystem = new Lang.Class({
|
|
|
|
Name: 'SearchSystem',
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
this._providers = [];
|
|
|
|
this.reset();
|
|
|
|
},
|
|
|
|
|
|
|
|
registerProvider: function (provider) {
|
2011-07-24 10:29:36 +00:00
|
|
|
provider.searchSystem = this;
|
2009-11-29 22:45:30 +00:00
|
|
|
this._providers.push(provider);
|
|
|
|
},
|
|
|
|
|
2011-08-28 11:20:37 +00:00
|
|
|
unregisterProvider: function (provider) {
|
|
|
|
let index = this._providers.indexOf(provider);
|
|
|
|
if (index == -1)
|
|
|
|
return;
|
|
|
|
provider.searchSystem = null;
|
|
|
|
this._providers.splice(index, 1);
|
|
|
|
},
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
getProviders: function() {
|
|
|
|
return this._providers;
|
|
|
|
},
|
|
|
|
|
|
|
|
getTerms: function() {
|
|
|
|
return this._previousTerms;
|
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
this._previousTerms = [];
|
|
|
|
this._previousResults = [];
|
|
|
|
},
|
|
|
|
|
2012-02-21 18:53:25 +00:00
|
|
|
pushResults: function(provider, results) {
|
|
|
|
let i = this._providers.indexOf(provider);
|
|
|
|
if (i == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._previousResults[i] = [provider, results];
|
|
|
|
this.emit('search-updated', this._previousResults[i]);
|
2011-07-24 10:29:36 +00:00
|
|
|
},
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
updateSearch: function(searchString) {
|
2010-05-13 19:46:04 +00:00
|
|
|
searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
2009-11-29 22:45:30 +00:00
|
|
|
if (searchString == '')
|
2011-07-24 10:29:36 +00:00
|
|
|
return;
|
2009-11-29 22:45:30 +00:00
|
|
|
|
|
|
|
let terms = searchString.split(/\s+/);
|
2011-07-24 10:29:36 +00:00
|
|
|
this.updateSearchResults(terms);
|
|
|
|
},
|
|
|
|
|
|
|
|
updateSearchResults: function(terms) {
|
|
|
|
if (!terms)
|
|
|
|
return;
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-02 19:26:54 +00:00
|
|
|
let previousResultsArr = this._previousResults;
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
let results = [];
|
2012-05-02 19:26:54 +00:00
|
|
|
this._previousTerms = terms;
|
|
|
|
this._previousResults = results;
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
if (isSubSearch) {
|
2011-07-24 10:29:36 +00:00
|
|
|
for (let i = 0; i < this._providers.length; i++) {
|
2012-05-02 19:26:54 +00:00
|
|
|
let [provider, previousResults] = previousResultsArr[i];
|
2010-09-21 09:36:24 +00:00
|
|
|
try {
|
2012-05-02 19:54:25 +00:00
|
|
|
results.push([provider, []]);
|
|
|
|
provider.getSubsearchResultSet(previousResults, terms);
|
2010-09-21 09:36:24 +00:00
|
|
|
} catch (error) {
|
2012-05-10 01:37:42 +00:00
|
|
|
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
2010-09-21 09:36:24 +00:00
|
|
|
}
|
2009-11-29 22:45:30 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (let i = 0; i < this._providers.length; i++) {
|
|
|
|
let provider = this._providers[i];
|
2010-09-21 09:36:24 +00:00
|
|
|
try {
|
2012-05-02 19:54:25 +00:00
|
|
|
results.push([provider, []]);
|
|
|
|
provider.getInitialResultSet(terms);
|
2010-09-21 09:36:24 +00:00
|
|
|
} catch (error) {
|
2012-05-10 01:37:42 +00:00
|
|
|
log('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
2010-09-21 09:36:24 +00:00
|
|
|
}
|
2009-11-29 22:45:30 +00:00
|
|
|
}
|
|
|
|
}
|
2011-07-24 10:29:36 +00:00
|
|
|
},
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2009-11-29 22:45:30 +00:00
|
|
|
Signals.addSignalMethods(SearchSystem.prototype);
|