File indexing completed on 2024-05-19 04:29:21

0001 /*
0002  *  This file is part of KimageShop^WKrayon^WKrita
0003  *
0004  *  SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
0005  *  SPDX-FileCopyrightText: 1999 Michael Koch <koch@kde.org>
0006  *  SPDX-FileCopyrightText: 1999 Carsten Pfeiffer <pfeiffer@kde.org>
0007  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0008  *  SPDX-FileCopyrightText: 2003-2011 Boudewijn Rempt <boud@valdyas.org>
0009  *  SPDX-FileCopyrightText: 2004 Clarence Dang <dang@kde.org>
0010  *  SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
0011  *  SPDX-FileCopyrightText: 2017 L. E. Segovia <amy@amyspark.me>
0012  *
0013  *  SPDX-License-Identifier: GPL-2.0-or-later
0014  */
0015 
0016 #include <stdio.h>
0017 
0018 #include "KisViewManager.h"
0019 #include <QPrinter>
0020 
0021 #include <QAction>
0022 #include <QApplication>
0023 #include <QBuffer>
0024 #include <QByteArray>
0025 #include <QStandardPaths>
0026 #include <QDesktopWidget>
0027 #include <QDesktopServices>
0028 #include <QGridLayout>
0029 #include <QMainWindow>
0030 #include <QMenu>
0031 #include <QMenuBar>
0032 #include <QMessageBox>
0033 #include <QObject>
0034 #include <QPoint>
0035 #include <QPrintDialog>
0036 #include <QPushButton>
0037 #include <QRect>
0038 #include <QScrollBar>
0039 #include <QStatusBar>
0040 #include <QToolBar>
0041 #include <QUrl>
0042 #include <QWidget>
0043 
0044 #include <kactioncollection.h>
0045 #include <klocalizedstring.h>
0046 #include <KoResourcePaths.h>
0047 #include <kselectaction.h>
0048 
0049 #include <KoCanvasController.h>
0050 #include <KoCompositeOp.h>
0051 #include <KoDockRegistry.h>
0052 #include <KoDockWidgetTitleBar.h>
0053 #include <KoFileDialog.h>
0054 #include <KoProperties.h>
0055 #include <KisResourceItemChooserSync.h>
0056 #include <KoSelection.h>
0057 #include <KoStore.h>
0058 #include <KoToolManager.h>
0059 #include <KoToolRegistry.h>
0060 #include <KoViewConverter.h>
0061 #include <KoZoomHandler.h>
0062 #include <KoPluginLoader.h>
0063 #include <KoDocumentInfo.h>
0064 #include <KoColorSpaceRegistry.h>
0065 #include <KisResourceLocator.h>
0066 
0067 #include "input/kis_input_manager.h"
0068 #include "canvas/kis_canvas2.h"
0069 #include "canvas/kis_canvas_controller.h"
0070 #include "canvas/kis_grid_manager.h"
0071 #include "input/kis_input_profile_manager.h"
0072 #include "kis_action_manager.h"
0073 #include "kis_action.h"
0074 #include "kis_canvas_controls_manager.h"
0075 #include "kis_canvas_resource_provider.h"
0076 #include "kis_composite_progress_proxy.h"
0077 #include <KoProgressUpdater.h>
0078 #include "kis_config.h"
0079 #include "kis_config_notifier.h"
0080 #include "kis_control_frame.h"
0081 #include "kis_coordinates_converter.h"
0082 #include "KisDocument.h"
0083 #include "kis_favorite_resource_manager.h"
0084 #include "kis_filter_manager.h"
0085 #include "kis_group_layer.h"
0086 #include <kis_image.h>
0087 #include "kis_image_manager.h"
0088 #include <kis_layer.h>
0089 #include "kis_mainwindow_observer.h"
0090 #include "kis_mask_manager.h"
0091 #include "kis_mimedata.h"
0092 #include "kis_mirror_manager.h"
0093 #include "kis_node_commands_adapter.h"
0094 #include "kis_node.h"
0095 #include "kis_node_manager.h"
0096 #include "KisDecorationsManager.h"
0097 #include <kis_paint_layer.h>
0098 #include "kis_paintop_box.h"
0099 #include <brushengine/kis_paintop_preset.h>
0100 #include "KisPart.h"
0101 #include <KoUpdater.h>
0102 #include "kis_selection.h"
0103 #include "kis_selection_mask.h"
0104 #include "kis_selection_manager.h"
0105 #include "kis_shape_controller.h"
0106 #include "kis_shape_layer.h"
0107 #include <kis_signal_compressor.h>
0108 #include "kis_statusbar.h"
0109 #include <KisTemplateCreateDia.h>
0110 #include <kis_tool_freehand.h>
0111 #include <kis_undo_adapter.h>
0112 #include "KisView.h"
0113 #include "kis_zoom_manager.h"
0114 #include "widgets/kis_floating_message.h"
0115 #include "kis_signal_auto_connection.h"
0116 #include "kis_icon_utils.h"
0117 #include "kis_guides_manager.h"
0118 #include "kis_derived_resources.h"
0119 #include "dialogs/kis_delayed_save_dialog.h"
0120 #include <KisMainWindow.h>
0121 #include "kis_signals_blocker.h"
0122 #include "imagesize/imagesize.h"
0123 #include <KoToolDocker.h>
0124 #include <KisIdleTasksManager.h>
0125 #include <KisImageBarrierLock.h>
0126 
0127 #include "kis_filter_configuration.h"
0128 
0129 #ifdef Q_OS_WIN
0130 #include "KisWindowsPackageUtils.h"
0131 #endif
0132 
0133 class BlockingUserInputEventFilter : public QObject
0134 {
0135     bool eventFilter(QObject *watched, QEvent *event) override
0136     {
0137         Q_UNUSED(watched);
0138         if(dynamic_cast<QWheelEvent*>(event)
0139                 || dynamic_cast<QKeyEvent*>(event)
0140                 || dynamic_cast<QMouseEvent*>(event)) {
0141             return true;
0142         }
0143         else {
0144             return false;
0145         }
0146     }
0147 };
0148 
0149 class KisViewManager::KisViewManagerPrivate
0150 {
0151 
0152 public:
0153 
0154     KisViewManagerPrivate(KisViewManager *_q, KisKActionCollection *_actionCollection, QWidget *_q_parent)
0155         : filterManager(_q)
0156         , selectionManager(_q)
0157         , statusBar(_q)
0158         , controlFrame(_q, _q_parent)
0159         , nodeManager(_q)
0160         , imageManager(_q)
0161         , gridManager(_q)
0162         , canvasControlsManager(_q)
0163         , paintingAssistantsManager(_q)
0164         , actionManager(_q, _actionCollection)
0165         , canvasResourceProvider(_q)
0166         , canvasResourceManager()
0167         , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q)
0168         , actionCollection(_actionCollection)
0169         , mirrorManager(_q)
0170         , inputManager(_q)
0171     {
0172         KisViewManager::initializeResourceManager(&canvasResourceManager);
0173     }
0174 
0175 public:
0176     KisFilterManager filterManager;
0177     KisAction *createTemplate {nullptr};
0178     KisAction *createCopy {nullptr};
0179     KisAction *saveIncremental {nullptr};
0180     KisAction *saveIncrementalBackup {nullptr};
0181     KisAction *openResourcesDirectory {nullptr};
0182     KisAction *rotateCanvasRight {nullptr};
0183     KisAction *rotateCanvasLeft {nullptr};
0184     KisAction *resetCanvasRotation {nullptr};
0185     KisAction *wrapAroundAction {nullptr};
0186     KisAction *wrapAroundHVAxisAction {nullptr};
0187     KisAction *wrapAroundHAxisAction {nullptr};
0188     KisAction *wrapAroundVAxisAction {nullptr};
0189     QActionGroup *wrapAroundAxisActions {nullptr};
0190     KisAction *levelOfDetailAction {nullptr};
0191     KisAction *showRulersAction {nullptr};
0192     KisAction *rulersTrackMouseAction {nullptr};
0193     KisAction *zoomTo100pct {nullptr};
0194     KisAction *zoomIn {nullptr};
0195     KisAction *zoomOut {nullptr};
0196     KisAction *zoomToFit {nullptr};
0197     KisAction *zoomToFitWidth {nullptr};
0198     KisAction *zoomToFitHeight {nullptr};
0199     KisAction *toggleZoomToFit {nullptr};
0200     KisAction *resetDisplay {nullptr};
0201     KisAction *viewPrintSize {nullptr};
0202     KisAction *softProof {nullptr};
0203     KisAction *gamutCheck {nullptr};
0204     KisAction *toggleFgBg {nullptr};
0205     KisAction *resetFgBg {nullptr};
0206     KisAction *toggleBrushOutline {nullptr};
0207 
0208     KisSelectionManager selectionManager;
0209     KisGuidesManager guidesManager;
0210     KisStatusBar statusBar;
0211     QPointer<KoUpdater> persistentImageProgressUpdater;
0212 
0213     QScopedPointer<KoProgressUpdater> persistentUnthreadedProgressUpdaterRouter;
0214     QPointer<KoUpdater> persistentUnthreadedProgressUpdater;
0215 
0216     KisControlFrame controlFrame;
0217     KisNodeManager nodeManager;
0218     KisImageManager imageManager;
0219     KisGridManager gridManager;
0220     KisCanvasControlsManager canvasControlsManager;
0221     KisDecorationsManager paintingAssistantsManager;
0222     BlockingUserInputEventFilter blockingEventFilter;
0223     KisActionManager actionManager;
0224     QMainWindow* mainWindow {nullptr};
0225     QPointer<KisFloatingMessage> savedFloatingMessage;
0226     bool showFloatingMessage {true};
0227     QPointer<KisView> currentImageView;
0228     KisCanvasResourceProvider canvasResourceProvider;
0229     KoCanvasResourceProvider canvasResourceManager;
0230     KisSignalCompressor guiUpdateCompressor;
0231     KisKActionCollection *actionCollection {nullptr};
0232     KisMirrorManager mirrorManager;
0233     KisInputManager inputManager;
0234     KisIdleTasksManager idleTasksManager;
0235 
0236     KisSignalAutoConnectionsStore viewConnections;
0237     KSelectAction *actionAuthor {nullptr}; // Select action for author profile.
0238     KisAction *showPixelGrid {nullptr};
0239 
0240     QByteArray canvasStateInNormalMode;
0241     QByteArray canvasStateInCanvasOnlyMode;
0242 
0243     struct CanvasOnlyOptions : boost::equality_comparable<CanvasOnlyOptions>
0244     {
0245         CanvasOnlyOptions(const KisConfig &cfg)
0246             : hideStatusbarFullscreen(cfg.hideStatusbarFullscreen())
0247             , hideDockersFullscreen(cfg.hideDockersFullscreen())
0248             , hideTitlebarFullscreen(cfg.hideTitlebarFullscreen())
0249             , hideMenuFullscreen(cfg.hideMenuFullscreen())
0250             , hideToolbarFullscreen(cfg.hideToolbarFullscreen())
0251         {
0252         }
0253 
0254         bool hideStatusbarFullscreen = false;
0255         bool hideDockersFullscreen = false;
0256         bool hideTitlebarFullscreen = false;
0257         bool hideMenuFullscreen = false;
0258         bool hideToolbarFullscreen = false;
0259 
0260         bool operator==(const CanvasOnlyOptions &rhs) {
0261             return hideStatusbarFullscreen == rhs.hideStatusbarFullscreen &&
0262                 hideDockersFullscreen == rhs.hideDockersFullscreen &&
0263                 hideTitlebarFullscreen == rhs.hideTitlebarFullscreen &&
0264                 hideMenuFullscreen == rhs.hideMenuFullscreen &&
0265                 hideToolbarFullscreen == rhs.hideToolbarFullscreen;
0266         }
0267     };
0268     std::optional<CanvasOnlyOptions> canvasOnlyOptions;
0269 
0270     bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force);
0271 };
0272 
0273 KisViewManager::KisViewManager(QWidget *parent, KisKActionCollection *_actionCollection)
0274     : d(new KisViewManagerPrivate(this, _actionCollection, parent))
0275 {
0276     d->actionCollection = _actionCollection;
0277     d->mainWindow = dynamic_cast<QMainWindow*>(parent);
0278     d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager);
0279     connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout()));
0280 
0281     createActions();
0282     setupManagers();
0283 
0284     // These initialization functions must wait until KisViewManager ctor is complete.
0285     d->statusBar.setup();
0286     d->persistentImageProgressUpdater =
0287             d->statusBar.progressUpdater()->startSubtask(1, "", true);
0288     // reset state to "completed"
0289     d->persistentImageProgressUpdater->setRange(0,100);
0290     d->persistentImageProgressUpdater->setValue(100);
0291 
0292     d->persistentUnthreadedProgressUpdater =
0293             d->statusBar.progressUpdater()->startSubtask(1, "", true);
0294     // reset state to "completed"
0295     d->persistentUnthreadedProgressUpdater->setRange(0,100);
0296     d->persistentUnthreadedProgressUpdater->setValue(100);
0297 
0298     d->persistentUnthreadedProgressUpdaterRouter.reset(
0299                 new KoProgressUpdater(d->persistentUnthreadedProgressUpdater,
0300                                       KoProgressUpdater::Unthreaded));
0301     d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true);
0302     d->persistentUnthreadedProgressUpdaterRouter->start();
0303 
0304     // just a clumsy way to mark the updater as completed, the subtask will
0305     // be automatically deleted on completion...
0306     d->persistentUnthreadedProgressUpdaterRouter->startSubtask()->setProgress(100);
0307 
0308     d->controlFrame.setup(parent);
0309 
0310 
0311     //Check to draw scrollbars after "Canvas only mode" toggle is created.
0312     this->showHideScrollbars();
0313 
0314     QScopedPointer<KoDummyCanvasController> dummy(new KoDummyCanvasController(actionCollection()));
0315     KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data());
0316 
0317     connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)),
0318             d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice)));
0319 
0320     connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*)),
0321             d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*)));
0322 
0323     connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)),
0324             canvasResourceProvider(), SLOT(slotNodeActivated(KisNodeSP)));
0325 
0326     connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*)));
0327     connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*)));
0328     connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)),
0329             d->controlFrame.paintopBox(), SLOT(updatePresetConfig()));
0330 
0331     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions()));
0332     connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction()));
0333 
0334     KisInputProfileManager::instance()->loadProfiles();
0335 
0336     KisConfig cfg(true);
0337     d->showFloatingMessage = cfg.showCanvasMessages();
0338     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0339     KoColor foreground(Qt::black, cs);
0340     d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground));
0341     KoColor background(Qt::white, cs);
0342     d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background));
0343 
0344     // Initialize the old imagesize plugin
0345     new ImageSize(this);
0346 }
0347 
0348 
0349 KisViewManager::~KisViewManager()
0350 {
0351     KisConfig cfg(false);
0352     if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) {
0353         cfg.writeKoColor("LastForeGroundColor",canvasResourceProvider()->fgColor());
0354         cfg.writeKoColor("LastBackGroundColor",canvasResourceProvider()->bgColor());
0355     }
0356 
0357     cfg.writeEntry("baseLength", KisResourceItemChooserSync::instance()->baseLength());
0358     cfg.writeEntry("CanvasOnlyActive", false); // We never restart in CanvasOnlyMode
0359     delete d;
0360 }
0361 
0362 #include <KoActiveCanvasResourceDependencyKoResource.h>
0363 
0364 void KisViewManager::initializeResourceManager(KoCanvasResourceProvider *resourceManager)
0365 {
0366     resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter));
0367     resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter));
0368     resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter));
0369     resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter));
0370     resourceManager->addDerivedResourceConverter(toQShared(new KisFadeResourceConverter));
0371     resourceManager->addDerivedResourceConverter(toQShared(new KisScatterResourceConverter));
0372     resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter));
0373     resourceManager->addDerivedResourceConverter(toQShared(new KisBrushRotationResourceConverter));
0374     resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter));
0375     resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdResourceConverter));
0376     resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter));
0377     resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter));
0378     resourceManager->addDerivedResourceConverter(toQShared(new KisPatternSizeResourceConverter));
0379     resourceManager->addDerivedResourceConverter(toQShared(new KisBrushNameResourceConverter));
0380     resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator));
0381 
0382     resourceManager->addActiveCanvasResourceDependency(
0383         toQShared(new KoActiveCanvasResourceDependencyKoResource<KisPaintOpPreset>(
0384                       KoCanvasResource::CurrentPaintOpPreset,
0385                       KoCanvasResource::CurrentGradient)));
0386 
0387     resourceManager->addActiveCanvasResourceDependency(
0388         toQShared(new KoActiveCanvasResourceDependencyKoResource<KoAbstractGradient>(
0389                       KoCanvasResource::CurrentGradient,
0390                       KoCanvasResource::ForegroundColor)));
0391 
0392     resourceManager->addActiveCanvasResourceDependency(
0393         toQShared(new KoActiveCanvasResourceDependencyKoResource<KoAbstractGradient>(
0394                       KoCanvasResource::CurrentGradient,
0395                       KoCanvasResource::BackgroundColor)));
0396 
0397     KSharedConfigPtr config =  KSharedConfig::openConfig();
0398     KConfigGroup miscGroup = config->group("Misc");
0399     const uint handleRadius = miscGroup.readEntry("HandleRadius", 5);
0400     resourceManager->setHandleRadius(handleRadius);
0401 }
0402 
0403 KisKActionCollection *KisViewManager::actionCollection() const
0404 {
0405     return d->actionCollection;
0406 }
0407 
0408 void KisViewManager::slotViewAdded(KisView *view)
0409 {
0410     // WARNING: this slot is called even when a view from another main windows is added!
0411     //          Don't expect \p view be a child of this view manager!
0412 
0413     if (view->viewManager() == this && viewCount() == 0) {
0414         d->statusBar.showAllStatusBarItems();
0415     }
0416 }
0417 
0418 void KisViewManager::slotViewRemoved(KisView *view)
0419 {
0420     // WARNING: this slot is called even when a view from another main windows is removed!
0421     //          Don't expect \p view be a child of this view manager!
0422 
0423     if (view->viewManager() == this && viewCount() == 0) {
0424         d->statusBar.hideAllStatusBarItems();
0425     }
0426 }
0427 
0428 void KisViewManager::setCurrentView(KisView *view)
0429 {
0430     if (d->currentImageView) {
0431         d->currentImageView->notifyCurrentStateChanged(false);
0432 
0433         d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor));
0434         KisDocument* doc = d->currentImageView->document();
0435         if (doc) {
0436             doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater);
0437             doc->disconnect(this);
0438         }
0439         d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar);
0440         d->viewConnections.clear();
0441         d->idleTasksManager.setImage(0);
0442     }
0443 
0444 
0445     QPointer<KisView> imageView = qobject_cast<KisView*>(view);
0446     d->currentImageView = imageView;
0447 
0448     if (imageView) {
0449         /// idle tasks managed should be reconnected to the new image the first,
0450         /// because other dockers may request it to recalcualte stuff
0451         d->idleTasksManager.setImage(d->currentImageView->image());
0452 
0453         d->softProof->setChecked(imageView->softProofing());
0454         d->gamutCheck->setChecked(imageView->gamutCheck());
0455 
0456         // Wait for the async image to have loaded
0457         KisDocument* doc = imageView->document();
0458 
0459         if (KisConfig(true).readEntry<bool>("EnablePositionLabel", false)) {
0460             connect(d->currentImageView->canvasController()->proxyObject,
0461                     SIGNAL(documentMousePositionChanged(QPointF)),
0462                     &d->statusBar,
0463                     SLOT(documentMousePositionChanged(QPointF)));
0464         }
0465 
0466         KisCanvasController *canvasController = dynamic_cast<KisCanvasController*>(d->currentImageView->canvasController());
0467         KIS_ASSERT(canvasController);
0468 
0469         d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode()));
0470         d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15()));
0471         d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15()));
0472         d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation()));
0473 
0474         d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool)));
0475         d->wrapAroundAction->setChecked(canvasController->wrapAroundMode());
0476         d->viewConnections.addUniqueConnection(d->wrapAroundHVAxisAction, SIGNAL(triggered()), canvasController, SLOT(slotSetWrapAroundModeAxisHV()));
0477         d->wrapAroundHVAxisAction->setChecked(canvasController->wrapAroundModeAxis() == WRAPAROUND_BOTH);
0478         d->viewConnections.addUniqueConnection(d->wrapAroundHAxisAction, SIGNAL(triggered()), canvasController, SLOT(slotSetWrapAroundModeAxisH()));
0479         d->wrapAroundHAxisAction->setChecked(canvasController->wrapAroundModeAxis() == WRAPAROUND_HORIZONTAL);
0480         d->viewConnections.addUniqueConnection(d->wrapAroundVAxisAction, SIGNAL(triggered()), canvasController, SLOT(slotSetWrapAroundModeAxisV()));
0481         d->wrapAroundVAxisAction->setChecked(canvasController->wrapAroundModeAxis() == WRAPAROUND_VERTICAL);
0482 
0483         d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool)));
0484         d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode());
0485 
0486         d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*)));
0487         d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool)));
0488         d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool)));
0489         d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100()));
0490         d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn()));
0491         d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut()));
0492         d->viewConnections.addUniqueConnection(d->zoomToFit, SIGNAL(triggered()), imageView->zoomManager(), SLOT(slotZoomToFit()));
0493         d->viewConnections.addUniqueConnection(d->zoomToFitWidth, SIGNAL(triggered()), imageView->zoomManager(), SLOT(slotZoomToFitWidth()));
0494         d->viewConnections.addUniqueConnection(d->zoomToFitHeight, SIGNAL(triggered()), imageView->zoomManager(), SLOT(slotZoomToFitHeight()));
0495         d->viewConnections.addUniqueConnection(d->toggleZoomToFit, SIGNAL(triggered()), imageView->zoomManager(), SLOT(slotToggleZoomToFit()));
0496 
0497         d->viewConnections.addUniqueConnection(d->resetDisplay, SIGNAL(triggered()), imageView->viewManager(), SLOT(slotResetDisplay()));
0498 
0499         d->viewConnections.addUniqueConnection(d->viewPrintSize, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(changeCanvasMappingMode(bool)));
0500         d->viewConnections.addUniqueConnection(d->viewPrintSize, SIGNAL(toggled(bool)), imageView->zoomController()->zoomAction(), SLOT(setCanvasMappingMode(bool)));
0501 
0502         d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) );
0503         d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) );
0504 
0505         // set up progress reporting
0506         doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater);
0507         d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation()));
0508 
0509         d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool)));
0510 
0511         imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked());
0512         imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked());
0513 
0514         showHideScrollbars();
0515     }
0516 
0517     d->filterManager.setView(imageView);
0518     d->selectionManager.setView(imageView);
0519     d->guidesManager.setView(imageView);
0520     d->nodeManager.setView(imageView);
0521     d->imageManager.setView(imageView);
0522     d->canvasControlsManager.setView(imageView);
0523     d->actionManager.setView(imageView);
0524     d->gridManager.setView(imageView);
0525     d->statusBar.setView(imageView);
0526     d->paintingAssistantsManager.setView(imageView);
0527     d->mirrorManager.setView(imageView);
0528 
0529     if (d->currentImageView) {
0530         d->currentImageView->notifyCurrentStateChanged(true);
0531         d->currentImageView->canvasController()->activate();
0532         d->currentImageView->canvasController()->setFocus();
0533         d->currentImageView->zoomManager()->updateCurrentZoomResource();
0534 
0535         d->viewConnections.addUniqueConnection(
0536                     image(), SIGNAL(sigSizeChanged(QPointF,QPointF)),
0537                     canvasResourceProvider(), SLOT(slotImageSizeChanged()));
0538 
0539         d->viewConnections.addUniqueConnection(
0540                     image(), SIGNAL(sigResolutionChanged(double,double)),
0541                     canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged()));
0542 
0543         d->viewConnections.addUniqueConnection(
0544                     image(), SIGNAL(sigNodeChanged(KisNodeSP)),
0545                     this, SLOT(updateGUI()));
0546 
0547         d->viewConnections.addUniqueConnection(
0548                     d->currentImageView->zoomManager()->zoomController(),
0549                     SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
0550                     canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged()));
0551     }
0552 
0553     d->actionManager.updateGUI();
0554 
0555     canvasResourceProvider()->slotImageSizeChanged();
0556     canvasResourceProvider()->slotOnScreenResolutionChanged();
0557 
0558     Q_EMIT viewChanged();
0559 }
0560 
0561 
0562 KoZoomController *KisViewManager::zoomController() const
0563 {
0564     if (d->currentImageView) {
0565         return d->currentImageView->zoomController();
0566     }
0567     return 0;
0568 }
0569 
0570 KisImageWSP KisViewManager::image() const
0571 {
0572     if (document()) {
0573         return document()->image();
0574     }
0575     return 0;
0576 }
0577 
0578 KisCanvasResourceProvider * KisViewManager::canvasResourceProvider()
0579 {
0580     return &d->canvasResourceProvider;
0581 }
0582 
0583 KisCanvas2 * KisViewManager::canvasBase() const
0584 {
0585     if (d && d->currentImageView) {
0586         return d->currentImageView->canvasBase();
0587     }
0588     return 0;
0589 }
0590 
0591 QWidget* KisViewManager::canvas() const
0592 {
0593     if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) {
0594         return d->currentImageView->canvasBase()->canvasWidget();
0595     }
0596     return 0;
0597 }
0598 
0599 KisStatusBar * KisViewManager::statusBar() const
0600 {
0601     return &d->statusBar;
0602 }
0603 
0604 KisPaintopBox* KisViewManager::paintOpBox() const
0605 {
0606     return d->controlFrame.paintopBox();
0607 }
0608 
0609 QPointer<KoUpdater> KisViewManager::createUnthreadedUpdater(const QString &name)
0610 {
0611     return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false);
0612 }
0613 
0614 QPointer<KoUpdater> KisViewManager::createThreadedUpdater(const QString &name)
0615 {
0616     return d->statusBar.progressUpdater()->startSubtask(1, name, false);
0617 }
0618 
0619 KisSelectionManager * KisViewManager::selectionManager()
0620 {
0621     return &d->selectionManager;
0622 }
0623 
0624 KisNodeSP KisViewManager::activeNode()
0625 {
0626     return d->nodeManager.activeNode();
0627 }
0628 
0629 KisLayerSP KisViewManager::activeLayer()
0630 {
0631     return d->nodeManager.activeLayer();
0632 }
0633 
0634 KisPaintDeviceSP KisViewManager::activeDevice()
0635 {
0636     return d->nodeManager.activePaintDevice();
0637 }
0638 
0639 KisZoomManager * KisViewManager::zoomManager()
0640 {
0641     if (d->currentImageView) {
0642         return d->currentImageView->zoomManager();
0643     }
0644     return 0;
0645 }
0646 
0647 KisFilterManager * KisViewManager::filterManager()
0648 {
0649     return &d->filterManager;
0650 }
0651 
0652 KisImageManager * KisViewManager::imageManager()
0653 {
0654     return &d->imageManager;
0655 }
0656 
0657 KisInputManager* KisViewManager::inputManager() const
0658 {
0659     return &d->inputManager;
0660 }
0661 
0662 KisIdleTasksManager *KisViewManager::idleTasksManager()
0663 {
0664     return &d->idleTasksManager;
0665 }
0666 
0667 KisSelectionSP KisViewManager::selection()
0668 {
0669     if (d->currentImageView) {
0670         return d->currentImageView->selection();
0671     }
0672     return 0;
0673 
0674 }
0675 
0676 bool KisViewManager::selectionEditable()
0677 {
0678     KisLayerSP layer = activeLayer();
0679     if (layer) {
0680         KisSelectionMaskSP mask = layer->selectionMask();
0681         if (mask) {
0682             return mask->isEditable();
0683         }
0684     }
0685     // global selection is always editable
0686     return true;
0687 }
0688 
0689 KisUndoAdapter * KisViewManager::undoAdapter()
0690 {
0691     if (!document()) return 0;
0692 
0693     KisImageWSP image = document()->image();
0694     Q_ASSERT(image);
0695 
0696     return image->undoAdapter();
0697 }
0698 
0699 void KisViewManager::createActions()
0700 {
0701     KisConfig cfg(true);
0702 
0703     d->saveIncremental = actionManager()->createAction("save_incremental_version");
0704     connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental()));
0705 
0706     d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup");
0707     connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup()));
0708 
0709     connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved()));
0710 
0711     d->saveIncremental->setEnabled(false);
0712     d->saveIncrementalBackup->setEnabled(false);
0713 
0714     KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger");
0715     connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger()));
0716 
0717     d->createTemplate = actionManager()->createAction("create_template");
0718     connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate()));
0719 
0720     d->createCopy = actionManager()->createAction("create_copy");
0721     connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy()));
0722 
0723     d->openResourcesDirectory = actionManager()->createAction("open_resources_directory");
0724     connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory()));
0725 
0726     d->rotateCanvasRight   = actionManager()->createAction("rotate_canvas_right");
0727     d->rotateCanvasLeft    = actionManager()->createAction("rotate_canvas_left");
0728     d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation");
0729     d->wrapAroundAction    = actionManager()->createAction("wrap_around_mode");
0730     d->wrapAroundHVAxisAction = actionManager()->createAction("wrap_around_hv_axis");
0731     d->wrapAroundHAxisAction  = actionManager()->createAction("wrap_around_h_axis");
0732     d->wrapAroundVAxisAction  = actionManager()->createAction("wrap_around_v_axis");
0733     d->wrapAroundAxisActions = new QActionGroup(this);
0734     d->wrapAroundAxisActions->addAction(d->wrapAroundHVAxisAction);
0735     d->wrapAroundAxisActions->addAction(d->wrapAroundHAxisAction);
0736     d->wrapAroundAxisActions->addAction(d->wrapAroundVAxisAction);
0737     d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode");
0738     d->softProof           = actionManager()->createAction("softProof");
0739     d->gamutCheck          = actionManager()->createAction("gamutCheck");
0740 
0741     KisAction *tAction = actionManager()->createAction("showStatusBar");
0742     tAction->setChecked(cfg.showStatusBar());
0743     connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool)));
0744 
0745     tAction = actionManager()->createAction("view_show_canvas_only");
0746     tAction->setChecked(false);
0747     connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool)));
0748 
0749     //Workaround, by default has the same shortcut as mirrorCanvas
0750     KisAction *a = dynamic_cast<KisAction*>(actionCollection()->action("format_italic"));
0751     if (a) {
0752         a->setDefaultShortcut(QKeySequence());
0753     }
0754 
0755     actionManager()->createAction("ruler_pixel_multiple2");
0756     d->showRulersAction = actionManager()->createAction("view_ruler");
0757     d->showRulersAction->setChecked(cfg.showRulers());
0758     connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool)));
0759 
0760     d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse");
0761     d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse());
0762     connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool)));
0763 
0764     d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct");
0765 
0766     d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, "");
0767     d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, "");
0768 
0769     d->zoomToFit = actionManager()->createAction("zoom_to_fit");
0770     d->zoomToFitWidth = actionManager()->createAction("zoom_to_fit_width");
0771     d->zoomToFitHeight = actionManager()->createAction("zoom_to_fit_height");
0772     d->toggleZoomToFit = actionManager()->createAction("toggle_zoom_to_fit");
0773 
0774     d->resetDisplay = actionManager()->createAction("reset_display");
0775 
0776     d->viewPrintSize = actionManager()->createAction("view_print_size");
0777 
0778     d->actionAuthor  = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this);
0779     connect(d->actionAuthor, SIGNAL(triggered(QString)), this, SLOT(changeAuthorProfile(QString)));
0780     actionCollection()->addAction("settings_active_author", d->actionAuthor);
0781     slotUpdateAuthorProfileActions();
0782 
0783     d->showPixelGrid = actionManager()->createAction("view_pixel_grid");
0784     slotUpdatePixelGridAction();
0785 
0786     d->toggleFgBg = actionManager()->createAction("toggle_fg_bg");
0787     connect(d->toggleFgBg, SIGNAL(triggered(bool)), this, SLOT(slotToggleFgBg()));
0788 
0789     d->resetFgBg =  actionManager()->createAction("reset_fg_bg");
0790     connect(d->resetFgBg, SIGNAL(triggered(bool)), this, SLOT(slotResetFgBg()));
0791 
0792     d->toggleBrushOutline =  actionManager()->createAction("toggle_brush_outline");
0793     connect(d->toggleBrushOutline, SIGNAL(triggered(bool)), this, SLOT(slotToggleBrushOutline()));
0794 
0795 }
0796 
0797 void KisViewManager::setupManagers()
0798 {
0799     // Create the managers for filters, selections, layers etc.
0800     // XXX: When the current layer changes, call updateGUI on all
0801     // managers
0802 
0803     d->filterManager.setup(actionCollection(), actionManager());
0804 
0805     d->selectionManager.setup(actionManager());
0806 
0807     d->guidesManager.setup(actionManager());
0808 
0809     d->nodeManager.setup(actionCollection(), actionManager());
0810 
0811     d->imageManager.setup(actionManager());
0812 
0813     d->gridManager.setup(actionManager());
0814 
0815     d->paintingAssistantsManager.setup(actionManager());
0816 
0817     d->canvasControlsManager.setup(actionManager());
0818 
0819     d->mirrorManager.setup(actionCollection());
0820 }
0821 
0822 void KisViewManager::updateGUI()
0823 {
0824     d->guiUpdateCompressor.start();
0825 }
0826 
0827 KisNodeManager * KisViewManager::nodeManager() const
0828 {
0829     return &d->nodeManager;
0830 }
0831 
0832 KisActionManager* KisViewManager::actionManager() const
0833 {
0834     return &d->actionManager;
0835 }
0836 
0837 KisGridManager * KisViewManager::gridManager() const
0838 {
0839     return &d->gridManager;
0840 }
0841 
0842 KisGuidesManager * KisViewManager::guidesManager() const
0843 {
0844     return &d->guidesManager;
0845 }
0846 
0847 KisDocument *KisViewManager::document() const
0848 {
0849     if (d->currentImageView && d->currentImageView->document()) {
0850         return d->currentImageView->document();
0851     }
0852     return 0;
0853 }
0854 
0855 int KisViewManager::viewCount() const
0856 {
0857     KisMainWindow *mw  = qobject_cast<KisMainWindow*>(d->mainWindow);
0858     if (mw) {
0859         return mw->viewCount();
0860     }
0861     return 0;
0862 }
0863 
0864 bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force)
0865 {
0866     const int busyWaitDelay = 1000;
0867     KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow);
0868     dialog.blockIfImageIsBusy();
0869 
0870     return dialog.result() == QDialog::Accepted;
0871 }
0872 
0873 
0874 bool KisViewManager::blockUntilOperationsFinished(KisImageSP image)
0875 {
0876     return d->blockUntilOperationsFinishedImpl(image, false);
0877 }
0878 
0879 void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image)
0880 {
0881     d->blockUntilOperationsFinishedImpl(image, true);
0882 }
0883 
0884 void KisViewManager::slotCreateTemplate()
0885 {
0886     if (!document()) return;
0887     KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow());
0888 }
0889 
0890 void KisViewManager::slotCreateCopy()
0891 {
0892     KisDocument *srcDoc = document();
0893     if (!srcDoc) return;
0894 
0895     if (!this->blockUntilOperationsFinished(srcDoc->image())) return;
0896 
0897     KisDocument *doc = 0;
0898     {
0899         KisImageReadOnlyBarrierLock l(srcDoc->image());
0900         doc = srcDoc->clone(true);
0901     }
0902     KIS_SAFE_ASSERT_RECOVER_RETURN(doc);
0903 
0904     QString name = srcDoc->documentInfo()->aboutInfo("name");
0905     if (name.isEmpty()) {
0906         name = document()->path();
0907     }
0908     name = i18n("%1 (Copy)", name);
0909     doc->documentInfo()->setAboutInfo("title", name);
0910     doc->resetPath();
0911 
0912     KisPart::instance()->addDocument(doc);
0913     KisMainWindow *mw  = qobject_cast<KisMainWindow*>(d->mainWindow);
0914     mw->addViewAndNotifyLoadingCompleted(doc);
0915 }
0916 
0917 
0918 QMainWindow* KisViewManager::qtMainWindow() const
0919 {
0920     if (d->mainWindow)
0921         return d->mainWindow;
0922 
0923     //Fallback for when we have not yet set the main window.
0924     QMainWindow* w = qobject_cast<QMainWindow*>(qApp->activeWindow());
0925     if(w)
0926         return w;
0927 
0928     return mainWindow();
0929 }
0930 
0931 void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow)
0932 {
0933     d->mainWindow = newMainWindow;
0934 }
0935 
0936 void KisViewManager::slotDocumentSaved()
0937 {
0938     d->saveIncremental->setEnabled(true);
0939     d->saveIncrementalBackup->setEnabled(true);
0940 }
0941 
0942 QString KisViewManager::canonicalPath()
0943 {
0944 #ifdef Q_OS_ANDROID
0945     QString path = QFileInfo(document()->localFilePath()).canonicalPath();
0946     // if the path is based on a document tree then a directory would be returned. So check if it exists and more
0947     // importantly check if we have permissions
0948     if (QDir(path).exists()) {
0949         return path;
0950     } else {
0951         KoFileDialog dialog(nullptr, KoFileDialog::ImportDirectory, "OpenDirectory");
0952         dialog.setDirectoryUrl(QUrl(document()->localFilePath()));
0953         return dialog.filename();
0954     }
0955 #else
0956     return QFileInfo(document()->localFilePath()).canonicalPath();
0957 #endif
0958 }
0959 
0960 void KisViewManager::slotSaveIncremental()
0961 {
0962     if (!document()) return;
0963 
0964     if (document()->path().isEmpty()) {
0965         KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
0966         mw->saveDocument(document(), true, false);
0967         return;
0968     }
0969 
0970     bool foundVersion;
0971     bool fileAlreadyExists;
0972     bool isBackup;
0973     QString version = "000";
0974     QString newVersion;
0975     QString letter;
0976     QString path = canonicalPath();
0977 
0978     QString fileName = QFileInfo(document()->localFilePath()).fileName();
0979 
0980     // Find current version filenames
0981     // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well
0982     // Considering our incremental version and backup scheme, format is filename_001~001.ext
0983     QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
0984     regex.indexIn(fileName);     //  Perform the search
0985     QStringList matches = regex.capturedTexts();
0986     foundVersion = matches.at(0).isEmpty() ? false : true;
0987 
0988     // Ensure compatibility with Save Incremental Backup
0989     // If this regex is not kept separate, the entire algorithm needs modification;
0990     // It's simpler to just add this.
0991     QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
0992     regexAux.indexIn(fileName);     //  Perform the search
0993     QStringList matchesAux = regexAux.capturedTexts();
0994     isBackup = matchesAux.at(0).isEmpty() ? false : true;
0995 
0996     // If the filename has a version, prepare it for incrementation
0997     if (foundVersion) {
0998         version = matches.at(matches.count() - 1);     //  Look at the last index, we don't care about other matches
0999         if (version.contains(QRegExp("[a-z]"))) {
1000             version.chop(1);             //  Trim "."
1001             letter = version.right(1);   //  Save letter
1002             version.chop(1);             //  Trim letter
1003         } else {
1004             version.chop(1);             //  Trim "."
1005         }
1006         version.remove(0, 1);            //  Trim "_"
1007     } else {
1008         // TODO: this will not work with files extensions like jp2
1009         // ...else, simply add a version to it so the next loop works
1010         QRegExp regex2("[.][a-z]{2,4}$");  //  Heuristic to find file extension
1011         regex2.indexIn(fileName);
1012         QStringList matches2 = regex2.capturedTexts();
1013         QString extensionPlusVersion = matches2.at(0);
1014         extensionPlusVersion.prepend(version);
1015         extensionPlusVersion.prepend("_");
1016         fileName.replace(regex2, extensionPlusVersion);
1017     }
1018 
1019     // Prepare the base for new version filename
1020     int intVersion = version.toInt(0);
1021     ++intVersion;
1022     QString baseNewVersion = QString::number(intVersion);
1023     while (baseNewVersion.length() < version.length()) {
1024         baseNewVersion.prepend("0");
1025     }
1026 
1027     // Check if the file exists under the new name and search until options are exhausted (test appending a to z)
1028     do {
1029         newVersion = baseNewVersion;
1030         newVersion.prepend("_");
1031         if (!letter.isNull()) newVersion.append(letter);
1032         if (isBackup) {
1033             newVersion.append("~");
1034         } else {
1035             newVersion.append(".");
1036         }
1037         fileName.replace(regex, newVersion);
1038         fileAlreadyExists = QFileInfo(path + '/' + fileName).exists();
1039         if (fileAlreadyExists) {
1040             if (!letter.isNull()) {
1041                 char letterCh = letter.at(0).toLatin1();
1042                 ++letterCh;
1043                 letter = QString(QChar(letterCh));
1044             } else {
1045                 letter = 'a';
1046             }
1047         }
1048     } while (fileAlreadyExists && letter != "{");  // x, y, z, {...
1049 
1050     if (letter == "{") {
1051         QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number"));
1052         return;
1053     }
1054     QString newFilePath = path + '/' + fileName;
1055     document()->setFileBatchMode(true);
1056     document()->saveAs(newFilePath, document()->mimeType(), true);
1057     document()->setFileBatchMode(false);
1058     KisPart::instance()->queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl::fromLocalFile(newFilePath),
1059                                                                       QUrl::fromLocalFile(document()->path()));
1060 }
1061 
1062 void KisViewManager::slotSaveIncrementalBackup()
1063 {
1064     if (!document()) return;
1065 
1066     if (document()->path().isEmpty()) {
1067         KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
1068         mw->saveDocument(document(), true, false);
1069         return;
1070     }
1071 
1072     bool workingOnBackup;
1073     bool fileAlreadyExists;
1074     QString version = "000";
1075     QString newVersion;
1076     QString letter;
1077     QString path = canonicalPath();
1078     QString fileName = QFileInfo(document()->localFilePath()).fileName();
1079 
1080     // First, discover if working on a backup file, or a normal file
1081     QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]");
1082     regex.indexIn(fileName);     //  Perform the search
1083     QStringList matches = regex.capturedTexts();
1084     workingOnBackup = matches.at(0).isEmpty() ? false : true;
1085 
1086     if (workingOnBackup) {
1087         // Try to save incremental version (of backup), use letter for alt versions
1088         version = matches.at(matches.count() - 1);     //  Look at the last index, we don't care about other matches
1089         if (version.contains(QRegExp("[a-z]"))) {
1090             version.chop(1);             //  Trim "."
1091             letter = version.right(1);   //  Save letter
1092             version.chop(1);             //  Trim letter
1093         } else {
1094             version.chop(1);             //  Trim "."
1095         }
1096         version.remove(0, 1);            //  Trim "~"
1097 
1098         // Prepare the base for new version filename
1099         int intVersion = version.toInt(0);
1100         ++intVersion;
1101         QString baseNewVersion = QString::number(intVersion);
1102         QString backupFileName = document()->localFilePath();
1103         while (baseNewVersion.length() < version.length()) {
1104             baseNewVersion.prepend("0");
1105         }
1106 
1107         // Check if the file exists under the new name and search until options are exhausted (test appending a to z)
1108         do {
1109             newVersion = baseNewVersion;
1110             newVersion.prepend("~");
1111             if (!letter.isNull()) newVersion.append(letter);
1112             newVersion.append(".");
1113             backupFileName.replace(regex, newVersion);
1114             fileAlreadyExists = QFile(path + '/' + backupFileName).exists();
1115             if (fileAlreadyExists) {
1116                 if (!letter.isNull()) {
1117                     char letterCh = letter.at(0).toLatin1();
1118                     ++letterCh;
1119                     letter = QString(QChar(letterCh));
1120                 } else {
1121                     letter = 'a';
1122                 }
1123             }
1124         } while (fileAlreadyExists && letter != "{");  // x, y, z, {...
1125 
1126         if (letter == "{") {
1127             QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number"));
1128             return;
1129         }
1130         QFile::copy(path + '/' + fileName, path + '/' + backupFileName);
1131         document()->saveAs(path + '/' + fileName, document()->mimeType(), true);
1132     }
1133     else { // if NOT working on a backup...
1134         // Navigate directory searching for latest backup version, ignore letters
1135         const quint8 HARDCODED_DIGIT_COUNT = 3;
1136         QString baseNewVersion = "000";
1137         QString backupFileName = QFileInfo(document()->localFilePath()).fileName();
1138         QRegExp regex2("[.][a-z]{2,4}$");  //  Heuristic to find file extension
1139         regex2.indexIn(backupFileName);
1140         QStringList matches2 = regex2.capturedTexts();
1141         QString extensionPlusVersion = matches2.at(0);
1142         extensionPlusVersion.prepend(baseNewVersion);
1143         extensionPlusVersion.prepend("~");
1144         backupFileName.replace(regex2, extensionPlusVersion);
1145 
1146         // Save version with 1 number higher than the highest version found ignoring letters
1147         do {
1148             newVersion = baseNewVersion;
1149             newVersion.prepend("~");
1150             newVersion.append(".");
1151             backupFileName.replace(regex, newVersion);
1152             fileAlreadyExists = QFile(path + '/' + backupFileName).exists();
1153             if (fileAlreadyExists) {
1154                 // Prepare the base for new version filename, increment by 1
1155                 int intVersion = baseNewVersion.toInt(0);
1156                 ++intVersion;
1157                 baseNewVersion = QString::number(intVersion);
1158                 while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) {
1159                     baseNewVersion.prepend("0");
1160                 }
1161             }
1162         } while (fileAlreadyExists);
1163 
1164         // Save both as backup and on current file for interapplication workflow
1165         document()->setFileBatchMode(true);
1166         QFile::copy(path + '/' + fileName, path + '/' + backupFileName);
1167         document()->saveAs(path + '/' + fileName, document()->mimeType(), true);
1168         document()->setFileBatchMode(false);
1169     }
1170 }
1171 
1172 void KisViewManager::disableControls()
1173 {
1174     // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel
1175     // this is for Bug 250944
1176     // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool
1177     // see KisToolFreehand::initPaint() and endPaint()
1178     d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter);
1179     Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
1180         child->installEventFilter(&d->blockingEventFilter);
1181     }
1182 }
1183 
1184 void KisViewManager::enableControls()
1185 {
1186     d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter);
1187     Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
1188         child->removeEventFilter(&d->blockingEventFilter);
1189     }
1190 }
1191 
1192 void KisViewManager::showStatusBar(bool toggled)
1193 {
1194     KisMainWindow *mw = mainWindow();
1195     if(mw && mw->statusBar()) {
1196         mw->statusBar()->setVisible(toggled);
1197         KisConfig cfg(false);
1198         cfg.setShowStatusBar(toggled);
1199     }
1200 }
1201 
1202 void KisViewManager::notifyWorkspaceLoaded()
1203 {
1204     d->canvasStateInNormalMode.clear();
1205     d->canvasStateInCanvasOnlyMode.clear();
1206     d->canvasOnlyOptions = std::nullopt;
1207 }
1208 
1209 void KisViewManager::switchCanvasOnly(bool toggled)
1210 {
1211     KisConfig cfg(false);
1212     KisMainWindow *main = mainWindow();
1213 
1214     if(!main) {
1215         dbgUI << "Unable to switch to canvas-only mode, main window not found";
1216         return;
1217     }
1218 
1219     cfg.writeEntry("CanvasOnlyActive", toggled);
1220 
1221     KisViewManagerPrivate::CanvasOnlyOptions options(cfg);
1222 
1223     if (toggled) {
1224         d->canvasStateInNormalMode = qtMainWindow()->saveState();
1225     } else {
1226         d->canvasStateInCanvasOnlyMode = qtMainWindow()->saveState();
1227         d->canvasOnlyOptions = options;
1228     }
1229 
1230     if (options.hideStatusbarFullscreen) {
1231         if (main->statusBar()) {
1232             if (!toggled) {
1233                 if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) {
1234                     if (main->statusBar()->property("wasvisible").toBool()) {
1235                         main->statusBar()->setVisible(true);
1236                     }
1237                 }
1238             }
1239             else {
1240                 main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible());
1241                 main->statusBar()->setVisible(false);
1242             }
1243         }
1244     }
1245 
1246     if (options.hideDockersFullscreen) {
1247         KisAction* action = qobject_cast<KisAction*>(main->actionCollection()->action("view_toggledockers"));
1248         if (action) {
1249             action->setCheckable(true);
1250             if (toggled) {
1251                 if (action->isChecked()) {
1252                     cfg.setShowDockers(action->isChecked());
1253                     action->setChecked(false);
1254                 } else {
1255                     cfg.setShowDockers(false);
1256                 }
1257             } else {
1258                 action->setChecked(cfg.showDockers());
1259             }
1260         }
1261     }
1262 
1263     // QT in windows does not return to maximized upon 4th tab in a row
1264     // https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/
1265     if (options.hideTitlebarFullscreen && !cfg.fullscreenMode()) {
1266         if(toggled) {
1267             main->setWindowState( main->windowState() | Qt::WindowFullScreen);
1268         } else {
1269             main->setWindowState( main->windowState() & ~Qt::WindowFullScreen);
1270         }
1271     }
1272 
1273     if (options.hideMenuFullscreen) {
1274         if (!toggled) {
1275             if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) {
1276                 if (main->menuBar()->property("wasvisible").toBool()) {
1277                     main->menuBar()->setVisible(true);
1278                 }
1279             }
1280         }
1281         else {
1282             main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible());
1283             main->menuBar()->setVisible(false);
1284         }
1285     }
1286 
1287     if (options.hideToolbarFullscreen) {
1288         QList<QToolBar*> toolBars = main->findChildren<QToolBar*>();
1289         Q_FOREACH (QToolBar* toolbar, toolBars) {
1290             if (!toggled) {
1291                 if (toolbar->dynamicPropertyNames().contains("wasvisible")) {
1292                     if (toolbar->property("wasvisible").toBool()) {
1293                         toolbar->setVisible(true);
1294                     }
1295                 }
1296             }
1297             else {
1298                 toolbar->setProperty("wasvisible", toolbar->isVisible());
1299                 toolbar->setVisible(false);
1300             }
1301         }
1302     }
1303 
1304     showHideScrollbars();
1305 
1306     if (toggled) {
1307         if (!d->canvasStateInCanvasOnlyMode.isEmpty() &&
1308             d->canvasOnlyOptions &&
1309             *d->canvasOnlyOptions == options) {
1310 
1311             /**
1312              * Restore state uses the current layout state of the window,
1313              * but removal of the menu will be backed into this state after
1314              * receiving of some events in the event queue. Hence we cannot
1315              * apply the application of the saved state directly. We need to
1316              * postpone that via the events queue.
1317              *
1318              * See https://bugs.kde.org/show_bug.cgi?id=475973
1319              */
1320             QTimer::singleShot(0, this, [this] () {
1321                 this->mainWindow()->restoreState(d->canvasStateInCanvasOnlyMode);
1322             });
1323         }
1324 
1325                // show a fading heads-up display about the shortcut to go back
1326         showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.",
1327                                  actionCollection()->action("view_show_canvas_only")->shortcut().toString(QKeySequence::NativeText)), QIcon(),
1328                             2000,
1329                             KisFloatingMessage::Low);
1330     }
1331     else {
1332         if (!d->canvasStateInNormalMode.isEmpty()) {
1333             main->restoreState(d->canvasStateInNormalMode);
1334         }
1335     }
1336 
1337 }
1338 
1339 void KisViewManager::toggleTabletLogger()
1340 {
1341     d->inputManager.toggleTabletLogger();
1342 }
1343 
1344 void KisViewManager::openResourcesDirectory()
1345 {
1346     QString resourcePath = KisResourceLocator::instance()->resourceLocationBase();
1347 #ifdef Q_OS_WIN
1348 
1349     QString folderInStandardAppData;
1350     QString folderInPrivateAppData;
1351     KoResourcePaths::getAllUserResourceFoldersLocationsForWindowsStore(folderInStandardAppData, folderInPrivateAppData);
1352 
1353     if (!folderInPrivateAppData.isEmpty()) {
1354 
1355         const auto pathToDisplay = [](const QString &path) {
1356             // Due to how Unicode word wrapping works, the string does not
1357             // wrap after backslashes in Qt 5.12. We don't want the path to
1358             // become too long, so we add a U+200B ZERO WIDTH SPACE to allow
1359             // wrapping. The downside is that we cannot let the user select
1360             // and copy the path because it now contains invisible unicode
1361             // code points.
1362             // See: https://bugreports.qt.io/browse/QTBUG-80892
1363             return QDir::toNativeSeparators(path).replace(QChar('\\'), QStringLiteral(u"\\\u200B"));
1364         };
1365 
1366         QMessageBox mbox(qApp->activeWindow());
1367         mbox.setIcon(QMessageBox::Information);
1368         mbox.setWindowTitle(i18nc("@title:window resource folder", "Open Resource Folder"));
1369         // Similar text is also used in kis_dlg_preferences.cc
1370 
1371         mbox.setText(i18nc("@info resource folder",
1372             "<p>You are using the Microsoft Store package version of Krita. "
1373             "Even though Krita can be configured to place resources under the "
1374             "user AppData location, Windows may actually store the files "
1375             "inside a private app location.</p>\n"
1376             "<p>You should check both locations to determine where "
1377             "the files are located.</p>\n"
1378             "<p><b>User AppData</b>:<br/>\n"
1379             "%1</p>\n"
1380             "<p><b>Private app location</b>:<br/>\n"
1381             "%2</p>",
1382             pathToDisplay(folderInStandardAppData),
1383             pathToDisplay(folderInPrivateAppData)
1384         ));
1385         mbox.setTextInteractionFlags(Qt::NoTextInteraction);
1386 
1387         const auto *btnOpenUserAppData = mbox.addButton(i18nc("@action:button resource folder", "Open in &user AppData"), QMessageBox::AcceptRole);
1388         const auto *btnOpenPrivateAppData = mbox.addButton(i18nc("@action:button resource folder", "Open in &private app location"), QMessageBox::AcceptRole);
1389 
1390         mbox.addButton(QMessageBox::Close);
1391         mbox.setDefaultButton(QMessageBox::Close);
1392         mbox.exec();
1393 
1394         if (mbox.clickedButton() == btnOpenPrivateAppData) {
1395             resourcePath = folderInPrivateAppData;
1396         } else if (mbox.clickedButton() == btnOpenUserAppData) {
1397             // no-op: resourcePath = resourceDir.absolutePath();
1398         } else {
1399             return;
1400         }
1401 
1402 
1403     }
1404 #endif
1405     QDesktopServices::openUrl(QUrl::fromLocalFile(resourcePath));
1406 }
1407 
1408 void KisViewManager::updateIcons()
1409 {
1410     if (mainWindow()) {
1411         QList<QDockWidget*> dockers = mainWindow()->dockWidgets();
1412         Q_FOREACH (QDockWidget* dock, dockers) {
1413             KoDockWidgetTitleBar* titlebar = dynamic_cast<KoDockWidgetTitleBar*>(dock->titleBarWidget());
1414             if (titlebar) {
1415                 titlebar->updateIcons();
1416             }
1417             if (qobject_cast<KoToolDocker*>(dock)) {
1418                 // Tool options widgets icons are updated by KoToolManager
1419                 continue;
1420             }
1421             QObjectList objects;
1422             objects.append(dock);
1423             while (!objects.isEmpty()) {
1424                 QObject* object = objects.takeFirst();
1425                 objects.append(object->children());
1426                 KisIconUtils::updateIconCommon(object);
1427             }
1428         }
1429     }
1430 }
1431 
1432 void KisViewManager::guiUpdateTimeout()
1433 {
1434     d->nodeManager.updateGUI();
1435     d->selectionManager.updateGUI();
1436     d->filterManager.updateGUI();
1437     if (zoomManager()) {
1438         zoomManager()->updateGuiAfterDocumentSize();
1439         zoomManager()->slotZoomLevelsChanged();
1440     }
1441     d->gridManager.updateGUI();
1442     d->actionManager.updateGUI();
1443 }
1444 
1445 void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
1446 {
1447     if (!d->currentImageView) return;
1448     d->currentImageView->showFloatingMessage(message, icon, timeout, priority, alignment);
1449 
1450     emit floatingMessageRequested(message, icon.name());
1451 }
1452 
1453 KisMainWindow *KisViewManager::mainWindow() const
1454 {
1455     return qobject_cast<KisMainWindow*>(d->mainWindow);
1456 }
1457 
1458 QWidget *KisViewManager::mainWindowAsQWidget() const
1459 {
1460     return mainWindow();
1461 }
1462 
1463 
1464 void KisViewManager::showHideScrollbars()
1465 {
1466     if (!d->currentImageView) return;
1467     if (!d->currentImageView->canvasController()) return;
1468 
1469     KisConfig cfg(true);
1470     bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked();
1471 
1472     if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) {
1473         d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1474         d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1475     } else {
1476         d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
1477         d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
1478     }
1479 }
1480 
1481 void KisViewManager::slotSaveShowRulersState(bool value)
1482 {
1483     KisConfig cfg(false);
1484     cfg.setShowRulers(value);
1485 }
1486 
1487 void KisViewManager::slotSaveRulersTrackMouseState(bool value)
1488 {
1489     KisConfig cfg(false);
1490     cfg.setRulersTrackMouse(value);
1491 }
1492 
1493 void KisViewManager::setShowFloatingMessage(bool show)
1494 {
1495     d->showFloatingMessage = show;
1496 }
1497 
1498 void KisViewManager::changeAuthorProfile(const QString &profileName)
1499 {
1500     KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author");
1501     if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) {
1502         appAuthorGroup.writeEntry("active-profile", "");
1503     } else {
1504         appAuthorGroup.writeEntry("active-profile", profileName);
1505     }
1506     appAuthorGroup.sync();
1507     Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
1508         doc->documentInfo()->updateParameters();
1509     }
1510 }
1511 
1512 void KisViewManager::slotUpdateAuthorProfileActions()
1513 {
1514     Q_ASSERT(d->actionAuthor);
1515     if (!d->actionAuthor) {
1516         return;
1517     }
1518     d->actionAuthor->clear();
1519     d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous"));
1520 
1521     KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author");
1522     QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
1523     QString authorInfo = KoResourcePaths::getAppDataLocation() + "/authorinfo/";
1524     QStringList filters = QStringList() << "*.authorinfo";
1525     QDir dir(authorInfo);
1526     Q_FOREACH(QString entry, dir.entryList(filters)) {
1527         int ln = QString(".authorinfo").size();
1528         entry.chop(ln);
1529         if (!profiles.contains(entry)) {
1530             profiles.append(entry);
1531         }
1532     }
1533     Q_FOREACH (const QString &profile , profiles) {
1534         d->actionAuthor->addAction(profile);
1535     }
1536 
1537     KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author");
1538     QString profileName = appAuthorGroup.readEntry("active-profile", "");
1539 
1540     if (profileName == "anonymous" || profileName.isEmpty()) {
1541         d->actionAuthor->setCurrentItem(0);
1542     } else if (profiles.contains(profileName)) {
1543         d->actionAuthor->setCurrentAction(profileName);
1544     }
1545 }
1546 
1547 void KisViewManager::slotUpdatePixelGridAction()
1548 {
1549     KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid);
1550 
1551     KisSignalsBlocker b(d->showPixelGrid);
1552 
1553     KisConfig cfg(true);
1554     d->showPixelGrid->setChecked(cfg.pixelGridEnabled() && cfg.useOpenGL());
1555 }
1556 
1557 void KisViewManager::updatePrintSizeAction(bool canvasMappingMode)
1558 {
1559     d->viewPrintSize->setChecked(canvasMappingMode);
1560 }
1561 
1562 void KisViewManager::slotActivateTransformTool()
1563 {
1564     if(KoToolManager::instance()->activeToolId() == "KisToolTransform") {
1565         KoToolBase* tool = KoToolManager::instance()->toolById(canvasBase(), "KisToolTransform");
1566 
1567         QSet<KoShape*> dummy;
1568         // Start a new stroke
1569         tool->deactivate();
1570         tool->activate(dummy);
1571     }
1572 
1573     KoToolManager::instance()->switchToolRequested("KisToolTransform");
1574 }
1575 
1576 void KisViewManager::slotToggleFgBg()
1577 {
1578 
1579     KoColor newFg = d->canvasResourceManager.backgroundColor();
1580     KoColor newBg = d->canvasResourceManager.foregroundColor();
1581 
1582     /**
1583      * NOTE: Some of color selectors do not differentiate foreground
1584      *       and background colors, so if one wants them to end up
1585      *       being set up to foreground color, it should be set the
1586      *       last.
1587      */
1588     d->canvasResourceManager.setBackgroundColor(newBg);
1589     d->canvasResourceManager.setForegroundColor(newFg);
1590 }
1591 
1592 void KisViewManager::slotResetFgBg()
1593 {
1594     // see a comment in slotToggleFgBg()
1595     d->canvasResourceManager.setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8()));
1596     d->canvasResourceManager.setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()));
1597 }
1598 
1599 void KisViewManager::slotToggleBrushOutline()
1600 {
1601     KisConfig cfg(true);
1602 
1603     OutlineStyle style;
1604 
1605     if (cfg.newOutlineStyle() != OUTLINE_NONE) {
1606         style = OUTLINE_NONE;
1607         cfg.setLastUsedOutlineStyle(cfg.newOutlineStyle());
1608     } else {
1609         style = cfg.lastUsedOutlineStyle();
1610         cfg.setLastUsedOutlineStyle(OUTLINE_NONE);
1611     }
1612 
1613     cfg.setNewOutlineStyle(style);
1614 
1615     emit brushOutlineToggled();
1616 }
1617 
1618 void KisViewManager::slotResetRotation()
1619 {
1620     KisCanvasController *canvasController = d->currentImageView->canvasController();
1621     canvasController->resetCanvasRotation();
1622 }
1623 
1624 void KisViewManager::slotResetDisplay()
1625 {
1626     KisCanvasController *canvasController = d->currentImageView->canvasController();
1627     canvasController->resetCanvasRotation();
1628     canvasController->mirrorCanvas(false);
1629     zoomManager()->slotZoomToFit();
1630 }
1631 
1632 void KisViewManager::slotToggleFullscreen()
1633 {
1634     KisConfig cfg(false);
1635     KisMainWindow *main = mainWindow();
1636     main->viewFullscreen(!main->isFullScreen());
1637     cfg.fullscreenMode(main->isFullScreen());
1638 }