2 * Copyright (c) 2005 Casper Boemann <cbr@boemann.dk>
3 * Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
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.
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.
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.
35 #include <QFontMetrics>
36 #include <QMouseEvent>
38 #include <QPaintEvent>
50 #include "kis_curve_widget.h"
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
58 #include "kis_curve_widget_p.h"
61 //static bool pointLessThan(const QPointF &a, const QPointF &b);
64 KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WFlags f)
65 : QWidget(parent, f), d(new KisCurveWidget::Private(this))
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);
80 setMouseTracking(true);
81 setAutoFillBackground(false);
82 setAttribute(Qt::WA_OpaquePaintEvent);
83 setMinimumSize(150, 150);
84 setMaximumSize(350, 350);
86 d->setCurveModified();
88 setFocusPolicy(Qt::StrongFocus);
91 KisCurveWidget::~KisCurveWidget()
94 delete d->m_pixmapCache;
98 void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
103 if (!d->m_intIn || !d->m_intOut)
109 d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
110 d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
113 connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
114 connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
118 void KisCurveWidget::dropInOutControls()
120 if (!d->m_intIn || !d->m_intOut)
123 disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
124 disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
126 d->m_intIn = d->m_intOut = NULL;
130 void KisCurveWidget::inOutChanged(int)
134 Q_ASSERT(d->m_grab_point_index >= 0);
136 pt.setX(d->io2sp(d->m_intIn->value()));
137 pt.setY(d->io2sp(d->m_intOut->value()));
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);
143 pt = d->m_curve.points()[d->m_grab_point_index];
146 d->m_intIn->blockSignals(true);
147 d->m_intOut->blockSignals(true);
149 d->m_intIn->setValue(d->sp2io(pt.x()));
150 d->m_intOut->setValue(d->sp2io(pt.y()));
152 d->m_intIn->blockSignals(false);
153 d->m_intOut->blockSignals(false);
155 d->setCurveModified();
159 void KisCurveWidget::reset(void)
161 d->m_grab_point_index = -1;
162 d->m_guideVisible = false;
164 d->setCurveModified();
167 void KisCurveWidget::setCurveGuide(const QColor & color)
169 d->m_guideVisible = true;
170 d->m_colorGuide = color;
174 void KisCurveWidget::setPixmap(const QPixmap & pix)
177 d->m_pixmapDirty = true;
178 d->setCurveRepaint();
181 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
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();
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;
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;
196 new_grab_point_index = d->m_grab_point_index;
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);
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);
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();
214 QWidget::keyPressEvent(e);
217 void KisCurveWidget::addPointInTheMiddle()
219 QPointF pt(0.5, d->m_curve.value(0.5));
221 if (!d->jumpOverExistingPoints(pt, -1))
224 d->m_grab_point_index = d->m_curve.addPoint(pt);
227 d->m_intIn->setFocus(Qt::TabFocusReason);
228 d->setCurveModified();
231 void KisCurveWidget::resizeEvent(QResizeEvent *e)
233 d->m_pixmapDirty = true;
234 QWidget::resizeEvent(e);
237 void KisCurveWidget::paintEvent(QPaintEvent *)
239 int wWidth = width() - 1;
240 int wHeight = height() - 1;
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);
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);
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;
262 p.drawPixmap(0, 0, *d->m_pixmapCache);
264 p.fillRect(rect(), palette().background());
267 d->drawGrid(p, wWidth, wHeight);
270 if (cfg.antialiasCurves())
271 p.setRenderHint(QPainter::Antialiasing);*/
274 p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
275 p.drawLine(QLineF(0, wHeight, wWidth, 0));
278 double prevY = wHeight - d->m_curve.value(0.) * wHeight;
284 p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
285 for (x = 0 ; x < wWidth ; x++) {
286 normalizedX = double(x) / wWidth;
287 curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
290 * Keep in mind that QLineF rounds doubles
291 * to ints mathematically, not just rounds down
294 p.drawLine(QLineF(prevX, prevY,
299 p.drawLine(QLineF(prevX, prevY ,
300 x, wHeight - d->m_curve.value(1.0) * wHeight));
302 // Drawing curve handles.
305 if (!d->m_readOnlyMode) {
306 for (int i = 0; i < d->m_curve.points().count(); ++i) {
307 curveX = d->m_curve.points().at(i).x();
308 curveY = d->m_curve.points().at(i).y();
310 if (i == d->m_grab_point_index) {
311 p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
312 p.drawEllipse(QRectF(curveX * wWidth - 2,
313 wHeight - 2 - curveY * wHeight, 4, 4));
315 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
316 p.drawEllipse(QRectF(curveX * wWidth - 3,
317 wHeight - 3 - curveY * wHeight, 6, 6));
323 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
325 if (d->m_readOnlyMode) return;
327 double x = e->pos().x() / (double)(width() - 1);
328 double y = 1.0 - e->pos().y() / (double)(height() - 1);
332 int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
334 if (e->button() == Qt::RightButton && closest_point_index > 0 && closest_point_index < d->m_curve.points().count() - 1) {
335 d->m_curve.removePoint(closest_point_index);
336 setCursor(Qt::ArrowCursor);
337 d->setState(ST_NORMAL);
338 if (closest_point_index < d->m_grab_point_index)
339 --d->m_grab_point_index;
340 d->setCurveModified();
342 } else if (e->button() != Qt::LeftButton) return;
344 if (closest_point_index < 0) {
345 if (d->m_maxPoints > 0 && d->m_curve.points().count() >= d->m_maxPoints)
347 QPointF newPoint(x, y);
348 if (!d->jumpOverExistingPoints(newPoint, -1))
350 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
352 d->m_grab_point_index = closest_point_index;
355 d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
356 d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
357 d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
358 d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
359 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
361 d->m_draggedAwayPointIndex = -1;
362 d->setState(ST_DRAG);
365 d->setCurveModified();
369 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
371 if (d->m_readOnlyMode) return;
373 if (e->button() != Qt::LeftButton)
376 setCursor(Qt::ArrowCursor);
377 d->setState(ST_NORMAL);
379 d->setCurveModified();
383 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
385 if (d->m_readOnlyMode) return;
387 double x = e->pos().x() / (double)(width() - 1);
388 double y = 1.0 - e->pos().y() / (double)(height() - 1);
390 if (d->state() == ST_NORMAL) { // If no point is selected set the the cursor shape if on top
391 int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
393 if (nearestPointIndex < 0)
394 setCursor(Qt::ArrowCursor);
396 setCursor(Qt::CrossCursor);
397 } else { // Else, drag the selected point
398 bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
399 e->pos().x() < -MOUSE_AWAY_THRES;
400 bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES ||
401 e->pos().y() < -MOUSE_AWAY_THRES;
403 bool removePoint = (crossedHoriz || crossedVert);
405 if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
406 // point is no longer dragged away so reinsert it
407 QPointF newPoint(d->m_draggedAwayPoint);
408 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
409 d->m_draggedAwayPointIndex = -1;
413 (d->m_draggedAwayPointIndex >= 0))
417 setCursor(Qt::CrossCursor);
419 x += d->m_grabOffsetX;
420 y += d->m_grabOffsetY;
424 if (d->m_grab_point_index == 0) {
426 if (d->m_curve.points().count() > 1)
427 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
430 } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
431 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
434 Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
436 // the 1E-4 addition so we can grab the dot later.
437 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
438 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
441 x = bounds(x, leftX, rightX);
442 y = bounds(y, 0., 1.);
444 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
446 if (removePoint && d->m_curve.points().count() > 2) {
447 d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
448 d->m_draggedAwayPointIndex = d->m_grab_point_index;
449 d->m_curve.removePoint(d->m_grab_point_index);
450 d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
453 d->setCurveModified();
457 KisCubicCurve KisCurveWidget::curve()
462 void KisCurveWidget::setCurve(KisCubicCurve inlist)
465 d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
466 d->setCurveModified();
469 void KisCurveWidget::leaveEvent(QEvent *)
473 void KisCurveWidget::setMaxPoints(int max)
475 d->m_maxPoints = max;
478 #include "kis_curve_widget.moc"