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