]> Pileus Git - ~andy/gtk/commitdiff
Test handling of empty menus.
authorMatthias Clasen <maclas@gmx.de>
Tue, 30 Sep 2003 20:55:24 +0000 (20:55 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Tue, 30 Sep 2003 20:55:24 +0000 (20:55 +0000)
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.

12 files changed:
ChangeLog
ChangeLog.pre-2-10
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
docs/reference/ChangeLog
docs/reference/gtk/tmpl/gtkuimanager.sgml
gtk/gtkaction.c
gtk/gtkuimanager.c
tests/merge-1.ui
tests/merge-2.ui
tests/testmerge.c

index 9c226228cb31534536460c1635e3ed0fbf4ad67f..55cb342d15b8d7623ec4e853e512c7596dbb661a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+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
index 9c226228cb31534536460c1635e3ed0fbf4ad67f..55cb342d15b8d7623ec4e853e512c7596dbb661a 100644 (file)
@@ -1,3 +1,21 @@
+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
index 9c226228cb31534536460c1635e3ed0fbf4ad67f..55cb342d15b8d7623ec4e853e512c7596dbb661a 100644 (file)
@@ -1,3 +1,21 @@
+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
index 9c226228cb31534536460c1635e3ed0fbf4ad67f..55cb342d15b8d7623ec4e853e512c7596dbb661a 100644 (file)
@@ -1,3 +1,21 @@
+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
index 9c226228cb31534536460c1635e3ed0fbf4ad67f..55cb342d15b8d7623ec4e853e512c7596dbb661a 100644 (file)
@@ -1,3 +1,21 @@
+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
index dbfc1ac38d35eaf4bbf918c30991e905a1286613..68c2d53baf9fbf4a540ff3572567aca9b6af7313 100644 (file)
@@ -1,5 +1,7 @@
 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.
index 61950043509568793d57efc82f68db6c4711b706..c2aa340148e9b2713872778c95714f5a07079e18 100644 (file)
@@ -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>
 
index 50bc3c4f0f39903476f9b351b289e7e4727a6e3a..33bacf738af5ee0ee93760bc5c53a248bfa5c6dd 100644 (file)
@@ -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
index 16796c3cabfb8071a2b43147df9c15409288c3c4..c3040c295091b581540ea0388268f24371b6c8a8 100644 (file)
@@ -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);
                  }
index a7fb9779d4733775e28a69c5a47866bf86b4969e..b2cb72218c5bcefe5084aa902e2a9217f622252d 100644 (file)
@@ -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" />
index a073f917dc3787e6428921df27cb595e1de33792..05a641fe96f70d76e2999807b08685d344102df6 100644 (file)
@@ -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>
index d74ed6fd7fa1920a210ab59be3bce5007b4d1e24..5c9a59b7361a22bf5f52a109b8b4fb9e230ea110 100644 (file)
@@ -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);