perlgtk3cairogobject-introspection

Cairo::SolidPattern is not of type GooCanvas2::CairoPattern


I'm trying to convert old Gtk2 perl script to Gtk3. Here is how the Gtk2 version looks like:

#!/usr/bin/perl

use 5.30.0;
use strict;
use warnings;
use diagnostics;

use Gtk2 '-init';
use Goo::Canvas;

my $canvas = Goo::Canvas->new;
my $pattern = Cairo::SolidPattern->create_rgba(0, 0, 0, 0);
$pattern = Goo::Cairo::Pattern->new($pattern);

my $rect = Goo::Canvas::Rect->new(
    $canvas->get_root_item,
    0, 0, 100, 100,
    'fill-pattern' => $pattern,
    'line-dash'    => Goo::Canvas::LineDash->new([5, 5]),
    'line-width'   => 1,
    'stroke-color' => 'black',
);

This is my Gtk3 attempt:

#!/usr/bin/perl

use 5.30.0;
use strict;
use warnings;
use diagnostics;

use Gtk3 '-init';
use GooCanvas2;

my $canvas = GooCanvas2::Canvas->new;
my $pattern = Cairo::SolidPattern->create_rgba(0, 0, 0, 0);

my $rect = GooCanvas2::CanvasRect->new(
    parent => $canvas->get_root_item,
    x => 0, y => 0, width => 100, height => 100,
    'fill-pattern' => $pattern,
    'line-dash'    => GooCanvas2::CanvasLineDash->newv([5, 5]),
    'line-width'   => 1,
    'stroke-color' => 'black',
);

But this results in error:

Uncaught exception from user code:
    Cairo::SolidPattern=SCALAR(0x558de4acb260) is not of type GooCanvas2::CairoPattern at /usr/lib64/perl5/vendor_perl/5.30.1/x86_64-linux/Glib.pm line 222.
    Glib::Object::_LazyLoader::AUTOLOAD("GooCanvas2::CanvasRect", "parent", GooCanvas2::CanvasGroup=HASH(0x558de4abe1a0), "x", 0, "y", 0, "width", ...) called at test.pl line 14

I also tried to insert $pattern = GooCanvas2::CairoPattern->new($pattern); but that didn't help:

Uncaught exception from user code:
    Could not fetch information for package GooCanvas2::CairoPattern; perhaps it has not been loaded via Glib::Object::Introspection? at test.pl line 13.

But looking at source of GooCanvas2.pm, it does load GooCanvas2 via Glib::Object::Introspection.

Any suggestions how to fix this?


Solution

  • I tried to install libgoocanvas-2.0-dev on Ubuntu 20.04 and it installs the introspection XML file for GooCanvas as /usr/share/gir-1.0/GooCanvas-2.0.gir. In this file I see the definition for the binding for GooCanvas2::CanvasRect->new():

    <class name="CanvasRect"
           c:symbol-prefix="canvas_rect"
           c:type="GooCanvasRect"
           parent="CanvasItemSimple"
           glib:type-name="GooCanvasRect"
           glib:get-type="goo_canvas_rect_get_type"
           glib:type-struct="CanvasRectClass">
      <implements name="CanvasItem"/>
      <function name="new"
                c:identifier="goo_canvas_rect_new"
                introspectable="0">
        <return-value transfer-ownership="full">
          <type name="CanvasItem" c:type="GooCanvasItem*"/>
        </return-value>
        <parameters>
          <parameter name="parent" transfer-ownership="none" skip="1">
            <type name="CanvasItem" c:type="GooCanvasItem*"/>
          </parameter>
          <parameter name="x" transfer-ownership="none">
            <type name="gdouble" c:type="gdouble"/>
          </parameter>
          <parameter name="y" transfer-ownership="none">
            <type name="gdouble" c:type="gdouble"/>
          </parameter>
          <parameter name="width" transfer-ownership="none">
            <type name="gdouble" c:type="gdouble"/>
          </parameter>
          <parameter name="height" transfer-ownership="none">
            <type name="gdouble" c:type="gdouble"/>
          </parameter>
          <parameter name="..." transfer-ownership="none">
            <varargs/>
          </parameter>
        </parameters>
      </function>
    

    I notice that the definition for the fill-pattern parameter is probably included in the last name="..." parameter pack. So it is not clear what type the parameter should be. But later in the file there is maybe a clue, on line 244 we have:

    <glib:boxed glib:name="CairoPattern"
                c:symbol-prefix="cairo_pattern"
                glib:type-name="GooCairoPattern"
                glib:get-type="goo_cairo_pattern_get_type">
    </glib:boxed>
    

    I am not sure what exactly the glib:boxed means, but according to the documentation for Glib::Object::Introspection :

    Classes, interfaces and boxed and fundamental types get their own namespaces, in a way, as the concept of the GType is completely replaced in the Perl bindings by the Perl package name.

    So boxed types get their own namespace in Perl. More about boxed types here.

    The error message:

    Cairo::SolidPattern=SCALAR(0x55a027382d18) is not of type GooCanvas2::CairoPattern ...
    

    Indicates that it expects a GooCanvas2::CairoPattern type, but when I try to use such a type, e.g.

    my $pattern = GooCanvas2::CairoPattern->new();
    

    I get error message:

    Could not fetch information for package GooCanvas2::CairoPattern; perhaps it has not been loaded via Glib::Object::Introspection?
    

    Update:

    The following C program works fine and shows that the pattern should be of type cairo_pattern_t :

    #include <gtk/gtk.h>
    #include <goocanvas.h>
    
    static gboolean
    on_delete_event (GtkWidget *window,
                     GdkEvent  *event,
                     gpointer   unused_data)
    {
        exit (0);
    }
    
    int main(int argc, char *argv[]) {
        gtk_init (&argc, &argv);
        GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        gtk_window_set_default_size (GTK_WINDOW (window), 640, 600);
        g_signal_connect (window, "delete_event", G_CALLBACK (on_delete_event), NULL);
        GtkWidget *scrolled_win = gtk_scrolled_window_new (NULL, NULL);
        gtk_container_add (GTK_CONTAINER (window), scrolled_win);
        GtkWidget *canvas = goo_canvas_new();
        goo_canvas_set_bounds (GOO_CANVAS (canvas), 0, 0, 1000, 1000);
        GooCanvasItem *root = goo_canvas_get_root_item (GOO_CANVAS (canvas));
        cairo_pattern_t *pattern = cairo_pattern_create_rgba( 0.0, 0.0, 1.0, 0.5 );
        GooCanvasItem *rect = goo_canvas_rect_new(
            root, 0, 0, 100, 100,
            "line-width", 1.0,
            "stroke-color", "black",
            "fill-pattern", pattern,
            NULL
        );
        gtk_widget_set_size_request (canvas, 600, 450);
        gtk_container_add (GTK_CONTAINER (scrolled_win), canvas);
        gtk_widget_show_all(window);
        gtk_main ();
        return 0;
    }
    

    Update 2:

    Some more debugging info: The call GooCanvas2::CanvasRect->new() is redirected to line 1327 in GObject.xs:

    SV *
    g_object_new (class, ...)
       // ...
    

    at line 1359 it finds the expected type for the property argument fill-pattern. It looks like this is type G_TYPE_BOXED. Then at line 1378

    gperl_value_from_sv (&params[i].value, ST (FIRST_ARG+i*2+1));
    

    It tries to retrieve the boxed value from the supplied Perl argument (of type Cairo::SolidPattern) which leads to line 136

        case G_TYPE_BOXED:
            /* SVs need special treatment! */
            if (G_VALUE_HOLDS (value, GPERL_TYPE_SV)) {
                g_value_set_boxed (value,
                                   gperl_sv_is_defined (sv)
                                   ? sv : NULL);
            } else {
                g_value_set_static_boxed (
                    value,
                    gperl_get_boxed_check (
                        sv, G_VALUE_TYPE(value)));
            }
    

    The first check fails so it enters the second alternative g_value_set_static_boxed() which first calls gperl_get_boxed_check(), see line 558 in GBoxed.xs. On line 568

    boxed_info = g_hash_table_lookup (info_by_gtype, (gpointer) gtype);
    

    boxed_info is returned as BoxedInfo * with content

    {
      gtype = 93825018188384,
      package = 0x555556e4e7a0 "GooCanvas2::CairoPattern",
      wrapper_class = 0x0
    }
    

    and at line 575 unwrap is set to _default_wrapper_class.unwrap and at line 583:

    return (*unwrap) (gtype, boxed_info->package, sv);
    

    which calls default_boxed_unwrap() at line 420 :

    which croaks at line 429:

    if (!sv_derived_from (sv, package))
        croak ("%s is not of type %s",
               gperl_format_variable_for_output (sv),
               package);
    

    since Cairo::SolidPattern is not derived from GooCanvas2::CairoPattern.

    Update 3:

    Looking at GooCanvas's source, it defines several setters/getters for the pattern: fill-color, fill-color-rgba, fill-color-gdk-rgba, fill-pixbuf, fill-pattern, all of which internally set/get the same property goo_canvas_style_fill_pattern_id. And other setters don't have the issue of having the wrong introspection wrapper to cairo_pattern_t. Therefore this works:

    my $rect = GooCanvas2::CanvasRect->new(
      ...
      'fill-color-gdk-rgba' => Gtk3::Gdk::RGBA::parse('red'),
    );
    

    Moreover, after $rect is constructed, we can get GooCanvas2::CairoPattern from it via $rect->get('fill-pattern').

    This doesn't let use other patterns created from cairo (linear, etc), but at least solid color works by providing arbitrary RGBA, and pixbuf setter should be enough for other needs.

    Update 4:

    I wrote GooCanvas2::CairoTypes to work this around at least for Patterns.

    It uses gperl_register_boxed_synonym(CAIRO_GOBJECT_TYPE_PATTERN, GOO_TYPE_CAIRO_PATTERN); to let Cairo::Pattern be used as GooCanvas::CairoPattern, and additionally provides a function to explicitly convert GooCanvas::CairoPattern to Cairo::Pattern via gperl_new_boxed(gperl_get_boxed_check(input, GOO_TYPE_CAIRO_PATTERN), CAIRO_GOBJECT_TYPE_PATTERN, 0).