File indexing completed on 2024-12-01 12:36:08
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 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 102) 0174 previewJob->setOverlayIconAlpha(0); 0175 #endif 0176 previewJob->setScaleType(KIO::PreviewJob::Scaled); 0177 return previewJob; 0178 } 0179 0180 void KImageFilePreview::gotPreview(const KFileItem &item, const QPixmap &pm) 0181 { 0182 if (item.url() != d->currentURL) { // Shouldn't happen 0183 return; 0184 } 0185 0186 if (d->m_timeLine) { 0187 if (d->m_timeLine->state() == QTimeLine::Running) { 0188 d->m_timeLine->setCurrentTime(0); 0189 } 0190 0191 d->m_pmTransition = pm; 0192 d->m_pmTransitionOpacity = 0; 0193 d->m_pmCurrentOpacity = 1; 0194 d->m_timeLine->setDirection(QTimeLine::Forward); 0195 d->m_timeLine->start(); 0196 } else { 0197 d->imageLabel->setPixmap(pm); 0198 } 0199 } 0200 0201 void KImageFilePreviewPrivate::slotFailed(const KFileItem &item) 0202 { 0203 if (item.isDir()) { 0204 imageLabel->clear(); 0205 } else if (item.url() == currentURL) { // should always be the case 0206 imageLabel->setPixmap(QIcon::fromTheme(QStringLiteral("image-missing")).pixmap(KIconLoader::SizeLarge, QIcon::Disabled)); 0207 } 0208 } 0209 0210 void KImageFilePreviewPrivate::slotResult(KJob *job) 0211 { 0212 if (job == m_job) { 0213 m_job = nullptr; 0214 } 0215 } 0216 0217 void KImageFilePreviewPrivate::slotStepAnimation() 0218 { 0219 const QSize currSize = m_pmCurrent.size(); 0220 const QSize transitionSize = m_pmTransition.size(); 0221 const int width = std::max(currSize.width(), transitionSize.width()); 0222 const int height = std::max(currSize.height(), transitionSize.height()); 0223 QPixmap pm(QSize(width, height)); 0224 pm.fill(Qt::transparent); 0225 0226 QPainter p(&pm); 0227 p.setOpacity(m_pmCurrentOpacity); 0228 0229 // If we have a current pixmap 0230 if (!m_pmCurrent.isNull()) { 0231 p.drawPixmap(QPoint(((float)pm.size().width() - m_pmCurrent.size().width()) / 2.0, ((float)pm.size().height() - m_pmCurrent.size().height()) / 2.0), 0232 m_pmCurrent); 0233 } 0234 if (!m_pmTransition.isNull()) { 0235 p.setOpacity(m_pmTransitionOpacity); 0236 p.drawPixmap( 0237 QPoint(((float)pm.size().width() - m_pmTransition.size().width()) / 2.0, ((float)pm.size().height() - m_pmTransition.size().height()) / 2.0), 0238 m_pmTransition); 0239 } 0240 p.end(); 0241 0242 imageLabel->setPixmap(pm); 0243 0244 m_pmCurrentOpacity = qMax(m_pmCurrentOpacity - 0.4, 0.0); // krazy:exclude=qminmax 0245 m_pmTransitionOpacity = qMin(m_pmTransitionOpacity + 0.4, 1.0); // krazy:exclude=qminmax 0246 } 0247 0248 void KImageFilePreviewPrivate::slotFinished() 0249 { 0250 m_pmCurrent = m_pmTransition; 0251 m_pmTransitionOpacity = 0; 0252 m_pmCurrentOpacity = 1; 0253 m_pmTransition = QPixmap(); 0254 // The animation might have lost some frames. Be sure that if the last one 0255 // was dropped, the last image shown is the opaque one. 0256 imageLabel->setPixmap(m_pmCurrent); 0257 clear = false; 0258 } 0259 0260 void KImageFilePreview::clearPreview() 0261 { 0262 if (d->m_job) { 0263 d->m_job->kill(); 0264 d->m_job = nullptr; 0265 } 0266 0267 if (d->clear || (d->m_timeLine && d->m_timeLine->state() == QTimeLine::Running)) { 0268 return; 0269 } 0270 0271 if (d->m_timeLine) { 0272 d->m_pmTransition = QPixmap(); 0273 // If we add a previous preview then we run the animation 0274 if (!d->m_pmCurrent.isNull()) { 0275 d->m_timeLine->setCurrentTime(0); 0276 d->m_timeLine->setDirection(QTimeLine::Backward); 0277 d->m_timeLine->start(); 0278 } 0279 d->currentURL.clear(); 0280 d->clear = true; 0281 } else { 0282 d->imageLabel->clear(); 0283 } 0284 } 0285 0286 #include "moc_kimagefilepreview.cpp"