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 }