/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * GtkItemFactory: Flexible item factory with automatic rc handling * Copyright (C) 1998 Tim Janik * * 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 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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" #include "gtkitemfactory.h" #include "gtk/gtksignal.h" #include "gtk/gtkoptionmenu.h" #include "gtk/gtkmenubar.h" #include "gtk/gtkmenu.h" #include "gtk/gtkmenuitem.h" #include "gtk/gtkradiomenuitem.h" #include "gtk/gtkcheckmenuitem.h" #include "gtk/gtkimagemenuitem.h" #include "gtk/gtktearoffmenuitem.h" #include "gtk/gtkaccelmap.h" #include "gtk/gtkaccellabel.h" #include "gdk/gdkkeysyms.h" #include "gtk/gtkimage.h" #include "gtk/gtkstock.h" #include "gtk/gtkiconfactory.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include /* --- defines --- */ #define ITEM_FACTORY_STRING ((gchar*) item_factory_string) #define ITEM_BLOCK_SIZE (128) /* --- structures --- */ typedef struct _GtkIFCBData GtkIFCBData; typedef struct _GtkIFDumpData GtkIFDumpData; struct _GtkIFCBData { GtkItemFactoryCallback func; guint callback_type; gpointer func_data; guint callback_action; }; /* --- prototypes --- */ static void gtk_item_factory_class_init (GtkItemFactoryClass *klass); static void gtk_item_factory_init (GtkItemFactory *ifactory); static void gtk_item_factory_destroy (GtkObject *object); static void gtk_item_factory_finalize (GObject *object); /* --- static variables --- */ static GtkItemFactoryClass *gtk_item_factory_class = NULL; static gpointer parent_class = NULL; static const gchar *item_factory_string = "Gtk-"; static GMemChunk *ifactory_item_chunks = NULL; static GMemChunk *ifactory_cb_data_chunks = NULL; static GQuark quark_popup_data = 0; static GQuark quark_if_menu_pos = 0; static GQuark quark_item_factory = 0; static GQuark quark_item_path = 0; static GQuark quark_action = 0; static GQuark quark_accel_group = 0; static GQuark quark_type_item = 0; static GQuark quark_type_title = 0; static GQuark quark_type_radio_item = 0; static GQuark quark_type_check_item = 0; static GQuark quark_type_toggle_item = 0; static GQuark quark_type_image_item = 0; static GQuark quark_type_stock_item = 0; static GQuark quark_type_tearoff_item = 0; static GQuark quark_type_separator_item = 0; static GQuark quark_type_branch = 0; static GQuark quark_type_last_branch = 0; /* --- functions --- */ GtkType gtk_item_factory_get_type (void) { static GtkType item_factory_type = 0; if (!item_factory_type) { static const GtkTypeInfo item_factory_info = { "GtkItemFactory", sizeof (GtkItemFactory), sizeof (GtkItemFactoryClass), (GtkClassInitFunc) gtk_item_factory_class_init, (GtkObjectInitFunc) gtk_item_factory_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; item_factory_type = gtk_type_unique (GTK_TYPE_OBJECT, &item_factory_info); } return item_factory_type; } static void gtk_item_factory_class_init (GtkItemFactoryClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); gtk_item_factory_class = class; parent_class = g_type_class_peek_parent (class); gobject_class->finalize = gtk_item_factory_finalize; object_class->destroy = gtk_item_factory_destroy; class->item_ht = g_hash_table_new (g_str_hash, g_str_equal); ifactory_item_chunks = g_mem_chunk_new ("GtkItemFactoryItem", sizeof (GtkItemFactoryItem), sizeof (GtkItemFactoryItem) * ITEM_BLOCK_SIZE, G_ALLOC_ONLY); ifactory_cb_data_chunks = g_mem_chunk_new ("GtkIFCBData", sizeof (GtkIFCBData), sizeof (GtkIFCBData) * ITEM_BLOCK_SIZE, G_ALLOC_AND_FREE); quark_popup_data = g_quark_from_static_string ("GtkItemFactory-popup-data"); quark_if_menu_pos = g_quark_from_static_string ("GtkItemFactory-menu-position"); quark_item_factory = g_quark_from_static_string ("GtkItemFactory"); quark_item_path = g_quark_from_static_string ("GtkItemFactory-path"); quark_action = g_quark_from_static_string ("GtkItemFactory-action"); quark_accel_group = g_quark_from_static_string ("GtkAccelGroup"); quark_type_item = g_quark_from_static_string (""); quark_type_title = g_quark_from_static_string (""); quark_type_radio_item = g_quark_from_static_string ("<RadioItem>"); quark_type_check_item = g_quark_from_static_string ("<CheckItem>"); quark_type_toggle_item = g_quark_from_static_string ("<ToggleItem>"); quark_type_image_item = g_quark_from_static_string ("<ImageItem>"); quark_type_stock_item = g_quark_from_static_string ("<StockItem>"); quark_type_separator_item = g_quark_from_static_string ("<Separator>"); quark_type_tearoff_item = g_quark_from_static_string ("<Tearoff>"); quark_type_branch = g_quark_from_static_string ("<Branch>"); quark_type_last_branch = g_quark_from_static_string ("<LastBranch>"); } static void gtk_item_factory_init (GtkItemFactory *ifactory) { GtkObject *object; object = GTK_OBJECT (ifactory); ifactory->path = NULL; ifactory->accel_group = NULL; ifactory->widget = NULL; ifactory->items = NULL; ifactory->translate_func = NULL; ifactory->translate_data = NULL; ifactory->translate_notify = NULL; } /** * gtk_item_factory_new: * @container_type: the kind of menu to create; can be * #GTK_TYPE_MENU_BAR, #GTK_TYPE_MENU or #GTK_TYPE_OPTION_MENU * @path: the factory path of the new item factory, a string of the form * <literal>"<name>"</literal> * @accel_group: a #GtkAccelGroup to which the accelerators for the * menu items will be added, or %NULL to create a new one * @returns: a new #GtkItemFactory * * Creates a new #GtkItemFactory. */ GtkItemFactory* gtk_item_factory_new (GtkType container_type, const gchar *path, GtkAccelGroup *accel_group) { GtkItemFactory *ifactory; g_return_val_if_fail (path != NULL, NULL); ifactory = gtk_type_new (GTK_TYPE_ITEM_FACTORY); gtk_item_factory_construct (ifactory, container_type, path, accel_group); return ifactory; } static void gtk_item_factory_callback_marshal (GtkWidget *widget, gpointer func_data) { GtkIFCBData *data; data = func_data; if (data->callback_type == 1) { GtkItemFactoryCallback1 func1 = (GtkItemFactoryCallback1) data->func; func1 (data->func_data, data->callback_action, widget); } else if (data->callback_type == 2) { GtkItemFactoryCallback2 func2 = (GtkItemFactoryCallback2) data->func; func2 (widget, data->func_data, data->callback_action); } } static void gtk_item_factory_item_remove_widget (GtkWidget *widget, GtkItemFactoryItem *item) { item->widgets = g_slist_remove (item->widgets, widget); gtk_object_remove_data_by_id (GTK_OBJECT (widget), quark_item_factory); gtk_object_remove_data_by_id (GTK_OBJECT (widget), quark_item_path); } /** * gtk_item_factory_add_foreign: * @accel_widget: widget to install an accelerator on * @full_path: the full path for the @accel_widget * @accel_group: the accelerator group to install the accelerator in * @keyval: key value of the accelerator * @modifiers: modifier combination of the accelerator * * Installs an accelerator for @accel_widget in @accel_group, that causes * the ::activate signal to be emitted if the accelerator is activated. * * This function can be used to make widgets participate in the accel * saving/restoring functionality provided by gtk_accel_map_save() and * gtk_accel_map_load(), even if they haven't been created by an item * factory. The recommended API for this purpose are the functions * gtk_menu_item_set_accel_path() and gtk_widget_set_accel_path(); don't * use gtk_item_factory_add_foreign() in new code, since it is likely to * be removed in the future. */ void gtk_item_factory_add_foreign (GtkWidget *accel_widget, const gchar *full_path, GtkAccelGroup *accel_group, guint keyval, GdkModifierType modifiers) { GtkItemFactoryClass *class; GtkItemFactoryItem *item; g_return_if_fail (GTK_IS_WIDGET (accel_widget)); g_return_if_fail (full_path != NULL); class = gtk_type_class (GTK_TYPE_ITEM_FACTORY); keyval = keyval != GDK_VoidSymbol ? keyval : 0; item = g_hash_table_lookup (class->item_ht, full_path); if (!item) { item = g_chunk_new (GtkItemFactoryItem, ifactory_item_chunks); item->path = g_strdup (full_path); item->widgets = NULL; g_hash_table_insert (class->item_ht, item->path, item); } item->widgets = g_slist_prepend (item->widgets, accel_widget); gtk_signal_connect (GTK_OBJECT (accel_widget), "destroy", GTK_SIGNAL_FUNC (gtk_item_factory_item_remove_widget), item); /* set the item path for the widget */ gtk_object_set_data_by_id (GTK_OBJECT (accel_widget), quark_item_path, item->path); gtk_widget_set_name (accel_widget, item->path); if (accel_group) { gtk_accel_group_ref (accel_group); gtk_object_set_data_by_id_full (GTK_OBJECT (accel_widget), quark_accel_group, accel_group, (GtkDestroyNotify) gtk_accel_group_unref); } else gtk_object_set_data_by_id (GTK_OBJECT (accel_widget), quark_accel_group, NULL); /* install defined accelerators */ if (gtk_signal_lookup ("activate", GTK_OBJECT_TYPE (accel_widget))) { if (accel_group) { gtk_accel_map_add_entry (full_path, keyval, modifiers); gtk_widget_set_accel_path (accel_widget, full_path, accel_group); } } } static void ifactory_cb_data_free (gpointer mem) { g_mem_chunk_free (ifactory_cb_data_chunks, mem); } static void gtk_item_factory_add_item (GtkItemFactory *ifactory, const gchar *path, const gchar *accelerator, GtkItemFactoryCallback callback, guint callback_action, gpointer callback_data, guint callback_type, gchar *item_type, GtkWidget *widget) { GtkItemFactoryClass *class; GtkItemFactoryItem *item; gchar *fpath; guint keyval; GdkModifierType mods; g_return_if_fail (widget != NULL); g_return_if_fail (item_type != NULL); class = GTK_ITEM_FACTORY_GET_CLASS (ifactory); /* set accelerator group on menu widgets */ if (GTK_IS_MENU (widget)) gtk_menu_set_accel_group ((GtkMenu*) widget, ifactory->accel_group); /* connect callback if neccessary */ if (callback) { GtkIFCBData *data; data = g_chunk_new (GtkIFCBData, ifactory_cb_data_chunks); data->func = callback; data->callback_type = callback_type; data->func_data = callback_data; data->callback_action = callback_action; gtk_object_weakref (GTK_OBJECT (widget), ifactory_cb_data_free, data); gtk_signal_connect (GTK_OBJECT (widget), "activate", GTK_SIGNAL_FUNC (gtk_item_factory_callback_marshal), data); } /* link the widget into its item-entry * and keep back pointer on both the item factory and the widget */ gtk_object_set_data_by_id (GTK_OBJECT (widget), quark_action, GUINT_TO_POINTER (callback_action)); gtk_object_set_data_by_id (GTK_OBJECT (widget), quark_item_factory, ifactory); if (accelerator) gtk_accelerator_parse (accelerator, &keyval, &mods); else { keyval = 0; mods = 0; } fpath = g_strconcat (ifactory->path, path, NULL); gtk_item_factory_add_foreign (widget, fpath, ifactory->accel_group, keyval, mods); item = g_hash_table_lookup (class->item_ht, fpath); g_free (fpath); g_return_if_fail (item != NULL); if (!g_slist_find (ifactory->items, item)) ifactory->items = g_slist_prepend (ifactory->items, item); } /** * gtk_item_factory_construct: * @ifactory: a #GtkItemFactory * @container_type: the kind of menu to create; can be * #GTK_TYPE_MENU_BAR, #GTK_TYPE_MENU or #GTK_TYPE_OPTION_MENU * @path: the factory path of @ifactory, a string of the form * <literal>"<name>"</literal> * @accel_group: a #GtkAccelGroup to which the accelerators for the * menu items will be added, or %NULL to create a new one * * Initializes an item factory. */ void gtk_item_factory_construct (GtkItemFactory *ifactory, GtkType container_type, const gchar *path, GtkAccelGroup *accel_group) { guint len; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (ifactory->accel_group == NULL); g_return_if_fail (path != NULL); if (!gtk_type_is_a (container_type, GTK_TYPE_OPTION_MENU)) g_return_if_fail (gtk_type_is_a (container_type, GTK_TYPE_MENU_SHELL)); len = strlen (path); if (path[0] != '<' && path[len - 1] != '>') { g_warning ("GtkItemFactory: invalid factory path `%s'", path); return; } if (accel_group) { ifactory->accel_group = accel_group; gtk_accel_group_ref (ifactory->accel_group); } else ifactory->accel_group = gtk_accel_group_new (); ifactory->path = g_strdup (path); ifactory->widget = g_object_connect (gtk_widget_new (container_type, NULL), "signal::destroy", gtk_widget_destroyed, &ifactory->widget, NULL); gtk_object_ref (GTK_OBJECT (ifactory)); gtk_object_sink (GTK_OBJECT (ifactory)); gtk_item_factory_add_item (ifactory, "", NULL, NULL, 0, NULL, 0, ITEM_FACTORY_STRING, ifactory->widget); } /** * gtk_item_factory_from_path: * @path: a string starting with a factory path of the form * <literal>"<name>"</literal> * @returns: the #GtkItemFactory created for the given factory path, or %NULL * * Finds an item factory which has been constructed using the * <literal>"<name>"</literal> prefix of @path as the @path argument * for gtk_item_factory_new(). */ GtkItemFactory* gtk_item_factory_from_path (const gchar *path) { GtkItemFactoryClass *class; GtkItemFactoryItem *item; gchar *fname; guint i; g_return_val_if_fail (path != NULL, NULL); g_return_val_if_fail (path[0] == '<', NULL); class = gtk_type_class (GTK_TYPE_ITEM_FACTORY); i = 0; while (path[i] && path[i] != '>') i++; if (path[i] != '>') { g_warning ("gtk_item_factory_from_path(): invalid factory path \"%s\"", path); return NULL; } fname = g_new (gchar, i + 2); g_memmove (fname, path, i + 1); fname[i + 1] = 0; item = g_hash_table_lookup (class->item_ht, fname); g_free (fname); if (item && item->widgets) return gtk_item_factory_from_widget (item->widgets->data); return NULL; } static void gtk_item_factory_destroy (GtkObject *object) { GtkItemFactory *ifactory; GSList *slist; g_return_if_fail (GTK_IS_ITEM_FACTORY (object)); ifactory = (GtkItemFactory*) object; if (ifactory->widget) { GtkObject *dobj; dobj = GTK_OBJECT (ifactory->widget); gtk_object_ref (dobj); gtk_object_sink (dobj); gtk_object_destroy (dobj); gtk_object_unref (dobj); ifactory->widget = NULL; } for (slist = ifactory->items; slist; slist = slist->next) { GtkItemFactoryItem *item = slist->data; GSList *link; for (link = item->widgets; link; link = link->next) if (gtk_object_get_data_by_id (link->data, quark_item_factory) == ifactory) gtk_object_remove_data_by_id (link->data, quark_item_factory); } g_slist_free (ifactory->items); ifactory->items = NULL; GTK_OBJECT_CLASS (parent_class)->destroy (object); } static void gtk_item_factory_finalize (GObject *object) { GtkItemFactory *ifactory; g_return_if_fail (GTK_IS_ITEM_FACTORY (object)); ifactory = GTK_ITEM_FACTORY (object); gtk_accel_group_unref (ifactory->accel_group); g_free (ifactory->path); g_assert (ifactory->widget == NULL); if (ifactory->translate_notify) ifactory->translate_notify (ifactory->translate_data); G_OBJECT_CLASS (parent_class)->finalize (object); } /** * gtk_item_factory_from_widget: * @widget: a widget * @returns: the item factory from which @widget was created, or %NULL * * Obtains the item factory from which a widget was created. */ GtkItemFactory* gtk_item_factory_from_widget (GtkWidget *widget) { GtkItemFactory *ifactory; g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); ifactory = gtk_object_get_data_by_id (GTK_OBJECT (widget), quark_item_factory); if (ifactory == NULL && GTK_IS_MENU_ITEM (widget) && GTK_MENU_ITEM (widget)->submenu != NULL) { GtkWidget *menu = GTK_MENU_ITEM (widget)->submenu; ifactory = gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_item_factory); } return ifactory; } /** * gtk_item_factory_path_from_widget: * @widget: a widget * @returns: the full path to @widget if it been created by an item factory, * %NULL otherwise. This value is owned by GTK+ and must not be * modified or freed. * * If @widget has been created by an item factory, returns the full path * to it. (The full path of a widget is the concatenation of the factory * path specified in gtk_item_factory_new() with the path specified in the * #GtkItemFactoryEntry from which the widget was created.) */ G_CONST_RETURN gchar* gtk_item_factory_path_from_widget (GtkWidget *widget) { gchar* path; g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); path = gtk_object_get_data_by_id (GTK_OBJECT (widget), quark_item_path); if (path == NULL && GTK_IS_MENU_ITEM (widget) && GTK_MENU_ITEM (widget)->submenu != NULL) { GtkWidget *menu = GTK_MENU_ITEM (widget)->submenu; path = gtk_object_get_data_by_id (GTK_OBJECT (menu), quark_item_path); } return path; } /** * gtk_item_factory_create_items: * @ifactory: a #GtkItemFactory * @n_entries: the length of @entries * @entries: an array of #GtkItemFactoryEntry<!-- -->s whose @callback members * must by of type #GtkItemFactoryCallback1 * @callback_data: data passed to the callback functions of all entries * * Creates the menu items from the @entries. */ void gtk_item_factory_create_items (GtkItemFactory *ifactory, guint n_entries, GtkItemFactoryEntry *entries, gpointer callback_data) { gtk_item_factory_create_items_ac (ifactory, n_entries, entries, callback_data, 1); } /** * gtk_item_factory_create_items_ac: * @ifactory: a #GtkItemFactory * @n_entries: the length of @entries * @entries: an array of #GtkItemFactoryEntry<!-- -->s * @callback_data: data passed to the callback functions of all entries * @callback_type: 1 if the callback functions in @entries are of type * #GtkItemFactoryCallback1, 2 if they are of type #GtkItemFactoryCallback2 * * Creates the menu items from the @entries. */ void gtk_item_factory_create_items_ac (GtkItemFactory *ifactory, guint n_entries, GtkItemFactoryEntry *entries, gpointer callback_data, guint callback_type) { guint i; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (callback_type >= 1 && callback_type <= 2); if (n_entries == 0) return; g_return_if_fail (entries != NULL); for (i = 0; i < n_entries; i++) gtk_item_factory_create_item (ifactory, entries + i, callback_data, callback_type); } /** * gtk_item_factory_get_widget: * @ifactory: a #GtkItemFactory * @path: the path to the widget * @returns: the widget for the given path, or %NULL if @path doesn't lead * to a widget * * Obtains the widget which corresponds to @path. * * If the widget corresponding to @path is a menu item which opens a * submenu, then the submenu is returned. If you are interested in the menu * item, use gtk_item_factory_get_item() instead. */ GtkWidget* gtk_item_factory_get_widget (GtkItemFactory *ifactory, const gchar *path) { GtkItemFactoryClass *class; GtkItemFactoryItem *item; g_return_val_if_fail (GTK_IS_ITEM_FACTORY (ifactory), NULL); g_return_val_if_fail (path != NULL, NULL); class = GTK_ITEM_FACTORY_GET_CLASS (ifactory); if (path[0] == '<') item = g_hash_table_lookup (class->item_ht, (gpointer) path); else { gchar *fpath; fpath = g_strconcat (ifactory->path, path, NULL); item = g_hash_table_lookup (class->item_ht, fpath); g_free (fpath); } if (item) { GSList *slist; for (slist = item->widgets; slist; slist = slist->next) { if (gtk_item_factory_from_widget (slist->data) == ifactory) return slist->data; } } return NULL; } /** * gtk_item_factory_get_widget_by_action: * @ifactory: a #GtkItemFactory * @action: an action as specified in the @callback_action field * of #GtkItemFactoryEntry * @returns: the widget which corresponds to the given action, or %NULL * if no widget was found * * Obtains the widget which was constructed from the #GtkItemFactoryEntry * with the given @action. * * If there are multiple items with the same action, the result is * undefined. */ GtkWidget* gtk_item_factory_get_widget_by_action (GtkItemFactory *ifactory, guint action) { GSList *slist; g_return_val_if_fail (GTK_IS_ITEM_FACTORY (ifactory), NULL); for (slist = ifactory->items; slist; slist = slist->next) { GtkItemFactoryItem *item = slist->data; GSList *link; for (link = item->widgets; link; link = link->next) if (gtk_object_get_data_by_id (link->data, quark_item_factory) == ifactory && gtk_object_get_data_by_id (link->data, quark_action) == GUINT_TO_POINTER (action)) return link->data; } return NULL; } /** * gtk_item_factory_get_item: * @ifactory: a #GtkItemFactory * @path: the path to the menu item * @returns: the menu item for the given path, or %NULL if @path doesn't * lead to a menu item * * Obtains the menu item which corresponds to @path. * * If the widget corresponding to @path is a menu item which opens a * submenu, then the item is returned. If you are interested in the submenu, * use gtk_item_factory_get_widget() instead. */ GtkWidget* gtk_item_factory_get_item (GtkItemFactory *ifactory, const gchar *path) { GtkWidget *widget; g_return_val_if_fail (GTK_IS_ITEM_FACTORY (ifactory), NULL); g_return_val_if_fail (path != NULL, NULL); widget = gtk_item_factory_get_widget (ifactory, path); if (GTK_IS_MENU (widget)) widget = gtk_menu_get_attach_widget (GTK_MENU (widget)); return GTK_IS_ITEM (widget) ? widget : NULL; } /** * gtk_item_factory_get_item_by_action: * @ifactory: a #GtkItemFactory * @action: an action as specified in the @callback_action field * of #GtkItemFactoryEntry * @returns: the menu item which corresponds to the given action, or %NULL * if no menu item was found * * Obtains the menu item which was constructed from the first * #GtkItemFactoryEntry with the given @action. */ GtkWidget* gtk_item_factory_get_item_by_action (GtkItemFactory *ifactory, guint action) { GtkWidget *widget; g_return_val_if_fail (GTK_IS_ITEM_FACTORY (ifactory), NULL); widget = gtk_item_factory_get_widget_by_action (ifactory, action); if (GTK_IS_MENU (widget)) widget = gtk_menu_get_attach_widget (GTK_MENU (widget)); return GTK_IS_ITEM (widget) ? widget : NULL; } static char * item_factory_find_separator_r (char *path) { gchar *result = NULL; gboolean escaped = FALSE; while (*path) { if (escaped) escaped = FALSE; else { if (*path == '\\') escaped = TRUE; else if (*path == '/') result = path; } path++; } return result; } static char * item_factory_unescape_label (const char *label) { char *new = g_malloc (strlen (label) + 1); char *p = new; gboolean escaped = FALSE; while (*label) { if (escaped) { *p++ = *label; escaped = FALSE; } else { if (*label == '\\') escaped = TRUE; else *p++ = *label; } label++; } *p = '\0'; return new; } static gboolean gtk_item_factory_parse_path (GtkItemFactory *ifactory, gchar *str, gchar **path, gchar **parent_path, gchar **item) { gchar *translation; gchar *p, *q; *path = g_strdup (str); p = q = *path; while (*p) { if (*p == '_') { if (p[1] == '_') { p++; *q++ = '_'; } } else { *q++ = *p; } p++; } *q = 0; *parent_path = g_strdup (*path); p = item_factory_find_separator_r (*parent_path); if (!p) { g_warning ("GtkItemFactory: invalid entry path `%s'", str); return FALSE; } *p = 0; if (ifactory->translate_func) translation = ifactory->translate_func (str, ifactory->translate_data); else translation = str; p = item_factory_find_separator_r (translation); if (p) p++; else p = translation; *item = item_factory_unescape_label (p); return TRUE; } /** * gtk_item_factory_create_item: * @ifactory: a #GtkItemFactory * @entry: the #GtkItemFactoryEntry to create an item for * @callback_data: data passed to the callback function of @entry * @callback_type: 1 if the callback function of @entry is of type * #GtkItemFactoryCallback1, 2 if it is of type #GtkItemFactoryCallback2 * * Creates an item for @entry. */ void gtk_item_factory_create_item (GtkItemFactory *ifactory, GtkItemFactoryEntry *entry, gpointer callback_data, guint callback_type) { GtkOptionMenu *option_menu = NULL; GtkWidget *parent; GtkWidget *widget; GtkWidget *image; GSList *radio_group; gchar *name; gchar *parent_path; gchar *path; gchar *accelerator; guint type_id; GtkType type; gchar *item_type_path; GtkStockItem stock_item; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (entry != NULL); g_return_if_fail (entry->path != NULL); g_return_if_fail (entry->path[0] == '/'); g_return_if_fail (callback_type >= 1 && callback_type <= 2); if (!entry->item_type || entry->item_type[0] == 0) { item_type_path = "<Item>"; type_id = quark_type_item; } else { item_type_path = entry->item_type; type_id = gtk_object_data_try_key (item_type_path); } radio_group = NULL; if (type_id == quark_type_item) type = GTK_TYPE_MENU_ITEM; else if (type_id == quark_type_title) type = GTK_TYPE_MENU_ITEM; else if (type_id == quark_type_radio_item) type = GTK_TYPE_RADIO_MENU_ITEM; else if (type_id == quark_type_check_item) type = GTK_TYPE_CHECK_MENU_ITEM; else if (type_id == quark_type_image_item) type = GTK_TYPE_IMAGE_MENU_ITEM; else if (type_id == quark_type_stock_item) type = GTK_TYPE_IMAGE_MENU_ITEM; else if (type_id == quark_type_tearoff_item) type = GTK_TYPE_TEAROFF_MENU_ITEM; else if (type_id == quark_type_toggle_item) type = GTK_TYPE_CHECK_MENU_ITEM; else if (type_id == quark_type_separator_item) type = GTK_TYPE_MENU_ITEM; else if (type_id == quark_type_branch) type = GTK_TYPE_MENU_ITEM; else if (type_id == quark_type_last_branch) type = GTK_TYPE_MENU_ITEM; else { GtkWidget *radio_link; radio_link = gtk_item_factory_get_widget (ifactory, item_type_path); if (radio_link && GTK_IS_RADIO_MENU_ITEM (radio_link)) { type = GTK_TYPE_RADIO_MENU_ITEM; radio_group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (radio_link)); } else { g_warning ("GtkItemFactory: entry path `%s' has invalid type `%s'", entry->path, item_type_path); return; } } if (!gtk_item_factory_parse_path (ifactory, entry->path, &path, &parent_path, &name)) return; parent = gtk_item_factory_get_widget (ifactory, parent_path); if (!parent) { GtkItemFactoryEntry pentry; gchar *ppath, *p; ppath = g_strdup (entry->path); p = item_factory_find_separator_r (ppath); g_return_if_fail (p != NULL); *p = 0; pentry.path = ppath; pentry.accelerator = NULL; pentry.callback = NULL; pentry.callback_action = 0; pentry.item_type = "<Branch>"; gtk_item_factory_create_item (ifactory, &pentry, NULL, 1); g_free (ppath); parent = gtk_item_factory_get_widget (ifactory, parent_path); g_return_if_fail (parent != NULL); } if (GTK_IS_OPTION_MENU (parent)) { option_menu = GTK_OPTION_MENU (parent); if (!option_menu->menu) { GtkWidget *menu = g_object_new (GTK_TYPE_MENU, NULL); gchar *p = g_strconcat (ifactory->path, parent_path, NULL); gtk_menu_set_accel_path (GTK_MENU (menu), p); g_free (p); gtk_option_menu_set_menu (option_menu, menu); } parent = option_menu->menu; } g_free (parent_path); g_return_if_fail (GTK_IS_CONTAINER (parent)); accelerator = entry->accelerator; widget = gtk_widget_new (type, "visible", TRUE, "sensitive", (type_id != quark_type_separator_item && type_id != quark_type_title), "parent", parent, NULL); if (option_menu && !option_menu->menu_item) gtk_option_menu_set_history (option_menu, 0); if (type == GTK_TYPE_RADIO_MENU_ITEM) gtk_radio_menu_item_set_group (GTK_RADIO_MENU_ITEM (widget), radio_group); if (GTK_IS_CHECK_MENU_ITEM (widget)) gtk_check_menu_item_set_show_toggle (GTK_CHECK_MENU_ITEM (widget), TRUE); if (type_id == quark_type_image_item) { GdkPixbuf *pixbuf = NULL; image = NULL; pixbuf = gdk_pixbuf_new_from_inline (-1, entry->extra_data, FALSE, NULL); if (pixbuf) image = gtk_image_new_from_pixbuf (pixbuf); if (image) { gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (widget), image); gtk_widget_show (image); } if (pixbuf) g_object_unref (G_OBJECT (pixbuf)); } if (type_id == quark_type_stock_item) { image = gtk_image_new_from_stock (entry->extra_data, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (widget), image); gtk_widget_show (image); if (gtk_stock_lookup (entry->extra_data, &stock_item)) { if (!accelerator) accelerator = gtk_accelerator_name (stock_item.keyval, stock_item.modifier); } } /* install underline accelerators for this item */ if (type_id != quark_type_separator_item && type_id != quark_type_tearoff_item && *name) { GtkWidget *label; label = gtk_widget_new (GTK_TYPE_ACCEL_LABEL, "visible", TRUE, "parent", widget, "accel_widget", widget, "xalign", 0.0, NULL); gtk_label_set_text_with_mnemonic (GTK_LABEL (label), name); } g_free (name); if (type_id == quark_type_branch || type_id == quark_type_last_branch) { gchar *p; if (entry->callback) g_warning ("gtk_item_factory_create_item(): Can't specify a callback on a branch: \"%s\"", entry->path); if (type_id == quark_type_last_branch) gtk_menu_item_set_right_justified (GTK_MENU_ITEM (widget), TRUE); parent = widget; widget = gtk_widget_new (GTK_TYPE_MENU, NULL); p = g_strconcat (ifactory->path, path, NULL); gtk_menu_set_accel_path (GTK_MENU (widget), p); g_free (p); gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent), widget); } gtk_item_factory_add_item (ifactory, path, accelerator, (type_id == quark_type_branch || type_id == quark_type_last_branch) ? (GtkItemFactoryCallback) NULL : entry->callback, entry->callback_action, callback_data, callback_type, item_type_path, widget); if (accelerator != entry->accelerator) g_free (accelerator); g_free (path); } /** * gtk_item_factory_create_menu_entries: * @n_entries: the length of @entries * @entries: an array of #GtkMenuEntry<!-- -->s * * Creates the menu items from the @entries. */ void gtk_item_factory_create_menu_entries (guint n_entries, GtkMenuEntry *entries) { static GPatternSpec *pspec_separator = NULL; static GPatternSpec *pspec_check = NULL; guint i; if (!n_entries) return; g_return_if_fail (entries != NULL); if (!pspec_separator) { pspec_separator = g_pattern_spec_new ("*<separator>*"); pspec_check = g_pattern_spec_new ("*<check>*"); } for (i = 0; i < n_entries; i++) { GtkItemFactory *ifactory; GtkItemFactoryEntry entry; gchar *path; gchar *cpath; path = entries[i].path; ifactory = gtk_item_factory_from_path (path); if (!ifactory) { g_warning ("gtk_item_factory_create_menu_entries(): " "entry[%u] refers to unknown item factory: \"%s\"", i, entries[i].path); continue; } while (*path != '>') path++; path++; cpath = NULL; entry.path = path; entry.accelerator = entries[i].accelerator; entry.callback = entries[i].callback; entry.callback_action = 0; if (g_pattern_match_string (pspec_separator, path)) entry.item_type = "<Separator>"; else if (!g_pattern_match_string (pspec_check, path)) entry.item_type = NULL; else { gboolean in_brace = FALSE; gchar *c; cpath = g_new (gchar, strlen (path)); c = cpath; while (*path != 0) { if (*path == '<') in_brace = TRUE; else if (*path == '>') in_brace = FALSE; else if (!in_brace) *(c++) = *path; path++; } *c = 0; entry.item_type = "<ToggleItem>"; entry.path = cpath; } gtk_item_factory_create_item (ifactory, &entry, entries[i].callback_data, 2); entries[i].widget = gtk_item_factory_get_widget (ifactory, entries[i].path); g_free (cpath); } } /** * gtk_item_factories_path_delete: * @ifactory_path: a factory path to prepend to @path. May be %NULL if @path * starts with a factory path * @path: a path * * Deletes all widgets constructed from the specified path. */ void gtk_item_factories_path_delete (const gchar *ifactory_path, const gchar *path) { GtkItemFactoryClass *class; GtkItemFactoryItem *item; g_return_if_fail (path != NULL); class = gtk_type_class (GTK_TYPE_ITEM_FACTORY); if (path[0] == '<') item = g_hash_table_lookup (class->item_ht, (gpointer) path); else { gchar *fpath; g_return_if_fail (ifactory_path != NULL); fpath = g_strconcat (ifactory_path, path, NULL); item = g_hash_table_lookup (class->item_ht, fpath); g_free (fpath); } if (item) { GSList *widget_list; GSList *slist; widget_list = NULL; for (slist = item->widgets; slist; slist = slist->next) { GtkWidget *widget; widget = slist->data; widget_list = g_slist_prepend (widget_list, widget); gtk_widget_ref (widget); } for (slist = widget_list; slist; slist = slist->next) { GtkWidget *widget; widget = slist->data; gtk_widget_destroy (widget); gtk_widget_unref (widget); } g_slist_free (widget_list); } } /** * gtk_item_factory_delete_item: * @ifactory: a #GtkItemFactory * @path: a path * * Deletes the menu item which was created for @path by the given * item factory. */ void gtk_item_factory_delete_item (GtkItemFactory *ifactory, const gchar *path) { GtkItemFactoryClass *class; GtkWidget *widget; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (path != NULL); class = GTK_ITEM_FACTORY_GET_CLASS (ifactory); widget = gtk_item_factory_get_widget (ifactory, path); if (widget) { if (GTK_IS_MENU (widget)) widget = gtk_menu_get_attach_widget (GTK_MENU (widget)); gtk_widget_destroy (widget); } } /** * gtk_item_factory_delete_entry: * @ifactory: a #GtkItemFactory * @entry: a #GtkItemFactoryEntry * * Deletes the menu item which was created from @entry by the given * item factory. */ void gtk_item_factory_delete_entry (GtkItemFactory *ifactory, GtkItemFactoryEntry *entry) { gchar *path; gchar *parent_path; gchar *name; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (entry != NULL); g_return_if_fail (entry->path != NULL); g_return_if_fail (entry->path[0] == '/'); if (!gtk_item_factory_parse_path (ifactory, entry->path, &path, &parent_path, &name)) return; gtk_item_factory_delete_item (ifactory, path); g_free (path); g_free (parent_path); g_free (name); } /** * gtk_item_factory_delete_entries: * @ifactory: a #GtkItemFactory * @n_entries: the length of @entries * @entries: an array of #GtkItemFactoryEntry<!-- -->s * * Deletes the menu items which were created from the @entries by the given * item factory. */ void gtk_item_factory_delete_entries (GtkItemFactory *ifactory, guint n_entries, GtkItemFactoryEntry *entries) { guint i; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); if (n_entries > 0) g_return_if_fail (entries != NULL); for (i = 0; i < n_entries; i++) gtk_item_factory_delete_entry (ifactory, entries + i); } typedef struct { guint x; guint y; } MenuPos; static void gtk_item_factory_menu_pos (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer func_data) { MenuPos *mpos = func_data; *x = mpos->x; *y = mpos->y; } /** * gtk_item_factory_popup_data_from_widget: * @widget: a widget * @returns: @popup_data associated with the item factory from * which @widget was created, or %NULL if @widget wasn't created * by an item factory * * Obtains the @popup_data which was passed to * gtk_item_factory_popup_with_data(). This data is available until the menu * is popped down again. */ gpointer gtk_item_factory_popup_data_from_widget (GtkWidget *widget) { GtkItemFactory *ifactory; g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); ifactory = gtk_item_factory_from_widget (widget); if (ifactory) return gtk_object_get_data_by_id (GTK_OBJECT (ifactory), quark_popup_data); return NULL; } /** * gtk_item_factory_popup_data: * @ifactory: a #GtkItemFactory * @returns: @popup_data associated with @ifactory * * Obtains the @popup_data which was passed to * gtk_item_factory_popup_with_data(). This data is available until the menu * is popped down again. */ gpointer gtk_item_factory_popup_data (GtkItemFactory *ifactory) { g_return_val_if_fail (GTK_IS_ITEM_FACTORY (ifactory), NULL); return gtk_object_get_data_by_id (GTK_OBJECT (ifactory), quark_popup_data); } static void ifactory_delete_popup_data (GtkObject *object, GtkItemFactory *ifactory) { gtk_signal_disconnect_by_func (object, GTK_SIGNAL_FUNC (ifactory_delete_popup_data), ifactory); gtk_object_remove_data_by_id (GTK_OBJECT (ifactory), quark_popup_data); } /** * gtk_item_factory_popup: * @ifactory: a #GtkItemFactory of type #GTK_TYPE_MENU (see gtk_item_factory_new()) * @x: the x position * @y: the y position * @mouse_button: the mouse button which was pressed to initiate this action * @time: a timestamp for this action * * Pops up the menu constructed from the item factory at (@x, @y). */ void gtk_item_factory_popup (GtkItemFactory *ifactory, guint x, guint y, guint mouse_button, guint32 time) { gtk_item_factory_popup_with_data (ifactory, NULL, NULL, x, y, mouse_button, time); } /** * gtk_item_factory_popup_with_data: * @ifactory: a #GtkItemFactory of type #GTK_TYPE_MENU (see gtk_item_factory_new()) * @popup_data: data available for callbacks while the menu is posted * @destroy: a #GtkDestroyNotify function to be called on @popup_data when * the menu is unposted * @x: the x position * @y: the y position * @mouse_button: the mouse button which was pressed to initiate this action * @time: a timestamp for this action * * Pops up the menu constructed from the item factory at (@x, @y). Callbacks * can access the @popup_data while the menu is posted via * gtk_item_factory_popup_data() and gtk_item_factory_popup_data_from_widget(). */ void gtk_item_factory_popup_with_data (GtkItemFactory *ifactory, gpointer popup_data, GtkDestroyNotify destroy, guint x, guint y, guint mouse_button, guint32 time) { MenuPos *mpos; g_return_if_fail (GTK_IS_ITEM_FACTORY (ifactory)); g_return_if_fail (GTK_IS_MENU (ifactory->widget)); mpos = gtk_object_get_data_by_id (GTK_OBJECT (ifactory->widget), quark_if_menu_pos); if (!mpos) { mpos = g_new0 (MenuPos, 1); gtk_object_set_data_by_id_full (GTK_OBJECT (ifactory->widget), quark_if_menu_pos, mpos, g_free); } mpos->x = x; mpos->y = y; if (popup_data != NULL) { gtk_object_set_data_by_id_full (GTK_OBJECT (ifactory), quark_popup_data, popup_data, destroy); gtk_signal_connect (GTK_OBJECT (ifactory->widget), "selection-done", GTK_SIGNAL_FUNC (ifactory_delete_popup_data), ifactory); } gtk_menu_popup (GTK_MENU (ifactory->widget), NULL, NULL, gtk_item_factory_menu_pos, mpos, mouse_button, time); } /** * gtk_item_factory_set_translate_func: * @ifactory: a #GtkItemFactory * @func: the #GtkTranslateFunc function to be used to translate path elements * @data: data to pass to @func and @notify * @notify: a #GtkDestroyNotify function to be called when @ifactory is * destroyed and when the translation function is changed again * * Sets a function to be used for translating the path elements before they * are displayed. */ void gtk_item_factory_set_translate_func (GtkItemFactory *ifactory, GtkTranslateFunc func, gpointer data, GtkDestroyNotify notify) { g_return_if_fail (ifactory != NULL); if (ifactory->translate_notify) ifactory->translate_notify (ifactory->translate_data); ifactory->translate_func = func; ifactory->translate_data = data; ifactory->translate_notify = notify; }