File indexing completed on 2025-01-19 03:50:47

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2015-08-07
0007  * Description : Trash view
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 "trashview.h"
0016 
0017 // Qt includes
0018 
0019 #include <QVBoxLayout>
0020 #include <QHBoxLayout>
0021 #include <QTableView>
0022 #include <QToolButton>
0023 #include <QPushButton>
0024 #include <QHeaderView>
0025 #include <QListView>
0026 #include <QMessageBox>
0027 #include <QPainter>
0028 #include <QAction>
0029 #include <QMenu>
0030 #include <QUrl>
0031 
0032 // KDE includes
0033 
0034 #include <klocalizedstring.h>
0035 
0036 // Local includes
0037 
0038 #include "digikam_debug.h"
0039 #include "dtrashiteminfo.h"
0040 #include "dtrashitemmodel.h"
0041 #include "thumbnailsize.h"
0042 #include "dio.h"
0043 
0044 namespace Digikam
0045 {
0046 
0047 class Q_DECL_HIDDEN TrashView::Private
0048 {
0049 
0050 public:
0051 
0052     explicit Private()
0053         : model             (nullptr),
0054           thumbDelegate     (nullptr),
0055           mainLayout        (nullptr),
0056           btnsLayout        (nullptr),
0057           tableView         (nullptr),
0058           undoButton        (nullptr),
0059           deleteButton      (nullptr),
0060           restoreAction     (nullptr),
0061           deleteAction      (nullptr),
0062           deleteAllAction   (nullptr),
0063           thumbSize         (ThumbnailSize::Large)
0064     {
0065     }
0066 
0067 public:
0068 
0069     DTrashItemModel*               model;
0070     ThumbnailAligningDelegate*     thumbDelegate;
0071     QVBoxLayout*                   mainLayout;
0072     QHBoxLayout*                   btnsLayout;
0073     QTableView*                    tableView;
0074     QPushButton*                   undoButton;
0075     QPushButton*                   deleteButton;
0076     QAction*                       restoreAction;
0077     QAction*                       deleteAction;
0078     QAction*                       deleteAllAction;
0079 
0080     QModelIndex                    lastSelectedIndex;
0081 
0082     QHash<QString, DTrashItemInfo> lastTrashItemCache;
0083 
0084     DTrashItemInfo                 lastSelectedItem;
0085     QModelIndexList                selectedIndexesToRemove;
0086     ThumbnailSize                  thumbSize;
0087 };
0088 
0089 TrashView::TrashView(QWidget* const parent)
0090     : QWidget(parent),
0091       d      (new Private)
0092 {
0093     // Layouts
0094 
0095     d->mainLayout    = new QVBoxLayout(this);
0096     d->btnsLayout    = new QHBoxLayout();
0097 
0098     // View and tools
0099 
0100     d->tableView     = new QTableView(this);
0101     d->model         = new DTrashItemModel(this, this);
0102     d->thumbDelegate = new ThumbnailAligningDelegate(this);
0103 
0104     // Table view settings
0105 
0106     d->tableView->setModel(d->model);
0107     d->tableView->setItemDelegateForColumn(0, d->thumbDelegate);
0108     d->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
0109     d->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
0110     d->tableView->verticalHeader()->setDefaultSectionSize(d->thumbSize.size());
0111     d->tableView->verticalHeader()->hide();
0112     d->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
0113     d->tableView->setShowGrid(false);
0114     d->tableView->setSortingEnabled(true);
0115     d->tableView->sortByColumn(DTrashItemModel::DTrashTimeStamp, Qt::DescendingOrder);
0116     d->tableView->setContextMenuPolicy(Qt::CustomContextMenu);
0117 
0118     // Action Buttons
0119 
0120     QPushButton* const helpButton = new QPushButton(QIcon::fromTheme(QLatin1String("help-browser")), i18n("Help"));
0121     helpButton->setToolTip(i18nc("@info", "Online help about trash-bin"));
0122 
0123     connect(helpButton, &QPushButton::clicked,
0124             this, []()
0125         {
0126             openOnlineDocumentation(QLatin1String("main_window"), QLatin1String("image_view"), QLatin1String("deleting-photograph"));
0127         }
0128     );
0129 
0130     d->undoButton      = new QPushButton(i18n("Undo"), this);
0131     d->undoButton->setToolTip(i18n("Restore only the last entry in the trash-bin."));
0132     d->undoButton->setIcon(QIcon::fromTheme(QLatin1String("edit-undo")));
0133     d->undoButton->setEnabled(false);
0134 
0135     QToolButton* const restoreButton = new QToolButton(this);
0136     d->restoreAction                 = new QAction(i18n("Restore"), this);
0137     d->restoreAction->setToolTip(i18n("Restore selection of files from the trash-bin."));
0138     d->restoreAction->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
0139     d->restoreAction->setEnabled(false);
0140     restoreButton->setDefaultAction(d->restoreAction);
0141     restoreButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0142 
0143     d->deleteButton    = new QPushButton(i18n("Delete..."), this);
0144     d->deleteButton->setToolTip(i18n("Remove permanently the items selection or all items from the trash-bin."));
0145     d->deleteButton->setIcon(QIcon::fromTheme(QLatin1String("edit-delete")));
0146     d->deleteButton->setEnabled(false);
0147 
0148     d->deleteAction    = new QAction(i18n("Selected Items Permanently"), this);
0149     d->deleteAction->setIcon(QIcon::fromTheme(QLatin1String("edit-delete")));
0150 
0151     d->deleteAllAction = new QAction(i18n("All Items Permanently"),      this);
0152     d->deleteAllAction->setIcon(QIcon::fromTheme(QLatin1String("edit-delete")));
0153 
0154     QMenu* const menu  = new QMenu(this);
0155     menu->addAction(d->deleteAction);
0156     menu->addAction(d->deleteAllAction);
0157 
0158     d->deleteButton->setMenu(menu);
0159 
0160     // Adding widgets to layouts
0161 
0162     d->mainLayout->addWidget(d->tableView);
0163 
0164     d->btnsLayout->addWidget(helpButton);
0165     d->btnsLayout->addWidget(d->undoButton);
0166     d->btnsLayout->addStretch();
0167     d->btnsLayout->addWidget(restoreButton);
0168     d->btnsLayout->addStretch();
0169     d->btnsLayout->addWidget(d->deleteButton);
0170     d->btnsLayout->setStretchFactor(restoreButton, 5);
0171 
0172     d->mainLayout->addLayout(d->btnsLayout);
0173     d->mainLayout->setContentsMargins(QMargins());
0174 
0175     // Signals and Slots connections
0176 
0177     connect(d->undoButton, SIGNAL(released()),
0178             this, SLOT(slotUndoLastDeletedItems()));
0179 
0180     connect(d->restoreAction, SIGNAL(triggered()),
0181             this, SLOT(slotRestoreSelectedItems()));
0182 
0183     connect(d->deleteAction, SIGNAL(triggered()),
0184             this, SLOT(slotDeleteSelectedItems()));
0185 
0186     connect(this, SIGNAL(signalEmptytrash()),
0187             this, SLOT(slotDeleteAllItems()));
0188 
0189     connect(d->deleteAllAction, SIGNAL(triggered()),
0190             this, SLOT(slotDeleteAllItems()));
0191 
0192     connect(d->model, SIGNAL(dataChange()),
0193             this, SLOT(slotDataChanged()));
0194 
0195     connect(d->model, SIGNAL(signalLoadingStarted()),
0196             this, SLOT(slotLoadingStarted()));
0197 
0198     connect(d->model, SIGNAL(signalLoadingFinished()),
0199             this, SLOT(slotLoadingFinished()));
0200 
0201     connect(d->tableView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0202             this, SLOT(slotChangeLastSelectedItem(QModelIndex,QModelIndex)));
0203 
0204     connect(d->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
0205             this, SLOT(slotSelectionChanged()));
0206 
0207     connect(d->tableView, SIGNAL(customContextMenuRequested(QPoint)),
0208             this, SLOT(slotContextMenuEmptyTrash(QPoint)));
0209 }
0210 
0211 TrashView::~TrashView()
0212 {
0213     delete d;
0214 }
0215 
0216 DTrashItemModel* TrashView::model() const
0217 {
0218     return d->model;
0219 }
0220 
0221 ThumbnailSize TrashView::getThumbnailSize() const
0222 {
0223     return d->thumbSize;
0224 }
0225 
0226 void TrashView::slotSelectionChanged()
0227 {
0228     if (d->tableView->selectionModel()->hasSelection())
0229     {
0230         d->restoreAction->setEnabled(true);
0231         d->deleteAction->setEnabled(true);
0232     }
0233     else
0234     {
0235         d->restoreAction->setEnabled(false);
0236         d->deleteAction->setEnabled(false);
0237     }
0238 
0239     Q_EMIT selectionChanged();
0240 }
0241 
0242 void TrashView::slotUndoLastDeletedItems()
0243 {
0244     qCDebug(DIGIKAM_GENERAL_LOG) << "Undo last deleted items from collection trash";
0245 
0246     DTrashItemInfoList items;
0247     d->selectedIndexesToRemove.clear();
0248     QDateTime lastDateTime = QDateTime::fromMSecsSinceEpoch(0);
0249 
0250     Q_FOREACH (const DTrashItemInfo& item, d->model->allItems())
0251     {
0252         if (item.deletionTimestamp > lastDateTime)
0253         {
0254             // cppcheck-suppress useStlAlgorithm
0255             lastDateTime = item.deletionTimestamp;
0256         }
0257     }
0258 
0259     Q_FOREACH (const DTrashItemInfo& item, d->model->allItems())
0260     {
0261         if (item.deletionTimestamp == lastDateTime)
0262         {
0263             QModelIndex index = d->model->indexForItem(item);
0264 
0265             if (index.isValid())
0266             {
0267                 items << item;
0268                 d->selectedIndexesToRemove << index;
0269             }
0270         }
0271     }
0272 
0273     if (items.isEmpty())
0274     {
0275         return;
0276     }
0277 
0278     QString title = i18n("Confirm Undo");
0279     QString msg   = i18np("Are you sure you want to restore %1 item?",
0280                           "Are you sure you want to restore %1 items?", items.count());
0281     int result    = QMessageBox::warning(this, title, msg, QMessageBox::Yes | QMessageBox::No);
0282 
0283     if (result == QMessageBox::No)
0284     {
0285         return;
0286     }
0287 
0288     qCDebug(DIGIKAM_GENERAL_LOG) << "Items to Restore:\n " << items;
0289 
0290     DIO::restoreTrash(items);
0291 
0292     connect(DIO::instance(), SIGNAL(signalTrashFinished()),
0293             this, SLOT(slotRemoveItemsFromModel()));
0294 }
0295 
0296 void TrashView::slotRestoreSelectedItems()
0297 {
0298     qCDebug(DIGIKAM_GENERAL_LOG) << "Restoring selected items from collection trash";
0299 
0300     d->selectedIndexesToRemove = d->tableView->selectionModel()->selectedRows();
0301     DTrashItemInfoList items   = d->model->itemsForIndexes(d->selectedIndexesToRemove);
0302 
0303     QString title = i18n("Confirm Restore");
0304     QString msg   = i18np("Are you sure you want to restore %1 item?",
0305                           "Are you sure you want to restore %1 items?", items.count());
0306     int result    = QMessageBox::warning(this, title, msg, QMessageBox::Yes | QMessageBox::No);
0307 
0308     if (result == QMessageBox::No)
0309     {
0310         return;
0311     }
0312 
0313     qCDebug(DIGIKAM_GENERAL_LOG) << "Items to Restore:\n " << items;
0314 
0315     DIO::restoreTrash(items);
0316 
0317     connect(DIO::instance(), SIGNAL(signalTrashFinished()),
0318             this, SLOT(slotRemoveItemsFromModel()));
0319 }
0320 
0321 void TrashView::slotDeleteSelectedItems()
0322 {
0323     qCDebug(DIGIKAM_GENERAL_LOG) << "Deleting selected items from collection trash";
0324 
0325     d->selectedIndexesToRemove = d->tableView->selectionModel()->selectedRows();
0326 
0327     if (d->selectedIndexesToRemove.isEmpty())
0328     {
0329         return;
0330     }
0331 
0332     QString title = i18n("Confirm Deletion");
0333     QString msg   = i18np("Are you sure you want to delete %1 item permanently?",
0334                           "Are you sure you want to delete %1 items permanently?",
0335                           d->selectedIndexesToRemove.count());
0336     int result    = QMessageBox::warning(this, title, msg, QMessageBox::Yes | QMessageBox::No);
0337 
0338     if (result == QMessageBox::No)
0339     {
0340         return;
0341     }
0342 
0343     DTrashItemInfoList items = d->model->itemsForIndexes(d->selectedIndexesToRemove);
0344 
0345     qCDebug(DIGIKAM_GENERAL_LOG) << "Items count: " << items.count();
0346 
0347     DIO::emptyTrash(items);
0348 
0349     connect(DIO::instance(), SIGNAL(signalTrashFinished()),
0350             this, SLOT(slotRemoveItemsFromModel()));
0351 }
0352 
0353 void TrashView::slotRemoveItemsFromModel()
0354 {
0355     disconnect(DIO::instance(), nullptr, this, nullptr);
0356 
0357     if (d->selectedIndexesToRemove.isEmpty())
0358     {
0359         return;
0360     }
0361 
0362     qCDebug(DIGIKAM_GENERAL_LOG) << "Removing deleted items from view";
0363 
0364     d->model->removeItems(d->selectedIndexesToRemove);
0365     d->selectedIndexesToRemove.clear();
0366 }
0367 
0368 void TrashView::slotRemoveAllItemsFromModel()
0369 {
0370     disconnect(DIO::instance(), nullptr, this, nullptr);
0371 
0372     d->model->clearCurrentData();
0373 }
0374 
0375 void TrashView::slotDeleteAllItems()
0376 {
0377     if (d->model->isEmpty())
0378     {
0379         return;
0380     }
0381 
0382     QString title = i18n("Confirm Deletion");
0383     QString msg   = i18n("Are you sure you want to delete ALL items permanently?");
0384     int result    = QMessageBox::warning(this, title, msg, QMessageBox::Yes | QMessageBox::No);
0385 
0386     if (result == QMessageBox::No)
0387     {
0388         return;
0389     }
0390 
0391     qCDebug(DIGIKAM_GENERAL_LOG) << "Removing all item from trash permanently";
0392 
0393     DIO::emptyTrash(d->model->allItems());
0394 
0395     connect(DIO::instance(), SIGNAL(signalTrashFinished()),
0396             this, SLOT(slotRemoveAllItemsFromModel()));
0397 }
0398 
0399 void TrashView::slotDataChanged()
0400 {
0401     selectLastSelected();
0402 
0403     if (d->model->isEmpty())
0404     {
0405         d->undoButton->setEnabled(false);
0406         d->deleteButton->setEnabled(false);
0407         d->restoreAction->setEnabled(false);
0408 
0409         return;
0410     }
0411 
0412     d->undoButton->setEnabled(true);
0413     d->deleteButton->setEnabled(true);
0414 }
0415 
0416 void TrashView::slotLoadingStarted()
0417 {
0418     if (!d->model->trashAlbumPath().isEmpty() && !d->lastSelectedItem.isNull())
0419     {
0420         d->lastTrashItemCache[d->model->trashAlbumPath()] = d->lastSelectedItem;
0421     }
0422 }
0423 
0424 void TrashView::slotLoadingFinished()
0425 {
0426     if (!d->model->trashAlbumPath().isEmpty())
0427     {
0428         DTrashItemInfo item = d->lastTrashItemCache.value(d->model->trashAlbumPath());
0429         QModelIndex index   = d->model->indexForItem(item);
0430 
0431         if (index.isValid())
0432         {
0433             d->lastSelectedIndex = index;
0434             d->lastSelectedItem  = item;
0435         }
0436     }
0437 
0438     selectLastSelected();
0439 }
0440 
0441 void TrashView::slotChangeLastSelectedItem(const QModelIndex& curr, const QModelIndex&)
0442 {
0443     d->lastSelectedIndex = curr;
0444     d->lastSelectedItem  = d->model->itemForIndex(curr);
0445 
0446     Q_EMIT selectionChanged();
0447 }
0448 
0449 void TrashView::slotContextMenuEmptyTrash(const QPoint& pos)
0450 {
0451     QMenu* const emptyTrashMenu = new QMenu(this);
0452     emptyTrashMenu->addSection(i18n("Selection"));
0453     emptyTrashMenu->addAction(d->restoreAction);
0454     emptyTrashMenu->addSection(i18n("Delete"));
0455     emptyTrashMenu->addAction(d->deleteAllAction);
0456     emptyTrashMenu->addAction(d->deleteAction);
0457     emptyTrashMenu->popup(d->tableView->viewport()->mapToGlobal(pos));
0458 
0459     return;
0460 }
0461 
0462 void TrashView::setThumbnailSize(const ThumbnailSize& thumbSize)
0463 {
0464     d->model->changeThumbSize(thumbSize.size());
0465     d->tableView->verticalHeader()->setDefaultSectionSize(thumbSize.size());
0466     d->thumbSize = thumbSize;
0467 }
0468 
0469 QUrl TrashView::lastSelectedItemUrl() const
0470 {
0471     return QUrl::fromLocalFile(d->lastSelectedItem.trashPath);
0472 }
0473 
0474 void TrashView::selectLastSelected()
0475 {
0476     if      (d->model->isEmpty())
0477     {
0478         d->lastSelectedIndex = QModelIndex();
0479     }
0480     else if (!d->lastSelectedIndex.isValid())
0481     {
0482         d->tableView->selectRow(0);
0483         d->lastSelectedIndex = d->model->index(0, 0);
0484     }
0485     else
0486     {
0487         d->tableView->selectRow(d->lastSelectedIndex.row());
0488         d->lastSelectedIndex = d->model->index(d->lastSelectedIndex.row(), 0);
0489     }
0490 
0491     if (!d->lastSelectedIndex.isValid())
0492     {
0493         d->lastSelectedItem = DTrashItemInfo();
0494     }
0495     else
0496     {
0497         d->lastSelectedItem = d->model->itemForIndex(d->lastSelectedIndex);
0498         d->tableView->scrollTo(d->lastSelectedIndex, QAbstractItemView::EnsureVisible);
0499     }
0500 
0501     Q_EMIT selectionChanged();
0502 }
0503 
0504 QString TrashView::statusBarText() const
0505 {
0506     int selectionCount = d->tableView->selectionModel()->selectedRows().count();
0507     int numberOfItems  = d->model->rowCount(QModelIndex());
0508 
0509     QString statusBarSelectionText;
0510 
0511     if (selectionCount == 0)
0512     {
0513         statusBarSelectionText
0514                 = i18np("No item selected (%1 item)",
0515                         "No item selected (%1 items)",
0516                         numberOfItems);
0517     }
0518     else
0519     {
0520         statusBarSelectionText
0521                 = i18n("%1/%2 items selected",
0522                        selectionCount, numberOfItems);
0523     }
0524 
0525     return statusBarSelectionText;
0526 }
0527 
0528 // --------------------------------------------------
0529 
0530 ThumbnailAligningDelegate::ThumbnailAligningDelegate(QObject* const parent)
0531     : QStyledItemDelegate(parent)
0532 {
0533 }
0534 
0535 void ThumbnailAligningDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0536 {
0537     QPixmap pixmap;
0538     pixmap        = index.data(Qt::DecorationRole).value<QPixmap>();
0539     QSize pixSize = (QSizeF(pixmap.size()) / pixmap.devicePixelRatio()).toSize();
0540     QPoint loc    = option.rect.center() - QRect(0, 0, pixSize.width(), pixSize.height()).center();
0541 
0542     painter->save();
0543 
0544     if      (option.state & QStyle::State_Selected)
0545     {
0546         painter->fillRect(option.rect, option.palette.highlight());
0547     }
0548     else if (option.state & QStyle::State_MouseOver)
0549     {
0550         painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
0551     }
0552 
0553     painter->drawPixmap(loc, pixmap);
0554     painter->restore();
0555 }
0556 
0557 } // namespace Digikam
0558 
0559 #include "moc_trashview.cpp"