1 /* This file is part of the KDE project
2  *
3  * Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
4  * Copyright (C) Lukáš Tvrdý <lukast.dev@gmail.com>, (C) 2010
5  * Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA..
20  */
21 
22 #include "kis_canvas2.h"
23 
24 #include <functional>
25 #include <numeric>
26 
27 #include <QApplication>
28 #include <QWidget>
29 #include <QVBoxLayout>
30 #include <QTime>
31 #include <QLabel>
32 #include <QMouseEvent>
33 #include <QDesktopWidget>
34 #include <QScreen>
35 #include <QWindow>
36 
37 #include <kis_debug.h>
38 
39 #include <KoUnit.h>
40 #include <KoShapeManager.h>
41 #include <KisSelectedShapesProxy.h>
42 #include <KoColorProfile.h>
43 #include <KoCanvasControllerWidget.h>
44 #include <KisDocument.h>
45 #include <KoSelection.h>
46 #include <KoShapeController.h>
47 
48 #include <KisUsageLogger.h>
49 
50 #include <kis_lod_transform.h>
51 #include "kis_tool_proxy.h"
52 #include "kis_coordinates_converter.h"
53 #include "kis_prescaled_projection.h"
54 #include "kis_image.h"
55 #include "kis_image_barrier_locker.h"
56 #include "kis_undo_adapter.h"
57 #include "flake/kis_shape_layer.h"
58 #include "kis_canvas_resource_provider.h"
59 #include "KisViewManager.h"
60 #include "kis_config.h"
61 #include "kis_config_notifier.h"
62 #include "kis_abstract_canvas_widget.h"
63 #include "kis_qpainter_canvas.h"
64 #include "kis_group_layer.h"
65 #include "flake/kis_shape_controller.h"
66 #include "kis_node_manager.h"
67 #include "kis_selection.h"
68 #include "kis_selection_component.h"
69 #include "flake/kis_shape_selection.h"
70 #include "kis_selection_mask.h"
71 #include "kis_image_config.h"
72 #include "kis_infinity_manager.h"
73 #include "kis_signal_compressor.h"
74 #include "kis_display_color_converter.h"
75 #include "kis_exposure_gamma_correction_interface.h"
76 #include "KisView.h"
77 #include "kis_canvas_controller.h"
78 #include "kis_grid_config.h"
79 
80 #include "kis_animation_player.h"
81 #include "kis_animation_frame_cache.h"
82 #include "opengl/kis_opengl_canvas2.h"
83 #include "opengl/kis_opengl.h"
84 #include "kis_fps_decoration.h"
85 
86 #include "KoColorConversionTransformation.h"
87 #include "KisProofingConfiguration.h"
88 
89 #include <kis_favorite_resource_manager.h>
90 #include <kis_popup_palette.h>
91 
92 #include "input/kis_input_manager.h"
93 #include "kis_painting_assistants_decoration.h"
94 
95 #include "kis_canvas_updates_compressor.h"
96 #include "KoZoomController.h"
97 
98 #include <KisStrokeSpeedMonitor.h>
99 #include "opengl/kis_opengl_canvas_debugger.h"
100 
101 #include "kis_algebra_2d.h"
102 #include "kis_image_signal_router.h"
103 
104 #include "KisSnapPixelStrategy.h"
105 
106 
107 class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
108 {
109 
110 public:
111 
KisCanvas2Private(KoCanvasBase * parent,KisCoordinatesConverter * coordConverter,QPointer<KisView> view,KoCanvasResourceProvider * resourceManager)112     KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer<KisView> view, KoCanvasResourceProvider* resourceManager)
113         : coordinatesConverter(coordConverter)
114         , view(view)
115         , shapeManager(parent)
116         , selectedShapesProxy(&shapeManager)
117         , toolProxy(parent)
118         , displayColorConverter(resourceManager, view)
119         , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
120     {
121     }
122 
123     KisCoordinatesConverter *coordinatesConverter;
124     QPointer<KisView>view;
125     KisAbstractCanvasWidget *canvasWidget = 0;
126     KoShapeManager shapeManager;
127     KisSelectedShapesProxy selectedShapesProxy;
128     bool currentCanvasIsOpenGL;
129     int openGLFilterMode;
130     KisToolProxy toolProxy;
131     KisPrescaledProjectionSP prescaledProjection;
132     bool vastScrolling;
133 
134     KisSignalCompressor canvasUpdateCompressor;
135     QRect savedUpdateRect;
136 
137     QBitArray channelFlags;
138     KisProofingConfigurationSP proofingConfig;
139     bool softProofing = false;
140     bool gamutCheck = false;
141     bool proofingConfigUpdated = false;
142 
143     KisPopupPalette *popupPalette = 0;
144     KisDisplayColorConverter displayColorConverter;
145 
146     KisCanvasUpdatesCompressor projectionUpdatesCompressor;
147     KisAnimationPlayer *animationPlayer;
148     KisAnimationFrameCacheSP frameCache;
149     bool lodAllowedInImage = false;
150     bool bootstrapLodBlocked;
151     QPointer<KoShapeManager> currentlyActiveShapeManager;
152     KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup;
153 
154     KisSignalCompressor frameRenderStartCompressor;
155 
156     KisSignalCompressor regionOfInterestUpdateCompressor;
157     QRect regionOfInterest;
158     qreal regionOfInterestMargin = 0.25;
159 
160     QRect renderingLimit;
161     int isBatchUpdateActive = 0;
162 
effectiveLodAllowedInImage()163     bool effectiveLodAllowedInImage() {
164         return lodAllowedInImage && !bootstrapLodBlocked;
165     }
166 
167     void setActiveShapeManager(KoShapeManager *shapeManager);
168 };
169 
170 namespace {
fetchShapeManagerFromNode(KisNodeSP node)171 KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node)
172 {
173     KoShapeManager *shapeManager = 0;
174     KisSelectionSP selection;
175 
176     if (KisLayer *layer = dynamic_cast<KisLayer*>(node.data())) {
177         KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(layer);
178         if (shapeLayer) {
179             shapeManager = shapeLayer->shapeManager();
180 
181         }
182     } else if (KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(node.data())) {
183         selection = mask->selection();
184     }
185 
186     if (!shapeManager && selection && selection->hasShapeSelection()) {
187         KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
188         KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0);
189 
190         shapeManager = shapeSelection->shapeManager();
191     }
192 
193     return shapeManager;
194 }
195 }
196 
KisCanvas2(KisCoordinatesConverter * coordConverter,KoCanvasResourceProvider * resourceManager,KisMainWindow * mainWindow,KisView * view,KoShapeControllerBase * sc)197 KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisMainWindow *mainWindow, KisView *view, KoShapeControllerBase *sc)
198     : KoCanvasBase(sc, resourceManager)
199     , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager))
200 {
201     /**
202      * While loading LoD should be blocked. Only when GUI has finished
203      * loading and zoom level settled down, LoD is given a green
204      * light.
205      */
206     m_d->bootstrapLodBlocked = true;
207     connect(mainWindow, SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished()));
208     connect(mainWindow, SIGNAL(screenChanged()), SLOT(slotConfigChanged()));
209 
210     KisImageConfig config(false);
211 
212     m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit());
213     m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
214 
215     m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit());
216     m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
217     snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy());
218 }
219 
setup()220 void KisCanvas2::setup()
221 {
222     // a bit of duplication from slotConfigChanged()
223     KisConfig cfg(true);
224     m_d->vastScrolling = cfg.vastScrolling();
225     m_d->lodAllowedInImage = cfg.levelOfDetailEnabled();
226     m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin();
227 
228     createCanvas(cfg.useOpenGL());
229 
230     setLodAllowedInCanvas(m_d->lodAllowedInImage);
231     m_d->animationPlayer = new KisAnimationPlayer(this);
232     connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint)));
233     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
234 
235     /**
236      * We switch the shape manager every time vector layer or
237      * shape selection is activated. Flake does not expect this
238      * and connects all the signals of the global shape manager
239      * to the clients in the constructor. To workaround this we
240      * forward the signals of local shape managers stored in the
241      * vector layers to the signals of global shape manager. So the
242      * sequence of signal deliveries is the following:
243      *
244      * shapeLayer.m_d.canvas.m_shapeManager.selection() ->
245      * shapeLayer ->
246      * shapeController ->
247      * globalShapeManager.selection()
248      */
249 
250     KisShapeController *kritaShapeController = static_cast<KisShapeController*>(shapeController()->documentBase());
251     connect(kritaShapeController, SIGNAL(selectionChanged()),
252             this, SLOT(slotSelectionChanged()));
253     connect(kritaShapeController, SIGNAL(selectionContentChanged()),
254             selectedShapesProxy(), SIGNAL(selectionContentChanged()));
255     connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
256             selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
257 
258     connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate()));
259 
260     connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start()));
261     connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection()));
262 
263     connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32)));
264 
265     connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest()));
266 
267     connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged()));
268 
269     initializeFpsDecoration();
270 }
271 
initializeFpsDecoration()272 void KisCanvas2::initializeFpsDecoration()
273 {
274     KisConfig cfg(true);
275 
276     const bool shouldShowDebugOverlay =
277         (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) ||
278         cfg.enableBrushSpeedLogging();
279 
280     if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) {
281         addDecoration(new KisFpsDecoration(imageView()));
282 
283         if (cfg.enableBrushSpeedLogging()) {
284             connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
285         }
286     } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) {
287         m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag);
288         disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
289     }
290 }
291 
~KisCanvas2()292 KisCanvas2::~KisCanvas2()
293 {
294     if (m_d->animationPlayer->isPlaying()) {
295         m_d->animationPlayer->forcedStopOnExit();
296     }
297     delete m_d;
298 }
299 
setCanvasWidget(KisAbstractCanvasWidget * widget)300 void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget)
301 {
302     if (m_d->popupPalette) {
303         m_d->popupPalette->setParent(widget->widget());
304     }
305 
306     if (m_d->canvasWidget) {
307         /**
308          * We are switching the canvas type. We should reinitialize our
309          * connections to decorations and input manager
310          */
311 
312         widget->setDecorations(m_d->canvasWidget->decorations());
313 
314         if(viewManager()) {
315             viewManager()->inputManager()->removeTrackedCanvas(this);
316             m_d->canvasWidget = widget;
317             viewManager()->inputManager()->addTrackedCanvas(this);
318         } else {
319             m_d->canvasWidget = widget;
320         }
321     } else {
322         m_d->canvasWidget = widget;
323     }
324 
325     if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) {
326         KisInfinityManager *manager = new KisInfinityManager(m_d->view, this);
327         manager->setVisible(true);
328         m_d->canvasWidget->addDecoration(manager);
329     }
330 
331     widget->widget()->setAutoFillBackground(false);
332     widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent);
333     widget->widget()->setMouseTracking(true);
334     widget->widget()->setAcceptDrops(true);
335 
336     KoCanvasControllerWidget *controller = dynamic_cast<KoCanvasControllerWidget*>(canvasController());
337     if (controller && controller->canvas() == this) {
338         controller->changeCanvasWidget(widget->widget());
339     }
340 }
341 
canvasIsOpenGL() const342 bool KisCanvas2::canvasIsOpenGL() const
343 {
344     return m_d->currentCanvasIsOpenGL;
345 }
346 
openGLFilterMode() const347 KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const
348 {
349     return KisOpenGL::FilterMode(m_d->openGLFilterMode);
350 }
351 
gridSize(QPointF * offset,QSizeF * spacing) const352 void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const
353 {
354     QTransform transform = coordinatesConverter()->imageToDocumentTransform();
355 
356     const QPoint intSpacing = m_d->view->document()->gridConfig().spacing();
357     const QPoint intOffset = m_d->view->document()->gridConfig().offset();
358 
359     QPointF size = transform.map(QPointF(intSpacing));
360     spacing->rwidth() = size.x();
361     spacing->rheight() = size.y();
362 
363     *offset = transform.map(QPointF(intOffset));
364 }
365 
snapToGrid() const366 bool KisCanvas2::snapToGrid() const
367 {
368     return m_d->view->document()->gridConfig().snapToGrid();
369 }
370 
rotationAngle() const371 qreal KisCanvas2::rotationAngle() const
372 {
373     return m_d->coordinatesConverter->rotationAngle();
374 }
375 
xAxisMirrored() const376 bool KisCanvas2::xAxisMirrored() const
377 {
378     return m_d->coordinatesConverter->xAxisMirrored();
379 }
380 
yAxisMirrored() const381 bool KisCanvas2::yAxisMirrored() const
382 {
383     return m_d->coordinatesConverter->yAxisMirrored();
384 }
385 
channelSelectionChanged()386 void KisCanvas2::channelSelectionChanged()
387 {
388     KisImageSP image = this->image();
389     m_d->channelFlags = image->rootLayer()->channelFlags();
390 
391     m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
392 
393     image->barrierLock();
394     m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
395     startUpdateInPatches(image->bounds());
396     image->unlock();
397 }
398 
addCommand(KUndo2Command * command)399 void KisCanvas2::addCommand(KUndo2Command *command)
400 {
401     // This method exists to support flake-related operations
402     m_d->view->document()->addCommand(command);
403 }
404 
setActiveShapeManager(KoShapeManager * shapeManager)405 void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager)
406 {
407     if (shapeManager != currentlyActiveShapeManager) {
408         currentlyActiveShapeManager = shapeManager;
409         selectedShapesProxy.setShapeManager(shapeManager);
410     }
411 }
412 
shapeManager() const413 KoShapeManager* KisCanvas2::shapeManager() const
414 {
415     KoShapeManager *localShapeManager = this->localShapeManager();
416 
417     // sanity check for consistency of the local shape manager
418     KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) {
419         localShapeManager = globalShapeManager();
420     }
421 
422     return localShapeManager ? localShapeManager : globalShapeManager();
423 }
424 
selectedShapesProxy() const425 KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const
426 {
427     return &m_d->selectedShapesProxy;
428 }
429 
globalShapeManager() const430 KoShapeManager* KisCanvas2::globalShapeManager() const
431 {
432     return &m_d->shapeManager;
433 }
434 
localShapeManager() const435 KoShapeManager *KisCanvas2::localShapeManager() const
436 {
437     KisNodeSP node = m_d->view->currentNode();
438     KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node);
439 
440     if (localShapeManager != m_d->currentlyActiveShapeManager) {
441         m_d->setActiveShapeManager(localShapeManager);
442     }
443 
444     return localShapeManager;
445 }
446 
updateInputMethodInfo()447 void KisCanvas2::updateInputMethodInfo()
448 {
449     // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget...
450 }
451 
coordinatesConverter() const452 const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const
453 {
454     return m_d->coordinatesConverter;
455 }
456 
viewConverter() const457 KoViewConverter* KisCanvas2::viewConverter() const
458 {
459     return m_d->coordinatesConverter;
460 }
461 
globalInputManager() const462 KisInputManager* KisCanvas2::globalInputManager() const
463 {
464     return m_d->view->globalInputManager();
465 }
466 
inputActionGroupsMask() const467 KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const
468 {
469     return m_d->inputActionGroupsMask;
470 }
471 
setInputActionGroupsMask(KisInputActionGroupsMask mask)472 void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask)
473 {
474     m_d->inputActionGroupsMask = mask;
475 }
476 
canvasWidget()477 QWidget* KisCanvas2::canvasWidget()
478 {
479     return m_d->canvasWidget->widget();
480 }
481 
canvasWidget() const482 const QWidget* KisCanvas2::canvasWidget() const
483 {
484     return m_d->canvasWidget->widget();
485 }
486 
487 
unit() const488 KoUnit KisCanvas2::unit() const
489 {
490     KoUnit unit(KoUnit::Pixel);
491 
492     KisImageWSP image = m_d->view->image();
493     if (image) {
494         if (!qFuzzyCompare(image->xRes(), image->yRes())) {
495             warnKrita << "WARNING: resolution of the image is anisotropic"
496                        << ppVar(image->xRes())
497                        << ppVar(image->yRes());
498         }
499 
500         const qreal resolution = image->xRes();
501         unit.setFactor(resolution);
502     }
503 
504     return unit;
505 }
506 
toolProxy() const507 KoToolProxy * KisCanvas2::toolProxy() const
508 {
509     return &m_d->toolProxy;
510 }
511 
createQPainterCanvas()512 void KisCanvas2::createQPainterCanvas()
513 {
514     m_d->currentCanvasIsOpenGL = false;
515 
516     KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view);
517     m_d->prescaledProjection = new KisPrescaledProjection();
518     m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter);
519     m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(),
520                                                 m_d->displayColorConverter.renderingIntent(),
521                                                 m_d->displayColorConverter.conversionFlags());
522     m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter());
523     canvasWidget->setPrescaledProjection(m_d->prescaledProjection);
524     setCanvasWidget(canvasWidget);
525 }
526 
createOpenGLCanvas()527 void KisCanvas2::createOpenGLCanvas()
528 {
529     KisConfig cfg(true);
530     m_d->openGLFilterMode = cfg.openGLFilteringMode();
531     m_d->currentCanvasIsOpenGL = true;
532 
533     KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter);
534     m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures());
535 
536     setCanvasWidget(canvasWidget);
537 }
538 
createCanvas(bool useOpenGL)539 void KisCanvas2::createCanvas(bool useOpenGL)
540 {
541     // deinitialize previous canvas structures
542     m_d->prescaledProjection = 0;
543     m_d->frameCache = 0;
544 
545     KisConfig cfg(true);
546     QDesktopWidget dw;
547     const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView()));
548     m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL());
549     m_d->displayColorConverter.setMonitorProfile(profile);
550 
551     if (useOpenGL && !KisOpenGL::hasOpenGL()) {
552         warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
553         useOpenGL = false;
554     }
555 
556     m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL);
557 
558     if (useOpenGL) {
559         createOpenGLCanvas();
560         if (cfg.canvasState() == "OPENGL_FAILED") {
561             // Creating the opengl canvas failed, fall back
562             warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter.";
563             m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false);
564             createQPainterCanvas();
565         }
566     } else {
567         createQPainterCanvas();
568     }
569 
570     if (m_d->popupPalette) {
571         m_d->popupPalette->setParent(m_d->canvasWidget->widget());
572     }
573 
574 }
575 
initializeImage()576 void KisCanvas2::initializeImage()
577 {
578     KisImageSP image = m_d->view->image();
579 
580     m_d->displayColorConverter.setImageColorSpace(image->colorSpace());
581     m_d->coordinatesConverter->setImage(image);
582     m_d->toolProxy.initializeImage(image);
583 
584     connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection);
585     connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection);
586     connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection);
587     connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection);
588 
589     connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig()));
590     connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection);
591     connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager()));
592 
593     connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()));
594     connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged()));
595 
596     connectCurrentCanvas();
597 }
598 
disconnectImage()599 void KisCanvas2::disconnectImage()
600 {
601     KisImageSP image = m_d->view->image();
602 
603     /**
604      * We explicitly don't use barrierLock() here, because we don't care about
605      * all the updates completed (we don't use image's content). We only need to
606      * guarantee that the image will not try to access us in a multithreaded way,
607      * while we are being destroyed.
608      */
609 
610     image->lock();
611     disconnect(image.data(), 0, this, 0);
612     image->unlock();
613 }
614 
connectCurrentCanvas()615 void KisCanvas2::connectCurrentCanvas()
616 {
617     KisImageWSP image = m_d->view->image();
618 
619     if (!m_d->currentCanvasIsOpenGL) {
620         Q_ASSERT(m_d->prescaledProjection);
621         m_d->prescaledProjection->setImage(image);
622     }
623 
624     startResizingImage();
625     setLodAllowedInCanvas(m_d->lodAllowedInImage);
626 
627     emit sigCanvasEngineChanged();
628 }
629 
resetCanvas(bool useOpenGL)630 void KisCanvas2::resetCanvas(bool useOpenGL)
631 {
632     // we cannot reset the canvas before it's created, but this method might be called,
633     // for instance when setting the monitor profile.
634     if (!m_d->canvasWidget) {
635         return;
636     }
637     KisConfig cfg(true);
638     bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) ||
639         (m_d->currentCanvasIsOpenGL &&
640          m_d->openGLFilterMode != cfg.openGLFilteringMode());
641 
642     if (needReset) {
643         createCanvas(useOpenGL);
644         connectCurrentCanvas();
645         notifyZoomChanged();
646     }
647     updateCanvasWidgetImpl();
648 }
649 
startUpdateInPatches(const QRect & imageRect)650 void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
651 {
652     /**
653      * We don't do patched loading for openGL canvas, because it loads
654      * the tiles, which are basically "patches". Therefore, big chunks
655      * of memory are never allocated.
656      */
657     if (m_d->currentCanvasIsOpenGL) {
658         startUpdateCanvasProjection(imageRect);
659     } else {
660         KisImageConfig imageConfig(true);
661         int patchWidth = imageConfig.updatePatchWidth();
662         int patchHeight = imageConfig.updatePatchHeight();
663 
664         for (int y = 0; y < imageRect.height(); y += patchHeight) {
665             for (int x = 0; x < imageRect.width(); x += patchWidth) {
666                 QRect patchRect(x, y, patchWidth, patchHeight);
667                 startUpdateCanvasProjection(patchRect);
668             }
669         }
670     }
671 }
672 
setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)673 void KisCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
674 {
675     m_d->displayColorConverter.setDisplayFilter(displayFilter);
676     KisImageSP image = this->image();
677 
678     m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
679 
680     image->barrierLock();
681     m_d->canvasWidget->setDisplayFilter(displayFilter);
682     image->unlock();
683 }
684 
displayFilter() const685 QSharedPointer<KisDisplayFilter> KisCanvas2::displayFilter() const
686 {
687     return m_d->displayColorConverter.displayFilter();
688 }
689 
slotImageColorSpaceChanged()690 void KisCanvas2::slotImageColorSpaceChanged()
691 {
692     KisImageSP image = this->image();
693 
694     m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
695 
696     m_d->displayColorConverter.setImageColorSpace(image->colorSpace());
697 
698     image->barrierLock();
699     m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace());
700     image->unlock();
701 }
702 
displayColorConverter() const703 KisDisplayColorConverter* KisCanvas2::displayColorConverter() const
704 {
705     return &m_d->displayColorConverter;
706 }
707 
exposureGammaCorrectionInterface() const708 KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const
709 {
710     QSharedPointer<KisDisplayFilter> displayFilter = m_d->displayColorConverter.displayFilter();
711 
712     return displayFilter ?
713         displayFilter->correctionInterface() :
714         KisDumbExposureGammaCorrectionInterface::instance();
715 }
716 
setProofingOptions(bool softProof,bool gamutCheck)717 void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck)
718 {
719     m_d->proofingConfig = this->image()->proofingConfiguration();
720     if (!m_d->proofingConfig) {
721         KisImageConfig cfg(false);
722         m_d->proofingConfig = cfg.defaultProofingconfiguration();
723     }
724     KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags;
725     if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
726         conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof);
727         if (softProof) {
728             conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck);
729         }
730     }
731     m_d->proofingConfig->conversionFlags = conversionFlags;
732 
733     m_d->proofingConfigUpdated = true;
734 
735     refetchDataFromImage();
736 }
737 
slotSoftProofing(bool softProofing)738 void KisCanvas2::slotSoftProofing(bool softProofing)
739 {
740     m_d->softProofing = softProofing;
741     setProofingOptions(m_d->softProofing, m_d->gamutCheck);
742 }
743 
slotGamutCheck(bool gamutCheck)744 void KisCanvas2::slotGamutCheck(bool gamutCheck)
745 {
746     m_d->gamutCheck = gamutCheck;
747     setProofingOptions(m_d->softProofing, m_d->gamutCheck);
748 }
749 
slotChangeProofingConfig()750 void KisCanvas2::slotChangeProofingConfig()
751 {
752     setProofingOptions(m_d->softProofing, m_d->gamutCheck);
753 }
754 
setProofingConfigUpdated(bool updated)755 void KisCanvas2::setProofingConfigUpdated(bool updated)
756 {
757     m_d->proofingConfigUpdated = updated;
758 }
759 
proofingConfigUpdated()760 bool KisCanvas2::proofingConfigUpdated()
761 {
762     return m_d->proofingConfigUpdated;
763 }
764 
proofingConfiguration() const765 KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const
766 {
767     if (!m_d->proofingConfig) {
768         m_d->proofingConfig = this->image()->proofingConfiguration();
769         if (!m_d->proofingConfig) {
770             m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration();
771         }
772     }
773     return m_d->proofingConfig;
774 }
775 
startResizingImage()776 void KisCanvas2::startResizingImage()
777 {
778     KisImageWSP image = this->image();
779     qint32 w = image->width();
780     qint32 h = image->height();
781 
782     emit sigContinueResizeImage(w, h);
783 
784     QRect imageBounds(0, 0, w, h);
785     startUpdateInPatches(imageBounds);
786 }
787 
finishResizingImage(qint32 w,qint32 h)788 void KisCanvas2::finishResizingImage(qint32 w, qint32 h)
789 {
790     m_d->canvasWidget->finishResizingImage(w, h);
791 }
792 
startUpdateCanvasProjection(const QRect & rc)793 void KisCanvas2::startUpdateCanvasProjection(const QRect & rc)
794 {
795     KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags);
796     if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) {
797         emit sigCanvasCacheUpdated();
798     }
799 }
800 
updateCanvasProjection()801 void KisCanvas2::updateCanvasProjection()
802 {
803     auto tryIssueCanvasUpdates = [this](const QRect &vRect) {
804         if (!m_d->isBatchUpdateActive) {
805             // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas
806             if (m_d->currentCanvasIsOpenGL) {
807                 m_d->savedUpdateRect |= vRect;
808 
809                 // we already had a compression in frameRenderStartCompressor, so force the update directly
810                 slotDoCanvasUpdate();
811             } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) {
812                 m_d->savedUpdateRect |= m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect();
813 
814                 // we already had a compression in frameRenderStartCompressor, so force the update directly
815                 slotDoCanvasUpdate();
816             }
817         }
818     };
819 
820     auto uploadData = [this, tryIssueCanvasUpdates](const QVector<KisUpdateInfoSP> &infoObjects) {
821         QVector<QRect> viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects);
822         const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(),
823                                             QRect(), std::bit_or<QRect>());
824 
825         tryIssueCanvasUpdates(vRect);
826     };
827 
828     bool shouldExplicitlyIssueUpdates = false;
829 
830     QVector<KisUpdateInfoSP> infoObjects;
831     KisUpdateInfoList originalInfoObjects;
832     m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects);
833 
834     for (auto it = originalInfoObjects.constBegin();
835          it != originalInfoObjects.constEnd();
836          ++it) {
837 
838         KisUpdateInfoSP info = *it;
839 
840         const KisMarkerUpdateInfo *batchInfo = dynamic_cast<const KisMarkerUpdateInfo*>(info.data());
841         if (batchInfo) {
842             if (!infoObjects.isEmpty()) {
843                 uploadData(infoObjects);
844                 infoObjects.clear();
845             }
846 
847             if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) {
848                 m_d->isBatchUpdateActive++;
849             } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) {
850                 m_d->isBatchUpdateActive--;
851                 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0);
852                 if (m_d->isBatchUpdateActive == 0) {
853                     shouldExplicitlyIssueUpdates = true;
854                 }
855             } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) {
856                 m_d->canvasWidget->setLodResetInProgress(true);
857             } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) {
858                 m_d->canvasWidget->setLodResetInProgress(false);
859                 shouldExplicitlyIssueUpdates = true;
860             }
861         } else {
862             infoObjects << info;
863         }
864     }
865 
866     if (!infoObjects.isEmpty()) {
867         uploadData(infoObjects);
868     } else if (shouldExplicitlyIssueUpdates) {
869         tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels());
870     }
871 }
872 
slotBeginUpdatesBatch()873 void KisCanvas2::slotBeginUpdatesBatch()
874 {
875     KisUpdateInfoSP info =
876         new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch,
877                                       m_d->coordinatesConverter->imageRectInImagePixels());
878     m_d->projectionUpdatesCompressor.putUpdateInfo(info);
879     emit sigCanvasCacheUpdated();
880 }
881 
slotEndUpdatesBatch()882 void KisCanvas2::slotEndUpdatesBatch()
883 {
884     KisUpdateInfoSP info =
885         new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch,
886                                       m_d->coordinatesConverter->imageRectInImagePixels());
887     m_d->projectionUpdatesCompressor.putUpdateInfo(info);
888     emit sigCanvasCacheUpdated();
889 }
890 
slotSetLodUpdatesBlocked(bool value)891 void KisCanvas2::slotSetLodUpdatesBlocked(bool value)
892 {
893     KisUpdateInfoSP info =
894         new KisMarkerUpdateInfo(value ?
895                                 KisMarkerUpdateInfo::BlockLodUpdates :
896                                 KisMarkerUpdateInfo::UnblockLodUpdates,
897                                 m_d->coordinatesConverter->imageRectInImagePixels());
898     m_d->projectionUpdatesCompressor.putUpdateInfo(info);
899     emit sigCanvasCacheUpdated();
900 }
901 
slotDoCanvasUpdate()902 void KisCanvas2::slotDoCanvasUpdate()
903 {
904     /**
905      * WARNING: in isBusy() we access openGL functions without making the painting
906      * context current. We hope that currently active context will be Qt's one,
907      * which is shared with our own.
908      */
909     if (m_d->canvasWidget->isBusy()) {
910         // just restarting the timer
911         updateCanvasWidgetImpl(m_d->savedUpdateRect);
912         return;
913     }
914 
915     if (!m_d->savedUpdateRect.isEmpty()) {
916         emit updateCanvasRequested(m_d->savedUpdateRect);
917 
918         if (wrapAroundViewingMode()) {
919             const QRectF rc = m_d->savedUpdateRect;
920             const QRectF widgetRect = m_d->canvasWidget->widget()->rect();
921             const QRectF imageRect = m_d->coordinatesConverter->imageRectInWidgetPixels();
922 
923             const qreal relX = KisAlgebra2D::wrapValue(rc.x() - imageRect.x(), imageRect.width());
924             const qreal relY = KisAlgebra2D::wrapValue(rc.y() - imageRect.y(), imageRect.height());
925 
926             const qreal baseX = std::fmod(imageRect.right(), imageRect.width()) - imageRect.width();
927             const qreal baseY = std::fmod(imageRect.bottom(), imageRect.height()) - imageRect.height();
928 
929             for (qreal y = baseY; y < widgetRect.bottom(); y += imageRect.height()) {
930                 for (qreal x = baseX; x < widgetRect.right(); x += imageRect.width()) {
931                     const QRectF proposedUpdateRect(x + relX, y + relY,
932                                                     rc.width(), rc.height());
933 
934                     const QRect updateRect = (proposedUpdateRect & widgetRect).toAlignedRect();
935                     if (!updateRect.isEmpty()) {
936                         m_d->canvasWidget->widget()->update(updateRect);
937                     }
938                 }
939             }
940         } else {
941             m_d->canvasWidget->widget()->update(m_d->savedUpdateRect);
942         }
943     }
944 
945     m_d->savedUpdateRect = QRect();
946 }
947 
updateCanvasWidgetImpl(const QRect & rc)948 void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc)
949 {
950     m_d->savedUpdateRect |= !rc.isEmpty() ? rc : m_d->canvasWidget->widget()->rect();
951     m_d->canvasUpdateCompressor.start();
952 }
953 
updateCanvas()954 void KisCanvas2::updateCanvas()
955 {
956     updateCanvasWidgetImpl();
957 }
958 
updateCanvas(const QRectF & documentRect)959 void KisCanvas2::updateCanvas(const QRectF& documentRect)
960 {
961     // updateCanvas is called from tools, never from the projection
962     // updates, so no need to prescale!
963     QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect();
964     widgetRect.adjust(-2, -2, 2, 2);
965     if (!widgetRect.isEmpty()) {
966         updateCanvasWidgetImpl(widgetRect);
967     }
968 }
969 
disconnectCanvasObserver(QObject * object)970 void KisCanvas2::disconnectCanvasObserver(QObject *object)
971 {
972     KoCanvasBase::disconnectCanvasObserver(object);
973     m_d->view->disconnect(object);
974 }
975 
notifyZoomChanged()976 void KisCanvas2::notifyZoomChanged()
977 {
978     if (!m_d->currentCanvasIsOpenGL) {
979         Q_ASSERT(m_d->prescaledProjection);
980         m_d->prescaledProjection->notifyZoomChanged();
981     }
982 
983     notifyLevelOfDetailChange();
984     updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction
985 
986     m_d->regionOfInterestUpdateCompressor.start();
987 }
988 
regionOfInterest() const989 QRect KisCanvas2::regionOfInterest() const
990 {
991     return m_d->regionOfInterest;
992 }
993 
slotUpdateRegionOfInterest()994 void KisCanvas2::slotUpdateRegionOfInterest()
995 {
996     const QRect oldRegionOfInterest = m_d->regionOfInterest;
997 
998     const qreal ratio = m_d->regionOfInterestMargin;
999     const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect();
1000 
1001     const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels();
1002 
1003     m_d->regionOfInterest = proposedRoi & imageRect;
1004 
1005     if (m_d->regionOfInterest != oldRegionOfInterest) {
1006         emit sigRegionOfInterestChanged(m_d->regionOfInterest);
1007     }
1008 }
1009 
slotReferenceImagesChanged()1010 void KisCanvas2::slotReferenceImagesChanged()
1011 {
1012     canvasController()->resetScrollBars();
1013 }
1014 
setRenderingLimit(const QRect & rc)1015 void KisCanvas2::setRenderingLimit(const QRect &rc)
1016 {
1017     m_d->renderingLimit = rc;
1018 }
1019 
renderingLimit() const1020 QRect KisCanvas2::renderingLimit() const
1021 {
1022     return m_d->renderingLimit;
1023 }
1024 
slotTrySwitchShapeManager()1025 void KisCanvas2::slotTrySwitchShapeManager()
1026 {
1027     KisNodeSP node = m_d->view->currentNode();
1028 
1029     QPointer<KoShapeManager> newManager;
1030     newManager = fetchShapeManagerFromNode(node);
1031 
1032     m_d->setActiveShapeManager(newManager);
1033 }
1034 
notifyLevelOfDetailChange()1035 void KisCanvas2::notifyLevelOfDetailChange()
1036 {
1037     if (!m_d->effectiveLodAllowedInImage()) return;
1038 
1039     const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom();
1040 
1041     KisConfig cfg(true);
1042     const int maxLod = cfg.numMipmapLevels();
1043 
1044     const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
1045 
1046     if (m_d->effectiveLodAllowedInImage()) {
1047         KisImageSP image = this->image();
1048         image->setDesiredLevelOfDetail(lod);
1049     }
1050 }
1051 
monitorProfile()1052 const KoColorProfile *  KisCanvas2::monitorProfile()
1053 {
1054     return m_d->displayColorConverter.monitorProfile();
1055 }
1056 
viewManager() const1057 KisViewManager* KisCanvas2::viewManager() const
1058 {
1059     if (m_d->view) {
1060         return m_d->view->viewManager();
1061     }
1062     return 0;
1063 }
1064 
imageView() const1065 QPointer<KisView>KisCanvas2::imageView() const
1066 {
1067     return m_d->view;
1068 }
1069 
image() const1070 KisImageWSP KisCanvas2::image() const
1071 {
1072     return m_d->view->image();
1073 
1074 }
1075 
currentImage() const1076 KisImageWSP KisCanvas2::currentImage() const
1077 {
1078     return m_d->view->image();
1079 }
1080 
documentOffsetMoved(const QPoint & documentOffset)1081 void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
1082 {
1083     QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
1084 
1085     // The given offset is in widget logical pixels. In order to prevent fuzzy
1086     // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio
1087     // is not integral, we adjusts the offset to map to whole device pixels.
1088     //
1089     // FIXME: This is a temporary hack for fixing the canvas under fractional
1090     //        DPI scaling before a new coordinate system is introduced.
1091     QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset);
1092 
1093     m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted);
1094     QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
1095 
1096     QPointF moveOffset = offsetAfter - offsetBefore;
1097 
1098     if (!m_d->currentCanvasIsOpenGL)
1099         m_d->prescaledProjection->viewportMoved(moveOffset);
1100 
1101     emit documentOffsetUpdateFinished();
1102 
1103     updateCanvas();
1104 
1105     m_d->regionOfInterestUpdateCompressor.start();
1106 }
1107 
slotConfigChanged()1108 void KisCanvas2::slotConfigChanged()
1109 {
1110     KisConfig cfg(true);
1111     m_d->vastScrolling = cfg.vastScrolling();
1112     m_d->regionOfInterestMargin = KisImageConfig(true).animationCacheRegionOfInterestMargin();
1113 
1114     resetCanvas(cfg.useOpenGL());
1115 
1116     // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the
1117     //       proper screenNumber when moving the window across screens. Using
1118     //       the coordinates should be able to work around this.
1119     // FIXME: We should change to associate the display profiles with the screen
1120     //        model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498
1121     int canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget());
1122     if (canvasScreenNumber != -1) {
1123         setDisplayProfile(cfg.displayProfile(canvasScreenNumber));
1124     } else {
1125         warnUI << "Failed to get screenNumber for updating display profile.";
1126     }
1127 
1128     initializeFpsDecoration();
1129 }
1130 
refetchDataFromImage()1131 void KisCanvas2::refetchDataFromImage()
1132 {
1133     KisImageSP image = this->image();
1134     KisImageBarrierLocker l(image);
1135     startUpdateInPatches(image->bounds());
1136 }
1137 
setDisplayProfile(const KoColorProfile * monitorProfile)1138 void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile)
1139 {
1140     if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return;
1141 
1142     m_d->displayColorConverter.setMonitorProfile(monitorProfile);
1143 
1144     {
1145         KisImageSP image = this->image();
1146         KisImageBarrierLocker l(image);
1147         m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter);
1148     }
1149 
1150     refetchDataFromImage();
1151 }
1152 
addDecoration(KisCanvasDecorationSP deco)1153 void KisCanvas2::addDecoration(KisCanvasDecorationSP deco)
1154 {
1155     m_d->canvasWidget->addDecoration(deco);
1156 }
1157 
decoration(const QString & id) const1158 KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const
1159 {
1160     return m_d->canvasWidget->decoration(id);
1161 }
1162 
1163 
documentOrigin() const1164 QPoint KisCanvas2::documentOrigin() const
1165 {
1166     /**
1167      * In Krita we don't use document origin anymore.
1168      * All the centering when needed (vastScrolling < 0.5) is done
1169      * automatically by the KisCoordinatesConverter.
1170      */
1171 
1172     return QPoint();
1173 }
1174 
documentOffset() const1175 QPoint KisCanvas2::documentOffset() const
1176 {
1177     return m_d->coordinatesConverter->documentOffset();
1178 }
1179 
setFavoriteResourceManager(KisFavoriteResourceManager * favoriteResourceManager)1180 void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager)
1181 {
1182     m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(),
1183                                             m_d->view->resourceProvider(), m_d->canvasWidget->widget());
1184     connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int)));
1185     connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
1186     connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons()));
1187 
1188     m_d->popupPalette->showPopupPalette(false);
1189 }
1190 
slotPopupPaletteRequestedZoomChange(int zoom)1191 void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) {
1192     m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom
1193     notifyZoomChanged();
1194 }
1195 
setCursor(const QCursor & cursor)1196 void KisCanvas2::setCursor(const QCursor &cursor)
1197 {
1198     canvasWidget()->setCursor(cursor);
1199 }
1200 
frameCache() const1201 KisAnimationFrameCacheSP KisCanvas2::frameCache() const
1202 {
1203     return m_d->frameCache;
1204 }
1205 
animationPlayer() const1206 KisAnimationPlayer *KisCanvas2::animationPlayer() const
1207 {
1208     return m_d->animationPlayer;
1209 }
1210 
slotSelectionChanged()1211 void KisCanvas2::slotSelectionChanged()
1212 {
1213     KisShapeLayer* shapeLayer = dynamic_cast<KisShapeLayer*>(viewManager()->activeLayer().data());
1214     if (!shapeLayer) {
1215         return;
1216     }
1217     m_d->shapeManager.selection()->deselectAll();
1218     Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) {
1219         m_d->shapeManager.selection()->select(shape);
1220     }
1221 }
1222 
isPopupPaletteVisible() const1223 bool KisCanvas2::isPopupPaletteVisible() const
1224 {
1225     if (!m_d->popupPalette) {
1226         return false;
1227     }
1228     return m_d->popupPalette->isVisible();
1229 }
1230 
1231 
setWrapAroundViewingMode(bool value)1232 void KisCanvas2::setWrapAroundViewingMode(bool value)
1233 {
1234     KisCanvasDecorationSP infinityDecoration =
1235         m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
1236 
1237     if (infinityDecoration) {
1238         infinityDecoration->setVisible(!value);
1239     }
1240 
1241     m_d->canvasWidget->setWrapAroundViewingMode(value);
1242 }
1243 
wrapAroundViewingMode() const1244 bool KisCanvas2::wrapAroundViewingMode() const
1245 {
1246     return m_d->canvasWidget->wrapAroundViewingMode();
1247 }
1248 
bootstrapFinished()1249 void KisCanvas2::bootstrapFinished()
1250 {
1251     if (!m_d->bootstrapLodBlocked) return;
1252 
1253     m_d->bootstrapLodBlocked = false;
1254     setLodAllowedInCanvas(m_d->lodAllowedInImage);
1255 }
1256 
setLodAllowedInCanvas(bool value)1257 void KisCanvas2::setLodAllowedInCanvas(bool value)
1258 {
1259     if (!KisOpenGL::supportsLoD()) {
1260         qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support";
1261     }
1262 
1263     m_d->lodAllowedInImage =
1264         value &&
1265         m_d->currentCanvasIsOpenGL &&
1266         KisOpenGL::supportsLoD() &&
1267         (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
1268          m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
1269 
1270     KisImageSP image = this->image();
1271 
1272     if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) {
1273         image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage());
1274     }
1275 
1276     notifyLevelOfDetailChange();
1277 
1278     KisConfig cfg(false);
1279     cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage);
1280 }
1281 
lodAllowedInCanvas() const1282 bool KisCanvas2::lodAllowedInCanvas() const
1283 {
1284     return m_d->lodAllowedInImage;
1285 }
1286 
slotShowPopupPalette(const QPoint & p)1287 void KisCanvas2::slotShowPopupPalette(const QPoint &p)
1288 {
1289     if (!m_d->popupPalette) {
1290         return;
1291     }
1292 
1293     m_d->popupPalette->showPopupPalette(p);
1294 }
1295 
paintingAssistantsDecoration() const1296 KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
1297 {
1298     KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
1299     return qobject_cast<KisPaintingAssistantsDecoration*>(deco.data());
1300 }
1301 
referenceImagesDecoration() const1302 KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const
1303 {
1304     KisCanvasDecorationSP deco = decoration("referenceImagesDecoration");
1305     return qobject_cast<KisReferenceImagesDecoration*>(deco.data());
1306 }
1307