File indexing completed on 2025-03-09 04:28:22
0001 /* 0002 SPDX-FileCopyrightText: 2005 C. Boemann <cbo@boemann.dk> 0003 SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 // Local includes. 0009 0010 #include "kis_curve_widget.h" 0011 #include "kdenlivesettings.h" 0012 // C++ includes. 0013 0014 #include <cmath> 0015 #include <cstdlib> 0016 0017 // Qt includes. 0018 0019 #include <QMouseEvent> 0020 #include <QPaintEvent> 0021 #include <QPainter> 0022 #include <QPen> 0023 #include <QPoint> 0024 #include <QRect> 0025 0026 #define bounds(x, a, b) (x < a ? a : (x > b ? b : x)) 0027 #define MOUSE_AWAY_THRES 15 0028 #define POINT_AREA 1E-4 0029 #define CURVE_AREA 1E-4 0030 0031 // static bool pointLessThan(const QPointF &a, const QPointF &b); 0032 0033 KisCurveWidget::KisCurveWidget(QWidget *parent) 0034 : AbstractCurveWidget(parent) 0035 , m_grabOffsetX(0) 0036 , m_grabOffsetY(0) 0037 , m_grabOriginalX(0) 0038 , m_grabOriginalY(0) 0039 , m_draggedAwayPointIndex(0) 0040 , m_guideVisible(false) 0041 0042 { 0043 setObjectName(QStringLiteral("KisCurveWidget")); 0044 0045 m_pixmapIsDirty = false; 0046 m_pixmapCache = nullptr; 0047 m_maxPoints = 0; 0048 m_curve = KisCubicCurve(); 0049 setMaxPoints(5); 0050 update(); 0051 } 0052 0053 KisCurveWidget::~KisCurveWidget() = default; 0054 0055 QSize KisCurveWidget::sizeHint() const 0056 { 0057 return {500, 500}; 0058 } 0059 0060 void KisCurveWidget::addPointInTheMiddle() 0061 { 0062 QPointF pt(0.5, m_curve.value(0.5)); 0063 0064 if (!jumpOverExistingPoints(pt, -1)) { 0065 return; 0066 } 0067 0068 m_currentPointIndex = m_curve.addPoint(pt); 0069 0070 update(); 0071 Q_EMIT modified(); 0072 } 0073 0074 void KisCurveWidget::paintEvent(QPaintEvent *) 0075 { 0076 QPainter p(this); 0077 paintBackground(&p); 0078 0079 // Draw curve. 0080 int x; 0081 QPolygonF poly; 0082 0083 p.setPen(QPen(palette().text().color(), 1, Qt::SolidLine)); 0084 poly.reserve(m_wWidth); 0085 for (x = 0; x < m_wWidth; ++x) { 0086 double normalizedX = double(x) / m_wWidth; 0087 double curY = m_wHeight - m_curve.value(normalizedX) * m_wHeight; 0088 poly.append(QPointF(x, curY)); 0089 } 0090 poly.append(QPointF(x, m_wHeight - m_curve.value(1.0) * m_wHeight)); 0091 p.drawPolyline(poly); 0092 0093 // Drawing curve handles. 0094 for (int i = 0; i < m_curve.points().count(); ++i) { 0095 double curveX = m_curve.points().at(i).x(); 0096 double curveY = m_curve.points().at(i).y(); 0097 0098 if (i == m_currentPointIndex) { 0099 p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); 0100 p.drawEllipse(QRectF(curveX * m_wWidth - 2, m_wHeight - 2 - curveY * m_wHeight, 4, 4)); 0101 } else { 0102 p.setPen(QPen(Qt::red, 1, Qt::SolidLine)); 0103 p.drawEllipse(QRectF(curveX * m_wWidth - 3, m_wHeight - 3 - curveY * m_wHeight, 6, 6)); 0104 } 0105 } 0106 } 0107 0108 void KisCurveWidget::mousePressEvent(QMouseEvent *e) 0109 { 0110 int wWidth = width() - 1; 0111 int wHeight = height() - 1; 0112 int offsetX = int(1 / 8. * m_zoomLevel * wWidth); 0113 int offsetY = int(1 / 8. * m_zoomLevel * wHeight); 0114 wWidth -= 2 * offsetX; 0115 wHeight -= 2 * offsetY; 0116 double x = (e->pos().x() - offsetX) / double(wWidth); 0117 double y = 1.0 - (e->pos().y() - offsetY) / double(wHeight); 0118 0119 int closest_point_index = nearestPointInRange(QPointF(x, y), width(), height()); 0120 0121 if (e->button() == Qt::RightButton && closest_point_index > 0 && closest_point_index < m_curve.points().count() - 1) { 0122 m_currentPointIndex = closest_point_index; 0123 slotDeleteCurrentPoint(); 0124 return; 0125 } else if (e->button() != Qt::LeftButton) { 0126 return; 0127 } 0128 0129 if (closest_point_index < 0) { 0130 if (m_maxPoints > 0 && m_curve.points().count() >= m_maxPoints) { 0131 return; 0132 } 0133 QPointF newPoint(x, y); 0134 if (!jumpOverExistingPoints(newPoint, -1)) { 0135 return; 0136 } 0137 m_currentPointIndex = m_curve.addPoint(newPoint); 0138 } else { 0139 m_currentPointIndex = closest_point_index; 0140 } 0141 0142 m_grabOriginalX = m_curve.points().at(m_currentPointIndex).x(); 0143 m_grabOriginalY = m_curve.points().at(m_currentPointIndex).y(); 0144 m_grabOffsetX = m_curve.points().at(m_currentPointIndex).x() - x; 0145 m_grabOffsetY = m_curve.points().at(m_currentPointIndex).y() - y; 0146 QPointF point(x + m_grabOffsetX, y + m_grabOffsetY); 0147 m_curve.setPoint(m_currentPointIndex, point); 0148 0149 m_draggedAwayPointIndex = -1; 0150 m_state = State_t::DRAG; 0151 0152 update(); 0153 Q_EMIT currentPoint(point, isCurrentPointExtremal()); 0154 } 0155 0156 void KisCurveWidget::mouseMoveEvent(QMouseEvent *e) 0157 { 0158 int wWidth = width() - 1; 0159 int wHeight = height() - 1; 0160 int offsetX = int(1 / 8. * m_zoomLevel * wWidth); 0161 int offsetY = int(1 / 8. * m_zoomLevel * wHeight); 0162 wWidth -= 2 * offsetX; 0163 wHeight -= 2 * offsetY; 0164 double x = (e->pos().x() - offsetX) / double(wWidth); 0165 double y = 1.0 - (e->pos().y() - offsetY) / double(wHeight); 0166 0167 if (m_state == State_t::NORMAL) { // If no point is selected set the cursor shape if on top 0168 int nearestPointIndex = nearestPointInRange(QPointF(x, y), width(), height()); 0169 0170 if (nearestPointIndex < 0) { 0171 setCursor(Qt::ArrowCursor); 0172 } else { 0173 setCursor(Qt::CrossCursor); 0174 } 0175 } else { // Else, drag the selected point 0176 bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; 0177 bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; 0178 0179 bool removePoint = (crossedHoriz || crossedVert); 0180 0181 if (!removePoint && m_draggedAwayPointIndex >= 0) { 0182 // point is no longer dragged away so reinsert it 0183 QPointF newPoint(m_draggedAwayPoint); 0184 m_currentPointIndex = m_curve.addPoint(newPoint); 0185 m_draggedAwayPointIndex = -1; 0186 } 0187 0188 if (removePoint && (m_draggedAwayPointIndex >= 0)) { 0189 return; 0190 } 0191 0192 setCursor(Qt::CrossCursor); 0193 0194 x += m_grabOffsetX; 0195 y += m_grabOffsetY; 0196 0197 double leftX; 0198 double rightX; 0199 if (m_currentPointIndex == 0) { 0200 leftX = 0.0; 0201 rightX = 0.0; 0202 /*if (m_curve.points().count() > 1) 0203 rightX = m_curve.points().at(m_currentPointIndex + 1).x() - POINT_AREA; 0204 else 0205 rightX = 1.0;*/ 0206 } else if (m_currentPointIndex == m_curve.points().count() - 1) { 0207 leftX = m_curve.points().at(m_currentPointIndex - 1).x() + POINT_AREA; 0208 rightX = 1.0; 0209 } else { 0210 Q_ASSERT(m_currentPointIndex > 0 && m_currentPointIndex < m_curve.points().count() - 1); 0211 0212 // the 1E-4 addition so we can grab the dot later. 0213 leftX = m_curve.points().at(m_currentPointIndex - 1).x() + POINT_AREA; 0214 rightX = m_curve.points().at(m_currentPointIndex + 1).x() - POINT_AREA; 0215 } 0216 0217 x = bounds(x, leftX, rightX); 0218 y = bounds(y, 0., 1.); 0219 QPointF point(x, y); 0220 0221 m_curve.setPoint(m_currentPointIndex, point); 0222 0223 if (removePoint && m_curve.points().count() > 2) { 0224 m_draggedAwayPoint = m_curve.points().at(m_currentPointIndex); 0225 m_draggedAwayPointIndex = m_currentPointIndex; 0226 m_curve.removePoint(m_currentPointIndex); 0227 m_currentPointIndex = bounds(m_currentPointIndex, 0, m_curve.points().count() - 1); 0228 } 0229 0230 update(); 0231 Q_EMIT currentPoint(point, isCurrentPointExtremal()); 0232 if (KdenliveSettings::dragvalue_directupdate()) { 0233 Q_EMIT modified(); 0234 } 0235 } 0236 } 0237 0238 bool KisCurveWidget::jumpOverExistingPoints(QPointF &pt, int skipIndex) 0239 { 0240 for (QPointF &it : m_curve.points()) { 0241 if (m_curve.points().indexOf(it) == skipIndex) { 0242 continue; 0243 } 0244 if (fabs(it.x() - pt.x()) < POINT_AREA) pt.rx() = pt.x() >= it.x() ? it.x() + POINT_AREA : it.x() - POINT_AREA; 0245 } 0246 return (pt.x() >= 0 && pt.x() <= 1.); 0247 } 0248 0249 int KisCurveWidget::nearestPointInRange(QPointF pt, int wWidth, int wHeight) const 0250 { 0251 double nearestDistanceSquared = 1000; 0252 int nearestIndex = -1; 0253 int i = 0; 0254 0255 for (QPointF &point : m_curve.points()) { 0256 double distanceSquared = (pt.x() - point.x()) * (pt.x() - point.x()) + (pt.y() - point.y()) * (pt.y() - point.y()); 0257 0258 if (distanceSquared < nearestDistanceSquared) { 0259 nearestIndex = i; 0260 nearestDistanceSquared = distanceSquared; 0261 } 0262 ++i; 0263 } 0264 0265 if (nearestIndex >= 0) { 0266 double dx = (pt.x() - m_curve.points().at(nearestIndex).x()) * wWidth; 0267 double dy = (pt.y() - m_curve.points().at(nearestIndex).y()) * wHeight; 0268 if (dx * dx + dy * dy <= m_grabRadius * m_grabRadius) { 0269 return nearestIndex; 0270 } 0271 } 0272 0273 return -1; 0274 } 0275 0276 // void KisCurveWidget::syncIOControls() 0277 // { 0278 // if (!m_intIn || !m_intOut) { 0279 // return; 0280 // } 0281 0282 // bool somethingSelected = (m_currentPointIndex >= 0); 0283 0284 // m_intIn->setEnabled(somethingSelected); 0285 // m_intOut->setEnabled(somethingSelected); 0286 0287 // if (m_currentPointIndex >= 0) { 0288 // m_intIn->blockSignals(true); 0289 // m_intOut->blockSignals(true); 0290 0291 // m_intIn->setValue(sp2io(m_curve.points().at(m_currentPointIndex).x())); 0292 // m_intOut->setValue(sp2io(m_curve.points().at(m_currentPointIndex).y())); 0293 0294 // m_intIn->blockSignals(false); 0295 // m_intOut->blockSignals(false); 0296 // } else { 0297 // /*FIXME: Ideally, these controls should hide away now */ 0298 // } 0299 // } 0300 void KisCurveWidget::setCurve(KisCubicCurve &&curve) 0301 { 0302 m_curve = curve; 0303 } 0304 0305 QList<QPointF> KisCurveWidget::getPoints() const 0306 { 0307 return m_curve.points(); 0308 }