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, 0);
289 : :
290 : : /* Side-step GtkListBox to catch row activation; it will not be emitted if
291 : : * this row has an action set (and it should). */
292 : 1 : controller = g_object_new (GTK_TYPE_GESTURE_CLICK,
293 : : "button", GDK_BUTTON_PRIMARY,
294 : : "propagation-phase", GTK_PHASE_BUBBLE,
295 : : "touch-only", FALSE,
296 : : NULL);
297 : 1 : g_signal_connect_object (controller,
298 : : "pressed",
299 : : G_CALLBACK (on_gesture_pressed),
300 : : self, 0);
301 : 1 : g_signal_connect_object (controller,
302 : : "released",
303 : : G_CALLBACK (on_gesture_released),
304 : : self, 0);
305 : 1 : gtk_widget_add_controller (GTK_WIDGET (row), g_steal_pointer (&controller));
306 : :
307 : 1 : controller = g_object_new (GTK_TYPE_EVENT_CONTROLLER_KEY,
308 : : "propagation-phase", GTK_PHASE_BUBBLE,
309 : : NULL);
310 : 1 : g_signal_connect_object (controller,
311 : : "key-pressed",
312 : : G_CALLBACK (on_key_pressed),
313 : : self, 0);
314 : 1 : gtk_widget_add_controller (GTK_WIDGET (row), g_steal_pointer (&controller));
315 : : }
316 : :
317 : : static void
318 : 10 : valent_menu_list_add (ValentMenuList *self,
319 : : int index_)
320 : : {
321 : 20 : g_autoptr (GMenuLinkIter) iter = NULL;
322 : 10 : const char *link_name;
323 : 10 : GMenuModel *link_value;
324 : :
325 [ + - ]: 10 : g_assert (VALENT_IS_MENU_LIST (self));
326 : :
327 : 10 : valent_menu_list_add_row (self, index_);
328 : :
329 : : //
330 : 10 : iter = g_menu_model_iterate_item_links (self->model, index_);
331 : :
332 [ + + ]: 22 : while (g_menu_link_iter_get_next (iter, &link_name, &link_value))
333 : : {
334 [ + + ]: 2 : if (g_strcmp0 (link_name, G_MENU_LINK_SECTION) == 0)
335 : 1 : valent_menu_list_add_section (self, index_, link_value);
336 : :
337 [ + - ]: 1 : else if (g_strcmp0 (link_name, G_MENU_LINK_SUBMENU) == 0)
338 : 1 : valent_menu_list_add_submenu (self, index_, link_value);
339 : :
340 [ - + ]: 14 : g_clear_object (&link_value);
341 : : }
342 : 10 : }
343 : :
344 : : static void
345 : 4 : valent_menu_list_remove (ValentMenuList *self,
346 : : int index_)
347 : : {
348 : 4 : GtkListBoxRow *row = NULL;
349 : 4 : unsigned int position = index_;
350 : :
351 [ + - ]: 4 : g_assert (VALENT_IS_MENU_LIST (self));
352 : :
353 : : /* Offset for "Previous" item */
354 [ - + ]: 4 : if (self->parent != NULL)
355 : 0 : position++;
356 : :
357 : 4 : row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->list), position);
358 : :
359 [ + - ]: 4 : if (row != NULL)
360 : 4 : gtk_list_box_remove (GTK_LIST_BOX (self->list), GTK_WIDGET (row));
361 : 4 : }
362 : :
363 : : static void
364 : 12 : on_items_changed (GMenuModel *model,
365 : : int position,
366 : : int removed,
367 : : int added,
368 : : ValentMenuList *self)
369 : : {
370 [ + - + - : 12 : g_assert (G_IS_MENU_MODEL (model));
+ - - + ]
371 [ - + ]: 12 : g_assert (VALENT_IS_MENU_LIST (self));
372 : :
373 [ + + ]: 16 : while (removed-- > 0)
374 : 4 : valent_menu_list_remove (self, position);
375 : :
376 [ + + ]: 22 : for (int i = 0; i < added; i++)
377 : 10 : valent_menu_list_add (self, position + i);
378 : 12 : }
379 : :
380 : : /*
381 : : * GActions
382 : : */
383 : : static void
384 : 0 : menu_submenu_action (GtkWidget *widget,
385 : : const char *name,
386 : : GVariant *parameter)
387 : : {
388 : 0 : GtkWidget *stack;
389 : 0 : const char *child_name;
390 : :
391 : 0 : stack = gtk_widget_get_ancestor (widget, GTK_TYPE_STACK);
392 : 0 : child_name = g_variant_get_string (parameter, NULL);
393 : 0 : gtk_stack_set_visible_child_name (GTK_STACK (stack), child_name);
394 : 0 : }
395 : :
396 : : /*
397 : : * GObject
398 : : */
399 : : static void
400 : 5 : valent_menu_list_dispose (GObject *object)
401 : : {
402 : 5 : ValentMenuList *self = VALENT_MENU_LIST (object);
403 : :
404 [ + - ]: 5 : if (self->model != NULL)
405 : 5 : g_signal_handlers_disconnect_by_data (self->model, self);
406 : :
407 [ + - ]: 5 : g_clear_pointer (&self->list, gtk_widget_unparent);
408 [ + + ]: 5 : g_clear_object (&self->parent);
409 : :
410 : 5 : G_OBJECT_CLASS (valent_menu_list_parent_class)->dispose (object);
411 : 5 : }
412 : :
413 : : static void
414 : 5 : valent_menu_list_finalize (GObject *object)
415 : : {
416 : 5 : ValentMenuList *self = VALENT_MENU_LIST (object);
417 : :
418 [ + - ]: 5 : g_clear_object (&self->model);
419 : :
420 : 5 : G_OBJECT_CLASS (valent_menu_list_parent_class)->finalize (object);
421 : 5 : }
422 : :
423 : : static void
424 : 0 : valent_menu_list_get_property (GObject *object,
425 : : guint prop_id,
426 : : GValue *value,
427 : : GParamSpec *pspec)
428 : : {
429 : 0 : ValentMenuList *self = VALENT_MENU_LIST (object);
430 : :
431 [ # # # ]: 0 : switch (prop_id)
432 : : {
433 : 0 : case PROP_MENU_MODEL:
434 : 0 : g_value_set_object (value, self->model);
435 : 0 : break;
436 : :
437 : 0 : case PROP_SUBMENU_OF:
438 : 0 : g_value_set_object (value, self->parent);
439 : 0 : break;
440 : :
441 : 0 : default:
442 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
443 : : }
444 : 0 : }
445 : :
446 : : static void
447 : 10 : valent_menu_list_set_property (GObject *object,
448 : : guint prop_id,
449 : : const GValue *value,
450 : : GParamSpec *pspec)
451 : : {
452 : 10 : ValentMenuList *self = VALENT_MENU_LIST (object);
453 : :
454 [ + + - ]: 10 : switch (prop_id)
455 : : {
456 : 5 : case PROP_MENU_MODEL:
457 : 5 : valent_menu_list_set_menu_model (self, g_value_get_object (value));
458 : 5 : break;
459 : :
460 : 5 : case PROP_SUBMENU_OF:
461 : 5 : valent_menu_list_set_submenu_of (self, g_value_get_object (value));
462 : 5 : break;
463 : :
464 : 0 : default:
465 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
466 : : }
467 : 10 : }
468 : :
469 : : static void
470 : 2 : valent_menu_list_class_init (ValentMenuListClass *klass)
471 : : {
472 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
473 : 2 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
474 : :
475 : 2 : object_class->dispose = valent_menu_list_dispose;
476 : 2 : object_class->finalize = valent_menu_list_finalize;
477 : 2 : object_class->get_property = valent_menu_list_get_property;
478 : 2 : object_class->set_property = valent_menu_list_set_property;
479 : :
480 : 2 : gtk_widget_class_install_action (widget_class, "menu.submenu", "s", menu_submenu_action);
481 : 2 : gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_GRID_LAYOUT);
482 : :
483 : : /**
484 : : * ValentMenuList:menu-model:
485 : : *
486 : : * The "model" property holds the `GMenuModel` used to build this list.
487 : : */
488 : 4 : properties [PROP_MENU_MODEL] =
489 : 2 : g_param_spec_object ("menu-model", NULL, NULL,
490 : : G_TYPE_MENU_MODEL,
491 : : (G_PARAM_READWRITE |
492 : : G_PARAM_EXPLICIT_NOTIFY |
493 : : G_PARAM_STATIC_STRINGS));
494 : :
495 : : /**
496 : : * ValentMenuList:submenu-of:
497 : : *
498 : : * The parent `ValentMenuList` this is a submenu for.
499 : : */
500 : 4 : properties [PROP_SUBMENU_OF] =
501 : 2 : g_param_spec_object ("submenu-of", NULL, NULL,
502 : : VALENT_TYPE_MENU_LIST,
503 : : (G_PARAM_READWRITE |
504 : : G_PARAM_CONSTRUCT_ONLY |
505 : : G_PARAM_EXPLICIT_NOTIFY |
506 : : G_PARAM_STATIC_STRINGS));
507 : :
508 : 2 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
509 : 2 : }
510 : :
511 : : static void
512 : 5 : valent_menu_list_init (ValentMenuList *self)
513 : : {
514 : 5 : GtkWidget *placeholder;
515 : :
516 : : /* Item List */
517 : 10 : self->list = g_object_new (GTK_TYPE_LIST_BOX,
518 : 5 : "css-classes", VALENT_STRV_INIT ("boxed-list",
519 : : "boxed-list-placeholder"),
520 : : "hexpand", TRUE,
521 : : "show-separators", TRUE,
522 : : NULL);
523 : 5 : gtk_widget_set_parent (self->list, GTK_WIDGET (self));
524 : 5 : gtk_widget_insert_after (self->list, GTK_WIDGET (self), NULL);
525 : :
526 : : /* Placeholder */
527 : 5 : placeholder = g_object_new (GTK_TYPE_LABEL,
528 : 5 : "label", _("No Actions"),
529 : : "margin-top", 18,
530 : : "margin-bottom", 18,
531 : : NULL);
532 : 5 : gtk_widget_add_css_class (placeholder, "dim-label");
533 : 5 : gtk_list_box_set_placeholder (GTK_LIST_BOX (self->list), placeholder);
534 : 5 : }
535 : :
536 : : /**
537 : : * valent_menu_list_new:
538 : : * @model: (nullable): a `GMenuModel`
539 : : *
540 : : * Create a new `ValentMenuList`.
541 : : *
542 : : * Returns: (transfer full): a `ValentMenuList`
543 : : */
544 : : ValentMenuList *
545 : 4 : valent_menu_list_new (GMenuModel *model)
546 : : {
547 : 4 : return g_object_new (VALENT_TYPE_MENU_LIST,
548 : : "menu-model", model,
549 : : NULL);
550 : : }
551 : :
552 : : /**
553 : : * valent_menu_list_get_menu_model:
554 : : * @self: a `ValentMenuList`
555 : : *
556 : : * Get the `GMenuModel` for @self.
557 : : *
558 : : * Returns: (transfer none): a `GMenuModel`
559 : : */
560 : : GMenuModel *
561 : 0 : valent_menu_list_get_menu_model (ValentMenuList *self)
562 : : {
563 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
564 : :
565 : 0 : return self->model;
566 : : }
567 : :
568 : : /**
569 : : * valent_menu_list_set_menu_model:
570 : : * @self: a `ValentMenuList`
571 : : * @model: (nullable): a `GMenuModel`
572 : : *
573 : : * Set the `GMenuModel` for @self.
574 : : */
575 : : void
576 : 8 : valent_menu_list_set_menu_model (ValentMenuList *list,
577 : : GMenuModel *model)
578 : : {
579 : 8 : unsigned int n_items;
580 : :
581 [ + - ]: 8 : g_return_if_fail (VALENT_IS_MENU_LIST (list));
582 [ + + + - : 8 : g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
+ - - + ]
583 : :
584 [ - + ]: 8 : if (list->model != NULL)
585 : : {
586 : 0 : g_signal_handlers_disconnect_by_data (list->model, list);
587 [ # # ]: 0 : g_clear_object (&list->model);
588 : : }
589 : :
590 [ + + ]: 8 : if (g_set_object (&list->model, model))
591 : : {
592 : 5 : g_signal_connect_object (list->model,
593 : : "items-changed",
594 : : G_CALLBACK (on_items_changed),
595 : : list, 0);
596 : :
597 : 5 : n_items = g_menu_model_get_n_items (model);
598 : 5 : on_items_changed (model, 0, 0, n_items, list);
599 : : }
600 : : }
601 : :
602 : : /**
603 : : * valent_menu_list_get_submenu_of:
604 : : * @self: a `ValentMenuList`
605 : : *
606 : : * Get the parent `ValentMenuList`.
607 : : *
608 : : * Returns: (transfer none) (nullable): a `ValentMenuList`
609 : : */
610 : : ValentMenuList *
611 : 0 : valent_menu_list_get_submenu_of (ValentMenuList *self)
612 : : {
613 [ # # ]: 0 : g_assert (VALENT_IS_MENU_LIST (self));
614 : :
615 : 0 : return self->parent;
616 : : }
617 : :
618 : : /**
619 : : * valent_menu_list_set_submenu_of:
620 : : * @self: a `ValentMenuList`
621 : : * @parent: (nullable): a `GMenuModel`
622 : : *
623 : : * Set the `GMenuModel` for @self.
624 : : */
625 : : void
626 : 5 : valent_menu_list_set_submenu_of (ValentMenuList *self,
627 : : ValentMenuList *parent)
628 : : {
629 : 5 : GtkWidget *row;
630 : 5 : GtkWidget *box;
631 : 5 : GtkWidget *icon;
632 : 5 : GtkWidget *label;
633 : :
634 [ + - ]: 5 : g_assert (VALENT_IS_MENU_LIST (self));
635 [ + + - + ]: 5 : g_assert (parent == NULL || VALENT_IS_MENU_LIST (parent));
636 : :
637 [ + + + - ]: 5 : if (!g_set_object (&self->parent, parent) || parent == NULL)
638 : : return;
639 : :
640 : 1 : row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
641 : : "action-target", g_variant_new_string ("main"),
642 : : "action-name", "menu.submenu",
643 : : "height-request", 56,
644 : : "selectable", FALSE,
645 : : NULL);
646 : 1 : gtk_widget_add_css_class (row, "accent");
647 : :
648 : 1 : box = g_object_new (GTK_TYPE_CENTER_BOX,
649 : : "margin-start", 12,
650 : : "margin-end", 12,
651 : : "margin-bottom", 8,
652 : : "margin-top", 8,
653 : : NULL);
654 : 1 : gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), GTK_WIDGET (box));
655 : :
656 : 1 : icon = g_object_new (GTK_TYPE_IMAGE,
657 : : "icon-name", "go-previous-symbolic",
658 : : "icon-size", GTK_ICON_SIZE_NORMAL,
659 : : NULL);
660 : 1 : gtk_center_box_set_start_widget (GTK_CENTER_BOX (box), icon);
661 : :
662 : 1 : label = g_object_new (GTK_TYPE_LABEL,
663 : 1 : "label", _("Previous"),
664 : : "halign", GTK_ALIGN_CENTER,
665 : : "hexpand", TRUE,
666 : : "valign", GTK_ALIGN_CENTER,
667 : : "vexpand", TRUE,
668 : : NULL);
669 : 1 : gtk_center_box_set_center_widget (GTK_CENTER_BOX (box), label);
670 : 1 : gtk_list_box_insert (GTK_LIST_BOX (self->list), GTK_WIDGET (row), 0);
671 : : }
672 : :
|