[gnome-builder/wip/gtk4-port: 54/94] core: add IdeMenuManager
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/gtk4-port: 54/94] core: add IdeMenuManager
- Date: Mon, 28 Mar 2022 20:27:08 +0000 (UTC)
commit cb72f67e4a91d42bf68fad669d800101983868b8
Author: Christian Hergert <chergert redhat com>
Date: Fri Sep 24 14:08:02 2021 -0700
core: add IdeMenuManager
This is pretty generic GIO stuff, so we can just keep it in core where we
have other stuff like IdeNotification.
src/libide/core/ide-menu-manager.c | 673 +++++++++++++++++++++++++++++++++++++
src/libide/core/ide-menu-manager.h | 56 +++
src/libide/core/libide-core.h | 1 +
src/libide/core/meson.build | 2 +
4 files changed, 732 insertions(+)
---
diff --git a/src/libide/core/ide-menu-manager.c b/src/libide/core/ide-menu-manager.c
new file mode 100644
index 000000000..89238121e
--- /dev/null
+++ b/src/libide/core/ide-menu-manager.c
@@ -0,0 +1,673 @@
+/* ide-menu-manager.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-menu-manager"
+
+#include "config.h"
+
+#include <string.h>
+
+#include "menus/ide-menu-manager.h"
+#include "util/ide-util-private.h"
+
+struct _IdeMenuManager
+{
+ GObject parent_instance;
+
+ guint last_merge_id;
+ GHashTable *models;
+};
+
+G_DEFINE_TYPE (IdeMenuManager, ide_menu_manager, G_TYPE_OBJECT)
+
+#define IDE_MENU_ATTRIBUTE_BEFORE "before"
+#define IDE_MENU_ATTRIBUTE_AFTER "after"
+#define IDE_MENU_ATTRIBUTE_MERGE_ID "dazzle-merge-id"
+
+/**
+ * IdeMenuManager:
+ *
+ * The goal of #IdeMenuManager is to simplify the process of merging multiple
+ * GtkBuilder .ui files containing menus into a single representation of the
+ * application menus. Additionally, it provides the ability to "unmerge"
+ * previously merged menus.
+ *
+ * This allows for an application to have plugins which seemlessly extends
+ * the core application menus.
+ *
+ * Implementation notes:
+ *
+ * To make this work, we don't use the GMenu instances created by a GtkBuilder
+ * instance. Instead, we create the menus ourself and recreate section and
+ * submenu links. This allows the #IdeMenuManager to be in full control of
+ * the generated menus.
+ *
+ * ide_menu_manager_get_menu_by_id() will always return a #GMenu, however
+ * that menu may contain no children until something has extended it later
+ * on during the application process.
+ *
+ * Since: 3.26
+ */
+
+static const gchar *
+get_object_id (GObject *object)
+{
+ g_assert (G_IS_OBJECT (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 void
+ide_menu_manager_dispose (GObject *object)
+{
+ IdeMenuManager *self = (IdeMenuManager *)object;
+
+ g_clear_pointer (&self->models, g_hash_table_unref);
+
+ G_OBJECT_CLASS (ide_menu_manager_parent_class)->dispose (object);
+}
+
+static void
+ide_menu_manager_class_init (IdeMenuManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_menu_manager_dispose;
+}
+
+static void
+ide_menu_manager_init (IdeMenuManager *self)
+{
+ self->models = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+static gint
+find_with_attribute_string (GMenuModel *model,
+ const gchar *attribute,
+ const gchar *value)
+{
+ guint n_items;
+
+ g_assert (G_IS_MENU_MODEL (model));
+ g_assert (attribute != NULL);
+ g_assert (value != NULL);
+
+ n_items = g_menu_model_get_n_items (model);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autofree gchar *item_value = NULL;
+
+ if (g_menu_model_get_item_attribute (model, i, attribute, "s", &item_value) &&
+ (g_strcmp0 (value, item_value) == 0))
+ return i;
+ }
+
+ return -1;
+}
+
+static gboolean
+ide_menu_manager_menu_contains (IdeMenuManager *self,
+ GMenu *menu,
+ GMenuItem *item)
+{
+ const gchar *link_id;
+ const gchar *label;
+
+ g_assert (IDE_IS_MENU_MANAGER (self));
+ g_assert (G_IS_MENU (menu));
+ g_assert (G_IS_MENU_ITEM (item));
+
+ /* try to find match by item label */
+ if (g_menu_item_get_attribute (item, G_MENU_ATTRIBUTE_LABEL, "&s", &label) &&
+ (find_with_attribute_string (G_MENU_MODEL (menu), G_MENU_ATTRIBUTE_LABEL, label) >= 0))
+ return TRUE;
+
+ /* try to find match by item link */
+ if (g_menu_item_get_attribute (item, "ide-link-id", "&s", &link_id) &&
+ (find_with_attribute_string (G_MENU_MODEL (menu), "ide-link-id", link_id) >= 0))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+model_copy_attributes_to_item (GMenuModel *model,
+ gint item_index,
+ GMenuItem *item)
+{
+ g_autoptr(GMenuAttributeIter) iter = NULL;
+ const gchar *attr_name;
+ GVariant *attr_value;
+
+ g_assert (G_IS_MENU_MODEL (model));
+ g_assert (item_index >= 0);
+ g_assert (G_IS_MENU_ITEM (item));
+
+ if (!(iter = g_menu_model_iterate_item_attributes (model, item_index)))
+ return;
+
+ while (g_menu_attribute_iter_get_next (iter, &attr_name, &attr_value))
+ {
+ g_menu_item_set_attribute_value (item, attr_name, attr_value);
+ g_variant_unref (attr_value);
+ }
+}
+
+static void
+model_copy_links_to_item (GMenuModel *model,
+ guint position,
+ GMenuItem *item)
+{
+ g_autoptr(GMenuLinkIter) link_iter = NULL;
+
+ g_assert (G_IS_MENU_MODEL (model));
+ g_assert (G_IS_MENU_ITEM (item));
+
+ link_iter = g_menu_model_iterate_item_links (model, position);
+
+ while (g_menu_link_iter_next (link_iter))
+ {
+ g_autoptr(GMenuModel) link_model = NULL;
+ const gchar *link_name;
+
+ link_name = g_menu_link_iter_get_name (link_iter);
+ link_model = g_menu_link_iter_get_value (link_iter);
+
+ g_menu_item_set_link (item, link_name, link_model);
+ }
+}
+
+static void
+menu_move_item_to (GMenu *menu,
+ guint position,
+ guint new_position)
+{
+ g_autoptr(GMenuItem) item = NULL;
+
+ g_assert (G_IS_MENU (menu));
+
+ item = g_menu_item_new (NULL, NULL);
+ model_copy_attributes_to_item (G_MENU_MODEL (menu), position, item);
+ model_copy_links_to_item (G_MENU_MODEL (menu), position, item);
+
+ g_menu_remove (menu, position);
+ g_menu_insert_item (menu, new_position, item);
+}
+
+static void
+ide_menu_manager_resolve_constraints (GMenu *menu)
+{
+ GMenuModel *model = (GMenuModel *)menu;
+ gint n_items;
+
+ g_assert (G_IS_MENU (menu));
+
+ n_items = (gint)g_menu_model_get_n_items (G_MENU_MODEL (menu));
+
+ /*
+ * We start iterating forwards. As we look at each row, we start
+ * again from the end working backwards to see if we need to be
+ * moved after that row.
+ *
+ * This way we know we see the furthest we might need to jump first.
+ */
+
+ for (gint i = 0; i < n_items; i++)
+ {
+ g_autofree gchar *i_after = NULL;
+
+ g_menu_model_get_item_attribute (model, i, IDE_MENU_ATTRIBUTE_AFTER, "s", &i_after);
+ if (i_after == NULL)
+ continue;
+
+ /* Work our way backwards from the end back to
+ * our current position (but not overlapping).
+ */
+ for (gint j = n_items - 1; j > i; j--)
+ {
+ g_autofree gchar *j_id = NULL;
+ g_autofree gchar *j_label = NULL;
+
+ g_menu_model_get_item_attribute (model, j, "id", "s", &j_id);
+ g_menu_model_get_item_attribute (model, j, "label", "s", &j_label);
+
+ if (ide_str_equal0 (i_after, j_id) || ide_str_equal0 (i_after, j_label))
+ {
+ /* You might think we need to place the item *AFTER*
+ * our position "j". But since we remove the row where
+ * "i" currently is, we get the proper location.
+ */
+ menu_move_item_to (menu, i, j);
+ i--;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Now we need to apply the same thing but for the "before" links
+ * in our model. To do this, we also want to ensure we find the
+ * furthest jump first. So we start from the end and work our way
+ * towards the front and for each of those nodes, start from the
+ * front and work our way back.
+ */
+
+ for (gint i = n_items - 1; i >= 0; i--)
+ {
+ g_autofree gchar *i_before = NULL;
+
+ g_menu_model_get_item_attribute (model, i, IDE_MENU_ATTRIBUTE_BEFORE, "s", &i_before);
+ if (i_before == NULL)
+ continue;
+
+ /* Work our way from the front back towards our current position
+ * that would cause our position to jump.
+ */
+ for (gint j = 0; j < i; j++)
+ {
+ g_autofree gchar *j_id = NULL;
+ g_autofree gchar *j_label = NULL;
+
+ g_menu_model_get_item_attribute (model, j, "id", "s", &j_id);
+ g_menu_model_get_item_attribute (model, j, "label", "s", &j_label);
+
+ if (ide_str_equal0 (i_before, j_id) || ide_str_equal0 (i_before, j_label))
+ {
+ /*
+ * This item needs to be placed before this item we just found.
+ * Since that is the furthest we could jump, just stop
+ * afterwards.
+ */
+ menu_move_item_to (menu, i, j);
+ i++;
+ break;
+ }
+ }
+ }
+}
+
+static void
+ide_menu_manager_add_to_menu (IdeMenuManager *self,
+ GMenu *menu,
+ GMenuItem *item)
+{
+ g_assert (IDE_IS_MENU_MANAGER (self));
+ g_assert (G_IS_MENU (menu));
+ g_assert (G_IS_MENU_ITEM (item));
+
+ /*
+ * The proplem here is one that could end up being an infinite
+ * loop if we tried to resolve all the position requirements
+ * until no more position changes were required. So instead we
+ * simplify the problem into an append, and two-passes as trying
+ * to fix up the positions.
+ */
+ g_menu_append_item (menu, item);
+ ide_menu_manager_resolve_constraints (menu);
+ ide_menu_manager_resolve_constraints (menu);
+}
+
+static void
+ide_menu_manager_merge_model (IdeMenuManager *self,
+ GMenu *menu,
+ GMenuModel *model,
+ guint merge_id)
+{
+ guint n_items;
+
+ g_assert (IDE_IS_MENU_MANAGER (self));
+ g_assert (G_IS_MENU (menu));
+ g_assert (G_IS_MENU_MODEL (model));
+ g_assert (merge_id > 0);
+
+ /*
+ * NOTES:
+ *
+ * Instead of using g_menu_item_new_from_model(), we create our own item
+ * and resolve section/submenu links. This allows us to be in full control
+ * of all of the menu items created.
+ *
+ * We move through each item in @model. If that item does not exist within
+ * @menu, we add it taking into account %IDE_MENU_ATTRIBUTE_BEFORE and
+ * %IDE_MENU_ATTRIBUTE_AFTER.
+ */
+
+ n_items = g_menu_model_get_n_items (model);
+
+ for (guint i = 0; i < n_items; i++)
+ {
+ g_autoptr(GMenuItem) item = NULL;
+ g_autoptr(GMenuLinkIter) link_iter = NULL;
+
+ item = g_menu_item_new (NULL, NULL);
+
+ /*
+ * Copy attributes from the model. This includes, label, action,
+ * target, before, after, etc. Also set our merge-id so that we
+ * can remove the item when we are unmerged.
+ */
+ model_copy_attributes_to_item (model, i, item);
+ g_menu_item_set_attribute (item, IDE_MENU_ATTRIBUTE_MERGE_ID, "u", merge_id);
+
+ /*
+ * If this is a link, resolve it from our already created GMenu.
+ * The menu might be empty now, but it will get filled in on a
+ * followup pass for that model.
+ */
+ link_iter = g_menu_model_iterate_item_links (model, i);
+ while (g_menu_link_iter_next (link_iter))
+ {
+ g_autoptr(GMenuModel) link_model = NULL;
+ const gchar *link_name;
+ const gchar *link_id;
+ GMenuModel *internal_menu;
+
+ link_name = g_menu_link_iter_get_name (link_iter);
+ link_model = g_menu_link_iter_get_value (link_iter);
+
+ g_assert (link_name != NULL);
+ g_assert (G_IS_MENU_MODEL (link_model));
+
+ link_id = get_object_id (G_OBJECT (link_model));
+
+ if (link_id == NULL)
+ {
+ g_warning ("Link of type \"%s\" missing \"id=\". "
+ "Merging will not be possible.",
+ link_name);
+ continue;
+ }
+
+ internal_menu = g_hash_table_lookup (self->models, link_id);
+
+ if (internal_menu == NULL)
+ {
+ g_warning ("linked menu %s has not been created", link_id);
+ continue;
+ }
+
+ /*
+ * Save the internal link reference-id to do merging of items
+ * later on. We need to know if an item matches when we might
+ * not have a "label" to work from.
+ */
+ g_menu_item_set_attribute (item, "ide-link-id", "s", link_id);
+
+ g_menu_item_set_link (item, link_name, internal_menu);
+ }
+
+ /*
+ * If the menu already has this item, that's fine. We will populate
+ * the submenu/section links in followup merges of their GMenuModel.
+ */
+ if (ide_menu_manager_menu_contains (self, menu, item))
+ continue;
+
+ ide_menu_manager_add_to_menu (self, menu, item);
+ }
+}
+
+static void
+ide_menu_manager_merge_builder (IdeMenuManager *self,
+ GtkBuilder *builder,
+ guint merge_id)
+{
+ const GSList *iter;
+ GSList *list;
+
+ g_assert (IDE_IS_MENU_MANAGER (self));
+ g_assert (GTK_IS_BUILDER (builder));
+ g_assert (merge_id > 0);
+
+ /*
+ * NOTES:
+ *
+ * We cannot re-use any of the created GMenu from the builder as we need
+ * control over all the created GMenu. Primarily because manipulating
+ * existing GMenu is such a PITA. So instead, we create our own GMenu and
+ * resolve links manually.
+ *
+ * Since GtkBuilder requires that all menus have an "id" element, we can
+ * resolve the menu->id fairly easily. First we create our own GMenu
+ * instances so that we can always resolve them during the creation process.
+ * Then we can go through and manually resolve links as we create items.
+ *
+ * We don't need to recursively create the menus since we will come across
+ * additional GMenu instances while iterating the available objects from the
+ * GtkBuilder. This does require 2 iterations of the objects, but that is
+ * not an issue.
+ */
+
+ list = gtk_builder_get_objects (builder);
+
+ /*
+ * For every menu with an id, check to see if we already created our
+ * instance of that menu. If not, create it now so we can resolve them
+ * while building the menu links.
+ */
+ for (iter = list; iter != NULL; iter = iter->next)
+ {
+ GObject *object = iter->data;
+ const gchar *id;
+ GMenu *menu;
+
+ if (!G_IS_MENU (object))
+ continue;
+
+ if (!(id = get_object_id (object)))
+ {
+ g_warning ("menu without identifier, implausible");
+ continue;
+ }
+
+ if (!(menu = g_hash_table_lookup (self->models, id)))
+ g_hash_table_insert (self->models, g_strdup (id), g_menu_new ());
+ }
+
+ /*
+ * Now build each menu we discovered in the GtkBuilder. We do not need to
+ * build them recursively since we will pass the linked menus as we make
+ * forward progress on the GtkBuilder created objects.
+ */
+
+ for (iter = list; iter != NULL; iter = iter->next)
+ {
+ GObject *object = iter->data;
+ const gchar *id;
+ GMenu *menu;
+
+ if (!G_IS_MENU_MODEL (object))
+ continue;
+
+ if (!(id = get_object_id (object)))
+ continue;
+
+ menu = g_hash_table_lookup (self->models, id);
+
+ g_assert (G_IS_MENU (menu));
+
+ ide_menu_manager_merge_model (self, menu, G_MENU_MODEL (object), merge_id);
+ }
+
+ g_slist_free (list);
+}
+
+IdeMenuManager *
+ide_menu_manager_new (void)
+{
+ return g_object_new (IDE_TYPE_MENU_MANAGER, NULL);
+}
+
+guint
+ide_menu_manager_add_filename (IdeMenuManager *self,
+ const gchar *filename,
+ GError **error)
+{
+ GtkBuilder *builder;
+ guint merge_id;
+
+ g_return_val_if_fail (IDE_IS_MENU_MANAGER (self), 0);
+ g_return_val_if_fail (filename != NULL, 0);
+
+ builder = gtk_builder_new ();
+
+ if (!gtk_builder_add_from_file (builder, filename, error))
+ {
+ g_object_unref (builder);
+ return 0;
+ }
+
+ merge_id = ++self->last_merge_id;
+ ide_menu_manager_merge_builder (self, builder, merge_id);
+ g_object_unref (builder);
+
+ return merge_id;
+}
+
+guint
+ide_menu_manager_merge (IdeMenuManager *self,
+ const gchar *menu_id,
+ GMenuModel *menu_model)
+{
+ GMenu *menu;
+ guint merge_id;
+
+ g_return_val_if_fail (IDE_IS_MENU_MANAGER (self), 0);
+ g_return_val_if_fail (menu_id != NULL, 0);
+ g_return_val_if_fail (G_IS_MENU_MODEL (menu_model), 0);
+
+ merge_id = ++self->last_merge_id;
+
+ if (!(menu = g_hash_table_lookup (self->models, menu_id)))
+ {
+ GMenu *new_model = g_menu_new ();
+ g_hash_table_insert (self->models, g_strdup (menu_id), new_model);
+ menu = new_model;
+ }
+
+ ide_menu_manager_merge_model (self, menu, menu_model, merge_id);
+
+ return merge_id;
+}
+
+/**
+ * ide_menu_manager_remove:
+ * @self: a #IdeMenuManager
+ * @merge_id: A previously registered merge id
+ *
+ * This removes items from menus that were added as part of a previous
+ * menu merge. Use the value returned from ide_menu_manager_merge() as
+ * the @merge_id.
+ *
+ * Since: 3.26
+ */
+void
+ide_menu_manager_remove (IdeMenuManager *self,
+ guint merge_id)
+{
+ GHashTableIter iter;
+ GMenu *menu;
+
+ g_return_if_fail (IDE_IS_MENU_MANAGER (self));
+ g_return_if_fail (merge_id != 0);
+
+ g_hash_table_iter_init (&iter, self->models);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&menu))
+ {
+ gint n_items;
+ gint i;
+
+ g_assert (G_IS_MENU (menu));
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu));
+
+ /* Iterate backward so we have a stable loop variable. */
+ for (i = n_items - 1; i >= 0; i--)
+ {
+ guint item_merge_id = 0;
+
+ if (g_menu_model_get_item_attribute (G_MENU_MODEL (menu),
+ i,
+ IDE_MENU_ATTRIBUTE_MERGE_ID,
+ "u", &item_merge_id))
+ {
+ if (item_merge_id == merge_id)
+ g_menu_remove (menu, i);
+ }
+ }
+ }
+}
+
+/**
+ * ide_menu_manager_get_menu_by_id:
+ *
+ * Returns: (transfer none): A #GMenu.
+ */
+GMenu *
+ide_menu_manager_get_menu_by_id (IdeMenuManager *self,
+ const gchar *menu_id)
+{
+ GMenu *menu;
+
+ g_return_val_if_fail (IDE_IS_MENU_MANAGER (self), NULL);
+ g_return_val_if_fail (menu_id != NULL, NULL);
+
+ menu = g_hash_table_lookup (self->models, menu_id);
+
+ if (menu == NULL)
+ {
+ menu = g_menu_new ();
+ g_hash_table_insert (self->models, g_strdup (menu_id), menu);
+ }
+
+ return menu;
+}
+
+guint
+ide_menu_manager_add_resource (IdeMenuManager *self,
+ const gchar *resource,
+ GError **error)
+{
+ GtkBuilder *builder;
+ guint merge_id;
+
+ g_return_val_if_fail (IDE_IS_MENU_MANAGER (self), 0);
+ g_return_val_if_fail (resource != NULL, 0);
+
+ if (g_str_has_prefix (resource, "resource://"))
+ resource += strlen ("resource://");
+
+ builder = gtk_builder_new ();
+
+ if (!gtk_builder_add_from_resource (builder, resource, error))
+ {
+ g_object_unref (builder);
+ return 0;
+ }
+
+ merge_id = ++self->last_merge_id;
+ ide_menu_manager_merge_builder (self, builder, merge_id);
+ g_object_unref (builder);
+
+ return merge_id;
+}
diff --git a/src/libide/core/ide-menu-manager.h b/src/libide/core/ide-menu-manager.h
new file mode 100644
index 000000000..161cbcf42
--- /dev/null
+++ b/src/libide/core/ide-menu-manager.h
@@ -0,0 +1,56 @@
+/* ide-menu-manager.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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_MANAGER_H
+#define IDE_MENU_MANAGER_H
+
+#include <gtk/gtk.h>
+
+#include "ide-version-macros.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_MENU_MANAGER (ide_menu_manager_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeMenuManager, ide_menu_manager, IDE, MENU_MANAGER, GObject)
+
+IDE_AVAILABLE_IN_ALL
+IdeMenuManager *ide_menu_manager_new (void);
+IDE_AVAILABLE_IN_ALL
+guint ide_menu_manager_add_filename (IdeMenuManager *self,
+ const gchar *filename,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+guint ide_menu_manager_add_resource (IdeMenuManager *self,
+ const gchar *resource,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+guint ide_menu_manager_merge (IdeMenuManager *self,
+ const gchar *menu_id,
+ GMenuModel *model);
+IDE_AVAILABLE_IN_ALL
+void ide_menu_manager_remove (IdeMenuManager *self,
+ guint merge_id);
+IDE_AVAILABLE_IN_ALL
+GMenu *ide_menu_manager_get_menu_by_id (IdeMenuManager *self,
+ const gchar *menu_id);
+
+G_END_DECLS
+
+#endif /* IDE_MENU_MANAGER_H */
diff --git a/src/libide/core/libide-core.h b/src/libide/core/libide-core.h
index da92b41a0..d83ab32df 100644
--- a/src/libide/core/libide-core.h
+++ b/src/libide/core/libide-core.h
@@ -31,6 +31,7 @@
#include "ide-global.h"
#include "ide-log.h"
#include "ide-macros.h"
+#include "ide-menu-manager.h"
#include "ide-notification.h"
#include "ide-notifications.h"
#include "ide-object.h"
diff --git a/src/libide/core/meson.build b/src/libide/core/meson.build
index de684fece..5e973f6e6 100644
--- a/src/libide/core/meson.build
+++ b/src/libide/core/meson.build
@@ -59,6 +59,7 @@ libide_core_public_headers = [
'ide-global.h',
'ide-log.h',
'ide-macros.h',
+ 'ide-menu-manager.h',
'ide-notification.h',
'ide-notifications.h',
'ide-object.h',
@@ -87,6 +88,7 @@ libide_core_public_sources = [
'ide-context-addin.c',
'ide-global.c',
'ide-log.c',
+ 'ide-menu-manager.c',
'ide-notification.c',
'ide-notifications.c',
'ide-object.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]