2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2009-11-29 22:45:30 +00:00
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2011-01-17 21:38:47 +00:00
|
|
|
const Lang = imports.lang;
|
2014-09-11 21:15:50 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2013-10-30 16:38:46 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2013-10-29 19:49:05 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
|
|
|
const Meta = imports.gi.Meta;
|
2009-11-29 22:45:30 +00:00
|
|
|
const Signals = imports.signals;
|
2014-06-17 19:31:53 +00:00
|
|
|
const Shell = imports.gi.Shell;
|
2013-10-29 19:49:05 +00:00
|
|
|
const St = imports.gi.St;
|
|
|
|
const Atk = imports.gi.Atk;
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
2013-10-29 19:49:05 +00:00
|
|
|
const DND = imports.ui.dnd;
|
|
|
|
const IconGrid = imports.ui.iconGrid;
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
const Overview = imports.ui.overview;
|
2013-10-30 16:38:46 +00:00
|
|
|
const RemoteSearch = imports.ui.remoteSearch;
|
2013-10-29 19:49:05 +00:00
|
|
|
const 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;
|
|
|
|
var MAX_GRID_SEARCH_RESULTS_ROWS = 1;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var MaxWidthBin = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'MaxWidthBin',
|
|
|
|
Extends: St.Bin,
|
|
|
|
|
|
|
|
vfunc_allocate: function(box, flags) {
|
|
|
|
let themeNode = this.get_theme_node();
|
|
|
|
let maxWidth = themeNode.get_max_width();
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let adjustedBox = box;
|
|
|
|
|
|
|
|
if (availWidth > maxWidth) {
|
|
|
|
let excessWidth = availWidth - maxWidth;
|
|
|
|
adjustedBox.x1 += Math.floor(excessWidth / 2);
|
|
|
|
adjustedBox.x2 -= Math.floor(excessWidth / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.parent(adjustedBox, flags);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var SearchResult = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'SearchResult',
|
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(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
|
|
|
|
|
|
|
this.actor = new St.Button({ reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
track_hover: true,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_fill: true });
|
|
|
|
|
|
|
|
this.actor._delegate = this;
|
2013-10-30 21:42:49 +00:00
|
|
|
this.actor.connect('clicked', Lang.bind(this, this.activate));
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-10-30 21:42:49 +00:00
|
|
|
activate: function() {
|
2013-02-09 00:04:24 +00:00
|
|
|
this.emit('activate', this.metaInfo.id);
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|
2013-02-09 00:04:24 +00:00
|
|
|
Signals.addSignalMethods(SearchResult.prototype);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var ListSearchResult = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'ListSearchResult',
|
|
|
|
Extends: SearchResult,
|
|
|
|
|
2017-06-20 19:21:20 +00:00
|
|
|
ICON_SIZE: 24,
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(provider, metaInfo, resultsView) {
|
|
|
|
this.parent(provider, metaInfo, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this.actor.style_class = 'list-search-result';
|
|
|
|
this.actor.x_fill = true;
|
|
|
|
|
|
|
|
let content = new St.BoxLayout({ style_class: 'list-search-result-content',
|
|
|
|
vertical: false });
|
|
|
|
this.actor.set_child(content);
|
|
|
|
|
2017-06-27 22:01:13 +00:00
|
|
|
this._termsChangedId = 0;
|
|
|
|
|
2017-06-20 19:13:48 +00:00
|
|
|
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 });
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
// An icon for, or thumbnail of, content
|
|
|
|
let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
|
|
|
|
if (icon) {
|
2017-06-20 19:13:48 +00:00
|
|
|
titleBox.add(icon);
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 19:13:48 +00:00
|
|
|
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 });
|
2017-07-12 23:51:59 +00:00
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
this.actor.label_actor = title;
|
|
|
|
|
|
|
|
if (this.metaInfo['description']) {
|
2017-06-27 22:01:13 +00:00
|
|
|
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 });
|
2017-06-20 19:13:48 +00:00
|
|
|
|
2017-06-27 22:01:13 +00:00
|
|
|
this._termsChangedId =
|
|
|
|
this._resultsView.connect('terms-changed',
|
|
|
|
Lang.bind(this, this._highlightTerms));
|
|
|
|
|
|
|
|
this._highlightTerms();
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
|
|
},
|
|
|
|
|
|
|
|
_highlightTerms: function() {
|
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);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy: function() {
|
|
|
|
if (this._termsChangedId)
|
|
|
|
this._resultsView.disconnect(this._termsChangedId);
|
|
|
|
this._termsChangedId = 0;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var GridSearchResult = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'GridSearchResult',
|
|
|
|
Extends: SearchResult,
|
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(provider, metaInfo, resultsView) {
|
|
|
|
this.parent(provider, metaInfo, resultsView);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this.actor.style_class = 'grid-search-result';
|
|
|
|
|
2014-08-20 16:39:02 +00:00
|
|
|
this.icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
|
|
|
{ createIcon: this.metaInfo['createIcon'] });
|
|
|
|
let content = new St.Bin({ child: this.icon.actor });
|
|
|
|
this.actor.set_child(content);
|
|
|
|
this.actor.label_actor = this.icon.label;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var SearchResultsBase = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'SearchResultsBase',
|
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(provider, resultsView) {
|
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 = [];
|
|
|
|
|
|
|
|
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 });
|
|
|
|
|
2017-07-13 18:34:47 +00:00
|
|
|
let separator = new St.Widget({ style_class: 'search-section-separator' });
|
|
|
|
this.actor.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
|
|
|
|
2017-05-03 08:28:31 +00:00
|
|
|
this._clipboard = St.Clipboard.get_default();
|
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._cancellable = new Gio.Cancellable();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
this._terms = [];
|
|
|
|
},
|
|
|
|
|
2014-08-20 16:39:02 +00:00
|
|
|
_createResultDisplay: function(meta) {
|
|
|
|
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;
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
clear: function() {
|
2014-02-23 15:18:46 +00:00
|
|
|
for (let resultId in this._resultDisplays)
|
|
|
|
this._resultDisplays[resultId].actor.destroy();
|
2013-02-08 23:59:15 +00:00
|
|
|
this._resultDisplays = {};
|
2013-10-29 19:49:05 +00:00
|
|
|
this._clearResultDisplay();
|
|
|
|
this.actor.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
_keyFocusIn: function(actor) {
|
|
|
|
this.emit('key-focus-in', actor);
|
|
|
|
},
|
|
|
|
|
2013-02-09 00:04:24 +00:00
|
|
|
_activateResult: function(result, id) {
|
2013-10-31 15:59:23 +00:00
|
|
|
this.provider.activateResult(id, this._terms);
|
2017-05-03 08:28:31 +00:00
|
|
|
if (result.metaInfo.clipboardText)
|
|
|
|
this._clipboard.set_text(St.ClipboardType.CLIPBOARD, result.metaInfo.clipboardText);
|
2013-10-31 15:59:23 +00:00
|
|
|
Main.overview.toggle();
|
2013-02-09 00:04:24 +00:00
|
|
|
},
|
|
|
|
|
2017-07-12 23:51:59 +00:00
|
|
|
_setMoreCount: function(count) {
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_ensureResultActors: function(results, callback) {
|
|
|
|
let metasNeeded = results.filter(Lang.bind(this, function(resultId) {
|
|
|
|
return this._resultDisplays[resultId] === undefined;
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (metasNeeded.length === 0) {
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(true);
|
2013-02-08 23:59:15 +00:00
|
|
|
} else {
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
this._cancellable.cancel();
|
|
|
|
this._cancellable.reset();
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
this.provider.getResultMetas(metasNeeded, Lang.bind(this, function(metas) {
|
2014-02-24 16:24:42 +00:00
|
|
|
if (metas.length != metasNeeded.length) {
|
2015-01-07 10:15:30 +00:00
|
|
|
log('Wrong number of result metas returned by search provider ' + this.provider.id +
|
|
|
|
': expected ' + metasNeeded.length + ' but got ' + metas.length);
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2015-03-08 23:20:10 +00:00
|
|
|
if (metas.some(function(meta) {
|
|
|
|
return !meta.name || !meta.id;
|
|
|
|
})) {
|
|
|
|
log('Invalid result meta returned from search provider ' + this.provider.id);
|
|
|
|
callback(false);
|
|
|
|
return;
|
|
|
|
}
|
2014-02-24 16:24:42 +00:00
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
metasNeeded.forEach(Lang.bind(this, function(resultId, i) {
|
|
|
|
let meta = metas[i];
|
|
|
|
let display = this._createResultDisplay(meta);
|
|
|
|
display.connect('activate', Lang.bind(this, this._activateResult));
|
|
|
|
display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this._resultDisplays[resultId] = display;
|
|
|
|
}));
|
2014-02-24 16:24:42 +00:00
|
|
|
callback(true);
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
}), this._cancellable);
|
2013-02-08 23:59:15 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
updateSearch: function(providerResults, terms, callback) {
|
|
|
|
this._terms = terms;
|
|
|
|
if (providerResults.length == 0) {
|
|
|
|
this._clearResultDisplay();
|
|
|
|
this.actor.hide();
|
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
let maxResults = this._getMaxDisplayedResults();
|
|
|
|
let results = this.provider.filterResults(providerResults, maxResults);
|
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
|
|
|
|
2014-02-24 16:24:42 +00:00
|
|
|
this._ensureResultActors(results, Lang.bind(this, function(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.
|
|
|
|
this.actor.hide();
|
|
|
|
this._clearResultDisplay();
|
2013-02-08 23:59:15 +00:00
|
|
|
results.forEach(Lang.bind(this, function(resultId) {
|
|
|
|
this._addItem(this._resultDisplays[resultId]);
|
|
|
|
}));
|
2017-07-12 23:51:59 +00:00
|
|
|
this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
|
2013-10-29 19:49:05 +00:00
|
|
|
this.actor.show();
|
|
|
|
callback();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var ListSearchResults = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'ListSearchResults',
|
|
|
|
Extends: SearchResultsBase,
|
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(provider, resultsView) {
|
|
|
|
this.parent(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);
|
|
|
|
this.providerInfo.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
|
|
|
this.providerInfo.connect('clicked', Lang.bind(this,
|
2013-10-29 19:49:05 +00:00
|
|
|
function() {
|
2017-07-12 23:51:59 +00:00
|
|
|
this.providerInfo.animateLaunch();
|
2013-10-29 19:49:05 +00:00
|
|
|
provider.launchSearch(this._terms);
|
|
|
|
Main.overview.toggle();
|
|
|
|
}));
|
|
|
|
|
2017-07-12 23:51:59 +00:00
|
|
|
this._container.add(this.providerInfo, { x_fill: false,
|
2013-10-29 19:49:05 +00:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2017-07-12 23:51:59 +00:00
|
|
|
_setMoreCount: function(count) {
|
|
|
|
this.providerInfo.setMoreCount(count);
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_getMaxDisplayedResults: function() {
|
|
|
|
return MAX_LIST_SEARCH_RESULTS_ROWS;
|
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_clearResultDisplay: function () {
|
|
|
|
this._content.remove_all_children();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_createResultDisplay: function(meta) {
|
2017-06-27 21:58:07 +00:00
|
|
|
return this.parent(meta, this._resultsView) ||
|
|
|
|
new ListSearchResult(this.provider, meta, this._resultsView);
|
2013-02-08 23:59:15 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_addItem: function(display) {
|
|
|
|
this._content.add_actor(display.actor);
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getFirstResult: function() {
|
|
|
|
if (this._content.get_n_children() > 0)
|
|
|
|
return this._content.get_child_at_index(0)._delegate;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(ListSearchResults.prototype);
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var GridSearchResults = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'GridSearchResults',
|
|
|
|
Extends: SearchResultsBase,
|
|
|
|
|
2017-06-27 21:58:07 +00:00
|
|
|
_init: function(provider, resultsView) {
|
|
|
|
this.parent(provider, resultsView);
|
2014-07-15 15:09:41 +00:00
|
|
|
// We need to use the parent container to know how much results we can show.
|
|
|
|
// None of the actors in this class can be used for that, since the main actor
|
|
|
|
// goes hidden when no results are displayed, and then it lost its allocation.
|
|
|
|
// Then on the next use of _getMaxDisplayedResults allocation is 0, en therefore
|
|
|
|
// it doesn't show any result although we have some.
|
2017-06-27 21:58:07 +00:00
|
|
|
this._parentContainer = resultsView.actor;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
|
|
|
|
xAlign: St.Align.START });
|
|
|
|
this._bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
|
|
|
this._bin.set_child(this._grid.actor);
|
|
|
|
|
|
|
|
this._resultDisplayBin.set_child(this._bin);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getMaxDisplayedResults: function() {
|
2014-07-15 15:09:41 +00:00
|
|
|
let parentThemeNode = this._parentContainer.get_theme_node();
|
|
|
|
let availableWidth = parentThemeNode.adjust_for_width(this._parentContainer.width);
|
|
|
|
return this._grid.columnsForWidth(availableWidth) * this._grid.getRowLimit();
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_clearResultDisplay: function () {
|
|
|
|
this._grid.removeAll();
|
|
|
|
},
|
|
|
|
|
2013-02-08 23:59:15 +00:00
|
|
|
_createResultDisplay: function(meta) {
|
2017-06-27 21:58:07 +00:00
|
|
|
return this.parent(meta, this._resultsView) ||
|
|
|
|
new GridSearchResult(this.provider, meta, this._resultsView);
|
2013-02-08 23:59:15 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_addItem: function(display) {
|
2013-10-30 17:06:56 +00:00
|
|
|
this._grid.addItem(display);
|
2013-02-08 23:59:15 +00:00
|
|
|
},
|
|
|
|
|
2013-10-29 19:49:05 +00:00
|
|
|
getFirstResult: function() {
|
|
|
|
if (this._grid.visibleItemsCount() > 0)
|
|
|
|
return this._grid.getItemAtIndex(0)._delegate;
|
|
|
|
else
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(GridSearchResults.prototype);
|
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var SearchResults = new Lang.Class({
|
2013-10-29 19:49:05 +00:00
|
|
|
Name: 'SearchResults',
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_init: function() {
|
2013-10-29 19:49:05 +00:00
|
|
|
this.actor = new St.BoxLayout({ name: 'searchResults',
|
|
|
|
vertical: true });
|
|
|
|
|
|
|
|
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
|
|
|
vertical: true });
|
|
|
|
this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true,
|
|
|
|
child: this._content });
|
|
|
|
|
|
|
|
let scrollChild = new St.BoxLayout();
|
|
|
|
scrollChild.add(this._contentBin, { expand: true });
|
|
|
|
|
|
|
|
this._scrollView = new St.ScrollView({ x_fill: true,
|
|
|
|
y_fill: false,
|
|
|
|
overlay_scrollbars: true,
|
|
|
|
style_class: 'search-display vfade' });
|
|
|
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
|
|
|
this._scrollView.add_actor(scrollChild);
|
|
|
|
let action = new Clutter.PanAction({ interpolate: true });
|
|
|
|
action.connect('pan', Lang.bind(this, this._onPan));
|
|
|
|
this._scrollView.add_action(action);
|
|
|
|
|
|
|
|
this.actor.add(this._scrollView, { x_fill: true,
|
|
|
|
y_fill: true,
|
|
|
|
expand: true,
|
|
|
|
x_align: St.Align.START,
|
|
|
|
y_align: St.Align.START });
|
|
|
|
|
|
|
|
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
|
|
|
this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
|
|
|
y_align: St.Align.MIDDLE });
|
2014-09-11 21:47:49 +00:00
|
|
|
this.actor.add(this._statusBin, { expand: true });
|
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 });
|
2014-09-11 19:45:51 +00:00
|
|
|
this._searchSettings.connect('changed::disabled', Lang.bind(this, this._reloadRemoteProviders));
|
2015-03-20 06:57:32 +00:00
|
|
|
this._searchSettings.connect('changed::enabled', Lang.bind(this, this._reloadRemoteProviders));
|
2014-09-11 19:45:51 +00:00
|
|
|
this._searchSettings.connect('changed::disable-external', Lang.bind(this, this._reloadRemoteProviders));
|
|
|
|
this._searchSettings.connect('changed::sort-order', Lang.bind(this, this._reloadRemoteProviders));
|
|
|
|
|
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());
|
|
|
|
this._reloadRemoteProviders();
|
2014-09-11 19:45:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_reloadRemoteProviders: function() {
|
|
|
|
let remoteProviders = this._providers.filter(function(provider) {
|
|
|
|
return provider.isRemoteProvider;
|
|
|
|
});
|
|
|
|
remoteProviders.forEach(Lang.bind(this, function(provider) {
|
|
|
|
this._unregisterProvider(provider);
|
|
|
|
}));
|
|
|
|
|
2015-03-20 06:58:19 +00:00
|
|
|
RemoteSearch.loadRemoteSearchProviders(this._searchSettings, Lang.bind(this, function(providers) {
|
2014-09-11 19:45:51 +00:00
|
|
|
providers.forEach(Lang.bind(this, this._registerProvider));
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
_registerProvider: function (provider) {
|
|
|
|
this._providers.push(provider);
|
|
|
|
this._ensureProviderDisplay(provider);
|
|
|
|
},
|
|
|
|
|
|
|
|
_unregisterProvider: function (provider) {
|
|
|
|
let index = this._providers.indexOf(provider);
|
|
|
|
this._providers.splice(index, 1);
|
|
|
|
|
|
|
|
if (provider.display)
|
|
|
|
provider.display.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
_gotResults: function(results, provider) {
|
|
|
|
this._results[provider.id] = results;
|
|
|
|
this._updateResults(provider, results);
|
|
|
|
},
|
|
|
|
|
2014-09-30 06:19:28 +00:00
|
|
|
_clearSearchTimeout: function() {
|
|
|
|
if (this._searchTimeoutId > 0) {
|
|
|
|
GLib.source_remove(this._searchTimeoutId);
|
|
|
|
this._searchTimeoutId = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-09-30 06:22:14 +00:00
|
|
|
_reset: function() {
|
|
|
|
this._terms = [];
|
|
|
|
this._results = {};
|
|
|
|
this._clearDisplay();
|
|
|
|
this._clearSearchTimeout();
|
|
|
|
this._defaultResult = null;
|
|
|
|
this._startingSearch = false;
|
|
|
|
|
|
|
|
this._updateSearchProgress();
|
|
|
|
},
|
|
|
|
|
2014-09-11 21:15:50 +00:00
|
|
|
_doSearch: function() {
|
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 = {};
|
|
|
|
|
|
|
|
this._providers.forEach(Lang.bind(this, function(provider) {
|
|
|
|
provider.searchInProgress = true;
|
|
|
|
|
|
|
|
let previousProviderResults = previousResults[provider.id];
|
|
|
|
if (this._isSubSearch && previousProviderResults)
|
|
|
|
provider.getSubsearchResultSet(previousProviderResults, this._terms, Lang.bind(this, this._gotResults, provider), this._cancellable);
|
|
|
|
else
|
|
|
|
provider.getInitialResultSet(this._terms, Lang.bind(this, this._gotResults, provider), this._cancellable);
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._updateSearchProgress();
|
|
|
|
|
2014-09-30 06:19:28 +00:00
|
|
|
this._clearSearchTimeout();
|
2014-09-11 23:51:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onSearchTimeout: function() {
|
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;
|
|
|
|
},
|
|
|
|
|
|
|
|
setTerms: function(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)
|
2014-09-11 23:51:12 +00:00
|
|
|
this._searchTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, Lang.bind(this, this._onSearchTimeout));
|
2017-06-27 22:01:13 +00:00
|
|
|
|
|
|
|
let escapedTerms = this._terms.map(term => Shell.util_regex_escape(term));
|
|
|
|
this._highlightRegex = new RegExp(`(${escapedTerms.join('|')})`, 'gi');
|
|
|
|
|
|
|
|
this.emit('terms-changed');
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onPan: function(action) {
|
|
|
|
let [dist, dx, dy] = action.get_motion_delta(0);
|
|
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
|
|
|
adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_keyFocusIn: function(provider, actor) {
|
|
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, actor);
|
|
|
|
},
|
|
|
|
|
2013-10-30 16:38:46 +00:00
|
|
|
_ensureProviderDisplay: function(provider) {
|
|
|
|
if (provider.display)
|
|
|
|
return;
|
|
|
|
|
2013-10-29 20:13:32 +00:00
|
|
|
let providerDisplay;
|
|
|
|
if (provider.appInfo)
|
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
|
|
|
|
|
|
|
providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn));
|
2014-09-30 01:51:24 +00:00
|
|
|
providerDisplay.actor.hide();
|
2013-10-29 19:49:05 +00:00
|
|
|
this._content.add(providerDisplay.actor);
|
2013-10-29 20:13:32 +00:00
|
|
|
provider.display = providerDisplay;
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_clearDisplay: function() {
|
2014-09-11 19:45:51 +00:00
|
|
|
this._providers.forEach(function(provider) {
|
2013-10-29 20:13:32 +00:00
|
|
|
provider.display.clear();
|
|
|
|
});
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_maybeSetInitialSelection: function() {
|
|
|
|
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
|
|
|
|
|
|
|
if (!display.actor.visible)
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-09-11 21:47:49 +00:00
|
|
|
get searchInProgress() {
|
|
|
|
if (this._startingSearch)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return this._providers.some(function(provider) {
|
|
|
|
return provider.searchInProgress;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateSearchProgress: function () {
|
2014-09-11 19:45:51 +00:00
|
|
|
let haveResults = this._providers.some(function(provider) {
|
2013-10-29 20:13:32 +00:00
|
|
|
let display = provider.display;
|
|
|
|
return (display.getFirstResult() != null);
|
|
|
|
});
|
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) {
|
2014-09-11 21:47:49 +00:00
|
|
|
if (this.searchInProgress) {
|
|
|
|
this._statusText.set_text(_("Searching…"));
|
|
|
|
} else {
|
|
|
|
this._statusText.set_text(_("No results."));
|
|
|
|
}
|
2014-09-11 23:14:54 +00:00
|
|
|
}
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
2014-09-11 19:45:51 +00:00
|
|
|
_updateResults: function(provider, results) {
|
|
|
|
let terms = this._terms;
|
2013-10-29 20:13:32 +00:00
|
|
|
let display = provider.display;
|
2013-10-29 19:49:05 +00:00
|
|
|
|
search: Make the internal search interface callback-based
Long ago, the search system worked in a synchronous manner: providers
were given a query, and results were collected in a single array of
[provider, results] pairs, and then the search display was updated
from that.
We introduced an asynchronous search system when we wanted to potentially
add a Zeitgeist search provider to the Shell in 3.2. For a while, search
providers were either async or sync, which worked by storing a dummy array
in the results, and adding a method for search providers to add results
later.
Later, we removed the search system entirely and ported the remaining
search providers to simply use the API to modify the empty array, but the
remains of the synchronous search system with its silly array still
lingered.
Finally, it's time to modernize. Promises^WCallbacks are the future.
Port the one remaining in-shell search engine (app search) to the new
callback based system, and simplify the remote search system in the
process.
2013-11-02 23:45:35 +00:00
|
|
|
display.updateSearch(results, terms, Lang.bind(this, function() {
|
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();
|
2013-10-29 19:49:05 +00:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
activateDefault: function() {
|
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();
|
|
|
|
},
|
|
|
|
|
|
|
|
highlightDefault: function(highlight) {
|
|
|
|
this._highlightDefault = highlight;
|
2014-08-20 16:39:02 +00:00
|
|
|
this._setSelected(this._defaultResult, highlight);
|
2013-10-29 19:49:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
navigateFocus: function(direction) {
|
|
|
|
let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL;
|
|
|
|
if (direction == Gtk.DirectionType.TAB_BACKWARD ||
|
|
|
|
direction == (rtl ? Gtk.DirectionType.RIGHT
|
|
|
|
: Gtk.DirectionType.LEFT) ||
|
|
|
|
direction == Gtk.DirectionType.UP) {
|
|
|
|
this.actor.navigate_focus(null, direction, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let from = this._defaultResult ? this._defaultResult.actor : null;
|
|
|
|
this.actor.navigate_focus(from, direction, false);
|
2014-08-20 16:39:02 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_setSelected: function(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');
|
|
|
|
}
|
2017-06-27 22:01:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
highlightTerms: function(description) {
|
|
|
|
if (!description)
|
|
|
|
return '';
|
|
|
|
|
|
|
|
if (!this._highlightRegex)
|
|
|
|
return description;
|
|
|
|
|
|
|
|
return description.replace(this._highlightRegex, '<b>$1</b>');
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|
2017-06-27 22:01:13 +00:00
|
|
|
Signals.addSignalMethods(SearchResults.prototype);
|
2013-10-29 19:49:05 +00:00
|
|
|
|
2017-07-18 17:41:25 +00:00
|
|
|
var ProviderInfo = new Lang.Class({
|
2017-07-12 23:51:59 +00:00
|
|
|
Name: 'ProviderInfo',
|
2013-10-29 19:49:05 +00:00
|
|
|
Extends: St.Button,
|
|
|
|
|
2017-06-20 19:21:20 +00:00
|
|
|
PROVIDER_ICON_SIZE: 32,
|
2013-10-29 19:49:05 +00:00
|
|
|
|
|
|
|
_init: function(provider) {
|
|
|
|
this.provider = provider;
|
|
|
|
this.parent({ style_class: 'search-provider-icon',
|
|
|
|
reactive: true,
|
|
|
|
can_focus: true,
|
|
|
|
accessible_name: provider.appInfo.get_name(),
|
|
|
|
track_hover: true });
|
|
|
|
|
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);
|
2014-06-17 19:31:53 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
animateLaunch: function() {
|
|
|
|
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-07-12 23:51:59 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
setMoreCount: function(count) {
|
|
|
|
this._moreLabel.text = _("%d more").format(count);
|
|
|
|
this._moreLabel.visible = count > 0;
|
2013-10-29 19:49:05 +00:00
|
|
|
}
|
|
|
|
});
|