Using blocks as slots in QtRuby/Korundum

On the #qtruby IRC channel kelko thought up a simple way of adding blocks as targets to Qt::connect() calls. After some more discussion rickdangerous suggested also adding a simple method that would work like 'signal_connect()' in ruby-gnome, which just takes a single signal name argument and a block. I've just added the code to both the Qt3 and Qt4 versions of QtRuby. There are three variants that allow you to replace the usual SLOT(:foobar) with a block.

First, the simplest version that works like ruby-gnome's signal_connect(), looks like this:

quit ='Quit')
quit.connect(SIGNAL(:clicked)) { puts 'quit pressed' }

The code in the block is just called in the context of where the connect() statement is called from (not in the context of the instance emitting the signal). With this form of connect() you can call any arbitrary method (which needn't been in a subclass of Qt::Object), like this:

class MyApp
def initialize
@button ='Quit')
@button.connect(SIGNAL(:clicked), &method(:quit_app))

def quit_app
p "quiting"

The second form of connect() with a block can be called like this from within a Qt::Object:

class MyButton < Qt::Button
def initialize(text)
connect(self, SIGNAL(:clicked)) { puts 'button clicked' }

The block is called in the context of the instance calling connect(), a Qt::PushButton in this case. The third form of connect() a block looks like this:

Qt::Object.connect(quit, SIGNAL(:clicked), app) { puts 'quit clicked' }

The block is executed in the context of the third argument, 'app' in this example. It was a bit tricky implementing this one as the block needs to be invoked on 'app', with any arguments that were emitted by the signal. However, the Object#instance_eval method in Ruby 1.8 doesn't allow you to pass any arguments to the block. In Ruby 1.9 there is a new method Object#instance_exec which does take arguments, and after a bit of searching with Google I found out that Rails has a version implemented in pure Ruby 1.8. See here for a discussion of instance_exec(). I was going to paste the code here but I get a 'Terminated request because of suspicious input data.' from the server when I try - it must think the code is a bit too clever for its own good or something :-).

Well, that's it - I'm looking forward to seeing what interesting uses people will find for this new functionality. I found when I added optional block to QtRuby constructors, it made a surprisingly large difference to the way the programs looked.


Isn't there a way to get rid of the SIGNAL(:clicked) and just use :clicked instead?
even :itemSelected(int) looks better then SIGNAL( :itemSelected(int) )

(even though the typing is totally weird in ruby... but i digress)

It seems like SIGNAL() should be implied whenever Qt::Object.connect() is called. This would also finally get rid of the odd looking macros which shouldn't be needed in ruby.


By bensch at Tue, 09/19/2006 - 13:58

oh very, very, very nice job too.
It looks ALOT better then before and so much more rubyish...

By bensch at Tue, 09/19/2006 - 13:59

Yes, I agree SIGNAL() and SLOT() look weird. I've thought about providing lower case versions or getting rid of them altogether. I noticed that the Qt Jambi Java bindings don't have them (I don't see how they work with overloaded signals though). But then if they aren't needed in Java I don't really see why they're needed in C++ either. You can't include brackets in a Ruby symbol, and so it can't be :itemSelected(int). Declaring a slot could be something Rails-like:

q_slot :item_selected, :expects => [{:value => :int}, {:id => :string], :returns => [ :int ]

Then just use :item_selected in the connect calls because you can't overload methods in Ruby, and giving the types is redundant.

On the other hand, I think it's an advantage to keep the C++-like calls because it makes it easier to use the C++ documentation. As long as you've got more rubyish version too.

By Richard Dale at Tue, 09/19/2006 - 14:41

I don't understand how using :item_selected will allow you to pick between overloaded methods unless you do the choosing inside the connect.

ohh, I think i understand. connect would automatically pick the "right" slot depending on the signature of the signal?

Unfortunately, this maight be too implicit. How about using mangling instead and allowing the user to explicitly choose which signal+slot he wants to connect by turning the signature if "item_selected(int, string)" into :item_selected_int_string?

then the user can be as explicit as needed. Maybe if he doesn't provide the full signiture, connect can try to do a prefix match against all of the slots and pick the best one with a warning... or just fail with an error.


By bensch at Wed, 09/20/2006 - 08:52

You make some good points, and have spotted that it isn't as easy as it first seems. Which is why I haven't quite wanted to implement an improved slots/signal syntax. Maybe the QtRuby code would look a bit less ugly, but if it comes at the cost of all sorts of 'gotchas' and arbitrary rules that are difficult to understand, then it's more trouble than it's worth. I'm hoping to wake up one day with a light bulb turned on in my head with the 'right solution', and then I'll do it. Or maybe someone like Kelko will come along with a compelling suggestion, like he did for implementing blocks as slots.

By Richard Dale at Wed, 09/20/2006 - 09:25