1/*
2 * Copyright © 2020 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-2.1-or-later
18 */
19
20#include "config.h"
21
22#include "gtkjoinedmenuprivate.h"
23
24typedef struct
25{
26 GMenuModel *model;
27 gulong items_changed_handler;
28} Menu;
29
30struct _GtkJoinedMenu
31{
32 GMenuModel parent_instance;
33 GArray *menus;
34};
35
36G_DEFINE_TYPE (GtkJoinedMenu, gtk_joined_menu, G_TYPE_MENU_MODEL)
37
38static void
39clear_menu (gpointer data)
40{
41 Menu *menu = data;
42
43 g_clear_signal_handler (&menu->items_changed_handler, menu->model);
44 g_clear_object (&menu->model);
45}
46
47static gint
48gtk_joined_menu_get_offset_at_index (GtkJoinedMenu *self,
49 gint index)
50{
51 gint offset = 0;
52
53 for (guint i = 0; i < index; i++)
54 offset += g_menu_model_get_n_items (g_array_index (self->menus, Menu, i).model);
55
56 return offset;
57}
58
59static gint
60gtk_joined_menu_get_offset_at_model (GtkJoinedMenu *self,
61 GMenuModel *model)
62{
63 gint offset = 0;
64
65 for (guint i = 0; i < self->menus->len; i++)
66 {
67 const Menu *menu = &g_array_index (self->menus, Menu, i);
68
69 if (menu->model == model)
70 break;
71
72 offset += g_menu_model_get_n_items (model: menu->model);
73 }
74
75 return offset;
76}
77
78static gboolean
79gtk_joined_menu_is_mutable (GMenuModel *model)
80{
81 return TRUE;
82}
83
84static gint
85gtk_joined_menu_get_n_items (GMenuModel *model)
86{
87 GtkJoinedMenu *self = (GtkJoinedMenu *)model;
88
89 if (self->menus->len == 0)
90 return 0;
91
92 return gtk_joined_menu_get_offset_at_index (self, index: self->menus->len);
93}
94
95static const Menu *
96gtk_joined_menu_get_item (GtkJoinedMenu *self,
97 gint *item_index)
98{
99 g_assert (GTK_IS_JOINED_MENU (self));
100
101 for (guint i = 0; i < self->menus->len; i++)
102 {
103 const Menu *menu = &g_array_index (self->menus, Menu, i);
104 gint n_items = g_menu_model_get_n_items (model: menu->model);
105
106 if (n_items > *item_index)
107 return menu;
108
109 (*item_index) -= n_items;
110 }
111
112 g_return_val_if_reached (NULL);
113}
114
115static void
116gtk_joined_menu_get_item_attributes (GMenuModel *model,
117 gint item_index,
118 GHashTable **attributes)
119{
120 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
121 G_MENU_MODEL_GET_CLASS (menu->model)->get_item_attributes (menu->model, item_index, attributes);
122}
123
124static GMenuAttributeIter *
125gtk_joined_menu_iterate_item_attributes (GMenuModel *model,
126 gint item_index)
127{
128 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
129 return G_MENU_MODEL_GET_CLASS (menu->model)->iterate_item_attributes (menu->model, item_index);
130}
131
132static GVariant *
133gtk_joined_menu_get_item_attribute_value (GMenuModel *model,
134 gint item_index,
135 const gchar *attribute,
136 const GVariantType *expected_type)
137{
138 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
139 return G_MENU_MODEL_GET_CLASS (menu->model)->get_item_attribute_value (menu->model, item_index, attribute, expected_type);
140}
141
142static void
143gtk_joined_menu_get_item_links (GMenuModel *model,
144 gint item_index,
145 GHashTable **links)
146{
147 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
148 G_MENU_MODEL_GET_CLASS (menu->model)->get_item_links (menu->model, item_index, links);
149}
150
151static GMenuLinkIter *
152gtk_joined_menu_iterate_item_links (GMenuModel *model,
153 gint item_index)
154{
155 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
156 return G_MENU_MODEL_GET_CLASS (menu->model)->iterate_item_links (menu->model, item_index);
157}
158
159static GMenuModel *
160gtk_joined_menu_get_item_link (GMenuModel *model,
161 gint item_index,
162 const gchar *link)
163{
164 const Menu *menu = gtk_joined_menu_get_item (self: GTK_JOINED_MENU (ptr: model), item_index: &item_index);
165 return G_MENU_MODEL_GET_CLASS (menu->model)->get_item_link (menu->model, item_index, link);
166}
167
168static void
169gtk_joined_menu_finalize (GObject *object)
170{
171 GtkJoinedMenu *self = (GtkJoinedMenu *)object;
172
173 g_clear_pointer (&self->menus, g_array_unref);
174
175 G_OBJECT_CLASS (gtk_joined_menu_parent_class)->finalize (object);
176}
177
178static void
179gtk_joined_menu_class_init (GtkJoinedMenuClass *klass)
180{
181 GObjectClass *object_class = G_OBJECT_CLASS (klass);
182 GMenuModelClass *menu_model_class = G_MENU_MODEL_CLASS (klass);
183
184 object_class->finalize = gtk_joined_menu_finalize;
185
186 menu_model_class->is_mutable = gtk_joined_menu_is_mutable;
187 menu_model_class->get_n_items = gtk_joined_menu_get_n_items;
188 menu_model_class->get_item_attributes = gtk_joined_menu_get_item_attributes;
189 menu_model_class->iterate_item_attributes = gtk_joined_menu_iterate_item_attributes;
190 menu_model_class->get_item_attribute_value = gtk_joined_menu_get_item_attribute_value;
191 menu_model_class->get_item_links = gtk_joined_menu_get_item_links;
192 menu_model_class->iterate_item_links = gtk_joined_menu_iterate_item_links;
193 menu_model_class->get_item_link = gtk_joined_menu_get_item_link;
194}
195
196static void
197gtk_joined_menu_init (GtkJoinedMenu *self)
198{
199 self->menus = g_array_new (FALSE, FALSE, element_size: sizeof (Menu));
200 g_array_set_clear_func (array: self->menus, clear_func: clear_menu);
201}
202
203static void
204gtk_joined_menu_on_items_changed (GtkJoinedMenu *self,
205 guint offset,
206 guint removed,
207 guint added,
208 GMenuModel *model)
209{
210 g_assert (GTK_IS_JOINED_MENU (self));
211 g_assert (G_IS_MENU_MODEL (model));
212
213 offset += gtk_joined_menu_get_offset_at_model (self, model);
214 g_menu_model_items_changed (G_MENU_MODEL (self), position: offset, removed, added);
215}
216
217GtkJoinedMenu *
218gtk_joined_menu_new (void)
219{
220 return g_object_new (GTK_TYPE_JOINED_MENU, NULL);
221}
222
223static void
224gtk_joined_menu_insert (GtkJoinedMenu *self,
225 GMenuModel *model,
226 gint index)
227{
228 Menu menu = { 0 };
229 gint offset;
230 gint n_items;
231
232 g_assert (GTK_IS_JOINED_MENU (self));
233 g_assert (G_IS_MENU_MODEL (model));
234 g_assert (index >= 0);
235 g_assert (index <= self->menus->len);
236
237 menu.model = g_object_ref (model);
238 menu.items_changed_handler =
239 g_signal_connect_swapped (menu.model,
240 "items-changed",
241 G_CALLBACK (gtk_joined_menu_on_items_changed),
242 self);
243 g_array_insert_val (self->menus, index, menu);
244
245 n_items = g_menu_model_get_n_items (model);
246 offset = gtk_joined_menu_get_offset_at_index (self, index);
247 g_menu_model_items_changed (G_MENU_MODEL (self), position: offset, removed: 0, added: n_items);
248}
249
250void
251gtk_joined_menu_append_menu (GtkJoinedMenu *self,
252 GMenuModel *model)
253{
254
255 g_return_if_fail (GTK_IS_JOINED_MENU (self));
256 g_return_if_fail (G_MENU_MODEL (model));
257
258 gtk_joined_menu_insert (self, model, index: self->menus->len);
259}
260
261void
262gtk_joined_menu_prepend_menu (GtkJoinedMenu *self,
263 GMenuModel *model)
264{
265 g_return_if_fail (GTK_IS_JOINED_MENU (self));
266 g_return_if_fail (G_MENU_MODEL (model));
267
268 gtk_joined_menu_insert (self, model, index: 0);
269}
270
271void
272gtk_joined_menu_remove_index (GtkJoinedMenu *self,
273 guint index)
274{
275 const Menu *menu;
276 gint n_items;
277 gint offset;
278
279 g_return_if_fail (GTK_IS_JOINED_MENU (self));
280 g_return_if_fail (index < self->menus->len);
281
282 menu = &g_array_index (self->menus, Menu, index);
283
284 offset = gtk_joined_menu_get_offset_at_index (self, index);
285 n_items = g_menu_model_get_n_items (model: menu->model);
286
287 g_array_remove_index (array: self->menus, index_: index);
288
289 g_menu_model_items_changed (G_MENU_MODEL (self), position: offset, removed: n_items, added: 0);
290}
291
292void
293gtk_joined_menu_remove_menu (GtkJoinedMenu *self,
294 GMenuModel *model)
295{
296 g_return_if_fail (GTK_IS_JOINED_MENU (self));
297 g_return_if_fail (G_IS_MENU_MODEL (model));
298
299 for (guint i = 0; i < self->menus->len; i++)
300 {
301 if (g_array_index (self->menus, Menu, i).model == model)
302 {
303 gtk_joined_menu_remove_index (self, index: i);
304 break;
305 }
306 }
307}
308
309guint
310gtk_joined_menu_get_n_joined (GtkJoinedMenu *self)
311{
312 g_return_val_if_fail (GTK_IS_JOINED_MENU (self), 0);
313
314 return self->menus->len;
315}
316

source code of gtk/gtk/gtkjoinedmenu.c