[gnome-builder/wip/chergert/perspective] libide: start on very basic GMenu merging for plugins
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/perspective] libide: start on very basic GMenu merging for plugins
- Date: Wed, 2 Dec 2015 11:23:27 +0000 (UTC)
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]