StefanSalewski / gintro

High level GObject-Introspection based GTK3/GTK4 bindings for Nim language
MIT License
298 stars 20 forks source link

Invalid object conversion [ObjectConversionDefect] gobject.Object to StringObject #128

Open gavr123456789 opened 3 years ago

gavr123456789 commented 3 years ago

listitem.getItem() returns Object
StringObject is ref object of gobject.Object
So it looks like Nim is missing UpCast? Or am I misunderstanding something.
Googling nim upcast get nothing.

import gintro/[gtk4, gobject, gio]
import std/with

proc setup_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  listitem.setChild(newLabel(""))

proc bind_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  var 
    lb = listitem.getChild().Label
    strobj = listitem.getItem().StringObject
    txt = gtk4.getString(strobj)
  echo txt
  lb.text = txt

proc unbind_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  echo "unbind"

proc teardown_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  listitem.setChild (nil)

proc activate(app: gtk4.Application) =
  let
    window = newApplicationWindow(app)
    scr = newScrolledWindow()

  var 
    sl = gtk4.newStringList("one", "two", "three", "four")
    ls = cast[ListModel](sl)
    ns = gtk4.newNoSelection(ls)
    factory = gtk4.newSignalListItemFactory()
    lv = newListView(ns, factory)

  scr.setChild lv

  with factory:
    connect("setup", setup_cb)
    connect("bind", bind_cb)
    connect("unbind", unbind_cb)
    connect("teardown", teardown_cb)

  with window:
    title = "Nim ListView"
    setChild scr
    show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  discard run(app)

main()

Error:

/home/gavr/Projects/Nim/gintro/examples/gtk4/ListView.nim(55) ListView
/home/gavr/Projects/Nim/gintro/examples/gtk4/ListView.nim(53) main
/home/gavr/.nimble/pkgs/gintro-#head/gintro/gio.nim(31021) run
/usr/lib/nim/core/macros.nim(559) connect_for_signal_cdecl_activate5
/home/gavr/Projects/Nim/gintro/examples/gtk4/ListView.nim(45) activate
/home/gavr/.nimble/pkgs/gintro-#head/gintro/gtk4.nim(3824) setChild
/usr/lib/nim/core/macros.nim(565) connect_for_signal_cdecl_bind2
/home/gavr/Projects/Nim/gintro/examples/gtk4/ListView.nim(10) bind_cb
/usr/lib/nim/system/fatal.nim(49) sysFatal
Error: unhandled exception: invalid object conversion [ObjectConversionDefect]
gavr123456789 commented 3 years ago

Wait, no, this is even more strange.
Label -> Widget
StringObject -> Object
Upcast of Widget to Label works with 'smart' upcast with .Label
Upcast of Object to StringObject throw exeption and works only with unsafe C 'up'cast.

This version can compiles:

import gintro/[gtk4, gobject, gio]
import std/with

proc setup_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  listitem.setChild(newLabel(""))

proc bind_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  var 
    lb = listitem.getChild().Label
    strobj = cast[StringObject](listitem.getItem())
    txt = gtk4.getString(strobj)
  echo txt
  lb.text = txt

proc unbind_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  echo "unbind"

proc teardown_cb(factory: gtk4.SignalListItemFactory, listitem: gtk4.ListItem) =
  listitem.setChild (nil)

proc activate(app: gtk4.Application) =
  let
    window = newApplicationWindow(app)
    scr = newScrolledWindow()

  var 
    sl = gtk4.newStringList("one", "two", "three", "four")
    ls = cast[ListModel](sl)
    ns = gtk4.newNoSelection(ls)
    factory = gtk4.newSignalListItemFactory()
    lv = newListView(ns, factory)

  scr.setChild lv

  with factory:
    connect("setup", setup_cb)
    connect("bind", bind_cb)
    connect("unbind", unbind_cb)
    connect("teardown", teardown_cb)

  with window:
    title = "Nim ListView"
    setChild scr
    show

proc main =
  let app = newApplication("org.gtk.example")
  app.connect("activate", activate)
  discard run(app)

main()
StefanSalewski commented 3 years ago

Thanks for reporting, I will try to investigate that issue soon.

StefanSalewski commented 3 years ago

Have you a working C code example? Starting from working C code is always the best way to debug. All the listview/treeview stuff is by far the hardest in GTK. In C code a lot of casts are performed, in Nim we should never use direct casts but provide converters or something similar. I really should read the Krause book again and all the tutorials. Well I will see if I can discover the problem.

gavr123456789 commented 3 years ago

@StefanSalewski yea, this is first example from here https://github.com/ToshioCP/Gtk4-tutorial/blob/main/gfm/sec24.md

StefanSalewski commented 3 years ago

Thanks, that will make debugging much easier.

StefanSalewski commented 3 years ago

OK, I have compiled and run his C example. But there is only a block of text displayed, really not that interesting.

I started with his C code and converted it to Nim, see below. Works, but we need two ugly casts indeed. getItem() returns a plain GObject, but we need a StringObject. And the other is StringList to StringModel. With getChild() and Label a conversion works because both are Widgets.

For fixing all this as clean as possible we have to learn the API well and then we can see if we can somehow introduce automatic conversions or give the user type safe converters. I think a few are already available. But all that is a task for maybe 6 weeks fulltime: Learning, writing documentation (the GTK4 book, I guess 50 pages), testing and providing converters when necessary. As long as we have zero users (well one with you) my motivation for that is not that large. Maybe just tell the figma guy what you did with ValaGtk and ask him how you can do that with figma. Maybe we should indeed switch to fidget? In another thread they recently promoted flutter.

Yesterday I got a request from Japan from someone who wants to translate the Nim Beginner book to Japanese, so I think I will try to add a few more pages to the book now. And I want to do some GTK programming myself again, have done nothing in the last years.

import gintro/[gtk4, gobject, gio]

proc setup_cb(factory: SignalListItemFactory; listitem: ListItem) =
  let lb = newLabel()
  listitem.setChild(lb)

proc bind_cb(self: SignalListItemFactory; listitem: ListItem) =
  let lb: Label = Label(listItem.getChild)
  let strobj: StringObject = cast[StringObject](listItem.getItem)
  let text = strobj.getString
  lb.setText(text)

proc unbind_cb(self: SignalListItemFactory; listitem: ListItem) =
  # There's nothing to do here.
  # If you does something like setting a signal in bind_cb,
  # then disconnecting the signal is necessary in unbind_cb.
  discard

proc teardown_cb(factory: SignalListItemFactory; listitem: ListItem) =
  listitem.setChild (nil)
  # When the child of listitem is set to NULL, the reference to GtkLabel will be released and lb will be destroyed.
  # Therefore, g_object_unref () for the GtkLabel object doesn't need in the user code.

# ----- activate, open, startup handlers -----
proc app_activate(app: Application) =
  echo "activate"
  let win = newApplicationWindow(app)
  win.setDefaultSize(600, 400)
  let scr = newScrolledWindow()
  win.setChild (scr)
  let arrax = ["one", "two", "three", "four"]
  let sl: gio.ListModel = cast[ListModel](newStringList(arrax)) # GtkStringList — A list model for strings
  let ns: NoSElection = newNoSelection(sl) # A selection model that does not allow selecting anything
  let factory: SignalListItemFactory = newSignalListItemFactory()
  factory.connect("setup", setup_cb)
  factory.connect("bind", bind_cb)
  factory.connect("unbind", unbind_cb)
  factory.connect("teardown", teardown_cb)
  let lv = newListView(ns, factory)
  scr.setChild (lv)
  win.show

proc app_startup(app: Application) =
  echo "startup"

proc main =
  let app = newApplication("org.gtk.example") # "com.github.ToshioCP.list1"
  app.connect("startup", app_startup)
  app.connect("activate", app_activate)
  let status = app.run
  quit(status)

main()
gavr123456789 commented 3 years ago

Heh, looks like you got the same program with same 2 C cast.
I think this problem can be solved with new concepts https://github.com/StefanSalewski/gintro/issues/130 when they will come out