window-list: Add option for grouping windows by application

https://bugzilla.gnome.org/show_bug.cgi?id=693171
This commit is contained in:
Florian Müllner
2013-01-24 20:13:52 +01:00
parent 7e3b48da9d
commit a4fe08d53d
4 changed files with 245 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
EXTENSION_ID = window-list
include ../../extension.mk
include ../../settings.mk

View File

@@ -1,4 +1,5 @@
const Clutter = imports.gi.Clutter;
const Gtk = imports.gi.Gtk;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
@@ -7,6 +8,16 @@ const Hash = imports.misc.hash;
const Lang = imports.lang;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const PopupMenu = imports.ui.popupMenu;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const GroupingMode = {
NEVER: 0,
ALWAYS: 1
};
function _minimizeOrActivateWindow(window) {
@@ -130,6 +141,151 @@ const WindowButton = new Lang.Class({
});
const AppButton = new Lang.Class({
Name: 'AppButton',
_init: function(app) {
this.app = app;
let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this.actor = new St.Button({ style_class: 'window-button',
x_fill: true,
can_focus: true,
child: stack });
this.actor._delegate = this;
this.actor.connect('allocation-changed',
Lang.bind(this, this._updateIconGeometry));
this._singleWindowTitle = new St.Bin({ x_expand: true,
x_align: St.Align.START });
stack.add_actor(this._singleWindowTitle);
this._multiWindowTitle = new St.BoxLayout({ x_expand: true });
stack.add_actor(this._multiWindowTitle);
let icon = new St.Bin({ style_class: 'window-button-icon',
child: app.create_icon_texture(24) });
this._multiWindowTitle.add(icon);
this._multiWindowTitle.add(new St.Label({ text: app.get_name() }));
this._menuManager = new PopupMenu.PopupMenuManager(this);
this._menu = new PopupMenu.PopupMenu(this.actor, 0.5, St.Side.BOTTOM);
this._menu.actor.hide();
this._menu.connect('activate', Lang.bind(this, this._onMenuActivate));
this._menuManager.addMenu(this._menu);
Main.uiGroup.add_actor(this._menu.actor);
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._switchWorkspaceId =
global.window_manager.connect('switch-workspace',
Lang.bind(this, this._updateVisibility));
this._updateVisibility();
this._windowsChangedId =
this.app.connect('windows-changed',
Lang.bind(this, this._windowsChanged));
this._windowsChanged();
this._windowTracker = Shell.WindowTracker.get_default();
this._notifyFocusId =
this._windowTracker.connect('notify::focus-app',
Lang.bind(this, this._updateStyle));
this._updateStyle();
},
_updateVisibility: function() {
let workspace = global.screen.get_active_workspace();
this.actor.visible = this.app.is_on_workspace(workspace);
},
_updateStyle: function() {
if (this._windowTracker.focus_app == this.app)
this.actor.add_style_class_name('focused');
else
this.actor.remove_style_class_name('focused');
},
_updateIconGeometry: function() {
let rect = new Meta.Rectangle();
[rect.x, rect.y] = this.actor.get_transformed_position();
[rect.width, rect.height] = this.actor.get_transformed_size();
let windows = this.app.get_windows();
windows.forEach(function(w) {
w.set_icon_geometry(rect);
});
},
_getWindowList: function() {
let workspace = global.screen.get_active_workspace();
return this.app.get_windows().filter(function(win) {
return win.located_on_workspace(workspace);
});
},
_windowsChanged: function() {
let windows = this._getWindowList();
this._singleWindowTitle.visible = windows.length == 1;
this._multiWindowTitle.visible = !this._singleWindowTitle.visible;
if (this._singleWindowTitle.visible) {
if (!this._windowTitle) {
this._windowTitle = new WindowTitle(windows[0]);
this._singleWindowTitle.child = this._windowTitle.actor;
}
} else {
if (this._windowTitle) {
this._singleWindowTitle.child = null;
this._windowTitle = null;
}
}
},
_onClicked: function() {
if (this._menu.isOpen) {
this._menu.close();
return;
}
let windows = this._getWindowList();
if (windows.length == 1) {
_minimizeOrActivateWindow(windows[0]);
} else {
this._menu.removeAll();
for (let i = 0; i < windows.length; i++) {
let windowTitle = new WindowTitle(windows[i]);
let item = new PopupMenu.PopupBaseMenuItem();
item.addActor(windowTitle.actor);
item._window = windows[i];
this._menu.addMenuItem(item);
}
this._menu.open();
let event = Clutter.get_current_event();
if (event && event.type() == Clutter.EventType.KEY_RELEASE)
this._menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}
},
_onMenuActivate: function(menu, child) {
child._window.activate(global.get_current_time());
},
_onDestroy: function() {
global.window_manager.disconnect(this._switchWorkspaceId);
this._windowTracker.disconnect(this._notifyFocusId);
this.app.disconnect(this._windowsChangedId);
this._menu.actor.destroy();
}
});
const TrayButton = new Lang.Class({
Name: 'TrayButton',
@@ -232,6 +388,11 @@ const WindowList = new Lang.Class({
trackFullscreen: true });
Main.ctrlAltTabManager.addGroup(this.actor, _('Window List'), 'start-here-symbolic');
this._appSystem = Shell.AppSystem.get_default();
this._appStateChangedId =
this._appSystem.connect('app-state-changed',
Lang.bind(this, this._onAppStateChanged));
this._monitorsChangedId =
Main.layoutManager.connect('monitors-changed',
Lang.bind(this, this._updatePosition));
@@ -264,9 +425,30 @@ const WindowList = new Lang.Class({
this._updateKeyboardAnchor();
}));
let windows = Meta.get_window_actors(global.screen);
for (let i = 0; i < windows.length; i++)
this._onWindowAdded(null, windows[i].metaWindow);
this._settings = Convenience.getSettings();
this._groupingModeChangedId =
this._settings.connect('changed::grouping-mode',
Lang.bind(this, this._groupingModeChanged));
this._groupingModeChanged();
},
_groupingModeChanged: function() {
this._groupingMode = this._settings.get_enum('grouping-mode');
this._populateWindowList();
},
_populateWindowList: function() {
this._windowList.destroy_all_children();
if (this._groupingMode == GroupingMode.NEVER) {
let windows = Meta.get_window_actors(global.screen);
for (let i = 0; i < windows.length; i++)
this._onWindowAdded(null, windows[i].metaWindow);
} else {
let apps = this._appSystem.get_running();
for (let i = 0; i < apps.length; i++)
this._addApp(apps[i]);
}
},
_updatePosition: function() {
@@ -283,10 +465,41 @@ const WindowList = new Lang.Class({
Main.keyboard.actor.anchor_y = anchorY;
},
_onAppStateChanged: function(appSys, app) {
if (this._groupingMode != GroupingMode.ALWAYS)
return;
if (app.state == Shell.AppState.RUNNING)
this._addApp(app);
else if (app.state == Shell.AppState.STOPPED)
this._removeApp(app);
},
_addApp: function(app) {
let button = new AppButton(app);
this._windowList.layout_manager.pack(button.actor,
true, true, true,
Clutter.BoxAlignment.START,
Clutter.BoxAlignment.START);
},
_removeApp: function(app) {
let children = this._windowList.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i]._delegate.app == app) {
children[i].destroy();
return;
}
}
},
_onWindowAdded: function(ws, win) {
if (!Shell.WindowTracker.get_default().is_window_interesting(win))
return;
if (this._groupingMode != GroupingMode.NEVER)
return;
let button = new WindowButton(win);
this._windowList.layout_manager.pack(button.actor,
true, true, true,
@@ -295,6 +508,9 @@ const WindowList = new Lang.Class({
},
_onWindowRemoved: function(ws, win) {
if (this._groupingMode != GroupingMode.NEVER)
return;
let children = this._windowList.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i]._delegate.metaWindow == win) {
@@ -333,8 +549,12 @@ const WindowList = new Lang.Class({
},
_onDestroy: function() {
Main.ctrlAltTabManager.removeGroup(this.actor);
this._appSystem.disconnect(this._appStateChangedId);
this._appStateChangedId = 0;
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
@@ -350,6 +570,8 @@ const WindowList = new Lang.Class({
Main.overview.disconnect(this._overviewShowingId);
Main.overview.disconnect(this._overviewHidingId);
this._settings.disconnect(this._groupingModeChangedId);
let windows = Meta.get_window_actors(global.screen);
for (let i = 0; i < windows.length; i++)
windows[i].metaWindow.set_icon_geometry(null);

View File

@@ -0,0 +1,18 @@
<schemalist gettext-domain="gnome-shell-extensions">
<enum id="org.gnome.shell.extensions.window-list.GroupingMode">
<value value="0" nick="never"/>
<value value="1" nick="always"/>
</enum>
<schema id="org.gnome.shell.extensions.window-list"
path="/org/gnome/shell/extensions/window-list/">
<key name="grouping-mode"
enum="org.gnome.shell.extensions.window-list.GroupingMode">
<default>'never'</default>
<_summary>When to group windows</_summary>
<_description>
Decides when to group windows from the same application on the
window list. Possible values are "never" and "always".
</_description>
</key>
</schema>
</schemalist>

View File

@@ -19,6 +19,7 @@ extensions/places-menu/placeDisplay.js
extensions/systemMonitor/extension.js
extensions/user-theme/extension.js
extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml.in
extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml.in
extensions/windowsNavigator/extension.js
extensions/workspace-indicator/extension.js
extensions/workspace-indicator/prefs.js