[geary] Export actions via application menu



commit 2a073e8bbbab1dd33914c13b1b572ad46fc6aaef
Author: Charles Lindsay <chaz yorba org>
Date:   Thu Dec 12 11:32:13 2013 -0800

    Export actions via application menu
    
    This adds an app menu, which should show up in modern Unity and GNOME
    Shell alike.  We're exporting our existing GtkActions as GActions using
    an adapter class, and we've created a new menu definition for the app
    menu.
    
    Closes: bgo#713018

 src/CMakeLists.txt                               |    1 +
 src/client/application/geary-action-adapter.vala |   67 ++++++++++++++++++++++
 src/client/application/geary-application.vala    |   10 +++-
 src/client/application/geary-controller.vala     |   62 ++++++++++++++++----
 ui/CMakeLists.txt                                |    1 +
 ui/app_menu.interface                            |   40 +++++++++++++
 6 files changed, 166 insertions(+), 15 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bfc9c5d..88290b8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -290,6 +290,7 @@ engine/util/util-trillian.vala
 )
 
 set(CLIENT_SRC
+client/application/geary-action-adapter.vala
 client/application/geary-application.vala
 client/application/geary-args.vala
 client/application/geary-config.vala
diff --git a/src/client/application/geary-action-adapter.vala 
b/src/client/application/geary-action-adapter.vala
new file mode 100644
index 0000000..707f395
--- /dev/null
+++ b/src/client/application/geary-action-adapter.vala
@@ -0,0 +1,67 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A bridge between Gtk.Action and GLib.Action.  Doesn't handle stateful
+ * Actions.  Also, assumes most properties of the Gtk.Action won't change
+ * during its life.
+ *
+ * NOTE: this *should* be subclassing SimpleAction, but trying that causes
+ * GCC to throw errors at compile time.  See bug #720159.  Also *should* be at
+ * least implementing Action, but trying that causes a whole different set of
+ * compile-time errors.  :'(
+ */
+public class Geary.ActionAdapter : BaseObject {
+    private delegate void RecursionGuardFunc();
+    
+    public Action action { get { return _action; } }
+    public Gtk.Action gtk_action { get; private set; }
+    
+    private SimpleAction _action;
+    private bool recursing = false;
+    
+    public ActionAdapter(Gtk.Action gtk_action) {
+        _action = new SimpleAction(gtk_action.name, null);
+        this.gtk_action = gtk_action;
+        
+        _action.activate.connect(on_activated);
+        _action.notify["enabled"].connect(on_enabled_changed);
+        
+        gtk_action.activate.connect(on_gtk_activated);
+        gtk_action.notify["sensitive"].connect(on_gtk_sensitive_changed);
+    }
+    
+    private void guard_recursion(RecursionGuardFunc f) {
+        if (recursing)
+            return;
+        
+        recursing = true;
+        f();
+        recursing = false;
+    }
+    
+    private void on_activated() {
+        guard_recursion(() => gtk_action.activate());
+    }
+    
+    private void on_enabled_changed() {
+        guard_recursion(() => {
+            if (gtk_action.sensitive != _action.enabled)
+                gtk_action.sensitive = _action.enabled;
+        });
+    }
+    
+    private void on_gtk_activated() {
+        guard_recursion(() => _action.activate(null));
+    }
+    
+    private void on_gtk_sensitive_changed() {
+        guard_recursion(() => {
+            if (_action.enabled != gtk_action.sensitive)
+                _action.set_enabled(gtk_action.sensitive);
+        });
+    }
+}
diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala
index a43e3e0..cf192b6 100644
--- a/src/client/application/geary-application.vala
+++ b/src/client/application/geary-application.vala
@@ -94,6 +94,9 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
     public Gtk.ActionGroup actions {
         get; private set; default = new Gtk.ActionGroup("GearyActionGroup");
     }
+    public Gee.Collection<Geary.ActionAdapter> action_adapters {
+        get; private set; default = new Gee.ArrayList<Geary.ActionAdapter>();
+    }
     
     public Gtk.UIManager ui_manager {
         get; private set; default = new Gtk.UIManager();
@@ -289,11 +292,14 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
         }
     }
     
+    public File get_ui_file(string filename) {
+        return get_resource_directory().get_child("ui").get_child(filename);
+    }
+    
     // Loads a UI file (in the ui directory) into the specified UI manager.
     public void load_ui_file_for_manager(Gtk.UIManager ui, string ui_filename) {
         try {
-            ui.add_ui_from_file(get_resource_directory().get_child("ui").get_child(
-                ui_filename).get_path());
+            ui.add_ui_from_file(get_ui_file(ui_filename).get_path());
         } catch(GLib.Error error) {
             warning("Unable to create Gtk.UIManager: %s".printf(error.message));
         }
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index af171d6..944dbbf 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -144,15 +144,9 @@ public class GearyController : Geary.BaseObject {
         IconFactory.instance.init();
         
         // Setup actions.
-        GearyApplication.instance.actions.add_actions(create_actions(), this);
-        GearyApplication.instance.actions.add_toggle_actions(create_toggle_actions(), this);
-        GearyApplication.instance.ui_manager.insert_action_group(
-            GearyApplication.instance.actions, 0);
+        setup_actions();
         GearyApplication.instance.load_ui_file("accelerators.ui");
         
-        // some actions need a little extra help
-        prepare_actions();
-        
         // Listen for attempts to close the application.
         GearyApplication.instance.exiting.connect(on_application_exiting);
         
@@ -387,12 +381,54 @@ public class GearyController : Geary.BaseObject {
         return entries;
     }
     
-    private void prepare_actions() {
-        GearyApplication.instance.get_action(ACTION_NEW_MESSAGE).is_important = true;
-        GearyApplication.instance.get_action(ACTION_REPLY_TO_MESSAGE).is_important = true;
-        GearyApplication.instance.get_action(ACTION_REPLY_ALL_MESSAGE).is_important = true;
-        GearyApplication.instance.get_action(ACTION_FORWARD_MESSAGE).is_important = true;
-        GearyApplication.instance.get_action(ACTION_DELETE_MESSAGE).is_important = true;
+    private void setup_actions() {
+        const string[] important_actions = {
+            ACTION_NEW_MESSAGE,
+            ACTION_REPLY_TO_MESSAGE,
+            ACTION_REPLY_ALL_MESSAGE,
+            ACTION_FORWARD_MESSAGE,
+            ACTION_DELETE_MESSAGE,
+        };
+        const string[] exported_actions = {
+            ACTION_ACCOUNTS,
+            ACTION_PREFERENCES,
+            ACTION_DONATE,
+            ACTION_HELP,
+            ACTION_ABOUT,
+            ACTION_QUIT,
+        };
+        
+        Gtk.ActionGroup action_group = GearyApplication.instance.actions;
+        
+        Gtk.ActionEntry[] action_entries = create_actions();
+        action_group.add_actions(action_entries, this);
+        foreach (Gtk.ActionEntry e in action_entries) {
+            Gtk.Action action = action_group.get_action(e.name);
+            assert(action != null);
+            
+            if (e.name in important_actions)
+                action.is_important = true;
+            GearyApplication.instance.action_adapters.add(new Geary.ActionAdapter(action));
+        }
+        
+        Gtk.ToggleActionEntry[] toggle_action_entries = create_toggle_actions();
+        action_group.add_toggle_actions(toggle_action_entries, this);
+        
+        foreach (Geary.ActionAdapter a in GearyApplication.instance.action_adapters) {
+            if (a.action.name in exported_actions)
+                GearyApplication.instance.add_action(a.action);
+        }
+        GearyApplication.instance.ui_manager.insert_action_group(action_group, 0);
+        
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            builder.add_from_file(
+                GearyApplication.instance.get_ui_file("app_menu.interface").get_path());
+        } catch (Error e) {
+            error("Unable to parse app_menu.interface: %s", e.message);
+        }
+        MenuModel menu = (MenuModel) builder.get_object("app-menu");
+        GearyApplication.instance.set_app_menu(menu);
     }
     
     private void open_account(Geary.Account account) {
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index f1a5644..a6d5767 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -4,6 +4,7 @@ install(FILES accelerators.ui DESTINATION ${UI_DEST})
 install(FILES account_list.glade DESTINATION ${UI_DEST})
 install(FILES account_cannot_remove.glade DESTINATION ${UI_DEST})
 install(FILES account_spinner.glade DESTINATION ${UI_DEST})
+install(FILES app_menu.interface DESTINATION ${UI_DEST})
 install(FILES composer.glade DESTINATION ${UI_DEST})
 install(FILES composer_accelerators.ui DESTINATION ${UI_DEST})
 install(FILES find_bar.glade DESTINATION ${UI_DEST})
diff --git a/ui/app_menu.interface b/ui/app_menu.interface
new file mode 100644
index 0000000..6ecd0c4
--- /dev/null
+++ b/ui/app_menu.interface
@@ -0,0 +1,40 @@
+<interface>
+    <menu id='app-menu'>
+        <section>
+            <item>
+                <attribute name='label' translatable='yes'>A_ccounts</attribute>
+                <attribute name='action'>app.GearyAccounts</attribute>
+                <attribute name='accel'>&lt;Primary&gt;M</attribute>
+            </item>
+            <item>
+                <attribute name='label' translatable='yes'>_Preferences</attribute>
+                <attribute name='action'>app.GearyPreferences</attribute>
+                <attribute name='accel'>&lt;Primary&gt;E</attribute>
+            </item>
+        </section>
+        <section>
+            <item>
+                <attribute name='label' translatable='yes'>_Donate</attribute>
+                <attribute name='action'>app.GearyDonate</attribute>
+            </item>
+        </section>
+        <section>
+            <item>
+                <attribute name='label' translatable='yes'>_Help</attribute>
+                <attribute name='action'>app.GearyHelp</attribute>
+                <attribute name='accel'>F1</attribute>
+            </item>
+            <item>
+                <attribute name='label' translatable='yes'>_About</attribute>
+                <attribute name='action'>app.GearyAbout</attribute>
+            </item>
+        </section>
+        <section>
+            <item>
+                <attribute name='label' translatable='yes'>_Quit</attribute>
+                <attribute name='action'>app.GearyQuit</attribute>
+                <attribute name='accel'>&lt;Primary&gt;Q</attribute>
+            </item>
+        </section>
+    </menu>
+</interface>


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