File indexing completed on 2024-05-26 04:32:07
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com> 0004 * SPDX-FileCopyrightText: 2022 Julian Schmidt <julisch1107@web.de> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "RulerAssistant.h" 0010 0011 #include <kis_debug.h> 0012 #include <klocalizedstring.h> 0013 0014 #include <QPainter> 0015 #include <QPainterPath> 0016 #include <QTransform> 0017 0018 #include <kis_canvas2.h> 0019 #include <kis_coordinates_converter.h> 0020 #include <kis_dom_utils.h> 0021 0022 #include <math.h> 0023 0024 RulerAssistant::RulerAssistant() 0025 : RulerAssistant("ruler", i18n("Ruler assistant")) 0026 { 0027 } 0028 0029 RulerAssistant::RulerAssistant(const QString& id, const QString& name) 0030 : KisPaintingAssistant(id, name) 0031 { 0032 } 0033 0034 KisPaintingAssistantSP RulerAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const 0035 { 0036 return KisPaintingAssistantSP(new RulerAssistant(*this, handleMap)); 0037 } 0038 0039 RulerAssistant::RulerAssistant(const RulerAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) 0040 : KisPaintingAssistant(rhs, handleMap) 0041 , m_subdivisions(rhs.m_subdivisions) 0042 , m_minorSubdivisions(rhs.m_minorSubdivisions) 0043 , m_hasFixedLength(rhs.m_hasFixedLength) 0044 , m_fixedLength(rhs.m_fixedLength) 0045 , m_fixedLengthUnit(rhs.m_fixedLengthUnit) 0046 { 0047 } 0048 0049 QPointF RulerAssistant::project(const QPointF& pt) const 0050 { 0051 Q_ASSERT(isAssistantComplete()); 0052 QPointF pt1 = *handles()[0]; 0053 QPointF pt2 = *handles()[1]; 0054 0055 QPointF a = pt - pt1; 0056 QPointF u = pt2 - pt1; 0057 0058 qreal u_norm = sqrt(u.x() * u.x() + u.y() * u.y()); 0059 0060 if(u_norm == 0) return pt; 0061 0062 u /= u_norm; 0063 0064 double t = a.x() * u.x() + a.y() * u.y(); 0065 0066 if(t < 0.0) return pt1; 0067 if(t > u_norm) return pt2; 0068 0069 return t * u + pt1; 0070 } 0071 0072 QPointF RulerAssistant::adjustPosition(const QPointF& pt, const QPointF& /*strokeBegin*/, const bool /*snapToAny*/, qreal /*moveThresholdPt*/) 0073 { 0074 return project(pt); 0075 } 0076 0077 void RulerAssistant::adjustLine(QPointF &point, QPointF &strokeBegin) 0078 { 0079 point = project(point); 0080 strokeBegin = project(strokeBegin); 0081 } 0082 0083 void RulerAssistant::drawSubdivisions(QPainter& gc, const KisCoordinatesConverter *converter) { 0084 if (subdivisions() == 0) { 0085 return; 0086 } 0087 0088 // Get handle positions 0089 QTransform document2widget = converter->documentToWidgetTransform(); 0090 0091 QPointF p1 = document2widget.map(*handles()[0]); 0092 QPointF p2 = document2widget.map(*handles()[1]); 0093 0094 const qreal scale = 16.0 / 2; 0095 const qreal minorScale = scale / 2; 0096 const QRectF clipping = QRectF(gc.viewport()).adjusted(-scale, -scale, scale, scale); 0097 // If the lines would end up closer to each other than this threshold (in 0098 // screen coordinates), they are not rendered, as they wouldn't be 0099 // distinguishable anymore. 0100 const qreal threshold = 3.0; 0101 0102 // Calculate line direction and normal vector 0103 QPointF delta = p2 - p1; 0104 qreal length = sqrt(KisPaintingAssistant::norm2(delta)); 0105 qreal stepsize = length / subdivisions(); 0106 0107 // Only draw if lines are far enough apart 0108 if (stepsize >= threshold) { 0109 QPointF normal = QPointF(delta.y(), -delta.x()); 0110 normal /= length; 0111 0112 QPainterPath path; 0113 0114 // Draw the major subdivisions 0115 for (int ii = 0; ii <= subdivisions(); ++ii) { 0116 0117 QPointF pos = p1 + delta * ((qreal)ii / subdivisions()); 0118 0119 if (clipping.contains(pos)) { 0120 path.moveTo(pos - normal * scale); 0121 path.lineTo(pos + normal * scale); 0122 } 0123 0124 // Draw minor subdivisions, if they exist (implicit check due to 0125 // the loop bounds) 0126 // Skip for the last iteration of the outer loop, which would 0127 // already be beyond the ruler's length 0128 // Also skip if major subdivisions are too close already 0129 if (ii == subdivisions() || stepsize / minorSubdivisions() < threshold) 0130 continue; 0131 // Draw minor marks in between the major ones 0132 for (int jj = 1; jj < minorSubdivisions(); ++jj) { 0133 0134 QPointF mpos = pos + delta * ((qreal)jj / (subdivisions() * minorSubdivisions())); 0135 0136 if (clipping.contains(mpos)) { 0137 path.moveTo(mpos - normal * minorScale); 0138 path.lineTo(mpos + normal * minorScale); 0139 } 0140 } 0141 } 0142 0143 gc.save(); 0144 gc.resetTransform(); 0145 drawPath(gc, path, isSnappingActive()); 0146 gc.restore(); 0147 } 0148 } 0149 0150 void RulerAssistant::drawHandleAnnotations(QPainter& gc, const KisCoordinatesConverter* converter) { 0151 gc.save(); 0152 gc.resetTransform(); 0153 0154 QTransform doc2widget = converter->documentToWidgetTransform(); 0155 QPointF center = doc2widget.map(*handles()[0]); 0156 QPointF handle = doc2widget.map(*handles()[1]); 0157 0158 QPainterPath path; 0159 0160 // Center / Movement handle 0161 for (int i = 0; i < 4; ++i) { 0162 QTransform rot = QTransform().rotate(i * 90); 0163 0164 path.moveTo(center + rot.map(QPointF(12, -3))); 0165 path.lineTo(center + rot.map(QPointF(9, 0))); 0166 path.lineTo(center + rot.map(QPointF(12, 3))); 0167 } 0168 0169 // Rotation handle 0170 QRectF bounds = QRectF(handle, QSizeF(0, 0)).adjusted(-11, -11, 11, 11); 0171 for (int i = 0; i < 2; ++i) { 0172 int dir = i == 0 ? 1 : -1; 0173 0174 path.moveTo(handle + QPointF(dir * 11, 0)); 0175 path.arcTo(bounds, i * 180, -90); 0176 } 0177 0178 drawPath(gc, path); 0179 gc.restore(); 0180 } 0181 0182 void RulerAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) 0183 { 0184 // Draw the subdivisions 0185 // When the number of subdivisions (or minor subdivisions) is set to 0186 // 0, the respective feature is turned off and won't be rendered. 0187 if (assistantVisible && isAssistantComplete() && subdivisions() > 0) { 0188 drawSubdivisions(gc, converter); 0189 } 0190 0191 // Indicate handle type on fixed-length handles 0192 if (canvas && canvas->paintingAssistantsDecoration()->isEditingAssistants() && hasFixedLength()) { 0193 drawHandleAnnotations(gc, converter); 0194 } 0195 0196 // Draw the ruler itself via drawCache 0197 KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); 0198 } 0199 0200 void RulerAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) 0201 { 0202 if (!assistantVisible || !isAssistantComplete()){ 0203 return; 0204 } 0205 0206 QTransform initialTransform = converter->documentToWidgetTransform(); 0207 0208 // Draw the line 0209 QPointF p1 = *handles()[0]; 0210 QPointF p2 = *handles()[1]; 0211 0212 gc.setTransform(initialTransform); 0213 QPainterPath path; 0214 path.moveTo(p1); 0215 path.lineTo(p2); 0216 drawPath(gc, path, isSnappingActive()); 0217 } 0218 0219 QPointF RulerAssistant::getDefaultEditorPosition() const 0220 { 0221 return (*handles()[0] + *handles()[1]) * 0.5; 0222 } 0223 0224 bool RulerAssistant::isAssistantComplete() const 0225 { 0226 return handles().size() >= 2; 0227 } 0228 0229 int RulerAssistant::subdivisions() const { 0230 return m_subdivisions; 0231 } 0232 0233 void RulerAssistant::setSubdivisions(int subdivisions) { 0234 if (subdivisions < 0) { 0235 m_subdivisions = 0; 0236 } else { 0237 m_subdivisions = subdivisions; 0238 } 0239 } 0240 0241 int RulerAssistant::minorSubdivisions() const { 0242 return m_minorSubdivisions; 0243 } 0244 0245 void RulerAssistant::setMinorSubdivisions(int subdivisions) { 0246 if (subdivisions < 0) { 0247 m_minorSubdivisions = 0; 0248 } else { 0249 m_minorSubdivisions = subdivisions; 0250 } 0251 } 0252 0253 bool RulerAssistant::hasFixedLength() const { 0254 return m_hasFixedLength; 0255 } 0256 0257 void RulerAssistant::enableFixedLength(bool enabled) { 0258 m_hasFixedLength = enabled; 0259 } 0260 0261 qreal RulerAssistant::fixedLength() const { 0262 return m_fixedLength; 0263 } 0264 0265 void RulerAssistant::setFixedLength(qreal length) { 0266 if (length < 0.0) { 0267 m_fixedLength = 0.0; 0268 } else { 0269 m_fixedLength = length; 0270 } 0271 } 0272 0273 QString RulerAssistant::fixedLengthUnit() const { 0274 return m_fixedLengthUnit; 0275 } 0276 0277 void RulerAssistant::setFixedLengthUnit(QString unit) { 0278 if (unit.isEmpty()) { 0279 m_fixedLengthUnit = "px"; 0280 } else { 0281 m_fixedLengthUnit = unit; 0282 } 0283 } 0284 0285 void RulerAssistant::ensureLength() { 0286 if (!hasFixedLength() || fixedLength() < 1e-3) { 0287 return; 0288 } 0289 0290 QPointF center = *handles()[0]; 0291 QPointF handle = *handles()[1]; 0292 QPointF direction = handle - center; 0293 qreal distance = sqrt(KisPaintingAssistant::norm2(direction)); 0294 QPointF delta = direction / distance * fixedLength(); 0295 *handles()[1] = center + delta; 0296 uncache(); 0297 } 0298 0299 void RulerAssistant::saveCustomXml(QXmlStreamWriter *xml) { 0300 if (xml) { 0301 xml->writeStartElement("subdivisions"); 0302 xml->writeAttribute("value", KisDomUtils::toString(subdivisions())); 0303 xml->writeEndElement(); 0304 xml->writeStartElement("minorSubdivisions"); 0305 xml->writeAttribute("value", KisDomUtils::toString(minorSubdivisions())); 0306 xml->writeEndElement(); 0307 xml->writeStartElement("fixedLength"); 0308 xml->writeAttribute("value", KisDomUtils::toString(fixedLength())); 0309 xml->writeAttribute("enabled", KisDomUtils::toString((int)hasFixedLength())); 0310 xml->writeAttribute("unit", fixedLengthUnit()); 0311 xml->writeEndElement(); 0312 } 0313 } 0314 0315 bool RulerAssistant::loadCustomXml(QXmlStreamReader *xml) { 0316 if (xml) { 0317 if (xml->name() == "subdivisions") { 0318 setSubdivisions(KisDomUtils::toInt(xml->attributes().value("value").toString())); 0319 } 0320 else if (xml->name() == "minorSubdivisions") { 0321 setMinorSubdivisions(KisDomUtils::toInt(xml->attributes().value("value").toString())); 0322 } 0323 else if (xml->name() == "fixedLength") { 0324 setFixedLength(KisDomUtils::toDouble(xml->attributes().value("value").toString())); 0325 enableFixedLength(0 != KisDomUtils::toInt(xml->attributes().value("enabled").toString())); 0326 setFixedLengthUnit(xml->attributes().value("unit").toString()); 0327 } 0328 } 0329 return true; 0330 } 0331 0332 0333 0334 RulerAssistantFactory::RulerAssistantFactory() = default; 0335 0336 RulerAssistantFactory::~RulerAssistantFactory() = default; 0337 0338 QString RulerAssistantFactory::id() const 0339 { 0340 return "ruler"; 0341 } 0342 0343 QString RulerAssistantFactory::name() const 0344 { 0345 return i18n("Ruler"); 0346 } 0347 0348 KisPaintingAssistant* RulerAssistantFactory::createPaintingAssistant() const 0349 { 0350 return new RulerAssistant; 0351 }