/* * 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 */ #include "gmenumodel.h" /** * SECTION:gmenumodel * @title: GMenuModel * @short_description: An abstract class representing the contents of a menu * @see_also: #GActionGroup * * #GMenuModel represents the contents of a menu -- an ordered list of * menu items. The items are associated with actions, which can be * activated through them. Items can be grouped in sections, and may * have submenus associated with them. Both items and sections usually * have some representation data, such as labels or icons. The type of * the associated action (ie whether it is stateful, and what kind of * state it has) can influence the representation of the item. * * The conceptual model of menus in #GMenuModel is hierarchical: * sections and submenus are again represented by #GMenuModels. * Menus themselves do not define their own roles. Rather, the role * of a particular #GMenuModel is defined by the item that references * it (or, in the case of the 'root' menu, is defined by the context * in which it is used). * * As an example, consider the visible portions of the menu in * . * * * * There are 8 "menus" visible in the screenshot: one menubar, two * submenus and 5 sections: * * the toplevel menubar (containing 4 items) * the View submenu (containing 3 sections) * the first section of the View submenu (containing 2 items) * the second section of the View submenu (containing 1 item) * the final section of the View submenu (containing 1 item) * the Highlight Mode submenu (containing 2 sections) * the Sources section (containing 2 items) * the Markup section (containing 2 items) * * * illustrates the conceptual connection between * these 8 menus. Each large block in the figure represents a menu and the * smaller blocks within the large block represent items in that menu. Some * items contain references to other menus. * * * * Notice that the separators visible in * appear nowhere in . This is because * separators are not explicitly represented in the menu model. Instead, * a separator is inserted between any two non-empty sections of a menu. * Section items can have labels just like any other item. In that case, * a display system may show a section header instead of a separator. * * The motivation for this abstract model of application controls is * that modern user interfaces tend to make these controls available * outside the application. Examples include global menus, jumplists, * dash boards, etc. To support such uses, it is necessary to 'export' * information about actions and their representation in menus, which * is exactly what the * GActionGroup exporter * and the * GMenuModel exporter * do for #GActionGroup and #GMenuModel. The client-side counterparts * to make use of the exported information are #GDBusActionGroup and * #GMenuProxy. * * The API of #GMenuModel is very generic, with iterators for the * attributes and links of an item, see g_menu_model_iterate_item_attributes() * and g_menu_model_iterate_item_links(). The 'standard' attributes and * link types have predefined names: %G_MENU_ATTRIBUTE_LABEL, * %G_MENU_ATTRIBUTE_ACTION, %G_MENU_ATTRIBUTE_TARGET, %G_MENU_LINK_SECTION * and %G_MENU_LINK_SUBMENU. * * FIXME: explain how items are associated with actions. * * While a wide variety of stateful actions is possible, the following * is the minimum that is expected to be supported by all users of exported * menu information: * * an action with no parameter type and no state * an action with no parameter type and boolean state * an action with string parameter type and string state * * * Stateless * * A stateless action typically corresponds to an ordinary menu item. * * * Selecting such a menu item will activate the action (with no parameter). * * * * Boolean State * * An action with a boolean state will most typically be used with a "toggle" * or "switch" menu item. The state can be set directly, but activating the * action (with no parameter) results in the state being toggled. * * * Selecting a toggle menu item will activate the action. The menu item should * be rendered as "checked" when the state is true. * * * * String Parameter and State * * Actions with string parameters and state will most typically be used to * represent an enumerated choice over the items available for a group of * radio menu items. Activating the action with a string parameter is * equivalent to setting that parameter as the state. * * * Radio menu items, in addition to being associated with the action, will * have a target value. Selecting that menu item will result in activation * of the action with the target value as the parameter. The menu item should * be rendered as "selected" when the state of the action is equal to the * target value of the menu item. * * */ /** * GMenuModel: * * #GMenuModel is an opaque structure type. You must access it using the * functions below. **/ /** * GMenuAttributeIter: * * #GMenuAttributeIter is an opaque structure type. You must access it * using the functions below. **/ /** * GMenuLinkIter: * * #GMenuLinkIter is an opaque structure type. You must access it using * the functions below. **/ typedef struct { GMenuLinkIter parent_instance; GHashTableIter iter; GHashTable *table; } GMenuLinkHashIter; typedef GMenuLinkIterClass GMenuLinkHashIterClass; G_DEFINE_TYPE (GMenuLinkHashIter, g_menu_link_hash_iter, G_TYPE_MENU_LINK_ITER) static gboolean g_menu_link_hash_iter_get_next (GMenuLinkIter *link_iter, const gchar **out_name, GMenuModel **value) { GMenuLinkHashIter *iter = (GMenuLinkHashIter *) link_iter; gpointer keyptr, valueptr; if (!g_hash_table_iter_next (&iter->iter, &keyptr, &valueptr)) return FALSE; *out_name = keyptr; *value = g_object_ref (valueptr); return TRUE; } static void g_menu_link_hash_iter_finalize (GObject *object) { GMenuLinkHashIter *iter = (GMenuLinkHashIter *) object; g_hash_table_unref (iter->table); G_OBJECT_CLASS (g_menu_link_hash_iter_parent_class) ->finalize (object); } static void g_menu_link_hash_iter_init (GMenuLinkHashIter *iter) { } static void g_menu_link_hash_iter_class_init (GMenuLinkHashIterClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = g_menu_link_hash_iter_finalize; class->get_next = g_menu_link_hash_iter_get_next; } typedef struct { GMenuAttributeIter parent_instance; GHashTableIter iter; GHashTable *table; } GMenuAttributeHashIter; typedef GMenuAttributeIterClass GMenuAttributeHashIterClass; G_DEFINE_TYPE (GMenuAttributeHashIter, g_menu_attribute_hash_iter, G_TYPE_MENU_ATTRIBUTE_ITER) static gboolean g_menu_attribute_hash_iter_get_next (GMenuAttributeIter *attr_iter, const gchar **name, GVariant **value) { GMenuAttributeHashIter *iter = (GMenuAttributeHashIter *) attr_iter; gpointer keyptr, valueptr; if (!g_hash_table_iter_next (&iter->iter, &keyptr, &valueptr)) return FALSE; *name = keyptr; *value = g_variant_ref (valueptr); return TRUE; } static void g_menu_attribute_hash_iter_finalize (GObject *object) { GMenuAttributeHashIter *iter = (GMenuAttributeHashIter *) object; g_hash_table_unref (iter->table); G_OBJECT_CLASS (g_menu_attribute_hash_iter_parent_class) ->finalize (object); } static void g_menu_attribute_hash_iter_init (GMenuAttributeHashIter *iter) { } static void g_menu_attribute_hash_iter_class_init (GMenuAttributeHashIterClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = g_menu_attribute_hash_iter_finalize; class->get_next = g_menu_attribute_hash_iter_get_next; } G_DEFINE_ABSTRACT_TYPE (GMenuModel, g_menu_model, G_TYPE_OBJECT) guint g_menu_model_items_changed_signal; static GMenuAttributeIter * g_menu_model_real_iterate_item_attributes (GMenuModel *model, gint item_index) { GHashTable *table = NULL; GMenuAttributeIter *result; G_MENU_MODEL_GET_CLASS (model)->get_item_attributes (model, item_index, &table); if (table) { GMenuAttributeHashIter *iter = g_object_new (g_menu_attribute_hash_iter_get_type (), NULL); g_hash_table_iter_init (&iter->iter, table); iter->table = g_hash_table_ref (table); result = G_MENU_ATTRIBUTE_ITER (iter); } else { g_critical ("GMenuModel implementation '%s' doesn't override iterate_item_attributes() " "and fails to return sane values from get_item_attributes()", G_OBJECT_TYPE_NAME (model)); result = NULL; } if (table != NULL) g_hash_table_unref (table); return result; } static GVariant * g_menu_model_real_get_item_attribute_value (GMenuModel *model, gint item_index, const gchar *attribute, const GVariantType *expected_type) { GHashTable *table = NULL; GVariant *value = NULL; G_MENU_MODEL_GET_CLASS (model) ->get_item_attributes (model, item_index, &table); if (table != NULL) { value = g_hash_table_lookup (table, attribute); if (value != NULL) { if (expected_type == NULL || g_variant_is_of_type (value, expected_type)) value = g_variant_ref (value); else value = NULL; } } else g_assert_not_reached (); if (table != NULL) g_hash_table_unref (table); return value; } static GMenuLinkIter * g_menu_model_real_iterate_item_links (GMenuModel *model, gint item_index) { GHashTable *table = NULL; GMenuLinkIter *result; G_MENU_MODEL_GET_CLASS (model) ->get_item_links (model, item_index, &table); if (table) { GMenuLinkHashIter *iter = g_object_new (g_menu_link_hash_iter_get_type (), NULL); g_hash_table_iter_init (&iter->iter, table); iter->table = g_hash_table_ref (table); result = G_MENU_LINK_ITER (iter); } else { g_critical ("GMenuModel implementation '%s' doesn't override iterate_item_links() " "and fails to return sane values from get_item_links()", G_OBJECT_TYPE_NAME (model)); result = NULL; } if (table != NULL) g_hash_table_unref (table); return result; } static GMenuModel * g_menu_model_real_get_item_link (GMenuModel *model, gint item_index, const gchar *link) { GHashTable *table = NULL; GMenuModel *value = NULL; G_MENU_MODEL_GET_CLASS (model) ->get_item_links (model, item_index, &table); if (table != NULL) value = g_hash_table_lookup (table, link); else g_assert_not_reached (); if (value != NULL) g_object_ref (value); if (table != NULL) g_hash_table_unref (table); return value; } static void g_menu_model_init (GMenuModel *model) { } static void g_menu_model_class_init (GMenuModelClass *class) { class->iterate_item_attributes = g_menu_model_real_iterate_item_attributes; class->get_item_attribute_value = g_menu_model_real_get_item_attribute_value; class->iterate_item_links = g_menu_model_real_iterate_item_links; class->get_item_link = g_menu_model_real_get_item_link; /** * GMenuModel::items-changed: * @model: the #GMenuModel that is changing * @position: the position of the change * @removed: the number of items removed * @added: the number of items added * * Emitted when a change has occured to the menu. * * The only changes that can occur to a menu is that items are removed * or added. Items may not change (except by being removed and added * back in the same location). This signal is capable of describing * both of those changes (at the same time). * * The signal means that starting at the index @position, @removed * items were removed and @added items were added in their place. If * @removed is zero then only items were added. If @added is zero * then only items were removed. * * As an example, if the menu contains items a, b, c, d (in that * order) and the signal (2, 1, 3) occurs then the new composition of * the menu will be a, b, _, _, _, d (with each _ representing some * new item). * * Signal handlers may query the model (particularly the added items) * and expect to see the results of the modification that is being * reported. The signal is emitted after the modification. **/ g_menu_model_items_changed_signal = g_signal_new ("items-changed", G_TYPE_MENU_MODEL, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); } /** * g_menu_model_is_mutable: * @model: a #GMenuModel * * Queries if @model is mutable. * * An immutable #GMenuModel will never emit the #GMenuModel::items-changed * signal. Consumers of the model may make optimisations accordingly. * * Returns: %TRUE if the model is mutable (ie: "items-changed" may be * emitted). **/ gboolean g_menu_model_is_mutable (GMenuModel *model) { return G_MENU_MODEL_GET_CLASS (model) ->is_mutable (model); } /** * g_menu_model_get_n_items: * @model: a #GMenuModel * * Query the number of items in @model. * * Returns: the number of items **/ gint g_menu_model_get_n_items (GMenuModel *model) { return G_MENU_MODEL_GET_CLASS (model) ->get_n_items (model); } /** * g_menu_model_iterate_item_attributes: * @model: a #GMenuModel * @item_index: the index of the item * * Creates a #GMenuAttributeIter to iterate over the attributes of * the item at position @item_index in @model. * * You must free the iterator with g_object_unref() when you are done. * * Returns: (transfer full): a new #GMenuAttributeIter */ GMenuAttributeIter * g_menu_model_iterate_item_attributes (GMenuModel *model, gint item_index) { return G_MENU_MODEL_GET_CLASS (model) ->iterate_item_attributes (model, item_index); } /** * g_menu_model_get_item_attribute_value: * @model: a #GMenuModel * @item_index: the index of the item * @attribute: the attribute to query * @expected_type: (allow-none): the expected type of the attribute, or * %NULL * * Queries the item at position @item_index in @model for the attribute * specified by @attribute. * * If @expected_type is non-%NULL then it specifies the expected type of * the attribute. If it is %NULL then any type will be accepted. * * If the attribute exists and matches @expected_type (or if the * expected type is unspecified) then the value is returned. * * If the attribute does not exist, or does not match the expected type * then %NULL is returned. * * Returns: (transfer full): the value of the attribute **/ GVariant * g_menu_model_get_item_attribute_value (GMenuModel *model, gint item_index, const gchar *attribute, const GVariantType *expected_type) { return G_MENU_MODEL_GET_CLASS (model) ->get_item_attribute_value (model, item_index, attribute, expected_type); } /** * g_menu_model_get_item_attribute: * @model: a #GMenuModel * @item_index: the index of the item * @attribute: the attribute to query * @format_string: a #GVariant format string * @...: positional parameters, as per @format_string * * Queries item at position @item_index in @model for the attribute * specified by @attribute. * * 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 **/ gboolean g_menu_model_get_item_attribute (GMenuModel *model, gint item_index, const gchar *attribute, const gchar *format_string, ...) { const GVariantType *expected_type; GVariant *value; va_list ap; expected_type = NULL; /* XXX devine the type, ensure no '&' */ value = g_menu_model_get_item_attribute_value (model, item_index, attribute, expected_type); if (value == NULL) return FALSE; va_start (ap, format_string); g_variant_get_va (value, format_string, NULL, &ap); g_variant_unref (value); va_end (ap); return TRUE; } /** * g_menu_model_iterate_item_links: * @model: a #GMenuModel * @item_index: the index of the item * * Creates a #GMenuLinkIter to iterate over the links of the item at * position @item_index in @model. * * You must free the iterator with g_object_unref() when you are done. * * Returns: (transfer full): a new #GMenuLinkIter **/ GMenuLinkIter * g_menu_model_iterate_item_links (GMenuModel *model, gint item_index) { return G_MENU_MODEL_GET_CLASS (model) ->iterate_item_links (model, item_index); } /** * g_menu_model_get_item_link: * @model: a #GMenuModel * @item_index: the index of the item * @link: the link to query * * Queries the item at position @item_index in @model for the link * specified by @link. * * If the link exists, the linked #GMenuModel is returned. If the link * does not exist, %NULL is returned. * * Returns: (transfer full): the linked #GMenuModel, or %NULL **/ GMenuModel * g_menu_model_get_item_link (GMenuModel *model, gint item_index, const gchar *link) { return G_MENU_MODEL_GET_CLASS (model) ->get_item_link (model, item_index, link); } /** * g_menu_model_items_changed: * @model: a #GMenuModel * @position: the position of the change * @removed: the number of items removed * @added: the number of items added * * Requests emission of the #GMenuModel::items-changed signal on @model. * * This function should never be called except by #GMenuModel * subclasses. Any other calls to this function will very likely lead * to a violation of the interface of the model. * * The implementation should update its internal representation of the * menu before emitting the signal. The implementation should further * expect to receive queries about the new state of the menu (and * particularly added menu items) while signal handlers are running. * * The implementation must dispatch this call directly from a mainloop * entry and not in response to calls -- particularly those from the * #GMenuModel API. Said another way: the menu must not change while * user code is running without returning to the mainloop. **/ void g_menu_model_items_changed (GMenuModel *model, gint position, gint removed, gint added) { g_signal_emit (model, g_menu_model_items_changed_signal, 0, position, removed, added); } G_DEFINE_ABSTRACT_TYPE (GMenuAttributeIter, g_menu_attribute_iter, G_TYPE_OBJECT) struct _GMenuAttributeIterPrivate { GQuark name; GVariant *value; gboolean valid; }; /** * g_menu_attribute_iter_get_next: * @iter: a #GMenuAttributeIter * @out_name: (out) (allow-none) (transfer none): the type of the attribute * @value: (out) (allow-none) (transfer full): the attribute value * * This function combines g_menu_attribute_iter_next() with * g_menu_attribute_iter_get_name() and g_menu_attribute_iter_get_value(). * * First the iterator is advanced to the next (possibly first) attribute. * If that fails, then %FALSE is returned and there are no other * effects. * * If successful, @name and @value are set to the name and value of the * attribute that has just been advanced to. At this point, * g_menu_item_get_name() and g_menu_item_get_value() will return the * same values again. * * The value returned in @name remains valid for as long as the iterator * remains at the current position. The value returned in @value must * be unreffed using g_variant_unref() when it is no longer in use. * * Returns: %TRUE on success, or %FALSE if there is no additional * attribute **/ gboolean g_menu_attribute_iter_get_next (GMenuAttributeIter *iter, const gchar **out_name, GVariant **value) { const gchar *name; if (iter->priv->value) { g_variant_unref (iter->priv->value); iter->priv->value = NULL; } iter->priv->valid = G_MENU_ATTRIBUTE_ITER_GET_CLASS (iter) ->get_next (iter, &name, &iter->priv->value); if (iter->priv->valid) { iter->priv->name = g_quark_from_string (name); if (out_name) *out_name = g_quark_to_string (iter->priv->name); if (value) *value = g_variant_ref (iter->priv->value); } return iter->priv->valid; } /** * g_menu_attribute_iter_next: * @iter: a #GMenuAttributeIter * * Attempts to advance the iterator to the next (possibly first) * attribute. * * %TRUE is returned on success, or %FALSE if there are no more * attributes. * * You must call this function when you first acquire the iterator * to advance it to the first attribute (and determine if the first * attribute exists at all). * * Returns: %TRUE on success, or %FALSE when there are no more attributes **/ gboolean g_menu_attribute_iter_next (GMenuAttributeIter *iter) { return g_menu_attribute_iter_get_next (iter, NULL, NULL); } /** * g_menu_attribute_iter_get_name: * @iter: a #GMenuAttributeIter * * Gets the name of the attribute at the current iterator position, as * a string. * * The iterator is not advanced. * * Returns: the name of the attribute **/ const gchar * g_menu_attribute_iter_get_name (GMenuAttributeIter *iter) { g_return_val_if_fail (iter->priv->valid, 0); return g_quark_to_string (iter->priv->name); } /** * g_menu_attribute_iter_get_value: * @iter: a #GMenuAttributeIter * * Gets the value of the attribute at the current iterator position. * * The iterator is not advanced. * * Returns: (transfer full): the value of the current attribute **/ GVariant * g_menu_attribute_iter_get_value (GMenuAttributeIter *iter) { g_return_val_if_fail (iter->priv->valid, NULL); return g_variant_ref (iter->priv->value); } static void g_menu_attribute_iter_finalize (GObject *object) { GMenuAttributeIter *iter = G_MENU_ATTRIBUTE_ITER (object); if (iter->priv->value) g_variant_unref (iter->priv->value); G_OBJECT_CLASS (g_menu_attribute_iter_parent_class) ->finalize (object); } static void g_menu_attribute_iter_init (GMenuAttributeIter *iter) { iter->priv = G_TYPE_INSTANCE_GET_PRIVATE (iter, G_TYPE_MENU_ATTRIBUTE_ITER, GMenuAttributeIterPrivate); } static void g_menu_attribute_iter_class_init (GMenuAttributeIterClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = g_menu_attribute_iter_finalize; g_type_class_add_private (class, sizeof (GMenuAttributeIterPrivate)); } G_DEFINE_ABSTRACT_TYPE (GMenuLinkIter, g_menu_link_iter, G_TYPE_OBJECT) struct _GMenuLinkIterPrivate { GQuark name; GMenuModel *value; gboolean valid; }; /** * g_menu_link_iter_get_next: * @iter: a #GMenuLinkIter * @out_link: (out) (allow-none) (transfer none): the name of the link * @value: (out) (allow-none) (transfer full): the linked #GMenuModel * * This function combines g_menu_link_iter_next() with * g_menu_link_iter_get_name() and g_menu_link_iter_get_value(). * * First the iterator is advanced to the next (possibly first) link. * If that fails, then %FALSE is returned and there are no other effects. * * If successful, @out_link and @value are set to the name and #GMenuModel * of the link that has just been advanced to. At this point, * g_menu_item_get_name() and g_menu_item_get_value() will return the * same values again. * * The value returned in @out_link remains valid for as long as the iterator * remains at the current position. The value returned in @value must * be unreffed using g_object_unref() when it is no longer in use. * * Returns: %TRUE on success, or %FALSE if there is no additional link **/ gboolean g_menu_link_iter_get_next (GMenuLinkIter *iter, const gchar **out_link, GMenuModel **value) { const gchar *name; if (iter->priv->value) { g_object_unref (iter->priv->value); iter->priv->value = NULL; } iter->priv->valid = G_MENU_LINK_ITER_GET_CLASS (iter) ->get_next (iter, &name, &iter->priv->value); if (iter->priv->valid) { g_assert (name != NULL); iter->priv->name = g_quark_from_string (name); if (out_link) *out_link = g_quark_to_string (iter->priv->name); if (value) *value = g_object_ref (iter->priv->value); } return iter->priv->valid; } /** * g_menu_link_iter_next: * @iter: a #GMenuLinkIter * * Attempts to advance the iterator to the next (possibly first) * link. * * %TRUE is returned on success, or %FALSE if there are no more links. * * You must call this function when you first acquire the iterator to * advance it to the first link (and determine if the first link exists * at all). * * Returns: %TRUE on success, or %FALSE when there are no more links **/ gboolean g_menu_link_iter_next (GMenuLinkIter *iter) { return g_menu_link_iter_get_next (iter, NULL, NULL); } /** * g_menu_link_iter_get_name: * @iter: a #GMenuLinkIter * * Gets the name of the link at the current iterator position. * * The iterator is not advanced. * * Returns: the type of the link **/ const gchar * g_menu_link_iter_get_name (GMenuLinkIter *iter) { g_return_val_if_fail (iter->priv->valid, 0); return g_quark_to_string (iter->priv->name); } /** * g_menu_link_iter_get_value: * @iter: a #GMenuLinkIter * * Gets the linked #GMenuModel at the current iterator position. * * The iterator is not advanced. * * Returns: (transfer full): the #GMenuModel that is linked to **/ GMenuModel * g_menu_link_iter_get_value (GMenuLinkIter *iter) { g_return_val_if_fail (iter->priv->valid, NULL); return g_object_ref (iter->priv->value); } static void g_menu_link_iter_finalize (GObject *object) { GMenuLinkIter *iter = G_MENU_LINK_ITER (object); if (iter->priv->value) g_object_unref (iter->priv->value); G_OBJECT_CLASS (g_menu_link_iter_parent_class) ->finalize (object); } static void g_menu_link_iter_init (GMenuLinkIter *iter) { iter->priv = G_TYPE_INSTANCE_GET_PRIVATE (iter, G_TYPE_MENU_LINK_ITER, GMenuLinkIterPrivate); } static void g_menu_link_iter_class_init (GMenuLinkIterClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = g_menu_link_iter_finalize; g_type_class_add_private (class, sizeof (GMenuLinkIterPrivate)); }