mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 07:26:15 +01:00
6798fcdd0f
Add g_menu_item_new_from_model() for constructing a GMenuItem that is a copy of a menu item that's in a GMenuModel.
1337 lines
37 KiB
C
1337 lines
37 KiB
C
/*
|
|
* 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 of the
|
|
* licence, 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
* USA.
|
|
*
|
|
* Author: Ryan Lortie <desrt@desrt.ca>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gmenu.h"
|
|
|
|
#include <string.h>
|
|
|
|
/**
|
|
* SECTION:gmenu
|
|
* @title: GMenu
|
|
* @short_description: A simple implementation of GMenuModel
|
|
*
|
|
* #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: (allow-none): the section label, or %NULL
|
|
* @detailed_action: (allow-none): 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: (allow-none): the section label, or %NULL
|
|
* @detailed_action: (allow-none): 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: (allow-none): the section label, or %NULL
|
|
* @detailed_action: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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);
|
|
}
|
|
|
|
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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): 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: (allow-none): the name of the action for this item
|
|
* @target_value: (allow-none): 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: (allow-none): the name of the action for this item
|
|
* @format_string: (allow-none): 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.
|
|
*
|
|
* If @detailed_action contains a double colon ("::") then it is used as
|
|
* a separator between an action name and a target string. In this
|
|
* case, this call is equivalent to calling
|
|
* g_menu_item_set_action_and_target() with the part before the "::" and
|
|
* with a string-type #GVariant containing the part following the "::".
|
|
*
|
|
* If @detailed_action doesn't contain "::" then the action is set to
|
|
* the given string (verbatim) and the target value is unset.
|
|
*
|
|
* 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)
|
|
{
|
|
const gchar *sep;
|
|
|
|
sep = strstr (detailed_action, "::");
|
|
|
|
if (sep != NULL)
|
|
{
|
|
gchar *action;
|
|
|
|
action = g_strndup (detailed_action, sep - detailed_action);
|
|
g_menu_item_set_action_and_target (menu_item, action, "s", sep + 2);
|
|
g_free (action);
|
|
}
|
|
|
|
else
|
|
g_menu_item_set_action_and_target_value (menu_item, detailed_action, NULL);
|
|
}
|
|
|
|
/**
|
|
* g_menu_item_new:
|
|
* @label: (allow-none): the section label, or %NULL
|
|
* @detailed_action: (allow-none): 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: (allow-none): 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: (allow-none): 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:
|
|
*
|
|
* <informalexample><programlisting><![CDATA[
|
|
* <menu id='edit-menu'>
|
|
* <section>
|
|
* <item label='Undo'/>
|
|
* <item label='Redo'/>
|
|
* </section>
|
|
* <section>
|
|
* <item label='Cut'/>
|
|
* <item label='Copy'/>
|
|
* <item label='Paste'/>
|
|
* </section>
|
|
* </menu>
|
|
* ]]></programlisting></informalexample>
|
|
*
|
|
* 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).
|
|
*
|
|
* <informalexample><programlisting><![CDATA[
|
|
* <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>
|
|
* ]]></programlisting></informalexample>
|
|
*
|
|
* 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;
|
|
}
|