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 }