File indexing completed on 2024-05-19 15:40:35
0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> 0002 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0003 0004 #include "imagemodel.h" 0005 0006 #include "config.h" 0007 #include "consolelog.h" 0008 #include "optimizer.h" 0009 #include <KIO/JobUiDelegateFactory> 0010 #include <KIO/OpenFileManagerWindowJob> 0011 #include <KIO/OpenUrlJob> 0012 #include <KPropertiesDialog> 0013 #include <QDebug> 0014 #include <QFileInfo> 0015 #include <QMimeDatabase> 0016 0017 using namespace Qt::Literals::StringLiterals; 0018 0019 ImageModel::ImageModel(QObject *parent) 0020 : QAbstractListModel(parent) 0021 { 0022 } 0023 0024 QVariant ImageModel::data(const QModelIndex &index, int role) const 0025 { 0026 Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)); 0027 0028 const auto &imageInfo = m_images[index.row()]; 0029 switch (role) { 0030 case Qt::DisplayRole: 0031 return imageInfo.path.fileName(); 0032 case FileNameRole: 0033 return imageInfo.path.toDisplayString(); 0034 case NewSizeRole: 0035 return imageInfo.size != -1 && imageInfo.size != imageInfo.oldSize ? imageInfo.size : -1; 0036 case AlreadyOptimizedRole: 0037 return imageInfo.size != -1 && imageInfo.size == imageInfo.oldSize; 0038 case SizeRole: 0039 return imageInfo.oldSize != -1 ? imageInfo.oldSize : QVariant(); 0040 case ProcessedRole: 0041 return imageInfo.processed; 0042 default: 0043 return {}; 0044 } 0045 } 0046 0047 int ImageModel::rowCount(const QModelIndex &parent) const 0048 { 0049 Q_UNUSED(parent); 0050 return m_images.count(); 0051 } 0052 0053 void ImageModel::addImages(const QList<QUrl> &paths) 0054 { 0055 const auto config = Config::self(); 0056 for (const auto &path : paths) { 0057 beginInsertRows({}, m_images.count(), m_images.count()); 0058 QFileInfo fileInfo(path.toLocalFile()); 0059 QMimeDatabase db; 0060 QMimeType type = db.mimeTypeForFile(path.toLocalFile()); 0061 0062 ImageInfo info; 0063 info.path = path; 0064 if (config->safeMode()) { 0065 info.newPath = QUrl(info.path.toString() + config->suffix()); 0066 } else { 0067 info.newPath = path; 0068 } 0069 info.oldSize = fileInfo.size(); 0070 if (!path.isLocalFile() || !fileInfo.isWritable()) { 0071 info.error = true; 0072 } 0073 0074 if (type.name() == u"image/png"_s) { 0075 info.imageType = ImageType::PNG; 0076 } else if (type.name() == u"image/jpeg"_s) { 0077 info.imageType = ImageType::JPEG; 0078 } else if (type.name() == u"image/svg+xml"_s) { 0079 info.imageType = ImageType::SVG; 0080 } else if (type.name() == u"image/webp"_s) { 0081 info.imageType = ImageType::WEBP; 0082 } else { 0083 info.imageType = ImageType::UNSURPORTED; 0084 } 0085 m_images << info; 0086 endInsertRows(); 0087 } 0088 0089 optimize(); 0090 } 0091 0092 QCoro::Task<> ImageModel::optimize() 0093 { 0094 if (m_running) { 0095 co_return; 0096 } 0097 m_running = true; 0098 Q_EMIT runningChanged(); 0099 0100 bool processedImages = false; 0101 0102 const auto &config = Config::self(); 0103 auto engine = qmlEngine(this); 0104 auto consoleLog = engine->singletonInstance<ConsoleLog *>(u"org.kde.optiimage"_s, u"ConsoleLog"_s); 0105 Q_ASSERT(consoleLog); 0106 0107 int i = 0; 0108 for (auto &image : m_images) { 0109 if (image.processed) { 0110 i++; 0111 continue; 0112 } 0113 image.processed = true; 0114 processedImages = true; 0115 0116 if (image.imageType == ImageType::UNSURPORTED) { 0117 i++; 0118 continue; 0119 } 0120 0121 if (image.imageType == ImageType::PNG) { 0122 co_await optimizePng(config, image, consoleLog); 0123 } else if (image.imageType == ImageType::JPEG) { 0124 co_await optimizeJpeg(config, image, consoleLog); 0125 } else if (image.imageType == ImageType::SVG) { 0126 co_await optimizeSvg(config, image, consoleLog); 0127 } else if (image.imageType == ImageType::WEBP) { 0128 co_await optimizeWebp(config, image, consoleLog); 0129 } 0130 0131 QFileInfo fileInfo(image.newPath.toLocalFile()); 0132 image.size = fileInfo.size(); 0133 image.processed = true; 0134 Q_EMIT dataChanged(index(i, 0), index(i, 0), {NewSizeRole, ProcessedRole, AlreadyOptimizedRole}); 0135 0136 i++; 0137 } 0138 m_running = false; 0139 Q_EMIT runningChanged(); 0140 0141 if (processedImages) { 0142 // In case new images still need to be looped over 0143 optimize(); 0144 } 0145 } 0146 0147 QHash<int, QByteArray> ImageModel::roleNames() const 0148 { 0149 return {{Qt::DisplayRole, QByteArrayLiteral("displayName")}, 0150 {FileNameRole, QByteArrayLiteral("filename")}, 0151 {SizeRole, QByteArrayLiteral("size")}, 0152 {NewSizeRole, QByteArrayLiteral("newSize")}, 0153 {AlreadyOptimizedRole, QByteArrayLiteral("alreadyOptimized")}, 0154 {ProcessedRole, QByteArrayLiteral("processed")}}; 0155 } 0156 0157 bool ImageModel::running() const 0158 { 0159 return m_running; 0160 } 0161 0162 void ImageModel::highlightInFileManager(const QString &url) 0163 { 0164 KIO::highlightInFileManager({QUrl(url)}); 0165 } 0166 0167 void ImageModel::open(const QString &url) 0168 { 0169 auto job = new KIO::OpenUrlJob(QUrl(url)); 0170 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); 0171 job->start(); 0172 } 0173 0174 void ImageModel::openProperties(const QString &url) 0175 { 0176 auto dialog = new KPropertiesDialog({QUrl(url)}); 0177 dialog->setAttribute(Qt::WA_DeleteOnClose); 0178 dialog->setModal(true); 0179 dialog->show(); 0180 }