/* * Copyright © 2011 Canonical Ltd. * * 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 . * * Author: Ryan Lortie */ #include "config.h" #include "gmenu.h" #include "gaction.h" #include #include "gicon.h" /** * SECTION:gmenu * @title: GMenu * @short_description: A simple implementation of GMenuModel * @include: gio/gio.h * * #GMenu is a simple implementation of #GMenuModel. * You populate a #GMenu by adding #GMenuItem instances to it. * * There are some convenience functions to allow you to directly * add items (avoiding #GMenuItem) for the common cases. To add * a regular item, use g_menu_insert(). To add a section, use * g_menu_insert_section(). To add a submenu, use * g_menu_insert_submenu(). */ /** * GMenu: * * #GMenu is an opaque structure type. You must access it using the * functions below. * * 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 || 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 && 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: (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: (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: * |[ * *
* * *
*
* * * *
*
* ]| * * 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). * |[ * * * * * * * * * * * * * * * * ]| * * 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); }