587 lines
19 KiB
C++
587 lines
19 KiB
C++
// vim: set tabstop=4 shiftwidth=4 expandtab:
|
|
/* Gwenview - A simple image viewer for KDE
|
|
Copyright 2000-2007 Aurélien Gâteau <agateau@kde.org>
|
|
This class is based on the ImagePreviewJob class from Konqueror.
|
|
*/
|
|
/* This file is part of the KDE project
|
|
Copyright (C) 2000 David Faure <faure@kde.org>
|
|
2000 Carsten Pfeiffer <pfeiffer@kde.org>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include "thumbnailprovider.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
// Qt
|
|
#include <QApplication>
|
|
#include <QCryptographicHash>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QStandardPaths>
|
|
#include <QTemporaryFile>
|
|
|
|
// KF
|
|
#include <KIO/FileCopyJob>
|
|
#include <KIO/PreviewJob>
|
|
#include <KIO/StatJob>
|
|
#include <KJobWidgets>
|
|
|
|
// Local
|
|
#include "gwenview_lib_debug.h"
|
|
#include "mimetypeutils.h"
|
|
#include "thumbnailgenerator.h"
|
|
#include "thumbnailwriter.h"
|
|
#include "urlutils.h"
|
|
|
|
namespace Gwenview
|
|
{
|
|
#undef ENABLE_LOG
|
|
#undef LOG
|
|
// #define ENABLE_LOG
|
|
#ifdef ENABLE_LOG
|
|
#define LOG(x) qCDebug(GWENVIEW_LIB_LOG) << x
|
|
#else
|
|
#define LOG(x) ;
|
|
#endif
|
|
|
|
Q_GLOBAL_STATIC(ThumbnailWriter, sThumbnailWriter)
|
|
|
|
static const ThumbnailGroup::Enum s_thumbnailGroups[] = {
|
|
ThumbnailGroup::Normal,
|
|
ThumbnailGroup::Large,
|
|
ThumbnailGroup::XLarge,
|
|
ThumbnailGroup::XXLarge,
|
|
};
|
|
|
|
static QString generateOriginalUri(const QUrl &url_)
|
|
{
|
|
QUrl url = url_;
|
|
return url.adjusted(QUrl::RemovePassword).url();
|
|
}
|
|
|
|
static QString generateThumbnailPath(const QString &uri, ThumbnailGroup::Enum group)
|
|
{
|
|
QString baseDir = ThumbnailProvider::thumbnailBaseDir(group);
|
|
QCryptographicHash md5(QCryptographicHash::Md5);
|
|
md5.addData(QFile::encodeName(uri));
|
|
return baseDir + QFile::encodeName(QString::fromLatin1(md5.result().toHex())) + QStringLiteral(".png");
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//
|
|
// ThumbnailProvider static methods
|
|
//
|
|
//------------------------------------------------------------------------
|
|
static QString sThumbnailBaseDir;
|
|
QString ThumbnailProvider::thumbnailBaseDir()
|
|
{
|
|
if (sThumbnailBaseDir.isEmpty()) {
|
|
const QByteArray customDir = qgetenv("GV_THUMBNAIL_DIR");
|
|
if (customDir.isEmpty()) {
|
|
sThumbnailBaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/thumbnails/");
|
|
} else {
|
|
sThumbnailBaseDir = QFile::decodeName(customDir) + QLatin1Char('/');
|
|
}
|
|
}
|
|
return sThumbnailBaseDir;
|
|
}
|
|
|
|
void ThumbnailProvider::setThumbnailBaseDir(const QString &dir)
|
|
{
|
|
sThumbnailBaseDir = dir;
|
|
}
|
|
|
|
QString ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Enum group)
|
|
{
|
|
QString dir = thumbnailBaseDir();
|
|
switch (group) {
|
|
case ThumbnailGroup::Normal:
|
|
dir += QStringLiteral("normal/");
|
|
break;
|
|
case ThumbnailGroup::Large:
|
|
dir += QStringLiteral("large/");
|
|
break;
|
|
case ThumbnailGroup::XLarge:
|
|
dir += QStringLiteral("x-large/");
|
|
break;
|
|
case ThumbnailGroup::XXLarge:
|
|
dir += QStringLiteral("xx-large/");
|
|
break;
|
|
default:
|
|
dir += QLatin1String("x-gwenview/"); // Should never be hit, but just in case
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
void ThumbnailProvider::deleteImageThumbnail(const QUrl &url)
|
|
{
|
|
QString uri = generateOriginalUri(url);
|
|
for (auto group : s_thumbnailGroups) {
|
|
QFile::remove(generateThumbnailPath(uri, group));
|
|
}
|
|
}
|
|
|
|
static void moveThumbnailHelper(const QString &oldUri, const QString &newUri, ThumbnailGroup::Enum group)
|
|
{
|
|
QString oldPath = generateThumbnailPath(oldUri, group);
|
|
QString newPath = generateThumbnailPath(newUri, group);
|
|
QImage thumb;
|
|
if (!thumb.load(oldPath)) {
|
|
return;
|
|
}
|
|
thumb.setText(QStringLiteral("Thumb::URI"), newUri);
|
|
thumb.save(newPath, "png");
|
|
QFile::remove(QFile::encodeName(oldPath));
|
|
}
|
|
|
|
void ThumbnailProvider::moveThumbnail(const QUrl &oldUrl, const QUrl &newUrl)
|
|
{
|
|
QString oldUri = generateOriginalUri(oldUrl);
|
|
QString newUri = generateOriginalUri(newUrl);
|
|
for (auto group : s_thumbnailGroups) {
|
|
moveThumbnailHelper(oldUri, newUri, group);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//
|
|
// ThumbnailProvider implementation
|
|
//
|
|
//------------------------------------------------------------------------
|
|
ThumbnailProvider::ThumbnailProvider()
|
|
: KIO::Job()
|
|
, mState(STATE_NEXTTHUMB)
|
|
, mOriginalTime(0)
|
|
{
|
|
LOG(this);
|
|
|
|
// Make sure we have a place to store our thumbnails
|
|
for (auto group : s_thumbnailGroups) {
|
|
const QString thumbnailDir = ThumbnailProvider::thumbnailBaseDir(group);
|
|
QDir().mkpath(thumbnailDir);
|
|
QFile::setPermissions(thumbnailDir, QFileDevice::WriteOwner | QFileDevice::ReadOwner | QFileDevice::ExeOwner);
|
|
}
|
|
|
|
// Look for images and store the items in our todo list
|
|
mCurrentItem = KFileItem();
|
|
mThumbnailGroup = ThumbnailGroup::XXLarge;
|
|
createNewThumbnailGenerator();
|
|
}
|
|
|
|
ThumbnailProvider::~ThumbnailProvider()
|
|
{
|
|
LOG(this);
|
|
disconnect(mThumbnailGenerator, nullptr, this, nullptr);
|
|
disconnect(mThumbnailGenerator, nullptr, sThumbnailWriter, nullptr);
|
|
abortSubjob();
|
|
mThumbnailGenerator->cancel();
|
|
if (mPreviousThumbnailGenerator) {
|
|
disconnect(mPreviousThumbnailGenerator, nullptr, sThumbnailWriter, nullptr);
|
|
}
|
|
sThumbnailWriter->requestInterruption();
|
|
sThumbnailWriter->wait();
|
|
}
|
|
|
|
void ThumbnailProvider::stop()
|
|
{
|
|
// Clear mItems and create a new ThumbnailGenerator if mThumbnailGenerator is running,
|
|
// but also make sure that at most two ThumbnailGenerators are running.
|
|
// startCreatingThumbnail() will take care that these two threads won't work on the same item.
|
|
mItems.clear();
|
|
abortSubjob();
|
|
if (!mThumbnailGenerator->isStopped() && !mPreviousThumbnailGenerator) {
|
|
mPreviousThumbnailGenerator = mThumbnailGenerator;
|
|
mPreviousThumbnailGenerator->cancel();
|
|
disconnect(mPreviousThumbnailGenerator, nullptr, this, nullptr);
|
|
connect(mPreviousThumbnailGenerator, SIGNAL(finished()), mPreviousThumbnailGenerator, SLOT(deleteLater()));
|
|
createNewThumbnailGenerator();
|
|
mCurrentItem = KFileItem();
|
|
}
|
|
}
|
|
|
|
const KFileItemList &ThumbnailProvider::pendingItems() const
|
|
{
|
|
return mItems;
|
|
}
|
|
|
|
void ThumbnailProvider::setThumbnailGroup(ThumbnailGroup::Enum group)
|
|
{
|
|
mThumbnailGroup = group;
|
|
}
|
|
|
|
void ThumbnailProvider::appendItems(const KFileItemList &items)
|
|
{
|
|
if (!mItems.isEmpty()) {
|
|
QSet<KFileItem> itemSet{mItems.begin(), mItems.end()};
|
|
|
|
for (const KFileItem &item : items) {
|
|
if (!itemSet.contains(item)) {
|
|
mItems.append(item);
|
|
}
|
|
}
|
|
} else {
|
|
mItems = items;
|
|
}
|
|
|
|
if (mCurrentItem.isNull()) {
|
|
determineNextIcon();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::removeItems(const KFileItemList &itemList)
|
|
{
|
|
if (mItems.isEmpty()) {
|
|
return;
|
|
}
|
|
for (const KFileItem &item : itemList) {
|
|
// If we are removing the next item, update to be the item after or the
|
|
// first if we removed the last item
|
|
mItems.removeAll(item);
|
|
|
|
if (item == mCurrentItem) {
|
|
abortSubjob();
|
|
}
|
|
}
|
|
|
|
// No more current item, carry on to the next remaining item
|
|
if (mCurrentItem.isNull()) {
|
|
determineNextIcon();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::removePendingItems()
|
|
{
|
|
mItems.clear();
|
|
}
|
|
|
|
bool ThumbnailProvider::isRunning() const
|
|
{
|
|
return !mCurrentItem.isNull();
|
|
}
|
|
|
|
//-Internal--------------------------------------------------------------
|
|
void ThumbnailProvider::createNewThumbnailGenerator()
|
|
{
|
|
mThumbnailGenerator = new ThumbnailGenerator;
|
|
connect(mThumbnailGenerator, SIGNAL(done(QImage, QSize)), SLOT(thumbnailReady(QImage, QSize)), Qt::QueuedConnection);
|
|
|
|
connect(mThumbnailGenerator,
|
|
SIGNAL(thumbnailReadyToBeCached(QString, QImage)),
|
|
sThumbnailWriter,
|
|
SLOT(queueThumbnail(QString, QImage)),
|
|
Qt::QueuedConnection);
|
|
}
|
|
|
|
void ThumbnailProvider::abortSubjob()
|
|
{
|
|
if (hasSubjobs()) {
|
|
LOG("Killing subjob");
|
|
KJob *job = subjobs().first();
|
|
job->kill();
|
|
removeSubjob(job);
|
|
mCurrentItem = KFileItem();
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::determineNextIcon()
|
|
{
|
|
LOG(this);
|
|
mState = STATE_NEXTTHUMB;
|
|
|
|
// No more items ?
|
|
if (mItems.isEmpty()) {
|
|
LOG("No more items. Nothing to do");
|
|
mCurrentItem = KFileItem();
|
|
Q_EMIT finished();
|
|
return;
|
|
}
|
|
|
|
mCurrentItem = mItems.takeFirst();
|
|
LOG("mCurrentItem.url=" << mCurrentItem.url());
|
|
|
|
// First, stat the orig file
|
|
mState = STATE_STATORIG;
|
|
mCurrentUrl = mCurrentItem.url().adjusted(QUrl::NormalizePathSegments);
|
|
mOriginalFileSize = mCurrentItem.size();
|
|
|
|
// Do direct stat instead of using KIO if the file is local (faster)
|
|
if (UrlUtils::urlIsFastLocalFile(mCurrentUrl)) {
|
|
QFileInfo fileInfo(mCurrentUrl.toLocalFile());
|
|
mOriginalTime = fileInfo.lastModified().toSecsSinceEpoch();
|
|
QMetaObject::invokeMethod(this, &ThumbnailProvider::checkThumbnail, Qt::QueuedConnection);
|
|
} else {
|
|
KIO::Job *job = KIO::stat(mCurrentUrl, KIO::HideProgressInfo);
|
|
KJobWidgets::setWindow(job, qApp->activeWindow());
|
|
LOG("KIO::stat orig" << mCurrentUrl.url());
|
|
addSubjob(job);
|
|
}
|
|
LOG("/determineNextIcon" << this);
|
|
}
|
|
|
|
void ThumbnailProvider::slotResult(KJob *job)
|
|
{
|
|
LOG(mState);
|
|
removeSubjob(job);
|
|
Q_ASSERT(subjobs().isEmpty()); // We should have only one job at a time
|
|
|
|
switch (mState) {
|
|
case STATE_NEXTTHUMB:
|
|
Q_ASSERT(false);
|
|
determineNextIcon();
|
|
return;
|
|
|
|
case STATE_STATORIG: {
|
|
// Could not stat original, drop this one and move on to the next one
|
|
if (job->error()) {
|
|
emitThumbnailLoadingFailed();
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
// Get modification time of the original file
|
|
KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
|
|
mOriginalTime = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
|
|
checkThumbnail();
|
|
return;
|
|
}
|
|
|
|
case STATE_DOWNLOADORIG:
|
|
if (job->error()) {
|
|
emitThumbnailLoadingFailed();
|
|
LOG("Delete temp file" << mTempPath);
|
|
QFile::remove(mTempPath);
|
|
mTempPath.clear();
|
|
determineNextIcon();
|
|
} else {
|
|
startCreatingThumbnail(mTempPath);
|
|
}
|
|
return;
|
|
|
|
case STATE_PREVIEWJOB:
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::thumbnailReady(const QImage &_img, const QSize &_size)
|
|
{
|
|
QImage img = _img;
|
|
QSize size = _size;
|
|
if (!img.isNull()) {
|
|
emitThumbnailLoaded(img, size);
|
|
} else {
|
|
emitThumbnailLoadingFailed();
|
|
}
|
|
if (!mTempPath.isEmpty()) {
|
|
LOG("Delete temp file" << mTempPath);
|
|
QFile::remove(mTempPath);
|
|
mTempPath.clear();
|
|
}
|
|
determineNextIcon();
|
|
}
|
|
|
|
QImage ThumbnailProvider::loadThumbnailFromCache() const
|
|
{
|
|
if (mThumbnailGroup > ThumbnailGroup::XXLarge) {
|
|
return {};
|
|
}
|
|
|
|
QImage image = sThumbnailWriter->value(mThumbnailPath);
|
|
if (!image.isNull()) {
|
|
return image;
|
|
}
|
|
|
|
if (!QFileInfo::exists(mThumbnailPath)) {
|
|
return {};
|
|
}
|
|
|
|
image = QImage(mThumbnailPath);
|
|
int largeThumbnailGroup = mThumbnailGroup;
|
|
while (image.isNull() && ++largeThumbnailGroup <= ThumbnailGroup::XXLarge) {
|
|
// If there is a large-sized thumbnail, generate the small-sized version from it
|
|
const QString largeThumbnailPath = generateThumbnailPath(mOriginalUri, static_cast<ThumbnailGroup::Enum>(largeThumbnailGroup));
|
|
const QImage largeImage(largeThumbnailPath);
|
|
if (!largeImage.isNull()) {
|
|
const int size = ThumbnailGroup::pixelSize(mThumbnailGroup);
|
|
image = largeImage.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
const QStringList textKeys = largeImage.textKeys();
|
|
for (const QString &key : textKeys) {
|
|
QString text = largeImage.text(key);
|
|
image.setText(key, text);
|
|
}
|
|
sThumbnailWriter->queueThumbnail(mThumbnailPath, image);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
void ThumbnailProvider::checkThumbnail()
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
// If we are in the thumbnail dir, just load the file
|
|
if (mCurrentUrl.isLocalFile() && mCurrentUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().startsWith(thumbnailBaseDir())) {
|
|
QImage image(mCurrentUrl.toLocalFile());
|
|
emitThumbnailLoaded(image, image.size());
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
|
|
mOriginalUri = generateOriginalUri(mCurrentUrl);
|
|
mThumbnailPath = generateThumbnailPath(mOriginalUri, mThumbnailGroup);
|
|
|
|
LOG("Stat thumb" << mThumbnailPath);
|
|
|
|
QImage thumb = loadThumbnailFromCache();
|
|
KIO::filesize_t fileSize = thumb.text(QStringLiteral("Thumb::Size")).toULongLong();
|
|
if (!thumb.isNull()) {
|
|
if (thumb.text(QStringLiteral("Thumb::URI")) == mOriginalUri && thumb.text(QStringLiteral("Thumb::MTime")).toInt() == mOriginalTime
|
|
&& (fileSize == 0 || fileSize == mOriginalFileSize)) {
|
|
int width = 0, height = 0;
|
|
QSize size;
|
|
bool ok;
|
|
|
|
width = thumb.text(QStringLiteral("Thumb::Image::Width")).toInt(&ok);
|
|
if (ok)
|
|
height = thumb.text(QStringLiteral("Thumb::Image::Height")).toInt(&ok);
|
|
if (ok) {
|
|
size = QSize(width, height);
|
|
} else {
|
|
LOG("Thumbnail for" << mOriginalUri << "does not contain correct image size information");
|
|
// Don't try to determine the size of a video, it probably won't work and
|
|
// will cause high I/O usage with big files (bug #307007).
|
|
if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) {
|
|
emitThumbnailLoaded(thumb, QSize());
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
}
|
|
emitThumbnailLoaded(thumb, size);
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Thumbnail not found or not valid
|
|
if (MimeTypeUtils::fileItemKind(mCurrentItem) == MimeTypeUtils::KIND_RASTER_IMAGE) {
|
|
if (mCurrentUrl.isLocalFile()) {
|
|
// Original is a local file, create the thumbnail
|
|
startCreatingThumbnail(mCurrentUrl.toLocalFile());
|
|
} else {
|
|
// Original is remote, download it
|
|
mState = STATE_DOWNLOADORIG;
|
|
|
|
QTemporaryFile tempFile;
|
|
tempFile.setAutoRemove(false);
|
|
if (!tempFile.open()) {
|
|
qCWarning(GWENVIEW_LIB_LOG) << "Couldn't create temp file to download " << mCurrentUrl.toDisplayString();
|
|
emitThumbnailLoadingFailed();
|
|
determineNextIcon();
|
|
return;
|
|
}
|
|
mTempPath = tempFile.fileName();
|
|
|
|
QUrl url = QUrl::fromLocalFile(mTempPath);
|
|
KIO::Job *job = KIO::file_copy(mCurrentUrl, url, -1, KIO::Overwrite | KIO::HideProgressInfo);
|
|
KJobWidgets::setWindow(job, qApp->activeWindow());
|
|
LOG("Download remote file" << mCurrentUrl.toDisplayString() << "to" << url.toDisplayString());
|
|
addSubjob(job);
|
|
}
|
|
} else {
|
|
// Not a raster image, use a KPreviewJob
|
|
LOG("Starting a KPreviewJob for" << mCurrentItem.url());
|
|
mState = STATE_PREVIEWJOB;
|
|
KFileItemList list;
|
|
list.append(mCurrentItem);
|
|
const int pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup);
|
|
if (mPreviewPlugins.isEmpty()) {
|
|
mPreviewPlugins = KIO::PreviewJob::availablePlugins();
|
|
}
|
|
KIO::Job *job = KIO::filePreview(list, QSize(pixelSize, pixelSize), &mPreviewPlugins);
|
|
// KJobWidgets::setWindow(job, qApp->activeWindow());
|
|
connect(job, SIGNAL(gotPreview(KFileItem, QPixmap)), this, SLOT(slotGotPreview(KFileItem, QPixmap)));
|
|
connect(job, SIGNAL(failed(KFileItem)), this, SLOT(emitThumbnailLoadingFailed()));
|
|
addSubjob(job);
|
|
}
|
|
}
|
|
|
|
void ThumbnailProvider::startCreatingThumbnail(const QString &pixPath)
|
|
{
|
|
LOG("Creating thumbnail from" << pixPath);
|
|
// If mPreviousThumbnailGenerator is already working on our current item
|
|
// its thumbnail will be passed to sThumbnailWriter when ready. So we
|
|
// connect mPreviousThumbnailGenerator's signal "finished" to determineNextIcon
|
|
// which will load the thumbnail from sThumbnailWriter or from disk
|
|
// (because we re-add mCurrentItem to mItems).
|
|
if (mPreviousThumbnailGenerator && !mPreviousThumbnailGenerator->isStopped() && mOriginalUri == mPreviousThumbnailGenerator->originalUri()
|
|
&& mOriginalTime == mPreviousThumbnailGenerator->originalTime() && mOriginalFileSize == mPreviousThumbnailGenerator->originalFileSize()
|
|
&& mCurrentItem.mimetype() == mPreviousThumbnailGenerator->originalMimeType()) {
|
|
connect(mPreviousThumbnailGenerator, SIGNAL(finished()), SLOT(determineNextIcon()));
|
|
mItems.prepend(mCurrentItem);
|
|
return;
|
|
}
|
|
mThumbnailGenerator->load(mOriginalUri, mOriginalTime, mOriginalFileSize, mCurrentItem.mimetype(), pixPath, mThumbnailPath, mThumbnailGroup);
|
|
}
|
|
|
|
void ThumbnailProvider::slotGotPreview(const KFileItem &item, const QPixmap &pixmap)
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
QSize size;
|
|
Q_EMIT thumbnailLoaded(item, pixmap, size, mOriginalFileSize);
|
|
}
|
|
|
|
void ThumbnailProvider::emitThumbnailLoaded(const QImage &img, const QSize &size)
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
QPixmap thumb = QPixmap::fromImage(img);
|
|
Q_EMIT thumbnailLoaded(mCurrentItem, thumb, size, mOriginalFileSize);
|
|
}
|
|
|
|
void ThumbnailProvider::emitThumbnailLoadingFailed()
|
|
{
|
|
if (mCurrentItem.isNull()) {
|
|
// This can happen if current item has been removed by removeItems()
|
|
return;
|
|
}
|
|
LOG(mCurrentItem.url());
|
|
Q_EMIT thumbnailLoadingFailed(mCurrentItem);
|
|
}
|
|
|
|
bool ThumbnailProvider::isThumbnailWriterEmpty()
|
|
{
|
|
return sThumbnailWriter->isEmpty();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#include "moc_thumbnailprovider.cpp"
|