summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk/gtkjoinedmenu.c315
-rw-r--r--gtk/gtkjoinedmenuprivate.h41
-rw-r--r--gtk/gtklabel.c10
-rw-r--r--gtk/gtkpasswordentry.c12
-rw-r--r--gtk/gtktext.c10
-rw-r--r--gtk/gtktextview.c11
-rw-r--r--gtk/meson.build1
7 files changed, 391 insertions, 9 deletions
diff --git a/gtk/gtkjoinedmenu.c b/gtk/gtkjoinedmenu.c
new file mode 100644
index 0000000000..bd1be741b2
--- /dev/null
+++ b/gtk/gtkjoinedmenu.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gtkjoinedmenuprivate.h"
+
+typedef struct
+{
+ GMenuModel *model;
+ gulong items_changed_handler;
+} Menu;
+
+struct _GtkJoinedMenu
+{
+ GMenuModel parent_instance;
+ GArray *menus;
+};
+
+G_DEFINE_TYPE (GtkJoinedMenu, gtk_joined_menu, G_TYPE_MENU_MODEL)
+
+static void
+clear_menu (gpointer data)
+{
+ Menu *menu = data;
+
+ g_clear_signal_handler (&menu->items_changed_handler, menu->model);
+ g_clear_object (&menu->model);
+}
+
+static gint
+gtk_joined_menu_get_offset_at_index (GtkJoinedMenu *self,
+ gint index)
+{
+ gint offset = 0;
+
+ for (guint i = 0; i < index; i++)
+ offset += g_menu_model_get_n_items (g_array_index (self->menus, Menu, i).model);
+
+ return offset;
+}
+
+static gint
+gtk_joined_menu_get_offset_at_model (GtkJoinedMenu *self,
+ GMenuModel *model)
+{
+ gint offset = 0;
+
+ for (guint i = 0; i < self->menus->len; i++)
+ {
+ const Menu *menu = &g_array_index (self->menus, Menu, i);
+
+ if (menu->model == model)
+ break;
+
+ offset += g_menu_model_get_n_items (menu->model);
+ }
+
+ return offset;
+}
+
+static gboolean
+gtk_joined_menu_is_mutable (GMenuModel *model)
+{
+ return TRUE;
+}
+
+static gint
+gtk_joined_menu_get_n_items (GMenuModel *model)
+{
+ GtkJoinedMenu *self = (GtkJoinedMenu *)model;
+
+ if (self->menus->len == 0)
+ return 0;
+
+ return gtk_joined_menu_get_offset_at_index (self, self->menus->len);
+}
+
+static const Menu *
+gtk_joined_menu_get_item (GtkJoinedMenu *self,
+ gint *item_index)
+{
+ g_assert (GTK_IS_JOINED_MENU (self));
+
+ for (guint i = 0; i < self->menus->len; i++)
+ {
+ const Menu *menu = &g_array_index (self->menus, Menu, i);
+ gint n_items = g_menu_model_get_n_items (menu->model);
+
+ if (n_items > *item_index)
+ return menu;
+
+ (*item_index) -= n_items;
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static void
+gtk_joined_menu_get_item_attributes (GMenuModel *model,
+ gint item_index,
+ GHashTable **attributes)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ G_MENU_MODEL_GET_CLASS (menu->model)->get_item_attributes (menu->model, item_index, attributes);
+}
+
+static GMenuAttributeIter *
+gtk_joined_menu_iterate_item_attributes (GMenuModel *model,
+ gint item_index)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ return G_MENU_MODEL_GET_CLASS (menu->model)->iterate_item_attributes (menu->model, item_index);
+}
+
+static GVariant *
+gtk_joined_menu_get_item_attribute_value (GMenuModel *model,
+ gint item_index,
+ const gchar *attribute,
+ const GVariantType *expected_type)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ return G_MENU_MODEL_GET_CLASS (menu->model)->get_item_attribute_value (menu->model, item_index, attribute, expected_type);
+}
+
+static void
+gtk_joined_menu_get_item_links (GMenuModel *model,
+ gint item_index,
+ GHashTable **links)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ G_MENU_MODEL_GET_CLASS (menu->model)->get_item_links (menu->model, item_index, links);
+}
+
+static GMenuLinkIter *
+gtk_joined_menu_iterate_item_links (GMenuModel *model,
+ gint item_index)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ return G_MENU_MODEL_GET_CLASS (menu->model)->iterate_item_links (menu->model, item_index);
+}
+
+static GMenuModel *
+gtk_joined_menu_get_item_link (GMenuModel *model,
+ gint item_index,
+ const gchar *link)
+{
+ const Menu *menu = gtk_joined_menu_get_item (GTK_JOINED_MENU (model), &item_index);
+ return G_MENU_MODEL_GET_CLASS (menu->model)->get_item_link (menu->model, item_index, link);
+}
+
+static void
+gtk_joined_menu_finalize (GObject *object)
+{
+ GtkJoinedMenu *self = (GtkJoinedMenu *)object;
+
+ g_clear_pointer (&self->menus, g_array_unref);
+
+ G_OBJECT_CLASS (gtk_joined_menu_parent_class)->finalize (object);
+}
+
+static void
+gtk_joined_menu_class_init (GtkJoinedMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GMenuModelClass *menu_model_class = G_MENU_MODEL_CLASS (klass);
+
+ object_class->finalize = gtk_joined_menu_finalize;
+
+ menu_model_class->is_mutable = gtk_joined_menu_is_mutable;
+ menu_model_class->get_n_items = gtk_joined_menu_get_n_items;
+ menu_model_class->get_item_attributes = gtk_joined_menu_get_item_attributes;
+ menu_model_class->iterate_item_attributes = gtk_joined_menu_iterate_item_attributes;
+ menu_model_class->get_item_attribute_value = gtk_joined_menu_get_item_attribute_value;
+ menu_model_class->get_item_links = gtk_joined_menu_get_item_links;
+ menu_model_class->iterate_item_links = gtk_joined_menu_iterate_item_links;
+ menu_model_class->get_item_link = gtk_joined_menu_get_item_link;
+}
+
+static void
+gtk_joined_menu_init (GtkJoinedMenu *self)
+{
+ self->menus = g_array_new (FALSE, FALSE, sizeof (Menu));
+ g_array_set_clear_func (self->menus, clear_menu);
+}
+
+static void
+gtk_joined_menu_on_items_changed (GtkJoinedMenu *self,
+ guint offset,
+ guint removed,
+ guint added,
+ GMenuModel *model)
+{
+ g_assert (GTK_IS_JOINED_MENU (self));
+ g_assert (G_IS_MENU_MODEL (model));
+
+ offset += gtk_joined_menu_get_offset_at_model (self, model);
+ g_menu_model_items_changed (G_MENU_MODEL (self), offset, removed, added);
+}
+
+GtkJoinedMenu *
+gtk_joined_menu_new (void)
+{
+ return g_object_new (GTK_TYPE_JOINED_MENU, NULL);
+}
+
+static void
+gtk_joined_menu_insert (GtkJoinedMenu *self,
+ GMenuModel *model,
+ gint index)
+{
+ Menu menu = { 0 };
+ gint offset;
+ gint n_items;
+
+ g_assert (GTK_IS_JOINED_MENU (self));
+ g_assert (G_IS_MENU_MODEL (model));
+ g_assert (index >= 0);
+ g_assert (index <= self->menus->len);
+
+ menu.model = g_object_ref (model);
+ menu.items_changed_handler =
+ g_signal_connect_swapped (menu.model,
+ "items-changed",
+ G_CALLBACK (gtk_joined_menu_on_items_changed),
+ self);
+ g_array_insert_val (self->menus, index, menu);
+
+ n_items = g_menu_model_get_n_items (model);
+ offset = gtk_joined_menu_get_offset_at_index (self, index);
+ g_menu_model_items_changed (G_MENU_MODEL (self), offset, 0, n_items);
+}
+
+void
+gtk_joined_menu_append_menu (GtkJoinedMenu *self,
+ GMenuModel *model)
+{
+
+ g_return_if_fail (GTK_IS_JOINED_MENU (self));
+ g_return_if_fail (G_MENU_MODEL (model));
+
+ gtk_joined_menu_insert (self, model, self->menus->len);
+}
+
+void
+gtk_joined_menu_prepend_menu (GtkJoinedMenu *self,
+ GMenuModel *model)
+{
+ g_return_if_fail (GTK_IS_JOINED_MENU (self));
+ g_return_if_fail (G_MENU_MODEL (model));
+
+ gtk_joined_menu_insert (self, model, 0);
+}
+
+void
+gtk_joined_menu_remove_index (GtkJoinedMenu *self,
+ guint index)
+{
+ const Menu *menu;
+ gint n_items;
+ gint offset;
+
+ g_return_if_fail (GTK_IS_JOINED_MENU (self));
+ g_return_if_fail (index < self->menus->len);
+
+ menu = &g_array_index (self->menus, Menu, index);
+
+ offset = gtk_joined_menu_get_offset_at_index (self, index);
+ n_items = g_menu_model_get_n_items (menu->model);
+
+ g_array_remove_index (self->menus, index);
+
+ g_menu_model_items_changed (G_MENU_MODEL (self), offset, n_items, 0);
+}
+
+void
+gtk_joined_menu_remove_menu (GtkJoinedMenu *self,
+ GMenuModel *model)
+{
+ g_return_if_fail (GTK_IS_JOINED_MENU (self));
+ g_return_if_fail (G_IS_MENU_MODEL (model));
+
+ for (guint i = 0; i < self->menus->len; i++)
+ {
+ if (g_array_index (self->menus, Menu, i).model == model)
+ {
+ gtk_joined_menu_remove_index (self, i);
+ break;
+ }
+ }
+}
+
+guint
+gtk_joined_menu_get_n_joined (GtkJoinedMenu *self)
+{
+ g_return_val_if_fail (GTK_IS_JOINED_MENU (self), 0);
+
+ return self->menus->len;
+}
diff --git a/gtk/gtkjoinedmenuprivate.h b/gtk/gtkjoinedmenuprivate.h
new file mode 100644
index 0000000000..8e4b61e6cb
--- /dev/null
+++ b/gtk/gtkjoinedmenuprivate.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2020 Red Hat, Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_JOINED_MENU (gtk_joined_menu_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkJoinedMenu, gtk_joined_menu, GTK, JOINED_MENU, GMenuModel)
+
+GtkJoinedMenu *gtk_joined_menu_new (void);
+guint gtk_joined_menu_get_n_joined (GtkJoinedMenu *self);
+void gtk_joined_menu_append_menu (GtkJoinedMenu *self,
+ GMenuModel *model);
+void gtk_joined_menu_prepend_menu (GtkJoinedMenu *self,
+ GMenuModel *model);
+void gtk_joined_menu_remove_menu (GtkJoinedMenu *self,
+ GMenuModel *model);
+void gtk_joined_menu_remove_index (GtkJoinedMenu *self,
+ guint index);
+
+G_END_DECLS
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c
index bfa231b9d9..ced834f980 100644
--- a/gtk/gtklabel.c
+++ b/gtk/gtklabel.c
@@ -52,6 +52,7 @@
#include "gtkdragsourceprivate.h"
#include "gtkdragicon.h"
#include "gtkcsscolorvalueprivate.h"
+#include "gtkjoinedmenuprivate.h"
#include <math.h>
#include <string.h>
@@ -5468,9 +5469,11 @@ gtk_label_move_cursor (GtkLabel *self,
static GMenuModel *
gtk_label_get_menu_model (GtkLabel *self)
{
+ GtkJoinedMenu *joined;
GMenu *menu, *section;
GMenuItem *item;
+ joined = gtk_joined_menu_new ();
menu = g_menu_new ();
section = g_menu_new ();
@@ -5498,10 +5501,13 @@ gtk_label_get_menu_model (GtkLabel *self)
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
+ gtk_joined_menu_append_menu (joined, G_MENU_MODEL (menu));
+ g_object_unref (menu);
+
if (self->extra_menu)
- g_menu_append_section (menu, NULL, self->extra_menu);
+ gtk_joined_menu_append_menu (joined, self->extra_menu);
- return G_MENU_MODEL (menu);
+ return G_MENU_MODEL (joined);
}
static void
diff --git a/gtk/gtkpasswordentry.c b/gtk/gtkpasswordentry.c
index 7e6e470dd7..4975ee7312 100644
--- a/gtk/gtkpasswordentry.c
+++ b/gtk/gtkpasswordentry.c
@@ -36,6 +36,7 @@
#include "gtkwidgetprivate.h"
#include "gtkcsspositionvalueprivate.h"
#include "gtkcssnodeprivate.h"
+#include "gtkjoinedmenuprivate.h"
/**
@@ -658,6 +659,7 @@ void
gtk_password_entry_set_extra_menu (GtkPasswordEntry *entry,
GMenuModel *model)
{
+ GtkJoinedMenu *joined;
GMenu *menu;
GMenu *section;
GMenuItem *item;
@@ -671,6 +673,7 @@ gtk_password_entry_set_extra_menu (GtkPasswordEntry *entry,
return;
}
+ joined = gtk_joined_menu_new ();
menu = g_menu_new ();
section = g_menu_new ();
@@ -682,12 +685,15 @@ gtk_password_entry_set_extra_menu (GtkPasswordEntry *entry,
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
+ gtk_joined_menu_append_menu (joined, G_MENU_MODEL (menu));
+ g_object_unref (menu);
+
if (model)
- g_menu_append_section (menu, NULL, model);
+ gtk_joined_menu_append_menu (joined, model);
- gtk_text_set_extra_menu (GTK_TEXT (entry->entry), G_MENU_MODEL (menu));
+ gtk_text_set_extra_menu (GTK_TEXT (entry->entry), G_MENU_MODEL (joined));
- g_object_unref (menu);
+ g_object_unref (joined);
g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_EXTRA_MENU]);
}
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index 8e7fbf3cc1..8d1bde4674 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -64,6 +64,7 @@
#include "gtkwindow.h"
#include "gtknative.h"
#include "gtkactionmuxerprivate.h"
+#include "gtkjoinedmenuprivate.h"
#include <cairo-gobject.h>
#include <string.h>
@@ -6035,9 +6036,11 @@ static GMenuModel *
gtk_text_get_menu_model (GtkText *self)
{
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
+ GtkJoinedMenu *joined;
GMenu *menu, *section;
GMenuItem *item;
+ joined = gtk_joined_menu_new ();
menu = g_menu_new ();
section = g_menu_new ();
@@ -6075,10 +6078,13 @@ gtk_text_get_menu_model (GtkText *self)
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
+ gtk_joined_menu_append_menu (joined, G_MENU_MODEL (menu));
+ g_object_unref (menu);
+
if (priv->extra_menu)
- g_menu_append_section (menu, NULL, priv->extra_menu);
+ gtk_joined_menu_append_menu (joined, priv->extra_menu);
- return G_MENU_MODEL (menu);
+ return G_MENU_MODEL (joined);
}
static gboolean
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 5bf134302c..4796a34b1e 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -56,6 +56,7 @@
#include "gtkpango.h"
#include "gtknative.h"
#include "gtkwidgetprivate.h"
+#include "gtkjoinedmenuprivate.h"
/**
* GtkTextView:
@@ -8733,9 +8734,12 @@ static GMenuModel *
gtk_text_view_get_menu_model (GtkTextView *text_view)
{
GtkTextViewPrivate *priv = text_view->priv;
+ GtkJoinedMenu *joined;
GMenu *menu, *section;
GMenuItem *item;
+ joined = gtk_joined_menu_new ();
+
menu = g_menu_new ();
section = g_menu_new ();
@@ -8785,10 +8789,13 @@ gtk_text_view_get_menu_model (GtkTextView *text_view)
g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
g_object_unref (section);
+ gtk_joined_menu_append_menu (joined, G_MENU_MODEL (menu));
+ g_object_unref (menu);
+
if (priv->extra_menu)
- g_menu_append_section (menu, NULL, priv->extra_menu);
+ gtk_joined_menu_append_menu (joined, priv->extra_menu);
- return G_MENU_MODEL (menu);
+ return G_MENU_MODEL (joined);
}
static void
diff --git a/gtk/meson.build b/gtk/meson.build
index 9e2e04d798..47ca9804a9 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -114,6 +114,7 @@ gtk_private_sources = files([
'gtkiconcache.c',
'gtkiconcachevalidator.c',
'gtkiconhelper.c',
+ 'gtkjoinedmenu.c',
'gtkkineticscrolling.c',
'gtkmagnifier.c',
'gtkmenusectionbox.c',