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: