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 ¤tUrl) 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"