File indexing completed on 2025-02-02 04:22:31
0001 /* 0002 * 0003 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kis_tool_measure.h" 0009 0010 #include <math.h> 0011 0012 #include <QPainter> 0013 #include <QLayout> 0014 #include <QWidget> 0015 #include <QLabel> 0016 #include <QPainterPath> 0017 #include <kcombobox.h> 0018 0019 #include <kis_debug.h> 0020 #include <klocalizedstring.h> 0021 0022 #include "kis_algebra_2d.h" 0023 #include "kis_image.h" 0024 #include "kis_cursor.h" 0025 #include "KoPointerEvent.h" 0026 #include "KoCanvasBase.h" 0027 #include <KoViewConverter.h> 0028 #include "krita_utils.h" 0029 #include "kis_floating_message.h" 0030 #include "kis_canvas2.h" 0031 #include "KisViewManager.h" 0032 #include <KisOptimizedBrushOutline.h> 0033 0034 #define INNER_RADIUS 50 0035 0036 KisToolMeasureOptionsWidget::KisToolMeasureOptionsWidget(QWidget* parent, KisImageWSP image) 0037 : QWidget(parent), 0038 m_resolution(image->xRes()), 0039 m_unit(KoUnit::Pixel) 0040 { 0041 m_distance = 0.0; 0042 0043 QGridLayout* optionLayout = new QGridLayout(this); 0044 Q_CHECK_PTR(optionLayout); 0045 optionLayout->setMargin(0); 0046 0047 optionLayout->addWidget(new QLabel(i18n("Distance:"), this), 0, 0); 0048 optionLayout->addWidget(new QLabel(i18n("Angle:"), this), 1, 0); 0049 0050 m_distanceLabel = new QLabel(this); 0051 m_distanceLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0052 optionLayout->addWidget(m_distanceLabel, 0, 1); 0053 0054 m_angleLabel = new QLabel(this); 0055 m_angleLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0056 optionLayout->addWidget(m_angleLabel, 1, 1); 0057 0058 KComboBox* unitBox = new KComboBox(this); 0059 unitBox->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); 0060 connect(unitBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUnitChanged(int))); 0061 unitBox->setCurrentIndex(m_unit.indexInListForUi(KoUnit::ListAll)); 0062 0063 optionLayout->addWidget(unitBox, 0, 2); 0064 optionLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0, 1, 2); 0065 0066 connect(image, SIGNAL(sigResolutionChanged(double, double)), this, SLOT(slotResolutionChanged(double, double))); 0067 } 0068 0069 void KisToolMeasureOptionsWidget::slotSetDistance(double distance) 0070 { 0071 m_distance = distance; 0072 updateDistance(); 0073 } 0074 0075 void KisToolMeasureOptionsWidget::slotSetAngle(double angle) 0076 { 0077 m_angleLabel->setText(i18nc("angle value in degrees", "%1°", KritaUtils::prettyFormatReal(angle))); 0078 } 0079 0080 void KisToolMeasureOptionsWidget::slotUnitChanged(int index) 0081 { 0082 m_unit = KoUnit::fromListForUi(index, KoUnit::ListAll, m_resolution); 0083 updateDistance(); 0084 } 0085 0086 void KisToolMeasureOptionsWidget::slotResolutionChanged(double xRes, double /*yRes*/) 0087 { 0088 m_resolution = xRes; 0089 updateDistance(); 0090 } 0091 0092 void KisToolMeasureOptionsWidget::updateDistance() 0093 { 0094 double distance = m_distance / m_resolution; 0095 m_distanceLabel->setText(KritaUtils::prettyFormatReal(m_unit.toUserValue(distance))); 0096 } 0097 0098 0099 KisToolMeasure::KisToolMeasure(KoCanvasBase * canvas) 0100 : KisTool(canvas, KisCursor::crossCursor()) 0101 { 0102 } 0103 0104 KisToolMeasure::~KisToolMeasure() 0105 { 0106 } 0107 QPointF KisToolMeasure::lockedAngle(QPointF pos) 0108 { 0109 const QPointF lineVector = pos - m_startPos; 0110 qreal lineAngle = normalizeAngle(std::atan2(lineVector.y(), lineVector.x())); 0111 0112 const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; 0113 0114 const quint32 constrainedLineIndex = static_cast<quint32>((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); 0115 const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; 0116 0117 const qreal lineLength = KisAlgebra2D::norm(lineVector); 0118 0119 const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); 0120 0121 const QPointF result = m_startPos + constrainedLineVector; 0122 0123 return result; 0124 } 0125 0126 void KisToolMeasure::paint(QPainter& gc, const KoViewConverter &converter) 0127 { 0128 QPen old = gc.pen(); 0129 QPen pen(Qt::SolidLine); 0130 gc.setPen(pen); 0131 0132 QPainterPath elbowPath; 0133 elbowPath.moveTo(m_endPos); 0134 elbowPath.lineTo(m_startPos); 0135 0136 QPointF offset = (m_baseLineVec * INNER_RADIUS).toPoint(); 0137 QPointF diff = m_endPos-m_startPos; 0138 0139 bool switch_elbow = QPointF::dotProduct(diff, offset) > 0.0; 0140 if(switch_elbow) { 0141 elbowPath.lineTo(m_startPos + offset); 0142 } else { 0143 elbowPath.lineTo(m_startPos - offset); 0144 } 0145 0146 if (distance() >= INNER_RADIUS) { 0147 QRectF rectangle(m_startPos.x() - INNER_RADIUS, m_startPos.y() - INNER_RADIUS, 2*INNER_RADIUS, 2*INNER_RADIUS); 0148 0149 double det = diff.x() * m_baseLineVec.y() - diff.y() * m_baseLineVec.x(); 0150 int startAngle = -atan2(m_baseLineVec.y(), m_baseLineVec.x()) / (2*M_PI) * 360; 0151 int spanAngle = switch_elbow ? -angle() : angle(); 0152 0153 if(!switch_elbow) { 0154 startAngle+=180; 0155 startAngle%=360; 0156 } 0157 0158 if(det > 0) { 0159 spanAngle = -spanAngle; 0160 } 0161 0162 elbowPath.arcTo(rectangle, startAngle, spanAngle); 0163 } 0164 0165 // The opengl renderer doesn't take the QPainter's transform, so the path is scaled here 0166 qreal sx, sy; 0167 converter.zoom(&sx, &sy); 0168 QTransform transf; 0169 transf.scale(sx / currentImage()->xRes(), sy / currentImage()->yRes()); 0170 paintToolOutline(&gc, transf.map(elbowPath)); 0171 0172 gc.setPen(old); 0173 } 0174 void KisToolMeasure::showDistanceAngleOnCanvas() 0175 { 0176 KisCanvas2 *kisCanvas = qobject_cast<KisCanvas2*>(canvas()); 0177 QString message = i18nc("%1=distance %2=unit of distance %3=angle in degrees", "%1 %2\n%3°", 0178 m_optionsWidget->m_distanceLabel->text(), 0179 m_optionsWidget->m_unit.symbol(), 0180 QString::number(angle(),'f',1)); 0181 kisCanvas->viewManager()->showFloatingMessage(message, QIcon(), 2000, KisFloatingMessage::High); 0182 } 0183 void KisToolMeasure::beginPrimaryAction(KoPointerEvent *event) 0184 { 0185 setMode(KisTool::PAINT_MODE); 0186 0187 // Erase old temporary lines 0188 canvas()->updateCanvas(convertToPt(boundingRect())); 0189 0190 m_startPos = convertToPixelCoord(event); 0191 m_endPos = m_startPos; 0192 m_baseLineVec = QVector2D(1.0f, 0.0f); 0193 0194 emit sigDistanceChanged(0.0); 0195 emit sigAngleChanged(0.0); 0196 } 0197 0198 void KisToolMeasure::continuePrimaryAction(KoPointerEvent *event) 0199 { 0200 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0201 0202 // Erase old temporary lines 0203 canvas()->updateCanvas(convertToPt(boundingRect())); 0204 0205 QPointF pos = convertToPixelCoord(event); 0206 0207 if (event->modifiers() & Qt::AltModifier) { 0208 QPointF trans = pos - m_endPos; 0209 m_startPos += trans; 0210 m_endPos += trans; 0211 } else if(event->modifiers() & Qt::ShiftModifier){ 0212 m_endPos = lockedAngle(pos); 0213 } else { 0214 m_endPos = pos; 0215 } 0216 0217 if(!(event->modifiers() & Qt::ControlModifier)) { 0218 m_chooseBaseLineVec = false; 0219 } else if(!m_chooseBaseLineVec) { 0220 m_chooseBaseLineVec = true; 0221 m_baseLineVec = QVector2D(m_endPos-m_startPos).normalized(); 0222 } 0223 0224 canvas()->updateCanvas(convertToPt(boundingRect())); 0225 emit sigDistanceChanged(distance()); 0226 emit sigAngleChanged(angle()); 0227 showDistanceAngleOnCanvas(); 0228 } 0229 0230 void KisToolMeasure::endPrimaryAction(KoPointerEvent *event) 0231 { 0232 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0233 0234 Q_UNUSED(event); 0235 setMode(KisTool::HOVER_MODE); 0236 } 0237 0238 QWidget* KisToolMeasure::createOptionWidget() 0239 { 0240 if (!currentImage()) 0241 return nullptr; 0242 m_optionsWidget = new KisToolMeasureOptionsWidget(nullptr, currentImage()); 0243 0244 // See https://bugs.kde.org/show_bug.cgi?id=316896 0245 QWidget *specialSpacer = new QWidget(m_optionsWidget); 0246 specialSpacer->setObjectName("SpecialSpacer"); 0247 specialSpacer->setFixedSize(0, 0); 0248 m_optionsWidget->layout()->addWidget(specialSpacer); 0249 0250 m_optionsWidget->setObjectName(toolId() + " option widget"); 0251 connect(this, SIGNAL(sigDistanceChanged(double)), m_optionsWidget, SLOT(slotSetDistance(double))); 0252 connect(this, SIGNAL(sigAngleChanged(double)), m_optionsWidget, SLOT(slotSetAngle(double))); 0253 m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); 0254 return m_optionsWidget; 0255 } 0256 0257 double KisToolMeasure::angle() 0258 { 0259 double dot = QVector2D::dotProduct(QVector2D(m_endPos-m_startPos).normalized(), m_baseLineVec); 0260 return acos(qAbs(dot)) / (2*M_PI)*360; 0261 } 0262 0263 double KisToolMeasure::distance() 0264 { 0265 return QVector2D(m_endPos - m_startPos).length(); 0266 } 0267 0268 QRectF KisToolMeasure::boundingRect() 0269 { 0270 QRectF bound; 0271 bound.setTopLeft(m_startPos); 0272 bound.setBottomRight(m_endPos); 0273 bound = bound.united(QRectF(m_startPos.x() - INNER_RADIUS, m_startPos.y() - INNER_RADIUS, 2 * INNER_RADIUS, 2 * INNER_RADIUS)); 0274 return bound.normalized(); 0275 }