You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

213 lines
6.4 KiB

/*
SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "abstractimagelistmodel.h"
#include <QPainter>
#include <QThreadPool>
#include <KFileItem>
#include <KIO/PreviewJob>
#include "../finder/mediametadatafinder.h"
#include "config-KF5KExiv2.h"
AbstractImageListModel::AbstractImageListModel(const QSize &targetSize, QObject *parent)
: QAbstractListModel(parent)
, m_screenshotSize(targetSize / 8)
, m_targetSize(targetSize)
{
constexpr int maxCacheSize = 30;
m_imageCache.setMaxCost(maxCacheSize);
m_backgroundTitleCache.setMaxCost(maxCacheSize);
m_backgroundAuthorCache.setMaxCost(maxCacheSize);
m_imageSizeCache.setMaxCost(maxCacheSize);
connect(this, &QAbstractListModel::rowsInserted, this, &AbstractImageListModel::countChanged);
connect(this, &QAbstractListModel::rowsRemoved, this, &AbstractImageListModel::countChanged);
connect(this, &QAbstractListModel::modelReset, this, &AbstractImageListModel::countChanged);
}
QHash<int, QByteArray> AbstractImageListModel::roleNames() const
{
return {
{Qt::DisplayRole, "display"},
{Qt::DecorationRole, "decoration"},
{AuthorRole, "author"},
{ScreenshotRole, "screenshot"},
{PathRole, "path"},
{PackageNameRole, "packageName"},
{RemovableRole, "removable"},
{PendingDeletionRole, "pendingDeletion"},
{ToggleRole, "checked"},
};
}
int AbstractImageListModel::count() const
{
return rowCount();
}
void AbstractImageListModel::reload()
{
if (m_loading || m_customPaths.empty()) {
return;
}
load(m_customPaths);
}
void AbstractImageListModel::slotTargetSizeChanged(const QSize &size)
{
m_targetSize = size;
reload();
}
void AbstractImageListModel::slotHandlePreview(const KFileItem &item, const QPixmap &preview)
{
auto job = qobject_cast<KIO::PreviewJob *>(sender());
if (!job) {
return;
}
const QString urlString = item.url().toLocalFile();
const QPersistentModelIndex idx = job->property("index").toPersistentModelIndex();
auto it = m_previewJobsUrls.find(idx);
Q_ASSERT(it != m_previewJobsUrls.end());
it->removeOne(urlString);
const QStringList paths = job->property("paths").toStringList();
QPixmap *cachedPreview = m_imageCache.object(paths);
if (!cachedPreview) {
// Insert full preview
m_imageCache.insert(paths, new QPixmap(preview), 0);
} else {
// Show multiple images side by side
QPainter p(cachedPreview);
const int i = paths.indexOf(urlString);
const double start = i / static_cast<double>(paths.size());
const double end = (i + 1) / static_cast<double>(paths.size());
// Cropped area
const QPoint topLeft(start * preview.width(), 0);
const QPoint bottomRight(end * preview.width(), preview.height());
// Inserted area
const QPoint topLeft2(start * cachedPreview->width(), 0);
const QPoint bottomRight2(end * cachedPreview->width(), cachedPreview->height());
p.drawPixmap(QRect(topLeft2, bottomRight2), preview.copy(QRect(topLeft, bottomRight)));
}
if (it->empty()) {
// All images in the list have been loaded
m_previewJobsUrls.erase(it);
cachedPreview = m_imageCache.object(paths);
auto finalPreview = new QPixmap(*cachedPreview);
if (m_imageCache.insert(paths, finalPreview, 1)) {
Q_EMIT dataChanged(idx, idx, {ScreenshotRole});
} else {
delete finalPreview;
}
}
}
void AbstractImageListModel::slotHandlePreviewFailed(const KFileItem &item)
{
auto job = qobject_cast<KIO::PreviewJob *>(sender());
if (!job) {
return;
}
auto it = m_previewJobsUrls.find(job->property("index").toPersistentModelIndex());
Q_ASSERT(it != m_previewJobsUrls.end());
it->removeOne(item.url().toLocalFile());
if (it->empty()) {
m_previewJobsUrls.erase(it);
}
}
void AbstractImageListModel::asyncGetPreview(const QStringList &paths, const QPersistentModelIndex &index) const
{
if (m_previewJobsUrls.contains(index) || paths.isEmpty()) {
return;
}
const QStringList availablePlugins = KIO::PreviewJob::availablePlugins();
KFileItemList list;
for (const QString &path : paths) {
list.append(KFileItem(QUrl::fromLocalFile(path), QString(), 0));
}
KIO::PreviewJob *const job = KIO::filePreview(list, m_screenshotSize, &availablePlugins);
job->setIgnoreMaximumSize(true);
job->setProperty("paths", paths);
job->setProperty("index", index);
connect(job, &KIO::PreviewJob::gotPreview, this, &AbstractImageListModel::slotHandlePreview);
connect(job, &KIO::PreviewJob::failed, this, &AbstractImageListModel::slotHandlePreviewFailed);
m_previewJobsUrls.insert(index, paths);
}
void AbstractImageListModel::asyncGetMediaMetadata(const QString &path, const QPersistentModelIndex &index) const
{
if (m_sizeJobsUrls.contains(path) || path.isEmpty()) {
return;
}
MediaMetadataFinder *finder = new MediaMetadataFinder(path);
connect(finder, &MediaMetadataFinder::metadataFound, this, &AbstractImageListModel::slotMediaMetadataFound);
QThreadPool::globalInstance()->start(finder);
m_sizeJobsUrls.insert(path, index);
}
void AbstractImageListModel::clearCache()
{
m_imageCache.clear();
m_backgroundTitleCache.clear();
m_backgroundAuthorCache.clear();
m_imageSizeCache.clear();
}
void AbstractImageListModel::slotMediaMetadataFound(const QString &path, const MediaMetadata &metadata)
{
const QPersistentModelIndex index = m_sizeJobsUrls.take(path);
#if HAVE_KF5KExiv2
if (!metadata.title.isEmpty()) {
auto title = new QString(metadata.title);
if (m_backgroundTitleCache.insert(path, title, 1)) {
Q_EMIT dataChanged(index, index, {Qt::DisplayRole});
} else {
delete title;
}
}
if (!metadata.author.isEmpty()) {
auto author = new QString(metadata.author);
if (m_backgroundAuthorCache.insert(path, author, 1)) {
Q_EMIT dataChanged(index, index, {AuthorRole});
} else {
delete author;
}
}
#endif
auto resolution = new QSize(metadata.resolution);
if (m_imageSizeCache.insert(path, resolution, 1)) {
Q_EMIT dataChanged(index, index, {ResolutionRole});
} else {
delete resolution;
}
}