Warning, file /graphics/gwenview/app/viewmainpage.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002 Gwenview: an image viewer
0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0004 
0005 This program is free software; you can redistribute it and/or
0006 modify it under the terms of the GNU General Public License
0007 as published by the Free Software Foundation; either version 2
0008 of the License, or (at your option) any later version.
0009 
0010 This program is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 GNU General Public License for more details.
0014 
0015 You should have received a copy of the GNU General Public License
0016 along with this program; if not, write to the Free Software
0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018 
0019 */
0020 #include "viewmainpage.h"
0021 #include "config-gwenview.h"
0022 
0023 // Qt
0024 #include <QApplication>
0025 #include <QCheckBox>
0026 #include <QItemSelectionModel>
0027 #include <QMenu>
0028 #include <QShortcut>
0029 #include <QVBoxLayout>
0030 
0031 // KF
0032 #include <KActionCategory>
0033 #include <KActionCollection>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 #include <KModelIndexProxyMapper>
0037 #include <KSqueezedTextLabel>
0038 #include <KToggleAction>
0039 #if HAVE_KACTIVITIES
0040 #include <PlasmaActivities/ResourceInstance>
0041 #endif
0042 
0043 // Local
0044 #include "fileoperations.h"
0045 #include "gwenview_app_debug.h"
0046 #include "splitter.h"
0047 #include <gvcore.h>
0048 #include <lib/documentview/abstractdocumentviewadapter.h>
0049 #include <lib/documentview/abstractrasterimageviewtool.h>
0050 #include <lib/documentview/documentview.h>
0051 #include <lib/documentview/documentviewcontainer.h>
0052 #include <lib/documentview/documentviewcontroller.h>
0053 #include <lib/documentview/documentviewsynchronizer.h>
0054 #include <lib/fullscreenbar.h>
0055 #include <lib/gvdebug.h>
0056 #include <lib/gwenviewconfig.h>
0057 #include <lib/mimetypeutils.h>
0058 #include <lib/paintutils.h>
0059 #include <lib/semanticinfo/sorteddirmodel.h>
0060 #include <lib/slidecontainer.h>
0061 #include <lib/slideshow.h>
0062 #include <lib/statusbartoolbutton.h>
0063 #include <lib/stylesheetutils.h>
0064 #include <lib/thumbnailview/thumbnailbarview.h>
0065 #include <lib/zoommode.h>
0066 #include <lib/zoomwidget.h>
0067 
0068 namespace Gwenview
0069 {
0070 #undef ENABLE_LOG
0071 #undef LOG
0072 // #define ENABLE_LOG
0073 #ifdef ENABLE_LOG
0074 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
0075 #else
0076 #define LOG(x) ;
0077 #endif
0078 
0079 const int ViewMainPage::MaxViewCount = 6;
0080 
0081 /*
0082  * Layout of mThumbnailSplitter is:
0083  *
0084  * +-mThumbnailSplitter------------------------------------------------+
0085  * |+-mAdapterContainer-----------------------------------------------+|
0086  * ||+-mDocumentViewContainer----------------------------------------+||
0087  * |||+-DocumentView----------------++-DocumentView-----------------+|||
0088  * ||||                             ||                              ||||
0089  * ||||                             ||                              ||||
0090  * ||||                             ||                              ||||
0091  * ||||                             ||                              ||||
0092  * ||||                             ||                              ||||
0093  * ||||                             ||                              ||||
0094  * |||+-----------------------------++------------------------------+|||
0095  * ||+---------------------------------------------------------------+||
0096  * ||+-mToolContainer------------------------------------------------+||
0097  * |||                                                               |||
0098  * ||+---------------------------------------------------------------+||
0099  * |+-----------------------------------------------------------------+|
0100  * |===================================================================|
0101  * |+-mThumbnailBar---------------------------------------------------+|
0102  * ||                                                                 ||
0103  * ||                                                                 ||
0104  * |+-mStatusBarContainer---------------------------------------------+|
0105  * ||[mToggleSideBarButton][mToggleThumbnailBarButton]   [mZoomWidget]||
0106  * |+-----------------------------------------------------------------+|
0107  * +-------------------------------------------------------------------+
0108  */
0109 struct ViewMainPagePrivate {
0110     ViewMainPage *q = nullptr;
0111     SlideShow *mSlideShow = nullptr;
0112     KActionCollection *mActionCollection = nullptr;
0113     GvCore *mGvCore = nullptr;
0114     KModelIndexProxyMapper *mDirModelToBarModelProxyMapper = nullptr;
0115     QSplitter *mThumbnailSplitter = nullptr;
0116     QWidget *mAdapterContainer = nullptr;
0117     DocumentViewController *mDocumentViewController = nullptr;
0118     QList<DocumentView *> mDocumentViews;
0119     DocumentViewSynchronizer *mSynchronizer = nullptr;
0120     QToolButton *mToggleSideBarButton = nullptr;
0121     QToolButton *mToggleThumbnailBarButton = nullptr;
0122     ZoomWidget *mZoomWidget = nullptr;
0123     DocumentViewContainer *mDocumentViewContainer = nullptr;
0124     SlideContainer *mToolContainer = nullptr;
0125     QWidget *mStatusBarContainer = nullptr;
0126     ThumbnailBarView *mThumbnailBar = nullptr;
0127     KToggleAction *mToggleThumbnailBarAction = nullptr;
0128     KToggleAction *mSynchronizeAction = nullptr;
0129     QCheckBox *mSynchronizeCheckBox = nullptr;
0130     KSqueezedTextLabel *mDocumentCountLabel = nullptr;
0131 
0132     bool mCompareMode;
0133     ZoomMode::Enum mZoomMode;
0134 
0135     void setupWidgets()
0136     {
0137         mToolContainer = new SlideContainer;
0138         mToolContainer->setAutoFillBackground(true);
0139         mToolContainer->setBackgroundRole(QPalette::Mid);
0140         //--
0141         mStatusBarContainer = new QWidget;
0142         mStatusBarContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0143         mToggleSideBarButton = new StatusBarToolButton;
0144         mToggleThumbnailBarButton = new StatusBarToolButton;
0145         mZoomWidget = new ZoomWidget;
0146         mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize"));
0147         mSynchronizeCheckBox->hide();
0148         mDocumentCountLabel = new KSqueezedTextLabel;
0149         mDocumentCountLabel->setAlignment(Qt::AlignCenter);
0150         mDocumentCountLabel->setTextElideMode(Qt::ElideRight);
0151         QMargins labelMargins = mDocumentCountLabel->contentsMargins();
0152         labelMargins.setLeft(15);
0153         labelMargins.setRight(15);
0154         mDocumentCountLabel->setContentsMargins(labelMargins);
0155 
0156         auto statusBarContainerLayout = new QHBoxLayout(mStatusBarContainer);
0157         // Use toolbar-like margins and spacing
0158         int margins = q->style()->pixelMetric(QStyle::PM_ToolBarItemMargin) + q->style()->pixelMetric(QStyle::PM_ToolBarFrameWidth);
0159         statusBarContainerLayout->setContentsMargins(margins, margins, margins, margins);
0160         statusBarContainerLayout->setSpacing(q->style()->pixelMetric(QStyle::PM_ToolBarItemSpacing));
0161         statusBarContainerLayout->addWidget(mToggleSideBarButton);
0162         statusBarContainerLayout->addWidget(mToggleThumbnailBarButton);
0163         statusBarContainerLayout->addStretch();
0164         statusBarContainerLayout->addWidget(mSynchronizeCheckBox);
0165         // Ensure document count label takes up all available space,
0166         // so its autohide feature works properly (stretch factor = 1)
0167         statusBarContainerLayout->addWidget(mDocumentCountLabel, 1);
0168         statusBarContainerLayout->addStretch();
0169         statusBarContainerLayout->addWidget(mZoomWidget);
0170         //--
0171         mAdapterContainer = new QWidget;
0172 
0173         auto adapterContainerLayout = new QVBoxLayout(mAdapterContainer);
0174         adapterContainerLayout->setContentsMargins(0, 0, 0, 0);
0175         adapterContainerLayout->setSpacing(0);
0176         mDocumentViewContainer = new DocumentViewContainer;
0177         mDocumentViewContainer->setAutoFillBackground(true);
0178         mDocumentViewContainer->setBackgroundRole(QPalette::Base);
0179         adapterContainerLayout->addWidget(mDocumentViewContainer);
0180         adapterContainerLayout->addWidget(mToolContainer);
0181         //--
0182         mThumbnailBar = new ThumbnailBarView;
0183         auto delegate = new ThumbnailBarItemDelegate(mThumbnailBar);
0184         mThumbnailBar->setItemDelegate(delegate);
0185         mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection);
0186         //--
0187         Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation();
0188         mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q);
0189         mThumbnailBar->setOrientation(orientation);
0190         mThumbnailBar->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio());
0191         mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount());
0192         mThumbnailSplitter->addWidget(mAdapterContainer);
0193         mThumbnailSplitter->addWidget(mThumbnailBar);
0194         mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes());
0195         // Show the thumbnail bar after setting the parent to avoid recreating
0196         // the native window and to avoid QTBUG-87345.
0197         mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible());
0198         mThumbnailBar->installEventFilter(q);
0199 
0200         auto viewMainPageLayout = new QVBoxLayout(q);
0201         viewMainPageLayout->setContentsMargins(0, 0, 0, 0);
0202         viewMainPageLayout->setSpacing(0);
0203         viewMainPageLayout->addWidget(mThumbnailSplitter);
0204         viewMainPageLayout->addWidget(mStatusBarContainer);
0205         //--
0206         mDocumentViewController = new DocumentViewController(mActionCollection, q);
0207         mDocumentViewController->setZoomWidget(mZoomWidget);
0208         mDocumentViewController->setToolContainer(mToolContainer);
0209         mSynchronizer = new DocumentViewSynchronizer(&mDocumentViews, q);
0210     }
0211 
0212     void setupThumbnailBarStyleSheet()
0213     {
0214         QPalette pal = mGvCore->palette(GvCore::NormalViewPalette);
0215         mThumbnailBar->setPalette(pal);
0216         Qt::Orientation orientation = mThumbnailBar->orientation();
0217         QColor bgColor = pal.color(QPalette::Normal, QPalette::Base);
0218         QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight);
0219         QColor bgHovColor = pal.color(QPalette::Normal, QPalette::Highlight);
0220 
0221         // Avoid dark and bright colors
0222         bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4);
0223 
0224         // Hover uses lighter/faded version of select color. Combine with bgColor to adapt to different backgrounds
0225         bgHovColor.setHsv(bgHovColor.hue(), (bgHovColor.saturation() / 2), ((bgHovColor.value() + bgColor.value()) / 2));
0226 
0227         QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value()));
0228         QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value()));
0229         QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value()));
0230 
0231         QString itemCss =
0232             "QListView::item {"
0233             "   background-color: %1;"
0234             "   border-left: 1px solid %2;"
0235             "   border-right: 1px solid %3;"
0236             "}";
0237         itemCss = itemCss.arg(StyleSheetUtils::gradient(orientation, bgColor, 46),
0238                               StyleSheetUtils::gradient(orientation, leftBorderColor, 36),
0239                               StyleSheetUtils::gradient(orientation, rightBorderColor, 26));
0240 
0241         QString itemSelCss =
0242             "QListView::item:selected {"
0243             "   background-color: %1;"
0244             "   border-left: 1px solid %2;"
0245             "   border-right: 1px solid %2;"
0246             "}";
0247         itemSelCss = itemSelCss.arg(StyleSheetUtils::gradient(orientation, bgSelColor, 56), StyleSheetUtils::rgba(borderSelColor));
0248 
0249         QString itemHovCss =
0250             "QListView::item:hover:!selected {"
0251             "  background-color: %1;"
0252             "  border-left: 1px solid %2;"
0253             "  border-right: 1px solid %3;"
0254             "}";
0255         itemHovCss = itemHovCss.arg(StyleSheetUtils::gradient(orientation, bgHovColor, 56),
0256                                     StyleSheetUtils::rgba(leftBorderColor),
0257                                     StyleSheetUtils::rgba(rightBorderColor));
0258 
0259         QString css = itemCss + itemSelCss + itemHovCss;
0260         if (orientation == Qt::Vertical) {
0261             css.replace(QLatin1String("left"), QLatin1String("top")).replace(QLatin1String("right"), QLatin1String("bottom"));
0262         }
0263 
0264         mThumbnailBar->setStyleSheet(css);
0265     }
0266 
0267     DocumentView *createDocumentView()
0268     {
0269         DocumentView *view = mDocumentViewContainer->createView();
0270 
0271         // Connect context menu
0272         // If you need to connect another view signal, make sure it is disconnected in deleteDocumentView
0273         QObject::connect(view, &DocumentView::contextMenuRequested, q, &ViewMainPage::showContextMenu);
0274 
0275         QObject::connect(view, &DocumentView::completed, q, &ViewMainPage::completed);
0276         QObject::connect(view, &DocumentView::previousImageRequested, q, &ViewMainPage::previousImageRequested);
0277         QObject::connect(view, &DocumentView::nextImageRequested, q, &ViewMainPage::nextImageRequested);
0278         QObject::connect(view, &DocumentView::openUrlRequested, q, &ViewMainPage::openUrlRequested);
0279         QObject::connect(view, &DocumentView::openDirUrlRequested, q, &ViewMainPage::openDirUrlRequested);
0280         QObject::connect(view, &DocumentView::captionUpdateRequested, q, &ViewMainPage::captionUpdateRequested);
0281         QObject::connect(view, &DocumentView::toggleFullScreenRequested, q, &ViewMainPage::toggleFullScreenRequested);
0282         QObject::connect(view, &DocumentView::focused, q, &ViewMainPage::slotViewFocused);
0283         QObject::connect(view, &DocumentView::hudTrashClicked, q, &ViewMainPage::trashView);
0284         QObject::connect(view, &DocumentView::hudDeselectClicked, q, &ViewMainPage::deselectView);
0285 
0286         QObject::connect(view, &DocumentView::videoFinished, mSlideShow, &SlideShow::resumeAndGoToNextUrl);
0287 
0288         mDocumentViews << view;
0289 
0290         return view;
0291     }
0292 
0293     void deleteDocumentView(DocumentView *view)
0294     {
0295         if (mDocumentViewController->view() == view) {
0296             mDocumentViewController->setView(nullptr);
0297         }
0298 
0299         // Make sure we do not get notified about this view while it is going away.
0300         // mDocumentViewController->deleteView() animates the view deletion so
0301         // the view still exists for a short while when we come back to the
0302         // event loop)
0303         QObject::disconnect(view, nullptr, q, nullptr);
0304         QObject::disconnect(view, nullptr, mSlideShow, nullptr);
0305 
0306         mDocumentViews.removeOne(view);
0307         mDocumentViewContainer->deleteView(view);
0308     }
0309 
0310     void saveSplitterConfig()
0311     {
0312         if (mThumbnailBar->isVisible()) {
0313             if (mThumbnailSplitter->sizes().constLast() > 0) {
0314                 GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes());
0315             } else {
0316                 // mThumbnailBar's size has been collapsed to 0. We reset the sizes to default so users can't "lose" the bar.
0317                 const bool didUseDefaults = GwenviewConfig::self()->useDefaults(true);
0318                 const auto defaultSplitterSizes = GwenviewConfig::thumbnailSplitterSizes();
0319                 GwenviewConfig::self()->useDefaults(didUseDefaults);
0320                 GwenviewConfig::setThumbnailSplitterSizes(defaultSplitterSizes);
0321             }
0322         }
0323     }
0324 
0325     DocumentView *currentView() const
0326     {
0327         return mDocumentViewController->view();
0328     }
0329 
0330     void setCurrentView(DocumentView *view)
0331     {
0332         DocumentView *oldView = currentView();
0333         if (view == oldView) {
0334             return;
0335         }
0336         if (oldView) {
0337             oldView->setCurrent(false);
0338         }
0339         view->setCurrent(true);
0340         mDocumentViewController->setView(view);
0341         mSynchronizer->setCurrentView(view);
0342 
0343         QModelIndex index = indexForView(view);
0344         if (index.isValid()) {
0345             // Index may be invalid when Gwenview is started as
0346             // `gwenview /foo/image.png` because in this situation it loads image.png
0347             // *before* listing /foo (because it matters less to the user)
0348             mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
0349         }
0350         QObject::connect(view, &DocumentView::currentToolChanged, q, &ViewMainPage::updateFocus);
0351     }
0352 
0353     QModelIndex indexForView(DocumentView *view) const
0354     {
0355         QUrl url = view->url();
0356         if (!url.isValid()) {
0357             qCWarning(GWENVIEW_APP_LOG) << "View does not display any document!";
0358             return {};
0359         }
0360 
0361         SortedDirModel *dirModel = mGvCore->sortedDirModel();
0362         QModelIndex srcIndex = dirModel->indexForUrl(url);
0363         if (!mDirModelToBarModelProxyMapper) {
0364             // Delay the initialization of the mapper to its first use because
0365             // mThumbnailBar->model() is not set after ViewMainPage ctor is
0366             // done.
0367             const_cast<ViewMainPagePrivate *>(this)->mDirModelToBarModelProxyMapper = new KModelIndexProxyMapper(dirModel, mThumbnailBar->model(), q);
0368         }
0369         QModelIndex index = mDirModelToBarModelProxyMapper->mapLeftToRight(srcIndex);
0370         return index;
0371     }
0372 
0373     void applyPalette(bool fullScreenMode)
0374     {
0375         mDocumentViewContainer->applyPalette(mGvCore->palette(fullScreenMode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette));
0376         setupThumbnailBarStyleSheet();
0377     }
0378 
0379     void updateDocumentCountLabel()
0380     {
0381         const int current = mThumbnailBar->currentIndex().row() + 1; // zero-based
0382         const int total = mThumbnailBar->model()->rowCount();
0383         const QString text = i18nc("@info:status %1 current document index, %2 total documents", "%1 of %2", current, total);
0384         mDocumentCountLabel->setText(text);
0385     }
0386 };
0387 
0388 ViewMainPage::ViewMainPage(QWidget *parent, SlideShow *slideShow, KActionCollection *actionCollection, GvCore *gvCore)
0389     : QWidget(parent)
0390     , d(new ViewMainPagePrivate)
0391 {
0392     d->q = this;
0393     d->mDirModelToBarModelProxyMapper = nullptr; // Initialized later
0394     d->mSlideShow = slideShow;
0395     d->mActionCollection = actionCollection;
0396     d->mGvCore = gvCore;
0397     d->mCompareMode = false;
0398 
0399     auto enterKeyShortcut = new QShortcut(Qt::Key_Return, this);
0400     connect(enterKeyShortcut, &QShortcut::activated, this, &ViewMainPage::slotEnterPressed);
0401 
0402     d->setupWidgets();
0403 
0404     auto view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);
0405 
0406     d->mToggleThumbnailBarAction = view->add<KToggleAction>(QStringLiteral("toggle_thumbnailbar"));
0407     d->mToggleThumbnailBarAction->setText(i18n("Show Thumbnails"));
0408     // clang-format off
0409     // i18n: For languages that are read right to left, "right side" refers to the left side in this context.
0410     d->mToggleThumbnailBarAction->setWhatsThis(xi18nc("@info:whatsthis", "<para>This toggles a small bar showing all the other images in the current folder. "
0411         "Selecting one of them changes the currently viewed image.</para><para>Select multiple images at the same time to see them side by side.</para>"
0412         "<para>The bar can optionally be moved to the right side of the screen and contain multiple rows of images; <link url='%1'>configure these options in the settings</link>.</para>", QStringLiteral("gwenview:/config/imageview")));
0413         // Keep the previous link address in sync with MainWindow::Private::SettingsOpenerHelper::eventFilter().
0414     // clang-format on
0415     actionCollection->setDefaultShortcut(d->mToggleThumbnailBarAction, Qt::CTRL | Qt::Key_B);
0416     connect(d->mToggleThumbnailBarAction, &KToggleAction::triggered, this, &ViewMainPage::setThumbnailBarVisibility);
0417     d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction);
0418     connect(d->mThumbnailSplitter, &QSplitter::splitterMoved, this, [this]() {
0419         // The splitter can be moved until the bar has a size of zero which makes it invisible.
0420         const bool isThumbnailBarVisible = d->mThumbnailSplitter->sizes().constLast() > 0;
0421         GwenviewConfig::setThumbnailBarIsVisible(isThumbnailBarVisible);
0422         d->mToggleThumbnailBarAction->setChecked(isThumbnailBarVisible);
0423     });
0424 
0425     d->mSynchronizeAction = view->add<KToggleAction>(QStringLiteral("synchronize_views"));
0426     d->mSynchronizeAction->setText(i18n("Synchronize"));
0427     actionCollection->setDefaultShortcut(d->mSynchronizeAction, Qt::CTRL | Qt::Key_Y);
0428     connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizer, &DocumentViewSynchronizer::setActive);
0429     // Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync
0430     connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizeCheckBox, &QAbstractButton::setChecked);
0431     connect(d->mSynchronizeCheckBox, &QAbstractButton::toggled, d->mSynchronizeAction, &QAction::setChecked);
0432 
0433     // Connections for the document count
0434     connect(d->mThumbnailBar, &ThumbnailBarView::rowsInsertedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved);
0435     connect(d->mThumbnailBar, &ThumbnailBarView::rowsRemovedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved);
0436 
0437     connect(qApp, &QApplication::paletteChanged, this, [this]() {
0438         d->applyPalette(window()->isFullScreen());
0439     });
0440 
0441     installEventFilter(this);
0442 }
0443 
0444 ViewMainPage::~ViewMainPage()
0445 {
0446     delete d;
0447 }
0448 
0449 void ViewMainPage::loadConfig()
0450 {
0451     d->applyPalette(window()->isFullScreen());
0452 
0453     // FIXME: Not symmetric with saveConfig(). Check if it matters.
0454     for (DocumentView *view : qAsConst(d->mDocumentViews)) {
0455         view->loadAdapterConfig();
0456     }
0457 
0458     Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation();
0459     d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal);
0460     d->mThumbnailBar->setOrientation(orientation);
0461     d->setupThumbnailBarStyleSheet();
0462     d->mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible());
0463     d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible());
0464 
0465     int oldRowCount = d->mThumbnailBar->rowCount();
0466     int newRowCount = GwenviewConfig::thumbnailBarRowCount();
0467     if (oldRowCount != newRowCount) {
0468         d->mThumbnailBar->setUpdatesEnabled(false);
0469         int gridSize = d->mThumbnailBar->gridSize().width();
0470 
0471         d->mThumbnailBar->setRowCount(newRowCount);
0472 
0473         // Adjust splitter to ensure thumbnail size remains the same
0474         int delta = (newRowCount - oldRowCount) * gridSize;
0475         QList<int> sizes = d->mThumbnailSplitter->sizes();
0476         Q_ASSERT(sizes.count() == 2);
0477         sizes[0] -= delta;
0478         sizes[1] += delta;
0479         d->mThumbnailSplitter->setSizes(sizes);
0480 
0481         d->mThumbnailBar->setUpdatesEnabled(true);
0482     }
0483 
0484     d->mZoomMode = GwenviewConfig::zoomMode();
0485 }
0486 
0487 void ViewMainPage::saveConfig()
0488 {
0489     d->saveSplitterConfig();
0490     GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked());
0491 }
0492 
0493 void ViewMainPage::setThumbnailBarVisibility(bool visible)
0494 {
0495     d->saveSplitterConfig();
0496     d->mThumbnailBar->setVisible(visible);
0497     if (visible && d->mThumbnailSplitter->sizes().constLast() <= 0) {
0498         // mThumbnailBar is supposed to be made visible but its splitter has a size of 0 making it invisible.
0499         // We load the last good sizes from the config.
0500         d->mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes());
0501     }
0502 }
0503 
0504 int ViewMainPage::statusBarHeight() const
0505 {
0506     return d->mStatusBarContainer->height();
0507 }
0508 
0509 void ViewMainPage::setStatusBarVisible(bool visible)
0510 {
0511     d->mStatusBarContainer->setVisible(visible);
0512 }
0513 
0514 void ViewMainPage::setFullScreenMode(bool fullScreenMode)
0515 {
0516     if (fullScreenMode) {
0517         d->mThumbnailBar->setVisible(false);
0518     } else {
0519         d->mThumbnailBar->setVisible(d->mToggleThumbnailBarAction->isChecked());
0520     }
0521     d->applyPalette(fullScreenMode);
0522     d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode);
0523 }
0524 
0525 ThumbnailBarView *ViewMainPage::thumbnailBar() const
0526 {
0527     return d->mThumbnailBar;
0528 }
0529 
0530 inline void addActionToMenu(QMenu *menu, KActionCollection *actionCollection, const char *name)
0531 {
0532     QAction *action = actionCollection->action(name);
0533     if (action) {
0534         menu->addAction(action);
0535     }
0536 }
0537 
0538 inline void addActionToMenu(QMenu *menu, KActionCollection *actionCollection, const QString &name)
0539 {
0540     QAction *action = actionCollection->action(name);
0541     if (action) {
0542         menu->addAction(action);
0543     }
0544 }
0545 
0546 void ViewMainPage::showContextMenu()
0547 {
0548     QMenu menu(this);
0549     addActionToMenu(&menu, d->mActionCollection, "fullscreen");
0550     menu.addSeparator();
0551     addActionToMenu(&menu, d->mActionCollection, "go_previous");
0552     addActionToMenu(&menu, d->mActionCollection, "go_next");
0553     if (d->currentView()->canZoom()) {
0554         menu.addSeparator();
0555         addActionToMenu(&menu, d->mActionCollection, "view_actual_size");
0556         addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit");
0557         addActionToMenu(&menu, d->mActionCollection, "view_zoom_in");
0558         addActionToMenu(&menu, d->mActionCollection, "view_zoom_out");
0559         addActionToMenu(&menu, d->mActionCollection, "view_toggle_birdeyeview");
0560     }
0561     menu.addSeparator();
0562     QMenu backgroundColorModeMenu(i18nc("@item:inmenu", "Background Color Mode"), this);
0563     addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_auto");
0564     addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_light");
0565     addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_neutral");
0566     addActionToMenu(&backgroundColorModeMenu, d->mActionCollection, "view_background_colormode_dark");
0567     backgroundColorModeMenu.setIcon(backgroundColorModeMenu.actions().at(0)->icon());
0568     menu.addMenu(&backgroundColorModeMenu);
0569     if (d->mCompareMode) {
0570         menu.addSeparator();
0571         addActionToMenu(&menu, d->mActionCollection, "synchronize_views");
0572     }
0573 
0574     menu.addSeparator();
0575     addActionToMenu(&menu, d->mActionCollection, KStandardAction::name(KStandardAction::Copy));
0576     addActionToMenu(&menu, d->mActionCollection, "file_copy_to");
0577     addActionToMenu(&menu, d->mActionCollection, "file_move_to");
0578     addActionToMenu(&menu, d->mActionCollection, "file_link_to");
0579     menu.addSeparator();
0580     addActionToMenu(&menu, d->mActionCollection, "file_open_in_new_window");
0581     addActionToMenu(&menu, d->mActionCollection, "file_open_with");
0582     addActionToMenu(&menu, d->mActionCollection, "file_open_containing_folder");
0583 
0584     menu.exec(QCursor::pos());
0585 }
0586 
0587 QSize ViewMainPage::sizeHint() const
0588 {
0589     return QSize(400, 300);
0590 }
0591 
0592 QSize ViewMainPage::minimumSizeHint() const
0593 {
0594     if (!layout()) {
0595         return QSize();
0596     }
0597 
0598     QSize minimumSize = layout()->minimumSize();
0599     if (window()->isFullScreen()) {
0600         // Check minimum width of the overlay fullscreen bar
0601         // since there is no layout link which could do this
0602         const FullScreenBar *fullScreenBar = findChild<FullScreenBar *>();
0603         if (fullScreenBar && fullScreenBar->layout()) {
0604             const int fullScreenBarWidth = fullScreenBar->layout()->minimumSize().width();
0605             if (fullScreenBarWidth > minimumSize.width()) {
0606                 minimumSize.setWidth(fullScreenBarWidth);
0607             }
0608         }
0609     }
0610     return minimumSize;
0611 }
0612 
0613 QUrl ViewMainPage::url() const
0614 {
0615     GV_RETURN_VALUE_IF_FAIL(d->currentView(), QUrl());
0616     return d->currentView()->url();
0617 }
0618 
0619 Document::Ptr ViewMainPage::currentDocument() const
0620 {
0621     if (!d->currentView()) {
0622         LOG("!d->documentView()");
0623         return {};
0624     }
0625 
0626     return d->currentView()->document();
0627 }
0628 
0629 bool ViewMainPage::isEmpty() const
0630 {
0631     return !currentDocument();
0632 }
0633 
0634 RasterImageView *ViewMainPage::imageView() const
0635 {
0636     if (!d->currentView()) {
0637         return nullptr;
0638     }
0639     return d->currentView()->imageView();
0640 }
0641 
0642 DocumentView *ViewMainPage::documentView() const
0643 {
0644     return d->currentView();
0645 }
0646 
0647 void ViewMainPage::openUrl(const QUrl &url)
0648 {
0649     openUrls(QList<QUrl>() << url, url);
0650 }
0651 
0652 void ViewMainPage::openUrls(const QList<QUrl> &allUrls, const QUrl &currentUrl)
0653 {
0654     DocumentView::Setup setup;
0655 
0656     QSet<QUrl> urls(allUrls.begin(), allUrls.end());
0657     d->mCompareMode = urls.count() > 1;
0658 
0659     using ViewForUrlMap = QMap<QUrl, DocumentView *>;
0660     ViewForUrlMap viewForUrlMap;
0661 
0662     if (!d->mDocumentViews.isEmpty()) {
0663         d->mDocumentViewContainer->updateSetup(d->mDocumentViews.last());
0664     }
0665     if (d->mDocumentViews.isEmpty() || d->mZoomMode == ZoomMode::Autofit) {
0666         setup.valid = true;
0667         setup.zoomToFit = true;
0668     } else {
0669         setup = d->mDocumentViews.last()->setup();
0670     }
0671     // Destroy views which show urls we don't care about, remove from "urls" the
0672     // urls which already have a view.
0673     for (DocumentView *view : qAsConst(d->mDocumentViews)) {
0674         QUrl url = view->url();
0675         if (urls.contains(url)) {
0676             // view displays an url we must display, keep it
0677             urls.remove(url);
0678             viewForUrlMap.insert(url, view);
0679         } else {
0680             // view url is not interesting, drop it
0681             d->deleteDocumentView(view);
0682         }
0683     }
0684 
0685     // Create view for remaining urls
0686     for (const QUrl &url : qAsConst(urls)) {
0687         if (d->mDocumentViews.count() >= MaxViewCount) {
0688             qCWarning(GWENVIEW_APP_LOG) << "Too many documents to show";
0689             break;
0690         }
0691         DocumentView *view = d->createDocumentView();
0692         viewForUrlMap.insert(url, view);
0693     }
0694 
0695     // Set sortKey to match url order
0696     int sortKey = 0;
0697     for (const QUrl &url : allUrls) {
0698         viewForUrlMap[url]->setSortKey(sortKey);
0699         ++sortKey;
0700     }
0701 
0702     d->mDocumentViewContainer->updateLayout();
0703 
0704     // Load urls for new views. Do it only now because the view must have the
0705     // correct size before it starts loading its url. Do not do it later because
0706     // view->url() needs to be set for the next loop.
0707     ViewForUrlMap::ConstIterator it = viewForUrlMap.constBegin(), end = viewForUrlMap.constEnd();
0708     for (; it != end; ++it) {
0709         QUrl url = it.key();
0710         DocumentView *view = it.value();
0711         DocumentView::Setup savedSetup = d->mDocumentViewContainer->savedSetup(url);
0712         view->openUrl(url, d->mZoomMode == ZoomMode::Individual && savedSetup.valid ? savedSetup : setup);
0713 #if HAVE_KACTIVITIES
0714         if (GwenviewConfig::historyEnabled()) {
0715             KActivities::ResourceInstance::notifyAccessed(url);
0716         }
0717 #endif
0718     }
0719 
0720     // Init views
0721     for (DocumentView *view : qAsConst(d->mDocumentViews)) {
0722         view->setCompareMode(d->mCompareMode);
0723         if (view->url() == currentUrl) {
0724             d->setCurrentView(view);
0725         } else {
0726             view->setCurrent(false);
0727         }
0728     }
0729 
0730     d->mSynchronizeCheckBox->setVisible(d->mCompareMode);
0731     if (d->mCompareMode) {
0732         d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked());
0733     } else {
0734         d->mSynchronizer->setActive(false);
0735     }
0736 
0737     d->updateDocumentCountLabel();
0738     d->mDocumentCountLabel->setVisible(!d->mCompareMode);
0739 }
0740 
0741 void ViewMainPage::reload()
0742 {
0743     DocumentView *view = d->currentView();
0744     if (!view) {
0745         return;
0746     }
0747     Document::Ptr doc = view->document();
0748     if (!doc) {
0749         qCWarning(GWENVIEW_APP_LOG) << "!doc";
0750         return;
0751     }
0752     if (doc->isModified()) {
0753         KGuiItem cont = KStandardGuiItem::cont();
0754         cont.setText(i18nc("@action:button", "Discard Changes and Reload"));
0755         int answer = KMessageBox::warningContinueCancel(this,
0756                                                         i18nc("@info", "This image has been modified. Reloading it will discard all your changes."),
0757                                                         QString() /* caption */,
0758                                                         cont);
0759         if (answer != KMessageBox::Continue) {
0760             return;
0761         }
0762     }
0763     doc->reload();
0764     // Call openUrl again because DocumentView may need to switch to a new
0765     // adapter (for example because document was broken and it is not anymore)
0766     d->currentView()->openUrl(doc->url(), d->currentView()->setup());
0767 }
0768 
0769 void ViewMainPage::reset()
0770 {
0771     d->mDocumentViewController->reset();
0772     d->mDocumentViewContainer->reset();
0773     d->mDocumentViews.clear();
0774 }
0775 
0776 void ViewMainPage::slotViewFocused(DocumentView *view)
0777 {
0778     d->setCurrentView(view);
0779 }
0780 
0781 void ViewMainPage::slotEnterPressed()
0782 {
0783     DocumentView *view = d->currentView();
0784     if (view) {
0785         AbstractRasterImageViewTool *tool = view->currentTool();
0786         if (tool) {
0787             QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier);
0788             tool->keyPressEvent(&event);
0789             if (event.isAccepted()) {
0790                 return;
0791             }
0792         }
0793     }
0794 }
0795 
0796 bool ViewMainPage::eventFilter(QObject *watched, QEvent *event)
0797 {
0798     if (watched == d->mThumbnailBar && event->type() == QEvent::KeyPress) {
0799         auto ke = static_cast<QKeyEvent *>(event);
0800         switch (ke->key()) {
0801         case Qt::Key_Left:
0802             if (QApplication::isRightToLeft()) {
0803                 Q_EMIT nextImageRequested();
0804             } else {
0805                 Q_EMIT previousImageRequested();
0806             }
0807             return true;
0808         case Qt::Key_Up:
0809             Q_EMIT previousImageRequested();
0810             return true;
0811         case Qt::Key_Right:
0812             if (QApplication::isRightToLeft()) {
0813                 Q_EMIT previousImageRequested();
0814             } else {
0815                 Q_EMIT nextImageRequested();
0816             }
0817             return true;
0818         case Qt::Key_Down:
0819             Q_EMIT nextImageRequested();
0820             return true;
0821         default:
0822             break;
0823         }
0824     } else if (event->type() == QEvent::ShortcutOverride) {
0825         const int key = static_cast<QKeyEvent *>(event)->key();
0826         if (key == Qt::Key_Space || key == Qt::Key_Escape) {
0827             const DocumentView *view = d->currentView();
0828             if (view) {
0829                 AbstractRasterImageViewTool *tool = view->currentTool();
0830                 if (tool) {
0831                     QKeyEvent toolKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
0832                     tool->keyPressEvent(&toolKeyEvent);
0833                     if (toolKeyEvent.isAccepted()) {
0834                         event->accept();
0835                     }
0836                 }
0837             }
0838         }
0839         // Leave fullscreen when viewing an image
0840         if (window()->isFullScreen() && key == Qt::Key_Escape) {
0841             d->mActionCollection->action(QStringLiteral("leave_fullscreen"))->trigger();
0842             event->accept();
0843         }
0844     }
0845 
0846     return QWidget::eventFilter(watched, event);
0847 }
0848 
0849 void ViewMainPage::trashView(DocumentView *view)
0850 {
0851     QUrl url = view->url();
0852     deselectView(view);
0853     FileOperations::trash(QList<QUrl>() << url, this);
0854 }
0855 
0856 void ViewMainPage::deselectView(DocumentView *view)
0857 {
0858     DocumentView *newCurrentView = nullptr;
0859     if (view == d->currentView()) {
0860         // We need to find a new view to set as current
0861         int idx = d->mDocumentViews.indexOf(view);
0862         if (idx + 1 < d->mDocumentViews.count()) {
0863             newCurrentView = d->mDocumentViews.at(idx + 1);
0864         } else if (idx > 0) {
0865             newCurrentView = d->mDocumentViews.at(idx - 1);
0866         } else {
0867             GV_WARN_AND_RETURN("No view found to set as current");
0868         }
0869     }
0870 
0871     QModelIndex index = d->indexForView(view);
0872     QItemSelectionModel *selectionModel = d->mThumbnailBar->selectionModel();
0873     selectionModel->select(index, QItemSelectionModel::Deselect);
0874 
0875     if (newCurrentView) {
0876         d->setCurrentView(newCurrentView);
0877     }
0878 }
0879 
0880 QToolButton *ViewMainPage::toggleSideBarButton() const
0881 {
0882     return d->mToggleSideBarButton;
0883 }
0884 
0885 void ViewMainPage::showMessageWidget(QGraphicsWidget *widget, Qt::Alignment align)
0886 {
0887     d->mDocumentViewContainer->showMessageWidget(widget, align);
0888 }
0889 
0890 void ViewMainPage::updateFocus(const AbstractRasterImageViewTool *tool)
0891 {
0892     if (!tool) {
0893         d->mDocumentViewContainer->setFocus();
0894     }
0895 }
0896 
0897 void ViewMainPage::slotDirModelItemsAddedOrRemoved()
0898 {
0899     d->updateDocumentCountLabel();
0900 }
0901 
0902 } // namespace
0903 
0904 #include "moc_viewmainpage.cpp"