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 }