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"