KIO

kimagefilepreview.cpp
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
27/**** KImageFilePreview ****/
28
29class KImageFilePreviewPrivate
30{
31public:
32 KImageFilePreviewPrivate(KImageFilePreview *qq)
33 : q(qq)
34 {
35 if (q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)) {
36 m_timeLine = new QTimeLine(150, q);
37 m_timeLine->setEasingCurve(QEasingCurve::InCurve);
38 m_timeLine->setDirection(QTimeLine::Forward);
39 m_timeLine->setFrameRange(0, 100);
40 }
41 }
42
43 void slotResult(KJob *);
44 void slotFailed(const KFileItem &);
45 void slotStepAnimation();
46 void slotFinished();
47 void slotActuallyClear();
48
49 KImageFilePreview *q = nullptr;
50 QUrl currentURL;
51 QUrl lastShownURL;
52 QLabel *imageLabel;
53 KIO::PreviewJob *m_job = nullptr;
54 QTimeLine *m_timeLine = nullptr;
55 QPixmap m_pmCurrent;
56 QPixmap m_pmTransition;
57 float m_pmCurrentOpacity = 1;
58 float m_pmTransitionOpacity = 0;
59 bool clear = true;
60};
61
63 : KPreviewWidgetBase(parent)
64 , d(new KImageFilePreviewPrivate(this))
65{
66 QVBoxLayout *vb = new QVBoxLayout(this);
67 vb->setContentsMargins(0, 0, 0, 0);
68
69 d->imageLabel = new QLabel(this);
70 d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
71 d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
72 vb->addWidget(d->imageLabel);
73
74 setSupportedMimeTypes(KIO::PreviewJob::supportedMimeTypes());
76
77 if (d->m_timeLine) {
78 connect(d->m_timeLine, &QTimeLine::frameChanged, this, [this]() {
79 d->slotStepAnimation();
80 });
81 connect(d->m_timeLine, &QTimeLine::finished, this, [this]() {
82 d->slotFinished();
83 });
84 }
85}
86
88{
89 if (d->m_job) {
90 d->m_job->kill();
91 }
92}
93
94void KImageFilePreview::showPreview()
95{
96 // Pass a copy since clearPreview() will clear currentURL
97 QUrl url = d->currentURL;
98 showPreview(url, true);
99}
100
101// called via KPreviewWidgetBase interface
103{
104 showPreview(url, false);
105}
106
107void KImageFilePreview::showPreview(const QUrl &url, bool force)
108{
109 /* clang-format off */
110 if (!url.isValid()
111 || (d->lastShownURL.isValid()
112 && url.matches(d->lastShownURL, QUrl::StripTrailingSlash)
113 && d->currentURL.isValid())) {
114 return;
115 }
116 /* clang-format on*/
117
118 d->clear = false;
119 d->currentURL = url;
120 d->lastShownURL = url;
121
122 int w = d->imageLabel->contentsRect().width() - 4;
123 int h = d->imageLabel->contentsRect().height() - 4;
124
125 if (d->m_job) {
126 disconnect(d->m_job, nullptr, this, nullptr);
127
128 d->m_job->kill();
129 }
130
131 d->m_job = createJob(url, w, h);
132 if (force) { // explicitly requested previews shall always be generated!
133 d->m_job->setIgnoreMaximumSize(true);
134 }
135
136 connect(d->m_job, &KJob::result, this, [this](KJob *job) {
137 d->slotResult(job);
138 });
139 connect(d->m_job, &KIO::PreviewJob::gotPreview, this, &KImageFilePreview::gotPreview);
140 connect(d->m_job, &KIO::PreviewJob::failed, this, [this](const KFileItem &item) {
141 d->slotFailed(item);
142 });
143}
144
145void KImageFilePreview::resizeEvent(QResizeEvent *)
146{
147 // Nothing to do, if no current preview
148 if (d->imageLabel->pixmap().isNull()) {
149 return;
150 }
151
152 clearPreview();
153 d->currentURL = QUrl(); // force this to actually happen
154 showPreview(d->lastShownURL);
155}
156
158{
159 return QSize(100, 200);
160}
161
162KIO::PreviewJob *KImageFilePreview::createJob(const QUrl &url, int w, int h)
163{
164 if (!url.isValid()) {
165 return nullptr;
166 }
167
168 KFileItemList items;
169 items.append(KFileItem(url));
171
172 KIO::PreviewJob *previewJob = KIO::filePreview(items, QSize(w, h), &plugins);
174 return previewJob;
175}
176
177void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm)
178{
179 if (item.url() != d->currentURL) { // Shouldn't happen
180 return;
181 }
182
183 if (d->m_timeLine) {
184 if (d->m_timeLine->state() == QTimeLine::Running) {
185 d->m_timeLine->setCurrentTime(0);
186 }
187
188 d->m_pmTransition = pm;
189 d->m_pmTransitionOpacity = 0;
190 d->m_pmCurrentOpacity = 1;
191 d->m_timeLine->setDirection(QTimeLine::Forward);
192 d->m_timeLine->start();
193 } else {
194 d->imageLabel->setPixmap(pm);
195 }
196}
197
198void KImageFilePreviewPrivate::slotFailed(const KFileItem &item)
199{
200 if (item.isDir()) {
201 imageLabel->clear();
202 } else if (item.url() == currentURL) { // should always be the case
203 imageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("image-missing")).pixmap(KIconLoader::SizeLarge, QIcon::Disabled));
204 }
205}
206
207void KImageFilePreviewPrivate::slotResult(KJob *job)
208{
209 if (job == m_job) {
210 m_job = nullptr;
211 }
212}
213
214void KImageFilePreviewPrivate::slotStepAnimation()
215{
216 const QSize currSize = m_pmCurrent.size();
217 const QSize transitionSize = m_pmTransition.size();
218 const int width = std::max(currSize.width(), transitionSize.width());
219 const int height = std::max(currSize.height(), transitionSize.height());
220 QPixmap pm(QSize(width, height));
222
223 QPainter p(&pm);
224 p.setOpacity(m_pmCurrentOpacity);
225
226 // If we have a current pixmap
227 if (!m_pmCurrent.isNull()) {
228 p.drawPixmap(QPoint(((float)pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float)pm.size().height() - m_pmCurrent.size().height()) / 2.0),
229 m_pmCurrent);
230 }
231 if (!m_pmTransition.isNull()) {
232 p.setOpacity(m_pmTransitionOpacity);
233 p.drawPixmap(
234 QPoint(((float)pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float)pm.size().height() - m_pmTransition.size().height()) / 2.0),
235 m_pmTransition);
236 }
237 p.end();
238
239 imageLabel->setPixmap(pm);
240
241 m_pmCurrentOpacity = qMax(m_pmCurrentOpacity - 0.4, 0.0); // krazy:exclude=qminmax
242 m_pmTransitionOpacity = qMin(m_pmTransitionOpacity + 0.4, 1.0); // krazy:exclude=qminmax
243}
244
245void KImageFilePreviewPrivate::slotFinished()
246{
247 m_pmCurrent = m_pmTransition;
248 m_pmTransitionOpacity = 0;
249 m_pmCurrentOpacity = 1;
250 m_pmTransition = QPixmap();
251 // The animation might have lost some frames. Be sure that if the last one
252 // was dropped, the last image shown is the opaque one.
253 imageLabel->setPixmap(m_pmCurrent);
254 clear = false;
255}
256
258{
259 if (d->m_job) {
260 d->m_job->kill();
261 d->m_job = nullptr;
262 }
263
264 if (d->clear || (d->m_timeLine && d->m_timeLine->state() == QTimeLine::Running)) {
265 return;
266 }
267
268 if (d->m_timeLine) {
269 d->m_pmTransition = QPixmap();
270 // If we add a previous preview then we run the animation
271 if (!d->m_pmCurrent.isNull()) {
272 d->m_timeLine->setCurrentTime(0);
273 d->m_timeLine->setDirection(QTimeLine::Backward);
274 d->m_timeLine->start();
275 }
276 d->currentURL.clear();
277 d->clear = true;
278 } else {
279 d->imageLabel->clear();
280 }
281}
282
283#include "moc_kimagefilepreview.cpp"
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:632
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
KIO Job to get a thumbnail picture.
void setScaleType(ScaleType type)
Sets the scale type for the generated preview.
static QStringList supportedMimeTypes()
Returns a list of all supported MIME types.
static QStringList availablePlugins()
Returns a list of all available preview plugins.
@ Scaled
The preview will be scaled to the size specified when constructing the PreviewJob.
Definition previewjob.h:49
void failed(const KFileItem &item)
Emitted when a thumbnail for item could not be created, either because a ThumbCreator for its MIME ty...
void gotPreview(const KFileItem &item, const QPixmap &preview)
Emitted when a thumbnail picture for item has been successfully retrieved.
Image preview widget for the file dialog.
~KImageFilePreview() override
Destroys the image file preview.
QSize sizeHint() const override
Returns the size hint for this widget.
void clearPreview() override
Clears the preview.
KImageFilePreview(QWidget *parent=nullptr)
Creates a new image file preview.
void showPreview(const QUrl &url) override
Shows a preview for the given url.
void result(KJob *job)
Abstract baseclass for all preview widgets which shall be used via KFileDialog::setPreviewWidget(cons...
KIOGUI_EXPORT PreviewJob * filePreview(const KFileItemList &items, const QSize &size, const QStringList *enabledPlugins=nullptr)
Creates a PreviewJob to generate a preview image for the given items.
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QIcon fromTheme(const QString &name)
void clear()
void setPixmap(const QPixmap &)
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
void fill(const QColor &color)
bool isNull() const const
QSize size() const const
int height() const const
int width() const const
SH_Widget_Animate
virtual int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const const=0
AlignHCenter
transparent
void finished()
void frameChanged(int frame)
StripTrailingSlash
bool isValid() const const
bool matches(const QUrl &url, FormattingOptions options) const const
void setMinimumWidth(int minw)
QStyle * style() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.