File indexing completed on 2025-01-05 03:56:11
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2015-08-09 0007 * Description : DTrash item info model 0008 * 0009 * SPDX-FileCopyrightText: 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "dtrashitemmodel.h" 0016 0017 // Qt includes 0018 0019 #include <QIcon> 0020 #include <QTimer> 0021 #include <QPixmap> 0022 #include <QMimeType> 0023 #include <QMimeDatabase> 0024 #include <QPersistentModelIndex> 0025 0026 // KDE includes 0027 0028 #include <klocalizedstring.h> 0029 0030 // Local includes 0031 0032 #include "digikam_debug.h" 0033 #include "thumbnailsize.h" 0034 #include "iojobsmanager.h" 0035 0036 namespace Digikam 0037 { 0038 0039 class Q_DECL_HIDDEN DTrashItemModel::Private 0040 { 0041 0042 public: 0043 0044 explicit Private() 0045 : thumbSize (ThumbnailSize::Large), 0046 sortColumn (DTrashTimeStamp), 0047 sortOrder (Qt::DescendingOrder), 0048 sortEnabled (true), 0049 displayWidget (nullptr), 0050 itemsLoadingJob(nullptr), 0051 thumbnailThread(nullptr) 0052 { 0053 } 0054 0055 public: 0056 0057 int thumbSize; 0058 int sortColumn; 0059 0060 Qt::SortOrder sortOrder; 0061 bool sortEnabled; 0062 0063 QWidget* displayWidget; 0064 0065 IOJobsThread* itemsLoadingJob; 0066 ThumbnailLoadThread* thumbnailThread; 0067 0068 QString trashAlbumPath; 0069 0070 QStringList failedThumbnails; 0071 QStringList collectionThumbs; 0072 DTrashItemInfoList data; 0073 }; 0074 0075 DTrashItemModel::DTrashItemModel(QObject* const parent, QWidget* const widget) 0076 : QAbstractTableModel(parent), 0077 d (new Private) 0078 { 0079 qRegisterMetaType<DTrashItemInfo>("DTrashItemInfo"); 0080 0081 d->displayWidget = widget; 0082 d->thumbnailThread = new ThumbnailLoadThread; 0083 d->thumbnailThread->setSendSurrogatePixmap(false); 0084 0085 connect(d->thumbnailThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), 0086 this, SLOT(refreshThumbnails(LoadingDescription,QPixmap))); 0087 } 0088 0089 DTrashItemModel::~DTrashItemModel() 0090 { 0091 stopLoadingTrash(); 0092 0093 delete d->thumbnailThread; 0094 delete d; 0095 } 0096 0097 int DTrashItemModel::rowCount(const QModelIndex&) const 0098 { 0099 return d->data.count(); 0100 } 0101 0102 int DTrashItemModel::columnCount(const QModelIndex&) const 0103 { 0104 return DTrashNumCol; 0105 } 0106 0107 QVariant DTrashItemModel::data(const QModelIndex& index, int role) const 0108 { 0109 if ( 0110 (role != Qt::DisplayRole) && 0111 (role != Qt::DecorationRole) && 0112 (role != Qt::TextAlignmentRole) && 0113 (role != Qt::ToolTipRole) 0114 ) 0115 { 0116 return QVariant(); 0117 } 0118 0119 const DTrashItemInfo& item = d->data[index.row()]; 0120 0121 if (role == Qt::TextAlignmentRole) 0122 { 0123 return Qt::AlignCenter; 0124 } 0125 0126 if ((role == Qt::DecorationRole) && (index.column() == DTrashThumb)) 0127 { 0128 QPixmap pix; 0129 QString thumbPath; 0130 0131 if (!d->failedThumbnails.contains(item.collectionPath)) 0132 { 0133 d->collectionThumbs << item.collectionPath; 0134 thumbPath = item.collectionPath; 0135 } 0136 else if (!d->failedThumbnails.contains(item.trashPath)) 0137 { 0138 thumbPath = item.trashPath; 0139 } 0140 0141 if (thumbPath.isEmpty() || pixmapForItem(thumbPath, pix)) 0142 { 0143 if (pix.isNull()) 0144 { 0145 QMimeType mimeType = QMimeDatabase().mimeTypeForFile(item.trashPath); 0146 0147 if (mimeType.isValid()) 0148 { 0149 pix = QIcon::fromTheme(mimeType.genericIconName()).pixmap(128); 0150 } 0151 } 0152 0153 return pix; 0154 } 0155 0156 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0157 0158 QVariant var = QPixmap(); 0159 0160 return var; 0161 0162 #else 0163 0164 return QVariant(QVariant::Pixmap); 0165 0166 #endif 0167 0168 } 0169 0170 if ((role == Qt::ToolTipRole) && (index.column() == DTrashRelPath)) 0171 { 0172 return (item.collectionRelativePath); 0173 } 0174 0175 switch (index.column()) 0176 { 0177 case DTrashRelPath: 0178 { 0179 return (item.collectionRelativePath); 0180 } 0181 0182 case DTrashTimeStamp: 0183 { 0184 QString dateTimeFormat = QLocale().dateTimeFormat(); 0185 0186 if (!dateTimeFormat.contains(QLatin1String("yyyy"))) 0187 { 0188 dateTimeFormat.replace(QLatin1String("yy"), 0189 QLatin1String("yyyy")); 0190 } 0191 0192 return (item.deletionTimestamp.toString(dateTimeFormat)); 0193 } 0194 0195 default: 0196 { 0197 return QVariant(); 0198 } 0199 }; 0200 } 0201 0202 void DTrashItemModel::sort(int column, Qt::SortOrder order) 0203 { 0204 d->sortOrder = order; 0205 d->sortColumn = column; 0206 0207 if (!d->sortEnabled || (d->data.count() < 2)) 0208 { 0209 return; 0210 } 0211 0212 std::sort(d->data.begin(), d->data.end(), 0213 [column, order](const DTrashItemInfo& a, const DTrashItemInfo& b) 0214 { 0215 if ((column == DTrashTimeStamp) && (a.deletionTimestamp != b.deletionTimestamp)) 0216 { 0217 if (order == Qt::DescendingOrder) 0218 { 0219 return (a.deletionTimestamp > b.deletionTimestamp); 0220 } 0221 else 0222 { 0223 return (a.deletionTimestamp < b.deletionTimestamp); 0224 } 0225 } 0226 0227 if (order == Qt::DescendingOrder) 0228 { 0229 return (a.collectionRelativePath > b.collectionRelativePath); 0230 } 0231 0232 return (a.collectionRelativePath < b.collectionRelativePath); 0233 } 0234 ); 0235 0236 const QModelIndex topLeft = index(0, 0); 0237 const QModelIndex bottomRight = index(rowCount(QModelIndex()) - 1, 0238 columnCount(QModelIndex()) - 1); 0239 0240 Q_EMIT dataChanged(topLeft, bottomRight); 0241 } 0242 0243 bool DTrashItemModel::pixmapForItem(const QString& path, QPixmap& pix) const 0244 { 0245 double ratio = d->displayWidget->devicePixelRatio(); 0246 int thumbSize = qMin(qRound((double)d->thumbSize * ratio), (int)ThumbnailSize::HD); 0247 0248 bool ret = d->thumbnailThread->find(ThumbnailIdentifier(path), pix, thumbSize); 0249 pix.setDevicePixelRatio(ratio); 0250 0251 return ret; 0252 } 0253 0254 QVariant DTrashItemModel::headerData(int section, Qt::Orientation orientation, int role) const 0255 { 0256 if (orientation != Qt::Horizontal) 0257 { 0258 return QVariant(); 0259 } 0260 0261 if (role != Qt::DisplayRole) 0262 { 0263 return QVariant(); 0264 } 0265 0266 switch (section) 0267 { 0268 case DTrashThumb: 0269 { 0270 return i18n("Thumbnail"); 0271 } 0272 0273 case DTrashRelPath: 0274 { 0275 return i18n("Relative Path"); 0276 } 0277 0278 case DTrashTimeStamp: 0279 { 0280 return i18n("Deletion Time"); 0281 } 0282 0283 default: 0284 { 0285 return QVariant(); 0286 } 0287 } 0288 } 0289 0290 void DTrashItemModel::append(const DTrashItemInfo& itemInfo) 0291 { 0292 if (d->itemsLoadingJob != sender()) 0293 { 0294 return; 0295 } 0296 0297 beginInsertRows(QModelIndex(), d->data.count(), d->data.count()); 0298 d->data.append(itemInfo); 0299 endInsertRows(); 0300 } 0301 0302 void DTrashItemModel::removeItems(const QModelIndexList& indexes) 0303 { 0304 QList<QPersistentModelIndex> persistentIndexes; 0305 0306 Q_FOREACH (const QModelIndex& index, indexes) 0307 { 0308 persistentIndexes << index; 0309 } 0310 0311 Q_EMIT layoutAboutToBeChanged(); 0312 0313 Q_FOREACH (const QPersistentModelIndex& index, persistentIndexes) 0314 { 0315 if (!index.isValid()) 0316 { 0317 continue; 0318 } 0319 0320 const DTrashItemInfo& item = d->data[index.row()]; 0321 0322 d->failedThumbnails.removeAll(item.collectionPath); 0323 d->failedThumbnails.removeAll(item.trashPath); 0324 0325 beginRemoveRows(QModelIndex(), index.row(), index.row()); 0326 removeRow(index.row()); 0327 d->data.removeAt(index.row()); 0328 endRemoveRows(); 0329 } 0330 0331 Q_EMIT layoutChanged(); 0332 Q_EMIT dataChange(); 0333 } 0334 0335 void DTrashItemModel::refreshLayout() 0336 { 0337 const QModelIndex topLeft = index(0, 0); 0338 const QModelIndex bottomRight = index(rowCount(QModelIndex()) - 1, 0); 0339 0340 Q_EMIT dataChanged(topLeft, bottomRight); 0341 Q_EMIT layoutAboutToBeChanged(); 0342 Q_EMIT layoutChanged(); 0343 } 0344 0345 void DTrashItemModel::refreshThumbnails(const LoadingDescription& desc, const QPixmap& pix) 0346 { 0347 if (pix.isNull()) 0348 { 0349 if (!d->failedThumbnails.contains(desc.filePath)) 0350 { 0351 d->failedThumbnails << desc.filePath; 0352 } 0353 } 0354 0355 if (d->collectionThumbs.contains(desc.filePath)) 0356 { 0357 d->collectionThumbs.removeAll(desc.filePath); 0358 d->failedThumbnails << desc.filePath; 0359 } 0360 0361 const QModelIndex topLeft = index(0, 0); 0362 const QModelIndex bottomRight = index(rowCount(QModelIndex()) - 1, 0); 0363 0364 Q_EMIT dataChanged(topLeft, bottomRight); 0365 } 0366 0367 void DTrashItemModel::clearCurrentData() 0368 { 0369 d->failedThumbnails.clear(); 0370 beginResetModel(); 0371 d->data.clear(); 0372 endResetModel(); 0373 0374 Q_EMIT dataChange(); 0375 } 0376 0377 void DTrashItemModel::loadItemsForCollection(const QString& colPath) 0378 { 0379 stopLoadingTrash(); 0380 0381 Q_EMIT signalLoadingStarted(); 0382 0383 d->sortEnabled = false; 0384 d->trashAlbumPath = colPath; 0385 0386 clearCurrentData(); 0387 0388 d->itemsLoadingJob = IOJobsManager::instance()->startDTrashItemsListingForCollection(colPath); 0389 0390 connect(d->itemsLoadingJob, SIGNAL(collectionTrashItemInfo(DTrashItemInfo)), 0391 this, SLOT(append(DTrashItemInfo)), 0392 Qt::QueuedConnection); 0393 0394 connect(d->itemsLoadingJob, SIGNAL(signalFinished()), 0395 this, SLOT(slotLoadItemsFinished()), 0396 Qt::QueuedConnection); 0397 } 0398 0399 DTrashItemInfo DTrashItemModel::itemForIndex(const QModelIndex& index) 0400 { 0401 if (!index.isValid()) 0402 { 0403 return DTrashItemInfo(); 0404 } 0405 0406 return d->data.at(index.row()); 0407 } 0408 0409 DTrashItemInfoList DTrashItemModel::itemsForIndexes(const QList<QModelIndex>& indexes) 0410 { 0411 DTrashItemInfoList items; 0412 0413 Q_FOREACH (const QModelIndex& index, indexes) 0414 { 0415 if (!index.isValid()) 0416 { 0417 continue; 0418 } 0419 0420 items << itemForIndex(index); 0421 } 0422 0423 return items; 0424 } 0425 0426 QModelIndex DTrashItemModel::indexForItem(const DTrashItemInfo& itemInfo) const 0427 { 0428 int index = d->data.indexOf(itemInfo); 0429 0430 if (index != -1) 0431 { 0432 return createIndex(index, 0); 0433 } 0434 0435 return QModelIndex(); 0436 } 0437 0438 DTrashItemInfoList DTrashItemModel::allItems() 0439 { 0440 return d->data; 0441 } 0442 0443 bool DTrashItemModel::isEmpty() 0444 { 0445 return d->data.isEmpty(); 0446 } 0447 0448 void DTrashItemModel::changeThumbSize(int size) 0449 { 0450 d->thumbSize = size; 0451 0452 if (isEmpty()) 0453 { 0454 return; 0455 } 0456 0457 QTimer::singleShot(100, this, SLOT(refreshLayout())); 0458 } 0459 0460 void DTrashItemModel::stopLoadingTrash() 0461 { 0462 if (d->itemsLoadingJob) 0463 { 0464 disconnect(d->itemsLoadingJob, nullptr, this, nullptr); 0465 0466 d->itemsLoadingJob->cancel(); 0467 d->itemsLoadingJob = nullptr; 0468 } 0469 0470 d->thumbnailThread->stopAllTasks(); 0471 d->thumbnailThread->wait(); 0472 } 0473 0474 QString DTrashItemModel::trashAlbumPath() const 0475 { 0476 return d->trashAlbumPath; 0477 } 0478 0479 void DTrashItemModel::slotLoadItemsFinished() 0480 { 0481 d->sortEnabled = true; 0482 d->itemsLoadingJob = nullptr; 0483 sort(d->sortColumn, d->sortOrder); 0484 0485 Q_EMIT dataChange(); 0486 Q_EMIT signalLoadingFinished(); 0487 } 0488 0489 } // namespace Digikam 0490 0491 #include "moc_dtrashitemmodel.cpp"