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