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