1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org> |
4 | SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> |
5 | SPDX-FileCopyrightText: 2008 Pino Toscano <pino@kde.org> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-only |
8 | */ |
9 | |
10 | #include <kanimatedbutton.h> |
11 | |
12 | #include <QImageReader> |
13 | #include <QMovie> |
14 | #include <QPainter> |
15 | #include <QPixmap> |
16 | #include <QTimer> |
17 | |
18 | class KAnimatedButtonPrivate |
19 | { |
20 | public: |
21 | KAnimatedButtonPrivate(KAnimatedButton *qq) |
22 | : q(qq) |
23 | { |
24 | } |
25 | |
26 | void updateIcons(); |
27 | void updateCurrentIcon(); |
28 | void movieFrameChanged(int number); |
29 | void movieFinished(); |
30 | void timerUpdate(); |
31 | |
32 | KAnimatedButton *const q; |
33 | QMovie *movie = nullptr; |
34 | |
35 | int frames; |
36 | int current_frame; |
37 | QPixmap pixmap; |
38 | QTimer timer; |
39 | QString icon_path; |
40 | QList<QPixmap *> framesCache; // We keep copies of each frame so that |
41 | // the icon code can properly cache them in QPixmapCache, |
42 | // and not fill it up with dead copies |
43 | }; |
44 | |
45 | KAnimatedButton::KAnimatedButton(QWidget *parent) |
46 | : QToolButton(parent) |
47 | , d(new KAnimatedButtonPrivate(this)) |
48 | { |
49 | connect(sender: &d->timer, signal: &QTimer::timeout, context: this, slot: [this]() { |
50 | d->timerUpdate(); |
51 | }); |
52 | } |
53 | |
54 | KAnimatedButton::~KAnimatedButton() |
55 | { |
56 | d->timer.stop(); |
57 | qDeleteAll(c: d->framesCache); |
58 | delete d->movie; |
59 | } |
60 | |
61 | void KAnimatedButton::start() |
62 | { |
63 | if (d->movie) { |
64 | d->movie->start(); |
65 | } else { |
66 | d->current_frame = 0; |
67 | d->timer.start(msec: 50); |
68 | } |
69 | } |
70 | |
71 | void KAnimatedButton::stop() |
72 | { |
73 | if (d->movie) { |
74 | d->movie->stop(); |
75 | d->movie->jumpToFrame(frameNumber: 0); |
76 | d->movieFrameChanged(number: 0); |
77 | } else { |
78 | d->current_frame = 0; |
79 | d->timer.stop(); |
80 | d->updateCurrentIcon(); |
81 | } |
82 | } |
83 | |
84 | void KAnimatedButton::setAnimationPath(const QString &path) |
85 | { |
86 | if (d->icon_path == path) { |
87 | return; |
88 | } |
89 | |
90 | d->timer.stop(); |
91 | d->icon_path = path; |
92 | d->updateIcons(); |
93 | } |
94 | |
95 | QString KAnimatedButton::animationPath() const |
96 | { |
97 | return d->icon_path; |
98 | } |
99 | |
100 | void KAnimatedButtonPrivate::timerUpdate() |
101 | { |
102 | if (!q->isVisible()) { |
103 | return; |
104 | } |
105 | |
106 | current_frame++; |
107 | if (current_frame == frames) { |
108 | current_frame = 0; |
109 | } |
110 | |
111 | updateCurrentIcon(); |
112 | } |
113 | |
114 | void KAnimatedButtonPrivate::updateCurrentIcon() |
115 | { |
116 | if (pixmap.isNull()) { |
117 | return; |
118 | } |
119 | |
120 | QPixmap *frame = framesCache[current_frame]; |
121 | if (!frame) { |
122 | const int icon_size = qMin(a: pixmap.width(), b: pixmap.height()); |
123 | const int row_size = pixmap.width() / icon_size; |
124 | const int row = current_frame / row_size; |
125 | const int column = current_frame % row_size; |
126 | frame = new QPixmap(icon_size, icon_size); |
127 | frame->fill(fillColor: Qt::transparent); |
128 | QPainter p(frame); |
129 | p.drawPixmap(p: QPoint(0, 0), pm: pixmap, sr: QRect(column * icon_size, row * icon_size, icon_size, icon_size)); |
130 | p.end(); |
131 | framesCache[current_frame] = frame; |
132 | } |
133 | |
134 | q->setIcon(QIcon(*frame)); |
135 | } |
136 | |
137 | void KAnimatedButtonPrivate::movieFrameChanged(int number) |
138 | { |
139 | Q_UNUSED(number); |
140 | q->setIcon(QIcon(movie->currentPixmap())); |
141 | } |
142 | |
143 | void KAnimatedButtonPrivate::movieFinished() |
144 | { |
145 | // if not running, make it loop |
146 | if (movie->state() == QMovie::NotRunning) { |
147 | movie->start(); |
148 | } |
149 | } |
150 | |
151 | void KAnimatedButtonPrivate::updateIcons() |
152 | { |
153 | pixmap = QPixmap(); |
154 | QMovie *newMovie = nullptr; |
155 | QImageReader reader(icon_path); |
156 | if (QMovie::supportedFormats().contains(t: reader.format())) { |
157 | newMovie = new QMovie(icon_path); |
158 | frames = 0; |
159 | newMovie->setCacheMode(QMovie::CacheAll); |
160 | QObject::connect(sender: newMovie, signal: &QMovie::frameChanged, context: q, slot: [this](int value) { |
161 | movieFrameChanged(number: value); |
162 | }); |
163 | QObject::connect(sender: newMovie, signal: &QMovie::finished, context: q, slot: [this] { |
164 | movieFinished(); |
165 | }); |
166 | } else { |
167 | const QPixmap pix(icon_path); |
168 | if (pix.isNull()) { |
169 | return; |
170 | } |
171 | |
172 | const int icon_size = qMin(a: pix.width(), b: pix.height()); |
173 | if ((pix.height() % icon_size != 0) || (pix.width() % icon_size != 0)) { |
174 | return; |
175 | } |
176 | |
177 | frames = (pix.height() / icon_size) * (pix.width() / icon_size); |
178 | pixmap = pix; |
179 | } |
180 | |
181 | current_frame = 0; |
182 | qDeleteAll(c: framesCache); |
183 | framesCache.fill(t: nullptr); |
184 | framesCache.resize(size: frames); |
185 | delete movie; |
186 | movie = newMovie; |
187 | |
188 | if (movie) { |
189 | movie->jumpToFrame(frameNumber: 0); |
190 | movieFrameChanged(number: 0); |
191 | } else { |
192 | updateCurrentIcon(); |
193 | } |
194 | } |
195 | |
196 | #include "moc_kanimatedbutton.cpp" |
197 | |