2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-10-17 12:27:10 +00:00
|
|
|
/* exported SearchResultsView */
|
2009-11-29 22:45:30 +00:00
|
|
|
|
2019-08-07 15:39:25 +00:00
|
|
|
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
2013-10-29 19:49:05 +00:00
|
|
|
const IconGrid = imports.ui.iconGrid;
|
|
|
|
const Main = imports.ui.main;
|
2020-03-30 11:40:11 +00:00
|
|
|
const ParentalControlsManager = imports.misc.parentalControlsManager;
|
2013-10-30 16:38:46 +00:00
|
|
|
const RemoteSearch = imports.ui.remoteSearch;
|
2013-10-29 19:49:05 +00:00
|
|
|
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';
|
|
|
|
|
2017-07-18 17:47:27 +00:00
|
|
|
var MAX_LIST_SEARCH_RESULTS_ROWS = 5;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2018-11-24 21:33:05 +00:00
|
|
|
var MaxWidthBox = GObject.registerClass(
|
|
|
|
class MaxWidthBox extends St.BoxLayout {
|
2020-05-09 19:30:26 +00:00
|
|
|
vfunc_allocate(box) {
|
2013-10-29 19:49:05 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-05-09 19:30:26 +00:00
|
|
|
super.vfunc_allocate(adjustedBox);
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-10-17 12:27:10 +00:00
|
|
|
var SearchResult = GObject.registerClass(
|
|
|
|
class SearchResult extends St.Button {
|
2019-07-16 09:24:13 +00:00
|
|
|
_init(provider, metaInfo, resultsView) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.provider = provider;
|
|
|
|
this.metaInfo = metaInfo;
|
2017-06-27 21:58:07 +00:00
|
|
|
this._resultsView = resultsView;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
super._init({
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
track_hover: true,
|
|
|
|
});
|
2019-09-10 05:42:48 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-09-10 05:42:48 +00:00
|
|
|
vfunc_clicked() {
|
|
|
|
this.activate();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
activate() {
|
2019-10-16 00:23:22 +00:00
|
|
|
this.provider.activateResult(this.metaInfo.id, this._resultsView.terms);
|
|
|
|
|
2019-08-20 00:51:42 +00:00
|
|
|
if (this.metaInfo.clipboardText) {
|
2019-10-16 00:23:22 +00:00
|
|
|
St.Clipboard.get_default().set_text(
|
|
|
|
St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText);
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2019-10-16 00:23:22 +00:00
|
|
|
Main.overview.toggle();
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var ListSearchResult = GObject.registerClass(
|
|
|
|
class ListSearchResult extends SearchResult {
|
|
|
|
_init(provider, metaInfo, resultsView) {
|
|
|
|
super._init(provider, metaInfo, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.style_class = 'list-search-result';
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
let content = new St.BoxLayout({
|
|
|
|
style_class: 'list-search-result-content',
|
|
|
|
vertical: false,
|
2019-10-17 21:40:24 +00:00
|
|
|
x_align: Clutter.ActorAlign.START,
|
2019-10-21 18:44:00 +00:00
|
|
|
x_expand: true,
|
2019-10-17 21:40:24 +00:00
|
|
|
y_expand: true,
|
2019-10-21 18:44:00 +00:00
|
|
|
});
|
2019-07-16 09:24:13 +00:00
|
|
|
this.set_child(content);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-06-27 22:01:13 +00:00
|
|
|
this._termsChangedId = 0;
|
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
let titleBox = new St.BoxLayout({
|
|
|
|
style_class: 'list-search-result-title',
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
2017-06-20 19:13:48 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
content.add_child(titleBox);
|
2017-06-20 19:13:48 +00:00
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
// An icon for, or thumbnail of, content
|
|
|
|
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
2019-08-20 00:51:42 +00:00
|
|
|
if (icon)
|
2017-06-20 19:13:48 +00:00
|
|
|
titleBox.add(icon);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
let title = new St.Label({
|
|
|
|
text: this.metaInfo['name'],
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
titleBox.add_child(title);
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.label_actor = title;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
if (this.metaInfo['description']) {
|
2019-10-21 18:44:00 +00:00
|
|
|
this._descriptionLabel = new St.Label({
|
|
|
|
style_class: 'list-search-result-description',
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
content.add_child(this._descriptionLabel);
|
2017-06-20 19:13:48 +00:00
|
|
|
|
2017-06-27 22:01:13 +00:00
|
|
|
this._termsChangedId =
|
|
|
|
this._resultsView.connect('terms-changed',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._highlightTerms.bind(this));
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
this._highlightTerms();
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get ICON_SIZE() {
|
|
|
|
return 24;
|
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_highlightTerms() {
|
2017-07-09 16:25:49 +00:00
|
|
|
let markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]);
|
2017-06-27 22:01:13 +00:00
|
|
|
this._descriptionLabel.clutter_text.set_markup(markup);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2017-06-27 22:01:13 +00:00
|
|
|
if (this._termsChangedId)
|
|
|
|
this._resultsView.disconnect(this._termsChangedId);
|
|
|
|
this._termsChangedId = 0;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var GridSearchResult = GObject.registerClass(
|
|
|
|
class GridSearchResult extends SearchResult {
|
|
|
|
_init(provider, metaInfo, resultsView) {
|
|
|
|
super._init(provider, metaInfo, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.style_class = 'grid-search-result';
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2014-08-20 16:39:02 +00:00
|
|
|
this.icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
|
|
|
{ createIcon: this.metaInfo['createIcon'] });
|
2019-10-17 21:40:24 +00:00
|
|
|
let content = new St.Bin({
|
|
|
|
child: this.icon,
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
});
|
2019-07-16 09:24:13 +00:00
|
|
|
this.set_child(content);
|
|
|
|
this.label_actor = this.icon.label;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var SearchResultsBase = GObject.registerClass({
|
|
|
|
GTypeFlags: GObject.TypeFlags.ABSTRACT,
|
|
|
|
Properties: {
|
|
|
|
'focus-child': GObject.ParamSpec.object(
|
|
|
|
'focus-child', 'focus-child', 'focus-child',
|
|
|
|
GObject.ParamFlags.READABLE,
|
|
|
|
Clutter.Actor.$gtype),
|
2019-08-20 21:43:54 +00:00
|
|
|
},
|
2019-07-16 09:24:13 +00:00
|
|
|
}, class SearchResultsBase extends St.BoxLayout {
|
|
|
|
_init(provider, resultsView) {
|
|
|
|
super._init({ style_class: 'search-section', vertical: true });
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this.provider = provider;
|
2017-06-27 21:58:07 +00:00
|
|
|
this._resultsView = resultsView;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._terms = [];
|
2019-08-28 20:06:14 +00:00
|
|
|
this._focusChild = null;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-17 21:40:24 +00:00
|
|
|
this._resultDisplayBin = new St.Bin();
|
2019-10-21 18:44:00 +00:00
|
|
|
this.add_child(this._resultDisplayBin);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-07-13 18:34:47 +00:00
|
|
|
let separator = new St.Widget({ style_class: 'search-section-separator' });
|
2019-07-16 09:24:13 +00:00
|
|
|
this.add(separator);
|
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();
|
2019-08-31 21:40:52 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2019-08-31 21:40:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_onDestroy() {
|
2013-10-29 19:49:05 +00:00
|
|
|
this._terms = [];
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createResultDisplay(meta) {
|
2014-08-20 16:39:02 +00:00
|
|
|
if (this.provider.createResultObject)
|
2017-06-27 21:58:07 +00:00
|
|
|
return this.provider.createResultObject(meta, this._resultsView);
|
2014-08-20 16:39:02 +00:00
|
|
|
|
|
|
|
return null;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
clear() {
|
2018-08-23 16:14:38 +00:00
|
|
|
this._cancellable.cancel();
|
2014-02-23 15:18:46 +00:00
|
|
|
for (let resultId in this._resultDisplays)
|
2019-07-16 09:24:13 +00:00
|
|
|
this._resultDisplays[resultId].destroy();
|
2013-02-08 23:59:15 +00:00
|
|
|
this._resultDisplays = {};
|
2013-10-29 19:49:05 +00:00
|
|
|
this._clearResultDisplay();
|
2019-07-16 09:24:13 +00:00
|
|
|
this.hide();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-08-28 20:06:14 +00:00
|
|
|
get focusChild() {
|
|
|
|
return this._focusChild;
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyFocusIn(actor) {
|
2019-08-28 20:06:14 +00:00
|
|
|
if (this._focusChild == actor)
|
|
|
|
return;
|
|
|
|
this._focusChild = actor;
|
2019-07-16 09:24:13 +00:00
|
|
|
this.notify('focus-child');
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-01-31 14:08:10 +00:00
|
|
|
_setMoreCount(_count) {
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_ensureResultActors(results, callback) {
|
2017-10-31 00:38:18 +00:00
|
|
|
let metasNeeded = results.filter(
|
2020-04-03 23:52:29 +00:00
|
|
|
resultId => this._resultDisplays[resultId] === undefined);
|
2013-02-08 23:59:15 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
this.provider.getResultMetas(metasNeeded, metas => {
|
2018-08-30 05:11:24 +00:00
|
|
|
if (this._cancellable.is_cancelled()) {
|
|
|
|
if (metas.length > 0)
|
2020-02-14 15:10:34 +00:00
|
|
|
log('Search provider %s returned results after the request was canceled'.format(this.provider.id));
|
2018-08-30 05:11:24 +00:00
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 16:24:42 +00:00
|
|
|
if (metas.length != metasNeeded.length) {
|
2020-02-14 15:10:34 +00:00
|
|
|
log('Wrong number of result metas returned by search provider %s: '.format(this.provider.id) +
|
|
|
|
'expected %d but got %d'.format(metasNeeded.length, metas.length));
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2017-10-31 00:38:18 +00:00
|
|
|
if (metas.some(meta => !meta.name || !meta.id)) {
|
2020-02-14 15:10:34 +00:00
|
|
|
log('Invalid result meta returned from search provider %s'.format(this.provider.id));
|
2015-03-08 23:20:10 +00:00
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 16:24:42 +00:00
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
metasNeeded.forEach((resultId, i) => {
|
2013-02-08 23:59:15 +00:00
|
|
|
let meta = metas[i];
|
|
|
|
let display = this._createResultDisplay(meta);
|
2019-07-16 09:24:13 +00:00
|
|
|
display.connect('key-focus-in', this._keyFocusIn.bind(this));
|
2013-02-08 23:59:15 +00:00
|
|
|
this._resultDisplays[resultId] = display;
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(true);
|
2017-10-31 00:38:18 +00:00
|
|
|
}, this._cancellable);
|
2013-02-08 23:59:15 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-02-08 23:59:15 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
updateSearch(providerResults, terms, callback) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this._terms = terms;
|
|
|
|
if (providerResults.length == 0) {
|
|
|
|
this._clearResultDisplay();
|
2019-07-16 09:24:13 +00:00
|
|
|
this.hide();
|
2013-10-29 19:49:05 +00:00
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
let maxResults = this._getMaxDisplayedResults();
|
2019-08-07 14:42:17 +00:00
|
|
|
let results = maxResults > -1
|
|
|
|
? this.provider.filterResults(providerResults, maxResults)
|
|
|
|
: providerResults;
|
2017-07-12 23:51:59 +00:00
|
|
|
let moreCount = Math.max(providerResults.length - results.length, 0);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
this._ensureResultActors(results, successful => {
|
2014-09-11 21:36:40 +00:00
|
|
|
if (!successful) {
|
|
|
|
this._clearResultDisplay();
|
2015-01-07 09:53:30 +00:00
|
|
|
callback();
|
2014-02-24 16:24:42 +00:00
|
|
|
return;
|
2014-09-11 21:36:40 +00:00
|
|
|
}
|
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.
|
2019-07-16 09:24:13 +00:00
|
|
|
this.hide();
|
2013-10-29 19:49:05 +00:00
|
|
|
this._clearResultDisplay();
|
2017-10-31 00:38:18 +00:00
|
|
|
results.forEach(resultId => {
|
2013-02-08 23:59:15 +00:00
|
|
|
this._addItem(this._resultDisplays[resultId]);
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2017-07-12 23:51:59 +00:00
|
|
|
this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
|
2019-07-16 09:24:13 +00:00
|
|
|
this.show();
|
2013-10-29 19:49:05 +00:00
|
|
|
callback();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var ListSearchResults = GObject.registerClass(
|
|
|
|
class ListSearchResults extends SearchResultsBase {
|
|
|
|
_init(provider, resultsView) {
|
|
|
|
super._init(provider, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
|
2017-07-12 23:51:59 +00:00
|
|
|
this.providerInfo = new ProviderInfo(provider);
|
2017-12-02 00:27:35 +00:00
|
|
|
this.providerInfo.connect('key-focus-in', this._keyFocusIn.bind(this));
|
2017-10-31 00:38:18 +00:00
|
|
|
this.providerInfo.connect('clicked', () => {
|
|
|
|
this.providerInfo.animateLaunch();
|
|
|
|
provider.launchSearch(this._terms);
|
|
|
|
Main.overview.toggle();
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this._container.add_child(this.providerInfo);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this._content = new St.BoxLayout({
|
|
|
|
style_class: 'list-search-results',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
|
|
|
});
|
|
|
|
this._container.add_child(this._content);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._resultDisplayBin.set_child(this._container);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_setMoreCount(count) {
|
2017-07-12 23:51:59 +00:00
|
|
|
this.providerInfo.setMoreCount(count);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_getMaxDisplayedResults() {
|
2013-10-29 19:49:05 +00:00
|
|
|
return MAX_LIST_SEARCH_RESULTS_ROWS;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_clearResultDisplay() {
|
2013-02-08 23:59:15 +00:00
|
|
|
this._content.remove_all_children();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createResultDisplay(meta) {
|
2019-07-09 15:17:06 +00:00
|
|
|
return super._createResultDisplay(meta) ||
|
2017-06-27 21:58:07 +00:00
|
|
|
new ListSearchResult(this.provider, meta, this._resultsView);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-02-08 23:59:15 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addItem(display) {
|
2019-07-16 09:24:13 +00:00
|
|
|
this._content.add_actor(display);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
getFirstResult() {
|
2013-10-29 19:49:05 +00:00
|
|
|
if (this._content.get_n_children() > 0)
|
2019-07-16 09:24:13 +00:00
|
|
|
return this._content.get_child_at_index(0);
|
2013-10-29 19:49:05 +00:00
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2020-05-18 16:37:03 +00:00
|
|
|
var GridSearchResultsLayout = GObject.registerClass({
|
|
|
|
Properties: {
|
|
|
|
'spacing': GObject.ParamSpec.int('spacing', 'Spacing', 'Spacing',
|
|
|
|
GObject.ParamFlags.READWRITE, 0, GLib.MAXINT32, 0),
|
|
|
|
},
|
|
|
|
}, class GridSearchResultsLayout extends Clutter.LayoutManager {
|
|
|
|
_init() {
|
|
|
|
super._init();
|
|
|
|
this._spacing = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_set_container(container) {
|
|
|
|
this._container = container;
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_get_preferred_width(container, forHeight) {
|
|
|
|
let minWidth = 0;
|
|
|
|
let natWidth = 0;
|
|
|
|
let first = true;
|
|
|
|
|
|
|
|
for (let child of container) {
|
|
|
|
if (!child.visible)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const [childMinWidth, childNatWidth] = child.get_preferred_width(forHeight);
|
|
|
|
|
|
|
|
minWidth = Math.max(minWidth, childMinWidth);
|
|
|
|
natWidth += childNatWidth;
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
natWidth += this._spacing;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [minWidth, natWidth];
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_get_preferred_height(container, forWidth) {
|
|
|
|
let minHeight = 0;
|
|
|
|
let natHeight = 0;
|
|
|
|
|
|
|
|
for (let child of container) {
|
|
|
|
if (!child.visible)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const [childMinHeight, childNatHeight] = child.get_preferred_height(forWidth);
|
|
|
|
|
|
|
|
minHeight = Math.max(minHeight, childMinHeight);
|
|
|
|
natHeight = Math.max(natHeight, childNatHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [minHeight, natHeight];
|
|
|
|
}
|
|
|
|
|
2020-05-22 09:53:05 +00:00
|
|
|
vfunc_allocate(container, box) {
|
2020-05-18 16:37:03 +00:00
|
|
|
const width = box.get_width();
|
|
|
|
|
|
|
|
const childBox = new Clutter.ActorBox();
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
|
|
|
|
let first = true;
|
|
|
|
for (let child of container) {
|
|
|
|
if (!child.visible)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
childBox.x1 += this._spacing;
|
|
|
|
|
|
|
|
const [childWidth] = child.get_preferred_width(-1);
|
|
|
|
const [childHeight] = child.get_preferred_height(-1);
|
|
|
|
|
2020-10-24 18:25:49 +00:00
|
|
|
if (childBox.x1 + childWidth <= width)
|
|
|
|
childBox.set_size(childWidth, childHeight);
|
|
|
|
else
|
|
|
|
childBox.set_size(0, 0);
|
2020-05-18 16:37:03 +00:00
|
|
|
|
2020-05-22 09:53:05 +00:00
|
|
|
child.allocate(childBox);
|
2020-05-18 16:37:03 +00:00
|
|
|
|
|
|
|
childBox.x1 += childWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
columnsForWidth(width) {
|
|
|
|
if (!this._container)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
const [minWidth] = this.get_preferred_width(this._container, -1);
|
|
|
|
|
|
|
|
if (minWidth === 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
let nCols = 0;
|
|
|
|
while (width > minWidth) {
|
|
|
|
width -= minWidth;
|
|
|
|
if (nCols > 0)
|
|
|
|
width -= this._spacing;
|
|
|
|
nCols++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nCols;
|
|
|
|
}
|
|
|
|
|
|
|
|
get spacing() {
|
|
|
|
return this._spacing;
|
|
|
|
}
|
|
|
|
|
|
|
|
set spacing(v) {
|
|
|
|
if (this._spacing === v)
|
|
|
|
return;
|
|
|
|
this._spacing = v;
|
|
|
|
this.layout_changed();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var GridSearchResults = GObject.registerClass(
|
|
|
|
class GridSearchResults extends SearchResultsBase {
|
|
|
|
_init(provider, resultsView) {
|
|
|
|
super._init(provider, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2020-05-18 16:37:03 +00:00
|
|
|
this._grid = new St.Widget({ style_class: 'grid-search-results' });
|
|
|
|
this._grid.layout_manager = new GridSearchResultsLayout();
|
2018-07-02 14:03:07 +00:00
|
|
|
|
2020-05-18 16:37:03 +00:00
|
|
|
this._grid.connect('style-changed', () => {
|
|
|
|
const node = this._grid.get_theme_node();
|
|
|
|
this._grid.layout_manager.spacing = node.get_length('spacing');
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2020-05-18 16:37:03 +00:00
|
|
|
this._resultDisplayBin.set_child(new St.Bin({
|
|
|
|
child: this._grid,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
}));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-08-31 21:40:52 +00:00
|
|
|
_onDestroy() {
|
|
|
|
if (this._updateSearchLater) {
|
|
|
|
Meta.later_remove(this._updateSearchLater);
|
|
|
|
delete this._updateSearchLater;
|
|
|
|
}
|
|
|
|
|
|
|
|
super._onDestroy();
|
|
|
|
}
|
|
|
|
|
2018-11-24 23:57:00 +00:00
|
|
|
updateSearch(...args) {
|
|
|
|
if (this._notifyAllocationId)
|
2019-07-16 09:24:13 +00:00
|
|
|
this.disconnect(this._notifyAllocationId);
|
2019-08-31 21:40:52 +00:00
|
|
|
if (this._updateSearchLater) {
|
|
|
|
Meta.later_remove(this._updateSearchLater);
|
|
|
|
delete this._updateSearchLater;
|
|
|
|
}
|
2018-11-24 23:57:00 +00:00
|
|
|
|
|
|
|
// Make sure the maximum number of results calculated by
|
|
|
|
// _getMaxDisplayedResults() is updated after width changes.
|
2019-07-16 09:24:13 +00:00
|
|
|
this._notifyAllocationId = this.connect('notify::allocation', () => {
|
2019-08-31 21:40:52 +00:00
|
|
|
if (this._updateSearchLater)
|
|
|
|
return;
|
|
|
|
this._updateSearchLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
|
|
|
delete this._updateSearchLater;
|
2019-08-07 15:39:25 +00:00
|
|
|
super.updateSearch(...args);
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
2018-11-24 23:57:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
super.updateSearch(...args);
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_getMaxDisplayedResults() {
|
2019-07-16 09:24:13 +00:00
|
|
|
let width = this.allocation.get_width();
|
2019-08-07 14:42:17 +00:00
|
|
|
if (width == 0)
|
|
|
|
return -1;
|
|
|
|
|
2020-05-18 16:37:03 +00:00
|
|
|
return this._grid.layout_manager.columnsForWidth(width);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_clearResultDisplay() {
|
2020-05-18 16:37:03 +00:00
|
|
|
this._grid.remove_all_children();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createResultDisplay(meta) {
|
2019-07-09 15:17:06 +00:00
|
|
|
return super._createResultDisplay(meta) ||
|
2017-06-27 21:58:07 +00:00
|
|
|
new GridSearchResult(this.provider, meta, this._resultsView);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-02-08 23:59:15 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addItem(display) {
|
2020-05-18 16:37:03 +00:00
|
|
|
this._grid.add_child(display);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-02-08 23:59:15 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
getFirstResult() {
|
2020-05-18 16:37:03 +00:00
|
|
|
for (let child of this._grid) {
|
|
|
|
if (child.visible)
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
return null;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var SearchResultsView = GObject.registerClass({
|
2019-08-20 21:43:54 +00:00
|
|
|
Signals: { 'terms-changed': {} },
|
2019-07-16 09:24:13 +00:00
|
|
|
}, class SearchResultsView extends St.BoxLayout {
|
|
|
|
_init() {
|
|
|
|
super._init({ name: 'searchResults', vertical: true });
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2020-03-30 11:40:11 +00:00
|
|
|
this._parentalControlsManager = ParentalControlsManager.getDefault();
|
|
|
|
this._parentalControlsManager.connect('app-filter-changed', this._reloadRemoteProviders.bind(this));
|
|
|
|
|
2019-10-17 21:40:24 +00:00
|
|
|
this._content = new MaxWidthBox({
|
|
|
|
name: 'searchResultsContent',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this._scrollView = new St.ScrollView({
|
|
|
|
overlay_scrollbars: true,
|
|
|
|
style_class: 'search-display vfade',
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
});
|
2018-11-27 12:45:36 +00:00
|
|
|
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
2018-11-24 21:33:05 +00:00
|
|
|
this._scrollView.add_actor(this._content);
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
2017-12-02 00:27:35 +00:00
|
|
|
action.connect('pan', this._onPan.bind(this));
|
2013-10-29 19:49:05 +00:00
|
|
|
this._scrollView.add_action(action);
|
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this.add_child(this._scrollView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-11-06 11:05:56 +00:00
|
|
|
this._statusText = new St.Label({
|
|
|
|
style_class: 'search-statustext',
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
2019-10-21 18:44:00 +00:00
|
|
|
this._statusBin = new St.Bin({ y_expand: true });
|
|
|
|
this.add_child(this._statusBin);
|
2013-10-29 19:49:05 +00:00
|
|
|
this._statusBin.add_actor(this._statusText);
|
|
|
|
|
|
|
|
this._highlightDefault = false;
|
|
|
|
this._defaultResult = null;
|
2014-09-11 21:15:50 +00:00
|
|
|
this._startingSearch = false;
|
|
|
|
|
|
|
|
this._terms = [];
|
|
|
|
this._results = {};
|
2013-10-30 16:38:46 +00:00
|
|
|
|
2014-09-11 19:45:51 +00:00
|
|
|
this._providers = [];
|
|
|
|
|
2017-06-27 22:01:13 +00:00
|
|
|
this._highlightRegex = null;
|
|
|
|
|
2014-09-12 21:20:47 +00:00
|
|
|
this._searchSettings = new Gio.Settings({ schema_id: SEARCH_PROVIDERS_SCHEMA });
|
2017-12-02 00:27:35 +00:00
|
|
|
this._searchSettings.connect('changed::disabled', this._reloadRemoteProviders.bind(this));
|
|
|
|
this._searchSettings.connect('changed::enabled', this._reloadRemoteProviders.bind(this));
|
|
|
|
this._searchSettings.connect('changed::disable-external', this._reloadRemoteProviders.bind(this));
|
|
|
|
this._searchSettings.connect('changed::sort-order', this._reloadRemoteProviders.bind(this));
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2014-09-11 21:15:50 +00:00
|
|
|
this._searchTimeoutId = 0;
|
2014-09-11 19:45:51 +00:00
|
|
|
this._cancellable = new Gio.Cancellable();
|
2014-09-11 21:15:50 +00:00
|
|
|
|
|
|
|
this._registerProvider(new AppDisplay.AppSearchProvider());
|
2017-11-30 20:36:31 +00:00
|
|
|
|
|
|
|
let appSystem = Shell.AppSystem.get_default();
|
|
|
|
appSystem.connect('installed-changed', this._reloadRemoteProviders.bind(this));
|
2014-09-11 21:15:50 +00:00
|
|
|
this._reloadRemoteProviders();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2019-10-16 00:23:22 +00:00
|
|
|
get terms() {
|
|
|
|
return this._terms;
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_reloadRemoteProviders() {
|
2017-10-31 00:38:18 +00:00
|
|
|
let remoteProviders = this._providers.filter(p => p.isRemoteProvider);
|
|
|
|
remoteProviders.forEach(provider => {
|
2014-09-11 19:45:51 +00:00
|
|
|
this._unregisterProvider(provider);
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
RemoteSearch.loadRemoteSearchProviders(this._searchSettings, providers => {
|
2017-12-02 00:27:35 +00:00
|
|
|
providers.forEach(this._registerProvider.bind(this));
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_registerProvider(provider) {
|
2019-01-18 13:17:21 +00:00
|
|
|
provider.searchInProgress = false;
|
2020-03-30 11:40:11 +00:00
|
|
|
|
|
|
|
// Filter out unwanted providers.
|
|
|
|
if (provider.appInfo && !this._parentalControlsManager.shouldShowApp(provider.appInfo))
|
|
|
|
return;
|
|
|
|
|
2014-09-11 19:45:51 +00:00
|
|
|
this._providers.push(provider);
|
|
|
|
this._ensureProviderDisplay(provider);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_unregisterProvider(provider) {
|
2014-09-11 19:45:51 +00:00
|
|
|
let index = this._providers.indexOf(provider);
|
|
|
|
this._providers.splice(index, 1);
|
|
|
|
|
|
|
|
if (provider.display)
|
|
|
|
provider.display.destroy();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_gotResults(results, provider) {
|
2014-09-11 19:45:51 +00:00
|
|
|
this._results[provider.id] = results;
|
|
|
|
this._updateResults(provider, results);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_clearSearchTimeout() {
|
2014-09-30 06:19:28 +00:00
|
|
|
if (this._searchTimeoutId > 0) {
|
|
|
|
GLib.source_remove(this._searchTimeoutId);
|
|
|
|
this._searchTimeoutId = 0;
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-30 06:19:28 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_reset() {
|
2014-09-30 06:22:14 +00:00
|
|
|
this._terms = [];
|
|
|
|
this._results = {};
|
|
|
|
this._clearDisplay();
|
|
|
|
this._clearSearchTimeout();
|
|
|
|
this._defaultResult = null;
|
|
|
|
this._startingSearch = false;
|
|
|
|
|
|
|
|
this._updateSearchProgress();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-30 06:22:14 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_doSearch() {
|
2014-09-11 21:47:49 +00:00
|
|
|
this._startingSearch = false;
|
|
|
|
|
2014-09-11 21:15:50 +00:00
|
|
|
let previousResults = this._results;
|
|
|
|
this._results = {};
|
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
this._providers.forEach(provider => {
|
2014-09-11 21:15:50 +00:00
|
|
|
provider.searchInProgress = true;
|
|
|
|
|
|
|
|
let previousProviderResults = previousResults[provider.id];
|
2019-08-20 00:51:42 +00:00
|
|
|
if (this._isSubSearch && previousProviderResults) {
|
2017-12-02 00:27:35 +00:00
|
|
|
provider.getSubsearchResultSet(previousProviderResults,
|
|
|
|
this._terms,
|
|
|
|
results => {
|
|
|
|
this._gotResults(results, provider);
|
|
|
|
},
|
|
|
|
this._cancellable);
|
2019-08-20 00:51:42 +00:00
|
|
|
} else {
|
2017-12-02 00:27:35 +00:00
|
|
|
provider.getInitialResultSet(this._terms,
|
|
|
|
results => {
|
|
|
|
this._gotResults(results, provider);
|
|
|
|
},
|
|
|
|
this._cancellable);
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2014-09-11 21:15:50 +00:00
|
|
|
|
|
|
|
this._updateSearchProgress();
|
|
|
|
|
2014-09-30 06:19:28 +00:00
|
|
|
this._clearSearchTimeout();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 23:51:12 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onSearchTimeout() {
|
2014-09-11 21:15:50 +00:00
|
|
|
this._searchTimeoutId = 0;
|
2014-09-11 23:51:12 +00:00
|
|
|
this._doSearch();
|
2014-09-11 21:15:50 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 21:15:50 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
setTerms(terms) {
|
2014-10-01 00:47:07 +00:00
|
|
|
// Check for the case of making a duplicate previous search before
|
|
|
|
// setting state of the current search or cancelling the search.
|
|
|
|
// This will prevent incorrect state being as a result of a duplicate
|
|
|
|
// search while the previous search is still active.
|
|
|
|
let searchString = terms.join(' ');
|
|
|
|
let previousSearchString = this._terms.join(' ');
|
|
|
|
if (searchString == previousSearchString)
|
|
|
|
return;
|
|
|
|
|
2014-09-11 21:15:50 +00:00
|
|
|
this._startingSearch = true;
|
|
|
|
|
2014-09-11 19:45:51 +00:00
|
|
|
this._cancellable.cancel();
|
|
|
|
this._cancellable.reset();
|
|
|
|
|
2014-09-30 06:19:55 +00:00
|
|
|
if (terms.length == 0) {
|
2014-09-30 06:22:14 +00:00
|
|
|
this._reset();
|
2014-09-11 19:45:51 +00:00
|
|
|
return;
|
2014-09-11 21:15:50 +00:00
|
|
|
}
|
2014-09-11 19:45:51 +00:00
|
|
|
|
|
|
|
let isSubSearch = false;
|
|
|
|
if (this._terms.length > 0)
|
|
|
|
isSubSearch = searchString.indexOf(previousSearchString) == 0;
|
|
|
|
|
|
|
|
this._terms = terms;
|
2014-09-11 21:15:50 +00:00
|
|
|
this._isSubSearch = isSubSearch;
|
2014-09-11 21:47:49 +00:00
|
|
|
this._updateSearchProgress();
|
2014-09-11 21:15:50 +00:00
|
|
|
|
|
|
|
if (this._searchTimeoutId == 0)
|
2017-12-02 00:27:35 +00:00
|
|
|
this._searchTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, this._onSearchTimeout.bind(this));
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
let escapedTerms = this._terms.map(term => Shell.util_regex_escape(term));
|
2020-02-14 15:10:34 +00:00
|
|
|
this._highlightRegex = new RegExp('(%s)'.format(escapedTerms.join('|')), 'gi');
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
this.emit('terms-changed');
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onPan(action) {
|
2019-01-31 14:08:00 +00:00
|
|
|
let [dist_, dx_, dy] = action.get_motion_delta(0);
|
2013-10-29 19:49:05 +00:00
|
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
2019-07-16 09:24:13 +00:00
|
|
|
adjustment.value -= (dy / this.height) * adjustment.page_size;
|
2013-10-29 19:49:05 +00:00
|
|
|
return false;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-08-28 20:06:14 +00:00
|
|
|
_focusChildChanged(provider) {
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, provider.focusChild);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_ensureProviderDisplay(provider) {
|
2013-10-30 16:38:46 +00:00
|
|
|
if (provider.display)
|
|
|
|
return;
|
|
|
|
|
2013-10-29 20:13:32 +00:00
|
|
|
let providerDisplay;
|
|
|
|
if (provider.appInfo)
|
2017-06-27 21:58:07 +00:00
|
|
|
providerDisplay = new ListSearchResults(provider, this);
|
2013-10-29 20:13:32 +00:00
|
|
|
else
|
2017-06-27 21:58:07 +00:00
|
|
|
providerDisplay = new GridSearchResults(provider, this);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
providerDisplay.connect('notify::focus-child', this._focusChildChanged.bind(this));
|
|
|
|
providerDisplay.hide();
|
|
|
|
this._content.add(providerDisplay);
|
2013-10-29 20:13:32 +00:00
|
|
|
provider.display = providerDisplay;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_clearDisplay() {
|
2017-10-31 00:38:18 +00:00
|
|
|
this._providers.forEach(provider => {
|
2013-10-29 20:13:32 +00:00
|
|
|
provider.display.clear();
|
|
|
|
});
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_maybeSetInitialSelection() {
|
2013-10-29 19:49:05 +00:00
|
|
|
let newDefaultResult = null;
|
|
|
|
|
2014-09-11 19:45:51 +00:00
|
|
|
let providers = this._providers;
|
2013-10-29 20:13:32 +00:00
|
|
|
for (let i = 0; i < providers.length; i++) {
|
|
|
|
let provider = providers[i];
|
|
|
|
let display = provider.display;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
if (!display.visible)
|
2013-10-29 19:49:05 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
let firstResult = display.getFirstResult();
|
|
|
|
if (firstResult) {
|
|
|
|
newDefaultResult = firstResult;
|
|
|
|
break; // select this one!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newDefaultResult != this._defaultResult) {
|
2014-08-20 16:39:02 +00:00
|
|
|
this._setSelected(this._defaultResult, false);
|
|
|
|
this._setSelected(newDefaultResult, this._highlightDefault);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._defaultResult = newDefaultResult;
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2014-09-11 21:47:49 +00:00
|
|
|
get searchInProgress() {
|
|
|
|
if (this._startingSearch)
|
|
|
|
return true;
|
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
return this._providers.some(p => p.searchInProgress);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-09-11 21:47:49 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateSearchProgress() {
|
2017-10-31 00:38:18 +00:00
|
|
|
let haveResults = this._providers.some(provider => {
|
2013-10-29 20:13:32 +00:00
|
|
|
let display = provider.display;
|
2019-08-19 19:38:51 +00:00
|
|
|
return display.getFirstResult() != null;
|
2013-10-29 20:13:32 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2014-09-11 21:47:49 +00:00
|
|
|
this._scrollView.visible = haveResults;
|
|
|
|
this._statusBin.visible = !haveResults;
|
|
|
|
|
2014-09-11 23:14:54 +00:00
|
|
|
if (!haveResults) {
|
2019-08-20 00:51:42 +00:00
|
|
|
if (this.searchInProgress)
|
2014-09-11 21:47:49 +00:00
|
|
|
this._statusText.set_text(_("Searching…"));
|
2019-08-20 00:51:42 +00:00
|
|
|
else
|
2014-09-11 21:47:49 +00:00
|
|
|
this._statusText.set_text(_("No results."));
|
2014-09-11 23:14:54 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateResults(provider, results) {
|
2014-09-11 19:45:51 +00:00
|
|
|
let terms = this._terms;
|
2013-10-29 20:13:32 +00:00
|
|
|
let display = provider.display;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:38:18 +00:00
|
|
|
display.updateSearch(results, terms, () => {
|
2014-09-11 21:47:49 +00:00
|
|
|
provider.searchInProgress = false;
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
this._maybeSetInitialSelection();
|
2014-09-11 21:47:49 +00:00
|
|
|
this._updateSearchProgress();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
activateDefault() {
|
2014-09-12 00:01:08 +00:00
|
|
|
// If we have a search queued up, force the search now.
|
|
|
|
if (this._searchTimeoutId > 0)
|
|
|
|
this._doSearch();
|
2014-09-11 21:15:50 +00:00
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
if (this._defaultResult)
|
|
|
|
this._defaultResult.activate();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
highlightDefault(highlight) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this._highlightDefault = highlight;
|
2014-08-20 16:39:02 +00:00
|
|
|
this._setSelected(this._defaultResult, highlight);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
popupMenuDefault() {
|
2017-08-15 23:25:15 +00:00
|
|
|
// If we have a search queued up, force the search now.
|
|
|
|
if (this._searchTimeoutId > 0)
|
|
|
|
this._doSearch();
|
|
|
|
|
|
|
|
if (this._defaultResult)
|
2019-07-16 09:24:13 +00:00
|
|
|
this._defaultResult.popup_menu();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-08-15 23:25:15 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
navigateFocus(direction) {
|
2019-07-16 09:24:13 +00:00
|
|
|
let rtl = this.get_text_direction() == Clutter.TextDirection.RTL;
|
2018-11-27 12:58:25 +00:00
|
|
|
if (direction == St.DirectionType.TAB_BACKWARD ||
|
2019-08-19 19:33:15 +00:00
|
|
|
direction == (rtl
|
|
|
|
? St.DirectionType.RIGHT
|
|
|
|
: St.DirectionType.LEFT) ||
|
2018-11-27 12:58:25 +00:00
|
|
|
direction == St.DirectionType.UP) {
|
2019-07-16 09:24:13 +00:00
|
|
|
this.navigate_focus(null, direction, false);
|
2013-10-29 19:49:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-12 18:59:01 +00:00
|
|
|
const from = this._defaultResult ?? null;
|
2019-07-16 09:24:13 +00:00
|
|
|
this.navigate_focus(from, direction, false);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2014-08-20 16:39:02 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_setSelected(result, selected) {
|
2014-08-20 16:39:02 +00:00
|
|
|
if (!result)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (selected) {
|
2019-07-16 09:24:13 +00:00
|
|
|
result.add_style_pseudo_class('selected');
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, result);
|
2014-08-20 16:39:02 +00:00
|
|
|
} else {
|
2019-07-16 09:24:13 +00:00
|
|
|
result.remove_style_pseudo_class('selected');
|
2014-08-20 16:39:02 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
highlightTerms(description) {
|
2017-06-27 22:01:13 +00:00
|
|
|
if (!description)
|
|
|
|
return '';
|
|
|
|
|
|
|
|
if (!this._highlightRegex)
|
|
|
|
return description;
|
|
|
|
|
|
|
|
return description.replace(this._highlightRegex, '<b>$1</b>');
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
var ProviderInfo = GObject.registerClass(
|
|
|
|
class ProviderInfo extends St.Button {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(provider) {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.provider = provider;
|
2019-10-17 21:27:27 +00:00
|
|
|
super._init({
|
|
|
|
style_class: 'search-provider-icon',
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
accessible_name: provider.appInfo.get_name(),
|
|
|
|
track_hover: true,
|
|
|
|
y_align: Clutter.ActorAlign.START,
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-07-12 23:51:59 +00:00
|
|
|
this._content = new St.BoxLayout({ vertical: false,
|
|
|
|
style_class: 'list-search-provider-content' });
|
2013-10-29 19:49:05 +00:00
|
|
|
this.set_child(this._content);
|
|
|
|
|
|
|
|
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
|
|
|
gicon: provider.appInfo.get_icon() });
|
2017-07-12 23:51:59 +00:00
|
|
|
|
|
|
|
let detailsBox = new St.BoxLayout({ style_class: 'list-search-provider-details',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true });
|
|
|
|
|
|
|
|
let nameLabel = new St.Label({ text: provider.appInfo.get_name(),
|
|
|
|
x_align: Clutter.ActorAlign.START });
|
|
|
|
|
|
|
|
this._moreLabel = new St.Label({ x_align: Clutter.ActorAlign.START });
|
|
|
|
|
|
|
|
detailsBox.add_actor(nameLabel);
|
|
|
|
detailsBox.add_actor(this._moreLabel);
|
|
|
|
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
this._content.add_actor(icon);
|
2017-07-12 23:51:59 +00:00
|
|
|
this._content.add_actor(detailsBox);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get PROVIDER_ICON_SIZE() {
|
|
|
|
return 32;
|
|
|
|
}
|
2014-06-17 19:31:53 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
animateLaunch() {
|
2014-06-17 19:31:53 +00:00
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
let app = appSys.lookup_app(this.provider.appInfo.get_id());
|
|
|
|
if (app.state == Shell.AppState.STOPPED)
|
|
|
|
IconGrid.zoomOutActor(this._content);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
setMoreCount(count) {
|
2017-08-11 01:06:10 +00:00
|
|
|
this._moreLabel.text = ngettext("%d more", "%d more", count).format(count);
|
2017-07-12 23:51:59 +00:00
|
|
|
this._moreLabel.visible = count > 0;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|