1 /*
2 Gwenview: an image viewer
3 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 
19 */
20 #include "mainwindow.h"
21 #include <config-gwenview.h>
22 
23 // Qt
24 #include <QApplication>
25 #include <QClipboard>
26 #include <QDateTime>
27 #include <QDialog>
28 #include <QFileDialog>
29 #include <QLineEdit>
30 #include <QMenuBar>
31 #include <QMouseEvent>
32 #include <QPushButton>
33 #include <QShortcut>
34 #include <QSplitter>
35 #include <QStackedWidget>
36 #include <QTimer>
37 #include <QUndoGroup>
38 #include <QUrl>
39 #include <QVBoxLayout>
40 
41 #ifdef Q_OS_OSX
42 #include <QFileOpenEvent>
43 #endif
44 #include <QJsonArray>
45 #include <QJsonObject>
46 
47 // KF
48 #include <kxmlgui_version.h>
49 #include <KActionCategory>
50 #include <KActionCollection>
51 #include <KDirLister>
52 #include <KDirModel>
53 #include <KFileItem>
54 #include <KHamburgerMenu>
55 #include <KLinkItemSelectionModel>
56 #include <KLocalizedString>
57 #include <KMessageBox>
58 #include <KMessageWidget>
59 #include <KNotificationRestrictions>
60 #include <KProtocolManager>
61 #include <KRecentFilesAction>
62 #include <KStandardShortcut>
63 #include <KToggleFullScreenAction>
64 #include <KToolBar>
65 #include <KToolBarPopupAction>
66 #include <KUrlComboBox>
67 #include <KUrlNavigator>
68 #include <KXMLGUIFactory>
69 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
70 #include "lib/semanticinfo/semanticinfodirmodel.h"
71 #endif
72 #ifdef KF5Purpose_FOUND
73 #include <Purpose/AlternativesModel>
74 #include <PurposeWidgets/Menu>
75 #endif
76 
77 // Local
78 #include "gwenview_app_debug.h"
79 #include "alignwithsidebarwidgetaction.h"
80 #include "configdialog.h"
81 #include "dialogguard.h"
82 #include "documentinfoprovider.h"
83 #include "fileopscontextmanageritem.h"
84 #include "folderviewcontextmanageritem.h"
85 #include "fullscreencontent.h"
86 #include "gvcore.h"
87 #include "gwenview_app_debug.h"
88 #include "imageopscontextmanageritem.h"
89 #include "infocontextmanageritem.h"
90 #include "viewmainpage.h"
91 #ifdef KIPI_FOUND
92 #include "kipiexportaction.h"
93 #include "kipiinterface.h"
94 #endif
95 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
96 #include "semanticinfocontextmanageritem.h"
97 #endif
98 #include "browsemainpage.h"
99 #include "preloader.h"
100 #include "savebar.h"
101 #include "sidebar.h"
102 #include "splitter.h"
103 #include "startmainpage.h"
104 #include "thumbnailviewhelper.h"
105 #include <lib/archiveutils.h>
106 #include <lib/contextmanager.h>
107 #include <lib/disabledactionshortcutmonitor.h>
108 #include <lib/document/documentfactory.h>
109 #include <lib/documentonlyproxymodel.h>
110 #include <lib/gvdebug.h>
111 #include <lib/gwenviewconfig.h>
112 #include <lib/hud/hudbuttonbox.h>
113 #include <lib/mimetypeutils.h>
114 #ifdef HAVE_QTDBUS
115 #include <QDBusPendingReply>
116 #include <lib/mpris2/mpris2service.h>
117 #endif
118 #include <lib/print/printhelper.h>
119 #include <lib/semanticinfo/sorteddirmodel.h>
120 #include <lib/signalblocker.h>
121 #include <lib/slideshow.h>
122 #include <lib/thumbnailprovider/thumbnailprovider.h>
123 #include <lib/thumbnailview/thumbnailbarview.h>
124 #include <lib/thumbnailview/thumbnailview.h>
125 #include <lib/urlutils.h>
126 
127 namespace Gwenview
128 {
129 #undef ENABLE_LOG
130 #undef LOG
131 //#define ENABLE_LOG
132 #ifdef ENABLE_LOG
133 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
134 #else
135 #define LOG(x) ;
136 #endif
137 
138 static const int BROWSE_PRELOAD_DELAY = 1000;
139 static const int VIEW_PRELOAD_DELAY = 100;
140 
141 static const char *SESSION_CURRENT_PAGE_KEY = "Page";
142 static const char *SESSION_URL_KEY = "Url";
143 
144 enum MainPageId {
145     StartMainPageId,
146     BrowseMainPageId,
147     ViewMainPageId,
148 };
149 
150 struct MainWindowState {
151     bool mToolBarVisible;
152 };
153 
154 /*
155 
156 Layout of the main window looks like this:
157 
158 .-mCentralSplitter-----------------------------.
159 |.-mSideBar--. .-mContentWidget---------------.|
160 ||           | |.-mSaveBar-------------------.||
161 ||           | ||                            |||
162 ||           | |'----------------------------'||
163 ||           | |.-mViewStackedWidget---------.||
164 ||           | ||                            |||
165 ||           | ||                            |||
166 ||           | ||                            |||
167 ||           | ||                            |||
168 ||           | |'----------------------------'||
169 |'-----------' '------------------------------'|
170 '----------------------------------------------'
171 
172 */
173 struct MainWindow::Private {
174     GvCore *mGvCore;
175     MainWindow *q;
176     QSplitter *mCentralSplitter;
177     QWidget *mContentWidget;
178     ViewMainPage *mViewMainPage;
179     KUrlNavigator *mUrlNavigator;
180     ThumbnailView *mThumbnailView;
181     ThumbnailView *mActiveThumbnailView;
182     DocumentInfoProvider *mDocumentInfoProvider;
183     ThumbnailViewHelper *mThumbnailViewHelper;
184     QPointer<ThumbnailProvider> mThumbnailProvider;
185     BrowseMainPage *mBrowseMainPage;
186     StartMainPage *mStartMainPage;
187     SideBar *mSideBar;
188     KMessageWidget *mSharedMessage;
189     QStackedWidget *mViewStackedWidget;
190     FullScreenContent *mFullScreenContent;
191     SaveBar *mSaveBar;
192     bool mStartSlideShowWhenDirListerCompleted;
193     SlideShow *mSlideShow;
194 #ifdef HAVE_QTDBUS
195     Mpris2Service *mMpris2Service;
196 #endif
197     Preloader *mPreloader;
198     bool mPreloadDirectionIsForward;
199 #ifdef KIPI_FOUND
200     KIPIInterface *mKIPIInterface;
201 #endif
202 
203     QActionGroup *mViewModeActionGroup;
204     KRecentFilesAction *mFileOpenRecentAction;
205     QAction *mBrowseAction;
206     QAction *mViewAction;
207     QAction *mGoUpAction;
208     QAction *mGoToPreviousAction;
209     QAction *mGoToNextAction;
210     QAction *mGoToFirstAction;
211     QAction *mGoToLastAction;
212     KToggleAction *mToggleSideBarAction;
213     KToggleAction *mToggleOperationsSideBarAction;
214     QAction *mFullScreenAction;
215     QAction *mToggleSlideShowAction;
216     KToggleAction *mShowMenuBarAction;
217     KToggleAction *mShowStatusBarAction;
218     QPointer<HudButtonBox> hudButtonBox;
219 #ifdef KF5Purpose_FOUND
220     Purpose::Menu *mShareMenu;
221     KToolBarPopupAction *mShareAction;
222 #endif
223 #ifdef KIPI_FOUND
224     KIPIExportAction *mKIPIExportAction;
225 #endif
226     KHamburgerMenu *mHamburgerMenu;
227 
228     SortedDirModel *mDirModel;
229     DocumentOnlyProxyModel *mThumbnailBarModel;
230     KLinkItemSelectionModel *mThumbnailBarSelectionModel;
231     ContextManager *mContextManager;
232 
233     MainWindowState mStateBeforeFullScreen;
234 
235     QString mCaption;
236 
237     MainPageId mCurrentMainPageId;
238 
239     QDateTime mFullScreenLeftAt;
240     KNotificationRestrictions *mNotificationRestrictions;
241 
setupContextManagerGwenview::MainWindow::Private242     void setupContextManager()
243     {
244         mContextManager = new ContextManager(mDirModel, q);
245         connect(mContextManager, &ContextManager::selectionChanged, q, &MainWindow::slotSelectionChanged);
246         connect(mContextManager, &ContextManager::currentDirUrlChanged, q, &MainWindow::slotCurrentDirUrlChanged);
247     }
248 
setupWidgetsGwenview::MainWindow::Private249     void setupWidgets()
250     {
251         mFullScreenContent = new FullScreenContent(q, mGvCore);
252         connect(mContextManager, &ContextManager::currentUrlChanged, mFullScreenContent, &FullScreenContent::setCurrentUrl);
253 
254         mCentralSplitter = new Splitter(Qt::Horizontal, q);
255         q->setCentralWidget(mCentralSplitter);
256 
257         // Left side of splitter
258         mSideBar = new SideBar(mCentralSplitter);
259 
260         // Right side of splitter
261         mContentWidget = new QWidget(mCentralSplitter);
262 
263         mSharedMessage = new KMessageWidget(mContentWidget);
264         mSharedMessage->setVisible(false);
265 
266         mSaveBar = new SaveBar(mContentWidget, q->actionCollection());
267         connect(mContextManager, &ContextManager::currentUrlChanged, mSaveBar, &SaveBar::setCurrentUrl);
268         mViewStackedWidget = new QStackedWidget(mContentWidget);
269         auto *layout = new QVBoxLayout(mContentWidget);
270         layout->addWidget(mSharedMessage);
271         layout->addWidget(mSaveBar);
272         layout->addWidget(mViewStackedWidget);
273         layout->setContentsMargins(0, 0, 0, 0);
274         layout->setSpacing(0);
275         ////
276 
277         mStartSlideShowWhenDirListerCompleted = false;
278         mSlideShow = new SlideShow(q);
279         connect(mContextManager, &ContextManager::currentUrlChanged, mSlideShow, &SlideShow::setCurrentUrl);
280 
281         setupThumbnailView(mViewStackedWidget);
282         setupViewMainPage(mViewStackedWidget);
283         setupStartMainPage(mViewStackedWidget);
284         mViewStackedWidget->addWidget(mBrowseMainPage);
285         mViewStackedWidget->addWidget(mViewMainPage);
286         mViewStackedWidget->addWidget(mStartMainPage);
287         mViewStackedWidget->setCurrentWidget(mBrowseMainPage);
288 
289         mCentralSplitter->setStretchFactor(0, 0);
290         mCentralSplitter->setStretchFactor(1, 1);
291         mCentralSplitter->setChildrenCollapsible(false);
292 
293         mThumbnailView->setFocus();
294 
295         connect(mSaveBar, &SaveBar::requestSaveAll, mGvCore, &GvCore::saveAll);
296         connect(mSaveBar, &SaveBar::goToUrl, q, &MainWindow::goToUrl);
297 
298         connect(mSlideShow, &SlideShow::goToUrl, q, &MainWindow::goToUrl);
299     }
300 
setupThumbnailViewGwenview::MainWindow::Private301     void setupThumbnailView(QWidget *parent)
302     {
303         Q_ASSERT(mContextManager);
304         mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore);
305 
306         mThumbnailView = mBrowseMainPage->thumbnailView();
307         mThumbnailView->viewport()->installEventFilter(q);
308         mThumbnailView->setSelectionModel(mContextManager->selectionModel());
309         mUrlNavigator = mBrowseMainPage->urlNavigator();
310 
311         mDocumentInfoProvider = new DocumentInfoProvider(mDirModel);
312         mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider);
313 
314         mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection());
315         connect(mContextManager, &ContextManager::currentDirUrlChanged, mThumbnailViewHelper, &ThumbnailViewHelper::setCurrentDirUrl);
316         mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper);
317 
318         mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q);
319 
320         // Connect thumbnail view
321         connect(mThumbnailView, &ThumbnailView::indexActivated, q, &MainWindow::slotThumbnailViewIndexActivated);
322 
323         // Connect delegate
324         QAbstractItemDelegate *delegate = mThumbnailView->itemDelegate();
325         connect(delegate, SIGNAL(saveDocumentRequested(QUrl)), mGvCore, SLOT(save(QUrl)));
326         connect(delegate, SIGNAL(rotateDocumentLeftRequested(QUrl)), mGvCore, SLOT(rotateLeft(QUrl)));
327         connect(delegate, SIGNAL(rotateDocumentRightRequested(QUrl)), mGvCore, SLOT(rotateRight(QUrl)));
328         connect(delegate, SIGNAL(showDocumentInFullScreenRequested(QUrl)), q, SLOT(showDocumentInFullScreen(QUrl)));
329         connect(delegate, SIGNAL(setDocumentRatingRequested(QUrl, int)), mGvCore, SLOT(setRating(QUrl, int)));
330 
331         // Connect url navigator
332         connect(mUrlNavigator, &KUrlNavigator::urlChanged, q, &MainWindow::openDirUrl);
333     }
334 
setupViewMainPageGwenview::MainWindow::Private335     void setupViewMainPage(QWidget *parent)
336     {
337         mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore);
338         connect(mViewMainPage, &ViewMainPage::captionUpdateRequested, q, &MainWindow::slotUpdateCaption);
339         connect(mViewMainPage, &ViewMainPage::completed, q, &MainWindow::slotPartCompleted);
340         connect(mViewMainPage, &ViewMainPage::previousImageRequested, q, &MainWindow::goToPrevious);
341         connect(mViewMainPage, &ViewMainPage::nextImageRequested, q, &MainWindow::goToNext);
342         connect(mViewMainPage, &ViewMainPage::openUrlRequested, q, &MainWindow::openUrl);
343         connect(mViewMainPage, &ViewMainPage::openDirUrlRequested, q, &MainWindow::openDirUrl);
344 
345         setupThumbnailBar(mViewMainPage->thumbnailBar());
346     }
347 
setupThumbnailBarGwenview::MainWindow::Private348     void setupThumbnailBar(ThumbnailView *bar)
349     {
350         Q_ASSERT(mThumbnailBarModel);
351         Q_ASSERT(mThumbnailBarSelectionModel);
352         Q_ASSERT(mDocumentInfoProvider);
353         Q_ASSERT(mThumbnailViewHelper);
354         bar->setModel(mThumbnailBarModel);
355         bar->setSelectionModel(mThumbnailBarSelectionModel);
356         bar->setDocumentInfoProvider(mDocumentInfoProvider);
357         bar->setThumbnailViewHelper(mThumbnailViewHelper);
358     }
359 
setupStartMainPageGwenview::MainWindow::Private360     void setupStartMainPage(QWidget *parent)
361     {
362         mStartMainPage = new StartMainPage(parent, mGvCore);
363         connect(mStartMainPage, &StartMainPage::urlSelected, q, &MainWindow::slotStartMainPageUrlSelected);
364         connect(mStartMainPage, &StartMainPage::recentFileRemoved, [this](const QUrl &url) {
365             mFileOpenRecentAction->removeUrl(url);
366         });
367         connect(mStartMainPage, &StartMainPage::recentFilesCleared, [this]() {
368             mFileOpenRecentAction->clear();
369         });
370     }
371 
installDisabledActionShortcutMonitorGwenview::MainWindow::Private372     void installDisabledActionShortcutMonitor(QAction *action, const char *slot)
373     {
374         auto *monitor = new DisabledActionShortcutMonitor(action, q);
375         connect(monitor, SIGNAL(activated()), q, slot);
376     }
377 
setupActionsGwenview::MainWindow::Private378     void setupActions()
379     {
380         KActionCollection *actionCollection = q->actionCollection();
381         auto *file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
382         auto *view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);
383 
384         file->addAction(KStandardAction::Save, q, SLOT(saveCurrent()));
385         file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs()));
386         file->addAction(KStandardAction::Open, q, SLOT(openFile()));
387         mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(QUrl)), q);
388         connect(mFileOpenRecentAction, &KRecentFilesAction::recentListCleared, mGvCore, &GvCore::clearRecentFilesAndFolders);
389         auto *clearAction = mFileOpenRecentAction->menu()->findChild<QAction *>("clear_action");
390         if (clearAction) {
391             clearAction->setText(i18nc("@action Open Recent menu", "Clear List"));
392         }
393         file->addAction("file_open_recent", mFileOpenRecentAction);
394         file->addAction(KStandardAction::Print, q, SLOT(print()));
395         file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows()));
396 
397         QAction *action = file->addAction("reload", q, SLOT(reload()));
398         action->setText(i18nc("@action reload the currently viewed image", "Reload"));
399         action->setIcon(QIcon::fromTheme("view-refresh"));
400         actionCollection->setDefaultShortcuts(action, KStandardShortcut::reload());
401 
402         QAction *replaceLocationAction = actionCollection->addAction(QStringLiteral("replace_location"));
403         replaceLocationAction->setText(i18nc("@action:inmenu Navigation Bar", "Replace Location"));
404         actionCollection->setDefaultShortcut(replaceLocationAction, Qt::CTRL | Qt::Key_L);
405         connect(replaceLocationAction, &QAction::triggered, q, &MainWindow::replaceLocation);
406 
407         mBrowseAction = view->addAction("browse");
408         mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse"));
409         mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images"));
410         mBrowseAction->setCheckable(true);
411         mBrowseAction->setIcon(QIcon::fromTheme("view-list-icons"));
412         actionCollection->setDefaultShortcut(mBrowseAction, Qt::Key_Escape);
413         connect(mViewMainPage, &ViewMainPage::goToBrowseModeRequested, mBrowseAction, &QAction::trigger);
414 
415         mViewAction = view->addAction("view");
416         mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View"));
417         mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images"));
418         mViewAction->setIcon(QIcon::fromTheme("view-preview"));
419         mViewAction->setCheckable(true);
420 
421         mViewModeActionGroup = new QActionGroup(q);
422         mViewModeActionGroup->addAction(mBrowseAction);
423         mViewModeActionGroup->addAction(mViewAction);
424 
425         connect(mViewModeActionGroup, &QActionGroup::triggered, q, &MainWindow::setActiveViewModeAction);
426 
427         mFullScreenAction = KStandardAction::fullScreen(q, &MainWindow::toggleFullScreen, q, actionCollection);
428         QList<QKeySequence> shortcuts = mFullScreenAction->shortcuts();
429         shortcuts.append(QKeySequence(Qt::Key_F11));
430         actionCollection->setDefaultShortcuts(mFullScreenAction, shortcuts);
431 
432         connect(mViewMainPage, &ViewMainPage::toggleFullScreenRequested, mFullScreenAction, &QAction::trigger);
433 
434         QAction *leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen()));
435         leaveFullScreenAction->setIcon(QIcon::fromTheme("view-restore"));
436         leaveFullScreenAction->setText(i18nc("@action", "Exit Full Screen"));
437 
438         mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious()));
439         mGoToPreviousAction->setPriority(QAction::LowPriority);
440         mGoToPreviousAction->setIcon(QIcon::fromTheme("go-previous-view"));
441         mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous"));
442         mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image"));
443         installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached()));
444 
445         mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext()));
446         mGoToNextAction->setPriority(QAction::LowPriority);
447         mGoToNextAction->setIcon(QIcon::fromTheme("go-next-view"));
448         mGoToNextAction->setText(i18nc("@action Go to next image", "Next"));
449         mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image"));
450         installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached()));
451 
452         mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst()));
453         mGoToFirstAction->setPriority(QAction::LowPriority);
454         mGoToFirstAction->setIcon(QIcon::fromTheme("go-first-view"));
455         mGoToFirstAction->setText(i18nc("@action Go to first image", "First"));
456         mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image"));
457         actionCollection->setDefaultShortcut(mGoToFirstAction, Qt::Key_Home);
458 
459         mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast()));
460         mGoToLastAction->setPriority(QAction::LowPriority);
461         mGoToLastAction->setIcon(QIcon::fromTheme("go-last-view"));
462         mGoToLastAction->setText(i18nc("@action Go to last image", "Last"));
463         mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image"));
464         actionCollection->setDefaultShortcut(mGoToLastAction, Qt::Key_End);
465 
466         mPreloadDirectionIsForward = true;
467 
468         mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp()));
469 
470         action = view->addAction("go_start_page", q, SLOT(showStartMainPage()));
471         action->setPriority(QAction::LowPriority);
472         action->setIcon(QIcon::fromTheme("go-home"));
473         action->setText(i18nc("@action", "Start Page"));
474         action->setToolTip(i18nc("@info:tooltip", "Open the start page"));
475         actionCollection->setDefaultShortcuts(action, KStandardShortcut::home());
476 
477         mToggleSideBarAction = view->add<KToggleAction>("toggle_sidebar");
478         connect(mToggleSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleSideBar);
479         mToggleSideBarAction->setIcon(QIcon::fromTheme("view-sidetree"));
480         actionCollection->setDefaultShortcut(mToggleSideBarAction, Qt::Key_F4);
481         mToggleSideBarAction->setText(i18nc("@action", "Sidebar"));
482         connect(mBrowseMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger);
483         connect(mViewMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger);
484 
485         mToggleOperationsSideBarAction = view->add<KToggleAction>("toggle_operations_sidebar");
486         mToggleOperationsSideBarAction->setText(i18nc("@action opens crop, rename, etc.", "Show Editing Tools"));
487         mToggleOperationsSideBarAction->setIcon(QIcon::fromTheme("document-edit"));
488         connect(mToggleOperationsSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleOperationsSideBar);
489         connect(mSideBar, &QTabWidget::currentChanged, mToggleOperationsSideBarAction, [=]() {
490             mToggleOperationsSideBarAction->setChecked(mSideBar->isVisible() && mSideBar->currentPage() == QLatin1String("operations"));
491         });
492 
493         mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow()));
494         q->updateSlideShowAction();
495         connect(mSlideShow, &SlideShow::stateChanged, q, &MainWindow::updateSlideShowAction);
496 
497         q->setStandardToolBarMenuEnabled(true);
498 
499         mShowMenuBarAction = static_cast<KToggleAction *>(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar())));
500         mShowStatusBarAction = static_cast<KToggleAction *>(view->addAction(KStandardAction::ShowStatusbar, q, SLOT(toggleStatusBar(bool))));
501 
502         actionCollection->setDefaultShortcut(mShowStatusBarAction, Qt::Key_F3);
503 
504         view->addAction(KStandardAction::name(KStandardAction::KeyBindings),
505                         KStandardAction::keyBindings(q, &MainWindow::configureShortcuts, actionCollection));
506 
507         connect(q->guiFactory(), &KXMLGUIFactory::shortcutsSaved, q, [this]() {
508             q->guiFactory()->refreshActionProperties();
509         });
510 
511         view->addAction(KStandardAction::Preferences, q, SLOT(showConfigDialog()));
512 
513         view->addAction(KStandardAction::ConfigureToolbars, q, SLOT(configureToolbars()));
514 
515 #ifdef KIPI_FOUND
516         mKIPIExportAction = new KIPIExportAction(q);
517 #endif
518 
519 #ifdef KF5Purpose_FOUND
520         mShareAction = new KToolBarPopupAction(QIcon::fromTheme("document-share"), i18nc("@action Share images", "Share"), q);
521         mShareAction->setDelayed(false);
522         actionCollection->addAction("share", mShareAction);
523         mShareMenu = new Purpose::Menu(q);
524         mShareAction->setMenu(mShareMenu);
525 
526         connect(mShareMenu, &Purpose::Menu::finished, q, [this](const QJsonObject &output, int error, const QString &message) {
527             if (error && error != KIO::ERR_USER_CANCELED) {
528                 mSharedMessage->setText(i18n("Error while sharing: %1", message));
529                 mSharedMessage->setMessageType(KMessageWidget::MessageType::Error);
530                 mSharedMessage->animatedShow();
531             } else {
532                 const QString url = output[QStringLiteral("url")].toString();
533 
534                 if (!url.isEmpty()) {
535                     mSharedMessage->setText(i18n("The shared image link (<a href=\"%1\">%1</a>) has been copied to the clipboard.", url));
536                     mSharedMessage->setMessageType(KMessageWidget::MessageType::Positive);
537                     mSharedMessage->animatedShow();
538                     QApplication::clipboard()->setText(url);
539                 } else {
540                     mSharedMessage->setVisible(false);
541                 }
542             }
543         });
544 #endif
545 
546         auto alignWithSideBarWidgetAction = new AlignWithSideBarWidgetAction();
547         alignWithSideBarWidgetAction->setSideBar(mSideBar);
548         actionCollection->addAction("align_with_sidebar", alignWithSideBarWidgetAction);
549 
550         mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection);
551         mHamburgerMenu->setShowMenuBarAction(mShowMenuBarAction);
552         mHamburgerMenu->setMenuBar(q->menuBar());
553         connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, q, [this]() {
554             this->updateHamburgerMenu();
555             // Immediately disconnect. We only need to run this once, but on demand.
556             // NOTE: The nullptr at the end disconnects all connections between
557             // q and mHamburgerMenu's aboutToShowMenu signal.
558             disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, q, nullptr);
559         });
560     }
561 
updateHamburgerMenuGwenview::MainWindow::Private562     void updateHamburgerMenu()
563     {
564         KActionCollection *actionCollection = q->actionCollection();
565         auto *menu = new QMenu;
566         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Open)));
567         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::OpenRecent)));
568         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Save)));
569         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::SaveAs)));
570         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Print)));
571         menu->addSeparator();
572         menu->addAction(actionCollection->action(KStandardAction::name(KStandardAction::Copy)));
573         menu->addAction(actionCollection->action(QStringLiteral("file_trash")));
574         menu->addSeparator();
575         menu->addAction(mBrowseAction);
576         menu->addAction(mViewAction);
577         menu->addAction(actionCollection->action(QStringLiteral("sort_by")));
578         menu->addAction(mFullScreenAction);
579         menu->addAction(mToggleSlideShowAction);
580         menu->addSeparator();
581 #ifdef KF5Purpose_FOUND
582         menu->addMenu(mShareMenu);
583 #endif
584 #ifdef KIPI_FOUND
585         QMenu *pluginsMenu = static_cast<QMenu *>(q->guiFactory()->container("plugins", q));
586         if (pluginsMenu) {
587             menu->addMenu(pluginsMenu);
588         }
589 #endif
590         auto *configureMenu = new QMenu(i18nc("@title:menu submenu for actions that open configuration dialogs", "Configure"));
591         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure_keybinding")));
592         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure_toolbars")));
593         configureMenu->addAction(actionCollection->action(QStringLiteral("options_configure")));
594         menu->addMenu(configureMenu);
595         mHamburgerMenu->setMenu(menu);
596     }
597 
setupUndoActionsGwenview::MainWindow::Private598     void setupUndoActions()
599     {
600         // There is no KUndoGroup similar to KUndoStack. This code basically
601         // does the same as KUndoStack, but for the KUndoGroup actions.
602         QUndoGroup *undoGroup = DocumentFactory::instance()->undoGroup();
603         QAction *action;
604         KActionCollection *actionCollection = q->actionCollection();
605         auto *edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection);
606 
607         action = undoGroup->createRedoAction(actionCollection);
608         action->setObjectName(KStandardAction::name(KStandardAction::Redo));
609         action->setIcon(QIcon::fromTheme("edit-redo"));
610         action->setIconText(i18n("Redo"));
611         actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo());
612 
613         edit->addAction(action->objectName(), action);
614 
615         action = undoGroup->createUndoAction(actionCollection);
616         action->setObjectName(KStandardAction::name(KStandardAction::Undo));
617         action->setIcon(QIcon::fromTheme("edit-undo"));
618         action->setIconText(i18n("Undo"));
619         actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo());
620         edit->addAction(action->objectName(), action);
621     }
622 
setupContextManagerItemsGwenview::MainWindow::Private623     void setupContextManagerItems()
624     {
625         Q_ASSERT(mContextManager);
626         KActionCollection *actionCollection = q->actionCollection();
627 
628         // Create context manager items
629         auto *folderViewItem = new FolderViewContextManagerItem(mContextManager);
630         connect(folderViewItem, &FolderViewContextManagerItem::urlChanged, q, &MainWindow::folderViewUrlChanged);
631 
632         auto *infoItem = new InfoContextManagerItem(mContextManager);
633 
634 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
635         SemanticInfoContextManagerItem *semanticInfoItem = nullptr;
636         semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage);
637 #endif
638 
639         auto *imageOpsItem = new ImageOpsContextManagerItem(mContextManager, q);
640 
641         auto *fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q);
642 
643         // Fill sidebar
644         SideBarPage *page;
645         page = new SideBarPage(QIcon::fromTheme("folder"), i18n("Folders"));
646         page->setObjectName(QLatin1String("folders"));
647         page->addWidget(folderViewItem->widget());
648         page->layout()->setContentsMargins(0, 0, 0, 0);
649         mSideBar->addPage(page);
650 
651         page = new SideBarPage(QIcon::fromTheme("documentinfo"), i18n("Information"));
652         page->setObjectName(QLatin1String("information"));
653         page->addWidget(infoItem->widget());
654 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
655         if (semanticInfoItem) {
656             auto *separator = new QFrame;
657             separator->setFrameShape(QFrame::HLine);
658             separator->setLineWidth(1);
659             page->addWidget(separator);
660             page->addWidget(semanticInfoItem->widget());
661         }
662 #endif
663         mSideBar->addPage(page);
664 
665         page = new SideBarPage(QIcon::fromTheme("document-edit"), i18n("Operations"));
666         page->setObjectName(QLatin1String("operations"));
667         page->addWidget(imageOpsItem->widget());
668         auto *separator = new QFrame;
669         separator->setFrameShape(QFrame::HLine);
670         separator->setLineWidth(1);
671         page->addWidget(separator);
672         page->addWidget(fileOpsItem->widget());
673         page->addStretch();
674         mSideBar->addPage(page);
675     }
676 
initDirModelGwenview::MainWindow::Private677     void initDirModel()
678     {
679         mDirModel->setKindFilter(MimeTypeUtils::KIND_DIR | MimeTypeUtils::KIND_ARCHIVE | MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE
680                                  | MimeTypeUtils::KIND_VIDEO);
681 
682         connect(mDirModel, &QAbstractItemModel::rowsInserted, q, &MainWindow::slotDirModelNewItems);
683 
684         connect(mDirModel, &QAbstractItemModel::rowsRemoved, q, &MainWindow::updatePreviousNextActions);
685         connect(mDirModel, &QAbstractItemModel::modelReset, q, &MainWindow::updatePreviousNextActions);
686 
687         connect(mDirModel->dirLister(), SIGNAL(completed()), q, SLOT(slotDirListerCompleted()));
688     }
689 
setupThumbnailBarModelGwenview::MainWindow::Private690     void setupThumbnailBarModel()
691     {
692         mThumbnailBarModel = new DocumentOnlyProxyModel(q);
693         mThumbnailBarModel->setSourceModel(mDirModel);
694     }
695 
indexIsDirOrArchiveGwenview::MainWindow::Private696     bool indexIsDirOrArchive(const QModelIndex &index) const
697     {
698         Q_ASSERT(index.isValid());
699         KFileItem item = mDirModel->itemForIndex(index);
700         return ArchiveUtils::fileItemIsDirOrArchive(item);
701     }
702 
goToGwenview::MainWindow::Private703     void goTo(const QModelIndex &index)
704     {
705         if (!index.isValid()) {
706             return;
707         }
708         mThumbnailView->setCurrentIndex(index);
709         mThumbnailView->scrollTo(index);
710     }
711 
goToGwenview::MainWindow::Private712     void goTo(int offset)
713     {
714         mPreloadDirectionIsForward = offset > 0;
715         QModelIndex index = mContextManager->selectionModel()->currentIndex();
716         index = mDirModel->index(index.row() + offset, 0);
717         if (index.isValid() && !indexIsDirOrArchive(index)) {
718             goTo(index);
719         }
720     }
721 
goToFirstDocumentGwenview::MainWindow::Private722     void goToFirstDocument()
723     {
724         QModelIndex index;
725         for (int row = 0;; ++row) {
726             index = mDirModel->index(row, 0);
727             if (!index.isValid()) {
728                 return;
729             }
730 
731             if (!indexIsDirOrArchive(index)) {
732                 break;
733             }
734         }
735         goTo(index);
736     }
737 
goToLastDocumentGwenview::MainWindow::Private738     void goToLastDocument()
739     {
740         QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0);
741         goTo(index);
742     }
743 
setupFullScreenContentGwenview::MainWindow::Private744     void setupFullScreenContent()
745     {
746         mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow);
747         setupThumbnailBar(mFullScreenContent->thumbnailBar());
748     }
749 
setActionEnabledGwenview::MainWindow::Private750     inline void setActionEnabled(const char *name, bool enabled)
751     {
752         QAction *action = q->actionCollection()->action(name);
753         if (action) {
754             action->setEnabled(enabled);
755         } else {
756             qCWarning(GWENVIEW_APP_LOG) << "Action" << name << "not found";
757         }
758     }
759 
setActionsDisabledOnStartMainPageEnabledGwenview::MainWindow::Private760     void setActionsDisabledOnStartMainPageEnabled(bool enabled)
761     {
762         mBrowseAction->setEnabled(enabled);
763         mViewAction->setEnabled(enabled);
764         mToggleSideBarAction->setEnabled(enabled);
765         mToggleOperationsSideBarAction->setEnabled(enabled);
766         mShowStatusBarAction->setEnabled(enabled);
767         mFullScreenAction->setEnabled(enabled);
768         mToggleSlideShowAction->setEnabled(enabled);
769 
770         setActionEnabled("reload", enabled);
771         setActionEnabled("go_start_page", enabled);
772         setActionEnabled("add_folder_to_places", enabled);
773     }
774 
updateActionsGwenview::MainWindow::Private775     void updateActions()
776     {
777         bool isRasterImage = false;
778         bool canSave = false;
779         bool isModified = false;
780         const QUrl url = mContextManager->currentUrl();
781 
782         if (url.isValid()) {
783             isRasterImage = mContextManager->currentUrlIsRasterImage();
784             canSave = isRasterImage;
785             isModified = DocumentFactory::instance()->load(url)->isModified();
786             if (mCurrentMainPageId != ViewMainPageId && mContextManager->selectedFileItemList().count() != 1) {
787                 // Saving only makes sense if exactly one image is selected
788                 canSave = false;
789             }
790         }
791 
792         KActionCollection *actionCollection = q->actionCollection();
793         actionCollection->action("file_save")->setEnabled(canSave && isModified);
794         actionCollection->action("file_save_as")->setEnabled(canSave);
795         actionCollection->action("file_print")->setEnabled(isRasterImage);
796 
797 #ifdef KF5Purpose_FOUND
798         if (url.isEmpty()) {
799             mShareAction->setEnabled(false);
800         } else {
801             mShareAction->setEnabled(true);
802             mShareMenu->model()->setInputData(
803                 QJsonObject{{QStringLiteral("mimeType"), MimeTypeUtils::urlMimeType(url)}, {QStringLiteral("urls"), QJsonArray{url.toString()}}});
804             mShareMenu->model()->setPluginType(QStringLiteral("Export"));
805             mShareMenu->reload();
806         }
807 #endif
808     }
809 
sideBarVisibilityGwenview::MainWindow::Private810     bool sideBarVisibility() const
811     {
812         switch (mCurrentMainPageId) {
813         case StartMainPageId:
814             GV_WARN_AND_RETURN_VALUE(false, "Sidebar not implemented on start page");
815             break;
816         case BrowseMainPageId:
817             return GwenviewConfig::sideBarVisible();
818         case ViewMainPageId:
819             return q->isFullScreen() ? GwenviewConfig::sideBarVisibleViewModeFullScreen() : GwenviewConfig::sideBarVisible();
820         }
821         return false;
822     }
823 
saveSideBarVisibilityGwenview::MainWindow::Private824     void saveSideBarVisibility(const bool visible)
825     {
826         switch (mCurrentMainPageId) {
827         case StartMainPageId:
828             GV_WARN_AND_RETURN("Sidebar not implemented on start page");
829             break;
830         case BrowseMainPageId:
831             GwenviewConfig::setSideBarVisible(visible);
832             break;
833         case ViewMainPageId:
834             q->isFullScreen() ? GwenviewConfig::setSideBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setSideBarVisible(visible);
835             break;
836         }
837     }
838 
statusBarVisibilityGwenview::MainWindow::Private839     bool statusBarVisibility() const
840     {
841         switch (mCurrentMainPageId) {
842         case StartMainPageId:
843             GV_WARN_AND_RETURN_VALUE(false, "Statusbar not implemented on start page");
844             break;
845         case BrowseMainPageId:
846             return GwenviewConfig::statusBarVisibleBrowseMode();
847         case ViewMainPageId:
848             return q->isFullScreen() ? GwenviewConfig::statusBarVisibleViewModeFullScreen() : GwenviewConfig::statusBarVisibleViewMode();
849         }
850         return false;
851     }
852 
saveStatusBarVisibilityGwenview::MainWindow::Private853     void saveStatusBarVisibility(const bool visible)
854     {
855         switch (mCurrentMainPageId) {
856         case StartMainPageId:
857             GV_WARN_AND_RETURN("Statusbar not implemented on start page");
858             break;
859         case BrowseMainPageId:
860             GwenviewConfig::setStatusBarVisibleBrowseMode(visible);
861             break;
862         case ViewMainPageId:
863             q->isFullScreen() ? GwenviewConfig::setStatusBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setStatusBarVisibleViewMode(visible);
864             break;
865         }
866     }
867 
loadSplitterConfigGwenview::MainWindow::Private868     void loadSplitterConfig()
869     {
870         const QList<int> sizes = GwenviewConfig::sideBarSplitterSizes();
871         if (!sizes.isEmpty()) {
872             mCentralSplitter->setSizes(sizes);
873         }
874     }
875 
saveSplitterConfigGwenview::MainWindow::Private876     void saveSplitterConfig()
877     {
878         if (mSideBar->isVisible()) {
879             GwenviewConfig::setSideBarSplitterSizes(mCentralSplitter->sizes());
880         }
881     }
882 
setScreenSaverEnabledGwenview::MainWindow::Private883     void setScreenSaverEnabled(bool enabled)
884     {
885         // Always delete mNotificationRestrictions, it does not hurt
886         delete mNotificationRestrictions;
887         if (!enabled) {
888             mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q);
889         } else {
890             mNotificationRestrictions = nullptr;
891         }
892     }
893 
assignThumbnailProviderToThumbnailViewGwenview::MainWindow::Private894     void assignThumbnailProviderToThumbnailView(ThumbnailView *thumbnailView)
895     {
896         GV_RETURN_IF_FAIL(thumbnailView);
897         if (mActiveThumbnailView) {
898             mActiveThumbnailView->setThumbnailProvider(nullptr);
899         }
900         thumbnailView->setThumbnailProvider(mThumbnailProvider);
901         mActiveThumbnailView = thumbnailView;
902         if (mActiveThumbnailView->isVisible()) {
903             mThumbnailProvider->stop();
904             mActiveThumbnailView->generateThumbnailsForItems();
905         }
906     }
907 
autoAssignThumbnailProviderGwenview::MainWindow::Private908     void autoAssignThumbnailProvider()
909     {
910         if (mCurrentMainPageId == ViewMainPageId) {
911             if (q->windowState() & Qt::WindowFullScreen) {
912                 assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar());
913             } else {
914                 assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar());
915             }
916         } else if (mCurrentMainPageId == BrowseMainPageId) {
917             assignThumbnailProviderToThumbnailView(mThumbnailView);
918         } else if (mCurrentMainPageId == StartMainPageId) {
919             assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView());
920         }
921     }
922 };
923 
MainWindow()924 MainWindow::MainWindow()
925     : KXmlGuiWindow()
926     , d(new MainWindow::Private)
927 {
928     d->q = this;
929     d->mCurrentMainPageId = StartMainPageId;
930     d->mDirModel = new SortedDirModel(this);
931     d->setupContextManager();
932     d->setupThumbnailBarModel();
933     d->mGvCore = new GvCore(this, d->mDirModel);
934     d->mPreloader = new Preloader(this);
935     d->mNotificationRestrictions = nullptr;
936     d->mThumbnailProvider = new ThumbnailProvider();
937     d->mActiveThumbnailView = nullptr;
938     d->initDirModel();
939     d->setupWidgets();
940     d->setupActions();
941     d->setupUndoActions();
942     d->setupContextManagerItems();
943     d->setupFullScreenContent();
944 
945     d->updateActions();
946     updatePreviousNextActions();
947     d->mSaveBar->initActionDependentWidgets();
948 
949     createGUI();
950     loadConfig();
951 
952     connect(DocumentFactory::instance(), &DocumentFactory::modifiedDocumentListChanged, this, &MainWindow::slotModifiedDocumentListChanged);
953 
954 #ifdef HAVE_QTDBUS
955     d->mMpris2Service =
956         new Mpris2Service(d->mSlideShow, d->mContextManager, d->mToggleSlideShowAction, d->mFullScreenAction, d->mGoToPreviousAction, d->mGoToNextAction, this);
957 #endif
958 
959     auto *ratingMenu = static_cast<QMenu *>(guiFactory()->container("rating", this));
960     ratingMenu->setIcon(QIcon::fromTheme(QStringLiteral("rating-unrated")));
961 #ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE
962     if (ratingMenu) {
963         ratingMenu->menuAction()->setVisible(false);
964     }
965 #endif
966 
967 #ifdef KIPI_FOUND
968     d->mKIPIInterface = new KIPIInterface(this);
969     d->mKIPIExportAction->setKIPIInterface(d->mKIPIInterface);
970 #else
971     auto *pluginsMenu = static_cast<QMenu *>(guiFactory()->container("plugins", this));
972     if (pluginsMenu) {
973         pluginsMenu->menuAction()->setVisible(false);
974     }
975 #endif
976     setAutoSaveSettings();
977 #ifdef Q_OS_OSX
978     qApp->installEventFilter(this);
979 #endif
980 }
981 
~MainWindow()982 MainWindow::~MainWindow()
983 {
984     delete d->mThumbnailProvider;
985     delete d;
986 }
987 
contextManager() const988 ContextManager *MainWindow::contextManager() const
989 {
990     return d->mContextManager;
991 }
992 
viewMainPage() const993 ViewMainPage *MainWindow::viewMainPage() const
994 {
995     return d->mViewMainPage;
996 }
997 
setCaption(const QString & caption)998 void MainWindow::setCaption(const QString &caption)
999 {
1000     // Keep a trace of caption to use it in slotModifiedDocumentListChanged()
1001     d->mCaption = caption;
1002     KXmlGuiWindow::setCaption(caption);
1003 }
1004 
setCaption(const QString & caption,bool modified)1005 void MainWindow::setCaption(const QString &caption, bool modified)
1006 {
1007     d->mCaption = caption;
1008     KXmlGuiWindow::setCaption(caption, modified);
1009 }
1010 
slotUpdateCaption(const QString & caption)1011 void MainWindow::slotUpdateCaption(const QString &caption)
1012 {
1013     const QUrl url = d->mContextManager->currentUrl();
1014     const QList<QUrl> list = DocumentFactory::instance()->modifiedDocumentList();
1015     setCaption(caption, list.contains(url));
1016 }
1017 
slotModifiedDocumentListChanged()1018 void MainWindow::slotModifiedDocumentListChanged()
1019 {
1020     d->updateActions();
1021     slotUpdateCaption(d->mCaption);
1022 }
1023 
setInitialUrl(const QUrl & _url)1024 void MainWindow::setInitialUrl(const QUrl &_url)
1025 {
1026     Q_ASSERT(_url.isValid());
1027     QUrl url = UrlUtils::fixUserEnteredUrl(_url);
1028 #ifdef HAVE_QTDBUS
1029     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.FileManager1"),
1030                                                           QStringLiteral("/org/freedesktop/FileManager1"),
1031                                                           QStringLiteral("org.freedesktop.FileManager1"),
1032                                                           QStringLiteral("SortOrderForUrl"));
1033 
1034     QUrl dirUrl = url;
1035     dirUrl = dirUrl.adjusted(QUrl::RemoveFilename);
1036     message << dirUrl.toString();
1037 
1038     QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
1039     auto *watcher = new QDBusPendingCallWatcher(call, this);
1040 
1041     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *call) {
1042         QDBusPendingReply<QString, QString> reply = *call;
1043         // Fail silently
1044         if (!reply.isError()) {
1045             QString columnName = reply.argumentAt<0>();
1046             QString orderName = reply.argumentAt<1>();
1047 
1048             int column = -1;
1049             int sortRole = -1;
1050             Qt::SortOrder order = orderName == QStringLiteral("descending") ? Qt::DescendingOrder : Qt::AscendingOrder;
1051 
1052             if (columnName == "text") {
1053                 column = KDirModel::Name;
1054                 sortRole = Qt::DisplayRole;
1055             } else if (columnName == "modificationtime") {
1056                 column = KDirModel::ModifiedTime;
1057                 sortRole = Qt::DisplayRole;
1058             } else if (columnName == "size") {
1059                 column = KDirModel::Size;
1060                 sortRole = Qt::DisplayRole;
1061 #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
1062             } else if (columnName == "rating") {
1063                 column = KDirModel::Name;
1064                 sortRole = SemanticInfoDirModel::RatingRole;
1065 #endif
1066             }
1067 
1068             if (column >= 0 && sortRole >= 0) {
1069                 d->mDirModel->setSortRole(sortRole);
1070                 d->mDirModel->sort(column, order);
1071             }
1072         }
1073         call->deleteLater();
1074     });
1075 #endif
1076 
1077     if (UrlUtils::urlIsDirectory(url)) {
1078         d->mBrowseAction->trigger();
1079         openDirUrl(url);
1080     } else {
1081         openUrl(url);
1082     }
1083 }
1084 
startSlideShow()1085 void MainWindow::startSlideShow()
1086 {
1087     d->mViewAction->trigger();
1088     // We need to wait until we have listed all images in the dirlister to
1089     // start the slideshow because the SlideShow objects needs an image list to
1090     // work.
1091     d->mStartSlideShowWhenDirListerCompleted = true;
1092 }
1093 
setActiveViewModeAction(QAction * action)1094 void MainWindow::setActiveViewModeAction(QAction *action)
1095 {
1096     if (action == d->mViewAction) {
1097         d->mCurrentMainPageId = ViewMainPageId;
1098         // Switching to view mode
1099         d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage);
1100         openSelectedDocuments();
1101         d->mPreloadDirectionIsForward = true;
1102         QTimer::singleShot(VIEW_PRELOAD_DELAY, this, &MainWindow::preloadNextUrl);
1103     } else {
1104         d->mCurrentMainPageId = BrowseMainPageId;
1105         // Switching to browse mode
1106         d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage);
1107         if (!d->mViewMainPage->isEmpty() && KProtocolManager::supportsListing(d->mViewMainPage->url())) {
1108             // Reset the view to spare resources, but don't do it if we can't
1109             // browse the url, otherwise if the user starts Gwenview this way:
1110             // gwenview http://example.com/example.png
1111             // and switch to browse mode, switching back to view mode won't bring
1112             // his image back.
1113             d->mViewMainPage->reset();
1114         }
1115         setCaption(d->mUrlNavigator->locationUrl().adjusted(QUrl::RemoveScheme).toString());
1116     }
1117     d->autoAssignThumbnailProvider();
1118     toggleSideBar(d->sideBarVisibility());
1119     toggleStatusBar(d->statusBarVisibility());
1120 
1121     Q_EMIT viewModeChanged();
1122 }
1123 
slotThumbnailViewIndexActivated(const QModelIndex & index)1124 void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex &index)
1125 {
1126     if (!index.isValid()) {
1127         return;
1128     }
1129 
1130     KFileItem item = d->mDirModel->itemForIndex(index);
1131     if (item.isDir()) {
1132         // Item is a dir, open it
1133         openDirUrl(item.url());
1134     } else {
1135         QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype());
1136         if (!protocol.isEmpty()) {
1137             // Item is an archive, tweak url then open it
1138             QUrl url = item.url();
1139             url.setScheme(protocol);
1140             openDirUrl(url);
1141         } else {
1142             // Item is a document, switch to view mode
1143             d->mViewAction->trigger();
1144         }
1145     }
1146 }
1147 
openSelectedDocuments()1148 void MainWindow::openSelectedDocuments()
1149 {
1150     if (d->mCurrentMainPageId != ViewMainPageId) {
1151         return;
1152     }
1153 
1154     int count = 0;
1155     QList<QUrl> urls;
1156     const auto list = d->mContextManager->selectedFileItemList();
1157     for (const auto &item : list) {
1158         if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
1159             urls << item.url();
1160 
1161             ++count;
1162             if (count == ViewMainPage::MaxViewCount) {
1163                 break;
1164             }
1165         }
1166     }
1167 
1168     if (urls.isEmpty()) {
1169         // Selection contains no fitting items
1170         // Switch back to browsing mode
1171         d->mBrowseAction->trigger();
1172         d->mViewMainPage->reset();
1173         return;
1174     }
1175 
1176     QUrl currentUrl = d->mContextManager->currentUrl();
1177     if (currentUrl.isEmpty() || !urls.contains(currentUrl)) {
1178         // There is no current URL or it doesn't belong to selection
1179         // This can happen when user manually selects a group of items
1180         currentUrl = urls.first();
1181     }
1182 
1183     d->mViewMainPage->openUrls(urls, currentUrl);
1184 }
1185 
goUp()1186 void MainWindow::goUp()
1187 {
1188     if (d->mCurrentMainPageId == BrowseMainPageId) {
1189         QUrl url = d->mContextManager->currentDirUrl();
1190         url = KIO::upUrl(url);
1191         openDirUrl(url);
1192     } else {
1193         d->mBrowseAction->trigger();
1194     }
1195 }
1196 
showStartMainPage()1197 void MainWindow::showStartMainPage()
1198 {
1199     d->mCurrentMainPageId = StartMainPageId;
1200     d->setActionsDisabledOnStartMainPageEnabled(false);
1201 
1202     d->saveSplitterConfig();
1203     d->mSideBar->hide();
1204     d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage);
1205 
1206     d->updateActions();
1207     updatePreviousNextActions();
1208     d->mContextManager->setCurrentDirUrl(QUrl());
1209     d->mContextManager->setCurrentUrl(QUrl());
1210 
1211     d->autoAssignThumbnailProvider();
1212 }
1213 
slotStartMainPageUrlSelected(const QUrl & url)1214 void MainWindow::slotStartMainPageUrlSelected(const QUrl &url)
1215 {
1216     d->setActionsDisabledOnStartMainPageEnabled(true);
1217 
1218     if (d->mBrowseAction->isChecked()) {
1219         // Silently uncheck the action so that setInitialUrl() does the right thing
1220         SignalBlocker blocker(d->mBrowseAction);
1221         d->mBrowseAction->setChecked(false);
1222     }
1223 
1224     setInitialUrl(url);
1225 }
1226 
openDirUrl(const QUrl & url)1227 void MainWindow::openDirUrl(const QUrl &url)
1228 {
1229     const QUrl currentUrl = d->mContextManager->currentDirUrl();
1230 
1231     if (url == currentUrl) {
1232         return;
1233     }
1234 
1235     if (url.isParentOf(currentUrl)) {
1236         // Keep first child between url and currentUrl selected
1237         // If currentPath is      "/home/user/photos/2008/event"
1238         // and wantedPath is      "/home/user/photos"
1239         // pathToSelect should be "/home/user/photos/2008"
1240 
1241         // To anyone considering using QUrl::toLocalFile() instead of
1242         // QUrl::path() here. Please don't, using QUrl::path() is the right
1243         // thing to do here.
1244         const QString currentPath = QDir::cleanPath(currentUrl.adjusted(QUrl::StripTrailingSlash).path());
1245         const QString wantedPath = QDir::cleanPath(url.adjusted(QUrl::StripTrailingSlash).path());
1246         const QChar separator('/');
1247         const int slashCount = wantedPath.count(separator);
1248         const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1);
1249         QUrl urlToSelect = url;
1250         urlToSelect.setPath(pathToSelect);
1251         d->mContextManager->setUrlToSelect(urlToSelect);
1252     }
1253     d->mThumbnailProvider->stop();
1254     d->mContextManager->setCurrentDirUrl(url);
1255     d->mGvCore->addUrlToRecentFolders(url);
1256     d->mViewMainPage->reset();
1257 }
1258 
folderViewUrlChanged(const QUrl & url)1259 void MainWindow::folderViewUrlChanged(const QUrl &url)
1260 {
1261     const QUrl currentUrl = d->mContextManager->currentDirUrl();
1262 
1263     if (url == currentUrl) {
1264         switch (d->mCurrentMainPageId) {
1265         case ViewMainPageId:
1266             d->mBrowseAction->trigger();
1267             break;
1268         case BrowseMainPageId:
1269             d->mViewAction->trigger();
1270             break;
1271         case StartMainPageId:
1272             break;
1273         }
1274     } else {
1275         openDirUrl(url);
1276     }
1277 }
1278 
toggleSideBar(bool visible)1279 void MainWindow::toggleSideBar(bool visible)
1280 {
1281     d->saveSplitterConfig();
1282     d->mToggleSideBarAction->setChecked(visible);
1283     d->mToggleOperationsSideBarAction->setChecked(visible && d->mSideBar->currentPage() == QLatin1String("operations"));
1284     d->saveSideBarVisibility(visible);
1285     d->mSideBar->setVisible(visible);
1286 
1287     const QString iconName = QApplication::isRightToLeft() ? (visible ? "sidebar-collapse-right" : "sidebar-expand-right")
1288                                                            : (visible ? "sidebar-collapse-left" : "sidebar-expand-left");
1289     const QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar");
1290 
1291     const QList<QToolButton *> buttonList{d->mBrowseMainPage->toggleSideBarButton(), d->mViewMainPage->toggleSideBarButton()};
1292     for (auto button : buttonList) {
1293         button->setIcon(QIcon::fromTheme(iconName));
1294         button->setToolTip(toolTip);
1295     }
1296 }
1297 
toggleOperationsSideBar(bool visible)1298 void MainWindow::toggleOperationsSideBar(bool visible)
1299 {
1300     if (visible) {
1301         d->mSideBar->setCurrentPage(QLatin1String("operations"));
1302     }
1303     toggleSideBar(visible);
1304 }
1305 
toggleStatusBar(bool visible)1306 void MainWindow::toggleStatusBar(bool visible)
1307 {
1308     d->mShowStatusBarAction->setChecked(visible);
1309     d->saveStatusBarVisibility(visible);
1310 
1311     d->mViewMainPage->setStatusBarVisible(visible);
1312     d->mBrowseMainPage->setStatusBarVisible(visible);
1313 }
1314 
slotPartCompleted()1315 void MainWindow::slotPartCompleted()
1316 {
1317     d->updateActions();
1318     const QUrl url = d->mContextManager->currentUrl();
1319     if (!url.isEmpty() && GwenviewConfig::historyEnabled()) {
1320         d->mFileOpenRecentAction->addUrl(url);
1321         d->mGvCore->addUrlToRecentFiles(url);
1322     }
1323 
1324     if (KProtocolManager::supportsListing(url)) {
1325         const QUrl dirUrl = d->mContextManager->currentDirUrl();
1326         d->mGvCore->addUrlToRecentFolders(dirUrl);
1327     }
1328 }
1329 
slotSelectionChanged()1330 void MainWindow::slotSelectionChanged()
1331 {
1332     if (d->mCurrentMainPageId == ViewMainPageId) {
1333         // The user selected a new file in the thumbnail view, since the
1334         // document view is visible, let's show it
1335         openSelectedDocuments();
1336     } else {
1337         // No document view, we need to load the document to set the undo group
1338         // of document factory to the correct QUndoStack
1339         QModelIndex index = d->mThumbnailView->currentIndex();
1340         KFileItem item;
1341         if (index.isValid()) {
1342             item = d->mDirModel->itemForIndex(index);
1343         }
1344         QUndoGroup *undoGroup = DocumentFactory::instance()->undoGroup();
1345         if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
1346             QUrl url = item.url();
1347             Document::Ptr doc = DocumentFactory::instance()->load(url);
1348             undoGroup->addStack(doc->undoStack());
1349             undoGroup->setActiveStack(doc->undoStack());
1350         } else {
1351             undoGroup->setActiveStack(nullptr);
1352         }
1353     }
1354 
1355     // Update UI
1356     d->updateActions();
1357     updatePreviousNextActions();
1358 
1359     // Start preloading
1360     int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY;
1361     QTimer::singleShot(preloadDelay, this, &MainWindow::preloadNextUrl);
1362 }
1363 
slotCurrentDirUrlChanged(const QUrl & url)1364 void MainWindow::slotCurrentDirUrlChanged(const QUrl &url)
1365 {
1366     if (url.isValid()) {
1367         d->mUrlNavigator->setLocationUrl(url);
1368         d->mGoUpAction->setEnabled(url.path() != "/");
1369         if (d->mCurrentMainPageId == BrowseMainPageId) {
1370             setCaption(d->mUrlNavigator->locationUrl().adjusted(QUrl::RemoveScheme).toString());
1371         }
1372     } else {
1373         d->mGoUpAction->setEnabled(false);
1374     }
1375 }
1376 
slotDirModelNewItems()1377 void MainWindow::slotDirModelNewItems()
1378 {
1379     if (d->mContextManager->selectionModel()->hasSelection()) {
1380         updatePreviousNextActions();
1381     }
1382 }
1383 
slotDirListerCompleted()1384 void MainWindow::slotDirListerCompleted()
1385 {
1386     if (d->mStartSlideShowWhenDirListerCompleted) {
1387         d->mStartSlideShowWhenDirListerCompleted = false;
1388         QTimer::singleShot(0, d->mToggleSlideShowAction, &QAction::trigger);
1389     }
1390     if (d->mContextManager->selectionModel()->hasSelection()) {
1391         updatePreviousNextActions();
1392     } else if (!d->mContextManager->currentUrl().isValid()) {
1393         d->goToFirstDocument();
1394 
1395         // Try to select the first directory in case there are no images to select
1396         if (!d->mContextManager->selectionModel()->hasSelection()) {
1397             const QModelIndex index = d->mThumbnailView->model()->index(0, 0);
1398             if (index.isValid()) {
1399                 d->mThumbnailView->setCurrentIndex(index);
1400             }
1401         }
1402     }
1403     d->mThumbnailView->scrollToSelectedIndex();
1404     d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex();
1405     d->mFullScreenContent->thumbnailBar()->scrollToSelectedIndex();
1406 }
1407 
goToPrevious()1408 void MainWindow::goToPrevious()
1409 {
1410     const QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1411     QModelIndex previousIndex = d->mDirModel->index(currentIndex.row(), 0);
1412 
1413     KFileItem fileItem;
1414 
1415     // Besides images also folders and archives are listed as well,
1416     // we need to skip them in the slideshow
1417     do {
1418         previousIndex = d->mDirModel->index(previousIndex.row() - 1, 0);
1419         fileItem = previousIndex.data(KDirModel::FileItemRole).value<KFileItem>();
1420     } while (previousIndex.isValid() && !MimeTypeUtils::imageMimeTypes().contains(fileItem.currentMimeType().name()));
1421 
1422     if (!previousIndex.isValid()
1423         && (GwenviewConfig::navigationEndNotification() == SlideShow::NeverWarn
1424             || (GwenviewConfig::navigationEndNotification() == SlideShow::WarnOnSlideshow && !d->mSlideShow->isRunning() && !d->mFullScreenAction->isChecked())
1425             || d->hudButtonBox)) {
1426         d->goToLastDocument();
1427     } else if (!previousIndex.isValid()) {
1428         showFirstDocumentReached();
1429     } else {
1430         d->goTo(-1);
1431     }
1432 }
1433 
goToNext()1434 void MainWindow::goToNext()
1435 {
1436     const QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1437     QModelIndex nextIndex = d->mDirModel->index(currentIndex.row(), 0);
1438 
1439     KFileItem fileItem;
1440 
1441     // Besides images also folders and archives are listed as well,
1442     // we need to skip them in the slideshow
1443     do {
1444         nextIndex = d->mDirModel->index(nextIndex.row() + 1, 0);
1445         fileItem = nextIndex.data(KDirModel::FileItemRole).value<KFileItem>();
1446     } while (nextIndex.isValid() && !MimeTypeUtils::imageMimeTypes().contains(fileItem.currentMimeType().name()));
1447 
1448     if (!nextIndex.isValid()
1449         && (GwenviewConfig::navigationEndNotification() == SlideShow::NeverWarn
1450             || (GwenviewConfig::navigationEndNotification() == SlideShow::WarnOnSlideshow && !d->mSlideShow->isRunning() && !d->mFullScreenAction->isChecked())
1451             || d->hudButtonBox)) {
1452         d->goToFirstDocument();
1453     } else if (!nextIndex.isValid()) {
1454         showLastDocumentReached();
1455     } else {
1456         d->goTo(1);
1457     }
1458 }
1459 
goToFirst()1460 void MainWindow::goToFirst()
1461 {
1462     d->goToFirstDocument();
1463 }
1464 
goToLast()1465 void MainWindow::goToLast()
1466 {
1467     d->goToLastDocument();
1468 }
1469 
goToUrl(const QUrl & url)1470 void MainWindow::goToUrl(const QUrl &url)
1471 {
1472     if (d->mCurrentMainPageId == ViewMainPageId) {
1473         d->mViewMainPage->openUrl(url);
1474     }
1475     QUrl dirUrl = url;
1476     dirUrl = dirUrl.adjusted(QUrl::RemoveFilename);
1477     if (dirUrl != d->mContextManager->currentDirUrl()) {
1478         d->mContextManager->setCurrentDirUrl(dirUrl);
1479         d->mGvCore->addUrlToRecentFolders(dirUrl);
1480     }
1481     d->mContextManager->setUrlToSelect(url);
1482 }
1483 
updatePreviousNextActions()1484 void MainWindow::updatePreviousNextActions()
1485 {
1486     bool hasPrevious;
1487     bool hasNext;
1488     QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1489     if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) {
1490         int row = currentIndex.row();
1491         QModelIndex prevIndex = d->mDirModel->index(row - 1, 0);
1492         QModelIndex nextIndex = d->mDirModel->index(row + 1, 0);
1493         hasPrevious = GwenviewConfig::navigationEndNotification() != SlideShow::AlwaysWarn || (prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex));
1494         hasNext = GwenviewConfig::navigationEndNotification() != SlideShow::AlwaysWarn || (nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex));
1495     } else {
1496         hasPrevious = false;
1497         hasNext = false;
1498     }
1499 
1500     d->mGoToPreviousAction->setEnabled(hasPrevious);
1501     d->mGoToNextAction->setEnabled(hasNext);
1502     d->mGoToFirstAction->setEnabled(hasPrevious);
1503     d->mGoToLastAction->setEnabled(hasNext);
1504 }
1505 
leaveFullScreen()1506 void MainWindow::leaveFullScreen()
1507 {
1508     if (d->mFullScreenAction->isChecked()) {
1509         d->mFullScreenAction->trigger();
1510     }
1511 }
1512 
toggleFullScreen(bool checked)1513 void MainWindow::toggleFullScreen(bool checked)
1514 {
1515     setUpdatesEnabled(false);
1516     if (checked) {
1517         // Save MainWindow config now, this way if we quit while in
1518         // fullscreen, we are sure latest MainWindow changes are remembered.
1519         KConfigGroup saveConfigGroup = autoSaveConfigGroup();
1520         if (!isFullScreen()) {
1521             // Save state if window manager did not already switch to fullscreen.
1522             saveMainWindowSettings(saveConfigGroup);
1523             d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible();
1524         }
1525         setAutoSaveSettings(saveConfigGroup, false);
1526         resetAutoSaveSettings();
1527 
1528         // Go full screen
1529         KToggleFullScreenAction::setFullScreen(this, true);
1530         menuBar()->hide();
1531         toolBar()->hide();
1532 
1533         qApp->setProperty("KDE_COLOR_SCHEME_PATH", d->mGvCore->fullScreenPaletteName());
1534         QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette));
1535 
1536         d->setScreenSaverEnabled(false);
1537     } else {
1538         setAutoSaveSettings();
1539 
1540         // Back to normal
1541         qApp->setProperty("KDE_COLOR_SCHEME_PATH", QVariant());
1542         QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette));
1543 
1544         d->mSlideShow->pause();
1545         KToggleFullScreenAction::setFullScreen(this, false);
1546         menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1547         toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible);
1548 
1549         d->setScreenSaverEnabled(true);
1550 
1551         // See resizeEvent
1552         d->mFullScreenLeftAt = QDateTime::currentDateTime();
1553     }
1554 
1555     d->mFullScreenContent->setFullScreenMode(checked);
1556     d->mBrowseMainPage->setFullScreenMode(checked);
1557     d->mViewMainPage->setFullScreenMode(checked);
1558     d->mSaveBar->setFullScreenMode(checked);
1559 
1560     toggleSideBar(d->sideBarVisibility());
1561     toggleStatusBar(d->statusBarVisibility());
1562 
1563     setUpdatesEnabled(true);
1564     d->autoAssignThumbnailProvider();
1565 }
1566 
saveCurrent()1567 void MainWindow::saveCurrent()
1568 {
1569     d->mGvCore->save(d->mContextManager->currentUrl());
1570 }
1571 
saveCurrentAs()1572 void MainWindow::saveCurrentAs()
1573 {
1574     d->mGvCore->saveAs(d->mContextManager->currentUrl());
1575 }
1576 
reload()1577 void MainWindow::reload()
1578 {
1579     if (d->mCurrentMainPageId == ViewMainPageId) {
1580         d->mViewMainPage->reload();
1581     } else {
1582         d->mBrowseMainPage->reload();
1583     }
1584 }
1585 
openFile()1586 void MainWindow::openFile()
1587 {
1588     const QUrl dirUrl = d->mContextManager->currentDirUrl();
1589 
1590     auto *dialog = new QFileDialog(this);
1591     dialog->setAttribute(Qt::WA_DeleteOnClose);
1592     dialog->setModal(true);
1593     dialog->setDirectoryUrl(dirUrl);
1594     dialog->setWindowTitle(i18nc("@title:window", "Open Image"));
1595     dialog->setMimeTypeFilters(MimeTypeUtils::imageMimeTypes());
1596     dialog->setAcceptMode(QFileDialog::AcceptOpen);
1597     connect(dialog, &QDialog::accepted, this, [this, dialog]() {
1598         if (!dialog->selectedUrls().isEmpty()) {
1599             openUrl(dialog->selectedUrls().at(0));
1600         }
1601     });
1602 
1603     dialog->show();
1604 }
1605 
openUrl(const QUrl & url)1606 void MainWindow::openUrl(const QUrl &url)
1607 {
1608     d->setActionsDisabledOnStartMainPageEnabled(true);
1609     d->mContextManager->setUrlToSelect(url);
1610     d->mViewAction->trigger();
1611 }
1612 
showDocumentInFullScreen(const QUrl & url)1613 void MainWindow::showDocumentInFullScreen(const QUrl &url)
1614 {
1615     d->mContextManager->setUrlToSelect(url);
1616     d->mViewAction->trigger();
1617     d->mFullScreenAction->trigger();
1618 }
1619 
toggleSlideShow()1620 void MainWindow::toggleSlideShow()
1621 {
1622     if (d->mSlideShow->isRunning()) {
1623         d->mSlideShow->pause();
1624     } else {
1625         if (!d->mViewAction->isChecked()) {
1626             d->mViewAction->trigger();
1627         }
1628         if (!d->mFullScreenAction->isChecked()) {
1629             d->mFullScreenAction->trigger();
1630         }
1631         QList<QUrl> list;
1632         for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) {
1633             QModelIndex index = d->mDirModel->index(pos, 0);
1634             KFileItem item = d->mDirModel->itemForIndex(index);
1635             MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
1636             switch (kind) {
1637             case MimeTypeUtils::KIND_SVG_IMAGE:
1638             case MimeTypeUtils::KIND_RASTER_IMAGE:
1639             case MimeTypeUtils::KIND_VIDEO:
1640                 list << item.url();
1641                 break;
1642             default:
1643                 break;
1644             }
1645         }
1646         d->mSlideShow->start(list);
1647     }
1648     updateSlideShowAction();
1649 }
1650 
updateSlideShowAction()1651 void MainWindow::updateSlideShowAction()
1652 {
1653     if (d->mSlideShow->isRunning()) {
1654         d->mToggleSlideShowAction->setText(i18n("Pause Slideshow"));
1655         d->mToggleSlideShowAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
1656     } else {
1657         d->mToggleSlideShowAction->setText(d->mFullScreenAction->isChecked() ? i18n("Resume Slideshow") : i18n("Start Slideshow"));
1658         d->mToggleSlideShowAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
1659     }
1660 }
1661 
queryClose()1662 bool MainWindow::queryClose()
1663 {
1664     saveConfig();
1665     QList<QUrl> list = DocumentFactory::instance()->modifiedDocumentList();
1666     if (list.size() == 0) {
1667         return true;
1668     }
1669 
1670     KGuiItem yes(i18n("Save All Changes"), "document-save-all");
1671     KGuiItem no(i18n("Discard Changes"), "delete");
1672     QString msg =
1673         i18np("One image has been modified.", "%1 images have been modified.", list.size()) + '\n' + i18n("If you quit now, your changes will be lost.");
1674     int answer = KMessageBox::warningYesNoCancel(this, msg, QString() /* caption */, yes, no);
1675 
1676     switch (answer) {
1677     case KMessageBox::Yes:
1678         d->mGvCore->saveAll();
1679         // We need to wait a bit because the DocumentFactory is notified about
1680         // saved documents through a queued connection.
1681         qApp->processEvents();
1682         return DocumentFactory::instance()->modifiedDocumentList().isEmpty();
1683 
1684     case KMessageBox::No:
1685         return true;
1686 
1687     default: // cancel
1688         return false;
1689     }
1690 }
1691 
showConfigDialog()1692 void MainWindow::showConfigDialog()
1693 {
1694     // Save first so changes like thumbnail zoom level are not lost when reloading config
1695     saveConfig();
1696 
1697     auto *dialog = new ConfigDialog(this);
1698     dialog->setAttribute(Qt::WA_DeleteOnClose);
1699     dialog->setModal(true);
1700     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::loadConfig);
1701     dialog->show();
1702 }
1703 
configureShortcuts()1704 void MainWindow::configureShortcuts()
1705 {
1706 #if KXMLGUI_VERSION >= QT_VERSION_CHECK(5, 84, 0)
1707     guiFactory()->showConfigureShortcutsDialog();
1708 #else
1709     guiFactory()->configureShortcuts();
1710 #endif
1711 }
1712 
toggleMenuBar()1713 void MainWindow::toggleMenuBar()
1714 {
1715     if (!d->mFullScreenAction->isChecked()) {
1716         if (!d->mShowMenuBarAction->isChecked() && (!toolBar()->isVisible() || !toolBar()->actions().contains(d->mHamburgerMenu))) {
1717             const QString accel = d->mShowMenuBarAction->shortcut().toString();
1718             KMessageBox::information(this,
1719                                      i18n("This will hide the menu bar completely."
1720                                           " You can show it again by typing %1.",
1721                                           accel),
1722                                      i18n("Hide menu bar"),
1723                                      QLatin1String("HideMenuBarWarning"));
1724         }
1725         menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1726     }
1727 }
1728 
loadConfig()1729 void MainWindow::loadConfig()
1730 {
1731     d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions());
1732     d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos());
1733 
1734     if (GwenviewConfig::historyEnabled()) {
1735         d->mFileOpenRecentAction->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files"));
1736         const auto mFileOpenRecentActionUrls = d->mFileOpenRecentAction->urls();
1737         for (const QUrl &url : mFileOpenRecentActionUrls) {
1738             d->mGvCore->addUrlToRecentFiles(url);
1739         }
1740     } else {
1741         d->mFileOpenRecentAction->clear();
1742     }
1743     d->mFileOpenRecentAction->setVisible(GwenviewConfig::historyEnabled());
1744 
1745     d->mStartMainPage->loadConfig();
1746     d->mViewMainPage->loadConfig();
1747     d->mBrowseMainPage->loadConfig();
1748     d->mContextManager->loadConfig();
1749     d->mSideBar->loadConfig();
1750     d->loadSplitterConfig();
1751 }
1752 
saveConfig()1753 void MainWindow::saveConfig()
1754 {
1755     d->mFileOpenRecentAction->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files"));
1756     d->mViewMainPage->saveConfig();
1757     d->mBrowseMainPage->saveConfig();
1758     d->mContextManager->saveConfig();
1759     d->saveSplitterConfig();
1760     GwenviewConfig::setFullScreenModeActive(isFullScreen());
1761     // Save the last used version when Gwenview closes so we know which settings/features the user
1762     // is aware of which is needed for migration. The version format is: two digits each for
1763     // major minor bugfix version. Never decrease this number. Increase it when needed.
1764     GwenviewConfig::setLastUsedVersion(210800);
1765 }
1766 
print()1767 void MainWindow::print()
1768 {
1769     if (!d->mContextManager->currentUrlIsRasterImage()) {
1770         return;
1771     }
1772 
1773     Document::Ptr doc = DocumentFactory::instance()->load(d->mContextManager->currentUrl());
1774     PrintHelper printHelper(this);
1775     printHelper.print(doc);
1776 }
1777 
preloadNextUrl()1778 void MainWindow::preloadNextUrl()
1779 {
1780     static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0";
1781     if (disablePreload) {
1782         qCDebug(GWENVIEW_APP_LOG) << "Preloading disabled";
1783         return;
1784     }
1785     QItemSelection selection = d->mContextManager->selectionModel()->selection();
1786     if (selection.size() != 1) {
1787         return;
1788     }
1789 
1790     QModelIndexList indexList = selection.indexes();
1791     if (indexList.isEmpty()) {
1792         return;
1793     }
1794 
1795     QModelIndex index = indexList.at(0);
1796     if (!index.isValid()) {
1797         return;
1798     }
1799 
1800     if (d->mCurrentMainPageId == ViewMainPageId) {
1801         // If we are in view mode, preload the next url, otherwise preload the
1802         // selected one
1803         int offset = d->mPreloadDirectionIsForward ? 1 : -1;
1804         index = d->mDirModel->sibling(index.row() + offset, index.column(), index);
1805         if (!index.isValid()) {
1806             return;
1807         }
1808     }
1809 
1810     KFileItem item = d->mDirModel->itemForIndex(index);
1811     if (!ArchiveUtils::fileItemIsDirOrArchive(item)) {
1812         QUrl url = item.url();
1813         if (url.isLocalFile()) {
1814             QSize size = d->mViewStackedWidget->size();
1815             d->mPreloader->preload(url, size);
1816         }
1817     }
1818 }
1819 
1820 // Set a sane initial window size
sizeHint() const1821 QSize MainWindow::sizeHint() const
1822 {
1823     return KXmlGuiWindow::sizeHint().expandedTo(QSize(1020, 700));
1824 }
1825 
showEvent(QShowEvent * event)1826 void MainWindow::showEvent(QShowEvent *event)
1827 {
1828     // We need to delay initializing the action state until the menu bar has
1829     // been initialized, that's why it's done only in the showEvent()
1830     if (GwenviewConfig::lastUsedVersion() == -1 && toolBar()->actions().contains(d->mHamburgerMenu)) {
1831         menuBar()->hide();
1832     }
1833     d->mShowMenuBarAction->setChecked(menuBar()->isVisible());
1834     KXmlGuiWindow::showEvent(event);
1835 }
1836 
resizeEvent(QResizeEvent * event)1837 void MainWindow::resizeEvent(QResizeEvent *event)
1838 {
1839     KXmlGuiWindow::resizeEvent(event);
1840     // This is a small hack to execute code after leaving fullscreen, and after
1841     // the window has been resized back to its former size.
1842     if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) {
1843         if (d->mCurrentMainPageId == BrowseMainPageId) {
1844             d->mThumbnailView->scrollToSelectedIndex();
1845         }
1846         d->mFullScreenLeftAt = QDateTime();
1847     }
1848 }
1849 
eventFilter(QObject * obj,QEvent * event)1850 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
1851 {
1852     Q_UNUSED(obj);
1853     Q_UNUSED(event);
1854 #ifdef Q_OS_OSX
1855     /**
1856      * handle Mac OS X file open events (only exist on OS X)
1857      */
1858     if (event->type() == QEvent::FileOpen) {
1859         QFileOpenEvent *fileOpenEvent = static_cast<QFileOpenEvent *>(event);
1860         openUrl(fileOpenEvent->url());
1861         return true;
1862     }
1863 #endif
1864     if (obj == d->mThumbnailView->viewport()) {
1865         switch (event->type()) {
1866         case QEvent::MouseButtonPress:
1867         case QEvent::MouseButtonDblClick:
1868             mouseButtonNavigate(static_cast<QMouseEvent *>(event));
1869             break;
1870         default:;
1871         }
1872     }
1873     return false;
1874 }
1875 
mousePressEvent(QMouseEvent * event)1876 void MainWindow::mousePressEvent(QMouseEvent *event)
1877 {
1878     mouseButtonNavigate(event);
1879     KXmlGuiWindow::mousePressEvent(event);
1880 }
1881 
mouseDoubleClickEvent(QMouseEvent * event)1882 void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
1883 {
1884     mouseButtonNavigate(event);
1885     KXmlGuiWindow::mouseDoubleClickEvent(event);
1886 }
1887 
mouseButtonNavigate(QMouseEvent * event)1888 void MainWindow::mouseButtonNavigate(QMouseEvent *event)
1889 {
1890     switch (event->button()) {
1891     case Qt::ForwardButton:
1892         if (d->mGoToNextAction->isEnabled()) {
1893             d->mGoToNextAction->trigger();
1894             return;
1895         }
1896         break;
1897     case Qt::BackButton:
1898         if (d->mGoToPreviousAction->isEnabled()) {
1899             d->mGoToPreviousAction->trigger();
1900             return;
1901         }
1902         break;
1903     default:;
1904     }
1905 }
1906 
setDistractionFreeMode(bool value)1907 void MainWindow::setDistractionFreeMode(bool value)
1908 {
1909     d->mFullScreenContent->setDistractionFreeMode(value);
1910 }
1911 
saveProperties(KConfigGroup & group)1912 void MainWindow::saveProperties(KConfigGroup &group)
1913 {
1914     group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId));
1915     group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl().toString());
1916 }
1917 
readProperties(const KConfigGroup & group)1918 void MainWindow::readProperties(const KConfigGroup &group)
1919 {
1920     const QUrl url = group.readEntry(SESSION_URL_KEY, QUrl());
1921     if (url.isValid()) {
1922         goToUrl(url);
1923     }
1924 
1925     MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId)));
1926     if (pageId == StartMainPageId) {
1927         showStartMainPage();
1928     } else if (pageId == BrowseMainPageId) {
1929         d->mBrowseAction->trigger();
1930     } else {
1931         d->mViewAction->trigger();
1932     }
1933 }
1934 
showFirstDocumentReached()1935 void MainWindow::showFirstDocumentReached()
1936 {
1937     if (d->hudButtonBox || d->mCurrentMainPageId != ViewMainPageId) {
1938         return;
1939     }
1940     d->hudButtonBox = new HudButtonBox;
1941     d->hudButtonBox->setText(i18n("You reached the first document, what do you want to do?"));
1942     d->hudButtonBox->addButton(i18n("Stay There"));
1943     d->hudButtonBox->addAction(d->mGoToLastAction, i18n("Go to the Last Document"));
1944     d->hudButtonBox->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
1945     d->hudButtonBox->addCountDown(15000);
1946     d->mViewMainPage->showMessageWidget(d->hudButtonBox, Qt::AlignCenter);
1947 }
1948 
showLastDocumentReached()1949 void MainWindow::showLastDocumentReached()
1950 {
1951     if (d->hudButtonBox || d->mCurrentMainPageId != ViewMainPageId) {
1952         return;
1953     }
1954     d->hudButtonBox = new HudButtonBox;
1955     d->hudButtonBox->setText(i18n("You reached the last document, what do you want to do?"));
1956     d->hudButtonBox->addButton(i18n("Stay There"));
1957     d->hudButtonBox->addAction(d->mGoToFirstAction, i18n("Go to the First Document"));
1958     d->hudButtonBox->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
1959     d->hudButtonBox->addCountDown(15000);
1960     d->mViewMainPage->showMessageWidget(d->hudButtonBox, Qt::AlignCenter);
1961 }
1962 
replaceLocation()1963 void MainWindow::replaceLocation()
1964 {
1965     QLineEdit *lineEdit = d->mUrlNavigator->editor()->lineEdit();
1966 
1967     // If the text field currently has focus and everything is selected,
1968     // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
1969     if (d->mUrlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) {
1970         d->mUrlNavigator->setUrlEditable(false);
1971     } else {
1972         d->mUrlNavigator->setUrlEditable(true);
1973         d->mUrlNavigator->setFocus();
1974         lineEdit->selectAll();
1975     }
1976 }
1977 
1978 } // namespace
1979