Blob Blame History Raw
From 1155edadcd514ce053beab5e258df13fd2a79dd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
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