File indexing completed on 2025-04-27 04:04:22
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 }