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
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import Gio from 'gi://Gio';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Meta from 'gi://Meta';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import St from 'gi://St';
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as AppDisplay from './appDisplay.js';
|
|
|
|
import * as IconGrid from './iconGrid.js';
|
|
|
|
import * as Main from './main.js';
|
|
|
|
import * as ParentalControlsManager from '../misc/parentalControlsManager.js';
|
|
|
|
import * as RemoteSearch from './remoteSearch.js';
|
|
|
|
import {ensureActorVisibleInScrollView} from '../misc/animationUtils.js';
|
2011-01-17 21:38:47 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import {Highlighter} from '../misc/util.js';
|
2021-11-16 17:57:26 +00:00
|
|
|
|
2012-11-01 14:33:40 +00:00
|
|
|
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const MAX_LIST_SEARCH_RESULTS_ROWS = 5;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const MaxWidthBox = GObject.registerClass(
|
2018-11-24 21:33:05 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const SearchResult = GObject.registerClass(
|
2019-10-17 12:27:10 +00:00
|
|
|
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
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const ListSearchResult = GObject.registerClass(
|
2019-07-16 09:24:13 +00:00
|
|
|
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
|
|
|
|
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
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
this._resultsView.connectObject(
|
|
|
|
'terms-changed', this._highlightTerms.bind(this), this);
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
this._highlightTerms();
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
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
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const GridSearchResult = GObject.registerClass(
|
2019-07-16 09:24:13 +00:00
|
|
|
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
|
|
|
|
2023-08-06 23:45:22 +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
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const SearchResultsBase = GObject.registerClass({
|
2019-07-16 09:24:13 +00:00
|
|
|
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) {
|
2023-08-06 22:40:20 +00:00
|
|
|
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
|
|
|
|
2023-08-06 22:40:20 +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
|
|
|
|
2022-06-23 16:13:36 +00:00
|
|
|
async _ensureResultActors(results) {
|
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
|
|
|
|
2022-06-23 16:13:36 +00:00
|
|
|
if (metasNeeded.length === 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._cancellable.cancel();
|
|
|
|
this._cancellable.reset();
|
|
|
|
|
|
|
|
const metas = await this.provider.getResultMetas(metasNeeded, this._cancellable);
|
|
|
|
|
|
|
|
if (this._cancellable.is_cancelled()) {
|
|
|
|
if (metas.length > 0)
|
|
|
|
throw new Error(`Search provider ${this.provider.id} returned results after the request was canceled`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (metas.length !== metasNeeded.length) {
|
|
|
|
throw new Error(`Wrong number of result metas returned by search provider ${this.provider.id}: ` +
|
|
|
|
`expected ${metasNeeded.length} but got ${metas.length}`);
|
2013-02-08 23:59:15 +00:00
|
|
|
}
|
2022-06-23 16:13:36 +00:00
|
|
|
|
|
|
|
if (metas.some(meta => !meta.name || !meta.id))
|
|
|
|
throw new Error(`Invalid result meta returned from search provider ${this.provider.id}`);
|
|
|
|
|
|
|
|
metasNeeded.forEach((resultId, i) => {
|
|
|
|
let meta = metas[i];
|
|
|
|
let display = this._createResultDisplay(meta);
|
|
|
|
display.connect('key-focus-in', this._keyFocusIn.bind(this));
|
|
|
|
this._resultDisplays[resultId] = display;
|
|
|
|
});
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2013-02-08 23:59:15 +00:00
|
|
|
|
2022-06-23 16:13:36 +00:00
|
|
|
async 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
|
|
|
|
2022-06-23 16:13:36 +00:00
|
|
|
try {
|
|
|
|
await this._ensureResultActors(results);
|
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();
|
2022-06-23 16:13:36 +00:00
|
|
|
results.forEach(
|
|
|
|
resultId => this._addItem(this._resultDisplays[resultId]));
|
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();
|
2022-06-23 16:13:36 +00:00
|
|
|
} catch (e) {
|
|
|
|
this._clearResultDisplay();
|
|
|
|
callback();
|
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const ListSearchResults = GObject.registerClass(
|
2019-07-16 09:24:13 +00:00
|
|
|
class ListSearchResults extends SearchResultsBase {
|
|
|
|
_init(provider, resultsView) {
|
|
|
|
super._init(provider, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-08-06 22:40:20 +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
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const GridSearchResultsLayout = GObject.registerClass({
|
2020-05-18 16:37:03 +00:00
|
|
|
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);
|
2021-07-15 16:48:34 +00:00
|
|
|
child.can_focus = childBox.get_area() > 0;
|
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();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const GridSearchResults = GObject.registerClass(
|
2019-07-16 09:24:13 +00:00
|
|
|
class GridSearchResults extends SearchResultsBase {
|
|
|
|
_init(provider, resultsView) {
|
|
|
|
super._init(provider, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
this._grid = new St.Widget({style_class: 'grid-search-results'});
|
2020-05-18 16:37:03 +00:00
|
|
|
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) {
|
2022-09-07 18:23:38 +00:00
|
|
|
const laters = global.compositor.get_laters();
|
|
|
|
laters.remove(this._updateSearchLater);
|
2019-08-31 21:40:52 +00:00
|
|
|
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) {
|
2022-09-07 18:23:38 +00:00
|
|
|
const laters = global.compositor.get_laters();
|
|
|
|
laters.remove(this._updateSearchLater);
|
2019-08-31 21:40:52 +00:00
|
|
|
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;
|
2022-09-07 18:23:38 +00:00
|
|
|
const laters = global.compositor.get_laters();
|
|
|
|
this._updateSearchLater = laters.add(Meta.LaterType.BEFORE_REDRAW, () => {
|
2019-08-31 21:40:52 +00:00
|
|
|
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
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const SearchResultsView = GObject.registerClass({
|
2023-06-08 04:53:07 +00:00
|
|
|
Signals: {'terms-changed': {}},
|
2019-07-16 09:24:13 +00:00
|
|
|
}, class SearchResultsView extends St.BoxLayout {
|
|
|
|
_init() {
|
2023-04-20 23:08:23 +00:00
|
|
|
super._init({
|
|
|
|
name: 'searchResults',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: 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);
|
|
|
|
|
2023-08-06 22:40:20 +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,
|
|
|
|
});
|
2023-08-06 22:40:20 +00:00
|
|
|
this._statusBin = new St.Bin({y_expand: true});
|
2019-10-21 18:44:00 +00:00
|
|
|
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 = [];
|
|
|
|
|
2021-11-16 17:57:26 +00:00
|
|
|
this._highlighter = new Highlighter();
|
2017-06-27 22:01:13 +00:00
|
|
|
|
2023-08-06 22:40:20 +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
|
|
|
|
2022-06-23 16:17:57 +00:00
|
|
|
const providers = RemoteSearch.loadRemoteSearchProviders(this._searchSettings);
|
|
|
|
providers.forEach(this._registerProvider.bind(this));
|
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
|
|
|
_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
|
|
|
|
2022-06-23 16:13:36 +00:00
|
|
|
async _doProviderSearch(provider, previousResults) {
|
|
|
|
provider.searchInProgress = true;
|
|
|
|
|
|
|
|
let results;
|
|
|
|
if (this._isSubSearch && previousResults) {
|
|
|
|
results = await provider.getSubsearchResultSet(
|
|
|
|
previousResults,
|
|
|
|
this._terms,
|
|
|
|
this._cancellable);
|
|
|
|
} else {
|
|
|
|
results = await provider.getInitialResultSet(
|
|
|
|
this._terms,
|
|
|
|
this._cancellable);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._results[provider.id] = results;
|
|
|
|
this._updateResults(provider, results);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
let previousProviderResults = previousResults[provider.id];
|
2022-06-23 16:13:36 +00:00
|
|
|
this._doProviderSearch(provider, previousProviderResults);
|
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
|
|
|
|
2021-11-16 17:57:26 +00:00
|
|
|
this._highlighter = new Highlighter(this._terms);
|
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) {
|
2023-07-10 04:58:21 +00:00
|
|
|
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)
|
2023-08-06 22:34:20 +00:00
|
|
|
this._statusText.set_text(_('Searching…'));
|
2019-08-20 00:51:42 +00:00
|
|
|
else
|
2023-08-06 22:34:20 +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');
|
2023-07-10 04:58:21 +00:00
|
|
|
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 '';
|
|
|
|
|
2021-11-16 17:57:26 +00:00
|
|
|
return this._highlighter.highlight(description);
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const ProviderInfo = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
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
|
|
|
|
2020-03-29 21:51:13 +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);
|
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
const icon = new St.Icon({
|
|
|
|
icon_size: this.PROVIDER_ICON_SIZE,
|
|
|
|
gicon: provider.appInfo.get_icon(),
|
|
|
|
});
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
const detailsBox = new St.BoxLayout({
|
|
|
|
style_class: 'list-search-provider-details',
|
|
|
|
vertical: true,
|
|
|
|
x_expand: true,
|
|
|
|
});
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
const nameLabel = new St.Label({
|
|
|
|
text: provider.appInfo.get_name(),
|
|
|
|
x_align: Clutter.ActorAlign.START,
|
|
|
|
});
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
this._moreLabel = new St.Label({x_align: Clutter.ActorAlign.START});
|
2017-07-12 23:51:59 +00:00
|
|
|
|
|
|
|
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) {
|
2023-08-06 22:34:20 +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
|
|
|
}
|
|
|
|
});
|