Section 12.5. Other GUI Toolkits


12.4. QtRuby

Qt is a GUI toolkit created and distributed by Trolltech. The main focus of Qt is to be a multiplatform toolkit that provides the same programmatic interface for Windows, Mac, and UNIX operating systems. Developers need only write the code once; it compiles on each of the three platforms without modification.

Qt is distributed via dual licenseeither the GPL or a purchased commercial license for proprietary work. This dual license scenario is also used by other companies such as MySQL. It allows the toolkit to be used by open-source projects that may benefit from many of the offered features. It also allows Trolltech a revenue stream from the sale of commercial licenses for customers who may want to use a less restrictive license than the GPL.

12.4.1. Overview

The QtRuby bindings are the result of the work of many people, most notably Richard Dale. Ashley Winters, Germain Garand, and David Faure were responsible for much of the work involved in writing the backend used to generate the binding code (called SMOKE). Many others have contributed bug reports and fixes.

The QtRuby bindings have not only a large set of GUI-related classes but also a whole suite of application add-ons that are often needed by programmers (such as XML and SQL libraries). The entire Qt toolkit is supported.

For the past few years, the QtRuby bindings were centered around the version 3 major release of the Qt toolkit. In late 2005, version 4 became available. There are QtRuby bindings for both Qt3 and Qt4, but note that they are two different packages. Because Qt3 was not available as an open-source package on the Windows platform, this book only looks at the QtRuby bindings for Qt4. However, the code and example presented here are generally applicable to the Qt3 version as well. All code presented here runs equally well on Windows, Linux, and Mac platforms using the Qt4 QtRuby bindings.

A key aspect of Qt, and thus QtRuby, is the concept of signals and slots. Signals are asynchronous events that occur when something spontaneous happens, such as a mouse button press or a user entering some text into a field. A slot is simply a reacting method that will be called when a certain signal happens. We take advantage of them by using the connect method to associate signals with slots.

To take advantage of signals and slots, as well as many other QtRuby features, all of our classes use the Qt::Object class. Furthermore, any GUI classes we may create will inherit from the base class Qt::Widget, which itself inherits from Qt::Object.

12.4.2. A Simple Windowed Application

A QtRuby program must first do a require of the Qt library. QtRuby provides its functionality through the Qt module (meaning that Qt classes are prefixed with Qt::). Because all Qt classes start with the letter Q, this Q is dropped during the conversion from Qt to QtRuby. So for example, the Qt-based QWidget class becomes Qt::Widget in QtRuby.

require 'Qt' app = Qt::Application.new(ARGV) str = Time.now.strftime("Today is %B %d, %Y") label = Qt::Label.new(str) label.show app.exec


Let's look at the preceding code in detail. The initial call to Qt::Application.new is performed to start up a Qt-based application; it initializes the window system and gets it ready for us to create the widgets we will be using.

Then we create a Qt::Label, which is a simple way of presenting text to the user. In this case, the text is initialized to the string created in the previous line. The next line tells the label to display itself on the screen.

Finally, the application event loop is started with a call to app.exec. This method does not return until the application is told to terminate, generally by the user clicking the close button on the window.

12.4.3. Working with Buttons

Creating a pushbutton with QtRuby is as easy as creating a new instance of Qt::PushButton (see Listing 12.14 and Figure 12.7). Most likely, we will want to perform some event when the button is clicked. This is handled via QtRuby's signal and slots.

Listing 12.14. Buttons in Qt

require 'Qt' class MyWidget < Qt::Widget   slots 'buttonClickedSlot()'   def initialize(parent = nil)     super(parent)     setWindowTitle("QtRuby example");     @lineedit = Qt::LineEdit.new(self)     @button = Qt::PushButton.new("All Caps!",self)     connect(@button, SIGNAL('clicked()'),             self, SLOT('buttonClickedSlot()'))     box = Qt::HBoxLayout.new     box.addWidget(Qt::Label.new("Text:"))     box.addWidget(@lineedit)     box.addWidget(@button)     setLayout(box)   end   def buttonClickedSlot     @lineedit.setText(@lineedit.text.upcase)   end end app = Qt::Application.new(ARGV) widget = MyWidget.new widget.show app.exec

Figure 12.7. Buttons in Qt.


In this example, we create our own widget class named MyWidget; this inherits from the generic Qt::Widget class that we use for all custom widget classes.

Before the initializer, we establish a list of the slots that we will be defining in this class. Slots are ordinary Ruby class methods, but we must specify them by name so that the QtRuby runtime is aware that we want to be able to use them as slots. The call to the class method slots takes a list of strings, as shown here:

slots 'slot1()', 'slot2()'


The initializer for this class takes an argument named parent; almost all widget classes in the Qt world take such an argument. The parent argument simply specifies a widget that will take ownership of the widget being created. Passing nil as the parent means that it is a "top level widget" and that no other widget owns it. The "ownership" concept probably makes more sense in the C++ world; parents take ownership of their child widgets, so that when parents are destroyed or removed, their children are removed as well.

The class creates a Qt::LineEdit to allow a user to enter text and a Qt::PushButton with the text All Caps! on it. Note that we pass self as the parent argument to each of these widgets. This means that when a MyWidget instance is created, it "adopts" these widgets.

Next we use a key part of the Qt toolkitthe capability to connect signals and slots together. The Qt::Pushbutton class defines a clicked signal that is emitted whenever the button is clicked. We can connect that to a slot, which in this case is the ordinary method buttonClickedSlot. The name of the slot we connect to is not important; we sometimes use the suffix Slot for emphasis.

Finally, we create an instance of the Qt::HBoxLayout class. This class provides a nice way to have an automatically resizing layout by simply adding widgets to it. It handles the rest for us.

12.4.4. Working with Text Fields

As shown in Listing 12.14, QtRuby provides the Qt::LineEdit class for simple single-line input. The Qt::TextEdit class is for multiline editing.

In Listing 12.15, we see a multiline edit box. As the contents change, the current length of the text is reflected in a label at the bottom of the window, as shown in Figure 12.8.

Listing 12.15. A Simple Qt Editor

require 'Qt' class MyTextWindow < Qt::Widget   slots 'theTextChanged()'   def initialize(parent = nil)     super(parent)     @textedit = Qt::TextEdit.new(self)     @textedit.setWordWrapMode(Qt::TextOption::WordWrap)     @textedit.setFont( Qt::Font.new("Times", 24) )     @status = Qt::Label.new(self)     box = Qt::VBoxLayout.new     box.addWidget(@textedit)     box.addWidget(@status)     setLayout(box)     @textedit.insertPlainText("This really is an editor")     connect(@textedit, SIGNAL('textChanged()'),             self, SLOT('theTextChanged()'))   end   def theTextChanged     text = "Length: " + @textedit.toPlainText.length.to_s     @status.setText(text)   end end app = Qt::Application.new(ARGV) widget = MyTextWindow.new widget.setWindowTitle("QtRuby Text Editor") widget.show app.exec

Figure 12.8. A simple Qt editor.


We create our own custom widget much like the earlier button example. In this case, we create an instance of Qt::TextEdit and a Qt::Label used for status updates.

The first interesting thing of note is that we set the font of the @textedit to an instance of a 24-point Times font. Each class inheriting from Qt:Widget (including Qt::TextEdit) has a font property that we can both retrieve and set.

Next we create a vertical box layout (Qt::VBoxLayout) that holds the child widgets, inserts some text into the @textedit widget, and then performs the connection of the editor widget's textChanged signal to our custom theTextChanged slot.

Within the slot theTextChanged, we grab the text from the editor and query its length. Then we update the @status label to reflect this length.

Note that all the signal and slot action happens asynchronously. After the application enters into the event loop (app.exec), the GUI event loop takes over. This is why signals and slots are so important. We define the actions that can happen (signals) and the actions we want to take when they do (slots).

12.4.5. Working with Other Widgets

Qt provides many more GUI widgets for general consumption such as radio buttons, check boxes, and other display widgets. Listing 12.16 shows some more of these, and Figure 12.9 provides a screenshot.

Listing 12.16. Other Qt Widgets

require 'Qt' class MyWindow < Qt::Widget   slots 'somethingClicked(QAbstractButton *)'   def initialize(parent = nil)     super(parent)     groupbox = Qt::GroupBox.new("Some Radio Button",self)     radio1 = Qt::RadioButton.new("Radio Button 1", groupbox)     radio2 = Qt::RadioButton.new("Radio Button 2", groupbox)     check1 = Qt::CheckBox.new("Check Box 1", groupbox)     vbox = Qt::VBoxLayout.new     vbox.addWidget(radio1)     vbox.addWidget(radio2)     vbox.addWidget(check1)     groupbox.setLayout(vbox)     bg = Qt::ButtonGroup.new(self)     bg.addButton(radio1)     bg.addButton(radio2)     bg.addButton(check1)     connect(bg, SIGNAL('buttonClicked(QAbstractButton *)'),             self, SLOT('somethingClicked(QAbstractButton *)') )     @label = Qt::Label.new(self)     vbox = Qt::VBoxLayout.new     vbox.addWidget(groupbox)     vbox.addWidget(@label)     setLayout(vbox)   end   def somethingClicked(who)     @label.setText("You clicked on a " + who.className)   end end app = Qt::Application.new(ARGV) widget = MyWindow.new widget.show app.exec

Figure 12.9. Other Qt widgets.


In this new class we first create a Qt::GroupBox, which is a box with a frame and an optional title that can hold other widgets. We then create two Qt::RadioButtons and a Qt::CheckBox, setting the group box as their parent.

Next we create a Qt::VBoxLayout that holds the radio buttons and check box. Then we set that layout on the group box.

The next important thing is to create a Qt::ButtonGroup and add our check box and radio buttons to it. A Qt::ButtonGroup is a logical grouping of buttons, check boxes, and radio buttons. It has no impact on the visual layout of these widgets; instead, it allows us to group them together logically to provide things such as exclusion (unclicking certain widgets when certain others are clicked). In this case, we use the button group as a source of the buttonClicked signal, which is emitted when one of the buttons in that group becomes clicked.

The emission of this signal is a bit different from what we've previously seen, because this signal also emits an argument. In this case, it emits the object that was clicked. Note the C++ style syntax, namely in the use of the QAbstractButton * argument. Remember that Qt is a C++ toolkit, so some use of the C++ notation for certain parameter types is currently unavoidable (though it perhaps may be fixed in future versions).

The final result of the connect statement is that any time a button is clicked, that button is passed into the somethingClicked slot. Finally, we create a Qt::Label and a Qt::VBoxLayout and bring the whole thing together.

In the somethingClicked slot definition, we modify the text of the label every time a button is clicked. In this case, we display the class name of the object that caused the signal to be emitted and the slot to be invoked.

When using built-in widgets is not enough, Qt provides a powerful painting system for creation of your own custom widgets. Listing 12.17 shows a small example to highlight some of those features.

Listing 12.17. A Custom TimerClock Widget

require 'Qt' class TimerClock < Qt::Widget     def initialize(parent = nil)         super(parent)         @timer = Qt::Timer.new(self)         connect(@timer, SIGNAL('timeout()'), self, SLOT('update()'))         @timer.start(25)         setWindowTitle('Stop Watch')         resize(200, 200)     end     def paintEvent(e)         fastHand = Qt::Polygon.new([Qt::Point.new(7, 8),                                     Qt::Point.new(-7, 8),                                     Qt::Point.new(0, -80)])         secondHand = Qt::Polygon.new([Qt::Point.new(7, 8),                                       Qt::Point.new(-7, 8),                                       Qt::Point.new(0, -65)])         secondColor = Qt::Color.new(100, 0, 100)         fastColor = Qt::Color.new(0, 150, 150, 150)         side = [width, height].min         time = Qt::Time.currentTime         painter = Qt::Painter.new(self)         painter.renderHint = Qt::Painter::Antialiasing         painter.translate(width() / 2, height() / 2)         painter.scale(side / 200.0, side / 200.0)         painter.pen = Qt::NoPen         painter.brush = Qt::Brush.new(secondColor)         painter.save         painter.rotate(6.0 * time.second)         painter.drawConvexPolygon(secondHand)         painter.restore         painter.pen = secondColor         (0...12).each do |i|             painter.drawLine(88, 0, 96, 0)             painter.rotate(30.0)         end         painter.pen = Qt::NoPen         painter.brush = Qt::Brush.new(fastColor)         painter.save         painter.rotate(36.0 * (time.msec / 100.0) )         painter.drawConvexPolygon(fastHand)         painter.restore         painter.pen = fastColor         (0...60).each do |j|             if (j % 5) != 0                 painter.drawLine(92, 0, 96, 0)             end             painter.rotate(6.0)         end         painter.end     end end app = Qt::Application.new(ARGV) wid = TimerClock.new wid.show app.exec

In this example we again create a custom widget, this time called TimerClock. In its initializer we create an instance of Qt::Timer, which we can set up to emit a signal periodically. In this case, we connect its timeout signal to the update slot of the TimerClock. The update slot is built-in; it causes the widget to repaint itself.

The timer is started through a call to the start method. Its argument specifies that it is to timeout every 25 milliseconds (and emit a timeout signal). This means that the widget's update slot will also get executed every 25 milliseconds.

Next we create the paintEvent method. This method is an override of the method provided by Qt::Widget. When a widget wants to repaint itself (as this one will every time the Qt::Timer expires), this method gets called. Overriding this method allows us to customize how the widget draws itself on the screen. Within this method, the code is responsible for handling the primitive drawing routines for the widget.

From here, it's all about geometry. We create some Qt::Polygons that represent the hands on the clock we are about to draw. Note that the orientation of the polygons doesn't matter, as we will be able to manipulate them later.

We set up a few properties we will want to use. We define two Qt::Colors for the two hands that will be on the timer. The arguments to the Qt::Color initializer are "RGB" values followed by an optional alpha transparency value.

Because the timer we're drawing is laid out in a square, it is possible that the window could be rectangular making our widget an odd shape. We use the side variable to store whichever is smaller between the width and the height of the widget as it will be drawn on the screen. We also grab the current time using Qt::Time.currentTime.

Next we create a Qt::Painter and use it to begin executing drawing routines. We set up antialiasing during drawing to make edges look smooth. We also move the painter's starting coordinate to the middle of the drawing area by the call to painter.translate(width/2, height/2). The painter is also rescaled to a 200:200 frame of reference. This means that all of our drawing commands can rely on the fact that the drawing will be 200:200 units. If it gets resized bigger or smaller, the scaling automatically adjusts for us.

From here we perform a series of drawing operations. In some places where there are transformations such as rotations, they are enveloped inside a call to painter.save and painter.restore. The save operation stores the current painter properties on a stack so that they can easily be restored.

The code draws the two hands after rotating them to the proper angle to represent the time. Also we tell the painter to draw some tick marks at certain intervals along the outer edge of the clock face.

Finally we tell the painter we've finished (with a call to painter.end). We tie up our final loose ends with the four lines of code that create the Qt::Application and our timer clock widget and then start the event loop. Figure 12.10 shows the final result.

Figure 12.10. The TimerClock widget.


12.4.6. Other Notes

Because Qt is a C++ toolkit, some idioms are used in the toolkit that are necessary due to constraints in the language. Sometimes the translation to Ruby isn't 100% natural because the Ruby way of doing the same thing may be slightly different. So, in some places there are overrides that let you do some things in QtRuby in a Rubyish way.

For example, the camelCase naming of Qt methods can also be written as underscored names. The following two are equivalent:

Qt::Widget::minimumSizeHint Qt::Widget::minimum_size_hint


All Qt setters begin with the word set, such as Qt::Widget::setMinimumSize. This can be overridden in Ruby by dropping the set and using assignment. This means the following three statements are equivalent:

widget.setMinimumSize(50) widget.minimumSize = 50     # same widget.minimum_size = 50    # same


Similarly, some Boolean methods in Qt begin with is or has, such as Qt::Widget::isVisible. Again, QtRuby gives us a more Rubyish way of calling this method:

a.isVisible a.visible?     # same





The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net