File indexing completed on 2024-05-12 15:54:51

0001 /*
0002  * SPDX-FileCopyrightText: (C) 2014 Vishesh Handa <vhanda@kde.org>
0003  * SPDX-FileCopyrightText: (C) 2017 Atul Sharma <atulsharma406@gmail.com>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 
0008 #include "sortmodel.h"
0009 #include "roles.h"
0010 #include "types.h"
0011 #include <QDebug>
0012 #include <QIcon>
0013 #include <QTimer>
0014 
0015 #include <KIO/RestoreJob>
0016 #include <kimagecache.h>
0017 #include <kio/copyjob.h>
0018 #include <kio/previewjob.h>
0019 
0020 SortModel::SortModel(QObject *parent)
0021     : QSortFilterProxyModel(parent)
0022     , m_screenshotSize(256, 256)
0023     , m_containImages(false)
0024 {
0025     setSortLocaleAware(true);
0026     sort(0);
0027     m_selectionModel = new QItemSelectionModel(this);
0028 
0029     m_previewTimer = new QTimer(this);
0030     m_previewTimer->setSingleShot(true);
0031     connect(m_previewTimer, &QTimer::timeout, this, &SortModel::delayedPreview);
0032 
0033     connect(this, &SortModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) {
0034         Q_UNUSED(parent)
0035         for (int i = first; i <= last; i++) {
0036             if (Types::Image == data(index(i, 0, QModelIndex()), Roles::ItemTypeRole).toInt() && m_containImages == false) {
0037                 setContainImages(true);
0038                 break;
0039             }
0040         }
0041     });
0042 
0043     connect(this, &SortModel::sourceModelChanged, this, [this]() {
0044         if (!sourceModel()) {
0045             return;
0046         }
0047         for (int i = 0; i < sourceModel()->rowCount(); i++) {
0048             const auto itemType = sourceModel()->data(sourceModel()->index(i, 0, {}), Roles::ItemTypeRole).toInt();
0049             if (Types::Image == itemType && m_containImages == false) {
0050                 setContainImages(true);
0051                 break;
0052             }
0053         }
0054     });
0055 
0056     // using the same cache of the engine, they index both by url
0057     m_imageCache = new KImageCache(QStringLiteral("org.kde.koko"), 10485760);
0058 }
0059 
0060 SortModel::~SortModel()
0061 {
0062     delete m_imageCache;
0063 }
0064 
0065 void SortModel::setContainImages(bool value)
0066 {
0067     m_containImages = value;
0068     emit containImagesChanged();
0069 }
0070 
0071 QByteArray SortModel::sortRoleName() const
0072 {
0073     int role = sortRole();
0074     return roleNames().value(role);
0075 }
0076 
0077 void SortModel::setSortRoleName(const QByteArray &name)
0078 {
0079     if (!sourceModel()) {
0080         m_sortRoleName = name;
0081         return;
0082     }
0083 
0084     const QHash<int, QByteArray> roles = sourceModel()->roleNames();
0085     for (auto it = roles.begin(); it != roles.end(); it++) {
0086         if (it.value() == name) {
0087             setSortRole(it.key());
0088             return;
0089         }
0090     }
0091     qDebug() << "Sort role" << name << "not found";
0092 }
0093 
0094 QHash<int, QByteArray> SortModel::roleNames() const
0095 {
0096     QHash<int, QByteArray> hash = sourceModel()->roleNames();
0097     hash.insert(Roles::SelectedRole, "selected");
0098     hash.insert(Roles::Thumbnail, "thumbnail");
0099     hash.insert(Roles::SourceIndex, "sourceIndex");
0100     return hash;
0101 }
0102 
0103 QVariant SortModel::data(const QModelIndex &index, int role) const
0104 {
0105     if (!index.isValid()) {
0106         return {};
0107     }
0108 
0109     switch (role) {
0110     case Roles::SelectedRole: {
0111         return m_selectionModel->isSelected(index);
0112     }
0113 
0114     case Roles::Thumbnail: {
0115         QUrl thumbnailSource(QString(/*"file://" + */ data(index, Roles::ImageUrlRole).toString()));
0116 
0117         KFileItem item(thumbnailSource, QString());
0118         QImage preview = QImage(m_screenshotSize, QImage::Format_ARGB32_Premultiplied);
0119 
0120         if (m_imageCache->findImage(item.url().toString(), &preview)) {
0121             return preview;
0122         }
0123 
0124         m_previewTimer->start(100);
0125         const_cast<SortModel *>(this)->m_filesToPreview[item.url()] = QPersistentModelIndex(index);
0126         return {};
0127     }
0128 
0129     case Roles::SourceIndex: {
0130         return mapToSource(index).row();
0131     }
0132     }
0133 
0134     return QSortFilterProxyModel::data(index, role);
0135 }
0136 
0137 bool SortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
0138 {
0139     if (sourceModel()) {
0140         if ((sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder && sourceModel()->data(source_right, Roles::ItemTypeRole) == Types::Folder)
0141             || (sourceModel()->data(source_left, Roles::ItemTypeRole) != Types::Folder
0142                 && sourceModel()->data(source_right, Roles::ItemTypeRole) != Types::Folder)) {
0143             return QSortFilterProxyModel::lessThan(source_left, source_right);
0144         } else if (sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder
0145                    && sourceModel()->data(source_right, Roles::ItemTypeRole) != Types::Folder) {
0146             return true;
0147         } else {
0148             return false;
0149         }
0150     }
0151 
0152     return false;
0153 }
0154 
0155 void SortModel::setSourceModel(QAbstractItemModel *sourceModel)
0156 {
0157     QSortFilterProxyModel::setSourceModel(sourceModel);
0158 
0159     if (!m_sortRoleName.isEmpty()) {
0160         setSortRoleName(m_sortRoleName);
0161         m_sortRoleName.clear();
0162     }
0163 }
0164 
0165 bool SortModel::containImages()
0166 {
0167     return m_containImages;
0168 }
0169 
0170 bool SortModel::hasSelectedImages()
0171 {
0172     return m_selectionModel->hasSelection();
0173 }
0174 
0175 void SortModel::setSelected(int indexValue)
0176 {
0177     if (indexValue < 0)
0178         return;
0179 
0180     QModelIndex index = QSortFilterProxyModel::index(indexValue, 0);
0181     m_selectionModel->select(index, QItemSelectionModel::Select);
0182     emit dataChanged(index, index);
0183     emit selectedImagesChanged();
0184 }
0185 
0186 void SortModel::toggleSelected(int indexValue)
0187 {
0188     if (indexValue < 0)
0189         return;
0190 
0191     QModelIndex index = QSortFilterProxyModel::index(indexValue, 0);
0192     m_selectionModel->select(index, QItemSelectionModel::Toggle);
0193     emit dataChanged(index, index);
0194     emit selectedImagesChanged();
0195 }
0196 
0197 void SortModel::clearSelections()
0198 {
0199     if (m_selectionModel->hasSelection()) {
0200         QModelIndexList selectedIndex = m_selectionModel->selectedIndexes();
0201         m_selectionModel->clear();
0202         for (auto indexValue : selectedIndex) {
0203             emit dataChanged(indexValue, indexValue);
0204         }
0205     }
0206     emit selectedImagesChanged();
0207 }
0208 
0209 void SortModel::selectAll()
0210 {
0211     QModelIndexList indexList;
0212     for (int row = 0; row < rowCount(); row++) {
0213         indexList.append(index(row, 0, QModelIndex()));
0214     }
0215 
0216     if (m_selectionModel->hasSelection()) {
0217         m_selectionModel->clear();
0218     }
0219 
0220     for (auto index : indexList) {
0221         if (Types::Image == data(index, Roles::ItemTypeRole))
0222             m_selectionModel->select(index, QItemSelectionModel::Select);
0223     }
0224     emit dataChanged(index(0, 0, QModelIndex()), index(rowCount() - 1, 0, QModelIndex()));
0225     emit selectedImagesChanged();
0226 }
0227 
0228 void SortModel::deleteSelection()
0229 {
0230     QList<QUrl> filesToDelete;
0231 
0232     for (auto index : m_selectionModel->selectedIndexes()) {
0233         filesToDelete << data(index, Roles::ImageUrlRole).toUrl();
0234     }
0235 
0236     auto trashJob = KIO::trash(filesToDelete);
0237     trashJob->exec();
0238 }
0239 
0240 void SortModel::restoreSelection()
0241 {
0242     QList<QUrl> filesToRestore;
0243 
0244     foreach (QModelIndex index, m_selectionModel->selectedIndexes()) {
0245         filesToRestore << data(index, Roles::ImageUrlRole).toUrl();
0246     }
0247 
0248     auto restoreJob = KIO::restoreFromTrash(filesToRestore);
0249     restoreJob->exec();
0250 }
0251 
0252 int SortModel::proxyIndex(const int &indexValue)
0253 {
0254     if (sourceModel()) {
0255         return mapFromSource(sourceModel()->index(indexValue, 0, QModelIndex())).row();
0256     }
0257     return -1;
0258 }
0259 
0260 int SortModel::sourceIndex(const int &indexValue)
0261 {
0262     return mapToSource(index(indexValue, 0, QModelIndex())).row();
0263 }
0264 
0265 QJsonArray SortModel::selectedImages()
0266 {
0267     QJsonArray arr;
0268 
0269     for (auto index : m_selectionModel->selectedIndexes()) {
0270         arr.push_back(QJsonValue(data(index, Roles::ImageUrlRole).toString()));
0271     }
0272 
0273     return arr;
0274 }
0275 
0276 QJsonArray SortModel::selectedImagesMimeTypes()
0277 {
0278     QJsonArray arr;
0279 
0280     for (auto index : m_selectionModel->selectedIndexes()) {
0281         if (!arr.contains(QJsonValue(data(index, Roles::MimeTypeRole).toString()))) {
0282             arr.push_back(QJsonValue(data(index, Roles::MimeTypeRole).toString()));
0283         }
0284     }
0285 
0286     return arr;
0287 }
0288 
0289 int SortModel::indexForUrl(const QString &url)
0290 {
0291     QModelIndexList indexList;
0292     for (int row = 0; row < rowCount(); row++) {
0293         indexList.append(index(row, 0, QModelIndex()));
0294     }
0295     for (auto index : indexList) {
0296         if (url == data(index, Roles::ImageUrlRole).toString()) {
0297             return index.row();
0298         }
0299     }
0300     return -1;
0301 }
0302 
0303 void SortModel::delayedPreview()
0304 {
0305     QHash<QUrl, QPersistentModelIndex>::const_iterator i = m_filesToPreview.constBegin();
0306 
0307     KFileItemList list;
0308 
0309     while (i != m_filesToPreview.constEnd()) {
0310         QUrl file = i.key();
0311         QPersistentModelIndex index = i.value();
0312 
0313         if (!m_previewJobs.contains(file) && file.isValid()) {
0314             list.append(KFileItem(file, QString(), 0));
0315             m_previewJobs.insert(file, QPersistentModelIndex(index));
0316         }
0317 
0318         ++i;
0319     }
0320 
0321     if (list.size() > 0) {
0322         const auto pluginLists = KIO::PreviewJob::availablePlugins();
0323         KIO::PreviewJob *job = KIO::filePreview(list, m_screenshotSize, &pluginLists);
0324         job->setIgnoreMaximumSize(true);
0325         connect(job, &KIO::PreviewJob::gotPreview, this, &SortModel::showPreview);
0326         connect(job, &KIO::PreviewJob::failed, this, &SortModel::previewFailed);
0327     }
0328 
0329     m_filesToPreview.clear();
0330 }
0331 
0332 void SortModel::showPreview(const KFileItem &item, const QPixmap &preview)
0333 {
0334     QPersistentModelIndex index = m_previewJobs.value(item.url());
0335     m_previewJobs.remove(item.url());
0336 
0337     if (!index.isValid()) {
0338         return;
0339     }
0340 
0341     m_imageCache->insertImage(item.url().toString(), preview.toImage());
0342     // qDebug() << "preview size:" << preview.size();
0343     emit dataChanged(index, index);
0344 }
0345 
0346 void SortModel::previewFailed(const KFileItem &item)
0347 {
0348     // Use folder image instead of displaying nothing then thumbnail generation fails
0349     QPersistentModelIndex index = m_previewJobs.value(item.url());
0350     m_previewJobs.remove(item.url());
0351 
0352     if (!index.isValid()) {
0353         return;
0354     }
0355 
0356     m_imageCache->insertImage(item.url().toString(), QIcon::fromTheme(item.iconName()).pixmap(m_screenshotSize).toImage());
0357     Q_EMIT dataChanged(index, index);
0358 }