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