File indexing completed on 2024-07-14 03:54:09

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2001 Martin R. Jones <mjones@kde.org>
0004     SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0005     SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "kimagefilepreview.h"
0011 
0012 #include <QCheckBox>
0013 #include <QLabel>
0014 #include <QPainter>
0015 #include <QResizeEvent>
0016 #include <QStyle>
0017 #include <QTimeLine>
0018 #include <QVBoxLayout>
0019 
0020 #include <KConfig>
0021 #include <KConfigGroup>
0022 #include <KIconLoader>
0023 #include <KLocalizedString>
0024 #include <kfileitem.h>
0025 #include <kio/previewjob.h>
0026 
0027 /**** KImageFilePreview ****/
0028 
0029 class KImageFilePreviewPrivate
0030 {
0031 public:
0032     KImageFilePreviewPrivate(KImageFilePreview *qq)
0033         : q(qq)
0034     {
0035         if (q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)) {
0036             m_timeLine = new QTimeLine(150, q);
0037             m_timeLine->setEasingCurve(QEasingCurve::InCurve);
0038             m_timeLine->setDirection(QTimeLine::Forward);
0039             m_timeLine->setFrameRange(0, 100);
0040         }
0041     }
0042 
0043     void slotResult(KJob *);
0044     void slotFailed(const KFileItem &);
0045     void slotStepAnimation();
0046     void slotFinished();
0047     void slotActuallyClear();
0048 
0049     KImageFilePreview *q = nullptr;
0050     QUrl currentURL;
0051     QUrl lastShownURL;
0052     QLabel *imageLabel;
0053     KIO::PreviewJob *m_job = nullptr;
0054     QTimeLine *m_timeLine = nullptr;
0055     QPixmap m_pmCurrent;
0056     QPixmap m_pmTransition;
0057     float m_pmCurrentOpacity = 1;
0058     float m_pmTransitionOpacity = 0;
0059     bool clear = true;
0060 };
0061 
0062 KImageFilePreview::KImageFilePreview(QWidget *parent)
0063     : KPreviewWidgetBase(parent)
0064     , d(new KImageFilePreviewPrivate(this))
0065 {
0066     QVBoxLayout *vb = new QVBoxLayout(this);
0067     vb->setContentsMargins(0, 0, 0, 0);
0068 
0069     d->imageLabel = new QLabel(this);
0070     d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0071     d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
0072     vb->addWidget(d->imageLabel);
0073 
0074     setSupportedMimeTypes(KIO::PreviewJob::supportedMimeTypes());
0075     setMinimumWidth(50);
0076 
0077     if (d->m_timeLine) {
0078         connect(d->m_timeLine, &QTimeLine::frameChanged, this, [this]() {
0079             d->slotStepAnimation();
0080         });
0081         connect(d->m_timeLine, &QTimeLine::finished, this, [this]() {
0082             d->slotFinished();
0083         });
0084     }
0085 }
0086 
0087 KImageFilePreview::~KImageFilePreview()
0088 {
0089     if (d->m_job) {
0090         d->m_job->kill();
0091     }
0092 }
0093 
0094 void KImageFilePreview::showPreview()
0095 {
0096     // Pass a copy since clearPreview() will clear currentURL
0097     QUrl url = d->currentURL;
0098     showPreview(url, true);
0099 }
0100 
0101 // called via KPreviewWidgetBase interface
0102 void KImageFilePreview::showPreview(const QUrl &url)
0103 {
0104     showPreview(url, false);
0105 }
0106 
0107 void KImageFilePreview::showPreview(const QUrl &url, bool force)
0108 {
0109     /* clang-format off */
0110     if (!url.isValid()
0111         || (d->lastShownURL.isValid()
0112             && url.matches(d->lastShownURL, QUrl::StripTrailingSlash)
0113             && d->currentURL.isValid())) {
0114         return;
0115     }
0116     /* clang-format on*/
0117 
0118     d->clear = false;
0119     d->currentURL = url;
0120     d->lastShownURL = url;
0121 
0122     int w = d->imageLabel->contentsRect().width() - 4;
0123     int h = d->imageLabel->contentsRect().height() - 4;
0124 
0125     if (d->m_job) {
0126         disconnect(d->m_job, nullptr, this, nullptr);
0127 
0128         d->m_job->kill();
0129     }
0130 
0131     d->m_job = createJob(url, w, h);
0132     if (force) { // explicitly requested previews shall always be generated!
0133         d->m_job->setIgnoreMaximumSize(true);
0134     }
0135 
0136     connect(d->m_job, &KJob::result, this, [this](KJob *job) {
0137         d->slotResult(job);
0138     });
0139     connect(d->m_job, &KIO::PreviewJob::gotPreview, this, &KImageFilePreview::gotPreview);
0140     connect(d->m_job, &KIO::PreviewJob::failed, this, [this](const KFileItem &item) {
0141         d->slotFailed(item);
0142     });
0143 }
0144 
0145 void KImageFilePreview::resizeEvent(QResizeEvent *)
0146 {
0147     // Nothing to do, if no current preview
0148     if (d->imageLabel->pixmap().isNull()) {
0149         return;
0150     }
0151 
0152     clearPreview();
0153     d->currentURL = QUrl(); // force this to actually happen
0154     showPreview(d->lastShownURL);
0155 }
0156 
0157 QSize KImageFilePreview::sizeHint() const
0158 {
0159     return QSize(100, 200);
0160 }
0161 
0162 KIO::PreviewJob *KImageFilePreview::createJob(const QUrl &url, int w, int h)
0163 {
0164     if (!url.isValid()) {
0165         return nullptr;
0166     }
0167 
0168     KFileItemList items;
0169     items.append(KFileItem(url));
0170     QStringList plugins = KIO::PreviewJob::availablePlugins();
0171 
0172     KIO::PreviewJob *previewJob = KIO::filePreview(items, QSize(w, h), &plugins);
0173     previewJob->setScaleType(KIO::PreviewJob::Scaled);
0174     return previewJob;
0175 }
0176 
0177 void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm)
0178 {
0179     if (item.url() != d->currentURL) { // Shouldn't happen
0180         return;
0181     }
0182 
0183     if (d->m_timeLine) {
0184         if (d->m_timeLine->state() == QTimeLine::Running) {
0185             d->m_timeLine->setCurrentTime(0);
0186         }
0187 
0188         d->m_pmTransition = pm;
0189         d->m_pmTransitionOpacity = 0;
0190         d->m_pmCurrentOpacity = 1;
0191         d->m_timeLine->setDirection(QTimeLine::Forward);
0192         d->m_timeLine->start();
0193     } else {
0194         d->imageLabel->setPixmap(pm);
0195     }
0196 }
0197 
0198 void KImageFilePreviewPrivate::slotFailed(const KFileItem &item)
0199 {
0200     if (item.isDir()) {
0201         imageLabel->clear();
0202     } else if (item.url() == currentURL) { // should always be the case
0203         imageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("image-missing")).pixmap(KIconLoader::SizeLarge, QIcon::Disabled));
0204     }
0205 }
0206 
0207 void KImageFilePreviewPrivate::slotResult(KJob *job)
0208 {
0209     if (job == m_job) {
0210         m_job = nullptr;
0211     }
0212 }
0213 
0214 void KImageFilePreviewPrivate::slotStepAnimation()
0215 {
0216     const QSize currSize = m_pmCurrent.size();
0217     const QSize transitionSize = m_pmTransition.size();
0218     const int width = std::max(currSize.width(), transitionSize.width());
0219     const int height = std::max(currSize.height(), transitionSize.height());
0220     QPixmap pm(QSize(width, height));
0221     pm.fill(Qt::transparent);
0222 
0223     QPainter p(&pm);
0224     p.setOpacity(m_pmCurrentOpacity);
0225 
0226     // If we have a current pixmap
0227     if (!m_pmCurrent.isNull()) {
0228         p.drawPixmap(QPoint(((float)pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float)pm.size().height() - m_pmCurrent.size().height()) / 2.0),
0229                      m_pmCurrent);
0230     }
0231     if (!m_pmTransition.isNull()) {
0232         p.setOpacity(m_pmTransitionOpacity);
0233         p.drawPixmap(
0234             QPoint(((float)pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float)pm.size().height() - m_pmTransition.size().height()) / 2.0),
0235             m_pmTransition);
0236     }
0237     p.end();
0238 
0239     imageLabel->setPixmap(pm);
0240 
0241     m_pmCurrentOpacity = qMax(m_pmCurrentOpacity - 0.4, 0.0); // krazy:exclude=qminmax
0242     m_pmTransitionOpacity = qMin(m_pmTransitionOpacity + 0.4, 1.0); // krazy:exclude=qminmax
0243 }
0244 
0245 void KImageFilePreviewPrivate::slotFinished()
0246 {
0247     m_pmCurrent = m_pmTransition;
0248     m_pmTransitionOpacity = 0;
0249     m_pmCurrentOpacity = 1;
0250     m_pmTransition = QPixmap();
0251     // The animation might have lost some frames. Be sure that if the last one
0252     // was dropped, the last image shown is the opaque one.
0253     imageLabel->setPixmap(m_pmCurrent);
0254     clear = false;
0255 }
0256 
0257 void KImageFilePreview::clearPreview()
0258 {
0259     if (d->m_job) {
0260         d->m_job->kill();
0261         d->m_job = nullptr;
0262     }
0263 
0264     if (d->clear || (d->m_timeLine && d->m_timeLine->state() == QTimeLine::Running)) {
0265         return;
0266     }
0267 
0268     if (d->m_timeLine) {
0269         d->m_pmTransition = QPixmap();
0270         // If we add a previous preview then we run the animation
0271         if (!d->m_pmCurrent.isNull()) {
0272             d->m_timeLine->setCurrentTime(0);
0273             d->m_timeLine->setDirection(QTimeLine::Backward);
0274             d->m_timeLine->start();
0275         }
0276         d->currentURL.clear();
0277         d->clear = true;
0278     } else {
0279         d->imageLabel->clear();
0280     }
0281 }
0282 
0283 #include "moc_kimagefilepreview.cpp"