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(1000, 1000);
85 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
87 d->setCurveModified();
89 setFocusPolicy(Qt::StrongFocus);
92 KisCurveWidget::~KisCurveWidget()
95 delete d->m_pixmapCache;
99 QSize KisCurveWidget::sizeHint() const
101 return QSize(500, 500);
104 void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
109 if (!d->m_intIn || !d->m_intOut)
115 d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
116 d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
119 connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
120 connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
124 void KisCurveWidget::dropInOutControls()
126 if (!d->m_intIn || !d->m_intOut)
129 disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
130 disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
132 d->m_intIn = d->m_intOut = NULL;
136 void KisCurveWidget::inOutChanged(int)
140 Q_ASSERT(d->m_grab_point_index >= 0);
142 pt.setX(d->io2sp(d->m_intIn->value()));
143 pt.setY(d->io2sp(d->m_intOut->value()));
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);
149 pt = d->m_curve.points()[d->m_grab_point_index];
152 d->m_intIn->blockSignals(true);
153 d->m_intOut->blockSignals(true);
155 d->m_intIn->setValue(d->sp2io(pt.x()));
156 d->m_intOut->setValue(d->sp2io(pt.y()));
158 d->m_intIn->blockSignals(false);
159 d->m_intOut->blockSignals(false);
161 d->setCurveModified();
165 void KisCurveWidget::reset(void)
167 d->m_grab_point_index = -1;
168 d->m_guideVisible = false;
170 d->setCurveModified();
173 void KisCurveWidget::setCurveGuide(const QColor & color)
175 d->m_guideVisible = true;
176 d->m_colorGuide = color;
179 void KisCurveWidget::setPixmap(const QPixmap & pix)
182 d->m_pixmapDirty = true;
183 d->setCurveRepaint();
186 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
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();
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;
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;
201 new_grab_point_index = d->m_grab_point_index;
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);
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);
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();
219 QWidget::keyPressEvent(e);
222 void KisCurveWidget::addPointInTheMiddle()
224 QPointF pt(0.5, d->m_curve.value(0.5));
226 if (!d->jumpOverExistingPoints(pt, -1))
229 d->m_grab_point_index = d->m_curve.addPoint(pt);
232 d->m_intIn->setFocus(Qt::TabFocusReason);
233 d->setCurveModified();
236 void KisCurveWidget::resizeEvent(QResizeEvent *e)
238 d->m_pixmapDirty = true;
239 QWidget::resizeEvent(e);
242 void KisCurveWidget::paintEvent(QPaintEvent *)
244 int wWidth = width() - 1;
245 int wHeight = height() - 1;
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);
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);
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;
267 p.drawPixmap(0, 0, *d->m_pixmapCache);
269 p.fillRect(rect(), palette().background());
272 d->drawGrid(p, wWidth, wHeight);
275 if (cfg.antialiasCurves())
276 p.setRenderHint(QPainter::Antialiasing);*/
277 p.setRenderHint(QPainter::Antialiasing);
280 p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
281 p.drawLine(QLineF(0, wHeight, wWidth, 0));
284 double prevY = wHeight - d->m_curve.value(0.) * wHeight;
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;
296 * Keep in mind that QLineF rounds doubles
297 * to ints mathematically, not just rounds down
300 p.drawLine(QLineF(prevX, prevY,
305 p.drawLine(QLineF(prevX, prevY ,
306 x, wHeight - d->m_curve.value(1.0) * wHeight));
308 // Drawing curve handles.
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();
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));
321 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
322 p.drawEllipse(QRectF(curveX * wWidth - 3,
323 wHeight - 3 - curveY * wHeight, 6, 6));
329 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
331 if (d->m_readOnlyMode) return;
333 double x = e->pos().x() / (double)(width() - 1);
334 double y = 1.0 - e->pos().y() / (double)(height() - 1);
338 int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
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();
348 } else if (e->button() != Qt::LeftButton) return;
350 if (closest_point_index < 0) {
351 if (d->m_maxPoints > 0 && d->m_curve.points().count() >= d->m_maxPoints)
353 QPointF newPoint(x, y);
354 if (!d->jumpOverExistingPoints(newPoint, -1))
356 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
358 d->m_grab_point_index = closest_point_index;
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));
367 d->m_draggedAwayPointIndex = -1;
368 d->setState(ST_DRAG);
371 d->setCurveModified();
375 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
377 if (d->m_readOnlyMode) return;
379 if (e->button() != Qt::LeftButton)
382 setCursor(Qt::ArrowCursor);
383 d->setState(ST_NORMAL);
385 d->setCurveModified();
389 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
391 if (d->m_readOnlyMode) return;
393 double x = e->pos().x() / (double)(width() - 1);
394 double y = 1.0 - e->pos().y() / (double)(height() - 1);
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());
399 if (nearestPointIndex < 0)
400 setCursor(Qt::ArrowCursor);
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;
409 bool removePoint = (crossedHoriz || crossedVert);
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;
419 (d->m_draggedAwayPointIndex >= 0))
423 setCursor(Qt::CrossCursor);
425 x += d->m_grabOffsetX;
426 y += d->m_grabOffsetY;
430 if (d->m_grab_point_index == 0) {
433 /*if (d->m_curve.points().count() > 1)
434 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
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;
441 Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
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;
448 x = bounds(x, leftX, rightX);
449 y = bounds(y, 0., 1.);
451 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
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);
460 d->setCurveModified();
464 KisCubicCurve KisCurveWidget::curve()
469 void KisCurveWidget::setCurve(KisCubicCurve inlist)
472 d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
473 d->setCurveModified();
476 void KisCurveWidget::leaveEvent(QEvent *)
480 void KisCurveWidget::setMaxPoints(int max)
482 d->m_maxPoints = max;
485 #include "kis_curve_widget.moc"