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::load("tool_line_cursor.png", 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::load("tool_line_eraser_cursor.png", 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
237 showSize();
238}
239
241{
242 if (!m_strokeIsRunning) return;
243
244 m_helper->repaintLine(image(),
245 currentNode(),
246 image().data());
247}
248
250{
252 if (!m_strokeIsRunning) return;
253
254 // First ensure the old guideline is deleted
256
257 QPointF pos = convertToPixelCoordAndSnap(event);
258
259 if (event->modifiers() == Qt::AltModifier) {
260 QPointF trans = pos - m_endPoint;
261 m_helper->translatePoints(trans);
262 m_startPoint += trans;
263 m_endPoint += trans;
264 m_originalStartPoint += trans; // original start point is only original in terms of snapping to assistants
265 } else if (event->modifiers() == Qt::ShiftModifier) {
266 pos = straightLine(pos);
267 m_helper->addPoint(event, pos);
268 } else {
269 pos = snapToAssistants(pos);
270 m_helper->addPoint(event, pos);
271 m_helper->movePointsTo(m_startPoint, pos);
272 }
273 m_endPoint = pos;
274
275 // Draw preview if requested
276 if (m_chkShowPreview->isChecked()) {
277 // If the cursor has moved a significant amount, immediately clear the
278 // current preview and redraw. Otherwise, do slow redraws periodically.
279 auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength();
280 if (updateDistance > 10) {
281 m_helper->clearPaint();
284 m_lastUpdatedPoint = pos;
285 } else if (updateDistance > 1 && !m_strokeUpdateCompressor.isActive() && !m_longStrokeUpdateCompressor.isActive()) {
287 m_lastUpdatedPoint = pos;
288 }
289 }
290
291 if(event->modifiers() == Qt::AltModifier) {
292 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
293 KIS_ASSERT(kisCanvas);
294 kisCanvas->viewManager()->showFloatingMessage(i18n("X: %1 px\nY: %2 px", QString::number(m_startPoint.x(), 'f',1)
295 , QString::number(m_startPoint.y(), 'f',1))
296 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
297 }
298 else {
299 showSize();
300 }
301
304}
305
307{
308 Q_UNUSED(event);
311
313 endStroke();
314
315 if (static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
316 static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->endStroke();
317 }
318}
319
321{
322 return true;
323}
324
325
327{
328 NodePaintAbility nodeAbility = nodePaintAbility();
329
330 if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == UNPAINTABLE) {
331 m_helper->clearPoints();
332 return;
333 }
334
335 const KisToolShape::ShapeAddInfo info =
337
338 if ((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape) {
339 updateStroke();
340 m_helper->end();
341 }
342 else {
343 KisResourcesSnapshot resources(image(),
344 currentNode(),
345 canvas()->resourceManager());
346 KoPathShape* path = new KoPathShape();
347 path->setShapeId(KoPathShapeId);
348
349 QTransform resolutionMatrix;
350 resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
351 path->moveTo(resolutionMatrix.map(m_startPoint));
352 path->lineTo(resolutionMatrix.map(m_endPoint));
353 path->normalize();
354
356 path->setStroke(border);
357
358 KUndo2Command * cmd = canvas()->shapeController()->addShape(path, nullptr);
359 canvas()->addCommand(cmd);
360 }
361
362 m_strokeIsRunning = false;
364}
365
367{
368 if (!m_strokeIsRunning) return;
369 if (m_startPoint == m_endPoint) return;
370
376 if (m_helper->isRunning()) {
377 m_helper->cancel();
378 }
379
380 m_strokeIsRunning = false;
382}
383
384QPointF KisToolLine::straightLine(QPointF point)
385{
386 const QPointF lineVector = point - m_startPoint;
387 qreal lineAngle = std::atan2(lineVector.y(), lineVector.x());
388
389 if (lineAngle < 0) {
390 lineAngle += 2 * M_PI;
391 }
392
393 const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24;
394
395 const quint32 constrainedLineIndex = static_cast<quint32>((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5);
396 const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES;
397
398 const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y()));
399
400 const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle));
401
402 const QPointF result = m_startPoint + constrainedLineVector;
403
404 return result;
405}
406
407QPointF KisToolLine::snapToAssistants(QPointF point)
408{
409 if (m_chkSnapToAssistants->isChecked() && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
410 KisCanvas2* c = static_cast<KisCanvas2*>(canvas());
413 QPointF startPoint = m_originalStartPoint;
414
415 // startPoint etc. are in image coordinates system (pixels)
416 // but assistants work in document coordinates system ("points")
417 QPointF startPointInDoc = getCoordinatesConverter(canvas())->imageToDocument(startPoint);
418 QPointF pointInDoc = getCoordinatesConverter(canvas())->imageToDocument(point);
419
420 c->paintingAssistantsDecoration()->adjustLine(pointInDoc, startPointInDoc);
422
423 startPoint = getCoordinatesConverter(canvas())->documentToImage(startPointInDoc);
424 point = getCoordinatesConverter(canvas())->documentToImage(pointInDoc);
425
426 m_startPoint = startPoint;
427 return point;
428 }
429 return point;
430}
431
432
434{
435 if (canvas()) {
436 QRectF bound(m_startPoint, m_endPoint);
437 canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3)));
438 }
439}
440
441
443{
444 KisCanvas2 *kisCanvas =dynamic_cast<KisCanvas2*>(canvas());
445 KIS_ASSERT(kisCanvas);
446 kisCanvas->viewManager()->showFloatingMessage(i18n("Length: %1 px", QString::number(QLineF(m_startPoint,m_endPoint).length(), 'f',1))
447 , QIcon(), 1000, KisFloatingMessage::High, Qt::AlignLeft | Qt::TextWordWrap | Qt::AlignVCenter);
448}
449void KisToolLine::paintLine(QPainter& gc, const QRect&)
450{
451 QPointF viewStartPos = pixelToView(m_startPoint);
452 QPointF viewStartEnd = pixelToView(m_endPoint);
453
454 if (m_showGuideline && canvas()) {
455 QPainterPath path;
456 path.moveTo(viewStartPos);
457 path.lineTo(viewStartEnd);
458 paintToolOutline(&gc, path);
459 }
460}
461
463{
464 return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines");
465}
466
468{
469 return true;
470}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
#define KoPathShapeId
Definition KoPathShape.h:20
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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 load(const QString &cursorName, 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 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
Qt::KeyboardModifiers modifiers() const
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