XEmacs 21.4.17 "Jumbo Shrimp".
[chise/xemacs-chise.git.1] / src / menubar-gtk.c
index 4af6659..387e150 100644 (file)
@@ -1,4 +1,4 @@
-/* Implements an elisp-programmable menubar -- X interface.
+/* Implements an elisp-programmable menubar -- Gtk interface.
    Copyright (C) 1993, 1994 Free Software Foundation, Inc.
    Copyright (C) 1995 Tinker Systems and INS Engineering Corp.
 
@@ -46,7 +46,7 @@ Boston, MA 02111-1307, USA.  */
 #define SUBMENU_TYPE   1
 #define POPUP_TYPE     2
 
-static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr);
+static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr, GtkAccelGroup* accel_group);
 
 #define FRAME_MENUBAR_DATA(frame) ((frame)->menubar_data)
 #define XFRAME_MENUBAR_DATA_LASTBUFF(frame) (XCAR ((frame)->menubar_data))
@@ -110,14 +110,14 @@ gtk_xemacs_menubar_get_type (void)
   return xemacs_menubar_type;
 }
 
-static GtkWidgetClass *parent_class;
+static GtkWidgetClass *menubar_parent_class;
 
 static void gtk_xemacs_menubar_class_init      (GtkXEmacsMenubarClass *klass)
 {
   GtkWidgetClass *widget_class;
 
   widget_class = (GtkWidgetClass*) klass;
-  parent_class = (GtkWidgetClass *) gtk_type_class (gtk_menu_bar_get_type ());
+  menubar_parent_class = (GtkWidgetClass *) gtk_type_class (gtk_menu_bar_get_type ());
 
   widget_class->size_request = gtk_xemacs_menubar_size_request;
 }
@@ -131,7 +131,7 @@ static void gtk_xemacs_menubar_size_request (GtkWidget *widget, GtkRequisition *
   GtkXEmacsMenubar *x = GTK_XEMACS_MENUBAR (widget);
   GtkRequisition frame_size;
 
-  parent_class->size_request (widget, requisition);
+  menubar_parent_class->size_request (widget, requisition);
 
   /* #### BILL!
   ** We should really only do this if the menu has not been detached!
@@ -154,6 +154,117 @@ gtk_xemacs_menubar_new (struct frame *f)
   return (GTK_WIDGET (menubar));
 }
 \f
+/*
+ * Label with XEmacs accelerator character support.
+ *
+ * The default interfaces to GtkAccelLabel does not understand XEmacs
+ * keystroke printing conventions, nor is it convenient in the places where is
+ * it needed.  This subclass provides an alternative interface more suited to
+ * XEmacs needs but does not add new functionality.
+ */
+#define GTK_TYPE_XEMACS_ACCEL_LABEL           (gtk_xemacs_accel_label_get_type ())
+#define GTK_XEMACS_ACCEL_LABEL(obj)           (GTK_CHECK_CAST ((obj), GTK_TYPE_ACCEL_LABEL, GtkXEmacsAccelLabel))
+#define GTK_XEMACS_ACCEL_LABEL_CLASS(klass)    (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_ACCEL_LABEL, GtkXEmacsAccelLabelClass))
+#define GTK_IS_XEMACS_ACCEL_LABEL(obj)        (GTK_CHECK_TYPE ((obj), GTK_TYPE_XEMACS_ACCEL_LABEL))
+#define GTK_IS_XEMACS_ACCEL_LABEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XEMACS_ACCEL_LABEL))
+
+typedef struct _GtkXEmacsAccelLabel        GtkXEmacsAccelLabel;
+typedef struct _GtkXEmacsAccelLabelClass  GtkXEmacsAccelLabelClass;
+
+/* Instance structure. No additional fields required. */
+struct _GtkXEmacsAccelLabel
+{
+  GtkAccelLabel label;
+};
+
+/* Class structure. No additional fields required. */
+struct _GtkXEmacsAccelLabelClass
+{
+  GtkAccelLabelClass    parent_class;
+};
+
+static GtkType   gtk_xemacs_accel_label_get_type(void);
+static GtkWidget* gtk_xemacs_accel_label_new(const gchar *string);
+static void       gtk_xemacs_set_accel_keys(GtkXEmacsAccelLabel* l,
+                                      Lisp_Object keys);
+static void       gtk_xemacs_accel_label_class_init(GtkXEmacsAccelLabelClass *klass);
+static void       gtk_xemacs_accel_label_init(GtkXEmacsAccelLabel *xemacs);
+
+static GtkType
+gtk_xemacs_accel_label_get_type(void)
+{
+  static GtkType xemacs_accel_label_type = 0;
+
+  if (!xemacs_accel_label_type)
+    {
+      static const GtkTypeInfo xemacs_accel_label_info =
+      {
+       "GtkXEmacsAccelLabel",
+       sizeof (GtkXEmacsAccelLabel),
+       sizeof (GtkXEmacsAccelLabelClass),
+       (GtkClassInitFunc) gtk_xemacs_accel_label_class_init,
+       (GtkObjectInitFunc) gtk_xemacs_accel_label_init,
+       /* reserved_1 */ NULL,
+        /* reserved_2 */ NULL,
+        (GtkClassInitFunc) NULL,
+      };
+
+      xemacs_accel_label_type = gtk_type_unique (gtk_accel_label_get_type(), &xemacs_accel_label_info);
+    }
+
+  return xemacs_accel_label_type;
+}
+
+static void
+gtk_xemacs_accel_label_class_init(GtkXEmacsAccelLabelClass *klass)
+{
+  /* Nothing to do. */
+}
+
+static void
+gtk_xemacs_accel_label_init(GtkXEmacsAccelLabel *xemacs)
+{
+  /* Nothing to do. */
+}
+
+static GtkWidget*
+gtk_xemacs_accel_label_new (const gchar *string)
+{
+  GtkXEmacsAccelLabel *xemacs_accel_label;
+  
+  xemacs_accel_label = gtk_type_new (GTK_TYPE_XEMACS_ACCEL_LABEL);
+  
+  if (string && *string)
+    gtk_label_set_text (GTK_LABEL (xemacs_accel_label), string);
+  
+  return GTK_WIDGET (xemacs_accel_label);
+}
+
+/* Make the string <keys> the accelerator string for the label. */
+static void
+gtk_xemacs_set_accel_keys(GtkXEmacsAccelLabel* l, Lisp_Object keys)
+{
+  g_return_if_fail (l != NULL);
+  g_return_if_fail (GTK_IS_XEMACS_ACCEL_LABEL (l));
+
+  /* Disable the standard way of finding the accelerator string for the
+     label. */
+  gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL(l), NULL);
+
+  /* Set the string straight from the object. */
+  if (STRINGP (keys) && XSTRING_LENGTH (keys))
+    {
+      C_STRING_TO_EXTERNAL_MALLOC(XSTRING_DATA (keys),
+                                 l->label.accel_string,
+                                 Qctext);
+    }
+  else
+    {
+      /* l->label.accel_string = NULL;*/
+    }
+}
+\f
+
 /* We now return you to your regularly scheduled menus... */
 
 int dockable_menubar;
@@ -314,7 +425,8 @@ __activate_menu(GtkMenuItem *item, gpointer user_data)
        }
       else
        {
-         next = menu_descriptor_to_widget_1 (child);
+         next = menu_descriptor_to_widget_1 (child,
+                                             gtk_menu_ensure_uline_accel_group (GTK_MENU (item->submenu)));
        }
 
       if (!next)
@@ -349,6 +461,63 @@ __kill_stupid_gtk_timer (GtkObject *obj, gpointer user_data)
     }
 }
 
+/* Convert the XEmacs menu accelerator representation to Gtk mnemonic form. If
+  no accelerator has been provided, put one at the start of the string (this
+  mirrors the behaviour under X).  This algorithm is also found in
+  dialog-gtk.el:gtk-popup-convert-underscores.
+*/
+static char *
+convert_underscores(const char *name)
+{
+  char *rval;
+  int i,j;
+  int found_accel = FALSE;
+  int underscores = 0;
+
+  for (i = 0; name[i]; ++i)
+    if (name[i] == '%' && name[i+1] == '_')
+      {
+       found_accel = TRUE;
+      }
+    else if (name[i] == '_')
+      {
+       underscores++;
+      }
+
+  /* Allocate space for the original string, plus zero byte plus extra space
+     for all quoted underscores plus possible additional leading accelerator. */
+  rval = xmalloc_and_zero (strlen(name) + 1 + underscores
+                          + (found_accel ? 0 : 1));
+
+  if (!found_accel)
+    rval[0] = '_';
+
+  for (i = 0, j = (found_accel ? 0 : 1); name[i]; i++)
+    {
+      if (name[i]=='%')
+       {
+         i++;
+         if (!(name[i]))
+           continue;
+         
+         if ((name[i] != '_') && (name[i] != '%'))
+           i--;
+
+         found_accel = TRUE;
+       }
+      else if (name[i] == '_')
+       {
+         rval[j++] = '_';
+       }
+
+      rval[j++] = name[i];
+    }
+
+  return rval;
+}
+
+
+/* Remove the XEmacs menu accellerator representation from a string. */
 static char *
 remove_underscores(const char *name)
 {
@@ -362,7 +531,9 @@ remove_underscores(const char *name)
        if (!(name[i]))
          continue;
 
-       if ((name[i] == '_'))
+       if ((name[i] != '_') && (name[i] != '%'))
+         i--;
+       else
          continue;
       }
       rval[j++] = name[i];
@@ -375,7 +546,8 @@ remove_underscores(const char *name)
    DESCR is either a list (meaning a submenu), a vector, or nil (if
    you include a :filter keyword) */
 static GtkWidget *
-menu_convert (Lisp_Object desc, GtkWidget *reuse)
+menu_convert (Lisp_Object desc, GtkWidget *reuse,
+             GtkAccelGroup* menubar_accel_group)
 {
   GtkWidget *menu_item = NULL;
   GtkWidget *submenu = NULL;
@@ -392,8 +564,23 @@ menu_convert (Lisp_Object desc, GtkWidget *reuse)
 
       if (!reuse)
        {
-         char *temp_menu_name = remove_underscores (XSTRING_DATA (XCAR (desc)));
-         menu_item = gtk_menu_item_new_with_label (temp_menu_name);
+         char *temp_menu_name = convert_underscores (XSTRING_DATA (XCAR (desc)));
+         GtkWidget* accel_label = gtk_xemacs_accel_label_new(NULL);
+         guint accel_key;
+
+         gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
+         accel_key = gtk_label_parse_uline (GTK_LABEL (accel_label), temp_menu_name);
+
+         menu_item = gtk_menu_item_new ();
+         gtk_container_add (GTK_CONTAINER (menu_item), accel_label);
+         gtk_widget_show (accel_label);
+
+         if (menubar_accel_group)
+           gtk_widget_add_accelerator (menu_item,
+                                       "activate_item",
+                                       menubar_accel_group,
+                                       accel_key, GDK_MOD1_MASK,
+                                       GTK_ACCEL_LOCKED);
          free (temp_menu_name);
        }
       else
@@ -542,28 +729,13 @@ menu_convert (Lisp_Object desc, GtkWidget *reuse)
   return (menu_item);
 }
 
-static struct frame *
-__get_channel (GtkWidget *w)
-{
-  struct frame *f = NULL;
-
-  for (; w; w = w->parent)
-    {
-      if ((f = (struct frame *) gtk_object_get_data (GTK_OBJECT (w), "xemacs::frame")))
-       return (f);
-    }
-
-  return (selected_frame());
-}
-
-
 /* Called whenever a button, radio, or toggle is selected in the menu */
 static void
 __generic_button_callback (GtkMenuItem *item, gpointer user_data)
 {
   Lisp_Object callback, function, data, channel;
 
-  XSETFRAME (channel, __get_channel (GTK_WIDGET (item)));
+  XSETFRAME (channel, gtk_widget_to_frame (GTK_WIDGET (item)));
 
   VOID_TO_LISP (callback, user_data);
 
@@ -576,7 +748,8 @@ __generic_button_callback (GtkMenuItem *item, gpointer user_data)
 /* This function cannot GC.
    It is only called from menu_item_descriptor_to_widget_value, which
    prohibits GC. */
-static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
+static GtkWidget *
+menu_descriptor_to_widget_1 (Lisp_Object descr, GtkAccelGroup* accel_group)
 {
   if (STRINGP (descr))
     {
@@ -589,7 +762,7 @@ static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
   else if (LISTP (descr))
     {
       /* It is a submenu */
-      return (menu_convert (descr, NULL));
+      return (menu_convert (descr, NULL, accel_group));
     }
   else if (VECTORP (descr))
     {
@@ -610,6 +783,7 @@ static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
       int plist_p;
       int selected_spec = 0, included_spec = 0;
       GtkWidget *widget = NULL;
+      guint accel_key;
 
       if (length < 2)
        signal_simple_error ("button descriptors must be at least 2 long", descr);
@@ -705,8 +879,9 @@ static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
              sprintf (label_buffer, "%s ", XSTRING_DATA (name));
            }
 
-         temp_label = remove_underscores (label_buffer);
-         main_label = gtk_accel_label_new (temp_label);
+         temp_label = convert_underscores (label_buffer);
+         main_label = gtk_xemacs_accel_label_new (NULL);
+         accel_key = gtk_label_parse_uline (GTK_LABEL (main_label), temp_label);
          free (temp_label);
        }
 
@@ -822,36 +997,23 @@ static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
                          GTK_SIGNAL_FUNC (__generic_button_callback),
                          LISP_TO_VOID (callback));
 
-      /* We cheat here... GtkAccelLabel usually builds its
-        `accel_string' from the widget it is attached to, but we do
-        not want to go thru the overhead of converting our nice
-        string back into the modifier + key format that requires,
-        just so that they can convert it back into a (possibly
-        different/wrong) string
-
-        We set the label string manually, and things should 'just
-        work'
-
-        In an ideal world we would just subclass GtkLabel ourselves,
-        but I have known for a very long time that this is not an
-        ideal world.
-
-        #### Should do menu shortcuts `correctly' one of these days.
+      /* Now that all the information about the menu item is know, set the
+        remaining properties.
       */
       
       if (main_label)
        {
-         GtkAccelLabel *l = GTK_ACCEL_LABEL (main_label);
-
          gtk_container_add (GTK_CONTAINER (widget), main_label);
 
-         gtk_accel_label_set_accel_widget (l, NULL);
-         gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5);
+         gtk_misc_set_alignment (GTK_MISC (main_label), 0.0, 0.5);
+         gtk_xemacs_set_accel_keys(GTK_XEMACS_ACCEL_LABEL(main_label), keys);
 
-         if (STRINGP (keys) && XSTRING_LENGTH (keys))
-           {
-             l->accel_string = g_strdup (XSTRING_DATA (keys));
-           }
+         if (accel_group)
+           gtk_widget_add_accelerator (widget,
+                                       "activate_item",
+                                       accel_group,
+                                       accel_key, 0,
+                                       GTK_ACCEL_LOCKED);
        }
 
       return (widget);
@@ -859,11 +1021,12 @@ static GtkWidget *menu_descriptor_to_widget_1 (Lisp_Object descr)
   else
     {
       return (NULL);
-      /* abort (); ???? */
+      /* ABORT (); ???? */
     }
 }
 
-static GtkWidget *menu_descriptor_to_widget (Lisp_Object descr)
+static GtkWidget *
+menu_descriptor_to_widget (Lisp_Object descr, GtkAccelGroup* accel_group)
 {
   int count = specpdl_depth ();
   GtkWidget *rval = NULL;
@@ -873,7 +1036,7 @@ static GtkWidget *menu_descriptor_to_widget (Lisp_Object descr)
   gc_currently_forbidden = 1;
 
   /* Cannot GC from here on out... */
-  rval = menu_descriptor_to_widget_1 (descr);
+  rval = menu_descriptor_to_widget_1 (descr, accel_group);
   unbind_to (count, Qnil);
   return (rval);
   
@@ -883,23 +1046,24 @@ static gboolean
 menu_can_reuse_widget (GtkWidget *child, const char *label)
 {
   /* Everything up at the top level was done using
-  ** gtk_menu_item_new_with_label(), but we still double check to make
+  ** gtk_xemacs_accel_label_new(), but we still double check to make
   ** sure we don't seriously foobar ourselves.
   */
-  char *temp_label = NULL;
-  gpointer possible_child = g_list_nth_data (gtk_container_children (GTK_CONTAINER (child)), 0);
+  gpointer possible_child =
+    g_list_nth_data (gtk_container_children (GTK_CONTAINER (child)), 0);
+  gboolean ret_val = FALSE;
 
   if (possible_child && GTK_IS_LABEL (possible_child))
     {
-      if (!temp_label) temp_label = remove_underscores (label);
+      char *temp_label = remove_underscores (label);
+
       if (!strcmp (GTK_LABEL (possible_child)->label, temp_label))
-       {
-         free (temp_label);
-         return (TRUE);
-       }
+       ret_val = TRUE;
+
+      free (temp_label);
     }
-  if (temp_label) free (temp_label);
-  return (FALSE);
+
+  return ret_val;
 }
 
 /* Converts a menubar description into a GtkMenuBar... a menubar is a
@@ -915,6 +1079,7 @@ menu_create_menubar (struct frame *f, Lisp_Object descr)
   GtkWidget *menubar = FRAME_GTK_MENUBAR_WIDGET (f);
   GUI_ID id = (GUI_ID) gtk_object_get_data (GTK_OBJECT (menubar), XEMACS_MENU_GUIID_TAG);
   guint menu_position = 0;
+  GtkAccelGroup *menubar_accel_group;
 
   /* Remove any existing protection for old menu items */
   ungcpro_popup_callbacks (id);
@@ -922,6 +1087,8 @@ menu_create_menubar (struct frame *f, Lisp_Object descr)
   /* GCPRO the whole damn thing */
   gcpro_popup_callbacks (id, descr);
 
+  menubar_accel_group = gtk_accel_group_new();
+
   EXTERNAL_LIST_LOOP (tail, value)
     {
       gpointer current_child = g_list_nth_data (GTK_MENU_SHELL (menubar)->children, menu_position);
@@ -939,7 +1106,7 @@ menu_create_menubar (struct frame *f, Lisp_Object descr)
          /* It is a button description */
          GtkWidget *item;
 
-         item = menu_descriptor_to_widget (item_descr);
+         item = menu_descriptor_to_widget (item_descr, menubar_accel_group);
          gtk_widget_set_name (item, "XEmacsMenuButton");
 
          if (!item)
@@ -961,12 +1128,13 @@ menu_create_menubar (struct frame *f, Lisp_Object descr)
          if (current_child && menu_can_reuse_widget (GTK_WIDGET (current_child),
                                                      XSTRING_DATA (XCAR (item_descr))))
            {
-             widget = menu_convert (item_descr, GTK_WIDGET (current_child));
+             widget = menu_convert (item_descr, GTK_WIDGET (current_child),
+                                    menubar_accel_group);
              reused_p = TRUE;
            }
          else
            {
-             widget = menu_convert (item_descr, NULL);
+             widget = menu_convert (item_descr, NULL, menubar_accel_group);
              if (current_child) gtk_widget_destroy (GTK_WIDGET (current_child));
              gtk_menu_bar_insert (GTK_MENU_BAR (menubar), widget, menu_position);
            }
@@ -1005,6 +1173,10 @@ menu_create_menubar (struct frame *f, Lisp_Object descr)
          }
       }
   }
+
+  /* Attach the new accelerator group to the frame. */
+  gtk_window_add_accel_group (GTK_WINDOW (FRAME_GTK_SHELL_WIDGET(f)),
+                             menubar_accel_group);
 }
 
 \f
@@ -1227,7 +1399,7 @@ gtk_popup_menu (Lisp_Object menu_desc, Lisp_Object event)
   CHECK_STRING (XCAR (menu_desc));
 
   /* Now lets get down to business... */
-  widget = menu_descriptor_to_widget (menu_desc);
+  widget = menu_descriptor_to_widget (menu_desc, NULL);
   menu = GTK_MENU_ITEM (widget)->submenu;
   gtk_widget_set_name (widget, "XEmacsPopupMenu");
   id = gtk_object_get_data (GTK_OBJECT (widget), XEMACS_MENU_GUIID_TAG);
@@ -1271,7 +1443,7 @@ See the definition of `popup-menu' for more information on the format of MENU.
 */
        (menu))
 {
-  GtkWidget *w = menu_descriptor_to_widget (menu);
+  GtkWidget *w = menu_descriptor_to_widget (menu, NULL);
 
   return (w ? build_gtk_object (GTK_OBJECT (w)) : Qnil);
 }