From 1155edadcd514ce053beab5e258df13fd2a79dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Mon, 27 May 2013 23:41:56 +0200 Subject: [PATCH 1/2] window-list: Add context menu gnome-panel's window list had context menus on buttons, that gave easy access to common operations like close, minimize and maximize. Add something similar to the window-list. https://bugzilla.gnome.org/show_bug.cgi?id=699251 --- extensions/window-list/extension.js | 255 ++++++++++++++++++++++++++++++++---- 1 file changed, 232 insertions(+), 23 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 1c46791..7a9d392 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -37,6 +37,85 @@ function _minimizeOrActivateWindow(window) { window.activate(global.get_current_time()); } +function _openMenu(menu) { + menu.open(); + + let event = Clutter.get_current_event(); + if (event && event.type() == Clutter.EventType.KEY_RELEASE) + menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); +} + + +const WindowContextMenu = new Lang.Class({ + Name: 'WindowContextMenu', + Extends: PopupMenu.PopupMenu, + + _init: function(source, metaWindow) { + this.parent(source, 0.5, St.Side.BOTTOM); + + this._metaWindow = metaWindow; + + this._minimizeItem = new PopupMenu.PopupMenuItem(''); + this._minimizeItem.connect('activate', Lang.bind(this, function() { + if (this._metaWindow.minimized) + this._metaWindow.unminimize(); + else + this._metaWindow.minimize(); + })); + this.addMenuItem(this._minimizeItem); + + this._notifyMinimizedId = + this._metaWindow.connect('notify::minimized', + Lang.bind(this, this._updateMinimizeItem)); + this._updateMinimizeItem(); + + this._maximizeItem = new PopupMenu.PopupMenuItem(''); + this._maximizeItem.connect('activate', Lang.bind(this, function() { + if (this._metaWindow.maximized_vertically && + this._metaWindow.maximized_horizontally) + this._metaWindow.unmaximize(Meta.MaximizeFlags.HORIZONTAL | + Meta.MaximizeFlags.VERTICAL); + else + this._metaWindow.maximize(Meta.MaximizeFlags.HORIZONTAL | + Meta.MaximizeFlags.VERTICAL); + })); + this.addMenuItem(this._maximizeItem); + + this._notifyMaximizedHId = + this._metaWindow.connect('notify::maximized-horizontally', + Lang.bind(this, this._updateMaximizeItem)); + this._notifyMaximizedVId = + this._metaWindow.connect('notify::maximized-vertically', + Lang.bind(this, this._updateMaximizeItem)); + this._updateMaximizeItem(); + + let item = new PopupMenu.PopupMenuItem(_("Close")); + item.connect('activate', Lang.bind(this, function() { + this._metaWindow.delete(global.get_current_time()); + })); + this.addMenuItem(item); + + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + }, + + _updateMinimizeItem: function() { + this._minimizeItem.label.text = this._metaWindow.minimized ? _("Unminimize") + : _("Minimize"); + }, + + _updateMaximizeItem: function() { + let maximized = this._metaWindow.maximized_vertically && + this._metaWindow.maximized_horizontally; + this._maximizeItem.label.text = maximized ? _("Unmaximize") + : _("Maximize"); + }, + + _onDestroy: function() { + this._metaWindow.disconnect(this._notifyMinimizedId); + this._metaWindow.disconnect(this._notifyMaximizedHId); + this._metaWindow.disconnect(this._notifyMaximizedVId); + } +}); const WindowTitle = new Lang.Class({ Name: 'WindowTitle', @@ -99,13 +178,22 @@ const WindowButton = new Lang.Class({ this.actor = new St.Button({ style_class: 'window-button', x_fill: true, can_focus: true, + button_mask: St.ButtonMask.ONE | + St.ButtonMask.THREE, child: this._windowTitle.actor }); this.actor._delegate = this; + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._contextMenu = new WindowContextMenu(this.actor, this.metaWindow); + this._contextMenu.actor.hide(); + this._menuManager.addMenu(this._contextMenu); + Main.uiGroup.add_actor(this._contextMenu.actor); + this.actor.connect('allocation-changed', Lang.bind(this, this._updateIconGeometry)); this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu)); this._switchWorkspaceId = global.window_manager.connect('switch-workspace', @@ -118,8 +206,22 @@ const WindowButton = new Lang.Class({ this._updateStyle(); }, - _onClicked: function() { - _minimizeOrActivateWindow(this.metaWindow); + _onClicked: function(actor, button) { + if (this._contextMenu.isOpen) { + this._contextMenu.close(); + return; + } + + if (button == 1) + _minimizeOrActivateWindow(this.metaWindow); + else + _openMenu(this._contextMenu); + }, + + _onPopupMenu: function(actor) { + if (this._contextMenu.isOpen) + return; + _openMenu(this._contextMenu); }, _updateStyle: function() { @@ -151,10 +253,82 @@ const WindowButton = new Lang.Class({ _onDestroy: function() { global.window_manager.disconnect(this._switchWorkspaceId); global.display.disconnect(this._notifyFocusId); + this._contextMenu.actor.destroy(); } }); +const AppContextMenu = new Lang.Class({ + Name: 'AppContextMenu', + Extends: PopupMenu.PopupMenu, + + _init: function(source, app) { + this.parent(source, 0.5, St.Side.BOTTOM); + + this._app = app; + + this._minimizeItem = new PopupMenu.PopupMenuItem(_("Minimize all")); + this._minimizeItem.connect('activate', Lang.bind(this, function() { + this._app.get_windows().forEach(function(w) { + w.minimize(); + }); + })); + this.addMenuItem(this._minimizeItem); + + this._unminimizeItem = new PopupMenu.PopupMenuItem(_("Unminimize all")); + this._unminimizeItem.connect('activate', Lang.bind(this, function() { + this._app.get_windows().forEach(function(w) { + w.unminimize(); + }); + })); + this.addMenuItem(this._unminimizeItem); + + this._maximizeItem = new PopupMenu.PopupMenuItem(_("Maximize all")); + this._maximizeItem.connect('activate', Lang.bind(this, function() { + this._app.get_windows().forEach(function(w) { + w.maximize(Meta.MaximizeFlags.HORIZONTAL | + Meta.MaximizeFlags.VERTICAL); + }); + })); + this.addMenuItem(this._maximizeItem); + + this._unmaximizeItem = new PopupMenu.PopupMenuItem(_("Unmaximize all")); + this._unmaximizeItem.connect('activate', Lang.bind(this, function() { + this._app.get_windows().forEach(function(w) { + w.unmaximize(Meta.MaximizeFlags.HORIZONTAL | + Meta.MaximizeFlags.VERTICAL); + }); + })); + this.addMenuItem(this._unmaximizeItem); + + let item = new PopupMenu.PopupMenuItem(_("Close all")); + item.connect('activate', Lang.bind(this, function() { + this._app.get_windows().forEach(function(w) { + w.delete(global.get_current_time()); + }); + })); + this.addMenuItem(item); + }, + + open: function(animate) { + let windows = this._app.get_windows(); + this._minimizeItem.actor.visible = windows.some(function(w) { + return !w.minimized; + }); + this._unminimizeItem.actor.visible = windows.some(function(w) { + return w.minimized; + }); + this._maximizeItem.actor.visible = windows.some(function(w) { + return !(w.maximized_horizontally && w.maximized_vertically); + }); + this._unmaximizeItem.actor.visible = windows.some(function(w) { + return w.maximized_horizontally && w.maximized_vertically; + }); + + this.parent(animate); + } +}); + const AppButton = new Lang.Class({ Name: 'AppButton', @@ -165,6 +339,8 @@ const AppButton = new Lang.Class({ this.actor = new St.Button({ style_class: 'window-button', x_fill: true, can_focus: true, + button_mask: St.ButtonMask.ONE | + St.ButtonMask.THREE, child: stack }); this.actor._delegate = this; @@ -190,6 +366,12 @@ const AppButton = new Lang.Class({ this._menuManager.addMenu(this._menu); Main.uiGroup.add_actor(this._menu.actor); + this._contextMenuManager = new PopupMenu.PopupMenuManager(this); + this._appContextMenu = new AppContextMenu(this.actor, this.app); + this._appContextMenu.actor.hide(); + this._contextMenuManager.addMenu(this._appContextMenu); + Main.uiGroup.add_actor(this._appContextMenu.actor); + this._textureCache = St.TextureCache.get_default(); this._iconThemeChangedId = this._textureCache.connect('icon-theme-changed', Lang.bind(this, @@ -198,6 +380,7 @@ const AppButton = new Lang.Class({ })); this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu)); this._switchWorkspaceId = global.window_manager.connect('switch-workspace', @@ -258,43 +441,69 @@ const AppButton = new Lang.Class({ this.metaWindow = windows[0]; this._windowTitle = new WindowTitle(this.metaWindow); this._singleWindowTitle.child = this._windowTitle.actor; + this._windowContextMenu = new WindowContextMenu(this.actor, this.metaWindow); + Main.uiGroup.add_actor(this._windowContextMenu.actor); + this._windowContextMenu.actor.hide(); + this._contextMenuManager.addMenu(this._windowContextMenu); } + this._contextMenu = this._windowContextMenu; } else { if (this._windowTitle) { this.metaWindow = null; this._singleWindowTitle.child = null; this._windowTitle = null; + this._windowContextMenu.actor.destroy(); + this._windowContextMenu = null; } + this._contextMenu = this._appContextMenu; } + }, - _onClicked: function() { - if (this._menu.isOpen) { + _onClicked: function(actor, button) { + let menuWasOpen = this._menu.isOpen; + if (menuWasOpen) 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 contextMenuWasOpen = this._contextMenu.isOpen; + if (contextMenuWasOpen) + this._contextMenu.close(); + + if (button == 1) { + if (menuWasOpen) + return; - 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); + let windows = this._getWindowList(); + if (windows.length == 1) { + if (contextMenuWasOpen) + return; + _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); + } + _openMenu(this._menu); + } + } else { + if (contextMenuWasOpen) + return; + _openMenu(this._contextMenu); } }, + _onPopupMenu: function(actor) { + if (this._menu.isOpen || this._contextMenu.isOpen) + return; + _openMenu(this._contextMenu); + }, + + _onMenuActivate: function(menu, child) { child._window.activate(global.get_current_time()); }, -- 1.8.3.1