[gnome-shell] switcherPopup: Factor out altTab and ctrlAltTab's common code
- From: Rui Matos <rtcm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] switcherPopup: Factor out altTab and ctrlAltTab's common code
- Date: Tue, 4 Dec 2012 09:11:16 +0000 (UTC)
commit 00338bbc4bb34041568c215c32017b872a0a290e
Author: Rui Matos <tiagomatos gmail com>
Date: Fri Nov 30 16:16:48 2012 +0100
switcherPopup: Factor out altTab and ctrlAltTab's common code
https://bugzilla.gnome.org/show_bug.cgi?id=689528
data/theme/gnome-shell.css | 2 +-
js/Makefile.am | 1 +
js/ui/altTab.js | 697 ++++++--------------------------------------
js/ui/ctrlAltTab.js | 183 ++----------
js/ui/switcherPopup.js | 640 ++++++++++++++++++++++++++++++++++++++++
js/ui/windowManager.js | 2 +-
6 files changed, 753 insertions(+), 772 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index c2b69b1..a4b9842 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1542,7 +1542,7 @@ StScrollBar StButton#vhandle:active {
}
/* App Switcher */
-#altTabPopup {
+.switcher-popup {
padding: 8px;
spacing: 16px;
}
diff --git a/js/Makefile.am b/js/Makefile.am
index a3e4917..6d13262 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -85,6 +85,7 @@ nobase_dist_js_DATA = \
ui/status/power.js \
ui/status/volume.js \
ui/status/bluetooth.js \
+ ui/switcherPopup.js \
ui/tweener.js \
ui/unlockDialog.js \
ui/userMenu.js \
diff --git a/js/ui/altTab.js b/js/ui/altTab.js
index 5de7d7b..74a03a0 100644
--- a/js/ui/altTab.js
+++ b/js/ui/altTab.js
@@ -1,117 +1,56 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
-const Gdk = imports.gi.Gdk;
-const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
-const Signals = imports.signals;
const St = imports.gi.St;
const Atk = imports.gi.Atk;
const Main = imports.ui.main;
+const SwitcherPopup = imports.ui.switcherPopup;
const Tweener = imports.ui.tweener;
-const POPUP_APPICON_SIZE = 96;
-const POPUP_SCROLL_TIME = 0.10; // seconds
-const POPUP_DELAY_TIMEOUT = 150; // milliseconds
-const POPUP_FADE_OUT_TIME = 0.1; // seconds
-
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
-const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
-
const THUMBNAIL_DEFAULT_SIZE = 256;
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
const THUMBNAIL_FADE_TIME = 0.1; // seconds
const iconSizes = [96, 64, 48, 32, 22];
-function mod(a, b) {
- return (a + b) % b;
-}
-
-function primaryModifier(mask) {
- if (mask == 0)
- return 0;
-
- let primary = 1;
- while (mask > 1) {
- mask >>= 1;
- primary <<= 1;
- }
- return primary;
-}
-
const AltTabPopup = new Lang.Class({
Name: 'AltTabPopup',
+ Extends: SwitcherPopup.SwitcherPopup,
_init : function() {
- this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
- reactive: true,
- visible: false });
+ this.parent();
- this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
- this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
- this.actor.connect('allocate', Lang.bind(this, this._allocate));
-
- this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
-
- this._haveModal = false;
- this._modifierMask = 0;
-
- this._currentApp = 0;
- this._currentWindow = -1;
+ this._thumbnails = null;
this._thumbnailTimeoutId = 0;
- this._motionTimeoutId = 0;
- this._initialDelayTimeoutId = 0;
+ this._currentWindow = -1;
this.thumbnailsVisible = false;
-
- // Initially disable hover so we ignore the enter-event if
- // the switcher appears underneath the current pointer location
- this._disableHover();
-
- Main.uiGroup.add_actor(this.actor);
- },
-
- _getPreferredWidth: function (actor, forHeight, alloc) {
- alloc.min_size = global.screen_width;
- alloc.natural_size = global.screen_width;
- },
-
- _getPreferredHeight: function (actor, forWidth, alloc) {
- alloc.min_size = global.screen_height;
- alloc.natural_size = global.screen_height;
},
_allocate: function (actor, box, flags) {
- let childBox = new Clutter.ActorBox();
- let primary = Main.layoutManager.primaryMonitor;
-
- let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
- let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
- let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
- let vPadding = this.actor.get_theme_node().get_vertical_padding();
- let hPadding = leftPadding + rightPadding;
-
- // Allocate the appSwitcher
- // We select a size based on an icon size that does not overflow the screen
- let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
- let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
- childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
- childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
- childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
- childBox.y2 = childBox.y1 + childNaturalHeight;
- this._appSwitcher.actor.allocate(childBox, flags);
+ this.parent(actor, box, flags);
// Allocate the thumbnails
// We try to avoid overflowing the screen so we base the resulting size on
// those calculations
if (this._thumbnails) {
- let icon = this._appIcons[this._currentApp].actor;
+ let childBox = this._switcherList.actor.get_allocation_box();
+ let primary = Main.layoutManager.primaryMonitor;
+
+ let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+ let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+ let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+ let vPadding = this.actor.get_theme_node().get_vertical_padding();
+ let hPadding = leftPadding + rightPadding;
+
+ let icon = this._items[this._selectedIndex].actor;
let [posX, posY] = icon.get_transformed_position();
let thumbnailCenter = posX + icon.width / 2;
let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1);
@@ -126,7 +65,7 @@ const AltTabPopup = new Lang.Class({
childBox.x2 = childBox.x1 + childNaturalWidth;
if (childBox.x2 > primary.x + primary.width - rightPadding)
childBox.x2 = primary.x + primary.width - rightPadding;
- childBox.y1 = this._appSwitcher.actor.allocation.y2 + spacing;
+ childBox.y1 = this._switcherList.actor.allocation.y2 + spacing;
this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1);
let [childMinHeight, childNaturalHeight] = this._thumbnails.actor.get_preferred_height(-1);
childBox.y2 = childBox.y1 + childNaturalHeight;
@@ -164,280 +103,158 @@ const AltTabPopup = new Lang.Class({
return [apps, allApps];
},
- show : function(backward, binding, mask) {
+ _createSwitcher: function() {
let [localApps, otherApps] = this._getAppLists();
if (localApps.length == 0 && otherApps.length == 0)
return false;
- if (!Main.pushModal(this.actor)) {
- // Probably someone else has a pointer grab, try again with keyboard only
- if (!Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED })) {
- return false;
- }
- }
- this._haveModal = true;
- this._modifierMask = primaryModifier(mask);
+ this._switcherList = new AppSwitcher(localApps, otherApps, this);
+ this._items = this._switcherList.icons;
- this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
- this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
-
- this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
- this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
-
- this._appSwitcher = new AppSwitcher(localApps, otherApps, this);
- this.actor.add_actor(this._appSwitcher.actor);
- this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
- this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
-
- this._appIcons = this._appSwitcher.icons;
-
- // Need to force an allocation so we can figure out whether we
- // need to scroll when selecting
- this.actor.opacity = 0;
- this.actor.show();
- this.actor.get_allocation_box();
+ return true;
+ },
- // Make the initial selection
+ _initialSelection: function(backward, binding) {
if (binding == 'switch-group') {
if (backward) {
- this._select(0, this._appIcons[0].cachedWindows.length - 1);
+ this._select(0, this._items[0].cachedWindows.length - 1);
} else {
- if (this._appIcons[0].cachedWindows.length > 1)
+ if (this._items[0].cachedWindows.length > 1)
this._select(0, 1);
else
this._select(0, 0);
}
} else if (binding == 'switch-group-backward') {
- this._select(0, this._appIcons[0].cachedWindows.length - 1);
+ this._select(0, this._items[0].cachedWindows.length - 1);
} else if (binding == 'switch-windows-backward') {
- this._select(this._appIcons.length - 1);
- } else if (this._appIcons.length == 1) {
+ this._select(this._items.length - 1);
+ } else if (this._items.length == 1) {
this._select(0);
} else if (backward) {
- this._select(this._appIcons.length - 1);
+ this._select(this._items.length - 1);
} else {
this._select(1);
}
-
- // There's a race condition; if the user released Alt before
- // we got the grab, then we won't be notified. (See
- // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
- // details.) So we check now. (Have to do this after updating
- // selection.)
- let [x, y, mods] = global.get_pointer();
- if (!(mods & this._modifierMask)) {
- this._finish();
- return false;
- }
-
- // We delay showing the popup so that fast Alt+Tab users aren't
- // disturbed by the popup briefly flashing.
- this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
- Lang.bind(this, function () {
- this.actor.opacity = 255;
- this._initialDelayTimeoutId = 0;
- }));
-
- return true;
- },
-
- _nextApp : function() {
- return mod(this._currentApp + 1, this._appIcons.length);
- },
- _previousApp : function() {
- return mod(this._currentApp - 1, this._appIcons.length);
},
_nextWindow : function() {
// We actually want the second window if we're in the unset state
if (this._currentWindow == -1)
this._currentWindow = 0;
- return mod(this._currentWindow + 1,
- this._appIcons[this._currentApp].cachedWindows.length);
+ return SwitcherPopup.mod(this._currentWindow + 1,
+ this._items[this._selectedIndex].cachedWindows.length);
},
_previousWindow : function() {
// Also assume second window here
if (this._currentWindow == -1)
this._currentWindow = 1;
- return mod(this._currentWindow - 1,
- this._appIcons[this._currentApp].cachedWindows.length);
+ return SwitcherPopup.mod(this._currentWindow - 1,
+ this._items[this._selectedIndex].cachedWindows.length);
},
- _keyPressEvent : function(actor, event) {
- let keysym = event.get_key_symbol();
- let event_state = event.get_state();
- let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
- let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
-
- this._disableHover();
-
- if (keysym == Clutter.Escape) {
- this.destroy();
- } else if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
- this._select(this._currentApp, backwards ? this._previousWindow() : this._nextWindow());
+ _keyPressHandler: function(keysym, backwards, action) {
+ if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
+ this._select(this._selectedIndex, backwards ? this._previousWindow() : this._nextWindow());
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
- this._select(this._currentApp, this._previousWindow());
+ this._select(this._selectedIndex, this._previousWindow());
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
- this._select(backwards ? this._previousApp() : this._nextApp());
+ this._select(backwards ? this._previous() : this._next());
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
- this._select(this._previousApp());
+ this._select(this._previous());
} else if (this._thumbnailsFocused) {
if (keysym == Clutter.Left)
- this._select(this._currentApp, this._previousWindow());
+ this._select(this._selectedIndex, this._previousWindow());
else if (keysym == Clutter.Right)
- this._select(this._currentApp, this._nextWindow());
+ this._select(this._selectedIndex, this._nextWindow());
else if (keysym == Clutter.Up)
- this._select(this._currentApp, null, true);
+ this._select(this._selectedIndex, null, true);
} else {
if (keysym == Clutter.Left)
- this._select(this._previousApp());
+ this._select(this._previous());
else if (keysym == Clutter.Right)
- this._select(this._nextApp());
+ this._select(this._next());
else if (keysym == Clutter.Down)
- this._select(this._currentApp, 0);
+ this._select(this._selectedIndex, 0);
}
-
- return true;
- },
-
- _keyReleaseEvent : function(actor, event) {
- let [x, y, mods] = global.get_pointer();
- let state = mods & this._modifierMask;
-
- if (state == 0)
- this._finish();
-
- return true;
},
- _onScroll : function(actor, event) {
- let direction = event.get_scroll_direction();
+ _scrollHandler: function(direction) {
if (direction == Clutter.ScrollDirection.UP) {
if (this._thumbnailsFocused) {
if (this._currentWindow == 0 || this._currentWindow == -1)
- this._select(this._previousApp());
+ this._select(this._previous());
else
- this._select(this._currentApp, this._previousWindow());
+ this._select(this._selectedIndex, this._previousWindow());
} else {
- let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
+ let nwindows = this._items[this._selectedIndex].cachedWindows.length;
if (nwindows > 1)
- this._select(this._currentApp, nwindows - 1);
+ this._select(this._selectedIndex, nwindows - 1);
else
- this._select(this._previousApp());
+ this._select(this._previous());
}
} else if (direction == Clutter.ScrollDirection.DOWN) {
if (this._thumbnailsFocused) {
- if (this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
- this._select(this._nextApp());
+ if (this._currentWindow == this._items[this._selectedIndex].cachedWindows.length - 1)
+ this._select(this._next());
else
- this._select(this._currentApp, this._nextWindow());
+ this._select(this._selectedIndex, this._nextWindow());
} else {
- let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
+ let nwindows = this._items[this._selectedIndex].cachedWindows.length;
if (nwindows > 1)
- this._select(this._currentApp, 0);
+ this._select(this._selectedIndex, 0);
else
- this._select(this._nextApp());
+ this._select(this._next());
}
}
},
- _clickedOutside : function(actor, event) {
- this.destroy();
- },
-
- _appActivated : function(appSwitcher, n) {
+ _itemActivatedHandler: function(n) {
// If the user clicks on the selected app, activate the
// selected window; otherwise (eg, they click on an app while
// !mouseActive) activate the clicked-on app.
- let window;
- if (n == this._currentApp && this._currentWindow >= 0)
- window = this._appIcons[n].cachedWindows[this._currentWindow];
+ if (n == this._selectedIndex && this._currentWindow >= 0)
+ this._select(n, this._currentWindow);
else
- window = null;
- this._appIcons[n].app.activate_window(window, global.get_current_time());
- this.destroy();
+ this._select(n);
},
- _appEntered : function(appSwitcher, n) {
- if (!this._mouseActive)
- return;
-
+ _itemEnteredHandler: function(n) {
this._select(n);
},
_windowActivated : function(thumbnailList, n) {
- let appIcon = this._appIcons[this._currentApp];
+ let appIcon = this._items[this._selectedIndex];
Main.activateWindow(appIcon.cachedWindows[n]);
this.destroy();
},
_windowEntered : function(thumbnailList, n) {
- if (!this._mouseActive)
+ if (!this.mouseActive)
return;
- this._select(this._currentApp, n);
- },
-
- _disableHover : function() {
- this._mouseActive = false;
-
- if (this._motionTimeoutId != 0)
- Mainloop.source_remove(this._motionTimeoutId);
-
- this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
- },
-
- _mouseTimedOut : function() {
- this._motionTimeoutId = 0;
- this._mouseActive = true;
+ this._select(this._selectedIndex, n);
},
_finish : function() {
- let app = this._appIcons[this._currentApp];
- if (this._currentWindow >= 0) {
- Main.activateWindow(app.cachedWindows[this._currentWindow]);
- } else {
- app.app.activate_window(null, global.get_current_time());
- }
- this.destroy();
- },
-
- _popModal: function() {
- if (this._haveModal) {
- Main.popModal(this.actor);
- this._haveModal = false;
- }
- },
+ this.parent();
- destroy : function() {
- this._popModal();
- if (this.actor.visible) {
- Tweener.addTween(this.actor,
- { opacity: 0,
- time: POPUP_FADE_OUT_TIME,
- transition: 'easeOutQuad',
- onComplete: Lang.bind(this,
- function() {
- this.actor.destroy();
- })
- });
- } else
- this.actor.destroy();
+ let appIcon = this._items[this._selectedIndex];
+ let window;
+ if (this._currentWindow >= 0)
+ window = appIcon.cachedWindows[this._currentWindow];
+ else
+ window = null;
+ appIcon.app.activate_window(window, global.get_current_time());
},
_onDestroy : function() {
- this._popModal();
+ this.parent();
if (this._thumbnails)
this._destroyThumbnails();
-
- if (this._motionTimeoutId != 0)
- Mainloop.source_remove(this._motionTimeoutId);
if (this._thumbnailTimeoutId != 0)
Mainloop.source_remove(this._thumbnailTimeoutId);
- if (this._initialDelayTimeoutId != 0)
- Mainloop.source_remove(this._initialDelayTimeoutId);
},
/**
@@ -466,7 +283,7 @@ const AltTabPopup = new Lang.Class({
* app list will have the keyboard focus.
*/
_select : function(app, window, forceAppFocus) {
- if (app != this._currentApp || window == null) {
+ if (app != this._selectedIndex || window == null) {
if (this._thumbnails)
this._destroyThumbnails();
}
@@ -478,16 +295,16 @@ const AltTabPopup = new Lang.Class({
this._thumbnailsFocused = (window != null) && !forceAppFocus;
- this._currentApp = app;
+ this._selectedIndex = app;
this._currentWindow = window ? window : -1;
- this._appSwitcher.highlight(app, this._thumbnailsFocused);
+ this._switcherList.highlight(app, this._thumbnailsFocused);
if (window != null) {
if (!this._thumbnails)
this._createThumbnails();
this._currentWindow = window;
this._thumbnails.highlight(window, forceAppFocus);
- } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
+ } else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
!forceAppFocus) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
THUMBNAIL_POPUP_TIME,
@@ -515,11 +332,11 @@ const AltTabPopup = new Lang.Class({
})
});
this._thumbnails = null;
- this._appSwitcher._items[this._currentApp].remove_accessible_state (Atk.StateType.EXPANDED);
+ this._switcherList._items[this._selectedIndex].remove_accessible_state (Atk.StateType.EXPANDED);
},
_createThumbnails : function() {
- this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
+ this._thumbnails = new ThumbnailList (this._items[this._selectedIndex].cachedWindows);
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
@@ -537,314 +354,10 @@ const AltTabPopup = new Lang.Class({
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
});
- this._appSwitcher._items[this._currentApp].add_accessible_state (Atk.StateType.EXPANDED);
- }
-});
-
-const SwitcherList = new Lang.Class({
- Name: 'SwitcherList',
-
- _init : function(squareItems) {
- this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
- this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
- this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
- this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
-
- // Here we use a GenericContainer so that we can force all the
- // children except the separator to have the same width.
- this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
- this._list.spacing = 0;
- this._list.connect('style-changed', Lang.bind(this, function() {
- this._list.spacing = this._list.get_theme_node().get_length('spacing');
- }));
-
- this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
- this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
- this._list.connect('allocate', Lang.bind(this, this._allocate));
-
- this._scrollView = new St.ScrollView({ style_class: 'hfade',
- enable_mouse_scrolling: false });
- this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
-
- let scrollBox = new St.BoxLayout();
- scrollBox.add_actor(this._list);
- this._scrollView.add_actor(scrollBox);
- this.actor.add_actor(this._scrollView);
-
- // Those arrows indicate whether scrolling in one direction is possible
- this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
- pseudo_class: 'highlighted' });
- this._leftArrow.connect('repaint', Lang.bind(this,
- function() { _drawArrow(this._leftArrow, St.Side.LEFT); }));
- this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
- pseudo_class: 'highlighted' });
- this._rightArrow.connect('repaint', Lang.bind(this,
- function() { _drawArrow(this._rightArrow, St.Side.RIGHT); }));
-
- this.actor.add_actor(this._leftArrow);
- this.actor.add_actor(this._rightArrow);
-
- this._items = [];
- this._highlighted = -1;
- this._separator = null;
- this._squareItems = squareItems;
- this._minSize = 0;
- this._scrollableRight = true;
- this._scrollableLeft = false;
- },
-
- _allocateTop: function(actor, box, flags) {
- let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
- let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
-
- let childBox = new Clutter.ActorBox();
- let scrollable = this._minSize > box.x2 - box.x1;
-
- box.y1 -= this.actor.get_theme_node().get_padding(St.Side.TOP);
- box.y2 += this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
- this._scrollView.allocate(box, flags);
-
- let arrowWidth = Math.floor(leftPadding / 3);
- let arrowHeight = arrowWidth * 2;
- childBox.x1 = leftPadding / 2;
- childBox.y1 = this.actor.height / 2 - arrowWidth;
- childBox.x2 = childBox.x1 + arrowWidth;
- childBox.y2 = childBox.y1 + arrowHeight;
- this._leftArrow.allocate(childBox, flags);
- this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
-
- arrowWidth = Math.floor(rightPadding / 3);
- arrowHeight = arrowWidth * 2;
- childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
- childBox.y1 = this.actor.height / 2 - arrowWidth;
- childBox.x2 = childBox.x1 + arrowWidth;
- childBox.y2 = childBox.y1 + arrowHeight;
- this._rightArrow.allocate(childBox, flags);
- this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
- },
-
- addItem : function(item, label) {
- let bbox = new St.Button({ style_class: 'item-box',
- reactive: true });
-
- bbox.set_child(item);
- this._list.add_actor(bbox);
-
- let n = this._items.length;
- bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
- bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
-
- bbox.label_actor = label;
-
- this._items.push(bbox);
-
- return bbox;
- },
-
- _onItemClicked: function (index) {
- this._itemActivated(index);
- },
-
- _onItemEnter: function (index) {
- this._itemEntered(index);
- },
-
- addSeparator: function () {
- let box = new St.Bin({ style_class: 'separator' });
- this._separator = box;
- this._list.add_actor(box);
- },
-
- highlight: function(index, justOutline) {
- if (this._highlighted != -1) {
- this._items[this._highlighted].remove_style_pseudo_class('outlined');
- this._items[this._highlighted].remove_style_pseudo_class('selected');
- }
-
- this._highlighted = index;
-
- if (this._highlighted != -1) {
- if (justOutline)
- this._items[this._highlighted].add_style_pseudo_class('outlined');
- else
- this._items[this._highlighted].add_style_pseudo_class('selected');
- }
-
- let adjustment = this._scrollView.hscroll.adjustment;
- let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
- let [absItemX, absItemY] = this._items[index].get_transformed_position();
- let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
- let [containerWidth, containerHeight] = this.actor.get_transformed_size();
- if (posX + this._items[index].get_width() > containerWidth)
- this._scrollToRight();
- else if (this._items[index].allocation.x1 - value < 0)
- this._scrollToLeft();
-
- },
-
- _scrollToLeft : function() {
- let adjustment = this._scrollView.hscroll.adjustment;
- let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
-
- let item = this._items[this._highlighted];
-
- if (item.allocation.x1 < value)
- value = Math.min(0, item.allocation.x1);
- else if (item.allocation.x2 > value + pageSize)
- value = Math.max(upper, item.allocation.x2 - pageSize);
-
- this._scrollableRight = true;
- Tweener.addTween(adjustment,
- { value: value,
- time: POPUP_SCROLL_TIME,
- transition: 'easeOutQuad',
- onComplete: Lang.bind(this, function () {
- if (this._highlighted == 0) {
- this._scrollableLeft = false;
- this.actor.queue_relayout();
- }
- })
- });
- },
-
- _scrollToRight : function() {
- let adjustment = this._scrollView.hscroll.adjustment;
- let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
-
- let item = this._items[this._highlighted];
-
- if (item.allocation.x1 < value)
- value = Math.max(0, item.allocation.x1);
- else if (item.allocation.x2 > value + pageSize)
- value = Math.min(upper, item.allocation.x2 - pageSize);
-
- this._scrollableLeft = true;
- Tweener.addTween(adjustment,
- { value: value,
- time: POPUP_SCROLL_TIME,
- transition: 'easeOutQuad',
- onComplete: Lang.bind(this, function () {
- if (this._highlighted == this._items.length - 1) {
- this._scrollableRight = false;
- this.actor.queue_relayout();
- }
- })
- });
- },
-
- _itemActivated: function(n) {
- this.emit('item-activated', n);
- },
-
- _itemEntered: function(n) {
- this.emit('item-entered', n);
- },
-
- _maxChildWidth: function (forHeight) {
- let maxChildMin = 0;
- let maxChildNat = 0;
-
- for (let i = 0; i < this._items.length; i++) {
- let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
- maxChildMin = Math.max(childMin, maxChildMin);
- maxChildNat = Math.max(childNat, maxChildNat);
-
- if (this._squareItems) {
- let [childMin, childNat] = this._items[i].get_preferred_height(-1);
- maxChildMin = Math.max(childMin, maxChildMin);
- maxChildNat = Math.max(childNat, maxChildNat);
- }
- }
-
- return [maxChildMin, maxChildNat];
- },
-
- _getPreferredWidth: function (actor, forHeight, alloc) {
- let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
-
- let separatorWidth = 0;
- if (this._separator) {
- let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
- separatorWidth = sepNat + this._list.spacing;
- }
-
- let totalSpacing = this._list.spacing * (this._items.length - 1);
- alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
- alloc.natural_size = alloc.min_size;
- this._minSize = alloc.min_size;
- },
-
- _getPreferredHeight: function (actor, forWidth, alloc) {
- let maxChildMin = 0;
- let maxChildNat = 0;
-
- for (let i = 0; i < this._items.length; i++) {
- let [childMin, childNat] = this._items[i].get_preferred_height(-1);
- maxChildMin = Math.max(childMin, maxChildMin);
- maxChildNat = Math.max(childNat, maxChildNat);
- }
-
- if (this._squareItems) {
- let [childMin, childNat] = this._maxChildWidth(-1);
- maxChildMin = Math.max(childMin, maxChildMin);
- maxChildNat = maxChildMin;
- }
-
- alloc.min_size = maxChildMin;
- alloc.natural_size = maxChildNat;
- },
-
- _allocate: function (actor, box, flags) {
- let childHeight = box.y2 - box.y1;
-
- let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
- let totalSpacing = this._list.spacing * (this._items.length - 1);
-
- let separatorWidth = 0;
- if (this._separator) {
- let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
- separatorWidth = sepNat;
- totalSpacing += this._list.spacing;
- }
-
- let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
-
- let x = 0;
- let children = this._list.get_children();
- let childBox = new Clutter.ActorBox();
-
- let primary = Main.layoutManager.primaryMonitor;
- let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
-
- for (let i = 0; i < children.length; i++) {
- if (this._items.indexOf(children[i]) != -1) {
- let [childMin, childNat] = children[i].get_preferred_height(childWidth);
- let vSpacing = (childHeight - childNat) / 2;
- childBox.x1 = x;
- childBox.y1 = vSpacing;
- childBox.x2 = x + childWidth;
- childBox.y2 = childBox.y1 + childNat;
- children[i].allocate(childBox, flags);
-
- x += this._list.spacing + childWidth;
- } else if (children[i] == this._separator) {
- // We want the separator to be more compact than the rest.
- childBox.x1 = x;
- childBox.y1 = 0;
- childBox.x2 = x + separatorWidth;
- childBox.y2 = childHeight;
- children[i].allocate(childBox, flags);
- x += this._list.spacing + separatorWidth;
- } else {
- // Something else, eg, AppSwitcher's arrows;
- // we don't allocate it.
- }
- }
+ this._switcherList._items[this._selectedIndex].add_accessible_state (Atk.StateType.EXPANDED);
}
});
-Signals.addSignalMethods(SwitcherList.prototype);
-
const AppIcon = new Lang.Class({
Name: 'AppIcon',
@@ -869,7 +382,7 @@ const AppIcon = new Lang.Class({
const AppSwitcher = new Lang.Class({
Name: 'AppSwitcher',
- Extends: SwitcherList,
+ Extends: SwitcherPopup.SwitcherList,
_init : function(localApps, otherApps, altTabPopup) {
this.parent(true);
@@ -1023,7 +536,7 @@ const AppSwitcher = new Lang.Class({
let n = this._arrows.length;
let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
- arrow.connect('repaint', function() { _drawArrow(arrow, St.Side.BOTTOM); });
+ arrow.connect('repaint', function() { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); });
this._list.add_actor(arrow);
this._arrows.push(arrow);
@@ -1036,7 +549,7 @@ const AppSwitcher = new Lang.Class({
const ThumbnailList = new Lang.Class({
Name: 'ThumbnailList',
- Extends: SwitcherList,
+ Extends: SwitcherPopup.SwitcherList,
_init : function(windows) {
this.parent(false);
@@ -1119,45 +632,3 @@ const ThumbnailList = new Lang.Class({
}
});
-function _drawArrow(area, side) {
- let themeNode = area.get_theme_node();
- let borderColor = themeNode.get_border_color(side);
- let bodyColor = themeNode.get_foreground_color();
-
- let [width, height] = area.get_surface_size ();
- let cr = area.get_context();
-
- cr.setLineWidth(1.0);
- Clutter.cairo_set_source_color(cr, borderColor);
-
- switch (side) {
- case St.Side.TOP:
- cr.moveTo(0, height);
- cr.lineTo(Math.floor(width * 0.5), 0);
- cr.lineTo(width, height);
- break;
-
- case St.Side.BOTTOM:
- cr.moveTo(width, 0);
- cr.lineTo(Math.floor(width * 0.5), height);
- cr.lineTo(0, 0);
- break;
-
- case St.Side.LEFT:
- cr.moveTo(width, height);
- cr.lineTo(0, Math.floor(height * 0.5));
- cr.lineTo(width, 0);
- break;
-
- case St.Side.RIGHT:
- cr.moveTo(0, 0);
- cr.lineTo(width, Math.floor(height * 0.5));
- cr.lineTo(0, height);
- break;
- }
-
- cr.strokePreserve();
-
- Clutter.cairo_set_source_color(cr, bodyColor);
- cr.fill();
-}
diff --git a/js/ui/ctrlAltTab.js b/js/ui/ctrlAltTab.js
index 0519c99..14fba02 100644
--- a/js/ui/ctrlAltTab.js
+++ b/js/ui/ctrlAltTab.js
@@ -1,15 +1,14 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
-const Gdk = imports.gi.Gdk;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
-const AltTab = imports.ui.altTab;
const Main = imports.ui.main;
+const SwitcherPopup = imports.ui.switcherPopup;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
@@ -92,7 +91,7 @@ const CtrlAltTabManager = new Lang.Class({
return a.x - b.x;
},
- popup: function(backwards, mask) {
+ popup: function(backward, binding, mask) {
// Start with the set of focus groups that are currently mapped
let items = this._items.filter(function (item) { return item.proxy.mapped; });
@@ -123,8 +122,8 @@ const CtrlAltTabManager = new Lang.Class({
items.sort(Lang.bind(this, this._sortItems));
if (!this._popup) {
- this._popup = new CtrlAltTabPopup();
- this._popup.show(items, backwards, mask);
+ this._popup = new CtrlAltTabPopup(items);
+ this._popup.show(backward, binding, mask);
this._popup.actor.connect('destroy',
Lang.bind(this, function() {
@@ -134,176 +133,46 @@ const CtrlAltTabManager = new Lang.Class({
}
});
-function mod(a, b) {
- return (a + b) % b;
-}
-
const CtrlAltTabPopup = new Lang.Class({
Name: 'CtrlAltTabPopup',
+ Extends: SwitcherPopup.SwitcherPopup,
- _init : function() {
- this.actor = new Shell.GenericContainer({ name: 'ctrlAltTabPopup',
- reactive: true });
-
- this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
- this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
- this.actor.connect('allocate', Lang.bind(this, this._allocate));
-
- this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
-
- this._haveModal = false;
- this._modifierMask = 0;
- this._selection = 0;
-
- Main.uiGroup.add_actor(this.actor);
- },
-
- _getPreferredWidth: function (actor, forHeight, alloc) {
- let primary = Main.layoutManager.primaryMonitor;
-
- alloc.min_size = primary.width;
- alloc.natural_size = primary.width;
- },
-
- _getPreferredHeight: function (actor, forWidth, alloc) {
- let primary = Main.layoutManager.primaryMonitor;
-
- alloc.min_size = primary.height;
- alloc.natural_size = primary.height;
- },
-
- _allocate: function (actor, box, flags) {
- let childBox = new Clutter.ActorBox();
- let primary = Main.layoutManager.primaryMonitor;
-
- let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
- let vPadding = this.actor.get_theme_node().get_vertical_padding();
- let hPadding = this.actor.get_theme_node().get_horizontal_padding();
-
- let [childMinHeight, childNaturalHeight] = this._switcher.actor.get_preferred_height(primary.width - hPadding);
- let [childMinWidth, childNaturalWidth] = this._switcher.actor.get_preferred_width(childNaturalHeight);
- childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
- childBox.x2 = Math.min(primary.x + primary.width - hPadding, childBox.x1 + childNaturalWidth);
- childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
- childBox.y2 = childBox.y1 + childNaturalHeight;
- this._switcher.actor.allocate(childBox, flags);
- },
-
- show : function(items, startBackwards, mask) {
- if (!Main.pushModal(this.actor))
- return false;
- this._haveModal = true;
- this._modifierMask = AltTab.primaryModifier(mask);
-
- this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
- this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
-
- this._items = items;
- this._switcher = new CtrlAltTabSwitcher(items);
- this.actor.add_actor(this._switcher.actor);
-
- if (startBackwards)
- this._selection = this._items.length - 1;
- this._select(this._selection);
-
- let [x, y, mods] = global.get_pointer();
- if (!(mods & this._modifierMask)) {
- this._finish();
- return false;
- }
-
- this.actor.opacity = 0;
- this.actor.show();
- Tweener.addTween(this.actor,
- { opacity: 255,
- time: POPUP_FADE_TIME,
- transition: 'easeOutQuad'
- });
-
+ _createSwitcher: function() {
+ this._switcherList = new CtrlAltTabSwitcher(this._items);
return true;
},
- _next : function() {
- return mod(this._selection + 1, this._items.length);
- },
-
- _previous : function() {
- return mod(this._selection - 1, this._items.length);
+ _initialSelection: function(backward, binding) {
+ if (binding == 'switch-panels') {
+ if (backward)
+ this._selectedIndex = this._items.length - 1;
+ } else if (binding == 'switch-panels-backward') {
+ if (!backward)
+ this._selectedIndex = this._items.length - 1;
+ }
+ this._select(this._selectedIndex);
},
- _keyPressEvent : function(actor, event) {
- let keysym = event.get_key_symbol();
- let shift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);
- if (shift && keysym == Clutter.KEY_Tab)
- keysym = Clutter.ISO_Left_Tab;
-
- if (keysym == Clutter.KEY_Escape)
- this.destroy();
- else if (keysym == Clutter.KEY_Tab)
- this._select(this._next());
- else if (keysym == Clutter.KEY_ISO_Left_Tab)
- this._select(this._previous());
- else if (keysym == Clutter.KEY_Left)
+ _keyPressHandler: function(keysym, backwards, action) {
+ if (action == Meta.KeyBindingAction.SWITCH_PANELS)
+ this._select(backwards ? this._previous() : this._next());
+ else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
+ this._select(backwards ? this._next() : this._previous());
+ else if (keysym == Clutter.Left)
this._select(this._previous());
- else if (keysym == Clutter.KEY_Right)
+ else if (keysym == Clutter.Right)
this._select(this._next());
-
- return true;
- },
-
- _keyReleaseEvent : function(actor, event) {
- let [x, y, mods] = global.get_pointer();
- let state = mods & this._modifierMask;
-
- if (state == 0)
- this._finish();
-
- return true;
},
_finish : function() {
- this.destroy();
-
- Main.ctrlAltTabManager.focusGroup(this._items[this._selection]);
- },
-
- _popModal: function() {
- if (this._haveModal) {
- Main.popModal(this.actor);
- this._haveModal = false;
- }
+ this.parent();
+ Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex]);
},
-
- destroy : function() {
- this._popModal();
- Tweener.addTween(this.actor,
- { opacity: 0,
- time: POPUP_FADE_TIME,
- transition: 'easeOutQuad',
- onComplete: Lang.bind(this,
- function() {
- this.actor.destroy();
- })
- });
- },
-
- _onDestroy : function() {
- this._popModal();
- if (this._keyPressEventId)
- this.actor.disconnect(this._keyPressEventId);
- if (this._keyReleaseEventId)
- this.actor.disconnect(this._keyReleaseEventId);
- },
-
- _select : function(num) {
- this._selection = num;
- this._switcher.highlight(num);
- }
});
const CtrlAltTabSwitcher = new Lang.Class({
Name: 'CtrlAltTabSwitcher',
- Extends: AltTab.SwitcherList,
+ Extends: SwitcherPopup.SwitcherList,
_init : function(items) {
this.parent(true);
diff --git a/js/ui/switcherPopup.js b/js/ui/switcherPopup.js
new file mode 100644
index 0000000..82ebcbf
--- /dev/null
+++ b/js/ui/switcherPopup.js
@@ -0,0 +1,640 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const St = imports.gi.St;
+
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+
+const POPUP_DELAY_TIMEOUT = 150; // milliseconds
+
+const POPUP_SCROLL_TIME = 0.10; // seconds
+const POPUP_FADE_OUT_TIME = 0.1; // seconds
+
+const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
+
+function mod(a, b) {
+ return (a + b) % b;
+}
+
+function primaryModifier(mask) {
+ if (mask == 0)
+ return 0;
+
+ let primary = 1;
+ while (mask > 1) {
+ mask >>= 1;
+ primary <<= 1;
+ }
+ return primary;
+}
+
+const SwitcherPopup = new Lang.Class({
+ Name: 'SwitcherPopup',
+ Abstract: true,
+
+ _init: function(items) {
+ this._switcherList = null;
+
+ this._items = items || [];
+ this._selectedIndex = 0;
+
+ this.actor = new Shell.GenericContainer({ style_class: 'switcher-popup',
+ reactive: true });
+ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+ this.actor.connect('allocate', Lang.bind(this, this._allocate));
+ this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+ Main.uiGroup.add_actor(this.actor);
+
+ this._haveModal = false;
+ this._modifierMask = 0;
+
+ this._motionTimeoutId = 0;
+ this._initialDelayTimeoutId = 0;
+
+ // Initially disable hover so we ignore the enter-event if
+ // the switcher appears underneath the current pointer location
+ this._disableHover();
+ },
+
+ _getPreferredWidth: function(actor, forHeight, alloc) {
+ let primary = Main.layoutManager.primaryMonitor;
+
+ alloc.min_size = primary.width;
+ alloc.natural_size = primary.width;
+ },
+
+ _getPreferredHeight: function(actor, forWidth, alloc) {
+ let primary = Main.layoutManager.primaryMonitor;
+
+ alloc.min_size = primary.height;
+ alloc.natural_size = primary.height;
+ },
+
+ _allocate: function(actor, box, flags) {
+ let childBox = new Clutter.ActorBox();
+ let primary = Main.layoutManager.primaryMonitor;
+
+ let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+ let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+ let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+ let vPadding = this.actor.get_theme_node().get_vertical_padding();
+ let hPadding = leftPadding + rightPadding;
+
+ // Allocate the switcherList
+ // We select a size based on an icon size that does not overflow the screen
+ let [childMinHeight, childNaturalHeight] = this._switcherList.actor.get_preferred_height(primary.width - hPadding);
+ let [childMinWidth, childNaturalWidth] = this._switcherList.actor.get_preferred_width(childNaturalHeight);
+ childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
+ childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
+ childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
+ childBox.y2 = childBox.y1 + childNaturalHeight;
+ this._switcherList.actor.allocate(childBox, flags);
+ },
+
+ _createSwitcher: function() {
+ throw new Error('Not implemented');
+ },
+
+ _initialSelection: function(backward, binding) {
+ throw new Error('Not implemented');
+ },
+
+ show: function(backward, binding, mask) {
+ if (!this._createSwitcher())
+ return false;
+
+ if (!Main.pushModal(this.actor)) {
+ // Probably someone else has a pointer grab, try again with keyboard only
+ if (!Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED })) {
+ return false;
+ }
+ }
+ this._haveModal = true;
+ this._modifierMask = primaryModifier(mask);
+
+ this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
+ this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
+
+ this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
+ this.actor.connect('scroll-event', Lang.bind(this, this._scrollEvent));
+
+ this.actor.add_actor(this._switcherList.actor);
+ this._switcherList.connect('item-activated', Lang.bind(this, this._itemActivated));
+ this._switcherList.connect('item-entered', Lang.bind(this, this._itemEntered));
+
+ // Need to force an allocation so we can figure out whether we
+ // need to scroll when selecting
+ this.actor.opacity = 0;
+ this.actor.show();
+ this.actor.get_allocation_box();
+
+ if (this._items.length > 1)
+ this._selectedIndex = 1;
+ else
+ this._selectedIndex = 0;
+
+ this._initialSelection(backward, binding);
+
+ // There's a race condition; if the user released Alt before
+ // we got the grab, then we won't be notified. (See
+ // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
+ // details.) So we check now. (Have to do this after updating
+ // selection.)
+ let [x, y, mods] = global.get_pointer();
+ if (!(mods & this._modifierMask)) {
+ this._finish();
+ return false;
+ }
+
+ // We delay showing the popup so that fast Alt+Tab users aren't
+ // disturbed by the popup briefly flashing.
+ this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
+ Lang.bind(this, function () {
+ this.actor.opacity = 255;
+ this._initialDelayTimeoutId = 0;
+ }));
+ return true;
+ },
+
+ _next: function() {
+ return mod(this._selectedIndex + 1, this._items.length);
+ },
+
+ _previous: function() {
+ return mod(this._selectedIndex - 1, this._items.length);
+ },
+
+ _keyPressHandler: function(keysym, backwards, action) {
+ throw new Error('Not implemented');
+ },
+
+ _keyPressEvent: function(actor, event) {
+ let keysym = event.get_key_symbol();
+ let event_state = event.get_state();
+ let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
+ let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
+
+ this._disableHover();
+
+ if (keysym == Clutter.Escape)
+ this.destroy();
+ else
+ this._keyPressHandler(keysym, backwards, action);
+
+ return true;
+ },
+
+ _keyReleaseEvent: function(actor, event) {
+ let [x, y, mods] = global.get_pointer();
+ let state = mods & this._modifierMask;
+
+ if (state == 0)
+ this._finish();
+
+ return true;
+ },
+
+ _clickedOutside: function(actor, event) {
+ this.destroy();
+ },
+
+ _scrollHandler: function(direction) {
+ if (direction == Clutter.ScrollDirection.UP)
+ this._select(this._previous());
+ else if (direction == Clutter.ScrollDirection.DOWN)
+ this._select(this._next());
+ },
+
+ _scrollEvent: function(actor, event) {
+ this._scrollHandler(event.get_scroll_direction());
+ },
+
+ _itemActivatedHandler: function(n) {
+ this._select(n);
+ },
+
+ _itemActivated: function(switcher, n) {
+ this._itemActivatedHandler(n);
+ this._finish();
+ },
+
+ _itemEnteredHandler: function(n) {
+ this._select(n);
+ },
+
+ _itemEntered: function(switcher, n) {
+ if (!this.mouseActive)
+ return;
+ this._itemEnteredHandler(n);
+ },
+
+ _disableHover: function() {
+ this.mouseActive = false;
+
+ if (this._motionTimeoutId != 0)
+ Mainloop.source_remove(this._motionTimeoutId);
+
+ this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
+ },
+
+ _mouseTimedOut: function() {
+ this._motionTimeoutId = 0;
+ this.mouseActive = true;
+ },
+
+ _popModal: function() {
+ if (this._haveModal) {
+ Main.popModal(this.actor);
+ this._haveModal = false;
+ }
+ },
+
+ destroy: function() {
+ this._popModal();
+ if (this.actor.visible) {
+ Tweener.addTween(this.actor,
+ { opacity: 0,
+ time: POPUP_FADE_OUT_TIME,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this,
+ function() {
+ this.actor.destroy();
+ })
+ });
+ } else
+ this.actor.destroy();
+ },
+
+ _finish: function() {
+ this.destroy();
+ },
+
+ _onDestroy: function() {
+ this._popModal();
+
+ if (this._motionTimeoutId != 0)
+ Mainloop.source_remove(this._motionTimeoutId);
+ if (this._initialDelayTimeoutId != 0)
+ Mainloop.source_remove(this._initialDelayTimeoutId);
+ },
+
+ _select: function(num) {
+ this._selectedIndex = num;
+ this._switcherList.highlight(num);
+ }
+});
+
+const SwitcherList = new Lang.Class({
+ Name: 'SwitcherList',
+
+ _init : function(squareItems) {
+ this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
+ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+ this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
+
+ // Here we use a GenericContainer so that we can force all the
+ // children except the separator to have the same width.
+ this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
+ this._list.spacing = 0;
+ this._list.connect('style-changed', Lang.bind(this, function() {
+ this._list.spacing = this._list.get_theme_node().get_length('spacing');
+ }));
+
+ this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+ this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+ this._list.connect('allocate', Lang.bind(this, this._allocate));
+
+ this._scrollView = new St.ScrollView({ style_class: 'hfade',
+ enable_mouse_scrolling: false });
+ this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
+
+ let scrollBox = new St.BoxLayout();
+ scrollBox.add_actor(this._list);
+ this._scrollView.add_actor(scrollBox);
+ this.actor.add_actor(this._scrollView);
+
+ // Those arrows indicate whether scrolling in one direction is possible
+ this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
+ pseudo_class: 'highlighted' });
+ this._leftArrow.connect('repaint', Lang.bind(this,
+ function() { drawArrow(this._leftArrow, St.Side.LEFT); }));
+ this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
+ pseudo_class: 'highlighted' });
+ this._rightArrow.connect('repaint', Lang.bind(this,
+ function() { drawArrow(this._rightArrow, St.Side.RIGHT); }));
+
+ this.actor.add_actor(this._leftArrow);
+ this.actor.add_actor(this._rightArrow);
+
+ this._items = [];
+ this._highlighted = -1;
+ this._separator = null;
+ this._squareItems = squareItems;
+ this._minSize = 0;
+ this._scrollableRight = true;
+ this._scrollableLeft = false;
+ },
+
+ _allocateTop: function(actor, box, flags) {
+ let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+ let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+
+ let childBox = new Clutter.ActorBox();
+ let scrollable = this._minSize > box.x2 - box.x1;
+
+ box.y1 -= this.actor.get_theme_node().get_padding(St.Side.TOP);
+ box.y2 += this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+ this._scrollView.allocate(box, flags);
+
+ let arrowWidth = Math.floor(leftPadding / 3);
+ let arrowHeight = arrowWidth * 2;
+ childBox.x1 = leftPadding / 2;
+ childBox.y1 = this.actor.height / 2 - arrowWidth;
+ childBox.x2 = childBox.x1 + arrowWidth;
+ childBox.y2 = childBox.y1 + arrowHeight;
+ this._leftArrow.allocate(childBox, flags);
+ this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
+
+ arrowWidth = Math.floor(rightPadding / 3);
+ arrowHeight = arrowWidth * 2;
+ childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
+ childBox.y1 = this.actor.height / 2 - arrowWidth;
+ childBox.x2 = childBox.x1 + arrowWidth;
+ childBox.y2 = childBox.y1 + arrowHeight;
+ this._rightArrow.allocate(childBox, flags);
+ this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
+ },
+
+ addItem : function(item, label) {
+ let bbox = new St.Button({ style_class: 'item-box',
+ reactive: true });
+
+ bbox.set_child(item);
+ this._list.add_actor(bbox);
+
+ let n = this._items.length;
+ bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
+ bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
+
+ bbox.label_actor = label;
+
+ this._items.push(bbox);
+
+ return bbox;
+ },
+
+ _onItemClicked: function (index) {
+ this._itemActivated(index);
+ },
+
+ _onItemEnter: function (index) {
+ this._itemEntered(index);
+ },
+
+ addSeparator: function () {
+ let box = new St.Bin({ style_class: 'separator' });
+ this._separator = box;
+ this._list.add_actor(box);
+ },
+
+ highlight: function(index, justOutline) {
+ if (this._highlighted != -1) {
+ this._items[this._highlighted].remove_style_pseudo_class('outlined');
+ this._items[this._highlighted].remove_style_pseudo_class('selected');
+ }
+
+ this._highlighted = index;
+
+ if (this._highlighted != -1) {
+ if (justOutline)
+ this._items[this._highlighted].add_style_pseudo_class('outlined');
+ else
+ this._items[this._highlighted].add_style_pseudo_class('selected');
+ }
+
+ let adjustment = this._scrollView.hscroll.adjustment;
+ let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
+ let [absItemX, absItemY] = this._items[index].get_transformed_position();
+ let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
+ let [containerWidth, containerHeight] = this.actor.get_transformed_size();
+ if (posX + this._items[index].get_width() > containerWidth)
+ this._scrollToRight();
+ else if (this._items[index].allocation.x1 - value < 0)
+ this._scrollToLeft();
+
+ },
+
+ _scrollToLeft : function() {
+ let adjustment = this._scrollView.hscroll.adjustment;
+ let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
+
+ let item = this._items[this._highlighted];
+
+ if (item.allocation.x1 < value)
+ value = Math.min(0, item.allocation.x1);
+ else if (item.allocation.x2 > value + pageSize)
+ value = Math.max(upper, item.allocation.x2 - pageSize);
+
+ this._scrollableRight = true;
+ Tweener.addTween(adjustment,
+ { value: value,
+ time: POPUP_SCROLL_TIME,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this, function () {
+ if (this._highlighted == 0) {
+ this._scrollableLeft = false;
+ this.actor.queue_relayout();
+ }
+ })
+ });
+ },
+
+ _scrollToRight : function() {
+ let adjustment = this._scrollView.hscroll.adjustment;
+ let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
+
+ let item = this._items[this._highlighted];
+
+ if (item.allocation.x1 < value)
+ value = Math.max(0, item.allocation.x1);
+ else if (item.allocation.x2 > value + pageSize)
+ value = Math.min(upper, item.allocation.x2 - pageSize);
+
+ this._scrollableLeft = true;
+ Tweener.addTween(adjustment,
+ { value: value,
+ time: POPUP_SCROLL_TIME,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this, function () {
+ if (this._highlighted == this._items.length - 1) {
+ this._scrollableRight = false;
+ this.actor.queue_relayout();
+ }
+ })
+ });
+ },
+
+ _itemActivated: function(n) {
+ this.emit('item-activated', n);
+ },
+
+ _itemEntered: function(n) {
+ this.emit('item-entered', n);
+ },
+
+ _maxChildWidth: function (forHeight) {
+ let maxChildMin = 0;
+ let maxChildNat = 0;
+
+ for (let i = 0; i < this._items.length; i++) {
+ let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
+ maxChildMin = Math.max(childMin, maxChildMin);
+ maxChildNat = Math.max(childNat, maxChildNat);
+
+ if (this._squareItems) {
+ let [childMin, childNat] = this._items[i].get_preferred_height(-1);
+ maxChildMin = Math.max(childMin, maxChildMin);
+ maxChildNat = Math.max(childNat, maxChildNat);
+ }
+ }
+
+ return [maxChildMin, maxChildNat];
+ },
+
+ _getPreferredWidth: function (actor, forHeight, alloc) {
+ let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
+
+ let separatorWidth = 0;
+ if (this._separator) {
+ let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
+ separatorWidth = sepNat + this._list.spacing;
+ }
+
+ let totalSpacing = this._list.spacing * (this._items.length - 1);
+ alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
+ alloc.natural_size = alloc.min_size;
+ this._minSize = alloc.min_size;
+ },
+
+ _getPreferredHeight: function (actor, forWidth, alloc) {
+ let maxChildMin = 0;
+ let maxChildNat = 0;
+
+ for (let i = 0; i < this._items.length; i++) {
+ let [childMin, childNat] = this._items[i].get_preferred_height(-1);
+ maxChildMin = Math.max(childMin, maxChildMin);
+ maxChildNat = Math.max(childNat, maxChildNat);
+ }
+
+ if (this._squareItems) {
+ let [childMin, childNat] = this._maxChildWidth(-1);
+ maxChildMin = Math.max(childMin, maxChildMin);
+ maxChildNat = maxChildMin;
+ }
+
+ alloc.min_size = maxChildMin;
+ alloc.natural_size = maxChildNat;
+ },
+
+ _allocate: function (actor, box, flags) {
+ let childHeight = box.y2 - box.y1;
+
+ let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
+ let totalSpacing = this._list.spacing * (this._items.length - 1);
+
+ let separatorWidth = 0;
+ if (this._separator) {
+ let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
+ separatorWidth = sepNat;
+ totalSpacing += this._list.spacing;
+ }
+
+ let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
+
+ let x = 0;
+ let children = this._list.get_children();
+ let childBox = new Clutter.ActorBox();
+
+ let primary = Main.layoutManager.primaryMonitor;
+ let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
+
+ for (let i = 0; i < children.length; i++) {
+ if (this._items.indexOf(children[i]) != -1) {
+ let [childMin, childNat] = children[i].get_preferred_height(childWidth);
+ let vSpacing = (childHeight - childNat) / 2;
+ childBox.x1 = x;
+ childBox.y1 = vSpacing;
+ childBox.x2 = x + childWidth;
+ childBox.y2 = childBox.y1 + childNat;
+ children[i].allocate(childBox, flags);
+
+ x += this._list.spacing + childWidth;
+ } else if (children[i] == this._separator) {
+ // We want the separator to be more compact than the rest.
+ childBox.x1 = x;
+ childBox.y1 = 0;
+ childBox.x2 = x + separatorWidth;
+ childBox.y2 = childHeight;
+ children[i].allocate(childBox, flags);
+ x += this._list.spacing + separatorWidth;
+ } else {
+ // Something else, eg, AppSwitcher's arrows;
+ // we don't allocate it.
+ }
+ }
+ }
+});
+Signals.addSignalMethods(SwitcherList.prototype);
+
+function drawArrow(area, side) {
+ let themeNode = area.get_theme_node();
+ let borderColor = themeNode.get_border_color(side);
+ let bodyColor = themeNode.get_foreground_color();
+
+ let [width, height] = area.get_surface_size ();
+ let cr = area.get_context();
+
+ cr.setLineWidth(1.0);
+ Clutter.cairo_set_source_color(cr, borderColor);
+
+ switch (side) {
+ case St.Side.TOP:
+ cr.moveTo(0, height);
+ cr.lineTo(Math.floor(width * 0.5), 0);
+ cr.lineTo(width, height);
+ break;
+
+ case St.Side.BOTTOM:
+ cr.moveTo(width, 0);
+ cr.lineTo(Math.floor(width * 0.5), height);
+ cr.lineTo(0, 0);
+ break;
+
+ case St.Side.LEFT:
+ cr.moveTo(width, height);
+ cr.lineTo(0, Math.floor(height * 0.5));
+ cr.lineTo(width, 0);
+ break;
+
+ case St.Side.RIGHT:
+ cr.moveTo(0, 0);
+ cr.lineTo(width, Math.floor(height * 0.5));
+ cr.lineTo(0, height);
+ break;
+ }
+
+ cr.strokePreserve();
+
+ Clutter.cairo_set_source_color(cr, bodyColor);
+ cr.fill();
+}
+
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index c4896ee..42b2e66 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -621,7 +621,7 @@ const WindowManager = new Lang.Class({
_startA11ySwitcher : function(display, screen, window, binding) {
let modifiers = binding.get_modifiers();
let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
- Main.ctrlAltTabManager.popup(backwards, binding.get_mask());
+ Main.ctrlAltTabManager.popup(backwards, binding.get_name(), binding.get_mask());
},
_openAppMenu : function(display, screen, window, event, binding) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]