Krita Source Code Documentation
Loading...
Searching...
No Matches
RulerAssistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
4 * SPDX-FileCopyrightText: 2022 Julian Schmidt <julisch1107@web.de>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9#include "RulerAssistant.h"
10
11#include <kis_debug.h>
12#include <klocalizedstring.h>
13
14#include <QPainter>
15#include <QPainterPath>
16#include <QTransform>
17
18#include <kis_canvas2.h>
20#include <kis_dom_utils.h>
21
22#include <math.h>
23
25 : RulerAssistant("ruler", i18n("Ruler assistant"))
26{
27}
28
29RulerAssistant::RulerAssistant(const QString& id, const QString& name)
30 : KisPaintingAssistant(id, name)
31{
32}
33
34KisPaintingAssistantSP RulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
35{
36 return KisPaintingAssistantSP(new RulerAssistant(*this, handleMap));
37}
38
39RulerAssistant::RulerAssistant(const RulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
40 : KisPaintingAssistant(rhs, handleMap)
41 , m_subdivisions(rhs.m_subdivisions)
42 , m_minorSubdivisions(rhs.m_minorSubdivisions)
43 , m_hasFixedLength(rhs.m_hasFixedLength)
44 , m_fixedLength(rhs.m_fixedLength)
45 , m_fixedLengthUnit(rhs.m_fixedLengthUnit)
46{
47}
48
49QPointF RulerAssistant::project(const QPointF& pt) const
50{
51 Q_ASSERT(isAssistantComplete());
52 QPointF pt1 = *handles()[0];
53 QPointF pt2 = *handles()[1];
54
55 QPointF a = pt - pt1;
56 QPointF u = pt2 - pt1;
57
58 qreal u_norm = sqrt(u.x() * u.x() + u.y() * u.y());
59
60 if(u_norm == 0) return pt;
61
62 u /= u_norm;
63
64 double t = a.x() * u.x() + a.y() * u.y();
65
66 if(t < 0.0) return pt1;
67 if(t > u_norm) return pt2;
68
69 return t * u + pt1;
70}
71
72QPointF RulerAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/, const bool /*snapToAny*/, qreal /*moveThresholdPt*/)
73{
74 return project(pt);
75}
76
77void RulerAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
78{
79 point = project(point);
80 strokeBegin = project(strokeBegin);
81}
82
83void RulerAssistant::drawSubdivisions(QPainter& gc, const KisCoordinatesConverter *converter) {
84 if (subdivisions() == 0) {
85 return;
86 }
87
88 // Get handle positions
89 QTransform document2widget = converter->documentToWidgetTransform();
90
91 QPointF p1 = document2widget.map(*handles()[0]);
92 QPointF p2 = document2widget.map(*handles()[1]);
93
94 const qreal scale = 16.0 / 2;
95 const qreal minorScale = scale / 2;
96 const QRectF clipping = QRectF(gc.viewport()).adjusted(-scale, -scale, scale, scale);
97 // If the lines would end up closer to each other than this threshold (in
98 // screen coordinates), they are not rendered, as they wouldn't be
99 // distinguishable anymore.
100 const qreal threshold = 3.0;
101
102 // Calculate line direction and normal vector
103 QPointF delta = p2 - p1;
104 qreal length = sqrt(KisPaintingAssistant::norm2(delta));
105 qreal stepsize = length / subdivisions();
106
107 // Only draw if lines are far enough apart
108 if (stepsize >= threshold) {
109 QPointF normal = QPointF(delta.y(), -delta.x());
110 normal /= length;
111
112 QPainterPath path;
113
114 // Draw the major subdivisions
115 for (int ii = 0; ii <= subdivisions(); ++ii) {
116
117 QPointF pos = p1 + delta * ((qreal)ii / subdivisions());
118
119 if (clipping.contains(pos)) {
120 path.moveTo(pos - normal * scale);
121 path.lineTo(pos + normal * scale);
122 }
123
124 // Draw minor subdivisions, if they exist (implicit check due to
125 // the loop bounds)
126 // Skip for the last iteration of the outer loop, which would
127 // already be beyond the ruler's length
128 // Also skip if major subdivisions are too close already
129 if (ii == subdivisions() || stepsize / minorSubdivisions() < threshold)
130 continue;
131 // Draw minor marks in between the major ones
132 for (int jj = 1; jj < minorSubdivisions(); ++jj) {
133
134 QPointF mpos = pos + delta * ((qreal)jj / (subdivisions() * minorSubdivisions()));
135
136 if (clipping.contains(mpos)) {
137 path.moveTo(mpos - normal * minorScale);
138 path.lineTo(mpos + normal * minorScale);
139 }
140 }
141 }
142
143 gc.save();
144 gc.resetTransform();
145 drawPath(gc, path, isSnappingActive());
146 gc.restore();
147 }
148}
149
151 gc.save();
152 gc.resetTransform();
153
154 QTransform doc2widget = converter->documentToWidgetTransform();
155 QPointF center = doc2widget.map(*handles()[0]);
156 QPointF handle = doc2widget.map(*handles()[1]);
157
158 QPainterPath path;
159
160 // Center / Movement handle
161 for (int i = 0; i < 4; ++i) {
162 QTransform rot = QTransform().rotate(i * 90);
163
164 path.moveTo(center + rot.map(QPointF(12, -3)));
165 path.lineTo(center + rot.map(QPointF(9, 0)));
166 path.lineTo(center + rot.map(QPointF(12, 3)));
167 }
168
169 // Rotation handle
170 QRectF bounds = QRectF(handle, QSizeF(0, 0)).adjusted(-11, -11, 11, 11);
171 for (int i = 0; i < 2; ++i) {
172 int dir = i == 0 ? 1 : -1;
173
174 path.moveTo(handle + QPointF(dir * 11, 0));
175 path.arcTo(bounds, i * 180, -90);
176 }
177
178 drawPath(gc, path);
179 gc.restore();
180}
181
182void RulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
183{
184 // Draw the subdivisions
185 // When the number of subdivisions (or minor subdivisions) is set to
186 // 0, the respective feature is turned off and won't be rendered.
187 if (assistantVisible && isAssistantComplete() && subdivisions() > 0) {
188 drawSubdivisions(gc, converter);
189 }
190
191 // Indicate handle type on fixed-length handles
192 if (canvas && canvas->paintingAssistantsDecoration()->isEditingAssistants() && hasFixedLength()) {
193 drawHandleAnnotations(gc, converter);
194 }
195
196 // Draw the ruler itself via drawCache
197 KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
198}
199
200void RulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
201{
202 if (!assistantVisible || !isAssistantComplete()){
203 return;
204 }
205
206 QTransform initialTransform = converter->documentToWidgetTransform();
207
208 // Draw the line
209 QPointF p1 = *handles()[0];
210 QPointF p2 = *handles()[1];
211
212 gc.setTransform(initialTransform);
213 QPainterPath path;
214 path.moveTo(p1);
215 path.lineTo(p2);
216 drawPath(gc, path, isSnappingActive());
217}
218
220{
221 return (*handles()[0] + *handles()[1]) * 0.5;
222}
223
225{
226 return handles().size() >= 2;
227}
228
230 return m_subdivisions;
231}
232
233void RulerAssistant::setSubdivisions(int subdivisions) {
234 if (subdivisions < 0) {
235 m_subdivisions = 0;
236 } else {
238 }
239}
240
244
246 if (subdivisions < 0) {
248 } else {
250 }
251}
252
254 return m_hasFixedLength;
255}
256
258 m_hasFixedLength = enabled;
259}
260
262 return m_fixedLength;
263}
264
266 if (length < 0.0) {
267 m_fixedLength = 0.0;
268 } else {
270 }
271}
272
274 return m_fixedLengthUnit;
275}
276
278 if (unit.isEmpty()) {
279 m_fixedLengthUnit = "px";
280 } else {
281 m_fixedLengthUnit = unit;
282 }
283}
284
286 if (!hasFixedLength() || fixedLength() < 1e-3) {
287 return;
288 }
289
290 QPointF center = *handles()[0];
291 QPointF handle = *handles()[1];
292 QPointF direction = handle - center;
293 qreal distance = sqrt(KisPaintingAssistant::norm2(direction));
294 QPointF delta = direction / distance * fixedLength();
295 *handles()[1] = center + delta;
296 uncache();
297}
298
299void RulerAssistant::saveCustomXml(QXmlStreamWriter *xml) {
300 if (xml) {
301 xml->writeStartElement("subdivisions");
302 xml->writeAttribute("value", KisDomUtils::toString(subdivisions()));
303 xml->writeEndElement();
304 xml->writeStartElement("minorSubdivisions");
305 xml->writeAttribute("value", KisDomUtils::toString(minorSubdivisions()));
306 xml->writeEndElement();
307 xml->writeStartElement("fixedLength");
308 xml->writeAttribute("value", KisDomUtils::toString(fixedLength()));
309 xml->writeAttribute("enabled", KisDomUtils::toString((int)hasFixedLength()));
310 xml->writeAttribute("unit", fixedLengthUnit());
311 xml->writeEndElement();
312 }
313}
314
315bool RulerAssistant::loadCustomXml(QXmlStreamReader *xml) {
316 if (xml) {
317 if (xml->name() == "subdivisions") {
318 setSubdivisions(KisDomUtils::toInt(xml->attributes().value("value").toString()));
319 }
320 else if (xml->name() == "minorSubdivisions") {
321 setMinorSubdivisions(KisDomUtils::toInt(xml->attributes().value("value").toString()));
322 }
323 else if (xml->name() == "fixedLength") {
324 setFixedLength(KisDomUtils::toDouble(xml->attributes().value("value").toString()));
325 enableFixedLength(0 != KisDomUtils::toInt(xml->attributes().value("enabled").toString()));
326 setFixedLengthUnit(xml->attributes().value("unit").toString());
327 }
328 }
329 return true;
330}
331
332
333
335
337
339{
340 return "ruler";
341}
342
344{
345 return i18n("Ruler");
346}
347
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
qreal u
QPointF p2
QPointF p1
qreal distance(const QPointF &p1, const QPointF &p2)
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const
void drawPath(QPainter &painter, const QPainterPath &path, bool drawActive=true)
static double norm2(const QPointF &p)
virtual void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true)
const QList< KisPaintingAssistantHandleSP > & handles() const
~RulerAssistantFactory() override
QString id() const override
QString name() const override
KisPaintingAssistant * createPaintingAssistant() const override
bool loadCustomXml(QXmlStreamReader *xml) override
void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
void drawHandleAnnotations(QPainter &gc, const KisCoordinatesConverter *converter)
qreal fixedLength() const
void adjustLine(QPointF &point, QPointF &strokeBegin) override
QString m_fixedLengthUnit
void setFixedLength(qreal length)
void setFixedLengthUnit(QString unit)
void setMinorSubdivisions(int subdivisions)
bool isAssistantComplete() const override
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas, bool assistantVisible=true, bool previewVisible=true) override
QString fixedLengthUnit() const
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin, const bool snapToAny, qreal moveThresholdPt) override
bool hasFixedLength() const
void saveCustomXml(QXmlStreamWriter *xml) override
void setSubdivisions(int subdivisions)
QPointF project(const QPointF &pt) const
int subdivisions() const
KisPaintingAssistantSP clone(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap) const override
void enableFixedLength(bool enabled)
int minorSubdivisions() const
QPointF getDefaultEditorPosition() const override
void drawSubdivisions(QPainter &gc, const KisCoordinatesConverter *converter)
#define bounds(x, a, b)
QSharedPointer< KisPaintingAssistant > KisPaintingAssistantSP
Definition kis_types.h:189
double toDouble(const QString &str, bool *ok=nullptr)
int toInt(const QString &str, bool *ok=nullptr)
QString toString(const QString &value)