Notification of accelerator changes



Another problem that I ran into while working on plug/socket was a
need to keep track of what accelerators were registered for a given
toplevel.

This needs to be tracked for GtkPlug to keep track of what
accelerators it needs to pass to its parent GtkSocket and from there
to the actual toplevel.

[ 
  The way that the nascent XEMBED spec works is that all
  accelerators are registered with the toplevel and when
  a keystroke for one of the accelerators is detected,
  it sends it to the appriopriate child, bypassing normal
  key event propagation.
]

My first attempt at this is appended.

What I did was:

 * Made GtkAccelGroup a GObject

 * Added a ::changed signal to GtkAccelGroup for notification
   when the set of accelerators for a GtkAccelGroup changes.

   In order to keep this reasonably efficient, I emit the
   signal out of high-priority (GDK_PRIORITY_EVENTS - 1)
   idle handler.

 * Added a method:

   void gtk_accel_group_get_entries (GtkAccelGroup  *accel_group,
	  			     GtkAccelEntry **entries,
				     gint           *n_entries);

 * Add a ->accel_entries_changed() virtual function (not 
   signal) to GtkWindow that is called when accelerator
   groups are added and removed from the window, and 
   when any of the attached accelerator groups emits
   ::changed.

The code also includes a change to check accelerators _before_
sending events to the focused widget, instead of after. The
rational for this is:

 - Accelerators are visibly documented, while keybindings beyond
   the most basic are invisible and known only to power users.

   Hence, the user's expectation that accelerators work is 
   stronger; it looks more like a bug for an accelerator not
   to work than for a keybinding to be overriden by an accelerator.

 - It corresponds to what other toolkits do, so provides more
   opportunity for integration.

In a properly designed application, this change should be make no
difference, since there should be no collisions. But in case of
collisions, I believe it is correct for the accelerators to win.

In other words, the accelerator shalt smite the key bindings and
through its puissance render them vanquished. (*)

I'm not entirely comfortable with notification part of the patch,
since it seems hacky, and I see little general application for this
facility.  There is some argument that it should be documented as an
internal implementation detail of GTK+ for use with GtkPlug alone.

If people (especially Tim) have ideas about how to better structure
this, I'd be glad to hear them.

Regards,
                                        Owen

(*) This paragraph dedicated to jrb


Index: gtk/gtk-boxed.defs
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtk-boxed.defs,v
retrieving revision 1.12
diff -u -r1.12 gtk-boxed.defs
--- gtk/gtk-boxed.defs	2001/01/01 20:26:10	1.12
+++ gtk/gtk-boxed.defs	2001/03/03 18:50:31
@@ -9,10 +9,6 @@
 
 ;;; Gtk boxed types
 
-(define-boxed GtkAccelGroup
-  gtk_accel_group_ref
-  gtk_accel_group_unref)
-
 (define-boxed GtkSelectionData
   gtk_selection_data_copy
   gtk_selection_data_free)
Index: gtk/gtkaccelgroup.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkaccelgroup.c,v
retrieving revision 1.20
diff -u -r1.20 gtkaccelgroup.c
--- gtk/gtkaccelgroup.c	2000/10/25 22:34:11	1.20
+++ gtk/gtkaccelgroup.c	2001/03/03 18:50:31
@@ -31,9 +31,14 @@
 #include <string.h>
 #include "gtkaccelgroup.h"
 #include "gdk/gdkkeysyms.h"
+#include "gtkmarshal.h"
 #include "gtksignal.h"
 #include "gtkwidget.h"
 
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
 
 /* --- signals --- */
 typedef void (*GtkSignalAddAccelerator)	   (GtkObject	    *object,
@@ -59,9 +64,10 @@
 static const gchar	*accel_entries_key = "gtk-accel-entries";
 static guint		 accel_entries_key_id = 0;
 static GHashTable	*accel_entry_hash_table = NULL;
-static GMemChunk	*accel_tables_mem_chunk = NULL;
 static GMemChunk	*accel_entries_mem_chunk = NULL;
 
+static GObjectClass     *parent_class;
+static guint signals[LAST_SIGNAL] = { 0 };
 
 /* --- functions --- */
 static gint
@@ -95,11 +101,9 @@
   return h;
 }
 
-GtkAccelGroup*
-gtk_accel_group_new (void)
+void
+gtk_accel_group_init (GtkAccelGroup *accel_group)
 {
-  GtkAccelGroup *accel_group;
-  
   if (!accel_groups_key_id)
     {
       accel_groups_key_id = g_quark_from_static_string (accel_groups_key);
@@ -107,22 +111,78 @@
       
       accel_entry_hash_table = g_hash_table_new (gtk_accel_entries_hash,
 						 gtk_accel_entries_equal);
-      
-      accel_tables_mem_chunk = g_mem_chunk_create (GtkAccelGroup, 8, G_ALLOC_AND_FREE);
+
       accel_entries_mem_chunk = g_mem_chunk_create (GtkAccelEntry, 64, G_ALLOC_AND_FREE);
     }
   
-  accel_group = g_chunk_new (GtkAccelGroup, accel_tables_mem_chunk);
-  
-  accel_group->ref_count = 1;
   accel_group->lock_count = 0;
   accel_group->modifier_mask = gtk_accelerator_get_default_mod_mask ();
   accel_group->attach_objects = NULL;
+}
+
+void
+gtk_accel_group_finalize (GObject *object)
+{
+  GtkAccelGroup *accel_group = GTK_ACCEL_GROUP (object);
+
+  if (accel_group->changed_idle)
+    {
+      g_source_remove (accel_group->changed_idle);
+      accel_group->changed_idle = 0;
+    }
+}
+
+void
+gtk_accel_group_class_init (GtkAccelGroupClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  
+  parent_class = g_type_class_peek_parent (class);
+
+  gobject_class->finalize = gtk_accel_group_finalize;
+
+  signals[CHANGED] = g_signal_newc ("changed",
+				    G_TYPE_FROM_CLASS (class),
+				    G_SIGNAL_RUN_LAST,
+				    G_STRUCT_OFFSET (GtkAccelGroupClass, changed),
+				    NULL,
+				    gtk_marshal_NONE__NONE,
+				    G_TYPE_NONE, 0);
+}
+
+GType
+gtk_accel_group_get_type (void)
+{
+  static GtkType accel_group_type = 0;
+  
+  if (!accel_group_type)
+    {
+      static const GTypeInfo accel_group_info =
+      {
+	sizeof (GtkAccelGroupClass),
+	NULL,           /* base_init */
+	NULL,           /* base_finalize */
+	(GClassInitFunc) gtk_accel_group_class_init,
+	NULL,           /* class_finalize */
+	NULL,           /* class_data */
+	sizeof (GtkAccelGroup),
+	8,              /* n_preallocs */
+	(GInstanceInitFunc) gtk_accel_group_init,
+      };
+
+      accel_group_type = g_type_register_static (G_TYPE_OBJECT, "GtkAccelGroup", &accel_group_info, 0);
+    }
   
-  return accel_group;
+  return accel_group_type;
 }
 
 GtkAccelGroup*
+gtk_accel_group_new (void)
+{
+  return GTK_ACCEL_GROUP (g_object_new (GTK_TYPE_ACCEL_GROUP, NULL));
+}
+
+GtkAccelGroup*
 gtk_accel_group_get_default (void)
 {
   if (!default_accel_group)
@@ -134,26 +194,40 @@
 GtkAccelGroup*
 gtk_accel_group_ref (GtkAccelGroup	*accel_group)
 {
-  g_return_val_if_fail (accel_group != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_ACCEL_GROUP (accel_group), NULL);
   
-  accel_group->ref_count += 1;
-  
-  return accel_group;
+  return (GtkAccelGroup *)g_object_ref (G_OBJECT (accel_group));
 }
 
 void
 gtk_accel_group_unref (GtkAccelGroup  *accel_group)
 {
-  g_return_if_fail (accel_group != NULL);
-  g_return_if_fail (accel_group->ref_count > 0);
-  
-  accel_group->ref_count -= 1;
-  if (accel_group->ref_count == 0)
+  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
+
+  g_object_unref (G_OBJECT (accel_group));
+}
+
+static gboolean
+gtk_accel_group_changed_idle (gpointer data)
+{
+  GtkAccelGroup *accel_group = data;
+
+  accel_group->changed_idle = 0;
+
+  g_signal_emit (accel_group, signals[CHANGED], 0);
+
+  return FALSE;
+}
+
+static void
+gtk_accel_group_entries_changed (GtkAccelGroup *accel_group)
+{
+  if (!accel_group->changed_idle)
     {
-      g_return_if_fail (accel_group != default_accel_group);
-      g_return_if_fail (accel_group->attach_objects == NULL);
-      
-      g_chunk_free (accel_group, accel_tables_mem_chunk);
+      accel_group->changed_idle = g_idle_add_full (GDK_PRIORITY_EVENTS - 1,
+						   gtk_accel_group_changed_idle,
+						   accel_group,
+						   NULL);
     }
 }
 
@@ -495,6 +569,8 @@
       entry = slist->data;
       
       g_hash_table_remove (accel_entry_hash_table, entry);
+
+      gtk_accel_group_entries_changed (entry->accel_group);
       gtk_accel_group_unref (entry->accel_group);
       g_chunk_free (entry, accel_entries_mem_chunk);
     }
@@ -544,6 +620,8 @@
 			    NULL);
       slist = g_slist_prepend (slist, entry);
       gtk_object_set_data_by_id (object, accel_entries_key_id, slist);
+      
+      gtk_accel_group_entries_changed (accel_group);
     }
 }
 
@@ -634,7 +712,8 @@
 					       GTK_SIGNAL_FUNC (gtk_accel_group_delete_entries),
 					       NULL);
 	      gtk_object_set_data_by_id (object, accel_entries_key_id, slist);
-	      
+
+	      gtk_accel_group_entries_changed (accel_group);	      
 	      gtk_accel_group_unref (accel_group);
 	      
 	      g_chunk_free (entry, accel_entries_mem_chunk);
@@ -702,6 +781,46 @@
   
   return gtk_object_get_data_by_id (object, accel_entries_key_id);
 }
+
+
+typedef struct {
+  GtkAccelGroup *accel_group;
+  GArray *out;
+} GetEntriesInfo;
+
+static void
+get_entries_foreach (gpointer key, gpointer val, gpointer user_data)
+{
+  GetEntriesInfo *info = user_data;
+  GtkAccelEntry *entry = val;
+
+  if (entry->accel_group == info->accel_group)
+    g_array_append_val (info->out, *entry);
+}
+
+void
+gtk_accel_group_get_entries (GtkAccelGroup  *accel_group,
+			     GtkAccelEntry **entries,
+			     gint           *n_entries)
+{
+  GetEntriesInfo info;
+
+  g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
+  
+  info.accel_group = accel_group;
+  info.out = g_array_new (FALSE, FALSE, sizeof (GtkAccelEntry));
+
+  g_hash_table_foreach (accel_entry_hash_table, get_entries_foreach, &info);
+
+  if (n_entries)
+    *n_entries = info.out->len;
+    
+  if (entries)
+    *entries = (GtkAccelEntry *)info.out->data;
+
+  g_array_free (info.out, entries == NULL);
+}
+
 
 gboolean
 gtk_accelerator_valid (guint		  keyval,
Index: gtk/gtkaccelgroup.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkaccelgroup.h,v
retrieving revision 1.9
diff -u -r1.9 gtkaccelgroup.h
--- gtk/gtkaccelgroup.h	2000/08/30 00:33:36	1.9
+++ gtk/gtkaccelgroup.h	2001/03/03 18:50:31
@@ -41,8 +41,16 @@
 #endif /* __cplusplus */
 
 
-typedef struct _GtkAccelGroup	GtkAccelGroup;
-typedef struct _GtkAccelEntry	GtkAccelEntry;
+#define GTK_TYPE_ACCEL_GROUP            (gtk_accel_group_get_type ())
+#define GTK_ACCEL_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ACCEL_GROUP, GtkAccelGroup))
+#define GTK_ACCEL_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ACCEL_GROUP, GtkAccelGroupClass))
+#define GTK_IS_ACCEL_GROUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ACCEL_GROUP))
+#define GTK_IS_ACCEL_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ACCEL_GROUP))
+#define GTK_ACCEL_GROUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ACCEL_GROUP, GtkAccelGroupClass))
+
+typedef struct _GtkAccelGroup	    GtkAccelGroup;
+typedef struct _GtkAccelGroupClass  GtkAccelGroupClass;
+typedef struct _GtkAccelEntry	    GtkAccelEntry;
 
 typedef enum
 {
@@ -63,10 +71,19 @@
 
 struct _GtkAccelGroup
 {
-  guint	          ref_count;
+  GObject         parent_instance;
+  
   guint	          lock_count;
   GdkModifierType modifier_mask;
   GSList         *attach_objects;
+  guint           changed_idle;
+};
+
+struct _GtkAccelGroupClass
+{
+  GObjectClass    parent_class;
+
+  void (*changed) (GtkAccelGroup *accel_group);
 };
 
 struct _GtkAccelEntry
@@ -98,6 +115,7 @@
 
 /* Accelerator Groups
  */
+GType           gtk_accel_group_get_type        (void);
 GtkAccelGroup*  gtk_accel_group_new	      	(void);
 GtkAccelGroup*  gtk_accel_group_get_default    	(void);
 GtkAccelGroup*  gtk_accel_group_ref	     	(GtkAccelGroup	*accel_group);
@@ -163,8 +181,10 @@
  */
 GSList*	gtk_accel_groups_from_object		(GtkObject	*object);
 GSList*	gtk_accel_group_entries_from_object	(GtkObject	*object);
-
 
+void gtk_accel_group_get_entries (GtkAccelGroup  *accel_group,
+				  GtkAccelEntry **entries,
+				  gint           *n_entries);
 
 #ifdef __cplusplus
 }
Index: gtk/gtkwindow.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwindow.c,v
retrieving revision 1.99
diff -u -r1.99 gtkwindow.c
--- gtk/gtkwindow.c	2001/02/28 19:07:46	1.99
+++ gtk/gtkwindow.c	2001/03/03 18:50:31
@@ -617,15 +617,46 @@
   gtk_widget_queue_resize (GTK_WIDGET (window));
 }
 
+static void
+accel_entries_changed (GtkAccelGroup *accel_group, GtkWindow *window)
+{
+  GtkWindowClass *class = GTK_WINDOW_GET_CLASS (window);
+
+  if (class->accel_entries_changed)
+    class->accel_entries_changed (window);
+}
+
+static void
+disconnect_accel_entries_changed (GtkWindow *window, GtkAccelGroup *accel_group)
+{
+  GtkWindowClass *class = GTK_WINDOW_GET_CLASS (window);
+
+  g_signal_handlers_disconnect_matched (accel_group, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+					0, 0, NULL, accel_entries_changed, window);
+
+  if (class->accel_entries_changed)
+    class->accel_entries_changed (window);
+}
+
 void
 gtk_window_add_accel_group (GtkWindow        *window,
 			    GtkAccelGroup    *accel_group)
 {
+  GtkWindowClass *class;
+
   g_return_if_fail (window != NULL);
   g_return_if_fail (GTK_IS_WINDOW (window));
   g_return_if_fail (accel_group != NULL);
 
+  g_signal_connect_data (accel_group, "changed", accel_entries_changed, window, NULL, FALSE, FALSE);
+  gtk_signal_connect (GTK_OBJECT (window), "destroy",
+		      GTK_SIGNAL_FUNC (disconnect_accel_entries_changed), accel_group);
+
   gtk_accel_group_attach (accel_group, GTK_OBJECT (window));
+
+  class = GTK_WINDOW_GET_CLASS (window);
+  if (class->accel_entries_changed)
+    class->accel_entries_changed (window);
 }
 
 void
@@ -636,6 +667,8 @@
   g_return_if_fail (GTK_IS_WINDOW (window));
   g_return_if_fail (accel_group != NULL);
 
+  disconnect_accel_entries_changed (window, accel_group);
+
   gtk_accel_group_detach (accel_group, GTK_OBJECT (window));
 }
 
@@ -1642,9 +1675,10 @@
 
   window = GTK_WINDOW (widget);
 
-  handled = FALSE;
-  
-  if (window->focus_widget &&
+  handled = gtk_accel_groups_activate (GTK_OBJECT (window), event->keyval, event->state);
+
+  if (!handled &&
+      window->focus_widget &&
       window->focus_widget != widget &&
       GTK_WIDGET_IS_SENSITIVE (window->focus_widget))
     {
@@ -1652,9 +1686,6 @@
     }
     
   if (!handled)
-    handled = gtk_accel_groups_activate (GTK_OBJECT (window), event->keyval, event->state);
-
-  if (!handled)
     {
       switch (event->keyval)
 	{
Index: gtk/gtkwindow.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwindow.h,v
retrieving revision 1.26
diff -u -r1.26 gtkwindow.h
--- gtk/gtkwindow.h	2001/02/27 20:40:14	1.26
+++ gtk/gtkwindow.h	2001/03/03 18:50:31
@@ -104,6 +104,7 @@
 			    GtkWidget *focus);
   gboolean (* frame_event) (GtkWidget *widget,
 			    GdkEvent  *event);
+  void     (* accel_entries_changed) (GtkWindow *window);
 };
 
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]