andy128k / cl-gobject-introspection

BSD 2-Clause "Simplified" License
49 stars 15 forks source link

The widget method get_allocation generates an error when called (on a DrawingArea, at least) #50

Closed djeis97 closed 7 years ago

djeis97 commented 9 years ago

Although I am an experienced programmer, I admit being rather new at both lisp and gtk guis (although the gobject framework much less so), so feel free to call me out if I being an idiot here. Now that that's out of the way, I'm trying to translate one of the python examples of gi into lisp and it's giving me a consistent error. A quick inspection makes it look like an issue in the cffi translation code.

Here's a stripped down example of the problem:

(ql:quickload :cl-gobject-introspection) (defparameter gtk (gtk:ffi "Gtk")) (defun test-main () (gir:invoke (gtk 'init) nil) (let ((window (gir:invoke (gtk "Window" 'new) (gir:nget gtk "WindowType" :toplevel))) (draw-area (gir:invoke (gtk "DrawingArea" 'new)))) (gir:invoke (window 'add) draw-area) (gir:invoke (draw-area 'set_size_request) 400 400) (gir:connect draw-area "configure_event" (lambda (widget event) (gir:invoke (widget 'get_allocation)))) (gir:invoke (draw-area 'show)) (gir:invoke (window 'show)) (gir:invoke (gtk 'main))))

The program breaks down in the call to get_allocation in the configure_event with a complaint that something is trying to funcall a nil. The equivalent program in python3:

from gi.repository import Gtk def main (): Gtk.init() window = Gtk.Window(Gtk.WindowType.TOPLEVEL) draw_area = Gtk.DrawingArea() window.add(draw_area) draw_area.set_size_request(400, 400) draw_area.connect("configure_event", lambda widget, event: widget.get_allocation()) draw_area.show() window.show() Gtk.main()

Runs just fine, as far as I can tell.

Also, system info: I'm running ccl64 on arch 64-bit.

Kalimehtar commented 9 years ago

I can't test now (hardware problem). But I think, that in callback you recieve pointers, not gir objects. So rewrite (lambda (widget event) (gir:invoke (widget 'get_allocation))) to (lambda (widget-ptr event) (let ((widget (funcall (gir:nget gtk "Widget") widget-ptr))) (gir:invoke (widget 'get_allocation))))

djeis97 commented 9 years ago

I tried your modification and now it looks like the compiler is refusing to funcall (nget gtk "widget"), breaking before it even reaches get_allocation. Furthermore, printing "widget-ptr" shows it to be a gir::object, not a cffi:foreign-pointer like the readme suggests.

Thanks for the help though, any other ideas?

Kalimehtar commented 9 years ago

printing "widget-ptr" shows it to be a gir::object

It's good. Then it is already converted.

Please print (object-class widget-ptr) ; should return gir::object-class (nget widget-ptr 'get_allocation) ; should return function

if ok, then (funcall (nget widget-ptr 'get_allocation))

djeis97 commented 9 years ago

I don't have a function named object-class in my system, so I wasn't able to check that. However, printing the widget-ptr has a gir::object-class listed as it's :class, so I think it's alright. Printing (nget widget-ptr 'get_allocation) gives me a "compiled lexical closure", which also sounds about right. And yet, running funcall on it gives me a can't funcall a nil error, with the backtrace somewhere in "gir::make-out".

Kalimehtar commented 9 years ago

https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-get-allocation

void gtk_widget_get_allocation (GtkWidget widget, GtkAllocation allocation);

You should give an arg with type GtkAllocation* to 'get_allocation. I think, that in python it is overrided.

djeis97 commented 9 years ago

Looking at the python docs confirms that, which leaves me at a brick wall- how to create a blank allocation. There isn't any "Allocation" in gtk according to lisp, and python seems to be using a cairo RectangleInt. Either way, I think my issue has been solved.

Kalimehtar commented 9 years ago

https://developer.gnome.org/gtk3/stable/GtkWidget.html#GtkAllocation

typedef GdkRectangle GtkAllocation;

Perhaps there are (gdk "Rectangle" 'new)....

Btw, funcall nil maybe also because of the used arg type is not in Gtk*. Such cases should be avoided or overriden as in python.

djeis97 commented 9 years ago

As it turns out, the type is actually defined as a cairo RectangleInt internally, which is just a 4 int struct. I tried creating that with the cffi by hand, just to see if I could get something working, only to run into another issue: the function being created in the call to (nget widget 'get_allocation) only accepts one argument, so I cannot actually pass it a rectangle if had one to pass, so now I'm confused again.

Kalimehtar commented 9 years ago

the function being created in the call to (nget widget 'get_allocation) only accepts one argument, so I cannot actually pass it a rectangle if had one to pass, so now I'm confused again.

What's wrong with one argument? It should be used as

(let ((fun (nget widget-ptr 'get_allocation))
       (rect (make-rect)) ; make-rect may be (foreign-alloc 16)
  (funcall fun rect) ; only one arg used
  rect) ; here we can parse rect to get the coordinates
djeis97 commented 9 years ago

Well... I tried your example, filling in the make-rect with your foreign-alloc suggestion, and it refused to compile (funcall fun rect). So then I replaced it with my with-foreign-object on a pointer to a cstruct I created, and it gives me an error about how there should be one argument for 'get_allocation. And since I never used to get that error, I'm assuming the one argument is the widget.

Kalimehtar commented 9 years ago

I found, where the problem is. In function.lisp both build-struct-translator and build-union-translator return translator with nil in the field ">value". But in make-out it is used to get value from out arg. So, I confirm, that cl-gobject-introspection doesn't support out and return args with struct and union types. This month I don't have time to fix it. If somebody can fix it, I'll accept a patch, if not, then later I'll fix the issue.

Kalimehtar commented 9 years ago

Quick hack:

(let ((rect (make-rect)) ; make-rect may be (foreign-alloc :char :count 16) or, better, cstruct
  (cffi:foreign-funcall "gtk_widget_get_allocation" (gir:object-this widget) rect)
  rect) ; here we can parse rect to get the coordinates
djeis97 commented 9 years ago

I went ahead and defined a quick function to handle the translation while I wait for a patch and it's working just fine. Thanks for everything! Should I go ahead and close the issue?

Kalimehtar commented 9 years ago

Should I go ahead and close the issue?

No. The real problem is in struct and union handling in out arguments. It is still broken. So let issue will be opened until the problem is fixed.

hying-caritas commented 9 years ago

Sorry for late. I took a look at the gtk_widget_get_allocation and its gir dump, found that the second argument (allocation) is not pointer in gir information. So in function.lisp, for struct/union that is not pointer, I don't know how to unpack/pack it from/to giarg, so left the field NIL there.

Maybe this is why it is overridden in python binding?

And I found there is a related gap too, arg-info-is-caller-allocates is not supported now. It sounds doable.

Kalimehtar commented 9 years ago

In fact it is not overriden. As far as I understand, when GIR returns struct, it expect receiving party to free struct (given pointer) after use. Rest is the same as for pointer to struct.

hying-caritas commented 9 years ago

Yes. It is not overridden. struct here need to be treated as pointer anyway. I have implemented something to support get_widget_get_allocation according to python binding. May make it more complete when I have more knowledge/example about that.

hying-caritas commented 9 years ago

djeis97, can you try the latest version. The issue should be fixed.

djeis97 commented 8 years ago

Wow... Year and a half and I've come full circle in my exploration of lisp all the way back to GObject... Really sorry that I never got back to this. I've lost the code I was originally trying to get working, IIRC it had to do with running opengl in a Gtk window (so I could use gtk widgets). However, since I'm now trying to get something else working that works in python but breaks in lisp, I figured before I raised another issue I should at least try to close this one. Anyway, I ran the above code and, while it doesn't break, the "get_allocation" call does not return anything, so I guess the issue still stands. For reference, I modified the testing code to be

(ql:quickload :cl-gobject-introspection)
(defparameter *gtk* (gir:require-namespace "Gtk"))
(defparameter *alloc* nil)
(defun test-main ()
  (gir:invoke (*gtk* 'init) nil)
  (let ((window (gir:invoke (*gtk* "Window" 'new) (gir:nget *gtk* "WindowType" :toplevel)))
        (draw-area (gir:invoke (*gtk* "DrawingArea" 'new))))
    (gir:invoke (window 'add) draw-area)
    (gir:invoke (draw-area 'set_size_request) 400 400)
    (gir:connect draw-area "configure_event" (lambda (widget event)
                                               (print (setf *alloc* (gir:invoke (widget 'get_allocation))))))
    (gir:invoke (draw-area 'show))
    (gir:invoke (window 'show))
    (gir:invoke (*gtk* 'main))))

In order to ensure the event was getting fired and so I could inspect the allocation object, and all I get back each time is nil.

EDIT: Undid some extra changes to the example I'd made out of laziness...

Kalimehtar commented 8 years ago

get_allocation should return GtkAllocation which is typedef to GdkRectangle.

And GdkRectangle is described as

<alias name="Rectangle" c:type="GdkRectangle">
      <doc xml:whitespace="preserve">Defines the position and size of a rectangle. It is identical to
#cairo_rectangle_int_t.</doc>
      <type name="cairo.RectangleInt" c:type="cairo_rectangle_int_t"/>
    </alias>

In fact, it is not described.

So, use instead (gir:invoke (draw-area "get_allocated_height"))) and (gir:invoke (draw-area "get_allocated_width")))