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

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Boudewijn Rempt <boud@valdyas.org>
0003  * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "KisView.h"
0009 
0010 #include "KisView_p.h"
0011 
0012 #include <KoDockFactoryBase.h>
0013 #include <KoDockRegistry.h>
0014 #include <KoDocumentInfo.h>
0015 #include <KoToolManager.h>
0016 
0017 #include <kis_icon.h>
0018 
0019 #include <kactioncollection.h>
0020 #include <klocalizedstring.h>
0021 #include <kis_debug.h>
0022 #include <kselectaction.h>
0023 #include <kconfiggroup.h>
0024 
0025 #include <QMenu>
0026 #include <QMessageBox>
0027 #include <QUrl>
0028 #include <QTemporaryFile>
0029 #include <QApplication>
0030 #include <QDesktopWidget>
0031 #include <QDockWidget>
0032 #include <QDragEnterEvent>
0033 #include <QDropEvent>
0034 #include <QImage>
0035 #include <QList>
0036 #include <QPrintDialog>
0037 #include <QToolBar>
0038 #include <QStatusBar>
0039 #include <QMoveEvent>
0040 #include <QMdiSubWindow>
0041 #include <QFileInfo>
0042 #include <QScreen>
0043 
0044 #include <kis_image.h>
0045 #include <kis_node.h>
0046 
0047 #include <kis_group_layer.h>
0048 #include <kis_layer.h>
0049 #include <kis_mask.h>
0050 #include <kis_selection.h>
0051 
0052 #include "KisDocument.h"
0053 #include "KisImportExportManager.h"
0054 #include "KisMainWindow.h"
0055 #include "KisMimeDatabase.h"
0056 #include "KisPart.h"
0057 #include "KisReferenceImagesDecoration.h"
0058 #include "KisRemoteFileFetcher.h"
0059 #include "KisSynchronizedConnection.h"
0060 #include "KisViewManager.h"
0061 #include "input/kis_input_manager.h"
0062 #include "kis_canvas2.h"
0063 #include "kis_canvas_controller.h"
0064 #include "kis_canvas_resource_provider.h"
0065 #include "kis_clipboard.h"
0066 #include "kis_config.h"
0067 #include "kis_file_layer.h"
0068 #include "kis_fill_painter.h"
0069 #include "kis_filter_manager.h"
0070 #include "kis_image_manager.h"
0071 #include "kis_import_catcher.h"
0072 #include "kis_mimedata.h"
0073 #include "kis_node_commands_adapter.h"
0074 #include "kis_node_manager.h"
0075 #include "kis_paint_layer.h"
0076 #include "kis_painting_assistants_decoration.h"
0077 #include "kis_resources_snapshot.h"
0078 #include "kis_selection_manager.h"
0079 #include "kis_shape_controller.h"
0080 #include "kis_signal_compressor.h"
0081 #include "kis_zoom_manager.h"
0082 #include "krita_utils.h"
0083 #include "processing/fill_processing_visitor.h"
0084 #include "widgets/kis_canvas_drop.h"
0085 #include <commands_new/KisMergeLabeledLayersCommand.h>
0086 #include <kis_stroke_strategy_undo_command_based.h>
0087 #include <commands_new/kis_processing_command.h>
0088 #include <commands_new/kis_update_command.h>
0089 #include <kis_command_utils.h>
0090 #include <KisScreenMigrationTracker.h>
0091 #include "kis_memory_statistics_server.h"
0092 #include "kformat.h"
0093 
0094 
0095 //static
0096 QString KisView::newObjectName()
0097 {
0098     static int s_viewIFNumber = 0;
0099     QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
0100     return name;
0101 }
0102 
0103 bool KisView::s_firstView = true;
0104 
0105 class Q_DECL_HIDDEN KisView::Private
0106 {
0107 public:
0108     Private(KisView *_q,
0109             KisDocument *document,
0110             KisViewManager *viewManager)
0111         : actionCollection(viewManager->actionCollection())
0112         , viewConverter()
0113         , canvasController(_q, viewManager->mainWindow(), viewManager->actionCollection())
0114         , canvas(&viewConverter, viewManager->canvasResourceProvider()->resourceManager(), viewManager->mainWindow(), _q, document->shapeController())
0115         , zoomManager(_q, &this->viewConverter, &this->canvasController)
0116         , viewManager(viewManager)
0117         , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE)
0118         , screenMigrationTracker(_q)
0119     {
0120     }
0121 
0122     bool inOperation {false}; //in the middle of an operation (no screen refreshing)?
0123 
0124     QPointer<KisDocument> document; // our KisDocument
0125     QWidget *tempActiveWidget {nullptr};
0126 
0127     KisKActionCollection* actionCollection {nullptr};
0128     KisCoordinatesConverter viewConverter;
0129     KisCanvasController canvasController;
0130     KisCanvas2 canvas;
0131     KisZoomManager zoomManager;
0132     KisViewManager *viewManager {nullptr};
0133     KisNodeSP currentNode;
0134     KisPaintingAssistantsDecorationSP paintingAssistantsDecoration;
0135     KisReferenceImagesDecorationSP referenceImagesDecoration;
0136     bool isCurrent {false};
0137     bool showFloatingMessage {true};
0138     QPointer<KisFloatingMessage> savedFloatingMessage;
0139     KisSignalCompressor floatingMessageCompressor;
0140     QMdiSubWindow *subWindow {nullptr};
0141 
0142     bool softProofing {false};
0143     bool gamutCheck {false};
0144 
0145     KisSynchronizedConnection<KisNodeSP> addNodeConnection;
0146     KisSynchronizedConnection<KisNodeSP> removeNodeConnection;
0147 
0148     KisScreenMigrationTracker screenMigrationTracker;
0149 
0150     // Hmm sorry for polluting the private class with such a big inner class.
0151     // At the beginning it was a little struct :)
0152     class StatusBarItem : public boost::equality_comparable<StatusBarItem>
0153     {
0154     public:
0155 
0156         StatusBarItem(QWidget * widget, int stretch, bool permanent)
0157             : m_widget(widget),
0158               m_stretch(stretch),
0159               m_permanent(permanent),
0160               m_connected(false),
0161               m_hidden(false) {}
0162 
0163         bool operator==(const StatusBarItem& rhs) {
0164             return m_widget == rhs.m_widget;
0165         }
0166 
0167         QWidget * widget() const {
0168             return m_widget;
0169         }
0170 
0171         void ensureItemShown(QStatusBar * sb) {
0172             Q_ASSERT(m_widget);
0173             if (!m_connected) {
0174                 if (m_permanent)
0175                     sb->addPermanentWidget(m_widget, m_stretch);
0176                 else
0177                     sb->addWidget(m_widget, m_stretch);
0178 
0179                 if(!m_hidden)
0180                     m_widget->show();
0181 
0182                 m_connected = true;
0183             }
0184         }
0185         void ensureItemHidden(QStatusBar * sb) {
0186             if (m_connected) {
0187                 m_hidden = m_widget->isHidden();
0188                 sb->removeWidget(m_widget);
0189                 m_widget->hide();
0190                 m_connected = false;
0191             }
0192         }
0193 
0194     private:
0195         QWidget * m_widget = 0;
0196         int m_stretch;
0197         bool m_permanent;
0198         bool m_connected = false;
0199         bool m_hidden = false;
0200 
0201     };
0202 
0203 };
0204 
0205 KisView::KisView(KisDocument *document, KisViewManager *viewManager, QWidget *parent)
0206     : QWidget(parent)
0207     , d(new Private(this, document, viewManager))
0208 {
0209     Q_ASSERT(document);
0210     setObjectName(newObjectName());
0211 
0212     d->document = document;
0213 
0214     setFocusPolicy(Qt::StrongFocus);
0215 
0216     QStatusBar * sb = statusBar();
0217     if (sb) { // No statusbar in e.g. konqueror
0218         connect(d->document, SIGNAL(statusBarMessage(QString,int)),
0219                 this, SLOT(slotSavingStatusMessage(QString,int)));
0220         connect(d->document, SIGNAL(clearStatusBarMessage()),
0221                 this, SLOT(slotClearStatusText()));
0222     }
0223 
0224     d->canvas.setup();
0225 
0226     KisConfig cfg(false);
0227 
0228     d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0229     d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0230     d->canvasController.setVastScrolling(cfg.vastScrolling());
0231     d->canvasController.setCanvas(&d->canvas);
0232 
0233     d->zoomManager.setup(d->actionCollection);
0234 
0235 
0236     connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged()));
0237     setAcceptDrops(true);
0238 
0239     connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
0240 
0241     d->referenceImagesDecoration = new KisReferenceImagesDecoration(this, document, /* viewReady = */ false);
0242     d->canvas.addDecoration(d->referenceImagesDecoration);
0243     d->referenceImagesDecoration->setVisible(true);
0244 
0245     d->paintingAssistantsDecoration = new KisPaintingAssistantsDecoration(this);
0246     d->canvas.addDecoration(d->paintingAssistantsDecoration);
0247     d->paintingAssistantsDecoration->setVisible(true);
0248 
0249     d->showFloatingMessage = cfg.showCanvasMessages();
0250     slotScreenOrResolutionChanged();
0251 
0252     connect(document, SIGNAL(sigReadWriteChanged(bool)), this, SLOT(slotUpdateDocumentTitle()));
0253     connect(document, SIGNAL(sigRecoveredChanged(bool)), this, SLOT(slotUpdateDocumentTitle()));
0254     connect(document, SIGNAL(sigPathChanged(QString)), this, SLOT(slotUpdateDocumentTitle()));
0255     connect(KisMemoryStatisticsServer::instance(),
0256             SIGNAL(sigUpdateMemoryStatistics()),
0257             SLOT(slotUpdateDocumentTitle()));
0258     connect(document, SIGNAL(modified(bool)), this, SLOT(setWindowModified(bool)));
0259     slotUpdateDocumentTitle();
0260     setWindowModified(document->isModified());
0261 }
0262 
0263 KisView::~KisView()
0264 {
0265     if (d->viewManager) {
0266         if (d->viewManager->filterManager()->isStrokeRunning()) {
0267             d->viewManager->filterManager()->cancelDialog();
0268         }
0269 
0270         d->viewManager->mainWindow()->notifyChildViewDestroyed(this);
0271     }
0272 
0273     image()->requestStrokeCancellation();
0274 
0275     /**
0276      * KisCanvas2 maintains direct connections to the image, so we should
0277      * disconnect it from the image before the destruction process starts
0278      */
0279     d->canvas.disconnectImage();
0280 
0281     KoToolManager::instance()->removeCanvasController(&d->canvasController);
0282     d->canvasController.setCanvas(0);
0283 
0284     KisPart::instance()->removeView(this);
0285     delete d;
0286 }
0287 
0288 void KisView::notifyCurrentStateChanged(bool isCurrent)
0289 {
0290     d->isCurrent = isCurrent;
0291 
0292     if (!d->isCurrent && d->savedFloatingMessage) {
0293         d->savedFloatingMessage->removeMessage();
0294     }
0295 
0296     KisInputManager *inputManager = globalInputManager();
0297     if (d->isCurrent) {
0298         inputManager->attachPriorityEventFilter(&d->canvasController);
0299     } else {
0300         inputManager->detachPriorityEventFilter(&d->canvasController);
0301     }
0302 
0303     /**
0304      * When current view is changed, currently selected node is also changed,
0305      * therefore we should update selection overlay mask
0306      */
0307     viewManager()->selectionManager()->selectionChanged();
0308 }
0309 
0310 bool KisView::isCurrent() const
0311 {
0312     return d->isCurrent;
0313 }
0314 
0315 void KisView::setShowFloatingMessage(bool show)
0316 {
0317     d->showFloatingMessage = show;
0318 }
0319 
0320 void KisView::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
0321 {
0322     if (!d->viewManager) return;
0323 
0324     if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) {
0325         if (d->savedFloatingMessage) {
0326             d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment);
0327         } else {
0328             d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment);
0329             d->savedFloatingMessage->setShowOverParent(true);
0330             d->savedFloatingMessage->setIcon(icon);
0331 
0332             connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage()));
0333             d->floatingMessageCompressor.start();
0334         }
0335     }
0336 }
0337 
0338 bool KisView::canvasIsMirrored() const
0339 {
0340     return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored();
0341 }
0342 
0343 void KisView::setViewManager(KisViewManager *view)
0344 {
0345     d->viewManager = view;
0346 
0347     KoToolManager::instance()->addController(&d->canvasController);
0348     KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController);
0349     KisShapeController* shapeController = dynamic_cast<KisShapeController*>(d->document->shapeController());
0350     shapeController->setInitialShapeForCanvas(&d->canvas);
0351 
0352     if (resourceProvider()) {
0353         resourceProvider()->slotImageSizeChanged();
0354     }
0355 
0356     if (d->viewManager && d->viewManager->nodeManager()) {
0357         d->viewManager->nodeManager()->nodesUpdated();
0358     }
0359 
0360     connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,QPointF)));
0361     connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
0362 
0363 
0364     d->addNodeConnection.connectSync(image(), &KisImage::sigNodeAddedAsync,
0365                                      this, &KisView::slotContinueAddNode);
0366 
0367     // executed in a context of an image thread
0368     connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)),
0369             SLOT(slotImageNodeRemoved(KisNodeSP)),
0370             Qt::DirectConnection);
0371 
0372     d->removeNodeConnection.connectOutputSlot(this, &KisView::slotContinueRemoveNode);
0373 
0374     d->viewManager->updateGUI();
0375 
0376     KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
0377 }
0378 
0379 KisViewManager* KisView::viewManager() const
0380 {
0381     return d->viewManager;
0382 }
0383 
0384 void KisView::slotContinueAddNode(KisNodeSP newActiveNode)
0385 {
0386     /**
0387      * When deleting the last layer, root node got selected. We should
0388      * fix it when the first layer is added back.
0389      *
0390      * Here we basically reimplement what Qt's view/model do. But
0391      * since they are not connected, we should do it manually.
0392      */
0393 
0394     if (!d->isCurrent &&
0395             (!d->currentNode || !d->currentNode->parent())) {
0396 
0397         d->currentNode = newActiveNode;
0398     }
0399 }
0400 
0401 
0402 void KisView::slotImageNodeRemoved(KisNodeSP node)
0403 {
0404     d->removeNodeConnection.start(KritaUtils::nearestNodeAfterRemoval(node));
0405 }
0406 
0407 void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode)
0408 {
0409     if (!d->isCurrent) {
0410         d->currentNode = newActiveNode;
0411     }
0412 }
0413 
0414 KoZoomController *KisView::zoomController() const
0415 {
0416     return d->zoomManager.zoomController();
0417 }
0418 
0419 KisZoomManager *KisView::zoomManager() const
0420 {
0421     return &d->zoomManager;
0422 }
0423 
0424 KisCanvasController *KisView::canvasController() const
0425 {
0426     return &d->canvasController;
0427 }
0428 
0429 KisCanvasResourceProvider *KisView::resourceProvider() const
0430 {
0431     if (d->viewManager) {
0432         return d->viewManager->canvasResourceProvider();
0433     }
0434     return 0;
0435 }
0436 
0437 KisInputManager* KisView::globalInputManager() const
0438 {
0439     return d->viewManager ? d->viewManager->inputManager() : 0;
0440 }
0441 
0442 KisCanvas2 *KisView::canvasBase() const
0443 {
0444     return &d->canvas;
0445 }
0446 
0447 KisImageWSP KisView::image() const
0448 {
0449     if (d->document) {
0450         return d->document->image();
0451     }
0452     return 0;
0453 }
0454 
0455 
0456 KisCoordinatesConverter *KisView::viewConverter() const
0457 {
0458     return &d->viewConverter;
0459 }
0460 
0461 void KisView::dragEnterEvent(QDragEnterEvent *event)
0462 {
0463     dbgUI << Q_FUNC_INFO
0464           << "Formats: " << event->mimeData()->formats()
0465           << "Urls: " << event->mimeData()->urls()
0466           << "Has images: " << event->mimeData()->hasImage();
0467     if (event->mimeData()->hasImage()
0468             || event->mimeData()->hasUrls()
0469             || event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")
0470             || event->mimeData()->hasFormat("krita/x-colorsetentry")
0471             || event->mimeData()->hasColor()) {
0472         event->accept();
0473 
0474         // activate view if it should accept the drop
0475         this->setFocus();
0476     } else {
0477         event->ignore();
0478     }
0479 }
0480 
0481 void KisView::dropEvent(QDropEvent *event)
0482 {
0483     KisImageWSP kisimage = image();
0484     Q_ASSERT(kisimage);
0485 
0486     QPoint imgCursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint();
0487     QRect imageBounds = kisimage->bounds();
0488     boost::optional<QPoint> forcedCenter;
0489 
0490     if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(imgCursorPos)) {
0491         forcedCenter = imgCursorPos;
0492     }
0493 
0494     dbgUI << Q_FUNC_INFO;
0495     dbgUI << "\t Formats: " << event->mimeData()->formats();
0496     dbgUI << "\t Urls: " << event->mimeData()->urls();
0497     dbgUI << "\t Has images: " << event->mimeData()->hasImage();
0498 
0499     if (event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")) {
0500         KisShapeController *kritaShapeController =
0501                 dynamic_cast<KisShapeController*>(d->document->shapeController());
0502 
0503         bool copyNode = true;
0504         QList<KisNodeSP> nodes;
0505 
0506         if (forcedCenter) {
0507             nodes = KisMimeData::loadNodesFastAndRecenter(*forcedCenter, event->mimeData(), kisimage, kritaShapeController, copyNode);
0508         } else {
0509             nodes = KisMimeData::loadNodesFast(event->mimeData(), kisimage, kritaShapeController, copyNode);
0510         }
0511 
0512         Q_FOREACH (KisNodeSP node, nodes) {
0513             if (node) {
0514                 KisNodeCommandsAdapter adapter(viewManager());
0515                 if (!viewManager()->nodeManager()->activeLayer()) {
0516                     adapter.addNode(node, kisimage->rootLayer() , 0);
0517                 } else {
0518                     adapter.addNode(node,
0519                                     viewManager()->nodeManager()->activeLayer()->parent(),
0520                                     viewManager()->nodeManager()->activeLayer());
0521                 }
0522             }
0523         }
0524     } else if (event->mimeData()->hasImage() || event->mimeData()->hasUrls()) {
0525         const auto *data = event->mimeData();
0526 
0527         KisCanvasDrop dlgAction;
0528 
0529         const auto callPos = QCursor::pos();
0530 
0531         const KisCanvasDrop::Action action = dlgAction.dropAs(*data, callPos);
0532 
0533         if (action == KisCanvasDrop::INSERT_AS_NEW_LAYER) {
0534             const QPair<bool, KisClipboard::PasteFormatBehaviour> source =
0535                 KisClipboard::instance()->askUserForSource(data);
0536 
0537             if (!source.first) {
0538                 dbgUI << "Paste event cancelled";
0539                 return;
0540             }
0541 
0542             if (source.second != KisClipboard::PASTE_FORMAT_CLIP) {
0543                 const QList<QUrl> &urls = data->urls();
0544                 const auto url = std::find_if(
0545                     urls.constBegin(),
0546                     urls.constEnd(),
0547                     [&](const QUrl &url) {
0548                         if (source.second
0549                             == KisClipboard::PASTE_FORMAT_DOWNLOAD) {
0550                             return !url.isLocalFile();
0551                         } else if (source.second
0552                                    == KisClipboard::PASTE_FORMAT_LOCAL) {
0553                             return url.isLocalFile();
0554                         } else {
0555                             return false;
0556                         }
0557                     });
0558 
0559                 if (url != urls.constEnd()) {
0560                     QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
0561                     tmp->setAutoRemove(true);
0562 
0563                     const QUrl localUrl = [&]() -> QUrl {
0564                         if (!url->isLocalFile()) {
0565                             // download the file and substitute the url
0566                             KisRemoteFileFetcher fetcher;
0567                             tmp->setFileName(url->fileName());
0568 
0569                             if (!fetcher.fetchFile(*url, tmp.data())) {
0570                                 warnUI << "Fetching" << *url << "failed";
0571                                 return {};
0572                             }
0573                             return QUrl::fromLocalFile(tmp->fileName());
0574                         }
0575                         return *url;
0576                     }();
0577 
0578                     if (localUrl.isLocalFile()) {
0579                         this->mainWindow()
0580                             ->viewManager()
0581                             ->imageManager()
0582                             ->importImage(localUrl);
0583                         this->activateWindow();
0584                         return;
0585                     }
0586                 }
0587             }
0588 
0589             KisPaintDeviceSP clip =
0590                 KisClipboard::instance()->clipFromBoardContents(data,
0591                                                                 QRect(),
0592                                                                 true,
0593                                                                 -1,
0594                                                                 false,
0595                                                                 source);
0596             if (clip) {
0597                 const auto pos = this->viewConverter()
0598                                      ->imageToDocument(imgCursorPos)
0599                                      .toPoint();
0600 
0601                 clip->moveTo(pos.x(), pos.y());
0602 
0603                 KisImportCatcher::adaptClipToImageColorSpace(clip,
0604                                                              this->image());
0605 
0606                 KisPaintLayerSP layer = new KisPaintLayer(
0607                     this->image(),
0608                     this->image()->nextLayerName() + " " + i18n("(pasted)"),
0609                     OPACITY_OPAQUE_U8,
0610                     clip);
0611                 KisNodeCommandsAdapter adapter(
0612                     this->mainWindow()->viewManager());
0613                 adapter.addNode(
0614                     layer,
0615                     this->mainWindow()->viewManager()->activeNode()->parent(),
0616                     this->mainWindow()->viewManager()->activeNode());
0617                 this->activateWindow();
0618                 return;
0619             }
0620         } else if (action == KisCanvasDrop::INSERT_AS_REFERENCE_IMAGE) {
0621             KisPaintDeviceSP clip =
0622                 KisClipboard::instance()->clipFromMimeData(data, QRect(), true);
0623             if (clip) {
0624                 KisImportCatcher::adaptClipToImageColorSpace(clip,
0625                                                              this->image());
0626 
0627                 auto *reference =
0628                     KisReferenceImage::fromPaintDevice(clip,
0629                                                        *this->viewConverter(),
0630                                                        this);
0631 
0632                 if (reference) {
0633                     if (data->hasUrls()) {
0634                         const auto &urls = data->urls();
0635                         const auto url = std::find_if(urls.constBegin(), urls.constEnd(), std::mem_fn(&QUrl::isLocalFile));
0636                         if (url != urls.constEnd()) {
0637                             reference->setFilename((*url).toLocalFile());
0638                         }
0639                     }
0640                     const auto pos = this->canvasBase()
0641                                          ->coordinatesConverter()
0642                                          ->widgetToImage(event->pos());
0643                     reference->setPosition(
0644                         (*this->viewConverter()).imageToDocument(pos));
0645                     this->canvasBase()
0646                         ->referenceImagesDecoration()
0647                         ->addReferenceImage(reference);
0648                     KoToolManager::instance()->switchToolRequested(
0649                         "ToolReferenceImages");
0650                     return;
0651                 }
0652             }
0653         } else if (action != KisCanvasDrop::NONE) {
0654             // multiple URLs detected OR about to open a document
0655 
0656             for (QUrl url : data->urls()) { // do copy it
0657                 QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
0658                 tmp->setAutoRemove(true);
0659 
0660                 if (!url.isLocalFile()) {
0661                     // download the file and substitute the url
0662                     KisRemoteFileFetcher fetcher;
0663                     tmp->setFileName(url.fileName());
0664 
0665                     if (!fetcher.fetchFile(url, tmp.data())) {
0666                         qWarning() << "Fetching" << url << "failed";
0667                         continue;
0668                     }
0669                     url = QUrl::fromLocalFile(tmp->fileName());
0670                 }
0671 
0672                 if (url.isLocalFile()) {
0673                     if (action == KisCanvasDrop::INSERT_MANY_LAYERS) {
0674                         this->mainWindow()
0675                             ->viewManager()
0676                             ->imageManager()
0677                             ->importImage(url);
0678                         this->activateWindow();
0679                     } else if (action == KisCanvasDrop::INSERT_MANY_FILE_LAYERS
0680                                || action
0681                                    == KisCanvasDrop::INSERT_AS_NEW_FILE_LAYER) {
0682                         KisNodeCommandsAdapter adapter(
0683                             this->mainWindow()->viewManager());
0684                         QFileInfo fileInfo(url.toLocalFile());
0685 
0686                         QString type =
0687                             KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
0688                         QStringList mimes =
0689                             KisImportExportManager::supportedMimeTypes(
0690                                 KisImportExportManager::Import);
0691 
0692                         if (!mimes.contains(type)) {
0693                             QString msg =
0694                                 KisImportExportErrorCode(
0695                                     ImportExportCodes::FileFormatNotSupported)
0696                                     .errorMessage();
0697                             QMessageBox::warning(
0698                                 this,
0699                                 i18nc("@title:window", "Krita"),
0700                                 i18n("Could not open %2.\nReason: %1.",
0701                                      msg,
0702                                      url.toDisplayString()));
0703                             continue;
0704                         }
0705 
0706                         KisFileLayer *fileLayer =
0707                             new KisFileLayer(this->image(),
0708                                              "",
0709                                              url.toLocalFile(),
0710                                              KisFileLayer::None,
0711                                              "Bicubic",
0712                                              fileInfo.fileName(),
0713                                              OPACITY_OPAQUE_U8);
0714 
0715                         KisLayerSP above =
0716                             this->mainWindow()->viewManager()->activeLayer();
0717                         KisNodeSP parent = above ? above->parent()
0718                                                  : this->mainWindow()
0719                                                        ->viewManager()
0720                                                        ->image()
0721                                                        ->root();
0722 
0723                         adapter.addNode(fileLayer, parent, above);
0724                     } else if (action == KisCanvasDrop::OPEN_IN_NEW_DOCUMENT
0725                                || action
0726                                    == KisCanvasDrop::OPEN_MANY_DOCUMENTS) {
0727                         if (this->mainWindow()) {
0728                             this->mainWindow()->openDocument(
0729                                 url.toLocalFile(),
0730                                 KisMainWindow::None);
0731                         }
0732                     } else if (action
0733                                    == KisCanvasDrop::INSERT_AS_REFERENCE_IMAGES
0734                                || action
0735                                    == KisCanvasDrop::
0736                                        INSERT_AS_REFERENCE_IMAGE) {
0737                         auto *reference =
0738                             KisReferenceImage::fromFile(url.toLocalFile(),
0739                                                         *this->viewConverter(),
0740                                                         this);
0741 
0742                         if (reference) {
0743                             const auto pos = this->canvasBase()
0744                                                  ->coordinatesConverter()
0745                                                  ->widgetToImage(event->pos());
0746                             reference->setPosition(
0747                                 (*this->viewConverter()).imageToDocument(pos));
0748                             this->canvasBase()
0749                                 ->referenceImagesDecoration()
0750                                 ->addReferenceImage(reference);
0751 
0752                             KoToolManager::instance()->switchToolRequested(
0753                                 "ToolReferenceImages");
0754                         }
0755                     }
0756                 }
0757             }
0758         }
0759     } else if (event->mimeData()->hasColor() || event->mimeData()->hasFormat("krita/x-colorsetentry")) {
0760         if (!image()) {
0761             return;
0762         }
0763 
0764         // Cannot fill on non-painting layers (vector layer, clone layer, file layer, group layer)
0765         if (d->viewManager->activeNode().isNull() ||
0766             d->viewManager->activeNode()->inherits("KisShapeLayer") ||
0767             d->viewManager->activeNode()->inherits("KisCloneLayer") ||
0768             !d->viewManager->activeDevice()) {
0769             showFloatingMessage(i18n("You cannot drag and drop colors on the selected layer type."), QIcon());
0770             return;
0771         }
0772 
0773         // Cannot fill if the layer is not editable
0774         if (!d->viewManager->activeNode()->isEditable()) {
0775             QString message;
0776             if (!d->viewManager->activeNode()->visible() && d->viewManager->activeNode()->userLocked()) {
0777                 message = i18n("Layer is locked and invisible.");
0778             } else if (d->viewManager->activeNode()->userLocked()) {
0779                 message = i18n("Layer is locked.");
0780             } else if(!d->viewManager->activeNode()->visible()) {
0781                 message = i18n("Layer is invisible.");
0782             }
0783             showFloatingMessage(message, KisIconUtils::loadIcon("object-locked"));
0784             return;
0785         }
0786 
0787         // The cursor is outside the image
0788         if (!image()->wrapAroundModePermitted() && !image()->bounds().contains(imgCursorPos)) {
0789             return;
0790         }
0791             
0792         KisStrokeStrategyUndoCommandBased *strategy =
0793                 new KisStrokeStrategyUndoCommandBased(
0794                     kundo2_i18n("Flood Fill Layer"), false, image().data()
0795                 );
0796         strategy->setSupportsWrapAroundMode(true);
0797         KisStrokeId fillStrokeId = image()->startStroke(strategy);
0798         KIS_SAFE_ASSERT_RECOVER_RETURN(fillStrokeId);
0799 
0800         KisResourcesSnapshotSP resources =
0801             new KisResourcesSnapshot(image(), d->viewManager->activeNode(), d->viewManager->canvasResourceProvider()->resourceManager());
0802 
0803         if (event->mimeData()->hasColor()) {
0804             resources->setFGColorOverride(KoColor(event->mimeData()->colorData().value<QColor>(), image()->colorSpace()));
0805         } else {
0806             QByteArray byteData = event->mimeData()->data("krita/x-colorsetentry");
0807             KisSwatch s = KisSwatch::fromByteArray(byteData);
0808             resources->setFGColorOverride(s.color());
0809         }
0810 
0811         // Use same options as the fill tool
0812         KConfigGroup configGroup = KSharedConfig::openConfig()->group("KritaFill/KisToolFill");
0813         QString fillMode = configGroup.readEntry<QString>("whatToFill", "");
0814         if (fillMode.isEmpty()) {
0815             if (configGroup.readEntry<bool>("fillSelection", false)) {
0816                 fillMode = "fillSelection";
0817             } else {
0818                 fillMode = "fillContiguousRegion";
0819             }
0820         }
0821         const bool useCustomBlendingOptions = configGroup.readEntry<bool>("useCustomBlendingOptions", false);
0822         const int customOpacity =
0823             qBound(0, configGroup.readEntry<int>("customOpacity", 100), 100) * OPACITY_OPAQUE_U8 / 100;
0824         QString customCompositeOp = configGroup.readEntry<QString>("customCompositeOp", COMPOSITE_OVER);
0825         if (KoCompositeOpRegistry::instance().getKoID(customCompositeOp).id().isNull()) {
0826             customCompositeOp = COMPOSITE_OVER;
0827         }
0828             
0829         if (event->keyboardModifiers() == Qt::ShiftModifier) {
0830             if (fillMode == "fillSimilarRegions") {
0831                 fillMode = "fillSelection";
0832             } else {
0833                 fillMode = "fillSimilarRegions";
0834             }
0835         } else if (event->keyboardModifiers() == Qt::AltModifier) {
0836             if (fillMode == "fillContiguousRegion") {
0837                 fillMode = "fillSelection";
0838             } else {
0839                 fillMode = "fillContiguousRegion";
0840             }
0841         }
0842 
0843         if (fillMode == "fillSelection") {
0844             FillProcessingVisitor *visitor =  new FillProcessingVisitor(nullptr,
0845                                                                         selection(),
0846                                                                         resources);
0847             visitor->setSeedPoint(imgCursorPos);
0848             visitor->setSelectionOnly(true);
0849             visitor->setUseCustomBlendingOptions(useCustomBlendingOptions);
0850             if (useCustomBlendingOptions) {
0851                 visitor->setCustomOpacity(customOpacity);
0852                 visitor->setCustomCompositeOp(customCompositeOp);
0853             }
0854             image()->addJob(
0855                 fillStrokeId,
0856                 new KisStrokeStrategyUndoCommandBased::Data(
0857                     KUndo2CommandSP(new KisProcessingCommand(visitor, d->viewManager->activeNode())),
0858                     false,
0859                     KisStrokeJobData::SEQUENTIAL,
0860                     KisStrokeJobData::EXCLUSIVE
0861                 )
0862             );
0863         } else {
0864             const int threshold = configGroup.readEntry("thresholdAmount", 8);
0865             const int opacitySpread = configGroup.readEntry("opacitySpread", 100);
0866             const bool antiAlias = configGroup.readEntry("antiAlias", true);
0867             const int grow = configGroup.readEntry("growSelection", 0);
0868             const bool stopGrowingAtDarkestPixel = configGroup.readEntry<bool>("stopGrowingAtDarkestPixel", false);
0869             const int feather = configGroup.readEntry("featherAmount", 0);
0870             QString sampleLayersMode = configGroup.readEntry("sampleLayersMode", "");
0871             if (sampleLayersMode.isEmpty()) {
0872                 if (configGroup.readEntry("sampleMerged", false)) {
0873                     sampleLayersMode = "allLayers";
0874                 } else {
0875                     sampleLayersMode = "currentLayer";
0876                 }
0877             }
0878             QList<int> colorLabels;
0879             {
0880 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0881                 const QStringList colorLabelsStr = configGroup.readEntry<QString>("colorLabels", "").split(',', Qt::SkipEmptyParts);
0882 #else
0883                 const QStringList colorLabelsStr = configGroup.readEntry<QString>("colorLabels", "").split(',', QString::SkipEmptyParts);
0884 #endif
0885 
0886                 for (const QString &colorLabelStr : colorLabelsStr) {
0887                     bool ok;
0888                     const int colorLabel = colorLabelStr.toInt(&ok);
0889                     if (ok) {
0890                         colorLabels << colorLabel;
0891                     }
0892                 }
0893             }
0894             
0895             KisPaintDeviceSP referencePaintDevice = nullptr;
0896             if (sampleLayersMode == "allLayers") {
0897                 referencePaintDevice = image()->projection();
0898             } else if (sampleLayersMode == "currentLayer") {
0899                 referencePaintDevice = d->viewManager->activeNode()->paintDevice();
0900             } else if (sampleLayersMode == "colorLabeledLayers") {
0901                 referencePaintDevice = KisMergeLabeledLayersCommand::createRefPaintDevice(image(), "Fill Tool Reference Result Paint Device");
0902                 image()->addJob(
0903                     fillStrokeId,
0904                     new KisStrokeStrategyUndoCommandBased::Data(
0905                         KUndo2CommandSP(new KisMergeLabeledLayersCommand(image(),
0906                                                                          referencePaintDevice,
0907                                                                          colorLabels,
0908                                                                          KisMergeLabeledLayersCommand::GroupSelectionPolicy_SelectIfColorLabeled)),
0909                         false,
0910                         KisStrokeJobData::SEQUENTIAL,
0911                         KisStrokeJobData::EXCLUSIVE
0912                     )
0913                 );
0914             }
0915 
0916             QSharedPointer<KoColor> referenceColor(new KoColor);
0917             if (sampleLayersMode == "colorLabeledLayers") {
0918                 // We need to obtain the reference color from the reference paint
0919                 // device, but it is produced in a stroke, so we must get the color
0920                 // after the device is ready. So we get it in the stroke
0921                 image()->addJob(
0922                     fillStrokeId,
0923                     new KisStrokeStrategyUndoCommandBased::Data(
0924                         KUndo2CommandSP(new KisCommandUtils::LambdaCommand(
0925                             [referenceColor, referencePaintDevice, imgCursorPos]() -> KUndo2Command*
0926                             {
0927                                 *referenceColor = referencePaintDevice->pixel(imgCursorPos);
0928                                 return 0;
0929                             }
0930                         )),
0931                         false,
0932                         KisStrokeJobData::SEQUENTIAL,
0933                         KisStrokeJobData::EXCLUSIVE
0934                     )
0935                 );
0936             } else {
0937                 // Here the reference device is already ready, so we obtain the
0938                 // reference color directly
0939                 *referenceColor = referencePaintDevice->pixel(imgCursorPos);
0940             }
0941 
0942             if (fillMode == "fillContiguousRegion") {
0943                 const KisFillPainter::RegionFillingMode regionFillingMode =
0944                     configGroup.readEntry("contiguousFillMode", "") == "boundaryFill"
0945                     ? KisFillPainter::RegionFillingMode_BoundaryFill
0946                     : KisFillPainter::RegionFillingMode_FloodFill;
0947                 KoColor regionFillingBoundaryColor;
0948                 if (regionFillingMode == KisFillPainter::RegionFillingMode_BoundaryFill) {
0949                     const QString xmlColor = configGroup.readEntry("contiguousFillBoundaryColor", QString());
0950                     QDomDocument doc;
0951                     if (doc.setContent(xmlColor)) {
0952                         QDomElement e = doc.documentElement().firstChild().toElement();
0953                         QString channelDepthID = doc.documentElement().attribute("channeldepth", Integer16BitsColorDepthID.id());
0954                         bool ok;
0955                         if (e.hasAttribute("space") || e.tagName().toLower() == "srgb") {
0956                             regionFillingBoundaryColor = KoColor::fromXML(e, channelDepthID, &ok);
0957                         } else if (doc.documentElement().hasAttribute("space") || doc.documentElement().tagName().toLower() == "srgb"){
0958                             regionFillingBoundaryColor = KoColor::fromXML(doc.documentElement(), channelDepthID, &ok);
0959                         }
0960                     }
0961                 }
0962                 const bool useSelectionAsBoundary = configGroup.readEntry("useSelectionAsBoundary", false);
0963                 const bool blendingOptionsAreNoOp = useCustomBlendingOptions
0964                                                     ? (customOpacity == OPACITY_OPAQUE_U8 &&
0965                                                        customCompositeOp == COMPOSITE_OVER)
0966                                                     : (resources->opacity() == OPACITY_OPAQUE_U8 &&
0967                                                        resources->compositeOpId() == COMPOSITE_OVER);
0968                 const bool useFastMode = !resources->activeSelection() &&
0969                                          blendingOptionsAreNoOp &&
0970                                          opacitySpread == 100 &&
0971                                          useSelectionAsBoundary == false &&
0972                                          !antiAlias && grow == 0 && feather == 0 &&
0973                                          sampleLayersMode == "currentLayer";
0974 
0975                 FillProcessingVisitor *visitor = new FillProcessingVisitor(referencePaintDevice,
0976                                                                            selection(),
0977                                                                            resources);
0978                 visitor->setSeedPoint(imgCursorPos);
0979                 visitor->setUseFastMode(useFastMode);
0980                 visitor->setUseSelectionAsBoundary(useSelectionAsBoundary);
0981                 visitor->setFeather(feather);
0982                 visitor->setSizeMod(grow);
0983                 visitor->setStopGrowingAtDarkestPixel(stopGrowingAtDarkestPixel);
0984                 visitor->setRegionFillingMode(regionFillingMode);
0985                 if (regionFillingMode == KisFillPainter::RegionFillingMode_BoundaryFill) {
0986                     visitor->setRegionFillingBoundaryColor(regionFillingBoundaryColor);
0987                 }
0988                 visitor->setFillThreshold(threshold);
0989                 visitor->setOpacitySpread(opacitySpread);
0990                 visitor->setAntiAlias(antiAlias);
0991                 visitor->setUseCustomBlendingOptions(useCustomBlendingOptions);
0992                 if (useCustomBlendingOptions) {
0993                     visitor->setCustomOpacity(customOpacity);
0994                     visitor->setCustomCompositeOp(customCompositeOp);
0995                 }
0996                 
0997                 image()->addJob(
0998                     fillStrokeId,
0999                     new KisStrokeStrategyUndoCommandBased::Data(
1000                         KUndo2CommandSP(new KisProcessingCommand(visitor, d->viewManager->activeNode())),
1001                         false,
1002                         KisStrokeJobData::SEQUENTIAL,
1003                         KisStrokeJobData::EXCLUSIVE
1004                     )
1005                 );
1006             } else {
1007                 KisSelectionSP fillMask = new KisSelection;
1008                 QSharedPointer<KisProcessingVisitor::ProgressHelper>
1009                     progressHelper(new KisProcessingVisitor::ProgressHelper(currentNode()));
1010 
1011                 {
1012                     KisSelectionSP selection = this->selection();
1013                     KisFillPainter painter;
1014                     QRect bounds = image()->bounds();
1015                     if (selection) {
1016                         bounds = bounds.intersected(selection->projection()->selectedRect());
1017                     }
1018 
1019                     painter.setFillThreshold(threshold);
1020                     painter.setOpacitySpread(opacitySpread);
1021                     painter.setAntiAlias(antiAlias);
1022                     painter.setSizemod(grow);
1023                     painter.setStopGrowingAtDarkestPixel(stopGrowingAtDarkestPixel);
1024                     painter.setFeather(feather);
1025 
1026                     QVector<KisStrokeJobData*> jobs =
1027                         painter.createSimilarColorsSelectionJobs(
1028                             fillMask->pixelSelection(), referenceColor, referencePaintDevice,
1029                             bounds, selection ? selection->projection() : nullptr, progressHelper
1030                         );
1031 
1032                     for (KisStrokeJobData *job : jobs) {
1033                         image()->addJob(fillStrokeId, job);
1034                     }
1035                 }
1036 
1037                 {
1038                     FillProcessingVisitor *visitor =  new FillProcessingVisitor(nullptr,
1039                                                                                 fillMask,
1040                                                                                 resources);
1041 
1042                     visitor->setSeedPoint(imgCursorPos);
1043                     visitor->setSelectionOnly(true);
1044                     visitor->setProgressHelper(progressHelper);
1045 
1046                     image()->addJob(
1047                         fillStrokeId,
1048                         new KisStrokeStrategyUndoCommandBased::Data(
1049                             KUndo2CommandSP(new KisProcessingCommand(visitor, currentNode())),
1050                             false,
1051                             KisStrokeJobData::SEQUENTIAL,
1052                             KisStrokeJobData::EXCLUSIVE
1053                         )
1054                     );
1055                 }
1056             }
1057         }
1058 
1059         image()->addJob(
1060             fillStrokeId,
1061             new KisStrokeStrategyUndoCommandBased::Data(
1062                 KUndo2CommandSP(new KisUpdateCommand(d->viewManager->activeNode(), image()->bounds(), image().data())),
1063                 false,
1064                 KisStrokeJobData::SEQUENTIAL,
1065                 KisStrokeJobData::EXCLUSIVE
1066             )
1067         );
1068 
1069         image()->endStroke(fillStrokeId);
1070     }
1071 }
1072 
1073 void KisView::dragMoveEvent(QDragMoveEvent *event)
1074 {
1075     dbgUI << Q_FUNC_INFO
1076           << "Formats: " << event->mimeData()->formats()
1077           << "Urls: " << event->mimeData()->urls()
1078           << "Has images: " << event->mimeData()->hasImage();
1079     if (event->mimeData()->hasImage()
1080             || event->mimeData()->hasUrls()
1081             || event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")
1082             || event->mimeData()->hasFormat("krita/x-colorsetentry")
1083             || event->mimeData()->hasColor()) {
1084         return event->accept();
1085     }
1086 
1087     return event->ignore();
1088 }
1089 
1090 KisDocument *KisView::document() const
1091 {
1092     return d->document;
1093 }
1094 
1095 KisView *KisView::replaceBy(KisDocument *document)
1096 {
1097     KisMainWindow *window = mainWindow();
1098     QMdiSubWindow *subWindow = d->subWindow;
1099     delete this;
1100     return window->newView(document, subWindow);
1101 }
1102 
1103 KisMainWindow * KisView::mainWindow() const
1104 {
1105     return d->viewManager->mainWindow();
1106 }
1107 
1108 void KisView::setSubWindow(QMdiSubWindow *subWindow)
1109 {
1110     d->subWindow = subWindow;
1111 }
1112 
1113 QStatusBar * KisView::statusBar() const
1114 {
1115     KisMainWindow *mw = mainWindow();
1116     return mw ? mw->statusBar() : 0;
1117 }
1118 
1119 void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving)
1120 {
1121     QStatusBar *sb = statusBar();
1122     if (sb) {
1123         sb->showMessage(text, timeout);
1124     }
1125 
1126     KisConfig cfg(true);
1127 
1128     if (!sb || sb->isHidden() ||
1129         (!isAutoSaving && cfg.forceShowSaveMessages()) ||
1130         (cfg.forceShowAutosaveMessages() && isAutoSaving)) {
1131 
1132         viewManager()->showFloatingMessage(text, QIcon());
1133     }
1134 }
1135 
1136 void KisView::slotClearStatusText()
1137 {
1138     QStatusBar *sb = statusBar();
1139     if (sb) {
1140         sb->clearMessage();
1141     }
1142 }
1143 
1144 QList<QAction*> KisView::createChangeUnitActions(bool addPixelUnit)
1145 {
1146     UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
1147     return unitActions->actions();
1148 }
1149 
1150 void KisView::closeEvent(QCloseEvent *event)
1151 {
1152     // Check whether we're the last view
1153     int viewCount = KisPart::instance()->viewCount(document());
1154     if (viewCount > 1) {
1155         // there are others still, so don't bother the user
1156         event->accept();
1157         return;
1158     }
1159 
1160     if (queryClose()) {
1161         event->accept();
1162         return;
1163     }
1164 
1165     event->ignore();
1166 
1167 }
1168 
1169 bool KisView::queryClose()
1170 {
1171     if (!document())
1172         return true;
1173 
1174     document()->waitForSavingToComplete();
1175 
1176     if (document()->isModified()) {
1177         QString name;
1178         name = QFileInfo(document()->path()).fileName();
1179 
1180         if (name.isEmpty())
1181             name = i18n("Untitled");
1182 
1183         int res = QMessageBox::warning(this,
1184                                        i18nc("@title:window", "Krita"),
1185                                        i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
1186                                        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
1187 
1188         switch (res) {
1189         case QMessageBox::Yes : {
1190             bool isNative = (document()->mimeType() == document()->nativeFormatMimeType());
1191             if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false))
1192                 return false;
1193             break;
1194         }
1195         case QMessageBox::No : {
1196             KisImageSP image = document()->image();
1197             image->requestStrokeCancellation();
1198             viewManager()->blockUntilOperationsFinishedForced(image);
1199 
1200             document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered());
1201             document()->setModified(false);   // Now when queryClose() is called by closeEvent it won't do anything.
1202             break;
1203         }
1204         default : // case QMessageBox::Cancel :
1205             return false;
1206         }
1207     }
1208 
1209     return true;
1210 
1211 }
1212 
1213 void KisView::slotMigratedToScreen(QScreen *screen)
1214 {
1215     d->canvas.slotScreenChanged(screen);
1216 }
1217 
1218 void KisView::slotScreenOrResolutionChanged()
1219 {
1220     /**
1221      * slotScreenOrResolutionChanged() is guaranteed to come after
1222      * slotMigratedToScreen() when a migration happens
1223      */
1224     d->zoomManager.updateScreenResolution(this);
1225 
1226     if (d->canvas.resourceManager() && d->screenMigrationTracker.currentScreen()) {
1227         int penWidth = qRound(d->screenMigrationTracker.currentScreen()->devicePixelRatio());
1228         d->canvas.resourceManager()->setDecorationThickness(qMax(penWidth, 1));
1229     }
1230 }
1231 
1232 QScreen* KisView::currentScreen() const
1233 {
1234     return d->screenMigrationTracker.currentScreen();
1235 }
1236 
1237 void KisView::slotThemeChanged(QPalette pal)
1238 {
1239     this->setPalette(pal);
1240     for (int i=0; i<this->children().size();i++) {
1241         QWidget *w = qobject_cast<QWidget*> ( this->children().at(i));
1242         if (w) {
1243             w->setPalette(pal);
1244         }
1245     }
1246     if (canvasBase()) {
1247         canvasBase()->canvasWidget()->setPalette(pal);
1248     }
1249     if (canvasController()) {
1250         canvasController()->setPalette(pal);
1251     }
1252 }
1253 
1254 void KisView::slotUpdateDocumentTitle()
1255 {
1256     QString title = d->document->caption();
1257 
1258     if (!d->document->isReadWrite()) {
1259         title += " " + i18n("Write Protected");
1260     }
1261 
1262     if (d->document->isRecovered()) {
1263         title += " " + i18n("Recovered");
1264     }
1265 
1266     // show the file size for the document
1267     KisMemoryStatisticsServer::Statistics fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->document->image());
1268 
1269     if (fileSizeStats.imageSize) {
1270         title += QString(" (").append( KFormat().formatByteSize(qreal(fileSizeStats.imageSize))).append( ") ");
1271     }
1272 
1273     title += "[*]";
1274 
1275     this->setWindowTitle(title);
1276 }
1277 
1278 void KisView::resetImageSizeAndScroll(bool changeCentering,
1279                                       const QPointF &oldImageStillPoint,
1280                                       const QPointF &newImageStillPoint)
1281 {
1282     const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter();
1283 
1284     QPointF oldPreferredCenter = d->canvasController.preferredCenter();
1285 
1286     /**
1287      * Calculating the still point in old coordinates depending on the
1288      * parameters given
1289      */
1290 
1291     QPointF oldStillPoint;
1292 
1293     if (changeCentering) {
1294         oldStillPoint =
1295                 converter->imageToWidget(oldImageStillPoint) +
1296                 converter->documentOffset();
1297     } else {
1298         QSizeF oldDocumentSize = d->canvasController.documentSize();
1299         oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
1300     }
1301 
1302     /**
1303      * Updating the document size
1304      */
1305 
1306     QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
1307     KoZoomController *zc = d->zoomManager.zoomController();
1308     zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom(),
1309                 d->zoomManager.resolutionX(), d->zoomManager.resolutionY());
1310     zc->setPageSize(size);
1311     zc->setDocumentSize(size, true);
1312 
1313     /**
1314      * Calculating the still point in new coordinates depending on the
1315      * parameters given
1316      */
1317 
1318     QPointF newStillPoint;
1319 
1320     if (changeCentering) {
1321         newStillPoint =
1322                 converter->imageToWidget(newImageStillPoint) +
1323                 converter->documentOffset();
1324     } else {
1325         QSizeF newDocumentSize = d->canvasController.documentSize();
1326         newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
1327     }
1328 
1329     d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
1330 }
1331 
1332 void KisView::syncLastActiveNodeToDocument()
1333 {
1334     KisDocument *doc = document();
1335     if (doc) {
1336         doc->setPreActivatedNode(d->currentNode);
1337     }
1338 }
1339 
1340 void KisView::saveViewState(KisPropertiesConfiguration &config) const
1341 {
1342     config.setProperty("file", d->document->path());
1343     config.setProperty("window", mainWindow()->windowStateConfig().name());
1344 
1345     if (d->subWindow) {
1346         config.setProperty("geometry", d->subWindow->saveGeometry().toBase64());
1347     }
1348 
1349     config.setProperty("zoomMode", (int)zoomController()->zoomMode());
1350     config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom());
1351 
1352     d->canvasController.saveCanvasState(config);
1353 }
1354 
1355 void KisView::restoreViewState(const KisPropertiesConfiguration &config)
1356 {
1357     if (d->subWindow) {
1358         QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1());
1359         d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry));
1360     }
1361 
1362     qreal zoom = config.getFloat("zoom", 1.0f);
1363     int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE);
1364     d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom);
1365     d->canvasController.restoreCanvasState(config);
1366 }
1367 
1368 void KisView::setCurrentNode(KisNodeSP node)
1369 {
1370     d->currentNode = node;
1371     d->canvas.slotTrySwitchShapeManager();
1372 
1373     syncLastActiveNodeToDocument();
1374 }
1375 
1376 KisNodeSP KisView::currentNode() const
1377 {
1378     return d->currentNode;
1379 }
1380 
1381 KisLayerSP KisView::currentLayer() const
1382 {
1383     KisNodeSP node;
1384     KisMaskSP mask = currentMask();
1385     if (mask) {
1386         node = mask->parent();
1387     }
1388     else {
1389         node = d->currentNode;
1390     }
1391     return qobject_cast<KisLayer*>(node.data());
1392 }
1393 
1394 KisMaskSP KisView::currentMask() const
1395 {
1396     return dynamic_cast<KisMask*>(d->currentNode.data());
1397 }
1398 
1399 KisSelectionSP KisView::selection()
1400 {
1401     KisLayerSP layer = currentLayer();
1402     if (layer)
1403         return layer->selection(); // falls through to the global
1404     // selection, or 0 in the end
1405     if (image()) {
1406         return image()->globalSelection();
1407     }
1408     return 0;
1409 }
1410 
1411 void KisView::slotSoftProofing(bool softProofing)
1412 {
1413     d->softProofing = softProofing;
1414     QString message;
1415     if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
1416     {
1417         message = i18n("Soft Proofing doesn't work in floating point.");
1418         viewManager()->showFloatingMessage(message,QIcon());
1419         return;
1420     }
1421     if (softProofing){
1422         message = i18n("Soft Proofing turned on.");
1423     } else {
1424         message = i18n("Soft Proofing turned off.");
1425     }
1426     viewManager()->showFloatingMessage(message,QIcon());
1427     canvasBase()->slotSoftProofing();
1428 }
1429 
1430 void KisView::slotGamutCheck(bool gamutCheck)
1431 {
1432     d->gamutCheck = gamutCheck;
1433     QString message;
1434     if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
1435     {
1436         message = i18n("Gamut Warnings don't work in floating point.");
1437         viewManager()->showFloatingMessage(message,QIcon());
1438         return;
1439     }
1440 
1441     if (gamutCheck){
1442         message = i18n("Gamut Warnings turned on.");
1443         if (!d->softProofing){
1444             message += "\n "+i18n("But Soft Proofing is still off.");
1445         }
1446     } else {
1447         message = i18n("Gamut Warnings turned off.");
1448     }
1449     viewManager()->showFloatingMessage(message,QIcon());
1450     canvasBase()->slotGamutCheck();
1451 }
1452 
1453 bool KisView::softProofing()
1454 {
1455     return d->softProofing;
1456 }
1457 
1458 bool KisView::gamutCheck()
1459 {
1460     return d->gamutCheck;
1461 }
1462 
1463 void KisView::slotLoadingFinished()
1464 {
1465     if (!document()) return;
1466 
1467     /**
1468      * Cold-start of image size/resolution signals
1469      */
1470     slotImageResolutionChanged();
1471 
1472     if (image()->locked()) {
1473         // If this is the first view on the image, the image will have been locked
1474         // so unlock it.
1475         image()->blockSignals(false);
1476         image()->unlock();
1477     }
1478 
1479     canvasBase()->initializeImage();
1480 
1481     /**
1482      * Dirty hack alert
1483      */
1484     d->zoomManager.zoomController()->setCanvasMappingMode(false);
1485 
1486     if (viewConverter()) {
1487         viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE);
1488     }
1489 
1490     connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
1491     connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*)));
1492     connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF)));
1493 
1494     KisNodeSP activeNode = document()->preActivatedNode();
1495 
1496     if (!activeNode) {
1497         activeNode = image()->rootLayer()->lastChild();
1498     }
1499 
1500     while (activeNode && !activeNode->inherits("KisLayer")) {
1501         activeNode = activeNode->prevSibling();
1502     }
1503 
1504     setCurrentNode(activeNode);
1505     connect(&d->screenMigrationTracker, SIGNAL(sigScreenChanged(QScreen*)), this, SLOT(slotMigratedToScreen(QScreen*)));
1506     connect(&d->screenMigrationTracker, SIGNAL(sigScreenOrResolutionChanged(QScreen*)), this, SLOT(slotScreenOrResolutionChanged()));
1507     zoomManager()->updateImageBoundsSnapping();
1508 }
1509 
1510 void KisView::slotImageResolutionChanged()
1511 {
1512     resetImageSizeAndScroll(false);
1513     zoomManager()->updateImageBoundsSnapping();
1514     zoomManager()->updateGuiAfterDocumentSize();
1515 
1516     // update KoUnit value for the document
1517     if (resourceProvider()) {
1518         resourceProvider()->resourceManager()->
1519                 setResource(KoCanvasResource::Unit, d->canvas.unit());
1520     }
1521 }
1522 
1523 void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
1524 {
1525     resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
1526     zoomManager()->updateImageBoundsSnapping();
1527     zoomManager()->updateGuiAfterDocumentSize();
1528 }
1529 
1530 void KisView::closeView()
1531 {
1532     d->subWindow->close();
1533 }