1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2001 Martin R. Jones <mjones@kde.org>
4 SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
5 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-only
8*/
9
10#include "kimagefilepreview.h"
11
12#include <QCheckBox>
13#include <QLabel>
14#include <QPainter>
15#include <QResizeEvent>
16#include <QStyle>
17#include <QTimeLine>
18#include <QVBoxLayout>
19
20#include <KConfig>
21#include <KConfigGroup>
22#include <KIconLoader>
23#include <KLocalizedString>
24#include <kfileitem.h>
25#include <kio/previewjob.h>
26
27class KImageFilePreviewPrivate
28{
29public:
30 KImageFilePreviewPrivate(KImageFilePreview *qq)
31 : q(qq)
32 {
33 if (q->style()->styleHint(stylehint: QStyle::SH_Widget_Animate, opt: nullptr, widget: q)) {
34 m_timeLine = new QTimeLine(150, q);
35 m_timeLine->setEasingCurve(QEasingCurve::InCurve);
36 m_timeLine->setDirection(QTimeLine::Forward);
37 m_timeLine->setFrameRange(startFrame: 0, endFrame: 100);
38 }
39 }
40
41 void slotResult(KJob *);
42 void slotFailed(const KFileItem &);
43 void slotStepAnimation();
44 void slotFinished();
45 void slotActuallyClear();
46
47 KImageFilePreview *q = nullptr;
48 QUrl currentURL;
49 QUrl lastShownURL;
50 QLabel *imageLabel;
51 KIO::PreviewJob *m_job = nullptr;
52 QTimeLine *m_timeLine = nullptr;
53 QPixmap m_pmCurrent;
54 QPixmap m_pmTransition;
55 float m_pmCurrentOpacity = 1;
56 float m_pmTransitionOpacity = 0;
57 bool clear = true;
58};
59
60KImageFilePreview::KImageFilePreview(QWidget *parent)
61 : KPreviewWidgetBase(parent)
62 , d(new KImageFilePreviewPrivate(this))
63{
64 QVBoxLayout *vb = new QVBoxLayout(this);
65 vb->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
66
67 d->imageLabel = new QLabel(this);
68 d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
69 d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
70 vb->addWidget(d->imageLabel);
71
72 setSupportedMimeTypes(KIO::PreviewJob::supportedMimeTypes());
73 setMinimumWidth(50);
74
75 if (d->m_timeLine) {
76 connect(sender: d->m_timeLine, signal: &QTimeLine::frameChanged, context: this, slot: [this]() {
77 d->slotStepAnimation();
78 });
79 connect(sender: d->m_timeLine, signal: &QTimeLine::finished, context: this, slot: [this]() {
80 d->slotFinished();
81 });
82 }
83}
84
85KImageFilePreview::~KImageFilePreview()
86{
87 if (d->m_job) {
88 d->m_job->kill();
89 }
90}
91
92void KImageFilePreview::showPreview()
93{
94 // Pass a copy since clearPreview() will clear currentURL
95 QUrl url = d->currentURL;
96 showPreview(url, force: true);
97}
98
99// called via KPreviewWidgetBase interface
100void KImageFilePreview::showPreview(const QUrl &url)
101{
102 showPreview(url, force: false);
103}
104
105void KImageFilePreview::showPreview(const QUrl &url, bool force)
106{
107 /* clang-format off */
108 if (!url.isValid()
109 || (d->lastShownURL.isValid()
110 && url.matches(url: d->lastShownURL, options: QUrl::StripTrailingSlash)
111 && d->currentURL.isValid())) {
112 return;
113 }
114 /* clang-format on*/
115
116 d->clear = false;
117 d->currentURL = url;
118 d->lastShownURL = url;
119
120 int w = d->imageLabel->contentsRect().width() - 4;
121 int h = d->imageLabel->contentsRect().height() - 4;
122
123 if (d->m_job) {
124 disconnect(sender: d->m_job, signal: nullptr, receiver: this, member: nullptr);
125
126 d->m_job->kill();
127 }
128
129 d->m_job = createJob(url, width: w, height: h);
130 if (force) { // explicitly requested previews shall always be generated!
131 d->m_job->setIgnoreMaximumSize(true);
132 }
133
134 connect(sender: d->m_job, signal: &KJob::result, context: this, slot: [this](KJob *job) {
135 d->slotResult(job);
136 });
137 connect(sender: d->m_job, signal: &KIO::PreviewJob::gotPreview, context: this, slot: &KImageFilePreview::gotPreview);
138 connect(sender: d->m_job, signal: &KIO::PreviewJob::failed, context: this, slot: [this](const KFileItem &item) {
139 d->slotFailed(item);
140 });
141}
142
143void KImageFilePreview::resizeEvent(QResizeEvent *)
144{
145 // Nothing to do, if no current preview
146 if (d->imageLabel->pixmap().isNull()) {
147 return;
148 }
149
150 clearPreview();
151 d->currentURL = QUrl(); // force this to actually happen
152 showPreview(url: d->lastShownURL);
153}
154
155QSize KImageFilePreview::sizeHint() const
156{
157 return QSize(100, 200);
158}
159
160KIO::PreviewJob *KImageFilePreview::createJob(const QUrl &url, int w, int h)
161{
162 if (!url.isValid()) {
163 return nullptr;
164 }
165
166 KFileItemList items;
167 items.append(t: KFileItem(url));
168 QStringList plugins = KIO::PreviewJob::availablePlugins();
169
170 KIO::PreviewJob *previewJob = KIO::filePreview(items, size: QSize(w, h), enabledPlugins: &plugins);
171 previewJob->setScaleType(KIO::PreviewJob::Scaled);
172 return previewJob;
173}
174
175void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm)
176{
177 if (item.url() != d->currentURL) { // Shouldn't happen
178 return;
179 }
180
181 if (d->m_timeLine) {
182 if (d->m_timeLine->state() == QTimeLine::Running) {
183 d->m_timeLine->setCurrentTime(0);
184 }
185
186 d->m_pmTransition = pm;
187 d->m_pmTransitionOpacity = 0;
188 d->m_pmCurrentOpacity = 1;
189 d->m_timeLine->setDirection(QTimeLine::Forward);
190 d->m_timeLine->start();
191 } else {
192 d->imageLabel->setPixmap(pm);
193 }
194}
195
196void KImageFilePreviewPrivate::slotFailed(const KFileItem &item)
197{
198 if (item.isDir()) {
199 imageLabel->clear();
200 } else if (item.url() == currentURL) { // should always be the case
201 imageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("image-missing")).pixmap(extent: KIconLoader::SizeLarge, mode: QIcon::Disabled));
202 }
203}
204
205void KImageFilePreviewPrivate::slotResult(KJob *job)
206{
207 if (job == m_job) {
208 m_job = nullptr;
209 }
210}
211
212void KImageFilePreviewPrivate::slotStepAnimation()
213{
214 const QSize currSize = m_pmCurrent.size();
215 const QSize transitionSize = m_pmTransition.size();
216 const int width = std::max(a: currSize.width(), b: transitionSize.width());
217 const int height = std::max(a: currSize.height(), b: transitionSize.height());
218 QPixmap pm(QSize(width, height));
219 pm.fill(fillColor: Qt::transparent);
220
221 QPainter p(&pm);
222 p.setOpacity(m_pmCurrentOpacity);
223
224 // If we have a current pixmap
225 if (!m_pmCurrent.isNull()) {
226 p.drawPixmap(p: QPoint(((float)pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float)pm.size().height() - m_pmCurrent.size().height()) / 2.0),
227 pm: m_pmCurrent);
228 }
229 if (!m_pmTransition.isNull()) {
230 p.setOpacity(m_pmTransitionOpacity);
231 p.drawPixmap(
232 p: QPoint(((float)pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float)pm.size().height() - m_pmTransition.size().height()) / 2.0),
233 pm: m_pmTransition);
234 }
235 p.end();
236
237 imageLabel->setPixmap(pm);
238
239 m_pmCurrentOpacity = qMax(a: m_pmCurrentOpacity - 0.4, b: 0.0); // krazy:exclude=qminmax
240 m_pmTransitionOpacity = qMin(a: m_pmTransitionOpacity + 0.4, b: 1.0); // krazy:exclude=qminmax
241}
242
243void KImageFilePreviewPrivate::slotFinished()
244{
245 m_pmCurrent = m_pmTransition;
246 m_pmTransitionOpacity = 0;
247 m_pmCurrentOpacity = 1;
248 m_pmTransition = QPixmap();
249 // The animation might have lost some frames. Be sure that if the last one
250 // was dropped, the last image shown is the opaque one.
251 imageLabel->setPixmap(m_pmCurrent);
252 clear = false;
253}
254
255void KImageFilePreview::clearPreview()
256{
257 if (d->m_job) {
258 d->m_job->kill();
259 d->m_job = nullptr;
260 }
261
262 if (d->clear || (d->m_timeLine && d->m_timeLine->state() == QTimeLine::Running)) {
263 return;
264 }
265
266 if (d->m_timeLine) {
267 d->m_pmTransition = QPixmap();
268 // If we add a previous preview then we run the animation
269 if (!d->m_pmCurrent.isNull()) {
270 d->m_timeLine->setCurrentTime(0);
271 d->m_timeLine->setDirection(QTimeLine::Backward);
272 d->m_timeLine->start();
273 }
274 d->currentURL.clear();
275 d->clear = true;
276 } else {
277 d->imageLabel->clear();
278 }
279}
280
281#include "moc_kimagefilepreview.cpp"
282

source code of kio/src/filewidgets/kimagefilepreview.cpp