gnome-shell/js/ui/searchDisplay.js
Seif Lotfy fb5a3a53fa Don't refresh all the search providers in O(n^2) fashion; just refresh the one that changed
This modification reduces the faulty clearing and redrawing of the
results.  The old code refreshed the view for every async
search provider by iterating through all result sets (one result set per
provider) and refreshing all providers. In this case there were 40
redraws. This patch adds a new method _clearDisplayForProvider that
allows us to refresh only providers with changed results.
2011-03-04 16:42:15 -05:00

426 lines
15 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Gtk = imports.gi.Gtk;
const St = imports.gi.St;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const Search = imports.ui.search;
const MAX_SEARCH_RESULTS_ROWS = 1;
function SearchResult(provider, metaInfo, terms) {
this._init(provider, metaInfo, terms);
}
SearchResult.prototype = {
_init: function(provider, metaInfo, terms) {
this.provider = provider;
this.metaInfo = metaInfo;
this.actor = new St.Button({ style_class: 'search-result',
reactive: true,
x_align: St.Align.START,
y_fill: true });
this.actor._delegate = this;
let content = provider.createResultActor(metaInfo, terms);
if (content == null) {
content = new St.Bin({ style_class: 'search-result-content',
reactive: true,
track_hover: true });
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
{ createIcon: this.metaInfo['createIcon'] });
content.set_child(icon.actor);
}
this._content = content;
this.actor.set_child(content);
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
let draggable = DND.makeDraggable(this.actor);
draggable.connect('drag-begin',
Lang.bind(this, function() {
Main.overview.beginItemDrag(this);
}));
draggable.connect('drag-end',
Lang.bind(this, function() {
Main.overview.endItemDrag(this);
}));
},
setSelected: function(selected) {
if (selected)
this._content.add_style_pseudo_class('selected');
else
this._content.remove_style_pseudo_class('selected');
},
activate: function() {
this.provider.activateResult(this.metaInfo.id);
Main.overview.toggle();
},
_onResultClicked: function(actor) {
this.activate();
},
getDragActorSource: function() {
return this.metaInfo['icon'];
},
getDragActor: function(stageX, stageY) {
return new Clutter.Clone({ source: this.metaInfo['icon'] });
},
shellWorkspaceLaunch: function(params) {
if (this.provider.dragActivateResult)
this.provider.dragActivateResult(this.metaInfo.id, params);
else
this.provider.activateResult(this.metaInfo.id, params);
}
};
function GridSearchResults(provider) {
this._init(provider);
}
GridSearchResults.prototype = {
__proto__: Search.SearchResultDisplay.prototype,
_init: function(provider) {
Search.SearchResultDisplay.prototype._init.call(this, provider);
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
xAlign: St.Align.START });
this.actor = new St.Bin({ x_align: St.Align.START });
this.actor.set_child(this._grid.actor);
this.selectionIndex = -1;
},
getVisibleResultCount: function() {
return this._grid.visibleItemsCount();
},
renderResults: function(results, terms) {
for (let i = 0; i < results.length; i++) {
let result = results[i];
let meta = this.provider.getResultMeta(result);
let display = new SearchResult(this.provider, meta, terms);
this._grid.addItem(display.actor);
}
},
clear: function () {
this._grid.removeAll();
this.selectionIndex = -1;
},
selectIndex: function (index) {
let nVisible = this.getVisibleResultCount();
if (this.selectionIndex >= 0) {
let prevActor = this._grid.getItemAtIndex(this.selectionIndex);
prevActor._delegate.setSelected(false);
}
this.selectionIndex = -1;
if (index >= nVisible)
return false;
else if (index < 0)
return false;
let targetActor = this._grid.getItemAtIndex(index);
targetActor._delegate.setSelected(true);
this.selectionIndex = index;
return true;
},
activateSelected: function() {
if (this.selectionIndex < 0)
return;
let targetActor = this._grid.getItemAtIndex(this.selectionIndex);
targetActor._delegate.activate();
}
};
function SearchResults(searchSystem, openSearchSystem) {
this._init(searchSystem, openSearchSystem);
}
SearchResults.prototype = {
_init: function(searchSystem, openSearchSystem) {
this._searchSystem = searchSystem;
this._searchSystem.connect('results-updated', Lang.bind(this, this._updateResults));
this._openSearchSystem = openSearchSystem;
this.actor = new St.BoxLayout({ name: 'searchResults',
vertical: true });
this._content = new St.BoxLayout({ name: 'searchResultsContent',
vertical: true });
let scrollView = new St.ScrollView({ x_fill: true,
y_fill: false,
vfade: true });
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
scrollView.add_actor(this._content);
this.actor.add(scrollView, { x_fill: true,
y_fill: false,
expand: true,
x_align: St.Align.START,
y_align: St.Align.START });
this.actor.connect('notify::mapped', Lang.bind(this,
function() {
if (!this.actor.mapped)
return;
let adjustment = scrollView.vscroll.adjustment;
let direction = Overview.SwipeScrollDirection.VERTICAL;
Main.overview.setScrollAdjustment(adjustment, direction);
}));
this._statusText = new St.Label({ style_class: 'search-statustext' });
this._content.add(this._statusText);
this._selectedProvider = -1;
this._providers = this._searchSystem.getProviders();
this._providerMeta = [];
this._providerMetaResults = {};
for (let i = 0; i < this._providers.length; i++) {
this.createProviderMeta(this._providers[i]);
this._providerMetaResults[this.providers[i].title] = [];
}
this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' });
this.actor.add(this._searchProvidersBox);
this._openSearchProviders = [];
this._openSearchSystem.connect('changed', Lang.bind(this, this._updateOpenSearchProviderButtons));
this._updateOpenSearchProviderButtons();
},
_updateOpenSearchProviderButtons: function() {
this._selectedOpenSearchButton = -1;
for (let i = 0; i < this._openSearchProviders.length; i++)
this._openSearchProviders[i].actor.destroy();
this._openSearchProviders = this._openSearchSystem.getProviders();
for (let i = 0; i < this._openSearchProviders.length; i++)
this._createOpenSearchProviderButton(this._openSearchProviders[i]);
},
_updateOpenSearchButtonState: function() {
for (let i = 0; i < this._openSearchProviders.length; i++) {
if (i == this._selectedOpenSearchButton)
this._openSearchProviders[i].actor.add_style_pseudo_class('selected');
else
this._openSearchProviders[i].actor.remove_style_pseudo_class('selected');
}
},
_createOpenSearchProviderButton: function(provider) {
let button = new St.Button({ style_class: 'dash-search-button',
reactive: true,
x_fill: true,
y_align: St.Align.MIDDLE });
let bin = new St.Bin({ x_fill: false,
x_align:St.Align.MIDDLE });
button.connect('clicked', Lang.bind(this, function() {
this._openSearchSystem.activateResult(provider.id);
}));
let title = new St.Label({ text: provider.name,
style_class: 'dash-search-button-label' });
bin.set_child(title);
button.set_child(bin);
provider.actor = button;
this._searchProvidersBox.add(button);
},
createProviderMeta: function(provider) {
let providerBox = new St.BoxLayout({ style_class: 'search-section',
vertical: true });
let title = new St.Label({ style_class: 'search-section-header',
text: provider.title });
providerBox.add(title);
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
x_fill: true,
y_fill: true });
providerBox.add(resultDisplayBin, { expand: true });
let resultDisplay = provider.createResultContainerActor();
if (resultDisplay == null) {
resultDisplay = new GridSearchResults(provider);
}
resultDisplayBin.set_child(resultDisplay.actor);
this._providerMeta.push({ actor: providerBox,
resultDisplay: resultDisplay });
this._content.add(providerBox);
},
_clearDisplay: function() {
this._selectedProvider = -1;
this._visibleResultsCount = 0;
for (let i = 0; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
meta.resultDisplay.clear();
meta.actor.hide();
}
},
_clearDisplayForProvider: function(index) {
let meta = this._providerMeta[index];
meta.resultDisplay.clear();
meta.actor.hide();
},
reset: function() {
this._searchSystem.reset();
this._statusText.hide();
this._clearDisplay();
this._selectedOpenSearchButton = -1;
this._updateOpenSearchButtonState();
},
startingSearch: function() {
this.reset();
this._statusText.set_text(_("Searching..."));
this._statusText.show();
},
doSearch: function (searchString) {
this._searchSystem.updateSearch(searchString);
},
_metaForProvider: function(provider) {
return this._providerMeta[this._providers.indexOf(provider)];
},
_updateResults: function(searchSystem, results) {
if (results.length == 0) {
this._statusText.set_text(_("No matching results."));
this._statusText.show();
} else {
this._selectedOpenSearchButton = -1;
this._updateOpenSearchButtonState();
this._statusText.hide();
}
let terms = searchSystem.getTerms();
this._openSearchSystem.setSearchTerms(terms);
for (let i = 0; i < results.length; i++) {
let [provider, providerResults] = results[i];
if (providerResults.length == 0)
this._clearDisplayForProvider(i)
else {
if (this._providerMetaResults[provider.title] != providerResults) {
this._providerMetaResults[provider.title] = providerResults;
this._clearDisplayForProvider(i);
let meta = this._metaForProvider(provider);
meta.actor.show();
meta.resultDisplay.renderResults(providerResults, terms);
}
}
}
if (this._selectedOpenSearchButton == -1)
this.selectDown(false);
return true;
},
_modifyActorSelection: function(resultDisplay, up) {
let success;
let index = resultDisplay.getSelectionIndex();
if (up && index == -1)
index = resultDisplay.getVisibleResultCount() - 1;
else if (up)
index = index - 1;
else
index = index + 1;
return resultDisplay.selectIndex(index);
},
selectUp: function(recursing) {
if (this._selectedOpenSearchButton == -1) {
for (let i = this._selectedProvider; i >= 0; i--) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, true);
if (success) {
this._selectedProvider = i;
return;
}
}
}
if (this._selectedOpenSearchButton == -1)
this._selectedOpenSearchButton = this._openSearchProviders.length;
this._selectedOpenSearchButton--;
this._updateOpenSearchButtonState();
if (this._selectedOpenSearchButton >= 0)
return;
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = this._providerMeta.length - 1;
this.selectUp(true);
}
},
selectDown: function(recursing) {
let current = this._selectedProvider;
if (this._selectedOpenSearchButton == -1) {
if (current == -1)
current = 0;
for (let i = current; i < this._providerMeta.length; i++) {
let meta = this._providerMeta[i];
if (!meta.actor.visible)
continue;
let success = this._modifyActorSelection(meta.resultDisplay, false);
if (success) {
this._selectedProvider = i;
return;
}
}
}
this._selectedOpenSearchButton++;
if (this._selectedOpenSearchButton < this._openSearchProviders.length) {
this._updateOpenSearchButtonState();
return;
}
this._selectedOpenSearchButton = -1;
this._updateOpenSearchButtonState();
if (this._providerMeta.length > 0 && !recursing) {
this._selectedProvider = 0;
this.selectDown(true);
}
},
activateSelected: function() {
if (this._selectedOpenSearchButton != -1) {
let provider = this._openSearchProviders[this._selectedOpenSearchButton];
this._openSearchSystem.activateResult(provider.id);
Main.overview.hide();
return;
}
let current = this._selectedProvider;
if (current < 0)
return;
let meta = this._providerMeta[current];
let resultDisplay = meta.resultDisplay;
resultDisplay.activateSelected();
Main.overview.hide();
}
};