Cairo::SolidPattern 不是 GooCanvas2::CairoPattern 类型

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

我正在尝试将旧的 Gtk2 perl 脚本转换为 Gtk3。这是 Gtk2 版本的样子:

#!/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',
);

这是我的 Gtk3 尝试:

#!/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',
);

但这会导致错误:

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

我也试过插入 $pattern = GooCanvas2::CairoPattern->new($pattern); 但这没有帮助:

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.

但是查看 GooCanvas2.pm 的来源,它确实通过 Glib::Object::Introspection 加载 GooCanvas2

有什么解决方法的建议吗?

我尝试在 Ubuntu 20.04 上安装 libgoocanvas-2.0-dev,它安装了 GooCanvas 的内省 XML 文件作为 /usr/share/gir-1.0/GooCanvas-2.0.gir。在这个文件中,我看到了 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>

我注意到 fill-pattern 参数的定义可能包含在最后一个 name="..." 参数包中。所以不清楚参数应该是什么类型。但在文件的后面可能有线索,在第 244 行我们有:

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

我不确定 glib:boxed 到底是什么意思,但根据 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.

因此 boxed 类型在 Perl 中获得了它们自己的命名空间。更多关于盒装类型 here.

错误信息:

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

表示它需要一个 GooCanvas2::CairoPattern 类型,但是当我尝试使用这样的类型时,例如

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

我收到错误消息:

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

更新:

下面的 C 程序工作正常并表明模式应该是 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;
}

更新 2:

更多调试信息:调用 GooCanvas2::CanvasRect->new() 被重定向到 GObject.xs 中的 line 1327:

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

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));

它尝试从提供的 Perl 参数(Cairo::SolidPattern 类型)中检索装箱值,这导致 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)));
        }

第一次检查失败,因此它进入第二个选择 g_value_set_static_boxed(),它首先调用 gperl_get_boxed_check(),参见 GBoxed.xs 中的 line 558。第 568 行

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

boxed_info 返回为 BoxedInfo *,内容为

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

并且在第 575 行 unwrap 设置为 _default_wrapper_class.unwrap 并且在第 583 行:

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

line 420 调用 default_boxed_unwrap() :

第 429 行出现问题:

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

因为 Cairo::SolidPattern 不是从 GooCanvas2::CairoPattern 派生的。

更新 3:

查看GooCanvas's source,它为模式定义了几个setters/getters:fill-colorfill-color-rgbafill-color-gdk-rgbafill-pixbuffill-pattern,所有这些在内部 set/get 相同 属性 goo_canvas_style_fill_pattern_id。其他 setter 没有 cairo_pattern_t 的错误内省包装器的问题。因此这有效:

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

此外,构造$rect后,我们可以通过$rect->get('fill-pattern')得到GooCanvas2::CairoPattern

这不允许使用从 cairo 创建的其他图案(线性等),但至少纯色可以通过提供任意 RGBA 来工作,而 pixbuf setter 应该足以满足其他需求。

更新 4:

我写了 GooCanvas2::CairoTypes 来解决这个问题,至少是为了模式。

它使用gperl_register_boxed_synonym(CAIRO_GOBJECT_TYPE_PATTERN, GOO_TYPE_CAIRO_PATTERN);Cairo::Pattern作为GooCanvas::CairoPattern使用,另外提供了一个函数通过[=将GooCanvas::CairoPattern显式转换为Cairo::Pattern 61=].