| /* |
| * Copyright © 2011 Canonical Ltd. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
| * |
| * Author: Ryan Lortie <desrt@desrt.ca> |
| */ |
| |
| #include "config.h" |
| |
| #include "gmenu.h" |
| |
| #include "gaction.h" |
| #include <string.h> |
| |
| #include "gicon.h" |
| |
| /** |
| * GMenu: |
| * |
| * `GMenu` is a simple implementation of [class@Gio.MenuModel]. |
| * You populate a `GMenu` by adding [class@Gio.MenuItem] instances to it. |
| * |
| * There are some convenience functions to allow you to directly |
| * add items (avoiding [class@Gio.MenuItem]) for the common cases. To add |
| * a regular item, use [method@Gio.Menu.insert]. To add a section, use |
| * [method@Gio.Menu.insert_section]. To add a submenu, use |
| * [method@Gio.Menu.insert_submenu]. |
| * |
| * Since: 2.32 |
| */ |
| |
| /** |
| * GMenuItem: |
| * |
| * #GMenuItem is an opaque structure type. You must access it using the |
| * functions below. |
| * |
| * Since: 2.32 |
| */ |
| |
| struct _GMenuItem |
| { |
| GObject parent_instance; |
| |
| GHashTable *attributes; |
| GHashTable *links; |
| gboolean cow; |
| }; |
| |
| typedef GObjectClass GMenuItemClass; |
| |
| struct _GMenu |
| { |
| GMenuModel parent_instance; |
| |
| GArray *items; |
| gboolean mutable; |
| }; |
| |
| typedef GMenuModelClass GMenuClass; |
| |
| G_DEFINE_TYPE (GMenu, g_menu, G_TYPE_MENU_MODEL) |
| G_DEFINE_TYPE (GMenuItem, g_menu_item, G_TYPE_OBJECT) |
| |
| struct item |
| { |
| GHashTable *attributes; |
| GHashTable *links; |
| }; |
| |
| static gboolean |
| g_menu_is_mutable (GMenuModel *model) |
| { |
| GMenu *menu = G_MENU (model); |
| |
| return menu->mutable; |
| } |
| |
| static gint |
| g_menu_get_n_items (GMenuModel *model) |
| { |
| GMenu *menu = G_MENU (model); |
| |
| return menu->items->len; |
| } |
| |
| static void |
| g_menu_get_item_attributes (GMenuModel *model, |
| gint position, |
| GHashTable **table) |
| { |
| GMenu *menu = G_MENU (model); |
| |
| *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).attributes); |
| } |
| |
| static void |
| g_menu_get_item_links (GMenuModel *model, |
| gint position, |
| GHashTable **table) |
| { |
| GMenu *menu = G_MENU (model); |
| |
| *table = g_hash_table_ref (g_array_index (menu->items, struct item, position).links); |
| } |
| |
| /** |
| * g_menu_insert_item: |
| * @menu: a #GMenu |
| * @position: the position at which to insert the item |
| * @item: the #GMenuItem to insert |
| * |
| * Inserts @item into @menu. |
| * |
| * The "insertion" is actually done by copying all of the attribute and |
| * link values of @item and using them to form a new item within @menu. |
| * As such, @item itself is not really inserted, but rather, a menu item |
| * that is exactly the same as the one presently described by @item. |
| * |
| * This means that @item is essentially useless after the insertion |
| * occurs. Any changes you make to it are ignored unless it is inserted |
| * again (at which point its updated values will be copied). |
| * |
| * You should probably just free @item once you're done. |
| * |
| * There are many convenience functions to take care of common cases. |
| * See g_menu_insert(), g_menu_insert_section() and |
| * g_menu_insert_submenu() as well as "prepend" and "append" variants of |
| * each of these functions. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_insert_item (GMenu *menu, |
| gint position, |
| GMenuItem *item) |
| { |
| struct item new_item; |
| |
| g_return_if_fail (G_IS_MENU (menu)); |
| g_return_if_fail (G_IS_MENU_ITEM (item)); |
| |
| if (position < 0 || (guint) position > menu->items->len) |
| position = menu->items->len; |
| |
| new_item.attributes = g_hash_table_ref (item->attributes); |
| new_item.links = g_hash_table_ref (item->links); |
| item->cow = TRUE; |
| |
| g_array_insert_val (menu->items, position, new_item); |
| g_menu_model_items_changed (G_MENU_MODEL (menu), position, 0, 1); |
| } |
| |
| /** |
| * g_menu_prepend_item: |
| * @menu: a #GMenu |
| * @item: a #GMenuItem to prepend |
| * |
| * Prepends @item to the start of @menu. |
| * |
| * See g_menu_insert_item() for more information. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_prepend_item (GMenu *menu, |
| GMenuItem *item) |
| { |
| g_menu_insert_item (menu, 0, item); |
| } |
| |
| /** |
| * g_menu_append_item: |
| * @menu: a #GMenu |
| * @item: a #GMenuItem to append |
| * |
| * Appends @item to the end of @menu. |
| * |
| * See g_menu_insert_item() for more information. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_append_item (GMenu *menu, |
| GMenuItem *item) |
| { |
| g_menu_insert_item (menu, -1, item); |
| } |
| |
| /** |
| * g_menu_freeze: |
| * @menu: a #GMenu |
| * |
| * Marks @menu as frozen. |
| * |
| * After the menu is frozen, it is an error to attempt to make any |
| * changes to it. In effect this means that the #GMenu API must no |
| * longer be used. |
| * |
| * This function causes g_menu_model_is_mutable() to begin returning |
| * %FALSE, which has some positive performance implications. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_freeze (GMenu *menu) |
| { |
| g_return_if_fail (G_IS_MENU (menu)); |
| |
| menu->mutable = FALSE; |
| } |
| |
| /** |
| * g_menu_new: |
| * |
| * Creates a new #GMenu. |
| * |
| * The new menu has no items. |
| * |
| * Returns: a new #GMenu |
| * |
| * Since: 2.32 |
| */ |
| GMenu * |
| g_menu_new (void) |
| { |
| return g_object_new (G_TYPE_MENU, NULL); |
| } |
| |
| /** |
| * g_menu_insert: |
| * @menu: a #GMenu |
| * @position: the position at which to insert the item |
| * @label: (nullable): the section label, or %NULL |
| * @detailed_action: (nullable): the detailed action string, or %NULL |
| * |
| * Convenience function for inserting a normal menu item into @menu. |
| * Combine g_menu_item_new() and g_menu_insert_item() for a more flexible |
| * alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_insert (GMenu *menu, |
| gint position, |
| const gchar *label, |
| const gchar *detailed_action) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_menu_item_new (label, detailed_action); |
| g_menu_insert_item (menu, position, menu_item); |
| g_object_unref (menu_item); |
| } |
| |
| /** |
| * g_menu_prepend: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @detailed_action: (nullable): the detailed action string, or %NULL |
| * |
| * Convenience function for prepending a normal menu item to the start |
| * of @menu. Combine g_menu_item_new() and g_menu_insert_item() for a more |
| * flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_prepend (GMenu *menu, |
| const gchar *label, |
| const gchar *detailed_action) |
| { |
| g_menu_insert (menu, 0, label, detailed_action); |
| } |
| |
| /** |
| * g_menu_append: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @detailed_action: (nullable): the detailed action string, or %NULL |
| * |
| * Convenience function for appending a normal menu item to the end of |
| * @menu. Combine g_menu_item_new() and g_menu_insert_item() for a more |
| * flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_append (GMenu *menu, |
| const gchar *label, |
| const gchar *detailed_action) |
| { |
| g_menu_insert (menu, -1, label, detailed_action); |
| } |
| |
| /** |
| * g_menu_insert_section: |
| * @menu: a #GMenu |
| * @position: the position at which to insert the item |
| * @label: (nullable): the section label, or %NULL |
| * @section: a #GMenuModel with the items of the section |
| * |
| * Convenience function for inserting a section menu item into @menu. |
| * Combine g_menu_item_new_section() and g_menu_insert_item() for a more |
| * flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_insert_section (GMenu *menu, |
| gint position, |
| const gchar *label, |
| GMenuModel *section) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_menu_item_new_section (label, section); |
| g_menu_insert_item (menu, position, menu_item); |
| g_object_unref (menu_item); |
| } |
| |
| |
| /** |
| * g_menu_prepend_section: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @section: a #GMenuModel with the items of the section |
| * |
| * Convenience function for prepending a section menu item to the start |
| * of @menu. Combine g_menu_item_new_section() and g_menu_insert_item() for |
| * a more flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_prepend_section (GMenu *menu, |
| const gchar *label, |
| GMenuModel *section) |
| { |
| g_menu_insert_section (menu, 0, label, section); |
| } |
| |
| /** |
| * g_menu_append_section: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @section: a #GMenuModel with the items of the section |
| * |
| * Convenience function for appending a section menu item to the end of |
| * @menu. Combine g_menu_item_new_section() and g_menu_insert_item() for a |
| * more flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_append_section (GMenu *menu, |
| const gchar *label, |
| GMenuModel *section) |
| { |
| g_menu_insert_section (menu, -1, label, section); |
| } |
| |
| /** |
| * g_menu_insert_submenu: |
| * @menu: a #GMenu |
| * @position: the position at which to insert the item |
| * @label: (nullable): the section label, or %NULL |
| * @submenu: a #GMenuModel with the items of the submenu |
| * |
| * Convenience function for inserting a submenu menu item into @menu. |
| * Combine g_menu_item_new_submenu() and g_menu_insert_item() for a more |
| * flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_insert_submenu (GMenu *menu, |
| gint position, |
| const gchar *label, |
| GMenuModel *submenu) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_menu_item_new_submenu (label, submenu); |
| g_menu_insert_item (menu, position, menu_item); |
| g_object_unref (menu_item); |
| } |
| |
| /** |
| * g_menu_prepend_submenu: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @submenu: a #GMenuModel with the items of the submenu |
| * |
| * Convenience function for prepending a submenu menu item to the start |
| * of @menu. Combine g_menu_item_new_submenu() and g_menu_insert_item() for |
| * a more flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_prepend_submenu (GMenu *menu, |
| const gchar *label, |
| GMenuModel *submenu) |
| { |
| g_menu_insert_submenu (menu, 0, label, submenu); |
| } |
| |
| /** |
| * g_menu_append_submenu: |
| * @menu: a #GMenu |
| * @label: (nullable): the section label, or %NULL |
| * @submenu: a #GMenuModel with the items of the submenu |
| * |
| * Convenience function for appending a submenu menu item to the end of |
| * @menu. Combine g_menu_item_new_submenu() and g_menu_insert_item() for a |
| * more flexible alternative. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_append_submenu (GMenu *menu, |
| const gchar *label, |
| GMenuModel *submenu) |
| { |
| g_menu_insert_submenu (menu, -1, label, submenu); |
| } |
| |
| static void |
| g_menu_clear_item (struct item *item) |
| { |
| if (item->attributes != NULL) |
| g_hash_table_unref (item->attributes); |
| if (item->links != NULL) |
| g_hash_table_unref (item->links); |
| } |
| |
| /** |
| * g_menu_remove: |
| * @menu: a #GMenu |
| * @position: the position of the item to remove |
| * |
| * Removes an item from the menu. |
| * |
| * @position gives the index of the item to remove. |
| * |
| * It is an error if position is not in range the range from 0 to one |
| * less than the number of items in the menu. |
| * |
| * It is not possible to remove items by identity since items are added |
| * to the menu simply by copying their links and attributes (ie: |
| * identity of the item itself is not preserved). |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_remove (GMenu *menu, |
| gint position) |
| { |
| g_return_if_fail (G_IS_MENU (menu)); |
| g_return_if_fail (0 <= position && (guint) position < menu->items->len); |
| |
| g_menu_clear_item (&g_array_index (menu->items, struct item, position)); |
| g_array_remove_index (menu->items, position); |
| g_menu_model_items_changed (G_MENU_MODEL (menu), position, 1, 0); |
| } |
| |
| /** |
| * g_menu_remove_all: |
| * @menu: a #GMenu |
| * |
| * Removes all items in the menu. |
| * |
| * Since: 2.38 |
| **/ |
| void |
| g_menu_remove_all (GMenu *menu) |
| { |
| gint i, n; |
| |
| g_return_if_fail (G_IS_MENU (menu)); |
| n = menu->items->len; |
| |
| for (i = 0; i < n; i++) |
| g_menu_clear_item (&g_array_index (menu->items, struct item, i)); |
| g_array_set_size (menu->items, 0); |
| |
| g_menu_model_items_changed (G_MENU_MODEL (menu), 0, n, 0); |
| } |
| |
| static void |
| g_menu_finalize (GObject *object) |
| { |
| GMenu *menu = G_MENU (object); |
| struct item *items; |
| gint n_items; |
| gint i; |
| |
| n_items = menu->items->len; |
| items = (struct item *) g_array_free (menu->items, FALSE); |
| for (i = 0; i < n_items; i++) |
| g_menu_clear_item (&items[i]); |
| g_free (items); |
| |
| G_OBJECT_CLASS (g_menu_parent_class) |
| ->finalize (object); |
| } |
| |
| static void |
| g_menu_init (GMenu *menu) |
| { |
| menu->items = g_array_new (FALSE, FALSE, sizeof (struct item)); |
| menu->mutable = TRUE; |
| } |
| |
| static void |
| g_menu_class_init (GMenuClass *class) |
| { |
| GMenuModelClass *model_class = G_MENU_MODEL_CLASS (class); |
| GObjectClass *object_class = G_OBJECT_CLASS (class); |
| |
| object_class->finalize = g_menu_finalize; |
| |
| model_class->is_mutable = g_menu_is_mutable; |
| model_class->get_n_items = g_menu_get_n_items; |
| model_class->get_item_attributes = g_menu_get_item_attributes; |
| model_class->get_item_links = g_menu_get_item_links; |
| } |
| |
| |
| static void |
| g_menu_item_clear_cow (GMenuItem *menu_item) |
| { |
| if (menu_item->cow) |
| { |
| GHashTableIter iter; |
| GHashTable *new; |
| gpointer key; |
| gpointer val; |
| |
| new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); |
| g_hash_table_iter_init (&iter, menu_item->attributes); |
| while (g_hash_table_iter_next (&iter, &key, &val)) |
| g_hash_table_insert (new, g_strdup (key), g_variant_ref (val)); |
| g_hash_table_unref (menu_item->attributes); |
| menu_item->attributes = new; |
| |
| new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_object_unref); |
| g_hash_table_iter_init (&iter, menu_item->links); |
| while (g_hash_table_iter_next (&iter, &key, &val)) |
| g_hash_table_insert (new, g_strdup (key), g_object_ref (val)); |
| g_hash_table_unref (menu_item->links); |
| menu_item->links = new; |
| |
| menu_item->cow = FALSE; |
| } |
| } |
| |
| static void |
| g_menu_item_finalize (GObject *object) |
| { |
| GMenuItem *menu_item = G_MENU_ITEM (object); |
| |
| g_hash_table_unref (menu_item->attributes); |
| g_hash_table_unref (menu_item->links); |
| |
| G_OBJECT_CLASS (g_menu_item_parent_class) |
| ->finalize (object); |
| } |
| |
| static void |
| g_menu_item_init (GMenuItem *menu_item) |
| { |
| menu_item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); |
| menu_item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); |
| menu_item->cow = FALSE; |
| } |
| |
| static void |
| g_menu_item_class_init (GMenuItemClass *class) |
| { |
| class->finalize = g_menu_item_finalize; |
| } |
| |
| /* We treat attribute names the same as GSettings keys: |
| * - only lowercase ascii, digits and '-' |
| * - must start with lowercase |
| * - must not end with '-' |
| * - no consecutive '-' |
| * - not longer than 1024 chars |
| */ |
| static gboolean |
| valid_attribute_name (const gchar *name) |
| { |
| gint i; |
| |
| if (!g_ascii_islower (name[0])) |
| return FALSE; |
| |
| for (i = 1; name[i]; i++) |
| { |
| if (name[i] != '-' && |
| !g_ascii_islower (name[i]) && |
| !g_ascii_isdigit (name[i])) |
| return FALSE; |
| |
| if (name[i] == '-' && name[i + 1] == '-') |
| return FALSE; |
| } |
| |
| if (name[i - 1] == '-') |
| return FALSE; |
| |
| if (i > 1024) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /** |
| * g_menu_item_set_attribute_value: |
| * @menu_item: a #GMenuItem |
| * @attribute: the attribute to set |
| * @value: (nullable): a #GVariant to use as the value, or %NULL |
| * |
| * Sets or unsets an attribute on @menu_item. |
| * |
| * The attribute to set or unset is specified by @attribute. This |
| * can be one of the standard attribute names %G_MENU_ATTRIBUTE_LABEL, |
| * %G_MENU_ATTRIBUTE_ACTION, %G_MENU_ATTRIBUTE_TARGET, or a custom |
| * attribute name. |
| * Attribute names are restricted to lowercase characters, numbers |
| * and '-'. Furthermore, the names must begin with a lowercase character, |
| * must not end with a '-', and must not contain consecutive dashes. |
| * |
| * must consist only of lowercase |
| * ASCII characters, digits and '-'. |
| * |
| * If @value is non-%NULL then it is used as the new value for the |
| * attribute. If @value is %NULL then the attribute is unset. If |
| * the @value #GVariant is floating, it is consumed. |
| * |
| * See also g_menu_item_set_attribute() for a more convenient way to do |
| * the same. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_attribute_value (GMenuItem *menu_item, |
| const gchar *attribute, |
| GVariant *value) |
| { |
| g_return_if_fail (G_IS_MENU_ITEM (menu_item)); |
| g_return_if_fail (attribute != NULL); |
| g_return_if_fail (valid_attribute_name (attribute)); |
| |
| g_menu_item_clear_cow (menu_item); |
| |
| if (value != NULL) |
| g_hash_table_insert (menu_item->attributes, g_strdup (attribute), g_variant_ref_sink (value)); |
| else |
| g_hash_table_remove (menu_item->attributes, attribute); |
| } |
| |
| /** |
| * g_menu_item_set_attribute: |
| * @menu_item: a #GMenuItem |
| * @attribute: the attribute to set |
| * @format_string: (nullable): a #GVariant format string, or %NULL |
| * @...: positional parameters, as per @format_string |
| * |
| * Sets or unsets an attribute on @menu_item. |
| * |
| * The attribute to set or unset is specified by @attribute. This |
| * can be one of the standard attribute names %G_MENU_ATTRIBUTE_LABEL, |
| * %G_MENU_ATTRIBUTE_ACTION, %G_MENU_ATTRIBUTE_TARGET, or a custom |
| * attribute name. |
| * Attribute names are restricted to lowercase characters, numbers |
| * and '-'. Furthermore, the names must begin with a lowercase character, |
| * must not end with a '-', and must not contain consecutive dashes. |
| * |
| * If @format_string is non-%NULL then the proper position parameters |
| * are collected to create a #GVariant instance to use as the attribute |
| * value. If it is %NULL then the positional parameterrs are ignored |
| * and the named attribute is unset. |
| * |
| * See also g_menu_item_set_attribute_value() for an equivalent call |
| * that directly accepts a #GVariant. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_attribute (GMenuItem *menu_item, |
| const gchar *attribute, |
| const gchar *format_string, |
| ...) |
| { |
| GVariant *value; |
| |
| if (format_string != NULL) |
| { |
| va_list ap; |
| |
| va_start (ap, format_string); |
| value = g_variant_new_va (format_string, NULL, &ap); |
| va_end (ap); |
| } |
| else |
| value = NULL; |
| |
| g_menu_item_set_attribute_value (menu_item, attribute, value); |
| } |
| |
| /** |
| * g_menu_item_set_link: |
| * @menu_item: a #GMenuItem |
| * @link: type of link to establish or unset |
| * @model: (nullable): the #GMenuModel to link to (or %NULL to unset) |
| * |
| * Creates a link from @menu_item to @model if non-%NULL, or unsets it. |
| * |
| * Links are used to establish a relationship between a particular menu |
| * item and another menu. For example, %G_MENU_LINK_SUBMENU is used to |
| * associate a submenu with a particular menu item, and %G_MENU_LINK_SECTION |
| * is used to create a section. Other types of link can be used, but there |
| * is no guarantee that clients will be able to make sense of them. |
| * Link types are restricted to lowercase characters, numbers |
| * and '-'. Furthermore, the names must begin with a lowercase character, |
| * must not end with a '-', and must not contain consecutive dashes. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_link (GMenuItem *menu_item, |
| const gchar *link, |
| GMenuModel *model) |
| { |
| g_return_if_fail (G_IS_MENU_ITEM (menu_item)); |
| g_return_if_fail (link != NULL); |
| g_return_if_fail (valid_attribute_name (link)); |
| |
| g_menu_item_clear_cow (menu_item); |
| |
| if (model != NULL) |
| g_hash_table_insert (menu_item->links, g_strdup (link), g_object_ref (model)); |
| else |
| g_hash_table_remove (menu_item->links, link); |
| } |
| |
| /** |
| * g_menu_item_get_attribute_value: |
| * @menu_item: a #GMenuItem |
| * @attribute: the attribute name to query |
| * @expected_type: (nullable): the expected type of the attribute |
| * |
| * Queries the named @attribute on @menu_item. |
| * |
| * If @expected_type is specified and the attribute does not have this |
| * type, %NULL is returned. %NULL is also returned if the attribute |
| * simply does not exist. |
| * |
| * Returns: (nullable) (transfer full): the attribute value, or %NULL |
| * |
| * Since: 2.34 |
| */ |
| GVariant * |
| g_menu_item_get_attribute_value (GMenuItem *menu_item, |
| const gchar *attribute, |
| const GVariantType *expected_type) |
| { |
| GVariant *value; |
| |
| g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), NULL); |
| g_return_val_if_fail (attribute != NULL, NULL); |
| |
| value = g_hash_table_lookup (menu_item->attributes, attribute); |
| |
| if (value != NULL) |
| { |
| if (expected_type == NULL || g_variant_is_of_type (value, expected_type)) |
| g_variant_ref (value); |
| else |
| value = NULL; |
| } |
| |
| return value; |
| } |
| |
| /** |
| * g_menu_item_get_attribute: |
| * @menu_item: a #GMenuItem |
| * @attribute: the attribute name to query |
| * @format_string: a #GVariant format string |
| * @...: positional parameters, as per @format_string |
| * |
| * Queries the named @attribute on @menu_item. |
| * |
| * If the attribute exists and matches the #GVariantType corresponding |
| * to @format_string then @format_string is used to deconstruct the |
| * value into the positional parameters and %TRUE is returned. |
| * |
| * If the attribute does not exist, or it does exist but has the wrong |
| * type, then the positional parameters are ignored and %FALSE is |
| * returned. |
| * |
| * Returns: %TRUE if the named attribute was found with the expected |
| * type |
| * |
| * Since: 2.34 |
| */ |
| gboolean |
| g_menu_item_get_attribute (GMenuItem *menu_item, |
| const gchar *attribute, |
| const gchar *format_string, |
| ...) |
| { |
| GVariant *value; |
| va_list ap; |
| |
| g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), FALSE); |
| g_return_val_if_fail (attribute != NULL, FALSE); |
| g_return_val_if_fail (format_string != NULL, FALSE); |
| |
| value = g_hash_table_lookup (menu_item->attributes, attribute); |
| |
| if (value == NULL) |
| return FALSE; |
| |
| if (!g_variant_check_format_string (value, format_string, FALSE)) |
| return FALSE; |
| |
| va_start (ap, format_string); |
| g_variant_get_va (value, format_string, NULL, &ap); |
| va_end (ap); |
| |
| return TRUE; |
| } |
| |
| /** |
| * g_menu_item_get_link: |
| * @menu_item: a #GMenuItem |
| * @link: the link name to query |
| * |
| * Queries the named @link on @menu_item. |
| * |
| * Returns: (nullable) (transfer full): the link, or %NULL |
| * |
| * Since: 2.34 |
| */ |
| GMenuModel * |
| g_menu_item_get_link (GMenuItem *menu_item, |
| const gchar *link) |
| { |
| GMenuModel *model; |
| |
| g_return_val_if_fail (G_IS_MENU_ITEM (menu_item), NULL); |
| g_return_val_if_fail (link != NULL, NULL); |
| g_return_val_if_fail (valid_attribute_name (link), NULL); |
| |
| model = g_hash_table_lookup (menu_item->links, link); |
| |
| if (model) |
| g_object_ref (model); |
| |
| return model; |
| } |
| |
| /** |
| * g_menu_item_set_label: |
| * @menu_item: a #GMenuItem |
| * @label: (nullable): the label to set, or %NULL to unset |
| * |
| * Sets or unsets the "label" attribute of @menu_item. |
| * |
| * If @label is non-%NULL it is used as the label for the menu item. If |
| * it is %NULL then the label attribute is unset. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_label (GMenuItem *menu_item, |
| const gchar *label) |
| { |
| GVariant *value; |
| |
| if (label != NULL) |
| value = g_variant_new_string (label); |
| else |
| value = NULL; |
| |
| g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_LABEL, value); |
| } |
| |
| /** |
| * g_menu_item_set_submenu: |
| * @menu_item: a #GMenuItem |
| * @submenu: (nullable): a #GMenuModel, or %NULL |
| * |
| * Sets or unsets the "submenu" link of @menu_item to @submenu. |
| * |
| * If @submenu is non-%NULL, it is linked to. If it is %NULL then the |
| * link is unset. |
| * |
| * The effect of having one menu appear as a submenu of another is |
| * exactly as it sounds. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_submenu (GMenuItem *menu_item, |
| GMenuModel *submenu) |
| { |
| g_menu_item_set_link (menu_item, G_MENU_LINK_SUBMENU, submenu); |
| } |
| |
| /** |
| * g_menu_item_set_section: |
| * @menu_item: a #GMenuItem |
| * @section: (nullable): a #GMenuModel, or %NULL |
| * |
| * Sets or unsets the "section" link of @menu_item to @section. |
| * |
| * The effect of having one menu appear as a section of another is |
| * exactly as it sounds: the items from @section become a direct part of |
| * the menu that @menu_item is added to. See g_menu_item_new_section() |
| * for more information about what it means for a menu item to be a |
| * section. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_section (GMenuItem *menu_item, |
| GMenuModel *section) |
| { |
| g_menu_item_set_link (menu_item, G_MENU_LINK_SECTION, section); |
| } |
| |
| /** |
| * g_menu_item_set_action_and_target_value: |
| * @menu_item: a #GMenuItem |
| * @action: (nullable): the name of the action for this item |
| * @target_value: (nullable): a #GVariant to use as the action target |
| * |
| * Sets or unsets the "action" and "target" attributes of @menu_item. |
| * |
| * If @action is %NULL then both the "action" and "target" attributes |
| * are unset (and @target_value is ignored). |
| * |
| * If @action is non-%NULL then the "action" attribute is set. The |
| * "target" attribute is then set to the value of @target_value if it is |
| * non-%NULL or unset otherwise. |
| * |
| * Normal menu items (ie: not submenu, section or other custom item |
| * types) are expected to have the "action" attribute set to identify |
| * the action that they are associated with. The state type of the |
| * action help to determine the disposition of the menu item. See |
| * #GAction and #GActionGroup for an overview of actions. |
| * |
| * In general, clicking on the menu item will result in activation of |
| * the named action with the "target" attribute given as the parameter |
| * to the action invocation. If the "target" attribute is not set then |
| * the action is invoked with no parameter. |
| * |
| * If the action has no state then the menu item is usually drawn as a |
| * plain menu item (ie: with no additional decoration). |
| * |
| * If the action has a boolean state then the menu item is usually drawn |
| * as a toggle menu item (ie: with a checkmark or equivalent |
| * indication). The item should be marked as 'toggled' or 'checked' |
| * when the boolean state is %TRUE. |
| * |
| * If the action has a string state then the menu item is usually drawn |
| * as a radio menu item (ie: with a radio bullet or equivalent |
| * indication). The item should be marked as 'selected' when the string |
| * state is equal to the value of the @target property. |
| * |
| * See g_menu_item_set_action_and_target() or |
| * g_menu_item_set_detailed_action() for two equivalent calls that are |
| * probably more convenient for most uses. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_action_and_target_value (GMenuItem *menu_item, |
| const gchar *action, |
| GVariant *target_value) |
| { |
| GVariant *action_value; |
| |
| if (action != NULL) |
| { |
| action_value = g_variant_new_string (action); |
| } |
| else |
| { |
| action_value = NULL; |
| target_value = NULL; |
| } |
| |
| g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ACTION, action_value); |
| g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_TARGET, target_value); |
| } |
| |
| /** |
| * g_menu_item_set_action_and_target: |
| * @menu_item: a #GMenuItem |
| * @action: (nullable): the name of the action for this item |
| * @format_string: (nullable): a GVariant format string |
| * @...: positional parameters, as per @format_string |
| * |
| * Sets or unsets the "action" and "target" attributes of @menu_item. |
| * |
| * If @action is %NULL then both the "action" and "target" attributes |
| * are unset (and @format_string is ignored along with the positional |
| * parameters). |
| * |
| * If @action is non-%NULL then the "action" attribute is set. |
| * @format_string is then inspected. If it is non-%NULL then the proper |
| * position parameters are collected to create a #GVariant instance to |
| * use as the target value. If it is %NULL then the positional |
| * parameters are ignored and the "target" attribute is unset. |
| * |
| * See also g_menu_item_set_action_and_target_value() for an equivalent |
| * call that directly accepts a #GVariant. See |
| * g_menu_item_set_detailed_action() for a more convenient version that |
| * works with string-typed targets. |
| * |
| * See also g_menu_item_set_action_and_target_value() for a |
| * description of the semantics of the action and target attributes. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_action_and_target (GMenuItem *menu_item, |
| const gchar *action, |
| const gchar *format_string, |
| ...) |
| { |
| GVariant *value; |
| |
| if (format_string != NULL) |
| { |
| va_list ap; |
| |
| va_start (ap, format_string); |
| value = g_variant_new_va (format_string, NULL, &ap); |
| va_end (ap); |
| } |
| else |
| value = NULL; |
| |
| g_menu_item_set_action_and_target_value (menu_item, action, value); |
| } |
| |
| /** |
| * g_menu_item_set_detailed_action: |
| * @menu_item: a #GMenuItem |
| * @detailed_action: the "detailed" action string |
| * |
| * Sets the "action" and possibly the "target" attribute of @menu_item. |
| * |
| * The format of @detailed_action is the same format parsed by |
| * g_action_parse_detailed_name(). |
| * |
| * See g_menu_item_set_action_and_target() or |
| * g_menu_item_set_action_and_target_value() for more flexible (but |
| * slightly less convenient) alternatives. |
| * |
| * See also g_menu_item_set_action_and_target_value() for a description of |
| * the semantics of the action and target attributes. |
| * |
| * Since: 2.32 |
| */ |
| void |
| g_menu_item_set_detailed_action (GMenuItem *menu_item, |
| const gchar *detailed_action) |
| { |
| GError *error = NULL; |
| GVariant *target; |
| gchar *name; |
| |
| if (!g_action_parse_detailed_name (detailed_action, &name, &target, &error)) |
| g_error ("g_menu_item_set_detailed_action: %s", error->message); |
| |
| g_menu_item_set_action_and_target_value (menu_item, name, target); |
| if (target) |
| g_variant_unref (target); |
| g_free (name); |
| } |
| |
| /** |
| * g_menu_item_new: |
| * @label: (nullable): the section label, or %NULL |
| * @detailed_action: (nullable): the detailed action string, or %NULL |
| * |
| * Creates a new #GMenuItem. |
| * |
| * If @label is non-%NULL it is used to set the "label" attribute of the |
| * new item. |
| * |
| * If @detailed_action is non-%NULL it is used to set the "action" and |
| * possibly the "target" attribute of the new item. See |
| * g_menu_item_set_detailed_action() for more information. |
| * |
| * Returns: a new #GMenuItem |
| * |
| * Since: 2.32 |
| */ |
| GMenuItem * |
| g_menu_item_new (const gchar *label, |
| const gchar *detailed_action) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL); |
| |
| if (label != NULL) |
| g_menu_item_set_label (menu_item, label); |
| |
| if (detailed_action != NULL) |
| g_menu_item_set_detailed_action (menu_item, detailed_action); |
| |
| return menu_item; |
| } |
| |
| /** |
| * g_menu_item_new_submenu: |
| * @label: (nullable): the section label, or %NULL |
| * @submenu: a #GMenuModel with the items of the submenu |
| * |
| * Creates a new #GMenuItem representing a submenu. |
| * |
| * This is a convenience API around g_menu_item_new() and |
| * g_menu_item_set_submenu(). |
| * |
| * Returns: a new #GMenuItem |
| * |
| * Since: 2.32 |
| */ |
| GMenuItem * |
| g_menu_item_new_submenu (const gchar *label, |
| GMenuModel *submenu) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL); |
| |
| if (label != NULL) |
| g_menu_item_set_label (menu_item, label); |
| |
| g_menu_item_set_submenu (menu_item, submenu); |
| |
| return menu_item; |
| } |
| |
| /** |
| * g_menu_item_new_section: |
| * @label: (nullable): the section label, or %NULL |
| * @section: a #GMenuModel with the items of the section |
| * |
| * Creates a new #GMenuItem representing a section. |
| * |
| * This is a convenience API around g_menu_item_new() and |
| * g_menu_item_set_section(). |
| * |
| * The effect of having one menu appear as a section of another is |
| * exactly as it sounds: the items from @section become a direct part of |
| * the menu that @menu_item is added to. |
| * |
| * Visual separation is typically displayed between two non-empty |
| * sections. If @label is non-%NULL then it will be encorporated into |
| * this visual indication. This allows for labeled subsections of a |
| * menu. |
| * |
| * As a simple example, consider a typical "Edit" menu from a simple |
| * program. It probably contains an "Undo" and "Redo" item, followed by |
| * a separator, followed by "Cut", "Copy" and "Paste". |
| * |
| * This would be accomplished by creating three #GMenu instances. The |
| * first would be populated with the "Undo" and "Redo" items, and the |
| * second with the "Cut", "Copy" and "Paste" items. The first and |
| * second menus would then be added as submenus of the third. In XML |
| * format, this would look something like the following: |
| * |[ |
| * <menu id='edit-menu'> |
| * <section> |
| * <item label='Undo'/> |
| * <item label='Redo'/> |
| * </section> |
| * <section> |
| * <item label='Cut'/> |
| * <item label='Copy'/> |
| * <item label='Paste'/> |
| * </section> |
| * </menu> |
| * ]| |
| * |
| * The following example is exactly equivalent. It is more illustrative |
| * of the exact relationship between the menus and items (keeping in |
| * mind that the 'link' element defines a new menu that is linked to the |
| * containing one). The style of the second example is more verbose and |
| * difficult to read (and therefore not recommended except for the |
| * purpose of understanding what is really going on). |
| * |[ |
| * <menu id='edit-menu'> |
| * <item> |
| * <link name='section'> |
| * <item label='Undo'/> |
| * <item label='Redo'/> |
| * </link> |
| * </item> |
| * <item> |
| * <link name='section'> |
| * <item label='Cut'/> |
| * <item label='Copy'/> |
| * <item label='Paste'/> |
| * </link> |
| * </item> |
| * </menu> |
| * ]| |
| * |
| * Returns: a new #GMenuItem |
| * |
| * Since: 2.32 |
| */ |
| GMenuItem * |
| g_menu_item_new_section (const gchar *label, |
| GMenuModel *section) |
| { |
| GMenuItem *menu_item; |
| |
| menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL); |
| |
| if (label != NULL) |
| g_menu_item_set_label (menu_item, label); |
| |
| g_menu_item_set_section (menu_item, section); |
| |
| return menu_item; |
| } |
| |
| /** |
| * g_menu_item_new_from_model: |
| * @model: a #GMenuModel |
| * @item_index: the index of an item in @model |
| * |
| * Creates a #GMenuItem as an exact copy of an existing menu item in a |
| * #GMenuModel. |
| * |
| * @item_index must be valid (ie: be sure to call |
| * g_menu_model_get_n_items() first). |
| * |
| * Returns: a new #GMenuItem. |
| * |
| * Since: 2.34 |
| */ |
| GMenuItem * |
| g_menu_item_new_from_model (GMenuModel *model, |
| gint item_index) |
| { |
| GMenuModelClass *class = G_MENU_MODEL_GET_CLASS (model); |
| GMenuItem *menu_item; |
| |
| menu_item = g_object_new (G_TYPE_MENU_ITEM, NULL); |
| |
| /* With some trickery we can be pretty efficient. |
| * |
| * A GMenuModel must either implement iterate_item_attributes() or |
| * get_item_attributes(). If it implements get_item_attributes() then |
| * we are in luck -- we can just take a reference on the returned |
| * hashtable and mark ourselves as copy-on-write. |
| * |
| * In the case that the model is based on get_item_attributes (which |
| * is the case for both GMenu and GDBusMenuModel) then this is |
| * basically just g_hash_table_ref(). |
| */ |
| if (class->get_item_attributes) |
| { |
| GHashTable *attributes = NULL; |
| |
| class->get_item_attributes (model, item_index, &attributes); |
| if (attributes) |
| { |
| g_hash_table_unref (menu_item->attributes); |
| menu_item->attributes = attributes; |
| menu_item->cow = TRUE; |
| } |
| } |
| else |
| { |
| GMenuAttributeIter *iter; |
| const gchar *attribute; |
| GVariant *value; |
| |
| iter = g_menu_model_iterate_item_attributes (model, item_index); |
| while (g_menu_attribute_iter_get_next (iter, &attribute, &value)) |
| g_hash_table_insert (menu_item->attributes, g_strdup (attribute), value); |
| g_object_unref (iter); |
| } |
| |
| /* Same story for the links... */ |
| if (class->get_item_links) |
| { |
| GHashTable *links = NULL; |
| |
| class->get_item_links (model, item_index, &links); |
| if (links) |
| { |
| g_hash_table_unref (menu_item->links); |
| menu_item->links = links; |
| menu_item->cow = TRUE; |
| } |
| } |
| else |
| { |
| GMenuLinkIter *iter; |
| const gchar *link; |
| GMenuModel *value; |
| |
| iter = g_menu_model_iterate_item_links (model, item_index); |
| while (g_menu_link_iter_get_next (iter, &link, &value)) |
| g_hash_table_insert (menu_item->links, g_strdup (link), value); |
| g_object_unref (iter); |
| } |
| |
| return menu_item; |
| } |
| |
| /** |
| * g_menu_item_set_icon: |
| * @menu_item: a #GMenuItem |
| * @icon: a #GIcon, or %NULL |
| * |
| * Sets (or unsets) the icon on @menu_item. |
| * |
| * This call is the same as calling g_icon_serialize() and using the |
| * result as the value to g_menu_item_set_attribute_value() for |
| * %G_MENU_ATTRIBUTE_ICON. |
| * |
| * This API is only intended for use with "noun" menu items; things like |
| * bookmarks or applications in an "Open With" menu. Don't use it on |
| * menu items corresponding to verbs (eg: stock icons for 'Save' or |
| * 'Quit'). |
| * |
| * If @icon is %NULL then the icon is unset. |
| * |
| * Since: 2.38 |
| **/ |
| void |
| g_menu_item_set_icon (GMenuItem *menu_item, |
| GIcon *icon) |
| { |
| GVariant *value; |
| |
| g_return_if_fail (G_IS_MENU_ITEM (menu_item)); |
| g_return_if_fail (icon == NULL || G_IS_ICON (icon)); |
| |
| if (icon != NULL) |
| value = g_icon_serialize (icon); |
| else |
| value = NULL; |
| |
| g_menu_item_set_attribute_value (menu_item, G_MENU_ATTRIBUTE_ICON, value); |
| if (value) |
| g_variant_unref (value); |
| } |