File indexing completed on 2024-06-23 04:48:42
0001 /* 0002 SPDX-FileCopyrightText: 2010 Till Theato <root@ttill.de> 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "beziersplineeditor.h" 0007 #include "kdenlivesettings.h" 0008 0009 #include "complex" 0010 0011 #include <QMouseEvent> 0012 #include <QPainter> 0013 #include <QPainterPath> 0014 0015 BezierSplineEditor::BezierSplineEditor(QWidget *parent) 0016 : AbstractCurveWidget(parent) 0017 0018 { 0019 m_curve = CubicBezierSpline(); 0020 } 0021 0022 BezierSplineEditor::~BezierSplineEditor() = default; 0023 0024 void BezierSplineEditor::paintEvent(QPaintEvent *event) 0025 { 0026 Q_UNUSED(event) 0027 0028 QPainter p(this); 0029 0030 paintBackground(&p); 0031 0032 /* 0033 * Prepare Spline, Points 0034 */ 0035 int max = m_curve.count() - 1; 0036 if (max < 1) { 0037 return; 0038 } 0039 BPoint point(m_curve.getPoint(0, m_wWidth, m_wHeight, true)); 0040 0041 /* 0042 * Spline 0043 */ 0044 BPoint next; 0045 0046 QPainterPath splinePath(QPointF(point.p.x(), point.p.y())); 0047 for (int i = 0; i < max; ++i) { 0048 point = m_curve.getPoint(i, m_wWidth, m_wHeight, true); 0049 next = m_curve.getPoint(i + 1, m_wWidth, m_wHeight, true); 0050 splinePath.cubicTo(point.h2, next.h1, next.p); 0051 } 0052 p.setPen(QPen(palette().text().color(), 1, Qt::SolidLine)); 0053 p.drawPath(splinePath); 0054 0055 /* 0056 * Points + Handles 0057 */ 0058 p.setPen(QPen(Qt::red, 1, Qt::SolidLine)); 0059 0060 QPolygonF handle = QPolygonF() << QPointF(0, -3) << QPointF(3, 0) << QPointF(0, 3) << QPointF(-3, 0); 0061 0062 for (int i = 0; i <= max; ++i) { 0063 point = m_curve.getPoint(i, m_wWidth, m_wHeight, true); 0064 0065 if (i == m_currentPointIndex) { 0066 // selected point: fill p and handles 0067 p.setBrush(QBrush(QColor(Qt::red), Qt::SolidPattern)); 0068 // connect p and handles with lines 0069 if (i != 0) { 0070 p.drawLine(QLineF(point.h1.x(), point.h1.y(), point.p.x(), point.p.y())); 0071 } 0072 if (i != max) { 0073 p.drawLine(QLineF(point.p.x(), point.p.y(), point.h2.x(), point.h2.y())); 0074 } 0075 } 0076 0077 p.drawEllipse(QRectF(point.p.x() - 3, point.p.y() - 3, 6, 6)); 0078 if (i != 0 && (i == m_currentPointIndex || m_showAllHandles)) { 0079 p.drawConvexPolygon(handle.translated(point.h1.x(), point.h1.y())); 0080 } 0081 if (i != max && (i == m_currentPointIndex || m_showAllHandles)) { 0082 p.drawConvexPolygon(handle.translated(point.h2.x(), point.h2.y())); 0083 } 0084 0085 if (i == m_currentPointIndex) { 0086 p.setBrush(QBrush(Qt::NoBrush)); 0087 } 0088 } 0089 } 0090 0091 void BezierSplineEditor::mousePressEvent(QMouseEvent *event) 0092 { 0093 int wWidth = width() - 1; 0094 int wHeight = height() - 1; 0095 int offsetX = int(1 / 8. * m_zoomLevel * wWidth); 0096 int offsetY = int(1 / 8. * m_zoomLevel * wHeight); 0097 wWidth -= 2 * offsetX; 0098 wHeight -= 2 * offsetY; 0099 0100 double x = (event->pos().x() - offsetX) / double(wWidth); 0101 double y = 1.0 - (event->pos().y() - offsetY) / double(wHeight); 0102 0103 BPoint::PointType selectedPoint; 0104 int closestPointIndex = nearestPointInRange(QPointF(x, y), wWidth, wHeight, &selectedPoint); 0105 0106 if (event->button() == Qt::RightButton && closestPointIndex > 0 && closestPointIndex < m_curve.count() - 1 && selectedPoint == BPoint::PointType::P) { 0107 m_currentPointIndex = closestPointIndex; 0108 slotDeleteCurrentPoint(); 0109 return; 0110 } 0111 if (event->button() != Qt::LeftButton) { 0112 return; 0113 } 0114 0115 if (closestPointIndex < 0) { 0116 if (m_curve.count() < m_maxPoints) { 0117 m_currentPointIndex = m_curve.addPoint(QPointF(x, y)); 0118 m_currentPointType = BPoint::PointType::P; 0119 } 0120 } else { 0121 m_currentPointIndex = closestPointIndex; 0122 m_currentPointType = selectedPoint; 0123 } 0124 0125 BPoint point = m_curve.getPoint(m_currentPointIndex); 0126 0127 m_grabPOriginal = point; 0128 if (m_currentPointIndex > 0) { 0129 m_grabPPrevious = m_curve.getPoint(m_currentPointIndex - 1); 0130 } 0131 if (m_currentPointIndex < m_curve.count() - 1) { 0132 m_grabPNext = m_curve.getPoint(m_currentPointIndex + 1); 0133 } 0134 m_grabOffsetX = point[int(m_currentPointType)].x() - x; 0135 m_grabOffsetY = point[int(m_currentPointType)].y() - y; 0136 0137 point[int(m_currentPointType)] = QPointF(x + m_grabOffsetX, y + m_grabOffsetY); 0138 0139 m_curve.setPoint(m_currentPointIndex, point); 0140 0141 m_state = State_t::DRAG; 0142 0143 Q_EMIT currentPoint(point, isCurrentPointExtremal()); 0144 Q_EMIT modified(); 0145 update(); 0146 } 0147 0148 void BezierSplineEditor::mouseMoveEvent(QMouseEvent *event) 0149 { 0150 int wWidth = width() - 1; 0151 int wHeight = height() - 1; 0152 int offsetX = int(1 / 8. * m_zoomLevel * wWidth); 0153 int offsetY = int(1 / 8. * m_zoomLevel * wHeight); 0154 wWidth -= 2 * offsetX; 0155 wHeight -= 2 * offsetY; 0156 0157 double x = (event->pos().x() - offsetX) / double(wWidth); 0158 double y = 1.0 - (event->pos().y() - offsetY) / double(wHeight); 0159 0160 if (m_state == State_t::NORMAL) { 0161 // If no point is selected set the cursor shape if on top 0162 BPoint::PointType type; 0163 int nearestPointIndex = nearestPointInRange(QPointF(x, y), wWidth, wHeight, &type); 0164 0165 if (nearestPointIndex < 0) { 0166 setCursor(Qt::ArrowCursor); 0167 } else { 0168 setCursor(Qt::CrossCursor); 0169 } 0170 } else { 0171 // Else, drag the selected point 0172 setCursor(Qt::CrossCursor); 0173 0174 x += m_grabOffsetX; 0175 y += m_grabOffsetY; 0176 0177 double leftX = 0.; 0178 double rightX = 1.; 0179 BPoint point = m_curve.getPoint(m_currentPointIndex); 0180 switch (m_currentPointType) { 0181 case BPoint::PointType::H1: 0182 rightX = point.p.x(); 0183 if (m_currentPointIndex == 0) { 0184 leftX = -4; 0185 } else { 0186 leftX = m_curve.getPoint(m_currentPointIndex - 1).p.x(); 0187 } 0188 0189 x = qBound(leftX, x, rightX); 0190 point.setH1(QPointF(x, y)); 0191 break; 0192 0193 case BPoint::PointType::P: 0194 if (m_currentPointIndex == 0) { 0195 rightX = 0.0; 0196 } else if (m_currentPointIndex == m_curve.count() - 1) { 0197 leftX = 1.0; 0198 } 0199 0200 x = qBound(leftX, x, rightX); 0201 y = qBound(0., y, 1.); 0202 0203 // handles might have changed because we neared another point 0204 // try to restore 0205 point.h1 = m_grabPOriginal.h1; 0206 point.h2 = m_grabPOriginal.h2; 0207 // and move by same offset 0208 // (using update handle in point.setP won't work because the offset between new and old point is very small) 0209 point.h1 += QPointF(x, y) - m_grabPOriginal.p; 0210 point.h2 += QPointF(x, y) - m_grabPOriginal.p; 0211 0212 point.setP(QPointF(x, y), false); 0213 break; 0214 0215 case BPoint::PointType::H2: 0216 leftX = point.p.x(); 0217 if (m_currentPointIndex == m_curve.count() - 1) { 0218 rightX = 5; 0219 } else { 0220 rightX = m_curve.getPoint(m_currentPointIndex + 1).p.x(); 0221 } 0222 0223 x = qBound(leftX, x, rightX); 0224 point.setH2(QPointF(x, y)); 0225 }; 0226 0227 int index = m_currentPointIndex; 0228 m_currentPointIndex = m_curve.setPoint(m_currentPointIndex, point); 0229 0230 if (m_currentPointType == BPoint::PointType::P) { 0231 // we might have changed the handles of other points 0232 // try to restore 0233 if (index == m_currentPointIndex) { 0234 if (m_currentPointIndex > 0) { 0235 m_curve.setPoint(m_currentPointIndex - 1, m_grabPPrevious); 0236 } 0237 if (m_currentPointIndex < m_curve.count() - 1) { 0238 m_curve.setPoint(m_currentPointIndex + 1, m_grabPNext); 0239 } 0240 } else { 0241 if (m_currentPointIndex < index) { 0242 m_curve.setPoint(index, m_grabPPrevious); 0243 m_grabPNext = m_grabPPrevious; 0244 if (m_currentPointIndex > 0) { 0245 m_grabPPrevious = m_curve.getPoint(m_currentPointIndex - 1); 0246 } 0247 } else { 0248 m_curve.setPoint(index, m_grabPNext); 0249 m_grabPPrevious = m_grabPNext; 0250 if (m_currentPointIndex < m_curve.count() - 1) { 0251 m_grabPNext = m_curve.getPoint(m_currentPointIndex + 1); 0252 } 0253 } 0254 } 0255 } 0256 0257 Q_EMIT currentPoint(point, isCurrentPointExtremal()); 0258 if (KdenliveSettings::dragvalue_directupdate()) { 0259 Q_EMIT modified(); 0260 } 0261 update(); 0262 } 0263 } 0264 0265 void BezierSplineEditor::mouseDoubleClickEvent(QMouseEvent * /*event*/) 0266 { 0267 if (m_currentPointIndex >= 0) { 0268 BPoint p = m_curve.getPoint(m_currentPointIndex); 0269 p.handlesLinked = !p.handlesLinked; 0270 m_curve.setPoint(m_currentPointIndex, p); 0271 Q_EMIT currentPoint(p, isCurrentPointExtremal()); 0272 } 0273 } 0274 0275 int BezierSplineEditor::nearestPointInRange(const QPointF &p, int wWidth, int wHeight, BPoint::PointType *sel) 0276 { 0277 0278 auto nearest = m_curve.closestPoint(p); 0279 int nearestIndex = nearest.first; 0280 BPoint::PointType pointType = nearest.second; 0281 0282 if (nearestIndex >= 0 && (nearestIndex == m_currentPointIndex || pointType == BPoint::PointType::P || m_showAllHandles)) { 0283 // a point was found and it is not a hidden handle 0284 BPoint point = m_curve.getPoint(nearestIndex); 0285 double dx = (p.x() - point[int(pointType)].x()) * wWidth; 0286 double dy = (p.y() - point[int(pointType)].y()) * wHeight; 0287 if (dx * dx + dy * dy <= m_grabRadius * m_grabRadius) { 0288 *sel = pointType; 0289 return nearestIndex; 0290 } 0291 } 0292 0293 return -1; 0294 } 0295 0296 void BezierSplineEditor::setShowAllHandles(bool show) 0297 { 0298 if (m_showAllHandles != show) { 0299 m_showAllHandles = show; 0300 update(); 0301 } 0302 } 0303 0304 QList<BPoint> BezierSplineEditor::getPoints() const 0305 { 0306 return m_curve.getPoints(); 0307 }