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;
173 void KisCurveWidget::setPixmap(const QPixmap & pix)
176 d->m_pixmapDirty = true;
177 d->setCurveRepaint();
180 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
182 if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
183 if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) {
184 //x() find closest point to get focus afterwards
185 double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x();
187 int left_of_grab_point_index = d->m_grab_point_index - 1;
188 int right_of_grab_point_index = d->m_grab_point_index + 1;
189 int new_grab_point_index;
191 if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) <
192 fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) {
193 new_grab_point_index = left_of_grab_point_index;
195 new_grab_point_index = d->m_grab_point_index;
197 d->m_curve.removePoint(d->m_grab_point_index);
198 d->m_grab_point_index = new_grab_point_index;
199 setCursor(Qt::ArrowCursor);
200 d->setState(ST_NORMAL);
202 d->setCurveModified();
203 } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) {
204 d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY));
205 setCursor(Qt::ArrowCursor);
206 d->setState(ST_NORMAL);
208 d->setCurveModified();
209 } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) {
210 /* FIXME: Lets user choose the hotkeys */
211 addPointInTheMiddle();
213 QWidget::keyPressEvent(e);
216 void KisCurveWidget::addPointInTheMiddle()
218 QPointF pt(0.5, d->m_curve.value(0.5));
220 if (!d->jumpOverExistingPoints(pt, -1))
223 d->m_grab_point_index = d->m_curve.addPoint(pt);
226 d->m_intIn->setFocus(Qt::TabFocusReason);
227 d->setCurveModified();
230 void KisCurveWidget::resizeEvent(QResizeEvent *e)
232 d->m_pixmapDirty = true;
233 QWidget::resizeEvent(e);
236 void KisCurveWidget::paintEvent(QPaintEvent *)
238 int wWidth = width() - 1;
239 int wHeight = height() - 1;
243 // Antialiasing is not a good idea here, because
244 // the grid will drift one pixel to any side due to rounding of int
245 // FIXME: let's user tell the last word (in config)
246 //p.setRenderHint(QPainter::Antialiasing);
250 if (!d->m_pix.isNull()) {
251 if (d->m_pixmapDirty || !d->m_pixmapCache) {
252 if (d->m_pixmapCache)
253 delete d->m_pixmapCache;
254 d->m_pixmapCache = new QPixmap(width(), height());
255 QPainter cachePainter(d->m_pixmapCache);
257 cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height());
258 cachePainter.drawPixmap(0, 0, d->m_pix);
259 d->m_pixmapDirty = false;
261 p.drawPixmap(0, 0, *d->m_pixmapCache);
263 p.fillRect(rect(), palette().background());
266 d->drawGrid(p, wWidth, wHeight);
269 if (cfg.antialiasCurves())
270 p.setRenderHint(QPainter::Antialiasing);*/
273 p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
274 p.drawLine(QLineF(0, wHeight, wWidth, 0));
277 double prevY = wHeight - d->m_curve.value(0.) * wHeight;
283 p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
284 for (x = 0 ; x < wWidth ; x++) {
285 normalizedX = double(x) / wWidth;
286 curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
289 * Keep in mind that QLineF rounds doubles
290 * to ints mathematically, not just rounds down
293 p.drawLine(QLineF(prevX, prevY,
298 p.drawLine(QLineF(prevX, prevY ,
299 x, wHeight - d->m_curve.value(1.0) * wHeight));
301 // Drawing curve handles.
304 if (!d->m_readOnlyMode) {
305 for (int i = 0; i < d->m_curve.points().count(); ++i) {
306 curveX = d->m_curve.points().at(i).x();
307 curveY = d->m_curve.points().at(i).y();
309 if (i == d->m_grab_point_index) {
310 p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
311 p.drawEllipse(QRectF(curveX * wWidth - 2,
312 wHeight - 2 - curveY * wHeight, 4, 4));
314 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
315 p.drawEllipse(QRectF(curveX * wWidth - 3,
316 wHeight - 3 - curveY * wHeight, 6, 6));
322 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
324 if (d->m_readOnlyMode) return;
326 double x = e->pos().x() / (double)(width() - 1);
327 double y = 1.0 - e->pos().y() / (double)(height() - 1);
331 int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
333 if (e->button() == Qt::RightButton && closest_point_index > 0 && closest_point_index < d->m_curve.points().count() - 1) {
334 d->m_curve.removePoint(closest_point_index);
335 setCursor(Qt::ArrowCursor);
336 d->setState(ST_NORMAL);
337 if (closest_point_index < d->m_grab_point_index)
338 --d->m_grab_point_index;
339 d->setCurveModified();
341 } else if (e->button() != Qt::LeftButton) return;
343 if (closest_point_index < 0) {
344 if (d->m_maxPoints > 0 && d->m_curve.points().count() >= d->m_maxPoints)
346 QPointF newPoint(x, y);
347 if (!d->jumpOverExistingPoints(newPoint, -1))
349 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
351 d->m_grab_point_index = closest_point_index;
354 d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
355 d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
356 d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
357 d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
358 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
360 d->m_draggedAwayPointIndex = -1;
361 d->setState(ST_DRAG);
364 d->setCurveModified();
368 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
370 if (d->m_readOnlyMode) return;
372 if (e->button() != Qt::LeftButton)
375 setCursor(Qt::ArrowCursor);
376 d->setState(ST_NORMAL);
378 d->setCurveModified();
382 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
384 if (d->m_readOnlyMode) return;
386 double x = e->pos().x() / (double)(width() - 1);
387 double y = 1.0 - e->pos().y() / (double)(height() - 1);
389 if (d->state() == ST_NORMAL) { // If no point is selected set the the cursor shape if on top
390 int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
392 if (nearestPointIndex < 0)
393 setCursor(Qt::ArrowCursor);
395 setCursor(Qt::CrossCursor);
396 } else { // Else, drag the selected point
397 bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
398 e->pos().x() < -MOUSE_AWAY_THRES;
399 bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES ||
400 e->pos().y() < -MOUSE_AWAY_THRES;
402 bool removePoint = (crossedHoriz || crossedVert);
404 if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
405 // point is no longer dragged away so reinsert it
406 QPointF newPoint(d->m_draggedAwayPoint);
407 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
408 d->m_draggedAwayPointIndex = -1;
412 (d->m_draggedAwayPointIndex >= 0))
416 setCursor(Qt::CrossCursor);
418 x += d->m_grabOffsetX;
419 y += d->m_grabOffsetY;
423 if (d->m_grab_point_index == 0) {
425 if (d->m_curve.points().count() > 1)
426 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
429 } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
430 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
433 Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
435 // the 1E-4 addition so we can grab the dot later.
436 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
437 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
440 x = bounds(x, leftX, rightX);
441 y = bounds(y, 0., 1.);
443 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
445 if (removePoint && d->m_curve.points().count() > 2) {
446 d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
447 d->m_draggedAwayPointIndex = d->m_grab_point_index;
448 d->m_curve.removePoint(d->m_grab_point_index);
449 d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
452 d->setCurveModified();
456 KisCubicCurve KisCurveWidget::curve()
461 void KisCurveWidget::setCurve(KisCubicCurve inlist)
464 d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
465 d->setCurveModified();
468 void KisCurveWidget::leaveEvent(QEvent *)
472 void KisCurveWidget::setMaxPoints(int max)
474 d->m_maxPoints = max;
477 #include "kis_curve_widget.moc"