Difference between revisions of "Development/Tutorials/Qt4 Ruby Tutorial/Chapter 08"

Jump to: navigation, search
m (Text replace - "<code ruby>" to "<syntaxhighlight lang="ruby">")
Line 28: Line 28:
 
We now add the possibility of setting the range of the '''<tt>LCDRange</tt>'''. Until now, it has been fixed at 0 to 99.
 
We now add the possibility of setting the range of the '''<tt>LCDRange</tt>'''. Until now, it has been fixed at 0 to 99.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
def setRange(minVal, maxVal)
 
def setRange(minVal, maxVal)
 
   if minVal < 0 || maxVal > 99 || minVal > maxVal
 
   if minVal < 0 || maxVal > 99 || minVal > maxVal
Line 43: Line 43:
 
The '''<tt>setRange()</tt>''' slot sets the range of the slider in the '''<tt>LCDRange</tt>'''. Because we have set up the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] to always display two digits, we want to limit the possible range of '''<tt>minVal</tt>''' and '''<tt>maxVal</tt>''' to avoid overflow of the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber]. (We could have allowed values down to -9 but chose not to.) If the arguments are illegal, we use Qt's [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] function to issue a warning to the user and return immediately. [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] is a '''<tt>printf</tt>'''-like function that by default sends its output to '''<tt>$stderr</tt>'''. If you want, you can install your own handler function using [http://doc.qt.nokia.com/latest/qtglobal.html#qInstallMsgHandler QtGlobal::qInstallMsgHandler()].
 
The '''<tt>setRange()</tt>''' slot sets the range of the slider in the '''<tt>LCDRange</tt>'''. Because we have set up the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] to always display two digits, we want to limit the possible range of '''<tt>minVal</tt>''' and '''<tt>maxVal</tt>''' to avoid overflow of the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber]. (We could have allowed values down to -9 but chose not to.) If the arguments are illegal, we use Qt's [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] function to issue a warning to the user and return immediately. [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] is a '''<tt>printf</tt>'''-like function that by default sends its output to '''<tt>$stderr</tt>'''. If you want, you can install your own handler function using [http://doc.qt.nokia.com/latest/qtglobal.html#qInstallMsgHandler QtGlobal::qInstallMsgHandler()].
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
lcd.setSegmentStyle(Qt::LCDNumber::Filled)
 
lcd.setSegmentStyle(Qt::LCDNumber::Filled)
 
</code>
 
</code>
Line 51: Line 51:
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/cannon.rb cannon.rb]'''
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/cannon.rb cannon.rb]'''
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
@currentAngle = 45
 
@currentAngle = 45
 
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
 
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
Line 63: Line 63:
 
The [http://doc.qt.nokia.com/latest/qcolor.html Qt::Color] is specified as a RGB (red-green-blue) triplet, where each value is between 0 (dark) and 255 (bright). We could also have used a predefined color such as [http://doc.qt.nokia.com/latest/qt.html#GlobalColor-enum Qt::yellow] instead of specifying an RGB value.
 
The [http://doc.qt.nokia.com/latest/qcolor.html Qt::Color] is specified as a RGB (red-green-blue) triplet, where each value is between 0 (dark) and 255 (bright). We could also have used a predefined color such as [http://doc.qt.nokia.com/latest/qt.html#GlobalColor-enum Qt::yellow] instead of specifying an RGB value.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
def setAngle(angle)
 
def setAngle(angle)
 
   if angle < 5
 
   if angle < 5
Line 89: Line 89:
 
Finally, we emit the '''<tt>angleChanged()</tt>''' signal to tell the outside world that the angle has changed. The '''<tt>emit</tt>''' keyword is unique to Qt and not regular Ruby syntax.
 
Finally, we emit the '''<tt>angleChanged()</tt>''' signal to tell the outside world that the angle has changed. The '''<tt>emit</tt>''' keyword is unique to Qt and not regular Ruby syntax.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
def paintEvent(event)
 
def paintEvent(event)
 
   painter = Qt::Painter.new(self)
 
   painter = Qt::Painter.new(self)
Line 103: Line 103:
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/t8.rb t8.rb]'''
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/t8.rb t8.rb]'''
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
angle = LCDRange.new()
 
angle = LCDRange.new()
 
angle.setRange(5, 70)
 
angle.setRange(5, 70)
Line 110: Line 110:
 
In the constructor, we create and set up the LCDRange widget. We set the LCDRange to accept angles from 5 to 70 degrees.
 
In the constructor, we create and set up the LCDRange widget. We set the LCDRange to accept angles from 5 to 70 degrees.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
  cannonField = CannonField.new()
 
  cannonField = CannonField.new()
 
</code>
 
</code>
Line 116: Line 116:
 
We create our CannonField widget.
 
We create our CannonField widget.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
connect(angle, SIGNAL('valueChanged(int)'),
 
connect(angle, SIGNAL('valueChanged(int)'),
 
         cannonField, SLOT('setAngle(int)'))
 
         cannonField, SLOT('setAngle(int)'))
Line 129: Line 129:
 
Notice how important it is to emit the '''<tt>angleChanged()</tt>''' signal only when the angle actually changes. If both the '''<tt>LCDRange</tt>''' and the '''<tt>CannonField</tt>''' had omitted this check, the program would have entered an infinite loop upon the first change of one of the values.
 
Notice how important it is to emit the '''<tt>angleChanged()</tt>''' signal only when the angle actually changes. If both the '''<tt>LCDRange</tt>''' and the '''<tt>CannonField</tt>''' had omitted this check, the program would have entered an infinite loop upon the first change of one of the values.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
gridLayout = Qt::GridLayout.new()
 
gridLayout = Qt::GridLayout.new()
 
</code>
 
</code>
Line 141: Line 141:
 
The diagram above shows the layout we're trying to achieve. The left side shows a schematic view of the layout; the right side is an actual screenshot of the program. ''(These two images are copyrighted/owned by Nokia.)''
 
The diagram above shows the layout we're trying to achieve. The left side shows a schematic view of the layout; the right side is an actual screenshot of the program. ''(These two images are copyrighted/owned by Nokia.)''
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
     gridLayout.addWidget(quit, 0, 0)
 
     gridLayout.addWidget(quit, 0, 0)
 
</code>
 
</code>
Line 147: Line 147:
 
We add the <strong>Quit</strong> button in the top-left cell of the grid, i.e., the cell with coordinates (0, 0).
 
We add the <strong>Quit</strong> button in the top-left cell of the grid, i.e., the cell with coordinates (0, 0).
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
     gridLayout.addWidget(angle, 1, 0)
 
     gridLayout.addWidget(angle, 1, 0)
 
</code>
 
</code>
Line 153: Line 153:
 
We put the angle '''<tt>LCDRange</tt>''' cell (1, 0).
 
We put the angle '''<tt>LCDRange</tt>''' cell (1, 0).
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
gridLayout.addWidget(cannonField, 1, 1, 2, 1)
 
gridLayout.addWidget(cannonField, 1, 1, 2, 1)
 
</code>
 
</code>
Line 159: Line 159:
 
We let the '''<tt>CannonField</tt>''' object occupy cells (1, 1) and (2, 1).
 
We let the '''<tt>CannonField</tt>''' object occupy cells (1, 1) and (2, 1).
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
     gridLayout.setColumnStretch(1, 10)
 
     gridLayout.setColumnStretch(1, 10)
 
</code>
 
</code>
Line 167: Line 167:
 
In this particular example, any stretch factor greater than 0 for column 1 would have the same effect. In more complex layouts, you can use the stretch factors to tell that a particular column or row should stretch twice as fast as another by assigning appropriate stretch factors.
 
In this particular example, any stretch factor greater than 0 for column 1 would have the same effect. In more complex layouts, you can use the stretch factors to tell that a particular column or row should stretch twice as fast as another by assigning appropriate stretch factors.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
     angle.setValue(60)
 
     angle.setValue(60)
 
</code>
 
</code>
Line 173: Line 173:
 
We set an initial angle value. Note that this will trigger the connection from '''<tt>LCDRange</tt>''' to '''<tt>CannonField</tt>'''.
 
We set an initial angle value. Note that this will trigger the connection from '''<tt>LCDRange</tt>''' to '''<tt>CannonField</tt>'''.
  
<code ruby>
+
<syntaxhighlight lang="ruby">
 
     angle.setFocus()
 
     angle.setFocus()
 
</code>
 
</code>

Revision as of 21:43, 29 June 2011


Contents

Development/Tutorials/Qt4 Ruby Tutorial/Chapter 08


Preparing for Battle
Tutorial Series   Qt4 Ruby Tutorial
Previous   Tutorial 7 - One Thing Leads to Another
What's Next   Tutorial 9 - With Cannon You Can
Further Reading   n/a

Preparing for Battle

Qt4 Ruby Tutorial Screenshot 8.png

Files:

Overview

In this example, we introduce the first custom widget that can paint itself. We also add a useful keyboard interface (with two lines of code).

Line by Line Walkthrough

lcdrange.rb

This file is very similar to the lcdrange.rb in Chapter 7. We have added one slot: setRange().

We now add the possibility of setting the range of the LCDRange. Until now, it has been fixed at 0 to 99.

def setRange(minVal, maxVal)
  if minVal < 0 || maxVal > 99 || minVal > maxVal
    qWarning("LCDRange::setRange(#{minVal}, #{maxVal})\n" +
               "\tRange must be 0..99\n" +
               "\tand minVal must not be greater than maxVal")
    return
  end
 
  @slider.setRange(minVal, maxVal)
end
</code>
 
The '''<tt>setRange()</tt>''' slot sets the range of the slider in the '''<tt>LCDRange</tt>'''. Because we have set up the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber] to always display two digits, we want to limit the possible range of '''<tt>minVal</tt>''' and '''<tt>maxVal</tt>''' to avoid overflow of the [http://doc.qt.nokia.com/latest/qlcdnumber.html QLCDNumber]. (We could have allowed values down to -9 but chose not to.) If the arguments are illegal, we use Qt's [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] function to issue a warning to the user and return immediately. [http://doc.qt.nokia.com/latest/qtglobal.html#qWarning QtGlobal::qWarning()] is a '''<tt>printf</tt>'''-like function that by default sends its output to '''<tt>$stderr</tt>'''. If you want, you can install your own handler function using [http://doc.qt.nokia.com/latest/qtglobal.html#qInstallMsgHandler QtGlobal::qInstallMsgHandler()].
 
<syntaxhighlight lang="ruby">
lcd.setSegmentStyle(Qt::LCDNumber::Filled)
</code>
 
This makes our lcd numbers look way better. I'm not certain, but I believe what makes it possible to do this is setting a palette (see next section). What I do know is that this line has no effect when I tried it in previous chapters, but works here.
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/cannon.rb cannon.rb]'''
 
<syntaxhighlight lang="ruby">
@currentAngle = 45
setPalette(Qt::Palette.new(Qt::Color.new(250, 250, 200)))
setAutoFillBackground(true)
</code>
 
The constructor initializes the angle value to 45 degrees and sets a custom palette for this widget.
 
This palette uses the indicated color as background and picks other colors suitably. (For this widget only the background and text colors will actually be used.) We then call setAutoFillBackground(true) to tell Qt fill the background automatically.
 
The [http://doc.qt.nokia.com/latest/qcolor.html Qt::Color] is specified as a RGB (red-green-blue) triplet, where each value is between 0 (dark) and 255 (bright). We could also have used a predefined color such as [http://doc.qt.nokia.com/latest/qt.html#GlobalColor-enum Qt::yellow] instead of specifying an RGB value.
 
<syntaxhighlight lang="ruby">
def setAngle(angle)
  if angle < 5
    angle = 5
  elsif angle > 70
    angle = 70
  end
 
  if @currentAngle == angle
    return
  end
 
  @currentAngle = angle
  update()
  emit angleChanged(@currentAngle)
end  def setAngle(degrees)
</code>
 
This function sets the angle value. We have chosen a legal range of 5 to 70 and adjust the given number of degrees accordingly. We have chosen not to issue a warning if the new angle is out of range.
 
If the new angle equals the old one, we return immediately. It is important to only emit the '''<tt>angleChanged()</tt>''' signal when the angle really has changed.
 
Then we set the new angle value and repaint our widget. The [http://doc.qt.nokia.com/latest/qwidget.html#update Qt::Widget::update()] function clears the widget (usually filling it with its background color) and sends a paint event to the widget. This results in a call to the paint event function of the widget.
 
Finally, we emit the '''<tt>angleChanged()</tt>''' signal to tell the outside world that the angle has changed. The '''<tt>emit</tt>''' keyword is unique to Qt and not regular Ruby syntax.
 
<syntaxhighlight lang="ruby">
def paintEvent(event)
  painter = Qt::Painter.new(self)
  painter.drawText(200, 200, tr("Angle = #{@currentAngle}"))
  painter.end()
end
</code>
 
This is our first attempt to write a paint event handler. The event argument contains a description of the paint event. [http://doc.qt.nokia.com/latest/qpaintevent.html Qt::PaintEvent] contains the region in the widget that must be updated. For the time being, we will be lazy and just paint everything.
 
Our code displays the angle value in the widget at a fixed position. We create a [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter] operating on this widget and use it to paint a string. We'll come back to [http://doc.qt.nokia.com/latest/qpainter.html Qt::Painter] later; it can do a great many things.
 
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t8/t8.rb t8.rb]'''
 
<syntaxhighlight lang="ruby">
angle = LCDRange.new()
angle.setRange(5, 70)
</code>
 
In the constructor, we create and set up the LCDRange widget. We set the LCDRange to accept angles from 5 to 70 degrees.
 
<syntaxhighlight lang="ruby">
 cannonField = CannonField.new()
</code>
 
We create our CannonField widget.
 
<syntaxhighlight lang="ruby">
connect(angle, SIGNAL('valueChanged(int)'),
        cannonField, SLOT('setAngle(int)'))
connect(cannonField, SIGNAL('angleChanged(int)'),
        angle, SLOT('setValue(int)'))
</code>
 
Here we connect the '''<tt>valueChanged()</tt>''' signal of the '''<tt>LCDRange</tt>''' to the '''<tt>setValue()</tt>''' slot of the '''<tt>CannonField</tt>'''. This will update '''<tt>CannonField</tt>''''s angle value whenever the user operates the '''<tt>LCDRange</tt>'''. We also make the reverse connection so that changing the angle in the '''<tt>CannonField</tt>''' will update the '''<tt>LCDRange</tt>''' value. In our example we never change the angle of the '''<tt>CannonField</tt>''' directly; but by doing the last connect() we ensure that no future changes will disrupt the synchronization between those two values.
 
This illustrates the power of component programming and proper encapsulation.
 
Notice how important it is to emit the '''<tt>angleChanged()</tt>''' signal only when the angle actually changes. If both the '''<tt>LCDRange</tt>''' and the '''<tt>CannonField</tt>''' had omitted this check, the program would have entered an infinite loop upon the first change of one of the values.
 
<syntaxhighlight lang="ruby">
gridLayout = Qt::GridLayout.new()
</code>
 
So far, we have used [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::VBoxLayout] for geometry management. Now, however, we want to have a little more control over the layout, and we switch to the more powerful [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] class. [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] isn't a widget; it is a different class that can manage the children of any widget.
 
We don't need to specify any dimensions to the [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::GridLayout] constructor. The [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] will determine the number of rows and columns based on the grid cells we populate.
 
[[Image:Qt4_Ruby_Tutorial_Screenshot_8-layout.png]][[Image:Qt4_Ruby_Tutorial_Screenshot_8-reallayout.png]]
 
The diagram above shows the layout we're trying to achieve. The left side shows a schematic view of the layout; the right side is an actual screenshot of the program. ''(These two images are copyrighted/owned by Nokia.)''
 
<syntaxhighlight lang="ruby">
    gridLayout.addWidget(quit, 0, 0)
</code>
 
We add the <strong>Quit</strong> button in the top-left cell of the grid, i.e., the cell with coordinates (0, 0).
 
<syntaxhighlight lang="ruby">
    gridLayout.addWidget(angle, 1, 0)
</code>
 
We put the angle '''<tt>LCDRange</tt>''' cell (1, 0).
 
<syntaxhighlight lang="ruby">
gridLayout.addWidget(cannonField, 1, 1, 2, 1)
</code>
 
We let the '''<tt>CannonField</tt>''' object occupy cells (1, 1) and (2, 1).
 
<syntaxhighlight lang="ruby">
    gridLayout.setColumnStretch(1, 10)
</code>
 
We tell [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] that the right column (column 1) is stretchable, with a stretch factor of 10. Because the left column isn't (its stretch factor is 0, the default value), [http://doc.qt.nokia.com/latest/qgridlayout.html Qt::GridLayout] will try to let the left-hand widgets' sizes be unchanged and will resize just the '''<tt>CannonField</tt>''' when the '''<tt>MyWidget</tt>''' is resized.
 
In this particular example, any stretch factor greater than 0 for column 1 would have the same effect. In more complex layouts, you can use the stretch factors to tell that a particular column or row should stretch twice as fast as another by assigning appropriate stretch factors.
 
<syntaxhighlight lang="ruby">
    angle.setValue(60)
</code>
 
We set an initial angle value. Note that this will trigger the connection from '''<tt>LCDRange</tt>''' to '''<tt>CannonField</tt>'''.
 
<syntaxhighlight lang="ruby">
    angle.setFocus()
</code>
 
Our last action is to set '''<tt>angle</tt>''' to have keyboard focus so that keyboard input will go to the '''<tt>LCDRange</tt>''' widget by default.
 
=== Running the Application ===
When the slider is operated, the '''<tt>CannonField</tt>''' displays the new angle value. Upon resizing, '''<tt>CannonField</tt>''' is given as much space as possible.
 
=== Exercises ===
Try to resize the window. What happens if you make it really narrow or really squat?
 
If you give the left-hand column a non-zero stretch factor, what happens when you resize the window?
 
Try to change "Quit" to "&Quit". How does the button's look change? ( Whether it does change or not depends on the platform.) What happens if you press <strong>Alt+Q</strong> while the program is running?
 
Center the text in the '''<tt>CannonField</tt>'''.
 
[[Category:Ruby]]

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal