• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2011 Canonical Ltd.
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, but
10  * 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  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19 
20 #include "config.h"
21 
22 #include "gmenu.h"
23 
24 #include "gaction.h"
25 #include <string.h>
26 
27 #include "gicon.h"
28 
29 /**
30  * SECTION:gmenu
31  * @title: GMenu
32  * @short_description: A simple implementation of GMenuModel
33  * @include: gio/gio.h
34  *
35  * #GMenu is a simple implementation of #GMenuModel.
36  * You populate a #GMenu by adding #GMenuItem instances to it.
37  *
38  * There are some convenience functions to allow you to directly
39  * add items (avoiding #GMenuItem) for the common cases. To add
40  * a regular item, use g_menu_insert(). To add a section, use
41  * g_menu_insert_section(). To add a submenu, use
42  * g_menu_insert_submenu().
43  */
44 
45 /**
46  * GMenu:
47  *
48  * #GMenu is an opaque structure type.  You must access it using the
49  * functions below.
50  *
51  * Since: 2.32
52  */
53 
54 /**
55  * GMenuItem:
56  *
57  * #GMenuItem is an opaque structure type.  You must access it using the
58  * functions below.
59  *
60  * Since: 2.32
61  */
62 
63 struct _GMenuItem
64 {
65   GObject parent_instance;
66 
67   GHashTable *attributes;
68   GHashTable *links;
69   gboolean    cow;
70 };
71 
72 typedef GObjectClass GMenuItemClass;
73 
74 struct _GMenu
75 {
76   GMenuModel parent_instance;
77 
78   GArray   *items;
79   gboolean  mutable;
80 };
81 
82 typedef GMenuModelClass GMenuClass;
83 
84 G_DEFINE_TYPE (GMenu, g_menu, G_TYPE_MENU_MODEL)
85 G_DEFINE_TYPE (GMenuItem, g_menu_item, G_TYPE_OBJECT)
86 
87 struct item
88 {
89   GHashTable *attributes;
90   GHashTable *links;
91 };
92 
93 static gboolean
g_menu_is_mutable(GMenuModel * model)94 g_menu_is_mutable (GMenuModel *model)
95 {
96   GMenu *menu = G_MENU (model);
97 
98   return menu->mutable;
99 }
100 
101 static gint
g_menu_get_n_items(GMenuModel * model)102 g_menu_get_n_items (GMenuModel *model)
103 {
104   GMenu *menu = G_MENU (model);
105 
106   return menu->items->len;
107 }
108 
109 static void
g_menu_get_item_attributes(GMenuModel * model,gint position,GHashTable ** table)110 g_menu_get_item_attributes (GMenuModel  *model,
111                             gint         position,
112                             GHashTable **table)
113 {
114   GMenu *menu = G_MENU (model);
115 
116   *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes);
117 }
118 
119 static void
g_menu_get_item_links(GMenuModel * model,gint position,GHashTable ** table)120 g_menu_get_item_links (GMenuModel  *model,
121                        gint         position,
122                        GHashTable **table)
123 {
124   GMenu *menu = G_MENU (model);
125 
126   *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links);
127 }
128 
129 /**
130  * g_menu_insert_item:
131  * @menu: a #GMenu
132  * @position: the position at which to insert the item
133  * @item: the #GMenuItem to insert
134  *
135  * Inserts @item into @menu.
136  *
137  * The "insertion" is actually done by copying all of the attribute and
138  * link values of @item and using them to form a new item within @menu.
139  * As such, @item itself is not really inserted, but rather, a menu item
140  * that is exactly the same as the one presently described by @item.
141  *
142  * This means that @item is essentially useless after the insertion
143  * occurs.  Any changes you make to it are ignored unless it is inserted
144  * again (at which point its updated values will be copied).
145  *
146  * You should probably just free @item once you're done.
147  *
148  * There are many convenience functions to take care of common cases.
149  * See g_menu_insert(), g_menu_insert_section() and
150  * g_menu_insert_submenu() as well as "prepend" and "append" variants of
151  * each of these functions.
152  *
153  * Since: 2.32
154  */
155 void
g_menu_insert_item(GMenu * menu,gint position,GMenuItem * item)156 g_menu_insert_item (GMenu     *menu,
157                     gint       position,
158                     GMenuItem *item)
159 {
160   struct item new_item;
161 
162   g_return_if_fail (G_IS_MENU (menu));
163   g_return_if_fail (G_IS_MENU_ITEM (item));
164 
165   if (position < 0 || (guint) position > menu->items->len)
166     position = menu->items->len;
167 
168   new_item.attributes = g_hash_table_ref (item->attributes);
169   new_item.links = g_hash_table_ref (item->links);
170   item->cow = TRUE;
171 
172   g_array_insert_val (menu->items, position, new_item);
173   g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1);
174 }
175 
176 /**
177  * g_menu_prepend_item:
178  * @menu: a #GMenu
179  * @item: a #GMenuItem to prepend
180  *
181  * Prepends @item to the start of @menu.
182  *
183  * See g_menu_insert_item() for more information.
184  *
185  * Since: 2.32
186  */
187 void
g_menu_prepend_item(GMenu * menu,GMenuItem * item)188 g_menu_prepend_item (GMenu     *menu,
189                      GMenuItem *item)
190 {
191   g_menu_insert_item (menu, 0, item);
192 }
193 
194 /**
195  * g_menu_append_item:
196  * @menu: a #GMenu
197  * @item: a #GMenuItem to append
198  *
199  * Appends @item to the end of @menu.
200  *
201  * See g_menu_insert_item() for more information.
202  *
203  * Since: 2.32
204  */
205 void
g_menu_append_item(GMenu * menu,GMenuItem * item)206 g_menu_append_item (GMenu     *menu,
207                     GMenuItem *item)
208 {
209   g_menu_insert_item (menu, -1, item);
210 }
211 
212 /**
213  * g_menu_freeze:
214  * @menu: a #GMenu
215  *
216  * Marks @menu as frozen.
217  *
218  * After the menu is frozen, it is an error to attempt to make any
219  * changes to it.  In effect this means that the #GMenu API must no
220  * longer be used.
221  *
222  * This function causes g_menu_model_is_mutable() to begin returning
223  * %FALSE, which has some positive performance implications.
224  *
225  * Since: 2.32
226  */
227 void
g_menu_freeze(GMenu * menu)228 g_menu_freeze (GMenu *menu)
229 {
230   g_return_if_fail (G_IS_MENU (menu));
231 
232   menu->mutable = FALSE;
233 }
234 
235 /**
236  * g_menu_new:
237  *
238  * Creates a new #GMenu.
239  *
240  * The new menu has no items.
241  *
242  * Returns: a new #GMenu
243  *
244  * Since: 2.32
245  */
246 GMenu *
g_menu_new(void)247 g_menu_new (void)
248 {
249   return g_object_new (G_TYPE_MENU, NULL);
250 }
251 
252 /**
253  * g_menu_insert:
254  * @menu: a #GMenu
255  * @position: the position at which to insert the item
256  * @label: (nullable): the section label, or %NULL
257  * @detailed_action: (nullable): the detailed action string, or %NULL
258  *
259  * Convenience function for inserting a normal menu item into @menu.
260  * Combine g_menu_item_new() and g_menu_insert_item() for a more flexible
261  * alternative.
262  *
263  * Since: 2.32
264  */
265 void
g_menu_insert(GMenu * menu,gint position,const gchar * label,const gchar * detailed_action)266 g_menu_insert (GMenu       *menu,
267                gint         position,
268                const gchar *label,
269                const gchar *detailed_action)
270 {
271   GMenuItem *menu_item;
272 
273   menu_item = g_menu_item_new (label, detailed_action);
274   g_menu_insert_item (menu, position, menu_item);
275   g_object_unref (menu_item);
276 }
277 
278 /**
279  * g_menu_prepend:
280  * @menu: a #GMenu
281  * @label: (nullable): the section label, or %NULL
282  * @detailed_action: (nullable): the detailed action string, or %NULL
283  *
284  * Convenience function for prepending a normal menu item to the start
285  * of @menu.  Combine g_menu_item_new() and g_menu_insert_item() for a more
286  * flexible alternative.
287  *
288  * Since: 2.32
289  */
290 void
g_menu_prepend(GMenu * menu,const gchar * label,const gchar * detailed_action)291 g_menu_prepend (GMenu       *menu,
292                 const gchar *label,
293                 const gchar *detailed_action)
294 {
295   g_menu_insert (menu, 0, label, detailed_action);
296 }
297 
298 /**
299  * g_menu_append:
300  * @menu: a #GMenu
301  * @label: (nullable): the section label, or %NULL
302  * @detailed_action: (nullable): the detailed action string, or %NULL
303  *
304  * Convenience function for appending a normal menu item to the end of
305  * @menu.  Combine g_menu_item_new() and g_menu_insert_item() for a more
306  * flexible alternative.
307  *
308  * Since: 2.32
309  */
310 void
g_menu_append(GMenu * menu,const gchar * label,const gchar * detailed_action)311 g_menu_append (GMenu       *menu,
312                const gchar *label,
313                const gchar *detailed_action)
314 {
315   g_menu_insert (menu, -1, label, detailed_action);
316 }
317 
318 /**
319  * g_menu_insert_section:
320  * @menu: a #GMenu
321  * @position: the position at which to insert the item
322  * @label: (nullable): the section label, or %NULL
323  * @section: a #GMenuModel with the items of the section
324  *
325  * Convenience function for inserting a section menu item into @menu.
326  * Combine g_menu_item_new_section() and g_menu_insert_item() for a more
327  * flexible alternative.
328  *
329  * Since: 2.32
330  */
331 void
g_menu_insert_section(GMenu * menu,gint position,const gchar * label,GMenuModel * section)332 g_menu_insert_section (GMenu       *menu,
333                        gint         position,
334                        const gchar *label,
335                        GMenuModel  *section)
336 {
337   GMenuItem *menu_item;
338 
339   menu_item = g_menu_item_new_section (label, section);
340   g_menu_insert_item (menu, position, menu_item);
341   g_object_unref (menu_item);
342 }
343 
344 
345 /**
346  * g_menu_prepend_section:
347  * @menu: a #GMenu
348  * @label: (nullable): the section label, or %NULL
349  * @section: a #GMenuModel with the items of the section
350  *
351  * Convenience function for prepending a section menu item to the start
352  * of @menu.  Combine g_menu_item_new_section() and g_menu_insert_item() for
353  * a more flexible alternative.
354  *
355  * Since: 2.32
356  */
357 void
g_menu_prepend_section(GMenu * menu,const gchar * label,GMenuModel * section)358 g_menu_prepend_section (GMenu       *menu,
359                         const gchar *label,
360                         GMenuModel  *section)
361 {
362   g_menu_insert_section (menu, 0, label, section);
363 }
364 
365 /**
366  * g_menu_append_section:
367  * @menu: a #GMenu
368  * @label: (nullable): the section label, or %NULL
369  * @section: a #GMenuModel with the items of the section
370  *
371  * Convenience function for appending a section menu item to the end of
372  * @menu.  Combine g_menu_item_new_section() and g_menu_insert_item() for a
373  * more flexible alternative.
374  *
375  * Since: 2.32
376  */
377 void
g_menu_append_section(GMenu * menu,const gchar * label,GMenuModel * section)378 g_menu_append_section (GMenu       *menu,
379                        const gchar *label,
380                        GMenuModel  *section)
381 {
382   g_menu_insert_section (menu, -1, label, section);
383 }
384 
385 /**
386  * g_menu_insert_submenu:
387  * @menu: a #GMenu
388  * @position: the position at which to insert the item
389  * @label: (nullable): the section label, or %NULL
390  * @submenu: a #GMenuModel with the items of the submenu
391  *
392  * Convenience function for inserting a submenu menu item into @menu.
393  * Combine g_menu_item_new_submenu() and g_menu_insert_item() for a more
394  * flexible alternative.
395  *
396  * Since: 2.32
397  */
398 void
g_menu_insert_submenu(GMenu * menu,gint position,const gchar * label,GMenuModel * submenu)399 g_menu_insert_submenu (GMenu       *menu,
400                        gint         position,
401                        const gchar *label,
402                        GMenuModel  *submenu)
403 {
404   GMenuItem *menu_item;
405 
406   menu_item = g_menu_item_new_submenu (label, submenu);
407   g_menu_insert_item (menu, position, menu_item);
408   g_object_unref (menu_item);
409 }
410 
411 /**
412  * g_menu_prepend_submenu:
413  * @menu: a #GMenu
414  * @label: (nullable): the section label, or %NULL
415  * @submenu: a #GMenuModel with the items of the submenu
416  *
417  * Convenience function for prepending a submenu menu item to the start
418  * of @menu.  Combine g_menu_item_new_submenu() and g_menu_insert_item() for
419  * a more flexible alternative.
420  *
421  * Since: 2.32
422  */
423 void
g_menu_prepend_submenu(GMenu * menu,const gchar * label,GMenuModel * submenu)424 g_menu_prepend_submenu (GMenu       *menu,
425                         const gchar *label,
426                         GMenuModel  *submenu)
427 {
428   g_menu_insert_submenu (menu, 0, label, submenu);
429 }
430 
431 /**
432  * g_menu_append_submenu:
433  * @menu: a #GMenu
434  * @label: (nullable): the section label, or %NULL
435  * @submenu: a #GMenuModel with the items of the submenu
436  *
437  * Convenience function for appending a submenu menu item to the end of
438  * @menu.  Combine g_menu_item_new_submenu() and g_menu_insert_item() for a
439  * more flexible alternative.
440  *
441  * Since: 2.32
442  */
443 void
g_menu_append_submenu(GMenu * menu,const gchar * label,GMenuModel * submenu)444 g_menu_append_submenu (GMenu       *menu,
445                        const gchar *label,
446                        GMenuModel  *submenu)
447 {
448   g_menu_insert_submenu (menu, -1, label, submenu);
449 }
450 
451 static void
g_menu_clear_item(struct item * item)452 g_menu_clear_item (struct item *item)
453 {
454   if (item->attributes != NULL)
455     g_hash_table_unref (item->attributes);
456   if (item->links != NULL)
457     g_hash_table_unref (item->links);
458 }
459 
460 /**
461  * g_menu_remove:
462  * @menu: a #GMenu
463  * @position: the position of the item to remove
464  *
465  * Removes an item from the menu.
466  *
467  * @position gives the index of the item to remove.
468  *
469  * It is an error if position is not in range the range from 0 to one
470  * less than the number of items in the menu.
471  *
472  * It is not possible to remove items by identity since items are added
473  * to the menu simply by copying their links and attributes (ie:
474  * identity of the item itself is not preserved).
475  *
476  * Since: 2.32
477  */
478 void
g_menu_remove(GMenu * menu,gint position)479 g_menu_remove (GMenu *menu,
480                gint   position)
481 {
482   g_return_if_fail (G_IS_MENU (menu));
483   g_return_if_fail (0 <= position && (guint) position < menu->items->len);
484 
485   g_menu_clear_item (&g_array_index (menu->items, struct item, position));
486   g_array_remove_index (menu->items, position);
487   g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0);
488 }
489 
490 /**
491  * g_menu_remove_all:
492  * @menu: a #GMenu
493  *
494  * Removes all items in the menu.
495  *
496  * Since: 2.38
497  **/
498 void
g_menu_remove_all(GMenu * menu)499 g_menu_remove_all (GMenu *menu)
500 {
501   gint i, n;
502 
503   g_return_if_fail (G_IS_MENU (menu));
504   n = menu->items->len;
505 
506   for (i = 0; i < n; i++)
507     g_menu_clear_item (&g_array_index (menu->items, struct item, i));
508   g_array_set_size (menu->items, 0);
509 
510   g_menu_model_items_changed (G_MENU_MODEL (menu), 0, n, 0);
511 }
512 
513 static void
g_menu_finalize(GObject * object)514 g_menu_finalize (GObject *object)
515 {
516   GMenu *menu = G_MENU (object);
517   struct item *items;
518   gint n_items;
519   gint i;
520 
521   n_items = menu->items->len;
522   items = (struct item *) g_array_free (menu->items, FALSE);
523   for (i = 0; i < n_items; i++)
524     g_menu_clear_item (&items[i]);
525   g_free (items);
526 
527   G_OBJECT_CLASS (g_menu_parent_class)
528     ->finalize (object);
529 }
530 
531 static void
g_menu_init(GMenu * menu)532 g_menu_init (GMenu *menu)
533 {
534   menu->items = g_array_new (FALSE, FALSE, sizeof (struct item));
535   menu->mutable = TRUE;
536 }
537 
538 static void
g_menu_class_init(GMenuClass * class)539 g_menu_class_init (GMenuClass *class)
540 {
541   GMenuModelClass *model_class = G_MENU_MODEL_CLASS (class);
542   GObjectClass *object_class = G_OBJECT_CLASS (class);
543 
544   object_class->finalize = g_menu_finalize;
545 
546   model_class->is_mutable = g_menu_is_mutable;
547   model_class->get_n_items = g_menu_get_n_items;
548   model_class->get_item_attributes = g_menu_get_item_attributes;
549   model_class->get_item_links = g_menu_get_item_links;
550 }
551 
552 
553 static void
g_menu_item_clear_cow(GMenuItem * menu_item)554 g_menu_item_clear_cow (GMenuItem *menu_item)
555 {
556   if (menu_item->cow)
557     {
558       GHashTableIter iter;
559       GHashTable *new;
560       gpointer key;
561       gpointer val;
562 
563       new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
564       g_hash_table_iter_init (&iter, menu_item->attributes);
565       while (g_hash_table_iter_next (&iter, &key, &val))
566         g_hash_table_insert (new, g_strdup (key), g_variant_ref (val));
567       g_hash_table_unref (menu_item->attributes);
568       menu_item->attributes = new;
569 
570       new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref);
571       g_hash_table_iter_init (&iter, menu_item->links);
572       while (g_hash_table_iter_next (&iter, &key, &val))
573         g_hash_table_insert (new, g_strdup (key), g_object_ref (val));
574       g_hash_table_unref (menu_item->links);
575       menu_item->links = new;
576 
577       menu_item->cow = FALSE;
578     }
579 }
580 
581 static void
g_menu_item_finalize(GObject * object)582 g_menu_item_finalize (GObject *object)
583 {
584   GMenuItem *menu_item = G_MENU_ITEM (object);
585 
586   g_hash_table_unref (menu_item->attributes);
587   g_hash_table_unref (menu_item->links);
588 
589   G_OBJECT_CLASS (g_menu_item_parent_class)
590     ->finalize (object);
591 }
592 
593 static void
g_menu_item_init(GMenuItem * menu_item)594 g_menu_item_init (GMenuItem *menu_item)
595 {
596   menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
597   menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
598   menu_item->cow = FALSE;
599 }
600 
601 static void
g_menu_item_class_init(GMenuItemClass * class)602 g_menu_item_class_init (GMenuItemClass *class)
603 {
604   class->finalize = g_menu_item_finalize;
605 }
606 
607 /* We treat attribute names the same as GSettings keys:
608  * - only lowercase ascii, digits and '-'
609  * - must start with lowercase
610  * - must not end with '-'
611  * - no consecutive '-'
612  * - not longer than 1024 chars
613  */
614 static gboolean
valid_attribute_name(const gchar * name)615 valid_attribute_name (const gchar *name)
616 {
617   gint i;
618 
619   if (!g_ascii_islower (name[0]))
620     return FALSE;
621 
622   for (i = 1; name[i]; i++)
623     {
624       if (name[i] != '-' &&
625           !g_ascii_islower (name[i]) &&
626           !g_ascii_isdigit (name[i]))
627         return FALSE;
628 
629       if (name[i] == '-' && name[i + 1] == '-')
630         return FALSE;
631     }
632 
633   if (name[i - 1] == '-')
634     return FALSE;
635 
636   if (i > 1024)
637     return FALSE;
638 
639   return TRUE;
640 }
641 
642 /**
643  * g_menu_item_set_attribute_value:
644  * @menu_item: a #GMenuItem
645  * @attribute: the attribute to set
646  * @value: (nullable): a #GVariant to use as the value, or %NULL
647  *
648  * Sets or unsets an attribute on @menu_item.
649  *
650  * The attribute to set or unset is specified by @attribute. This
651  * can be one of the standard attribute names %G_MENU_ATTRIBUTE_LABEL,
652  * %G_MENU_ATTRIBUTE_ACTION, %G_MENU_ATTRIBUTE_TARGET, or a custom
653  * attribute name.
654  * Attribute names are restricted to lowercase characters, numbers
655  * and '-'. Furthermore, the names must begin with a lowercase character,
656  * must not end with a '-', and must not contain consecutive dashes.
657  *
658  * must consist only of lowercase
659  * ASCII characters, digits and '-'.
660  *
661  * If @value is non-%NULL then it is used as the new value for the
662  * attribute.  If @value is %NULL then the attribute is unset. If
663  * the @value #GVariant is floating, it is consumed.
664  *
665  * See also g_menu_item_set_attribute() for a more convenient way to do
666  * the same.
667  *
668  * Since: 2.32
669  */
670 void
g_menu_item_set_attribute_value(GMenuItem * menu_item,const gchar * attribute,GVariant * value)671 g_menu_item_set_attribute_value (GMenuItem   *menu_item,
672                                  const gchar *attribute,
673                                  GVariant    *value)
674 {
675   g_return_if_fail (G_IS_MENU_ITEM (menu_item));
676   g_return_if_fail (attribute != NULL);
677   g_return_if_fail (valid_attribute_name (attribute));
678 
679   g_menu_item_clear_cow (menu_item);
680 
681   if (value != NULL)
682     g_hash_table_insert (menu_item->attributes, g_strdup (attribute), g_variant_ref_sink (value));
683   else
684     g_hash_table_remove (menu_item->attributes, attribute);
685 }
686 
687 /**
688  * g_menu_item_set_attribute:
689  * @menu_item: a #GMenuItem
690  * @attribute: the attribute to set
691  * @format_string: (nullable): a #GVariant format string, or %NULL
692  * @...: positional parameters, as per @format_string
693  *
694  * Sets or unsets an attribute on @menu_item.
695  *
696  * The attribute to set or unset is specified by @attribute. This
697  * can be one of the standard attribute names %G_MENU_ATTRIBUTE_LABEL,
698  * %G_MENU_ATTRIBUTE_ACTION, %G_MENU_ATTRIBUTE_TARGET, or a custom
699  * attribute name.
700  * Attribute names are restricted to lowercase characters, numbers
701  * and '-'. Furthermore, the names must begin with a lowercase character,
702  * must not end with a '-', and must not contain consecutive dashes.
703  *
704  * If @format_string is non-%NULL then the proper position parameters
705  * are collected to create a #GVariant instance to use as the attribute
706  * value.  If it is %NULL then the positional parameterrs are ignored
707  * and the named attribute is unset.
708  *
709  * See also g_menu_item_set_attribute_value() for an equivalent call
710  * that directly accepts a #GVariant.
711  *
712  * Since: 2.32
713  */
714 void
g_menu_item_set_attribute(GMenuItem * menu_item,const gchar * attribute,const gchar * format_string,...)715 g_menu_item_set_attribute (GMenuItem   *menu_item,
716                            const gchar *attribute,
717                            const gchar *format_string,
718                            ...)
719 {
720   GVariant *value;
721 
722   if (format_string != NULL)
723     {
724       va_list ap;
725 
726       va_start (ap, format_string);
727       value = g_variant_new_va (format_string, NULL, &ap);
728       va_end (ap);
729     }
730   else
731     value = NULL;
732 
733   g_menu_item_set_attribute_value (menu_item, attribute, value);
734 }
735 
736 /**
737  * g_menu_item_set_link:
738  * @menu_item: a #GMenuItem
739  * @link: type of link to establish or unset
740  * @model: (nullable): the #GMenuModel to link to (or %NULL to unset)
741  *
742  * Creates a link from @menu_item to @model if non-%NULL, or unsets it.
743  *
744  * Links are used to establish a relationship between a particular menu
745  * item and another menu.  For example, %G_MENU_LINK_SUBMENU is used to
746  * associate a submenu with a particular menu item, and %G_MENU_LINK_SECTION
747  * is used to create a section. Other types of link can be used, but there
748  * is no guarantee that clients will be able to make sense of them.
749  * Link types are restricted to lowercase characters, numbers
750  * and '-'. Furthermore, the names must begin with a lowercase character,
751  * must not end with a '-', and must not contain consecutive dashes.
752  *
753  * Since: 2.32
754  */
755 void
g_menu_item_set_link(GMenuItem * menu_item,const gchar * link,GMenuModel * model)756 g_menu_item_set_link (GMenuItem   *menu_item,
757                       const gchar *link,
758                       GMenuModel  *model)
759 {
760   g_return_if_fail (G_IS_MENU_ITEM (menu_item));
761   g_return_if_fail (link != NULL);
762   g_return_if_fail (valid_attribute_name (link));
763 
764   g_menu_item_clear_cow (menu_item);
765 
766   if (model != NULL)
767     g_hash_table_insert (menu_item->links, g_strdup (link), g_object_ref (model));
768   else
769     g_hash_table_remove (menu_item->links, link);
770 }
771 
772 /**
773  * g_menu_item_get_attribute_value:
774  * @menu_item: a #GMenuItem
775  * @attribute: the attribute name to query
776  * @expected_type: (nullable): the expected type of the attribute
777  *
778  * Queries the named @attribute on @menu_item.
779  *
780  * If @expected_type is specified and the attribute does not have this
781  * type, %NULL is returned.  %NULL is also returned if the attribute
782  * simply does not exist.
783  *
784  * Returns: (nullable) (transfer full): the attribute value, or %NULL
785  *
786  * Since: 2.34
787  */
788 GVariant *
g_menu_item_get_attribute_value(GMenuItem * menu_item,const gchar * attribute,const GVariantType * expected_type)789 g_menu_item_get_attribute_value (GMenuItem          *menu_item,
790                                  const gchar        *attribute,
791                                  const GVariantType *expected_type)
792 {
793   GVariant *value;
794 
795   g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), NULL);
796   g_return_val_if_fail (attribute != NULL, NULL);
797 
798   value = g_hash_table_lookup (menu_item->attributes, attribute);
799 
800   if (value != NULL)
801     {
802       if (expected_type == NULL || g_variant_is_of_type (value, expected_type))
803         g_variant_ref (value);
804       else
805         value = NULL;
806     }
807 
808   return value;
809 }
810 
811 /**
812  * g_menu_item_get_attribute:
813  * @menu_item: a #GMenuItem
814  * @attribute: the attribute name to query
815  * @format_string: a #GVariant format string
816  * @...: positional parameters, as per @format_string
817  *
818  * Queries the named @attribute on @menu_item.
819  *
820  * If the attribute exists and matches the #GVariantType corresponding
821  * to @format_string then @format_string is used to deconstruct the
822  * value into the positional parameters and %TRUE is returned.
823  *
824  * If the attribute does not exist, or it does exist but has the wrong
825  * type, then the positional parameters are ignored and %FALSE is
826  * returned.
827  *
828  * Returns: %TRUE if the named attribute was found with the expected
829  *     type
830  *
831  * Since: 2.34
832  */
833 gboolean
g_menu_item_get_attribute(GMenuItem * menu_item,const gchar * attribute,const gchar * format_string,...)834 g_menu_item_get_attribute (GMenuItem   *menu_item,
835                            const gchar *attribute,
836                            const gchar *format_string,
837                            ...)
838 {
839   GVariant *value;
840   va_list ap;
841 
842   g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), FALSE);
843   g_return_val_if_fail (attribute != NULL, FALSE);
844   g_return_val_if_fail (format_string != NULL, FALSE);
845 
846   value = g_hash_table_lookup (menu_item->attributes, attribute);
847 
848   if (value == NULL)
849     return FALSE;
850 
851   if (!g_variant_check_format_string (value, format_string, FALSE))
852     return FALSE;
853 
854   va_start (ap, format_string);
855   g_variant_get_va (value, format_string, NULL, &ap);
856   va_end (ap);
857 
858   return TRUE;
859 }
860 
861 /**
862  * g_menu_item_get_link:
863  * @menu_item: a #GMenuItem
864  * @link: the link name to query
865  *
866  * Queries the named @link on @menu_item.
867  *
868  * Returns: (nullable) (transfer full): the link, or %NULL
869  *
870  * Since: 2.34
871  */
872 GMenuModel *
g_menu_item_get_link(GMenuItem * menu_item,const gchar * link)873 g_menu_item_get_link (GMenuItem   *menu_item,
874                       const gchar *link)
875 {
876   GMenuModel *model;
877 
878   g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), NULL);
879   g_return_val_if_fail (link != NULL, NULL);
880   g_return_val_if_fail (valid_attribute_name (link), NULL);
881 
882   model = g_hash_table_lookup (menu_item->links, link);
883 
884   if (model)
885     g_object_ref (model);
886 
887   return model;
888 }
889 
890 /**
891  * g_menu_item_set_label:
892  * @menu_item: a #GMenuItem
893  * @label: (nullable): the label to set, or %NULL to unset
894  *
895  * Sets or unsets the "label" attribute of @menu_item.
896  *
897  * If @label is non-%NULL it is used as the label for the menu item.  If
898  * it is %NULL then the label attribute is unset.
899  *
900  * Since: 2.32
901  */
902 void
g_menu_item_set_label(GMenuItem * menu_item,const gchar * label)903 g_menu_item_set_label (GMenuItem   *menu_item,
904                        const gchar *label)
905 {
906   GVariant *value;
907 
908   if (label != NULL)
909     value = g_variant_new_string (label);
910   else
911     value = NULL;
912 
913   g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_LABEL, value);
914 }
915 
916 /**
917  * g_menu_item_set_submenu:
918  * @menu_item: a #GMenuItem
919  * @submenu: (nullable): a #GMenuModel, or %NULL
920  *
921  * Sets or unsets the "submenu" link of @menu_item to @submenu.
922  *
923  * If @submenu is non-%NULL, it is linked to.  If it is %NULL then the
924  * link is unset.
925  *
926  * The effect of having one menu appear as a submenu of another is
927  * exactly as it sounds.
928  *
929  * Since: 2.32
930  */
931 void
g_menu_item_set_submenu(GMenuItem * menu_item,GMenuModel * submenu)932 g_menu_item_set_submenu (GMenuItem  *menu_item,
933                          GMenuModel *submenu)
934 {
935   g_menu_item_set_link (menu_item, G_MENU_LINK_SUBMENU, submenu);
936 }
937 
938 /**
939  * g_menu_item_set_section:
940  * @menu_item: a #GMenuItem
941  * @section: (nullable): a #GMenuModel, or %NULL
942  *
943  * Sets or unsets the "section" link of @menu_item to @section.
944  *
945  * The effect of having one menu appear as a section of another is
946  * exactly as it sounds: the items from @section become a direct part of
947  * the menu that @menu_item is added to.  See g_menu_item_new_section()
948  * for more information about what it means for a menu item to be a
949  * section.
950  *
951  * Since: 2.32
952  */
953 void
g_menu_item_set_section(GMenuItem * menu_item,GMenuModel * section)954 g_menu_item_set_section (GMenuItem  *menu_item,
955                          GMenuModel *section)
956 {
957   g_menu_item_set_link (menu_item, G_MENU_LINK_SECTION, section);
958 }
959 
960 /**
961  * g_menu_item_set_action_and_target_value:
962  * @menu_item: a #GMenuItem
963  * @action: (nullable): the name of the action for this item
964  * @target_value: (nullable): a #GVariant to use as the action target
965  *
966  * Sets or unsets the "action" and "target" attributes of @menu_item.
967  *
968  * If @action is %NULL then both the "action" and "target" attributes
969  * are unset (and @target_value is ignored).
970  *
971  * If @action is non-%NULL then the "action" attribute is set.  The
972  * "target" attribute is then set to the value of @target_value if it is
973  * non-%NULL or unset otherwise.
974  *
975  * Normal menu items (ie: not submenu, section or other custom item
976  * types) are expected to have the "action" attribute set to identify
977  * the action that they are associated with.  The state type of the
978  * action help to determine the disposition of the menu item.  See
979  * #GAction and #GActionGroup for an overview of actions.
980  *
981  * In general, clicking on the menu item will result in activation of
982  * the named action with the "target" attribute given as the parameter
983  * to the action invocation.  If the "target" attribute is not set then
984  * the action is invoked with no parameter.
985  *
986  * If the action has no state then the menu item is usually drawn as a
987  * plain menu item (ie: with no additional decoration).
988  *
989  * If the action has a boolean state then the menu item is usually drawn
990  * as a toggle menu item (ie: with a checkmark or equivalent
991  * indication).  The item should be marked as 'toggled' or 'checked'
992  * when the boolean state is %TRUE.
993  *
994  * If the action has a string state then the menu item is usually drawn
995  * as a radio menu item (ie: with a radio bullet or equivalent
996  * indication).  The item should be marked as 'selected' when the string
997  * state is equal to the value of the @target property.
998  *
999  * See g_menu_item_set_action_and_target() or
1000  * g_menu_item_set_detailed_action() for two equivalent calls that are
1001  * probably more convenient for most uses.
1002  *
1003  * Since: 2.32
1004  */
1005 void
g_menu_item_set_action_and_target_value(GMenuItem * menu_item,const gchar * action,GVariant * target_value)1006 g_menu_item_set_action_and_target_value (GMenuItem   *menu_item,
1007                                          const gchar *action,
1008                                          GVariant    *target_value)
1009 {
1010   GVariant *action_value;
1011 
1012   if (action != NULL)
1013     {
1014       action_value = g_variant_new_string (action);
1015     }
1016   else
1017     {
1018       action_value = NULL;
1019       target_value = NULL;
1020     }
1021 
1022   g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ACTION, action_value);
1023   g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_TARGET, target_value);
1024 }
1025 
1026 /**
1027  * g_menu_item_set_action_and_target:
1028  * @menu_item: a #GMenuItem
1029  * @action: (nullable): the name of the action for this item
1030  * @format_string: (nullable): a GVariant format string
1031  * @...: positional parameters, as per @format_string
1032  *
1033  * Sets or unsets the "action" and "target" attributes of @menu_item.
1034  *
1035  * If @action is %NULL then both the "action" and "target" attributes
1036  * are unset (and @format_string is ignored along with the positional
1037  * parameters).
1038  *
1039  * If @action is non-%NULL then the "action" attribute is set.
1040  * @format_string is then inspected.  If it is non-%NULL then the proper
1041  * position parameters are collected to create a #GVariant instance to
1042  * use as the target value.  If it is %NULL then the positional
1043  * parameters are ignored and the "target" attribute is unset.
1044  *
1045  * See also g_menu_item_set_action_and_target_value() for an equivalent
1046  * call that directly accepts a #GVariant.  See
1047  * g_menu_item_set_detailed_action() for a more convenient version that
1048  * works with string-typed targets.
1049  *
1050  * See also g_menu_item_set_action_and_target_value() for a
1051  * description of the semantics of the action and target attributes.
1052  *
1053  * Since: 2.32
1054  */
1055 void
g_menu_item_set_action_and_target(GMenuItem * menu_item,const gchar * action,const gchar * format_string,...)1056 g_menu_item_set_action_and_target (GMenuItem   *menu_item,
1057                                    const gchar *action,
1058                                    const gchar *format_string,
1059                                    ...)
1060 {
1061   GVariant *value;
1062 
1063   if (format_string != NULL)
1064     {
1065       va_list ap;
1066 
1067       va_start (ap, format_string);
1068       value = g_variant_new_va (format_string, NULL, &ap);
1069       va_end (ap);
1070     }
1071   else
1072     value = NULL;
1073 
1074   g_menu_item_set_action_and_target_value (menu_item, action, value);
1075 }
1076 
1077 /**
1078  * g_menu_item_set_detailed_action:
1079  * @menu_item: a #GMenuItem
1080  * @detailed_action: the "detailed" action string
1081  *
1082  * Sets the "action" and possibly the "target" attribute of @menu_item.
1083  *
1084  * The format of @detailed_action is the same format parsed by
1085  * g_action_parse_detailed_name().
1086  *
1087  * See g_menu_item_set_action_and_target() or
1088  * g_menu_item_set_action_and_target_value() for more flexible (but
1089  * slightly less convenient) alternatives.
1090  *
1091  * See also g_menu_item_set_action_and_target_value() for a description of
1092  * the semantics of the action and target attributes.
1093  *
1094  * Since: 2.32
1095  */
1096 void
g_menu_item_set_detailed_action(GMenuItem * menu_item,const gchar * detailed_action)1097 g_menu_item_set_detailed_action (GMenuItem   *menu_item,
1098                                  const gchar *detailed_action)
1099 {
1100   GError *error = NULL;
1101   GVariant *target;
1102   gchar *name;
1103 
1104   if (!g_action_parse_detailed_name (detailed_action, &name, &target, &error))
1105     g_error ("g_menu_item_set_detailed_action: %s", error->message);
1106 
1107   g_menu_item_set_action_and_target_value (menu_item, name, target);
1108   if (target)
1109     g_variant_unref (target);
1110   g_free (name);
1111 }
1112 
1113 /**
1114  * g_menu_item_new:
1115  * @label: (nullable): the section label, or %NULL
1116  * @detailed_action: (nullable): the detailed action string, or %NULL
1117  *
1118  * Creates a new #GMenuItem.
1119  *
1120  * If @label is non-%NULL it is used to set the "label" attribute of the
1121  * new item.
1122  *
1123  * If @detailed_action is non-%NULL it is used to set the "action" and
1124  * possibly the "target" attribute of the new item.  See
1125  * g_menu_item_set_detailed_action() for more information.
1126  *
1127  * Returns: a new #GMenuItem
1128  *
1129  * Since: 2.32
1130  */
1131 GMenuItem *
g_menu_item_new(const gchar * label,const gchar * detailed_action)1132 g_menu_item_new (const gchar *label,
1133                  const gchar *detailed_action)
1134 {
1135   GMenuItem *menu_item;
1136 
1137   menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL);
1138 
1139   if (label != NULL)
1140     g_menu_item_set_label (menu_item, label);
1141 
1142   if (detailed_action != NULL)
1143     g_menu_item_set_detailed_action (menu_item, detailed_action);
1144 
1145   return menu_item;
1146 }
1147 
1148 /**
1149  * g_menu_item_new_submenu:
1150  * @label: (nullable): the section label, or %NULL
1151  * @submenu: a #GMenuModel with the items of the submenu
1152  *
1153  * Creates a new #GMenuItem representing a submenu.
1154  *
1155  * This is a convenience API around g_menu_item_new() and
1156  * g_menu_item_set_submenu().
1157  *
1158  * Returns: a new #GMenuItem
1159  *
1160  * Since: 2.32
1161  */
1162 GMenuItem *
g_menu_item_new_submenu(const gchar * label,GMenuModel * submenu)1163 g_menu_item_new_submenu (const gchar *label,
1164                          GMenuModel  *submenu)
1165 {
1166   GMenuItem *menu_item;
1167 
1168   menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL);
1169 
1170   if (label != NULL)
1171     g_menu_item_set_label (menu_item, label);
1172 
1173   g_menu_item_set_submenu (menu_item, submenu);
1174 
1175   return menu_item;
1176 }
1177 
1178 /**
1179  * g_menu_item_new_section:
1180  * @label: (nullable): the section label, or %NULL
1181  * @section: a #GMenuModel with the items of the section
1182  *
1183  * Creates a new #GMenuItem representing a section.
1184  *
1185  * This is a convenience API around g_menu_item_new() and
1186  * g_menu_item_set_section().
1187  *
1188  * The effect of having one menu appear as a section of another is
1189  * exactly as it sounds: the items from @section become a direct part of
1190  * the menu that @menu_item is added to.
1191  *
1192  * Visual separation is typically displayed between two non-empty
1193  * sections.  If @label is non-%NULL then it will be encorporated into
1194  * this visual indication.  This allows for labeled subsections of a
1195  * menu.
1196  *
1197  * As a simple example, consider a typical "Edit" menu from a simple
1198  * program.  It probably contains an "Undo" and "Redo" item, followed by
1199  * a separator, followed by "Cut", "Copy" and "Paste".
1200  *
1201  * This would be accomplished by creating three #GMenu instances.  The
1202  * first would be populated with the "Undo" and "Redo" items, and the
1203  * second with the "Cut", "Copy" and "Paste" items.  The first and
1204  * second menus would then be added as submenus of the third.  In XML
1205  * format, this would look something like the following:
1206  * |[
1207  * <menu id='edit-menu'>
1208  *   <section>
1209  *     <item label='Undo'/>
1210  *     <item label='Redo'/>
1211  *   </section>
1212  *   <section>
1213  *     <item label='Cut'/>
1214  *     <item label='Copy'/>
1215  *     <item label='Paste'/>
1216  *   </section>
1217  * </menu>
1218  * ]|
1219  *
1220  * The following example is exactly equivalent.  It is more illustrative
1221  * of the exact relationship between the menus and items (keeping in
1222  * mind that the 'link' element defines a new menu that is linked to the
1223  * containing one).  The style of the second example is more verbose and
1224  * difficult to read (and therefore not recommended except for the
1225  * purpose of understanding what is really going on).
1226  * |[
1227  * <menu id='edit-menu'>
1228  *   <item>
1229  *     <link name='section'>
1230  *       <item label='Undo'/>
1231  *       <item label='Redo'/>
1232  *     </link>
1233  *   </item>
1234  *   <item>
1235  *     <link name='section'>
1236  *       <item label='Cut'/>
1237  *       <item label='Copy'/>
1238  *       <item label='Paste'/>
1239  *     </link>
1240  *   </item>
1241  * </menu>
1242  * ]|
1243  *
1244  * Returns: a new #GMenuItem
1245  *
1246  * Since: 2.32
1247  */
1248 GMenuItem *
g_menu_item_new_section(const gchar * label,GMenuModel * section)1249 g_menu_item_new_section (const gchar *label,
1250                          GMenuModel  *section)
1251 {
1252   GMenuItem *menu_item;
1253 
1254   menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL);
1255 
1256   if (label != NULL)
1257     g_menu_item_set_label (menu_item, label);
1258 
1259   g_menu_item_set_section (menu_item, section);
1260 
1261   return menu_item;
1262 }
1263 
1264 /**
1265  * g_menu_item_new_from_model:
1266  * @model: a #GMenuModel
1267  * @item_index: the index of an item in @model
1268  *
1269  * Creates a #GMenuItem as an exact copy of an existing menu item in a
1270  * #GMenuModel.
1271  *
1272  * @item_index must be valid (ie: be sure to call
1273  * g_menu_model_get_n_items() first).
1274  *
1275  * Returns: a new #GMenuItem.
1276  *
1277  * Since: 2.34
1278  */
1279 GMenuItem *
g_menu_item_new_from_model(GMenuModel * model,gint item_index)1280 g_menu_item_new_from_model (GMenuModel *model,
1281                             gint        item_index)
1282 {
1283   GMenuModelClass *class = G_MENU_MODEL_GET_CLASS (model);
1284   GMenuItem *menu_item;
1285 
1286   menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL);
1287 
1288   /* With some trickery we can be pretty efficient.
1289    *
1290    * A GMenuModel must either implement iterate_item_attributes() or
1291    * get_item_attributes().  If it implements get_item_attributes() then
1292    * we are in luck -- we can just take a reference on the returned
1293    * hashtable and mark ourselves as copy-on-write.
1294    *
1295    * In the case that the model is based on get_item_attributes (which
1296    * is the case for both GMenu and GDBusMenuModel) then this is
1297    * basically just g_hash_table_ref().
1298    */
1299   if (class->get_item_attributes)
1300     {
1301       GHashTable *attributes = NULL;
1302 
1303       class->get_item_attributes (model, item_index, &attributes);
1304       if (attributes)
1305         {
1306           g_hash_table_unref (menu_item->attributes);
1307           menu_item->attributes = attributes;
1308           menu_item->cow = TRUE;
1309         }
1310     }
1311   else
1312     {
1313       GMenuAttributeIter *iter;
1314       const gchar *attribute;
1315       GVariant *value;
1316 
1317       iter = g_menu_model_iterate_item_attributes (model, item_index);
1318       while (g_menu_attribute_iter_get_next (iter, &attribute, &value))
1319         g_hash_table_insert (menu_item->attributes, g_strdup (attribute), value);
1320       g_object_unref (iter);
1321     }
1322 
1323   /* Same story for the links... */
1324   if (class->get_item_links)
1325     {
1326       GHashTable *links = NULL;
1327 
1328       class->get_item_links (model, item_index, &links);
1329       if (links)
1330         {
1331           g_hash_table_unref (menu_item->links);
1332           menu_item->links = links;
1333           menu_item->cow = TRUE;
1334         }
1335     }
1336   else
1337     {
1338       GMenuLinkIter *iter;
1339       const gchar *link;
1340       GMenuModel *value;
1341 
1342       iter = g_menu_model_iterate_item_links (model, item_index);
1343       while (g_menu_link_iter_get_next (iter, &link, &value))
1344         g_hash_table_insert (menu_item->links, g_strdup (link), value);
1345       g_object_unref (iter);
1346     }
1347 
1348   return menu_item;
1349 }
1350 
1351 /**
1352  * g_menu_item_set_icon:
1353  * @menu_item: a #GMenuItem
1354  * @icon: a #GIcon, or %NULL
1355  *
1356  * Sets (or unsets) the icon on @menu_item.
1357  *
1358  * This call is the same as calling g_icon_serialize() and using the
1359  * result as the value to g_menu_item_set_attribute_value() for
1360  * %G_MENU_ATTRIBUTE_ICON.
1361  *
1362  * This API is only intended for use with "noun" menu items; things like
1363  * bookmarks or applications in an "Open With" menu.  Don't use it on
1364  * menu items corresponding to verbs (eg: stock icons for 'Save' or
1365  * 'Quit').
1366  *
1367  * If @icon is %NULL then the icon is unset.
1368  *
1369  * Since: 2.38
1370  **/
1371 void
g_menu_item_set_icon(GMenuItem * menu_item,GIcon * icon)1372 g_menu_item_set_icon (GMenuItem *menu_item,
1373                       GIcon     *icon)
1374 {
1375   GVariant *value;
1376 
1377   g_return_if_fail (G_IS_MENU_ITEM (menu_item));
1378   g_return_if_fail (icon == NULL || G_IS_ICON (icon));
1379 
1380   if (icon != NULL)
1381     value = g_icon_serialize (icon);
1382   else
1383     value = NULL;
1384 
1385   g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, value);
1386   if (value)
1387     g_variant_unref (value);
1388 }
1389