Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-menu-list"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n-lib.h>
10 : : #include <gtk/gtk.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-menu-list.h"
14 : :
15 : :
16 : : struct _ValentMenuList
17 : : {
18 : : GtkWidget parent_instance;
19 : :
20 : : GMenuModel *model;
21 : : ValentMenuList *parent;
22 : :
23 : : GtkWidget *list;
24 : : GtkWidget *active_row;
25 : : };
26 : :
27 [ + + + - ]: 87 : G_DEFINE_FINAL_TYPE (ValentMenuList, valent_menu_list, GTK_TYPE_WIDGET)
28 : :
29 : : typedef enum {
30 : : PROP_MENU_MODEL = 1,
31 : : PROP_SUBMENU_OF,
32 : : } ValentMenuListProperty;
33 : :
34 : : static GParamSpec *properties[PROP_SUBMENU_OF + 1] = { NULL, };
35 : :
36 : :
37 : : static void
38 : 0 : valent_menu_list_item_activate (GtkListBoxRow *row)
39 : : {
40 : 0 : GtkWidget *stack = NULL;
41 : 0 : GtkWidget *submenu = NULL;
42 : :
43 [ # # # # : 0 : g_assert (GTK_IS_LIST_BOX_ROW (row));
# # # # ]
44 : :
45 : 0 : submenu = g_object_get_data (G_OBJECT (row), "valent-submenu-item");
46 : :
47 [ # # ]: 0 : if (submenu != NULL)
48 : 0 : stack = gtk_widget_get_ancestor (GTK_WIDGET (submenu), GTK_TYPE_STACK);
49 : :
50 [ # # ]: 0 : if (stack != NULL)
51 : 0 : gtk_stack_set_visible_child (GTK_STACK (stack), submenu);
52 : 0 : }
53 : :
54 : : static void
55 : 0 : on_key_pressed (GtkEventControllerKey *controller,
56 : : unsigned int keyval,
57 : : unsigned int keycode,
58 : : GdkModifierType state,
59 : : ValentMenuList *self)
60 : : {
61 : 0 : GtkWidget *row = NULL;
62 : 0 : static uint32_t activate_keys[] = {
63 : : GDK_KEY_space,
64 : : GDK_KEY_KP_Space,
65 : : GDK_KEY_Return,
66 : : GDK_KEY_ISO_Enter,
67 : : GDK_KEY_KP_Enter,
68 : : };
69 : :
70 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
71 : :
72 [ # # ]: 0 : for (size_t i = 0; i < G_N_ELEMENTS (activate_keys); i++)
73 : : {
74 [ # # ]: 0 : if (activate_keys[i] != keyval)
75 : 0 : continue;
76 : :
77 : 0 : row = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller));
78 : 0 : valent_menu_list_item_activate (GTK_LIST_BOX_ROW (row));
79 : 0 : self->active_row = NULL;
80 : :
81 : 0 : break;
82 : : }
83 : 0 : }
84 : :
85 : : static void
86 : 0 : on_gesture_pressed (GtkGestureClick *gesture,
87 : : unsigned int n_press,
88 : : double x,
89 : : double y,
90 : : ValentMenuList *self)
91 : : {
92 : 0 : GtkWidget *row = NULL;
93 : :
94 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
95 : :
96 : 0 : row = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
97 : :
98 [ # # ]: 0 : if (gtk_widget_is_sensitive (row))
99 : 0 : self->active_row = row;
100 : : else
101 : 0 : self->active_row = NULL;
102 : 0 : }
103 : :
104 : : static void
105 : 0 : on_gesture_released (GtkGestureClick *gesture,
106 : : unsigned int n_press,
107 : : double x,
108 : : double y,
109 : : ValentMenuList *self)
110 : : {
111 : 0 : GtkWidget *row = NULL;
112 : :
113 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
114 : :
115 : 0 : row = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
116 : :
117 [ # # ]: 0 : if (self->active_row == row)
118 : 0 : valent_menu_list_item_activate (GTK_LIST_BOX_ROW (row));
119 : :
120 : 0 : self->active_row = NULL;
121 : 0 : }
122 : :
123 : : static void
124 : 1 : on_submenu_removed (GtkListBoxRow *row,
125 : : ValentMenuList *self)
126 : : {
127 : 1 : GtkWidget *stack;
128 : 1 : GtkWidget *main;
129 : 1 : GtkWidget *submenu;
130 : :
131 [ + - + - : 1 : g_assert (GTK_IS_WIDGET (row));
+ - - + ]
132 [ + - ]: 1 : g_assert (VALENT_MENU_LIST (self));
133 : :
134 : 1 : submenu = g_object_get_data (G_OBJECT (row), "valent-submenu-item");
135 [ + - + - : 1 : g_return_if_fail (GTK_IS_WIDGET (submenu));
+ - + - ]
136 : :
137 : : /* If the parent is being destroyed, the stack or page may not exist */
138 [ - + ]: 1 : if ((stack = gtk_widget_get_ancestor (submenu, GTK_TYPE_STACK)) == NULL)
139 : : return;
140 : :
141 [ # # ]: 0 : if ((main = gtk_stack_get_child_by_name (GTK_STACK (stack), "main")) == NULL)
142 : : return;
143 : :
144 [ # # ]: 0 : if (gtk_stack_get_visible_child (GTK_STACK (stack)) == submenu)
145 : 0 : gtk_stack_set_visible_child (GTK_STACK (stack), main);
146 : :
147 : 0 : gtk_stack_remove (GTK_STACK (stack), submenu);
148 : : }
149 : :
150 : : /*
151 : : * GMenuModel Callbacks
152 : : */
153 : : static void
154 : 10 : valent_menu_list_add_row (ValentMenuList *self,
155 : : int index_)
156 : : {
157 : 10 : GtkWidget *row;
158 : 10 : GtkWidget *row_icon;
159 : 10 : g_autofree char *hidden_when = NULL;
160 : 10 : g_autofree char *label = NULL;
161 : 10 : g_autofree char *action_name = NULL;
162 : 10 : g_autoptr (GVariant) action_target = NULL;
163 [ - + ]: 10 : g_autoptr (GVariant) vicon = NULL;
164 [ + + ]: 10 : g_autoptr (GIcon) gicon = NULL;
165 : 10 : unsigned int position = index_;
166 : :
167 : : /* Offset for "Previous" item */
168 [ + + ]: 10 : if (self->parent != NULL)
169 : 1 : position++;
170 : :
171 : : /* Row Label */
172 [ - + ]: 10 : if (!g_menu_model_get_item_attribute (self->model, index_,
173 : : "label", "s", &label))
174 : : {
175 : 0 : g_warning ("%s: menu item without label at %d", G_STRFUNC, index_);
176 : 0 : return;
177 : : }
178 : :
179 : : /* GAction */
180 [ + + ]: 10 : if (g_menu_model_get_item_attribute (self->model, index_,
181 : : "action", "s", &action_name))
182 : : {
183 : 8 : action_target = g_menu_model_get_item_attribute_value (self->model,
184 : : index_,
185 : : "target",
186 : : NULL);
187 : : }
188 : :
189 : : /* Icon */
190 : 10 : vicon = g_menu_model_get_item_attribute_value (self->model,
191 : : index_,
192 : : "icon",
193 : : NULL);
194 : :
195 [ + + ]: 10 : if (vicon != NULL)
196 : 8 : gicon = g_icon_deserialize (vicon);
197 : :
198 : 10 : row = g_object_new (ADW_TYPE_ACTION_ROW,
199 : : "action-target", action_target,
200 : : "action-name", action_name,
201 : : "activatable", TRUE,
202 : : "selectable", FALSE,
203 : : "title", label,
204 : : "height-request", 56,
205 : : NULL);
206 : :
207 : 10 : row_icon = g_object_new (GTK_TYPE_IMAGE,
208 : : "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION,
209 : : "gicon", gicon,
210 : : "icon-size", GTK_ICON_SIZE_NORMAL,
211 : : NULL);
212 : 10 : adw_action_row_add_prefix (ADW_ACTION_ROW (row), row_icon);
213 : :
214 : 10 : gtk_list_box_insert (GTK_LIST_BOX (self->list), GTK_WIDGET (row), position);
215 : :
216 : : /* NOTE: this must be done after the row is added to the list, otherwise it
217 : : * may be in a "realized" state and fail an assertion check.
218 : : */
219 [ + + ]: 10 : if (g_menu_model_get_item_attribute (self->model, index_,
220 : : "hidden-when", "s", &hidden_when))
221 : : {
222 [ + - ]: 8 : if (g_strcmp0 (hidden_when, "action-disabled") == 0)
223 : 8 : g_object_bind_property (G_OBJECT (row), "sensitive",
224 : : G_OBJECT (row), "visible",
225 : : G_BINDING_SYNC_CREATE);
226 : : }
227 : : }
228 : :
229 : : static void
230 : 1 : valent_menu_list_add_section (ValentMenuList *self,
231 : : int index_,
232 : : GMenuModel *model)
233 : : {
234 : 1 : ValentMenuList *section;
235 : 1 : unsigned int position = index_;
236 : :
237 [ - + ]: 1 : g_assert (VALENT_IS_MENU_LIST (self));
238 [ + - + - : 1 : g_assert (G_IS_MENU_MODEL (model));
+ - + - ]
239 : :
240 : : /* Offset for "Previous" item */
241 [ - + ]: 1 : if (self->parent != NULL)
242 : 0 : position++;
243 : :
244 : 1 : section = valent_menu_list_new (model);
245 : 1 : gtk_list_box_insert (GTK_LIST_BOX (self->list), GTK_WIDGET (section), position);
246 : 1 : }
247 : :
248 : : static void
249 : 1 : valent_menu_list_add_submenu (ValentMenuList *self,
250 : : int index_,
251 : : GMenuModel *model)
252 : : {
253 : 1 : GtkWidget *stack;
254 : 1 : ValentMenuList *submenu;
255 : 1 : GtkListBoxRow *row;
256 : 1 : GtkWidget *arrow;
257 : 1 : GtkEventController *controller;
258 : 1 : unsigned int position = index_;
259 : :
260 [ - + ]: 1 : g_assert (VALENT_IS_MENU_LIST (self));
261 [ + - + - : 1 : g_assert (G_IS_MENU_MODEL (model));
+ - + - ]
262 : :
263 : : /* Offset for "Previous" item */
264 [ - + ]: 1 : if (self->parent != NULL)
265 : 0 : position++;
266 : :
267 : 1 : row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->list), position);
268 [ + - ]: 1 : g_return_if_fail (ADW_IS_ACTION_ROW (row));
269 : :
270 : : /* Add an arrow the the row */
271 : 1 : arrow = gtk_image_new_from_icon_name ("go-next-symbolic");
272 : 1 : gtk_widget_add_css_class (arrow, "dim-label");
273 : 1 : adw_action_row_add_suffix (ADW_ACTION_ROW (row), arrow);
274 : :
275 : : /* Add a submenu to the stack, tied to the lifetime of the row */
276 : 1 : submenu = g_object_new (VALENT_TYPE_MENU_LIST,
277 : : "menu-model", model,
278 : : "submenu-of", self,
279 : : NULL);
280 : 1 : g_object_set_data (G_OBJECT (row), "valent-submenu-item", submenu);
281 : :
282 : 1 : stack = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_STACK);
283 : 1 : gtk_stack_add_child (GTK_STACK (stack), GTK_WIDGET (submenu));
284 : :
285 : 1 : g_signal_connect_object (row,
286 : : "destroy",
287 : : G_CALLBACK (on_submenu_removed),
288 : : self,
289 : : G_CONNECT_DEFAULT);
290 : :
291 : : /* Side-step GtkListBox to catch row activation; it will not be emitted if
292 : : * this row has an action set (and it should). */
293 : 1 : controller = g_object_new (GTK_TYPE_GESTURE_CLICK,
294 : : "button", GDK_BUTTON_PRIMARY,
295 : : "propagation-phase", GTK_PHASE_BUBBLE,
296 : : "touch-only", FALSE,
297 : : NULL);
298 : 1 : g_signal_connect_object (controller,
299 : : "pressed",
300 : : G_CALLBACK (on_gesture_pressed),
301 : : self,
302 : : G_CONNECT_DEFAULT);
303 : 1 : g_signal_connect_object (controller,
304 : : "released",
305 : : G_CALLBACK (on_gesture_released),
306 : : self,
307 : : G_CONNECT_DEFAULT);
308 : 1 : gtk_widget_add_controller (GTK_WIDGET (row), g_steal_pointer (&controller));
309 : :
310 : 1 : controller = g_object_new (GTK_TYPE_EVENT_CONTROLLER_KEY,
311 : : "propagation-phase", GTK_PHASE_BUBBLE,
312 : : NULL);
313 : 1 : g_signal_connect_object (controller,
314 : : "key-pressed",
315 : : G_CALLBACK (on_key_pressed),
316 : : self,
317 : : G_CONNECT_DEFAULT);
318 : 1 : gtk_widget_add_controller (GTK_WIDGET (row), g_steal_pointer (&controller));
319 : : }
320 : :
321 : : static void
322 : 10 : valent_menu_list_add (ValentMenuList *self,
323 : : int index_)
324 : : {
325 : 20 : g_autoptr (GMenuLinkIter) iter = NULL;
326 : 10 : const char *link_name;
327 : 10 : GMenuModel *link_value;
328 : :
329 [ - + ]: 10 : g_assert (VALENT_IS_MENU_LIST (self));
330 : :
331 : 10 : valent_menu_list_add_row (self, index_);
332 : :
333 : : //
334 : 10 : iter = g_menu_model_iterate_item_links (self->model, index_);
335 : :
336 [ + + ]: 22 : while (g_menu_link_iter_get_next (iter, &link_name, &link_value))
337 : : {
338 [ + + ]: 2 : if (g_strcmp0 (link_name, G_MENU_LINK_SECTION) == 0)
339 : 1 : valent_menu_list_add_section (self, index_, link_value);
340 : :
341 [ + - ]: 1 : else if (g_strcmp0 (link_name, G_MENU_LINK_SUBMENU) == 0)
342 : 1 : valent_menu_list_add_submenu (self, index_, link_value);
343 : :
344 [ - + ]: 14 : g_clear_object (&link_value);
345 : : }
346 : 10 : }
347 : :
348 : : static void
349 : 4 : valent_menu_list_remove (ValentMenuList *self,
350 : : int index_)
351 : : {
352 : 4 : GtkListBoxRow *row = NULL;
353 : 4 : unsigned int position = index_;
354 : :
355 [ - + ]: 4 : g_assert (VALENT_IS_MENU_LIST (self));
356 : :
357 : : /* Offset for "Previous" item */
358 [ - + ]: 4 : if (self->parent != NULL)
359 : 0 : position++;
360 : :
361 : 4 : row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->list), position);
362 : :
363 [ + - ]: 4 : if (row != NULL)
364 : 4 : gtk_list_box_remove (GTK_LIST_BOX (self->list), GTK_WIDGET (row));
365 : 4 : }
366 : :
367 : : static void
368 : 12 : on_items_changed (GMenuModel *model,
369 : : int position,
370 : : int removed,
371 : : int added,
372 : : ValentMenuList *self)
373 : : {
374 [ + - + - : 12 : g_assert (G_IS_MENU_MODEL (model));
+ - - + ]
375 [ - + ]: 12 : g_assert (VALENT_IS_MENU_LIST (self));
376 : :
377 [ + + ]: 16 : while (removed-- > 0)
378 : 4 : valent_menu_list_remove (self, position);
379 : :
380 [ + + ]: 22 : for (int i = 0; i < added; i++)
381 : 10 : valent_menu_list_add (self, position + i);
382 : 12 : }
383 : :
384 : : /*
385 : : * GActions
386 : : */
387 : : static void
388 : 0 : menu_submenu_action (GtkWidget *widget,
389 : : const char *name,
390 : : GVariant *parameter)
391 : : {
392 : 0 : GtkWidget *stack;
393 : 0 : const char *child_name;
394 : :
395 : 0 : stack = gtk_widget_get_ancestor (widget, GTK_TYPE_STACK);
396 : 0 : child_name = g_variant_get_string (parameter, NULL);
397 : 0 : gtk_stack_set_visible_child_name (GTK_STACK (stack), child_name);
398 : 0 : }
399 : :
400 : : /*
401 : : * GObject
402 : : */
403 : : static void
404 : 5 : valent_menu_list_dispose (GObject *object)
405 : : {
406 : 5 : ValentMenuList *self = VALENT_MENU_LIST (object);
407 : :
408 [ + - ]: 5 : if (self->model != NULL)
409 : 5 : g_signal_handlers_disconnect_by_data (self->model, self);
410 : :
411 [ + - ]: 5 : g_clear_pointer (&self->list, gtk_widget_unparent);
412 [ + + ]: 5 : g_clear_object (&self->parent);
413 : :
414 : 5 : G_OBJECT_CLASS (valent_menu_list_parent_class)->dispose (object);
415 : 5 : }
416 : :
417 : : static void
418 : 5 : valent_menu_list_finalize (GObject *object)
419 : : {
420 : 5 : ValentMenuList *self = VALENT_MENU_LIST (object);
421 : :
422 [ + - ]: 5 : g_clear_object (&self->model);
423 : :
424 : 5 : G_OBJECT_CLASS (valent_menu_list_parent_class)->finalize (object);
425 : 5 : }
426 : :
427 : : static void
428 : 0 : valent_menu_list_get_property (GObject *object,
429 : : guint prop_id,
430 : : GValue *value,
431 : : GParamSpec *pspec)
432 : : {
433 : 0 : ValentMenuList *self = VALENT_MENU_LIST (object);
434 : :
435 [ # # # ]: 0 : switch (prop_id)
436 : : {
437 : 0 : case PROP_MENU_MODEL:
438 : 0 : g_value_set_object (value, self->model);
439 : 0 : break;
440 : :
441 : 0 : case PROP_SUBMENU_OF:
442 : 0 : g_value_set_object (value, self->parent);
443 : 0 : break;
444 : :
445 : 0 : default:
446 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
447 : : }
448 : 0 : }
449 : :
450 : : static void
451 : 10 : valent_menu_list_set_property (GObject *object,
452 : : guint prop_id,
453 : : const GValue *value,
454 : : GParamSpec *pspec)
455 : : {
456 : 10 : ValentMenuList *self = VALENT_MENU_LIST (object);
457 : :
458 [ + + - ]: 10 : switch (prop_id)
459 : : {
460 : 5 : case PROP_MENU_MODEL:
461 : 5 : valent_menu_list_set_menu_model (self, g_value_get_object (value));
462 : 5 : break;
463 : :
464 : 5 : case PROP_SUBMENU_OF:
465 : 5 : valent_menu_list_set_submenu_of (self, g_value_get_object (value));
466 : 5 : break;
467 : :
468 : 0 : default:
469 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
470 : : }
471 : 10 : }
472 : :
473 : : static void
474 : 2 : valent_menu_list_class_init (ValentMenuListClass *klass)
475 : : {
476 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
477 : 2 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
478 : :
479 : 2 : object_class->dispose = valent_menu_list_dispose;
480 : 2 : object_class->finalize = valent_menu_list_finalize;
481 : 2 : object_class->get_property = valent_menu_list_get_property;
482 : 2 : object_class->set_property = valent_menu_list_set_property;
483 : :
484 : 2 : gtk_widget_class_install_action (widget_class, "menu.submenu", "s", menu_submenu_action);
485 : 2 : gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_GRID_LAYOUT);
486 : :
487 : : /**
488 : : * ValentMenuList:menu-model:
489 : : *
490 : : * The "model" property holds the `GMenuModel` used to build this list.
491 : : */
492 : 4 : properties [PROP_MENU_MODEL] =
493 : 2 : g_param_spec_object ("menu-model", NULL, NULL,
494 : : G_TYPE_MENU_MODEL,
495 : : (G_PARAM_READWRITE |
496 : : G_PARAM_EXPLICIT_NOTIFY |
497 : : G_PARAM_STATIC_STRINGS));
498 : :
499 : : /**
500 : : * ValentMenuList:submenu-of:
501 : : *
502 : : * The parent `ValentMenuList` this is a submenu for.
503 : : */
504 : 4 : properties [PROP_SUBMENU_OF] =
505 : 2 : g_param_spec_object ("submenu-of", NULL, NULL,
506 : : VALENT_TYPE_MENU_LIST,
507 : : (G_PARAM_READWRITE |
508 : : G_PARAM_CONSTRUCT_ONLY |
509 : : G_PARAM_EXPLICIT_NOTIFY |
510 : : G_PARAM_STATIC_STRINGS));
511 : :
512 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
513 : 2 : }
514 : :
515 : : static void
516 : 5 : valent_menu_list_init (ValentMenuList *self)
517 : : {
518 : 5 : GtkWidget *placeholder;
519 : :
520 : : /* Item List */
521 : 10 : self->list = g_object_new (GTK_TYPE_LIST_BOX,
522 : 5 : "css-classes", VALENT_STRV_INIT ("boxed-list",
523 : : "boxed-list-placeholder"),
524 : : "hexpand", TRUE,
525 : : "show-separators", TRUE,
526 : : NULL);
527 : 5 : gtk_widget_set_parent (self->list, GTK_WIDGET (self));
528 : 5 : gtk_widget_insert_after (self->list, GTK_WIDGET (self), NULL);
529 : :
530 : : /* Placeholder */
531 : 5 : placeholder = g_object_new (GTK_TYPE_LABEL,
532 : 5 : "label", _("No Actions"),
533 : : "margin-top", 18,
534 : : "margin-bottom", 18,
535 : : NULL);
536 : 5 : gtk_widget_add_css_class (placeholder, "dim-label");
537 : 5 : gtk_list_box_set_placeholder (GTK_LIST_BOX (self->list), placeholder);
538 : 5 : }
539 : :
540 : : /**
541 : : * valent_menu_list_new:
542 : : * @model: (nullable): a `GMenuModel`
543 : : *
544 : : * Create a new `ValentMenuList`.
545 : : *
546 : : * Returns: (transfer full): a `ValentMenuList`
547 : : */
548 : : ValentMenuList *
549 : 4 : valent_menu_list_new (GMenuModel *model)
550 : : {
551 : 4 : return g_object_new (VALENT_TYPE_MENU_LIST,
552 : : "menu-model", model,
553 : : NULL);
554 : : }
555 : :
556 : : /**
557 : : * valent_menu_list_get_menu_model:
558 : : * @self: a `ValentMenuList`
559 : : *
560 : : * Get the `GMenuModel` for @self.
561 : : *
562 : : * Returns: (transfer none): a `GMenuModel`
563 : : */
564 : : GMenuModel *
565 : 0 : valent_menu_list_get_menu_model (ValentMenuList *self)
566 : : {
567 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
568 : :
569 : 0 : return self->model;
570 : : }
571 : :
572 : : /**
573 : : * valent_menu_list_set_menu_model:
574 : : * @self: a `ValentMenuList`
575 : : * @model: (nullable): a `GMenuModel`
576 : : *
577 : : * Set the `GMenuModel` for @self.
578 : : */
579 : : void
580 : 8 : valent_menu_list_set_menu_model (ValentMenuList *list,
581 : : GMenuModel *model)
582 : : {
583 : 8 : unsigned int n_items;
584 : :
585 [ - + ]: 8 : g_return_if_fail (VALENT_IS_MENU_LIST (list));
586 [ + + + - : 8 : g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
+ - + - ]
587 : :
588 [ - + ]: 8 : if (list->model != NULL)
589 : : {
590 : 0 : g_signal_handlers_disconnect_by_data (list->model, list);
591 [ # # ]: 0 : g_clear_object (&list->model);
592 : : }
593 : :
594 [ + + ]: 8 : if (g_set_object (&list->model, model))
595 : : {
596 : 5 : g_signal_connect_object (list->model,
597 : : "items-changed",
598 : : G_CALLBACK (on_items_changed),
599 : : list,
600 : : G_CONNECT_DEFAULT);
601 : :
602 : 5 : n_items = g_menu_model_get_n_items (model);
603 : 5 : on_items_changed (model, 0, 0, n_items, list);
604 : : }
605 : : }
606 : :
607 : : /**
608 : : * valent_menu_list_get_submenu_of:
609 : : * @self: a `ValentMenuList`
610 : : *
611 : : * Get the parent `ValentMenuList`.
612 : : *
613 : : * Returns: (transfer none) (nullable): a `ValentMenuList`
614 : : */
615 : : ValentMenuList *
616 : 0 : valent_menu_list_get_submenu_of (ValentMenuList *self)
617 : : {
618 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
619 : :
620 : 0 : return self->parent;
621 : : }
622 : :
623 : : /**
624 : : * valent_menu_list_set_submenu_of:
625 : : * @self: a `ValentMenuList`
626 : : * @parent: (nullable): a `GMenuModel`
627 : : *
628 : : * Set the `GMenuModel` for @self.
629 : : */
630 : : void
631 : 5 : valent_menu_list_set_submenu_of (ValentMenuList *self,
632 : : ValentMenuList *parent)
633 : : {
634 : 5 : GtkWidget *row;
635 : 5 : GtkWidget *box;
636 : 5 : GtkWidget *icon;
637 : 5 : GtkWidget *label;
638 : :
639 [ - + ]: 5 : g_assert (VALENT_IS_MENU_LIST (self));
640 [ + + + - ]: 5 : g_assert (parent == NULL || VALENT_IS_MENU_LIST (parent));
641 : :
642 [ + + + - ]: 5 : if (!g_set_object (&self->parent, parent) || parent == NULL)
643 : : return;
644 : :
645 : 1 : row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
646 : : "action-target", g_variant_new_string ("main"),
647 : : "action-name", "menu.submenu",
648 : : "height-request", 56,
649 : : "selectable", FALSE,
650 : : NULL);
651 : 1 : gtk_widget_add_css_class (row, "accent");
652 : :
653 : 1 : box = g_object_new (GTK_TYPE_CENTER_BOX,
654 : : "margin-start", 12,
655 : : "margin-end", 12,
656 : : "margin-bottom", 8,
657 : : "margin-top", 8,
658 : : NULL);
659 : 1 : gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), GTK_WIDGET (box));
660 : :
661 : 1 : icon = g_object_new (GTK_TYPE_IMAGE,
662 : : "icon-name", "go-previous-symbolic",
663 : : "icon-size", GTK_ICON_SIZE_NORMAL,
664 : : NULL);
665 : 1 : gtk_center_box_set_start_widget (GTK_CENTER_BOX (box), icon);
666 : :
667 : 1 : label = g_object_new (GTK_TYPE_LABEL,
668 : 1 : "label", _("Previous"),
669 : : "halign", GTK_ALIGN_CENTER,
670 : : "hexpand", TRUE,
671 : : "valign", GTK_ALIGN_CENTER,
672 : : "vexpand", TRUE,
673 : : NULL);
674 : 1 : gtk_center_box_set_center_widget (GTK_CENTER_BOX (box), label);
675 : 1 : gtk_list_box_insert (GTK_LIST_BOX (self->list), GTK_WIDGET (row), 0);
676 : : }
677 : :
|