Languages/Ruby: Difference between revisions

From KDE TechBase
m (Text replace - "<code bash>" to "<syntaxhighlight lang="bash">")
m (Text replace - "<code ruby>" to "<syntaxhighlight lang="ruby">")
Line 12: Line 12:
= QtRuby =
= QtRuby =
Hello world example:
Hello world example:
<code ruby>
<syntaxhighlight lang="ruby">
#!/usr/bin/ruby -w
#!/usr/bin/ruby -w
require 'Qt4'
require 'Qt4'
Line 24: Line 24:
Hello Qt example in a more 'Rubyish' way:
Hello Qt example in a more 'Rubyish' way:


<code ruby>
<syntaxhighlight lang="ruby">
require 'Qt4'
require 'Qt4'


Line 65: Line 65:
Any underscores in method names are removed, and the following character is capitalised. For example, you can use either of these two forms to call the same method:
Any underscores in method names are removed, and the following character is capitalised. For example, you can use either of these two forms to call the same method:


<code ruby>
<syntaxhighlight lang="ruby">
create_standard_status_bar_action()
create_standard_status_bar_action()
createStandardStatusBarAction()
createStandardStatusBarAction()
Line 73: Line 73:
The full range of Qt operator methods is available, for example:
The full range of Qt operator methods is available, for example:


<code ruby>
<syntaxhighlight lang="ruby">
p1 = Qt::Point.new(5,5)  => (5, 5)
p1 = Qt::Point.new(5,5)  => (5, 5)
p2 = Qt::Point.new(20,20) => (20, 20)
p2 = Qt::Point.new(20,20) => (20, 20)
Line 82: Line 82:
Signals and slots are declared as list of strings like this:
Signals and slots are declared as list of strings like this:


<code ruby>
<syntaxhighlight lang="ruby">
slots 'setColor(QColor)', 'slotLoad(const QString&)'..
slots 'setColor(QColor)', 'slotLoad(const QString&)'..
signals 'clicked()'..
signals 'clicked()'..
Line 89: Line 89:
For slots and signals without arguments you can use Ruby symbols:
For slots and signals without arguments you can use Ruby symbols:


<code ruby>
<syntaxhighlight lang="ruby">
slots :slotLoad
slots :slotLoad
signals :clicked
signals :clicked
Line 98: Line 98:
Connect slots and signals like this:
Connect slots and signals like this:


<code ruby>
<syntaxhighlight lang="ruby">
Qt::Object.connect( @colormenu, SIGNAL( "activated(int)" ),
Qt::Object.connect( @colormenu, SIGNAL( "activated(int)" ),
                   self, SLOT( "slotColorMenu(int)" ) )
                   self, SLOT( "slotColorMenu(int)" ) )
Line 105: Line 105:
There is also two another possibilities:
There is also two another possibilities:


<code ruby>
<syntaxhighlight lang="ruby">
connect(:mysig, mytarget, :mymethod))
connect(:mysig, mytarget, :mymethod))
connect(SIGNAL('mysignal(int)'), mytarget, :mymethod))
connect(SIGNAL('mysignal(int)'), mytarget, :mymethod))
Line 112: Line 112:
Or you can connect signal to a block:
Or you can connect signal to a block:


<code ruby>
<syntaxhighlight lang="ruby">
quit_button.connect(SIGNAL :clicked) { $qApp.quit }
quit_button.connect(SIGNAL :clicked) { $qApp.quit }
</code>
</code>
Line 118: Line 118:
And emit signals like this:
And emit signals like this:


<code ruby>
<syntaxhighlight lang="ruby">
emit colorChanged( black )
emit colorChanged( black )
</code>
</code>
Line 126: Line 126:


The following code provides a method called to_variant that can be used to easily convert objects to Variants
The following code provides a method called to_variant that can be used to easily convert objects to Variants
<code ruby>
<syntaxhighlight lang="ruby">
class Qt::RubyVariant < Qt::Variant
class Qt::RubyVariant < Qt::Variant
     def initialize(value)
     def initialize(value)
Line 144: Line 144:


Note: as of KDE 4.5 you can simply use
Note: as of KDE 4.5 you can simply use
<code ruby>
<syntaxhighlight lang="ruby">
variant = Qt::Variant.fromValue(my_ruby_object)
variant = Qt::Variant.fromValue(my_ruby_object)
</code>
</code>
Line 150: Line 150:


This can be used as follows
This can be used as follows
<code ruby>
<syntaxhighlight lang="ruby">
class MyObject < Qt::Object
class MyObject < Qt::Object
     signals "mySignal(QVariant)"
     signals "mySignal(QVariant)"
Line 169: Line 169:
You can also try to emit a ruby class by emitting it's object_id (either as an Integer or a QVariant), and use ObjectSpace._id2ref to get the object back.
You can also try to emit a ruby class by emitting it's object_id (either as an Integer or a QVariant), and use ObjectSpace._id2ref to get the object back.


<code ruby>
<syntaxhighlight lang="ruby">
class Object
class Object
     def to_variant
     def to_variant
Line 186: Line 186:
You can call constructors in the conventional style:
You can call constructors in the conventional style:


<code ruby>
<syntaxhighlight lang="ruby">
quit = Qt::PushButton.new("Quit", self, "quit")
quit = Qt::PushButton.new("Quit", self, "quit")
</code>
</code>
Line 192: Line 192:
Or you can pass a block if you prefer:
Or you can pass a block if you prefer:


<code ruby>
<syntaxhighlight lang="ruby">
w = MyWidget.new { setCaption("foobar") }
w = MyWidget.new { setCaption("foobar") }
</code>
</code>
Line 200: Line 200:
Ordinary arguments can be provided as well as a block at the end:
Ordinary arguments can be provided as well as a block at the end:


<code ruby>
<syntaxhighlight lang="ruby">
w = MyWidget.new(nil) { setCaption("foobar") }
w = MyWidget.new(nil) { setCaption("foobar") }
</code>
</code>
Line 208: Line 208:
And there's more! You can also pass an arg to the block, and it will be run in the context of the arg:
And there's more! You can also pass an arg to the block, and it will be run in the context of the arg:


<code ruby>
<syntaxhighlight lang="ruby">
w = MyWidget.new { |theWidget| theWidget.setCaption "foobar" }
w = MyWidget.new { |theWidget| theWidget.setCaption "foobar" }
</code>
</code>
Line 215: Line 215:
When a ruby instance is garbage collected, the underlying C++ instance will only be deleted if it isn't 'owned' by a parent object. Normally this will 'just work', but there are occasions when you need to delete the C++ ahead of garbage collection, and whether or not it has a parent. Use the dispose(), isDisposed() and disposed? methods like this:
When a ruby instance is garbage collected, the underlying C++ instance will only be deleted if it isn't 'owned' by a parent object. Normally this will 'just work', but there are occasions when you need to delete the C++ ahead of garbage collection, and whether or not it has a parent. Use the dispose(), isDisposed() and disposed? methods like this:


<code ruby>
<syntaxhighlight lang="ruby">
item2.dispose
item2.dispose
if item2.disposed?
if item2.disposed?
Line 225: Line 225:
Ruby passes numeric values by value, and so they can't be changed when passed to a method. The Qt::Integer class provides a mutable numeric type which does get updated when passed as an argument. For example, this C++ method 'findByFileContent()':
Ruby passes numeric values by value, and so they can't be changed when passed to a method. The Qt::Integer class provides a mutable numeric type which does get updated when passed as an argument. For example, this C++ method 'findByFileContent()':


<code ruby>
<syntaxhighlight lang="ruby">
# static Ptr findByFileContent( const QString &fileName,  
# static Ptr findByFileContent( const QString &fileName,  
#                              int *accuracy=0 );
#                              int *accuracy=0 );
Line 238: Line 238:
There is a similar problem for bool arg types, and the mutable Qt::Boolean class can be used like this:
There is a similar problem for bool arg types, and the mutable Qt::Boolean class can be used like this:


<code ruby>
<syntaxhighlight lang="ruby">
# QFont getFont(bool * ok, const QFont&initial,  
# QFont getFont(bool * ok, const QFont&initial,  
#              QWidget* parent = 0, const char *name = 0);
#              QWidget* parent = 0, const char *name = 0);
Line 265: Line 265:
If a method call can't be matched in the Smoke library giving a 'method_missing' error, first check that you are passing correct class instance that is properly initialized (with super method called in constructors of custom Qt classes descendants). You can also turn on debugging to trace the matching process:
If a method call can't be matched in the Smoke library giving a 'method_missing' error, first check that you are passing correct class instance that is properly initialized (with super method called in constructors of custom Qt classes descendants). You can also turn on debugging to trace the matching process:


<code ruby>
<syntaxhighlight lang="ruby">
a = Qt::Application.new(ARGV)
a = Qt::Application.new(ARGV)
Qt.debug_level = Qt::DebugLevel::High
Qt.debug_level = Qt::DebugLevel::High
Line 287: Line 287:
You can trace virtual method callbacks:
You can trace virtual method callbacks:


<code ruby>
<syntaxhighlight lang="ruby">
Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VIRTUAL)
Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VIRTUAL)
</code>
</code>
Line 293: Line 293:
Or trace QtRuby garbage collection:
Or trace QtRuby garbage collection:


<code ruby>
<syntaxhighlight lang="ruby">
Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_GC)
Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_GC)
</code>
</code>
Line 312: Line 312:
Will add this to the end of the generated code:
Will add this to the end of the generated code:


<code ruby>
<syntaxhighlight lang="ruby">
if $0 == __FILE__
if $0 == __FILE__
     a = Qt::Application.new(ARGV)
     a = Qt::Application.new(ARGV)
Line 333: Line 333:
Will generate this top level code:
Will generate this top level code:


<code ruby>
<syntaxhighlight lang="ruby">
if $0 == __FILE__
if $0 == __FILE__
     about = KDE::AboutData.new("knotifywidgetbase",  
     about = KDE::AboutData.new("knotifywidgetbase",  
Line 423: Line 423:
Note how we create a stub to pass to QAbstractTableModel::data, which accepts a QModelIndex as an argument.
Note how we create a stub to pass to QAbstractTableModel::data, which accepts a QModelIndex as an argument.


<code ruby>
<syntaxhighlight lang="ruby">
describe CardModel do
describe CardModel do
     it "Should Implement QAbstractTableModel" do
     it "Should Implement QAbstractTableModel" do
Line 462: Line 462:
Below is a simple class which can be used to test out a class via the signals it emits:
Below is a simple class which can be used to test out a class via the signals it emits:


<code ruby>
<syntaxhighlight lang="ruby">
class RubySignalSpy < Qt::Object
class RubySignalSpy < Qt::Object
     def self.create(*args, &block)
     def self.create(*args, &block)
Line 510: Line 510:
</code>
</code>


<code ruby>
<syntaxhighlight lang="ruby">
       it "Should be able to emit a signal when called" do
       it "Should be able to emit a signal when called" do
         reciever = RubySignalSpy.create do
         reciever = RubySignalSpy.create do
Line 538: Line 538:


Some rudimentary tests can be written by simulating clicks are various locations. Watch this space as more tests get written
Some rudimentary tests can be written by simulating clicks are various locations. Watch this space as more tests get written
<code ruby>
<syntaxhighlight lang="ruby">
describe SomeTableView do
describe SomeTableView do
     before(:all) do
     before(:all) do
Line 558: Line 558:


=KDE Specific Infomation=
=KDE Specific Infomation=
Instead of <code ruby>require 'Qt4'</code>, use<code ruby>require 'korundum4'</code> for KDE programs.
Instead of <syntaxhighlight lang="ruby">require 'Qt4'</code>, use<syntaxhighlight lang="ruby">require 'korundum4'</code> for KDE programs.


The KDE K* classes such as KApplication are renamed as KDE::Application. The other KDE classes are in the KParts::, KIO:: or DOM:: namespaces, with the same names as their C++ counterparts.
The KDE K* classes such as KApplication are renamed as KDE::Application. The other KDE classes are in the KParts::, KIO:: or DOM:: namespaces, with the same names as their C++ counterparts.

Revision as of 20:42, 29 June 2011


Development/Languages/Ruby

Very complete bindings to both the KDE API and the Qt APIs. The Korundum package includes both a QtRuby Qt-only binding along with the full combined Qt/KDE one. The QtRuby package contains just Qt bindings with no dependencies on KDE.

Korundum/QtRuby - Ruby-KDE/Qt bindings

The book Rapid GUI Development with QtRuby (for Qt version 3.x) is available.

Being Smoke-based bindings means that they offer full access to most KDE 4.x and Qt 4.x classes.

QtRuby

Hello world example: <syntaxhighlight lang="ruby">

  1. !/usr/bin/ruby -w

require 'Qt4' a = Qt::Application.new(ARGV) hello = Qt::PushButton.new("Hello World!") hello.resize(100, 30) hello.show a.exec

Hello Qt example in a more 'Rubyish' way:

<syntaxhighlight lang="ruby"> require 'Qt4'

Qt::Application.new(ARGV) do

   Qt::Widget.new do
       self.window_title = 'Hello QtRuby v1.0'
       resize(200, 100)
   
       button = Qt::PushButton.new('Quit') do
           connect(SIGNAL :clicked) { Qt::Application.instance.quit }
       end
       label = Qt::Label.new('Hello Qt in the Ruby way!')
       
       self.layout = Qt::VBoxLayout.new do
           add_widget(label, 0, Qt::AlignCenter)
           add_widget(button, 0, Qt::AlignRight)
       end
       
       show
   end
   
   exec

end

Current api coverage overview

Available calls

You can call all Qt public and protected methods, and all friend methods such as bitBlt() etc

Virtual methods

All virtual methods can be overridden, not just event handlers

Properties

'foobar = 5' is a synonym for 'setFooBar(5)'

Use either CamelCase or lowercase with underscore naming

Any underscores in method names are removed, and the following character is capitalised. For example, you can use either of these two forms to call the same method:

<syntaxhighlight lang="ruby"> create_standard_status_bar_action() createStandardStatusBarAction()

Operator overloading

The full range of Qt operator methods is available, for example:

<syntaxhighlight lang="ruby"> p1 = Qt::Point.new(5,5) => (5, 5) p2 = Qt::Point.new(20,20) => (20, 20) p1 + p2 => (25, 25)

Declare signals and slots

Signals and slots are declared as list of strings like this:

<syntaxhighlight lang="ruby"> slots 'setColor(QColor)', 'slotLoad(const QString&)'.. signals 'clicked()'..

For slots and signals without arguments you can use Ruby symbols:

<syntaxhighlight lang="ruby"> slots :slotLoad signals :clicked

Currently C++ type signatures must be used, a future version of QtRuby will allow ruby type signatures instead. (see the section on emitting Ruby Classes)

Connect slots and signals like this:

<syntaxhighlight lang="ruby"> Qt::Object.connect( @colormenu, SIGNAL( "activated(int)" ),

                 self, SLOT( "slotColorMenu(int)" ) )

There is also two another possibilities:

<syntaxhighlight lang="ruby"> connect(:mysig, mytarget, :mymethod)) connect(SIGNAL('mysignal(int)'), mytarget, :mymethod))

Or you can connect signal to a block:

<syntaxhighlight lang="ruby"> quit_button.connect(SIGNAL :clicked) { $qApp.quit }

And emit signals like this:

<syntaxhighlight lang="ruby"> emit colorChanged( black )

Emitting Ruby Classes

Ruby classes can be emitted by embedding them inside a QVariant, and emitting the QVariant.

The following code provides a method called to_variant that can be used to easily convert objects to Variants <syntaxhighlight lang="ruby"> class Qt::RubyVariant < Qt::Variant

   def initialize(value)
       super()
       @value = value
   end
   attr_accessor :value

end

class Object

   def to_variant
       Qt::RubyVariant.new self
   end

end

Note: as of KDE 4.5 you can simply use <syntaxhighlight lang="ruby"> variant = Qt::Variant.fromValue(my_ruby_object) to create a QVariant that contains your ruby object. To get it back, you just call Qt::Variant#value as usual.

This can be used as follows <syntaxhighlight lang="ruby"> class MyObject < Qt::Object

   signals "mySignal(QVariant)"
   def doEmit
       # since KDE 4.5:
       # emit mySignal(Qt::Variant.fromValue(ruby_object))
       emit mySignal(ruby_object.to_variant)
   end
   slots "mySlot(QVariant)"
   def mySlot(variant)
       ruby_object = variant.value
   end

end

Alternate way to emit Ruby Classes

You can also try to emit a ruby class by emitting it's object_id (either as an Integer or a QVariant), and use ObjectSpace._id2ref to get the object back.

<syntaxhighlight lang="ruby"> class Object

   def to_variant
       Qt::Variant.new object_id
   end

end

class Qt::Variant

   def to_object
       ObjectSpace._id2ref to_int
   end

end

Constructors

You can call constructors in the conventional style:

<syntaxhighlight lang="ruby"> quit = Qt::PushButton.new("Quit", self, "quit")

Or you can pass a block if you prefer:

<syntaxhighlight lang="ruby"> w = MyWidget.new { setCaption("foobar") }

The block will be called in the context of the newly created instance.

Ordinary arguments can be provided as well as a block at the end:

<syntaxhighlight lang="ruby"> w = MyWidget.new(nil) { setCaption("foobar") }

They are run in the context of the new instance.

And there's more! You can also pass an arg to the block, and it will be run in the context of the arg:

<syntaxhighlight lang="ruby"> w = MyWidget.new { |theWidget| theWidget.setCaption "foobar" }

Garbage Collection

When a ruby instance is garbage collected, the underlying C++ instance will only be deleted if it isn't 'owned' by a parent object. Normally this will 'just work', but there are occasions when you need to delete the C++ ahead of garbage collection, and whether or not it has a parent. Use the dispose(), isDisposed() and disposed? methods like this:

<syntaxhighlight lang="ruby"> item2.dispose if item2.disposed? puts "item2 is disposed" end

C++ 'int*' and 'int&' argument types

Ruby passes numeric values by value, and so they can't be changed when passed to a method. The Qt::Integer class provides a mutable numeric type which does get updated when passed as an argument. For example, this C++ method 'findByFileContent()':

<syntaxhighlight lang="ruby">

  1. static Ptr findByFileContent( const QString &fileName,
  2. int *accuracy=0 );

acc = Qt::Integer.new(0) fc = KDE::MimeType.findByFileContent("mimetype.rb", acc)

It supports the arithmetic operators, and so expressions such as 'acc + 3' will work.

C++ 'bool*' and 'bool&' argument types

There is a similar problem for bool arg types, and the mutable Qt::Boolean class can be used like this:

<syntaxhighlight lang="ruby">

  1. QFont getFont(bool * ok, const QFont&initial,
  2. QWidget* parent = 0, const char *name = 0);

ok = Qt::Boolean.new font = Qt::FontDialog.getFont(ok,

                   Qt::Font.new("Helvetica [Cronyx]", 10), 
                   self)

if !ok.nil?

  1. font is set to the font the user selected

else

  1. the user canceled the dialog

end

Use 'nil?' to test the value returned in the Boolean

C++ (const )(unsigned )char* argument types

In some cases Qt/KDE object "takes ownership" over Ruby String passed as char* argument type. Programmer needs to make sure that Ruby String is not being garbage collected or changed for the time it's being used by Qt/KDE object. It is also quite possible that Qt/KDE object will change and eventually free it(memory used internally by Ruby String to store its data). Be very careful when you call this kind of methods and make sure that there is no overloaded version witch accepts QString or QByteArray first!

C++ unsigned char* functions

Very few functions (as QImage::bits()) return a uchar* to directly manipulate data. These functions are not supported in Ruby and will throw an ArgumentError. More information on the mail list.

Debugging

If a method call can't be matched in the Smoke library giving a 'method_missing' error, first check that you are passing correct class instance that is properly initialized (with super method called in constructors of custom Qt classes descendants). You can also turn on debugging to trace the matching process:

<syntaxhighlight lang="ruby"> a = Qt::Application.new(ARGV) Qt.debug_level = Qt::DebugLevel::High a.loadLibrary("foo") # Non existent method

Will give the following output:

      classname    == QApplication
      :: method == loadLibrary$
      -> methodIds == []
      candidate list:
      Possible prototypes:
          static QWidget* QApplication::widgetAt(int, int, bool)
			...

Here, the list of candidate methods 'methodIds' is empty

Another debugging mechanism allows various trace 'channels' to be switched on.

You can trace virtual method callbacks:

<syntaxhighlight lang="ruby"> Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VIRTUAL)

Or trace QtRuby garbage collection:

<syntaxhighlight lang="ruby"> Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_GC)

String i18n

QtRuby supports $KCODE values of 'u', 'e' and 's' or the corresponding '-K' options from the command line. Qt Designer .ui files have UTF-8 strings so if you use any 8 bit UTF-8 characters, you will need to set $KCODE='u' or use the -Ku command line option.

Other capabilities and offerings

Qt Designer

A 'rbuic4' tool is included in qtruby/tools/rbuic to compile .ui files into ruby code. As described above, Qt Designer uses UTF-8. In addition to the options in the original uic C++ utility an '-x' flag has been added. This will generate a top level stub in the code:

<syntaxhighlight lang="bash"> $ rbuic mainform.ui -x -o mainform.rb

Will add this to the end of the generated code:

<syntaxhighlight lang="ruby"> if $0 == __FILE__

   a = Qt::Application.new(ARGV)
   w = MainForm.new
   w.show
   a.exec

end

Then you can test the example code straight away:

      $ ruby mainform.rb

Use the '-kde' option to require the 'korundum4' extension rather than the 'Qt4' one. If the '-x' option is used in conjunction, it generates a KDE top level. For example:

<syntaxhighlight lang="bash"> $ rbuic4 -x -kde knotifywidgetbase.ui -o knotifywidgetbase.rb

Will generate this top level code:

<syntaxhighlight lang="ruby"> if $0 == __FILE__

   about = KDE::AboutData.new("knotifywidgetbase", 
		                       "KNotifyWidgetBase", "0.1")
   KDE::CmdLineArgs.init(ARGV, about)
   a = KDE::Application.new()
   w = KNotifyWidgetBase.new
   w.show
   a.exec

end

Loading .ui files at runtime with Qt::UILoader

Warning
This section needs improvements: Please help us to

cleanup confusing sections and fix sections which contain a todo


Remove example that does not work

You can load a Qt Designer .ui file at runtime with the 'quiloader' extension, for example:

      require 'Qt4'
      require 'quiloader'

      a = Qt::Application.new(ARGV)
      if ARGV.length == 0
        exit
      end

      if ARGV.length == 2
        QUI::WidgetFactory.loadImages ARGV[0]
        w = QUI::WidgetFactory.create ARGV[1]
        if w.nil?
          exit
        end
        w.show()
        a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()'))
        a.exec()
      end

With new version API changed a little.

      require 'Qt4'
      require 'qtuitools'

      a = Qt::Application.new(ARGV)
      if ARGV.length == 0
        exit
      end

      if ARGV.length == 1
        file = Qt::File.new(ARGV[0])
        file.open(Qt::File::ReadOnly)

        loader = Qt::UiLoader.new
        window = loader.load(file, nil)
        file.close
        if (window.nil?)
          print "Error. Window is nil.\n"
          exit
        end
        window.show
        a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()'))
        a.exec
      end

API reference

Use the bin/rbqtapi tool to discover which methods are available in the QtRuby api. This command:

<syntaxhighlight lang="bash"> $ rbqtapi Qt::TextEdit

Will list all the methods in the Qt::TextEdit class

<syntaxhighlight lang="bash"> $ rbqtapi -rsetCaption

Lists all methods whose names contain the string 'setCaption'

Example programs

The best way to start programming QtRuby is to look at some existing code and start messing with it.. The are various samples under qtrubyexamples and korundum/examples.

Writing Unit Tests

Using Ruby allows you the power to leverage testing frameworks such as RSpec in order to unit and integration test your classes.

Most classes such as Models can be easily tested via the APIs they expose. Below is a small extract with some interesting tests from the mingle_mover project (http://github.com/gja/mingle_mover). The class being tested is a TableModel

Note how we create a stub to pass to QAbstractTableModel::data, which accepts a QModelIndex as an argument.

<syntaxhighlight lang="ruby"> describe CardModel do

   it "Should Implement QAbstractTableModel" do
       CardModel.ancestors.should include Qt::AbstractTableModel
   end
   it "Should Not Be Editable" do
       @model.data(mock_index(1,2), Qt::EditRole).should_not be_valid
       @model.headerData(nil, nil, Qt::EditRole).should_not be_valid
       
       flags = @model.flags(nil)
       flags.should have_flag Qt::ItemIsEnabled
       flags.should have_flag Qt::ItemIsSelectable
       flags.should_not have_flag Qt::ItemIsEditable
   end
   it "Should not return any vertical headers" do
       @model.headerData(1, Qt::Vertical).should_not be_valid
   end
   it "Should color a row according to the status" do
       @model.data(mock_index(1,2), Qt::ForegroundRole).value.color.should == Qt::Color.new(Qt::red)
       @model.data(mock_index(0,2), Qt::ForegroundRole).value.color.should == Qt::Color.new(Qt::green)
       @model.data(mock_index(0,0), Qt::ForegroundRole).should_not be_valid
   end
   def mock_index(row, col)
       stub(:row => row, :column => col)
   end
   def have_flag(flag)
       return simple_matcher("A flag that matches " + flag.to_s) { |given| (given & flag) != 0 }
   end

end

Testing Out Signals and Slots

Below is a simple class which can be used to test out a class via the signals it emits:

<syntaxhighlight lang="ruby"> class RubySignalSpy < Qt::Object

   def self.create(*args, &block)
       Class.new(self).new(*args, &block)
   end
   def count(name)
       @calls[name].size
   end
   def params(name, invocation = 0)
       @calls[name][invocation]
   end
   def method_missing(name, *args, &block)
       @calls[name.to_sym] << args
       exec_action_for(name, args)
   end
   def responds_to?(name)
       true
   end
 private
   def initialize
       @calls = {}
       def @calls.[](index)
           super || self[index] = []
       end
       @actions = {}
       super
   end
   def mocked_slots(*names, &block)
       slots *names
       names.each { |name| @actions[name] = block }
   end
   def exec_action_for(name, args)
       @actions[name].call(self, args) if @actions[name]
   end
   def slots(*args)
       self.class.slots(*args)
   end

end

<syntaxhighlight lang="ruby">

     it "Should be able to emit a signal when called" do
       reciever = RubySignalSpy.create do
           slots "recieved(int, int)"                      # Explicitly name slots with parameters
           mocked_slot :some_other_slot do |spy, params|   # Pass a block to be executed when called
           end                                             # You must call mocked_slot with a symbol
       end
       class ClassWeAreTesting < Qt::Object
           signals "sending(int, int)"
           def broadcast
               emit sending(4, 2)
           end
       end
       sender = ClassWeAreTesting.new
       Qt::Object.connect(sender, SIGNAL("sending(int, int)"), reciever, SLOT("recieved(int, int)"))
       sender.broadcast
       reciever.count(:recieved).should == 1               # Get count of calls
       reciever.params(:recieved, 0).should == [4, 2]      # Get the parameters of nth invocation
   end

Testing UI Classes

It is much more difficult to test UI classes. In most cases, you will need to instantiate a QApplication so that you can create your widgets.

Some rudimentary tests can be written by simulating clicks are various locations. Watch this space as more tests get written <syntaxhighlight lang="ruby"> describe SomeTableView do

   before(:all) do
       @app = Qt::Application.new(ARGV)
       @view = SomeTableView.new
   end
   it "Should accept a mouse click on second row"        
       @row2 = @view.rowViewportPosition 1
       @view.mousePressEvent(Qt::MouseEvent.new(Qt::Event::MouseButtonPress, Qt::Point.new(0,@row2), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier))
       # Assert something here, such as that a signal has been emitted
   end
   after(:all) do
       @app.dispose!
   end

end

KDE Specific Infomation

Instead of <syntaxhighlight lang="ruby">require 'Qt4', use<syntaxhighlight lang="ruby">require 'korundum4' for KDE programs.

The KDE K* classes such as KApplication are renamed as KDE::Application. The other KDE classes are in the KParts::, KIO:: or DOM:: namespaces, with the same names as their C++ counterparts.

Use the 'rbkdeapi' script to introspect the Korundum api from the command line. For example:

<syntaxhighlight lang="bash"> $ rbkdeapi KDE::Action

Will list all the methods in the KDE::Action class. There are currently (as at KDE 3.3 beta 2) 977 classes/30841 methods in the Smoke library runtime, so the coverage of the Qt/KDE api is pretty complete.

Build dependencies

  • ruby 1.8 or greater (svn trunk works with 1.9.1)
  • cmake 2.6 or greater
  • Qt 4.0 or greater
  • KDE 4.1 or greater (for korundum)

Tutorials

There is a ruby translation of Qt Tutorial #1, and the corresponding ruby code is in qtruby/rubylib/tutorial/t1 to t14.

And a Qt4 version of the same tutorial translated to Ruby by Darshan Ishaya Qt4 Ruby Tutorial

Qt Tutorial #2, a Charting Application with ruby code in qtruby/rubylib/examples/qt-examples/chart.

The Qt Designer Color Tool Tutorial, with ruby code in qtruby/rubylib/designer/examples/colortool.

Paul Lutus has written a tutorial on how to get started with Ruby GUI programming with Qt

For KDE, there is a ruby translation of this KDE 3.0 tutorial originally written for C++ by Antonio Larrosa Jiménez. The sources are in korundum/rubylib/tutorials/p1 to p9.

The book Rapid GUI Development with QtRuby is now available.

There is also an approach to create an Ruby-Qt/KDE Book under a free license. The content will be created in this wiki. The book made with latex will be derived from the content in the wiki. Any Questions? Contact me!

Download

You can obtain recent SVN snapshots on the Rubyforge QtRuby/Korundum site.

More help

There are two IRC channels (#qtruby and #kde-ruby) in FreeNode. If you prefer e-mail, you can use the kde-bindings mailing-list (low traffic) or ask in the ruby-talk mailing list (you may use the Ruby Forum gateway to post in ruby-talk from web).

More information

A series of articles on ruby QT (inspired by the work done for the dradis project):