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