[gnome-builder/wip/chergert/perspective] libide: start on very basic GMenu merging for plugins



commit e05676103ad6cc07f34abce718bda630607038a9
Author: Christian Hergert <chergert redhat com>
Date:   Wed Dec 2 03:21:42 2015 -0800

    libide: start on very basic GMenu merging for plugins
    
    The goal here is to allow plugins to simply provide a gtk/menus.ui like
    an application would. However, the menus will get merged into the real
    menus within Builder.
    
    For example, we could extend the context menu for the source view with
    something like:
    
    <?xml version="1.0"?>
    <interface>
      <menu id="ide-source-view-popup-menu">
        <section id="ide-source-view-popup-menu-reveal-file-section">
          <attribute name="after">ide-source-view-popup-menu-selection-section</attribute>
          <item>
            <attribute name="label" translatable="yes">Re_veal in Project Tree</attribute>
            <attribute name="action">project-tree.reveal-file</attribute>
          </item>
        </section>
      </menu>
    </interface>
    
    The interesting part here is to implement "after" and "before" properly
    as well as support more than just sections (as we do now).

 libide/Makefile.am               |    6 +-
 libide/ide-application-private.h |    3 +
 libide/ide-application.c         |   16 +++
 libide/ide-menu-extension.c      |   41 ++++--
 libide/ide-menu-extension.h      |   20 ++-
 libide/ide-menu-merger.c         |  275 ++++++++++++++++++++++++++++++++++++++
 libide/ide-menu-merger.h         |   34 +++++
 7 files changed, 372 insertions(+), 23 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 632b731..7f4107b 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -288,14 +288,16 @@ libide_1_0_la_SOURCES = \
        ide-extension-util.c \
        ide-extension-util.h \
        ide-internal.h \
+       ide-keybindings.c \
+       ide-keybindings.h \
        ide-layout-stack-actions.c \
        ide-layout-stack-private.h \
        ide-line-change-gutter-renderer.c \
        ide-line-change-gutter-renderer.h \
        ide-line-diagnostics-gutter-renderer.c \
        ide-line-diagnostics-gutter-renderer.h \
-       ide-keybindings.c \
-       ide-keybindings.h \
+       ide-menu-merger.c \
+       ide-menu-merger.h \
        ide-ref-ptr.c \
        ide-ref-ptr.h \
        ide-search-reducer.c \
diff --git a/libide/ide-application-private.h b/libide/ide-application-private.h
index 5259a24..529bf49 100644
--- a/libide/ide-application-private.h
+++ b/libide/ide-application-private.h
@@ -23,6 +23,7 @@
 #include <libpeas/peas.h>
 
 #include "ide-application.h"
+#include "ide-menu-merger.h"
 #include "ide-keybindings.h"
 #include "ide-recent-projects.h"
 #include "ide-theme-manager.h"
@@ -52,6 +53,8 @@ struct _IdeApplication
   GDateTime           *started_at;
 
   IdeThemeManager     *theme_manager;
+
+  IdeMenuMerger       *menu_merger;
 };
 
 void     ide_application_discover_plugins   (IdeApplication   *self) G_GNUC_INTERNAL;
diff --git a/libide/ide-application.c b/libide/ide-application.c
index cb40426..1363ffe 100644
--- a/libide/ide-application.c
+++ b/libide/ide-application.c
@@ -116,6 +116,18 @@ ide_application_register_keybindings (IdeApplication *self)
 }
 
 static void
+ide_application_register_menus (IdeApplication *self)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  self->menu_merger = ide_menu_merger_new ();
+
+  IDE_EXIT;
+}
+
+static void
 ide_application_register_search_paths (IdeApplication *self)
 {
   g_assert (IDE_IS_APPLICATION (self));
@@ -330,6 +342,9 @@ ide_application_startup (GApplication *application)
 
   G_APPLICATION_CLASS (ide_application_parent_class)->startup (application);
 
+  if (self->mode == IDE_APPLICATION_MODE_PRIMARY)
+    ide_application_register_menus (self);
+
   ide_application_load_addins (self);
 }
 
@@ -355,6 +370,7 @@ ide_application_finalize (GObject *object)
   g_clear_object (&self->keybindings);
   g_clear_object (&self->recent_projects);
   g_clear_object (&self->theme_manager);
+  g_clear_object (&self->menu_merger);
 
   G_OBJECT_CLASS (ide_application_parent_class)->finalize (object);
 }
diff --git a/libide/ide-menu-extension.c b/libide/ide-menu-extension.c
index 189dd14..3689c4a 100644
--- a/libide/ide-menu-extension.c
+++ b/libide/ide-menu-extension.c
@@ -59,9 +59,9 @@ ide_menu_extension_dispose (GObject *object)
 
 static void
 ide_menu_extension_get_property (GObject    *object,
-                                guint       prop_id,
-                                GValue     *value,
-                                GParamSpec *pspec)
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
 {
        IdeMenuExtension *menu = IDE_MENU_EXTENSION (object);
 
@@ -77,10 +77,10 @@ ide_menu_extension_get_property (GObject    *object,
 }
 
 static void
-ide_menu_extension_set_property (GObject     *object,
-                                   guint         prop_id,
-                                   const GValue *value,
-                                   GParamSpec   *pspec)
+ide_menu_extension_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
 {
        IdeMenuExtension *menu = IDE_MENU_EXTENSION (object);
 
@@ -129,7 +129,7 @@ ide_menu_extension_new (GMenu *menu)
 
 IdeMenuExtension *
 ide_menu_extension_new_for_section (GMenu       *menu,
-                                   const gchar *section)
+                                   const gchar *section)
 {
        guint n_items;
        guint i;
@@ -180,28 +180,43 @@ ide_menu_extension_new_for_section (GMenu       *menu,
 
 void
 ide_menu_extension_append_menu_item (IdeMenuExtension *menu,
-                                    GMenuItem       *item)
+                                    GMenuItem        *item)
 {
        g_return_if_fail (IDE_IS_MENU_EXTENSION (menu));
        g_return_if_fail (G_IS_MENU_ITEM (item));
 
        if (menu->menu != NULL)
        {
-               g_menu_item_set_attribute (item, "gb-merge-id", "u", menu->merge_id);
+               g_menu_item_set_attribute (item, "ide-merge-id", "u", menu->merge_id);
                g_menu_append_item (menu->menu, item);
        }
 }
 
 void
+ide_menu_extension_insert_menu_item (IdeMenuExtension *menu,
+                                    gint              position,
+                                    GMenuItem        *item)
+{
+       g_return_if_fail (IDE_IS_MENU_EXTENSION (menu));
+       g_return_if_fail (G_IS_MENU_ITEM (item));
+
+       if (menu->menu != NULL)
+       {
+               g_menu_item_set_attribute (item, "ide-merge-id", "u", menu->merge_id);
+               g_menu_insert_item (menu->menu, position, item);
+       }
+}
+
+void
 ide_menu_extension_prepend_menu_item (IdeMenuExtension *menu,
-                                     GMenuItem       *item)
+                                     GMenuItem        *item)
 {
        g_return_if_fail (IDE_IS_MENU_EXTENSION (menu));
        g_return_if_fail (G_IS_MENU_ITEM (item));
 
        if (menu->menu != NULL)
        {
-               g_menu_item_set_attribute (item, "gb-merge-id", "u", menu->merge_id);
+               g_menu_item_set_attribute (item, "ide-merge-id", "u", menu->merge_id);
                g_menu_prepend_item (menu->menu, item);
        }
 }
@@ -220,7 +235,7 @@ ide_menu_extension_remove_items (IdeMenuExtension *menu)
                guint id = 0;
 
                if (g_menu_model_get_item_attribute (G_MENU_MODEL (menu->menu),
-                                                    i, "gb-merge-id", "u", &id) &&
+                                                    i, "ide-merge-id", "u", &id) &&
                    id == menu->merge_id)
                {
                        g_menu_remove (menu->menu, i);
diff --git a/libide/ide-menu-extension.h b/libide/ide-menu-extension.h
index 66699b3..fe90300 100644
--- a/libide/ide-menu-extension.h
+++ b/libide/ide-menu-extension.h
@@ -30,17 +30,21 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeMenuExtension, ide_menu_extension, IDE, MENU_EXTENSION, GObject)
 
-IdeMenuExtension       *ide_menu_extension_new                 (GMenu           *menu);
-IdeMenuExtension       *ide_menu_extension_new_for_section     (GMenu           *menu,
-                                                              const gchar     *section);
+IdeMenuExtension       *ide_menu_extension_new               (GMenu            *menu);
+IdeMenuExtension       *ide_menu_extension_new_for_section   (GMenu            *menu,
+                                                              const gchar      *section);
 
-void                   ide_menu_extension_append_menu_item    (IdeMenuExtension *menu,
-                                                              GMenuItem       *item);
+void                   ide_menu_extension_append_menu_item   (IdeMenuExtension *menu,
+                                                              GMenuItem        *item);
 
-void                   ide_menu_extension_prepend_menu_item   (IdeMenuExtension *menu,
-                                                              GMenuItem       *item);
+void                   ide_menu_extension_insert_menu_item   (IdeMenuExtension *menu,
+                                                             gint              position,
+                                                             GMenuItem        *item);
 
-void                   ide_menu_extension_remove_items        (IdeMenuExtension *menu);
+void                   ide_menu_extension_prepend_menu_item  (IdeMenuExtension *menu,
+                                                              GMenuItem        *item);
+
+void                   ide_menu_extension_remove_items       (IdeMenuExtension *menu);
 
 G_END_DECLS
 
diff --git a/libide/ide-menu-merger.c b/libide/ide-menu-merger.c
new file mode 100644
index 0000000..bb01fe5
--- /dev/null
+++ b/libide/ide-menu-merger.c
@@ -0,0 +1,275 @@
+/* ide-menu-merger.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-application.h"
+#include "ide-debug.h"
+#include "ide-macros.h"
+#include "ide-menu-extension.h"
+#include "ide-menu-merger.h"
+
+struct _IdeMenuMerger
+{
+  GObject     parent_instance;
+
+  /*
+   * A GHashTable containing GPtrArray of IdeMenuExtension.
+   * The resource path is the key to the array of menu extensions.
+   */
+  GHashTable *by_resource_path;
+};
+
+G_DEFINE_TYPE (IdeMenuMerger, ide_menu_merger, G_TYPE_OBJECT)
+
+const gchar *
+get_object_id (GObject *object)
+{
+  if (GTK_IS_BUILDABLE (object))
+    return gtk_buildable_get_name (GTK_BUILDABLE (object));
+  else
+    return g_object_get_data (object, "gtk-builder-name");
+}
+
+static gint
+find_position (GMenu       *menu,
+               const gchar *after)
+{
+  if (after != NULL)
+    {
+      gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu));
+      gint i;
+
+      for (i = 0; i < n_items; i++)
+        {
+          g_autoptr(GMenuLinkIter) iter = NULL;
+          g_autoptr(GMenuModel) value = NULL;
+          const gchar *name;
+
+          iter = g_menu_model_iterate_item_links (G_MENU_MODEL (menu), i);
+
+          while (g_menu_link_iter_get_next (iter, &name, &value))
+            {
+              const gchar *id = get_object_id (G_OBJECT (value));
+
+              if (ide_str_equal0 (id, after))
+                return i + 1;
+            }
+        }
+    }
+
+  return -1;
+}
+
+static void
+save_extension (IdeMenuMerger    *self,
+                const gchar      *resource_path,
+                IdeMenuExtension *extension)
+{
+  GPtrArray *ar;
+
+  g_assert (IDE_IS_MENU_MERGER (self));
+  g_assert (resource_path != NULL);
+  g_assert (IDE_IS_MENU_EXTENSION (extension));
+
+  ar = g_hash_table_lookup (self->by_resource_path, resource_path);
+
+  if (ar == NULL)
+    {
+      ar = g_ptr_array_new_with_free_func (g_object_unref);
+      g_hash_table_insert (self->by_resource_path, g_strdup (resource_path), ar);
+    }
+
+  g_ptr_array_add (ar, g_object_ref (extension));
+}
+
+static void
+ide_menu_merger_merge (IdeMenuMerger *self,
+                       const gchar   *resource_path,
+                       GMenu         *app_menu,
+                       GMenu         *menu)
+{
+  gint n_items;
+  gint i;
+
+  g_assert (IDE_IS_MENU_MERGER (self));
+  g_assert (resource_path != NULL);
+  g_assert (G_IS_MENU (app_menu));
+  g_assert (G_IS_MENU (menu));
+
+  n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu));
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(GMenuLinkIter) iter = NULL;
+      g_autoptr(GMenuModel) value = NULL;
+      const gchar *name;
+
+      iter = g_menu_model_iterate_item_links (G_MENU_MODEL (menu), i);
+
+      while (g_menu_link_iter_get_next (iter, &name, &value))
+        {
+          g_autofree gchar *after = NULL;
+          g_autoptr(GMenuItem) item = NULL;
+          g_autoptr(IdeMenuExtension) extension = NULL;
+          gint position;
+
+          if (!ide_str_equal0 (name, "section"))
+            continue;
+
+          if (!g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, "after", "s", &after))
+            after = NULL;
+
+          extension = ide_menu_extension_new (app_menu);
+
+          position = find_position (app_menu, after);
+          item = g_menu_item_new_section (NULL, value);
+          ide_menu_extension_insert_menu_item (extension, position, item);
+
+          save_extension (self, resource_path, extension);
+        }
+    }
+}
+
+static void
+ide_menu_merger_load_resource (IdeMenuMerger *self,
+                               const gchar   *resource_path)
+{
+  GtkApplication *app = GTK_APPLICATION (IDE_APPLICATION_DEFAULT);
+  GtkBuilder *builder;
+  const GSList *iter;
+  GSList *list;
+
+  g_assert (IDE_IS_MENU_MERGER (self));
+  g_assert (resource_path != NULL);
+
+  if (!g_resources_get_info (resource_path, 0, NULL, NULL, NULL))
+    return;
+
+  builder = gtk_builder_new_from_resource (resource_path);
+  list = gtk_builder_get_objects (builder);
+
+  for (iter = list; iter; iter = iter->next)
+    {
+      GObject *object = iter->data;
+      const gchar *id = get_object_id (object);
+      GMenu *app_menu;
+
+      if (id == NULL || !G_IS_MENU (object))
+        continue;
+
+      app_menu = gtk_application_get_menu_by_id (app, id);
+      if (app_menu == NULL)
+        continue;
+
+      ide_menu_merger_merge (self, resource_path, app_menu, G_MENU (object));
+    }
+
+  g_slist_free (list);
+  g_object_unref (builder);
+}
+
+static void
+ide_menu_merger_load_plugin (IdeMenuMerger  *self,
+                             PeasPluginInfo *plugin_info,
+                             PeasEngine     *engine)
+{
+  const gchar *module_name;
+  gchar *path;
+
+  g_assert (IDE_IS_MENU_MERGER (self));
+  g_assert (plugin_info != NULL);
+  g_assert (PEAS_IS_ENGINE (engine));
+
+  module_name = peas_plugin_info_get_module_name (plugin_info);
+
+  path = g_strdup_printf ("/org/gnome/builder/plugins/%s/gtk/menus.ui", module_name);
+  ide_menu_merger_load_resource (self, path);
+  g_free (path);
+}
+
+static void
+ide_menu_merger_unload_plugin (IdeMenuMerger  *self,
+                               PeasPluginInfo *plugin_info,
+                               PeasEngine     *engine)
+{
+  const gchar *module_name;
+  gchar *path;
+
+  g_assert (IDE_IS_MENU_MERGER (self));
+  g_assert (plugin_info != NULL);
+  g_assert (PEAS_IS_ENGINE (engine));
+
+  module_name = peas_plugin_info_get_module_name (plugin_info);
+
+  path = g_strdup_printf ("/org/gnome/builder/plugins/%s/gtk/menus.ui", module_name);
+  g_hash_table_remove (self->by_resource_path, path);
+  g_free (path);
+}
+
+static void
+ide_menu_merger_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (ide_menu_merger_parent_class)->finalize (object);
+}
+
+static void
+ide_menu_merger_class_init (IdeMenuMergerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_menu_merger_finalize;
+}
+
+static void
+ide_menu_merger_init (IdeMenuMerger *self)
+{
+  PeasEngine *engine = peas_engine_get_default ();
+  const GList *list;
+
+  self->by_resource_path = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                  g_free, (GDestroyNotify)g_ptr_array_unref);
+
+  g_signal_connect_object (engine,
+                           "load-plugin",
+                           G_CALLBACK (ide_menu_merger_load_plugin),
+                           self,
+                           G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (engine,
+                           "unload-plugin",
+                           G_CALLBACK (ide_menu_merger_unload_plugin),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  list = peas_engine_get_plugin_list (engine);
+
+  for (; list != NULL; list = list->next)
+    {
+      PeasPluginInfo *plugin_info = list->data;
+
+      ide_menu_merger_load_plugin (self, plugin_info, engine);
+    }
+}
+
+IdeMenuMerger *
+ide_menu_merger_new (void)
+{
+  return g_object_new (IDE_TYPE_MENU_MERGER, NULL);
+}
diff --git a/libide/ide-menu-merger.h b/libide/ide-menu-merger.h
new file mode 100644
index 0000000..51c9f20
--- /dev/null
+++ b/libide/ide-menu-merger.h
@@ -0,0 +1,34 @@
+/* ide-menu-merger.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_MENU_MERGER_H
+#define IDE_MENU_MERGER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_MENU_MERGER (ide_menu_merger_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeMenuMerger, ide_menu_merger, IDE, MENU_MERGER, GObject)
+
+IdeMenuMerger *ide_menu_merger_new (void);
+
+G_END_DECLS
+
+#endif /* IDE_MENU_MERGER_H */


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