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"