File indexing completed on 2024-05-19 08:11:52
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"