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

0001 // SPDX-FileCopyrightText: 2003-2022 Jesper K. Pedersen <jesper.pedersen@kdab.com>
0002 // SPDX-FileCopyrightText: 2005, 2007 Dirk Mueller <mueller@kde.org>
0003 // SPDX-FileCopyrightText: 2006 Tuomas Suutari <tuomas@nepnep.net>
0004 // SPDX-FileCopyrightText: 2008 Henner Zeller <h.zeller@acm.org>
0005 // SPDX-FileCopyrightText: 2008-2009 Jan Kundrát <jkt@flaska.net>
0006 // SPDX-FileCopyrightText: 2011 Andreas Neustifter <andreas.neustifter@gmail.com>
0007 // SPDX-FileCopyrightText: 2012 Miika Turkia <miika.turkia@gmail.com>
0008 // SPDX-FileCopyrightText: 2013-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0009 // SPDX-FileCopyrightText: 2015-2018 Tobias Leupold <tl@stonemx.de>
0010 // SPDX-FileCopyrightText: 2016 Matthias Füssel <matthias.fuessel@gmx.net>
0011 //
0012 // SPDX-License-Identifier: GPL-2.0-or-later
0013 
0014 #include "BrowserWidget.h"
0015 
0016 #include "CategoryPage.h"
0017 #include "ImageViewPage.h"
0018 #include "OverviewPage.h"
0019 #include "TreeCategoryModel.h"
0020 #include "TreeFilter.h"
0021 #include "enums.h"
0022 
0023 #include <DB/CategoryCollection.h>
0024 #include <DB/ImageDB.h>
0025 #include <DB/search/ImageSearchInfo.h>
0026 #include <Utilities/FileUtil.h>
0027 #include <Utilities/ShowBusyCursor.h>
0028 #include <kpabase/SettingsData.h>
0029 
0030 #include <KLocalizedString>
0031 #include <QApplication>
0032 #include <QHBoxLayout>
0033 #include <QHeaderView>
0034 #include <QKeyEvent>
0035 #include <QMouseEvent>
0036 #include <QStackedWidget>
0037 #include <QTreeView>
0038 #include <qtimer.h>
0039 
0040 Browser::BrowserWidget *Browser::BrowserWidget::s_instance = nullptr;
0041 bool Browser::BrowserWidget::s_isResizing = false;
0042 
0043 Browser::BrowserWidget::BrowserWidget(QWidget *parent)
0044     : QWidget(parent)
0045     , m_current(-1)
0046 {
0047     Q_ASSERT(!s_instance);
0048     s_instance = this;
0049 
0050     createWidgets();
0051 
0052     connect(DB::ImageDB::instance()->categoryCollection(), &DB::CategoryCollection::categoryCollectionChanged,
0053             this, &BrowserWidget::reload);
0054     connect(this, &BrowserWidget::viewChanged, this, &BrowserWidget::resetIconViewSearch);
0055     connect(this, &BrowserWidget::viewChanged, DB::ImageDB::instance(), &DB::ImageDB::setCurrentScope);
0056 
0057     m_filterProxy = new TreeFilter(this);
0058     m_filterProxy->setFilterKeyColumn(0);
0059     m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
0060     m_filterProxy->setSortRole(ValueRole);
0061     m_filterProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
0062 
0063     addAction(new OverviewPage(Breadcrumb::home(), DB::ImageSearchInfo(), this));
0064     QTimer::singleShot(0, this, SLOT(emitSignals()));
0065 }
0066 
0067 void Browser::BrowserWidget::forward()
0068 {
0069     int targetIndex = m_current;
0070     while (targetIndex < m_list.count() - 1) {
0071         targetIndex++;
0072         if (m_list[targetIndex]->showDuringMovement()) {
0073             break;
0074         }
0075     }
0076     activatePage(targetIndex);
0077 }
0078 
0079 void Browser::BrowserWidget::back()
0080 {
0081     int targetIndex = m_current;
0082     while (targetIndex > 0) {
0083         targetIndex--;
0084         if (m_list[targetIndex]->showDuringMovement())
0085             break;
0086     }
0087     activatePage(targetIndex);
0088 }
0089 
0090 void Browser::BrowserWidget::activatePage(int pageIndex)
0091 {
0092     if (pageIndex != m_current) {
0093         if (currentAction() != nullptr) {
0094             currentAction()->deactivate();
0095         }
0096         m_current = pageIndex;
0097         go();
0098     }
0099 }
0100 
0101 void Browser::BrowserWidget::go()
0102 {
0103     switchToViewType(currentAction()->viewType());
0104     currentAction()->activate();
0105     setBranchOpen(QModelIndex(), true);
0106     adjustTreeViewColumnSize();
0107     emitSignals();
0108 }
0109 
0110 void Browser::BrowserWidget::addSearch(DB::ImageSearchInfo &info)
0111 {
0112     addAction(new OverviewPage(Breadcrumb::empty(), info, this));
0113 }
0114 
0115 void Browser::BrowserWidget::addImageView(const DB::FileName &context)
0116 {
0117     addAction(new ImageViewPage(context, this));
0118 }
0119 
0120 void Browser::BrowserWidget::addAction(Browser::BrowserPage *action)
0121 {
0122     // remove actions which would go forward in the breadcrumbs
0123     while (m_list.count() > m_current + 1) {
0124         BrowserPage *m = m_list.back();
0125         m_list.pop_back();
0126         delete m;
0127     }
0128 
0129     m_list.append(action);
0130     activatePage(m_list.count() - 1);
0131 }
0132 
0133 void Browser::BrowserWidget::emitSignals()
0134 {
0135     Q_EMIT canGoBack(m_current > 0);
0136     Q_EMIT canGoForward(m_current < m_list.count() - 1);
0137     if (currentAction()->viewer() == ShowBrowser)
0138         Q_EMIT showingOverview();
0139 
0140     Q_EMIT isSearchable(currentAction()->isSearchable());
0141     Q_EMIT isFilterable(currentAction()->viewer() == ShowImageViewer);
0142     Q_EMIT isViewChangeable(currentAction()->isViewChangeable());
0143 
0144     bool isCategoryAction = (dynamic_cast<CategoryPage *>(currentAction()) != nullptr);
0145 
0146     if (isCategoryAction) {
0147         DB::CategoryPtr category = DB::ImageDB::instance()->categoryCollection()->categoryForName(currentCategory());
0148         // category may be deleted by now:
0149         if (!category) {
0150             home();
0151             return;
0152         }
0153     }
0154 
0155     Q_EMIT pathChanged(createPath());
0156     Q_EMIT viewChanged(currentAction()->searchInfo());
0157     Q_EMIT imageCount(DB::ImageDB::instance()->count(currentAction()->searchInfo()).total());
0158 }
0159 
0160 void Browser::BrowserWidget::home()
0161 {
0162     addAction(new OverviewPage(Breadcrumb::home(), DB::ImageSearchInfo(), this));
0163 }
0164 
0165 void Browser::BrowserWidget::reload()
0166 {
0167     currentAction()->activate();
0168 }
0169 
0170 Browser::BrowserWidget *Browser::BrowserWidget::instance()
0171 {
0172     Q_ASSERT(s_instance);
0173     return s_instance;
0174 }
0175 
0176 void Browser::BrowserWidget::load(const QString &category, const QString &value)
0177 {
0178     DB::ImageSearchInfo info;
0179     info.addAnd(category, value);
0180 
0181     DB::MediaCount counts = DB::ImageDB::instance()->count(info);
0182     bool loadImages = (counts.total() < Settings::SettingsData::instance()->autoShowThumbnailView());
0183     if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
0184         loadImages = !loadImages;
0185 
0186     if (loadImages)
0187         addAction(new ImageViewPage(info, this));
0188     else
0189         addAction(new OverviewPage(Breadcrumb(value, true), info, this));
0190 
0191     go();
0192     topLevelWidget()->raise();
0193     activateWindow();
0194 }
0195 
0196 DB::ImageSearchInfo Browser::BrowserWidget::currentContext()
0197 {
0198     return currentAction()->searchInfo();
0199 }
0200 
0201 void Browser::BrowserWidget::slotSmallListView()
0202 {
0203     changeViewTypeForCurrentView(DB::Category::TreeView);
0204 }
0205 
0206 void Browser::BrowserWidget::slotLargeListView()
0207 {
0208     changeViewTypeForCurrentView(DB::Category::ThumbedTreeView);
0209 }
0210 
0211 void Browser::BrowserWidget::slotSmallIconView()
0212 {
0213     changeViewTypeForCurrentView(DB::Category::IconView);
0214 }
0215 
0216 void Browser::BrowserWidget::slotLargeIconView()
0217 {
0218     changeViewTypeForCurrentView(DB::Category::ThumbedIconView);
0219 }
0220 
0221 void Browser::BrowserWidget::slotSortViewNaturally(bool on)
0222 {
0223     m_filterProxy->setNaturalSortOrder(on);
0224     reload();
0225 }
0226 
0227 void Browser::BrowserWidget::changeViewTypeForCurrentView(DB::Category::ViewType type)
0228 {
0229     Q_ASSERT(m_list.size() > 0);
0230 
0231     DB::CategoryPtr category = DB::ImageDB::instance()->categoryCollection()->categoryForName(currentCategory());
0232     Q_ASSERT(category.data());
0233     category->setViewType(type);
0234 
0235     switchToViewType(type);
0236     reload();
0237 }
0238 
0239 void Browser::BrowserWidget::setFocus()
0240 {
0241     m_curView->setFocus();
0242 }
0243 
0244 QString Browser::BrowserWidget::currentCategory() const
0245 {
0246     if (CategoryPage *action = dynamic_cast<CategoryPage *>(currentAction()))
0247         return action->category()->name();
0248     else
0249         return QString();
0250 }
0251 
0252 void Browser::BrowserWidget::slotLimitToMatch(const QString &str)
0253 {
0254     m_filterProxy->resetCache();
0255     m_filterProxy->setFilterFixedString(str);
0256     setBranchOpen(QModelIndex(), true);
0257     adjustTreeViewColumnSize();
0258 
0259     // if nothing is selected, select the first item to make the UI consistent with the behaviour of slotInvokeSelected:
0260     if (!m_curView->currentIndex().isValid()) {
0261         if (m_filterProxy->rowCount(QModelIndex()) > 0) {
0262             // Use the first item
0263             m_curView->selectionModel()->select(m_filterProxy->index(0, 0, QModelIndex()), QItemSelectionModel::Select);
0264         }
0265     }
0266 }
0267 
0268 void Browser::BrowserWidget::resetIconViewSearch()
0269 {
0270     m_filterProxy->resetCache();
0271     m_filterProxy->setFilterRegExp(QString());
0272     adjustTreeViewColumnSize();
0273 }
0274 
0275 void Browser::BrowserWidget::slotInvokeSeleted()
0276 {
0277     if (!m_curView->currentIndex().isValid()) {
0278         if (m_filterProxy->rowCount(QModelIndex()) == 0) {
0279             // Absolutely nothing to see here :-)
0280             return;
0281         } else {
0282             // Use the first item
0283             itemClicked(m_filterProxy->index(0, 0, QModelIndex()));
0284         }
0285     } else
0286         itemClicked(m_curView->currentIndex());
0287 }
0288 
0289 void Browser::BrowserWidget::itemClicked(const QModelIndex &index)
0290 {
0291     Utilities::ShowBusyCursor busy;
0292     BrowserPage *action = currentAction()->activateChild(m_filterProxy->mapToSource(index));
0293     if (action)
0294         addAction(action);
0295 }
0296 
0297 Browser::BrowserPage *Browser::BrowserWidget::currentAction() const
0298 {
0299     return m_current >= 0 ? m_list[m_current] : nullptr;
0300 }
0301 
0302 void Browser::BrowserWidget::setModel(QAbstractItemModel *model)
0303 {
0304     m_filterProxy->setSourceModel(model);
0305     // make sure the view knows about the source model change:
0306     m_curView->setModel(m_filterProxy);
0307 
0308     const auto *treeModel = qobject_cast<TreeCategoryModel *>(model);
0309     if (treeModel) {
0310         connect(treeModel, &TreeCategoryModel::dataChanged, this, &BrowserWidget::reload);
0311     }
0312 }
0313 
0314 void Browser::BrowserWidget::switchToViewType(DB::Category::ViewType type)
0315 {
0316     if (m_curView) {
0317         m_curView->setModel(nullptr);
0318         disconnect(m_curView, &QAbstractItemView::activated, this, &BrowserWidget::itemClicked);
0319     }
0320 
0321     if (type == DB::Category::TreeView || type == DB::Category::ThumbedTreeView) {
0322         m_curView = m_treeView;
0323     } else {
0324         m_curView = m_listView;
0325         m_filterProxy->invalidate();
0326         m_filterProxy->sort(0, Qt::AscendingOrder); // sort by column 0
0327 
0328         m_listView->setViewMode(dynamic_cast<OverviewPage *>(currentAction()) == nullptr ? CenteringIconView::NormalIconView : CenteringIconView::CenterView);
0329     }
0330 
0331     if (CategoryPage *action = dynamic_cast<CategoryPage *>(currentAction())) {
0332         const int size = action->category()->thumbnailSize();
0333         m_curView->setIconSize(QSize(size, size));
0334         //        m_curView->setGridSize( QSize( size+10, size+10 ) );
0335     }
0336 
0337     // Hook up the new view
0338     m_curView->setModel(m_filterProxy);
0339     connect(m_curView, &QAbstractItemView::activated, this, &BrowserWidget::itemClicked);
0340 
0341     m_stack->setCurrentWidget(m_curView);
0342     adjustTreeViewColumnSize();
0343 }
0344 
0345 void Browser::BrowserWidget::setBranchOpen(const QModelIndex &parent, bool open)
0346 {
0347     if (m_curView != m_treeView)
0348         return;
0349 
0350     const int count = m_filterProxy->rowCount(parent);
0351     if (count > 5)
0352         open = false;
0353 
0354     m_treeView->setExpanded(parent, open);
0355     for (int row = 0; row < count; ++row)
0356         setBranchOpen(m_filterProxy->index(row, 0, parent), open);
0357 }
0358 
0359 Browser::BreadcrumbList Browser::BrowserWidget::createPath() const
0360 {
0361     BreadcrumbList result;
0362 
0363     for (int i = 0; i <= m_current; ++i)
0364         result.append(m_list[i]->breadcrumb());
0365 
0366     return result;
0367 }
0368 
0369 void Browser::BrowserWidget::widenToBreadcrumb(const Browser::Breadcrumb &breadcrumb)
0370 {
0371     while (currentAction()->breadcrumb() != breadcrumb)
0372         m_current--;
0373     go();
0374 }
0375 
0376 void Browser::BrowserWidget::adjustTreeViewColumnSize()
0377 {
0378     m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
0379 }
0380 
0381 void Browser::BrowserWidget::createWidgets()
0382 {
0383     m_stack = new QStackedWidget;
0384     QHBoxLayout *layout = new QHBoxLayout(this);
0385     layout->setContentsMargins(0, 0, 0, 0);
0386     layout->addWidget(m_stack);
0387 
0388     m_listView = new CenteringIconView(m_stack);
0389     m_listView->setIconSize(QSize(100, 75));
0390     m_listView->setSelectionMode(QListView::SingleSelection);
0391     m_listView->setSpacing(10);
0392     m_listView->setUniformItemSizes(true);
0393     m_listView->setResizeMode(QListView::Adjust);
0394     m_stack->addWidget(m_listView);
0395 
0396     m_treeView = new QTreeView(m_stack);
0397 
0398     m_treeView->setDragEnabled(true);
0399     m_treeView->setAcceptDrops(true);
0400     m_treeView->setDropIndicatorShown(true);
0401     m_treeView->setDefaultDropAction(Qt::MoveAction);
0402     m_treeView->setBackgroundRole(QPalette::Window);
0403 
0404     m_treeView->header()->setStretchLastSection(false);
0405     m_treeView->header()->setSortIndicatorShown(true);
0406     m_treeView->setSortingEnabled(true);
0407     m_treeView->sortByColumn(0, Qt::AscendingOrder);
0408     m_stack->addWidget(m_treeView);
0409 
0410     // Do not give focus to the widgets when they are scrolled with the wheel.
0411     m_listView->setFocusPolicy(Qt::StrongFocus);
0412     m_treeView->setFocusPolicy(Qt::StrongFocus);
0413 
0414     m_treeView->installEventFilter(this);
0415     m_treeView->viewport()->installEventFilter(this);
0416     m_listView->installEventFilter(this);
0417     m_listView->viewport()->installEventFilter(this);
0418 
0419     connect(m_treeView, &QTreeView::expanded, this, &BrowserWidget::adjustTreeViewColumnSize);
0420 
0421     m_curView = nullptr;
0422 }
0423 
0424 bool Browser::BrowserWidget::eventFilter(QObject * /* obj */, QEvent *event)
0425 {
0426     if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonRelease) {
0427         QMouseEvent *me = static_cast<QMouseEvent *>(event);
0428         Q_ASSERT(me != nullptr);
0429         if (me->buttons() & Qt::MiddleButton || me->button() & Qt::MiddleButton) {
0430             handleResizeEvent(me);
0431             return true;
0432         }
0433     }
0434     if (event->type() == QEvent::KeyPress) {
0435         const auto *keyEvent = static_cast<QKeyEvent *>(event);
0436         Q_ASSERT(keyEvent != nullptr);
0437         if (keyEvent->key() == Qt::Key_Slash) {
0438             Q_EMIT showSearch();
0439         }
0440     }
0441 
0442     return false;
0443 }
0444 
0445 void Browser::BrowserWidget::scrollKeyPressed(QKeyEvent *event)
0446 {
0447     QApplication::sendEvent(m_curView, event);
0448 }
0449 
0450 void Browser::BrowserWidget::handleResizeEvent(QMouseEvent *event)
0451 {
0452     static int offset;
0453 
0454     CategoryPage *action = dynamic_cast<CategoryPage *>(currentAction());
0455     if (!action)
0456         return;
0457 
0458     DB::CategoryPtr category = action->category();
0459 
0460     if (event->type() == QEvent::MouseButtonPress) {
0461         m_resizePressPos = event->pos();
0462         offset = category->thumbnailSize();
0463         s_isResizing = true;
0464     }
0465 
0466     else if (event->type() == QEvent::MouseMove) {
0467         int distance = (event->pos() - m_resizePressPos).x() + (event->pos() - m_resizePressPos).y() / 3;
0468         int size = distance + offset;
0469         size = qMax(qMin(512, size), 32);
0470         action->category()->setThumbnailSize(size);
0471 
0472         m_curView->setIconSize(QSize(size, size));
0473         m_filterProxy->invalidate();
0474         adjustTreeViewColumnSize();
0475     } else if (event->type() == QEvent::MouseButtonRelease) {
0476         s_isResizing = false;
0477         update();
0478     }
0479 }
0480 
0481 // vi:expandtab:tabstop=4 shiftwidth=4:
0482 
0483 #include "moc_BrowserWidget.cpp"