File indexing completed on 2024-05-05 04:19:18

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 "mainwindow.h"
0021 #include <config-gwenview.h>
0022 
0023 // Qt
0024 #include <QActionGroup>
0025 #include <QApplication>
0026 #include <QClipboard>
0027 #include <QDateTime>
0028 #include <QDialog>
0029 #include <QFileDialog>
0030 #include <QLineEdit>
0031 #include <QMenuBar>
0032 #include <QMouseEvent>
0033 #include <QPushButton>
0034 #include <QShortcut>
0035 #include <QSplitter>
0036 #include <QStackedWidget>
0037 #include <QTimer>
0038 #include <QUndoGroup>
0039 #include <QUrl>
0040 #include <QVBoxLayout>
0041 
0042 #ifdef Q_OS_OSX
0043 #include <QFileOpenEvent>
0044 #endif
0045 #include <QJsonArray>
0046 #include <QJsonObject>
0047 
0048 // KF
0049 #include <KActionCategory>
0050 #include <KActionCollection>
0051 #include <KDirLister>
0052 #include <KDirModel>
0053 #include <KFileItem>
0054 #include <KHamburgerMenu>
0055 #include <KLinkItemSelectionModel>
0056 #include <KLocalizedString>
0057 #include <KMessageBox>
0058 #include <KMessageWidget>
0059 #include <KProtocolManager>
0060 #include <KRecentFilesAction>
0061 #include <KStandardShortcut>
0062 #include <KToggleFullScreenAction>
0063 #include <KToolBar>
0064 #include <KToolBarPopupAction>
0065 #include <KUrlComboBox>
0066 #include <KUrlNavigator>
0067 #include <KXMLGUIFactory>
0068 #include <kwidgetsaddons_version.h>
0069 #include <kxmlgui_version.h>
0070 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0071 #include "lib/semanticinfo/semanticinfodirmodel.h"
0072 #endif
0073 #if HAVE_PURPOSE
0074 #include <Purpose/AlternativesModel>
0075 #include <Purpose/Menu>
0076 #include <purpose_version.h>
0077 #endif
0078 
0079 // Local
0080 #include "alignwithsidebarwidgetaction.h"
0081 #include "configdialog.h"
0082 #include "documentinfoprovider.h"
0083 #include "fileopscontextmanageritem.h"
0084 #include "folderviewcontextmanageritem.h"
0085 #include "fullscreencontent.h"
0086 #include "gvcore.h"
0087 #include "gwenview_app_debug.h"
0088 #include "imageopscontextmanageritem.h"
0089 #include "infocontextmanageritem.h"
0090 #include "viewmainpage.h"
0091 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0092 #include "semanticinfocontextmanageritem.h"
0093 #endif
0094 #include "browsemainpage.h"
0095 #include "preloader.h"
0096 #include "savebar.h"
0097 #include "sidebar.h"
0098 #include "splitter.h"
0099 #include "startmainpage.h"
0100 #include "thumbnailviewhelper.h"
0101 #include <lib/archiveutils.h>
0102 #include <lib/contextmanager.h>
0103 #include <lib/disabledactionshortcutmonitor.h>
0104 #include <lib/document/documentfactory.h>
0105 #include <lib/documentonlyproxymodel.h>
0106 #include <lib/gvdebug.h>
0107 #include <lib/gwenviewconfig.h>
0108 #include <lib/hud/hudbuttonbox.h>
0109 #include <lib/mimetypeutils.h>
0110 #ifdef HAVE_QTDBUS
0111 #include <QDBusConnection>
0112 #include <QDBusPendingReply>
0113 #include <QDBusReply>
0114 #include <lib/mpris2/mpris2service.h>
0115 #endif
0116 #include <lib/print/printhelper.h>
0117 #include <lib/semanticinfo/sorteddirmodel.h>
0118 #include <lib/signalblocker.h>
0119 #include <lib/slideshow.h>
0120 #include <lib/thumbnailprovider/thumbnailprovider.h>
0121 #include <lib/thumbnailview/thumbnailbarview.h>
0122 #include <lib/thumbnailview/thumbnailview.h>
0123 #include <lib/urlutils.h>
0124 
0125 namespace Gwenview
0126 {
0127 #undef ENABLE_LOG
0128 #undef LOG
0129 // #define ENABLE_LOG
0130 #ifdef ENABLE_LOG
0131 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
0132 #else
0133 #define LOG(x) ;
0134 #endif
0135 
0136 static const int BROWSE_PRELOAD_DELAY = 1000;
0137 static const int VIEW_PRELOAD_DELAY = 100;
0138 
0139 static const char *SESSION_CURRENT_PAGE_KEY = "Page";
0140 static const char *SESSION_URL_KEY = "Url";
0141 
0142 enum MainPageId {
0143     StartMainPageId,
0144     BrowseMainPageId,
0145     ViewMainPageId,
0146 };
0147 
0148 struct MainWindowState {
0149     bool mToolBarVisible;
0150 };
0151 
0152 /*
0153 
0154 Layout of the main window looks like this:
0155 
0156 .-mCentralSplitter-----------------------------.
0157 |.-mSideBar--. .-mContentWidget---------------.|
0158 ||           | |.-mSaveBar-------------------.||
0159 ||           | ||                            |||
0160 ||           | |'----------------------------'||
0161 ||           | |.-mViewStackedWidget---------.||
0162 ||           | ||                            |||
0163 ||           | ||                            |||
0164 ||           | ||                            |||
0165 ||           | ||                            |||
0166 ||           | |'----------------------------'||
0167 |'-----------' '------------------------------'|
0168 '----------------------------------------------'
0169 
0170 */
0171 struct MainWindow::Private {
0172     GvCore *mGvCore = nullptr;
0173     MainWindow *q = nullptr;
0174     QSplitter *mCentralSplitter = nullptr;
0175     QWidget *mContentWidget = nullptr;
0176     ViewMainPage *mViewMainPage = nullptr;
0177     KUrlNavigator *mUrlNavigator = nullptr;
0178     ThumbnailView *mThumbnailView = nullptr;
0179     ThumbnailView *mActiveThumbnailView = nullptr;
0180     DocumentInfoProvider *mDocumentInfoProvider = nullptr;
0181     ThumbnailViewHelper *mThumbnailViewHelper = nullptr;
0182     QPointer<ThumbnailProvider> mThumbnailProvider;
0183     BrowseMainPage *mBrowseMainPage = nullptr;
0184     StartMainPage *mStartMainPage = nullptr;
0185     SideBar *mSideBar = nullptr;
0186     KMessageWidget *mSharedMessage = nullptr;
0187     QStackedWidget *mViewStackedWidget = nullptr;
0188     FullScreenContent *mFullScreenContent = nullptr;
0189     SaveBar *mSaveBar = nullptr;
0190     bool mStartSlideShowWhenDirListerCompleted;
0191     SlideShow *mSlideShow = nullptr;
0192 #ifdef HAVE_QTDBUS
0193     Mpris2Service *mMpris2Service = nullptr;
0194 #endif
0195     Preloader *mPreloader = nullptr;
0196     bool mPreloadDirectionIsForward;
0197 
0198     QActionGroup *mViewModeActionGroup = nullptr;
0199     KRecentFilesAction *mFileOpenRecentAction = nullptr;
0200     QAction *mBrowseAction = nullptr;
0201     QAction *mViewAction = nullptr;
0202     QAction *mGoUpAction = nullptr;
0203     QAction *mGoToPreviousAction = nullptr;
0204     QAction *mGoToNextAction = nullptr;
0205     QAction *mGoToFirstAction = nullptr;
0206     QAction *mGoToLastAction = nullptr;
0207     KToggleAction *mToggleSideBarAction = nullptr;
0208     KToggleAction *mToggleOperationsSideBarAction = nullptr;
0209     QAction *mFullScreenAction = nullptr;
0210     QAction *mSpotlightModeAction = nullptr;
0211     QAction *mToggleSlideShowAction = nullptr;
0212     KToggleAction *mShowMenuBarAction = nullptr;
0213     KToggleAction *mShowStatusBarAction = nullptr;
0214     QPointer<HudButtonBox> hudButtonBox = nullptr;
0215 #if HAVE_PURPOSE
0216     Purpose::Menu *mShareMenu = nullptr;
0217     KToolBarPopupAction *mShareAction = nullptr;
0218 #endif
0219     KHamburgerMenu *mHamburgerMenu = nullptr;
0220 
0221     SortedDirModel *mDirModel = nullptr;
0222     DocumentOnlyProxyModel *mThumbnailBarModel = nullptr;
0223     KLinkItemSelectionModel *mThumbnailBarSelectionModel = nullptr;
0224     ContextManager *mContextManager = nullptr;
0225 
0226     MainWindowState mStateBeforeFullScreen;
0227 
0228     QString mCaption;
0229 
0230     MainPageId mCurrentMainPageId;
0231 
0232     QDateTime mFullScreenLeftAt;
0233     uint screenSaverDbusCookie = 0;
0234 
0235     void setupContextManager()
0236     {
0237         mContextManager = new ContextManager(mDirModel, q);
0238         connect(mContextManager, &ContextManager::selectionChanged, q, &MainWindow::slotSelectionChanged);
0239         connect(mContextManager, &ContextManager::currentDirUrlChanged, q, &MainWindow::slotCurrentDirUrlChanged);
0240     }
0241 
0242     void setupWidgets()
0243     {
0244         mFullScreenContent = new FullScreenContent(q, mGvCore);
0245         connect(mContextManager, &ContextManager::currentUrlChanged, mFullScreenContent, &FullScreenContent::setCurrentUrl);
0246 
0247         mCentralSplitter = new Splitter(Qt::Horizontal, q);
0248         q->setCentralWidget(mCentralSplitter);
0249 
0250         // Left side of splitter
0251         mSideBar = new SideBar(mCentralSplitter);
0252 
0253         // Right side of splitter
0254         mContentWidget = new QWidget(mCentralSplitter);
0255 
0256         mSharedMessage = new KMessageWidget(mContentWidget);
0257         mSharedMessage->setVisible(false);
0258 
0259         mSaveBar = new SaveBar(mContentWidget, q->actionCollection());
0260         connect(mContextManager, &ContextManager::currentUrlChanged, mSaveBar, &SaveBar::setCurrentUrl);
0261         mViewStackedWidget = new QStackedWidget(mContentWidget);
0262         auto layout = new QVBoxLayout(mContentWidget);
0263         layout->addWidget(mSharedMessage);
0264         layout->addWidget(mSaveBar);
0265         layout->addWidget(mViewStackedWidget);
0266         layout->setContentsMargins(0, 0, 0, 0);
0267         layout->setSpacing(0);
0268         ////
0269 
0270         mStartSlideShowWhenDirListerCompleted = false;
0271         mSlideShow = new SlideShow(q);
0272         connect(mContextManager, &ContextManager::currentUrlChanged, mSlideShow, &SlideShow::setCurrentUrl);
0273 
0274         setupThumbnailView(mViewStackedWidget);
0275         setupViewMainPage(mViewStackedWidget);
0276         setupStartMainPage(mViewStackedWidget);
0277         mViewStackedWidget->addWidget(mBrowseMainPage);
0278         mViewStackedWidget->addWidget(mViewMainPage);
0279         mViewStackedWidget->addWidget(mStartMainPage);
0280         mViewStackedWidget->setCurrentWidget(mBrowseMainPage);
0281 
0282         mCentralSplitter->setStretchFactor(0, 0);
0283         mCentralSplitter->setStretchFactor(1, 1);
0284         mCentralSplitter->setChildrenCollapsible(false);
0285 
0286         mThumbnailView->setFocus();
0287 
0288         connect(mSaveBar, &SaveBar::requestSaveAll, mGvCore, &GvCore::saveAll);
0289         connect(mSaveBar, &SaveBar::goToUrl, q, &MainWindow::goToUrl);
0290 
0291         connect(mSlideShow, &SlideShow::goToUrl, q, &MainWindow::goToUrl);
0292     }
0293 
0294     void setupThumbnailView(QWidget *parent)
0295     {
0296         Q_ASSERT(mContextManager);
0297         mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore);
0298 
0299         mThumbnailView = mBrowseMainPage->thumbnailView();
0300         mThumbnailView->viewport()->installEventFilter(q);
0301         mThumbnailView->setSelectionModel(mContextManager->selectionModel());
0302         mUrlNavigator = mBrowseMainPage->urlNavigator();
0303 
0304         mDocumentInfoProvider = new DocumentInfoProvider(mDirModel);
0305         mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider);
0306 
0307         mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection());
0308         connect(mContextManager, &ContextManager::currentDirUrlChanged, mThumbnailViewHelper, &ThumbnailViewHelper::setCurrentDirUrl);
0309         mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper);
0310 
0311         mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q);
0312 
0313         // Connect thumbnail view
0314         connect(mThumbnailView, &ThumbnailView::indexActivated, q, &MainWindow::slotThumbnailViewIndexActivated);
0315 
0316         // Connect delegate
0317         QAbstractItemDelegate *delegate = mThumbnailView->itemDelegate();
0318         connect(delegate, SIGNAL(saveDocumentRequested(QUrl)), mGvCore, SLOT(save(QUrl)));
0319         connect(delegate, SIGNAL(rotateDocumentLeftRequested(QUrl)), mGvCore, SLOT(rotateLeft(QUrl)));
0320         connect(delegate, SIGNAL(rotateDocumentRightRequested(QUrl)), mGvCore, SLOT(rotateRight(QUrl)));
0321         connect(delegate, SIGNAL(showDocumentInFullScreenRequested(QUrl)), q, SLOT(showDocumentInFullScreen(QUrl)));
0322         connect(delegate, SIGNAL(setDocumentRatingRequested(QUrl, int)), mGvCore, SLOT(setRating(QUrl, int)));
0323 
0324         // Connect url navigator
0325         connect(mUrlNavigator, &KUrlNavigator::urlChanged, q, &MainWindow::openDirUrl);
0326     }
0327 
0328     void setupViewMainPage(QWidget *parent)
0329     {
0330         mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore);
0331         connect(mViewMainPage, &ViewMainPage::captionUpdateRequested, q, &MainWindow::slotUpdateCaption);
0332         connect(mViewMainPage, &ViewMainPage::completed, q, &MainWindow::slotPartCompleted);
0333         connect(mViewMainPage, &ViewMainPage::previousImageRequested, q, &MainWindow::goToPrevious);
0334         connect(mViewMainPage, &ViewMainPage::nextImageRequested, q, &MainWindow::goToNext);
0335         connect(mViewMainPage, &ViewMainPage::openUrlRequested, q, &MainWindow::openUrl);
0336         connect(mViewMainPage, &ViewMainPage::openDirUrlRequested, q, &MainWindow::openDirUrl);
0337 
0338         setupThumbnailBar(mViewMainPage->thumbnailBar());
0339     }
0340 
0341     void setupThumbnailBar(ThumbnailView *bar)
0342     {
0343         Q_ASSERT(mThumbnailBarModel);
0344         Q_ASSERT(mThumbnailBarSelectionModel);
0345         Q_ASSERT(mDocumentInfoProvider);
0346         Q_ASSERT(mThumbnailViewHelper);
0347         bar->setModel(mThumbnailBarModel);
0348         bar->setSelectionModel(mThumbnailBarSelectionModel);
0349         bar->setDocumentInfoProvider(mDocumentInfoProvider);
0350         bar->setThumbnailViewHelper(mThumbnailViewHelper);
0351     }
0352 
0353     void setupStartMainPage(QWidget *parent)
0354     {
0355         mStartMainPage = new StartMainPage(parent, mGvCore);
0356         connect(mStartMainPage, &StartMainPage::urlSelected, q, &MainWindow::slotStartMainPageUrlSelected);
0357         connect(mStartMainPage, &StartMainPage::recentFileRemoved, [this](const QUrl &url) {
0358             mFileOpenRecentAction->removeUrl(url);
0359         });
0360         connect(mStartMainPage, &StartMainPage::recentFilesCleared, [this]() {
0361             mFileOpenRecentAction->clear();
0362         });
0363     }
0364 
0365     void installDisabledActionShortcutMonitor(QAction *action, const char *slot)
0366     {
0367         auto monitor = new DisabledActionShortcutMonitor(action, q);
0368         connect(monitor, SIGNAL(activated()), q, slot);
0369     }
0370 
0371     void setupActions()
0372     {
0373         KActionCollection *actionCollection = q->actionCollection();
0374         auto file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
0375         auto view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);
0376 
0377         file->addAction(KStandardAction::Save, q, SLOT(saveCurrent()));
0378         file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs()));
0379         file->addAction(KStandardAction::Open, q, SLOT(openFile()));
0380         mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(QUrl)), q);
0381         connect(mFileOpenRecentAction, &KRecentFilesAction::recentListCleared, mGvCore, &GvCore::clearRecentFilesAndFolders);
0382         auto clearAction = mFileOpenRecentAction->menu()->findChild<QAction *>("clear_action");
0383         if (clearAction) {
0384             clearAction->setText(i18nc("@action Open Recent menu", "Clear List"));
0385         }
0386         file->addAction("file_open_recent", mFileOpenRecentAction);
0387         file->addAction(KStandardAction::Print, q, SLOT(print()));
0388         file->addAction(KStandardAction::PrintPreview, q, SLOT(printPreview()));
0389         file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows()));
0390 
0391         QAction *action = file->addAction(QStringLiteral("reload"), q, SLOT(reload()));
0392         action->setText(i18nc("@action reload the currently viewed image", "Reload"));
0393         action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0394         actionCollection->setDefaultShortcuts(action, KStandardShortcut::reload());
0395 
0396         QAction *replaceLocationAction = actionCollection->addAction(QStringLiteral("replace_location"));
0397         replaceLocationAction->setText(i18nc("@action:inmenu Navigation Bar", "Replace Location"));
0398         actionCollection->setDefaultShortcut(replaceLocationAction, Qt::CTRL | Qt::Key_L);
0399         connect(replaceLocationAction, &QAction::triggered, q, &MainWindow::replaceLocation);
0400 
0401         mBrowseAction = view->addAction(QStringLiteral("browse"));
0402         mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse"));
0403         mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images"));
0404         mBrowseAction->setCheckable(true);
0405         mBrowseAction->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons")));
0406         actionCollection->setDefaultShortcut(mBrowseAction, Qt::Key_Escape);
0407         connect(mViewMainPage, &ViewMainPage::goToBrowseModeRequested, mBrowseAction, &QAction::trigger);
0408 
0409         mViewAction = view->addAction(QStringLiteral("view"));
0410         mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View"));
0411         mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images"));
0412         mViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-preview")));
0413         mViewAction->setCheckable(true);
0414 
0415         mViewModeActionGroup = new QActionGroup(q);
0416         mViewModeActionGroup->addAction(mBrowseAction);
0417         mViewModeActionGroup->addAction(mViewAction);
0418 
0419         connect(mViewModeActionGroup, &QActionGroup::triggered, q, &MainWindow::setActiveViewModeAction);
0420 
0421         mFullScreenAction = KStandardAction::fullScreen(q, &MainWindow::toggleFullScreen, q, actionCollection);
0422         QList<QKeySequence> shortcuts = mFullScreenAction->shortcuts();
0423         shortcuts.append(QKeySequence(Qt::Key_F11));
0424         actionCollection->setDefaultShortcuts(mFullScreenAction, shortcuts);
0425 
0426         connect(mViewMainPage, &ViewMainPage::toggleFullScreenRequested, mFullScreenAction, &QAction::trigger);
0427 
0428         QAction *leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen()));
0429         leaveFullScreenAction->setIcon(QIcon::fromTheme(QStringLiteral("view-restore")));
0430         leaveFullScreenAction->setText(i18nc("@action", "Exit Full Screen"));
0431 
0432         mSpotlightModeAction = view->addAction(QStringLiteral("view_toggle_spotlightmode"), q, SLOT(toggleSpotlightMode(bool)));
0433         mSpotlightModeAction->setCheckable(true);
0434         mSpotlightModeAction->setText(i18nc("@action", "Spotlight Mode"));
0435         mSpotlightModeAction->setIcon(QIcon::fromTheme(QStringLiteral("image-navigator-symbolic")));
0436 
0437         mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious()));
0438         mGoToPreviousAction->setPriority(QAction::LowPriority);
0439         mGoToPreviousAction->setIcon(QIcon::fromTheme(QGuiApplication::layoutDirection() == Qt::LeftToRight ? "go-previous" : "go-previous-symbolic-rtl"));
0440         mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous"));
0441         mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image"));
0442         installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached()));
0443 
0444         mGoToNextAction = view->addAction(QStringLiteral("go_next"), q, SLOT(goToNext()));
0445         mGoToNextAction->setPriority(QAction::LowPriority);
0446         mGoToNextAction->setIcon(QIcon::fromTheme(QGuiApplication::layoutDirection() == Qt::LeftToRight ? "go-next" : "go-next-symbolic-rtl"));
0447         mGoToNextAction->setText(i18nc("@action Go to next image", "Next"));
0448         mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image"));
0449         installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached()));
0450 
0451         mGoToFirstAction = view->addAction(QStringLiteral("go_first"), q, SLOT(goToFirst()));
0452         mGoToFirstAction->setPriority(QAction::LowPriority);
0453         mGoToFirstAction->setIcon(QIcon::fromTheme(QStringLiteral("go-first-view")));
0454         mGoToFirstAction->setText(i18nc("@action Go to first image", "First"));
0455         mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image"));
0456         actionCollection->setDefaultShortcut(mGoToFirstAction, Qt::Key_Home);
0457 
0458         mGoToLastAction = view->addAction(QStringLiteral("go_last"), q, SLOT(goToLast()));
0459         mGoToLastAction->setPriority(QAction::LowPriority);
0460         mGoToLastAction->setIcon(QIcon::fromTheme(QStringLiteral("go-last-view")));
0461         mGoToLastAction->setText(i18nc("@action Go to last image", "Last"));
0462         mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image"));
0463         actionCollection->setDefaultShortcut(mGoToLastAction, Qt::Key_End);
0464 
0465         mPreloadDirectionIsForward = true;
0466 
0467         mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp()));
0468 
0469         action = view->addAction("go_start_page", q, SLOT(showStartMainPage()));
0470         action->setPriority(QAction::LowPriority);
0471         action->setIcon(QIcon::fromTheme(QStringLiteral("go-home")));
0472         action->setText(i18nc("@action", "Start Page"));
0473         action->setToolTip(i18nc("@info:tooltip", "Open the start page"));
0474         actionCollection->setDefaultShortcuts(action, KStandardShortcut::home());
0475 
0476         mToggleSideBarAction = view->add<KToggleAction>(QStringLiteral("toggle_sidebar"));
0477         connect(mToggleSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleSideBar);
0478         mToggleSideBarAction->setIcon(QIcon::fromTheme(QStringLiteral("view-sidetree")));
0479         actionCollection->setDefaultShortcut(mToggleSideBarAction, Qt::Key_F4);
0480         mToggleSideBarAction->setText(i18nc("@action", "Sidebar"));
0481         connect(mBrowseMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger);
0482         connect(mViewMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger);
0483 
0484         mToggleOperationsSideBarAction = view->add<KToggleAction>(QStringLiteral("toggle_operations_sidebar"));
0485         mToggleOperationsSideBarAction->setText(i18nc("@action opens crop, rename, etc.", "Show Editing Tools"));
0486         mToggleOperationsSideBarAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-image"), QIcon::fromTheme(QStringLiteral("document-edit"))));
0487         connect(mToggleOperationsSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleOperationsSideBar);
0488         connect(mSideBar, &QTabWidget::currentChanged, mToggleOperationsSideBarAction, [=]() {
0489             mToggleOperationsSideBarAction->setChecked(mSideBar->isVisible() && mSideBar->currentPage() == QLatin1String("operations"));
0490         });
0491 
0492         mToggleSlideShowAction = view->addAction(QStringLiteral("toggle_slideshow"), q, SLOT(toggleSlideShow()));
0493         q->updateSlideShowAction();
0494         connect(mSlideShow, &SlideShow::stateChanged, q, &MainWindow::updateSlideShowAction);
0495 
0496         q->setStandardToolBarMenuEnabled(true);
0497 
0498         mShowMenuBarAction = static_cast<KToggleAction *>(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar())));
0499         mShowStatusBarAction = static_cast<KToggleAction *>(view->addAction(KStandardAction::ShowStatusbar, q, SLOT(toggleStatusBar(bool))));
0500 
0501         actionCollection->setDefaultShortcut(mShowStatusBarAction, Qt::Key_F3);
0502 
0503         view->addAction(KStandardAction::name(KStandardAction::KeyBindings),
0504                         KStandardAction::keyBindings(q, &MainWindow::configureShortcuts, actionCollection));
0505 
0506         connect(q->guiFactory(), &KXMLGUIFactory::shortcutsSaved, q, [this]() {
0507             q->guiFactory()->refreshActionProperties();
0508         });
0509 
0510         view->addAction(KStandardAction::Preferences, q, SLOT(showConfigDialog()));
0511 
0512         view->addAction(KStandardAction::ConfigureToolbars, q, SLOT(configureToolbars()));
0513 
0514 #if HAVE_PURPOSE
0515         mShareAction = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("document-share")), i18nc("@action Share images", "Share"), q);
0516 
0517         mShareAction->setPopupMode(KToolBarPopupAction::InstantPopup);
0518         actionCollection->addAction(QStringLiteral("share"), mShareAction);
0519         mShareMenu = new Purpose::Menu(q);
0520         mShareAction->setMenu(mShareMenu);
0521 
0522         connect(mShareMenu, &Purpose::Menu::finished, q, [this](const QJsonObject &output, int error, const QString &message) {
0523             if (error && error != KIO::ERR_USER_CANCELED) {
0524                 mSharedMessage->setText(i18n("Error while sharing: %1", message));
0525                 mSharedMessage->setMessageType(KMessageWidget::MessageType::Error);
0526                 mSharedMessage->animatedShow();
0527             } else {
0528                 const QString url = output[QStringLiteral("url")].toString();
0529 
0530                 if (!url.isEmpty()) {
0531                     mSharedMessage->setText(i18n("The shared image link (<a href=\"%1\">%1</a>) has been copied to the clipboard.", url));
0532                     mSharedMessage->setMessageType(KMessageWidget::MessageType::Positive);
0533                     mSharedMessage->animatedShow();
0534                     QApplication::clipboard()->setText(url);
0535                 } else {
0536                     mSharedMessage->setVisible(false);
0537                 }
0538             }
0539         });
0540 #endif
0541 
0542         auto alignWithSideBarWidgetAction = new AlignWithSideBarWidgetAction(q);
0543         alignWithSideBarWidgetAction->setSideBar(mSideBar);
0544         actionCollection->addAction("align_with_sidebar", alignWithSideBarWidgetAction);
0545 
0546         mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection);
0547         mHamburgerMenu->setShowMenuBarAction(mShowMenuBarAction);
0548         mHamburgerMenu->setMenuBar(q->menuBar());
0549         connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, q, [this]() {
0550             this->updateHamburgerMenu();
0551             // Immediately disconnect. We only need to run this once, but on demand.
0552             // NOTE: The nullptr at the end disconnects all connections between
0553             // q and mHamburgerMenu's aboutToShowMenu signal.
0554             disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, q, nullptr);
0555         });
0556     }
0557 
0558     void updateHamburgerMenu()
0559     {
0560         KActionCollection *actionCollection = q->actionCollection();
0561         auto menu = new QMenu;
0562         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Open)));
0563         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::OpenRecent)));
0564         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Save)));
0565         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::SaveAs)));
0566         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Print)));
0567         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::PrintPreview)));
0568         menu->addSeparator();
0569         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Copy)));
0570         menu->addAction(actionCollection->action(QStringLiteral("file_trash")));
0571         menu->addSeparator();
0572         menu->addAction(mBrowseAction);
0573         menu->addAction(mViewAction);
0574         menu->addAction(actionCollection->action(QStringLiteral("sort_by")));
0575         menu->addAction(mFullScreenAction);
0576         menu->addAction(mToggleSlideShowAction);
0577         menu->addSeparator();
0578 #if HAVE_PURPOSE
0579         menu->addMenu(mShareMenu);
0580 #endif
0581         auto configureMenu = new QMenu(i18nc("@title:menu submenu for actions that open configuration dialogs", "Configure"));
0582         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure_keybinding")));
0583         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure_toolbars")));
0584         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure")));
0585         menu->addMenu(configureMenu);
0586         mHamburgerMenu->setMenu(menu);
0587     }
0588 
0589     void setupUndoActions()
0590     {
0591         // There is no KUndoGroup similar to KUndoStack. This code basically
0592         // does the same as KUndoStack, but for the KUndoGroup actions.
0593         QUndoGroup *undoGroup = DocumentFactory::instance()->undoGroup();
0594         QAction *action;
0595         KActionCollection *actionCollection = q->actionCollection();
0596         auto edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection);
0597 
0598         action = undoGroup->createRedoAction(actionCollection);
0599         action->setObjectName(KStandardAction::name(KStandardAction::Redo));
0600         action->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo")));
0601         action->setIconText(i18n("Redo"));
0602         actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo());
0603 
0604         edit->addAction(action->objectName(), action);
0605 
0606         action = undoGroup->createUndoAction(actionCollection);
0607         action->setObjectName(KStandardAction::name(KStandardAction::Undo));
0608         action->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
0609         action->setIconText(i18n("Undo"));
0610         actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo());
0611         edit->addAction(action->objectName(), action);
0612     }
0613 
0614     void setupContextManagerItems()
0615     {
0616         Q_ASSERT(mContextManager);
0617         KActionCollection *actionCollection = q->actionCollection();
0618 
0619         // Create context manager items
0620         auto folderViewItem = new FolderViewContextManagerItem(mContextManager);
0621         connect(folderViewItem, &FolderViewContextManagerItem::urlChanged, q, &MainWindow::folderViewUrlChanged);
0622 
0623         auto infoItem = new InfoContextManagerItem(mContextManager);
0624 
0625 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0626         SemanticInfoContextManagerItem *semanticInfoItem = nullptr;
0627         semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage);
0628 #endif
0629 
0630         auto imageOpsItem = new ImageOpsContextManagerItem(mContextManager, q);
0631 
0632         auto fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q);
0633 
0634         // Fill sidebar
0635         SideBarPage *page;
0636         page = new SideBarPage(QIcon::fromTheme(QStringLiteral("folder")), i18n("Folders"));
0637         page->setObjectName(QStringLiteral("folders"));
0638         page->addWidget(folderViewItem->widget());
0639         page->layout()->setContentsMargins(0, 0, 0, 0);
0640         mSideBar->addPage(page);
0641 
0642         page = new SideBarPage(QIcon::fromTheme(QStringLiteral("documentinfo")), i18n("Information"));
0643         page->setObjectName(QStringLiteral("information"));
0644 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
0645         // If we have semantic info, we want to share the sidebar using a splitter,
0646         // so the user can dynamically resize the two widgets.
0647         if (semanticInfoItem) {
0648             auto splitter = new QSplitter;
0649             splitter->setObjectName(QStringLiteral("information_splitter")); // This name is used to find it when loading previous sizes.
0650             splitter->setOrientation(Qt::Vertical);
0651             splitter->setHandleWidth(5);
0652 
0653             splitter->addWidget(infoItem->widget());
0654             splitter->setCollapsible(0, false);
0655 
0656             // Cram the semantic info widget and a separator into a separate widget,
0657             // so that they can be added to the splitter together (layouts can't be added directly).
0658             // This will give the splitter a visible separator between the two widgets.
0659             auto separator = new QFrame;
0660             separator->setFrameShape(QFrame::HLine);
0661             separator->setLineWidth(1);
0662 
0663             auto container = new QWidget;
0664             auto containerLayout = new QVBoxLayout(container);
0665             containerLayout->setContentsMargins(0, 0, 0, 0);
0666             containerLayout->addWidget(separator);
0667             containerLayout->addWidget(semanticInfoItem->widget());
0668 
0669             splitter->addWidget(container);
0670             splitter->setCollapsible(1, false);
0671 
0672             page->addWidget(splitter);
0673         } else {
0674             page->addWidget(infoItem->widget());
0675         }
0676 #else
0677         page->addWidget(infoItem->widget());
0678 #endif
0679         mSideBar->addPage(page);
0680 
0681         page = new SideBarPage(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Operations"));
0682         page->setObjectName(QStringLiteral("operations"));
0683         page->addWidget(imageOpsItem->widget());
0684         auto separator = new QFrame;
0685         separator->setFrameShape(QFrame::HLine);
0686         separator->setLineWidth(1);
0687         page->addWidget(separator);
0688         page->addWidget(fileOpsItem->widget());
0689         page->addStretch();
0690         mSideBar->addPage(page);
0691     }
0692 
0693     void initDirModel()
0694     {
0695         mDirModel->setKindFilter(MimeTypeUtils::KIND_DIR | MimeTypeUtils::KIND_ARCHIVE | MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE
0696                                  | MimeTypeUtils::KIND_VIDEO);
0697 
0698         connect(mDirModel, &QAbstractItemModel::rowsInserted, q, &MainWindow::slotDirModelNewItems);
0699 
0700         connect(mDirModel, &QAbstractItemModel::rowsRemoved, q, &MainWindow::updatePreviousNextActions);
0701         connect(mDirModel, &QAbstractItemModel::modelReset, q, &MainWindow::updatePreviousNextActions);
0702 
0703         connect(mDirModel->dirLister(), SIGNAL(completed()), q, SLOT(slotDirListerCompleted()));
0704     }
0705 
0706     void setupThumbnailBarModel()
0707     {
0708         mThumbnailBarModel = new DocumentOnlyProxyModel(q);
0709         mThumbnailBarModel->setSourceModel(mDirModel);
0710     }
0711 
0712     bool indexIsDirOrArchive(const QModelIndex &index) const
0713     {
0714         Q_ASSERT(index.isValid());
0715         KFileItem item = mDirModel->itemForIndex(index);
0716         return ArchiveUtils::fileItemIsDirOrArchive(item);
0717     }
0718 
0719     void goTo(const QModelIndex &index)
0720     {
0721         if (!index.isValid()) {
0722             return;
0723         }
0724         mThumbnailView->setCurrentIndex(index);
0725         mThumbnailView->scrollTo(index);
0726     }
0727 
0728     void goTo(int offset)
0729     {
0730         mPreloadDirectionIsForward = offset > 0;
0731         QModelIndex index = mContextManager->selectionModel()->currentIndex();
0732         index = mDirModel->index(index.row() + offset, 0);
0733         if (index.isValid() && !indexIsDirOrArchive(index)) {
0734             goTo(index);
0735         }
0736     }
0737 
0738     void goToFirstDocument()
0739     {
0740         QModelIndex index;
0741         for (int row = 0;; ++row) {
0742             index = mDirModel->index(row, 0);
0743             if (!index.isValid()) {
0744                 return;
0745             }
0746 
0747             if (!indexIsDirOrArchive(index)) {
0748                 break;
0749             }
0750         }
0751         goTo(index);
0752     }
0753 
0754     void goToLastDocument()
0755     {
0756         QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0);
0757         goTo(index);
0758     }
0759 
0760     void setupFullScreenContent()
0761     {
0762         mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow);
0763         setupThumbnailBar(mFullScreenContent->thumbnailBar());
0764     }
0765 
0766     inline void setActionEnabled(const char *name, bool enabled)
0767     {
0768         QAction *action = q->actionCollection()->action(name);
0769         if (action) {
0770             action->setEnabled(enabled);
0771         } else {
0772             qCWarning(GWENVIEW_APP_LOG) << "Action" << name << "not found";
0773         }
0774     }
0775 
0776     void setActionsDisabledOnStartMainPageEnabled(bool enabled)
0777     {
0778         mBrowseAction->setEnabled(enabled);
0779         mViewAction->setEnabled(enabled);
0780         mToggleSideBarAction->setEnabled(enabled);
0781         mToggleOperationsSideBarAction->setEnabled(enabled);
0782         mShowStatusBarAction->setEnabled(enabled);
0783         mFullScreenAction->setEnabled(enabled);
0784         mToggleSlideShowAction->setEnabled(enabled);
0785 
0786         setActionEnabled("reload", enabled);
0787         setActionEnabled("go_start_page", enabled);
0788         setActionEnabled("add_folder_to_places", enabled);
0789         setActionEnabled("view_toggle_spotlightmode", enabled);
0790     }
0791 
0792     void updateActions()
0793     {
0794         bool isRasterImage = false;
0795         bool canSave = false;
0796         bool isModified = false;
0797         const QUrl url = mContextManager->currentUrl();
0798 
0799         if (url.isValid()) {
0800             isRasterImage = mContextManager->currentUrlIsRasterImage();
0801             canSave = isRasterImage;
0802             isModified = DocumentFactory::instance()->load(url)->isModified();
0803             if (mCurrentMainPageId != ViewMainPageId && mContextManager->selectedFileItemList().count() != 1) {
0804                 // Saving only makes sense if exactly one image is selected
0805                 canSave = false;
0806             }
0807         }
0808 
0809         KActionCollection *actionCollection = q->actionCollection();
0810         actionCollection->action(QStringLiteral("file_save"))->setEnabled(canSave && isModified);
0811         actionCollection->action(QStringLiteral("file_save_as"))->setEnabled(canSave);
0812         actionCollection->action(QStringLiteral("file_print"))->setEnabled(isRasterImage);
0813         actionCollection->action(QStringLiteral("file_print_preview"))->setEnabled(isRasterImage);
0814 
0815 #if HAVE_PURPOSE
0816         const KFileItemList selectedFiles = mContextManager->selectedFileItemList();
0817 
0818         if (selectedFiles.isEmpty()) {
0819             mShareAction->setEnabled(false);
0820         } else {
0821             mShareAction->setEnabled(true);
0822 
0823             QJsonArray urls;
0824             for (const KFileItem &fileItem : selectedFiles) {
0825                 urls << QJsonValue(fileItem.url().toString());
0826             }
0827 
0828             mShareMenu->model()->setInputData(QJsonObject{{QStringLiteral("mimeType"), MimeTypeUtils::urlMimeType(url)}, {QStringLiteral("urls"), urls}});
0829             mShareMenu->model()->setPluginType(QStringLiteral("Export"));
0830             mShareMenu->reload();
0831         }
0832 #endif
0833     }
0834 
0835     bool sideBarVisibility() const
0836     {
0837         switch (mCurrentMainPageId) {
0838         case StartMainPageId:
0839             GV_WARN_AND_RETURN_VALUE(false, "Sidebar not implemented on start page");
0840             break;
0841         case BrowseMainPageId:
0842             return GwenviewConfig::sideBarVisible();
0843         case ViewMainPageId:
0844             return q->isFullScreen()              ? GwenviewConfig::sideBarVisibleViewModeFullScreen()
0845                 : GwenviewConfig::spotlightMode() ? GwenviewConfig::sideBarVisibleSpotlightMode()
0846                                                   : GwenviewConfig::sideBarVisible();
0847         }
0848         return false;
0849     }
0850 
0851     void saveSideBarVisibility(const bool visible)
0852     {
0853         switch (mCurrentMainPageId) {
0854         case StartMainPageId:
0855             GV_WARN_AND_RETURN("Sidebar not implemented on start page");
0856             break;
0857         case BrowseMainPageId:
0858             GwenviewConfig::setSideBarVisible(visible);
0859             break;
0860         case ViewMainPageId:
0861             q->isFullScreen()                     ? GwenviewConfig::setSideBarVisibleViewModeFullScreen(visible)
0862                 : GwenviewConfig::spotlightMode() ? GwenviewConfig::setSideBarVisibleSpotlightMode(visible)
0863                                                   : GwenviewConfig::setSideBarVisible(visible);
0864             break;
0865         }
0866     }
0867 
0868     bool statusBarVisibility() const
0869     {
0870         switch (mCurrentMainPageId) {
0871         case StartMainPageId:
0872             GV_WARN_AND_RETURN_VALUE(false, "Statusbar not implemented on start page");
0873             break;
0874         case BrowseMainPageId:
0875             return GwenviewConfig::statusBarVisibleBrowseMode();
0876         case ViewMainPageId:
0877             return q->isFullScreen()              ? GwenviewConfig::statusBarVisibleViewModeFullScreen()
0878                 : GwenviewConfig::spotlightMode() ? GwenviewConfig::statusBarVisibleSpotlightMode()
0879                                                   : GwenviewConfig::statusBarVisibleViewMode();
0880         }
0881         return false;
0882     }
0883 
0884     void saveStatusBarVisibility(const bool visible)
0885     {
0886         switch (mCurrentMainPageId) {
0887         case StartMainPageId:
0888             GV_WARN_AND_RETURN("Statusbar not implemented on start page");
0889             break;
0890         case BrowseMainPageId:
0891             GwenviewConfig::setStatusBarVisibleBrowseMode(visible);
0892             break;
0893         case ViewMainPageId:
0894             q->isFullScreen()                     ? GwenviewConfig::setStatusBarVisibleViewModeFullScreen(visible)
0895                 : GwenviewConfig::spotlightMode() ? GwenviewConfig::setStatusBarVisibleSpotlightMode(visible)
0896                                                   : GwenviewConfig::setStatusBarVisibleViewMode(visible);
0897             break;
0898         }
0899     }
0900 
0901     void loadSplitterConfig()
0902     {
0903         const QList<int> sizes = GwenviewConfig::sideBarSplitterSizes();
0904         if (!sizes.isEmpty()) {
0905             mCentralSplitter->setSizes(sizes);
0906         }
0907     }
0908 
0909     void saveSplitterConfig()
0910     {
0911         if (mSideBar->isVisible()) {
0912             GwenviewConfig::setSideBarSplitterSizes(mCentralSplitter->sizes());
0913         }
0914     }
0915 
0916     void loadInformationSplitterConfig()
0917     {
0918         const QList<int> sizes = GwenviewConfig::informationSplitterSizes();
0919         if (!sizes.isEmpty()) {
0920             // Find the splitter inside the sidebar by objectName.
0921             auto informationSidebar = mSideBar->findChild<QSplitter *>(QStringLiteral("information_splitter"), Qt::FindChildrenRecursively);
0922             if (informationSidebar) {
0923                 informationSidebar->setSizes(sizes);
0924             } else {
0925                 qCWarning(GWENVIEW_APP_LOG) << "Could not find information splitter in sidebar when loading old position.";
0926             }
0927         }
0928     }
0929 
0930     void saveInformationSplitterConfig()
0931     {
0932         auto informationSidebar = mSideBar->findChild<QSplitter *>(QStringLiteral("information_splitter"), Qt::FindChildrenRecursively);
0933         if (informationSidebar) {
0934             GwenviewConfig::setInformationSplitterSizes(informationSidebar->sizes());
0935         } else {
0936             qCWarning(GWENVIEW_APP_LOG) << "Could not find information splitter in sidebar when saving new position.";
0937         }
0938     }
0939 
0940     void setScreenSaverEnabled(bool enabled)
0941     {
0942 #ifdef HAVE_QTDBUS
0943         if (enabled) {
0944             QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"),
0945                                                                   QStringLiteral("/ScreenSaver"),
0946                                                                   QStringLiteral("org.freedesktop.ScreenSaver"),
0947                                                                   QStringLiteral("Inhibit"));
0948             message << QGuiApplication::desktopFileName();
0949             message << i18n("Viewing media");
0950             QDBusReply<uint> reply = QDBusConnection::sessionBus().call(message);
0951             if (reply.isValid()) {
0952                 screenSaverDbusCookie = reply.value();
0953             }
0954         } else {
0955             if (screenSaverDbusCookie != 0) {
0956                 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"),
0957                                                                       QStringLiteral("/ScreenSaver"),
0958                                                                       QStringLiteral("org.freedesktop.ScreenSaver"),
0959                                                                       QStringLiteral("UnInhibit"));
0960                 message << static_cast<uint>(screenSaverDbusCookie);
0961                 screenSaverDbusCookie = 0;
0962                 QDBusConnection::sessionBus().send(message);
0963             }
0964         }
0965 #endif
0966     }
0967 
0968     void assignThumbnailProviderToThumbnailView(ThumbnailView *thumbnailView)
0969     {
0970         GV_RETURN_IF_FAIL(thumbnailView);
0971         if (mActiveThumbnailView) {
0972             mActiveThumbnailView->setThumbnailProvider(nullptr);
0973         }
0974         thumbnailView->setThumbnailProvider(mThumbnailProvider);
0975         mActiveThumbnailView = thumbnailView;
0976         if (mActiveThumbnailView->isVisible()) {
0977             mThumbnailProvider->stop();
0978             mActiveThumbnailView->generateThumbnailsForItems();
0979         }
0980     }
0981 
0982     void autoAssignThumbnailProvider()
0983     {
0984         if (mCurrentMainPageId == ViewMainPageId) {
0985             if (q->windowState() & Qt::WindowFullScreen) {
0986                 assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar());
0987             } else {
0988                 assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar());
0989             }
0990         } else if (mCurrentMainPageId == BrowseMainPageId) {
0991             assignThumbnailProviderToThumbnailView(mThumbnailView);
0992         } else if (mCurrentMainPageId == StartMainPageId) {
0993             assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView());
0994         }
0995     }
0996 
0997     enum class ShowPreview { Yes, No };
0998     void print(ShowPreview showPreview)
0999     {
1000         if (!mContextManager->currentUrlIsRasterImage()) {
1001             return;
1002         }
1003 
1004         Document::Ptr doc = DocumentFactory::instance()->load(mContextManager->currentUrl());
1005         PrintHelper printHelper(q);
1006         if (showPreview == ShowPreview::Yes) {
1007             printHelper.printPreview(doc);
1008         } else {
1009             printHelper.print(doc);
1010         }
1011     }
1012 
1013     /// Handles the clicking of links in help texts if the clicked url uses the "gwenview:" scheme.
1014     class InternalUrlClickedHandler : public QObject
1015     {
1016     public:
1017         InternalUrlClickedHandler(MainWindow *parent)
1018             : QObject(parent)
1019         {
1020             Q_CHECK_PTR(parent);
1021         }
1022 
1023         inline bool eventFilter(QObject * /* watched */, QEvent *event) override
1024         {
1025             if (event->type() != QEvent::WhatsThisClicked) {
1026                 return false;
1027             }
1028             const QString linkAddress = static_cast<QWhatsThisClickedEvent *>(event)->href();
1029             if (!linkAddress.startsWith(QStringLiteral("gwenview:"))) {
1030                 // This eventFilter only handles our internal "gwenview" scheme. Everything else is handled by KXmlGui::KToolTipHelper.
1031                 return false;
1032             }
1033             event->accept();
1034             auto linkPathList = linkAddress.split(QLatin1Char('/'));
1035             linkPathList.removeFirst(); // remove "gwenview:/"
1036             Q_ASSERT(!linkPathList.isEmpty());
1037             Q_ASSERT_X(linkPathList.constFirst() == QStringLiteral("config"), "link resolver", "Handling of this URL is not yet implemented");
1038             linkPathList.removeFirst(); // remove "config/"
1039             auto mainWindow = static_cast<MainWindow *>(parent());
1040             if (linkPathList.isEmpty()) {
1041                 mainWindow->showConfigDialog();
1042             } else if (linkPathList.constFirst() == QStringLiteral("general")) {
1043                 mainWindow->showConfigDialog(0); // "0" should open General.
1044             } else if (linkPathList.constFirst() == QStringLiteral("imageview")) {
1045                 mainWindow->showConfigDialog(1); // "1" should open Image View.
1046             } else if (linkPathList.constFirst() == QStringLiteral("advanced")) {
1047                 mainWindow->showConfigDialog(2); // "2" should open Advanced.
1048             } else {
1049                 Q_ASSERT_X(false, "config link resolver", "Handling of this config URL is not yet implemented");
1050             }
1051             return true;
1052         }
1053     };
1054 };
1055 
1056 MainWindow::MainWindow()
1057     : KXmlGuiWindow()
1058     , d(new MainWindow::Private)
1059 {
1060     d->q = this;
1061     d->mCurrentMainPageId = StartMainPageId;
1062     d->mDirModel = new SortedDirModel(this);
1063     d->setupContextManager();
1064     d->setupThumbnailBarModel();
1065     d->mGvCore = new GvCore(this, d->mDirModel);
1066     d->mPreloader = new Preloader(this);
1067     d->mThumbnailProvider = new ThumbnailProvider();
1068     d->mActiveThumbnailView = nullptr;
1069     d->initDirModel();
1070     d->setupWidgets();
1071     d->setupActions();
1072     d->setupUndoActions();
1073     d->setupContextManagerItems();
1074     d->setupFullScreenContent();
1075 
1076     d->updateActions();
1077     updatePreviousNextActions();
1078     d->mSaveBar->initActionDependentWidgets();
1079 
1080     createGUI();
1081     loadConfig();
1082 
1083     connect(DocumentFactory::instance(), &DocumentFactory::modifiedDocumentListChanged, this, &MainWindow::slotModifiedDocumentListChanged);
1084     connect(qApp, &QApplication::focusChanged, this, &MainWindow::onFocusChanged);
1085 
1086 #ifdef HAVE_QTDBUS
1087     d->mMpris2Service =
1088         new Mpris2Service(d->mSlideShow, d->mContextManager, d->mToggleSlideShowAction, d->mFullScreenAction, d->mGoToPreviousAction, d->mGoToNextAction, this);
1089 #endif
1090 
1091     auto ratingMenu = static_cast<QMenu *>(guiFactory()->container(QStringLiteral("rating"), this));
1092     ratingMenu->setIcon(QIcon::fromTheme(QStringLiteral("rating-unrated")));
1093 #ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE
1094     if (ratingMenu) {
1095         ratingMenu->menuAction()->setVisible(false);
1096     }
1097 #endif
1098     setAutoSaveSettings();
1099 #ifdef Q_OS_OSX
1100     qApp->installEventFilter(this);
1101 #endif
1102     qApp->installEventFilter(new Private::InternalUrlClickedHandler(this));
1103 }
1104 
1105 MainWindow::~MainWindow()
1106 {
1107     delete d->mThumbnailProvider;
1108     delete d;
1109 }
1110 
1111 ContextManager *MainWindow::contextManager() const
1112 {
1113     return d->mContextManager;
1114 }
1115 
1116 ViewMainPage *MainWindow::viewMainPage() const
1117 {
1118     return d->mViewMainPage;
1119 }
1120 
1121 void MainWindow::setCaption(const QString &caption)
1122 {
1123     // Keep a trace of caption to use it in slotModifiedDocumentListChanged()
1124     d->mCaption = caption;
1125     KXmlGuiWindow::setCaption(caption);
1126 }
1127 
1128 void MainWindow::setCaption(const QString &caption, bool modified)
1129 {
1130     d->mCaption = caption;
1131     KXmlGuiWindow::setCaption(caption, modified);
1132 }
1133 
1134 void MainWindow::slotUpdateCaption(const QString &caption)
1135 {
1136     const QUrl url = d->mContextManager->currentUrl();
1137     const QList<QUrl> list = DocumentFactory::instance()->modifiedDocumentList();
1138     setCaption(caption, list.contains(url));
1139 }
1140 
1141 void MainWindow::slotModifiedDocumentListChanged()
1142 {
1143     d->updateActions();
1144     slotUpdateCaption(d->mCaption);
1145 }
1146 
1147 void MainWindow::setInitialUrl(const QUrl &_url)
1148 {
1149     Q_ASSERT(_url.isValid());
1150     QUrl url = UrlUtils::fixUserEnteredUrl(_url);
1151     d->mGvCore->setTrackFileManagerSorting(true);
1152     syncSortOrder(url);
1153 
1154     if (UrlUtils::urlIsDirectory(url)) {
1155         d->mBrowseAction->trigger();
1156         openDirUrl(url);
1157     } else {
1158         openUrl(url);
1159     }
1160 }
1161 
1162 void MainWindow::startSlideShow()
1163 {
1164     d->mViewAction->trigger();
1165     // We need to wait until we have listed all images in the dirlister to
1166     // start the slideshow because the SlideShow objects needs an image list to
1167     // work.
1168     d->mStartSlideShowWhenDirListerCompleted = true;
1169 }
1170 
1171 void MainWindow::setActiveViewModeAction(QAction *action)
1172 {
1173     if (action == d->mViewAction) {
1174         d->mCurrentMainPageId = ViewMainPageId;
1175         // Switching to view mode
1176         d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage);
1177         openSelectedDocuments();
1178         d->mPreloadDirectionIsForward = true;
1179         QTimer::singleShot(VIEW_PRELOAD_DELAY, this, &MainWindow::preloadNextUrl);
1180     } else {
1181         d->mCurrentMainPageId = BrowseMainPageId;
1182         // Switching to browse mode
1183         d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage);
1184         if (!d->mViewMainPage->isEmpty() && KProtocolManager::supportsListing(d->mViewMainPage->url())) {
1185             // Reset the view to spare resources, but don't do it if we can't
1186             // browse the url, otherwise if the user starts Gwenview this way:
1187             // gwenview http://example.com/example.png
1188             // and switch to browse mode, switching back to view mode won't bring
1189             // his image back.
1190             d->mViewMainPage->reset();
1191         }
1192         setCaption(d->mUrlNavigator->locationUrl().adjusted(QUrl::RemoveScheme).toString());
1193     }
1194     d->autoAssignThumbnailProvider();
1195     toggleSideBar(d->sideBarVisibility());
1196     toggleStatusBar(d->statusBarVisibility());
1197 
1198     Q_EMIT viewModeChanged();
1199 }
1200 
1201 void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex &index)
1202 {
1203     if (!index.isValid()) {
1204         return;
1205     }
1206 
1207     KFileItem item = d->mDirModel->itemForIndex(index);
1208     syncSortOrder(item.url());
1209     if (item.isDir()) {
1210         // Item is a dir, open it
1211         openDirUrl(item.url());
1212     } else {
1213         QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype());
1214         if (!protocol.isEmpty()) {
1215             // Item is an archive, tweak url then open it
1216             QUrl url = item.url();
1217             url.setScheme(protocol);
1218             openDirUrl(url);
1219         } else {
1220             // Item is a document, switch to view mode
1221             d->mViewAction->trigger();
1222         }
1223     }
1224 }
1225 
1226 void MainWindow::openSelectedDocuments()
1227 {
1228     if (d->mCurrentMainPageId != ViewMainPageId) {
1229         return;
1230     }
1231 
1232     int count = 0;
1233     QList<QUrl> urls;
1234     const auto list = d->mContextManager->selectedFileItemList();
1235     for (const auto &item : list) {
1236         if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
1237             urls << item.url();
1238 
1239             ++count;
1240             if (count == ViewMainPage::MaxViewCount) {
1241                 break;
1242             }
1243         }
1244     }
1245 
1246     if (urls.isEmpty()) {
1247         // Selection contains no fitting items
1248         // Switch back to browsing mode
1249         d->mBrowseAction->trigger();
1250         d->mViewMainPage->reset();
1251         return;
1252     }
1253 
1254     QUrl currentUrl = d->mContextManager->currentUrl();
1255     if (currentUrl.isEmpty() || !urls.contains(currentUrl)) {
1256         // There is no current URL or it doesn't belong to selection
1257         // This can happen when user manually selects a group of items
1258         currentUrl = urls.first();
1259     }
1260 
1261     d->mViewMainPage->openUrls(urls, currentUrl);
1262 }
1263 
1264 void MainWindow::goUp()
1265 {
1266     if (d->mCurrentMainPageId == BrowseMainPageId) {
1267         QUrl url = d->mContextManager->currentDirUrl();
1268         url = KIO::upUrl(url);
1269         openDirUrl(url);
1270     } else {
1271         d->mBrowseAction->trigger();
1272     }
1273 }
1274 
1275 void MainWindow::showStartMainPage()
1276 {
1277     d->mCurrentMainPageId = StartMainPageId;
1278     d->setActionsDisabledOnStartMainPageEnabled(false);
1279 
1280     d->saveSplitterConfig();
1281     d->mSideBar->hide();
1282     d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage);
1283 
1284     d->updateActions();
1285     updatePreviousNextActions();
1286     d->mContextManager->setCurrentDirUrl(QUrl());
1287     d->mContextManager->setCurrentUrl(QUrl());
1288 
1289     d->autoAssignThumbnailProvider();
1290 }
1291 
1292 void MainWindow::slotStartMainPageUrlSelected(const QUrl &url)
1293 {
1294     d->setActionsDisabledOnStartMainPageEnabled(true);
1295 
1296     if (d->mBrowseAction->isChecked()) {
1297         // Silently uncheck the action so that setInitialUrl() does the right thing
1298         SignalBlocker blocker(d->mBrowseAction);
1299         d->mBrowseAction->setChecked(false);
1300     }
1301 
1302     setInitialUrl(url);
1303 }
1304 
1305 void MainWindow::openDirUrl(const QUrl &url)
1306 {
1307     const QUrl currentUrl = d->mContextManager->currentDirUrl();
1308 
1309     if (url == currentUrl) {
1310         return;
1311     }
1312 
1313     if (url.isParentOf(currentUrl)) {
1314         // Keep first child between url and currentUrl selected
1315         // If currentPath is      "/home/user/photos/2008/event"
1316         // and wantedPath is      "/home/user/photos"
1317         // pathToSelect should be "/home/user/photos/2008"
1318 
1319         // To anyone considering using QUrl::toLocalFile() instead of
1320         // QUrl::path() here. Please don't, using QUrl::path() is the right
1321         // thing to do here.
1322         const QString currentPath = QDir::cleanPath(currentUrl.adjusted(QUrl::StripTrailingSlash).path());
1323         const QString wantedPath = QDir::cleanPath(url.adjusted(QUrl::StripTrailingSlash).path());
1324         const QChar separator('/');
1325         const int slashCount = wantedPath.count(separator);
1326         const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1);
1327         QUrl urlToSelect = url;
1328         urlToSelect.setPath(pathToSelect);
1329         d->mContextManager->setUrlToSelect(urlToSelect);
1330     }
1331     d->mThumbnailProvider->stop();
1332     d->mContextManager->setCurrentDirUrl(url);
1333     d->mGvCore->addUrlToRecentFolders(url);
1334     d->mViewMainPage->reset();
1335 }
1336 
1337 void MainWindow::folderViewUrlChanged(const QUrl &url)
1338 {
1339     const QUrl currentUrl = d->mContextManager->currentDirUrl();
1340 
1341     if (url == currentUrl) {
1342         switch (d->mCurrentMainPageId) {
1343         case ViewMainPageId:
1344             d->mBrowseAction->trigger();
1345             break;
1346         case BrowseMainPageId:
1347             d->mViewAction->trigger();
1348             break;
1349         case StartMainPageId:
1350             break;
1351         }
1352     } else {
1353         openDirUrl(url);
1354     }
1355 }
1356 
1357 void MainWindow::syncSortOrder(const QUrl &url)
1358 {
1359     if (!d->mGvCore->trackFileManagerSorting()) {
1360         return;
1361     }
1362 
1363 #ifdef HAVE_QTDBUS
1364     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.FileManager1"),
1365                                                           QStringLiteral("/org/freedesktop/FileManager1"),
1366                                                           QStringLiteral("org.freedesktop.FileManager1"),
1367                                                           QStringLiteral("SortOrderForUrl"));
1368 
1369     QUrl dirUrl = url;
1370     dirUrl = dirUrl.adjusted(QUrl::RemoveFilename);
1371     message << dirUrl.toString();
1372 
1373     QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
1374     auto watcher = new QDBusPendingCallWatcher(call, this);
1375 
1376     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *call) {
1377         QDBusPendingReply<QString, QString> reply = *call;
1378         // Fail silently
1379         if (!reply.isError()) {
1380             QString columnName = reply.argumentAt<0>();
1381             QString orderName = reply.argumentAt<1>();
1382 
1383             int column = -1;
1384             int sortRole = -1;
1385             Qt::SortOrder order = orderName == QStringLiteral("descending") ? Qt::DescendingOrder : Qt::AscendingOrder;
1386 
1387             if (columnName == "text") {
1388                 column = KDirModel::Name;
1389                 sortRole = Qt::DisplayRole;
1390                 qCDebug(GWENVIEW_APP_LOG) << "Sorting according to file manager: text";
1391             } else if (columnName == "modificationtime") {
1392                 column = KDirModel::ModifiedTime;
1393                 sortRole = Qt::DisplayRole;
1394                 qCDebug(GWENVIEW_APP_LOG) << "Sorting according to file manager: modtime";
1395             } else if (columnName == "size") {
1396                 column = KDirModel::Size;
1397                 sortRole = Qt::DisplayRole;
1398                 qCDebug(GWENVIEW_APP_LOG) << "Sorting according to file manager: size";
1399 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
1400             } else if (columnName == "rating") {
1401                 column = KDirModel::Name;
1402                 sortRole = SemanticInfoDirModel::RatingRole;
1403                 qCDebug(GWENVIEW_APP_LOG) << "Sorting according to file manager: rating";
1404 #endif
1405             }
1406 
1407             if (column >= 0 && sortRole >= 0) {
1408                 d->mDirModel->setSortRole(sortRole);
1409                 d->mDirModel->sort(column, order);
1410             }
1411         }
1412         call->deleteLater();
1413     });
1414 #endif
1415 }
1416 
1417 void MainWindow::toggleSideBar(bool visible)
1418 {
1419     d->saveSplitterConfig();
1420     d->mToggleSideBarAction->setChecked(visible);
1421     d->mToggleOperationsSideBarAction->setChecked(visible && d->mSideBar->currentPage() == QLatin1String("operations"));
1422     d->saveSideBarVisibility(visible);
1423     d->mSideBar->setVisible(visible);
1424 
1425     const QString iconName = QApplication::isRightToLeft() ? (visible ? "sidebar-collapse-right" : "sidebar-expand-right")
1426                                                            : (visible ? "sidebar-collapse-left" : "sidebar-expand-left");
1427     const QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar");
1428 
1429     const QList<QToolButton *> buttonList{d->mBrowseMainPage->toggleSideBarButton(), d->mViewMainPage->toggleSideBarButton()};
1430     for (auto button : buttonList) {
1431         button->setIcon(QIcon::fromTheme(iconName));
1432         button->setToolTip(toolTip);
1433     }
1434 }
1435 
1436 void MainWindow::toggleOperationsSideBar(bool visible)
1437 {
1438     if (visible) {
1439         d->mSideBar->setCurrentPage(QLatin1String("operations"));
1440     }
1441     toggleSideBar(visible);
1442 }
1443 
1444 void MainWindow::toggleStatusBar(bool visible)
1445 {
1446     d->mShowStatusBarAction->setChecked(visible);
1447     d->saveStatusBarVisibility(visible);
1448 
1449     d->mViewMainPage->setStatusBarVisible(visible);
1450     d->mBrowseMainPage->setStatusBarVisible(visible);
1451 }
1452 
1453 void MainWindow::slotPartCompleted()
1454 {
1455     d->updateActions();
1456     const QUrl url = d->mContextManager->currentUrl();
1457     if (!url.isEmpty() && GwenviewConfig::historyEnabled()) {
1458         d->mFileOpenRecentAction->addUrl(url);
1459         d->mGvCore->addUrlToRecentFiles(url);
1460     }
1461 
1462     if (KProtocolManager::supportsListing(url)) {
1463         const QUrl dirUrl = d->mContextManager->currentDirUrl();
1464         d->mGvCore->addUrlToRecentFolders(dirUrl);
1465     }
1466 }
1467 
1468 void MainWindow::slotSelectionChanged()
1469 {
1470     if (d->mCurrentMainPageId == ViewMainPageId) {
1471         // The user selected a new file in the thumbnail view, since the
1472         // document view is visible, let's show it
1473         openSelectedDocuments();
1474     } else {
1475         // No document view, we need to load the document to set the undo group
1476         // of document factory to the correct QUndoStack
1477         QModelIndex index = d->mThumbnailView->currentIndex();
1478         KFileItem item;
1479         if (index.isValid()) {
1480             item = d->mDirModel->itemForIndex(index);
1481         }
1482         QUndoGroup *undoGroup = DocumentFactory::instance()->undoGroup();
1483         if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
1484             QUrl url = item.url();
1485             Document::Ptr doc = DocumentFactory::instance()->load(url);
1486             undoGroup->addStack(doc->undoStack());
1487             undoGroup->setActiveStack(doc->undoStack());
1488         } else {
1489             undoGroup->setActiveStack(nullptr);
1490         }
1491     }
1492 
1493     // Update UI
1494     d->updateActions();
1495     updatePreviousNextActions();
1496 
1497     // Start preloading
1498     int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY;
1499     QTimer::singleShot(preloadDelay, this, &MainWindow::preloadNextUrl);
1500 }
1501 
1502 void MainWindow::slotCurrentDirUrlChanged(const QUrl &url)
1503 {
1504     if (url.isValid()) {
1505         d->mUrlNavigator->setLocationUrl(url);
1506         d->mGoUpAction->setEnabled(url.path() != "/");
1507         if (d->mCurrentMainPageId == BrowseMainPageId) {
1508             setCaption(d->mUrlNavigator->locationUrl().toDisplayString(QUrl::PreferLocalFile));
1509         }
1510     } else {
1511         d->mGoUpAction->setEnabled(false);
1512     }
1513 }
1514 
1515 void MainWindow::slotDirModelNewItems()
1516 {
1517     if (d->mContextManager->selectionModel()->hasSelection()) {
1518         updatePreviousNextActions();
1519     }
1520 }
1521 
1522 void MainWindow::slotDirListerCompleted()
1523 {
1524     if (d->mStartSlideShowWhenDirListerCompleted) {
1525         d->mStartSlideShowWhenDirListerCompleted = false;
1526         QTimer::singleShot(0, d->mToggleSlideShowAction, &QAction::trigger);
1527     }
1528     if (d->mContextManager->selectionModel()->hasSelection()) {
1529         updatePreviousNextActions();
1530     } else if (!d->mContextManager->currentUrl().isValid()) {
1531         d->goToFirstDocument();
1532 
1533         // Try to select the first directory in case there are no images to select
1534         if (!d->mContextManager->selectionModel()->hasSelection()) {
1535             const QModelIndex index = d->mThumbnailView->model()->index(0, 0);
1536             if (index.isValid()) {
1537                 d->mThumbnailView->setCurrentIndex(index);
1538             }
1539         }
1540     }
1541     d->mThumbnailView->scrollToSelectedIndex();
1542     d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex();
1543     d->mFullScreenContent->thumbnailBar()->scrollToSelectedIndex();
1544 }
1545 
1546 void MainWindow::goToPrevious()
1547 {
1548     const QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1549     QModelIndex previousIndex = d->mDirModel->index(currentIndex.row(), 0);
1550 
1551     constexpr QFlags<MimeTypeUtils::Kind> allowedKinds = MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO;
1552     KFileItem fileItem;
1553 
1554     // Besides images also folders and archives are listed as well,
1555     // we need to skip them in the slideshow
1556     do {
1557         previousIndex = d->mDirModel->index(previousIndex.row() - 1, 0);
1558         fileItem = previousIndex.data(KDirModel::FileItemRole).value<KFileItem>();
1559     } while (previousIndex.isValid() && !(allowedKinds & MimeTypeUtils::fileItemKind(fileItem)));
1560 
1561     if (!previousIndex.isValid()
1562         && (GwenviewConfig::navigationEndNotification() == SlideShow::NeverWarn
1563             || (GwenviewConfig::navigationEndNotification() == SlideShow::WarnOnSlideshow && !d->mSlideShow->isRunning() && !d->mFullScreenAction->isChecked())
1564             || d->hudButtonBox)) {
1565         d->goToLastDocument();
1566     } else if (!previousIndex.isValid()) {
1567         showFirstDocumentReached();
1568     } else {
1569         d->goTo(-1);
1570     }
1571 }
1572 
1573 void MainWindow::goToNext()
1574 {
1575     const QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1576     QModelIndex nextIndex = d->mDirModel->index(currentIndex.row(), 0);
1577 
1578     constexpr QFlags<MimeTypeUtils::Kind> allowedKinds = MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO;
1579     KFileItem fileItem;
1580 
1581     // Besides images also folders and archives are listed as well,
1582     // we need to skip them in the slideshow
1583     do {
1584         nextIndex = d->mDirModel->index(nextIndex.row() + 1, 0);
1585         fileItem = nextIndex.data(KDirModel::FileItemRole).value<KFileItem>();
1586     } while (nextIndex.isValid() && !(allowedKinds & MimeTypeUtils::fileItemKind(fileItem)));
1587 
1588     if (!nextIndex.isValid()
1589         && (GwenviewConfig::navigationEndNotification() == SlideShow::NeverWarn
1590             || (GwenviewConfig::navigationEndNotification() == SlideShow::WarnOnSlideshow && !d->mSlideShow->isRunning() && !d->mFullScreenAction->isChecked())
1591             || d->hudButtonBox)) {
1592         d->goToFirstDocument();
1593     } else if (!nextIndex.isValid()) {
1594         showLastDocumentReached();
1595     } else {
1596         d->goTo(1);
1597     }
1598 }
1599 
1600 void MainWindow::goToFirst()
1601 {
1602     d->goToFirstDocument();
1603 }
1604 
1605 void MainWindow::goToLast()
1606 {
1607     d->goToLastDocument();
1608 }
1609 
1610 void MainWindow::goToUrl(const QUrl &url)
1611 {
1612     if (d->mCurrentMainPageId == ViewMainPageId) {
1613         d->mViewMainPage->openUrl(url);
1614     }
1615     QUrl dirUrl = url;
1616     dirUrl = dirUrl.adjusted(QUrl::RemoveFilename);
1617     if (dirUrl != d->mContextManager->currentDirUrl()) {
1618         d->mContextManager->setCurrentDirUrl(dirUrl);
1619         d->mGvCore->addUrlToRecentFolders(dirUrl);
1620     }
1621     d->mContextManager->setUrlToSelect(url);
1622 }
1623 
1624 void MainWindow::updatePreviousNextActions()
1625 {
1626     bool hasPrevious;
1627     bool hasNext;
1628     QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1629     if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) {
1630         int row = currentIndex.row();
1631         QModelIndex prevIndex = d->mDirModel->index(row - 1, 0);
1632         QModelIndex nextIndex = d->mDirModel->index(row + 1, 0);
1633         hasPrevious = GwenviewConfig::navigationEndNotification() != SlideShow::AlwaysWarn || (prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex));
1634         hasNext = GwenviewConfig::navigationEndNotification() != SlideShow::AlwaysWarn || (nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex));
1635     } else {
1636         hasPrevious = false;
1637         hasNext = false;
1638     }
1639 
1640     d->mGoToPreviousAction->setEnabled(hasPrevious);
1641     d->mGoToNextAction->setEnabled(hasNext);
1642     d->mGoToFirstAction->setEnabled(hasPrevious);
1643     d->mGoToLastAction->setEnabled(hasNext);
1644 }
1645 
1646 void MainWindow::leaveFullScreen()
1647 {
1648     if (d->mFullScreenAction->isChecked()) {
1649         d->mFullScreenAction->trigger();
1650     }
1651 }
1652 
1653 void MainWindow::toggleFullScreen(bool checked)
1654 {
1655     setUpdatesEnabled(false);
1656     if (checked) {
1657         leaveSpotlightMode();
1658         // Save MainWindow config now, this way if we quit while in
1659         // fullscreen, we are sure latest MainWindow changes are remembered.
1660         KConfigGroup saveConfigGroup = autoSaveConfigGroup();
1661         if (!isFullScreen()) {
1662             // Save state if window manager did not already switch to fullscreen.
1663             saveMainWindowSettings(saveConfigGroup);
1664             d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible();
1665         }
1666         setAutoSaveSettings(saveConfigGroup, false);
1667         resetAutoSaveSettings();
1668 
1669         // Go full screen
1670         KToggleFullScreenAction::setFullScreen(this, true);
1671         menuBar()->hide();
1672         toolBar()->hide();
1673 
1674         qApp->setProperty("KDE_COLOR_SCHEME_PATH", d->mGvCore->fullScreenPaletteName());
1675         QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette));
1676 
1677         d->setScreenSaverEnabled(false);
1678     } else {
1679         setAutoSaveSettings();
1680 
1681         // Back to normal
1682         qApp->setProperty("KDE_COLOR_SCHEME_PATH", QVariant());
1683         QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette));
1684 
1685         d->mSlideShow->pause();
1686         KToggleFullScreenAction::setFullScreen(this, false);
1687         menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1688         toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible);
1689 
1690         d->setScreenSaverEnabled(true);
1691 
1692         // See resizeEvent
1693         d->mFullScreenLeftAt = QDateTime::currentDateTime();
1694     }
1695 
1696     d->mFullScreenContent->setFullScreenMode(checked);
1697     d->mBrowseMainPage->setFullScreenMode(checked);
1698     d->mViewMainPage->setFullScreenMode(checked);
1699     d->mSaveBar->setFullScreenMode(checked);
1700 
1701     toggleSideBar(d->sideBarVisibility());
1702     toggleStatusBar(d->statusBarVisibility());
1703 
1704     setUpdatesEnabled(true);
1705     d->autoAssignThumbnailProvider();
1706 }
1707 
1708 void MainWindow::leaveSpotlightMode()
1709 {
1710     if (d->mSpotlightModeAction->isChecked()) {
1711         d->mSpotlightModeAction->trigger();
1712     }
1713 }
1714 
1715 void MainWindow::toggleSpotlightMode(bool checked)
1716 {
1717     setUpdatesEnabled(false);
1718     if (checked) {
1719         leaveFullScreen();
1720         KConfigGroup saveConfigGroup = autoSaveConfigGroup();
1721         saveMainWindowSettings(saveConfigGroup);
1722         d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible();
1723 
1724         setAutoSaveSettings(saveConfigGroup, false);
1725         resetAutoSaveSettings();
1726 
1727         menuBar()->hide();
1728         toolBar()->hide();
1729     } else {
1730         setAutoSaveSettings();
1731 
1732         menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1733         toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible);
1734     }
1735 
1736     d->mViewMainPage->setSpotlightMode(checked);
1737 
1738     toggleSideBar(d->sideBarVisibility());
1739     toggleStatusBar(d->statusBarVisibility());
1740     setUpdatesEnabled(true);
1741 }
1742 
1743 void MainWindow::saveCurrent()
1744 {
1745     d->mGvCore->save(d->mContextManager->currentUrl());
1746 }
1747 
1748 void MainWindow::saveCurrentAs()
1749 {
1750     d->mGvCore->saveAs(d->mContextManager->currentUrl());
1751 }
1752 
1753 void MainWindow::reload()
1754 {
1755     if (d->mCurrentMainPageId == ViewMainPageId) {
1756         d->mViewMainPage->reload();
1757     } else {
1758         d->mBrowseMainPage->reload();
1759     }
1760 }
1761 
1762 void MainWindow::openFile()
1763 {
1764     const QUrl dirUrl = d->mContextManager->currentDirUrl();
1765 
1766     auto dialog = new QFileDialog(this);
1767     dialog->setAttribute(Qt::WA_DeleteOnClose);
1768     dialog->setModal(true);
1769     dialog->setDirectoryUrl(dirUrl);
1770     dialog->setWindowTitle(i18nc("@title:window", "Open Image"));
1771     dialog->setMimeTypeFilters(MimeTypeUtils::imageMimeTypes());
1772     dialog->setAcceptMode(QFileDialog::AcceptOpen);
1773     connect(dialog, &QDialog::accepted, this, [this, dialog]() {
1774         if (!dialog->selectedUrls().isEmpty()) {
1775             openUrl(dialog->selectedUrls().at(0));
1776         }
1777     });
1778 
1779     dialog->show();
1780 }
1781 
1782 void MainWindow::openUrl(const QUrl &url)
1783 {
1784     d->setActionsDisabledOnStartMainPageEnabled(true);
1785     d->mContextManager->setUrlToSelect(url);
1786     d->mViewAction->trigger();
1787 }
1788 
1789 void MainWindow::showDocumentInFullScreen(const QUrl &url)
1790 {
1791     d->mContextManager->setUrlToSelect(url);
1792     d->mViewAction->trigger();
1793     d->mFullScreenAction->trigger();
1794 }
1795 
1796 void MainWindow::toggleSlideShow()
1797 {
1798     if (d->mSlideShow->isRunning()) {
1799         d->mSlideShow->pause();
1800     } else {
1801         if (!d->mViewAction->isChecked()) {
1802             d->mViewAction->trigger();
1803         }
1804         if (!d->mFullScreenAction->isChecked()) {
1805             d->mFullScreenAction->trigger();
1806         }
1807         QList<QUrl> list;
1808         for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) {
1809             QModelIndex index = d->mDirModel->index(pos, 0);
1810             KFileItem item = d->mDirModel->itemForIndex(index);
1811             MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
1812             switch (kind) {
1813             case MimeTypeUtils::KIND_SVG_IMAGE:
1814             case MimeTypeUtils::KIND_RASTER_IMAGE:
1815             case MimeTypeUtils::KIND_VIDEO:
1816                 list << item.url();
1817                 break;
1818             default:
1819                 break;
1820             }
1821         }
1822         d->mSlideShow->start(list);
1823     }
1824     updateSlideShowAction();
1825 }
1826 
1827 void MainWindow::updateSlideShowAction()
1828 {
1829     if (d->mSlideShow->isRunning()) {
1830         d->mToggleSlideShowAction->setText(i18n("Pause Slideshow"));
1831         d->mToggleSlideShowAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
1832     } else {
1833         d->mToggleSlideShowAction->setText(d->mFullScreenAction->isChecked() ? i18n("Resume Slideshow") : i18n("Start Slideshow"));
1834         d->mToggleSlideShowAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
1835     }
1836 }
1837 
1838 bool MainWindow::queryClose()
1839 {
1840     saveConfig();
1841     QList<QUrl> list = DocumentFactory::instance()->modifiedDocumentList();
1842     if (list.size() == 0) {
1843         return true;
1844     }
1845 
1846     KGuiItem yes(i18n("Save All Changes"), "document-save-all");
1847     KGuiItem no(i18n("Discard Changes"), "delete");
1848     QString msg =
1849         i18np("One image has been modified.", "%1 images have been modified.", list.size()) + '\n' + i18n("If you quit now, your changes will be lost.");
1850     int answer = KMessageBox::warningTwoActionsCancel(this, msg, QString() /* caption */, yes, no);
1851 
1852     switch (answer) {
1853     case KMessageBox::PrimaryAction:
1854         d->mGvCore->saveAll();
1855         // We need to wait a bit because the DocumentFactory is notified about
1856         // saved documents through a queued connection.
1857         qApp->processEvents();
1858         return DocumentFactory::instance()->modifiedDocumentList().isEmpty();
1859 
1860     case KMessageBox::SecondaryAction:
1861         return true;
1862 
1863     default: // cancel
1864         return false;
1865     }
1866 }
1867 
1868 void MainWindow::showConfigDialog(int page)
1869 {
1870     // Save first so changes like thumbnail zoom level are not lost when reloading config
1871     saveConfig();
1872 
1873     auto dialog = new ConfigDialog(this);
1874     dialog->setAttribute(Qt::WA_DeleteOnClose);
1875     dialog->setModal(true);
1876     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::loadConfig);
1877     dialog->setCurrentPage(page);
1878     dialog->show();
1879 }
1880 
1881 void MainWindow::configureShortcuts()
1882 {
1883     guiFactory()->showConfigureShortcutsDialog();
1884 }
1885 
1886 void MainWindow::toggleMenuBar()
1887 {
1888     if (!d->mFullScreenAction->isChecked()) {
1889         if (!d->mShowMenuBarAction->isChecked() && (!toolBar()->isVisible() || !toolBar()->actions().contains(d->mHamburgerMenu))) {
1890             const QString accel = d->mShowMenuBarAction->shortcut().toString();
1891             KMessageBox::information(this,
1892                                      i18n("This will hide the menu bar completely."
1893                                           " You can show it again by typing %1.",
1894                                           accel),
1895                                      i18n("Hide menu bar"),
1896                                      QLatin1String("HideMenuBarWarning"));
1897         }
1898         menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1899     }
1900 }
1901 
1902 void MainWindow::loadConfig()
1903 {
1904     d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions());
1905     d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos());
1906 
1907     if (GwenviewConfig::historyEnabled()) {
1908         d->mFileOpenRecentAction->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files"));
1909         const auto mFileOpenRecentActionUrls = d->mFileOpenRecentAction->urls();
1910         for (const QUrl &url : mFileOpenRecentActionUrls) {
1911             d->mGvCore->addUrlToRecentFiles(url);
1912         }
1913     } else {
1914         d->mFileOpenRecentAction->clear();
1915     }
1916     d->mFileOpenRecentAction->setVisible(GwenviewConfig::historyEnabled());
1917 
1918     d->mStartMainPage->loadConfig();
1919     d->mViewMainPage->loadConfig();
1920     d->mBrowseMainPage->loadConfig();
1921     d->mContextManager->loadConfig();
1922     d->mSideBar->loadConfig();
1923     d->loadSplitterConfig();
1924     d->loadInformationSplitterConfig();
1925 }
1926 
1927 void MainWindow::saveConfig()
1928 {
1929     d->mFileOpenRecentAction->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files"));
1930     d->mViewMainPage->saveConfig();
1931     d->mBrowseMainPage->saveConfig();
1932     d->mContextManager->saveConfig();
1933     d->saveSplitterConfig();
1934     d->saveInformationSplitterConfig();
1935     GwenviewConfig::setFullScreenModeActive(isFullScreen());
1936     GwenviewConfig::setSpotlightMode(d->mSpotlightModeAction->isChecked());
1937     // Save the last used version when Gwenview closes so we know which settings/features the user
1938     // is aware of which is needed for migration. The version format is: two digits each for
1939     // major minor bugfix version. Never decrease this number. Increase it when needed.
1940     GwenviewConfig::setLastUsedVersion(210800);
1941 }
1942 
1943 void MainWindow::print()
1944 {
1945     d->print(Private::ShowPreview::No);
1946 }
1947 
1948 void MainWindow::printPreview()
1949 {
1950     d->print(Private::ShowPreview::Yes);
1951 }
1952 
1953 void MainWindow::preloadNextUrl()
1954 {
1955     static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0";
1956     if (disablePreload) {
1957         qCDebug(GWENVIEW_APP_LOG) << "Preloading disabled";
1958         return;
1959     }
1960     QItemSelection selection = d->mContextManager->selectionModel()->selection();
1961     if (selection.size() != 1) {
1962         return;
1963     }
1964 
1965     QModelIndexList indexList = selection.indexes();
1966     if (indexList.isEmpty()) {
1967         return;
1968     }
1969 
1970     QModelIndex index = indexList.at(0);
1971     if (!index.isValid()) {
1972         return;
1973     }
1974 
1975     if (d->mCurrentMainPageId == ViewMainPageId) {
1976         // If we are in view mode, preload the next url, otherwise preload the
1977         // selected one
1978         int offset = d->mPreloadDirectionIsForward ? 1 : -1;
1979         index = d->mDirModel->sibling(index.row() + offset, index.column(), index);
1980         if (!index.isValid()) {
1981             return;
1982         }
1983     }
1984 
1985     KFileItem item = d->mDirModel->itemForIndex(index);
1986     if (!ArchiveUtils::fileItemIsDirOrArchive(item)) {
1987         QUrl url = item.url();
1988         if (url.isLocalFile()) {
1989             QSize size = d->mViewStackedWidget->size();
1990             d->mPreloader->preload(url, size);
1991         }
1992     }
1993 }
1994 
1995 // Set a sane initial window size
1996 QSize MainWindow::sizeHint() const
1997 {
1998     return KXmlGuiWindow::sizeHint().expandedTo(QSize(1020, 700));
1999 }
2000 
2001 void MainWindow::showEvent(QShowEvent *event)
2002 {
2003     // We need to delay initializing the action state until the menu bar has
2004     // been initialized, that's why it's done only in the showEvent()
2005     if (GwenviewConfig::lastUsedVersion() == -1 && toolBar()->actions().contains(d->mHamburgerMenu)) {
2006         menuBar()->hide();
2007     }
2008     d->mShowMenuBarAction->setChecked(menuBar()->isVisible());
2009     KXmlGuiWindow::showEvent(event);
2010 }
2011 
2012 void MainWindow::resizeEvent(QResizeEvent *event)
2013 {
2014     KXmlGuiWindow::resizeEvent(event);
2015     // This is a small hack to execute code after leaving fullscreen, and after
2016     // the window has been resized back to its former size.
2017     if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) {
2018         if (d->mCurrentMainPageId == BrowseMainPageId) {
2019             d->mThumbnailView->scrollToSelectedIndex();
2020         }
2021         d->mFullScreenLeftAt = QDateTime();
2022     }
2023 }
2024 
2025 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
2026 {
2027     Q_UNUSED(obj);
2028     Q_UNUSED(event);
2029 #ifdef Q_OS_OSX
2030     /**
2031      * handle Mac OS X file open events (only exist on OS X)
2032      */
2033     if (event->type() == QEvent::FileOpen) {
2034         QFileOpenEvent *fileOpenEvent = static_cast<QFileOpenEvent *>(event);
2035         openUrl(fileOpenEvent->url());
2036         return true;
2037     }
2038 #endif
2039     if (obj == d->mThumbnailView->viewport()) {
2040         switch (event->type()) {
2041         case QEvent::MouseButtonPress:
2042         case QEvent::MouseButtonDblClick:
2043             mouseButtonNavigate(static_cast<QMouseEvent *>(event));
2044             break;
2045         default:;
2046         }
2047     }
2048     return false;
2049 }
2050 
2051 void MainWindow::mousePressEvent(QMouseEvent *event)
2052 {
2053     mouseButtonNavigate(event);
2054     KXmlGuiWindow::mousePressEvent(event);
2055 }
2056 
2057 void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
2058 {
2059     mouseButtonNavigate(event);
2060     KXmlGuiWindow::mouseDoubleClickEvent(event);
2061 }
2062 
2063 void MainWindow::mouseButtonNavigate(QMouseEvent *event)
2064 {
2065     switch (event->button()) {
2066     case Qt::ForwardButton:
2067         if (d->mGoToNextAction->isEnabled()) {
2068             d->mGoToNextAction->trigger();
2069             return;
2070         }
2071         break;
2072     case Qt::BackButton:
2073         if (d->mGoToPreviousAction->isEnabled()) {
2074             d->mGoToPreviousAction->trigger();
2075             return;
2076         }
2077         break;
2078     default:;
2079     }
2080 }
2081 
2082 void MainWindow::setDistractionFreeMode(bool value)
2083 {
2084     d->mFullScreenContent->setDistractionFreeMode(value);
2085 }
2086 
2087 void MainWindow::saveProperties(KConfigGroup &group)
2088 {
2089     group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId));
2090     group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl().toString());
2091 }
2092 
2093 void MainWindow::readProperties(const KConfigGroup &group)
2094 {
2095     const QUrl url = group.readEntry(SESSION_URL_KEY, QUrl());
2096     if (url.isValid()) {
2097         goToUrl(url);
2098     }
2099 
2100     MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId)));
2101     if (pageId == StartMainPageId) {
2102         showStartMainPage();
2103     } else if (pageId == BrowseMainPageId) {
2104         d->mBrowseAction->trigger();
2105     } else {
2106         d->mViewAction->trigger();
2107     }
2108 }
2109 
2110 void MainWindow::showFirstDocumentReached()
2111 {
2112     if (d->hudButtonBox || d->mCurrentMainPageId != ViewMainPageId) {
2113         return;
2114     }
2115     d->hudButtonBox = new HudButtonBox;
2116     d->hudButtonBox->setText(i18n("You reached the first document, what do you want to do?"));
2117     d->hudButtonBox->addButton(i18n("Stay There"));
2118     d->hudButtonBox->addAction(d->mGoToLastAction, i18n("Go to the Last Document"));
2119     d->hudButtonBox->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
2120     d->hudButtonBox->addCountDown(15000);
2121     d->mViewMainPage->showMessageWidget(d->hudButtonBox, Qt::AlignCenter);
2122 }
2123 
2124 void MainWindow::showLastDocumentReached()
2125 {
2126     if (d->hudButtonBox || d->mCurrentMainPageId != ViewMainPageId) {
2127         return;
2128     }
2129     d->hudButtonBox = new HudButtonBox;
2130     d->hudButtonBox->setText(i18n("You reached the last document, what do you want to do?"));
2131     d->hudButtonBox->addButton(i18n("Stay There"));
2132     d->hudButtonBox->addAction(d->mGoToFirstAction, i18n("Go to the First Document"));
2133     d->hudButtonBox->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
2134     d->hudButtonBox->addCountDown(15000);
2135     d->mViewMainPage->showMessageWidget(d->hudButtonBox, Qt::AlignCenter);
2136 }
2137 
2138 void MainWindow::replaceLocation()
2139 {
2140     QLineEdit *lineEdit = d->mUrlNavigator->editor()->lineEdit();
2141 
2142     // If the text field currently has focus and everything is selected,
2143     // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
2144     if (d->mUrlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) {
2145         d->mUrlNavigator->setUrlEditable(false);
2146     } else {
2147         d->mUrlNavigator->setUrlEditable(true);
2148         d->mUrlNavigator->setFocus();
2149         lineEdit->selectAll();
2150     }
2151 }
2152 
2153 void MainWindow::onFocusChanged(QWidget *old, QWidget *now)
2154 {
2155     if (old == nullptr) {
2156         if (now != nullptr && isFullScreen()) {
2157             d->setScreenSaverEnabled(false);
2158         }
2159     } else {
2160         if (now == nullptr) {
2161             d->setScreenSaverEnabled(true);
2162         }
2163     }
2164 }
2165 
2166 } // namespace
2167 
2168 #include "moc_mainwindow.cpp"