File indexing completed on 2024-05-12 16:01:56

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 
0043 #include <kis_image.h>
0044 #include <kis_node.h>
0045 
0046 #include <kis_group_layer.h>
0047 #include <kis_layer.h>
0048 #include <kis_mask.h>
0049 #include <kis_selection.h>
0050 
0051 #include "KisDocument.h"
0052 #include "KisImageSignals.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 "dialogs/kis_dlg_paste_format.h"
0062 #include "input/kis_input_manager.h"
0063 #include "kis_canvas2.h"
0064 #include "kis_canvas_controller.h"
0065 #include "kis_canvas_resource_provider.h"
0066 #include "kis_clipboard.h"
0067 #include "kis_config.h"
0068 #include "kis_file_layer.h"
0069 #include "kis_fill_painter.h"
0070 #include "kis_filter_manager.h"
0071 #include "kis_image_manager.h"
0072 #include "kis_import_catcher.h"
0073 #include "kis_mimedata.h"
0074 #include "kis_mirror_axis.h"
0075 #include "kis_node_commands_adapter.h"
0076 #include "kis_node_manager.h"
0077 #include "kis_paint_layer.h"
0078 #include "kis_painting_assistants_decoration.h"
0079 #include "kis_processing_applicator.h"
0080 #include "kis_progress_widget.h"
0081 #include "kis_resources_snapshot.h"
0082 #include "kis_selection_manager.h"
0083 #include "kis_shape_controller.h"
0084 #include "kis_signal_compressor.h"
0085 #include "kis_statusbar.h"
0086 #include "kis_tool_freehand.h"
0087 #include "kis_zoom_manager.h"
0088 #include "krita_utils.h"
0089 #include "processing/fill_processing_visitor.h"
0090 #include "widgets/kis_canvas_drop.h"
0091 
0092 //static
0093 QString KisView::newObjectName()
0094 {
0095     static int s_viewIFNumber = 0;
0096     QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
0097     return name;
0098 }
0099 
0100 bool KisView::s_firstView = true;
0101 
0102 class Q_DECL_HIDDEN KisView::Private
0103 {
0104 public:
0105     Private(KisView *_q,
0106             KisDocument *document,
0107             KisViewManager *viewManager)
0108         : actionCollection(viewManager->actionCollection())
0109         , viewConverter()
0110         , canvasController(_q, viewManager->mainWindow(), viewManager->actionCollection())
0111         , canvas(&viewConverter, viewManager->canvasResourceProvider()->resourceManager(), viewManager->mainWindow(), _q, document->shapeController())
0112         , zoomManager(_q, &this->viewConverter, &this->canvasController)
0113         , viewManager(viewManager)
0114         , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE)
0115     {
0116     }
0117 
0118     bool inOperation {false}; //in the middle of an operation (no screen refreshing)?
0119 
0120     QPointer<KisDocument> document; // our KisDocument
0121     QWidget *tempActiveWidget {nullptr};
0122 
0123     KisKActionCollection* actionCollection {nullptr};
0124     KisCoordinatesConverter viewConverter;
0125     KisCanvasController canvasController;
0126     KisCanvas2 canvas;
0127     KisZoomManager zoomManager;
0128     KisViewManager *viewManager {nullptr};
0129     KisNodeSP currentNode;
0130     KisPaintingAssistantsDecorationSP paintingAssistantsDecoration;
0131     KisReferenceImagesDecorationSP referenceImagesDecoration;
0132     bool isCurrent {false};
0133     bool showFloatingMessage {true};
0134     QPointer<KisFloatingMessage> savedFloatingMessage;
0135     KisSignalCompressor floatingMessageCompressor;
0136     QMdiSubWindow *subWindow {nullptr};
0137 
0138     bool softProofing {false};
0139     bool gamutCheck {false};
0140 
0141     KisSynchronizedConnection<KisNodeSP> addNodeConnection;
0142     KisSynchronizedConnection<KisNodeSP> removeNodeConnection;
0143 
0144     // Hmm sorry for polluting the private class with such a big inner class.
0145     // At the beginning it was a little struct :)
0146     class StatusBarItem
0147     {
0148     public:
0149 
0150         StatusBarItem(QWidget * widget, int stretch, bool permanent)
0151             : m_widget(widget),
0152               m_stretch(stretch),
0153               m_permanent(permanent),
0154               m_connected(false),
0155               m_hidden(false) {}
0156 
0157         bool operator==(const StatusBarItem& rhs) {
0158             return m_widget == rhs.m_widget;
0159         }
0160 
0161         bool operator!=(const StatusBarItem& rhs) {
0162             return m_widget != rhs.m_widget;
0163         }
0164 
0165         QWidget * widget() const {
0166             return m_widget;
0167         }
0168 
0169         void ensureItemShown(QStatusBar * sb) {
0170             Q_ASSERT(m_widget);
0171             if (!m_connected) {
0172                 if (m_permanent)
0173                     sb->addPermanentWidget(m_widget, m_stretch);
0174                 else
0175                     sb->addWidget(m_widget, m_stretch);
0176 
0177                 if(!m_hidden)
0178                     m_widget->show();
0179 
0180                 m_connected = true;
0181             }
0182         }
0183         void ensureItemHidden(QStatusBar * sb) {
0184             if (m_connected) {
0185                 m_hidden = m_widget->isHidden();
0186                 sb->removeWidget(m_widget);
0187                 m_widget->hide();
0188                 m_connected = false;
0189             }
0190         }
0191     private:
0192         QWidget * m_widget = 0;
0193         int m_stretch;
0194         bool m_permanent;
0195         bool m_connected = false;
0196         bool m_hidden = false;
0197 
0198     };
0199 
0200 };
0201 
0202 KisView::KisView(KisDocument *document, KisViewManager *viewManager, QWidget *parent)
0203     : QWidget(parent)
0204     , d(new Private(this, document, viewManager))
0205 {
0206     Q_ASSERT(document);
0207     connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool)));
0208     setObjectName(newObjectName());
0209 
0210     d->document = document;
0211 
0212     setFocusPolicy(Qt::StrongFocus);
0213 
0214     QStatusBar * sb = statusBar();
0215     if (sb) { // No statusbar in e.g. konqueror
0216         connect(d->document, SIGNAL(statusBarMessage(QString,int)),
0217                 this, SLOT(slotSavingStatusMessage(QString,int)));
0218         connect(d->document, SIGNAL(clearStatusBarMessage()),
0219                 this, SLOT(slotClearStatusText()));
0220     }
0221 
0222     d->canvas.setup();
0223 
0224     KisConfig cfg(false);
0225 
0226     d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0227     d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0228     d->canvasController.setVastScrolling(cfg.vastScrolling());
0229     d->canvasController.setCanvas(&d->canvas);
0230 
0231     d->zoomManager.setup(d->actionCollection);
0232 
0233 
0234     connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged()));
0235     setAcceptDrops(true);
0236 
0237     connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
0238     connect(d->document, SIGNAL(sigSavingFinished(QString)), this, SLOT(slotSavingFinished()));
0239 
0240     d->referenceImagesDecoration = new KisReferenceImagesDecoration(this, document, /* viewReady = */ false);
0241     d->canvas.addDecoration(d->referenceImagesDecoration);
0242     d->referenceImagesDecoration->setVisible(true);
0243 
0244     d->paintingAssistantsDecoration = new KisPaintingAssistantsDecoration(this);
0245     d->canvas.addDecoration(d->paintingAssistantsDecoration);
0246     d->paintingAssistantsDecoration->setVisible(true);
0247 
0248     d->showFloatingMessage = cfg.showCanvasMessages();
0249     d->zoomManager.updateScreenResolution(this);
0250 }
0251 
0252 KisView::~KisView()
0253 {
0254     if (d->viewManager) {
0255         if (d->viewManager->filterManager()->isStrokeRunning()) {
0256             d->viewManager->filterManager()->cancelDialog();
0257         }
0258 
0259         d->viewManager->mainWindow()->notifyChildViewDestroyed(this);
0260     }
0261 
0262     image()->requestStrokeCancellation();
0263 
0264     /**
0265      * KisCanvas2 maintains direct connections to the image, so we should
0266      * disconnect it from the image before the destruction process starts
0267      */
0268     d->canvas.disconnectImage();
0269 
0270     KoToolManager::instance()->removeCanvasController(&d->canvasController);
0271     d->canvasController.setCanvas(0);
0272 
0273     KisPart::instance()->removeView(this);
0274     delete d;
0275 }
0276 
0277 void KisView::notifyCurrentStateChanged(bool isCurrent)
0278 {
0279     d->isCurrent = isCurrent;
0280 
0281     if (!d->isCurrent && d->savedFloatingMessage) {
0282         d->savedFloatingMessage->removeMessage();
0283     }
0284 
0285     KisInputManager *inputManager = globalInputManager();
0286     if (d->isCurrent) {
0287         inputManager->attachPriorityEventFilter(&d->canvasController);
0288     } else {
0289         inputManager->detachPriorityEventFilter(&d->canvasController);
0290     }
0291 
0292     /**
0293      * When current view is changed, currently selected node is also changed,
0294      * therefore we should update selection overlay mask
0295      */
0296     viewManager()->selectionManager()->selectionChanged();
0297 }
0298 
0299 bool KisView::isCurrent() const
0300 {
0301     return d->isCurrent;
0302 }
0303 
0304 void KisView::setShowFloatingMessage(bool show)
0305 {
0306     d->showFloatingMessage = show;
0307 }
0308 
0309 void KisView::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
0310 {
0311     if (!d->viewManager) return;
0312 
0313     if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) {
0314         if (d->savedFloatingMessage) {
0315             d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment);
0316         } else {
0317             d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment);
0318             d->savedFloatingMessage->setShowOverParent(true);
0319             d->savedFloatingMessage->setIcon(icon);
0320 
0321             connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage()));
0322             d->floatingMessageCompressor.start();
0323         }
0324     }
0325 }
0326 
0327 bool KisView::canvasIsMirrored() const
0328 {
0329     return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored();
0330 }
0331 
0332 void KisView::setViewManager(KisViewManager *view)
0333 {
0334     d->viewManager = view;
0335 
0336     KoToolManager::instance()->addController(&d->canvasController);
0337     KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController);
0338     dynamic_cast<KisShapeController*>(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas);
0339 
0340     if (resourceProvider()) {
0341         resourceProvider()->slotImageSizeChanged();
0342     }
0343 
0344     if (d->viewManager && d->viewManager->nodeManager()) {
0345         d->viewManager->nodeManager()->nodesUpdated();
0346     }
0347 
0348     connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,QPointF)));
0349     connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
0350 
0351 
0352     d->addNodeConnection.connectSync(image(), &KisImage::sigNodeAddedAsync,
0353                                      this, &KisView::slotContinueAddNode);
0354 
0355     // executed in a context of an image thread
0356     connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)),
0357             SLOT(slotImageNodeRemoved(KisNodeSP)),
0358             Qt::DirectConnection);
0359 
0360     d->removeNodeConnection.connectOutputSlot(this, &KisView::slotContinueRemoveNode);
0361 
0362     d->viewManager->updateGUI();
0363 
0364     KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
0365 }
0366 
0367 KisViewManager* KisView::viewManager() const
0368 {
0369     return d->viewManager;
0370 }
0371 
0372 void KisView::slotContinueAddNode(KisNodeSP newActiveNode)
0373 {
0374     /**
0375      * When deleting the last layer, root node got selected. We should
0376      * fix it when the first layer is added back.
0377      *
0378      * Here we basically reimplement what Qt's view/model do. But
0379      * since they are not connected, we should do it manually.
0380      */
0381 
0382     if (!d->isCurrent &&
0383             (!d->currentNode || !d->currentNode->parent())) {
0384 
0385         d->currentNode = newActiveNode;
0386     }
0387 }
0388 
0389 
0390 void KisView::slotImageNodeRemoved(KisNodeSP node)
0391 {
0392     d->removeNodeConnection.start(KritaUtils::nearestNodeAfterRemoval(node));
0393 }
0394 
0395 void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode)
0396 {
0397     if (!d->isCurrent) {
0398         d->currentNode = newActiveNode;
0399     }
0400 }
0401 
0402 KoZoomController *KisView::zoomController() const
0403 {
0404     return d->zoomManager.zoomController();
0405 }
0406 
0407 KisZoomManager *KisView::zoomManager() const
0408 {
0409     return &d->zoomManager;
0410 }
0411 
0412 KisCanvasController *KisView::canvasController() const
0413 {
0414     return &d->canvasController;
0415 }
0416 
0417 KisCanvasResourceProvider *KisView::resourceProvider() const
0418 {
0419     if (d->viewManager) {
0420         return d->viewManager->canvasResourceProvider();
0421     }
0422     return 0;
0423 }
0424 
0425 KisInputManager* KisView::globalInputManager() const
0426 {
0427     return d->viewManager ? d->viewManager->inputManager() : 0;
0428 }
0429 
0430 KisCanvas2 *KisView::canvasBase() const
0431 {
0432     return &d->canvas;
0433 }
0434 
0435 KisImageWSP KisView::image() const
0436 {
0437     if (d->document) {
0438         return d->document->image();
0439     }
0440     return 0;
0441 }
0442 
0443 
0444 KisCoordinatesConverter *KisView::viewConverter() const
0445 {
0446     return &d->viewConverter;
0447 }
0448 
0449 void KisView::dragEnterEvent(QDragEnterEvent *event)
0450 {
0451     dbgUI << Q_FUNC_INFO
0452           << "Formats: " << event->mimeData()->formats()
0453           << "Urls: " << event->mimeData()->urls()
0454           << "Has images: " << event->mimeData()->hasImage();
0455     if (event->mimeData()->hasImage()
0456             || event->mimeData()->hasUrls()
0457             || event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")
0458             || event->mimeData()->hasFormat("krita/x-colorsetentry")
0459             || event->mimeData()->hasColor()) {
0460         event->accept();
0461 
0462         // activate view if it should accept the drop
0463         this->setFocus();
0464     } else {
0465         event->ignore();
0466     }
0467 }
0468 
0469 void KisView::dropEvent(QDropEvent *event)
0470 {
0471     KisImageWSP kisimage = image();
0472     Q_ASSERT(kisimage);
0473 
0474     QPoint imgCursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint();
0475     QRect imageBounds = kisimage->bounds();
0476     boost::optional<QPoint> forcedCenter;
0477 
0478     if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(imgCursorPos)) {
0479         forcedCenter = imgCursorPos;
0480     }
0481 
0482     dbgUI << Q_FUNC_INFO;
0483     dbgUI << "\t Formats: " << event->mimeData()->formats();
0484     dbgUI << "\t Urls: " << event->mimeData()->urls();
0485     dbgUI << "\t Has images: " << event->mimeData()->hasImage();
0486 
0487     if (event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")) {
0488         KisShapeController *kritaShapeController =
0489                 dynamic_cast<KisShapeController*>(d->document->shapeController());
0490 
0491         bool copyNode = true;
0492         QList<KisNodeSP> nodes;
0493 
0494         if (forcedCenter) {
0495             nodes = KisMimeData::loadNodesFastAndRecenter(*forcedCenter, event->mimeData(), kisimage, kritaShapeController, copyNode);
0496         } else {
0497             nodes = KisMimeData::loadNodesFast(event->mimeData(), kisimage, kritaShapeController, copyNode);
0498         }
0499 
0500         Q_FOREACH (KisNodeSP node, nodes) {
0501             if (node) {
0502                 KisNodeCommandsAdapter adapter(viewManager());
0503                 if (!viewManager()->nodeManager()->activeLayer()) {
0504                     adapter.addNode(node, kisimage->rootLayer() , 0);
0505                 } else {
0506                     adapter.addNode(node,
0507                                     viewManager()->nodeManager()->activeLayer()->parent(),
0508                                     viewManager()->nodeManager()->activeLayer());
0509                 }
0510             }
0511         }
0512     } else if (event->mimeData()->hasImage() || event->mimeData()->hasUrls()) {
0513         const auto *data = event->mimeData();
0514 
0515         KisCanvasDrop dlgAction;
0516 
0517         const auto callPos = QCursor::pos();
0518 
0519         const KisCanvasDrop::Action action = dlgAction.dropAs(*data, callPos);
0520 
0521         if (action == KisCanvasDrop::INSERT_AS_NEW_LAYER) {
0522             const QPair<bool, KisClipboard::PasteFormatBehaviour> source =
0523                 KisClipboard::instance()->askUserForSource(data);
0524 
0525             if (!source.first) {
0526                 dbgUI << "Paste event cancelled";
0527                 return;
0528             }
0529 
0530             if (source.second != KisClipboard::PASTE_FORMAT_CLIP) {
0531                 const QList<QUrl> &urls = data->urls();
0532                 const auto url = std::find_if(
0533                     urls.constBegin(),
0534                     urls.constEnd(),
0535                     [&](const QUrl &url) {
0536                         if (source.second
0537                             == KisClipboard::PASTE_FORMAT_DOWNLOAD) {
0538                             return !url.isLocalFile();
0539                         } else if (source.second
0540                                    == KisClipboard::PASTE_FORMAT_LOCAL) {
0541                             return url.isLocalFile();
0542                         } else {
0543                             return false;
0544                         }
0545                     });
0546 
0547                 if (url != urls.constEnd()) {
0548                     QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
0549                     tmp->setAutoRemove(true);
0550 
0551                     const QUrl localUrl = [&]() -> QUrl {
0552                         if (!url->isLocalFile()) {
0553                             // download the file and substitute the url
0554                             KisRemoteFileFetcher fetcher;
0555                             tmp->setFileName(url->fileName());
0556 
0557                             if (!fetcher.fetchFile(*url, tmp.data())) {
0558                                 warnUI << "Fetching" << *url << "failed";
0559                                 return {};
0560                             }
0561                             return QUrl::fromLocalFile(tmp->fileName());
0562                         }
0563                         return *url;
0564                     }();
0565 
0566                     if (localUrl.isLocalFile()) {
0567                         this->mainWindow()
0568                             ->viewManager()
0569                             ->imageManager()
0570                             ->importImage(localUrl);
0571                         this->activateWindow();
0572                         return;
0573                     }
0574                 }
0575             }
0576 
0577             KisPaintDeviceSP clip =
0578                 KisClipboard::instance()->clipFromBoardContents(data,
0579                                                                 QRect(),
0580                                                                 true,
0581                                                                 -1,
0582                                                                 false,
0583                                                                 source);
0584             if (clip) {
0585                 const auto pos = this->viewConverter()
0586                                      ->imageToDocument(imgCursorPos)
0587                                      .toPoint();
0588 
0589                 clip->moveTo(pos.x(), pos.y());
0590 
0591                 KisImportCatcher::adaptClipToImageColorSpace(clip,
0592                                                              this->image());
0593 
0594                 KisPaintLayerSP layer = new KisPaintLayer(
0595                     this->image(),
0596                     this->image()->nextLayerName() + " " + i18n("(pasted)"),
0597                     OPACITY_OPAQUE_U8,
0598                     clip);
0599                 KisNodeCommandsAdapter adapter(
0600                     this->mainWindow()->viewManager());
0601                 adapter.addNode(
0602                     layer,
0603                     this->mainWindow()->viewManager()->activeNode()->parent(),
0604                     this->mainWindow()->viewManager()->activeNode());
0605                 this->activateWindow();
0606                 return;
0607             }
0608         } else if (action == KisCanvasDrop::INSERT_AS_REFERENCE_IMAGE) {
0609             KisPaintDeviceSP clip =
0610                 KisClipboard::instance()->clipFromMimeData(data, QRect(), true);
0611             if (clip) {
0612                 KisImportCatcher::adaptClipToImageColorSpace(clip,
0613                                                              this->image());
0614 
0615                 auto *reference =
0616                     KisReferenceImage::fromPaintDevice(clip,
0617                                                        *this->viewConverter(),
0618                                                        this);
0619 
0620                 if (reference) {
0621                     const auto pos = this->canvasBase()
0622                                          ->coordinatesConverter()
0623                                          ->widgetToImage(event->pos());
0624                     reference->setPosition(
0625                         (*this->viewConverter()).imageToDocument(pos));
0626                     this->canvasBase()
0627                         ->referenceImagesDecoration()
0628                         ->addReferenceImage(reference);
0629                     KoToolManager::instance()->switchToolRequested(
0630                         "ToolReferenceImages");
0631                     return;
0632                 }
0633             }
0634         } else if (action != KisCanvasDrop::NONE) {
0635             // multiple URLs detected OR about to open a document
0636 
0637             for (QUrl url : data->urls()) { // do copy it
0638                 QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile());
0639                 tmp->setAutoRemove(true);
0640 
0641                 if (!url.isLocalFile()) {
0642                     // download the file and substitute the url
0643                     KisRemoteFileFetcher fetcher;
0644                     tmp->setFileName(url.fileName());
0645 
0646                     if (!fetcher.fetchFile(url, tmp.data())) {
0647                         qWarning() << "Fetching" << url << "failed";
0648                         continue;
0649                     }
0650                     url = QUrl::fromLocalFile(tmp->fileName());
0651                 }
0652 
0653                 if (url.isLocalFile()) {
0654                     if (action == KisCanvasDrop::INSERT_MANY_LAYERS) {
0655                         this->mainWindow()
0656                             ->viewManager()
0657                             ->imageManager()
0658                             ->importImage(url);
0659                         this->activateWindow();
0660                     } else if (action == KisCanvasDrop::INSERT_MANY_FILE_LAYERS
0661                                || action
0662                                    == KisCanvasDrop::INSERT_AS_NEW_FILE_LAYER) {
0663                         KisNodeCommandsAdapter adapter(
0664                             this->mainWindow()->viewManager());
0665                         QFileInfo fileInfo(url.toLocalFile());
0666 
0667                         QString type =
0668                             KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
0669                         QStringList mimes =
0670                             KisImportExportManager::supportedMimeTypes(
0671                                 KisImportExportManager::Import);
0672 
0673                         if (!mimes.contains(type)) {
0674                             QString msg =
0675                                 KisImportExportErrorCode(
0676                                     ImportExportCodes::FileFormatNotSupported)
0677                                     .errorMessage();
0678                             QMessageBox::warning(
0679                                 this,
0680                                 i18nc("@title:window", "Krita"),
0681                                 i18n("Could not open %2.\nReason: %1.",
0682                                      msg,
0683                                      url.toDisplayString()));
0684                             continue;
0685                         }
0686 
0687                         KisFileLayer *fileLayer =
0688                             new KisFileLayer(this->image(),
0689                                              "",
0690                                              url.toLocalFile(),
0691                                              KisFileLayer::None,
0692                                              fileInfo.fileName(),
0693                                              OPACITY_OPAQUE_U8);
0694 
0695                         KisLayerSP above =
0696                             this->mainWindow()->viewManager()->activeLayer();
0697                         KisNodeSP parent = above ? above->parent()
0698                                                  : this->mainWindow()
0699                                                        ->viewManager()
0700                                                        ->image()
0701                                                        ->root();
0702 
0703                         adapter.addNode(fileLayer, parent, above);
0704                     } else if (action == KisCanvasDrop::OPEN_IN_NEW_DOCUMENT
0705                                || action
0706                                    == KisCanvasDrop::OPEN_MANY_DOCUMENTS) {
0707                         if (this->mainWindow()) {
0708                             this->mainWindow()->openDocument(
0709                                 url.toLocalFile(),
0710                                 KisMainWindow::None);
0711                         }
0712                     } else if (action
0713                                    == KisCanvasDrop::INSERT_AS_REFERENCE_IMAGES
0714                                || action
0715                                    == KisCanvasDrop::
0716                                        INSERT_AS_REFERENCE_IMAGE) {
0717                         auto *reference =
0718                             KisReferenceImage::fromFile(url.toLocalFile(),
0719                                                         *this->viewConverter(),
0720                                                         this);
0721 
0722                         if (reference) {
0723                             const auto pos = this->canvasBase()
0724                                                  ->coordinatesConverter()
0725                                                  ->widgetToImage(event->pos());
0726                             reference->setPosition(
0727                                 (*this->viewConverter()).imageToDocument(pos));
0728                             this->canvasBase()
0729                                 ->referenceImagesDecoration()
0730                                 ->addReferenceImage(reference);
0731 
0732                             KoToolManager::instance()->switchToolRequested(
0733                                 "ToolReferenceImages");
0734                         }
0735                     }
0736                 }
0737             }
0738         }
0739     } else if (event->mimeData()->hasColor() || event->mimeData()->hasFormat("krita/x-colorsetentry")) {
0740         if (!image()) {
0741             return;
0742         }
0743 
0744         // Cannot fill on non-painting layers (vector layer, clone layer, file layer, group layer)
0745         if (d->viewManager->activeNode().isNull() ||
0746             d->viewManager->activeNode()->inherits("KisShapeLayer") ||
0747             d->viewManager->activeNode()->inherits("KisCloneLayer") ||
0748             !d->viewManager->activeDevice()) {
0749             showFloatingMessage(i18n("You cannot drag and drop colors on the selected layer type."), QIcon());
0750             return;
0751         }
0752 
0753         // Cannot fill if the layer is not editable
0754         if (!d->viewManager->activeNode()->isEditable()) {
0755             QString message;
0756             if (!d->viewManager->activeNode()->visible() && d->viewManager->activeNode()->userLocked()) {
0757                 message = i18n("Layer is locked and invisible.");
0758             } else if (d->viewManager->activeNode()->userLocked()) {
0759                 message = i18n("Layer is locked.");
0760             } else if(!d->viewManager->activeNode()->visible()) {
0761                 message = i18n("Layer is invisible.");
0762             }
0763             showFloatingMessage(message, KisIconUtils::loadIcon("object-locked"));
0764             return;
0765         }
0766 
0767         // The cursor is outside the image
0768         if (!image()->wrapAroundModePermitted() && !image()->bounds().contains(imgCursorPos)) {
0769             return;
0770         }
0771 
0772         KisProcessingApplicator applicator(image(), d->viewManager->activeNode(),
0773                                             KisProcessingApplicator::NONE,
0774                                             KisImageSignalVector(),
0775                                             kundo2_i18n("Flood Fill Layer"));
0776 
0777         KisResourcesSnapshotSP resources =
0778             new KisResourcesSnapshot(image(), d->viewManager->activeNode(), d->viewManager->canvasResourceProvider()->resourceManager());
0779 
0780         if (event->mimeData()->hasColor()) {
0781             resources->setFGColorOverride(KoColor(event->mimeData()->colorData().value<QColor>(), image()->colorSpace()));
0782         } else {
0783             QByteArray byteData = event->mimeData()->data("krita/x-colorsetentry");
0784             KisSwatch s = KisSwatch::fromByteArray(byteData);
0785             resources->setFGColorOverride(s.color());
0786         }
0787 
0788         // Use same options as the fill tool
0789         KConfigGroup configGroup = KSharedConfig::openConfig()->group("KritaFill/KisToolFill");
0790         const bool isAltPressed = event->keyboardModifiers() & Qt::AltModifier;
0791         const bool fillSelectionOnly = configGroup.readEntry("fillSelection", false) != isAltPressed;
0792         const int thresholdAmount = configGroup.readEntry("thresholdAmount", 8);
0793         const int opacitySpread = configGroup.readEntry("opacitySpread", 100);
0794         const bool useSelectionAsBoundary = configGroup.readEntry("useSelectionAsBoundary", false);
0795         const bool antiAlias = configGroup.readEntry("antiAlias", true);
0796         const int growSelection = configGroup.readEntry("growSelection", 0);
0797         const int featherAmount = configGroup.readEntry("featherAmount", 0);
0798         const QString SAMPLE_LAYERS_MODE_CURRENT = {"currentLayer"};
0799         const QString SAMPLE_LAYERS_MODE_ALL = {"allLayers"};
0800         const QString SAMPLE_LAYERS_MODE_COLOR_LABELED = {"colorLabeledLayers"};
0801         QString sampleLayersMode;
0802         if (configGroup.hasKey("sampleLayersMode")) {
0803             sampleLayersMode = configGroup.readEntry("sampleLayersMode", SAMPLE_LAYERS_MODE_CURRENT);
0804         } else { // if neither option is present in the configuration, it will fall back to CURRENT
0805             bool sampleMerged = configGroup.readEntry("sampleMerged", false);
0806             sampleLayersMode = sampleMerged ? SAMPLE_LAYERS_MODE_ALL : SAMPLE_LAYERS_MODE_CURRENT;
0807         }
0808         const bool useFastMode = !resources->activeSelection() &&
0809                                  opacitySpread == 100 &&
0810                                  useSelectionAsBoundary == false &&
0811                                  !antiAlias && growSelection == 0 && featherAmount == 0 &&
0812                                  sampleLayersMode == SAMPLE_LAYERS_MODE_CURRENT;
0813         // If the sample layer mode is other than SAMPLE_LAYERS_MODE_ALL just
0814         // default to SAMPLE_LAYERS_MODE_CURRENT. This means that
0815         // SAMPLE_LAYERS_MODE_COLOR_LABELED is not supported yet since the color
0816         // labels are not stored in the config
0817         // TODO: make this work with color labels or reference layers in the future
0818         if (sampleLayersMode != SAMPLE_LAYERS_MODE_ALL) {
0819             sampleLayersMode = SAMPLE_LAYERS_MODE_CURRENT;
0820         }
0821 
0822         KisPaintDeviceSP referencePaintDevice = nullptr;
0823         if (sampleLayersMode == SAMPLE_LAYERS_MODE_ALL) {
0824             referencePaintDevice = image()->projection();
0825         } else if (sampleLayersMode == SAMPLE_LAYERS_MODE_CURRENT) {
0826             referencePaintDevice = d->viewManager->activeNode()->paintDevice();
0827         }
0828         KIS_ASSERT(referencePaintDevice);
0829         
0830         FillProcessingVisitor *visitor = new FillProcessingVisitor(referencePaintDevice,
0831                                                                    selection(),
0832                                                                    resources);
0833         visitor->setSeedPoint(imgCursorPos);
0834         visitor->setUseFastMode(useFastMode);
0835         visitor->setSelectionOnly(fillSelectionOnly);
0836         visitor->setUseSelectionAsBoundary(useSelectionAsBoundary);
0837         visitor->setFeather(featherAmount);
0838         visitor->setSizeMod(growSelection);
0839         visitor->setFillThreshold(thresholdAmount);
0840         visitor->setOpacitySpread(opacitySpread);
0841         visitor->setAntiAlias(antiAlias);
0842         
0843         applicator.applyVisitor(visitor,
0844                                 KisStrokeJobData::SEQUENTIAL,
0845                                 KisStrokeJobData::EXCLUSIVE);
0846 
0847         applicator.end();
0848     }
0849 }
0850 
0851 void KisView::dragMoveEvent(QDragMoveEvent *event)
0852 {
0853     dbgUI << Q_FUNC_INFO
0854           << "Formats: " << event->mimeData()->formats()
0855           << "Urls: " << event->mimeData()->urls()
0856           << "Has images: " << event->mimeData()->hasImage();
0857     if (event->mimeData()->hasImage()
0858             || event->mimeData()->hasUrls()
0859             || event->mimeData()->hasFormat("application/x-krita-node-internal-pointer")
0860             || event->mimeData()->hasFormat("krita/x-colorsetentry")
0861             || event->mimeData()->hasColor()) {
0862         return event->accept();
0863     }
0864 
0865     return event->ignore();
0866 }
0867 
0868 KisDocument *KisView::document() const
0869 {
0870     return d->document;
0871 }
0872 
0873 KisView *KisView::replaceBy(KisDocument *document)
0874 {
0875     KisMainWindow *window = mainWindow();
0876     QMdiSubWindow *subWindow = d->subWindow;
0877     delete this;
0878     return window->newView(document, subWindow);
0879 }
0880 
0881 KisMainWindow * KisView::mainWindow() const
0882 {
0883     return d->viewManager->mainWindow();
0884 }
0885 
0886 void KisView::setSubWindow(QMdiSubWindow *subWindow)
0887 {
0888     d->subWindow = subWindow;
0889 }
0890 
0891 QStatusBar * KisView::statusBar() const
0892 {
0893     KisMainWindow *mw = mainWindow();
0894     return mw ? mw->statusBar() : 0;
0895 }
0896 
0897 void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving)
0898 {
0899     QStatusBar *sb = statusBar();
0900     if (sb) {
0901         sb->showMessage(text, timeout);
0902     }
0903 
0904     KisConfig cfg(true);
0905 
0906     if (!sb || sb->isHidden() ||
0907         (!isAutoSaving && cfg.forceShowSaveMessages()) ||
0908         (cfg.forceShowAutosaveMessages() && isAutoSaving)) {
0909 
0910         viewManager()->showFloatingMessage(text, QIcon());
0911     }
0912 }
0913 
0914 void KisView::slotClearStatusText()
0915 {
0916     QStatusBar *sb = statusBar();
0917     if (sb) {
0918         sb->clearMessage();
0919     }
0920 }
0921 
0922 QList<QAction*> KisView::createChangeUnitActions(bool addPixelUnit)
0923 {
0924     UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
0925     return unitActions->actions();
0926 }
0927 
0928 void KisView::closeEvent(QCloseEvent *event)
0929 {
0930     // Check whether we're the last (user visible) view
0931     int viewCount = KisPart::instance()->viewCount(document());
0932     if (viewCount > 1 || !isVisible()) {
0933         // there are others still, so don't bother the user
0934         event->accept();
0935         return;
0936     }
0937 
0938     if (queryClose()) {
0939         event->accept();
0940         return;
0941     }
0942 
0943     event->ignore();
0944 
0945 }
0946 
0947 bool KisView::queryClose()
0948 {
0949     if (!document())
0950         return true;
0951 
0952     document()->waitForSavingToComplete();
0953 
0954     if (document()->isModified()) {
0955         QString name;
0956         name = QFileInfo(document()->path()).fileName();
0957 
0958         if (name.isEmpty())
0959             name = i18n("Untitled");
0960 
0961         int res = QMessageBox::warning(this,
0962                                        i18nc("@title:window", "Krita"),
0963                                        i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
0964                                        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
0965 
0966         switch (res) {
0967         case QMessageBox::Yes : {
0968             bool isNative = (document()->mimeType() == document()->nativeFormatMimeType());
0969             if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false))
0970                 return false;
0971             break;
0972         }
0973         case QMessageBox::No : {
0974             KisImageSP image = document()->image();
0975             image->requestStrokeCancellation();
0976             viewManager()->blockUntilOperationsFinishedForced(image);
0977 
0978             document()->removeAutoSaveFiles(document()->localFilePath(), document()->isRecovered());
0979             document()->setModified(false);   // Now when queryClose() is called by closeEvent it won't do anything.
0980             break;
0981         }
0982         default : // case QMessageBox::Cancel :
0983             return false;
0984         }
0985     }
0986 
0987     return true;
0988 
0989 }
0990 
0991 void KisView::slotScreenChanged()
0992 {
0993     d->zoomManager.updateScreenResolution(this);
0994 }
0995 
0996 void KisView::slotThemeChanged(QPalette pal)
0997 {
0998     this->setPalette(pal);
0999     for (int i=0; i<this->children().size();i++) {
1000         QWidget *w = qobject_cast<QWidget*> ( this->children().at(i));
1001         if (w) {
1002             w->setPalette(pal);
1003         }
1004     }
1005     if (canvasBase()) {
1006         canvasBase()->canvasWidget()->setPalette(pal);
1007     }
1008     if (canvasController()) {
1009         canvasController()->setPalette(pal);
1010     }
1011 }
1012 
1013 void KisView::resetImageSizeAndScroll(bool changeCentering,
1014                                       const QPointF &oldImageStillPoint,
1015                                       const QPointF &newImageStillPoint)
1016 {
1017     const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter();
1018 
1019     QPointF oldPreferredCenter = d->canvasController.preferredCenter();
1020 
1021     /**
1022      * Calculating the still point in old coordinates depending on the
1023      * parameters given
1024      */
1025 
1026     QPointF oldStillPoint;
1027 
1028     if (changeCentering) {
1029         oldStillPoint =
1030                 converter->imageToWidget(oldImageStillPoint) +
1031                 converter->documentOffset();
1032     } else {
1033         QSizeF oldDocumentSize = d->canvasController.documentSize();
1034         oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
1035     }
1036 
1037     /**
1038      * Updating the document size
1039      */
1040 
1041     QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
1042     KoZoomController *zc = d->zoomManager.zoomController();
1043     zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom(),
1044                 d->zoomManager.resolutionX(), d->zoomManager.resolutionY());
1045     zc->setPageSize(size);
1046     zc->setDocumentSize(size, true);
1047 
1048     /**
1049      * Calculating the still point in new coordinates depending on the
1050      * parameters given
1051      */
1052 
1053     QPointF newStillPoint;
1054 
1055     if (changeCentering) {
1056         newStillPoint =
1057                 converter->imageToWidget(newImageStillPoint) +
1058                 converter->documentOffset();
1059     } else {
1060         QSizeF newDocumentSize = d->canvasController.documentSize();
1061         newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
1062     }
1063 
1064     d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
1065 }
1066 
1067 void KisView::syncLastActiveNodeToDocument()
1068 {
1069     KisDocument *doc = document();
1070     if (doc) {
1071         doc->setPreActivatedNode(d->currentNode);
1072     }
1073 }
1074 
1075 void KisView::saveViewState(KisPropertiesConfiguration &config) const
1076 {
1077     config.setProperty("file", d->document->path());
1078     config.setProperty("window", mainWindow()->windowStateConfig().name());
1079 
1080     if (d->subWindow) {
1081         config.setProperty("geometry", d->subWindow->saveGeometry().toBase64());
1082     }
1083 
1084     config.setProperty("zoomMode", (int)zoomController()->zoomMode());
1085     config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom());
1086 
1087     d->canvasController.saveCanvasState(config);
1088 }
1089 
1090 void KisView::restoreViewState(const KisPropertiesConfiguration &config)
1091 {
1092     if (d->subWindow) {
1093         QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1());
1094         d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry));
1095     }
1096 
1097     qreal zoom = config.getFloat("zoom", 1.0f);
1098     int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE);
1099     d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom);
1100     d->canvasController.restoreCanvasState(config);
1101 }
1102 
1103 void KisView::setCurrentNode(KisNodeSP node)
1104 {
1105     d->currentNode = node;
1106     d->canvas.slotTrySwitchShapeManager();
1107 
1108     syncLastActiveNodeToDocument();
1109 }
1110 
1111 KisNodeSP KisView::currentNode() const
1112 {
1113     return d->currentNode;
1114 }
1115 
1116 KisLayerSP KisView::currentLayer() const
1117 {
1118     KisNodeSP node;
1119     KisMaskSP mask = currentMask();
1120     if (mask) {
1121         node = mask->parent();
1122     }
1123     else {
1124         node = d->currentNode;
1125     }
1126     return qobject_cast<KisLayer*>(node.data());
1127 }
1128 
1129 KisMaskSP KisView::currentMask() const
1130 {
1131     return dynamic_cast<KisMask*>(d->currentNode.data());
1132 }
1133 
1134 KisSelectionSP KisView::selection()
1135 {
1136     KisLayerSP layer = currentLayer();
1137     if (layer)
1138         return layer->selection(); // falls through to the global
1139     // selection, or 0 in the end
1140     if (image()) {
1141         return image()->globalSelection();
1142     }
1143     return 0;
1144 }
1145 
1146 void KisView::slotSoftProofing(bool softProofing)
1147 {
1148     d->softProofing = softProofing;
1149     QString message;
1150     if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
1151     {
1152         message = i18n("Soft Proofing doesn't work in floating point.");
1153         viewManager()->showFloatingMessage(message,QIcon());
1154         return;
1155     }
1156     if (softProofing){
1157         message = i18n("Soft Proofing turned on.");
1158     } else {
1159         message = i18n("Soft Proofing turned off.");
1160     }
1161     viewManager()->showFloatingMessage(message,QIcon());
1162     canvasBase()->slotSoftProofing();
1163 }
1164 
1165 void KisView::slotGamutCheck(bool gamutCheck)
1166 {
1167     d->gamutCheck = gamutCheck;
1168     QString message;
1169     if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
1170     {
1171         message = i18n("Gamut Warnings don't work in floating point.");
1172         viewManager()->showFloatingMessage(message,QIcon());
1173         return;
1174     }
1175 
1176     if (gamutCheck){
1177         message = i18n("Gamut Warnings turned on.");
1178         if (!d->softProofing){
1179             message += "\n "+i18n("But Soft Proofing is still off.");
1180         }
1181     } else {
1182         message = i18n("Gamut Warnings turned off.");
1183     }
1184     viewManager()->showFloatingMessage(message,QIcon());
1185     canvasBase()->slotGamutCheck();
1186 }
1187 
1188 bool KisView::softProofing()
1189 {
1190     return d->softProofing;
1191 }
1192 
1193 bool KisView::gamutCheck()
1194 {
1195     return d->gamutCheck;
1196 }
1197 
1198 void KisView::slotLoadingFinished()
1199 {
1200     if (!document()) return;
1201 
1202     /**
1203      * Cold-start of image size/resolution signals
1204      */
1205     slotImageResolutionChanged();
1206 
1207     if (image()->locked()) {
1208         // If this is the first view on the image, the image will have been locked
1209         // so unlock it.
1210         image()->blockSignals(false);
1211         image()->unlock();
1212     }
1213 
1214     canvasBase()->initializeImage();
1215 
1216     /**
1217      * Dirty hack alert
1218      */
1219     d->zoomManager.zoomController()->setCanvasMappingMode(false);
1220 
1221     if (viewConverter()) {
1222         viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE);
1223     }
1224 
1225     connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
1226     connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*)));
1227     connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF)));
1228 
1229     KisNodeSP activeNode = document()->preActivatedNode();
1230 
1231     if (!activeNode) {
1232         activeNode = image()->rootLayer()->lastChild();
1233     }
1234 
1235     while (activeNode && !activeNode->inherits("KisLayer")) {
1236         activeNode = activeNode->prevSibling();
1237     }
1238 
1239     setCurrentNode(activeNode);
1240     connect(d->viewManager->mainWindow(), SIGNAL(screenChanged()), SLOT(slotScreenChanged()));
1241     zoomManager()->updateImageBoundsSnapping();
1242 }
1243 
1244 void KisView::slotSavingFinished()
1245 {
1246     if (d->viewManager && d->viewManager->mainWindow()) {
1247         d->viewManager->mainWindow()->updateCaption();
1248     }
1249 }
1250 
1251 void KisView::slotImageResolutionChanged()
1252 {
1253     resetImageSizeAndScroll(false);
1254     zoomManager()->updateImageBoundsSnapping();
1255     zoomManager()->updateGuiAfterDocumentSize();
1256 
1257     // update KoUnit value for the document
1258     if (resourceProvider()) {
1259         resourceProvider()->resourceManager()->
1260                 setResource(KoCanvasResource::Unit, d->canvas.unit());
1261     }
1262 }
1263 
1264 void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
1265 {
1266     resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
1267     zoomManager()->updateImageBoundsSnapping();
1268     zoomManager()->updateGuiAfterDocumentSize();
1269 }
1270 
1271 void KisView::closeView()
1272 {
1273     d->subWindow->close();
1274 }