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"