Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_mirror_axis.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 */
7
8#include "kis_mirror_axis.h"
9
10#include "KoConfig.h"
11
12#include <QPainter>
13#include <QApplication>
14#include <QPaintEngine>
15#include <QOpenGLContext>
16#include <QOpenGLFunctions>
17#include <QAction>
18
19#include <kis_icon.h>
20
21#include "kis_canvas2.h"
23#include "KisViewManager.h"
24#include "KisView.h"
25#include "kis_image.h"
28#include "kis_algebra_2d.h"
29
30#include <KisMirrorAxisConfig.h>
31#include <kis_signals_blocker.h>
32#include <kactioncollection.h>
35
36#ifdef Q_OS_MACOS
37 // HACK alert
38 // macOS.SDK openGL does not define GL_MULTISAMPLE_EXT
39 #define GL_MULTISAMPLE_EXT GL_MULTISAMPLE
40#endif
41
87
89 : KisCanvasDecoration("mirror_axis", parent)
90 , d(new Private(this))
91{
92 d->resourceProvider = provider;
93 connect(d->resourceProvider, SIGNAL(mirrorModeChanged()), SLOT(mirrorModeChanged()));
94 connect(d->resourceProvider, SIGNAL(moveMirrorVerticalCenter()), SLOT(moveVerticalAxisToCenter()));
95 connect(d->resourceProvider, SIGNAL(moveMirrorHorizontalCenter()), SLOT(moveHorizontalAxisToCenter()));
96
97 d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
98 d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
99 d->horizontalIcon = KisIconUtils::loadIcon("mirrorAxis-HorizontalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
100 d->verticalIcon = KisIconUtils::loadIcon("mirrorAxis-VerticalMove").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
101 d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
102 d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
103 setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
104
105 d->image = parent->canvasBase()->image();
106}
107
111
113{
114 return d->config.handleSize();
115}
116
118{
119 if(d->config.handleSize() != newSize) {
120 d->config.setHandleSize(newSize);
121 d->horizontalIcon = KisIconUtils::loadIcon("symmetry-horizontal").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
122 d->verticalIcon = KisIconUtils::loadIcon("symmetry-vertical").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
123 d->horizontalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
124 d->verticalHandleIcon = KisIconUtils::loadIcon("transform-move").pixmap(d->config.handleSize(), QIcon::Normal, QIcon::On).toImage();
125 d->minHandlePosition = d->sideMargin + newSize;
126 Q_EMIT handleSizeChanged();
127 Q_EMIT sigConfigChanged();
128 }
129}
130
131void KisMirrorAxis::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter* converter, KisCanvas2* canvas)
132{
133 Q_UNUSED(updateArea);
134 Q_UNUSED(converter);
135 Q_UNUSED(canvas);
136
137 if (!view()->isCurrent()) {
138 return;
139 }
140
141 gc.save();
142
143 KoColor c;
144 c.fromQColor(QColor(0, 0, 0, 64));
145
146 QPen pen1(canvas->displayRendererInterface()->convertColorToDisplayColorSpace(c), 2 * decorationThickness(), Qt::DashDotDotLine, Qt::RoundCap, Qt::RoundJoin);
147 pen1.setCosmetic(true);
148 QPen pen2 = pen1;
149 c.fromQColor(QColor(0, 0, 0, 128));
151 pen2.setStyle(Qt::SolidLine);
152 QPen pen3 = pen2;
153 pen3.setWidth(1 * decorationThickness());
154 gc.setPen(pen3);
155 c.fromQColor(Qt::white);
157 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
158
159 QOpenGLContext *ctx = QOpenGLContext::currentContext();
160 bool hasMultisample = false;
161 if (ctx) {
162 hasMultisample = ((gc.paintEngine()->type() == QPaintEngine::OpenGL2)
163 && (ctx->hasExtension("GL_ARB_multisample") || ctx->hasExtension("GL_EXT_multisample_compatibility")));
164
165 // QPainter cannot anti-alias the edges of circles etc. when using OpenGL
166 // So instead, use native OpenGL anti-aliasing when available.
167 if (hasMultisample) {
168 gc.beginNativePainting();
169 ctx->functions()->glEnable(GL_MULTISAMPLE_EXT);
170 gc.endNativePainting();
171 }
172 }
173
174 float halfHandleSize = d->config.handleSize() / 2;
175
176 const qreal dpr = canvas->canvasWidget()->devicePixelRatioF();
177 d->recomputeVisibleAxes(QRect(gc.viewport().topLeft() / dpr, gc.viewport().size() / dpr));
178
179 if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
180 if (!d->horizontalAxis.isNull()) {
181 // QPointF horizontalIndicatorCenter = d->horizontalAxis.unitVector().pointAt(15);
182 // QRectF horizontalIndicator = QRectF(horizontalIndicatorCenter.x() - halfHandleSize, horizontalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
183
184 float horizontalHandlePosition = qBound<float>(d->minHandlePosition, d->config.horizontalHandlePosition(), d->horizontalAxis.length() - d->minHandlePosition);
185 QPointF horizontalHandleCenter = d->horizontalAxis.unitVector().pointAt(horizontalHandlePosition);
186 d->horizontalHandle = QRectF(horizontalHandleCenter.x() - halfHandleSize, horizontalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
187
188 gc.setPen(pen1);
189 gc.drawLine(d->horizontalAxis);
190
191 // gc.drawEllipse(horizontalIndicator);
192 // gc.drawPixmap(horizontalIndicator.adjusted(5, 5, -5, -5).toRect(), d->horizontalIcon);
193
194 // don't draw the handles if we are locking the axis for movement
195 if (!d->config.lockHorizontal()) {
196 gc.setPen(pen2);
197 gc.drawEllipse(d->horizontalHandle);
198 gc.drawImage(d->horizontalHandle.adjusted(5, 5, -5, -5).toRect(), canvas->displayRendererInterface()->convertImageToDisplayColorSpace(d->horizontalIcon));
199 }
200
201 } else {
202 d->horizontalHandle = QRectF();
203 }
204 }
205
206 if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
207 if (!d->verticalAxis.isNull()) {
208
209 gc.setPen(pen1);
210 gc.drawLine(d->verticalAxis);
211
212
213 // QPointF verticalIndicatorCenter = d->verticalAxis.unitVector().pointAt(15);
214 // QRectF verticalIndicator = QRectF(verticalIndicatorCenter.x() - halfHandleSize, verticalIndicatorCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
215
216 float verticalHandlePosition = qBound<float>(d->minHandlePosition, d->config.verticalHandlePosition(), d->verticalAxis.length() - d->minHandlePosition);
217 QPointF verticalHandleCenter = d->verticalAxis.unitVector().pointAt(verticalHandlePosition);
218 d->verticalHandle = QRectF(verticalHandleCenter.x() - halfHandleSize, verticalHandleCenter.y() - halfHandleSize, d->config.handleSize(), d->config.handleSize());
219
220 // don't draw the handles if we are locking the axis for movement
221 if (!d->config.lockVertical()) {
222 gc.setPen(pen2);
223 gc.drawEllipse(d->verticalHandle);
224 gc.drawImage(d->verticalHandle.adjusted(5, 5, -5, -5).toRect(), canvas->displayRendererInterface()->convertImageToDisplayColorSpace(d->verticalIcon));
225 }
226
227 } else {
228 d->verticalHandle = QRectF();
229 }
230 }
231
232 if (hasMultisample) {
233 gc.beginNativePainting();
234 ctx->functions()->glDisable(GL_MULTISAMPLE_EXT);
235 gc.endNativePainting();
236 }
237
238 gc.restore();
239
240}
241
242static KoPointerEvent *getKoPointerEvent(QEvent *event)
243{
244 switch (event->type()) {
245 case QEvent::MouseButtonPress:
246 case QEvent::MouseMove:
247 case QEvent::MouseButtonRelease: {
248 QMouseEvent *me = static_cast<QMouseEvent *>(event);
249 return new KoPointerEvent(me, me->pos());
250 }
251 case QEvent::TabletPress:
252 case QEvent::TabletMove:
253 case QEvent::TabletRelease: {
254 QTabletEvent *te = static_cast<QTabletEvent *>(event);
255 return new KoPointerEvent(te, te->pos());
256 }
257 case QEvent::TouchBegin:
258 case QEvent::TouchUpdate:
259 case QEvent::TouchEnd:
260 case QEvent::TouchCancel: {
261 QTouchEvent *te = static_cast<QTouchEvent *>(event);
262 return new KoPointerEvent(te, te->touchPoints().at(0).pos());
263 }
264 default:
265 return nullptr;
266 }
267}
268
269bool KisMirrorAxis::eventFilter(QObject* target, QEvent* event)
270{
271 if (!visible()) return false;
272
273 QObject *expectedCanvasWidget = view() ?
274 view()->canvasBase()->canvasWidget() : 0;
275
276 if (!expectedCanvasWidget || target != expectedCanvasWidget) return false;
277
278 if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TabletPress
279 || event->type() == QEvent::TouchBegin) {
280 const QScopedPointer<KoPointerEvent> pointerEvent(getKoPointerEvent(event));
281 const QPoint pos = !pointerEvent.isNull() ? pointerEvent->pos() : QPoint(77,77);
282
283 if(d->config.mirrorHorizontal() && d->horizontalHandle.contains(pos) && !d->config.lockHorizontal() && !d->config.hideHorizontalDecoration() ) {
284 d->xActive = true;
285 QApplication::setOverrideCursor(Qt::ClosedHandCursor);
286 event->accept();
287 return true;
288 }
289
290 if(d->config.mirrorVertical() && d->verticalHandle.contains(pos) && !d->config.lockVertical() && !d->config.hideVerticalDecoration()) {
291 d->yActive = true;
292 QApplication::setOverrideCursor(Qt::ClosedHandCursor);
293 event->accept();
294 return true;
295 }
296 }
297 if (event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove
298 || event->type() == QEvent::TouchUpdate) {
299 const QScopedPointer<KoPointerEvent> pointerEvent(getKoPointerEvent(event));
300 const QPoint pos = !pointerEvent.isNull() ? pointerEvent->pos() : QPoint(77,77);
301
302 if(d->xActive) {
303 float axisX = view()->viewConverter()->widgetToImage<QPoint>(pos).x();
304 // axisX should be either int or int + 0.5
305 axisX *= 2; // to be able to choose in the middle of the pixel
306 axisX = round(axisX); // find the closest acceptable point
307 axisX = axisX/2; // return to the original space
308
309
310 d->setAxisPosition(axisX, d->config.axisPosition().y());
311 d->config.setHorizontalHandlePosition(KisAlgebra2D::dotProduct<QPointF>(pos - d->horizontalAxis.p1(), d->horizontalAxis.unitVector().p2() - d->horizontalAxis.p1()));
312 Q_EMIT sigConfigChanged();
313
314 event->accept();
315 view()->showFloatingMessage(i18n("X: %1 px",QString::number(d->config.axisPosition().x(), 'f', 1))
316 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
317 return true;
318 }
319 if(d->yActive) {
320 float axisY = view()->viewConverter()->widgetToImage<QPoint>(pos).y();
321 // axisX should be either int or int + 0.5
322 axisY *= 2; // to be able to choose in the middle of the pixel
323 axisY = round(axisY); // find the closest acceptable point
324 axisY = axisY/2; // return to the original space
325
326 d->setAxisPosition(d->config.axisPosition().x(), axisY);
327 d->config.setVerticalHandlePosition(KisAlgebra2D::dotProduct<QPointF>(pos - d->verticalAxis.p1(), d->verticalAxis.unitVector().p2() - d->verticalAxis.p1()));
328 Q_EMIT sigConfigChanged();
329
330 event->accept();
331 view()->showFloatingMessage(i18n("Y: %1 px",QString::number(d->config.axisPosition().y(), 'f', 1))
332 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
333 return true;
334 }
335 if(d->config.mirrorHorizontal() && !d->config.hideHorizontalDecoration()) {
336 if(d->horizontalHandle.contains(pos) && !d->config.lockHorizontal()) {
337 if(!d->horizontalContainsCursor) {
338 QApplication::setOverrideCursor(Qt::OpenHandCursor);
339 d->horizontalContainsCursor = true;
340 }
341 } else if(d->horizontalContainsCursor) {
342 QApplication::restoreOverrideCursor();
343 d->horizontalContainsCursor = false;
344 }
345 }
346 if(d->config.mirrorVertical() && !d->config.hideVerticalDecoration()) {
347 if(d->verticalHandle.contains(pos) && !d->config.lockVertical()) {
348 if(!d->verticalContainsCursor) {
349 QApplication::setOverrideCursor(Qt::OpenHandCursor);
350 d->verticalContainsCursor = true;
351 }
352 } else if(d->verticalContainsCursor) {
353 QApplication::restoreOverrideCursor();
354 d->verticalContainsCursor = false;
355 }
356 }
357 }
358 if (event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::TabletRelease
359 || event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
360
361 if(d->xActive) {
362 while (QApplication::overrideCursor()) {
363 QApplication::restoreOverrideCursor();
364 }
365 d->xActive = false;
366 event->accept();
367 return true;
368 }
369 if(d->yActive) {
370 while (QApplication::overrideCursor()) {
371 QApplication::restoreOverrideCursor();
372 }
373 d->yActive = false;
374 event->accept();
375 return true;
376 }
377 }
378
379 return QObject::eventFilter(target, event);
380}
381
383{
384 if (!view()->isCurrent()) {
385 return;
386 }
387
388 d->config.setMirrorHorizontal(d->resourceProvider->mirrorHorizontal());
389 d->config.setMirrorVertical(d->resourceProvider->mirrorVertical());
390
391 d->config.setLockHorizontal(d->resourceProvider->mirrorHorizontalLock());
392 d->config.setLockVertical(d->resourceProvider->mirrorVerticalLock());
393
394 d->config.setHideHorizontalDecoration(d->resourceProvider->mirrorHorizontalHideDecorations());
395 d->config.setHideVerticalDecoration(d->resourceProvider->mirrorVerticalHideDecorations());
396
397 setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
398
399 Q_EMIT sigConfigChanged();
400}
401
403{
405
406
407 KisInputManager *inputManager = view() ? view()->canvasBase()->globalInputManager() : 0;
408 if (!inputManager) return;
409
410 if (v) {
411 inputManager->attachPriorityEventFilter(this);
412 } else {
413 inputManager->detachPriorityEventFilter(this);
414 }
415}
416
418{
419 if (config != d->config) {
420 KisSignalsBlocker blocker(d->resourceProvider);
421
422 d->config = config;
423
424 d->resourceProvider->setMirrorHorizontal(d->config.mirrorHorizontal());
425 d->resourceProvider->setMirrorVertical(d->config.mirrorVertical());
426
427 d->resourceProvider->setMirrorHorizontalLock(d->config.lockHorizontal());
428 d->resourceProvider->setMirrorVerticalLock(d->config.lockVertical());
429
430 d->resourceProvider->setMirrorHorizontalHideDecorations(d->config.hideHorizontalDecoration());
431 d->resourceProvider->setMirrorVerticalHideDecorations(d->config.hideVerticalDecoration());
432
433 if (view()) {
434 view()->canvasBase()->updateCanvas();
435 }
436 }
437
439 setVisible(d->config.mirrorHorizontal() || d->config.mirrorVertical());
440}
441
443{
444 return d->config;
445}
446
448{
449 KisKActionCollection* collection = view()->viewManager()->actionCollection();
450 // first uncheck the action, then set according to config;
451 // otherwise the connected KisHighlightedToolButton's highlight color is not
452 // properly set
453 collection->action("hmirror_action")->setChecked(false);
454 collection->action("vmirror_action")->setChecked(false);
455
456 if (d->config.mirrorHorizontal()) {
457 collection->action("hmirror_action")->setChecked(d->config.mirrorHorizontal());
458 }
459
460 if (d->config.mirrorVertical()) {
461 collection->action("vmirror_action")->setChecked(d->config.mirrorVertical());
462 }
463
464 collection->action("mirrorX-lock")->setChecked(d->config.lockHorizontal());
465 collection->action("mirrorY-lock")->setChecked(d->config.lockVertical());
466
467 collection->action("mirrorX-hideDecorations")->setChecked(d->config.hideHorizontalDecoration());
468 collection->action("mirrorY-hideDecorations")->setChecked(d->config.hideVerticalDecoration());
469}
470
472{
473 if (!view()->isCurrent()) {
474 return;
475 }
476
477 d->setAxisPosition(d->image->width()/2, d->config.axisPosition().y());
478 Q_EMIT sigConfigChanged();
479}
480
482{
483 if (!view()->isCurrent()) {
484 return;
485 }
486
487 d->setAxisPosition(d->config.axisPosition().x(), d->image->height()/2 );
488 Q_EMIT sigConfigChanged();
489}
490
491
493{
494 QPointF newPosition = QPointF(x, y);
495
496 config.setAxisPosition(newPosition);
497
498 q->view()->canvasBase()->updateCanvas();
499}
500
501
503{
504 KisCoordinatesConverter *converter = q->view()->viewConverter();
505
506 QPointF samplePt1 = converter->imageToWidget<QPointF>(QPointF(config.axisPosition().x(), 0));
507 QPointF samplePt2 = converter->imageToWidget<QPointF>(QPointF(config.axisPosition().x(), 100));
508
509 horizontalAxis = QLineF(samplePt1, samplePt2);
510 if (!KisAlgebra2D::intersectLineRect(horizontalAxis, viewport, true)) horizontalAxis = QLineF();
511
512 samplePt1 = converter->imageToWidget<QPointF>(QPointF(0, config.axisPosition().y()));
513 samplePt2 = converter->imageToWidget<QPointF>(QPointF(100, config.axisPosition().y()));
514 verticalAxis = QLineF(samplePt1, samplePt2);
515 if (!KisAlgebra2D::intersectLineRect(verticalAxis, viewport, true)) verticalAxis = QLineF();
516}
517
qreal v
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
KoColorDisplayRendererInterface * displayRendererInterface() const override
displayRendererInterface The display renderer interface has a number of color conversion functions wh...
KisAbstractCanvasWidget * canvasWidget
QPointer< KisView > view() const
virtual void setVisible(bool v)
_Private::Traits< T >::Result imageToWidget(const T &obj) const
Central object to manage canvas input.
void detachPriorityEventFilter(QObject *filter)
detachPriorityEventFilter
void attachPriorityEventFilter(QObject *filter, int priority=0)
attachPriorityEventFilter
A container for a set of QAction objects.
QAction * action(int index) const
The KisMirrorAxisConfig class stores configuration for the KisMirrorAxis canvas decoration....
void setAxisPosition(QPointF position)
void setMirrorHorizontal(bool state)
Private(KisMirrorAxis *qq)
KisMirrorAxisConfig config
KisCanvasResourceProvider * resourceProvider
void recomputeVisibleAxes(QRect viewport)
void setAxisPosition(float x, float y)
void sigConfigChanged()
bool eventFilter(QObject *target, QEvent *event) override
const QScopedPointer< Private > d
void handleSizeChanged()
const KisMirrorAxisConfig & mirrorAxisConfig() const
~KisMirrorAxis() override
void drawDecoration(QPainter &gc, const QRectF &updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas) override
void moveVerticalAxisToCenter()
void setHandleSize(float newSize)
void setVisible(bool v) override
void setMirrorAxisConfig(const KisMirrorAxisConfig &config)
void moveHorizontalAxisToCenter()
KisMirrorAxis(KisCanvasResourceProvider *provider, QPointer< KisView > parent)
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
virtual QImage convertImageToDisplayColorSpace(const QImage source) const =0
convertImageToDisplayColorSpace
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
Definition KoColor.cpp:213
static KoPointerEvent * getKoPointerEvent(QEvent *event)
bool intersectLineRect(QLineF &line, const QRect rect, bool extend)
QIcon loadIcon(const QString &name)