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 view->viewManager()->
273 showFloatingMessage(
274 i18nc("floating message about rotation", "Rotation: %1° ",
275 KritaUtils::prettyFormatReal(rotationAngle)),
276 QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
277}
278
283
288
289void KisCanvasController::rotateCanvas(qreal angle, const std::optional<KoViewTransformStillPoint> &stillPoint, bool isNativeGesture)
290{
291 if(isNativeGesture) {
293 }
294 const KisCanvasState oldCanvasState = canvasState();
295 m_d->coordinatesConverter->rotate(stillPoint, angle);
296 const KisCanvasState newCanvasState = canvasState();
297 emitSignals(oldCanvasState, newCanvasState);
298
300}
301
303{
304 rotateCanvas(angle, std::nullopt);
305}
306
311
316
321
323{
324 const KisCanvasState oldCanvasState = canvasState();
325 m_d->coordinatesConverter->resetRotation(std::nullopt);
326 const KisCanvasState newCanvasState = canvasState();
327 emitSignals(oldCanvasState, newCanvasState);
328
330}
331
333{
334 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
335 Q_ASSERT(kritaCanvas);
336
337 if (!canvas()->canvasIsOpenGL() && value) {
338 m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n"
339 "To visualize wrap-around mode, enable OpenGL."), QIcon());
340 }
341 kritaCanvas->setWrapAroundViewingMode(value);
342 kritaCanvas->image()->setWrapAroundModePermitted(value);
343}
344
346{
347 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
348 Q_ASSERT(kritaCanvas);
349
350 return kritaCanvas->wrapAroundViewingMode();
351}
352
354{
355 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
356 Q_ASSERT(kritaCanvas);
357
359 kritaCanvas->image()->setWrapAroundModeAxis(value);
360}
361
366
371
376
378{
379 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
380 Q_ASSERT(kritaCanvas);
381
382 return kritaCanvas->wrapAroundViewingModeAxis();
383}
384
392
394{
395 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
396 Q_ASSERT(kritaCanvas);
397
398 kritaCanvas->setLodPreferredInCanvas(value);
399
400 bool result = levelOfDetailMode();
401
402 if (!value || result) {
403 m_d->view->viewManager()->showFloatingMessage(
404 i18n("Instant Preview Mode: %1", result ?
405 i18n("ON") : i18n("OFF")),
406 QIcon(), 500, KisFloatingMessage::Low);
407 } else {
408 QString reason;
409
410 if (!kritaCanvas->canvasIsOpenGL()) {
411 reason = i18n("Instant Preview is only supported with OpenGL activated");
412 }
413 else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ||
415 QString filteringMode =
417 i18n("Bilinear") : i18n("Nearest Neighbour");
418 reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode);
419 }
420
421 m_d->view->viewManager()->showFloatingMessage(
422 i18n("Failed activating Instant Preview mode!\n\n%1", reason),
423 QIcon(), 5000, KisFloatingMessage::Low);
424 }
425
426
427}
428
430{
431 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
432 Q_ASSERT(kritaCanvas);
433
434 return kritaCanvas->lodPreferredInCanvas();
435}
436
438{
439 const QPointF &center = preferredCenter();
440 config.setProperty("panX", center.x());
441 config.setProperty("panY", center.y());
442
443 config.setProperty("rotation", rotation());
445 config.setProperty("wrapAround", wrapAroundMode());
446 config.setProperty("wrapAroundAxis", wrapAroundModeAxis());
447 config.setProperty("enableInstantPreview", levelOfDetailMode());
448}
449
451{
452 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
453 Q_ASSERT(kritaCanvas);
454
455 mirrorCanvas(config.getBool("mirror", false));
456 rotateCanvas(config.getFloat("rotation", 0.0f));
457
458 const QPointF &center = preferredCenter();
459 float panX = config.getFloat("panX", center.x());
460 float panY = config.getFloat("panY", center.y());
461 setPreferredCenter(QPointF(panX, panY));
462
463 slotToggleWrapAroundMode(config.getBool("wrapAround", false));
464 slotSetWrapAroundModeAxis((WrapAroundAxis)config.getInt("wrapAroundAxis", 0));
465 kritaCanvas->setLodPreferredInCanvas(config.getBool("enableInstantPreview", false));
466}
467
468void KisCanvasController::syncOnReferencesChange(const QRectF &referencesRect)
469{
470 const KisCanvasState oldCanvasState = canvasState();
471 m_d->coordinatesConverter->setExtraReferencesBounds(referencesRect.toAlignedRect());
472 const KisCanvasState newCanvasState = canvasState();
473
474 emitSignals(oldCanvasState, newCanvasState);
475}
476
497
498void KisCanvasController::syncOnImageSizeChange(const QPointF &oldStillPoint, const QPointF &newStillPoint)
499{
500 const KisCanvasState oldCanvasState = canvasState();
501
502 KisImageSP image = m_d->view->image();
503 m_d->coordinatesConverter->setImageBounds(image->bounds(), oldStillPoint, newStillPoint);
504
505 const KisCanvasState newCanvasState = canvasState();
506
507 emitSignals(oldCanvasState, newCanvasState);
508}
509
511{
512 // not need to snap to device pixels, it is done by the converter automatically
514}
515
520
521void KisCanvasController::updateCanvasZoomInternal(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const std::optional<KoViewTransformStillPoint> &docStillPoint)
522{
523 m_d->coordinatesConverter->setZoom(mode, zoom, resolutionX, resolutionY, docStillPoint);
524}
525
530
535
537{
538 const QPointF transformedImageBoundsTopleft = m_d->coordinatesConverter->imageRectInWidgetPixels().topLeft();
539 return m_d->coordinatesConverter->widgetCenterPoint() - transformedImageBoundsTopleft;
540}
541
542void KisCanvasController::setPreferredCenter(const QPointF &viewPoint)
543{
544 const KisCanvasState oldState = canvasState();
545
546 const QPointF expectedTransformedImageBoundsTopleft = m_d->coordinatesConverter->widgetCenterPoint() - viewPoint;
547 updateCanvasOffsetInternal(-expectedTransformedImageBoundsTopleft);
548
549 emitSignals(oldState, canvasState());
550}
551
553{
554 zoomInImpl(std::nullopt);
555}
556
558{
559 zoomOutImpl(std::nullopt);
560}
561
563{
564 zoomInImpl(stillPoint);
565}
566
568{
569 zoomOutImpl(stillPoint);
570}
571
572void KisCanvasController::zoomInImpl(const std::optional<KoViewTransformStillPoint> &stillPoint)
573{
575 if (!qFuzzyCompare(newZoom, m_d->coordinatesConverter->zoom())) {
576 if (stillPoint) {
577 setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, *stillPoint);
578 } else {
580 }
581 }
582}
583
584void KisCanvasController::zoomOutImpl(const std::optional<KoViewTransformStillPoint> &stillPoint)
585{
587 if (!qFuzzyCompare(newZoom, m_d->coordinatesConverter->zoom())) {
588 if (stillPoint) {
589 setZoom(KoZoomMode::ZOOM_CONSTANT, newZoom, *stillPoint);
590 } else {
592 }
593 }
594}
595
596void KisCanvasController::zoomToInternal(const QRect &viewRect)
597{
598 m_d->coordinatesConverter->zoomTo(viewRect);
599}
600
602{
603 // The scrollbar value always points at the top-left corner of the
604 // bit of image we paint.
605
606 const QPoint minOffset = m_d->coordinatesConverter->minimumOffset();
607 const QPoint maxOffset = m_d->coordinatesConverter->maximumOffset();
608 const QPoint offset = m_d->coordinatesConverter->documentOffset();
609
610 QScrollBar *hScroll = horizontalScrollBar();
611 QScrollBar *vScroll = verticalScrollBar();
612
613 QSignalBlocker b1(hScroll);
614 QSignalBlocker b2(vScroll);
615
616 hScroll->setRange(minOffset.x(), maxOffset.x());
617 hScroll->setValue(offset.x());
618 vScroll->setRange(minOffset.y(), maxOffset.y());
619 vScroll->setValue(offset.y());
620
621 const int drawH = viewport()->height() / 4;
622 const int drawW = viewport()->width() / 4;
623 const int fontHeight = QFontMetrics(font()).height();
624
625 vScroll->setPageStep(drawH);
626 vScroll->setSingleStep(fontHeight);
627 hScroll->setPageStep(drawW);
628 hScroll->setSingleStep(fontHeight);
629}
630
635
637{
638 const bool changed = value != m_d->usePrintResolutionMode;
639
640 KisImageSP image = m_d->view->image();
641
642 // changeCanvasMappingMode is called with the same canvasMappingMode when the window is
643 // moved across screens. Preserve the old zoomMode if this is the case.
644 const KoZoomMode::Mode newMode =
646 const qreal newZoom = m_d->coordinatesConverter->zoom();
647
649
651
652 if (changed) {
654 }
655}
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 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 updateCanvasWidgetSizeInternal(const QSize &newSize) 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