12.2.1.  QPixmap and QThread Animation Example: Movie Player

[ fromfile: threads.xml id: moviethread ]

In this section we write a multithreaded animation viewer application. This application loops through a sequence of eight images on the screen. The user controls the time interval between images to speed up or slow down the process. Figure 12.4 shows the two main classes for this application.

Figure 12.4.  UML diagram for Movie and MovieView

UML diagram for Movie and MovieView

We will start with the top-level code in Example 12.13 and then drill down to the lower layers of code.

Example 12.13. src/threads/animate/moviethreadmain.cpp

[ . . . . ]
int main(int argc, char** argv) {
    QApplication app(argc, argv);
    MovieView view;
    MovieThread movie;  
    app.connect(&movie, SIGNAL(show(const QPixmap*)), 
            &view, SLOT(showPix(const QPixmap*)));
    app.connect(&view, SIGNAL(intervalChanged(int)), 
            &movie, SLOT(setInterval(int)));
    app.connect(&app, SIGNAL(aboutToQuit()), &movie, SLOT(stop()));
    movie.start(); 1
    view.show();
    return app.exec();
}

[ . . . . ]

1

A new thread starts executing at this point, but the method returns immediately. The new thread starts by calling movie.run().

The interface for starting a thread is similar to that of starting a process - in both cases, start() returns immediately. Instead of running another program, the newly created thread runs in the same process, shares the same memory, and starts by calling the run() function.

The MovieThread does not actually display anything - it is a model that stores the data representing the movie. Periodically, it emits a signal containing a pointer to the correct pixmap to display, as we see in Example 12.14.

Example 12.14. src/threads/animate/moviethread.cpp

[ . . . . ]

void MovieThread::run() {
    int current(0), picCount(m_Pics.size());
    while (true) {
        msleep(m_Delay);   
        emit show(&m_Pics[current]);
        current = (current + 1) % picCount; 
    }
}


In Example 12.15 we can see the slot that actually displays the images and the slot that responds to the slider signals. These slots are in the view class, as they should be.

Example 12.15. src/threads/animate/movieview.cpp

[ . . . . ]

void MovieView::showPix(const QPixmap* pic) {
    label->setPixmap(*pic);
}


void MovieView::setDelay(int newValue) {
    QString str;
    str = QString("%1ms delay between frames").arg(newValue);
    slider->setToolTip(str);
    emit intervalChanged(newValue);
}


To summarize: the run() function emits a signal from the (model) MovieThread object which will be received by a slot in the (view) MovieView object, as arranged by the connect() statement in the main() function (shown in Example 12.13). Signals are ideal for transmitting data to objects across threads. In Example 12.16 we see how the view is created, and how the slider controls the speed of the animation.

Example 12.16. src/threads/animate/movieview.cpp

[ . . . . ]


MovieView::MovieView() {
    resize(200, 200);
    
    slider = new QSlider(Qt::Vertical);
    slider->setRange(1,500);
    slider->setTickInterval(10);
    slider->setValue(100);
    slider->setToolTip("How fast is it spinning?");
    connect(slider, SIGNAL(valueChanged(int)), this, SLOT(setDelay(int)));
    QDockWidget* qdw = new QDockWidget("Delay");
    qdw->setWidget(slider);
    addDockWidget(Qt::LeftDockWidgetArea, qdw);
    label = new QLabel("Movie");
    setCentralWidget(label);
    
}

The user controls the time interval between image exposures with a QSlider widget that is placed in a dock widget.

In Example 12.17, to avoid a crash on exit, we introduced a delay that makes sure the thread has enough time to be properly terminated.

Example 12.17. src/threads/animate/moviethread.cpp

[ . . . . ]


void MovieThread::stop() {
    terminate();
    wait(5000);
}


Putting it all together, we have produced a movie of a spinning yin-yang symbol, using resources, signals, slots, and threads, with an interface that gives the user control of the spin speed via a docked slider widget. The following figure is a screenshot of the running program.

To load images from disk, we use the Qt resource feature (discussed in Section 11.4), which permits us to “embed” binary files into the executable.

Example 12.18. src/threads/animate/moviethread.cpp

[ . . . . ]


void MovieThread::loadPics() {
    FileVisitor fv("*.jpg");
    connect (&fv, SIGNAL(foundFile(const QString&)),
             this, SLOT(addFile(const QString&)));
    fv.processEntry(":/images/"); 1
}

void MovieThread::addFile(const QString& filename) {
    m_Pics << QPixmap(filename);
}

1

We are using resources, which link binary files into the executable. They exist in a file system rooted at “:“ See file: animate.qrc for list of embedded resources (.jpg files)