File indexing completed on 2025-01-19 03:53:22

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-05-06
0007  * Description : Albums folder view.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2006 by Joern Ahrens <joern dot ahrens at kdemail dot net>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009-2012 by Andi Clemens <andi dot clemens at gmail dot com>
0012  * SPDX-FileCopyrightText: 2009-2011 by Johannes Wienke <languitar at semipol dot de>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "albumselectiontreeview.h"
0019 
0020 // Qt includes
0021 
0022 #include <QMessageBox>
0023 #include <QAction>
0024 #include <QEvent>
0025 #include <QIcon>
0026 
0027 // KDE includes
0028 
0029 #include <klocalizedstring.h>
0030 
0031 // Local includes
0032 
0033 #include "digikam_debug.h"
0034 #include "tagscache.h"
0035 #include "itemscanner.h"
0036 #include "albummanager.h"
0037 #include "facescansettings.h"
0038 #include "applicationsettings.h"
0039 #include "contextmenuhelper.h"
0040 #include "itemviewtooltip.h"
0041 #include "tooltipfiller.h"
0042 #include "thumbsgenerator.h"
0043 #include "newitemsfinder.h"
0044 #include "facesdetector.h"
0045 #include "coredbaccess.h"
0046 #include "coredb.h"
0047 
0048 namespace Digikam
0049 {
0050 
0051 class Q_DECL_HIDDEN AlbumViewToolTip: public ItemViewToolTip
0052 {
0053     Q_OBJECT
0054 
0055 public:
0056 
0057     explicit AlbumViewToolTip(AlbumSelectionTreeView* const view)
0058         : ItemViewToolTip(view)
0059     {
0060     }
0061 
0062     AlbumSelectionTreeView* view() const
0063     {
0064         return static_cast<AlbumSelectionTreeView*>(ItemViewToolTip::view());
0065     }
0066 
0067 protected:
0068 
0069     QString tipContents() override
0070     {
0071         PAlbum* const album = view()->albumForIndex(currentIndex());
0072 
0073         return (ToolTipFiller::albumTipContents(album, view()->albumModel()->albumCount(album)));
0074     }
0075 };
0076 
0077 // ----------------------------------------------------------------------------------------------------
0078 
0079 class Q_DECL_HIDDEN AlbumSelectionTreeView::Private
0080 {
0081 
0082 public:
0083 
0084     explicit Private()
0085       : enableToolTips          (false),
0086         albumModificationHelper (nullptr),
0087         toolTip                 (nullptr),
0088         renameAction            (nullptr),
0089         resetIconAction         (nullptr),
0090         findDuplAction          (nullptr),
0091         scanFacesAction         (nullptr),
0092         repairHiddenAction      (nullptr),
0093         rebuildThumbsAction     (nullptr),
0094         expandSelected          (nullptr),
0095         expandAllAlbums         (nullptr),
0096         collapseSelected        (nullptr),
0097         collapseAllAlbums       (nullptr),
0098         contextMenuElement      (nullptr)
0099     {
0100     }
0101 
0102     bool                                      enableToolTips;
0103 
0104     AlbumModificationHelper*                  albumModificationHelper;
0105     AlbumViewToolTip*                         toolTip;
0106 
0107     QAction*                                  renameAction;
0108     QAction*                                  resetIconAction;
0109     QAction*                                  findDuplAction;
0110     QAction*                                  scanFacesAction;
0111     QAction*                                  repairHiddenAction;
0112     QAction*                                  rebuildThumbsAction;
0113     QAction*                                  expandSelected;
0114     QAction*                                  expandAllAlbums;
0115     QAction*                                  collapseSelected;
0116     QAction*                                  collapseAllAlbums;
0117 
0118     class AlbumSelectionTreeViewContextMenuElement;
0119     AlbumSelectionTreeViewContextMenuElement* contextMenuElement;
0120 };
0121 
0122 // ----------------------------------------------------------------------------------------------------
0123 
0124 class Q_DECL_HIDDEN AlbumSelectionTreeView::Private::AlbumSelectionTreeViewContextMenuElement
0125     : public AbstractAlbumTreeView::ContextMenuElement
0126 {
0127 
0128 public:
0129 
0130     explicit AlbumSelectionTreeViewContextMenuElement(AlbumSelectionTreeView::Private* const dd)
0131         : d(dd)
0132     {
0133     }
0134 
0135     void addActions(AbstractAlbumTreeView*, ContextMenuHelper& cmh, Album* a) override
0136     {
0137         if (!a || a->isRoot())
0138         {
0139             return;
0140         }
0141 
0142         PAlbum* const album = dynamic_cast<PAlbum*>(a);
0143 
0144         if (!album)
0145         {
0146             return;
0147         }
0148 
0149         cmh.addActionNewAlbum(d->albumModificationHelper, album);
0150         cmh.addAction(QLatin1String("album_openinfilemanager"));
0151         cmh.addSeparator();
0152 
0153         // --------------------------------------------------------
0154 
0155         cmh.addActionRenameAlbum(d->albumModificationHelper, album);
0156         cmh.addActionResetAlbumIcon(d->albumModificationHelper, album);
0157         cmh.addSeparator();
0158 
0159         // --------------------------------------------------------
0160 
0161         cmh.addAction(d->expandSelected);
0162         cmh.addAction(d->collapseSelected);
0163         cmh.addAction(d->expandAllAlbums);
0164         cmh.addAction(d->collapseAllAlbums);
0165         cmh.addSeparator();
0166 
0167         // --------------------------------------------------------
0168 
0169         cmh.addAction(d->findDuplAction);
0170         d->albumModificationHelper->bindAlbum(d->findDuplAction, album);
0171         cmh.addAction(d->scanFacesAction);
0172         d->albumModificationHelper->bindAlbum(d->scanFacesAction, album);
0173         cmh.addAction(d->rebuildThumbsAction);
0174         d->albumModificationHelper->bindAlbum(d->rebuildThumbsAction, album);
0175         cmh.addImportMenu();
0176         cmh.addExportMenu();
0177         cmh.addSeparator();
0178 
0179         // --------------------------------------------------------
0180 
0181         cmh.addAction(d->repairHiddenAction);
0182         d->albumModificationHelper->bindAlbum(d->repairHiddenAction, album);
0183 
0184         // --------------------------------------------------------
0185 
0186         if (!album->isAlbumRoot())
0187         {
0188             cmh.addSeparator();
0189             cmh.addActionDeleteAlbum(d->albumModificationHelper, album);
0190             cmh.addSeparator();
0191 
0192             // --------------------------------------------------------
0193 
0194             cmh.addActionEditAlbum(d->albumModificationHelper, album);
0195         }
0196     }
0197 
0198 public:
0199 
0200     AlbumSelectionTreeView::Private* const d;
0201 };
0202 
0203 // ----------------------------------------------------------------------------------------------------
0204 
0205 AlbumSelectionTreeView::AlbumSelectionTreeView(QWidget* const parent,
0206                                                AlbumModel* const model,
0207                                                AlbumModificationHelper* const albumModificationHelper)
0208     : AlbumTreeView(parent),
0209       d            (new Private)
0210 {
0211     setAlbumModel(model);
0212     d->albumModificationHelper = albumModificationHelper;
0213     d->toolTip                 = new AlbumViewToolTip(this);
0214 
0215     d->expandSelected          = new QAction(QIcon::fromTheme(QLatin1String("go-down")),
0216                                              i18n("Expand Selected Nodes"), this);
0217 
0218     d->expandAllAlbums         = new QAction(QIcon::fromTheme(QLatin1String("expand-all")),
0219                                              i18n("Expand all Albums"), this);
0220 
0221     d->collapseSelected        = new QAction(QIcon::fromTheme(QLatin1String("go-up")),
0222                                              i18n("Collapse Selected Recursively"), this);
0223 
0224     d->collapseAllAlbums       = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")),
0225                                              i18n("Collapse all Albums"), this);
0226 
0227     d->findDuplAction          = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")),
0228                                              i18n("Find Duplicates..."), this);
0229 
0230     d->scanFacesAction         = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")),
0231                                              i18n("Scan for Faces"), this);
0232 
0233     d->repairHiddenAction      = new QAction(QIcon::fromTheme(QLatin1String("edit-group")),
0234                                              i18n("Repair hidden Items"), this);
0235 
0236     d->rebuildThumbsAction     = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")),
0237                                              i18n("Refresh"), this);
0238 
0239     connect(d->expandSelected, SIGNAL(triggered()),
0240             this, SLOT(slotExpandNode()));
0241 
0242     connect(d->expandAllAlbums, SIGNAL(triggered()),
0243             this, SLOT(expandAll()));
0244 
0245     connect(d->collapseSelected, SIGNAL(triggered()),
0246             this, SLOT(slotCollapseNode()));
0247 
0248     connect(d->collapseAllAlbums, SIGNAL(triggered()),
0249             this, SLOT(slotCollapseAllNodes()));
0250 
0251     connect(d->findDuplAction, SIGNAL(triggered()),
0252             this, SLOT(slotFindDuplicates()));
0253 
0254     connect(d->scanFacesAction, SIGNAL(triggered()),
0255             this, SLOT(slotScanForFaces()));
0256 
0257     connect(d->repairHiddenAction, SIGNAL(triggered()),
0258             this, SLOT(slotRepairHiddenItems()));
0259 
0260     connect(d->rebuildThumbsAction, SIGNAL(triggered()),
0261             this, SLOT(slotRebuildThumbs()));
0262 
0263     setSortingEnabled(true);
0264     setSelectAlbumOnClick(true);
0265     setEnableContextMenu(true);
0266     setContextMenuTitle(i18n("Albums"));
0267 
0268     d->contextMenuElement = new Private::AlbumSelectionTreeViewContextMenuElement(d);
0269     addContextMenuElement(d->contextMenuElement);
0270 }
0271 
0272 AlbumSelectionTreeView::~AlbumSelectionTreeView()
0273 {
0274     delete d->contextMenuElement;
0275     delete d;
0276 }
0277 
0278 void AlbumSelectionTreeView::setEnableToolTips(bool enable)
0279 {
0280     d->enableToolTips = enable;
0281 }
0282 
0283 void AlbumSelectionTreeView::slotFindDuplicates()
0284 {
0285     Q_EMIT signalFindDuplicates(QList<PAlbum*> { d->albumModificationHelper->boundAlbum(sender()) });
0286 }
0287 
0288 void AlbumSelectionTreeView::slotScanForFaces()
0289 {
0290     PAlbum* const album = d->albumModificationHelper->boundAlbum(sender());
0291 
0292     if (!album)
0293     {
0294         return;
0295     }
0296 
0297     AlbumList albums;
0298     albums << album;
0299 
0300     if (album->childCount())
0301     {
0302         if (QMessageBox::question(this, i18nc("@title:window", "Scan for Faces"),
0303                                   i18n("Should sub-albums be scanned too?"))
0304             == QMessageBox::Yes)
0305         {
0306             albums << album->childAlbums(true);
0307         }
0308     }
0309 
0310     FaceScanSettings settings;
0311 
0312     settings.accuracy               = ApplicationSettings::instance()->getFaceDetectionAccuracy();
0313     settings.useYoloV3              = ApplicationSettings::instance()->getFaceDetectionYoloV3();
0314     settings.task                   = FaceScanSettings::DetectAndRecognize;
0315     settings.alreadyScannedHandling = FaceScanSettings::Rescan;
0316     settings.albums                 = albums;
0317 
0318     FacesDetector* const tool = new FacesDetector(settings);
0319     tool->start();
0320 }
0321 
0322 void AlbumSelectionTreeView::slotRepairHiddenItems()
0323 {
0324     PAlbum* const album = d->albumModificationHelper->boundAlbum(sender());
0325 
0326     if (!album)
0327     {
0328         return;
0329     }
0330 
0331     int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
0332 
0333     Q_FOREACH (const qlonglong& id, CoreDbAccess().db()->getItemIDsInAlbum(album->id()))
0334     {
0335         ItemInfo info(id);
0336 
0337         if (!info.isNull() && info.isGrouped())
0338         {
0339             if (info.groupImage().albumId() != album->id())
0340             {
0341                 info.removeFromGroup();
0342             }
0343         }
0344 
0345         if (!info.hasDerivedImages() && info.tagIds().contains(originalVersionTag))
0346         {
0347             ItemScanner::resolveImageHistory(info.id());
0348             ItemScanner::tagItemHistoryGraph(info.id());
0349         }
0350     }
0351 }
0352 
0353 void AlbumSelectionTreeView::slotRebuildThumbs()
0354 {
0355     PAlbum* const album = d->albumModificationHelper->boundAlbum(sender());
0356 
0357     if (!album)
0358     {
0359         return;
0360     }
0361 
0362     ThumbsGenerator* const tool = new ThumbsGenerator(true, album->id());
0363     tool->start();
0364 
0365     // if physical album, schedule a collection scan of current album's path
0366 
0367     if (album->type() == Album::PHYSICAL)
0368     {
0369         NewItemsFinder* const tool2 = new NewItemsFinder(NewItemsFinder::ScheduleCollectionScan,
0370                                                          QStringList() << static_cast<PAlbum*>(album)->folderPath());
0371         tool2->start();
0372     }
0373 }
0374 
0375 bool AlbumSelectionTreeView::viewportEvent(QEvent* event)
0376 {
0377     // let the base class handle the event if it is not a tool tip request
0378 
0379     if (event->type() != QEvent::ToolTip)
0380     {
0381         return AlbumTreeView::viewportEvent(event);
0382     }
0383 
0384     // only show tool tips if requested
0385 
0386     if (!d->enableToolTips)
0387     {
0388         return false;
0389     }
0390 
0391     // check that we got a correct event
0392 
0393     QHelpEvent* const helpEvent = dynamic_cast<QHelpEvent*> (event);
0394 
0395     if (!helpEvent)
0396     {
0397         qCDebug(DIGIKAM_GENERAL_LOG) << "Unable to determine the correct type of the event. "
0398                                      << "This should not happen.";
0399         return false;
0400     }
0401 
0402     // find the item this tool tip belongs to
0403 
0404     QModelIndex index = indexAt(helpEvent->pos());
0405 
0406     if (!index.isValid())
0407     {
0408         return true;
0409     }
0410 
0411     PAlbum* const album = albumForIndex(index);
0412 
0413     if (!album || album->isRoot() || album->isAlbumRoot())
0414     {
0415         // there was no album so we really don't want to show a tooltip.
0416 
0417         return true;
0418     }
0419 
0420     QRect itemRect = visualRect(index);
0421 
0422     if (!itemRect.contains(helpEvent->pos()))
0423     {
0424         return true;
0425     }
0426 
0427     QStyleOptionViewItem option;
0428 
0429 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0430 
0431     initViewItemOption(&option);
0432 
0433 #else
0434 
0435     option = viewOptions();
0436 
0437 #endif
0438 
0439     option.rect                 = itemRect;
0440 
0441     // visualRect can be larger than viewport, intersect with viewport rect
0442 
0443     option.rect                &= viewport()->rect();
0444     option.state               |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None);
0445     d->toolTip->show(option, index);
0446 
0447     return true;
0448 }
0449 
0450 } // namespace Digikam
0451 
0452 #include "albumselectiontreeview.moc"
0453 
0454 #include "moc_albumselectiontreeview.cpp"