Effective Qt in ruby (part 2)

In the first part of this series, I listed some of the reasons why you should consider writing your Qt/KDE applications in ruby. This post details some of the technical differences between writing Qt code in C++ and in ruby.

One of the first problems that pop up when starting a new Qt/KDE project in ruby is how to use it in such a way that your code doesn’t end up being completely unidiomatic. This can happen very easily if one tries to stick to the usual conventions that apply when writing Qt code in C++.

If you take any piece of C++ code using Qt, you can very trivially translate it into ruby. That works, and sometimes is useful, but writing code in this way completely misses the point of using a dynamic language. You might as well write directly in C++, and enjoy the improved performance.

So I believe it’s important to identify the baggage that Qt brings from its C++ roots, and eliminate it when using it from ruby. Here are some ideas to achieve that.

Use the ruby convention for method names

A minor point, but important for code readability.

Qt uses camel case for method names, while ruby methods are conventionally written with underscores. Mixing the two styles inevitably results in an unreadable mess, so the ruby convention should be used at all times.

Fortunately, QtRuby allows you to call C++ methods by spelling their name with underscores, so it’s quite easy to achieve a satisfactory level of consistency with minimum effort.

Never declare signals

The signal/slot mechanism is a very important Qt feature, because it allows to work around the static nature of C++ by allowing dynamic calls to methods. You won’t need that in ruby. For instance, you can use the standard observer library to fire events and set callbacks. It’s completely dynamic and there’s no need to define your signals beforehand.

Never use slots

Slots are useless in ruby. QtRuby allows you to attach a block to a connect call, and that is what you should always be using. Never use the SLOT function with a C++ signature.

Avoid C++ signatures altogether

This seems impossible. It might be easy to use symbols (without using the SIGNAL "macro") to specify signals with no arguments, like

button.on(:clicked) { puts "hello world" }

but if a signal has arguments, and possibly overloads, specifying only its name doesn’t seem to be enough to determine which particular overload we are interested in.

Indeed, it’s not possible in general, but you can disambiguate using the block arity for most overloaded signals, and add type annotations in those rare cases where the arity is not enough.

Here is my on method, which accomplishes this:

def on(sig, types = nil, &blk)
  sig = Signal.create(sig, types)
  candidates = if is_a? Qt::Object
    signal_map[sig.symbol]
  end
  if candidates
    if types
      # find candidate with the correct argument types
      candidates = candidates.find_all{|s| s[1] == types }
    end
    if candidates.size > 1
      # find candidate with the correct arity
      arity = blk.arity
      if blk.arity == -1
        # take first
        candidates = [candidates.first]
      else
        candidates = candidates.find_all{|s| s[1].size == arity }
      end
    end
    if candidates.size > 1
      raise "Ambiguous overload for #{sig} with arity #{arity}"
    elsif candidates.empty?
      msg = if types
        "with types #{types.join(' ')}"
      else
        "with arity #{blk.arity}"
      end
      raise "No overload for #{sig} #{msg}"
    end
    sign = SIGNAL(candidates.first[0])
    connect(sign, &blk)
    SignalDisconnecter.new(self, sign)
  else
    observer = observe(sig.symbol, &blk)
    ObserverDisconnecter.new(self, observer)
  end
end

The Signal class maintains the signal name and (optional) specified types. The method lazily creates a signal map for each class, which maps symbols to C++ signatures, and proceeds to disambiguate among all the possibilities by using types, or just the block arity, when no explicit types are provided. If no signal is found, or if the ambiguity could not be resolved, an exception is thrown.

For example, the following line:

combobox.on(:current_index_changed, ["int"]) {|i| self.index = i }

is referring to currentIndexChanged(int) and not to the other possible signal currentIndexChanged(QString), because of the explicit type annotation.

The advantage of this trick is that I can write, for example:

model.on(:rows_about_to_be_inserted) do |p, i, j|
  # ...
end

without specifying any C++ signature, which in this case would be quite hefty:

rowsAboutToBeInserted(const QModelIndex& parent, int start, int end)

Conclusion

QtRuby is an exceptional library, but to use it effectively you need to let go of some of the established practices of Qt programming in C++, and embrace the greater dynamicity of ruby.

In the next article I’ll show you how I tried to push this idea to the extreme with AutoGUI, a declarative GUI DSL built on top of QtRuby.

About these ads

10 Responses to “Effective Qt in ruby (part 2)”

  1. Paulo Fidalgo Says:

    Thanks a lot for your ruby articles…
    Maybe you could get more expose for your articles by posting them in rubyflow or rubyinside sites. (I’m not affiliated, although I subscribed them via RSS like planet KDE)

    I will subscribe your blog too… in case you decide not to publish articles on those sites :)

    Best regards and keep posting :)

  2. pcapriotti Says:

    @Paulo
    Just posted on rubyflow, thanks for the suggestion.

  3. frank Says:

    And that’s why a found your nice post. Good work. Guess i have to try it out.

  4. TrueNeo Says:

    Ciao,
    scrivo qui perché non ho trovato una mail di riferimento sul tuo blog, vorrei invitarti a collaborare con noi di qt-italia.org, non abbiamo nessuno bravo ed esperto come te e ci farebbe piacere la tua presenza.

    Daniele

  5. Daniel Gaytán Says:

    Wow, I worked on Qt using Python some years ago, I will try now with Ruby.

    Thanks for your posts.

  6. x37v Says:

    So is your ‘on’ method a method of Qt::Base?
    Can you give the whole signature [requires, includes, etc] because my build of Qt4 doesn’t seem to have ‘Signal’ .. ..

    • x37v Says:

      Ahh, I see, it is part of your kaya work, which looks really nice. It would be great to see your utils/wrappers brought out into their own library so that others [like me] can easily use them in their own projects.

      • pcapriotti Says:

        Yes, I’ve been working for a while on extracting its GUI abstraction into a library. It’s not exactly straightforward, some parts were ad-hoc for kaya, and I didn’t think at the time that they could be useful independently, but it’s coming along.
        Hopefully, it should land on github some time soon.

  7. x37v Says:

    One comment about the ‘never use slots’ guideline that you have here.

    My guess is that if you’re not actually doing any processing on the data from a signal, just passing the args along to another QObject [which would have a slot for this signal], that using ruby to connect a signal to a slot would be significantly faster than using the ruby widget.on block style. I assume this would simply connect the sig/slot and not invoke any ruby code unless the slot is in fact defined on the ruby side.

    Obviously the ruby style is more elegant, but I figure it might be useful to keep this in mind when doing profiling.

    Am I totally off base here?

  8. Michael SchLS Says:

    Hi, you wrote that using the signal/slot concept is totally useless.
    If I use the uic to compile the user interface file i got a rb file which contains some code but also with connect …. as I know it from c++.

    So I tried out the same way but i fails because of the following error:
    “…No superclass method connect…”. So I started searching in google and so I found your page.

    What I wanna do is in a derived class (Baseclass is Qt::MainWindow) do a connect from a variable to a progressbar, but at the moment I’m totally lost how this should work. Do you have any idea?

    example:
    class Test
    def start
    @cnt = @cnt + 1
    end
    end

    if @cnt changes then a calculation should be started and the result should be displayed in the Qt progressbar.

    rgds
    Michael

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: