Languages/Ruby: Difference between revisions

    From KDE TechBase
    (→‎Tutorials: Corrected links)
    mNo edit summary
    Line 1: Line 1:
    {{Template:I18n/Language Navigation Bar|Development/Languages/Ruby}}
    [[Image:ruby.png]]
    [[Image:ruby.png]]



    Revision as of 11:50, 22 September 2008


    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

    NEW - The book Rapid GUI Development with QtRuby is now available.

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

    QtRuby

    Hello world example:

    1. !/usr/bin/ruby -w

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

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

    Currently C++ type signatures must be used, a future version of QtRuby will allow ruby type signatures instead.

    Connect slots and signals like this:

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

    And emit signals like this:

          emit colorChanged( black )
    

    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

    Debugging

    If a method call can't be matched in the Smoke library giving a 'method_missing' error, you can 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 'rbuic' tool is included in qtruby/rubylib/designer/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
              a.setMainWidget(w)
              w.show
              a.exec
          end
    

    Then you can test the example code straight away:

          $ ruby mainform.rb
    

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

          $ rbuic -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
        	a.setMainWidget(w)
        	w.show
        	a.exec
          end
    

    Loading .ui files at runtime with QUI::WidgetFactory

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

          require 'Qt'
          require 'qui'
    
          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
    

    QtRuby shell

    You can use the QtRuby shell in bin/rbqtsh to create widgets interactively from the command line.

    API reference

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

    	  $ rbqtapish 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 qtruby/rubylib/examples and korundum/rubylib/examples.

    KDE Specific Infomation

    Instead of require 'Qt', use require 'Korundum' 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.

    DCOP Support

    Here is a minimal ruby dcop slot implementation:

          require 'Korundum'
    			
          class MyWidget < KDE::PushButton
          	  k_dcop 'QPoint mySlot(int,QString)'
    	
          	  def initialize(parent, name)
                  super
              end
    			
          	  def mySlot(counter,greeting)
                  return Qt::Point.new(50, 100)
          	  end
          end   
    

    This slot is passed an integer and a string, and returns a Qt::Point.

    Note that the class doesn't have to inherit from DCOPObject. If you include a 'k_dcop' slots declaration a 'listener' dcop object instance is created automatically, and these four methods are added to your class:

          interfaces()
          functions()
          connectDCOPSignal()
          disconnectDCOPSignal()
    

    The name of the object is always the ruby classname, and you can only instantiate one instance for each ruby class that has 'k_dcop' declarations. See examples/dcop/dcopslot.rb and dcopsignal.rb for an example of the simplest approach.

    If you wish to use the full functionality of a DCOPObject, you can subclass it and call all the methods, not just the four above. Additionally, you can instantiate more than one instance per class and rename the dcop object with the setObjId() method or by passing the name to the constructor. See the examples/dcop/petshop.rb code for an example of a more complex dcop object.

    Define a dcop slot like this in one ruby program:

          k_dcop 'QPoint getPoint(QString)'
    
          def getPoint(msg)
              puts "message: #{msg}"
              return Qt::Point.new(50, 100)
          end
    

    Call it from another program and print the reply, like this:

          dcopRef = KDE::DCOPRef.new("dcopslot", "MyWidget")
    

    There are three different ways to specify a DCOP call:

          res = dcopRef.call("getPoint(QString)", "Hello from dcopcall")
          res = dcopRef.call("getPoint", "Hello from dcopcall")
          res = dcopRef.getPoint("Hello from dcopcall")
    

    If the dcop slot has a 'void' or 'ASYNC' type, the result will be true if the call succeeds or nil if it fails

    DCOP Attributes

    You can set a dcop attribute like this, instead of calling:

          klipper.setClipboardContents("Hello there klipper")
    

    Assign to the DCOP attribute:

          klipper = DCOPRef.new("klipper", "klipper")
          klipper.clipboardContents = "Hello there klipper"
    

    Amaze your friends! Do the programming equivalent of leaping over tall buildings in one bound! Here with one line of quite clear code, we read a file from disc and assign it the 'clipboardContents' klipper attribute via dcop:

          klipper.clipboardContents = IO.readlines("myfile").to_s
    

    DCOP Predicates

    Instead of:

          result = dcopRef.isFoo()
    

    You can use this more rubyish form:

          if dcopRef.foo?
              puts "foo is true"
          else
              puts "foo? is false"
          end
    

    Similarly you can use foo? as an alias for methods of the form hasFoo(). See examples/dcop/dcoppredicate.rb and dcopslot.rb

    Underscore to CamelCase DCOP method name conversion

    Any underscores in a method name are removed, and the following character is capitalised. For example:

          res = dcopRef.get_point("Hello from dcopsend")
    

    Is a synonym for:

          res = dcopRef.getPoint("Hello from dcopsend")
    

    Send to a DCOPRef

    There are two different ways to specify a DCOP send:

          res = dcopRef.send("mySlot(QString)", "Hello from dcopsend")
          res = dcopRef.send("mySlot", "Hello from dcopsend")
    

    The result will either be true or false (but not nil for fail like DCOPRef.call() method described above).

    When a call of the form 'dcopRef.getPoint(5, "foobar")' is made, the C++ type signature is obtained from the list of those returned by DCOPRef.functions(). However, if a method name is overloaded the ruby argument types are used to derive a type signature, in order to resolve the call like this:

          String => QString
          Float => double
          Integer => int
          TrueClass|FalseClass (ie 'true' or 'false') => bool
          Qt::Widget etc => QWidget
          KDE::URL etc => KURL
          Array => QStringList
    

    Specify the full C++ type signature using the form 'dcopRef.call("getPoint(int,QString)", 5, "foobar")' if these rules fail to pick the right method.

    Defining DCOP Signals

          k_dcop_signals 'void testEmitSignal(QString)'
    		
          def doit()
              emit testEmitSignal("Hello DCOP Slot")
          end
    

    Connect slot 'mySlot' to a DCOP signal like this:

          res = slottest.connectDCOPSignal("dcopsignal", 
                                  "SenderWidget", 
                                  "testEmitSignal(QString)", 
                                  "mySlot(QString)", 
                                  true)
    

    Build dependencies

    • ruby 1.8 or greater
    • automake 1.7 or greater
    • Qt 3.1 or greater
    • KDE 3.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 Tutorial #1

    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.

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