File indexing completed on 2024-05-05 16:27:56

0001 // SPDX-FileCopyrightText: 2003-2013 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0002 // SPDX-FileCopyrightText: 2009 Jan Kundrát <jkt@flaska.net>
0003 // SPDX-FileCopyrightText: 2011-2012 Miika Turkia <miika.turkia@gmail.com>
0004 // SPDX-FileCopyrightText: 2012 Frederik Schwarzer <schwarzer@kde.org>
0005 // SPDX-FileCopyrightText: 2012 Tuomas Suutari <tuomas@nepnep.net>
0006 // SPDX-FileCopyrightText: 2012-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0007 // SPDX-FileCopyrightText: 2015-2019 Tobias Leupold <tl@stonemx.de>
0008 // SPDX-FileCopyrightText: 2016 Luigi Toscano <luigi.toscano@tiscali.it>
0009 // SPDX-FileCopyrightText: 2017 Robert Krawitz <rlk@alum.mit.edu>
0010 // SPDX-FileCopyrightText: 2018 Antoni Bella Pérez <antonibella5@yahoo.com>
0011 //
0012 // SPDX-License-Identifier: GPL-2.0-or-later
0013 
0014 #include "OverviewPage.h"
0015 
0016 #include "BrowserWidget.h"
0017 #include "CategoryPage.h"
0018 #include "ImageViewPage.h"
0019 #include "enums.h"
0020 
0021 #include <AnnotationDialog/Dialog.h>
0022 #ifdef HAVE_MARBLE
0023 #include "GeoPositionPage.h"
0024 #endif
0025 #include <DB/CategoryCollection.h>
0026 #include <DB/ImageDB.h>
0027 #include <Exif/SearchDialog.h>
0028 #include <MainWindow/Window.h>
0029 #include <Utilities/ShowBusyCursor.h>
0030 #include <kpabase/Logging.h>
0031 
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 #include <QElapsedTimer>
0035 #include <QIcon>
0036 #include <QPixmap>
0037 
0038 const int THUMBNAILSIZE = 70;
0039 
0040 AnnotationDialog::Dialog *Browser::OverviewPage::s_config = nullptr;
0041 Browser::OverviewPage::OverviewPage(const Breadcrumb &breadcrumb, const DB::ImageSearchInfo &info, BrowserWidget *browser)
0042     : BrowserPage(info, browser)
0043     , m_breadcrumb(breadcrumb)
0044 {
0045     // updateImageCount();
0046 }
0047 
0048 int Browser::OverviewPage::rowCount(const QModelIndex &parent) const
0049 {
0050     if (parent != QModelIndex())
0051         return 0;
0052 
0053     return categories().count() +
0054 #ifdef HAVE_MARBLE
0055         1 +
0056 #endif
0057         4; // Exiv search + Search info + Untagged Images + Show Image
0058 }
0059 
0060 QVariant Browser::OverviewPage::data(const QModelIndex &index, int role) const
0061 {
0062     const int row = index.row();
0063     if (isCategoryIndex(row))
0064         return categoryInfo(row, role);
0065 #ifdef HAVE_MARBLE
0066     else if (isGeoPositionIndex(row))
0067         return geoPositionInfo(role);
0068 #endif
0069     else if (isExivIndex(row))
0070         return exivInfo(role);
0071     else if (isSearchIndex(row))
0072         return searchInfo(role);
0073     else if (isUntaggedImagesIndex(row))
0074         return untaggedImagesInfo(role);
0075     else if (isImageIndex(row))
0076         return imageInfo(role);
0077     return QVariant();
0078 }
0079 
0080 bool Browser::OverviewPage::isCategoryIndex(int row) const
0081 {
0082     return row < categories().count() && row >= 0;
0083 }
0084 
0085 bool Browser::OverviewPage::isGeoPositionIndex(int row) const
0086 {
0087 #ifdef HAVE_MARBLE
0088     return row == categories().count();
0089 #else
0090     Q_UNUSED(row);
0091     return false;
0092 #endif
0093 }
0094 
0095 bool Browser::OverviewPage::isExivIndex(int row) const
0096 {
0097     int exivRow = categories().count();
0098 #ifdef HAVE_MARBLE
0099     exivRow++;
0100 #endif
0101     return row == exivRow;
0102 }
0103 
0104 bool Browser::OverviewPage::isSearchIndex(int row) const
0105 {
0106     return rowCount() - 3 == row;
0107 }
0108 
0109 bool Browser::OverviewPage::isUntaggedImagesIndex(int row) const
0110 {
0111     return rowCount() - 2 == row;
0112 }
0113 
0114 bool Browser::OverviewPage::isImageIndex(int row) const
0115 {
0116     return rowCount() - 1 == row;
0117 }
0118 
0119 QList<DB::CategoryPtr> Browser::OverviewPage::categories() const
0120 {
0121     return DB::ImageDB::instance()->categoryCollection()->categories();
0122 }
0123 
0124 QVariant Browser::OverviewPage::categoryInfo(int row, int role) const
0125 {
0126     const auto category = categories()[row];
0127     if (role == Qt::DisplayRole || role == ValueRole)
0128         return category->name();
0129     else if (role == Qt::DecorationRole)
0130         return category->icon(THUMBNAILSIZE);
0131     else if (role == SortPriorityRole) {
0132         if (category->isSpecialCategory()) {
0133             return 1;
0134         } else {
0135             return 0;
0136         }
0137     }
0138 
0139     return QVariant();
0140 }
0141 
0142 QVariant Browser::OverviewPage::geoPositionInfo(int role) const
0143 {
0144     if (role == Qt::DisplayRole || role == ValueRole)
0145         return i18n("Geo Position");
0146     else if (role == Qt::DecorationRole) {
0147         return QIcon::fromTheme(QString::fromLatin1("globe")).pixmap(THUMBNAILSIZE);
0148     } else if (role == SortPriorityRole)
0149         return 2;
0150 
0151     return QVariant();
0152 }
0153 
0154 QVariant Browser::OverviewPage::exivInfo(int role) const
0155 {
0156     if (role == Qt::DisplayRole || role == ValueRole)
0157         return i18n("Exif Info");
0158     else if (role == Qt::DecorationRole) {
0159         return QIcon::fromTheme(QString::fromLatin1("document-properties")).pixmap(THUMBNAILSIZE);
0160     } else if (role == SortPriorityRole)
0161         return 2;
0162 
0163     return QVariant();
0164 }
0165 
0166 QVariant Browser::OverviewPage::searchInfo(int role) const
0167 {
0168     if (role == Qt::DisplayRole || role == ValueRole)
0169         return i18nc("@action Search button in the browser view.", "Search");
0170     else if (role == Qt::DecorationRole)
0171         return QIcon::fromTheme(QString::fromLatin1("system-search")).pixmap(THUMBNAILSIZE);
0172     else if (role == SortPriorityRole)
0173         return 2;
0174     return QVariant();
0175 }
0176 
0177 QVariant Browser::OverviewPage::untaggedImagesInfo(int role) const
0178 {
0179     if (role == Qt::DisplayRole || role == ValueRole)
0180         return i18n("Untagged Images");
0181     else if (role == Qt::DecorationRole)
0182         return QIcon::fromTheme(QString::fromUtf8("archive-insert")).pixmap(THUMBNAILSIZE);
0183     else if (role == SortPriorityRole)
0184         return 3;
0185     return QVariant();
0186 }
0187 
0188 QVariant Browser::OverviewPage::imageInfo(int role) const
0189 {
0190     if (role == Qt::DisplayRole || role == ValueRole)
0191         return i18n("Show Thumbnails");
0192     else if (role == Qt::DecorationRole) {
0193         QIcon icon = QIcon::fromTheme(QString::fromUtf8("view-preview"));
0194         QPixmap pixmap = icon.pixmap(THUMBNAILSIZE);
0195         // workaround for QListView in Qt 5.5:
0196         // On Qt5.5 if the last item in the list view has no DecorationRole, then
0197         // the whole list view "collapses" to the size of text-only items,
0198         // cutting off the existing thumbnails.
0199         // This can be triggered by an incomplete icon theme.
0200         if (pixmap.isNull()) {
0201             pixmap = QPixmap(THUMBNAILSIZE, THUMBNAILSIZE);
0202             pixmap.fill(Qt::transparent);
0203         }
0204         return pixmap;
0205     } else if (role == SortPriorityRole)
0206         return 4;
0207     return QVariant();
0208 }
0209 
0210 Browser::BrowserPage *Browser::OverviewPage::activateChild(const QModelIndex &index)
0211 {
0212     const int row = index.row();
0213 
0214     if (isCategoryIndex(row))
0215         return new Browser::CategoryPage(categories()[row], BrowserPage::searchInfo(), browser());
0216 #ifdef HAVE_MARBLE
0217     else if (isGeoPositionIndex(row))
0218         return new Browser::GeoPositionPage(BrowserPage::searchInfo(), browser());
0219 #endif
0220     else if (isExivIndex(row))
0221         return activateExivAction();
0222     else if (isSearchIndex(row))
0223         return activateSearchAction();
0224     else if (isUntaggedImagesIndex(row)) {
0225         return activateUntaggedImagesAction();
0226     } else if (isImageIndex(row))
0227         return new ImageViewPage(BrowserPage::searchInfo(), browser());
0228 
0229     return nullptr;
0230 }
0231 
0232 void Browser::OverviewPage::activate()
0233 {
0234     updateImageCount();
0235     browser()->setModel(this);
0236 }
0237 
0238 Qt::ItemFlags Browser::OverviewPage::flags(const QModelIndex &index) const
0239 {
0240     if (isCategoryIndex(index.row()) && !m_rowHasSubcategories[index.row()])
0241         return QAbstractListModel::flags(index) & ~Qt::ItemIsEnabled;
0242     else
0243         return QAbstractListModel::flags(index);
0244 }
0245 
0246 bool Browser::OverviewPage::isSearchable() const
0247 {
0248     return true;
0249 }
0250 
0251 Browser::BrowserPage *Browser::OverviewPage::activateExivAction()
0252 {
0253     QPointer<Exif::SearchDialog> dialog = new Exif::SearchDialog(DB::ImageDB::instance()->exifDB(), browser());
0254 
0255     {
0256         Utilities::ShowBusyCursor undoTheBusyWhileShowingTheDialog(Qt::ArrowCursor);
0257 
0258         if (dialog->exec() == QDialog::Rejected) {
0259             delete dialog;
0260             return nullptr;
0261         }
0262         // Dialog can be deleted by its parent in event loop while in exec()
0263         if (dialog.isNull())
0264             return nullptr;
0265     }
0266 
0267     DB::ImageSearchInfo info = BrowserPage::searchInfo();
0268     info.addExifSearchInfo(dialog->info());
0269 
0270     delete dialog;
0271 
0272     if (DB::ImageDB::instance()->count(info).total() == 0) {
0273         KMessageBox::information(browser(), i18n("Search did not match any images or videos."), i18n("Empty Search Result"));
0274         return nullptr;
0275     }
0276 
0277     return new OverviewPage(Breadcrumb(i18n("Exif Search")), info, browser());
0278 }
0279 
0280 Browser::BrowserPage *Browser::OverviewPage::activateSearchAction()
0281 {
0282     if (!s_config)
0283         s_config = new AnnotationDialog::Dialog(browser());
0284 
0285     Utilities::ShowBusyCursor undoTheBusyWhileShowingTheDialog(Qt::ArrowCursor);
0286     DB::ImageSearchInfo tmpInfo = BrowserPage::searchInfo();
0287     DB::ImageSearchInfo info = s_config->search(&tmpInfo); // PENDING(blackie) why take the address?
0288 
0289     if (info.isNull())
0290         return nullptr;
0291 
0292     if (DB::ImageDB::instance()->count(info).total() == 0) {
0293         KMessageBox::information(browser(), i18n("Search did not match any images or videos."), i18n("Empty Search Result"));
0294         return nullptr;
0295     }
0296 
0297     return new OverviewPage(Breadcrumb(i18nc("Breadcrumb denoting that we 'browsed' to a search result.", "search")), info, browser());
0298 }
0299 
0300 Browser::Breadcrumb Browser::OverviewPage::breadcrumb() const
0301 {
0302     return m_breadcrumb;
0303 }
0304 
0305 bool Browser::OverviewPage::showDuringMovement() const
0306 {
0307     return true;
0308 }
0309 
0310 void Browser::OverviewPage::updateImageCount()
0311 {
0312     QElapsedTimer timer;
0313     timer.start();
0314     int row = 0;
0315     for (const DB::CategoryPtr &category : categories()) {
0316         QMap<QString, DB::CountWithRange> items = DB::ImageDB::instance()->classify(BrowserPage::searchInfo(), category->name(), DB::anyMediaType, DB::ClassificationMode::PartialCount);
0317         m_rowHasSubcategories[row] = items.count() > 1;
0318         ++row;
0319     }
0320     qCDebug(TimingLog) << "Browser::Overview::updateImageCount(): " << timer.elapsed() << "ms.";
0321 }
0322 
0323 Browser::BrowserPage *Browser::OverviewPage::activateUntaggedImagesAction()
0324 {
0325     if (DB::ImageDB::instance()->untaggedCategoryFeatureConfigured()) {
0326         DB::ImageSearchInfo info = BrowserPage::searchInfo();
0327         info.addAnd(Settings::SettingsData::instance()->untaggedCategory(),
0328                     Settings::SettingsData::instance()->untaggedTag());
0329         return new ImageViewPage(info, browser());
0330     } else {
0331         // Note: the same dialog text is used in MainWindow::Window::slotMarkUntagged(),
0332         // so if it is changed, be sure to also change it there!
0333         KMessageBox::information(browser(),
0334                                  i18n("<p>You have not yet configured which tag to use for indicating untagged images.</p>"
0335                                       "<p>Please follow these steps to do so:"
0336                                       "<ul><li>In the menu bar choose <b>Settings</b></li>"
0337                                       "<li>From there choose <b>Configure KPhotoAlbum</b></li>"
0338                                       "<li>Now choose the <b>Categories</b> icon</li>"
0339                                       "<li>Now configure section <b>Untagged Images</b></li></ul></p>"),
0340                                  i18n("Feature has not been configured"));
0341         return nullptr;
0342     }
0343 }
0344 
0345 // vi:expandtab:tabstop=4 shiftwidth=4: