Languages/Ruby: Difference between revisions

    From KDE TechBase
    (Example of Emitting a Ruby Class)
    m (Ochurlaud moved page Development/Languages/Ruby to Languages/Ruby)
    (14 intermediate revisions by 8 users not shown)
    Line 1: Line 1:
    {{Template:I18n/Language Navigation Bar|Development/Languages/Ruby}}
    {{Warning|Some important links are non-functional, specially the official Korundum page. Until we solve this problem you can find the source code at [https://projects.kde.org/projects/kde/kdebindings/ruby kdebindings project page]}}
     
     
    [[Image:ruby.png]]
    [[Image:ruby.png]]


    Line 12: Line 14:
    = 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 20: Line 22:
    hello.show
    hello.show
    a.exec
    a.exec
    </code>
    </syntaxhighlight>


    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 49: Line 51:
         exec
         exec
    end
    end
    </code>
    </syntaxhighlight>


    = Current api coverage overview =
    = Current api coverage overview =
    Line 65: Line 67:
    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()
    </code>
    </syntaxhighlight>


    ==Operator overloading==
    ==Operator overloading==
    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)
    p1 + p2                  => (25, 25)
    p1 + p2                  => (25, 25)
    </code>
    </syntaxhighlight>


    ==Declare signals and slots==
    ==Declare signals and slots==
    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()'..
    </code>
    </syntaxhighlight>


    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
    </code>
    </syntaxhighlight>


    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)
    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)
    Line 98: Line 100:
    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)" ) )
    </code>
    </syntaxhighlight>


    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))
    </code>
    </syntaxhighlight>


    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>
    </syntaxhighlight>


    And emit signals like this:
    And emit signals like this:


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


    ==Emitting Ruby Classes==
    ==Emitting Ruby Classes==
    Line 126: Line 128:


    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 141: Line 143:
         end
         end
    end
    end
    </code>
    </syntaxhighlight>
     
    Note: as of KDE 4.5 you can simply use
    <syntaxhighlight lang="ruby">
    variant = Qt::Variant.fromValue(my_ruby_object)
    </syntaxhighlight>
    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
    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)"
         def doEmit
         def doEmit
            # since KDE 4.5:
            # emit mySignal(Qt::Variant.fromValue(ruby_object))
             emit mySignal(ruby_object.to_variant)
             emit mySignal(ruby_object.to_variant)
         end
         end
    Line 156: Line 166:
         end
         end
    end
    end
    </code>
    </syntaxhighlight>
     
    === 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
    </syntaxhighlight>


    ==Constructors==
    ==Constructors==
    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>
    </syntaxhighlight>


    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>
    </syntaxhighlight>


    The block will be called in the context of the newly created instance.
    The block will be called in the context of the newly created instance.
    Line 175: Line 202:
    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>
    </syntaxhighlight>


    They are run in the context of the new instance.
    They are run in the context of the new instance.
    Line 183: Line 210:
    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>
    </syntaxhighlight>


    ==Garbage Collection==
    ==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:
    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?
    puts "item2 is disposed"
    puts "item2 is disposed"
    end
    end
    </code>
    </syntaxhighlight>


    ==C++ 'int*' and 'int&' argument types==
    ==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()':
    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 206: Line 233:
    acc = Qt::Integer.new(0)
    acc = Qt::Integer.new(0)
    fc = KDE::MimeType.findByFileContent("mimetype.rb", acc)
    fc = KDE::MimeType.findByFileContent("mimetype.rb", acc)
    </code>
    </syntaxhighlight>


    It supports the arithmetic operators, and so expressions such as 'acc + 3' will work.
    It supports the arithmetic operators, and so expressions such as 'acc + 3' will work.
    Line 213: Line 240:
    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 226: Line 253:
    # the user canceled the dialog
    # the user canceled the dialog
    end
    end
    </code>
    </syntaxhighlight>


    Use 'nil?' to test the value returned in the Boolean
    Use 'nil?' to test the value returned in the Boolean
    Line 240: Line 267:
    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
    a.loadLibrary("foo")  # Non existent method
    a.loadLibrary("foo")  # Non existent method
    </code>
    </syntaxhighlight>


    Will give the following output:
    Will give the following output:
    Line 262: Line 289:
    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>
    </syntaxhighlight>


    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>
    </syntaxhighlight>


    ==String i18n==
    ==String i18n==
    Line 281: Line 308:
    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:
    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:


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


    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 294: Line 321:
         a.exec
         a.exec
    end
    end
    </code>
    </syntaxhighlight>


    Then you can test the example code straight away:
    Then you can test the example code straight away:
    Line 302: Line 329:
    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:
    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:


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


    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 318: Line 345:
         a.exec
         a.exec
    end
    end
    </code>
    </syntaxhighlight>


    ==Loading .ui files at runtime with Qt::UILoader==
    ==Loading .ui files at runtime with Qt::UiLoader==
    {{improve|Remove example that does not work}}
     
    Let's say you created a file in your project directory called 'gui.ui' using Qt Designer ('/usr/bin/designer', comes with Qt. At least on Arch).
    You can load it like so:
     
    <syntaxhighlight lang="ruby">
    require 'Qt4'
    require 'qtuitools'


    You can load a Qt Designer .ui file at runtime with the 'quiloader' extension, for example:
    class MyApp < Qt::Application
        def initialize
            super ARGV


          require 'Qt4'
            # 'gui.ui' was created with qt designer ('designer' on arch linux)
          require 'quiloader'
            file = Qt::File.new 'gui.ui' do
                open Qt::File::ReadOnly
          a = Qt::Application.new(ARGV)
            end
          if ARGV.length == 0
           
            exit
            window = Qt::UiLoader.new.load file
          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.
            file.close


          require 'Qt4'
            if window.nil?
          require 'qtuitools'
                print "Error. Window is nil.\n"
                exit
          a = Qt::Application.new(ARGV)
            end  
          if ARGV.length == 0
            window.show
            exit
        end
          end
    end
          if ARGV.length == 1
            file = Qt::File.new(ARGV[1])
            file.open(Qt::File::ReadOnly)
            loader = Qt::UiLoader.new
            window = loader.load(file, nil)
            file.close


            if (window.nil?)
    a = MyApp.new
              print "Error. Window is nil.\n"
    a.exec
              exit
    </syntaxhighlight>
            end
            window.show
            a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()'))
            a.exec
          end


    ==API reference==
    ==API reference==
    Line 375: Line 385:
    Use the bin/rbqtapi tool to discover which methods are available in the QtRuby api. This command:
    Use the bin/rbqtapi tool to discover which methods are available in the QtRuby api. This command:


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


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


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


    Lists all methods whose names contain the string 'setCaption'
    Lists all methods whose names contain the string 'setCaption'
    Line 390: Line 400:


    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.
    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
    </syntaxhighlight>
    === 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>
    <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
    </syntaxhighlight>
    === 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
    </syntaxhighlight>


    =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'</syntaxhighlight>, use<syntaxhighlight lang="ruby">require 'korundum4'</syntaxhighlight> 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.
    Line 398: Line 549:
    Use the 'rbkdeapi' script to introspect the Korundum api from the command line. For example:
    Use the 'rbkdeapi' script to introspect the Korundum api from the command line. For example:


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


    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.
    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.
    Line 441: Line 592:
    * [http://weblog.nomejortu.com/x-windows/ruby-qt-custom-widget-example ruby Qt custom widget example]
    * [http://weblog.nomejortu.com/x-windows/ruby-qt-custom-widget-example ruby Qt custom widget example]
    * [http://weblog.nomejortu.com/x-windows/ruby-qttreewidget-example ruby Qt::TreeWidget example]
    * [http://weblog.nomejortu.com/x-windows/ruby-qttreewidget-example ruby Qt::TreeWidget example]
    * [http://www.ruby-forum.com/topic/189346#new Very usefull link how to create your first Qt window dialog]
    * [http://www.ruby-forum.com/topic/189346#new Very useful link how to create your first Qt window dialog]


    [[Category:Ruby]]
    [[Category:Ruby]]

    Revision as of 17:39, 10 March 2016

    Warning
    Some important links are non-functional, specially the official Korundum page. Until we solve this problem you can find the source code at kdebindings project page


    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:

    #!/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:

    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('<big>Hello Qt in the Ruby way!</big>')
            
            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:

    create_standard_status_bar_action()
    createStandardStatusBarAction()
    

    Operator overloading

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

    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:

    slots 'setColor(QColor)', 'slotLoad(const QString&)'..
    signals 'clicked()'..
    

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

    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:

    Qt::Object.connect( @colormenu, SIGNAL( "activated(int)" ),
                      self, SLOT( "slotColorMenu(int)" ) )
    

    There is also two another possibilities:

    connect(:mysig, mytarget, :mymethod))
    connect(SIGNAL('mysignal(int)'), mytarget, :mymethod))
    

    Or you can connect signal to a block:

    quit_button.connect(SIGNAL :clicked) { $qApp.quit }
    

    And emit signals like this:

    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

    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

    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

    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.

    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:

    quit = Qt::PushButton.new("Quit", self, "quit")
    

    Or you can pass a block if you prefer:

    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:

    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:

    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:

    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()':

    # static Ptr findByFileContent( const QString &fileName, 
    #                               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:

    # QFont getFont(bool * ok, const QFont&initial, 
    #               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? 
    # font is set to the font the user selected
    else 
    # 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:

    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:

    Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VIRTUAL)
    

    Or trace QtRuby garbage collection:

    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:

    $ rbuic mainform.ui -x -o mainform.rb
    

    Will add this to the end of the generated code:

    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:

    $ rbuic4 -x -kde knotifywidgetbase.ui -o knotifywidgetbase.rb
    

    Will generate this top level code:

    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

    Let's say you created a file in your project directory called 'gui.ui' using Qt Designer ('/usr/bin/designer', comes with Qt. At least on Arch). You can load it like so:

    require 'Qt4'
    require 'qtuitools'
    
    class MyApp < Qt::Application
        def initialize
            super ARGV
    
            # 'gui.ui' was created with qt designer ('designer' on arch linux)
            file = Qt::File.new 'gui.ui' do
                open Qt::File::ReadOnly
            end
            
            window = Qt::UiLoader.new.load file
    
            file.close
    
            if window.nil?
                print "Error. Window is nil.\n"
                exit
            end 
            window.show
        end
    end
    
    a = MyApp.new
    a.exec
    

    API reference

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

    $ rbqtapi Qt::TextEdit
    

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

    $ 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.

    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:

    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
    
          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

    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

    require 'Qt4'
    

    , use

    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:

    $ 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):