Tricky GtkPlug, hierarchy_changed issue.

I've been working some today on really robustifying GtkPlug/GtkSocket
today, in particular making same application plug/socket embedding
work well, using the normal widget mechanisms.

In doing so, I ran into a mildly nasty corner case with
gtk_widget_get_toplevel() that I thought I should write up in case
anybody had bright ideas or at least for posterity.

 * The ::hierarchy_changed signal is a recently introduced
   signal that is sent to a widget when it its
   "anchored state" changed. A widget is anchored
   when its topmost ancestor is a real toplevel widget
   (only anchored widgets can be realized or mapped).
   ::hierarchy_changed is emitted when the widget changes
   from anchored to unanchored or unanchored to anchored.
   If a widget is moved from one toplevel to another,
   ::hierarchy_changed is emitted on it twice.

   gtk_widget_get_toplevel() doesn't always get a real
   toplevel - it just gets the topmost ancestor. So,
   before my recent GtkPlug changes, the way to find
   the real toplevel was typically one of:

    toplevel = gtk_widget_get_toplevel (widget);
    if (GTK_IS_WINDOW (toplevel))

    toplevel = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
    if (toplevel)

 * In order to have efficient local GtkPlug/GtkSocket embedding,
   I made GtkPlug able to act as either a toplevel or a child
   widget. This involves code like:

    static void
    gtk_plug_hide (GtkWidget *widget)
      if (GTK_WIDGET_TOPLEVEL (widget))
         GTK_WIDGET_CLASS (parent_class)->hide (widget);
         GTK_WIDGET_CLASS (bin_class)->hide (widget);

   Which works quite well. However, 
   gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW) no longer
   is reliable, because a GtkPlug can be a intermediate
   ancestor, and a GtkPlug is a descendent of GtkWindow.
   So, it's necessary to encourage people to use 
   gtk_widget_get_toplevel (widget) instead. Which is
   fine ... it's considerably faster anyways.

 * But it turns out that 
     toplevel = gtk_widget_get_toplevel (widget);
     if (GTK_IS_WINDOW (toplevel))

   Is no longer quite correct either. By correct, I mean
   that it doesn't correspond to the quantity that 
   ::hierarchy changed is notifying on.

   The reason why is that when I transform a child GtkPlug
   into a toplevel GtkPlug, the sequence of operations

     - Unparent the GtkPlug (::hierarchy_changed emitted
       for unanchoring)
     - Change the GtkPlug into a toplevel
     - Emit ::hierarchy_changed for anchoring.

   During the first ::hierarchy_changed, the children
   of the GtkPlug are unanchored - they are not descendents
   of a real toplevel. However, gtk_widget_get_toplevel (widget);
   does return a GtkWindow. So, the _actual_ correct thing
   to write is:
     toplevel = gtk_widget_get_toplevel (widget);
     if (GTK_IS_TOPLEVEL (toplevel))

   I've documented this in the gtk_widget_get_toplevel() docs,
   and that may be good enough since this corner case only matters

     a) You are writing a widget
     b) You care that your widget works inside a GtkPlug
     c) You connect to ::hierarchy_changed
     d) You get the toplevel inside your ::hierarchy_changed
        signal without checking whether you are being anchored
        or unanchored. (You can tell which is the case
        from the previous_toplevel argument which will
        be NULL for anchoring and the the old toplevel
        for anchoring.)

   Still it makes me a little nervous ... if I we could ignore    
   backwards compatibility, gtk_widget_get_toplevel() should
   return NULL unlesss GTK_IS_TOPLEVEL (result). BUt that
   makes a lot of existing code that doesn't check the
   result less robust.

   The other solution would be to remove the idea of first
   unanchoring and then anchoring, and then write some
   code so that for this case (and for gtk_widget_reparent
   as well), the deanchoring ::hierarchy_changed signals
   could be skipped entirely.


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