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
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2011-01-17 21:38:47 +00:00
|
|
|
const Lang = imports.lang;
|
2013-10-30 16:38:46 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2013-10-29 19:49:05 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
|
|
|
const Meta = imports.gi.Meta;
|
2009-11-29 22:45:30 +00:00
|
|
|
const Signals = imports.signals;
|
2013-10-29 19:49:05 +00:00
|
|
|
const St = imports.gi.St;
|
|
|
|
const Atk = imports.gi.Atk;
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
2013-10-29 19:49:05 +00:00
|
|
|
const DND = imports.ui.dnd;
|
|
|
|
const IconGrid = imports.ui.iconGrid;
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
const Overview = imports.ui.overview;
|
2013-10-30 16:38:46 +00:00
|
|
|
const RemoteSearch = imports.ui.remoteSearch;
|
2013-10-29 19:49:05 +00:00
|
|
|
const Separator = imports.ui.separator;
|
|
|
|
const Util = imports.misc.util;
|
2011-01-17 21:38:47 +00:00
|
|
|
|
2012-11-01 14:33:40 +00:00
|
|
|
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
const MAX_LIST_SEARCH_RESULTS_ROWS = 3;
|
|
|
|
const MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
|
|
|
|
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 = [];
|
2013-10-30 16:38:46 +00:00
|
|
|
|
|
|
|
this._registerProvider(new AppDisplay.AppSearchProvider());
|
|
|
|
|
2014-06-24 19:17:09 +00:00
|
|
|
this._searchSettings = new Gio.Settings({ schema_id: SEARCH_PROVIDERS_SCHEMA });
|
2014-08-01 11:35:31 +00:00
|
|
|
this._searchSettings.connect('changed::enabled', Lang.bind(this, this._reloadRemoteProviders));
|
2013-10-30 16:38:46 +00:00
|
|
|
this._searchSettings.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders));
|
|
|
|
this._searchSettings.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders));
|
|
|
|
this._searchSettings.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders));
|
|
|
|
|
|
|
|
this._reloadRemoteProviders();
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
|
|
|
|
this._cancellable = new Gio.Cancellable();
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
addProvider: function(provider) {
|
2009-11-29 22:45:30 +00:00
|
|
|
this._providers.push(provider);
|
2013-10-30 16:38:46 +00:00
|
|
|
this.emit('providers-changed');
|
|
|
|
},
|
|
|
|
|
|
|
|
_reloadRemoteProviders: function() {
|
|
|
|
let remoteProviders = this._providers.filter(function(provider) {
|
|
|
|
return provider.isRemoteProvider;
|
|
|
|
});
|
|
|
|
remoteProviders.forEach(Lang.bind(this, function(provider) {
|
|
|
|
this._unregisterProvider(provider);
|
|
|
|
}));
|
|
|
|
|
2013-11-02 22:03:52 +00:00
|
|
|
RemoteSearch.loadRemoteSearchProviders(Lang.bind(this, function(providers) {
|
|
|
|
providers.forEach(Lang.bind(this, this._registerProvider));
|
2013-10-30 16:38:46 +00:00
|
|
|
}));
|
2013-11-02 22:03:52 +00:00
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
this.emit('providers-changed');
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_registerProvider: function (provider) {
|
|
|
|
this._providers.push(provider);
|
|
|
|
},
|
|
|
|
|
|
|
|
_unregisterProvider: function (provider) {
|
2011-08-28 11:20:37 +00:00
|
|
|
let index = this._providers.indexOf(provider);
|
|
|
|
this._providers.splice(index, 1);
|
2014-04-22 03:34:11 +00:00
|
|
|
|
|
|
|
if (provider.display)
|
|
|
|
provider.display.destroy();
|
2011-08-28 11:20:37 +00:00
|
|
|
},
|
|
|
|
|
2009-11-29 22:45:30 +00:00
|
|
|
getProviders: function() {
|
|
|
|
return this._providers;
|
|
|
|
},
|
|
|
|
|
|
|
|
getTerms: function() {
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
return this._terms;
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._terms = [];
|
|
|
|
this._results = {};
|
2009-11-29 22:45:30 +00:00
|
|
|
},
|
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
_gotResults: function(results, provider) {
|
|
|
|
this._results[provider.id] = results;
|
|
|
|
this.emit('search-updated', provider, results);
|
2011-07-24 10:29:36 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
setTerms: function(terms) {
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._cancellable.cancel();
|
|
|
|
this._cancellable.reset();
|
|
|
|
|
|
|
|
let previousResults = this._results;
|
|
|
|
let previousTerms = this._terms;
|
|
|
|
this.reset();
|
|
|
|
|
2011-07-24 10:29:36 +00:00
|
|
|
if (!terms)
|
|
|
|
return;
|
|
|
|
|
2013-02-16 04:10:42 +00:00
|
|
|
let searchString = terms.join(' ');
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
let previousSearchString = previousTerms.join(' ');
|
2013-02-16 04:15:13 +00:00
|
|
|
if (searchString == previousSearchString)
|
|
|
|
return;
|
2013-02-16 04:10:42 +00:00
|
|
|
|
|
|
|
let isSubSearch = false;
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
if (previousTerms.length > 0)
|
2013-02-16 04:10:42 +00:00
|
|
|
isSubSearch = searchString.indexOf(previousSearchString) == 0;
|
2009-11-29 22:45:30 +00:00
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._terms = terms;
|
2012-05-02 19:26:54 +00:00
|
|
|
|
search: Only do a subsearch if the previous results have returned from DBus
There's a potential race condition in the search code: if we have an
outstanding search call to a provider for search "A", and if before it comes
back we do a subsearch for "AB", we won't have any results to pass along.
Previously, we used an empty list when storing the provider results, so we
effectively told the remote search app to filter through this empty list for
any search results that meet the new query, meaning we showed the user 0
results for the provider in this case.
Now that we don't store an empty list, but instead store `undefined`, this race
raises a warning. Solve it by doing an initial search query in this case
instead.
The search code isn't too smart about chained subsearches: now, if we hit this
race while already on a subsearch, we'll do an initial search for the subsearch
query instead, but that is much better than showing the user nothing. This
could be fixed in the future for a performance improvement.
Reviewed-by: Florian Müllner <fmuellner@gnome.org>
2013-11-04 19:47:13 +00:00
|
|
|
this._providers.forEach(Lang.bind(this, function(provider) {
|
|
|
|
let previousProviderResults = previousResults[provider.id];
|
|
|
|
if (isSubSearch && previousProviderResults)
|
2013-11-03 00:09:14 +00:00
|
|
|
provider.getSubsearchResultSet(previousProviderResults, terms, Lang.bind(this, this._gotResults, provider), this._cancellable);
|
search: Only do a subsearch if the previous results have returned from DBus
There's a potential race condition in the search code: if we have an
outstanding search call to a provider for search "A", and if before it comes
back we do a subsearch for "AB", we won't have any results to pass along.
Previously, we used an empty list when storing the provider results, so we
effectively told the remote search app to filter through this empty list for
any search results that meet the new query, meaning we showed the user 0
results for the provider in this case.
Now that we don't store an empty list, but instead store `undefined`, this race
raises a warning. Solve it by doing an initial search query in this case
instead.
The search code isn't too smart about chained subsearches: now, if we hit this
race while already on a subsearch, we'll do an initial search for the subsearch
query instead, but that is much better than showing the user nothing. This
could be fixed in the future for a performance improvement.
Reviewed-by: Florian Müllner <fmuellner@gnome.org>
2013-11-04 19:47:13 +00:00
|
|
|
else
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
provider.getInitialResultSet(terms, Lang.bind(this, this._gotResults, provider), this._cancellable);
|
search: Only do a subsearch if the previous results have returned from DBus
There's a potential race condition in the search code: if we have an
outstanding search call to a provider for search "A", and if before it comes
back we do a subsearch for "AB", we won't have any results to pass along.
Previously, we used an empty list when storing the provider results, so we
effectively told the remote search app to filter through this empty list for
any search results that meet the new query, meaning we showed the user 0
results for the provider in this case.
Now that we don't store an empty list, but instead store `undefined`, this race
raises a warning. Solve it by doing an initial search query in this case
instead.
The search code isn't too smart about chained subsearches: now, if we hit this
race while already on a subsearch, we'll do an initial search for the subsearch
query instead, but that is much better than showing the user nothing. This
could be fixed in the future for a performance improvement.
Reviewed-by: Florian Müllner <fmuellner@gnome.org>
2013-11-04 19:47:13 +00:00
|
|
|
}));
|
2013-02-09 01:48:02 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2009-11-29 22:45:30 +00:00
|
|
|
Signals.addSignalMethods(SearchSystem.prototype);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
const MaxWidthBin = new Lang.Class({
|
|
|
|
Name: 'MaxWidthBin',
|
|
|
|
Extends: St.Bin,
|
|
|
|
|
|
|
|
vfunc_allocate: function(box, flags) {
|
|
|
|
let themeNode = this.get_theme_node();
|
|
|
|
let maxWidth = themeNode.get_max_width();
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let adjustedBox = box;
|
|
|
|
|
|
|
|
if (availWidth > maxWidth) {
|
|
|
|
let excessWidth = availWidth - maxWidth;
|
|
|
|
adjustedBox.x1 += Math.floor(excessWidth / 2);
|
|
|
|
adjustedBox.x2 -= Math.floor(excessWidth / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.parent(adjustedBox, flags);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const SearchResult = new Lang.Class({
|
|
|
|
Name: 'SearchResult',
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
_init: function(provider, metaInfo) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.provider = provider;
|
|
|
|
this.metaInfo = metaInfo;
|
|
|
|
|
|
|
|
this.actor = new St.Button({ reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
track_hover: true,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_fill: true });
|
|
|
|
|
|
|
|
this.actor._delegate = this;
|
2013-10-30 21:42:49 +00:00
|
|
|
this.actor.connect('clicked', Lang.bind(this, this.activate));
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 21:42:49 +00:00
|
|
|
activate: function() {
|
2013-02-09 00:04:24 +00:00
|
|
|
this.emit('activate', this.metaInfo.id);
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
setSelected: function(selected) {
|
|
|
|
if (selected)
|
|
|
|
this.actor.add_style_pseudo_class('selected');
|
|
|
|
else
|
|
|
|
this.actor.remove_style_pseudo_class('selected');
|
|
|
|
}
|
|
|
|
});
|
2013-02-09 00:04:24 +00:00
|
|
|
Signals.addSignalMethods(SearchResult.prototype);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
const ListSearchResult = new Lang.Class({
|
|
|
|
Name: 'ListSearchResult',
|
|
|
|
Extends: SearchResult,
|
|
|
|
|
|
|
|
ICON_SIZE: 64,
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
_init: function(provider, metaInfo) {
|
|
|
|
this.parent(provider, metaInfo);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this.actor.style_class = 'list-search-result';
|
|
|
|
this.actor.x_fill = true;
|
|
|
|
|
|
|
|
let content = new St.BoxLayout({ style_class: 'list-search-result-content',
|
|
|
|
vertical: false });
|
|
|
|
this.actor.set_child(content);
|
|
|
|
|
|
|
|
// An icon for, or thumbnail of, content
|
|
|
|
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
|
|
|
if (icon) {
|
|
|
|
content.add(icon);
|
|
|
|
}
|
|
|
|
|
|
|
|
let details = new St.BoxLayout({ vertical: true });
|
|
|
|
content.add(details, { x_fill: true,
|
|
|
|
y_fill: false,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.MIDDLE });
|
|
|
|
|
|
|
|
let title = new St.Label({ style_class: 'list-search-result-title',
|
|
|
|
text: this.metaInfo['name'] })
|
|
|
|
details.add(title, { x_fill: false,
|
|
|
|
y_fill: false,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.START });
|
|
|
|
this.actor.label_actor = title;
|
|
|
|
|
|
|
|
if (this.metaInfo['description']) {
|
|
|
|
let description = new St.Label({ style_class: 'list-search-result-description' });
|
|
|
|
description.clutter_text.set_markup(this.metaInfo['description']);
|
|
|
|
details.add(description, { x_fill: false,
|
|
|
|
y_fill: false,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.END });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const GridSearchResult = new Lang.Class({
|
|
|
|
Name: 'GridSearchResult',
|
|
|
|
Extends: SearchResult,
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
_init: function(provider, metaInfo) {
|
|
|
|
this.parent(provider, metaInfo);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this.actor.style_class = 'grid-search-result';
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
let content = provider.createResultObject(metaInfo);
|
2013-10-29 19:49:05 +00:00
|
|
|
let dragSource = null;
|
|
|
|
|
|
|
|
if (content == null) {
|
|
|
|
let actor = new St.Bin();
|
|
|
|
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
|
|
|
{ createIcon: this.metaInfo['createIcon'] });
|
|
|
|
actor.set_child(icon.actor);
|
|
|
|
actor.label_actor = icon.label;
|
|
|
|
dragSource = icon.icon;
|
|
|
|
content = { actor: actor, icon: icon };
|
|
|
|
} else {
|
2014-08-19 16:57:36 +00:00
|
|
|
if (content.getDragActorSource)
|
|
|
|
dragSource = content.getDragActorSource();
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.actor.set_child(content.actor);
|
|
|
|
this.actor.label_actor = content.actor.label_actor;
|
|
|
|
this.icon = content.icon;
|
|
|
|
|
|
|
|
let draggable = DND.makeDraggable(this.actor);
|
|
|
|
draggable.connect('drag-begin',
|
|
|
|
Lang.bind(this, function() {
|
|
|
|
Main.overview.beginItemDrag(this);
|
|
|
|
}));
|
|
|
|
draggable.connect('drag-cancelled',
|
|
|
|
Lang.bind(this, function() {
|
|
|
|
Main.overview.cancelledItemDrag(this);
|
|
|
|
}));
|
|
|
|
draggable.connect('drag-end',
|
|
|
|
Lang.bind(this, function() {
|
|
|
|
Main.overview.endItemDrag(this);
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (!dragSource)
|
|
|
|
// not exactly right, but alignment problems are hard to notice
|
|
|
|
dragSource = content;
|
|
|
|
this._dragActorSource = dragSource;
|
|
|
|
},
|
|
|
|
|
|
|
|
getDragActorSource: function() {
|
|
|
|
return this._dragActorSource;
|
|
|
|
},
|
|
|
|
|
|
|
|
getDragActor: function() {
|
|
|
|
return this.metaInfo['createIcon'](Main.overview.dashIconSize);
|
|
|
|
},
|
|
|
|
|
|
|
|
shellWorkspaceLaunch: function(params) {
|
|
|
|
if (this.provider.dragActivateResult)
|
|
|
|
this.provider.dragActivateResult(this.metaInfo.id, params);
|
|
|
|
else
|
|
|
|
this.provider.activateResult(this.metaInfo.id, this.terms);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const SearchResultsBase = new Lang.Class({
|
|
|
|
Name: 'SearchResultsBase',
|
|
|
|
|
|
|
|
_init: function(provider) {
|
|
|
|
this.provider = provider;
|
|
|
|
|
|
|
|
this._terms = [];
|
|
|
|
|
|
|
|
this.actor = new St.BoxLayout({ style_class: 'search-section',
|
|
|
|
vertical: true });
|
|
|
|
|
|
|
|
this._resultDisplayBin = new St.Bin({ x_fill: true,
|
|
|
|
y_fill: true });
|
|
|
|
this.actor.add(this._resultDisplayBin, { expand: true });
|
|
|
|
|
|
|
|
let separator = new Separator.HorizontalSeparator({ style_class: 'search-section-separator' });
|
|
|
|
this.actor.add(separator.actor);
|
2013-02-08 23:59:15 +00:00
|
|
|
|
|
|
|
this._resultDisplays = {};
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
|
|
|
|
this._cancellable = new Gio.Cancellable();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
this._terms = [];
|
|
|
|
},
|
|
|
|
|
|
|
|
_clearResultDisplay: function() {
|
|
|
|
},
|
|
|
|
|
|
|
|
clear: function() {
|
2014-02-23 15:18:46 +00:00
|
|
|
for (let resultId in this._resultDisplays)
|
|
|
|
this._resultDisplays[resultId].actor.destroy();
|
2013-02-08 23:59:15 +00:00
|
|
|
this._resultDisplays = {};
|
2013-10-29 19:49:05 +00:00
|
|
|
this._clearResultDisplay();
|
|
|
|
this.actor.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
_keyFocusIn: function(actor) {
|
|
|
|
this.emit('key-focus-in', actor);
|
|
|
|
},
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
_activateResult: function(result, id) {
|
2013-10-31 15:59:23 +00:00
|
|
|
this.provider.activateResult(id, this._terms);
|
|
|
|
Main.overview.toggle();
|
2013-02-09 00:04:24 +00:00
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
_setMoreIconVisible: function(visible) {
|
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_ensureResultActors: function(results, callback) {
|
|
|
|
let metasNeeded = results.filter(Lang.bind(this, function(resultId) {
|
|
|
|
return this._resultDisplays[resultId] === undefined;
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (metasNeeded.length === 0) {
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(true);
|
2013-02-08 23:59:15 +00:00
|
|
|
} else {
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._cancellable.cancel();
|
|
|
|
this._cancellable.reset();
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
this.provider.getResultMetas(metasNeeded, Lang.bind(this, function(metas) {
|
2014-02-24 16:24:42 +00:00
|
|
|
if (metas.length == 0) {
|
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (metas.length != metasNeeded.length) {
|
|
|
|
log('Wrong number of result metas returned by search provider');
|
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
metasNeeded.forEach(Lang.bind(this, function(resultId, i) {
|
|
|
|
let meta = metas[i];
|
|
|
|
let display = this._createResultDisplay(meta);
|
|
|
|
display.connect('activate', Lang.bind(this, this._activateResult));
|
|
|
|
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this._resultDisplays[resultId] = display;
|
|
|
|
}));
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(true);
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
}), this._cancellable);
|
2013-02-08 23:59:15 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
updateSearch: function(providerResults, terms, callback) {
|
|
|
|
this._terms = terms;
|
|
|
|
|
|
|
|
if (providerResults.length == 0) {
|
|
|
|
this._clearResultDisplay();
|
|
|
|
this.actor.hide();
|
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
let maxResults = this._getMaxDisplayedResults();
|
|
|
|
let results = this.provider.filterResults(providerResults, maxResults);
|
|
|
|
let hasMoreResults = results.length < providerResults.length;
|
|
|
|
|
2014-02-24 16:24:42 +00:00
|
|
|
this._ensureResultActors(results, Lang.bind(this, function(successful) {
|
2013-02-08 23:59:15 +00:00
|
|
|
this._clearResultDisplay();
|
2014-02-24 16:24:42 +00:00
|
|
|
if (!successful)
|
|
|
|
return;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
// To avoid CSS transitions causing flickering when
|
|
|
|
// the first search result stays the same, we hide the
|
|
|
|
// content while filling in the results.
|
|
|
|
this.actor.hide();
|
|
|
|
this._clearResultDisplay();
|
2013-02-08 23:59:15 +00:00
|
|
|
results.forEach(Lang.bind(this, function(resultId) {
|
|
|
|
this._addItem(this._resultDisplays[resultId]);
|
|
|
|
}));
|
2013-10-29 19:49:05 +00:00
|
|
|
this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch);
|
|
|
|
this.actor.show();
|
|
|
|
callback();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const ListSearchResults = new Lang.Class({
|
|
|
|
Name: 'ListSearchResults',
|
|
|
|
Extends: SearchResultsBase,
|
|
|
|
|
|
|
|
_init: function(provider) {
|
|
|
|
this.parent(provider);
|
|
|
|
|
|
|
|
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
|
|
|
|
this.providerIcon = new ProviderIcon(provider);
|
|
|
|
this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this.providerIcon.connect('clicked', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
provider.launchSearch(this._terms);
|
|
|
|
Main.overview.toggle();
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._container.add(this.providerIcon, { x_fill: false,
|
|
|
|
y_fill: false,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.START });
|
|
|
|
|
|
|
|
this._content = new St.BoxLayout({ style_class: 'list-search-results',
|
|
|
|
vertical: true });
|
|
|
|
this._container.add(this._content, { expand: true });
|
|
|
|
|
|
|
|
this._resultDisplayBin.set_child(this._container);
|
|
|
|
},
|
|
|
|
|
|
|
|
_setMoreIconVisible: function(visible) {
|
2014-08-19 16:07:38 +00:00
|
|
|
this.providerIcon.moreIcon.visible = visible;
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_getMaxDisplayedResults: function() {
|
|
|
|
return MAX_LIST_SEARCH_RESULTS_ROWS;
|
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_clearResultDisplay: function () {
|
|
|
|
this._content.remove_all_children();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_createResultDisplay: function(meta) {
|
|
|
|
return new ListSearchResult(this.provider, meta);
|
|
|
|
},
|
|
|
|
|
|
|
|
_addItem: function(display) {
|
|
|
|
this._content.add_actor(display.actor);
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getFirstResult: function() {
|
|
|
|
if (this._content.get_n_children() > 0)
|
|
|
|
return this._content.get_child_at_index(0)._delegate;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(ListSearchResults.prototype);
|
|
|
|
|
|
|
|
const GridSearchResults = new Lang.Class({
|
|
|
|
Name: 'GridSearchResults',
|
|
|
|
Extends: SearchResultsBase,
|
|
|
|
|
2014-07-15 15:09:41 +00:00
|
|
|
_init: function(provider, parentContainer) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.parent(provider);
|
2014-07-15 15:09:41 +00:00
|
|
|
// We need to use the parent container to know how much results we can show.
|
|
|
|
// None of the actors in this class can be used for that, since the main actor
|
|
|
|
// goes hidden when no results are displayed, and then it lost its allocation.
|
|
|
|
// Then on the next use of _getMaxDisplayedResults allocation is 0, en therefore
|
|
|
|
// it doesn't show any result although we have some.
|
|
|
|
this._parentContainer = parentContainer;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
|
|
|
xAlign: St.Align.START });
|
|
|
|
this._bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
|
|
|
this._bin.set_child(this._grid.actor);
|
|
|
|
|
|
|
|
this._resultDisplayBin.set_child(this._bin);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getMaxDisplayedResults: function() {
|
2014-07-15 15:09:41 +00:00
|
|
|
let parentThemeNode = this._parentContainer.get_theme_node();
|
|
|
|
let availableWidth = parentThemeNode.adjust_for_width(this._parentContainer.width);
|
|
|
|
return this._grid.columnsForWidth(availableWidth) * this._grid.getRowLimit();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_renderResults: function(metas) {
|
|
|
|
for (let i = 0; i < metas.length; i++) {
|
2013-02-09 00:04:24 +00:00
|
|
|
let display = new GridSearchResult(this.provider, metas[i]);
|
|
|
|
display.connect('activate', Lang.bind(this, this._activateResult));
|
2013-10-29 19:49:05 +00:00
|
|
|
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this._grid.addItem(display);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_clearResultDisplay: function () {
|
|
|
|
this._grid.removeAll();
|
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_createResultDisplay: function(meta) {
|
|
|
|
return new GridSearchResult(this.provider, meta);
|
|
|
|
},
|
|
|
|
|
|
|
|
_addItem: function(display) {
|
2013-10-30 17:06:56 +00:00
|
|
|
this._grid.addItem(display);
|
2013-02-08 23:59:15 +00:00
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
getFirstResult: function() {
|
|
|
|
if (this._grid.visibleItemsCount() > 0)
|
|
|
|
return this._grid.getItemAtIndex(0)._delegate;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(GridSearchResults.prototype);
|
|
|
|
|
|
|
|
const SearchResults = new Lang.Class({
|
|
|
|
Name: 'SearchResults',
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_init: function() {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.actor = new St.BoxLayout({ name: 'searchResults',
|
|
|
|
vertical: true });
|
|
|
|
|
|
|
|
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
|
|
|
vertical: true });
|
|
|
|
this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true,
|
|
|
|
child: this._content });
|
|
|
|
|
|
|
|
let scrollChild = new St.BoxLayout();
|
|
|
|
scrollChild.add(this._contentBin, { expand: true });
|
|
|
|
|
|
|
|
this._scrollView = new St.ScrollView({ x_fill: true,
|
|
|
|
y_fill: false,
|
|
|
|
overlay_scrollbars: true,
|
|
|
|
style_class: 'search-display vfade' });
|
|
|
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
|
|
this._scrollView.add_actor(scrollChild);
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
|
|
|
action.connect('pan', Lang.bind(this, this._onPan));
|
|
|
|
this._scrollView.add_action(action);
|
|
|
|
|
|
|
|
this.actor.add(this._scrollView, { x_fill: true,
|
|
|
|
y_fill: true,
|
|
|
|
expand: true,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.START });
|
|
|
|
|
|
|
|
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
|
|
|
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
|
|
|
y_align: St.Align.MIDDLE });
|
|
|
|
this._content.add(this._statusBin, { expand: true });
|
|
|
|
this._statusBin.add_actor(this._statusText);
|
|
|
|
|
|
|
|
this._highlightDefault = false;
|
|
|
|
this._defaultResult = null;
|
2013-10-30 16:38:46 +00:00
|
|
|
|
|
|
|
this._searchSystem = new SearchSystem();
|
|
|
|
this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults));
|
|
|
|
this._searchSystem.connect('providers-changed', Lang.bind(this, this._updateProviderDisplays));
|
|
|
|
this._updateProviderDisplays();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onPan: function(action) {
|
|
|
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
|
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
|
|
|
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_keyFocusIn: function(provider, actor) {
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
|
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_ensureProviderDisplay: function(provider) {
|
|
|
|
if (provider.display)
|
|
|
|
return;
|
|
|
|
|
2013-10-29 20:13:32 +00:00
|
|
|
let providerDisplay;
|
|
|
|
if (provider.appInfo)
|
2013-10-29 19:49:05 +00:00
|
|
|
providerDisplay = new ListSearchResults(provider);
|
2013-10-29 20:13:32 +00:00
|
|
|
else
|
2014-07-15 15:09:41 +00:00
|
|
|
providerDisplay = new GridSearchResults(provider, this._content);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this._content.add(providerDisplay.actor);
|
2013-10-29 20:13:32 +00:00
|
|
|
provider.display = providerDisplay;
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_updateProviderDisplays: function() {
|
|
|
|
this._searchSystem.getProviders().forEach(Lang.bind(this, this._ensureProviderDisplay));
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_clearDisplay: function() {
|
2013-10-29 20:13:32 +00:00
|
|
|
this._searchSystem.getProviders().forEach(function(provider) {
|
|
|
|
provider.display.clear();
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
this._searchSystem.reset();
|
|
|
|
this._statusBin.hide();
|
|
|
|
this._clearDisplay();
|
|
|
|
this._defaultResult = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
startingSearch: function() {
|
|
|
|
this.reset();
|
|
|
|
this._statusText.set_text(_("Searching…"));
|
|
|
|
this._statusBin.show();
|
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
setTerms: function(terms) {
|
|
|
|
this._searchSystem.setTerms(terms);
|
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
_maybeSetInitialSelection: function() {
|
|
|
|
let newDefaultResult = null;
|
|
|
|
|
2013-10-29 20:13:32 +00:00
|
|
|
let providers = this._searchSystem.getProviders();
|
|
|
|
for (let i = 0; i < providers.length; i++) {
|
|
|
|
let provider = providers[i];
|
|
|
|
let display = provider.display;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
if (!display.actor.visible)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
let firstResult = display.getFirstResult();
|
|
|
|
if (firstResult) {
|
|
|
|
newDefaultResult = firstResult;
|
|
|
|
break; // select this one!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newDefaultResult != this._defaultResult) {
|
|
|
|
if (this._defaultResult)
|
|
|
|
this._defaultResult.setSelected(false);
|
2014-03-20 10:27:05 +00:00
|
|
|
if (newDefaultResult) {
|
2013-10-29 19:49:05 +00:00
|
|
|
newDefaultResult.setSelected(this._highlightDefault);
|
2014-03-20 10:27:05 +00:00
|
|
|
if (this._highlightDefault)
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, newDefaultResult.actor);
|
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._defaultResult = newDefaultResult;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateStatusText: function () {
|
2013-10-29 20:13:32 +00:00
|
|
|
let haveResults = this._searchSystem.getProviders().some(function(provider) {
|
|
|
|
let display = provider.display;
|
|
|
|
return (display.getFirstResult() != null);
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
if (!haveResults) {
|
|
|
|
this._statusText.set_text(_("No results."));
|
|
|
|
this._statusBin.show();
|
|
|
|
} else {
|
|
|
|
this._statusBin.hide();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
_updateResults: function(searchSystem, provider, results) {
|
2013-10-29 19:49:05 +00:00
|
|
|
let terms = searchSystem.getTerms();
|
2013-10-29 20:13:32 +00:00
|
|
|
let display = provider.display;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
display.updateSearch(results, terms, Lang.bind(this, function() {
|
2013-10-29 19:49:05 +00:00
|
|
|
this._maybeSetInitialSelection();
|
|
|
|
this._updateStatusText();
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
activateDefault: function() {
|
|
|
|
if (this._defaultResult)
|
|
|
|
this._defaultResult.activate();
|
|
|
|
},
|
|
|
|
|
|
|
|
highlightDefault: function(highlight) {
|
|
|
|
this._highlightDefault = highlight;
|
2014-03-20 10:27:05 +00:00
|
|
|
if (this._defaultResult) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this._defaultResult.setSelected(highlight);
|
2014-03-20 10:27:05 +00:00
|
|
|
if (highlight)
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, this._defaultResult.actor);
|
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
navigateFocus: function(direction) {
|
|
|
|
let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL;
|
|
|
|
if (direction == Gtk.DirectionType.TAB_BACKWARD ||
|
|
|
|
direction == (rtl ? Gtk.DirectionType.RIGHT
|
|
|
|
: Gtk.DirectionType.LEFT) ||
|
|
|
|
direction == Gtk.DirectionType.UP) {
|
|
|
|
this.actor.navigate_focus(null, direction, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let from = this._defaultResult ? this._defaultResult.actor : null;
|
|
|
|
this.actor.navigate_focus(from, direction, false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const ProviderIcon = new Lang.Class({
|
|
|
|
Name: 'ProviderIcon',
|
|
|
|
Extends: St.Button,
|
|
|
|
|
|
|
|
PROVIDER_ICON_SIZE: 48,
|
|
|
|
|
|
|
|
_init: function(provider) {
|
|
|
|
this.provider = provider;
|
|
|
|
this.parent({ style_class: 'search-provider-icon',
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
accessible_name: provider.appInfo.get_name(),
|
|
|
|
track_hover: true });
|
|
|
|
|
|
|
|
this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
|
|
|
this.set_child(this._content);
|
|
|
|
|
|
|
|
let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL);
|
|
|
|
|
|
|
|
this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more',
|
|
|
|
visible: false,
|
|
|
|
x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END,
|
|
|
|
y_align: Clutter.ActorAlign.END,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true });
|
|
|
|
|
|
|
|
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
|
|
|
gicon: provider.appInfo.get_icon() });
|
|
|
|
this._content.add_actor(icon);
|
|
|
|
this._content.add_actor(this.moreIcon);
|
|
|
|
}
|
|
|
|
});
|