mainwindow.cpp 61.4 KB
Newer Older
1
2
/*
Gwenview: an image viewer
Aurélien Gâteau's avatar
Aurélien Gâteau committed
3
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
David Edmundson's avatar
David Edmundson committed
20
#include "mainwindow.h"
21
#include <config-gwenview.h>
22
#include "dialogguard.h"
23
24

// Qt
25
#include <QApplication>
26
#include <QDateTime>
27
#include <QLineEdit>
28
#include <QPushButton>
29
#include <QShortcut>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
30
#include <QSplitter>
31
#include <QStackedWidget>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
32
#include <QTimer>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
33
#include <QUndoGroup>
34
#include <QVBoxLayout>
Laurent Montel's avatar
Laurent Montel committed
35
#include <QMenuBar>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
36
#include <QUrl>
37
#include <QMouseEvent>
38
39
40
#ifdef Q_OS_OSX
#include <QFileOpenEvent>
#endif
41
42
#include <QJsonArray>
#include <QJsonObject>
43
44

// KDE
Aurélien Gâteau's avatar
Aurélien Gâteau committed
45
#include <KActionCategory>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
46
#include <KActionCollection>
47
#include <QFileDialog>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
48
#include <KFileItem>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
49
#include <KLocalizedString>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
50
51
52
#include <KMessageBox>
#include <KNotificationRestrictions>
#include <KProtocolManager>
53
#include <KLinkItemSelectionModel>
Kevin Funk's avatar
Kevin Funk committed
54
#include <KRecentFilesAction>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
55
56
#include <KStandardShortcut>
#include <KToggleFullScreenAction>
57
#include <KUrlComboBox>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
58
#include <KUrlNavigator>
Laurent Montel's avatar
Laurent Montel committed
59
#include <KToolBar>
60
#include <KToolBarPopupAction>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
61
#include <KXMLGUIFactory>
Laurent Montel's avatar
Laurent Montel committed
62
#include <KDirLister>
63
64
#include <PurposeWidgets/Menu>
#include <Purpose/AlternativesModel>
65
66

// Local
67
#include "configdialog.h"
68
#include "documentinfoprovider.h"
69
#include "viewmainpage.h"
70
#include "fileopscontextmanageritem.h"
71
#include "folderviewcontextmanageritem.h"
72
#include "fullscreencontent.h"
Aurélien Gâteau's avatar
Aurélien Gâteau committed
73
#include "gvcore.h"
74
#include "imageopscontextmanageritem.h"
75
#include "infocontextmanageritem.h"
Angelo Naselli's avatar
Angelo Naselli committed
76
#ifdef KIPI_FOUND
Aurélien Gâteau's avatar
Aurélien Gâteau committed
77
#include "kipiexportaction.h"
Angelo Naselli's avatar
Angelo Naselli committed
78
79
#include "kipiinterface.h"
#endif
Aurélien Gâteau's avatar
Aurélien Gâteau committed
80
#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
81
#include "semanticinfocontextmanageritem.h"
82
#endif
Aurélien Gâteau's avatar
Aurélien Gâteau committed
83
#include "preloader.h"
84
#include "savebar.h"
85
#include "sidebar.h"
Aurélien Gâteau's avatar
Aurélien Gâteau committed
86
#include "splitter.h"
Aurélien Gâteau's avatar
Aurélien Gâteau committed
87
#include "startmainpage.h"
88
#include "thumbnailviewhelper.h"
89
#include "browsemainpage.h"
90
#include <lib/hud/hudbuttonbox.h>
91
#include <lib/archiveutils.h>
92
#include <lib/contextmanager.h>
93
#include <lib/disabledactionshortcutmonitor.h>
94
#include <lib/document/documentfactory.h>
95
#include <lib/documentonlyproxymodel.h>
96
#include <lib/gvdebug.h>
97
#include <lib/gwenviewconfig.h>
98
#include <lib/mimetypeutils.h>
99
100
101
#ifdef HAVE_QTDBUS
#include <lib/mpris2/mpris2service.h>
#endif
Aurélien Gâteau's avatar
Aurélien Gâteau committed
102
#include <lib/print/printhelper.h>
103
#include <lib/slideshow.h>
104
#include <lib/signalblocker.h>
Aurélien Gâteau's avatar
Aurélien Gâteau committed
105
#include <lib/semanticinfo/sorteddirmodel.h>
106
#include <lib/thumbnailprovider/thumbnailprovider.h>
107
#include <lib/thumbnailview/thumbnailbarview.h>
108
#include <lib/thumbnailview/thumbnailview.h>
109
#include <lib/urlutils.h>
110

111
112
namespace Gwenview
{
113

114
115
116
117
#undef ENABLE_LOG
#undef LOG
//#define ENABLE_LOG
#ifdef ENABLE_LOG
David Edmundson's avatar
David Edmundson committed
118
#define LOG(x) qDebug() << x
119
120
121
#else
#define LOG(x) ;
#endif
122

123
124
static const int BROWSE_PRELOAD_DELAY = 1000;
static const int VIEW_PRELOAD_DELAY = 100;
125

126
127
128
static const char* SESSION_CURRENT_PAGE_KEY = "Page";
static const char* SESSION_URL_KEY = "Url";

Aurélien Gâteau's avatar
Aurélien Gâteau committed
129
enum MainPageId {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
130
    StartMainPageId,
Aurélien Gâteau's avatar
Aurélien Gâteau committed
131
132
    BrowseMainPageId,
    ViewMainPageId
133
134
};

Aurélien Gâteau's avatar
Aurélien Gâteau committed
135
136
struct MainWindowState
{
137
    bool mToolBarVisible;
138
139
};

140
141
142
143
/*

Layout of the main window looks like this:

144
145
146
147
148
149
150
151
152
153
154
155
156
.-mCentralSplitter-----------------------------.
|.-mSideBar--. .-mContentWidget---------------.|
||           | |.-mSaveBar-------------------.||
||           | ||                            |||
||           | |'----------------------------'||
||           | |.-mViewStackedWidget---------.||
||           | ||                            |||
||           | ||                            |||
||           | ||                            |||
||           | ||                            |||
||           | |'----------------------------'||
|'-----------' '------------------------------'|
'----------------------------------------------'
157
158

*/
Aurélien Gâteau's avatar
Aurélien Gâteau committed
159
160
struct MainWindow::Private
{
161
    GvCore* mGvCore;
162
    MainWindow* q;
163
164
    QSplitter* mCentralSplitter;
    QWidget* mContentWidget;
165
    ViewMainPage* mViewMainPage;
166
167
    KUrlNavigator* mUrlNavigator;
    ThumbnailView* mThumbnailView;
Benjamin Löwe's avatar
Benjamin Löwe committed
168
    ThumbnailView* mActiveThumbnailView;
169
170
    DocumentInfoProvider* mDocumentInfoProvider;
    ThumbnailViewHelper* mThumbnailViewHelper;
171
    QPointer<ThumbnailProvider> mThumbnailProvider;
172
    BrowseMainPage* mBrowseMainPage;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
173
    StartMainPage* mStartMainPage;
174
175
176
177
178
179
    SideBar* mSideBar;
    QStackedWidget* mViewStackedWidget;
    FullScreenContent* mFullScreenContent;
    SaveBar* mSaveBar;
    bool mStartSlideShowWhenDirListerCompleted;
    SlideShow* mSlideShow;
180
181
182
#ifdef HAVE_QTDBUS
    Mpris2Service* mMpris2Service;
#endif
183
    Preloader* mPreloader;
184
    bool mPreloadDirectionIsForward;
Angelo Naselli's avatar
Angelo Naselli committed
185
#ifdef KIPI_FOUND
186
    KIPIInterface* mKIPIInterface;
Angelo Naselli's avatar
Angelo Naselli committed
187
#endif
188

189
    QActionGroup* mViewModeActionGroup;
Kevin Funk's avatar
Kevin Funk committed
190
    KRecentFilesAction* mFileOpenRecentAction;
David Edmundson's avatar
David Edmundson committed
191
192
193
194
195
196
197
    QAction * mBrowseAction;
    QAction * mViewAction;
    QAction * mGoUpAction;
    QAction * mGoToPreviousAction;
    QAction * mGoToNextAction;
    QAction * mGoToFirstAction;
    QAction * mGoToLastAction;
198
    KToggleAction* mToggleSideBarAction;
199
    QAction* mFullScreenAction;
David Edmundson's avatar
David Edmundson committed
200
    QAction * mToggleSlideShowAction;
201
    KToggleAction* mShowMenuBarAction;
Valerio Pilo's avatar
Valerio Pilo committed
202
    KToggleAction* mShowStatusBarAction;
203
204
    Purpose::Menu* mShareMenu;
    KToolBarPopupAction* mShareAction;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
205
#ifdef KIPI_FOUND
206
    KIPIExportAction* mKIPIExportAction;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
207
#endif
208

209
    SortedDirModel* mDirModel;
210
211
    DocumentOnlyProxyModel* mThumbnailBarModel;
    KLinkItemSelectionModel* mThumbnailBarSelectionModel;
212
213
214
215
216
217
    ContextManager* mContextManager;

    MainWindowState mStateBeforeFullScreen;

    QString mCaption;

Aurélien Gâteau's avatar
Aurélien Gâteau committed
218
    MainPageId mCurrentMainPageId;
219
220
221
222

    QDateTime mFullScreenLeftAt;
    KNotificationRestrictions* mNotificationRestrictions;

223
224
225
    void setupContextManager()
    {
        mContextManager = new ContextManager(mDirModel, q);
Laurent Montel's avatar
Laurent Montel committed
226
227
228
229
        connect(mContextManager, &ContextManager::selectionChanged,
            q, &MainWindow::slotSelectionChanged);
        connect(mContextManager, &ContextManager::currentDirUrlChanged,
            q, &MainWindow::slotCurrentDirUrlChanged);
230
231
    }

232
233
    void setupWidgets()
    {
234
        mFullScreenContent = new FullScreenContent(q, mGvCore);
Laurent Montel's avatar
Laurent Montel committed
235
        connect(mContextManager, &ContextManager::currentUrlChanged, mFullScreenContent, &FullScreenContent::setCurrentUrl);
236

237
238
        mCentralSplitter = new Splitter(Qt::Horizontal, q);
        q->setCentralWidget(mCentralSplitter);
239
240
241
242
243
244
245

        // Left side of splitter
        mSideBar = new SideBar(mCentralSplitter);

        // Right side of splitter
        mContentWidget = new QWidget(mCentralSplitter);

246
        mSaveBar = new SaveBar(mContentWidget, q->actionCollection());
Laurent Montel's avatar
Laurent Montel committed
247
        connect(mContextManager, &ContextManager::currentUrlChanged, mSaveBar, &SaveBar::setCurrentUrl);
248
249
250
251
        mViewStackedWidget = new QStackedWidget(mContentWidget);
        QVBoxLayout* layout = new QVBoxLayout(mContentWidget);
        layout->addWidget(mSaveBar);
        layout->addWidget(mViewStackedWidget);
Laurent Montel's avatar
Laurent Montel committed
252
        layout->setContentsMargins(0, 0, 0, 0);
253
254
255
256
        layout->setSpacing(0);
        ////

        mStartSlideShowWhenDirListerCompleted = false;
257
        mSlideShow = new SlideShow(q);
Laurent Montel's avatar
Laurent Montel committed
258
        connect(mContextManager, &ContextManager::currentUrlChanged, mSlideShow, &SlideShow::setCurrentUrl);
259
260

        setupThumbnailView(mViewStackedWidget);
261
        setupViewMainPage(mViewStackedWidget);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
262
        setupStartMainPage(mViewStackedWidget);
263
        mViewStackedWidget->addWidget(mBrowseMainPage);
264
        mViewStackedWidget->addWidget(mViewMainPage);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
265
        mViewStackedWidget->addWidget(mStartMainPage);
266
        mViewStackedWidget->setCurrentWidget(mBrowseMainPage);
267
268
269

        mCentralSplitter->setStretchFactor(0, 0);
        mCentralSplitter->setStretchFactor(1, 1);
270
        mCentralSplitter->setChildrenCollapsible(false);
271

272
273
        mThumbnailView->setFocus();

Laurent Montel's avatar
Laurent Montel committed
274
275
276
277
        connect(mSaveBar, &SaveBar::requestSaveAll,
                mGvCore, &GvCore::saveAll);
        connect(mSaveBar, &SaveBar::goToUrl,
                q, &MainWindow::goToUrl);
278

Laurent Montel's avatar
Laurent Montel committed
279
280
        connect(mSlideShow, &SlideShow::goToUrl,
                q, &MainWindow::goToUrl);
281
282
283
284
    }

    void setupThumbnailView(QWidget* parent)
    {
285
        Q_ASSERT(mContextManager);
286
        mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore);
287

288
        mThumbnailView = mBrowseMainPage->thumbnailView();
289
        mThumbnailView->viewport()->installEventFilter(q);
290
        mThumbnailView->setSelectionModel(mContextManager->selectionModel());
291
        mUrlNavigator = mBrowseMainPage->urlNavigator();
292
293
294
295

        mDocumentInfoProvider = new DocumentInfoProvider(mDirModel);
        mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider);

296
        mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection());
Laurent Montel's avatar
Laurent Montel committed
297
298
        connect(mContextManager, &ContextManager::currentDirUrlChanged,
            mThumbnailViewHelper, &ThumbnailViewHelper::setCurrentDirUrl);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
299
        mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper);
300

301
        mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q);
302

303
        // Connect thumbnail view
Laurent Montel's avatar
Laurent Montel committed
304
305
        connect(mThumbnailView, &ThumbnailView::indexActivated,
                q, &MainWindow::slotThumbnailViewIndexActivated);
306
307
308

        // Connect delegate
        QAbstractItemDelegate* delegate = mThumbnailView->itemDelegate();
David Edmundson's avatar
David Edmundson committed
309
310
311
312
313
314
315
316
317
318
        connect(delegate, SIGNAL(saveDocumentRequested(QUrl)),
                mGvCore, SLOT(save(QUrl)));
        connect(delegate, SIGNAL(rotateDocumentLeftRequested(QUrl)),
                mGvCore, SLOT(rotateLeft(QUrl)));
        connect(delegate, SIGNAL(rotateDocumentRightRequested(QUrl)),
                mGvCore, SLOT(rotateRight(QUrl)));
        connect(delegate, SIGNAL(showDocumentInFullScreenRequested(QUrl)),
                q, SLOT(showDocumentInFullScreen(QUrl)));
        connect(delegate, SIGNAL(setDocumentRatingRequested(QUrl,int)),
                mGvCore, SLOT(setRating(QUrl,int)));
319
320

        // Connect url navigator
Laurent Montel's avatar
Laurent Montel committed
321
322
        connect(mUrlNavigator, &KUrlNavigator::urlChanged,
                q, &MainWindow::openDirUrl);
323
324
    }

325
    void setupViewMainPage(QWidget* parent)
326
    {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
327
        mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore);
Laurent Montel's avatar
Laurent Montel committed
328
329
330
331
332
333
334
335
        connect(mViewMainPage, &ViewMainPage::captionUpdateRequested,
                q, &MainWindow::slotUpdateCaption);
        connect(mViewMainPage, &ViewMainPage::completed,
                q, &MainWindow::slotPartCompleted);
        connect(mViewMainPage, &ViewMainPage::previousImageRequested,
                q, &MainWindow::goToPrevious);
        connect(mViewMainPage, &ViewMainPage::nextImageRequested,
                q, &MainWindow::goToNext);
336
337
338
339
        connect(mViewMainPage, &ViewMainPage::openUrlRequested,
                q, &MainWindow::openUrl);
        connect(mViewMainPage, &ViewMainPage::openDirUrlRequested,
                q, &MainWindow::openDirUrl);
340

341
342
343
344
345
346
347
348
349
350
351
        setupThumbnailBar(mViewMainPage->thumbnailBar());
    }

    void setupThumbnailBar(ThumbnailView* bar)
    {
        Q_ASSERT(mThumbnailBarModel);
        Q_ASSERT(mThumbnailBarSelectionModel);
        Q_ASSERT(mDocumentInfoProvider);
        Q_ASSERT(mThumbnailViewHelper);
        bar->setModel(mThumbnailBarModel);
        bar->setSelectionModel(mThumbnailBarSelectionModel);
352
353
354
355
        bar->setDocumentInfoProvider(mDocumentInfoProvider);
        bar->setThumbnailViewHelper(mThumbnailViewHelper);
    }

Aurélien Gâteau's avatar
Aurélien Gâteau committed
356
    void setupStartMainPage(QWidget* parent)
357
    {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
358
        mStartMainPage = new StartMainPage(parent, mGvCore);
Laurent Montel's avatar
Laurent Montel committed
359
360
        connect(mStartMainPage, &StartMainPage::urlSelected,
                q, &MainWindow::slotStartMainPageUrlSelected);
361
362
363
364
365
366
        connect(mStartMainPage, &StartMainPage::recentFileRemoved, [this](const QUrl& url) {
            mFileOpenRecentAction->removeUrl(url);
        });
        connect(mStartMainPage, &StartMainPage::recentFilesCleared, [this]() {
            mFileOpenRecentAction->clear();
        });
367
368
    }

369
370
371
372
373
374
    void installDisabledActionShortcutMonitor(QAction* action, const char* slot)
    {
        DisabledActionShortcutMonitor* monitor = new DisabledActionShortcutMonitor(action, q);
        connect(monitor, SIGNAL(activated()), q, slot);
    }

375
376
    void setupActions()
    {
377
        KActionCollection* actionCollection = q->actionCollection();
378
379
380
        KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
        KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);

381
382
383
        file->addAction(KStandardAction::Save, q, SLOT(saveCurrent()));
        file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs()));
        file->addAction(KStandardAction::Open, q, SLOT(openFile()));
Laurent Montel's avatar
Laurent Montel committed
384
        mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(QUrl)), q);
385
386
        connect(mFileOpenRecentAction, &KRecentFilesAction::recentListCleared,
                mGvCore, &GvCore::clearRecentFilesAndFolders);
387
        QAction * clearAction = mFileOpenRecentAction->menu()->findChild<QAction*>("clear_action");
388
389
390
        if (clearAction) {
            clearAction->setText(i18nc("@action Open Recent menu", "Forget All Files && Folders"));
        }
Kevin Funk's avatar
Kevin Funk committed
391
        file->addAction("file_open_recent", mFileOpenRecentAction);
392
        file->addAction(KStandardAction::Print, q, SLOT(print()));
393
        file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows()));
394

David Edmundson's avatar
David Edmundson committed
395
        QAction * action = file->addAction("reload", q, SLOT(reload()));
396
        action->setText(i18nc("@action reload the currently viewed image", "Reload"));
David Edmundson's avatar
David Edmundson committed
397
        action->setIcon(QIcon::fromTheme("view-refresh"));
398
        actionCollection->setDefaultShortcuts(action, KStandardShortcut::reload());
399
400
401
402
403
        
        QAction * replaceLocationAction = actionCollection->addAction(QStringLiteral("replace_location"));
        replaceLocationAction->setText(i18nc("@action:inmenu Navigation Bar", "Replace Location"));
        actionCollection->setDefaultShortcut(replaceLocationAction, Qt::CTRL + Qt::Key_L);
        connect(replaceLocationAction, &QAction::triggered, q, &MainWindow::replaceLocation);
404
405

        mBrowseAction = view->addAction("browse");
406
407
        mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse"));
        mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images"));
408
        mBrowseAction->setCheckable(true);
David Edmundson's avatar
David Edmundson committed
409
        mBrowseAction->setIcon(QIcon::fromTheme("view-list-icons"));
410
        actionCollection->setDefaultShortcut(mBrowseAction, Qt::Key_Escape);
Laurent Montel's avatar
Laurent Montel committed
411
412
        connect(mViewMainPage, &ViewMainPage::goToBrowseModeRequested,
            mBrowseAction, &QAction::trigger);
413
414

        mViewAction = view->addAction("view");
415
        mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View"));
416
        mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images"));
David Edmundson's avatar
David Edmundson committed
417
        mViewAction->setIcon(QIcon::fromTheme("view-preview"));
418
419
        mViewAction->setCheckable(true);

420
        mViewModeActionGroup = new QActionGroup(q);
421
422
423
        mViewModeActionGroup->addAction(mBrowseAction);
        mViewModeActionGroup->addAction(mViewAction);

Laurent Montel's avatar
Laurent Montel committed
424
425
        connect(mViewModeActionGroup, &QActionGroup::triggered,
                q, &MainWindow::setActiveViewModeAction);
426

427
        mFullScreenAction = KStandardAction::fullScreen(q, &MainWindow::toggleFullScreen, q, actionCollection);
428
429
        QList<QKeySequence> shortcuts = mFullScreenAction->shortcuts();
        shortcuts.append(QKeySequence(Qt::Key_F11));
430
        actionCollection->setDefaultShortcuts(mFullScreenAction, shortcuts);
431

Laurent Montel's avatar
Laurent Montel committed
432
433
        connect(mViewMainPage, &ViewMainPage::toggleFullScreenRequested,
                mFullScreenAction, &QAction::trigger);
434

David Edmundson's avatar
David Edmundson committed
435
        QAction * leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen()));
David Edmundson's avatar
David Edmundson committed
436
        leaveFullScreenAction->setIcon(QIcon::fromTheme("view-restore"));
437
        leaveFullScreenAction->setPriority(QAction::LowPriority);
438
        leaveFullScreenAction->setText(i18nc("@action", "Leave Fullscreen Mode"));
439

440
        mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious()));
441
        mGoToPreviousAction->setPriority(QAction::LowPriority);
442
        mGoToPreviousAction->setIcon(QIcon::fromTheme("go-previous-view"));
443
        mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous"));
444
        mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image"));
445
        actionCollection->setDefaultShortcut(mGoToPreviousAction, Qt::Key_Backspace);
446
        installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached()));
447

448
        mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext()));
449
        mGoToNextAction->setPriority(QAction::LowPriority);
450
        mGoToNextAction->setIcon(QIcon::fromTheme("go-next-view"));
451
        mGoToNextAction->setText(i18nc("@action Go to next image", "Next"));
452
        mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image"));
453
        actionCollection->setDefaultShortcut(mGoToNextAction, Qt::Key_Space);
454
        installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached()));
455

456
        mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst()));
457
        mGoToFirstAction->setPriority(QAction::LowPriority);
458
        mGoToFirstAction->setIcon(QIcon::fromTheme("go-first-view"));
459
        mGoToFirstAction->setText(i18nc("@action Go to first image", "First"));
460
        mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image"));
461
        actionCollection->setDefaultShortcut(mGoToFirstAction, Qt::Key_Home);
462

463
        mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast()));
464
        mGoToLastAction->setPriority(QAction::LowPriority);
465
        mGoToLastAction->setIcon(QIcon::fromTheme("go-last-view"));
466
        mGoToLastAction->setText(i18nc("@action Go to last image", "Last"));
467
        mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image"));
468
        actionCollection->setDefaultShortcut(mGoToLastAction, Qt::Key_End);
469

470
471
        mPreloadDirectionIsForward = true;

472
        mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp()));
473

Aurélien Gâteau's avatar
Aurélien Gâteau committed
474
        action = view->addAction("go_start_page", q, SLOT(showStartMainPage()));
475
        action->setPriority(QAction::LowPriority);
David Edmundson's avatar
David Edmundson committed
476
        action->setIcon(QIcon::fromTheme("go-home"));
477
        action->setText(i18nc("@action", "Start Page"));
478
        action->setToolTip(i18nc("@info:tooltip", "Open the start page"));
479
        actionCollection->setDefaultShortcuts(action, KStandardShortcut::home());
480
481

        mToggleSideBarAction = view->add<KToggleAction>("toggle_sidebar");
482
        connect(mToggleSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleSideBar);
David Edmundson's avatar
David Edmundson committed
483
        mToggleSideBarAction->setIcon(QIcon::fromTheme("view-sidetree"));
484
        actionCollection->setDefaultShortcut(mToggleSideBarAction, Qt::Key_F4);
485
        mToggleSideBarAction->setText(i18nc("@action", "Sidebar"));
Laurent Montel's avatar
Laurent Montel committed
486
487
488
489
        connect(mBrowseMainPage->toggleSideBarButton(), &QAbstractButton::clicked,
                mToggleSideBarAction, &QAction::trigger);
        connect(mViewMainPage->toggleSideBarButton(), &QAbstractButton::clicked,
                mToggleSideBarAction, &QAction::trigger);
490

491
492
        mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow()));
        q->updateSlideShowAction();
Laurent Montel's avatar
Laurent Montel committed
493
494
        connect(mSlideShow, &SlideShow::stateChanged,
                q, &MainWindow::updateSlideShowAction);
495

Valerio Pilo's avatar
Valerio Pilo committed
496
497
        q->setStandardToolBarMenuEnabled(true);

498
        mShowMenuBarAction = static_cast<KToggleAction*>(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar())));
499
        mShowStatusBarAction = static_cast<KToggleAction*>(view->addAction(KStandardAction::ShowStatusbar, q, SLOT(toggleStatusBar(bool))));
500

501
502
        actionCollection->setDefaultShortcut(mShowStatusBarAction, Qt::Key_F3);

503
504
        view->addAction(KStandardAction::name(KStandardAction::KeyBindings),
                        KStandardAction::keyBindings(q, &MainWindow::configureShortcuts, actionCollection));
505

506
        view->addAction(KStandardAction::Preferences, q,
507
508
                        SLOT(showConfigDialog()));

509
        view->addAction(KStandardAction::ConfigureToolbars, q,
510
                        SLOT(configureToolbars()));
Aurélien Gâteau's avatar
Aurélien Gâteau committed
511
512

#ifdef KIPI_FOUND
513
        mKIPIExportAction = new KIPIExportAction(q);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
514
#endif
515
516

        mShareAction = new KToolBarPopupAction(QIcon::fromTheme("document-share"), "Share", q);
517
        mShareAction->setDelayed(false);
518
519
520
        actionCollection->addAction("share", mShareAction);
        mShareMenu = new Purpose::Menu(q);
        mShareAction->setMenu(mShareMenu);
521
522
523
524
525
526
527
528
    }

    void setupUndoActions()
    {
        // There is no KUndoGroup similar to KUndoStack. This code basically
        // does the same as KUndoStack, but for the KUndoGroup actions.
        QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup();
        QAction* action;
529
        KActionCollection* actionCollection =  q->actionCollection();
530
531
532
533
        KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection);

        action = undoGroup->createRedoAction(actionCollection);
        action->setObjectName(KStandardAction::name(KStandardAction::Redo));
David Edmundson's avatar
David Edmundson committed
534
        action->setIcon(QIcon::fromTheme("edit-redo"));
535
        action->setIconText(i18n("Redo"));
536
537
        actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo());

538
539
540
541
        edit->addAction(action->objectName(), action);

        action = undoGroup->createUndoAction(actionCollection);
        action->setObjectName(KStandardAction::name(KStandardAction::Undo));
David Edmundson's avatar
David Edmundson committed
542
        action->setIcon(QIcon::fromTheme("edit-undo"));
543
        action->setIconText(i18n("Undo"));
544
        actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo());
545
546
547
        edit->addAction(action->objectName(), action);
    }

548
    void setupContextManagerItems()
549
    {
550
        Q_ASSERT(mContextManager);
551
        KActionCollection* actionCollection = q->actionCollection();
552
553
554

        // Create context manager items
        FolderViewContextManagerItem* folderViewItem = new FolderViewContextManagerItem(mContextManager);
555
556
        connect(folderViewItem, &FolderViewContextManagerItem::urlChanged,
                q, &MainWindow::folderViewUrlChanged);
557
558

        InfoContextManagerItem* infoItem = new InfoContextManagerItem(mContextManager);
559

560
#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
561
        SemanticInfoContextManagerItem* semanticInfoItem = nullptr;
Vishesh Handa's avatar
Vishesh Handa committed
562
        semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage);
563
564
565
#endif

        ImageOpsContextManagerItem* imageOpsItem =
566
            new ImageOpsContextManagerItem(mContextManager, q);
567

568
        FileOpsContextManagerItem* fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q);
569
570
571
572
573
574

        // Fill sidebar
        SideBarPage* page;
        page = new SideBarPage(i18n("Folders"));
        page->setObjectName(QLatin1String("folders"));
        page->addWidget(folderViewItem->widget());
Laurent Montel's avatar
Laurent Montel committed
575
        page->layout()->setContentsMargins(0, 0, 0, 0);
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
        mSideBar->addPage(page);

        page = new SideBarPage(i18n("Information"));
        page->setObjectName(QLatin1String("information"));
        page->addWidget(infoItem->widget());
#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
        if (semanticInfoItem) {
            page->addWidget(semanticInfoItem->widget());
        }
#endif
        mSideBar->addPage(page);

        page = new SideBarPage(i18n("Operations"));
        page->setObjectName(QLatin1String("operations"));
        page->addWidget(imageOpsItem->widget());
        page->addWidget(fileOpsItem->widget());
        page->addStretch();
        mSideBar->addPage(page);
    }

    void initDirModel()
    {
        mDirModel->setKindFilter(
599
600
601
            MimeTypeUtils::KIND_DIR
            | MimeTypeUtils::KIND_ARCHIVE
            | MimeTypeUtils::KIND_RASTER_IMAGE
602
603
604
            | MimeTypeUtils::KIND_SVG_IMAGE
            | MimeTypeUtils::KIND_VIDEO);

Laurent Montel's avatar
Laurent Montel committed
605
606
        connect(mDirModel, &QAbstractItemModel::rowsInserted,
                q, &MainWindow::slotDirModelNewItems);
607

Laurent Montel's avatar
Laurent Montel committed
608
609
610
611
        connect(mDirModel, &QAbstractItemModel::rowsRemoved,
                q, &MainWindow::updatePreviousNextActions);
        connect(mDirModel, &QAbstractItemModel::modelReset,
                q, &MainWindow::updatePreviousNextActions);
612
613

        connect(mDirModel->dirLister(), SIGNAL(completed()),
614
                q, SLOT(slotDirListerCompleted()));
615
616
    }

617
    void setupThumbnailBarModel()
618
    {
619
620
        mThumbnailBarModel = new DocumentOnlyProxyModel(q);
        mThumbnailBarModel->setSourceModel(mDirModel);
621
622
    }

Aurélien Gâteau's avatar
Aurélien Gâteau committed
623
624
    bool indexIsDirOrArchive(const QModelIndex& index) const
    {
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
        Q_ASSERT(index.isValid());
        KFileItem item = mDirModel->itemForIndex(index);
        return ArchiveUtils::fileItemIsDirOrArchive(item);
    }

    void goTo(const QModelIndex& index)
    {
        if (!index.isValid()) {
            return;
        }
        mThumbnailView->setCurrentIndex(index);
        mThumbnailView->scrollTo(index);
    }

    void goTo(int offset)
    {
641
        mPreloadDirectionIsForward = offset > 0;
642
        QModelIndex index = mContextManager->selectionModel()->currentIndex();
643
        index = mDirModel->index(index.row() + offset, 0);
644
        if (index.isValid() && !indexIsDirOrArchive(index)) {
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
            goTo(index);
        }
    }

    void goToFirstDocument()
    {
        QModelIndex index;
        for (int row = 0;; ++row) {
            index = mDirModel->index(row, 0);
            if (!index.isValid()) {
                return;
            }

            if (!indexIsDirOrArchive(index)) {
                break;
            }
        }
        goTo(index);
    }

    void goToLastDocument()
    {
        QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0);
        goTo(index);
    }

671
    void setupFullScreenContent()
672
    {
673
        mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow);
674
        setupThumbnailBar(mFullScreenContent->thumbnailBar());
675
676
677
678
    }

    inline void setActionEnabled(const char* name, bool enabled)
    {
679
        QAction* action = q->actionCollection()->action(name);
680
681
682
        if (action) {
            action->setEnabled(enabled);
        } else {
David Edmundson's avatar
David Edmundson committed
683
            qWarning() << "Action" << name << "not found";
684
685
686
        }
    }

Aurélien Gâteau's avatar
Aurélien Gâteau committed
687
    void setActionsDisabledOnStartMainPageEnabled(bool enabled)
688
689
690
691
    {
        mBrowseAction->setEnabled(enabled);
        mViewAction->setEnabled(enabled);
        mToggleSideBarAction->setEnabled(enabled);
Valerio Pilo's avatar
Valerio Pilo committed
692
        mShowStatusBarAction->setEnabled(enabled);
693
694
695
696
697
698
699
700
701
702
703
704
705
        mFullScreenAction->setEnabled(enabled);
        mToggleSlideShowAction->setEnabled(enabled);

        setActionEnabled("reload", enabled);
        setActionEnabled("go_start_page", enabled);
        setActionEnabled("add_folder_to_places", enabled);
    }

    void updateActions()
    {
        bool isRasterImage = false;
        bool canSave = false;
        bool isModified = false;
David Edmundson's avatar
David Edmundson committed
706
        const QUrl url = mContextManager->currentUrl();
707
708

        if (url.isValid()) {
709
            isRasterImage = mContextManager->currentUrlIsRasterImage();
710
711
            canSave = isRasterImage;
            isModified = DocumentFactory::instance()->load(url)->isModified();
712
713
            if (mCurrentMainPageId != ViewMainPageId
                    && mContextManager->selectedFileItemList().count() != 1) {
714
                // Saving only makes sense if exactly one image is selected
715
                canSave = false;
716
717
718
            }
        }

719
        KActionCollection* actionCollection = q->actionCollection();
720
721
722
        actionCollection->action("file_save")->setEnabled(canSave && isModified);
        actionCollection->action("file_save_as")->setEnabled(canSave);
        actionCollection->action("file_print")->setEnabled(isRasterImage);
723
724
725
726
727
728
729
730
731
732
733
734

        if (url.isEmpty()) {
            mShareAction->setEnabled(false);
        } else {
            mShareAction->setEnabled(true);
            mShareMenu->model()->setInputData(QJsonObject{
                { QStringLiteral("mimeType"), MimeTypeUtils::urlMimeType(url) },
                { QStringLiteral("urls"), QJsonArray{url.toString()} }
            });
            mShareMenu->model()->setPluginType( QStringLiteral("Export") );
            mShareMenu->reload();
        }
735
736
    }

737
    bool sideBarVisibility() const
Benjamin Löwe's avatar
Benjamin Löwe committed
738
    {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
739
        switch (mCurrentMainPageId) {
Aurélien Gâteau's avatar
Aurélien Gâteau committed
740
        case StartMainPageId:
741
            GV_WARN_AND_RETURN_VALUE(false, "Sidebar not implemented on start page");
742
            break;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
743
        case BrowseMainPageId:
744
            return GwenviewConfig::sideBarVisibleBrowseMode();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
745
        case ViewMainPageId:
746
747
748
            return q->isFullScreen()
                ? GwenviewConfig::sideBarVisibleViewModeFullScreen()
                : GwenviewConfig::sideBarVisibleViewMode();
749
        }
750
        return false;
751
752
    }

753
    void saveSideBarVisibility(const bool visible)
754
    {
755
756
757
758
759
760
761
762
763
764
765
766
767
        switch (mCurrentMainPageId) {
        case StartMainPageId:
            GV_WARN_AND_RETURN("Sidebar not implemented on start page");
            break;
        case BrowseMainPageId:
            GwenviewConfig::setSideBarVisibleBrowseMode(visible);
            break;
        case ViewMainPageId:
            q->isFullScreen()
                ? GwenviewConfig::setSideBarVisibleViewModeFullScreen(visible)
                : GwenviewConfig::setSideBarVisibleViewMode(visible);
            break;
        }
768
769
    }

770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
    bool statusBarVisibility() const
    {
        switch (mCurrentMainPageId) {
        case StartMainPageId:
            GV_WARN_AND_RETURN_VALUE(false, "Statusbar not implemented on start page");
            break;
        case BrowseMainPageId:
            return GwenviewConfig::statusBarVisibleBrowseMode();
        case ViewMainPageId:
            return q->isFullScreen()
                ? GwenviewConfig::statusBarVisibleViewModeFullScreen()
                : GwenviewConfig::statusBarVisibleViewMode();
        }
        return false;
    }

786
    void saveStatusBarVisibility(const bool visible)
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
    {
        switch (mCurrentMainPageId) {
        case StartMainPageId:
            GV_WARN_AND_RETURN("Statusbar not implemented on start page");
            break;
        case BrowseMainPageId:
            GwenviewConfig::setStatusBarVisibleBrowseMode(visible);
            break;
        case ViewMainPageId:
            q->isFullScreen()
                ? GwenviewConfig::setStatusBarVisibleViewModeFullScreen(visible)
                : GwenviewConfig::setStatusBarVisibleViewMode(visible);
            break;
        }
    }

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
    void loadSplitterConfig()
    {
        const QList<int> sizes = GwenviewConfig::sideBarSplitterSizes();
        if (!sizes.isEmpty()) {
            mCentralSplitter->setSizes(sizes);
        }
    }

    void saveSplitterConfig()
    {
        if (mSideBar->isVisible()) {
            GwenviewConfig::setSideBarSplitterSizes(mCentralSplitter->sizes());
        }
    }

818
819
820
821
822
    void setScreenSaverEnabled(bool enabled)
    {
        // Always delete mNotificationRestrictions, it does not hurt
        delete mNotificationRestrictions;
        if (!enabled) {
823
            mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q);
824
        } else {
Laurent Montel's avatar
Laurent Montel committed
825
            mNotificationRestrictions = nullptr;
826
827
        }
    }
Benjamin Löwe's avatar
Benjamin Löwe committed
828

829
    void assignThumbnailProviderToThumbnailView(ThumbnailView* thumbnailView)
Benjamin Löwe's avatar
Benjamin Löwe committed
830
831
832
    {
        GV_RETURN_IF_FAIL(thumbnailView);
        if (mActiveThumbnailView) {
Laurent Montel's avatar
Laurent Montel committed
833
            mActiveThumbnailView->setThumbnailProvider(nullptr);
Benjamin Löwe's avatar
Benjamin Löwe committed
834
        }
835
        thumbnailView->setThumbnailProvider(mThumbnailProvider);
Benjamin Löwe's avatar
Benjamin Löwe committed
836
        mActiveThumbnailView = thumbnailView;
837
838
        if (mActiveThumbnailView->isVisible()) {
            mThumbnailProvider->stop();
839
            mActiveThumbnailView->generateThumbnailsForItems();
840
        }
Benjamin Löwe's avatar
Benjamin Löwe committed
841
842
    }

843
    void autoAssignThumbnailProvider()
Benjamin Löwe's avatar
Benjamin Löwe committed
844
845
846
    {
        if (mCurrentMainPageId == ViewMainPageId) {
            if (q->windowState() & Qt::WindowFullScreen) {
847
                assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar());
Benjamin Löwe's avatar
Benjamin Löwe committed
848
            } else {
849
                assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar());
Benjamin Löwe's avatar
Benjamin Löwe committed
850
851
            }
        } else if (mCurrentMainPageId == BrowseMainPageId) {
852
            assignThumbnailProviderToThumbnailView(mThumbnailView);
Benjamin Löwe's avatar
Benjamin Löwe committed
853
        } else if (mCurrentMainPageId == StartMainPageId) {
854
            assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView());
Benjamin Löwe's avatar
Benjamin Löwe committed
855
856
        }
    }
857
};
858
859

MainWindow::MainWindow()
860
: KXmlGuiWindow(),
861
      d(new MainWindow::Private)
862
{
863
    d->q = this;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
864
    d->mCurrentMainPageId = StartMainPageId;
865
    d->mDirModel = new SortedDirModel(this);
866
    d->setupContextManager();
867
    d->setupThumbnailBarModel();
868
869
    d->mGvCore = new GvCore(this, d->mDirModel);
    d->mPreloader = new Preloader(this);
870