Use the old dash code to implement the view selector
The view selector is a tabbed interface with a search entry. Starting a search switches focus to the results' tab, ending a search moves the focus back to the previously selected tab. Activating a normal tab while a search is active cancels the search. https://bugzilla.gnome.org/show_bug.cgi?id=634948
This commit is contained in:
parent
e6bb06a7cc
commit
48fff0e96b
@ -355,90 +355,92 @@ StTooltip StLabel {
|
|||||||
spacing: 12px;
|
spacing: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#viewSelector {
|
||||||
|
spacing: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchArea {
|
||||||
|
padding: 0px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
#searchEntry {
|
#searchEntry {
|
||||||
padding: 4px;
|
padding: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
color: #a8a8a8;
|
color: rgb(128, 128, 128);
|
||||||
border: 1px solid #565656;
|
border: 2px solid rgba(128, 128, 128, 0.4);
|
||||||
background-color: #404040;
|
background-gradient-start: rgba(0, 0, 0, 0.2);
|
||||||
caret-color: #fff;
|
background-gradient-end: rgba(128, 128, 128, 0.2);
|
||||||
|
background-gradient-direction: vertical;
|
||||||
|
caret-color: rgb(128, 128, 128);
|
||||||
caret-size: 1px;
|
caret-size: 1px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
width: 250px;
|
||||||
transition-duration: 300;
|
transition-duration: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchEntry:focus {
|
#searchEntry:focus {
|
||||||
color: #545454;
|
border: 2px solid #ffffff;
|
||||||
border: 1px solid #3a3a3a;
|
background-gradient-start: rgba(0, 0, 0, 0.2);
|
||||||
background-color: #e8e8e8;
|
background-gradient-end: #ffffff;
|
||||||
caret-color: #545454;
|
background-gradient-direction: vertical;
|
||||||
|
color: rgb(64, 64, 64);
|
||||||
|
font-weight: bold;
|
||||||
-st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9);
|
-st-shadow: 0px 0px 6px 2px rgba(255,255,255,0.9);
|
||||||
transition-duration: 0;
|
transition-duration: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#searchEntry:hover {
|
#searchEntry:hover {
|
||||||
color: #a8a8a8;
|
border: 2px solid #e8e8e8;
|
||||||
border: 1px solid #4d4d4d;
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
caret-color: #545454;
|
caret-color: #545454;
|
||||||
transition-duration: 500;
|
transition-duration: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-section {
|
.view-tab-title {
|
||||||
|
color: #888a85;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tab-title:selected {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tab-boxpointer {
|
||||||
|
-arrow-border-radius: 9px;
|
||||||
|
-arrow-background-color: rgba(0,0,0,0.5);
|
||||||
|
-arrow-border-width: 2px;
|
||||||
|
-arrow-border-color: rgba(255,255,255,0.5);
|
||||||
|
-arrow-base: 30px;
|
||||||
|
-arrow-rise: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults {
|
||||||
|
padding: 20px 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResultsContent {
|
||||||
|
padding: 0 10px;
|
||||||
spacing: 8px;
|
spacing: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header-inner {
|
|
||||||
spacing: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-text-content {
|
|
||||||
padding: 4px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dash-section-content {
|
|
||||||
color: #ffffff;
|
|
||||||
spacing: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-link {
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-link-expander {
|
|
||||||
background-image: url("section-more.svg");
|
|
||||||
width: 9px;
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-link-expander.open {
|
|
||||||
background-image: url("section-more-open.svg");
|
|
||||||
width: 9px;
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dash-pane {
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #111111;
|
|
||||||
border: 2px solid #868686;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 30px 10px 10px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dashAppSearchResults {
|
|
||||||
padding: 8px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-statustext,
|
.search-statustext,
|
||||||
.search-section-header {
|
.search-section-header {
|
||||||
padding: 4px 0px;
|
padding: 4px 12px;
|
||||||
spacing: 4px;
|
spacing: 4px;
|
||||||
|
color: #6f6f6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
background-color: rgba(128, 128, 128, .1);
|
||||||
|
border: 1px solid rgba(50, 50, 50, .4);
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-section-results {
|
.search-section-results {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(50, 50, 50, .4);
|
||||||
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-section-list-results {
|
.search-section-list-results {
|
||||||
@ -446,17 +448,15 @@ StTooltip StLabel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-result-content {
|
.search-result-content {
|
||||||
padding: 3px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-content:selected {
|
.search-result-content:selected {
|
||||||
padding: 2px;
|
border-radius: 4px;
|
||||||
border: 1px solid #5c5c5c;
|
background: rgba(255,255,255,0.33);
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #1e1e1e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-results-container {
|
.results-container {
|
||||||
spacing: 4px;
|
spacing: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/status/volume.js \
|
ui/status/volume.js \
|
||||||
ui/telepathyClient.js \
|
ui/telepathyClient.js \
|
||||||
ui/tweener.js \
|
ui/tweener.js \
|
||||||
|
ui/viewSelector.js \
|
||||||
ui/windowAttentionHandler.js \
|
ui/windowAttentionHandler.js \
|
||||||
ui/windowManager.js \
|
ui/windowManager.js \
|
||||||
ui/workspace.js \
|
ui/workspace.js \
|
||||||
|
589
js/ui/dash.js
589
js/ui/dash.js
@ -1,6 +1,5 @@
|
|||||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
|
||||||
const Mainloop = imports.mainloop;
|
const Mainloop = imports.mainloop;
|
||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
@ -12,597 +11,9 @@ const _ = Gettext.gettext;
|
|||||||
const AppDisplay = imports.ui.appDisplay;
|
const AppDisplay = imports.ui.appDisplay;
|
||||||
const AppFavorites = imports.ui.appFavorites;
|
const AppFavorites = imports.ui.appFavorites;
|
||||||
const DND = imports.ui.dnd;
|
const DND = imports.ui.dnd;
|
||||||
const DocDisplay = imports.ui.docDisplay;
|
|
||||||
const PlaceDisplay = imports.ui.placeDisplay;
|
|
||||||
const Main = imports.ui.main;
|
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;
|
const Workspace = imports.ui.workspace;
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
* is wrapped in if necessary.
|
|
||||||
*
|
|
||||||
* index: prior index, expects 0 <= index < length
|
|
||||||
* increment: the change in index, expects abs(increment) <= length
|
|
||||||
* length: the length of the array
|
|
||||||
*/
|
|
||||||
function _getIndexWrapped(index, increment, length) {
|
|
||||||
return (index + increment + length) % length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Pane() {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
Pane.prototype = {
|
|
||||||
_init: function () {
|
|
||||||
this._open = false;
|
|
||||||
|
|
||||||
this.actor = new St.BoxLayout({ style_class: 'dash-pane',
|
|
||||||
vertical: true,
|
|
||||||
reactive: true });
|
|
||||||
this.actor.connect('button-press-event', Lang.bind(this, function (a, e) {
|
|
||||||
// Eat button press events so they don't go through and close the pane
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Hidden by default
|
|
||||||
this.actor.hide();
|
|
||||||
},
|
|
||||||
|
|
||||||
open: function () {
|
|
||||||
if (this._open)
|
|
||||||
return;
|
|
||||||
this._open = true;
|
|
||||||
this.emit('open-state-changed', this._open);
|
|
||||||
this.actor.opacity = 0;
|
|
||||||
this.actor.show();
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 255,
|
|
||||||
time: Overview.PANE_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
close: function () {
|
|
||||||
if (!this._open)
|
|
||||||
return;
|
|
||||||
this._open = false;
|
|
||||||
Tweener.addTween(this.actor,
|
|
||||||
{ opacity: 0,
|
|
||||||
time: Overview.PANE_FADE_TIME,
|
|
||||||
transition: 'easeOutQuad',
|
|
||||||
onComplete: Lang.bind(this, function() {
|
|
||||||
this.actor.hide();
|
|
||||||
this.emit('open-state-changed', this._open);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyContent: function() {
|
|
||||||
let children = this.actor.get_children();
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
children[i].destroy();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle: function () {
|
|
||||||
if (this._open)
|
|
||||||
this.close();
|
|
||||||
else
|
|
||||||
this.open();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Signals.addSignalMethods(Pane.prototype);
|
|
||||||
|
|
||||||
function ResultArea() {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultArea.prototype = {
|
|
||||||
_init : function() {
|
|
||||||
this.actor = new St.BoxLayout({ vertical: true });
|
|
||||||
this.resultsContainer = new St.BoxLayout({ style_class: 'dash-results-container' });
|
|
||||||
this.actor.add(this.resultsContainer, { expand: true });
|
|
||||||
|
|
||||||
this.display = new DocDisplay.DocDisplay();
|
|
||||||
this.resultsContainer.add(this.display.actor, { expand: true });
|
|
||||||
this.display.load();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function ResultPane(dash) {
|
|
||||||
this._init(dash);
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultPane.prototype = {
|
|
||||||
__proto__: Pane.prototype,
|
|
||||||
|
|
||||||
_init: function(dash) {
|
|
||||||
Pane.prototype._init.call(this);
|
|
||||||
|
|
||||||
let resultArea = new ResultArea();
|
|
||||||
this.actor.add(resultArea.actor, { expand: true });
|
|
||||||
this.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
|
|
||||||
resultArea.display.resetState();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function SearchEntry() {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchEntry.prototype = {
|
|
||||||
_init : function() {
|
|
||||||
this.actor = new St.Entry({ name: 'searchEntry',
|
|
||||||
hint_text: _("Find") });
|
|
||||||
this.entry = this.actor.clutter_text;
|
|
||||||
|
|
||||||
this.actor.clutter_text.connect('text-changed', Lang.bind(this,
|
|
||||||
function() {
|
|
||||||
if (this.isActive())
|
|
||||||
this.actor.set_secondary_icon_from_file(global.imagedir +
|
|
||||||
'close-black.svg');
|
|
||||||
else
|
|
||||||
this.actor.set_secondary_icon_from_file(null);
|
|
||||||
}));
|
|
||||||
this.actor.connect('secondary-icon-clicked', Lang.bind(this,
|
|
||||||
function() {
|
|
||||||
this.reset();
|
|
||||||
}));
|
|
||||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
||||||
|
|
||||||
global.stage.connect('notify::key-focus', Lang.bind(this, this._updateCursorVisibility));
|
|
||||||
|
|
||||||
this.pane = null;
|
|
||||||
|
|
||||||
this._capturedEventId = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateCursorVisibility: function() {
|
|
||||||
let focus = global.stage.get_key_focus();
|
|
||||||
if (focus == global.stage || focus == this.entry)
|
|
||||||
this.entry.set_cursor_visible(true);
|
|
||||||
else
|
|
||||||
this.entry.set_cursor_visible(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function() {
|
|
||||||
if (this._capturedEventId == 0)
|
|
||||||
this._capturedEventId = global.stage.connect('captured-event',
|
|
||||||
Lang.bind(this, this._onCapturedEvent));
|
|
||||||
this.entry.set_cursor_visible(true);
|
|
||||||
this.entry.set_selection(0, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: function() {
|
|
||||||
if (this.isActive())
|
|
||||||
this.reset();
|
|
||||||
if (this._capturedEventId > 0) {
|
|
||||||
global.stage.disconnect(this._capturedEventId);
|
|
||||||
this._capturedEventId = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: function () {
|
|
||||||
let [x, y, mask] = global.get_pointer();
|
|
||||||
let actor = global.stage.get_actor_at_pos (Clutter.PickMode.REACTIVE,
|
|
||||||
x, y);
|
|
||||||
// this.actor is never hovered directly, only its clutter_text and icon
|
|
||||||
let hovered = this.actor == actor.get_parent();
|
|
||||||
|
|
||||||
this.actor.set_hover(hovered);
|
|
||||||
|
|
||||||
this.entry.text = '';
|
|
||||||
global.stage.set_key_focus(null);
|
|
||||||
this.entry.set_cursor_visible(true);
|
|
||||||
this.entry.set_selection(0, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
getText: function () {
|
|
||||||
return this.entry.get_text().replace(/^\s+/g, '').replace(/\s+$/g, '');
|
|
||||||
},
|
|
||||||
|
|
||||||
// some search term has been entered
|
|
||||||
isActive: function() {
|
|
||||||
return this.actor.get_text() != '';
|
|
||||||
},
|
|
||||||
|
|
||||||
// the entry does not show the hint
|
|
||||||
_isActivated: function() {
|
|
||||||
return this.entry.text == this.actor.get_text();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onCapturedEvent: function(actor, event) {
|
|
||||||
let source = event.get_source();
|
|
||||||
let panelEvent = source && Main.panel.actor.contains(source);
|
|
||||||
|
|
||||||
switch (event.type()) {
|
|
||||||
case Clutter.EventType.BUTTON_PRESS:
|
|
||||||
// the user clicked outside after activating the entry, but
|
|
||||||
// with no search term entered - cancel the search
|
|
||||||
if (source != this.entry && this.entry.text == '') {
|
|
||||||
this.reset();
|
|
||||||
// allow only panel events to continue
|
|
||||||
return !panelEvent;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case Clutter.EventType.KEY_PRESS:
|
|
||||||
// If neither the stage nor our entry have key focus, some
|
|
||||||
// "special" actor grabbed the focus (run dialog, looking
|
|
||||||
// glass); we don't want to interfere with that
|
|
||||||
let focus = global.stage.get_key_focus();
|
|
||||||
if (focus != global.stage && focus != this.entry)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let sym = event.get_key_symbol();
|
|
||||||
|
|
||||||
// If we have an active search, Escape cancels it - if we
|
|
||||||
// haven't, the key is ignored
|
|
||||||
if (sym == Clutter.Escape)
|
|
||||||
if (this._isActivated()) {
|
|
||||||
this.reset();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore non-printable keys
|
|
||||||
if (!Clutter.keysym_to_unicode(sym))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Search started - move the key focus to the entry and
|
|
||||||
// "repeat" the event
|
|
||||||
if (!this._isActivated()) {
|
|
||||||
global.stage.set_key_focus(this.entry);
|
|
||||||
this.entry.event(event, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
// Suppress all other events outside the panel while the entry
|
|
||||||
// is activated and no search has been entered - any click
|
|
||||||
// outside the entry will cancel the search
|
|
||||||
return (this.entry.text == '' && !panelEvent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onDestroy: function() {
|
|
||||||
if (this._capturedEventId > 0) {
|
|
||||||
global.stage.disconnect(this._capturedEventId);
|
|
||||||
this._capturedEventId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Signals.addSignalMethods(SearchEntry.prototype);
|
|
||||||
|
|
||||||
|
|
||||||
function MoreLink() {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
MoreLink.prototype = {
|
|
||||||
_init : function () {
|
|
||||||
this.actor = new St.BoxLayout({ style_class: 'more-link',
|
|
||||||
reactive: true });
|
|
||||||
this.pane = null;
|
|
||||||
|
|
||||||
this._expander = new St.Bin({ style_class: 'more-link-expander' });
|
|
||||||
this.actor.add(this._expander, { expand: true, y_fill: false });
|
|
||||||
},
|
|
||||||
|
|
||||||
activate: function() {
|
|
||||||
if (!this.actor.visible)
|
|
||||||
return true; // If the link isn't visible we don't want the header to react
|
|
||||||
// to clicks
|
|
||||||
if (this.pane == null) {
|
|
||||||
// Ensure the pane is created; the activated handler will call setPane
|
|
||||||
this.emit('activated');
|
|
||||||
}
|
|
||||||
this._pane.toggle();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
setPane: function (pane) {
|
|
||||||
this._pane = pane;
|
|
||||||
this._pane.connect('open-state-changed', Lang.bind(this, function(pane, isOpen) {
|
|
||||||
if (isOpen)
|
|
||||||
this._expander.add_style_class_name('open');
|
|
||||||
else
|
|
||||||
this._expander.remove_style_class_name('open');
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Signals.addSignalMethods(MoreLink.prototype);
|
|
||||||
|
|
||||||
function SectionHeader(title, suppressBrowse) {
|
|
||||||
this._init(title, suppressBrowse);
|
|
||||||
}
|
|
||||||
|
|
||||||
SectionHeader.prototype = {
|
|
||||||
_init : function (title, suppressBrowse) {
|
|
||||||
this.actor = new St.Bin({ style_class: 'section-header',
|
|
||||||
x_align: St.Align.START,
|
|
||||||
x_fill: true,
|
|
||||||
y_fill: true,
|
|
||||||
reactive: !suppressBrowse });
|
|
||||||
this._innerBox = new St.BoxLayout({ style_class: 'section-header-inner' });
|
|
||||||
this.actor.set_child(this._innerBox);
|
|
||||||
|
|
||||||
let textBox = new St.BoxLayout({ style_class: 'section-text-content' });
|
|
||||||
this.text = new St.Label({ style_class: 'section-title',
|
|
||||||
text: title });
|
|
||||||
textBox.add(this.text, { x_align: St.Align.START });
|
|
||||||
|
|
||||||
this._innerBox.add(textBox, { expand: true });
|
|
||||||
|
|
||||||
if (!suppressBrowse) {
|
|
||||||
this.moreLink = new MoreLink();
|
|
||||||
this._innerBox.add(this.moreLink.actor, { x_align: St.Align.END });
|
|
||||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onButtonPress: function() {
|
|
||||||
this.moreLink.activate();
|
|
||||||
},
|
|
||||||
|
|
||||||
setMoreLinkVisible : function(visible) {
|
|
||||||
if (visible)
|
|
||||||
this.moreLink.actor.show();
|
|
||||||
else
|
|
||||||
this.moreLink.actor.hide();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Signals.addSignalMethods(SectionHeader.prototype);
|
|
||||||
|
|
||||||
|
|
||||||
function Section(titleString, suppressBrowse) {
|
|
||||||
this._init(titleString, suppressBrowse);
|
|
||||||
}
|
|
||||||
|
|
||||||
Section.prototype = {
|
|
||||||
_init: function(titleString, suppressBrowse) {
|
|
||||||
this.actor = new St.BoxLayout({ style_class: 'dash-section',
|
|
||||||
vertical: true });
|
|
||||||
this.header = new SectionHeader(titleString, suppressBrowse);
|
|
||||||
this.actor.add(this.header.actor);
|
|
||||||
this.content = new St.BoxLayout({ style_class: 'dash-section-content',
|
|
||||||
vertical: true });
|
|
||||||
this.actor.add(this.content);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function OldDash() {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
OldDash.prototype = {
|
|
||||||
_init : function() {
|
|
||||||
// dash and the popup panes need to be reactive so that the clicks in unoccupied places on them
|
|
||||||
// are not passed to the transparent background underneath them. This background is used for the workspaces area when
|
|
||||||
// the additional dash panes are being shown and it handles clicks by closing the additional panes, so that the user
|
|
||||||
// can interact with the workspaces. However, this behavior is not desirable when the click is actually over a pane.
|
|
||||||
//
|
|
||||||
// We have to make the individual panes reactive instead of making the whole dash actor reactive because the width
|
|
||||||
// of the Group actor ends up including the width of its hidden children, so we were getting a reactive object as
|
|
||||||
// wide as the details pane that was blocking the clicks to the workspaces underneath it even when the details pane
|
|
||||||
// was actually hidden.
|
|
||||||
this.actor = new St.BoxLayout({ name: 'dash',
|
|
||||||
vertical: true,
|
|
||||||
reactive: true });
|
|
||||||
|
|
||||||
// The searchArea just holds the entry
|
|
||||||
this.searchArea = new St.BoxLayout({ name: 'dashSearchArea',
|
|
||||||
vertical: true });
|
|
||||||
this.sectionArea = new St.BoxLayout({ name: 'dashSections',
|
|
||||||
vertical: true });
|
|
||||||
|
|
||||||
this.actor.add(this.searchArea);
|
|
||||||
this.actor.add(this.sectionArea);
|
|
||||||
|
|
||||||
// The currently active popup display
|
|
||||||
this._activePane = null;
|
|
||||||
|
|
||||||
/***** Search *****/
|
|
||||||
|
|
||||||
this._searchActive = false;
|
|
||||||
this._searchPending = false;
|
|
||||||
this._searchEntry = new SearchEntry();
|
|
||||||
this.searchArea.add(this._searchEntry.actor, { y_fill: false, expand: true });
|
|
||||||
|
|
||||||
this._searchSystem = new Search.SearchSystem();
|
|
||||||
this._searchSystem.registerProvider(new AppDisplay.AppSearchProvider());
|
|
||||||
this._searchSystem.registerProvider(new AppDisplay.PrefsSearchProvider());
|
|
||||||
this._searchSystem.registerProvider(new PlaceDisplay.PlaceSearchProvider());
|
|
||||||
this._searchSystem.registerProvider(new DocDisplay.DocSearchProvider());
|
|
||||||
|
|
||||||
this.searchResults = new SearchDisplay.SearchResults(this._searchSystem);
|
|
||||||
this.actor.add(this.searchResults.actor);
|
|
||||||
this.searchResults.actor.hide();
|
|
||||||
|
|
||||||
this._keyPressId = 0;
|
|
||||||
this._searchTimeoutId = 0;
|
|
||||||
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
|
|
||||||
let searchPreviouslyActive = this._searchActive;
|
|
||||||
this._searchActive = this._searchEntry.isActive();
|
|
||||||
this._searchPending = this._searchActive && !searchPreviouslyActive;
|
|
||||||
if (this._searchPending) {
|
|
||||||
this.searchResults.startingSearch();
|
|
||||||
}
|
|
||||||
if (this._searchActive) {
|
|
||||||
this.searchResults.actor.show();
|
|
||||||
this.sectionArea.hide();
|
|
||||||
} else {
|
|
||||||
this.searchResults.actor.hide();
|
|
||||||
this.sectionArea.show();
|
|
||||||
}
|
|
||||||
if (!this._searchActive) {
|
|
||||||
if (this._searchTimeoutId > 0) {
|
|
||||||
Mainloop.source_remove(this._searchTimeoutId);
|
|
||||||
this._searchTimeoutId = 0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._searchTimeoutId > 0)
|
|
||||||
return;
|
|
||||||
this._searchTimeoutId = Mainloop.timeout_add(150, Lang.bind(this, this._doSearch));
|
|
||||||
}));
|
|
||||||
this._searchEntry.entry.connect('activate', Lang.bind(this, function (se) {
|
|
||||||
if (this._searchTimeoutId > 0) {
|
|
||||||
Mainloop.source_remove(this._searchTimeoutId);
|
|
||||||
this._doSearch();
|
|
||||||
}
|
|
||||||
this.searchResults.activateSelected();
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
|
|
||||||
/***** Applications *****/
|
|
||||||
|
|
||||||
this._appsSection = new Section(_("APPLICATIONS"));
|
|
||||||
let appWell = new AppDisplay.AppWell();
|
|
||||||
this._appsSection.content.add(appWell.actor, { expand: true });
|
|
||||||
|
|
||||||
this._allApps = null;
|
|
||||||
this._appsSection.header.moreLink.connect('activated', Lang.bind(this, function (link) {
|
|
||||||
if (this._allApps == null) {
|
|
||||||
this._allApps = new AppDisplay.AllAppDisplay();
|
|
||||||
this._addPane(this._allApps, St.Align.START);
|
|
||||||
link.setPane(this._allApps);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.sectionArea.add(this._appsSection.actor);
|
|
||||||
|
|
||||||
/***** Places *****/
|
|
||||||
|
|
||||||
/* Translators: This is in the sense of locations for documents,
|
|
||||||
network locations, etc. */
|
|
||||||
this._placesSection = new Section(_("PLACES & DEVICES"), true);
|
|
||||||
let placesDisplay = new PlaceDisplay.DashPlaceDisplay();
|
|
||||||
this._placesSection.content.add(placesDisplay.actor, { expand: true });
|
|
||||||
this.sectionArea.add(this._placesSection.actor);
|
|
||||||
|
|
||||||
/***** Documents *****/
|
|
||||||
|
|
||||||
this._docsSection = new Section(_("RECENT ITEMS"));
|
|
||||||
|
|
||||||
this._docDisplay = new DocDisplay.DashDocDisplay();
|
|
||||||
this._docsSection.content.add(this._docDisplay.actor, { expand: true });
|
|
||||||
|
|
||||||
this._moreDocsPane = null;
|
|
||||||
this._docsSection.header.moreLink.connect('activated', Lang.bind(this, function (link) {
|
|
||||||
if (this._moreDocsPane == null) {
|
|
||||||
this._moreDocsPane = new ResultPane(this);
|
|
||||||
this._addPane(this._moreDocsPane, St.Align.END);
|
|
||||||
link.setPane(this._moreDocsPane);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this._docDisplay.connect('changed', Lang.bind(this, function () {
|
|
||||||
this._docsSection.header.setMoreLinkVisible(
|
|
||||||
this._docDisplay.actor.get_children().length > 0);
|
|
||||||
}));
|
|
||||||
this._docDisplay.emit('changed');
|
|
||||||
|
|
||||||
this.sectionArea.add(this._docsSection.actor, { expand: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyPress: function(stage, event) {
|
|
||||||
// If neither the stage nor the search entry have key focus, some
|
|
||||||
// "special" actor grabbed the focus (run dialog, looking glass);
|
|
||||||
// we don't want to interfere with that
|
|
||||||
let focus = stage.get_key_focus();
|
|
||||||
if (focus != stage && focus != this._searchEntry.entry)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let symbol = event.get_key_symbol();
|
|
||||||
if (symbol == Clutter.Escape) {
|
|
||||||
// If we're in one of the "more" modes or showing the
|
|
||||||
// details pane, close them
|
|
||||||
if (this._activePane != null)
|
|
||||||
this._activePane.close();
|
|
||||||
// Otherwise, just close the Overview entirely
|
|
||||||
else
|
|
||||||
Main.overview.hide();
|
|
||||||
return true;
|
|
||||||
} else if (symbol == Clutter.Up) {
|
|
||||||
if (!this._searchActive)
|
|
||||||
return true;
|
|
||||||
this.searchResults.selectUp(false);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else if (symbol == Clutter.Down) {
|
|
||||||
if (!this._searchActive)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
this.searchResults.selectDown(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_doSearch: function () {
|
|
||||||
this._searchTimeoutId = 0;
|
|
||||||
let text = this._searchEntry.getText();
|
|
||||||
this.searchResults.updateSearch(text);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
addSearchProvider: function(provider) {
|
|
||||||
//Add a new search provider to the dash.
|
|
||||||
|
|
||||||
this._searchSystem.registerProvider(provider);
|
|
||||||
this.searchResults.createProviderMeta(provider);
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function() {
|
|
||||||
this._searchEntry.show();
|
|
||||||
if (this._keyPressId == 0)
|
|
||||||
this._keyPressId = global.stage.connect('key-press-event',
|
|
||||||
Lang.bind(this, this._onKeyPress));
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: function() {
|
|
||||||
this._firstSelectAfterOverlayShow = true;
|
|
||||||
this._searchEntry.hide();
|
|
||||||
if (this._activePane != null)
|
|
||||||
this._activePane.close();
|
|
||||||
if (this._keyPressId > 0) {
|
|
||||||
global.stage.disconnect(this._keyPressId);
|
|
||||||
this._keyPressId = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
closePanes: function () {
|
|
||||||
if (this._activePane != null)
|
|
||||||
this._activePane.close();
|
|
||||||
},
|
|
||||||
|
|
||||||
_addPane: function(pane, align) {
|
|
||||||
pane.connect('open-state-changed', Lang.bind(this, function (pane, isOpen) {
|
|
||||||
if (isOpen) {
|
|
||||||
if (pane != this._activePane && this._activePane != null) {
|
|
||||||
this._activePane.close();
|
|
||||||
}
|
|
||||||
this._activePane = pane;
|
|
||||||
} else if (pane == this._activePane) {
|
|
||||||
this._activePane = null;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
Main.overview.addPane(pane, align);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Signals.addSignalMethods(Dash.prototype);
|
|
||||||
|
|
||||||
|
|
||||||
function Dash() {
|
function Dash() {
|
||||||
this._init();
|
this._init();
|
||||||
|
517
js/ui/viewSelector.js
Normal file
517
js/ui/viewSelector.js
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
const Gettext = imports.gettext.domain('gnome-shell');
|
||||||
|
const _ = Gettext.gettext;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const Search = imports.ui.search;
|
||||||
|
const SearchDisplay = imports.ui.searchDisplay;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
|
|
||||||
|
function SearchEntry() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchEntry.prototype = {
|
||||||
|
_init : function() {
|
||||||
|
this.actor = new St.Entry({ name: 'searchEntry',
|
||||||
|
hint_text: _("Search your computer") });
|
||||||
|
this.entry = this.actor.clutter_text;
|
||||||
|
|
||||||
|
this.actor.clutter_text.connect('text-changed', Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
if (this.isActive())
|
||||||
|
this.actor.set_secondary_icon_from_file(global.imagedir +
|
||||||
|
'close-black.svg');
|
||||||
|
else
|
||||||
|
this.actor.set_secondary_icon_from_file(null);
|
||||||
|
}));
|
||||||
|
this.actor.connect('secondary-icon-clicked', Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this.reset();
|
||||||
|
}));
|
||||||
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||||
|
|
||||||
|
global.stage.connect('notify::key-focus', Lang.bind(this, this._updateCursorVisibility));
|
||||||
|
|
||||||
|
this.pane = null;
|
||||||
|
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateCursorVisibility: function() {
|
||||||
|
let focus = global.stage.get_key_focus();
|
||||||
|
if (focus == global.stage || focus == this.entry)
|
||||||
|
this.entry.set_cursor_visible(true);
|
||||||
|
else
|
||||||
|
this.entry.set_cursor_visible(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
if (this._capturedEventId == 0)
|
||||||
|
this._capturedEventId = global.stage.connect('captured-event',
|
||||||
|
Lang.bind(this, this._onCapturedEvent));
|
||||||
|
this.entry.set_cursor_visible(true);
|
||||||
|
this.entry.set_selection(0, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
if (this.isActive())
|
||||||
|
this.reset();
|
||||||
|
if (this._capturedEventId > 0) {
|
||||||
|
global.stage.disconnect(this._capturedEventId);
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function () {
|
||||||
|
let [x, y, mask] = global.get_pointer();
|
||||||
|
let actor = global.stage.get_actor_at_pos (Clutter.PickMode.REACTIVE,
|
||||||
|
x, y);
|
||||||
|
// this.actor is never hovered directly, only its clutter_text and icon
|
||||||
|
let hovered = this.actor == actor.get_parent();
|
||||||
|
|
||||||
|
this.actor.set_hover(hovered);
|
||||||
|
|
||||||
|
this.entry.text = '';
|
||||||
|
this.entry.set_cursor_visible(true);
|
||||||
|
this.entry.set_selection(0, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
getText: function () {
|
||||||
|
return this.entry.get_text().replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
// some search term has been entered
|
||||||
|
isActive: function() {
|
||||||
|
return this.actor.get_text() != '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// the entry does not show the hint
|
||||||
|
_isActivated: function() {
|
||||||
|
return this.entry.text == this.actor.get_text();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCapturedEvent: function(actor, event) {
|
||||||
|
let source = event.get_source();
|
||||||
|
let panelEvent = source && Main.panel.actor.contains(source);
|
||||||
|
|
||||||
|
switch (event.type()) {
|
||||||
|
case Clutter.EventType.BUTTON_PRESS:
|
||||||
|
// the user clicked outside after activating the entry, but
|
||||||
|
// with no search term entered - cancel the search
|
||||||
|
if (source != this.entry && this.entry.text == '') {
|
||||||
|
this.reset();
|
||||||
|
// allow only panel events to continue
|
||||||
|
return !panelEvent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case Clutter.EventType.KEY_PRESS:
|
||||||
|
// If neither the stage nor our entry have key focus, some
|
||||||
|
// "special" actor grabbed the focus (run dialog, looking
|
||||||
|
// glass); we don't want to interfere with that
|
||||||
|
let focus = global.stage.get_key_focus();
|
||||||
|
if (focus != global.stage && focus != this.entry)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let sym = event.get_key_symbol();
|
||||||
|
|
||||||
|
// If we have an active search, Escape cancels it - if we
|
||||||
|
// haven't, the key is ignored
|
||||||
|
if (sym == Clutter.Escape)
|
||||||
|
if (this._isActivated()) {
|
||||||
|
this.reset();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore non-printable keys
|
||||||
|
if (!Clutter.keysym_to_unicode(sym))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Search started - move the key focus to the entry and
|
||||||
|
// "repeat" the event
|
||||||
|
if (!this._isActivated()) {
|
||||||
|
global.stage.set_key_focus(this.entry);
|
||||||
|
this.entry.event(event, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
// Suppress all other events outside the panel while the entry
|
||||||
|
// is activated and no search has been entered - any click
|
||||||
|
// outside the entry will cancel the search
|
||||||
|
return (this.entry.text == '' && !panelEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDestroy: function() {
|
||||||
|
if (this._capturedEventId > 0) {
|
||||||
|
global.stage.disconnect(this._capturedEventId);
|
||||||
|
this._capturedEventId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Signals.addSignalMethods(SearchEntry.prototype);
|
||||||
|
|
||||||
|
|
||||||
|
function BaseTab(titleActor, pageActor) {
|
||||||
|
this._init(titleActor, pageActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseTab.prototype = {
|
||||||
|
_init: function(titleActor, pageActor) {
|
||||||
|
this.title = titleActor;
|
||||||
|
this.page = new St.Bin({ child: pageActor,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START,
|
||||||
|
x_fill: true,
|
||||||
|
y_fill: true,
|
||||||
|
style_class: 'view-tab-page' });
|
||||||
|
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
this.visible = true;
|
||||||
|
this.page.opacity = 0;
|
||||||
|
this.page.show();
|
||||||
|
|
||||||
|
Tweener.addTween(this.page,
|
||||||
|
{ opacity: 255,
|
||||||
|
time: 0.1,
|
||||||
|
transition: 'easeOutQuad' });
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
this.visible = false;
|
||||||
|
Tweener.addTween(this.page,
|
||||||
|
{ opacity: 0,
|
||||||
|
time: 0.1,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this.page.hide();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_activate: function() {
|
||||||
|
this.emit('activated');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Signals.addSignalMethods(BaseTab.prototype);
|
||||||
|
|
||||||
|
|
||||||
|
function ViewTab(label, pageActor) {
|
||||||
|
this._init(label, pageActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewTab.prototype = {
|
||||||
|
__proto__: BaseTab.prototype;
|
||||||
|
|
||||||
|
_init: function(label, pageActor) {
|
||||||
|
let titleActor = new St.Button({ label: label,
|
||||||
|
style_class: 'view-tab-title' });
|
||||||
|
titleActor.connect('clicked', Lang.bind(this, this._activate));
|
||||||
|
|
||||||
|
BaseTab.prototype._init.call(this, titleActor, pageActor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function SearchTab(entryActor, pageActor) {
|
||||||
|
this._init(entryActor, pageActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchTab.prototype = {
|
||||||
|
__proto__: BaseTab.prototype
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function ViewSelector() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewSelector.prototype = {
|
||||||
|
_init : function() {
|
||||||
|
this.actor = new St.BoxLayout({ name: 'viewSelector',
|
||||||
|
vertical: true });
|
||||||
|
|
||||||
|
// The tab bar is located at the top of the view selector and
|
||||||
|
// holds both "normal" tab labels and the search entry. The former
|
||||||
|
// is left aligned, the latter right aligned - unless the text
|
||||||
|
// direction is RTL, in which case the order is reversed.
|
||||||
|
this._tabBar = new Shell.GenericContainer();
|
||||||
|
this._tabBar.connect('get-preferred-width',
|
||||||
|
Lang.bind(this, this._getPreferredTabBarWidth));
|
||||||
|
this._tabBar.connect('get-preferred-height',
|
||||||
|
Lang.bind(this, this._getPreferredTabBarHeight));
|
||||||
|
this._tabBar.connect('allocate',
|
||||||
|
Lang.bind(this, this._allocateTabBar));
|
||||||
|
this.actor.add(this._tabBar);
|
||||||
|
|
||||||
|
// Box to hold "normal" tab labels
|
||||||
|
this._tabBox = new St.BoxLayout({ name: 'viewSelectorTabBar' });
|
||||||
|
this._tabBar.add_actor(this._tabBox);
|
||||||
|
|
||||||
|
// The searchArea just holds the entry
|
||||||
|
this._searchArea = new St.Bin({ name: 'searchArea' });
|
||||||
|
this._tabBar.add_actor(this._searchArea);
|
||||||
|
|
||||||
|
// The page area holds the tab pages. Every page is given the
|
||||||
|
// area's full allocation, so that the pages would appear on top
|
||||||
|
// of each other if the inactive ones weren't hidden.
|
||||||
|
this._pageArea = new Shell.Stack();
|
||||||
|
this.actor.add(this._pageArea, { x_fill: true,
|
||||||
|
y_fill: true,
|
||||||
|
expand: true });
|
||||||
|
|
||||||
|
this._tabs = [];
|
||||||
|
this._activeTab = null;
|
||||||
|
|
||||||
|
this._itemDragBeginId = 0;
|
||||||
|
this._overviewHidingId = 0;
|
||||||
|
|
||||||
|
// Public constraints which may be used to tie actors' height or
|
||||||
|
// vertical position to the current tab's content; as the content's
|
||||||
|
// height and position depend on the view selector's style properties
|
||||||
|
// (e.g. font size, padding, spacing, ...) it would be extremely hard
|
||||||
|
// and ugly to get these from the outside. While it would be possible
|
||||||
|
// to use position and height properties directly, outside code would
|
||||||
|
// need to ensure that the content is properly allocated before
|
||||||
|
// accessing the properties.
|
||||||
|
this.constrainY = new Clutter.BindConstraint({ source: this._pageArea,
|
||||||
|
coordinate: Clutter.BindCoordinate.Y });
|
||||||
|
this.constrainHeight = new Clutter.BindConstraint({ source: this._pageArea,
|
||||||
|
coordinate: Clutter.BindCoordinate.HEIGHT });
|
||||||
|
|
||||||
|
/***** Search *****/
|
||||||
|
this._searchActive = false;
|
||||||
|
this._searchPending = false;
|
||||||
|
this._searchEntry = new SearchEntry();
|
||||||
|
this._searchArea.set_child(this._searchEntry.actor);
|
||||||
|
|
||||||
|
this._searchSystem = new Search.SearchSystem();
|
||||||
|
|
||||||
|
this.searchResults = new SearchDisplay.SearchResults(this._searchSystem);
|
||||||
|
this._searchTab = new SearchTab(this._searchArea,
|
||||||
|
this.searchResults.actor);
|
||||||
|
this._pageArea.add_actor(this._searchTab.page);
|
||||||
|
this._searchTab.hide();
|
||||||
|
|
||||||
|
this._keyPressId = 0;
|
||||||
|
this._searchTimeoutId = 0;
|
||||||
|
this._searchEntry.entry.connect('text-changed', Lang.bind(this, function (se, prop) {
|
||||||
|
let searchPreviouslyActive = this._searchActive;
|
||||||
|
this._searchActive = this._searchEntry.isActive();
|
||||||
|
this._searchPending = this._searchActive && !searchPreviouslyActive;
|
||||||
|
if (this._searchPending) {
|
||||||
|
this.searchResults.startingSearch();
|
||||||
|
}
|
||||||
|
if (this._searchActive) {
|
||||||
|
this._switchTab(this._searchTab);
|
||||||
|
} else {
|
||||||
|
this._switchTab(this._activeTab);
|
||||||
|
}
|
||||||
|
if (!this._searchActive) {
|
||||||
|
if (this._searchTimeoutId > 0) {
|
||||||
|
Mainloop.source_remove(this._searchTimeoutId);
|
||||||
|
this._searchTimeoutId = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._searchTimeoutId > 0)
|
||||||
|
return;
|
||||||
|
this._searchTimeoutId = Mainloop.timeout_add(150, Lang.bind(this, this._doSearch));
|
||||||
|
}));
|
||||||
|
this._searchEntry.entry.connect('activate', Lang.bind(this, function (se) {
|
||||||
|
if (this._searchTimeoutId > 0) {
|
||||||
|
Mainloop.source_remove(this._searchTimeoutId);
|
||||||
|
this._doSearch();
|
||||||
|
}
|
||||||
|
this.searchResults.activateSelected();
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
addViewTab: function(title, pageActor) {
|
||||||
|
let viewTab = new ViewTab(title, pageActor);
|
||||||
|
this._tabs.push(viewTab);
|
||||||
|
this._tabBox.add(viewTab.title);
|
||||||
|
this._pageArea.add_actor(viewTab.page);
|
||||||
|
viewTab.page.hide();
|
||||||
|
|
||||||
|
viewTab.connect('activated', Lang.bind(this,
|
||||||
|
function(tab) {
|
||||||
|
this._switchTab(tab);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_switchTab: function(tab) {
|
||||||
|
if (this._activeTab && this._activeTab.visible) {
|
||||||
|
if (this._activeTab == tab)
|
||||||
|
return;
|
||||||
|
this._activeTab.title.remove_style_pseudo_class('selected');
|
||||||
|
this._activeTab.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab != this._searchTab) {
|
||||||
|
tab.title.add_style_pseudo_class('selected');
|
||||||
|
this._activeTab = tab;
|
||||||
|
if (this._searchTab.visible) {
|
||||||
|
this._searchTab.hide();
|
||||||
|
this._searchEntry.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tab.visible)
|
||||||
|
tab.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
_switchDefaultTab: function() {
|
||||||
|
if (this._tabs.length > 0)
|
||||||
|
this._switchTab(this._tabs[0]);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredTabBarWidth: function(box, forHeight, alloc) {
|
||||||
|
let children = box.get_children();
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
let [childMin, childNat] = children[i].get_preferred_width(forHeight);
|
||||||
|
alloc.min_size += childMin;
|
||||||
|
alloc.natural_size += childNat;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPreferredTabBarHeight: function(box, forWidth, alloc) {
|
||||||
|
let children = box.get_children();
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
let [childMin, childNatural] = children[i].get_preferred_height(forWidth);
|
||||||
|
if (childMin > alloc.min_size)
|
||||||
|
alloc.min_size = childMin;
|
||||||
|
if (childNatural > alloc.natural_size)
|
||||||
|
alloc.natural_size = childNatural;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_allocateTabBar: function(container, box, flags) {
|
||||||
|
let allocWidth = box.x2 - box.x1;
|
||||||
|
let allocHeight = box.y2 - box.y1;
|
||||||
|
|
||||||
|
let [searchMinWidth, searchNatWidth] = this._searchArea.get_preferred_width(-1);
|
||||||
|
let [barMinWidth, barNatWidth] = this._tabBox.get_preferred_width(-1);
|
||||||
|
let childBox = new Clutter.ActorBox();
|
||||||
|
childBox.y1 = 0;
|
||||||
|
childBox.y2 = allocHeight;
|
||||||
|
if (this.actor.get_direction() == St.TextDirection.RTL) {
|
||||||
|
childBox.x1 = allocWidth - barNatWidth;
|
||||||
|
childBox.x2 = allocWidth;
|
||||||
|
} else {
|
||||||
|
childBox.x1 = 0;
|
||||||
|
childBox.x2 = barNatWidth;
|
||||||
|
}
|
||||||
|
this._tabBox.allocate(childBox, flags);
|
||||||
|
|
||||||
|
if (this.actor.get_direction() == St.TextDirection.RTL) {
|
||||||
|
childBox.x1 = 0;
|
||||||
|
childBox.x2 = searchNatWidth;
|
||||||
|
} else {
|
||||||
|
childBox.x1 = allocWidth - searchNatWidth;
|
||||||
|
childBox.x2 = allocWidth;
|
||||||
|
}
|
||||||
|
this._searchArea.allocate(childBox, flags);
|
||||||
|
|
||||||
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this,
|
||||||
|
function() {
|
||||||
|
this.constrainY.offset = this.actor.y;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onKeyPress: function(stage, event) {
|
||||||
|
// If neither the stage nor the search entry have key focus, some
|
||||||
|
// "special" actor grabbed the focus (run dialog, looking glass);
|
||||||
|
// we don't want to interfere with that
|
||||||
|
let focus = stage.get_key_focus();
|
||||||
|
if (focus != stage && focus != this._searchEntry.entry)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let symbol = event.get_key_symbol();
|
||||||
|
if (symbol == Clutter.Escape) {
|
||||||
|
Main.overview.hide();
|
||||||
|
return true;
|
||||||
|
} else if (symbol == Clutter.Up) {
|
||||||
|
if (!this._searchActive)
|
||||||
|
return true;
|
||||||
|
this.searchResults.selectUp(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (symbol == Clutter.Down) {
|
||||||
|
if (!this._searchActive)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
this.searchResults.selectDown(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_doSearch: function () {
|
||||||
|
this._searchTimeoutId = 0;
|
||||||
|
let text = this._searchEntry.getText();
|
||||||
|
this.searchResults.updateSearch(text);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
addSearchProvider: function(provider) {
|
||||||
|
this._searchSystem.registerProvider(provider);
|
||||||
|
this.searchResults.createProviderMeta(provider);
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
// Connect type-as-you-find listener
|
||||||
|
this._searchEntry.show();
|
||||||
|
|
||||||
|
if (this._itemDragBeginId == 0)
|
||||||
|
this._itemDragBeginId = Main.overview.connect('item-drag-begin',
|
||||||
|
Lang.bind(this, this._switchDefaultTab));
|
||||||
|
if (this._overviewHidingId == 0)
|
||||||
|
this._overviewHidingId = Main.overview.connect('hiding',
|
||||||
|
Lang.bind(this, this._switchDefaultTab));
|
||||||
|
if (this._keyPressId == 0)
|
||||||
|
this._keyPressId = global.stage.connect('key-press-event',
|
||||||
|
Lang.bind(this, this._onKeyPress));
|
||||||
|
|
||||||
|
this._switchDefaultTab();
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
// Disconnect type-as-you-find listener
|
||||||
|
this._searchEntry.hide();
|
||||||
|
|
||||||
|
if (this._keyPressId > 0) {
|
||||||
|
global.stage.disconnect(this._keyPressId);
|
||||||
|
this._keyPressId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._itemDragBeginId > 0) {
|
||||||
|
Main.overview.disconnect(this._itemDragBeginId);
|
||||||
|
this._itemDragBeginId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._overviewHidingId > 0) {
|
||||||
|
Main.overview.disconnect(this._overviewHidingId);
|
||||||
|
this._overviewHidingId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Signals.addSignalMethods(ViewSelector.prototype);
|
@ -15,6 +15,7 @@ js/ui/popupMenu.js
|
|||||||
js/ui/runDialog.js
|
js/ui/runDialog.js
|
||||||
js/ui/statusMenu.js
|
js/ui/statusMenu.js
|
||||||
js/ui/status/accessibility.js
|
js/ui/status/accessibility.js
|
||||||
|
js/ui/viewSelector.js
|
||||||
js/ui/windowAttentionHandler.js
|
js/ui/windowAttentionHandler.js
|
||||||
js/ui/workspacesView.js
|
js/ui/workspacesView.js
|
||||||
src/gvc/gvc-mixer-control.c
|
src/gvc/gvc-mixer-control.c
|
||||||
|
Loading…
Reference in New Issue
Block a user