71759a0769
While we aren't using those destructured variables, they are still useful to document the meaning of those elements. We don't want eslint to keep warning about them though, so mark them accordingly. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/627
769 lines
25 KiB
JavaScript
769 lines
25 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
|
|
const Signals = imports.signals;
|
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
|
const IconGrid = imports.ui.iconGrid;
|
|
const Main = imports.ui.main;
|
|
const RemoteSearch = imports.ui.remoteSearch;
|
|
const Util = imports.misc.util;
|
|
|
|
const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';
|
|
|
|
var MAX_LIST_SEARCH_RESULTS_ROWS = 5;
|
|
var MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
|
|
|
var MaxWidthBox = GObject.registerClass(
|
|
class MaxWidthBox extends St.BoxLayout {
|
|
vfunc_allocate(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);
|
|
}
|
|
|
|
super.vfunc_allocate(adjustedBox, flags);
|
|
}
|
|
});
|
|
|
|
var SearchResult = class {
|
|
constructor(provider, metaInfo, resultsView) {
|
|
this.provider = provider;
|
|
this.metaInfo = metaInfo;
|
|
this._resultsView = resultsView;
|
|
|
|
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;
|
|
this.actor.connect('clicked', this.activate.bind(this));
|
|
}
|
|
|
|
activate() {
|
|
this.emit('activate', this.metaInfo.id);
|
|
}
|
|
};
|
|
Signals.addSignalMethods(SearchResult.prototype);
|
|
|
|
var ListSearchResult = class extends SearchResult {
|
|
|
|
constructor(provider, metaInfo, resultsView) {
|
|
super(provider, metaInfo, resultsView);
|
|
|
|
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);
|
|
|
|
this._termsChangedId = 0;
|
|
|
|
let titleBox = new St.BoxLayout({ style_class: 'list-search-result-title' });
|
|
|
|
content.add(titleBox, { x_fill: true,
|
|
y_fill: false,
|
|
x_align: St.Align.START,
|
|
y_align: St.Align.MIDDLE });
|
|
|
|
// An icon for, or thumbnail of, content
|
|
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
|
if (icon) {
|
|
titleBox.add(icon);
|
|
}
|
|
|
|
let title = new St.Label({ text: this.metaInfo['name'] });
|
|
titleBox.add(title, { x_fill: false,
|
|
y_fill: false,
|
|
x_align: St.Align.START,
|
|
y_align: St.Align.MIDDLE });
|
|
|
|
this.actor.label_actor = title;
|
|
|
|
if (this.metaInfo['description']) {
|
|
this._descriptionLabel = new St.Label({ style_class: 'list-search-result-description' });
|
|
content.add(this._descriptionLabel, { x_fill: false,
|
|
y_fill: false,
|
|
x_align: St.Align.START,
|
|
y_align: St.Align.MIDDLE });
|
|
|
|
this._termsChangedId =
|
|
this._resultsView.connect('terms-changed',
|
|
this._highlightTerms.bind(this));
|
|
|
|
this._highlightTerms();
|
|
}
|
|
|
|
this.actor.connect('destroy', this._onDestroy.bind(this));
|
|
}
|
|
|
|
get ICON_SIZE() {
|
|
return 24;
|
|
}
|
|
|
|
_highlightTerms() {
|
|
let markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]);
|
|
this._descriptionLabel.clutter_text.set_markup(markup);
|
|
}
|
|
|
|
_onDestroy() {
|
|
if (this._termsChangedId)
|
|
this._resultsView.disconnect(this._termsChangedId);
|
|
this._termsChangedId = 0;
|
|
}
|
|
};
|
|
|
|
var GridSearchResult = class extends SearchResult {
|
|
constructor(provider, metaInfo, resultsView) {
|
|
super(provider, metaInfo, resultsView);
|
|
|
|
this.actor.style_class = 'grid-search-result';
|
|
|
|
this.icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
|
{ createIcon: this.metaInfo['createIcon'] });
|
|
let content = new St.Bin({ child: this.icon });
|
|
this.actor.set_child(content);
|
|
this.actor.label_actor = this.icon.label;
|
|
}
|
|
};
|
|
|
|
var SearchResultsBase = class {
|
|
constructor(provider, resultsView) {
|
|
this.provider = provider;
|
|
this._resultsView = resultsView;
|
|
|
|
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 St.Widget({ style_class: 'search-section-separator' });
|
|
this.actor.add(separator);
|
|
|
|
this._resultDisplays = {};
|
|
|
|
this._clipboard = St.Clipboard.get_default();
|
|
|
|
this._cancellable = new Gio.Cancellable();
|
|
}
|
|
|
|
destroy() {
|
|
this.actor.destroy();
|
|
this._terms = [];
|
|
}
|
|
|
|
_createResultDisplay(meta) {
|
|
if (this.provider.createResultObject)
|
|
return this.provider.createResultObject(meta, this._resultsView);
|
|
|
|
return null;
|
|
}
|
|
|
|
clear() {
|
|
this._cancellable.cancel();
|
|
for (let resultId in this._resultDisplays)
|
|
this._resultDisplays[resultId].actor.destroy();
|
|
this._resultDisplays = {};
|
|
this._clearResultDisplay();
|
|
this.actor.hide();
|
|
}
|
|
|
|
_keyFocusIn(actor) {
|
|
this.emit('key-focus-in', actor);
|
|
}
|
|
|
|
_activateResult(result, id) {
|
|
this.provider.activateResult(id, this._terms);
|
|
if (result.metaInfo.clipboardText)
|
|
this._clipboard.set_text(St.ClipboardType.CLIPBOARD, result.metaInfo.clipboardText);
|
|
Main.overview.toggle();
|
|
}
|
|
|
|
_setMoreCount(_count) {
|
|
}
|
|
|
|
_ensureResultActors(results, callback) {
|
|
let metasNeeded = results.filter(
|
|
resultId => this._resultDisplays[resultId] === undefined
|
|
);
|
|
|
|
if (metasNeeded.length === 0) {
|
|
callback(true);
|
|
} else {
|
|
this._cancellable.cancel();
|
|
this._cancellable.reset();
|
|
|
|
this.provider.getResultMetas(metasNeeded, metas => {
|
|
if (this._cancellable.is_cancelled()) {
|
|
if (metas.length > 0)
|
|
log(`Search provider ${this.provider.id} returned results after the request was canceled`);
|
|
callback(false);
|
|
return;
|
|
}
|
|
if (metas.length != metasNeeded.length) {
|
|
log(`Wrong number of result metas returned by search provider ${this.provider.id}: ` +
|
|
`expected ${metasNeeded.length} but got ${metas.length}`);
|
|
callback(false);
|
|
return;
|
|
}
|
|
if (metas.some(meta => !meta.name || !meta.id)) {
|
|
log(`Invalid result meta returned from search provider ${this.provider.id}`);
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
metasNeeded.forEach((resultId, i) => {
|
|
let meta = metas[i];
|
|
let display = this._createResultDisplay(meta);
|
|
display.connect('activate', this._activateResult.bind(this));
|
|
display.actor.connect('key-focus-in', this._keyFocusIn.bind(this));
|
|
this._resultDisplays[resultId] = display;
|
|
});
|
|
callback(true);
|
|
}, this._cancellable);
|
|
}
|
|
}
|
|
|
|
updateSearch(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 moreCount = Math.max(providerResults.length - results.length, 0);
|
|
|
|
this._ensureResultActors(results, successful => {
|
|
if (!successful) {
|
|
this._clearResultDisplay();
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
// 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();
|
|
results.forEach(resultId => {
|
|
this._addItem(this._resultDisplays[resultId]);
|
|
});
|
|
this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
|
|
this.actor.show();
|
|
callback();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
var ListSearchResults = class extends SearchResultsBase {
|
|
constructor(provider, resultsView) {
|
|
super(provider, resultsView);
|
|
|
|
this._container = new St.BoxLayout({ style_class: 'search-section-content' });
|
|
this.providerInfo = new ProviderInfo(provider);
|
|
this.providerInfo.connect('key-focus-in', this._keyFocusIn.bind(this));
|
|
this.providerInfo.connect('clicked', () => {
|
|
this.providerInfo.animateLaunch();
|
|
provider.launchSearch(this._terms);
|
|
Main.overview.toggle();
|
|
});
|
|
|
|
this._container.add(this.providerInfo, { 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);
|
|
}
|
|
|
|
_setMoreCount(count) {
|
|
this.providerInfo.setMoreCount(count);
|
|
}
|
|
|
|
_getMaxDisplayedResults() {
|
|
return MAX_LIST_SEARCH_RESULTS_ROWS;
|
|
}
|
|
|
|
_clearResultDisplay() {
|
|
this._content.remove_all_children();
|
|
}
|
|
|
|
_createResultDisplay(meta) {
|
|
return super._createResultDisplay(meta) ||
|
|
new ListSearchResult(this.provider, meta, this._resultsView);
|
|
}
|
|
|
|
_addItem(display) {
|
|
this._content.add_actor(display.actor);
|
|
}
|
|
|
|
getFirstResult() {
|
|
if (this._content.get_n_children() > 0)
|
|
return this._content.get_child_at_index(0)._delegate;
|
|
else
|
|
return null;
|
|
}
|
|
};
|
|
Signals.addSignalMethods(ListSearchResults.prototype);
|
|
|
|
var GridSearchResults = class extends SearchResultsBase {
|
|
constructor(provider, resultsView) {
|
|
super(provider, resultsView);
|
|
|
|
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);
|
|
|
|
this._resultDisplayBin.set_child(this._bin);
|
|
}
|
|
|
|
updateSearch(...args) {
|
|
if (this._notifyAllocationId)
|
|
this.actor.disconnect(this._notifyAllocationId);
|
|
|
|
// Make sure the maximum number of results calculated by
|
|
// _getMaxDisplayedResults() is updated after width changes.
|
|
this._notifyAllocationId = this.actor.connect('notify::allocation', () => {
|
|
super.updateSearch(...args);
|
|
});
|
|
|
|
super.updateSearch(...args);
|
|
}
|
|
|
|
_getMaxDisplayedResults() {
|
|
let allocation = this.actor.allocation;
|
|
let nCols = this._grid.columnsForWidth(allocation.x2 - allocation.x1);
|
|
return nCols * this._grid.getRowLimit();
|
|
}
|
|
|
|
_clearResultDisplay() {
|
|
this._grid.removeAll();
|
|
}
|
|
|
|
_createResultDisplay(meta) {
|
|
return super._createResultDisplay(meta) ||
|
|
new GridSearchResult(this.provider, meta, this._resultsView);
|
|
}
|
|
|
|
_addItem(display) {
|
|
this._grid.addItem(display);
|
|
}
|
|
|
|
getFirstResult() {
|
|
if (this._grid.visibleItemsCount() > 0)
|
|
return this._grid.getItemAtIndex(0)._delegate;
|
|
else
|
|
return null;
|
|
}
|
|
};
|
|
Signals.addSignalMethods(GridSearchResults.prototype);
|
|
|
|
var SearchResults = class {
|
|
constructor() {
|
|
this.actor = new St.BoxLayout({ name: 'searchResults',
|
|
vertical: true });
|
|
|
|
this._content = new MaxWidthBox({ name: 'searchResultsContent',
|
|
vertical: true });
|
|
|
|
this._scrollView = new St.ScrollView({ x_fill: true,
|
|
y_fill: false,
|
|
overlay_scrollbars: true,
|
|
style_class: 'search-display vfade' });
|
|
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
|
this._scrollView.add_actor(this._content);
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
|
action.connect('pan', this._onPan.bind(this));
|
|
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.actor.add(this._statusBin, { expand: true });
|
|
this._statusBin.add_actor(this._statusText);
|
|
|
|
this._highlightDefault = false;
|
|
this._defaultResult = null;
|
|
this._startingSearch = false;
|
|
|
|
this._terms = [];
|
|
this._results = {};
|
|
|
|
this._providers = [];
|
|
|
|
this._highlightRegex = null;
|
|
|
|
this._searchSettings = new Gio.Settings({ schema_id: SEARCH_PROVIDERS_SCHEMA });
|
|
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));
|
|
|
|
this._searchTimeoutId = 0;
|
|
this._cancellable = new Gio.Cancellable();
|
|
|
|
this._registerProvider(new AppDisplay.AppSearchProvider());
|
|
|
|
let appSystem = Shell.AppSystem.get_default();
|
|
appSystem.connect('installed-changed', this._reloadRemoteProviders.bind(this));
|
|
this._reloadRemoteProviders();
|
|
}
|
|
|
|
_reloadRemoteProviders() {
|
|
let remoteProviders = this._providers.filter(p => p.isRemoteProvider);
|
|
remoteProviders.forEach(provider => {
|
|
this._unregisterProvider(provider);
|
|
});
|
|
|
|
RemoteSearch.loadRemoteSearchProviders(this._searchSettings, providers => {
|
|
providers.forEach(this._registerProvider.bind(this));
|
|
});
|
|
}
|
|
|
|
_registerProvider(provider) {
|
|
provider.searchInProgress = false;
|
|
this._providers.push(provider);
|
|
this._ensureProviderDisplay(provider);
|
|
}
|
|
|
|
_unregisterProvider(provider) {
|
|
let index = this._providers.indexOf(provider);
|
|
this._providers.splice(index, 1);
|
|
|
|
if (provider.display)
|
|
provider.display.destroy();
|
|
}
|
|
|
|
_gotResults(results, provider) {
|
|
this._results[provider.id] = results;
|
|
this._updateResults(provider, results);
|
|
}
|
|
|
|
_clearSearchTimeout() {
|
|
if (this._searchTimeoutId > 0) {
|
|
GLib.source_remove(this._searchTimeoutId);
|
|
this._searchTimeoutId = 0;
|
|
}
|
|
}
|
|
|
|
_reset() {
|
|
this._terms = [];
|
|
this._results = {};
|
|
this._clearDisplay();
|
|
this._clearSearchTimeout();
|
|
this._defaultResult = null;
|
|
this._startingSearch = false;
|
|
|
|
this._updateSearchProgress();
|
|
}
|
|
|
|
_doSearch() {
|
|
this._startingSearch = false;
|
|
|
|
let previousResults = this._results;
|
|
this._results = {};
|
|
|
|
this._providers.forEach(provider => {
|
|
provider.searchInProgress = true;
|
|
|
|
let previousProviderResults = previousResults[provider.id];
|
|
if (this._isSubSearch && previousProviderResults)
|
|
provider.getSubsearchResultSet(previousProviderResults,
|
|
this._terms,
|
|
results => {
|
|
this._gotResults(results, provider);
|
|
},
|
|
this._cancellable);
|
|
else
|
|
provider.getInitialResultSet(this._terms,
|
|
results => {
|
|
this._gotResults(results, provider);
|
|
},
|
|
this._cancellable);
|
|
});
|
|
|
|
this._updateSearchProgress();
|
|
|
|
this._clearSearchTimeout();
|
|
}
|
|
|
|
_onSearchTimeout() {
|
|
this._searchTimeoutId = 0;
|
|
this._doSearch();
|
|
return GLib.SOURCE_REMOVE;
|
|
}
|
|
|
|
setTerms(terms) {
|
|
// 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;
|
|
|
|
this._startingSearch = true;
|
|
|
|
this._cancellable.cancel();
|
|
this._cancellable.reset();
|
|
|
|
if (terms.length == 0) {
|
|
this._reset();
|
|
return;
|
|
}
|
|
|
|
let isSubSearch = false;
|
|
if (this._terms.length > 0)
|
|
isSubSearch = searchString.indexOf(previousSearchString) == 0;
|
|
|
|
this._terms = terms;
|
|
this._isSubSearch = isSubSearch;
|
|
this._updateSearchProgress();
|
|
|
|
if (this._searchTimeoutId == 0)
|
|
this._searchTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, this._onSearchTimeout.bind(this));
|
|
|
|
let escapedTerms = this._terms.map(term => Shell.util_regex_escape(term));
|
|
this._highlightRegex = new RegExp(`(${escapedTerms.join('|')})`, 'gi');
|
|
|
|
this.emit('terms-changed');
|
|
}
|
|
|
|
_onPan(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(provider, actor) {
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
|
|
}
|
|
|
|
_ensureProviderDisplay(provider) {
|
|
if (provider.display)
|
|
return;
|
|
|
|
let providerDisplay;
|
|
if (provider.appInfo)
|
|
providerDisplay = new ListSearchResults(provider, this);
|
|
else
|
|
providerDisplay = new GridSearchResults(provider, this);
|
|
|
|
providerDisplay.connect('key-focus-in', this._keyFocusIn.bind(this));
|
|
providerDisplay.actor.hide();
|
|
this._content.add(providerDisplay.actor);
|
|
provider.display = providerDisplay;
|
|
}
|
|
|
|
_clearDisplay() {
|
|
this._providers.forEach(provider => {
|
|
provider.display.clear();
|
|
});
|
|
}
|
|
|
|
_maybeSetInitialSelection() {
|
|
let newDefaultResult = null;
|
|
|
|
let providers = this._providers;
|
|
for (let i = 0; i < providers.length; i++) {
|
|
let provider = providers[i];
|
|
let display = provider.display;
|
|
|
|
if (!display.actor.visible)
|
|
continue;
|
|
|
|
let firstResult = display.getFirstResult();
|
|
if (firstResult) {
|
|
newDefaultResult = firstResult;
|
|
break; // select this one!
|
|
}
|
|
}
|
|
|
|
if (newDefaultResult != this._defaultResult) {
|
|
this._setSelected(this._defaultResult, false);
|
|
this._setSelected(newDefaultResult, this._highlightDefault);
|
|
|
|
this._defaultResult = newDefaultResult;
|
|
}
|
|
}
|
|
|
|
get searchInProgress() {
|
|
if (this._startingSearch)
|
|
return true;
|
|
|
|
return this._providers.some(p => p.searchInProgress);
|
|
}
|
|
|
|
_updateSearchProgress() {
|
|
let haveResults = this._providers.some(provider => {
|
|
let display = provider.display;
|
|
return (display.getFirstResult() != null);
|
|
});
|
|
|
|
this._scrollView.visible = haveResults;
|
|
this._statusBin.visible = !haveResults;
|
|
|
|
if (!haveResults) {
|
|
if (this.searchInProgress) {
|
|
this._statusText.set_text(_("Searching…"));
|
|
} else {
|
|
this._statusText.set_text(_("No results."));
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateResults(provider, results) {
|
|
let terms = this._terms;
|
|
let display = provider.display;
|
|
|
|
display.updateSearch(results, terms, () => {
|
|
provider.searchInProgress = false;
|
|
|
|
this._maybeSetInitialSelection();
|
|
this._updateSearchProgress();
|
|
});
|
|
}
|
|
|
|
activateDefault() {
|
|
// If we have a search queued up, force the search now.
|
|
if (this._searchTimeoutId > 0)
|
|
this._doSearch();
|
|
|
|
if (this._defaultResult)
|
|
this._defaultResult.activate();
|
|
}
|
|
|
|
highlightDefault(highlight) {
|
|
this._highlightDefault = highlight;
|
|
this._setSelected(this._defaultResult, highlight);
|
|
}
|
|
|
|
popupMenuDefault() {
|
|
// If we have a search queued up, force the search now.
|
|
if (this._searchTimeoutId > 0)
|
|
this._doSearch();
|
|
|
|
if (this._defaultResult)
|
|
this._defaultResult.actor.popup_menu();
|
|
}
|
|
|
|
navigateFocus(direction) {
|
|
let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL;
|
|
if (direction == St.DirectionType.TAB_BACKWARD ||
|
|
direction == (rtl ? St.DirectionType.RIGHT
|
|
: St.DirectionType.LEFT) ||
|
|
direction == St.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);
|
|
}
|
|
|
|
_setSelected(result, selected) {
|
|
if (!result)
|
|
return;
|
|
|
|
if (selected) {
|
|
result.actor.add_style_pseudo_class('selected');
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, result.actor);
|
|
} else {
|
|
result.actor.remove_style_pseudo_class('selected');
|
|
}
|
|
}
|
|
|
|
highlightTerms(description) {
|
|
if (!description)
|
|
return '';
|
|
|
|
if (!this._highlightRegex)
|
|
return description;
|
|
|
|
return description.replace(this._highlightRegex, '<b>$1</b>');
|
|
}
|
|
};
|
|
Signals.addSignalMethods(SearchResults.prototype);
|
|
|
|
var ProviderInfo = GObject.registerClass(
|
|
class ProviderInfo extends St.Button {
|
|
_init(provider) {
|
|
this.provider = provider;
|
|
super._init({ style_class: 'search-provider-icon',
|
|
reactive: true,
|
|
can_focus: true,
|
|
accessible_name: provider.appInfo.get_name(),
|
|
track_hover: true });
|
|
|
|
this._content = new St.BoxLayout({ vertical: false,
|
|
style_class: 'list-search-provider-content' });
|
|
this.set_child(this._content);
|
|
|
|
let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
|
|
gicon: provider.appInfo.get_icon() });
|
|
|
|
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);
|
|
|
|
|
|
this._content.add_actor(icon);
|
|
this._content.add_actor(detailsBox);
|
|
}
|
|
|
|
get PROVIDER_ICON_SIZE() {
|
|
return 32;
|
|
}
|
|
|
|
animateLaunch() {
|
|
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);
|
|
}
|
|
|
|
setMoreCount(count) {
|
|
this._moreLabel.text = ngettext("%d more", "%d more", count).format(count);
|
|
this._moreLabel.visible = count > 0;
|
|
}
|
|
});
|