File indexing completed on 2024-05-05 04:19:20
0001 /* 0002 Gwenview: an image viewer 0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU General Public License 0007 as published by the Free Software Foundation; either version 2 0008 of the License, or (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 GNU General Public License for more details. 0014 0015 You should have received a copy of the GNU General Public License 0016 along with this program; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 0019 */ 0020 #include "viewmainpage.h" 0021 #include "config-gwenview.h" 0022 0023 // Qt 0024 #include <QApplication> 0025 #include <QCheckBox> 0026 #include <QItemSelectionModel> 0027 #include <QMenu> 0028 #include <QShortcut> 0029 #include <QVBoxLayout> 0030 0031 // KF 0032 #include <KActionCategory> 0033 #include <KActionCollection> 0034 #include <KLocalizedString> 0035 #include <KMessageBox> 0036 #include <KModelIndexProxyMapper> 0037 #include <KSqueezedTextLabel> 0038 #include <KToggleAction> 0039 #if HAVE_KACTIVITIES 0040 #include <PlasmaActivities/ResourceInstance> 0041 #endif 0042 0043 // Local 0044 #include "fileoperations.h" 0045 #include "gwenview_app_debug.h" 0046 #include "splitter.h" 0047 #include "spotlightmode.h" 0048 #include <gvcore.h> 0049 #include <lib/documentview/abstractdocumentviewadapter.h> 0050 #include <lib/documentview/abstractrasterimageviewtool.h> 0051 #include <lib/documentview/documentview.h> 0052 #include <lib/documentview/documentviewcontainer.h> 0053 #include <lib/documentview/documentviewcontroller.h> 0054 #include <lib/documentview/documentviewsynchronizer.h> 0055 #include <lib/fullscreenbar.h> 0056 #include <lib/gvdebug.h> 0057 #include <lib/gwenviewconfig.h> 0058 #include <lib/mimetypeutils.h> 0059 #include <lib/paintutils.h> 0060 #include <lib/semanticinfo/sorteddirmodel.h> 0061 #include <lib/slidecontainer.h> 0062 #include <lib/slideshow.h> 0063 #include <lib/statusbartoolbutton.h> 0064 #include <lib/stylesheetutils.h> 0065 #include <lib/thumbnailview/thumbnailbarview.h> 0066 #include <lib/zoommode.h> 0067 #include <lib/zoomwidget.h> 0068 0069 namespace Gwenview 0070 { 0071 #undef ENABLE_LOG 0072 #undef LOG 0073 // #define ENABLE_LOG 0074 #ifdef ENABLE_LOG 0075 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x 0076 #else 0077 #define LOG(x) ; 0078 #endif 0079 0080 const int ViewMainPage::MaxViewCount = 6; 0081 0082 /* 0083 * Layout of mThumbnailSplitter is: 0084 * 0085 * +-mThumbnailSplitter------------------------------------------------+ 0086 * |+-mAdapterContainer-----------------------------------------------+| 0087 * ||+-mDocumentViewContainer----------------------------------------+|| 0088 * |||+-DocumentView----------------++-DocumentView-----------------+||| 0089 * |||| || |||| 0090 * |||| || |||| 0091 * |||| || |||| 0092 * |||| || |||| 0093 * |||| || |||| 0094 * |||| || |||| 0095 * |||+-----------------------------++------------------------------+||| 0096 * ||+---------------------------------------------------------------+|| 0097 * ||+-mToolContainer------------------------------------------------+|| 0098 * ||| ||| 0099 * ||+---------------------------------------------------------------+|| 0100 * |+-----------------------------------------------------------------+| 0101 * |===================================================================| 0102 * |+-mThumbnailBar---------------------------------------------------+| 0103 * || || 0104 * || || 0105 * |+-mStatusBarContainer---------------------------------------------+| 0106 * ||[mToggleSideBarButton][mToggleThumbnailBarButton] [mZoomWidget]|| 0107 * |+-----------------------------------------------------------------+| 0108 * +-------------------------------------------------------------------+ 0109 */ 0110 struct ViewMainPagePrivate { 0111 ViewMainPage *q = nullptr; 0112 SlideShow *mSlideShow = nullptr; 0113 KActionCollection *mActionCollection = nullptr; 0114 GvCore *mGvCore = nullptr; 0115 KModelIndexProxyMapper *mDirModelToBarModelProxyMapper = nullptr; 0116 QSplitter *mThumbnailSplitter = nullptr; 0117 QWidget *mAdapterContainer = nullptr; 0118 DocumentViewController *mDocumentViewController = nullptr; 0119 QList<DocumentView *> mDocumentViews; 0120 DocumentViewSynchronizer *mSynchronizer = nullptr; 0121 QToolButton *mToggleSideBarButton = nullptr; 0122 QToolButton *mToggleThumbnailBarButton = nullptr; 0123 ZoomWidget *mZoomWidget = nullptr; 0124 DocumentViewContainer *mDocumentViewContainer = nullptr; 0125 SlideContainer *mToolContainer = nullptr; 0126 QWidget *mStatusBarContainer = nullptr; 0127 ThumbnailBarView *mThumbnailBar = nullptr; 0128 KToggleAction *mToggleThumbnailBarAction = nullptr; 0129 KToggleAction *mSynchronizeAction = nullptr; 0130 QCheckBox *mSynchronizeCheckBox = nullptr; 0131 KSqueezedTextLabel *mDocumentCountLabel = nullptr; 0132 SpotlightMode *mSpotlightMode = nullptr; 0133 0134 bool mCompareMode; 0135 ZoomMode::Enum mZoomMode; 0136 0137 void setupWidgets() 0138 { 0139 mToolContainer = new SlideContainer; 0140 mToolContainer->setAutoFillBackground(true); 0141 mToolContainer->setBackgroundRole(QPalette::Mid); 0142 //-- 0143 mStatusBarContainer = new QWidget; 0144 mStatusBarContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 0145 mToggleSideBarButton = new StatusBarToolButton; 0146 mToggleThumbnailBarButton = new StatusBarToolButton; 0147 mZoomWidget = new ZoomWidget; 0148 mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize")); 0149 mSynchronizeCheckBox->hide(); 0150 mDocumentCountLabel = new KSqueezedTextLabel; 0151 mDocumentCountLabel->setAlignment(Qt::AlignCenter); 0152 mDocumentCountLabel->setTextElideMode(Qt::ElideRight); 0153 QMargins labelMargins = mDocumentCountLabel->contentsMargins(); 0154 labelMargins.setLeft(15); 0155 labelMargins.setRight(15); 0156 mDocumentCountLabel->setContentsMargins(labelMargins); 0157 0158 auto statusBarContainerLayout = new QHBoxLayout(mStatusBarContainer); 0159 // Use toolbar-like margins and spacing 0160 int margins = q->style()->pixelMetric(QStyle::PM_ToolBarItemMargin) + q->style()->pixelMetric(QStyle::PM_ToolBarFrameWidth); 0161 statusBarContainerLayout->setContentsMargins(margins, margins, margins, margins); 0162 statusBarContainerLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_ToolBarItemSpacing)); 0163 statusBarContainerLayout->addWidget(mToggleSideBarButton); 0164 statusBarContainerLayout->addWidget(mToggleThumbnailBarButton); 0165 statusBarContainerLayout->addStretch(); 0166 statusBarContainerLayout->addWidget(mSynchronizeCheckBox); 0167 // Ensure document count label takes up all available space, 0168 // so its autohide feature works properly (stretch factor = 1) 0169 statusBarContainerLayout->addWidget(mDocumentCountLabel, 1); 0170 statusBarContainerLayout->addStretch(); 0171 statusBarContainerLayout->addWidget(mZoomWidget); 0172 //-- 0173 mAdapterContainer = new QWidget; 0174 0175 auto adapterContainerLayout = new QVBoxLayout(mAdapterContainer); 0176 adapterContainerLayout->setContentsMargins(0, 0, 0, 0); 0177 adapterContainerLayout->setSpacing(0); 0178 mDocumentViewContainer = new DocumentViewContainer; 0179 mDocumentViewContainer->setAutoFillBackground(true); 0180 mDocumentViewContainer->setBackgroundRole(QPalette::Base); 0181 adapterContainerLayout->addWidget(mDocumentViewContainer); 0182 adapterContainerLayout->addWidget(mToolContainer); 0183 //-- 0184 mSpotlightMode = new SpotlightMode(mDocumentViewContainer, mActionCollection); 0185 adapterContainerLayout->addWidget(mDocumentViewContainer); 0186 adapterContainerLayout->addWidget(mToolContainer); 0187 //-- 0188 mThumbnailBar = new ThumbnailBarView; 0189 auto delegate = new ThumbnailBarItemDelegate(mThumbnailBar); 0190 mThumbnailBar->setItemDelegate(delegate); 0191 mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); 0192 //-- 0193 Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); 0194 mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q); 0195 mThumbnailBar->setOrientation(orientation); 0196 mThumbnailBar->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); 0197 mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount()); 0198 mThumbnailSplitter->addWidget(mAdapterContainer); 0199 mThumbnailSplitter->addWidget(mThumbnailBar); 0200 mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); 0201 // Show the thumbnail bar after setting the parent to avoid recreating 0202 // the native window and to avoid QTBUG-87345. 0203 mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); 0204 mThumbnailBar->installEventFilter(q); 0205 0206 auto viewMainPageLayout = new QVBoxLayout(q); 0207 viewMainPageLayout->setContentsMargins(0, 0, 0, 0); 0208 viewMainPageLayout->setSpacing(0); 0209 viewMainPageLayout->addWidget(mThumbnailSplitter); 0210 viewMainPageLayout->addWidget(mStatusBarContainer); 0211 //-- 0212 mDocumentViewController = new DocumentViewController(mActionCollection, q); 0213 mDocumentViewController->setZoomWidget(mZoomWidget); 0214 mDocumentViewController->setToolContainer(mToolContainer); 0215 mSynchronizer = new DocumentViewSynchronizer(&mDocumentViews, q); 0216 } 0217 0218 void setupThumbnailBarStyleSheet() 0219 { 0220 QPalette pal = mGvCore->palette(GvCore::NormalViewPalette); 0221 mThumbnailBar->setPalette(pal); 0222 Qt::Orientation orientation = mThumbnailBar->orientation(); 0223 QColor bgColor = pal.color(QPalette::Normal, QPalette::Base); 0224 QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight); 0225 QColor bgHovColor = pal.color(QPalette::Normal, QPalette::Highlight); 0226 0227 // Avoid dark and bright colors 0228 bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4); 0229 0230 // Hover uses lighter/faded version of select color. Combine with bgColor to adapt to different backgrounds 0231 bgHovColor.setHsv(bgHovColor.hue(), (bgHovColor.saturation() / 2), ((bgHovColor.value() + bgColor.value()) / 2)); 0232 0233 QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value())); 0234 QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value())); 0235 QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value())); 0236 0237 QString itemCss = 0238 "QListView::item {" 0239 " background-color: %1;" 0240 " border-left: 1px solid %2;" 0241 " border-right: 1px solid %3;" 0242 "}"; 0243 itemCss = itemCss.arg(StyleSheetUtils::gradient(orientation, bgColor, 46), 0244 StyleSheetUtils::gradient(orientation, leftBorderColor, 36), 0245 StyleSheetUtils::gradient(orientation, rightBorderColor, 26)); 0246 0247 QString itemSelCss = 0248 "QListView::item:selected {" 0249 " background-color: %1;" 0250 " border-left: 1px solid %2;" 0251 " border-right: 1px solid %2;" 0252 "}"; 0253 itemSelCss = itemSelCss.arg(StyleSheetUtils::gradient(orientation, bgSelColor, 56), StyleSheetUtils::rgba(borderSelColor)); 0254 0255 QString itemHovCss = 0256 "QListView::item:hover:!selected {" 0257 " background-color: %1;" 0258 " border-left: 1px solid %2;" 0259 " border-right: 1px solid %3;" 0260 "}"; 0261 itemHovCss = itemHovCss.arg(StyleSheetUtils::gradient(orientation, bgHovColor, 56), 0262 StyleSheetUtils::rgba(leftBorderColor), 0263 StyleSheetUtils::rgba(rightBorderColor)); 0264 0265 QString css = itemCss + itemSelCss + itemHovCss; 0266 if (orientation == Qt::Vertical) { 0267 css.replace(QLatin1String("left"), QLatin1String("top")).replace(QLatin1String("right"), QLatin1String("bottom")); 0268 } 0269 0270 mThumbnailBar->setStyleSheet(css); 0271 } 0272 0273 DocumentView *createDocumentView() 0274 { 0275 DocumentView *view = mDocumentViewContainer->createView(); 0276 0277 // Connect context menu 0278 // If you need to connect another view signal, make sure it is disconnected in deleteDocumentView 0279 QObject::connect(view, &DocumentView::contextMenuRequested, q, &ViewMainPage::showContextMenu); 0280 0281 QObject::connect(view, &DocumentView::completed, q, &ViewMainPage::completed); 0282 QObject::connect(view, &DocumentView::previousImageRequested, q, &ViewMainPage::previousImageRequested); 0283 QObject::connect(view, &DocumentView::nextImageRequested, q, &ViewMainPage::nextImageRequested); 0284 QObject::connect(view, &DocumentView::openUrlRequested, q, &ViewMainPage::openUrlRequested); 0285 QObject::connect(view, &DocumentView::openDirUrlRequested, q, &ViewMainPage::openDirUrlRequested); 0286 QObject::connect(view, &DocumentView::captionUpdateRequested, q, &ViewMainPage::captionUpdateRequested); 0287 QObject::connect(view, &DocumentView::toggleFullScreenRequested, q, &ViewMainPage::toggleFullScreenRequested); 0288 QObject::connect(view, &DocumentView::focused, q, &ViewMainPage::slotViewFocused); 0289 QObject::connect(view, &DocumentView::hudTrashClicked, q, &ViewMainPage::trashView); 0290 QObject::connect(view, &DocumentView::hudDeselectClicked, q, &ViewMainPage::deselectView); 0291 0292 QObject::connect(view, &DocumentView::videoFinished, mSlideShow, &SlideShow::resumeAndGoToNextUrl); 0293 0294 mDocumentViews << view; 0295 0296 return view; 0297 } 0298 0299 void deleteDocumentView(DocumentView *view) 0300 { 0301 if (mDocumentViewController->view() == view) { 0302 mDocumentViewController->setView(nullptr); 0303 } 0304 0305 // Make sure we do not get notified about this view while it is going away. 0306 // mDocumentViewController->deleteView() animates the view deletion so 0307 // the view still exists for a short while when we come back to the 0308 // event loop) 0309 QObject::disconnect(view, nullptr, q, nullptr); 0310 QObject::disconnect(view, nullptr, mSlideShow, nullptr); 0311 0312 mDocumentViews.removeOne(view); 0313 mDocumentViewContainer->deleteView(view); 0314 } 0315 0316 void saveSplitterConfig() 0317 { 0318 if (mThumbnailBar->isVisible()) { 0319 if (mThumbnailSplitter->sizes().constLast() > 0) { 0320 GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes()); 0321 } else { 0322 // mThumbnailBar's size has been collapsed to 0. We reset the sizes to default so users can't "lose" the bar. 0323 const bool didUseDefaults = GwenviewConfig::self()->useDefaults(true); 0324 const auto defaultSplitterSizes = GwenviewConfig::thumbnailSplitterSizes(); 0325 GwenviewConfig::self()->useDefaults(didUseDefaults); 0326 GwenviewConfig::setThumbnailSplitterSizes(defaultSplitterSizes); 0327 } 0328 } 0329 } 0330 0331 DocumentView *currentView() const 0332 { 0333 return mDocumentViewController->view(); 0334 } 0335 0336 void setCurrentView(DocumentView *view) 0337 { 0338 DocumentView *oldView = currentView(); 0339 if (view == oldView) { 0340 return; 0341 } 0342 if (oldView) { 0343 oldView->setCurrent(false); 0344 } 0345 view->setCurrent(true); 0346 mDocumentViewController->setView(view); 0347 mSynchronizer->setCurrentView(view); 0348 0349 QModelIndex index = indexForView(view); 0350 if (index.isValid()) { 0351 // Index may be invalid when Gwenview is started as 0352 // `gwenview /foo/image.png` because in this situation it loads image.png 0353 // *before* listing /foo (because it matters less to the user) 0354 mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); 0355 } 0356 QObject::connect(view, &DocumentView::currentToolChanged, q, &ViewMainPage::updateFocus); 0357 } 0358 0359 QModelIndex indexForView(DocumentView *view) const 0360 { 0361 QUrl url = view->url(); 0362 if (!url.isValid()) { 0363 qCWarning(GWENVIEW_APP_LOG) << "View does not display any document!"; 0364 return {}; 0365 } 0366 0367 SortedDirModel *dirModel = mGvCore->sortedDirModel(); 0368 QModelIndex srcIndex = dirModel->indexForUrl(url); 0369 if (!mDirModelToBarModelProxyMapper) { 0370 // Delay the initialization of the mapper to its first use because 0371 // mThumbnailBar->model() is not set after ViewMainPage ctor is 0372 // done. 0373 const_cast<ViewMainPagePrivate *>(this)->mDirModelToBarModelProxyMapper = new KModelIndexProxyMapper(dirModel, mThumbnailBar->model(), q); 0374 } 0375 QModelIndex index = mDirModelToBarModelProxyMapper->mapLeftToRight(srcIndex); 0376 return index; 0377 } 0378 0379 void applyPalette(bool fullScreenMode) 0380 { 0381 mDocumentViewContainer->applyPalette(mGvCore->palette(fullScreenMode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); 0382 setupThumbnailBarStyleSheet(); 0383 } 0384 0385 void updateDocumentCountLabel() 0386 { 0387 const int current = mThumbnailBar->currentIndex().row() + 1; // zero-based 0388 const int total = mThumbnailBar->model()->rowCount(); 0389 const QString text = i18nc("@info:status %1 current document index, %2 total documents", "%1 of %2", current, total); 0390 mDocumentCountLabel->setText(text); 0391 } 0392 }; 0393 0394 ViewMainPage::ViewMainPage(QWidget *parent, SlideShow *slideShow, KActionCollection *actionCollection, GvCore *gvCore) 0395 : QWidget(parent) 0396 , d(new ViewMainPagePrivate) 0397 { 0398 d->q = this; 0399 d->mDirModelToBarModelProxyMapper = nullptr; // Initialized later 0400 d->mSlideShow = slideShow; 0401 d->mActionCollection = actionCollection; 0402 d->mGvCore = gvCore; 0403 d->mCompareMode = false; 0404 0405 auto enterKeyShortcut = new QShortcut(Qt::Key_Return, this); 0406 connect(enterKeyShortcut, &QShortcut::activated, this, &ViewMainPage::slotEnterPressed); 0407 0408 d->setupWidgets(); 0409 0410 auto view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); 0411 0412 d->mToggleThumbnailBarAction = view->add<KToggleAction>(QStringLiteral("toggle_thumbnailbar")); 0413 d->mToggleThumbnailBarAction->setText(i18n("Show Thumbnails")); 0414 // clang-format off 0415 // i18n: For languages that are read right to left, "right side" refers to the left side in this context. 0416 d->mToggleThumbnailBarAction->setWhatsThis(xi18nc("@info:whatsthis", "<para>This toggles a small bar showing all the other images in the current folder. " 0417 "Selecting one of them changes the currently viewed image.</para><para>Select multiple images at the same time to see them side by side.</para>" 0418 "<para>The bar can optionally be moved to the right side of the screen and contain multiple rows of images; <link url='%1'>configure these options in the settings</link>.</para>", QStringLiteral("gwenview:/config/imageview"))); 0419 // Keep the previous link address in sync with MainWindow::Private::SettingsOpenerHelper::eventFilter(). 0420 // clang-format on 0421 actionCollection->setDefaultShortcut(d->mToggleThumbnailBarAction, Qt::CTRL | Qt::Key_B); 0422 connect(d->mToggleThumbnailBarAction, &KToggleAction::triggered, this, &ViewMainPage::setThumbnailBarVisibility); 0423 d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction); 0424 connect(d->mThumbnailSplitter, &QSplitter::splitterMoved, this, [this]() { 0425 // The splitter can be moved until the bar has a size of zero which makes it invisible. 0426 const bool isThumbnailBarVisible = d->mThumbnailSplitter->sizes().constLast() > 0; 0427 GwenviewConfig::setThumbnailBarIsVisible(isThumbnailBarVisible); 0428 d->mToggleThumbnailBarAction->setChecked(isThumbnailBarVisible); 0429 }); 0430 0431 d->mSynchronizeAction = view->add<KToggleAction>(QStringLiteral("synchronize_views")); 0432 d->mSynchronizeAction->setText(i18n("Synchronize")); 0433 actionCollection->setDefaultShortcut(d->mSynchronizeAction, Qt::CTRL | Qt::Key_Y); 0434 connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizer, &DocumentViewSynchronizer::setActive); 0435 // Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync 0436 connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizeCheckBox, &QAbstractButton::setChecked); 0437 connect(d->mSynchronizeCheckBox, &QAbstractButton::toggled, d->mSynchronizeAction, &QAction::setChecked); 0438 0439 // Connections for the document count 0440 connect(d->mThumbnailBar, &ThumbnailBarView::rowsInsertedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved); 0441 connect(d->mThumbnailBar, &ThumbnailBarView::rowsRemovedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved); 0442 0443 connect(qApp, &QApplication::paletteChanged, this, [this]() { 0444 d->applyPalette(window()->isFullScreen()); 0445 }); 0446 0447 installEventFilter(this); 0448 } 0449 0450 ViewMainPage::~ViewMainPage() 0451 { 0452 delete d; 0453 } 0454 0455 void ViewMainPage::loadConfig() 0456 { 0457 d->applyPalette(window()->isFullScreen()); 0458 0459 // FIXME: Not symmetric with saveConfig(). Check if it matters. 0460 for (DocumentView *view : qAsConst(d->mDocumentViews)) { 0461 view->loadAdapterConfig(); 0462 } 0463 0464 Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); 0465 d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); 0466 d->mThumbnailBar->setOrientation(orientation); 0467 d->setupThumbnailBarStyleSheet(); 0468 d->mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); 0469 d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible()); 0470 0471 int oldRowCount = d->mThumbnailBar->rowCount(); 0472 int newRowCount = GwenviewConfig::thumbnailBarRowCount(); 0473 if (oldRowCount != newRowCount) { 0474 d->mThumbnailBar->setUpdatesEnabled(false); 0475 int gridSize = d->mThumbnailBar->gridSize().width(); 0476 0477 d->mThumbnailBar->setRowCount(newRowCount); 0478 0479 // Adjust splitter to ensure thumbnail size remains the same 0480 int delta = (newRowCount - oldRowCount) * gridSize; 0481 QList<int> sizes = d->mThumbnailSplitter->sizes(); 0482 Q_ASSERT(sizes.count() == 2); 0483 sizes[0] -= delta; 0484 sizes[1] += delta; 0485 d->mThumbnailSplitter->setSizes(sizes); 0486 0487 d->mThumbnailBar->setUpdatesEnabled(true); 0488 } 0489 0490 d->mZoomMode = GwenviewConfig::zoomMode(); 0491 if (GwenviewConfig::spotlightMode() != d->mActionCollection->action(QStringLiteral("view_toggle_spotlightmode"))->isChecked()) 0492 if (parentWidget()->isVisible()) 0493 d->mActionCollection->action(QStringLiteral("view_toggle_spotlightmode"))->trigger(); 0494 } 0495 0496 void ViewMainPage::saveConfig() 0497 { 0498 d->saveSplitterConfig(); 0499 GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked()); 0500 } 0501 0502 void ViewMainPage::setThumbnailBarVisibility(bool visible) 0503 { 0504 d->saveSplitterConfig(); 0505 d->mThumbnailBar->setVisible(visible); 0506 if (visible && d->mThumbnailSplitter->sizes().constLast() <= 0) { 0507 // mThumbnailBar is supposed to be made visible but its splitter has a size of 0 making it invisible. 0508 // We load the last good sizes from the config. 0509 d->mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); 0510 } 0511 } 0512 0513 int ViewMainPage::statusBarHeight() const 0514 { 0515 return d->mStatusBarContainer->height(); 0516 } 0517 0518 void ViewMainPage::setStatusBarVisible(bool visible) 0519 { 0520 d->mStatusBarContainer->setVisible(visible); 0521 } 0522 0523 void ViewMainPage::setFullScreenMode(bool fullScreenMode) 0524 { 0525 if (fullScreenMode) { 0526 d->mThumbnailBar->setVisible(false); 0527 } else { 0528 d->mThumbnailBar->setVisible(d->mToggleThumbnailBarAction->isChecked()); 0529 } 0530 d->applyPalette(fullScreenMode); 0531 d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode); 0532 } 0533 0534 ThumbnailBarView *ViewMainPage::thumbnailBar() const 0535 { 0536 return d->mThumbnailBar; 0537 } 0538 0539 inline void addActionToMenu(QMenu *menu, KActionCollection *actionCollection, const char *name) 0540 { 0541 QAction *action = actionCollection->action(name); 0542 if (action) { 0543 menu->addAction(action); 0544 } 0545 } 0546 0547 inline void addActionToMenu(QMenu *menu, KActionCollection *actionCollection, const QString &name) 0548 { 0549 QAction *action = actionCollection->action(name); 0550 if (action) { 0551 menu->addAction(action); 0552 } 0553 } 0554 0555 void ViewMainPage::showContextMenu() 0556 { 0557 QMenu menu(this); 0558 addActionToMenu(&menu, d->mActionCollection, "fullscreen"); 0559 menu.addSeparator(); 0560 addActionToMenu(&menu, d->mActionCollection, "go_previous"); 0561 addActionToMenu(&menu, d->mActionCollection, "go_next"); 0562 if (d->currentView()->canZoom()) { 0563 menu.addSeparator(); 0564 addActionToMenu(&menu, d->mActionCollection, "view_actual_size"); 0565 addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit"); 0566 addActionToMenu(&menu, d->mActionCollection, "view_zoom_in"); 0567 addActionToMenu(&menu, d->mActionCollection, "view_zoom_out"); 0568 addActionToMenu(&menu, d->mActionCollection, "view_toggle_birdeyeview"); 0569 } 0570 menu.addSeparator(); 0571 QMenu backgroundColorModeMenu(i18nc("@item:inmenu", "Background Color Mode"), this); 0572 addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_auto"); 0573 addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_light"); 0574 addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_neutral"); 0575 addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_dark"); 0576 backgroundColorModeMenu.setIcon(backgroundColorModeMenu.actions().at(0)->icon()); 0577 menu.addMenu(&backgroundColorModeMenu); 0578 if (d->mCompareMode) { 0579 menu.addSeparator(); 0580 addActionToMenu(&menu, d->mActionCollection, "synchronize_views"); 0581 } 0582 0583 menu.addSeparator(); 0584 addActionToMenu(&menu, d->mActionCollection, KStandardAction::name(KStandardAction::Copy)); 0585 addActionToMenu(&menu, d->mActionCollection, "file_copy_to"); 0586 addActionToMenu(&menu, d->mActionCollection, "file_move_to"); 0587 addActionToMenu(&menu, d->mActionCollection, "file_link_to"); 0588 menu.addSeparator(); 0589 addActionToMenu(&menu, d->mActionCollection, "file_open_in_new_window"); 0590 addActionToMenu(&menu, d->mActionCollection, "file_open_with"); 0591 addActionToMenu(&menu, d->mActionCollection, "file_open_containing_folder"); 0592 0593 menu.exec(QCursor::pos()); 0594 } 0595 0596 QSize ViewMainPage::sizeHint() const 0597 { 0598 return QSize(400, 300); 0599 } 0600 0601 QSize ViewMainPage::minimumSizeHint() const 0602 { 0603 if (!layout()) { 0604 return QSize(); 0605 } 0606 0607 QSize minimumSize = layout()->minimumSize(); 0608 if (window()->isFullScreen()) { 0609 // Check minimum width of the overlay fullscreen bar 0610 // since there is no layout link which could do this 0611 const FullScreenBar *fullScreenBar = findChild<FullScreenBar *>(); 0612 if (fullScreenBar && fullScreenBar->layout()) { 0613 const int fullScreenBarWidth = fullScreenBar->layout()->minimumSize().width(); 0614 if (fullScreenBarWidth > minimumSize.width()) { 0615 minimumSize.setWidth(fullScreenBarWidth); 0616 } 0617 } 0618 } 0619 return minimumSize; 0620 } 0621 0622 QUrl ViewMainPage::url() const 0623 { 0624 GV_RETURN_VALUE_IF_FAIL(d->currentView(), QUrl()); 0625 return d->currentView()->url(); 0626 } 0627 0628 Document::Ptr ViewMainPage::currentDocument() const 0629 { 0630 if (!d->currentView()) { 0631 LOG("!d->documentView()"); 0632 return {}; 0633 } 0634 0635 return d->currentView()->document(); 0636 } 0637 0638 bool ViewMainPage::isEmpty() const 0639 { 0640 return !currentDocument(); 0641 } 0642 0643 RasterImageView *ViewMainPage::imageView() const 0644 { 0645 if (!d->currentView()) { 0646 return nullptr; 0647 } 0648 return d->currentView()->imageView(); 0649 } 0650 0651 DocumentView *ViewMainPage::documentView() const 0652 { 0653 return d->currentView(); 0654 } 0655 0656 void ViewMainPage::openUrl(const QUrl &url) 0657 { 0658 openUrls(QList<QUrl>() << url, url); 0659 } 0660 0661 void ViewMainPage::openUrls(const QList<QUrl> &allUrls, const QUrl ¤tUrl) 0662 { 0663 DocumentView::Setup setup; 0664 0665 QSet<QUrl> urls(allUrls.begin(), allUrls.end()); 0666 d->mCompareMode = urls.count() > 1; 0667 0668 using ViewForUrlMap = QMap<QUrl, DocumentView *>; 0669 ViewForUrlMap viewForUrlMap; 0670 0671 if (!d->mDocumentViews.isEmpty()) { 0672 d->mDocumentViewContainer->updateSetup(d->mDocumentViews.last()); 0673 } 0674 if (d->mDocumentViews.isEmpty() || d->mZoomMode == ZoomMode::Autofit) { 0675 setup.valid = true; 0676 setup.zoomToFit = true; 0677 } else { 0678 setup = d->mDocumentViews.last()->setup(); 0679 } 0680 // Destroy views which show urls we don't care about, remove from "urls" the 0681 // urls which already have a view. 0682 for (DocumentView *view : qAsConst(d->mDocumentViews)) { 0683 QUrl url = view->url(); 0684 if (urls.contains(url)) { 0685 // view displays an url we must display, keep it 0686 urls.remove(url); 0687 viewForUrlMap.insert(url, view); 0688 } else { 0689 // view url is not interesting, drop it 0690 d->deleteDocumentView(view); 0691 } 0692 } 0693 0694 // Create view for remaining urls 0695 for (const QUrl &url : qAsConst(urls)) { 0696 if (d->mDocumentViews.count() >= MaxViewCount) { 0697 qCWarning(GWENVIEW_APP_LOG) << "Too many documents to show"; 0698 break; 0699 } 0700 DocumentView *view = d->createDocumentView(); 0701 viewForUrlMap.insert(url, view); 0702 } 0703 0704 // Set sortKey to match url order 0705 int sortKey = 0; 0706 for (const QUrl &url : allUrls) { 0707 viewForUrlMap[url]->setSortKey(sortKey); 0708 ++sortKey; 0709 } 0710 0711 d->mDocumentViewContainer->updateLayout(); 0712 0713 // Load urls for new views. Do it only now because the view must have the 0714 // correct size before it starts loading its url. Do not do it later because 0715 // view->url() needs to be set for the next loop. 0716 ViewForUrlMap::ConstIterator it = viewForUrlMap.constBegin(), end = viewForUrlMap.constEnd(); 0717 for (; it != end; ++it) { 0718 QUrl url = it.key(); 0719 DocumentView *view = it.value(); 0720 DocumentView::Setup savedSetup = d->mDocumentViewContainer->savedSetup(url); 0721 view->openUrl(url, d->mZoomMode == ZoomMode::Individual && savedSetup.valid ? savedSetup : setup); 0722 #if HAVE_KACTIVITIES 0723 if (GwenviewConfig::historyEnabled()) { 0724 KActivities::ResourceInstance::notifyAccessed(url); 0725 } 0726 #endif 0727 } 0728 0729 // Init views 0730 for (DocumentView *view : qAsConst(d->mDocumentViews)) { 0731 view->setCompareMode(d->mCompareMode); 0732 if (view->url() == currentUrl) { 0733 d->setCurrentView(view); 0734 } else { 0735 view->setCurrent(false); 0736 } 0737 } 0738 0739 d->mSynchronizeCheckBox->setVisible(d->mCompareMode); 0740 if (d->mCompareMode) { 0741 d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked()); 0742 } else { 0743 d->mSynchronizer->setActive(false); 0744 } 0745 0746 d->updateDocumentCountLabel(); 0747 d->mDocumentCountLabel->setVisible(!d->mCompareMode); 0748 } 0749 0750 void ViewMainPage::reload() 0751 { 0752 DocumentView *view = d->currentView(); 0753 if (!view) { 0754 return; 0755 } 0756 Document::Ptr doc = view->document(); 0757 if (!doc) { 0758 qCWarning(GWENVIEW_APP_LOG) << "!doc"; 0759 return; 0760 } 0761 if (doc->isModified()) { 0762 KGuiItem cont = KStandardGuiItem::cont(); 0763 cont.setText(i18nc("@action:button", "Discard Changes and Reload")); 0764 int answer = KMessageBox::warningContinueCancel(this, 0765 i18nc("@info", "This image has been modified. Reloading it will discard all your changes."), 0766 QString() /* caption */, 0767 cont); 0768 if (answer != KMessageBox::Continue) { 0769 return; 0770 } 0771 } 0772 doc->reload(); 0773 // Call openUrl again because DocumentView may need to switch to a new 0774 // adapter (for example because document was broken and it is not anymore) 0775 d->currentView()->openUrl(doc->url(), d->currentView()->setup()); 0776 } 0777 0778 void ViewMainPage::reset() 0779 { 0780 d->mDocumentViewController->reset(); 0781 d->mDocumentViewContainer->reset(); 0782 d->mDocumentViews.clear(); 0783 } 0784 0785 void ViewMainPage::slotViewFocused(DocumentView *view) 0786 { 0787 d->setCurrentView(view); 0788 } 0789 0790 void ViewMainPage::slotEnterPressed() 0791 { 0792 DocumentView *view = d->currentView(); 0793 if (view) { 0794 AbstractRasterImageViewTool *tool = view->currentTool(); 0795 if (tool) { 0796 QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); 0797 tool->keyPressEvent(&event); 0798 if (event.isAccepted()) { 0799 return; 0800 } 0801 } 0802 } 0803 } 0804 0805 bool ViewMainPage::eventFilter(QObject *watched, QEvent *event) 0806 { 0807 if (watched == d->mThumbnailBar && event->type() == QEvent::KeyPress) { 0808 auto ke = static_cast<QKeyEvent *>(event); 0809 switch (ke->key()) { 0810 case Qt::Key_Left: 0811 if (QApplication::isRightToLeft()) { 0812 Q_EMIT nextImageRequested(); 0813 } else { 0814 Q_EMIT previousImageRequested(); 0815 } 0816 return true; 0817 case Qt::Key_Up: 0818 Q_EMIT previousImageRequested(); 0819 return true; 0820 case Qt::Key_Right: 0821 if (QApplication::isRightToLeft()) { 0822 Q_EMIT previousImageRequested(); 0823 } else { 0824 Q_EMIT nextImageRequested(); 0825 } 0826 return true; 0827 case Qt::Key_Down: 0828 Q_EMIT nextImageRequested(); 0829 return true; 0830 default: 0831 break; 0832 } 0833 } else if (event->type() == QEvent::ShortcutOverride) { 0834 const int key = static_cast<QKeyEvent *>(event)->key(); 0835 if (key == Qt::Key_Space || key == Qt::Key_Escape) { 0836 const DocumentView *view = d->currentView(); 0837 if (view) { 0838 AbstractRasterImageViewTool *tool = view->currentTool(); 0839 if (tool) { 0840 QKeyEvent toolKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); 0841 tool->keyPressEvent(&toolKeyEvent); 0842 if (toolKeyEvent.isAccepted()) { 0843 event->accept(); 0844 } 0845 } 0846 } 0847 } 0848 // Leave fullscreen when viewing an image 0849 if (window()->isFullScreen() && key == Qt::Key_Escape) { 0850 d->mActionCollection->action(QStringLiteral("leave_fullscreen"))->trigger(); 0851 event->accept(); 0852 } 0853 } 0854 0855 return QWidget::eventFilter(watched, event); 0856 } 0857 0858 void ViewMainPage::trashView(DocumentView *view) 0859 { 0860 QUrl url = view->url(); 0861 deselectView(view); 0862 FileOperations::trash(QList<QUrl>() << url, this); 0863 } 0864 0865 void ViewMainPage::deselectView(DocumentView *view) 0866 { 0867 DocumentView *newCurrentView = nullptr; 0868 if (view == d->currentView()) { 0869 // We need to find a new view to set as current 0870 int idx = d->mDocumentViews.indexOf(view); 0871 if (idx + 1 < d->mDocumentViews.count()) { 0872 newCurrentView = d->mDocumentViews.at(idx + 1); 0873 } else if (idx > 0) { 0874 newCurrentView = d->mDocumentViews.at(idx - 1); 0875 } else { 0876 GV_WARN_AND_RETURN("No view found to set as current"); 0877 } 0878 } 0879 0880 QModelIndex index = d->indexForView(view); 0881 QItemSelectionModel *selectionModel = d->mThumbnailBar->selectionModel(); 0882 selectionModel->select(index, QItemSelectionModel::Deselect); 0883 0884 if (newCurrentView) { 0885 d->setCurrentView(newCurrentView); 0886 } 0887 } 0888 0889 QToolButton *ViewMainPage::toggleSideBarButton() const 0890 { 0891 return d->mToggleSideBarButton; 0892 } 0893 0894 void ViewMainPage::showMessageWidget(QGraphicsWidget *widget, Qt::Alignment align) 0895 { 0896 d->mDocumentViewContainer->showMessageWidget(widget, align); 0897 } 0898 0899 void ViewMainPage::updateFocus(const AbstractRasterImageViewTool *tool) 0900 { 0901 if (!tool) { 0902 d->mDocumentViewContainer->setFocus(); 0903 } 0904 } 0905 0906 void ViewMainPage::slotDirModelItemsAddedOrRemoved() 0907 { 0908 d->updateDocumentCountLabel(); 0909 } 0910 0911 void ViewMainPage::setSpotlightMode(bool spotlightmode) 0912 { 0913 GwenviewConfig::setSpotlightMode(spotlightmode); 0914 d->mSpotlightMode->setVisibleSpotlightModeQuitButton(spotlightmode); 0915 if (spotlightmode) { 0916 d->mThumbnailBar->setVisible(false); 0917 } else { 0918 d->mThumbnailBar->setVisible(d->mToggleThumbnailBarAction->isChecked()); 0919 } 0920 } 0921 0922 } // namespace 0923 0924 #include "moc_viewmainpage.cpp"