Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_line.cc
Go to the documentation of this file.
1/*
2 * kis_tool_line.cc - part of Krayon
3 *
4 * SPDX-FileCopyrightText: 2000 John Califf <jwcaliff@compuzone.net>
5 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
6 * SPDX-FileCopyrightText: 2003 Boudewijn Rempt <boud@valdyas.org>
7 * SPDX-FileCopyrightText: 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
8 * SPDX-FileCopyrightText: 2007, 2010 Cyrille Berger <cberger@cberger.net>
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 */
12
13#include "kis_tool_line.h"
14
15
16
17#include <ksharedconfig.h>
18
19#include <KoCanvasBase.h>
20#include <KoPointerEvent.h>
21#include <KoPathShape.h>
22#include <KoShapeController.h>
23#include <KoShapeStroke.h>
24
25#include <kis_debug.h>
26#include <kis_cursor.h>
29#include <kis_canvas2.h>
31#include <KisViewManager.h>
32#include <kis_action_registry.h>
34
36
37
39{
40 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
41 KIS_ASSERT(kritaCanvas);
42 return kritaCanvas->coordinatesConverter();
43}
44
45
47 : KisToolShape(canvas, KisCursor::loadWithSize("tool_line_cursor.svg", 32, 32, 6, 6)),
48 m_showGuideline(true),
49 m_strokeIsRunning(false),
51 m_helper(new KisToolLineHelper(m_infoBuilder.data(),
52 canvas->resourceManager(),
53 kundo2_i18n("Draw Line"))),
54 m_strokeUpdateCompressor(200, KisSignalCompressor::POSTPONE),
55 m_longStrokeUpdateCompressor(750, KisSignalCompressor::FIRST_INACTIVE)
56{
57 setObjectName("tool_line");
58
60
62
63 connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
64 connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
65
66 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
67
68 connect(kritaCanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(resetCursorStyle()));
69}
70
74
76{
77 if (isEraser() && (nodePaintAbility() == PAINT)) {
78 useCursor(KisCursor::loadWithSize("tool_line_eraser_cursor.svg", 32, 32, 6, 6));
79 } else {
81 }
82
84}
85
86void KisToolLine::activate(const QSet<KoShape*> &shapes)
87{
89 configGroup = KSharedConfig::openConfig()->group(toolId());
90}
91
97
99{
100 QWidget* widget = KisToolPaint::createOptionWidget();
101
102 m_chkUseSensors = new QCheckBox(i18n("Use sensors"));
104
105 m_chkShowPreview = new QCheckBox(i18n("Show Preview"));
107
108 m_chkShowGuideline = new QCheckBox(i18n("Show Guideline"));
110
111 m_chkSnapToAssistants = new QCheckBox(i18n("Snap to Assistants"));
113
114 m_chkSnapEraser = new QCheckBox(i18n("Snap Eraser"));
116
117
118
119
120 // hook up connections for value changing
121 connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) );
122 connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) );
123 connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) );
124 connect(m_chkSnapToAssistants, SIGNAL(clicked(bool)), this, SLOT(setSnapToAssistants(bool)) );
125
126
127 // read values in from configuration
128 m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true));
129 m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true));
130 m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true));
131 m_chkSnapToAssistants->setChecked(configGroup.readEntry("snapToAssistants", false));
132 m_chkSnapEraser->setChecked(configGroup.readEntry("snapEraser", false));
133 if (!m_chkSnapToAssistants->isChecked()) {
134 m_chkSnapEraser->setEnabled(false);
135 }
136
137 return widget;
138}
139
141{
142 configGroup.writeEntry("useSensors", value);
143}
144
146{
148 configGroup.writeEntry("showGuideline", value);
149}
150
152{
153 configGroup.writeEntry("showPreview", value);
154}
155
157{
158 configGroup.writeEntry("snapToAssistants", value);
159 m_chkSnapEraser->setEnabled(value);
160}
161
163{
164 configGroup.writeEntry("snapEraser", value);
165}
166
171
173{
174 // Terminate any in-progress strokes
175 if (nodePaintAbility() == PAINT && m_helper->isRunning()) {
176 endStroke();
177 }
178}
179
180void KisToolLine::updatePreviewTimer(bool showGuideline)
181{
182 // If the user disables the guideline, we will want to try to draw some
183 // preview lines even if they're slow, so set the timer to FIRST_ACTIVE.
184 if (showGuideline) {
186 } else {
188 }
189}
190
191
192void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter)
193{
194 Q_UNUSED(converter);
195
196 if(mode() == KisTool::PAINT_MODE) {
197 paintLine(gc,QRect());
198 }
199 KisToolPaint::paint(gc,converter);
200}
201
203{
204 NodePaintAbility nodeAbility = nodePaintAbility();
205 if (nodeAbility == UNPAINTABLE || !nodeEditable()) {
206 event->ignore();
207 return;
208 }
209
210 if (nodeAbility == MYPAINTBRUSH_UNPAINTABLE) {
211 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
212 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
213 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
214 event->ignore();
215 return;
216 }
217
219
220 const KisToolShape::ShapeAddInfo info =
222
223 // Always show guideline on vector layers
224 m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT;
226 m_helper->setEnabled((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape);
227 m_helper->setUseSensors(m_chkUseSensors->isChecked());
228 m_helper->start(event, canvas()->resourceManager());
229
234
235 m_strokeIsRunning = true;
236 m_altInitiallyHeld = event->modifiers().testFlag(Qt::AltModifier);
237
238 showSize();
239}
240
242{
243 if (!m_strokeIsRunning) return;
244
245 m_helper->repaintLine(image(),
246 currentNode(),
247 image().data());
248}
249
251{
253 if (!m_strokeIsRunning) return;
254
255 // If the user was holding Alt at the start of the line, we don't want to
256 // move the origin around because moving the origin of a zero-length line
257 // is silly and this interferes with users coming from PS binding Alt to
258 // the quick switch line tool.
259 Qt::KeyboardModifiers effectiveModifiers = event->modifiers();
261 if (effectiveModifiers.testFlag(Qt::AltModifier)) {
262 // Remove the modifier if it was held at the beginning. The checks
263 // in the subsequent code use equality instead of testing the flag,
264 // so this retains the expected behavior without much rejigging.
265 effectiveModifiers.setFlag(Qt::AltModifier, false);
266 } else {
267 // User lifted the Alt key, we'll let them re-press it from this
268 // point on to move the origin after all.
269 m_altInitiallyHeld = false;
270 }
271 }
272
273 // First ensure the old guideline is deleted
275
276 QPointF pos = convertToPixelCoordAndSnap(event);
277
278 if (effectiveModifiers == Qt::AltModifier) {
279 QPointF trans = pos - m_endPoint;
280 m_helper->translatePoints(trans);
281 m_startPoint += trans;
282 m_endPoint += trans;
283 m_originalStartPoint += trans; // original start point is only original in terms of snapping to assistants
284 } else if (effectiveModifiers == Qt::ShiftModifier) {
285 pos = straightLine(pos);
286 m_helper->addPoint(event, pos);
287 } else {
288 pos = snapToAssistants(pos);
289 m_helper->addPoint(event, pos);
290 m_helper->movePointsTo(m_startPoint, pos);
291 }
292 m_endPoint = pos;
293
294 // Draw preview if requested
295 if (m_chkShowPreview->isChecked()) {
296 // If the cursor has moved a significant amount, immediately clear the
297 // current preview and redraw. Otherwise, do slow redraws periodically.
298 auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength();
299 if (updateDistance > 10) {
300 m_helper->clearPaint();
303 m_lastUpdatedPoint = pos;
304 } else if (updateDistance > 1 && !m_strokeUpdateCompressor.isActive() && !m_longStrokeUpdateCompressor.isActive()) {
306 m_lastUpdatedPoint = pos;
307 }
308 }
309
310 if(effectiveModifiers == Qt::AltModifier) {
311 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
312 KIS_ASSERT(kisCanvas);
313 kisCanvas->viewManager()->showFloatingMessage(i18n("X: %1 px\nY: %2 px", QString::number(m_startPoint.x(), 'f',1)
314 , QString::number(m_startPoint.y(), 'f',1))
315 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
316 }
317 else {
318 showSize();
319 }
320
323}
324
326{
327 Q_UNUSED(event);
330
332 endStroke();
333
334 if (static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
335 static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->endStroke();
336 }
337}
338
340{
341 return true;
342}
343
344
346{
347 NodePaintAbility nodeAbility = nodePaintAbility();
348
349 if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == UNPAINTABLE) {
350 m_helper->clearPoints();
351 return;
352 }
353
354 const KisToolShape::ShapeAddInfo info =
356
357 if ((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape) {
358 updateStroke();
359 m_helper->end();
360 }
361 else {
362 KisResourcesSnapshot resources(image(),
363 currentNode(),
364 canvas()->resourceManager());
365 KoPathShape* path = new KoPathShape();
366 path->setShapeId(KoPathShapeId);
367
368 QTransform resolutionMatrix;
369 resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
370 path->moveTo(resolutionMatrix.map(m_startPoint));
371 path->lineTo(resolutionMatrix.map(m_endPoint));
372 path->normalize();
373
375 path->setStroke(border);
376
377 KUndo2Command * cmd = canvas()->shapeController()->addShape(path, nullptr);
378 canvas()->addCommand(cmd);
379 }
380
381 m_strokeIsRunning = false;
383}
384
386{
387 if (!m_strokeIsRunning) return;
388 if (m_startPoint == m_endPoint) return;
389
395 if (m_helper->isRunning()) {
396 m_helper->cancel();
397 }
398
399 m_strokeIsRunning = false;
401}
402
403QPointF KisToolLine::straightLine(QPointF point)
404{
405 const QPointF lineVector = point - m_startPoint;
406 qreal lineAngle = std::atan2(lineVector.y(), lineVector.x());
407
408 if (lineAngle < 0) {
409 lineAngle += 2 * M_PI;
410 }
411
412 const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24;
413
414 const quint32 constrainedLineIndex = static_cast<quint32>((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5);
415 const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES;
416
417 const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y()));
418
419 const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle));
420
421 const QPointF result = m_startPoint + constrainedLineVector;
422
423 return result;
424}
425
426QPointF KisToolLine::snapToAssistants(QPointF point)
427{
428 if (m_chkSnapToAssistants->isChecked() && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
429 KisCanvas2* c = static_cast<KisCanvas2*>(canvas());
432 QPointF startPoint = m_originalStartPoint;
433
434 // startPoint etc. are in image coordinates system (pixels)
435 // but assistants work in document coordinates system ("points")
436 QPointF startPointInDoc = getCoordinatesConverter(canvas())->imageToDocument(startPoint);
437 QPointF pointInDoc = getCoordinatesConverter(canvas())->imageToDocument(point);
438
439 c->paintingAssistantsDecoration()->adjustLine(pointInDoc, startPointInDoc);
441
442 startPoint = getCoordinatesConverter(canvas())->documentToImage(startPointInDoc);
443 point = getCoordinatesConverter(canvas())->documentToImage(pointInDoc);
444
445 m_startPoint = startPoint;
446 return point;
447 }
448 return point;
449}
450
451
453{
454 if (canvas()) {
455 QRectF bound(m_startPoint, m_endPoint);
456 canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3)));
457 }
458}
459
460
462{
463 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
464 KIS_ASSERT(kisCanvas);
465 kisCanvas->viewManager()->showFloatingMessage(i18n("Length: %1 px", QString::number(QLineF(m_startPoint,m_endPoint).length(), 'f',1))
466 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
467}
468void KisToolLine::paintLine(QPainter& gc, const QRect&)
469{
470 QPointF viewStartPos = pixelToView(m_startPoint);
471 QPointF viewStartEnd = pixelToView(m_endPoint);
472
473 if (m_showGuideline && canvas()) {
474 QPainterPath path;
475 path.moveTo(viewStartPos);
476 path.lineTo(viewStartEnd);
477 paintToolOutline(&gc, path);
478 }
479}
480
482{
483 return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines");
484}
485
487{
488 return true;
489}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
#define KoPathShapeId
Definition KoPathShape.h:20
KisCoordinatesConverter * coordinatesConverter
void addCommand(KUndo2Command *command) override
void updateCanvas(const QRectF &rc) override
KisViewManager * viewManager() const
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const
_Private::Traits< T >::Result documentToImage(const T &obj) const
_Private::Traits< T >::Result imageToDocument(const T &obj) const
static QCursor loadWithSize(const QString &cursorName, int width, int height, int hotspotX=-1, int hotspotY=-1)
void setOnlyOneAssistantSnap(bool assistant)
sets whether we snap to only one assistant
void setEraserSnap(bool assistant)
sets whether eraser brushes snap
void adjustLine(QPointF &point, QPointF &strokeBegin)
The KisResourcesSnapshot class takes a snapshot of the various resources like colors and settings use...
KisSignalCompressor m_strokeUpdateCompressor
QCheckBox * m_chkUseSensors
QPointF snapToAssistants(QPointF point)
QPointF m_startPoint
void paintLine(QPainter &gc, const QRect &rc)
void endPrimaryAction(KoPointerEvent *event) override
void setShowPreview(bool value)
bool m_showGuideline
void setSnapToAssistants(bool value)
QCheckBox * m_chkShowGuideline
void setUseSensors(bool value)
void cancelStroke()
QString quickHelp() const override
void setSnapEraser(bool value)
bool m_strokeIsRunning
QCheckBox * m_chkSnapToAssistants
bool m_altInitiallyHeld
bool supportsPaintingAssistants() const override
void paint(QPainter &gc, const KoViewConverter &converter) override
KisToolLine(KoCanvasBase *canvas)
QPointF m_endPoint
QScopedPointer< KisToolLineHelper > m_helper
QPointF m_originalStartPoint
void resetCursorStyle() override
QCheckBox * m_chkShowPreview
bool primaryActionSupportsHiResEvents() const override
KisSignalCompressor m_longStrokeUpdateCompressor
QPointF straightLine(QPointF point)
void setShowGuideline(bool value)
void beginPrimaryAction(KoPointerEvent *event) override
KConfigGroup configGroup
void deactivate() override
void activate(const QSet< KoShape * > &shapes) override
QPointF m_lastUpdatedPoint
void updateGuideline()
void requestStrokeCancellation() override
void continuePrimaryAction(KoPointerEvent *event) override
QWidget * createOptionWidget() override
void updateStroke()
void requestStrokeEnd() override
~KisToolLine() override
QCheckBox * m_chkSnapEraser
void updatePreviewTimer(bool showGuide)
void setSupportOutline(bool supportOutline)
virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
void deactivate() override
QWidget * createOptionWidget() override
void activate(const QSet< KoShape * > &shapes) override
void paint(QPainter &gc, const KoViewConverter &converter) override
void setMode(ToolMode mode) override
virtual void addOptionWidgetOption(QWidget *control, QWidget *label=nullptr)
Add a widget and a label to the current option widget layout.
bool isEraser() const
ShapeAddInfo shouldAddShape(KisNodeSP currentNode) const
qreal currentStrokeWidth() const
KisCanvasResourceProvider * canvasResourceProvider()
void showFloatingMessage(const QString &message, const QIcon &icon, int timeout=4500, KisFloatingMessage::Priority priority=KisFloatingMessage::Medium, int alignment=Qt::AlignCenter|Qt::TextWordWrap)
shows a floating message in the top right corner of the canvas
QPointer< KoShapeController > shapeController
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
The position of a path point within a path shape.
Definition KoPathShape.h:63
QPointF point
The point in document coordinates.
Q_INVOKABLE QString toolId() const
void setIsOpacityPresetMode(bool value)
void useCursor(const QCursor &cursor)
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define M_PI
Definition kis_global.h:111
#define koIcon(name)
Use these macros for icons without any issues.
Definition kis_icon.h:25
#define CHECK_MODE_SANITY_OR_RETURN(_mode)
Definition kis_tool.h:27
const KisCoordinatesConverter * getCoordinatesConverter(KoCanvasBase *canvas)
KUndo2MagicString kundo2_i18n(const char *text)
virtual ToolMode mode() const
Definition kis_tool.cc:407
KisImageWSP currentImage()
Definition kis_tool.cc:393
KisTool::NodePaintAbility nodePaintAbility()
Definition kis_tool.cc:539
virtual void resetCursorStyle()
Definition kis_tool.cc:613
bool nodeEditable()
Checks checks if the current node is editable.
Definition kis_tool.cc:651
QPointF pixelToView(const QPoint &pixelCoord) const
Definition kis_tool.cc:269
KisNodeSP currentNode() const
Definition kis_tool.cc:370
bool overrideCursorIfNotEditable()
Override the cursor appropriately if current node is not editable.
Definition kis_tool.cc:618
void paintToolOutline(QPainter *painter, const KisOptimizedBrushOutline &path)
Definition kis_tool.cc:589
QRectF convertToPt(const QRectF &rect)
Definition kis_tool.cc:252
KisImageWSP image() const
Definition kis_tool.cc:332
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset=QPointF(), bool useModifiers=true)
Definition kis_tool.cc:214
NodePaintAbility
Definition kis_tool.h:148
@ MYPAINTBRUSH_UNPAINTABLE
Definition kis_tool.h:153
@ UNPAINTABLE
Definition kis_tool.h:152
KisCanvas2 * canvas