[gnome-shell] Implement window menus in gnome-shell



commit e7af257814b1b56a4cbd863c6f69699813a580dd
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu Mar 13 18:51:10 2014 -0400

    Implement window menus in gnome-shell
    
    https://bugzilla.gnome.org/show_bug.cgi?id=726352

 js/js-resources.gresource.xml |    1 +
 js/ui/windowManager.js        |    8 ++
 js/ui/windowMenu.js           |  145 +++++++++++++++++++++++++++++++++++++++++
 src/gnome-shell-plugin.c      |   11 +++-
 src/shell-wm-private.h        |    2 +
 src/shell-wm.c                |   14 ++++
 6 files changed, 180 insertions(+), 1 deletions(-)
---
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 6d9d585..47bdd00 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -84,6 +84,7 @@
     <file>ui/userWidget.js</file>
     <file>ui/viewSelector.js</file>
     <file>ui/windowAttentionHandler.js</file>
+    <file>ui/windowMenu.js</file>
     <file>ui/windowManager.js</file>
     <file>ui/workspace.js</file>
     <file>ui/workspaceSwitcherPopup.js</file>
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index ae6a207..0acf4f6 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -15,6 +15,7 @@ const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
 const Main = imports.ui.main;
 const ModalDialog = imports.ui.modalDialog;
 const Tweener = imports.ui.tweener;
+const WindowMenu = imports.ui.windowMenu;
 
 const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
 const WINDOW_ANIMATION_TIME = 0.25;
@@ -480,6 +481,7 @@ const WindowManager = new Lang.Class({
         this._shellwm.connect('switch-workspace', Lang.bind(this, this._switchWorkspace));
         this._shellwm.connect('show-tile-preview', Lang.bind(this, this._showTilePreview));
         this._shellwm.connect('hide-tile-preview', Lang.bind(this, this._hideTilePreview));
+        this._shellwm.connect('show-window-menu', Lang.bind(this, this._showWindowMenu));
         this._shellwm.connect('minimize', Lang.bind(this, this._minimizeWindow));
         this._shellwm.connect('maximize', Lang.bind(this, this._maximizeWindow));
         this._shellwm.connect('unmaximize', Lang.bind(this, this._unmaximizeWindow));
@@ -669,6 +671,8 @@ const WindowManager = new Lang.Class({
                 this._dimWindow(this._dimmedWindows[i]);
         }));
 
+        this._windowMenuManager = new WindowMenu.WindowMenuManager();
+
         if (Main.sessionMode.hasWorkspaces)
             this._workspaceTracker = new WorkspaceTracker(this);
 
@@ -1159,6 +1163,10 @@ const WindowManager = new Lang.Class({
         this._tilePreview.hide();
     },
 
+    _showWindowMenu: function(shellwm, window) {
+        this._windowMenuManager.showForWindow(window);
+    },
+
     _startAppSwitcher : function(display, screen, window, binding) {
         /* prevent a corner case where both popups show up at once */
         if (this._workspaceSwitcherPopup != null)
diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js
new file mode 100644
index 0000000..6738277
--- /dev/null
+++ b/js/ui/windowMenu.js
@@ -0,0 +1,145 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*
+
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Meta = imports.gi.Meta;
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+
+const BoxPointer = imports.ui.boxpointer;
+const Main = imports.ui.main;
+const PopupMenu = imports.ui.popupMenu;
+
+const WindowMenu = new Lang.Class({
+    Name: 'WindowMenu',
+    Extends: PopupMenu.PopupMenu,
+
+    _init: function(window) {
+        this.parent(Main.layoutManager.dummyCursor, 0, St.Side.TOP);
+
+        this.actor.add_style_class_name('window-menu');
+
+        Main.layoutManager.uiGroup.add_actor(this.actor);
+        this.actor.hide();
+
+        this._buildMenu(window);
+    },
+
+    _buildMenu: function(window) {
+        let type = window.get_window_type();
+
+        let item;
+
+        item = this.addAction(_("Minimize"), Lang.bind(this, function(event) {
+            window.minimize();
+        }));
+        if (!window.can_minimize())
+            item.setSensitive(false);
+
+        if (window.get_maximized()) {
+            item = this.addAction(_("Unmaximize"), Lang.bind(this, function() {
+                window.unmaximize(Meta.MaximizeFlags.BOTH);
+            }));
+        } else {
+            item = this.addAction(_("Maximize"), Lang.bind(this, function() {
+                window.maximize(Meta.MaximizeFlags.BOTH);
+            }));
+        }
+        if (!window.can_maximize())
+            item.setSensitive(false);
+
+        item = this.addAction(_("Move"), Lang.bind(this, function(event) {
+            window.begin_grab_op(Meta.GrabOp.KEYBOARD_MOVING, true, event.get_time());
+        }));
+        if (!window.allows_move())
+            item.setSensitive(false);
+
+        item = this.addAction(_("Resize"), Lang.bind(this, function(event) {
+            window.begin_grab_op(Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, true, event.get_time());
+        }));
+        if (!window.allows_resize())
+            item.setSensitive(false);
+
+        if (!window.titlebar_is_onscreen() && type != Meta.WindowType.DOCK && type != 
Meta.WindowType.DESKTOP) {
+            this.addAction(_("Move Titlebar Onscreen"), Lang.bind(this, function(event) {
+                window.shove_titlebar_onscreen();
+            }));
+        }
+
+        item = this.addAction("Always on Top", Lang.bind(this, function() {
+            if (window.is_above())
+                window.unmake_above();
+            else
+                window.make_above();
+        }));
+        if (window.is_above())
+            item.setOrnament(PopupMenu.Ornament.DOT);
+        if (window.get_maximized() ||
+            type == Meta.WindowType.DOCK ||
+            type == Meta.WindowType.DESKTOP ||
+            type == Meta.WindowType.SPLASHSCREEN)
+            item.setSensitive(false);
+
+        if (!Meta.prefs_get_workspaces_only_on_primary() || window.is_on_primary_monitor()) {
+            let isSticky = window.is_on_all_workspaces();
+
+            item = this.addAction(_("Always on Visible Workspace"), Lang.bind(this, function() {
+                if (isSticky)
+                    window.unstick();
+                else
+                    window.stick();
+            }));
+            if (isSticky)
+                item.setOrnament(PopupMenu.Ornament.DOT);
+            if (window.is_always_on_all_workspaces())
+                item.setSensitive(false);
+
+            let nWorkspaces = global.screen.n_workspaces;
+
+            if (!isSticky) {
+                let workspace = window.get_workspace();
+                let idx = workspace.index();
+                if (idx > 0) {
+                    this.addAction(_("Move to Workspace Up"), Lang.bind(this, function(event) {
+                        window.change_workspace(workspace.get_neighbor(Meta.MotionDirection.UP));
+                    }));
+                }
+                if (idx < nWorkspaces) {
+                     this.addAction(_("Move to Workspace Down"), Lang.bind(this, function(event) {
+                        window.change_workspace(workspace.get_neighbor(Meta.MotionDirection.DOWN));
+                    }));
+                }
+            }
+        }
+
+        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+
+        item = this.addAction(_("Close"), Lang.bind(this, function(event) {
+            window.close(event.get_time());
+        }));
+        if (!window.can_close())
+            item.setSensitive(false);
+    }
+});
+
+const WindowMenuManager = new Lang.Class({
+    Name: 'WindowMenuManager',
+
+    _init: function() {
+        this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor });
+    },
+
+    showForWindow: function(window) {
+        let menu = new WindowMenu(window);
+        this._manager.addMenu(menu);
+
+        let [x, y] = global.get_pointer();
+        Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
+        menu.open(BoxPointer.PopupAnimation.NONE);
+        menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+        menu.connect('open-state-changed', Lang.bind(this, function(menu_, isOpen) {
+            if (!isOpen)
+                menu.destroy();
+        }));
+    },
+});
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
index 71503bc..75a3bdd 100644
--- a/src/gnome-shell-plugin.c
+++ b/src/gnome-shell-plugin.c
@@ -73,7 +73,8 @@ static void gnome_shell_plugin_show_tile_preview (MetaPlugin      *plugin,
                                                   MetaRectangle   *tile_rect,
                                                   int              tile_monitor);
 static void gnome_shell_plugin_hide_tile_preview (MetaPlugin *plugin);
-
+static void gnome_shell_plugin_show_window_menu  (MetaPlugin *plugin,
+                                                  MetaWindow *window);
 
 static gboolean              gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
                                                                XEvent     *event);
@@ -140,6 +141,7 @@ gnome_shell_plugin_class_init (GnomeShellPluginClass *klass)
 
   plugin_class->show_tile_preview = gnome_shell_plugin_show_tile_preview;
   plugin_class->hide_tile_preview = gnome_shell_plugin_hide_tile_preview;
+  plugin_class->show_window_menu = gnome_shell_plugin_show_window_menu;
 
   plugin_class->xevent_filter     = gnome_shell_plugin_xevent_filter;
   plugin_class->keybinding_filter = gnome_shell_plugin_keybinding_filter;
@@ -303,6 +305,13 @@ gnome_shell_plugin_hide_tile_preview (MetaPlugin *plugin)
   _shell_wm_hide_tile_preview (get_shell_wm ());
 }
 
+static void
+gnome_shell_plugin_show_window_menu (MetaPlugin *plugin,
+                                     MetaWindow *window)
+{
+  _shell_wm_show_window_menu (get_shell_wm (), window);
+}
+
 static gboolean
 gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
                                   XEvent     *xev)
diff --git a/src/shell-wm-private.h b/src/shell-wm-private.h
index 9df4ada..35cbcb0 100644
--- a/src/shell-wm-private.h
+++ b/src/shell-wm-private.h
@@ -40,6 +40,8 @@ void _shell_wm_show_tile_preview     (ShellWM             *wm,
                                       MetaRectangle       *tile_rect,
                                       int                  tile_monitor);
 void _shell_wm_hide_tile_preview     (ShellWM             *wm);
+void _shell_wm_show_window_menu      (ShellWM             *wm,
+                                      MetaWindow          *window);
 
 gboolean _shell_wm_filter_keybinding (ShellWM             *wm,
                                       MetaKeyBinding      *binding);
diff --git a/src/shell-wm.c b/src/shell-wm.c
index f2c484d..0d51606 100644
--- a/src/shell-wm.c
+++ b/src/shell-wm.c
@@ -28,6 +28,7 @@ enum
   KILL_WINDOW_EFFECTS,
   SHOW_TILE_PREVIEW,
   HIDE_TILE_PREVIEW,
+  SHOW_WINDOW_MENU,
   FILTER_KEYBINDING,
   CONFIRM_DISPLAY_CHANGE,
 
@@ -135,6 +136,13 @@ shell_wm_class_init (ShellWMClass *klass)
                   0,
                   NULL, NULL, NULL,
                   G_TYPE_NONE, 0);
+  shell_wm_signals[SHOW_WINDOW_MENU] =
+    g_signal_new ("show-window-menu",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 1,
+                  META_TYPE_WINDOW);
   shell_wm_signals[FILTER_KEYBINDING] =
     g_signal_new ("filter-keybinding",
                   G_TYPE_FROM_CLASS (klass),
@@ -288,6 +296,12 @@ _shell_wm_hide_tile_preview (ShellWM *wm)
   g_signal_emit (wm, shell_wm_signals[HIDE_TILE_PREVIEW], 0);
 }
 
+void
+_shell_wm_show_window_menu (ShellWM    *wm,
+                            MetaWindow *window)
+{
+  g_signal_emit (wm, shell_wm_signals[SHOW_WINDOW_MENU], 0, window);
+}
 
 void
 _shell_wm_minimize (ShellWM         *wm,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]