Qt Tutorial - Chapter 11

# Chapter 11: Giving It a Shot

In this example, we introduce timer events to implement shooting.

- lcdrange.h contains the LCDRange class definition
- lcdrange.cpp contains the LCDRange implementation
- cannon.h contains the CannonField class definition
- cannon.cpp contains the CannonField implementation
- main.cpp contains MyWidget and main.
- Makefile contains some rules for generating the meta object information necessary for signal/slot creation.

## Line by Line Walk-Through

### cannon.h

The CannonField now has a shooting capabilities.

            bool  isShooting() const { return shooting; }

Returns TRUE if a shot is underway.

            void  shoot();

Calling this slot will make the cannon shoot if a shot is not in the air.

        protected:
            void  timerEvent( QTimerEvent * );

The timer event is the third type of widget event we encounter. You can make Qt call this event handler at regular intervals.

        private:
            void  stopShooting();

This private function stops a shot in mid-air.

            void  paintShot( QPainter * );

This private function paints the shot.

            QRect shotRect() const;

This private function returns the shot's enclosing rectangle if one is in the air, otherwise the returned rectangle is undefined.

            bool  shooting;

This private variable is TRUE when there's a shot in the air.

            int   timerCount;
            float shoot_ang;
            float shoot_f;
        };

These private variables contain information that describes the shot. The `timerCount` keeps track of the time passed since the shot was fired. The `shoot_ang` is the cannon angle and `shoot_f` is the cannon force when the shot was fired.

### cannon.cpp

        #include <math.h>

We include the math library because we need the sin() and cos() functions.

        CannonField::CannonField( QWidget *parent, const char *name )
                : QWidget( parent, name )
        {
            ang           = 45;
            f             = 0;
            shooting      = FALSE;
            timerCount    = 0;
            shoot_ang     = 0;
            shoot_f       = 0;
        }

We initialize our new private variables.

        void CannonField::shoot()
        {
            if ( shooting )
                return;
            timerCount = 0;
            shoot_ang  = ang;
            shoot_f    = f;
            shooting   = TRUE;
            startTimer( 50 );
        }

This function shoots a shot unless a shot is in the air. The `timerCount` is reset to zero. The `shoot_ang` and `shoot_f` are set to the current cannon angle and force. `shooting` is set to TRUE to indicate that a shot is in the air. Finally we start timer events.

Calling startTimer() will make Qt send us timer events at regular intervals. In this case, we want timer events every 50 milliseconds.

        void CannonField::timerEvent( QTimerEvent * )
        {
            erase( shotRect() );
            timerCount++;

            QRect shotR = shotRect();

            if ( shotR.x() > width() || shotR.y() > height() ) {
                stopShooting();
                return;
            }
            repaint( shotR, FALSE );
        }

The timerEvent() function is an event handler that receives timer events from Qt. In our case, it is called every 50 milliseconds when a shot is in the air.

First we erase the old shot using the QWidget::erase() function. Note that this is not strictly necessary the first time the timerEvent() is called after a shot has been fired (but it does no harm). The `shotRect()` function (explained below) returns the enclosing rectangle of the shot at the current position.

Then we increment the `timerCount,` which has the effect of moving the shot one step along its trajectory.

Next, we fetch the new shot rectangle.

If the shot has moved beyond the right or bottom edge of the widget, we stop shooting and return.

If not, we paint the new shot by repainting the portion of the widget that contains the shot. The FALSE argument indicates that the specified rectangle should not be erased before a paint event is sent to the widget.

        void CannonField::paintEvent( QPaintEvent *e )
        {
            QRect updateR = e->rect();
            QPainter p;
            p.begin( this );

            if ( updateR.intersects( cannonRect() ) )
                paintCannon( &p );
            if ( shooting &&  updateR.intersects( shotRect() ) )
                paintShot( &p );
            p.end();
        }

This paint event function is basically the same as the one in the previous chapter. The only difference is that we also paint the shot if necessary.

        void CannonField::stopShooting()
        {
            shooting = FALSE;
            killTimers();
        }

This private function stops a shot in mid-air. First it resets the `shooting` variable, then it kills all timer events for this widget. It is also possible to kill a single timer.

        void CannonField::paintShot( QPainter *p )
        {
            p->setBrush( black );
            p->setPen( NoPen );
            p->drawRect( shotRect() );
        }

This private function paints the shot by drawing a black filled rectangle.

        QRect CannonField::shotRect() const
        {
            const double gravity = 4;

            double time      = timerCount / 4.0;
            double velocity  = shoot_f;
            double radians   = shoot_ang*3.14159265/180;

            double velx      = velocity*cos( radians );
            double vely      = velocity*sin( radians );
            double x0        = ( barrel_rect.right()  + 5 )*cos(radians);
            double y0        = ( barrel_rect.right()  + 5 )*sin(radians);
            double x         = x0 + velx*time;
            double y         = y0 + vely*time - 0.5*gravity*time*time;

            QRect r = QRect( 0, 0, 6, 6 );
            r.moveCenter( QPoint( qRound(x), height() - 1 - qRound(y) ) );
            return r;
        }

This private function calculates the center point of the shot and returns the enclosing rectangle of the shot. It uses the initial cannon force and angle in addition to `timerCount,` which increases as time passes.

The formula used is the classical Newtonian formula for frictionless movement in a gravity field. For simplicity, we've chosen to disregard any Einsteinian effects.

We calculate the center point in a coordinate system where y coordinates increase upwards. After we have calculated the center point, we construct a QRect with size 6x6 and move its center point to the point calculated above. In the same operation, we convert the point into the widget's coodinate system (see The Coordinate System).

The qRound() function is an inline function defined in qglobal.h (included by all other Qt header files). qRound() rounds a double to the closest integer.

### main.cpp

        class MyWidget : public QWidget
        {
        public:
            MyWidget( QWidget *parent=0, const char *name=0 );
        protected:
            void resizeEvent( QResizeEvent * );
        private:
            QPushButton *quit;
            QPushButton *shoot;
            LCDRange    *angle;
            LCDRange    *force;
            CannonField *cannonField;
        };

The only addition is the shoot button.

            shoot = new QPushButton( "Shoot", this, "shoot" );
            shoot->setGeometry( 90, 10, 75, 30 );
            shoot->setFont( QFont( "Times", 18, QFont::Bold ) );

In the constructor we create and set up the shoot button exactly like we did with the quit button. Note that the first argument to the constructor is the button text and the third is the widget's name.

            connect( shoot, SIGNAL(clicked()), cannonField, SLOT(shoot()) );

Connects the clicked() signal of the shoot button to the shoot() slot of the CannonField.

## Behavior

The cannon can shoot, but there's nothing to shoot at.

## Excercises

Make the shot a filled circle (hint: QPainter::drawEllipse() ).

Change the color of the cannon when a shot is in the air.

You may now go on to chapter twelve.

[Previous tutorial] [Next tutorial] [Main tutorial page]