diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index f76bb690c..48b527846 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -431,25 +431,25 @@ StTooltip StLabel { padding: 8px 0px; } -.dash-search-statustext, -.dash-search-section-header { +.search-statustext, +.search-section-header { padding: 4px 0px; spacing: 4px; } -.dash-search-section-results { +.search-section-results { color: #ffffff; } -.dash-search-section-list-results { +.search-section-list-results { spacing: 4px; } -.dash-search-result-content { +.search-result-content { padding: 3px; } -.dash-search-result-content:selected { +.search-result-content:selected { padding: 2px; border: 1px solid #5c5c5c; border-radius: 2px; diff --git a/js/Makefile.am b/js/Makefile.am index 47fac8237..b03cd07da 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -40,6 +40,7 @@ nobase_dist_js_DATA = \ ui/runDialog.js \ ui/scripting.js \ ui/search.js \ + ui/searchDisplay.js \ ui/shellDBus.js \ ui/statusIconDispatcher.js \ ui/statusMenu.js \ diff --git a/js/ui/dash.js b/js/ui/dash.js index 3347d2772..53767052b 100644 --- a/js/ui/dash.js +++ b/js/ui/dash.js @@ -17,12 +17,10 @@ const PlaceDisplay = imports.ui.placeDisplay; const Main = imports.ui.main; const Overview = imports.ui.overview; const Search = imports.ui.search; +const SearchDisplay = imports.ui.searchDisplay; const Tweener = imports.ui.tweener; const Workspace = imports.ui.workspace; -// 25 search results (per result type) should be enough for everyone -const MAX_RENDERED_SEARCH_RESULTS = 25; - /* * Returns the index in an array of a given length that is obtained * if the provided index is incremented by an increment and the array @@ -283,295 +281,6 @@ SearchEntry.prototype = { }; Signals.addSignalMethods(SearchEntry.prototype); -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.Clickable({ style_class: 'dash-search-result', - reactive: true, - x_align: St.Align.START, - x_fill: true, - y_fill: true }); - this.actor._delegate = this; - - let content = provider.createResultActor(metaInfo, terms); - if (content == null) { - content = new St.BoxLayout({ style_class: 'dash-search-result-content' }); - let title = new St.Label({ text: this.metaInfo['name'] }); - let icon = this.metaInfo['icon']; - content.add(icon, { y_fill: false }); - content.add(title, { expand: true, y_fill: false }); - } - 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, event) { - this.activate(); - }, - - getDragActorSource: function() { - return this.metaInfo['icon']; - }, - - getDragActor: function(stageX, stageY) { - return new Clutter.Clone({ source: this.metaInfo['icon'] }); - }, - - shellWorkspaceLaunch: function() { - if (this.provider.dragActivateResult) - this.provider.dragActivateResult(this.metaInfo.id); - else - this.provider.activateResult(this.metaInfo.id); - } -}; - -function OverflowSearchResults(provider) { - this._init(provider); -} - -OverflowSearchResults.prototype = { - __proto__: Search.SearchResultDisplay.prototype, - - _init: function(provider) { - Search.SearchResultDisplay.prototype._init.call(this, provider); - this.actor = new St.OverflowBox({ style_class: 'dash-search-section-list-results' }); - }, - - getVisibleResultCount: function() { - return this.actor.get_n_visible(); - }, - - renderResults: function(results, terms) { - for (let i = 0; i < results.length && i < MAX_RENDERED_SEARCH_RESULTS; i++) { - let result = results[i]; - let meta = this.provider.getResultMeta(result); - let display = new SearchResult(this.provider, meta, terms); - this.actor.add_actor(display.actor); - } - }, - - selectIndex: function(index) { - let nVisible = this.actor.get_n_visible(); - let children = this.actor.get_children(); - if (this.selectionIndex >= 0) { - let prevActor = children[this.selectionIndex]; - prevActor._delegate.setSelected(false); - } - this.selectionIndex = -1; - if (index >= nVisible) - return false; - else if (index < 0) - return false; - let targetActor = children[index]; - targetActor._delegate.setSelected(true); - this.selectionIndex = index; - return true; - }, - - activateSelected: function() { - let children = this.actor.get_children(); - let targetActor = children[this.selectionIndex]; - targetActor._delegate.activate(); - } -}; - -function SearchResults(searchSystem) { - this._init(searchSystem); -} - -SearchResults.prototype = { - _init: function(searchSystem) { - this._searchSystem = searchSystem; - - this.actor = new St.BoxLayout({ name: 'dashSearchResults', - vertical: true }); - this._statusText = new St.Label({ style_class: 'dash-search-statustext' }); - this.actor.add(this._statusText); - this._selectedProvider = -1; - this._providers = this._searchSystem.getProviders(); - this._providerMeta = []; - for (let i = 0; i < this._providers.length; i++) - this.createProviderMeta(this._providers[i]); - }, - - createProviderMeta: function(provider) { - let providerBox = new St.BoxLayout({ style_class: 'dash-search-section', - vertical: true }); - let titleButton = new St.Button({ style_class: 'dash-search-section-header', - reactive: true, - x_fill: true, - y_fill: true }); - titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); })); - providerBox.add(titleButton); - let titleBox = new St.BoxLayout(); - titleButton.set_child(titleBox); - let title = new St.Label({ text: provider.title }); - let count = new St.Label(); - titleBox.add(title, { expand: true }); - titleBox.add(count); - - let resultDisplayBin = new St.Bin({ style_class: 'dash-search-section-results', - x_fill: true, - y_fill: true }); - providerBox.add(resultDisplayBin, { expand: true }); - let resultDisplay = provider.createResultContainerActor(); - if (resultDisplay == null) { - resultDisplay = new OverflowSearchResults(provider); - } - resultDisplayBin.set_child(resultDisplay.actor); - - this._providerMeta.push({ actor: providerBox, - resultDisplay: resultDisplay, - count: count }); - this.actor.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(); - } - }, - - reset: function() { - this._searchSystem.reset(); - this._statusText.hide(); - this._clearDisplay(); - }, - - startingSearch: function() { - this.reset(); - this._statusText.set_text(_("Searching...")); - this._statusText.show(); - }, - - _metaForProvider: function(provider) { - return this._providerMeta[this._providers.indexOf(provider)]; - }, - - updateSearch: function (searchString) { - let results = this._searchSystem.updateSearch(searchString); - - this._clearDisplay(); - - if (results.length == 0) { - this._statusText.set_text(_("No matching results.")); - this._statusText.show(); - return true; - } else { - this._statusText.hide(); - } - - let terms = this._searchSystem.getTerms(); - - for (let i = 0; i < results.length; i++) { - let [provider, providerResults] = results[i]; - let meta = this._metaForProvider(provider); - meta.actor.show(); - meta.resultDisplay.renderResults(providerResults, terms); - meta.count.set_text('' + providerResults.length); - } - - this.selectDown(false); - - return true; - }, - - _onHeaderClicked: function(provider) { - provider.expandSearch(this._searchSystem.getTerms()); - }, - - _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) { - 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._providerMeta.length > 0 && !recursing) { - this._selectedProvider = this._providerMeta.length - 1; - this.selectUp(true); - } - }, - - selectDown: function(recursing) { - let current = this._selectedProvider; - 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; - } - } - if (this._providerMeta.length > 0 && !recursing) { - this._selectedProvider = 0; - this.selectDown(true); - } - }, - - activateSelected: function() { - let current = this._selectedProvider; - if (current < 0) - return; - let meta = this._providerMeta[current]; - let resultDisplay = meta.resultDisplay; - resultDisplay.activateSelected(); - Main.overview.hide(); - } -}; function MoreLink() { this._init(); @@ -654,27 +363,6 @@ SectionHeader.prototype = { Signals.addSignalMethods(SectionHeader.prototype); -function SearchSectionHeader(title, onClick) { - this._init(title, onClick); -} - -SearchSectionHeader.prototype = { - _init : function(title, onClick) { - this.actor = new St.Button({ style_class: 'dash-search-section-header', - x_fill: true, - y_fill: true }); - let box = new St.BoxLayout(); - this.actor.set_child(box); - let titleText = new St.Label({ style_class: 'dash-search-section-title', - text: title }); - this.countText = new St.Label({ style_class: 'dash-search-section-count' }); - - box.add(titleText); - box.add(this.countText, { expand: true, x_fill: false, x_align: St.Align.END }); - - this.actor.connect('clicked', onClick); - } -}; function Section(titleString, suppressBrowse) { this._init(titleString, suppressBrowse); @@ -736,7 +424,7 @@ OldDash.prototype = { this._searchSystem.registerProvider(new PlaceDisplay.PlaceSearchProvider()); this._searchSystem.registerProvider(new DocDisplay.DocSearchProvider()); - this.searchResults = new SearchResults(this._searchSystem); + this.searchResults = new SearchDisplay.SearchResults(this._searchSystem); this.actor.add(this.searchResults.actor); this.searchResults.actor.hide(); diff --git a/js/ui/search.js b/js/ui/search.js index f2a991524..33ecce8a8 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -182,7 +182,7 @@ SearchProvider.prototype = { * implementation will show the icon next to the name. * * The actor should be an instance of St.Widget, with the style class - * 'dash-search-result-content'. + * 'search-result-content'. */ createResultActor: function(resultMeta, terms) { return null; diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js new file mode 100644 index 000000000..10d1739cc --- /dev/null +++ b/js/ui/searchDisplay.js @@ -0,0 +1,304 @@ +/* -*- 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 DND = imports.ui.dnd; +const Main = imports.ui.main; +const Search = imports.ui.search; + + +// 25 search results (per result type) should be enough for everyone +const MAX_RENDERED_SEARCH_RESULTS = 25; + +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.Clickable({ style_class: 'search-result', + reactive: true, + x_align: St.Align.START, + x_fill: true, + y_fill: true }); + this.actor._delegate = this; + + let content = provider.createResultActor(metaInfo, terms); + if (content == null) { + content = new St.BoxLayout({ style_class: 'search-result-content' }); + let title = new St.Label({ text: this.metaInfo['name'] }); + let icon = this.metaInfo['icon']; + content.add(icon, { y_fill: false }); + content.add(title, { expand: true, y_fill: false }); + } + 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, event) { + this.activate(); + }, + + getDragActorSource: function() { + return this.metaInfo['icon']; + }, + + getDragActor: function(stageX, stageY) { + return new Clutter.Clone({ source: this.metaInfo['icon'] }); + }, + + shellWorkspaceLaunch: function() { + if (this.provider.dragActivateResult) + this.provider.dragActivateResult(this.metaInfo.id); + else + this.provider.activateResult(this.metaInfo.id); + } +}; + +function OverflowSearchResults(provider) { + this._init(provider); +} + +OverflowSearchResults.prototype = { + __proto__: Search.SearchResultDisplay.prototype, + + _init: function(provider) { + Search.SearchResultDisplay.prototype._init.call(this, provider); + this.actor = new St.OverflowBox({ style_class: 'search-section-list-results' }); + }, + + getVisibleResultCount: function() { + return this.actor.get_n_visible(); + }, + + renderResults: function(results, terms) { + for (let i = 0; i < results.length && i < MAX_RENDERED_SEARCH_RESULTS; i++) { + let result = results[i]; + let meta = this.provider.getResultMeta(result); + let display = new SearchResult(this.provider, meta, terms); + this.actor.add_actor(display.actor); + } + }, + + selectIndex: function(index) { + let nVisible = this.actor.get_n_visible(); + let children = this.actor.get_children(); + if (this.selectionIndex >= 0) { + let prevActor = children[this.selectionIndex]; + prevActor._delegate.setSelected(false); + } + this.selectionIndex = -1; + if (index >= nVisible) + return false; + else if (index < 0) + return false; + let targetActor = children[index]; + targetActor._delegate.setSelected(true); + this.selectionIndex = index; + return true; + }, + + activateSelected: function() { + let children = this.actor.get_children(); + let targetActor = children[this.selectionIndex]; + targetActor._delegate.activate(); + } +}; + +function SearchResults(searchSystem) { + this._init(searchSystem); +} + +SearchResults.prototype = { + _init: function(searchSystem) { + this._searchSystem = searchSystem; + + this.actor = new St.BoxLayout({ name: 'searchResults', + vertical: true }); + this._statusText = new St.Label({ style_class: 'search-statustext' }); + this.actor.add(this._statusText); + this._selectedProvider = -1; + this._providers = this._searchSystem.getProviders(); + this._providerMeta = []; + for (let i = 0; i < this._providers.length; i++) + this.createProviderMeta(this._providers[i]); + }, + + createProviderMeta: function(provider) { + let providerBox = new St.BoxLayout({ style_class: 'search-section', + vertical: true }); + let titleButton = new St.Button({ style_class: 'search-section-header', + reactive: true, + x_fill: true, + y_fill: true }); + titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); })); + providerBox.add(titleButton); + let titleBox = new St.BoxLayout(); + titleButton.set_child(titleBox); + let title = new St.Label({ text: provider.title }); + let count = new St.Label(); + titleBox.add(title, { expand: true }); + titleBox.add(count); + + 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 OverflowSearchResults(provider); + } + resultDisplayBin.set_child(resultDisplay.actor); + + this._providerMeta.push({ actor: providerBox, + resultDisplay: resultDisplay, + count: count }); + this.actor.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(); + } + }, + + reset: function() { + this._searchSystem.reset(); + this._statusText.hide(); + this._clearDisplay(); + }, + + startingSearch: function() { + this.reset(); + this._statusText.set_text(_("Searching...")); + this._statusText.show(); + }, + + _metaForProvider: function(provider) { + return this._providerMeta[this._providers.indexOf(provider)]; + }, + + updateSearch: function (searchString) { + let results = this._searchSystem.updateSearch(searchString); + + this._clearDisplay(); + + if (results.length == 0) { + this._statusText.set_text(_("No matching results.")); + this._statusText.show(); + return true; + } else { + this._statusText.hide(); + } + + let terms = this._searchSystem.getTerms(); + + for (let i = 0; i < results.length; i++) { + let [provider, providerResults] = results[i]; + let meta = this._metaForProvider(provider); + meta.actor.show(); + meta.resultDisplay.renderResults(providerResults, terms); + meta.count.set_text('' + providerResults.length); + } + + this.selectDown(false); + + return true; + }, + + _onHeaderClicked: function(provider) { + provider.expandSearch(this._searchSystem.getTerms()); + }, + + _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) { + 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._providerMeta.length > 0 && !recursing) { + this._selectedProvider = this._providerMeta.length - 1; + this.selectUp(true); + } + }, + + selectDown: function(recursing) { + let current = this._selectedProvider; + 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; + } + } + if (this._providerMeta.length > 0 && !recursing) { + this._selectedProvider = 0; + this.selectDown(true); + } + }, + + activateSelected: function() { + let current = this._selectedProvider; + if (current < 0) + return; + let meta = this._providerMeta[current]; + let resultDisplay = meta.resultDisplay; + resultDisplay.activateSelected(); + Main.overview.hide(); + } +};