]> git.sesse.net Git - kdenlive/blob - src/beziercurve/beziersplineeditor.cpp
Bézier spline:
[kdenlive] / src / beziercurve / beziersplineeditor.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Till Theato (root@ttill.de)                     *
3  *   This file is part of Kdenlive (www.kdenlive.org).                     *
4  *                                                                         *
5  *   Kdenlive is free software: you can redistribute it and/or modify      *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation, either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   Kdenlive is distributed in the hope that it will be useful,           *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
17  ***************************************************************************/
18
19 #include "beziersplineeditor.h"
20
21 #include <QPainter>
22 #include <QMouseEvent>
23
24 #include <KDebug>
25
26 BezierSplineEditor::BezierSplineEditor(QWidget* parent) :
27         QWidget(parent),
28         m_mode(ModeNormal),
29         m_currentPointIndex(-1)
30 {
31     setMouseTracking(true);
32     setAutoFillBackground(false);
33     setAttribute(Qt::WA_OpaquePaintEvent);
34     setMinimumSize(150, 150);
35     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
36 }
37
38 CubicBezierSpline BezierSplineEditor::spline()
39 {
40     return m_spline;
41 }
42
43 void BezierSplineEditor::setSpline(const CubicBezierSpline& spline)
44 {
45     // TODO: cleanup
46     m_spline.fromString(spline.toString());
47 }
48
49 BPoint BezierSplineEditor::getCurrentPoint()
50 {
51     if (m_currentPointIndex >= 0)
52         return m_spline.points()[m_currentPointIndex];
53     else
54         return BPoint();
55 }
56
57 void BezierSplineEditor::updateCurrentPoint(const BPoint& p)
58 {
59     if (m_currentPointIndex >= 0) {
60         m_spline.setPoint(m_currentPointIndex, p);
61         // during validation the point might have changed
62         emit currentPoint(m_spline.points()[m_currentPointIndex]);
63         update();
64     }
65 }
66
67 void BezierSplineEditor::paintEvent(QPaintEvent* event)
68 {
69     Q_UNUSED(event);
70
71     QPainter p(this);
72
73     p.fillRect(rect(), palette().background());
74
75     int    wWidth = width() - 1;
76     int    wHeight = height() - 1;
77
78     /*
79      * Spline
80      */
81     double prevY = wHeight - m_spline.value(0.) * wHeight;
82     double prevX = 0.;
83     double curY;
84     double normalizedX = -1;
85     int x;
86     
87     p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
88     for (x = 0 ; x < wWidth ; ++x) {
89         normalizedX = x / (double)wWidth;
90         curY = wHeight - m_spline.value(normalizedX, true) * wHeight;
91         
92         /*
93          * Keep in mind that QLineF rounds doubles
94          * to ints mathematically, not just rounds down
95          * like in C
96          */
97         p.drawLine(QLineF(prevX, prevY,
98                           x, curY));
99         prevX = x;
100         prevY = curY;
101     }
102     p.drawLine(QLineF(prevX, prevY ,
103                       x, wHeight - m_spline.value(1.0, true) * wHeight));
104
105     /*
106      * Points + Handles
107      */
108     p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
109     BPoint point;
110     QPolygon handle(4);
111     handle.setPoints(4,
112                      1,  -2,
113                      4,  1,
114                      1,  4,
115                      -2, 1);
116     for (int i = 0; i < m_spline.points().count(); ++i) {
117         point = m_spline.points().at(i);
118         if (i == m_currentPointIndex)
119             p.setBrush(QBrush(QColor(Qt::red), Qt::SolidPattern));
120
121         p.drawConvexPolygon(handle.translated(point.h1.x() * wWidth, wHeight - point.h1.y() * wHeight));
122         p.drawEllipse(QRectF(point.p.x() * wWidth - 3,
123                             wHeight - 3 - point.p.y() * wHeight, 6, 6));
124         p.drawConvexPolygon(handle.translated(point.h2.x() * wWidth, wHeight - point.h2.y() * wHeight));
125
126         if ( i == m_currentPointIndex)
127             p.setBrush(QBrush(Qt::NoBrush));
128     }
129 }
130
131 void BezierSplineEditor::resizeEvent(QResizeEvent* event)
132 {
133     m_spline.setPrecision(width());
134     QWidget::resizeEvent(event);
135 }
136
137 void BezierSplineEditor::mousePressEvent(QMouseEvent* event)
138 {
139     double x = event->pos().x() / (double)(width() - 1);
140     double y = 1.0 - event->pos().y() / (double)(height() - 1);
141
142     point_types selectedPoint;
143     int closestPointIndex = nearestPointInRange(QPointF(x, y), width(), height(), &selectedPoint);
144
145     if (event->button() == Qt::RightButton && closestPointIndex > 0 && closestPointIndex < m_spline.points().count() - 1 && selectedPoint == PTypeP) {
146         m_spline.removePoint(closestPointIndex);
147         setCursor(Qt::ArrowCursor);
148         m_mode = ModeNormal;
149         if (closestPointIndex < m_currentPointIndex)
150             --m_currentPointIndex;
151         update();
152         if (m_currentPointIndex >= 0)
153             emit currentPoint(m_spline.points()[m_currentPointIndex]);
154         else
155             emit currentPoint(BPoint());
156         emit modified();
157         return;
158     } else if (event->button() != Qt::LeftButton) {
159         return;
160     }
161
162     if (closestPointIndex < 0) {
163         BPoint po;
164         po.p = QPointF(x, y);
165         po.h1 = QPointF(x-0.05, y-0.05);
166         po.h2 = QPointF(x+0.05, y+0.05);
167         m_currentPointIndex = m_spline.addPoint(po);
168         m_currentPointType = PTypeP;
169         /*if (!d->jumpOverExistingPoints(newPoint, -1)) return;*/
170     } else {
171         m_currentPointIndex = closestPointIndex;
172         m_currentPointType = selectedPoint;
173     }
174
175     BPoint point = m_spline.points()[m_currentPointIndex];
176     QPointF p;
177     switch (m_currentPointType) {
178     case PTypeH1:
179         p = point.h1;
180         break;
181     case PTypeP:
182         p = point.p;
183         break;
184     case PTypeH2:
185         p = point.h2;
186     }
187
188     m_grabOriginalX = p.x();
189     m_grabOriginalY = p.y();
190     m_grabOffsetX = p.x() - x;
191     m_grabOffsetY = p.y() - y;
192
193     switch (m_currentPointType) {
194         case PTypeH1:
195             point.h1 = QPointF(x + m_grabOffsetX, y + m_grabOffsetY);
196             break;
197         case PTypeP:
198             point.p = QPointF(x + m_grabOffsetX, y + m_grabOffsetY);
199             break;
200         case PTypeH2:
201             point.h2 = QPointF(x + m_grabOffsetX, y + m_grabOffsetY);
202     }
203     m_spline.setPoint(m_currentPointIndex, point);
204
205     //d->m_draggedAwayPointIndex = -1;
206
207     m_mode = ModeDrag;
208
209     emit currentPoint(point);
210     update();
211 }
212
213 void BezierSplineEditor::mouseReleaseEvent(QMouseEvent* event)
214 {
215     if (event->button() != Qt::LeftButton)
216         return;
217
218     setCursor(Qt::ArrowCursor);
219     m_mode = ModeNormal;
220
221     emit modified();
222 }
223
224 void BezierSplineEditor::mouseMoveEvent(QMouseEvent* event)
225 {
226     double x = event->pos().x() / (double)(width() - 1);
227     double y = 1.0 - event->pos().y() / (double)(height() - 1);
228     
229     if (m_mode == ModeNormal) { // If no point is selected set the the cursor shape if on top
230         point_types type;
231         int nearestPointIndex = nearestPointInRange(QPointF(x, y), width(), height(), &type);
232         
233         if (nearestPointIndex < 0)
234             setCursor(Qt::ArrowCursor);
235         else
236             setCursor(Qt::CrossCursor);
237     } else { // Else, drag the selected point
238         /*bool crossedHoriz = event->pos().x() - width() > MOUSE_AWAY_THRES ||
239         event->pos().x() < -MOUSE_AWAY_THRES;
240         bool crossedVert =  event->pos().y() - height() > MOUSE_AWAY_THRES ||
241         event->pos().y() < -MOUSE_AWAY_THRES;
242         
243         bool removePoint = (crossedHoriz || crossedVert);
244         
245         if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
246             // point is no longer dragged away so reinsert it
247             QPointF newPoint(d->m_draggedAwayPoint);
248             d->m_grab_point_index = d->m_curve.addPoint(newPoint);
249             d->m_draggedAwayPointIndex = -1;
250         }
251         
252         if (removePoint &&
253             (d->m_draggedAwayPointIndex >= 0))
254             return;
255         */
256         
257         setCursor(Qt::CrossCursor);
258         
259         x += m_grabOffsetX;
260         y += m_grabOffsetY;
261         
262         double leftX, rightX;
263         BPoint point = m_spline.points()[m_currentPointIndex];
264         switch (m_currentPointType) {
265         case PTypeH1:
266             rightX = point.p.x();
267             if (m_currentPointIndex == 0)
268                 leftX = -1000;
269             else
270                 leftX = m_spline.points()[m_currentPointIndex - 1].p.x();
271             x = qBound(leftX, x, rightX);
272             point.h1 = QPointF(x, y);
273             break;
274         case PTypeP:
275             if (m_currentPointIndex == 0) {
276                 leftX = 0.0;
277                 rightX = 0.0;
278                 /*if (d->m_curve.points().count() > 1)
279                  *           rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
280                  *       else
281                  *           rightX = 1.0;*/
282             } else if (m_currentPointIndex == m_spline.points().count() - 1) {
283                 leftX = 1.0;//m_spline.points()[m_currentPointIndex - 1].p.x();
284                 rightX = 1.0;
285             } else {
286                 //// the 1E-4 addition so we can grab the dot later.
287                 leftX = m_spline.points()[m_currentPointIndex - 1].p.x();// + POINT_AREA;
288                 rightX = m_spline.points()[m_currentPointIndex + 1].p.x();// - POINT_AREA;
289             }
290             x = qBound(leftX, x, rightX);
291             y = qBound(0., y, 1.);
292
293             // move handles by same offset
294             point.h1 += QPointF(x, y) - point.p;
295             point.h2 += QPointF(x, y) - point.p;
296
297             point.p = QPointF(x, y);
298             break;
299         case PTypeH2:
300             leftX = point.p.x();
301             if (m_currentPointIndex == m_spline.points().count() - 1)
302                 rightX = 1001;
303             else
304                 rightX = m_spline.points()[m_currentPointIndex + 1].p.x();
305             x = qBound(leftX, x, rightX);
306             point.h2 = QPointF(x, y);
307         };
308
309         m_spline.setPoint(m_currentPointIndex, point);
310         
311         /*if (removePoint && d->m_curve.points().count() > 2) {
312             d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
313             d->m_draggedAwayPointIndex = d->m_grab_point_index;
314             d->m_curve.removePoint(d->m_grab_point_index);
315             d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
316         }
317         
318         d->setCurveModified();*/
319         emit currentPoint(point);
320         update();
321     }
322 }
323
324 void BezierSplineEditor::leaveEvent(QEvent* event)
325 {
326     QWidget::leaveEvent(event);
327 }
328
329 int BezierSplineEditor::nearestPointInRange(QPointF p, int wWidth, int wHeight, BezierSplineEditor::point_types* sel)
330 {
331     double nearestDistanceSquared = 1000;
332     point_types selectedPoint;
333     int nearestIndex = -1;
334     int i = 0;
335
336     double distanceSquared;
337     foreach(const BPoint & point, m_spline.points()) {
338         distanceSquared = pow(point.h1.x() - p.x(), 2) + pow(point.h1.y() - p.y(), 2);
339         if (distanceSquared < nearestDistanceSquared) {
340             nearestIndex = i;
341             nearestDistanceSquared = distanceSquared;
342             selectedPoint = PTypeH1;
343         }
344         distanceSquared = pow(point.p.x() - p.x(), 2) + pow(point.p.y() - p.y(), 2);
345         if (distanceSquared < nearestDistanceSquared) {
346             nearestIndex = i;
347             nearestDistanceSquared = distanceSquared;
348             selectedPoint = PTypeP;
349         }
350         distanceSquared = pow(point.h2.x() - p.x(), 2) + pow(point.h2.y() - p.y(), 2);
351         if (distanceSquared < nearestDistanceSquared) {
352             nearestIndex = i;
353             nearestDistanceSquared = distanceSquared;
354             selectedPoint = PTypeH2;
355         }
356         ++i;
357     }
358
359     if (nearestIndex >= 0) {
360         BPoint point = m_spline.points()[nearestIndex];
361         QPointF p2;
362         switch (selectedPoint) {
363         case PTypeH1:
364             p2 = point.h1;
365             break;
366         case PTypeP:
367             p2 = point.p;
368             break;
369         case PTypeH2:
370             p2 = point.h2;
371         }
372         if (qAbs(p.x() - p2.x()) * (wWidth - 1) < 5 && qAbs(p.y() - p2.y()) * (wHeight - 1) < 5) {
373             *sel = selectedPoint;
374             return nearestIndex;
375         }
376     }
377
378     return -1;
379 }
380
381 #include "beziersplineeditor.moc"