Commit 57f13d81 authored by Matthias Clasen's avatar Matthias Clasen Committed by Matthias Clasen

Test handling of empty menus.

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.
parent 039c6b3b
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.
......
......@@ -146,15 +146,31 @@ actions even if they have no visible proxies.
<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>
......
......@@ -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:
* <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,
......@@ -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
......
......@@ -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);
}
......
......@@ -3,15 +3,20 @@
<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" />
......
......@@ -8,6 +8,14 @@
<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>
......
......@@ -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, "<control>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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment