Nested Grabs In GtkSharp

Published on Thursday, April 15, 2010

I ran across this while working on some complex GtkSharp grabbing behavior to mimic window focusing for a docking framework. Turns out there is a weird inconsistency in the way Gtk+ manages the grab stack. While the list of grabbed Widgets is indeed a stack, a flag on each Widget (Widget.HasGrab) is used to check if a Widget has the grab or not. The problem is that Grab.Add (which calls the Gtk+ method gtk_grab_add) never clears the flag for the currently grabbed Widget if you're nesting grabs. That means that every Widget in the grab stack will have Widget.HasGrab set to true. If you try to add a Widget to the grab stack and it's already in the stack (even if there are multiple other grabbed Widgets after it in the stack), it won't get added. Because the flag is set though, it will get removed at the first place it was in the stack on a call to Grab.Remove (gtk_grab_remove).

While it may not be a bug, this is certainly odd behavior. The solution (at least for me) was to write two small utility methods. SafeAdd first removes the Widget.HasGrab flag to ensure that the Widget always gets added to the grab stack, regardless of if it's previously in it. SafeAdd should be paired with SafeRemove which checks the newly grabbed Widget after removing one to make sure it still has the Widget.HasGrab flag set. Note that it doesn't clear the Widget.HasGrab flag for grabbed Widget getting replaced by a new grabbed Widget on the grab stack as you might expect. This maintains compatibility with all the other Gtk code that might be expecting Widgets anywhere in the stack to have the flag set.

public static void SafeAdd(Widget widget)
{
 if (widget == null)
 {
  throw new ArgumentNullException("widget");
 }
 if (widget.Sensitive)
 {
  widget.ClearFlag(WidgetFlags.HasGrab);
  Grab.Add(widget);
 }
}

public static void SafeRemove(Widget widget)
{
 if (widget == null)
 {
  throw new ArgumentNullException("widget");
 }
 Grab.Remove(widget);
 Widget current = Grab.Current;
 if( current != null && !current.HasGrab )
 {
  current.SetFlag(WidgetFlags.HasGrab);
 }
}