]> git.sesse.net Git - kdenlive/blob - src/kis_curve_widget.cpp
- Increase maximum size of curve widget
[kdenlive] / src / kis_curve_widget.cpp
1 /*
2  *  Copyright (c) 2005 Casper Boemann <cbr@boemann.dk>
3  *  Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
4  *
5  *  This program 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  *  This program 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 this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20
21 // C++ includes.
22
23 #include <cmath>
24 #include <cstdlib>
25
26 // Qt includes.
27
28 #include <QPixmap>
29 #include <QPainter>
30 #include <QPoint>
31 #include <QPen>
32 #include <QEvent>
33 #include <QRect>
34 #include <QFont>
35 #include <QFontMetrics>
36 #include <QMouseEvent>
37 #include <QKeyEvent>
38 #include <QPaintEvent>
39 #include <QList>
40
41 #include <QSpinBox>
42
43 // KDE includes.
44
45 #include <kcursor.h>
46 #include <klocale.h>
47
48 // Local includes.
49
50 #include "kis_curve_widget.h"
51
52
53 #define bounds(x,a,b) (x<a ? a : (x>b ? b :x))
54 #define MOUSE_AWAY_THRES 15
55 #define POINT_AREA       1E-4
56 #define CURVE_AREA       1E-4
57
58 #include "kis_curve_widget_p.h"
59
60
61 //static bool pointLessThan(const QPointF &a, const QPointF &b);
62
63
64 KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WFlags f)
65         : QWidget(parent, f), d(new KisCurveWidget::Private(this))
66 {
67     setObjectName("KisCurveWidget");
68     d->m_grab_point_index = -1;
69     d->m_readOnlyMode   = false;
70     d->m_guideVisible   = false;
71     d->m_pixmapDirty = true;
72     d->m_pixmapCache = NULL;
73     d->setState(ST_NORMAL);
74
75     d->m_intIn = NULL;
76     d->m_intOut = NULL;
77     
78     d->m_maxPoints = -1;
79
80     setMouseTracking(true);
81     setAutoFillBackground(false);
82     setAttribute(Qt::WA_OpaquePaintEvent);
83     setMinimumSize(150, 150);
84     setMaximumSize(350, 350);
85
86     d->setCurveModified();
87
88     setFocusPolicy(Qt::StrongFocus);
89 }
90
91 KisCurveWidget::~KisCurveWidget()
92 {
93     if (d->m_pixmapCache)
94         delete d->m_pixmapCache;
95     delete d;
96 }
97
98 void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
99 {
100     d->m_intIn = in;
101     d->m_intOut = out;
102
103     if (!d->m_intIn || !d->m_intOut)
104         return;
105
106     d->m_inOutMin = min;
107     d->m_inOutMax = max;
108
109     d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
110     d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
111
112
113     connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
114     connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
115     d->syncIOControls();
116
117 }
118 void KisCurveWidget::dropInOutControls()
119 {
120     if (!d->m_intIn || !d->m_intOut)
121         return;
122
123     disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
124     disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
125
126     d->m_intIn = d->m_intOut = NULL;
127
128 }
129
130 void KisCurveWidget::inOutChanged(int)
131 {
132     QPointF pt;
133
134     Q_ASSERT(d->m_grab_point_index >= 0);
135
136     pt.setX(d->io2sp(d->m_intIn->value()));
137     pt.setY(d->io2sp(d->m_intOut->value()));
138
139     if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) {
140         d->m_curve.setPoint(d->m_grab_point_index, pt);
141         d->m_grab_point_index = d->m_curve.points().indexOf(pt);
142     } else
143         pt = d->m_curve.points()[d->m_grab_point_index];
144
145
146     d->m_intIn->blockSignals(true);
147     d->m_intOut->blockSignals(true);
148
149     d->m_intIn->setValue(d->sp2io(pt.x()));
150     d->m_intOut->setValue(d->sp2io(pt.y()));
151
152     d->m_intIn->blockSignals(false);
153     d->m_intOut->blockSignals(false);
154
155     d->setCurveModified();
156 }
157
158
159 void KisCurveWidget::reset(void)
160 {
161     d->m_grab_point_index = -1;
162     d->m_guideVisible = false;
163
164     d->setCurveModified();
165 }
166
167 void KisCurveWidget::setCurveGuide(const QColor & color)
168 {
169     d->m_guideVisible = true;
170     d->m_colorGuide   = color;
171
172 }
173
174 void KisCurveWidget::setPixmap(const QPixmap & pix)
175 {
176     d->m_pix = pix;
177     d->m_pixmapDirty = true;
178     d->setCurveRepaint();
179 }
180
181 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
182 {
183     if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
184         if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) {
185             //x() find closest point to get focus afterwards
186             double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x();
187
188             int left_of_grab_point_index = d->m_grab_point_index - 1;
189             int right_of_grab_point_index = d->m_grab_point_index + 1;
190             int new_grab_point_index;
191
192             if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) <
193                     fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) {
194                 new_grab_point_index = left_of_grab_point_index;
195             } else {
196                 new_grab_point_index = d->m_grab_point_index;
197             }
198             d->m_curve.removePoint(d->m_grab_point_index);
199             d->m_grab_point_index = new_grab_point_index;
200             setCursor(Qt::ArrowCursor);
201             d->setState(ST_NORMAL);
202         }
203         d->setCurveModified();
204     } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) {
205         d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) );
206         setCursor(Qt::ArrowCursor);
207         d->setState(ST_NORMAL);
208
209         d->setCurveModified();
210     } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) {
211         /* FIXME: Lets user choose the hotkeys */
212         addPointInTheMiddle();
213     } else
214         QWidget::keyPressEvent(e);
215 }
216
217 void KisCurveWidget::addPointInTheMiddle()
218 {
219     QPointF pt(0.5, d->m_curve.value(0.5));
220
221     if (!d->jumpOverExistingPoints(pt, -1))
222         return;
223
224     d->m_grab_point_index = d->m_curve.addPoint(pt);
225
226     if (d->m_intIn)
227         d->m_intIn->setFocus(Qt::TabFocusReason);
228     d->setCurveModified();
229 }
230
231 void KisCurveWidget::resizeEvent(QResizeEvent *e)
232 {
233     d->m_pixmapDirty = true;
234     QWidget::resizeEvent(e);
235 }
236
237 void KisCurveWidget::paintEvent(QPaintEvent *)
238 {
239     int    wWidth = width() - 1;
240     int    wHeight = height() - 1;
241
242     QPainter p(this);
243
244     // Antialiasing is not a good idea here, because
245     // the grid will drift one pixel to any side due to rounding of int
246     // FIXME: let's user tell the last word (in config)
247     //p.setRenderHint(QPainter::Antialiasing);
248
249
250     //  draw background
251     if (!d->m_pix.isNull()) {
252         if (d->m_pixmapDirty || !d->m_pixmapCache) {
253             if (d->m_pixmapCache)
254                 delete d->m_pixmapCache;
255             d->m_pixmapCache = new QPixmap(width(), height());
256             QPainter cachePainter(d->m_pixmapCache);
257
258             cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height());
259             cachePainter.drawPixmap(0, 0, d->m_pix);
260             d->m_pixmapDirty = false;
261         }
262         p.drawPixmap(0, 0, *d->m_pixmapCache);
263     } else
264         p.fillRect(rect(), palette().background());
265
266
267     d->drawGrid(p, wWidth, wHeight);
268
269     /*KisConfig cfg;
270     if (cfg.antialiasCurves())
271         p.setRenderHint(QPainter::Antialiasing);*/
272
273     // Draw curve.
274     double prevY = wHeight - d->m_curve.value(0.) * wHeight;
275     double prevX = 0.;
276     double curY;
277     double normalizedX;
278     int x;
279
280     p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
281     for (x = 0 ; x < wWidth ; x++) {
282         normalizedX = double(x) / wWidth;
283         curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
284
285         /**
286          * Keep in mind that QLineF rounds doubles
287          * to ints mathematically, not just rounds down
288          * like in C
289          */
290         p.drawLine(QLineF(prevX, prevY,
291                           x, curY));
292         prevX = x;
293         prevY = curY;
294     }
295     p.drawLine(QLineF(prevX, prevY ,
296                       x, wHeight - d->m_curve.value(1.0) * wHeight));
297
298     // Drawing curve handles.
299     double curveX;
300     double curveY;
301     if (!d->m_readOnlyMode) {
302         for (int i = 0; i < d->m_curve.points().count(); ++i) {
303             curveX = d->m_curve.points().at(i).x();
304             curveY = d->m_curve.points().at(i).y();
305
306             if (i == d->m_grab_point_index) {
307                 p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
308                 p.drawEllipse(QRectF(curveX * wWidth - 2,
309                                      wHeight - 2 - curveY * wHeight, 4, 4));
310             } else {
311                 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
312                 p.drawEllipse(QRectF(curveX * wWidth - 3,
313                                      wHeight - 3 - curveY * wHeight, 6, 6));
314             }
315         }
316     }
317 }
318
319 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
320 {
321     if (d->m_readOnlyMode) return;
322     
323     double x = e->pos().x() / (double)(width() - 1);
324     double y = 1.0 - e->pos().y() / (double)(height() - 1);
325
326
327
328     int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
329     
330     if (e->button() == Qt::RightButton && closest_point_index > 0 && closest_point_index < d->m_curve.points().count() - 1) {
331         d->m_curve.removePoint(closest_point_index);
332         setCursor(Qt::ArrowCursor);
333         d->setState(ST_NORMAL);
334         if (closest_point_index < d->m_grab_point_index)
335             --d->m_grab_point_index;
336         d->setCurveModified();
337         return;
338     } else if (e->button() != Qt::LeftButton) return;
339     
340     if (closest_point_index < 0) {
341         if (d->m_maxPoints > 0 && d->m_curve.points().count() >= d->m_maxPoints)
342             return;
343         QPointF newPoint(x, y);
344         if (!d->jumpOverExistingPoints(newPoint, -1))
345             return;
346         d->m_grab_point_index = d->m_curve.addPoint(newPoint);
347     } else {
348         d->m_grab_point_index = closest_point_index;
349     }
350
351     d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
352     d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
353     d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
354     d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
355     d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
356
357     d->m_draggedAwayPointIndex = -1;
358     d->setState(ST_DRAG);
359
360
361     d->setCurveModified();
362 }
363
364
365 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
366 {
367     if (d->m_readOnlyMode) return;
368
369     if (e->button() != Qt::LeftButton)
370         return;
371
372     setCursor(Qt::ArrowCursor);
373     d->setState(ST_NORMAL);
374
375     d->setCurveModified();
376 }
377
378
379 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
380 {
381     if (d->m_readOnlyMode) return;
382
383     double x = e->pos().x() / (double)(width() - 1);
384     double y = 1.0 - e->pos().y() / (double)(height() - 1);
385
386     if (d->state() == ST_NORMAL) { // If no point is selected set the the cursor shape if on top
387         int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
388
389         if (nearestPointIndex < 0)
390             setCursor(Qt::ArrowCursor);
391         else
392             setCursor(Qt::CrossCursor);
393     } else { // Else, drag the selected point
394         bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
395                             e->pos().x() < -MOUSE_AWAY_THRES;
396         bool crossedVert =  e->pos().y() - height() > MOUSE_AWAY_THRES ||
397                             e->pos().y() < -MOUSE_AWAY_THRES;
398
399         bool removePoint = (crossedHoriz || crossedVert);
400
401         if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
402             // point is no longer dragged away so reinsert it
403             QPointF newPoint(d->m_draggedAwayPoint);
404             d->m_grab_point_index = d->m_curve.addPoint(newPoint);
405             d->m_draggedAwayPointIndex = -1;
406         }
407
408         if (removePoint &&
409                 (d->m_draggedAwayPointIndex >= 0))
410             return;
411
412
413         setCursor(Qt::CrossCursor);
414
415         x += d->m_grabOffsetX;
416         y += d->m_grabOffsetY;
417
418         double leftX;
419         double rightX;
420         if (d->m_grab_point_index == 0) {
421             leftX = 0.0;
422             if (d->m_curve.points().count() > 1)
423                 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
424             else
425                 rightX = 1.0;
426         } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
427             leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
428             rightX = 1.0;
429         } else {
430             Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
431
432             // the 1E-4 addition so we can grab the dot later.
433             leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
434             rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
435         }
436
437         x = bounds(x, leftX, rightX);
438         y = bounds(y, 0., 1.);
439
440         d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
441
442         if (removePoint && d->m_curve.points().count() > 2) {
443             d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
444             d->m_draggedAwayPointIndex = d->m_grab_point_index;
445             d->m_curve.removePoint(d->m_grab_point_index);
446             d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
447         }
448
449         d->setCurveModified();
450     }
451 }
452
453 KisCubicCurve KisCurveWidget::curve()
454 {
455     return d->m_curve;
456 }
457
458 void KisCurveWidget::setCurve(KisCubicCurve inlist)
459 {
460     d->m_curve = inlist;
461     d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
462     d->setCurveModified();
463 }
464
465 void KisCurveWidget::leaveEvent(QEvent *)
466 {
467 }
468
469 void KisCurveWidget::setMaxPoints(int max)
470 {
471     d->m_maxPoints = max;
472 }
473
474 #include "kis_curve_widget.moc"