Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ /*!
++ \class QMovie
++
++ \inmodule QtGui
++
++ \brief The QMovie class is a convenience class for playing movies
++ with QImageReader.
++
++ This class is used to show simple animations without sound.
++
++ First, create a QMovie object by passing either the name of a file or a
++ pointer to a QIODevice containing an animated image format to QMovie's
++ constructor. You can call isValid() to check if the image data is valid,
++ before starting the movie. To start the movie, call start(). QMovie will
++ enter \l Running state, and emit started() and stateChanged(). To get the
++ current state of the movie, call state().
++
++ To display the movie in your application, you can pass your QMovie object
++ to QLabel::setMovie(). Example:
++
++ \snippet code/src_gui_image_qmovie.cpp 0
++
++ Whenever a new frame is available in the movie, QMovie will emit
++ updated(). If the size of the frame changes, resized() is emitted. You can
++ call currentImage() or currentPixmap() to get a copy of the current
++ frame. When the movie is done, QMovie emits finished(). If any error
++ occurs during playback (i.e, the image file is corrupt), QMovie will emit
++ error().
++
++ You can control the speed of the movie playback by calling setSpeed(),
++ which takes the percentage of the original speed as an argument. Pause the
++ movie by calling setPaused(true). QMovie will then enter \l Paused state
++ and emit stateChanged(). If you call setPaused(false), QMovie will reenter
++ \l Running state and start the movie again. To stop the movie, call
++ stop().
++
++ Certain animation formats allow you to set the background color. You can
++ call setBackgroundColor() to set the color, or backgroundColor() to
++ retrieve the current background color.
++
++ currentFrameNumber() returns the sequence number of the current frame. The
++ first frame in the animation has the sequence number 0. frameCount()
++ returns the total number of frames in the animation, if the image format
++ supports this. You can call loopCount() to get the number of times the
++ movie should loop before finishing. nextFrameDelay() returns the number of
++ milliseconds the current frame should be displayed.
++
++ QMovie can be instructed to cache frames of an animation by calling
++ setCacheMode().
++
++ Call supportedFormats() for a list of formats that QMovie supports.
++
++ \sa QLabel, QImageReader
++ */
++
++ /*! \enum QMovie::MovieState
++
++ This enum describes the different states of QMovie.
++
++ \value NotRunning The movie is not running. This is QMovie's initial
++ state, and the state it enters after stop() has been called or the movie
++ is finished.
++
++ \value Paused The movie is paused, and QMovie stops emitting updated() or
++ resized(). This state is entered after calling pause() or
++ setPaused(true). The current frame number it kept, and the movie will
++ continue with the next frame when unpause() or setPaused(false) is called.
++
++ \value Running The movie is running.
++ */
++
++ /*! \enum QMovie::CacheMode
++
++ This enum describes the different cache modes of QMovie.
++
++ \value CacheNone No frames are cached (the default).
++
++ \value CacheAll All frames are cached.
++ */
++
++ /*! \fn void QMovie::started()
++
++ This signal is emitted after QMovie::start() has been called, and QMovie
++ has entered QMovie::Running state.
++ */
++
++ /*! \fn void QMovie::resized(const QSize &size)
++
++ This signal is emitted when the current frame has been resized to \a
++ size. This effect is sometimes used in animations as an alternative to
++ replacing the frame. You can call currentImage() or currentPixmap() to get a
++ copy of the updated frame.
++ */
++
++ /*! \fn void QMovie::updated(const QRect &rect)
++
++ This signal is emitted when the rect \a rect in the current frame has been
++ updated. You can call currentImage() or currentPixmap() to get a copy of the
++ updated frame.
++ */
++
++ /*! \fn void QMovie::frameChanged(int frameNumber)
++
++ This signal is emitted when the frame number has changed to
++ \a frameNumber. You can call currentImage() or currentPixmap() to get a
++ copy of the frame.
++ */
++
++ /*!
++ \fn void QMovie::stateChanged(QMovie::MovieState state)
++
++ This signal is emitted every time the state of the movie changes. The new
++ state is specified by \a state.
++
++ \sa QMovie::state()
++ */
++
++ /*! \fn void QMovie::error(QImageReader::ImageReaderError error)
++
++ This signal is emitted by QMovie when the error \a error occurred during
++ playback. QMovie will stop the movie, and enter QMovie::NotRunning state.
++
++ \sa lastError(), lastErrorString()
++ */
++
++ /*! \fn void QMovie::finished()
++
++ This signal is emitted when the movie has finished.
++
++ \sa QMovie::stop()
++ */
++
++ #include "qmovie.h"
++
++ #include "qglobal.h"
++ #include "qelapsedtimer.h"
++ #include "qimage.h"
++ #include "qimagereader.h"
++ #include "qpixmap.h"
++ #include "qrect.h"
++ #include "qelapsedtimer.h"
++ #include "qtimer.h"
++ #include "qlist.h"
++ #include "qbuffer.h"
++ #include "qdir.h"
++ #include "qloggingcategory.h"
++ #include "private/qimage_p.h"
++ #include "private/qobject_p.h"
++ #include "private/qproperty_p.h"
++
++ #include <map>
++ #include <memory>
++
++ #define QMOVIE_INVALID_DELAY -1
++
++ QT_BEGIN_NAMESPACE
++
++ class QFrameInfo
++ {
++ public:
++ QPixmap pixmap;
++ int delay;
++ bool endMark;
++ inline QFrameInfo(bool endMark)
++ : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(endMark)
++ { }
++
++ inline QFrameInfo()
++ : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(false)
++ { }
++
++ inline QFrameInfo(QPixmap &&pixmap, int delay)
++ : pixmap(std::move(pixmap)), delay(delay), endMark(false)
++ { }
++
++ inline bool isValid()
++ {
++ return endMark || !(pixmap.isNull() && (delay == QMOVIE_INVALID_DELAY));
++ }
++
++ inline bool isEndMarker()
++ { return endMark; }
++
++ static inline QFrameInfo endMarker()
++ { return QFrameInfo(true); }
++ };
++ Q_DECLARE_TYPEINFO(QFrameInfo, Q_RELOCATABLE_TYPE);
++
++ class QMoviePrivate : public QObjectPrivate
++ {
++ Q_DECLARE_PUBLIC(QMovie)
++
++ public:
++ QMoviePrivate();
++
++ void init(QMovie *qq, std::unique_ptr<QImageReader> r);
++
++ bool isDone();
++ bool next();
++ int speedAdjustedDelay(int delay) const;
++ bool isValid() const;
++ bool jumpToFrame(int frameNumber);
++ int frameCount() const;
++ bool jumpToNextFrame();
++ QFrameInfo infoForFrame(int frameNumber);
++ void reset();
++
++ inline void enterState(QMovie::MovieState newState) {
++ movieState = newState;
++ emit q_func()->stateChanged(newState);
++ }
++
++ // private slots
++ void _q_loadNextFrame();
++ void _q_loadNextFrame(bool starting);
++
++ std::unique_ptr<QImageReader> reader = nullptr;
++
++ void setSpeed(int percentSpeed) { q_func()->setSpeed(percentSpeed); }
++ Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QMoviePrivate, int, speed, &QMoviePrivate::setSpeed, 100)
++
++ QMovie::MovieState movieState = QMovie::NotRunning;
++ QRect frameRect;
++ QPixmap currentPixmap;
++ int currentFrameNumber = -1;
++ int nextFrameNumber = 0;
++ int greatestFrameNumber = -1;
++ int nextDelay = 0;
++ int playCounter = -1;
++ qint64 initialDevicePos = 0;
++ Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QMoviePrivate, QMovie::CacheMode, cacheMode,
++ QMovie::CacheNone)
++ bool haveReadAll = false;
++ bool isFirstIteration = true;
++ std::map<int, QFrameInfo> frameMap;
++ QString absoluteFilePath;
++
++ QTimer *nextImageTimer = nullptr;
++ };
++
++ /*! \internal
++ */
++ QMoviePrivate::QMoviePrivate()
++ {
++ }
++
++ void QMoviePrivate::init(QMovie *qq, std::unique_ptr<QImageReader> r)
++ {
++ q_ptr = qq;
++ reader = std::move(r);
++ nextImageTimer = new QTimer(qq);
++ nextImageTimer->setSingleShot(true);
++ QObject::connect(nextImageTimer, SIGNAL(timeout()),
++ qq, SLOT(_q_loadNextFrame()));
++ }
++
++ /*! \internal
++ */
++ void QMoviePrivate::reset()
++ {
++ nextImageTimer->stop();
++ if (reader->device())
++ initialDevicePos = reader->device()->pos();
++ currentFrameNumber = -1;
++ nextFrameNumber = 0;
++ greatestFrameNumber = -1;
++ nextDelay = 0;
++ playCounter = -1;
++ haveReadAll = false;
++ isFirstIteration = true;
++ frameMap.clear();
++ }
++
++ /*! \internal
++ */
++ bool QMoviePrivate::isDone()
++ {
++ return (playCounter == 0);
++ }
++
++ /*!
++ \internal
++
++ Given the original \a delay, this function returns the
++ actual number of milliseconds to delay according to
++ the current speed. E.g. if the speed is 200%, the
++ result will be half of the original delay.
++ */
++ int QMoviePrivate::speedAdjustedDelay(int delay) const
++ {
++ return int( (qint64(delay) * qint64(100) ) / qint64(speed) );
++ }
++
++ /*!
++ \internal
++
++ Returns the QFrameInfo for the given \a frameNumber.
++
++ If the frame number is invalid, an invalid QFrameInfo is
++ returned.
++
++ If the end of the animation has been reached, a
++ special end marker QFrameInfo is returned.
++
++ */
++ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
++ {
++ Q_Q(QMovie);
++
++ if (frameNumber < 0)
++ return QFrameInfo(); // Invalid
++
++ if (haveReadAll && (frameNumber > greatestFrameNumber)) {
++ if (frameNumber == greatestFrameNumber+1)
++ return QFrameInfo::endMarker();
++ return QFrameInfo(); // Invalid
++ }
++
++ // For an animated image format, the tradition is that QMovie calls read()
++ // until canRead() == false, because the number of frames may not be known
++ // in advance; but if we're abusing a multi-frame format as an animation,
++ // canRead() may remain true, and we need to stop after reading the maximum
++ // number of frames that the image provides.
++ const bool supportsAnimation = reader->supportsOption(QImageIOHandler::Animation);
++ const int stopAtFrame = supportsAnimation ? -1 : frameCount();
++
++ // For an animated image format, QImageIOHandler::nextImageDelay() should
++ // provide the time to wait until showing the next frame; but multi-frame
++ // formats are not expected to provide this value, so use 1000 ms by default.
++ const auto nextFrameDelay = [&]() { return supportsAnimation ? reader->nextImageDelay() : 1000; };
++
++ if (cacheMode == QMovie::CacheNone) {
++ if (frameNumber != currentFrameNumber+1) {
++ // Non-sequential frame access
++ if (!reader->jumpToImage(frameNumber)) {
++ if (frameNumber == 0) {
++ // Special case: Attempt to "rewind" so we can loop
++ // ### This could be implemented as QImageReader::rewind()
++ if (reader->device()->isSequential())
++ return QFrameInfo(); // Invalid
++ QString fileName = reader->fileName();
++ QByteArray format = reader->format();
++ QIODevice *device = reader->device();
++ QColor bgColor = reader->backgroundColor();
++ QSize scaledSize = reader->scaledSize();
++ if (fileName.isEmpty())
++ reader = std::make_unique<QImageReader>(device, format);
++ else
++ reader = std::make_unique<QImageReader>(absoluteFilePath, format);
++ if (!reader->canRead()) // Provoke a device->open() call
++ emit q->error(reader->error());
++ reader->device()->seek(initialDevicePos);
++ reader->setBackgroundColor(bgColor);
++ reader->setScaledSize(scaledSize);
++ } else {
++ return QFrameInfo(); // Invalid
++ }
++ }
++ }
++ qCDebug(lcImageIo, "CacheNone: read frame %d of %d", frameNumber, stopAtFrame);
++ if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
++ // reader says we can read. Attempt to actually read image
++ // But if it's a non-animated multi-frame format and we know the frame count, stop there.
++ if (stopAtFrame > 0)
++ reader->jumpToImage(frameNumber);
++ QImage anImage = reader->read();
++ if (anImage.isNull()) {
++ // Reading image failed.
++ return QFrameInfo(); // Invalid
++ }
++ if (frameNumber > greatestFrameNumber)
++ greatestFrameNumber = frameNumber;
++ return QFrameInfo(QPixmap::fromImage(std::move(anImage)), nextFrameDelay());
++ } else if (frameNumber != 0) {
++ // We've read all frames now. Return an end marker
++ haveReadAll = true;
++ return QFrameInfo::endMarker();
++ } else {
++ // No readable frames
++ haveReadAll = true;
++ return QFrameInfo();
++ }
++ }
++
++ // CacheMode == CacheAll
++ if (frameNumber > greatestFrameNumber) {
++ // Frame hasn't been read from file yet. Try to do it
++ for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) {
++ qCDebug(lcImageIo, "CacheAll: read frame %d of %d", frameNumber, stopAtFrame);
++ if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
++ // reader says we can read. Attempt to actually read image
++ // But if it's a non-animated multi-frame format and we know the frame count, stop there.
++ if (stopAtFrame > 0)
++ reader->jumpToImage(frameNumber);
++ QImage anImage = reader->read();
++ if (anImage.isNull()) {
++ // Reading image failed.
++ return QFrameInfo(); // Invalid
++ }
++ greatestFrameNumber = i;
++ QFrameInfo info(QPixmap::fromImage(std::move(anImage)), nextFrameDelay());
++ // Cache it!
++ auto &e = frameMap[i] = std::move(info);
++ if (i == frameNumber) {
++ return e;
++ }
++ } else {
++ // We've read all frames now. Return an end marker
++ haveReadAll = true;
++ return frameNumber == greatestFrameNumber + 1 ? QFrameInfo::endMarker() : QFrameInfo();
++ }
++ }
++ }
++ // Return info for requested (cached) frame
++ const auto it = frameMap.find(frameNumber);
++ return it == frameMap.cend() ? QFrameInfo() : it->second;
++ }
++
++ /*!
++ \internal
++
++ Attempts to advance the animation to the next frame.
++ If successful, currentFrameNumber, currentPixmap and
++ nextDelay are updated accordingly, and true is returned.
++ Otherwise, false is returned.
++ When false is returned, isDone() can be called to
++ determine whether the animation ended gracefully or
++ an error occurred when reading the frame.
++ */
++ bool QMoviePrivate::next()
++ {
++ QElapsedTimer time;
++ time.start();
++ QFrameInfo info = infoForFrame(nextFrameNumber);
++ if (!info.isValid())
++ return false;
++ if (info.isEndMarker()) {
++ // We reached the end of the animation.
++ if (isFirstIteration) {
++ if (nextFrameNumber == 0) {
++ // No frames could be read at all (error).
++ return false;
++ }
++ // End of first iteration. Initialize play counter
++ playCounter = reader->loopCount();
++ isFirstIteration = false;
++ }
++ // Loop as appropriate
++ if (playCounter != 0) {
++ if (playCounter != -1) // Infinite?
++ playCounter--; // Nope
++ nextFrameNumber = 0;
++ return next();
++ }
++ // Loop no more. Done
++ return false;
++ }
++ // Image and delay OK, update internal state
++ currentFrameNumber = nextFrameNumber++;
++ currentPixmap = info.pixmap;
++
++ if (!speed)
++ return true;
++
++ nextDelay = speedAdjustedDelay(info.delay);
++ // Adjust delay according to the time it took to read the frame
++ int processingTime = time.elapsed();
++ if (processingTime > nextDelay)
++ nextDelay = 0;
++ else
++ nextDelay = nextDelay - processingTime;
++ return true;
++ }
++
++ /*! \internal
++ */
++ void QMoviePrivate::_q_loadNextFrame()
++ {
++ _q_loadNextFrame(false);
++ }
++
++ void QMoviePrivate::_q_loadNextFrame(bool starting)
++ {
++ Q_Q(QMovie);
++ if (next()) {
++ if (starting && movieState == QMovie::NotRunning) {
++ enterState(QMovie::Running);
++ emit q->started();
++ }
++
++ if (frameRect.size() != currentPixmap.rect().size()) {
++ frameRect = currentPixmap.rect();
++ emit q->resized(frameRect.size());
++ }
++
++ emit q->updated(frameRect);
++ emit q->frameChanged(currentFrameNumber);
++
++ if (speed && movieState == QMovie::Running)
++ nextImageTimer->start(nextDelay);
++ } else {
++ // Could not read another frame
++ if (!isDone()) {
++ emit q->error(reader->error());
++ }
++
++ // A paused movie keeps its state so it can resume later.
++ if (movieState == QMovie::Paused)
++ return;
++
++ nextFrameNumber = 0;
++ isFirstIteration = true;
++ playCounter = -1;
++
++ // Only report a graceful finish when the movie was actually running;
++ // a failed start() played nothing and must not emit finished().
++ if (movieState == QMovie::Running) {
++ enterState(QMovie::NotRunning);
++ emit q->finished();
++ }
++ }
++ }
++
++ /*!
++ \internal
++ */
++ bool QMoviePrivate::isValid() const
++ {
++ Q_Q(const QMovie);
++
++ if (greatestFrameNumber >= 0)
++ return true; // have we seen valid data
++ bool canRead = reader->canRead();
++ if (!canRead) {
++ // let the consumer know it's broken
++ //
++ // ### the const_cast here is ugly, but 'const' of this method is
++ // technically wrong right now, since it may cause the underlying device
++ // to open.
++ emit const_cast<QMovie*>(q)->error(reader->error());
++ }
++ return canRead;
++ }
++
++ /*!
++ \internal
++ */
++ bool QMoviePrivate::jumpToFrame(int frameNumber)
++ {
++ if (frameNumber < 0)
++ return false;
++ if (currentFrameNumber == frameNumber)
++ return true;
++ nextFrameNumber = frameNumber;
++ if (movieState == QMovie::Running)
++ nextImageTimer->stop();
++ _q_loadNextFrame();
++ return (nextFrameNumber == currentFrameNumber+1);
++ }
++
++ /*!
++ \internal
++ */
++ int QMoviePrivate::frameCount() const
++ {
++ int result;
++ if ((result = reader->imageCount()) != 0)
++ return result;
++ if (haveReadAll)
++ return greatestFrameNumber+1;
++ return 0; // Don't know
++ }
++
++ /*!
++ \internal
++ */
++ bool QMoviePrivate::jumpToNextFrame()
++ {
++ return jumpToFrame(currentFrameNumber+1);
++ }
++
++ /*!
++ Constructs a QMovie object, passing the \a parent object to QObject's
++ constructor.
++
++ \sa setFileName(), setDevice(), setFormat()
++ */
++ QMovie::QMovie(QObject *parent)
++ : QObject(*new QMoviePrivate, parent)
++ {
++ Q_D(QMovie);
++ d->init(this, std::make_unique<QImageReader>());
++ }
++
++ /*!
++ Constructs a QMovie object. QMovie will use read image data from \a
++ device, which it assumes is open and readable. If \a format is not empty,
++ QMovie will use the image format \a format for decoding the image
++ data. Otherwise, QMovie will attempt to guess the format.
++
++ The \a parent object is passed to QObject's constructor.
++ */
++ QMovie::QMovie(QIODevice *device, const QByteArray &format, QObject *parent)
++ : QObject(*new QMoviePrivate, parent)
++ {
++ Q_D(QMovie);
++ d->init(this, std::make_unique<QImageReader>(device, format));
++ d->initialDevicePos = device->pos();
++ }
++
++ /*!
++ Constructs a QMovie object. QMovie will use read image data from \a
++ fileName. If \a format is not empty, QMovie will use the image format \a
++ format for decoding the image data. Otherwise, QMovie will attempt to
++ guess the format.
++
++ The \a parent object is passed to QObject's constructor.
++ */
++ QMovie::QMovie(const QString &fileName, const QByteArray &format, QObject *parent)
++ : QObject(*new QMoviePrivate, parent)
++ {
++ Q_D(QMovie);
++ d->init(this, std::make_unique<QImageReader>(fileName, format));
++ d->absoluteFilePath = QDir(fileName).absolutePath();
++ if (d->reader->device())
++ d->initialDevicePos = d->reader->device()->pos();
++ }
++
++ /*!
++ Destructs the QMovie object.
++ */
++ QMovie::~QMovie()
++ {
++ Q_D(QMovie);
++ d->reader.reset();
++ }
++
++ /*!
++ Sets the current device to \a device. QMovie will read image data from
++ this device when the movie is running.
++
++ \sa device(), setFormat()
++ */
++ void QMovie::setDevice(QIODevice *device)
++ {
++ Q_D(QMovie);
++ d->reader->setDevice(device);
++ d->reset();
++ }
++
++ /*!
++ Returns the device QMovie reads image data from. If no device has
++ currently been assigned, \nullptr is returned.
++
++ \sa setDevice(), fileName()
++ */
++ QIODevice *QMovie::device() const
++ {
++ Q_D(const QMovie);
++ return d->reader->device();
++ }
++
++ /*!
++ Sets the name of the file that QMovie reads image data from, to \a
++ fileName.
++
++ \sa fileName(), setDevice(), setFormat()
++ */
++ void QMovie::setFileName(const QString &fileName)
++ {
++ Q_D(QMovie);
++ d->absoluteFilePath = QDir(fileName).absolutePath();
++ d->reader->setFileName(fileName);
++ d->reset();
++ }
++
++ /*!
++ Returns the name of the file that QMovie reads image data from. If no file
++ name has been assigned, or if the assigned device is not a file, an empty
++ QString is returned.
++
++ \sa setFileName(), device()
++ */
++ QString QMovie::fileName() const
++ {
++ Q_D(const QMovie);
++ return d->reader->fileName();
++ }
++
++ /*!
++ Sets the format that QMovie will use when decoding image data, to \a
++ format. By default, QMovie will attempt to guess the format of the image
++ data.
++
++ You can call supportedFormats() for the full list of formats
++ QMovie supports.
++
++ \sa QImageReader::supportedImageFormats()
++ */
++ void QMovie::setFormat(const QByteArray &format)
++ {
++ Q_D(QMovie);
++ d->reader->setFormat(format);
++ }
++
++ /*!
++ Returns the format that QMovie uses when decoding image data. If no format
++ has been assigned, an empty QByteArray() is returned.
++
++ \sa setFormat()
++ */
++ QByteArray QMovie::format() const
++ {
++ Q_D(const QMovie);
++ return d->reader->format();
++ }
++
++ /*!
++ For image formats that support it, this function sets the background color
++ to \a color.
++
++ \sa backgroundColor()
++ */
++ void QMovie::setBackgroundColor(const QColor &color)
++ {
++ Q_D(QMovie);
++ d->reader->setBackgroundColor(color);
++ }
++
++ /*!
++ Returns the background color of the movie. If no background color has been
++ assigned, an invalid QColor is returned.
++
++ \sa setBackgroundColor()
++ */
++ QColor QMovie::backgroundColor() const
++ {
++ Q_D(const QMovie);
++ return d->reader->backgroundColor();
++ }
++
++ /*!
++ Returns the current state of QMovie.
++
++ \sa MovieState, stateChanged()
++ */
++ QMovie::MovieState QMovie::state() const
++ {
++ Q_D(const QMovie);
++ return d->movieState;
++ }
++
++ /*!
++ Returns the rect of the last frame. If no frame has yet been updated, an
++ invalid QRect is returned.
++
++ \sa currentImage(), currentPixmap()
++ */
++ QRect QMovie::frameRect() const
++ {
++ Q_D(const QMovie);
++ return d->frameRect;
++ }
++
++ /*!
++ Returns the current frame as a QPixmap.
++
++ \sa currentImage(), updated()
++ */
++ QPixmap QMovie::currentPixmap() const
++ {
++ Q_D(const QMovie);
++ return d->currentPixmap;
++ }
++
++ /*!
++ Returns the current frame as a QImage.
++
++ \sa currentPixmap(), updated()
++ */
++ QImage QMovie::currentImage() const
++ {
++ Q_D(const QMovie);
++ return d->currentPixmap.toImage();
++ }
++
++ /*!
++ Returns \c true if the movie is valid (e.g., the image data is readable and
++ the image format is supported); otherwise returns \c false.
++
++ For information about why the movie is not valid, see lastError().
++ */
++ bool QMovie::isValid() const
++ {
++ Q_D(const QMovie);
++ return d->isValid();
++ }
++
++ /*!
++ Returns the most recent error that occurred while attempting to read image data.
++
++ \sa lastErrorString()
++ */
++ QImageReader::ImageReaderError QMovie::lastError() const
++ {
++ Q_D(const QMovie);
++ return d->reader->error();
++ }
++
++ /*!
++ Returns a human-readable representation of the most recent error that occurred
++ while attempting to read image data.
++
++ \sa lastError()
++ */
++ QString QMovie::lastErrorString() const
++ {
++ Q_D(const QMovie);
++ return d->reader->errorString();
++ }
++
++ /*!
++ Returns the number of frames in the movie.
++
++ Certain animation formats do not support this feature, in which
++ case 0 is returned.
++ */
++ int QMovie::frameCount() const
++ {
++ Q_D(const QMovie);
++ return d->frameCount();
++ }
++
++ /*!
++ Returns the number of milliseconds QMovie will wait before updating the
++ next frame in the animation.
++ */
++ int QMovie::nextFrameDelay() const
++ {
++ Q_D(const QMovie);
++ return d->nextDelay;
++ }
++
++ /*!
++ Returns the sequence number of the current frame. The number of the first
++ frame in the movie is 0.
++ */
++ int QMovie::currentFrameNumber() const
++ {
++ Q_D(const QMovie);
++ return d->currentFrameNumber;
++ }
++
++ /*!
++ Jumps to the next frame. Returns \c true on success; otherwise returns \c false.
++ */
++ bool QMovie::jumpToNextFrame()
++ {
++ Q_D(QMovie);
++ return d->jumpToNextFrame();
++ }
++
++ /*!
++ Jumps to frame number \a frameNumber. Returns \c true on success; otherwise
++ returns \c false.
++ */
++ bool QMovie::jumpToFrame(int frameNumber)
++ {
++ Q_D(QMovie);
++ return d->jumpToFrame(frameNumber);
++ }
++
++ /*!
++ Returns the number of times the movie will loop before it finishes.
++ If the movie will only play once (no looping), loopCount returns 0.
++ If the movie loops forever, loopCount returns -1.
++
++ Note that, if the image data comes from a sequential device (e.g. a
++ socket), QMovie can only loop the movie if the cacheMode is set to
++ QMovie::CacheAll.
++ */
++ int QMovie::loopCount() const
++ {
++ Q_D(const QMovie);
++ return d->reader->loopCount();
++ }
++
++ /*!
++ If \a paused is true, QMovie will enter \l Paused state and emit
++ stateChanged(Paused); otherwise it will enter \l Running state and emit
++ stateChanged(Running).
++
++ \sa state()
++ */
++ void QMovie::setPaused(bool paused)
++ {
++ Q_D(QMovie);
++ if (paused) {
++ if (d->movieState == NotRunning)
++ return;
++ d->enterState(Paused);
++ d->nextImageTimer->stop();
++ } else {
++ if (d->movieState == Running)
++ return;
++ d->enterState(Running);
++ d->nextImageTimer->start(nextFrameDelay());
++ }
++ }
++
++ /*!
++ \property QMovie::speed
++ \brief the movie's speed
++
++ The speed is measured in percentage of the original movie speed.
++ The default speed is 100%.
++ Example:
++
++ \snippet code/src_gui_image_qmovie.cpp 1
++ */
++ void QMovie::setSpeed(int percentSpeed)
++ {
++ Q_D(QMovie);
++ if (!d->speed && d->movieState == Running)
++ d->nextImageTimer->start(nextFrameDelay());
++ if (percentSpeed != d->speed) {
++ d->speed = percentSpeed;
++ d->speed.notify();
++ } else {
++ d->speed.removeBindingUnlessInWrapper();
++ }
++ }
++
++ int QMovie::speed() const
++ {
++ Q_D(const QMovie);
++ return d->speed;
++ }
++
++ QBindable<int> QMovie::bindableSpeed()
++ {
++ Q_D(QMovie);
++ return &d->speed;
++ }
++
++ /*!
++ Starts the movie. QMovie will enter \l Running state, and start emitting
++ updated() and resized() as the movie progresses.
++
++ If QMovie is in the \l Paused state, this function is equivalent
++ to calling setPaused(false). If QMovie is already in the \l
++ Running state, this function does nothing.
++
++ \sa stop(), setPaused()
++ */
++ void QMovie::start()
++ {
++ Q_D(QMovie);
++ if (d->movieState == NotRunning) {
++ d->_q_loadNextFrame(true);
++ } else if (d->movieState == Paused) {
++ setPaused(false);
++ }
++ }
++
++ /*!
++ Stops the movie. QMovie enters \l NotRunning state, and stops emitting
++ updated() and resized(). If start() is called again, the movie will
++ restart from the beginning.
++
++ If QMovie is already in the \l NotRunning state, this function
++ does nothing.
++
++ \sa start(), setPaused()
++ */
++ void QMovie::stop()
++ {
++ Q_D(QMovie);
++ if (d->movieState == NotRunning)
++ return;
++ d->enterState(NotRunning);
++ d->nextImageTimer->stop();
++ d->nextFrameNumber = 0;
++ }
++
++ /*!
++ Returns the scaled size of frames.
++
++ \sa QImageReader::scaledSize()
++ */
++ QSize QMovie::scaledSize()
++ {
++ Q_D(QMovie);
++ return d->reader->scaledSize();
++ }
++
++ /*!
++ Sets the scaled frame size to \a size.
++
++ \sa QImageReader::setScaledSize()
++ */
++ void QMovie::setScaledSize(const QSize &size)
++ {
++ Q_D(QMovie);
++ d->reader->setScaledSize(size);
++ }
++
++ /*!
++ Returns the list of image formats supported by QMovie.
++
++ \sa QImageReader::supportedImageFormats()
++ */
++ QList<QByteArray> QMovie::supportedFormats()
++ {
++ QList<QByteArray> list = QImageReader::supportedImageFormats();
++
++ QBuffer buffer;
++ buffer.open(QIODevice::ReadOnly);
++
++ const auto doesntSupportAnimation =
++ [&buffer](const QByteArray &format) {
++ return !QImageReader(&buffer, format).supportsOption(QImageIOHandler::Animation);
++ };
++
++ list.removeIf(doesntSupportAnimation);
++ return list;
++ }
++
++ /*!
++ \property QMovie::cacheMode
++ \brief the movie's cache mode
++
++ Caching frames can be useful when the underlying animation format handler
++ that QMovie relies on to decode the animation data does not support
++ jumping to particular frames in the animation, or even "rewinding" the
++ animation to the beginning (for looping). Furthermore, if the image data
++ comes from a sequential device, it is not possible for the underlying
++ animation handler to seek back to frames whose data has already been read
++ (making looping altogether impossible).
++
++ To aid in such situations, a QMovie object can be instructed to cache the
++ frames, at the added memory cost of keeping the frames in memory for the
++ lifetime of the object.
++
++ By default, this property is set to \l CacheNone.
++
++ \sa QMovie::CacheMode
++ */
++
++ QMovie::CacheMode QMovie::cacheMode() const
++ {
++ Q_D(const QMovie);
++ return d->cacheMode;
++ }
++
++ void QMovie::setCacheMode(CacheMode cacheMode)
++ {
++ Q_D(QMovie);
++ d->cacheMode = cacheMode;
++ }
++
++ QBindable<QMovie::CacheMode> QMovie::bindableCacheMode()
++ {
++ Q_D(QMovie);
++ return &d->cacheMode;
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qmovie.cpp"