/* * 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 . * * 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; }