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