Development/Tutorials/Qt4 Ruby Tutorial/Chapter 12: Difference between revisions

From KDE TechBase
No edit summary
No edit summary
 
(5 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Template:I18n/Language Navigation Bar|Development/Tutorials/Qt4 Ruby Tutorial/Chapter 12}}
<languages />


{{TutorialBrowser|


series=[[Development/Tutorials/Qt4_Ruby_Tutorial|Qt4 Ruby Tutorial]]|


name=Hanging in the Air the Way Bricks Don't|
{{<translate><!--T:1-->
 
TutorialBrowser</translate>|
pre=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_11|Tutorial 11 - Giving It a Shot]]|
series=[[Special:myLanguage/Development/Tutorials/Qt4_Ruby_Tutorial|<translate><!--T:2-->
 
Qt4 Ruby Tutorial</translate>]]|
next=[[Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_13|Tutorial 13 - Game Over]]
name=<translate><!--T:3-->
Hanging in the Air the Way Bricks Don't</translate>|
pre=[[Special:myLanguage/Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_11|<translate><!--T:4-->
Tutorial 11 - Giving It a Shot</translate>]]|
next=[[Special:myLanguage/Development/Tutorials/Qt4_Ruby_Tutorial/Chapter_13|<translate><!--T:5-->
Tutorial 13 - Game Over</translate>]]
}}
}}
== Hanging in the Air the Way Bricks Don't ==
<translate>
== Hanging in the Air the Way Bricks Don't == <!--T:6-->
</translate>
<translate>
<!--T:7-->
[[Image:Qt4_Ruby_Tutorial_Screenshot_12.png|center]]
[[Image:Qt4_Ruby_Tutorial_Screenshot_12.png|center]]
</translate>
<translate>
<!--T:8-->
Files:
Files:
</translate>
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/lcdrange.rb lcdrange.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/lcdrange.rb lcdrange.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/t12.rb t12.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/t12.rb t12.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/cannon.rb cannon.rb]
* [http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/cannon.rb cannon.rb]


=== Overview ===
<translate>
=== Overview === <!--T:9-->
 
<!--T:10-->
In this example, we extend our '''<tt>LCDRange</tt>''' class to include a text label. We also provide something to shoot at.   
In this example, we extend our '''<tt>LCDRange</tt>''' class to include a text label. We also provide something to shoot at.   


=== Line by Line Walkthrough ===
=== Line by Line Walkthrough === <!--T:11-->
</translate>
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/lcdrange.rb lcdrange.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/lcdrange.rb lcdrange.rb]'''


<code ruby>
<syntaxhighlight lang="ruby">
def initialize(s, parent = nil)
def initialize(s, parent = nil)
   super(parent)
   super(parent)
Line 30: Line 45:
   setText(s)
   setText(s)
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:12-->
This constructor first calls '''<tt>init()</tt>''' and then sets the label text. '''<tt>init()</tt>''' is a separate function performing initialization mosty because of function overloading matters in the original C++ version.
This constructor first calls '''<tt>init()</tt>''' and then sets the label text. '''<tt>init()</tt>''' is a separate function performing initialization mosty because of function overloading matters in the original C++ version.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
def init()
def init()
   lcd = Qt::LCDNumber.new(2)
   lcd = Qt::LCDNumber.new(2)
Line 59: Line 77:
   setFocusProxy(@slider)
   setFocusProxy(@slider)
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:13-->
The setup of '''<tt>lcd</tt>''' and '''<tt>slider</tt>''' is the same as in the previous chapter. Next we create a [http://doc.qt.nokia.com/latest/qlabel.html Qt::Label] and tell it to align the contents centered horizontally and to the top vertically. The [http://doc.qt.nokia.com/latest/qobject.html#connect Qt::Object::connect()] calls have also been taken from the previous chapter.
The setup of '''<tt>lcd</tt>''' and '''<tt>slider</tt>''' is the same as in the previous chapter. Next we create a [http://doc.qt.nokia.com/latest/qlabel.html Qt::Label] and tell it to align the contents centered horizontally and to the top vertically. The [http://doc.qt.nokia.com/latest/qobject.html#connect Qt::Object::connect()] calls have also been taken from the previous chapter.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
def setText(s)
def setText(s)
   @label.setText(s)
   @label.setText(s)
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:14-->
This function sets the label text.
This function sets the label text.
</translate>


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/cannon.rb cannon.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/cannon.rb cannon.rb]'''


<translate>
<!--T:15-->
The '''<tt>CannonField</tt>''' now has two new signals: '''<tt>hit()</tt>''' and '''<tt>missed()</tt>'''. In addition, it contains a target.
The '''<tt>CannonField</tt>''' now has two new signals: '''<tt>hit()</tt>''' and '''<tt>missed()</tt>'''. In addition, it contains a target.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
   signals 'hit()', 'missed()' #...
   signals 'hit()', 'missed()' #...
</code>
</syntaxhighlight>


<translate>
<!--T:16-->
The '''<tt>hit()</tt>''' signal is emitted when a shot hits the target. The '''<tt>missed()</tt>''' signal is emitted when the shot moves beyond the right or bottom edge of the widget (i.e., it is certain that it has not and will not hit the target).
The '''<tt>hit()</tt>''' signal is emitted when a shot hits the target. The '''<tt>missed()</tt>''' signal is emitted when the shot moves beyond the right or bottom edge of the widget (i.e., it is certain that it has not and will not hit the target).
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
     newTarget()
     newTarget()
</code>
</syntaxhighlight>


<translate>
<!--T:17-->
This line has been added to the constructor. It creates a "random" position for the target. In fact, the '''<tt>newTarget()</tt>''' function will try to paint the target. Because we are in a constructor, the '''<tt>CannonField</tt>''' widget is invisible. Qt guarantees that no harm is done when calling [http://doc.qt.nokia.com/latest/qwidget.html#update Qt::Widget::update()] on a hidden widget.
This line has been added to the constructor. It creates a "random" position for the target. In fact, the '''<tt>newTarget()</tt>''' function will try to paint the target. Because we are in a constructor, the '''<tt>CannonField</tt>''' widget is invisible. Qt guarantees that no harm is done when calling [http://doc.qt.nokia.com/latest/qwidget.html#update Qt::Widget::update()] on a hidden widget.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
@@first_time = true
@@first_time = true


Line 100: Line 133:
   update()
   update()
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:18-->
This function creates a target center point at a new random position.
This function creates a target center point at a new random position.


<!--T:19-->
We create the [http://doc.qt.nokia.com/latest/qtime.html Qt::Time] object '''<tt>midnight</tt>''', which represents the time 00:00:00. Next we fetch the number of seconds from midnight until now and use it as a random seed. See the documentation for [http://doc.qt.nokia.com/latest/qdate.html Qt::Date], [http://doc.qt.nokia.com/latest/qtime.html Qt::Time], and [http://doc.qt.nokia.com/latest/qdatetime.html Qt::DateTime] for more information.
We create the [http://doc.qt.nokia.com/latest/qtime.html Qt::Time] object '''<tt>midnight</tt>''', which represents the time 00:00:00. Next we fetch the number of seconds from midnight until now and use it as a random seed. See the documentation for [http://doc.qt.nokia.com/latest/qdate.html Qt::Date], [http://doc.qt.nokia.com/latest/qtime.html Qt::Time], and [http://doc.qt.nokia.com/latest/qdatetime.html Qt::DateTime] for more information.


<!--T:20-->
Finally we calculate the target's center point. We keep it within the rectangle (x = 200, y = 35, width = 190, height = 255), i.e., the possible x and y values are 200 to 389 and 35 to 289, respectively) in a coordinate system where we put y position 0 at the bottom edge of the widget and let y values increase upwards x is as normal, with 0 at the left edge and with x values increasing to the right.
Finally we calculate the target's center point. We keep it within the rectangle (x = 200, y = 35, width = 190, height = 255), i.e., the possible x and y values are 200 to 389 and 35 to 289, respectively) in a coordinate system where we put y position 0 at the bottom edge of the widget and let y values increase upwards x is as normal, with 0 at the left edge and with x values increasing to the right.


<!--T:21-->
By experimentation we have found this to always be in reach of the shot.
By experimentation we have found this to always be in reach of the shot.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
def moveShot()
def moveShot()
   region = Qt::Region.new(shotRect())
   region = Qt::Region.new(shotRect())
Line 116: Line 155:


   shotR = shotRect()
   shotR = shotRect()
</code>
</syntaxhighlight>


<translate>
<!--T:22-->
This part of the timer event has not changed from the previous chapter.
This part of the timer event has not changed from the previous chapter.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
if shotR.intersects(targetRect())  
if shotR.intersects(targetRect())  
   @autoShootTimer.stop()
   @autoShootTimer.stop()
   emit hit()
   emit hit()
</code>
</syntaxhighlight>


<translate>
<!--T:23-->
This '''<tt>if</tt>''' statement checks whether the shot rectangle intersects the target rectangle. If it does, the shot has hit the target (ouch!). We stop the shoot timer and emit the '''<tt>hit()</tt>''' signal to tell the outside world that a target was destroyed, and return. Note that we could have created a new target on the spot, but because the '''<tt>CannonField</tt>''' is a component we leave such decisions to the user of the component.
This '''<tt>if</tt>''' statement checks whether the shot rectangle intersects the target rectangle. If it does, the shot has hit the target (ouch!). We stop the shoot timer and emit the '''<tt>hit()</tt>''' signal to tell the outside world that a target was destroyed, and return. Note that we could have created a new target on the spot, but because the '''<tt>CannonField</tt>''' is a component we leave such decisions to the user of the component.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
elsif shotR.x() > width() || shotR.y() > height()
elsif shotR.x() > width() || shotR.y() > height()
   @autoShootTimer.stop()
   @autoShootTimer.stop()
   emit missed()
   emit missed()
</code>
</syntaxhighlight>


<translate>
<!--T:24-->
This is the same as in the previous chapter, except that it now emits the '''<tt>missed()</tt>''' signal to tell the outside world about the failure.
This is the same as in the previous chapter, except that it now emits the '''<tt>missed()</tt>''' signal to tell the outside world about the failure.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
   else
   else
     region = region.unite(Qt::Region.new(shotR))
     region = region.unite(Qt::Region.new(shotR))
Line 143: Line 191:
   update(region)
   update(region)
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:25-->
And the rest of the function is as before.
And the rest of the function is as before.


<!--T:26-->
'''<tt>CannonField::paintEvent()</tt>''' is as before, except that this has been added:
'''<tt>CannonField::paintEvent()</tt>''' is as before, except that this has been added:
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
     paintTarget(painter)
     paintTarget(painter)
</code>
</syntaxhighlight>
 
<translate>
<!--T:27-->
This line makes sure that the target is also painted when necessary.
This line makes sure that the target is also painted when necessary.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
def paintTarget(painter)
def paintTarget(painter)
   painter.setBrush(Qt::Brush.new(Qt::red))
   painter.setBrush(Qt::Brush.new(Qt::red))
Line 160: Line 216:
   painter.drawRect(targetRect())
   painter.drawRect(targetRect())
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:28-->
This function paints the target; a rectangle filled with red and with a black outline.
This function paints the target; a rectangle filled with red and with a black outline.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
def targetRect()
def targetRect()
   result = Qt::Rect.new(0, 0, 20, 10)
   result = Qt::Rect.new(0, 0, 20, 10)
Line 170: Line 229:
   return result
   return result
end
end
</code>
</syntaxhighlight>


<translate>
<!--T:29-->
This private function returns the enclosing rectangle of the target. Remember from '''<tt>newTarget()</tt>''' that the '''<tt>target</tt>''' point uses y coordinate 0 at the bottom of the widget. We calculate the point in widget coordinates before we call [http://doc.qt.nokia.com/latest/qrect.html#moveCenter Qt::Rect::moveCenter()].
This private function returns the enclosing rectangle of the target. Remember from '''<tt>newTarget()</tt>''' that the '''<tt>target</tt>''' point uses y coordinate 0 at the bottom of the widget. We calculate the point in widget coordinates before we call [http://doc.qt.nokia.com/latest/qrect.html#moveCenter Qt::Rect::moveCenter()].


<!--T:30-->
The reason we have chosen this coordinate mapping is to fix the distance between the target and the bottom of the widget. Remember that the widget can be resized by the user or the program at any time.
The reason we have chosen this coordinate mapping is to fix the distance between the target and the bottom of the widget. Remember that the widget can be resized by the user or the program at any time.
</translate>


'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/t12.rb t12.rb]'''
'''[http://www.darshancomputing.com/qt4-qtruby-tutorial/tutorial/t12/t12.rb t12.rb]'''


<translate>
<!--T:31-->
There are no new members in the '''<tt>MyWidget</tt>''' class, but we have slightly changed the constructor to set the new '''<tt>LCDRange</tt>''' text labels.
There are no new members in the '''<tt>MyWidget</tt>''' class, but we have slightly changed the constructor to set the new '''<tt>LCDRange</tt>''' text labels.
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
     angle = LCDRange.new(tr('ANGLE'))
     angle = LCDRange.new(tr('ANGLE'))
</code>
</syntaxhighlight>


<translate>
<!--T:32-->
We set the angle text label to "ANGLE".
We set the angle text label to "ANGLE".
</translate>


<code ruby>
<syntaxhighlight lang="ruby">
     force  = LCDRange.new(tr('FORCE'))
     force  = LCDRange.new(tr('FORCE'))
</code>
</syntaxhighlight>


<translate>
<!--T:33-->
We set the force text label to "FORCE".
We set the force text label to "FORCE".
</translate>


=== Running the Application ===
<translate>
=== Running the Application === <!--T:34-->


<!--T:35-->
The '''<tt>LCDRange</tt>''' widgets look a bit strange: When resizing '''<tt>MyWidget</tt>''', the built-in layout management in [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] gives the labels too much space and the rest not enough; making the space between the two '''<tt>LCDRange</tt>''' widgets change size. We'll fix that in the next chapter
The '''<tt>LCDRange</tt>''' widgets look a bit strange: When resizing '''<tt>MyWidget</tt>''', the built-in layout management in [http://doc.qt.nokia.com/latest/qvboxlayout.html Qt::VBoxLayout] gives the labels too much space and the rest not enough; making the space between the two '''<tt>LCDRange</tt>''' widgets change size. We'll fix that in the next chapter


=== Exercises ===
=== Exercises === <!--T:36-->


<!--T:37-->
Make a cheat button that, when pressed, makes the '''<tt>CannonField</tt>''' display the shot trajectory for five seconds.
Make a cheat button that, when pressed, makes the '''<tt>CannonField</tt>''' display the shot trajectory for five seconds.


<!--T:38-->
If you did the "round shot" exercise from the previous chapter, try changing the '''<tt>shotRect()</tt>''' to a '''<tt>shotRegion()</tt>''' that returns a [http://doc.qt.nokia.com/latest/qregion.html Qt::Region] so you can have really accurate collision detection.
If you did the "round shot" exercise from the previous chapter, try changing the '''<tt>shotRect()</tt>''' to a '''<tt>shotRegion()</tt>''' that returns a [http://doc.qt.nokia.com/latest/qregion.html Qt::Region] so you can have really accurate collision detection.


<!--T:39-->
Make a moving target.
Make a moving target.


<!--T:40-->
Make sure that the target is always created entirely on-screen.
Make sure that the target is always created entirely on-screen.


<!--T:41-->
Make sure that the widget cannot be resized so that the target isn't visible. [Hint: [http://doc.qt.nokia.com/latest/qwidget.html#minimumSize-prop Qt::Widget::setMinimumSize()] is your friend.]
Make sure that the widget cannot be resized so that the target isn't visible. [Hint: [http://doc.qt.nokia.com/latest/qwidget.html#minimumSize-prop Qt::Widget::setMinimumSize()] is your friend.]


<!--T:42-->
Not easy; make it possible to have several shots in the air at the same time. [Hint: Make a '''<tt>Shot</tt>''' class.]
Not easy; make it possible to have several shots in the air at the same time. [Hint: Make a '''<tt>Shot</tt>''' class.]
<!--T:43-->
[[Category:Ruby]]
</translate>

Latest revision as of 14:23, 18 July 2012

Other languages:


Hanging in the Air the Way Bricks Don't
Tutorial Series   Qt4 Ruby Tutorial
Previous   Tutorial 11 - Giving It a Shot
What's Next   Tutorial 13 - Game Over
Further Reading   n/a

Hanging in the Air the Way Bricks Don't

Files:

Overview

In this example, we extend our LCDRange class to include a text label. We also provide something to shoot at.

Line by Line Walkthrough

lcdrange.rb

def initialize(s, parent = nil)
  super(parent)
  init()
  setText(s)
end

This constructor first calls init() and then sets the label text. init() is a separate function performing initialization mosty because of function overloading matters in the original C++ version.

def init()
  lcd = Qt::LCDNumber.new(2)
  lcd.setSegmentStyle(Qt::LCDNumber::Filled)

  @slider = Qt::Slider.new(Qt::Horizontal)
  @slider.setRange(0, 99)
  @slider.setValue(0)

  @label = Qt::Label.new()
  @label.setAlignment(Qt::AlignHCenter.to_i | Qt::AlignTop.to_i)

  connect(@slider, SIGNAL('valueChanged(int)'),
  lcd, SLOT('display(int)'))
  connect(@slider, SIGNAL('valueChanged(int)'),
  self, SIGNAL('valueChanged(int)'))

  layout = Qt::VBoxLayout.new()
  layout.addWidget(lcd)
  layout.addWidget(@slider)
  layout.addWidget(@label)
  setLayout(layout)

  setFocusProxy(@slider)
end

The setup of lcd and slider is the same as in the previous chapter. Next we create a Qt::Label and tell it to align the contents centered horizontally and to the top vertically. The Qt::Object::connect() calls have also been taken from the previous chapter.

def setText(s)
  @label.setText(s)
end

This function sets the label text.

cannon.rb

The CannonField now has two new signals: hit() and missed(). In addition, it contains a target.

  signals 'hit()', 'missed()' #...

The hit() signal is emitted when a shot hits the target. The missed() signal is emitted when the shot moves beyond the right or bottom edge of the widget (i.e., it is certain that it has not and will not hit the target).

    newTarget()

This line has been added to the constructor. It creates a "random" position for the target. In fact, the newTarget() function will try to paint the target. Because we are in a constructor, the CannonField widget is invisible. Qt guarantees that no harm is done when calling Qt::Widget::update() on a hidden widget.

@@first_time = true

def newTarget()
  if @@first_time
    @@first_time = false
    midnight = Qt::Time.new(0, 0, 0)
    srand(midnight.secsTo(Qt::Time.currentTime()))
  end

  @target = Qt::Point.new(200 + rand(190), 10 + rand(255))
  update()
end

This function creates a target center point at a new random position.

We create the Qt::Time object midnight, which represents the time 00:00:00. Next we fetch the number of seconds from midnight until now and use it as a random seed. See the documentation for Qt::Date, Qt::Time, and Qt::DateTime for more information.

Finally we calculate the target's center point. We keep it within the rectangle (x = 200, y = 35, width = 190, height = 255), i.e., the possible x and y values are 200 to 389 and 35 to 289, respectively) in a coordinate system where we put y position 0 at the bottom edge of the widget and let y values increase upwards x is as normal, with 0 at the left edge and with x values increasing to the right.

By experimentation we have found this to always be in reach of the shot.

def moveShot()
  region = Qt::Region.new(shotRect())
  @timerCount += 1

  shotR = shotRect()

This part of the timer event has not changed from the previous chapter.

if shotR.intersects(targetRect()) 
  @autoShootTimer.stop()
  emit hit()

This if statement checks whether the shot rectangle intersects the target rectangle. If it does, the shot has hit the target (ouch!). We stop the shoot timer and emit the hit() signal to tell the outside world that a target was destroyed, and return. Note that we could have created a new target on the spot, but because the CannonField is a component we leave such decisions to the user of the component.

elsif shotR.x() > width() || shotR.y() > height()
  @autoShootTimer.stop()
  emit missed()

This is the same as in the previous chapter, except that it now emits the missed() signal to tell the outside world about the failure.

  else
    region = region.unite(Qt::Region.new(shotR))
  end
    
  update(region)
end

And the rest of the function is as before.

CannonField::paintEvent() is as before, except that this has been added:

    paintTarget(painter)

This line makes sure that the target is also painted when necessary.

def paintTarget(painter)
  painter.setBrush(Qt::Brush.new(Qt::red))
  painter.setPen(Qt::Pen.new(Qt::Color.new(Qt::black)))
  painter.drawRect(targetRect())
end

This function paints the target; a rectangle filled with red and with a black outline.

def targetRect()
  result = Qt::Rect.new(0, 0, 20, 10)
  result.moveCenter(Qt::Point.new(@target.x(), height() - 1 - @target.y()))
  return result
end

This private function returns the enclosing rectangle of the target. Remember from newTarget() that the target point uses y coordinate 0 at the bottom of the widget. We calculate the point in widget coordinates before we call Qt::Rect::moveCenter().

The reason we have chosen this coordinate mapping is to fix the distance between the target and the bottom of the widget. Remember that the widget can be resized by the user or the program at any time.

t12.rb

There are no new members in the MyWidget class, but we have slightly changed the constructor to set the new LCDRange text labels.

    angle = LCDRange.new(tr('ANGLE'))

We set the angle text label to "ANGLE".

    force  = LCDRange.new(tr('FORCE'))

We set the force text label to "FORCE".

Running the Application

The LCDRange widgets look a bit strange: When resizing MyWidget, the built-in layout management in Qt::VBoxLayout gives the labels too much space and the rest not enough; making the space between the two LCDRange widgets change size. We'll fix that in the next chapter

Exercises

Make a cheat button that, when pressed, makes the CannonField display the shot trajectory for five seconds.

If you did the "round shot" exercise from the previous chapter, try changing the shotRect() to a shotRegion() that returns a Qt::Region so you can have really accurate collision detection.

Make a moving target.

Make sure that the target is always created entirely on-screen.

Make sure that the widget cannot be resized so that the target isn't visible. [Hint: Qt::Widget::setMinimumSize() is your friend.]

Not easy; make it possible to have several shots in the air at the same time. [Hint: Make a Shot class.]