Re: cellrenderer GET_SIZE super method



Kevin Ryde wrote:
I struck a bit of trouble with Gtk2::Ex::Datasheet::DBI not having a
GET_SIZE method in its Gtk2::Ex::Datasheet::DBI::CellRendererText
package.  (RT 36996)

Nosing around the GtkCellRenderer.xs fallback GET_SIZE, I wondered if it
could look up through the classes of the given object instead of using
"caller".  The first superclass which had class->get_size func not equal
to gtk2perl_cell_renderer_get_size would be the one to call, would it?

This is actually a very long-standing problem.  And your suggestion of looking
for the first non-Perl class in the ancestry might very well solve most of the
issues!

The attached caller-v1.patch was my first attempt at this from four years (!)
ago.  It did seem to fix most of the problems I saw, but it fails in one
important case, as muppet explained back then: you get endless loops when you
have a hierarchy where a Perl class inherits from a Perl class.  Something
like this:

  ...
  +- GtkCellRenderer
     +- Foo::RendererOne
        +- Foo::RendererTwo

When something calls the get_size vfunc on a Foo::RendererTwo,
Foo::RendererTwo::GET_SIZE is invoked if present.  If it's not present, or if
it chains up, then Foo::RendererOne::GET_SIZE is called.  If this one is not
present, or if it chains up, our fallback Gtk2::CellRenderer::GET_SIZE kicks
in.  The fallback uses G_OBJECT_TYPE() on the object, and since
G_OBJECT_TYPE() always returns the bottommost type, this yields the type of
Foo::RendererTwo.  The fallback then finds out that Foo::RendererOne is the
parent of Foo::RendererTwo and goes on to call Foo::RendererOne::GET_SIZE
which eventually gets us into Gtk2::CellRenderer::GET_SIZE again.  Endless loop.

So, if both subclasses either don't provide GET_SIZE or if they both chain up
so that Gtk2::CellRenderer::GET_SIZE is reached, we get an endless loop with
caller-v1.patch due to G_OBJECT_TYPE() always returning the bottommost type.
This was the original reason of using caller() instead of simply G_OBJECT_TYPE().

But as far as I can tell, your suggestion of chaining up to the first parent
that is not a Perl subclass does indeed fix this.  It's implemented and tested
in caller-v2.patch.  The patch contains some more rationale of why I think
this works.  The patch also contains a FIXME comment detailing a scenario
where this approach still results in an endless loop: a Perl class that
inherits from a C class that inherits from a Perl class.

But since this scenario is rather contrived, and since the current
caller()-using code will fail in this case just as well, and since your new
suggestion does fix another problem, I think we should commit caller-v2.patch.

Daniel, Kevin: can you verify that caller-v2.patch fixes the
Gtk2::Ex::Datasheet::DBI::CellRendererText issue even when you revert the
GET_SIZE "no-op" fix you applied already?

muppet: what do you think?

-- 
Bye,
-Torsten
Index: xs/GtkCellRenderer.xs
===================================================================
RCS file: /cvsroot/gtk2-perl/gtk2-perl-xs/Gtk2/xs/GtkCellRenderer.xs,v
retrieving revision 1.32
diff -u -d -p -r1.32 GtkCellRenderer.xs
--- xs/GtkCellRenderer.xs       7 Jan 2008 19:54:49 -0000       1.32
+++ xs/GtkCellRenderer.xs       29 Jun 2008 14:41:10 -0000
@@ -653,23 +653,16 @@ GET_SIZE (GtkCellRenderer * cell, ...)
        Gtk2::CellRenderer::parent_start_editing = 7
     PREINIT:
        GtkCellRendererClass * class;
-       GType thisclass, parent_class;
-       SV * saveddefsv;
+       GType this, parent;
     PPCODE:
-       /* may i ask who's calling? */
-       saveddefsv = newSVsv (DEFSV);
-       eval_pv ("$_ = caller;", 0);
-       thisclass = gperl_type_from_package (SvPV_nolen (DEFSV));
-       SvSetSV (DEFSV, saveddefsv);
-       if (!thisclass)
-               thisclass = G_OBJECT_TYPE (cell);
        /* look up his parent */
-       parent_class = g_type_parent (thisclass);
-       if (! g_type_is_a (parent_class, GTK_TYPE_CELL_RENDERER))
+       this = G_OBJECT_TYPE (cell);
+       parent = g_type_parent (this);
+       if (! g_type_is_a (parent, GTK_TYPE_CELL_RENDERER))
                croak ("parent of %s is not a GtkCellRenderer",
-                      g_type_name (thisclass));
+                      g_type_name (this));
        /* that's our boy.  call one of his functions. */
-       class = g_type_class_peek (parent_class);
+       class = g_type_class_peek (parent);
        switch (ix) {
            case 4: /* deprecated parent_get_size */
            case 0: /* GET_SIZE */
? t/GtkCellRendererIface-Chaining.t
Index: xs/GtkCellRenderer.xs
===================================================================
RCS file: /cvsroot/gtk2-perl/gtk2-perl-xs/Gtk2/xs/GtkCellRenderer.xs,v
retrieving revision 1.32
diff -u -d -p -r1.32 GtkCellRenderer.xs
--- xs/GtkCellRenderer.xs       7 Jan 2008 19:54:49 -0000       1.32
+++ xs/GtkCellRenderer.xs       29 Jun 2008 16:52:00 -0000
@@ -652,36 +652,63 @@ GET_SIZE (GtkCellRenderer * cell, ...)
        Gtk2::CellRenderer::parent_activate      = 6
        Gtk2::CellRenderer::parent_start_editing = 7
     PREINIT:
-       GtkCellRendererClass * class;
-       GType thisclass, parent_class;
-       SV * saveddefsv;
+       GtkCellRendererClass *parent_class = NULL;
+       GType this, parent;
     PPCODE:
-       /* may i ask who's calling? */
-       saveddefsv = newSVsv (DEFSV);
-       eval_pv ("$_ = caller;", 0);
-       thisclass = gperl_type_from_package (SvPV_nolen (DEFSV));
-       SvSetSV (DEFSV, saveddefsv);
-       if (!thisclass)
-               thisclass = G_OBJECT_TYPE (cell);
-       /* look up his parent */
-       parent_class = g_type_parent (thisclass);
-       if (! g_type_is_a (parent_class, GTK_TYPE_CELL_RENDERER))
-               croak ("parent of %s is not a GtkCellRenderer",
-                      g_type_name (thisclass));
-       /* that's our boy.  call one of his functions. */
-       class = g_type_class_peek (parent_class);
+       /* look up the parent.
+        *
+        * FIXME: this approach runs into an endless loop with a hierarchy
+        * where a Perl class inherits from a C class which inherits from a
+        * Perl class.  Like this:
+        *
+        *   ...
+        *   +- GtkCellRenderer
+        *      +- Foo::RendererOne             (Perl subclass)
+        *         +- FooRendererTwo            (C subclass)
+        *            +- Foo::RendererThree     (Perl subclass)
+        *
+        * yes, this is contrived.  But possible!
+        */
+       this = G_OBJECT_TYPE (cell);
+       while ((parent = g_type_parent (this))) {
+               if (! g_type_is_a (parent, GTK_TYPE_CELL_RENDERER))
+                       croak ("parent of %s is not a GtkCellRenderer",
+                              g_type_name (this));
+
+               parent_class = g_type_class_peek (parent);
+
+               /* check if this class isn't actually one of ours.  if it is a
+                * Perl class, then we must not chain up to it: if it had a sub
+                * defined for the current vfunc, we wouldn't be in this
+                * fallback one here; so chaining up would result in the
+                * fallback being called again.  this will lead to an endless
+                * loop.
+                *
+                * so, if it's not a Perl class, we're done.  if it is,
+                * continue in the while loop to the next parent. */
+               if (parent_class->get_size != gtk2perl_cell_renderer_get_size) {
+                       break;
+               }
+
+               this = parent;
+       }
+
+       /* the ancestry will always contain GtkCellRenderer, so parent and
+        * parent_class should never be NULL. */
+       assert (parent != NULL && parent_class != NULL);
+
        switch (ix) {
            case 4: /* deprecated parent_get_size */
            case 0: /* GET_SIZE */
-               if (class->get_size) {
+               if (parent_class->get_size) {
                        gint x_offset, y_offset, width, height;
-                       class->get_size (cell,
-                                        SvGtkWidget (ST (1)),
-                                        SvGdkRectangle_ornull (ST (2)),
-                                        &x_offset,
-                                        &y_offset,
-                                        &width,
-                                        &height);
+                       parent_class->get_size (cell,
+                                               SvGtkWidget (ST (1)),
+                                               SvGdkRectangle_ornull (ST (2)),
+                                               &x_offset,
+                                               &y_offset,
+                                               &width,
+                                               &height);
                        EXTEND (SP, 4);
                        PUSHs (sv_2mortal (newSViv (x_offset)));
                        PUSHs (sv_2mortal (newSViv (y_offset)));
@@ -691,41 +718,41 @@ GET_SIZE (GtkCellRenderer * cell, ...)
                break;
            case 5: /* deprecated parent_render */
            case 1: /* RENDER */
-               if (class->render)
-                       class->render (cell,
-                                      SvGdkDrawable_ornull (ST (1)), /* drawable */
-                                      SvGtkWidget_ornull (ST (2)), /* widget */
-                                      SvGdkRectangle_ornull (ST (3)), /* background_area */
-                                      SvGdkRectangle_ornull (ST (4)), /* cell_area */
-                                      SvGdkRectangle_ornull (ST (5)), /* expose_area */
-                                      SvGtkCellRendererState (ST (6))); /* flags */
+               if (parent_class->render)
+                       parent_class->render (cell,
+                                             SvGdkDrawable_ornull (ST (1)), /* drawable */
+                                             SvGtkWidget_ornull (ST (2)), /* widget */
+                                             SvGdkRectangle_ornull (ST (3)), /* background_area */
+                                             SvGdkRectangle_ornull (ST (4)), /* cell_area */
+                                             SvGdkRectangle_ornull (ST (5)), /* expose_area */
+                                             SvGtkCellRendererState (ST (6))); /* flags */
                break;
            case 6: /* deprecated parent_activate */
            case 2: /* ACTIVATE */
-               if (class->activate) {
+               if (parent_class->activate) {
                        gboolean ret;
-                       ret = class->activate (cell,
-                                              SvGdkEvent (ST (1)),
-                                              SvGtkWidget (ST (2)),
-                                              SvGChar (ST (3)),
-                                              SvGdkRectangle_ornull (ST (4)),
-                                              SvGdkRectangle_ornull (ST (5)),
-                                              SvGtkCellRendererState (ST (6)));
+                       ret = parent_class->activate (cell,
+                                                     SvGdkEvent (ST (1)),
+                                                     SvGtkWidget (ST (2)),
+                                                     SvGChar (ST (3)),
+                                                     SvGdkRectangle_ornull (ST (4)),
+                                                     SvGdkRectangle_ornull (ST (5)),
+                                                     SvGtkCellRendererState (ST (6)));
                        EXTEND (SP, 1);
                        PUSHs (sv_2mortal (newSViv (ret)));
                }
                break;
            case 7: /* deprecated parent_start_editing */
            case 3: /* START_EDITING */
-               if (class->start_editing) {
+               if (parent_class->start_editing) {
                        GtkCellEditable * editable;
-                       editable = class->start_editing (cell,
-                                                        SvGdkEvent_ornull (ST (1)),
-                                                        SvGtkWidget (ST (2)),
-                                                        SvGChar (ST (3)),
-                                                        SvGdkRectangle_ornull (ST (4)),
-                                                        SvGdkRectangle_ornull (ST (5)),
-                                                        SvGtkCellRendererState (ST (6)));
+                       editable = parent_class->start_editing (cell,
+                                                               SvGdkEvent_ornull (ST (1)),
+                                                               SvGtkWidget (ST (2)),
+                                                               SvGChar (ST (3)),
+                                                               SvGdkRectangle_ornull (ST (4)),
+                                                               SvGdkRectangle_ornull (ST (5)),
+                                                               SvGtkCellRendererState (ST (6)));
                        EXTEND (SP, 1);
                        PUSHs (sv_2mortal (newSVGtkCellEditable_ornull (editable)));
                }
--- /dev/null   2008-04-13 20:57:27.000000000 +0200
+++ t/GtkCellRendererIface-Chaining.t   2008-06-29 18:10:19.000000000 +0200
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+# $Id$
+
+use Gtk2::TestHelper tests => 2;
+
+package CellRendererFoo;
+
+use Glib::Object::Subclass
+       Gtk2::CellRendererText::,
+       ;
+
+our $hits = 0;
+
+sub GET_SIZE {
+       #warn __PACKAGE__;
+       $hits++;
+       if ($hits > 50) {
+               die 'Overflow';
+       }
+       shift->SUPER::GET_SIZE (@_);
+}
+
+package CellRendererBar;
+
+use Glib::Object::Subclass
+       CellRendererFoo::,
+       ;
+
+our $hits = 0;
+
+sub GET_SIZE {
+       #warn __PACKAGE__;
+       $hits++;
+       shift->SUPER::GET_SIZE (@_);
+}
+
+package main;
+
+my $model = Gtk2::ListStore->new ('Glib::String');
+foreach (qw/foo fluffy flurble frob frobnitz ftang fire/) {
+       my $iter = $model->append;
+       $model->set ($iter, 0 => $_);
+}
+
+my $view = Gtk2::TreeView->new ($model);
+
+my $renderer = CellRendererBar->new;
+my $column = Gtk2::TreeViewColumn->new_with_attributes ('F-Words', $renderer,
+                                                        text => 0);
+$view->append_column ($column);
+
+my $window = Gtk2::Window->new;
+$window->add ($view);
+
+ok (eval { $window->show_all; 1; }, 'no overflow');
+ok ($CellRendererFoo::hits == $CellRendererBar::hits,
+    'both classes were hit just as often');


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