Notification of shadowing by gtk_grab_add()



In which our saga concludeth with the strange tale of the
Sir GtkPlug and the modal dialog.

Here's one more issue that came up working on plug/socket,
though I think some aspects of it have wider applicability
as well.

An issue that the current plug/socket implementation has is
that when an application-modal dialog is up, plugs embedded
inside the application don't know about it and will continue
receiving and processing events.

In order to handle this properly, the socket needs to be
notified when it is shadowed by a gtk_grab_add(). (A widget
is shadowed by a gtk_grab_add() when the topmost grab widget
in the grab stack is not its ancestor.)

My original attempt at an implementation is attached. What
this patch does is add a signal:

  void (* grab_notify)         (GtkWidget        *widget,
				gboolean          was_grabbed);

When a widget is becomes shadowed, it receives a ::grab_notify
signal with was_grabbed=FALSE. When a widget becomes unshadowed,
it gets a ::grab_notify signal with was_grabbed=TRUE.

Since the implementation is pretty intertwined, the patch
also includes the grab-group code I mentioned a few
weeks ago in the thread "application modality vs. window modality".

(The relationship here, other than the involvement of grabs, is 
that when a plug is notified that it was shadowed by a modal grab, 
it needs some way of blocking events. It does that by creating a 
grab group containing just the plug and a dummy window.)


It occurs to me, however, that by modifying ::grab_notify a
bit it could be made more elegant, and perhaps could even
provide the basis for a solution of #1579 "Setting Gtk
widget insensitive can lock GUI".

The idea is that we introduce the concept of the "event state"
for a widget. It can be one of:

 GTK_EVENT_STATE_NORMAL     - the widget receives events
 GTK_EVENT_STATE_SHADOWED   - the widget does not receive events because
                              it is shadowed by a gtk_grab_add()
 GTK_EVENT_STATE_INSENSTIVE - the widget does not receive events because
                              it is insensitive.

Notification of changes to this can either be done with 
a traditional gtk_widget_get_event_state()/::event_state_changed signal,
or spiffier, with a ::event_state read-only property.


My proposed resolution to #1579 would then be:

 a) Port my patch: "only sensitive widgets can hold a GTK+ grab"
    from the GTK+-1.2 branch to HEAD

 b) Require that the rare widgets that need to do an explicit
    gdk_pointer_grab() or gdk_keyboard_grab() monitor the widgets
    event state, and ungrab if the state changes to something
    other than GTK_EVENT_STATE_NORMAL.


So, there ends my account of changes to the GTK+ core that
have come up in my quest for for a really robust and fully
functional plug/socket implementation.

I'll give people a few days to comment, then start committing
this stuff, starting from the more straightforward parts.

Regards,
                                        Owen

Index: gtk/gtkmain.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmain.c,v
retrieving revision 1.151
diff -u -r1.151 gtkmain.c
--- gtk/gtkmain.c	2001/02/27 20:40:05	1.151
+++ gtk/gtkmain.c	2001/03/03 18:50:31
@@ -101,6 +101,12 @@
 					  gint                source,
 					  GdkInputCondition   condition);
 
+static void gtk_main_get_grabs (GtkWidget  *widget,
+				GSList    **grab_list,
+				GtkWindow **grab_group);
+static void gtk_main_set_grabs (GtkWidget *widget,
+				GSList    *grabs);
+
 #if 0
 static void  gtk_error			 (gchar		     *str);
 static void  gtk_warning		 (gchar		     *str);
@@ -120,9 +126,6 @@
 
 static GSList *main_loops = NULL;      /* stack of currently executing main loops */
 
-static GSList *grabs = NULL;		   /* A stack of unique grabs. The grabbing
-					    *  widget is the first one on the list.
-					    */
 static GList *init_functions = NULL;	   /* A list of init functions.
 					    */
 static GList *quit_functions = NULL;	   /* A list of quit functions.
@@ -750,6 +753,7 @@
   GtkWidget *grab_widget;
   GdkEvent *next_event;
   GList *tmp_list;
+  GSList *grabs;
 
   /* If there are any events pending then get the next one.
    */
@@ -810,6 +814,8 @@
    * gtk_current_event_get().
    */
   current_events = g_list_prepend (current_events, event);
+
+  gtk_main_get_grabs (event_widget, &grabs, NULL);
   
   /* If there is a grab in effect...
    */
@@ -962,23 +968,143 @@
   return FALSE;
 }
 
+static void
+gtk_main_get_grabs (GtkWidget   *widget,
+		    GSList     **grab_list,
+		    GtkWindow  **grab_group)
+{
+  GtkWidget *toplevel = NULL;
+
+  if (widget)
+    toplevel = gtk_widget_get_toplevel (widget);
+
+  if (toplevel && GTK_IS_WINDOW (toplevel))
+    {
+      if (grab_list)
+	*grab_list = _gtk_window_get_grabs (GTK_WINDOW (toplevel));
+      if (grab_group)
+	*grab_group = gtk_window_get_grab_group (GTK_WINDOW (toplevel));
+    }
+  else
+    {
+      if (grab_list)
+	*grab_list = _gtk_window_get_grabs (NULL);
+      if (grab_group)
+	*grab_group = NULL;
+    }
+}
+
+static void
+gtk_main_set_grabs (GtkWidget *widget, GSList *grabs)
+{
+  GtkWidget *toplevel = NULL;
+
+  if (widget)
+    toplevel = gtk_widget_get_toplevel (widget);
+
+  if (toplevel && GTK_IS_WINDOW (toplevel))
+    _gtk_window_set_grabs (GTK_WINDOW (toplevel), grabs);
+  else
+    _gtk_window_set_grabs (NULL, grabs);
+}
+
+typedef struct
+{
+  gboolean was_grabbed;
+  GtkWidget *grab_widget;
+} GrabNotifyInfo;
+
+
+static void
+gtk_grab_notify_foreach (GtkWidget *child,
+			 gpointer   data)
+			 
+{
+  GrabNotifyInfo *info = data;
+
+  if (child != info->grab_widget)
+    {
+      g_object_ref (G_OBJECT (child));
+
+      gtk_signal_emit_by_name (GTK_OBJECT (child), "grab_notify", info->was_grabbed);
+
+      if (GTK_IS_CONTAINER (child))
+	gtk_container_foreach (GTK_CONTAINER (child), gtk_grab_notify_foreach, info);
+      
+      g_object_unref (G_OBJECT (child));
+    }
+}
+
+static void
+gtk_grab_notify (GtkWindow *grab_group,
+		 GtkWidget *grab_widget,
+		 gboolean   was_grabbed)
+{
+  GList *toplevels;
+  GrabNotifyInfo info;
+
+  info.grab_widget = grab_widget;
+  info.was_grabbed = was_grabbed;
+
+  if (grab_group)
+    g_object_ref (G_OBJECT (grab_group));
+  g_object_ref (G_OBJECT (grab_widget));
+  
+  toplevels = gtk_window_list_toplevels ();
+  while (toplevels)
+    {
+      GtkWidget *toplevel = toplevels->data;
+      GtkWindow *toplevel_grab_group;
+      
+      toplevels = g_list_delete_link (toplevels, toplevels);
+
+      toplevel_grab_group = gtk_window_get_grab_group (GTK_WINDOW (toplevel));
+
+      if (toplevel_grab_group == grab_group)
+	gtk_container_foreach (GTK_CONTAINER (toplevel), gtk_grab_notify_foreach, &info);
+      
+      g_object_unref (G_OBJECT (toplevel));
+    }
+
+  if (grab_group)
+    g_object_unref (G_OBJECT (grab_group));
+  g_object_unref (G_OBJECT (grab_widget));
+}
+
 void
 gtk_grab_add (GtkWidget *widget)
 {
+  GSList *grabs;
+  GtkWindow *grab_group;
+  gboolean was_grabbed;
+  
   g_return_if_fail (widget != NULL);
   
   if (!GTK_WIDGET_HAS_GRAB (widget))
     {
       GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_GRAB);
       
-      grabs = g_slist_prepend (grabs, widget);
+      gtk_main_get_grabs (widget, &grabs, &grab_group);
+
+      was_grabbed = (grabs != NULL);
+      
       gtk_widget_ref (widget);
+      grabs = g_slist_prepend (grabs, widget);
+      
+      gtk_main_set_grabs (widget, grabs);
+
+      if (!was_grabbed)
+	gtk_grab_notify (grab_group, widget, FALSE);
     }
 }
 
 GtkWidget*
 gtk_grab_get_current (void)
 {
+  GSList *grabs;
+
+  gtk_main_get_grabs (NULL, &grabs, NULL);
+  
   if (grabs)
     return GTK_WIDGET (grabs->data);
   return NULL;
@@ -987,14 +1113,23 @@
 void
 gtk_grab_remove (GtkWidget *widget)
 {
+  GSList *grabs;
+  GtkWindow *grab_group;
+  
   g_return_if_fail (widget != NULL);
   
   if (GTK_WIDGET_HAS_GRAB (widget))
     {
       GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_GRAB);
-      
+
+      gtk_main_get_grabs (widget, &grabs, &grab_group);
       grabs = g_slist_remove (grabs, widget);
+      gtk_main_set_grabs (widget, grabs);
+      
       gtk_widget_unref (widget);
+
+      if (!grabs)
+	gtk_grab_notify (grab_group, widget, TRUE);
     }
 }
 
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
@@ -445,6 +458,14 @@
 		    gtk_marshal_VOID__ENUM,
 		    GTK_TYPE_NONE, 1,
 		    GTK_TYPE_TEXT_DIRECTION);
+  widget_signals[GRAB_NOTIFY] =
+    gtk_signal_new ("grab_notify",
+		    GTK_RUN_FIRST,
+		    GTK_CLASS_TYPE (object_class),
+		    GTK_SIGNAL_OFFSET (GtkWidgetClass, grab_notify),
+		    gtk_marshal_VOID__BOOLEAN,
+		    GTK_TYPE_NONE, 1,
+		    GTK_TYPE_BOOL);
   widget_signals[ADD_ACCELERATOR] =
     gtk_accel_group_create_add (GTK_CLASS_TYPE (object_class), GTK_RUN_LAST,
 				GTK_SIGNAL_OFFSET (GtkWidgetClass, add_accelerator));
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);
+  void (* grab_notify)         (GtkWidget        *widget,
+				gboolean          was_grabbed);
   
   /* accelerators */
   gint (* add_accelerator)     (GtkWidget      *widget,
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)
 	{
@@ -2807,8 +2838,81 @@
     }
 }
 
+static const gchar *grab_group_key = "gtk-grab-group";
+static const gchar *grab_key = "gtk-grabs";
+
+static GSList *global_grabs = NULL;
+
+static void
+grab_group_disconnect (GtkObject *window, GtkObject *group)
+{
+  gtk_object_set_data (window, grab_group_key, NULL);
+  gtk_signal_disconnect_by_func (window, grab_group_disconnect, group);
+
+  if (group != window)
+    gtk_signal_disconnect_by_func (group, grab_group_disconnect, window);
+}
+
+void
+gtk_window_set_grab_group (GtkWindow *window,
+			   GtkWindow *grab_group)
+{
+  GtkWindow *old_group = gtk_object_get_data (GTK_OBJECT (window), grab_group_key);
+
+  /* FIXME: We need to handle emitting ::modality_notify as necessary here */
+  
+  if (old_group != grab_group)
+    {
+      if (old_group)
+	grab_group_disconnect (GTK_OBJECT (window), GTK_OBJECT (old_group));
+
+      gtk_object_set_data (GTK_OBJECT (window), grab_group_key, grab_group);
+
+      if (grab_group)
+	{
+	  gtk_signal_connect_object (GTK_OBJECT (grab_group), "destroy",
+				     grab_group_disconnect, GTK_OBJECT (window));
+	  gtk_signal_connect (GTK_OBJECT (window), "destroy",
+			      grab_group_disconnect, grab_group);
+	}
+    }
+}
+
+GtkWindow *
+gtk_window_get_grab_group (GtkWindow *window)
+{
+  return gtk_object_get_data (GTK_OBJECT (window), grab_group_key);
+}
+
+GSList *
+_gtk_window_get_grabs (GtkWindow *window)
+{
+  GtkObject *group = NULL;
+
+  if (window)
+    group = gtk_object_get_data (GTK_OBJECT (window), grab_group_key);
+  
+  if (group)
+    return gtk_object_get_data (group, grab_key);
+  else
+    return global_grabs;
+}
+
+void
+_gtk_window_set_grabs (GtkWindow *window,
+		       GSList    *grabs)
+{
+  GtkObject *group = NULL;
 
+  if (window)
+    group = gtk_object_get_data (GTK_OBJECT (window), grab_group_key);
 
+  if (group)
+    gtk_object_set_data (group, grab_key, grabs);
+  else
+    global_grabs = grabs;
+}
+
 /**
  * gtk_window_present:
  * @window: a #GtkWindow


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