Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_canvas_controller.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QMouseEvent>
10#include <QScrollBar>
11#include <QTabletEvent>
12
13#include <klocalizedstring.h>
14#include <kactioncollection.h>
17#include "kis_canvas2.h"
19#include "KisDocument.h"
20#include "kis_image.h"
21#include "KisViewManager.h"
22#include "KisView.h"
23#include "krita_utils.h"
24#include "kis_config.h"
26#include "kis_config_notifier.h"
27#include <KoUnit.h>
29
30#include "KisCanvasState.h"
31
32static const int gRulersUpdateDelay = 80 /* ms */;
33
36 : q(qq)
37 {
38 using namespace std::placeholders;
39
40 std::function<void (QPoint)> callback(
42
46 callback,
48 }
49
53 QScopedPointer<KisSignalCompressorWithParam<QPoint> > mousePositionCompressor;
55 qreal physicalDpiX {72.0};
56 qreal physicalDpiY {72.0};
57 qreal devicePixelRatio {1.0};
58
59 void emitPointerPositionChangedSignals(QPoint pointerPos);
62};
63
65{
66 if (!coordinatesConverter) return;
67
68 QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos);
69
70 q->proxyObject->emitDocumentMousePositionChanged(documentPos);
71 q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
72}
73
80
85
87{
88 if (canvas) {
89 KisCanvas2 *kritaCanvas = qobject_cast<KisCanvas2*>(canvas);
91 const_cast<KisCoordinatesConverter*>(kritaCanvas->coordinatesConverter());
92 } else {
94 }
95
97}
98
100{
101 if (qFuzzyCompare(parentWidget->physicalDpiX(), m_d->physicalDpiX) &&
102 qFuzzyCompare(parentWidget->physicalDpiY(), m_d->physicalDpiY) &&
103 qFuzzyCompare(parentWidget->devicePixelRatioF(), m_d->devicePixelRatio)) {
104
105 return;
106 }
107
108 m_d->physicalDpiX = parentWidget->physicalDpiX();
109 m_d->physicalDpiY = parentWidget->physicalDpiY();
110 m_d->devicePixelRatio = parentWidget->devicePixelRatioF();
111
113
115}
116
122
128
133
135{
136 KoCanvasBase *canvas = m_d->view->canvasBase();
137 QWidget *canvasWidget = canvas->canvasWidget();
138 const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos());
139
140 return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget);
141}
142
144{
150 Q_UNUSED(event);
151}
152
153void KisCanvasController::wheelEvent(QWheelEvent *event)
154{
160 Q_UNUSED(event);
161}
162
163bool KisCanvasController::eventFilter(QObject *watched, QEvent *event)
164{
165 KoCanvasBase *canvas = this->canvas();
166 if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false;
167
168 if (event->type() == QEvent::MouseMove) {
169 QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
170 m_d->mousePositionCompressor->start(mevent->pos());
171 } else if (event->type() == QEvent::TabletMove) {
172 QTabletEvent *tevent = static_cast<QTabletEvent*>(event);
173 m_d->mousePositionCompressor->start(tevent->pos());
174 } else if (event->type() == QEvent::FocusIn) {
175 m_d->view->syncLastActiveNodeToDocument();
176 }
177
178 return false;
179}
180
181void KisCanvasController::ensureVisibleDoc(const QRectF &docRect, bool smooth)
182{
183 const QRect currentVisible = viewport()->rect();
184 const QRect viewRect = m_d->coordinatesConverter->documentToWidget(docRect).toAlignedRect();
185
186 if (!viewRect.isValid() || currentVisible.contains(viewRect))
187 return; // its visible. Nothing to do.
188
189 // if we move, we move a little more so the amount of times we have to move is less.
190 int jumpWidth = smooth ? 0 : currentVisible.width() / 5;
191 int jumpHeight = smooth ? 0 : currentVisible.height() / 5;
192 if (!smooth && viewRect.width() + jumpWidth > currentVisible.width())
193 jumpWidth = 0;
194 if (!smooth && viewRect.height() + jumpHeight > currentVisible.height())
195 jumpHeight = 0;
196
197 int horizontalMove = 0;
198 if (currentVisible.width() <= viewRect.width()) // center view
199 horizontalMove = viewRect.center().x() - currentVisible.center().x();
200 else if (currentVisible.x() > viewRect.x()) // move left
201 horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth;
202 else if (currentVisible.right() < viewRect.right()) // move right
203 horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth);
204
205 int verticalMove = 0;
206 if (currentVisible.height() <= viewRect.height()) // center view
207 verticalMove = viewRect.center().y() - currentVisible.center().y();
208 if (currentVisible.y() > viewRect.y()) // move up
209 verticalMove = viewRect.y() - currentVisible.y() - jumpHeight;
210 else if (currentVisible.bottom() < viewRect.bottom()) // move down
211 verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight);
212
213 pan(QPoint(horizontalMove, verticalMove));
214}
215
217{
218 bool isXMirrored = coordinatesConverter->xAxisMirrored();
219
220 view->viewManager()->
221 showFloatingMessage(
222 i18nc("floating message about mirroring",
223 "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")),
224 QIcon(), 500, KisFloatingMessage::Low);
225}
226
227void KisCanvasController::mirrorCanvasImpl(const std::optional<KoViewTransformStillPoint> &stillPoint, bool enable)
228{
229 const KisCanvasState oldCanvasState = canvasState();
230 m_d->coordinatesConverter->mirror(stillPoint, enable, false);
231 const KisCanvasState newCanvasState = canvasState();
232 emitSignals(oldCanvasState, newCanvasState);
233
235}
236
238{
239 mirrorCanvasImpl(std::nullopt, enable);
240}
241
243{
244 QVariant customPos = sender()->property("customPosition");
245 QPoint pos = customPos.isValid()
246 ? customPos.value<QPoint>()
247 : QCursor::pos();
248 KoCanvasBase* canvas = m_d->view->canvasBase();
249 QWidget *canvasWidget = canvas->canvasWidget();
250 const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(pos);
251
252 std::optional<KoViewTransformStillPoint> stillPoint;
253
254 if (canvasWidget->rect().contains(cursorPosWidget.toPoint())) {
255 stillPoint = m_d->coordinatesConverter->makeWidgetStillPoint(cursorPosWidget);
256 }
257
258 mirrorCanvasImpl(stillPoint, enable);
259}
260
262{
263 auto stillPoint =
266 mirrorCanvasImpl(stillPoint, enable);
267}
268
270{
271 qreal rotationAngle = coordinatesConverter->rotationAngle();
272 // Prevent the displayed rotation angle from jittering between -0.0 and 0.0.
273 if (rotationAngle > -0.05 && rotationAngle < 0.05) {
274 rotationAngle = 0.0;
275 }
276 view->viewManager()->
277 showFloatingRotationMessage(
278 i18nc("floating message about rotation", "Rotation: %1° ",
279 KritaUtils::prettyFormatReal(rotationAngle)));
280}
281
286
291
292void KisCanvasController::rotateCanvas(qreal angle, const std::optional<KoViewTransformStillPoint> &stillPoint, bool isNativeGesture)
293{
294 if(isNativeGesture) {
296 }
297 const KisCanvasState oldCanvasState = canvasState();
298 m_d->coordinatesConverter->rotate(stillPoint, angle);
299 const KisCanvasState newCanvasState = canvasState();
300 emitSignals(oldCanvasState, newCanvasState);
301
303}
304
306{
307 rotateCanvas(angle, std::nullopt);
308}
309
314
319
324
326{
327 const KisCanvasState oldCanvasState = canvasState();
328 m_d->coordinatesConverter->resetRotation(std::nullopt);
329 const KisCanvasState newCanvasState = canvasState();
330 emitSignals(oldCanvasState, newCanvasState);
331
333}
334
336{
337 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
338 Q_ASSERT(kritaCanvas);
339
340 if (!canvas()->canvasIsOpenGL() && value) {
341 m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n"
342 "To visualize wrap-around mode, enable OpenGL."), QIcon());
343 }
344 kritaCanvas->setWrapAroundViewingMode(value);
345 kritaCanvas->image()->setWrapAroundModePermitted(value);
346}
347
349{
350 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
351 Q_ASSERT(kritaCanvas);
352
353 return kritaCanvas->wrapAroundViewingMode();
354}
355
357{
358 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
359 Q_ASSERT(kritaCanvas);
360
362 kritaCanvas->image()->setWrapAroundModeAxis(value);
363}
364
369
374
379
381{
382 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
383 Q_ASSERT(kritaCanvas);
384
385 return kritaCanvas->wrapAroundViewingModeAxis();
386}
387
395
397{
398 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
399 Q_ASSERT(kritaCanvas);
400
401 kritaCanvas->setLodPreferredInCanvas(value);
402
403 bool result = levelOfDetailMode();
404
405 if (!value || result) {
406 m_d->view->viewManager()->showFloatingMessage(
407 i18n("Instant Preview Mode: %1", result ?
408 i18n("ON") : i18n("OFF")),
409 QIcon(), 500, KisFloatingMessage::Low);
410 } else {
411 QString reason;
412
413 if (!kritaCanvas->canvasIsOpenGL()) {
414 reason = i18n("Instant Preview is only supported with OpenGL activated");
415 }
416 else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ||
418 QString filteringMode =
420 i18n("Bilinear") : i18n("Nearest Neighbour");
421 reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode);
422 }
423
424 m_d->view->viewManager()->showFloatingMessage(
425 i18n("Failed activating Instant Preview mode!\n\n%1", reason),
426 QIcon(), 5000, KisFloatingMessage::Low);
427 }
428
429
430}
431
433{
434 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
435 Q_ASSERT(kritaCanvas);
436
437 return kritaCanvas->lodPreferredInCanvas();
438}
439
441{
442 const QPointF &center = preferredCenter();
443 config.setProperty("panX", center.x());
444 config.setProperty("panY", center.y());
445
446 config.setProperty("rotation", rotation());
448 config.setProperty("wrapAround", wrapAroundMode());
449 config.setProperty("wrapAroundAxis", wrapAroundModeAxis());
450 config.setProperty("enableInstantPreview", levelOfDetailMode());
451}
452
454{
455 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
456 Q_ASSERT(kritaCanvas);
457
458 mirrorCanvas(config.getBool("mirror", false));
459 rotateCanvas(config.getFloat("rotation", 0.0f));
460
461 const QPointF &center = preferredCenter();
462 float panX = config.getFloat("panX", center.x());
463 float panY = config.getFloat("panY", center.y());
464 setPreferredCenter(QPointF(panX, panY));
465
466 slotToggleWrapAroundMode(config.getBool("wrapAround", false));
467 slotSetWrapAroundModeAxis((WrapAroundAxis)config.getInt("wrapAroundAxis", 0));
468 kritaCanvas->setLodPreferredInCanvas(config.getBool("enableInstantPreview", false));
469}
470
471void KisCanvasController::syncOnReferencesChange(const QRectF &referencesRect)
472{
473 const KisCanvasState oldCanvasState = canvasState();
474 m_d->coordinatesConverter->setExtraReferencesBounds(referencesRect.toAlignedRect());
475 const KisCanvasState newCanvasState = canvasState();
476
477 emitSignals(oldCanvasState, newCanvasState);
478}
479
500
501void KisCanvasController::syncOnImageSizeChange(const QPointF &oldStillPoint, const QPointF &newStillPoint)
502{
503 const KisCanvasState oldCanvasState = canvasState();
504
505 KisImageSP image = m_d->view->image();
506 m_d->coordinatesConverter->setImageBounds(image->bounds(), oldStillPoint, newStillPoint);
507
508 const KisCanvasState newCanvasState = canvasState();
509
510 emitSignals(oldCanvasState, newCanvasState);
511}
512
514{
515 // not need to snap to device pixels, it is done by the converter automatically
517}
518
519void KisCanvasController::updateCanvasWidgetSizeInternal(const QSize &newSize, qreal devicePixelRatio)
520{
521 m_d->coordinatesConverter->setDevicePixelRatio(devicePixelRatio);
523}
524
525void KisCanvasController::updateCanvasZoomInternal(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const std::optional<KoViewTransformStillPoint> &docStillPoint)
526{
527 m_d->coordinatesConverter->setZoom(mode, zoom, resolutionX, resolutionY, docStillPoint);
528}
529
534
539
541{
542 const QPointF transformedImageBoundsTopleft = m_d->coordinatesConverter->imageRectInWidgetPixels().topLeft();
543 return m_d->coordinatesConverter->widgetCenterPoint() - transformedImageBoundsTopleft;
544}
545
546void KisCanvasController::setPreferredCenter(const QPointF &viewPoint)
547{
548 const KisCanvasState oldState = canvasState();
549
550 const QPointF expectedTransformedImageBoundsTopleft = m_d->coordinatesConverter->widgetCenterPoint() - viewPoint;
551 updateCanvasOffsetInternal(-expectedTransformedImageBoundsTopleft);
552
553 emitSignals(oldState, canvasState());
554}
555
557{
558 zoomInImpl(std::nullopt);
559}
560
562{
563 zoomOutImpl(std::nullopt);
564}
565
567{
568 zoomInImpl(stillPoint);
569}
570
572{
573 zoomOutImpl(stillPoint);
574}
575
576void KisCanvasController::zoomInImpl(const std::optional<KoViewTransformStillPoint> &stillPoint)
577{
579 if (!qFuzzyCompare(newZoom, m_d->coordinatesConverter->zoom())) {
580 if (stillPoint) {
581 setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, *stillPoint);
582 } else {
584 }
585 }
586}
587
588void KisCanvasController::zoomOutImpl(const std::optional<KoViewTransformStillPoint> &stillPoint)
589{
591 if (!qFuzzyCompare(newZoom, m_d->coordinatesConverter->zoom())) {
592 if (stillPoint) {
593 setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, *stillPoint);
594 } else {
596 }
597 }
598}
599
600void KisCanvasController::zoomToInternal(const QRect &viewRect)
601{
602 m_d->coordinatesConverter->zoomTo(viewRect);
603}
604
606{
607 // The scrollbar value always points at the top-left corner of the
608 // bit of image we paint.
609
610 const QPoint minOffset = m_d->coordinatesConverter->minimumOffset();
611 const QPoint maxOffset = m_d->coordinatesConverter->maximumOffset();
612 const QPoint offset = m_d->coordinatesConverter->documentOffset();
613
614 QScrollBar *hScroll = horizontalScrollBar();
615 QScrollBar *vScroll = verticalScrollBar();
616
617 QSignalBlocker b1(hScroll);
618 QSignalBlocker b2(vScroll);
619
620 hScroll->setRange(minOffset.x(), maxOffset.x());
621 hScroll->setValue(offset.x());
622 vScroll->setRange(minOffset.y(), maxOffset.y());
623 vScroll->setValue(offset.y());
624
625 const int drawH = viewport()->height() / 4;
626 const int drawW = viewport()->width() / 4;
627 const int fontHeight = QFontMetrics(font()).height();
628
629 vScroll->setPageStep(drawH);
630 vScroll->setSingleStep(fontHeight);
631 hScroll->setPageStep(drawW);
632 hScroll->setSingleStep(fontHeight);
633}
634
639
641{
642 const bool changed = value != m_d->usePrintResolutionMode;
643
644 KisImageSP image = m_d->view->image();
645
646 // changeCanvasMappingMode is called with the same canvasMappingMode when the window is
647 // moved across screens. Preserve the old zoomMode if this is the case.
648 const KoZoomMode::Mode newMode =
650 const qreal newZoom = m_d->coordinatesConverter->zoom();
651
653
655
656 if (changed) {
658 }
659}
float value(const T *src, size_t ch)
WrapAroundAxis
@ WRAPAROUND_HORIZONTAL
@ WRAPAROUND_BOTH
@ WRAPAROUND_VERTICAL
constexpr qreal POINT_TO_INCH(qreal px)
Definition KoUnit.h:37
void setLodPreferredInCanvas(bool value)
bool wrapAroundViewingMode() const
void setWrapAroundViewingMode(bool value)
KisCoordinatesConverter * coordinatesConverter
KisImageWSP image() const
WrapAroundAxis wrapAroundViewingModeAxis() const
bool canvasIsOpenGL() const override
bool lodPreferredInCanvas() const
void setWrapAroundViewingModeAxis(WrapAroundAxis value)
bool eventFilter(QObject *watched, QEvent *event) override
void zoomInImpl(const std::optional< KoViewTransformStillPoint > &stillPoint)
void saveCanvasState(KisPropertiesConfiguration &config) const
void updateCanvasOffsetInternal(const QPointF &newOffset) override
void keyPressEvent(QKeyEvent *event) override
void syncOnReferencesChange(const QRectF &referencesRect)
void setPreferredCenter(const QPointF &viewPoint) override
void mirrorCanvasImpl(const std::optional< KoViewTransformStillPoint > &stillPoint, bool enable)
WrapAroundAxis wrapAroundModeAxis() const
void updateCanvasWidgetSizeInternal(const QSize &newSize, qreal devicePixelRatio) override
void ensureVisibleDoc(const QRectF &docRect, bool smooth) override
Scrolls the content of the canvas so that the given rect is visible.
void updateScreenResolution(QWidget *parentWidget)
void slotToggleLevelOfDetailMode(bool value)
KoZoomState zoomState() const override
void setUsePrintResolutionMode(bool value)
void setCanvas(KoCanvasBase *canvas) override
void syncOnImageSizeChange(const QPointF &oldStillPoint, const QPointF &newStillPoint)
KisCanvasController(QPointer< KisView >parent, KoCanvasSupervisor *observerProvider, KisKActionCollection *actionCollection)
void zoomOutImpl(const std::optional< KoViewTransformStillPoint > &stillPoint)
void updateCanvasZoomInternal(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const std::optional< KoViewTransformStillPoint > &docStillPoint) override
void slotToggleWrapAroundMode(bool value)
qreal effectiveCanvasResolutionY() const
void mirrorCanvasAroundCanvas(bool enable)
void wheelEvent(QWheelEvent *event) override
KisCanvasState canvasState() const override
void mirrorCanvasAroundCursor(bool enable)
void activate() override
Reimplemented from KoCanvasController.
void slotTogglePixelGrid(bool value)
QPointF currentCursorPosition() const override
void sigUsePrintResolutionModeChanged(bool value)
void zoomToInternal(const QRect &viewRect) override
qreal effectiveCanvasResolutionX() const
void rotateCanvas(qreal angle, const std::optional< KoViewTransformStillPoint > &stillPoint, bool isNativeGesture=false)
void restoreCanvasState(const KisPropertiesConfiguration &config)
void slotSetWrapAroundModeAxis(WrapAroundAxis axis)
QPointF preferredCenter() const override
Returns the currently set preferred center point in view coordinates (pixels)
KoZoomState zoomState() const
static KisCanvasState fromConverter(const KisCoordinatesConverter &converter)
static KisConfigNotifier * instance()
void enablePixelGrid(bool v) const
_Private::Traits< T >::Result widgetToDocument(const T &obj) const
void setZoom(qreal zoom) override
void resetRotation(const std::optional< KoViewTransformStillPoint > &stillPoint)
resets canvas rotation
static qreal findNextZoom(qreal currentZoom, const QVector< qreal > &zoomLevels)
_Private::Traits< T >::Result documentToWidget(const T &obj) const
void setCanvasWidgetSizeKeepZoom(const QSizeF &size)
void setExtraReferencesBounds(const QRect &imageRect)
void setImageResolution(qreal xRes, qreal yRes)
void setImageBounds(const QRect &rect, const QPointF oldImageStillPoint, const QPointF newImageStillPoint)
void setDocumentOffset(const QPointF &offset)
QVector< qreal > standardZoomLevels() const
void rotate(const std::optional< KoViewTransformStillPoint > &stillPoint, qreal angle)
rotates the canvas
KoViewTransformStillPoint makeWidgetStillPoint(const QPointF &viewPoint) const override
Creates a still point that links the viewPoint of the widget to the corresponding point of the image.
KoViewTransformStillPoint makeDocStillPoint(const QPointF &docPoint) const override
Creates a still point that links the docPoint of the image (in document pixels!) to the corresponding...
static qreal findPrevZoom(qreal currentZoom, const QVector< qreal > &zoomLevels)
void mirror(const std::optional< KoViewTransformStillPoint > &stillPoint, bool mirrorXAxis, bool mirrorYAxis)
mirrors the canvas
void zoomTo(const QRectF &widgetRect)
void setWrapAroundModeAxis(WrapAroundAxis value)
double xRes() const
double yRes() const
QRect bounds() const override
void setWrapAroundModePermitted(bool value)
A container for a set of QAction objects.
@ NearestFilterMode
Definition kis_opengl.h:34
@ BilinearFilterMode
Definition kis_opengl.h:35
virtual void activate()
Reimplemented from KoCanvasController.
void pan(const QPoint &distance) override
void setCanvas(KoCanvasBase *canvas) override
void setZoom(KoZoomMode::Mode mode, qreal zoom) override
QPointer< KoCanvasBase > canvas
void emitSignals(const KisCanvasState &oldState, const KisCanvasState &newState)
QPointer< KoCanvasControllerProxyObject > proxyObject
KisKActionCollection * actionCollection
KoZoomMode::Mode zoomMode() const
void zoom(qreal *zoomX, qreal *zoomY) const override
@ ZOOM_CONSTANT
zoom x %
Definition KoZoomMode.h:24
static bool qFuzzyCompare(half p1, half p2)
static const int gRulersUpdateDelay
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
KisCoordinatesConverter * coordinatesConverter
QScopedPointer< KisSignalCompressorWithParam< QPoint > > mousePositionCompressor
void emitPointerPositionChangedSignals(QPoint pointerPos)
virtual void setProperty(const QString &name, const QVariant &value)
bool getBool(const QString &name, bool def=false) const
int getInt(const QString &name, int def=0) const
float getFloat(const QString &name, float def=0.0) const