diff --git a/js/Makefile.am b/js/Makefile.am index 4fdbae8aa..677af7895 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -90,7 +90,6 @@ nobase_dist_js_DATA = \ ui/screenShield.js \ ui/scripting.js \ ui/search.js \ - ui/searchDisplay.js \ ui/shellDBus.js \ ui/status/accessibility.js \ ui/status/brightness.js \ diff --git a/js/ui/search.js b/js/ui/search.js index 3648fa130..1ee9471ed 100644 --- a/js/ui/search.js +++ b/js/ui/search.js @@ -1,10 +1,26 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +const Clutter = imports.gi.Clutter; const Lang = imports.lang; +const Gtk = imports.gi.Gtk; +const Meta = imports.gi.Meta; const Signals = imports.signals; +const St = imports.gi.St; +const Atk = imports.gi.Atk; + +const DND = imports.ui.dnd; +const IconGrid = imports.ui.iconGrid; +const Main = imports.ui.main; +const Overview = imports.ui.overview; +const Separator = imports.ui.separator; +const Search = imports.ui.search; +const Util = imports.misc.util; const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers'; +const MAX_LIST_SEARCH_RESULTS_ROWS = 3; +const MAX_GRID_SEARCH_RESULTS_ROWS = 1; + const SearchSystem = new Lang.Class({ Name: 'SearchSystem', @@ -95,3 +111,550 @@ const SearchSystem = new Lang.Class({ } }); Signals.addSignalMethods(SearchSystem.prototype); + +const MaxWidthBin = new Lang.Class({ + 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); + } +}); + +const SearchResult = new Lang.Class({ + Name: 'SearchResult', + + _init: function(provider, metaInfo, terms) { + this.provider = provider; + this.metaInfo = metaInfo; + this.terms = terms; + + 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', Lang.bind(this, this.activate)); + }, + + activate: function() { + this.provider.activateResult(this.metaInfo.id, this.terms); + Main.overview.toggle(); + }, + + setSelected: function(selected) { + if (selected) + this.actor.add_style_pseudo_class('selected'); + else + this.actor.remove_style_pseudo_class('selected'); + } +}); + +const ListSearchResult = new Lang.Class({ + Name: 'ListSearchResult', + Extends: SearchResult, + + ICON_SIZE: 64, + + _init: function(provider, metaInfo, terms) { + this.parent(provider, metaInfo, terms); + + 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); + + // An icon for, or thumbnail of, content + let icon = this.metaInfo['createIcon'](this.ICON_SIZE); + if (icon) { + content.add(icon); + } + + let details = new St.BoxLayout({ vertical: true }); + content.add(details, { x_fill: true, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.MIDDLE }); + + let title = new St.Label({ style_class: 'list-search-result-title', + text: this.metaInfo['name'] }) + details.add(title, { x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.START }); + this.actor.label_actor = title; + + if (this.metaInfo['description']) { + let description = new St.Label({ style_class: 'list-search-result-description' }); + description.clutter_text.set_markup(this.metaInfo['description']); + details.add(description, { x_fill: false, + y_fill: false, + x_align: St.Align.START, + y_align: St.Align.END }); + } + } +}); + +const GridSearchResult = new Lang.Class({ + Name: 'GridSearchResult', + Extends: SearchResult, + + _init: function(provider, metaInfo, terms) { + this.parent(provider, metaInfo, terms); + + this.actor.style_class = 'grid-search-result'; + + let content = provider.createResultObject(metaInfo, terms); + let dragSource = null; + + if (content == null) { + let actor = new St.Bin(); + let icon = new IconGrid.BaseIcon(this.metaInfo['name'], + { createIcon: this.metaInfo['createIcon'] }); + actor.set_child(icon.actor); + actor.label_actor = icon.label; + dragSource = icon.icon; + content = { actor: actor, icon: icon }; + } else { + if (content._delegate && content._delegate.getDragActorSource) + dragSource = content._delegate.getDragActorSource(); + } + + this.actor.set_child(content.actor); + this.actor.label_actor = content.actor.label_actor; + this.icon = content.icon; + + let draggable = DND.makeDraggable(this.actor); + draggable.connect('drag-begin', + Lang.bind(this, function() { + Main.overview.beginItemDrag(this); + })); + draggable.connect('drag-cancelled', + Lang.bind(this, function() { + Main.overview.cancelledItemDrag(this); + })); + draggable.connect('drag-end', + Lang.bind(this, function() { + Main.overview.endItemDrag(this); + })); + + if (!dragSource) + // not exactly right, but alignment problems are hard to notice + dragSource = content; + this._dragActorSource = dragSource; + }, + + getDragActorSource: function() { + return this._dragActorSource; + }, + + getDragActor: function() { + return this.metaInfo['createIcon'](Main.overview.dashIconSize); + }, + + shellWorkspaceLaunch: function(params) { + if (this.provider.dragActivateResult) + this.provider.dragActivateResult(this.metaInfo.id, params); + else + this.provider.activateResult(this.metaInfo.id, this.terms); + } +}); + +const SearchResultsBase = new Lang.Class({ + Name: 'SearchResultsBase', + + _init: function(provider) { + this.provider = provider; + + 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 Separator.HorizontalSeparator({ style_class: 'search-section-separator' }); + this.actor.add(separator.actor); + }, + + destroy: function() { + this.actor.destroy(); + this._terms = []; + }, + + _clearResultDisplay: function() { + }, + + clear: function() { + this._clearResultDisplay(); + this.actor.hide(); + }, + + _keyFocusIn: function(actor) { + this.emit('key-focus-in', actor); + }, + + _setMoreIconVisible: function(visible) { + }, + + 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); + let hasMoreResults = results.length < providerResults.length; + + this.provider.getResultMetas(results, Lang.bind(this, function(metas) { + this.clear(); + + // 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(); + this._renderResults(metas); + this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch); + this.actor.show(); + callback(); + })); + } + } +}); + +const ListSearchResults = new Lang.Class({ + Name: 'ListSearchResults', + Extends: SearchResultsBase, + + _init: function(provider) { + this.parent(provider); + + this._container = new St.BoxLayout({ style_class: 'search-section-content' }); + this.providerIcon = new ProviderIcon(provider); + this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); + this.providerIcon.connect('clicked', Lang.bind(this, + function() { + provider.launchSearch(this._terms); + Main.overview.toggle(); + })); + + this._container.add(this.providerIcon, { 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); + }, + + _setMoreIconVisible: function(visible) { + this.providerIcon.moreIcon.visible = true; + }, + + _getMaxDisplayedResults: function() { + return MAX_LIST_SEARCH_RESULTS_ROWS; + }, + + _renderResults: function(metas) { + for (let i = 0; i < metas.length; i++) { + let display = new ListSearchResult(this.provider, metas[i], this._terms); + display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); + this._content.add_actor(display.actor); + } + }, + + _clearResultDisplay: function () { + this._content.destroy_all_children(); + }, + + 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); + +const GridSearchResults = new Lang.Class({ + Name: 'GridSearchResults', + Extends: SearchResultsBase, + + _init: function(provider) { + this.parent(provider); + + 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() { + return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); + }, + + _renderResults: function(metas) { + for (let i = 0; i < metas.length; i++) { + let display = new GridSearchResult(this.provider, metas[i], this._terms); + display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); + this._grid.addItem(display); + } + }, + + _clearResultDisplay: function () { + this._grid.removeAll(); + }, + + getFirstResult: function() { + if (this._grid.visibleItemsCount() > 0) + return this._grid.getItemAtIndex(0)._delegate; + else + return null; + } +}); +Signals.addSignalMethods(GridSearchResults.prototype); + +const SearchResults = new Lang.Class({ + Name: 'SearchResults', + + _init: function(searchSystem) { + this._searchSystem = searchSystem; + this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); + + 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 }); + this._content.add(this._statusBin, { expand: true }); + this._statusBin.add_actor(this._statusText); + this._providers = this._searchSystem.getProviders(); + this._providerDisplays = {}; + for (let i = 0; i < this._providers.length; i++) { + this.createProviderDisplay(this._providers[i]); + } + + this._highlightDefault = false; + this._defaultResult = null; + }, + + _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); + }, + + createProviderDisplay: function(provider) { + let providerDisplay = null; + + if (provider.appInfo) { + providerDisplay = new ListSearchResults(provider); + } else { + providerDisplay = new GridSearchResults(provider); + } + + providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); + this._providerDisplays[provider.id] = providerDisplay; + this._content.add(providerDisplay.actor); + }, + + destroyProviderDisplay: function(provider) { + this._providerDisplays[provider.id].destroy(); + delete this._providerDisplays[provider.id]; + }, + + _clearDisplay: function() { + for (let i = 0; i < this._providers.length; i++) { + let provider = this._providers[i]; + let providerDisplay = this._providerDisplays[provider.id]; + providerDisplay.clear(); + } + }, + + reset: function() { + this._searchSystem.reset(); + this._statusBin.hide(); + this._clearDisplay(); + this._defaultResult = null; + }, + + startingSearch: function() { + this.reset(); + this._statusText.set_text(_("Searching…")); + this._statusBin.show(); + }, + + _maybeSetInitialSelection: function() { + let newDefaultResult = null; + + for (let i = 0; i < this._providers.length; i++) { + let provider = this._providers[i]; + let display = this._providerDisplays[provider.id]; + + if (!display.actor.visible) + continue; + + let firstResult = display.getFirstResult(); + if (firstResult) { + newDefaultResult = firstResult; + break; // select this one! + } + } + + if (newDefaultResult != this._defaultResult) { + if (this._defaultResult) + this._defaultResult.setSelected(false); + if (newDefaultResult) + newDefaultResult.setSelected(this._highlightDefault); + + this._defaultResult = newDefaultResult; + } + }, + + _updateStatusText: function () { + let haveResults = false; + + for (let i = 0; i < this._providers.length; i++) { + let provider = this._providers[i]; + let display = this._providerDisplays[provider.id]; + if (display.getFirstResult()) { + haveResults = true; + break; + } + } + + if (!haveResults) { + this._statusText.set_text(_("No results.")); + this._statusBin.show(); + } else { + this._statusBin.hide(); + } + }, + + _updateResults: function(searchSystem, results) { + let terms = searchSystem.getTerms(); + let [provider, providerResults] = results; + let display = this._providerDisplays[provider.id]; + + display.updateSearch(providerResults, terms, Lang.bind(this, function() { + this._maybeSetInitialSelection(); + this._updateStatusText(); + })); + }, + + activateDefault: function() { + if (this._defaultResult) + this._defaultResult.activate(); + }, + + highlightDefault: function(highlight) { + this._highlightDefault = highlight; + if (this._defaultResult) + this._defaultResult.setSelected(highlight); + }, + + 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); + } +}); + +const ProviderIcon = new Lang.Class({ + Name: 'ProviderIcon', + Extends: St.Button, + + PROVIDER_ICON_SIZE: 48, + + _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 }); + + this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); + this.set_child(this._content); + + let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL); + + this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more', + visible: false, + x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true }); + + let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE, + gicon: provider.appInfo.get_icon() }); + this._content.add_actor(icon); + this._content.add_actor(this.moreIcon); + } +}); diff --git a/js/ui/searchDisplay.js b/js/ui/searchDisplay.js deleted file mode 100644 index 8ae97abba..000000000 --- a/js/ui/searchDisplay.js +++ /dev/null @@ -1,567 +0,0 @@ -// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- - -const Clutter = imports.gi.Clutter; -const Lang = imports.lang; -const Gtk = imports.gi.Gtk; -const Meta = imports.gi.Meta; -const Signals = imports.signals; -const St = imports.gi.St; -const Atk = imports.gi.Atk; - -const DND = imports.ui.dnd; -const IconGrid = imports.ui.iconGrid; -const Main = imports.ui.main; -const Overview = imports.ui.overview; -const Separator = imports.ui.separator; -const Search = imports.ui.search; -const Util = imports.misc.util; - -const MAX_LIST_SEARCH_RESULTS_ROWS = 3; -const MAX_GRID_SEARCH_RESULTS_ROWS = 1; - -const MaxWidthBin = new Lang.Class({ - 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); - } -}); - -const SearchResult = new Lang.Class({ - Name: 'SearchResult', - - _init: function(provider, metaInfo, terms) { - this.provider = provider; - this.metaInfo = metaInfo; - this.terms = terms; - - 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', Lang.bind(this, this.activate)); - }, - - activate: function() { - this.provider.activateResult(this.metaInfo.id, this.terms); - Main.overview.toggle(); - }, - - setSelected: function(selected) { - if (selected) - this.actor.add_style_pseudo_class('selected'); - else - this.actor.remove_style_pseudo_class('selected'); - } -}); - -const ListSearchResult = new Lang.Class({ - Name: 'ListSearchResult', - Extends: SearchResult, - - ICON_SIZE: 64, - - _init: function(provider, metaInfo, terms) { - this.parent(provider, metaInfo, terms); - - 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); - - // An icon for, or thumbnail of, content - let icon = this.metaInfo['createIcon'](this.ICON_SIZE); - if (icon) { - content.add(icon); - } - - let details = new St.BoxLayout({ vertical: true }); - content.add(details, { x_fill: true, - y_fill: false, - x_align: St.Align.START, - y_align: St.Align.MIDDLE }); - - let title = new St.Label({ style_class: 'list-search-result-title', - text: this.metaInfo['name'] }) - details.add(title, { x_fill: false, - y_fill: false, - x_align: St.Align.START, - y_align: St.Align.START }); - this.actor.label_actor = title; - - if (this.metaInfo['description']) { - let description = new St.Label({ style_class: 'list-search-result-description' }); - description.clutter_text.set_markup(this.metaInfo['description']); - details.add(description, { x_fill: false, - y_fill: false, - x_align: St.Align.START, - y_align: St.Align.END }); - } - } -}); - -const GridSearchResult = new Lang.Class({ - Name: 'GridSearchResult', - Extends: SearchResult, - - _init: function(provider, metaInfo, terms) { - this.parent(provider, metaInfo, terms); - - this.actor.style_class = 'grid-search-result'; - - let content = provider.createResultObject(metaInfo, terms); - let dragSource = null; - - if (content == null) { - let actor = new St.Bin(); - let icon = new IconGrid.BaseIcon(this.metaInfo['name'], - { createIcon: this.metaInfo['createIcon'] }); - actor.set_child(icon.actor); - actor.label_actor = icon.label; - dragSource = icon.icon; - content = { actor: actor, icon: icon }; - } else { - if (content._delegate && content._delegate.getDragActorSource) - dragSource = content._delegate.getDragActorSource(); - } - - this.actor.set_child(content.actor); - this.actor.label_actor = content.actor.label_actor; - this.icon = content.icon; - - let draggable = DND.makeDraggable(this.actor); - draggable.connect('drag-begin', - Lang.bind(this, function() { - Main.overview.beginItemDrag(this); - })); - draggable.connect('drag-cancelled', - Lang.bind(this, function() { - Main.overview.cancelledItemDrag(this); - })); - draggable.connect('drag-end', - Lang.bind(this, function() { - Main.overview.endItemDrag(this); - })); - - if (!dragSource) - // not exactly right, but alignment problems are hard to notice - dragSource = content; - this._dragActorSource = dragSource; - }, - - getDragActorSource: function() { - return this._dragActorSource; - }, - - getDragActor: function() { - return this.metaInfo['createIcon'](Main.overview.dashIconSize); - }, - - shellWorkspaceLaunch: function(params) { - if (this.provider.dragActivateResult) - this.provider.dragActivateResult(this.metaInfo.id, params); - else - this.provider.activateResult(this.metaInfo.id, this.terms); - } -}); - -const SearchResultsBase = new Lang.Class({ - Name: 'SearchResultsBase', - - _init: function(provider) { - this.provider = provider; - - 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 Separator.HorizontalSeparator({ style_class: 'search-section-separator' }); - this.actor.add(separator.actor); - }, - - destroy: function() { - this.actor.destroy(); - this._terms = []; - }, - - _clearResultDisplay: function() { - }, - - clear: function() { - this._clearResultDisplay(); - this.actor.hide(); - }, - - _keyFocusIn: function(actor) { - this.emit('key-focus-in', actor); - }, - - _setMoreIconVisible: function(visible) { - }, - - 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); - let hasMoreResults = results.length < providerResults.length; - - this.provider.getResultMetas(results, Lang.bind(this, function(metas) { - this.clear(); - - // 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(); - this._renderResults(metas); - this._setMoreIconVisible(hasMoreResults && this.provider.canLaunchSearch); - this.actor.show(); - callback(); - })); - } - } -}); - -const ListSearchResults = new Lang.Class({ - Name: 'ListSearchResults', - Extends: SearchResultsBase, - - _init: function(provider) { - this.parent(provider); - - this._container = new St.BoxLayout({ style_class: 'search-section-content' }); - this.providerIcon = new ProviderIcon(provider); - this.providerIcon.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); - this.providerIcon.connect('clicked', Lang.bind(this, - function() { - provider.launchSearch(this._terms); - Main.overview.toggle(); - })); - - this._container.add(this.providerIcon, { 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); - }, - - _setMoreIconVisible: function(visible) { - this.providerIcon.moreIcon.visible = true; - }, - - _getMaxDisplayedResults: function() { - return MAX_LIST_SEARCH_RESULTS_ROWS; - }, - - _renderResults: function(metas) { - for (let i = 0; i < metas.length; i++) { - let display = new ListSearchResult(this.provider, metas[i], this._terms); - display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); - this._content.add_actor(display.actor); - } - }, - - _clearResultDisplay: function () { - this._content.destroy_all_children(); - }, - - 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); - -const GridSearchResults = new Lang.Class({ - Name: 'GridSearchResults', - Extends: SearchResultsBase, - - _init: function(provider) { - this.parent(provider); - - 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() { - return this._grid.columnsForWidth(this._bin.width) * this._grid.getRowLimit(); - }, - - _renderResults: function(metas) { - for (let i = 0; i < metas.length; i++) { - let display = new GridSearchResult(this.provider, metas[i], this._terms); - display.actor.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); - this._grid.addItem(display); - } - }, - - _clearResultDisplay: function () { - this._grid.removeAll(); - }, - - getFirstResult: function() { - if (this._grid.visibleItemsCount() > 0) - return this._grid.getItemAtIndex(0)._delegate; - else - return null; - } -}); -Signals.addSignalMethods(GridSearchResults.prototype); - -const SearchResults = new Lang.Class({ - Name: 'SearchResults', - - _init: function(searchSystem) { - this._searchSystem = searchSystem; - this._searchSystem.connect('search-updated', Lang.bind(this, this._updateResults)); - - 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 }); - this._content.add(this._statusBin, { expand: true }); - this._statusBin.add_actor(this._statusText); - this._providers = this._searchSystem.getProviders(); - this._providerDisplays = {}; - for (let i = 0; i < this._providers.length; i++) { - this.createProviderDisplay(this._providers[i]); - } - - this._highlightDefault = false; - this._defaultResult = null; - }, - - _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); - }, - - createProviderDisplay: function(provider) { - let providerDisplay = null; - - if (provider.appInfo) { - providerDisplay = new ListSearchResults(provider); - } else { - providerDisplay = new GridSearchResults(provider); - } - - providerDisplay.connect('key-focus-in', Lang.bind(this, this._keyFocusIn)); - this._providerDisplays[provider.id] = providerDisplay; - this._content.add(providerDisplay.actor); - }, - - destroyProviderDisplay: function(provider) { - this._providerDisplays[provider.id].destroy(); - delete this._providerDisplays[provider.id]; - }, - - _clearDisplay: function() { - for (let i = 0; i < this._providers.length; i++) { - let provider = this._providers[i]; - let providerDisplay = this._providerDisplays[provider.id]; - providerDisplay.clear(); - } - }, - - reset: function() { - this._searchSystem.reset(); - this._statusBin.hide(); - this._clearDisplay(); - this._defaultResult = null; - }, - - startingSearch: function() { - this.reset(); - this._statusText.set_text(_("Searching…")); - this._statusBin.show(); - }, - - _maybeSetInitialSelection: function() { - let newDefaultResult = null; - - for (let i = 0; i < this._providers.length; i++) { - let provider = this._providers[i]; - let display = this._providerDisplays[provider.id]; - - if (!display.actor.visible) - continue; - - let firstResult = display.getFirstResult(); - if (firstResult) { - newDefaultResult = firstResult; - break; // select this one! - } - } - - if (newDefaultResult != this._defaultResult) { - if (this._defaultResult) - this._defaultResult.setSelected(false); - if (newDefaultResult) - newDefaultResult.setSelected(this._highlightDefault); - - this._defaultResult = newDefaultResult; - } - }, - - _updateStatusText: function () { - let haveResults = false; - - for (let i = 0; i < this._providers.length; i++) { - let provider = this._providers[i]; - let display = this._providerDisplays[provider.id]; - if (display.getFirstResult()) { - haveResults = true; - break; - } - } - - if (!haveResults) { - this._statusText.set_text(_("No results.")); - this._statusBin.show(); - } else { - this._statusBin.hide(); - } - }, - - _updateResults: function(searchSystem, results) { - let terms = searchSystem.getTerms(); - let [provider, providerResults] = results; - let display = this._providerDisplays[provider.id]; - - display.updateSearch(providerResults, terms, Lang.bind(this, function() { - this._maybeSetInitialSelection(); - this._updateStatusText(); - })); - }, - - activateDefault: function() { - if (this._defaultResult) - this._defaultResult.activate(); - }, - - highlightDefault: function(highlight) { - this._highlightDefault = highlight; - if (this._defaultResult) - this._defaultResult.setSelected(highlight); - }, - - 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); - } -}); - -const ProviderIcon = new Lang.Class({ - Name: 'ProviderIcon', - Extends: St.Button, - - PROVIDER_ICON_SIZE: 48, - - _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 }); - - this._content = new St.Widget({ layout_manager: new Clutter.BinLayout() }); - this.set_child(this._content); - - let rtl = (this.get_text_direction() == Clutter.TextDirection.RTL); - - this.moreIcon = new St.Widget({ style_class: 'search-provider-icon-more', - visible: false, - x_align: rtl ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.END, - x_expand: true, - y_expand: true }); - - let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE, - gicon: provider.appInfo.get_icon() }); - this._content.add_actor(icon); - this._content.add_actor(this.moreIcon); - } -}); diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index 9e86288ec..3c9cf2131 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -16,7 +16,6 @@ const OverviewControls = imports.ui.overviewControls; const Params = imports.misc.params; const RemoteSearch = imports.ui.remoteSearch; const Search = imports.ui.search; -const SearchDisplay = imports.ui.searchDisplay; const ShellEntry = imports.ui.shellEntry; const Tweener = imports.ui.tweener; const WorkspacesView = imports.ui.workspacesView; @@ -102,7 +101,7 @@ const ViewSelector = new Lang.Class({ this._appsPage = this._addPage(this.appDisplay.actor, _("Applications"), 'view-grid-symbolic'); - this._searchResults = new SearchDisplay.SearchResults(this._searchSystem); + this._searchResults = new Search.SearchResults(this._searchSystem); this._searchPage = this._addPage(this._searchResults.actor, _("Search"), 'edit-find-symbolic', { a11yFocus: this._entry }); diff --git a/po/POTFILES.in b/po/POTFILES.in index 6ec28e757..3113662dc 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -38,7 +38,7 @@ js/ui/panel.js js/ui/popupMenu.js js/ui/runDialog.js js/ui/screenShield.js -js/ui/searchDisplay.js +js/ui/search.js js/ui/shellEntry.js js/ui/shellMountOperation.js js/ui/status/accessibility.js