From 57f13d815ddbdb0d001fa92c823d44744b3577e4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 30 Sep 2003 20:55:24 +0000 Subject: [PATCH] Test handling of empty menus. 2003-09-30 Matthias Clasen * tests/merge-*.ui: * tests/testmerge.c: Test handling of empty menus. * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine whether a menu is empty. Used in gtkaction.c. (update_smart_separators): Also update the visibility of empty menus. (update_node): When creating a new menu proxy, insert an "Empty" menu item which only gets shown if the menu is empty. * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of "is_important" for menu proxies. (_gtk_action_sync_menu_visible): New function to sync the visibility of menu proxies. Used in gtkuimanager.c. (gtk_action_sync_visible): New function to sync the visibility of proxies. --- ChangeLog | 18 +++++ ChangeLog.pre-2-10 | 18 +++++ ChangeLog.pre-2-4 | 18 +++++ ChangeLog.pre-2-6 | 18 +++++ ChangeLog.pre-2-8 | 18 +++++ docs/reference/ChangeLog | 2 + docs/reference/gtk/tmpl/gtkuimanager.sgml | 30 ++++++-- gtk/gtkaction.c | 65 ++++++++++++++++-- gtk/gtkuimanager.c | 83 +++++++++++++++++++++-- tests/merge-1.ui | 7 +- tests/merge-2.ui | 8 +++ tests/testmerge.c | 6 ++ 12 files changed, 273 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9c226228cb..55cb342d15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2003-09-30 Matthias Clasen + + * tests/merge-*.ui: + * tests/testmerge.c: Test handling of empty menus. + + * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine + whether a menu is empty. Used in gtkaction.c. + (update_smart_separators): Also update the visibility of empty menus. + (update_node): When creating a new menu proxy, insert an "Empty" menu + item which only gets shown if the menu is empty. + + * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of + "is_important" for menu proxies. + (_gtk_action_sync_menu_visible): New function to sync the visibility + of menu proxies. Used in gtkuimanager.c. + (gtk_action_sync_visible): New function to sync the visibility of + proxies. + Tue Sep 30 21:43:34 2003 Kristian Rietveld * gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 9c226228cb..55cb342d15 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,21 @@ +2003-09-30 Matthias Clasen + + * tests/merge-*.ui: + * tests/testmerge.c: Test handling of empty menus. + + * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine + whether a menu is empty. Used in gtkaction.c. + (update_smart_separators): Also update the visibility of empty menus. + (update_node): When creating a new menu proxy, insert an "Empty" menu + item which only gets shown if the menu is empty. + + * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of + "is_important" for menu proxies. + (_gtk_action_sync_menu_visible): New function to sync the visibility + of menu proxies. Used in gtkuimanager.c. + (gtk_action_sync_visible): New function to sync the visibility of + proxies. + Tue Sep 30 21:43:34 2003 Kristian Rietveld * gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 9c226228cb..55cb342d15 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,21 @@ +2003-09-30 Matthias Clasen + + * tests/merge-*.ui: + * tests/testmerge.c: Test handling of empty menus. + + * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine + whether a menu is empty. Used in gtkaction.c. + (update_smart_separators): Also update the visibility of empty menus. + (update_node): When creating a new menu proxy, insert an "Empty" menu + item which only gets shown if the menu is empty. + + * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of + "is_important" for menu proxies. + (_gtk_action_sync_menu_visible): New function to sync the visibility + of menu proxies. Used in gtkuimanager.c. + (gtk_action_sync_visible): New function to sync the visibility of + proxies. + Tue Sep 30 21:43:34 2003 Kristian Rietveld * gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 9c226228cb..55cb342d15 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,21 @@ +2003-09-30 Matthias Clasen + + * tests/merge-*.ui: + * tests/testmerge.c: Test handling of empty menus. + + * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine + whether a menu is empty. Used in gtkaction.c. + (update_smart_separators): Also update the visibility of empty menus. + (update_node): When creating a new menu proxy, insert an "Empty" menu + item which only gets shown if the menu is empty. + + * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of + "is_important" for menu proxies. + (_gtk_action_sync_menu_visible): New function to sync the visibility + of menu proxies. Used in gtkuimanager.c. + (gtk_action_sync_visible): New function to sync the visibility of + proxies. + Tue Sep 30 21:43:34 2003 Kristian Rietveld * gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 9c226228cb..55cb342d15 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,21 @@ +2003-09-30 Matthias Clasen + + * tests/merge-*.ui: + * tests/testmerge.c: Test handling of empty menus. + + * gtk/gtkuimanager.c (_gtk_menu_is_empty): New function to determine + whether a menu is empty. Used in gtkaction.c. + (update_smart_separators): Also update the visibility of empty menus. + (update_node): When creating a new menu proxy, insert an "Empty" menu + item which only gets shown if the menu is empty. + + * gtk/gtkaction.c (gtk_action_class_init): Document the meaning of + "is_important" for menu proxies. + (_gtk_action_sync_menu_visible): New function to sync the visibility + of menu proxies. Used in gtkuimanager.c. + (gtk_action_sync_visible): New function to sync the visibility of + proxies. + Tue Sep 30 21:43:34 2003 Kristian Rietveld * gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog index dbfc1ac38d..68c2d53baf 100644 --- a/docs/reference/ChangeLog +++ b/docs/reference/ChangeLog @@ -1,5 +1,7 @@ 2003-09-30 Matthias Clasen + * gtk/tmpl/gtkuimanager.sgml: Add a section about empty menus. + * gdk/tmpl/keys.sgml: Small addition. * gdk/gdk-sections.txt: Add GdkDisplayClass and GdkScreenClass. diff --git a/docs/reference/gtk/tmpl/gtkuimanager.sgml b/docs/reference/gtk/tmpl/gtkuimanager.sgml index 6195004350..c2aa340148 100644 --- a/docs/reference/gtk/tmpl/gtkuimanager.sgml +++ b/docs/reference/gtk/tmpl/gtkuimanager.sgml @@ -146,15 +146,31 @@ actions even if they have no visible proxies. Smart Separators -The separators created by #GtkUIManager are "smart", i.e. they do not show up in the -UI unless they end up between two visible menu or tool items. Separators which are located -at the very beginning or end of the menu or toolbar containing them, or multiple separators -next to each other, are hidden. This is a useful feature, since the merging of UI elements -from multiple sources can make it hard or impossible to determine in advance whether a -separator will end up in such an unfortunate position. +The separators created by #GtkUIManager are "smart", i.e. they do not show up +in the UI unless they end up between two visible menu or tool items. Separators +which are located at the very beginning or end of the menu or toolbar +containing them, or multiple separators next to each other, are hidden. This +is a useful feature, since the merging of UI elements from multiple sources +can make it hard or impossible to determine in advance whether a separator +will end up in such an unfortunate position. + + + +Empty Menus + +Submenus pose similar problems to separators inconnection with merging. It is +impossible to know in advance whether they will end up empty after merging. +#GtkUIManager offers two ways to treat empty submenus: + +make them disappear by hiding the menu item they're attached to + +add an insensitive "Empty" item + + +The behaviour is chosen based on the "is_important" property of the action +to which the submenu is associated. - diff --git a/gtk/gtkaction.c b/gtk/gtkaction.c index 50bc3c4f0f..33bacf738a 100644 --- a/gtk/gtkaction.c +++ b/gtk/gtkaction.c @@ -40,6 +40,7 @@ #include "gtkmarshalers.h" #include "gtkmenuitem.h" #include "gtkstock.h" +#include "gtktearoffmenuitem.h" #include "gtktoolbutton.h" #include "gtktoolbar.h" @@ -88,7 +89,7 @@ enum PROP_STOCK_ID, PROP_IS_IMPORTANT, PROP_SENSITIVE, - PROP_VISIBLE, + PROP_VISIBLE }; static void gtk_action_init (GtkAction *action); @@ -216,7 +217,7 @@ gtk_action_class_init (GtkActionClass *klass) PROP_IS_IMPORTANT, g_param_spec_boolean ("is_important", _("Is important"), - _("Whether the action is considered important. When TRUE, toolitem proxies for this action show text in GTK_TOOLBAR_BOTH_HORIZ mode"), + _("Whether the action is considered important. When TRUE, toolitem proxies for this action show text in GTK_TOOLBAR_BOTH_HORIZ mode, and empty menu proxies for this action are not hidden."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, @@ -234,7 +235,6 @@ gtk_action_class_init (GtkActionClass *klass) TRUE, G_PARAM_READWRITE)); - /** * GtkAction::activate: * @action: the #GtkAction @@ -530,6 +530,63 @@ gtk_action_sync_property (GtkAction *action, g_value_unset (&value); } +/** + * _gtk_action_sync_menu_visible: + * @action: a #GtkAction, or %NULL to determine the action from @proxy + * @proxy: a proxy menu item + * @empty: whether the submenu attached to @proxy is empty + * + * Updates the visibility of @proxy from the visibility of @action + * according to the following rules: + * + * if @action is invisible, @proxy is too + * + * if @empty is %TRUE, hide @proxy unless @action is important + * + * + * + * This function is used in the implementation of #GtkUIManager. + **/ +void +_gtk_action_sync_menu_visible (GtkAction *action, + GtkWidget *proxy, + gboolean empty) +{ + gboolean visible, important; + + g_return_if_fail (GTK_IS_MENU_ITEM (proxy)); + g_return_if_fail (action == NULL || GTK_IS_ACTION (action)); + + if (action == NULL) + action = g_object_get_data (G_OBJECT (proxy), "gtk-action"); + + g_object_get (G_OBJECT (action), + "visible", &visible, + "is_important", &important, + NULL); + + g_object_set (G_OBJECT (proxy), + "visible", visible && (important || !empty), + NULL); +} + +gboolean _gtk_menu_is_empty (GtkWidget *menu); + +static void +gtk_action_sync_visible (GtkAction *action, + GParamSpec *pspec, + GtkWidget *proxy) +{ + if (GTK_IS_MENU_ITEM (proxy)) + { + GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (proxy)); + + _gtk_action_sync_menu_visible (action, proxy, _gtk_menu_is_empty (menu)); + } + else + gtk_action_sync_property (action, pspec, proxy); +} + static void gtk_action_sync_label (GtkAction *action, GParamSpec *pspec, @@ -627,7 +684,7 @@ connect_proxy (GtkAction *action, gtk_widget_set_sensitive (proxy, action->private_data->sensitive); g_signal_connect_object (action, "notify::visible", - G_CALLBACK (gtk_action_sync_property), proxy, 0); + G_CALLBACK (gtk_action_sync_visible), proxy, 0); if (action->private_data->visible) gtk_widget_show (proxy); else diff --git a/gtk/gtkuimanager.c b/gtk/gtkuimanager.c index 16796c3cab..c3040c2950 100644 --- a/gtk/gtkuimanager.c +++ b/gtk/gtkuimanager.c @@ -60,8 +60,7 @@ typedef enum NODE_TYPE_ACCELERATOR } NodeType; - -typedef struct _Node Node; +typedef struct _Node Node; struct _Node { NodeType type; @@ -71,7 +70,7 @@ struct _Node { GQuark action_name; GtkAction *action; GtkWidget *proxy; - GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/ + GtkWidget *extra; /* second separator for placeholders */ GList *uifiles; @@ -831,13 +830,14 @@ start_element_handler (GMarkupParseContext *context, const gchar *action; GQuark action_quark; gboolean top; - + gboolean raise_error = TRUE; node_name = NULL; action = NULL; action_quark = 0; top = FALSE; + for (i = 0; attribute_names[i] != NULL; i++) { if (!strcmp (attribute_names[i], "name")) @@ -1642,12 +1642,55 @@ find_toolbar_position (GNode *node, return TRUE; } +/** + * _gtk_menu_is_empty: + * @menu: a #GtkMenu or %NULL + * + * Determines whether @menu is empty. A menu is considered empty if it + * the only visible children are tearoff menu items or "filler" menu + * items which were inserted to mark the menu as empty. + * + * This function is used by #GtkAction. + * + * Return value: whether @menu is empty. + **/ +gboolean +_gtk_menu_is_empty (GtkWidget *menu) +{ + GList *children, *cur; + + g_return_val_if_fail (menu == NULL || GTK_IS_MENU (menu), TRUE); + + if (!menu) + return TRUE; + + children = gtk_container_get_children (GTK_CONTAINER (menu)); + + cur = children; + while (cur) + { + if (GTK_WIDGET_VISIBLE (cur->data)) + { + if (!GTK_IS_TEAROFF_MENU_ITEM (cur->data) && + !g_object_get_data (cur->data, "gtk-empty-menu-item")) + return FALSE; + } + cur = cur->next; + } + + return TRUE; +} + enum { SEPARATOR_MODE_SMART, SEPARATOR_MODE_VISIBLE, SEPARATOR_MODE_HIDDEN }; +void _gtk_action_sync_menu_visible (GtkAction *action, + GtkWidget *proxy, + gboolean empty); + static void update_smart_separators (GtkWidget *proxy) { @@ -1661,15 +1704,23 @@ update_smart_separators (GtkWidget *proxy) if (parent) { gboolean visible; + gboolean empty; GList *children, *cur, *last; + GtkWidget *filler; children = gtk_container_get_children (GTK_CONTAINER (parent)); visible = FALSE; last = NULL; + empty = TRUE; + filler = NULL; + cur = children; while (cur) { + if (g_object_get_data (cur->data, "gtk-empty-menu-item")) + filler = cur->data; + if (GTK_IS_SEPARATOR_MENU_ITEM (cur->data) || GTK_IS_SEPARATOR_TOOL_ITEM (cur->data)) { @@ -1701,10 +1752,13 @@ update_smart_separators (GtkWidget *proxy) else if (GTK_WIDGET_VISIBLE (cur->data)) { last = NULL; - if (GTK_IS_TEAROFF_MENU_ITEM (cur->data)) + if (GTK_IS_TEAROFF_MENU_ITEM (cur->data) || cur->data == filler) visible = FALSE; else - visible = TRUE; + { + visible = TRUE; + empty = FALSE; + } } cur = cur->next; @@ -1712,6 +1766,15 @@ update_smart_separators (GtkWidget *proxy) if (last) gtk_widget_hide (GTK_WIDGET (last->data)); + + if (GTK_IS_MENU (parent)) + { + GtkWidget *item; + + item = gtk_menu_get_attach_widget (GTK_MENU (parent)); + _gtk_action_sync_menu_visible (NULL, item, empty); + g_object_set (G_OBJECT (filler), "visible", empty, NULL); + } } } @@ -1843,12 +1906,20 @@ update_node (GtkUIManager *self, if (find_menu_position (node, &menushell, &pos)) { GtkWidget *tearoff; + GtkWidget *filler; info->proxy = gtk_action_create_menu_item (action); menu = gtk_menu_new (); tearoff = gtk_tearoff_menu_item_new (); gtk_widget_set_no_show_all (tearoff, TRUE); gtk_menu_shell_append (GTK_MENU_SHELL (menu), tearoff); + filler = gtk_menu_item_new_with_label (_("Empty")); + g_object_set_data (G_OBJECT (filler), + "gtk-empty-menu-item", + GINT_TO_POINTER (TRUE)); + gtk_widget_set_sensitive (filler, FALSE); + gtk_widget_set_no_show_all (filler, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), filler); gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu); gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos); } diff --git a/tests/merge-1.ui b/tests/merge-1.ui index a7fb9779d4..b2cb72218c 100644 --- a/tests/merge-1.ui +++ b/tests/merge-1.ui @@ -3,15 +3,20 @@ + + + + + - + diff --git a/tests/merge-2.ui b/tests/merge-2.ui index a073f917dc..05a641fe96 100644 --- a/tests/merge-2.ui +++ b/tests/merge-2.ui @@ -8,6 +8,14 @@ + + + + + + + + diff --git a/tests/testmerge.c b/tests/testmerge.c index d74ed6fd7f..5c9a59b736 100644 --- a/tests/testmerge.c +++ b/tests/testmerge.c @@ -123,6 +123,8 @@ static GtkActionEntry entries[] = { { "EditMenuAction", NULL, "_Edit" }, { "HelpMenuAction", NULL, "_Help" }, { "JustifyMenuAction", NULL, "_Justify" }, + { "EmptyMenu1Action", NULL, "Empty 1" }, + { "EmptyMenu2Action", NULL, "Empty 2" }, { "Test", NULL, "Test" }, { "QuitAction", GTK_STOCK_QUIT, NULL, "q", "Quit", G_CALLBACK (gtk_main_quit) }, @@ -541,6 +543,10 @@ main (int argc, char **argv) gtk_action_group_add_actions (action_group, entries, n_entries, NULL); + action = gtk_action_group_get_action (action_group, "EmptyMenu1Action"); + g_object_set (G_OBJECT (action), "is_important", TRUE, NULL); + action = gtk_action_group_get_action (action_group, "EmptyMenu2Action"); + g_object_set (G_OBJECT (action), "is_important", FALSE, NULL); gtk_action_group_add_toggle_actions (action_group, toggle_entries, n_toggle_entries, NULL);