Forum: Ruby-Gnome 2 User data in signal callbacks backwards?

Posted by Michal Suchanek (Guest)
on 2011-08-26 23:23
(Received via mailing list)
Hello,

I wrote a demo with some treeview to prototype list behaviour but
there is one surprising feature of ruby-gtk2 which I found:

In C you are supposed to get this callback:

The "changed" signal

void                user_function
(GtkTreeSelection *treeselection,
                                                        gpointer
   user_data)          : Run First

meaning that the object which triggered the signal is passed in as the
first pointer which is usually used for the 'self' pointer in glib
fake C object interface.

When I create a glade layout where I connect a tree view selection
"changed" signal and pass another object as the user data I get the
user data as self and the object that triggered the signal as
argument. Effectively the arguments are reversed. Without user data I
get the object that loaded the layout as self and the object that
triggered the signal as argument.

Is it intentional that the callback interface is so much different from 
C?

I would expect to just get two arguments in the case with user data.
It is the same order as C then and I get access to the layout that way
and just adding user data does not change the call completely.

Thanks

Michal
Posted by Kouhei Sutou (Guest)
on 2011-08-27 08:20
(Received via mailing list)
Hi,

In <CAOMqctTB_5p=aL=pfDixPQYdARG8+9MpELET9QEEAyeq3ffxxg@mail.gmail.com>
  "[ruby-gnome2-devel-en] User data in signal callbacks backwards?" on 
Fri, 26 Aug 2011 23:22:43 +0200,
  Michal Suchanek <hramrach@centrum.cz> wrote:

>    user_data)          : Run First
> triggered the signal as argument.
Could you give us a sample Ruby code and a glade file for
this problem?
If you provide them to us, we may fix it.


Thanks,
--
kou
Posted by Michal Suchanek (Guest)
on 2011-08-28 12:23
Attachment: twin_tree_view.rb (16,7 KB)
Attachment: gtktreeviews.c (3,94 KB)
(Received via mailing list)
On 27 August 2011 08:18, Kouhei Sutou <kou@cozmixng.org> wrote:
>>
>>
>
I am not sure it is actually an error. It just looks backwards if you
look both at Ruby sample and C sample.

Attaching code for both.

Thanks

Michal
Posted by Michal Suchanek (Guest)
on 2011-08-30 16:05
(Received via mailing list)
On 27 August 2011 08:18, Kouhei Sutou <kou@cozmixng.org> wrote:
>>
>>
>
OK, perhaps the sample I sent is too long so I will put only the
relevant excerpts here.

In C I write:

void selection_changed  (GtkTreeSelection *self, gpointer data)

g_signal_connect (lsel, "changed",
                    G_CALLBACK(selection_changed), rsel);

Now when lsel changes selection_changed is called with arguments lsel,
rsel - selection_changed (lsel, rsel).

In glade I have

                      <object class="GtkTreeView" id="treeview1">
                        <property name="visible">True</property>
                        <property name="can_focus">True</property>
                        <property name="model">liststore</property>
                        <property name="enable_search">False</property>
                        <child internal-child="selection">
                          <object class="GtkTreeSelection"
id="treeview-selection1">
                            <signal name="changed"
handler="selection_changed" object="treeview-selection2"
swapped="no"/>
                            <signal name="changed"
handler="selection_changed" swapped="no"/>

when treewiew-selection1 changes treewiew-selection2.selection_changed
treeview_selection1 is called.

In my demo it is useful that it's called that way but since I did not
opt for swapped call I see this as backwards.

The other call is performed as <the class that loaded the glade
data>.selection_changed selection1.

The glade loading incantation is taken from some sample:

      @builder = Gtk::Builder.new
      @builder << path_or_data
      @builder.connect_signals {|name| method(name)}

In C there is special call to connect the signals swapped which I
would expect to result in calling the method on the argument:

g_signal_connect_swapped (button, "clicked",
                            G_CALLBACK (gtk_widget_destroy),
                            window);

Thanks

Michal
Posted by Simon Arnaud (sarnaud)
on 2011-08-31 11:32
Michal Suchanek wrote in post #1018884:
> On 27 August 2011 08:18, Kouhei Sutou <kou@cozmixng.org> wrote:
>>>
>>>
>>
> I am not sure it is actually an error. It just looks backwards if you
> look both at Ruby sample and C sample.
>
> Attaching code for both.
>
> Thanks
>
> Michal

Looking at attached files, I don't quite get where you try to use "user 
data". In fact, I don't think you can retrieve "user data" with 
Gtk::Builder in ruby.

Looking at treeselection1, you have 2 handlers, one with user data, and 
one without. However, if you just comment out the one without user data, 
you will notice that the one *with* is just discarded, nothing is 
connected.

Just add puts "Adding callback #{name}" to the builder.connect_signal, 
and you will see it is called only once for each selection.

You might think it is backward because, when you change something in 
ts1, if it changes something in ts2, the ts2 'selection_changed' is then 
called, and it is called before you finish what you were doing in ts1. 
At least, that's the only way I can think of.

The main problem, afaik, is that ruby gtk::builder does not handle "user 
data". I took a look at rbgtkbuilder.c, but I'm really too bad at C to 
understand what is going on.

regards

Simon
Posted by Michal Suchanek (Guest)
on 2011-08-31 12:26
(Received via mailing list)
On 31 August 2011 11:32, Simon Arnaud 
<ruby-forum-incoming@andreas-s.net> wrote:
>> Thanks
> connected.
No, that's not how things work.

The user data in glade can be set to one of the other objects in the
glade layout.

If the callback was just discarded the demo would not work which is
not what I see.

The callback selection_changed is set on treeview-selection1 with user
data treeview-selection2 and when triggered on treeview-selection1
treeview-selection2.selection_changed treeview_selection1 is called.

>
> Just add puts "Adding callback #{name}" to the builder.connect_signal,
> and you will see it is called only once for each selection.

It is called twice with selection_changed.

When I comment out the two with user data it is also called twice but
the demo does not work then so the signals with user data are not
connected in the block but are still connected.

>
> You might think it is backward because, when you change something in
> ts1, if it changes something in ts2, the ts2 'selection_changed' is then
> called, and it is called before you finish what you were doing in ts1.
> At least, that's the only way I can think of.

No, when I have Item 4 selected in one view and select item 4 in the
other I get:
sel "Item 4" "Item 4"
sel "Item 4" nil
sav "Item 4" nil
sav "Item 5" "Item 4"

So the other callback fires before the first finishes due to the
unselect call - the method producing the sav output is called from the
callback.
There is no problem with that, though. The sel output is produced at
the very start of the callback and is in order.

>
> The main problem, afaik, is that ruby gtk::builder does not handle "user
> data". I took a look at rbgtkbuilder.c, but I'm really too bad at C to
> understand what is going on.

Obviously it does.

Thanks

Michal
Posted by Simon Arnaud (sarnaud)
on 2011-08-31 14:51
Attachment: user_data.rb (429 Bytes)
Attachment: user_data.glade (2,03 KB)
Michal Suchanek wrote in post #1019345:
> The callback selection_changed is set on treeview-selection1 with user
> data treeview-selection2 and when triggered on treeview-selection1
> treeview-selection2.selection_changed treeview_selection1 is called.

Indeed. I tested this, see attachment, and this is really bad.

Basically, ruby Gtk::Builder sends a message to the "user data" object, 
with the name of the callback, passing expected arguments for the 
signal. And it does so even if you do not connect_signals. Just comment 
out 'method(handler)' line 20 to be sure.

>> Just add puts "Adding callback #{name}" to the builder.connect_signal,
>> and you will see it is called only once for each selection.
>
> It is called twice with selection_changed.

And it should be called 4 times. It's only called for the one without 
"user data".

> When I comment out the two with user data it is also called twice but
> the demo does not work then so the signals with user data are not
> connected in the block but are still connected.

That's because of the buggy behaviour of gtk::builder I outline above.

>> You might think it is backward because, when you change something in
>> ts1, if it changes something in ts2, the ts2 'selection_changed' is then
>> called, and it is called before you finish what you were doing in ts1.
>> At least, that's the only way I can think of.
>
> No, when I have Item 4 selected in one view and select item 4 in the
> other I get:
> sel "Item 4" "Item 4"
> sel "Item 4" nil
> sav "Item 4" nil
> sav "Item 5" "Item 4"
>
> So the other callback fires before the first finishes due to the
> unselect call - the method producing the sav output is called from the
> callback.
> There is no problem with that, though. The sel output is produced at
> the very start of the callback and is in order.

After more thorough testing, it calls 3 callbacks.
The first two are the *backward* callback, which I would call *buggy*, 
and should not exist.
The third one is the expected callback, which just sets the title, and 
nothing else.

>> The main problem, afaik, is that ruby gtk::builder does not handle "user
>> data". I took a look at rbgtkbuilder.c, but I'm really too bad at C to
>> understand what is going on.
>
> Obviously it does.

I still think it does not. It just has a buggy behaviour which makes it 
work somewhat, but is not what I expect.

for example, if I have :

<object class="GtkButton" id="test_button">
  <signal name="clicked" handler="my_callback" object="my_entry" 
swapped="no"/>
</object>

I want to write somewhere in my code :

def my_callback(button, user_data)

and not :

my_entry = builder['y_entry']
def my_entry.my_callback(button)

or even worse, reopening Gtk::Entry, like you do on TreeSelection :
class Gtk::Entry
  def my_callback(button)


I think it should be fixed. And signals should handle "user data", and 
passing them as the last argument of the signal, if they are present.

Simon
Posted by Michal Suchanek (Guest)
on 2011-08-31 16:08
(Received via mailing list)
On 31 August 2011 14:51, Simon Arnaud 
<ruby-forum-incoming@andreas-s.net> wrote:
> Michal Suchanek wrote in post #1019345:

>> sav "Item 5" "Item 4"
> The third one is the expected callback, which just sets the title, and
> nothing else.

So we can agree what's called, and that it is not what is expected.

> for example, if I have :
> and not :
>
> my_entry = builder['y_entry']
> def my_entry.my_callback(button)
>
> or even worse, reopening Gtk::Entry, like you do on TreeSelection :
> class Gtk::Entry
>  def my_callback(button)

I would totally reopen the TreeSelection even if I got the callbacks
correctly. It fits my use case nicely. The problem is I have to, even
though I did not opt for this.

My first guess would be that Gtk::Builder somehow interprets the
swapped="no" as meaning swapped because earlier glades only generated
swapped="yes" and not swapped="no".

The swapped="yes" callbacks should arguably be handled internally as
they would be in C. The
g_signal_connect_swapped (button, "clicked",
                           G_CALLBACK (gtk_widget_destroy),
                           window);
can be done in Glade by <signal name="clicked"
handler="gtk_widget_destroy" object="window1" swapped="yes"/>
to produce a button that closes the application without any C code.

Indeed, adding <signal name="row-activated" handler="destroy"
object="window1" swapped="yes"/> to the treeviews and
replacing the app_quit handlers on the button with <signal
name="clicked"  handler="destroy" object="window1" swapped="yes"/>
allows closing the application window without running any code of my
own.

Thanks

Michal
Posted by Michal Suchanek (Guest)
on 2011-09-01 18:07
(Received via mailing list)
On 27 August 2011 08:18, Kouhei Sutou <kou@cozmixng.org> wrote:
>>
>>
>> When I create a glade layout where I connect a tree view selection
>> "changed" signal and pass another object as the user data I get the
>> user data as self and the object that triggered the signal as
>> argument. Effectively the arguments are reversed. Without user data I
>> get the object that loaded the layout as self and the object that
>> triggered the signal as argument.
>
> Could you give us a sample Ruby code and a glade file for
> this problem?
> If you provide them to us, we may fix it.

I think it may be fixed with something like this:

--- ../../lib/gtk2/base.rb~  1970-01-01 01:00:00.000000000 +0100
+++ ../../lib/gtk2/base.rb  2011-09-01 18:04:16.000000000 +0200
@@ -76,6 +76,11 @@
                               handler_name, connect_object, flags)
         handler_name = canonical_handler_name(handler_name)
         if connect_object
+           if ! flags.swapped? then
+              tmp = object
+              object = connect_object
+              connect_object = tmp
+           end
           handler = connect_object.method(handler_name)
         else
           handler = connector.call(handler_name)

Will test later.

Thanks

Michal
Posted by Michal Suchanek (Guest)
on 2011-09-02 00:52
(Received via mailing list)
On 1 September 2011 18:06, Michal Suchanek <hramrach@centrum.cz> wrote:
>>> In C you are supposed to get this callback:
>>> fake C object interface.
>> If you provide them to us, we may fix it.
>
> I think it may be fixed with something like this:

Hmm. forget that.

The current function generally does not work for any but the simplest 
case.

There is a fundamental problem that the ruby-gtk2 callback mechanism
calls a method on an object and hands it the arguments gtk provides,
and first of the arguments is what would be normally considered the
receiver in Glib.

The other fundamental issue is that swapped callbacks should 'just
work' so they are called on the data, with arguments ignored.

Given button B and window W you can  g_signal_connect( B, 'clicked' ,
gtk_widget_destroy, W) to have gtk_widget_destroy(B,W) called whenever
B is clicked. Of course, you will probably want
g_signal_connect_swapped( B, 'clicked' , gtk_widget_destroy, W) to
have gtk_widget_destroy(W,B) called. Destroy takes one argument so B
is ignored.

Now in Ruby you would connect a method, not a function. So what would
get called is something like
O.destroy B,W or O.destroy W,B where O is some arbitrary object chosen
when connecting the signal.

To make the swapped signal work as expected so that "glade programs"
work builder connects
W.destroy when W is set as data for the (B - clicked - destroy)
connection in glade. The arguments B,W or W,B passed by GTK are
ignored because destroy takes no argument in Ruby.

Maybe whatever does the callback dispatching in ruby-gtk2 could weed
out the first argument if it is equal to the receiver. That would not
give consistent results when calling callbacks on different objects,
though.

I put together this update to the connect_signals method which
distinguishes three cases:

1) no user data (connect_object) passed to signal in Glade - ask user
for method, as before
2) user data is supplied and signal marked as swapped - connect to
data, as before
3) user data is supplied and signal is not swapped - ask user for
method - previously the signal was handled as swapped

      def __connect_signals__(connector, object, signal_name,
                              handler_name, connect_object, flags)
        handler_name = canonical_handler_name(handler_name)
        trigger = object
        if connect_object and flags.swapped? then
          # Handle swapped signals automagically
          receiver = connect_object
          data = object
          handler = receiver.method(handler_name)
        else
          handler = connector.call(handler_name)
          data = connect_object
        end
        unless handler
          $stderr.puts("Undefined handler: #{handler_name}") if $DEBUG
          return
        end
        if flags.after?
          signal_connect_method = :signal_connect_after
        else
          signal_connect_method = :signal_connect
        end

        args = [signal_connect_method, signal_name]
        args << data if data

        if handler.arity.zero?
          $stderr.puts("Ignoring user data: #{data} while connecting 
signal
#{signal_name.inspect} of #{trigger} to #{handler_name}" + (receiver ?
" on #{receiver}" : "")) if $DEBUG && data
          trigger.send(*args) {handler.call}
        else
          $stderr.puts("Insufficient arity: #{handler.arity} of
#{handler} while connecting signal
#{signal_name.inspect} of #{trigger} to #{handler_name}" + (receiver ?
" on #{receiver}" : "")) if $DEBUG && data && (handler.arity < 2)
          trigger.send(*args, &handler)
        end
      end

Thanks

Michal
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.