A ::hierarchy_changed signal



There are a number of cases where a widget needs to know
if there is a change in its set of ancestors.

Two examples of this came up in work I was recently doing
on GtkSocket:

 - GtkSocket needs to track whether the toplevel it in
   has the focus so it can propagate that information
   to the GtkPlug.

 - GtkSocket needs to track what toplevel it is in, so
   it can forward accelerators from the socket to the
   toplevel.

Another example involving non-toplevels would be one of 
the schemes that was floated earlier this week for
automatic addition of underline accelerators / mnemonics:

 - When a widget is added to a notebook page or a window,
   it walks up the widget hierarchy, finds the correct
   accelerator group and adds itself to it. 

What is basically needed is an extension of the ::parent_set
signal that GtkWidget has to arbitrary changes in ancestors.

The attached implementation adds a hierarchy_changed 
signal to GtkWidget that is emitted any time the parent
of the widget changes, or the parent of any ancestor
changes.

(I do the recursion here out of the default handler so
that the user can control whether they see pre-recursive
or post-recursive behavior by choosing between connect()
and connect_after(). The recursion could also be done
directly for more a bit more simplicity and robustness.)

Tim had some worries that this could be pretty inefficient,
since worst-case you can get n*(n-1)/2 emissions for n
widgets. (If you have a widget hierarchy without branching
and you build it from the leaf to the root.)

But it's quite hard to construct plausible scenarious where
more than a couple of hundred emissions would be involved
in creating a toplevel, and benchmarking indicates that,
even pre-gsignal-optimization, there isn't much reason
for concern with that.

Conceivably more efficient alternatives include:

 - Having to explicitely register for notification.

   (There is something a bit like this with the cross-references
   in beast/bse/bsecontainer.c, though either I don't 
   understand them or they would need to become more
   complicated to handle the above scenarious.)

   This is more efficient if only a few widgets need
   notification, but as more widgets need notification,
   the book-keeping overhead here could become large.

 - Do notification only when the hierarchy terminates
   at a GtkWindow. This reduces the worst case for
   signal emission from n*(n-1)/2 to n, and should 
   be as good for almost all purposes.

   The only real disadvantage of this is it is a little
   more complicated to explain and a could be little 
   trickier to use in some cases.

Regards,
                                        Owen

 
Index: gtk/gtkwidget.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwidget.c,v
retrieving revision 1.187
diff -u -r1.187 gtkwidget.c
--- gtk/gtkwidget.c	2001/02/28 19:07:45	1.187
+++ gtk/gtkwidget.c	2001/03/03 18:50:31
@@ -56,8 +56,10 @@
   SIZE_ALLOCATE,
   STATE_CHANGED,
   PARENT_SET,
+  HIERARCHY_CHANGED,
   STYLE_SET,
   DIRECTION_CHANGED,
   ADD_ACCELERATOR,
   REMOVE_ACCELERATOR,
   GRAB_FOCUS,
@@ -155,6 +157,7 @@
 						  GtkRequisition    *requisition);
 static void gtk_widget_real_size_allocate	 (GtkWidget	    *widget,
 						  GtkAllocation	    *allocation);
+static void gtk_widget_real_hierarchy_changed    (GtkWidget         *widget);
 static gint gtk_widget_real_key_press_event      (GtkWidget         *widget,
 						  GdkEventKey       *event);
 static gint gtk_widget_real_key_release_event    (GtkWidget         *widget,
@@ -295,8 +298,11 @@
   klass->size_allocate = gtk_widget_real_size_allocate;
   klass->state_changed = NULL;
   klass->parent_set = NULL;
+  klass->hierarchy_changed = gtk_widget_real_hierarchy_changed;
   klass->style_set = gtk_widget_style_set;
   klass->direction_changed = gtk_widget_direction_changed;
   klass->add_accelerator = (void*) gtk_accel_group_handle_add;
   klass->remove_accelerator = (void*) gtk_accel_group_handle_remove;
   klass->grab_focus = gtk_widget_real_grab_focus;
@@ -429,6 +435,13 @@
 		    gtk_marshal_VOID__POINTER,
 		    GTK_TYPE_NONE, 1,
 		    GTK_TYPE_OBJECT);
+  widget_signals[HIERARCHY_CHANGED] =
+    gtk_signal_new ("hierarchy_changed",
+		    GTK_RUN_LAST,
+		    GTK_CLASS_TYPE (object_class),
+		    GTK_SIGNAL_OFFSET (GtkWidgetClass, hierarchy_changed),
+		    gtk_marshal_NONE__NONE,
+		    GTK_TYPE_NONE, 0);
   widget_signals[STYLE_SET] =
     gtk_signal_new ("style_set",
 		    GTK_RUN_FIRST,
@@ -1232,6 +1253,7 @@
   widget->parent = NULL;
   gtk_widget_set_parent_window (widget, NULL);
   gtk_signal_emit (GTK_OBJECT (widget), widget_signals[PARENT_SET], old_parent);
+  gtk_signal_emit (GTK_OBJECT (widget), widget_signals[HIERARCHY_CHANGED]);
   
   gtk_widget_unref (widget);
 }
@@ -2005,6 +2027,22 @@
 }
 
 static void
+hierarchy_changed_foreach (GtkWidget *child, gpointer data)
+{
+  gtk_signal_emit (GTK_OBJECT (child), widget_signals[HIERARCHY_CHANGED]);
+}
+
+static void
+gtk_widget_real_hierarchy_changed (GtkWidget *widget)
+{
+  if (GTK_IS_CONTAINER (widget))
+    {
+      gtk_container_foreach (GTK_CONTAINER (widget),
+			     hierarchy_changed_foreach, NULL);
+    }
+}
+
+static void
 gtk_widget_real_size_allocate (GtkWidget     *widget,
 			       GtkAllocation *allocation)
 {
@@ -2980,6 +3018,7 @@
   gtk_widget_set_style_recurse (widget, NULL);
 
   gtk_signal_emit (GTK_OBJECT (widget), widget_signals[PARENT_SET], NULL);
+  gtk_signal_emit (GTK_OBJECT (widget), widget_signals[HIERARCHY_CHANGED]);
 }
 
 /*****************************************
Index: gtk/gtkwidget.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkwidget.h,v
retrieving revision 1.91
diff -u -r1.91 gtkwidget.h
--- gtk/gtkwidget.h	2001/02/28 19:07:46	1.91
+++ gtk/gtkwidget.h	2001/03/03 18:50:31
@@ -250,10 +250,13 @@
 				GtkStateType   	  previous_state);
   void (* parent_set)	       (GtkWidget        *widget,
 				GtkWidget        *previous_parent);
+  void (* hierarchy_changed)   (GtkWidget        *widget);
   void (* style_set)	       (GtkWidget        *widget,
 				GtkStyle         *previous_style);
   void (* direction_changed)   (GtkWidget        *widget,
 				GtkTextDirection  previous_direction);
   
   /* accelerators */
   gint (* add_accelerator)     (GtkWidget      *widget,




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