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