+2003-09-30 Matthias Clasen <maclas@gmx.de>
+
+ * 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 <kris@gtk.org>
* gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set
+2003-09-30 Matthias Clasen <maclas@gmx.de>
+
+ * 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 <kris@gtk.org>
* gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set
+2003-09-30 Matthias Clasen <maclas@gmx.de>
+
+ * 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 <kris@gtk.org>
* gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set
+2003-09-30 Matthias Clasen <maclas@gmx.de>
+
+ * 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 <kris@gtk.org>
* gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set
+2003-09-30 Matthias Clasen <maclas@gmx.de>
+
+ * 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 <kris@gtk.org>
* gtk/gtkcombobox.c (gtk_combo_box_menu_button_press): set
2003-09-30 Matthias Clasen <maclas@gmx.de>
+ * gtk/tmpl/gtkuimanager.sgml: Add a section about empty menus.
+
* gdk/tmpl/keys.sgml: Small addition.
* gdk/gdk-sections.txt: Add GdkDisplayClass and GdkScreenClass.
<refsect2 id="Smart-Separators">
<title>Smart Separators</title>
<para>
-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.
+</para>
+</refsect2>
+<refsect2>
+<title>Empty Menus</title>
+<para>
+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:
+<itemizedlist>
+<listitem><para>make them disappear by hiding the menu item they're attached to
+</para></listitem>
+<listitem><para>add an insensitive "Empty" item
+</para></listitem>
+</itemizedlist>
+The behaviour is chosen based on the "is_important" property of the action
+to which the submenu is associated.
</para>
</refsect2>
-
<!-- ##### SECTION See_Also ##### -->
<para>
#include "gtkmarshalers.h"
#include "gtkmenuitem.h"
#include "gtkstock.h"
+#include "gtktearoffmenuitem.h"
#include "gtktoolbutton.h"
#include "gtktoolbar.h"
PROP_STOCK_ID,
PROP_IS_IMPORTANT,
PROP_SENSITIVE,
- PROP_VISIBLE,
+ PROP_VISIBLE
};
static void gtk_action_init (GtkAction *action);
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,
TRUE,
G_PARAM_READWRITE));
-
/**
* GtkAction::activate:
* @action: the #GtkAction
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:
+ * <itemizedlist>
+ * <listitem><para>if @action is invisible, @proxy is too
+ * </para></listitem>
+ * <listitem><para>if @empty is %TRUE, hide @proxy unless @action is important
+ * </para></listitem>
+ * </itemizedlist>
+ *
+ * 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,
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
NODE_TYPE_ACCELERATOR
} NodeType;
-
-typedef struct _Node Node;
+typedef struct _Node Node;
struct _Node {
NodeType type;
GQuark action_name;
GtkAction *action;
GtkWidget *proxy;
- GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/
+ GtkWidget *extra; /* second separator for placeholders */
GList *uifiles;
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"))
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)
{
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))
{
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;
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);
+ }
}
}
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);
}
<menubar>
<menu name="FileMenu" action="FileMenuAction">
<menuitem name="Open" action="OpenAction" />
+ <menuitem name="Bold" action="BoldAction" />
</menu>
<menu name="EditMenu" action="EditMenuAction">
<menuitem name="Cut" action="CutAction" />
+ <menu name="EmptyMenu1" action="EmptyMenu1Action">
+ </menu>
+ <menu name="EmptyMenu2" action="EmptyMenu2Action">
+ </menu>
</menu>
<placeholder name="TestPlaceholder" />
</menubar>
<toolbar name="toolbar1">
<placeholder name="ToolbarPlaceholder">
- <toolitem name="nb2" action="NewAction" />
+ <toolitem name="nb2" action="New2Action" />
<separator name="Sep1" />
</placeholder>
<toolitem name="NewButton" action="NewAction" />
<menuitem name="Quit" action="QuitAction" />
<separator name="Sep3" />
</menu>
+ <menu name="EditMenu" action="EditMenuAction">
+ <menu name="EmptyMenu1" action="EmptyMenu1Action">
+ <menuitem name="Cut" action="CutAction" />
+ </menu>
+ <menu name="EmptyMenu2" action="EmptyMenu2Action">
+ <menuitem name="Cut" action="CutAction" />
+ </menu>
+ </menu>
<menu name="HelpMenu" action="HelpMenuAction">
<menuitem name="About" action="AboutAction" />
</menu>
{ "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, "<control>q", "Quit", G_CALLBACK (gtk_main_quit) },
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);