File indexing completed on 2024-04-28 03:58:59

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
0004     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0005     SPDX-FileCopyrightText: 2008 Pino Toscano <pino@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include <kanimatedbutton.h>
0011 
0012 #include <QImageReader>
0013 #include <QMovie>
0014 #include <QPainter>
0015 #include <QPixmap>
0016 #include <QTimer>
0017 
0018 class KAnimatedButtonPrivate
0019 {
0020 public:
0021     KAnimatedButtonPrivate(KAnimatedButton *qq)
0022         : q(qq)
0023     {
0024     }
0025 
0026     void updateIcons();
0027     void updateCurrentIcon();
0028     void movieFrameChanged(int number);
0029     void movieFinished();
0030     void timerUpdate();
0031 
0032     KAnimatedButton *const q;
0033     QMovie *movie = nullptr;
0034 
0035     int frames;
0036     int current_frame;
0037     QPixmap pixmap;
0038     QTimer timer;
0039     QString icon_path;
0040     QList<QPixmap *> framesCache; // We keep copies of each frame so that
0041     // the icon code can properly cache them in QPixmapCache,
0042     // and not fill it up with dead copies
0043 };
0044 
0045 KAnimatedButton::KAnimatedButton(QWidget *parent)
0046     : QToolButton(parent)
0047     , d(new KAnimatedButtonPrivate(this))
0048 {
0049     connect(&d->timer, &QTimer::timeout, this, [this]() {
0050         d->timerUpdate();
0051     });
0052 }
0053 
0054 KAnimatedButton::~KAnimatedButton()
0055 {
0056     d->timer.stop();
0057     qDeleteAll(d->framesCache);
0058     delete d->movie;
0059 }
0060 
0061 void KAnimatedButton::start()
0062 {
0063     if (d->movie) {
0064         d->movie->start();
0065     } else {
0066         d->current_frame = 0;
0067         d->timer.start(50);
0068     }
0069 }
0070 
0071 void KAnimatedButton::stop()
0072 {
0073     if (d->movie) {
0074         d->movie->stop();
0075         d->movie->jumpToFrame(0);
0076         d->movieFrameChanged(0);
0077     } else {
0078         d->current_frame = 0;
0079         d->timer.stop();
0080         d->updateCurrentIcon();
0081     }
0082 }
0083 
0084 void KAnimatedButton::setAnimationPath(const QString &path)
0085 {
0086     if (d->icon_path == path) {
0087         return;
0088     }
0089 
0090     d->timer.stop();
0091     d->icon_path = path;
0092     d->updateIcons();
0093 }
0094 
0095 QString KAnimatedButton::animationPath() const
0096 {
0097     return d->icon_path;
0098 }
0099 
0100 void KAnimatedButtonPrivate::timerUpdate()
0101 {
0102     if (!q->isVisible()) {
0103         return;
0104     }
0105 
0106     current_frame++;
0107     if (current_frame == frames) {
0108         current_frame = 0;
0109     }
0110 
0111     updateCurrentIcon();
0112 }
0113 
0114 void KAnimatedButtonPrivate::updateCurrentIcon()
0115 {
0116     if (pixmap.isNull()) {
0117         return;
0118     }
0119 
0120     QPixmap *frame = framesCache[current_frame];
0121     if (!frame) {
0122         const int icon_size = qMin(pixmap.width(), pixmap.height());
0123         const int row_size = pixmap.width() / icon_size;
0124         const int row = current_frame / row_size;
0125         const int column = current_frame % row_size;
0126         frame = new QPixmap(icon_size, icon_size);
0127         frame->fill(Qt::transparent);
0128         QPainter p(frame);
0129         p.drawPixmap(QPoint(0, 0), pixmap, QRect(column * icon_size, row * icon_size, icon_size, icon_size));
0130         p.end();
0131         framesCache[current_frame] = frame;
0132     }
0133 
0134     q->setIcon(QIcon(*frame));
0135 }
0136 
0137 void KAnimatedButtonPrivate::movieFrameChanged(int number)
0138 {
0139     Q_UNUSED(number);
0140     q->setIcon(QIcon(movie->currentPixmap()));
0141 }
0142 
0143 void KAnimatedButtonPrivate::movieFinished()
0144 {
0145     // if not running, make it loop
0146     if (movie->state() == QMovie::NotRunning) {
0147         movie->start();
0148     }
0149 }
0150 
0151 void KAnimatedButtonPrivate::updateIcons()
0152 {
0153     pixmap = QPixmap();
0154     QMovie *newMovie = nullptr;
0155     QImageReader reader(icon_path);
0156     if (QMovie::supportedFormats().contains(reader.format())) {
0157         newMovie = new QMovie(icon_path);
0158         frames = 0;
0159         newMovie->setCacheMode(QMovie::CacheAll);
0160         QObject::connect(newMovie, &QMovie::frameChanged, q, [this](int value) {
0161             movieFrameChanged(value);
0162         });
0163         QObject::connect(newMovie, &QMovie::finished, q, [this] {
0164             movieFinished();
0165         });
0166     } else {
0167         const QPixmap pix(icon_path);
0168         if (pix.isNull()) {
0169             return;
0170         }
0171 
0172         const int icon_size = qMin(pix.width(), pix.height());
0173         if ((pix.height() % icon_size != 0) || (pix.width() % icon_size != 0)) {
0174             return;
0175         }
0176 
0177         frames = (pix.height() / icon_size) * (pix.width() / icon_size);
0178         pixmap = pix;
0179     }
0180 
0181     current_frame = 0;
0182     qDeleteAll(framesCache);
0183     framesCache.fill(nullptr);
0184     framesCache.resize(frames);
0185     delete movie;
0186     movie = newMovie;
0187 
0188     if (movie) {
0189         movie->jumpToFrame(0);
0190         movieFrameChanged(0);
0191     } else {
0192         updateCurrentIcon();
0193     }
0194 }
0195 
0196 #include "moc_kanimatedbutton.cpp"