Warning, file /graphics/koko/src/sortmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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         emit sortRoleNameChanged();
0082         return;
0083     }
0084 
0085     const QHash<int, QByteArray> roles = sourceModel()->roleNames();
0086     for (auto it = roles.begin(); it != roles.end(); it++) {
0087         if (it.value() == name) {
0088             setSortRole(it.key());
0089             emit sortRoleNameChanged();
0090             return;
0091         }
0092     }
0093     qDebug() << "Sort role" << name << "not found";
0094 }
0095 
0096 QHash<int, QByteArray> SortModel::roleNames() const
0097 {
0098     if (!sourceModel()) {
0099         return {};
0100     }
0101     QHash<int, QByteArray> hash = sourceModel()->roleNames();
0102     hash.insert(Roles::SelectedRole, "selected");
0103     hash.insert(Roles::Thumbnail, "thumbnail");
0104     hash.insert(Roles::SourceIndex, "sourceIndex");
0105     return hash;
0106 }
0107 
0108 QVariant SortModel::data(const QModelIndex &index, int role) const
0109 {
0110     if (!index.isValid()) {
0111         return {};
0112     }
0113 
0114     switch (role) {
0115     case Roles::SelectedRole: {
0116         return m_selectionModel->isSelected(index);
0117     }
0118 
0119     case Roles::Thumbnail: {
0120         QUrl thumbnailSource(QString(/*"file://" + */ data(index, Roles::ImageUrlRole).toString()));
0121 
0122         KFileItem item(thumbnailSource, QString());
0123         QImage preview = QImage(m_screenshotSize, QImage::Format_ARGB32_Premultiplied);
0124 
0125         if (m_imageCache->findImage(item.url().toString(), &preview)) {
0126             return preview;
0127         }
0128 
0129         m_previewTimer->start(100);
0130         const_cast<SortModel *>(this)->m_filesToPreview[item.url()] = QPersistentModelIndex(index);
0131         return {};
0132     }
0133 
0134     case Roles::SourceIndex: {
0135         return mapToSource(index).row();
0136     }
0137     }
0138 
0139     return QSortFilterProxyModel::data(index, role);
0140 }
0141 
0142 bool SortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
0143 {
0144     if (sourceModel()) {
0145         if ((sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder && sourceModel()->data(source_right, Roles::ItemTypeRole) == Types::Folder)
0146             || (sourceModel()->data(source_left, Roles::ItemTypeRole) != Types::Folder
0147                 && sourceModel()->data(source_right, Roles::ItemTypeRole) != Types::Folder)) {
0148             return QSortFilterProxyModel::lessThan(source_left, source_right);
0149         } else if (sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder
0150                    && sourceModel()->data(source_right, Roles::ItemTypeRole) != Types::Folder) {
0151             return true;
0152         } else {
0153             return false;
0154         }
0155     }
0156 
0157     return false;
0158 }
0159 
0160 void SortModel::setSourceModel(QAbstractItemModel *sourceModel)
0161 {
0162     QSortFilterProxyModel::setSourceModel(sourceModel);
0163 
0164     if (!m_sortRoleName.isEmpty()) {
0165         setSortRoleName(m_sortRoleName);
0166         m_sortRoleName.clear();
0167     }
0168 }
0169 
0170 bool SortModel::containImages()
0171 {
0172     return m_containImages;
0173 }
0174 
0175 bool SortModel::hasSelectedImages()
0176 {
0177     return m_selectionModel->hasSelection();
0178 }
0179 
0180 void SortModel::setSelected(int indexValue)
0181 {
0182     if (indexValue < 0)
0183         return;
0184 
0185     QModelIndex index = QSortFilterProxyModel::index(indexValue, 0);
0186     m_selectionModel->select(index, QItemSelectionModel::Select);
0187     emit dataChanged(index, index);
0188     emit selectedImagesChanged();
0189 }
0190 
0191 void SortModel::toggleSelected(int indexValue)
0192 {
0193     if (indexValue < 0)
0194         return;
0195 
0196     QModelIndex index = QSortFilterProxyModel::index(indexValue, 0);
0197     m_selectionModel->select(index, QItemSelectionModel::Toggle);
0198     emit dataChanged(index, index);
0199     emit selectedImagesChanged();
0200 }
0201 
0202 void SortModel::clearSelections()
0203 {
0204     if (m_selectionModel->hasSelection()) {
0205         QModelIndexList selectedIndex = m_selectionModel->selectedIndexes();
0206         m_selectionModel->clear();
0207         for (auto indexValue : selectedIndex) {
0208             emit dataChanged(indexValue, indexValue);
0209         }
0210     }
0211     emit selectedImagesChanged();
0212 }
0213 
0214 void SortModel::selectAll()
0215 {
0216     QModelIndexList indexList;
0217     for (int row = 0; row < rowCount(); row++) {
0218         indexList.append(index(row, 0, QModelIndex()));
0219     }
0220 
0221     if (m_selectionModel->hasSelection()) {
0222         m_selectionModel->clear();
0223     }
0224 
0225     for (auto index : indexList) {
0226         if (Types::Image == data(index, Roles::ItemTypeRole))
0227             m_selectionModel->select(index, QItemSelectionModel::Select);
0228     }
0229     emit dataChanged(index(0, 0, QModelIndex()), index(rowCount() - 1, 0, QModelIndex()));
0230     emit selectedImagesChanged();
0231 }
0232 
0233 void SortModel::deleteSelection()
0234 {
0235     QList<QUrl> filesToDelete;
0236 
0237     for (auto index : m_selectionModel->selectedIndexes()) {
0238         filesToDelete << data(index, Roles::ImageUrlRole).toUrl();
0239     }
0240 
0241     auto trashJob = KIO::trash(filesToDelete);
0242     trashJob->exec();
0243 }
0244 
0245 void SortModel::restoreSelection()
0246 {
0247     QList<QUrl> filesToRestore;
0248 
0249     foreach (QModelIndex index, m_selectionModel->selectedIndexes()) {
0250         filesToRestore << data(index, Roles::ImageUrlRole).toUrl();
0251     }
0252 
0253     auto restoreJob = KIO::restoreFromTrash(filesToRestore);
0254     restoreJob->exec();
0255 }
0256 
0257 int SortModel::proxyIndex(const int &indexValue)
0258 {
0259     if (sourceModel()) {
0260         return mapFromSource(sourceModel()->index(indexValue, 0, QModelIndex())).row();
0261     }
0262     return -1;
0263 }
0264 
0265 int SortModel::sourceIndex(const int &indexValue)
0266 {
0267     return mapToSource(index(indexValue, 0, QModelIndex())).row();
0268 }
0269 
0270 QJsonArray SortModel::selectedImages()
0271 {
0272     QJsonArray arr;
0273 
0274     for (auto index : m_selectionModel->selectedIndexes()) {
0275         arr.push_back(QJsonValue(data(index, Roles::ImageUrlRole).toString()));
0276     }
0277 
0278     return arr;
0279 }
0280 
0281 QJsonArray SortModel::selectedImagesMimeTypes()
0282 {
0283     QJsonArray arr;
0284 
0285     for (auto index : m_selectionModel->selectedIndexes()) {
0286         if (!arr.contains(QJsonValue(data(index, Roles::MimeTypeRole).toString()))) {
0287             arr.push_back(QJsonValue(data(index, Roles::MimeTypeRole).toString()));
0288         }
0289     }
0290 
0291     return arr;
0292 }
0293 
0294 int SortModel::indexForUrl(const QString &url)
0295 {
0296     QModelIndexList indexList;
0297     for (int row = 0; row < rowCount(); row++) {
0298         indexList.append(index(row, 0, QModelIndex()));
0299     }
0300     for (auto index : indexList) {
0301         if (url == data(index, Roles::ImageUrlRole).toString()) {
0302             return index.row();
0303         }
0304     }
0305     return -1;
0306 }
0307 
0308 void SortModel::delayedPreview()
0309 {
0310     QHash<QUrl, QPersistentModelIndex>::const_iterator i = m_filesToPreview.constBegin();
0311 
0312     KFileItemList list;
0313 
0314     while (i != m_filesToPreview.constEnd()) {
0315         QUrl file = i.key();
0316         QPersistentModelIndex index = i.value();
0317 
0318         if (!m_previewJobs.contains(file) && file.isValid()) {
0319             list.append(KFileItem(file, QString(), 0));
0320             m_previewJobs.insert(file, QPersistentModelIndex(index));
0321         }
0322 
0323         ++i;
0324     }
0325 
0326     if (list.size() > 0) {
0327         const auto pluginLists = KIO::PreviewJob::availablePlugins();
0328         KIO::PreviewJob *job = KIO::filePreview(list, m_screenshotSize, &pluginLists);
0329         job->setIgnoreMaximumSize(true);
0330         connect(job, &KIO::PreviewJob::gotPreview, this, &SortModel::showPreview);
0331         connect(job, &KIO::PreviewJob::failed, this, &SortModel::previewFailed);
0332     }
0333 
0334     m_filesToPreview.clear();
0335 }
0336 
0337 void SortModel::showPreview(const KFileItem &item, const QPixmap &preview)
0338 {
0339     QPersistentModelIndex index = m_previewJobs.value(item.url());
0340     m_previewJobs.remove(item.url());
0341 
0342     if (!index.isValid()) {
0343         return;
0344     }
0345 
0346     m_imageCache->insertImage(item.url().toString(), preview.toImage());
0347     // qDebug() << "preview size:" << preview.size();
0348     emit dataChanged(index, index);
0349 }
0350 
0351 void SortModel::previewFailed(const KFileItem &item)
0352 {
0353     // Use folder image instead of displaying nothing then thumbnail generation fails
0354     QPersistentModelIndex index = m_previewJobs.value(item.url());
0355     m_previewJobs.remove(item.url());
0356 
0357     if (!index.isValid()) {
0358         return;
0359     }
0360 
0361     m_imageCache->insertImage(item.url().toString(), QIcon::fromTheme(item.iconName()).pixmap(m_screenshotSize).toImage());
0362     Q_EMIT dataChanged(index, index);
0363 }