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 }