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 }