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);
78 setMouseTracking(true);
79 setAutoFillBackground(false);
80 setAttribute(Qt::WA_OpaquePaintEvent);
81 setMinimumSize(150, 150);
82 setMaximumSize(250, 250);
84 d->setCurveModified();
86 setFocusPolicy(Qt::StrongFocus);
89 KisCurveWidget::~KisCurveWidget()
92 delete d->m_pixmapCache;
96 void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
101 if (!d->m_intIn || !d->m_intOut)
107 d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
108 d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
111 connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
112 connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
116 void KisCurveWidget::dropInOutControls()
118 if (!d->m_intIn || !d->m_intOut)
121 disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
122 disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
124 d->m_intIn = d->m_intOut = NULL;
128 void KisCurveWidget::inOutChanged(int)
132 Q_ASSERT(d->m_grab_point_index >= 0);
134 pt.setX(d->io2sp(d->m_intIn->value()));
135 pt.setY(d->io2sp(d->m_intOut->value()));
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);
141 pt = d->m_curve.points()[d->m_grab_point_index];
144 d->m_intIn->blockSignals(true);
145 d->m_intOut->blockSignals(true);
147 d->m_intIn->setValue(d->sp2io(pt.x()));
148 d->m_intOut->setValue(d->sp2io(pt.y()));
150 d->m_intIn->blockSignals(false);
151 d->m_intOut->blockSignals(false);
153 d->setCurveModified();
157 void KisCurveWidget::reset(void)
159 d->m_grab_point_index = -1;
160 d->m_guideVisible = false;
162 d->setCurveModified();
165 void KisCurveWidget::setCurveGuide(const QColor & color)
167 d->m_guideVisible = true;
168 d->m_colorGuide = color;
172 void KisCurveWidget::setPixmap(const QPixmap & pix)
175 d->m_pixmapDirty = true;
176 d->setCurveRepaint();
179 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
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();
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;
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;
194 new_grab_point_index = d->m_grab_point_index;
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);
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);
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();
212 QWidget::keyPressEvent(e);
215 void KisCurveWidget::addPointInTheMiddle()
217 QPointF pt(0.5, d->m_curve.value(0.5));
219 if (!d->jumpOverExistingPoints(pt, -1))
222 d->m_grab_point_index = d->m_curve.addPoint(pt);
225 d->m_intIn->setFocus(Qt::TabFocusReason);
226 d->setCurveModified();
229 void KisCurveWidget::resizeEvent(QResizeEvent *e)
231 d->m_pixmapDirty = true;
232 QWidget::resizeEvent(e);
235 void KisCurveWidget::paintEvent(QPaintEvent *)
237 int wWidth = width() - 1;
238 int wHeight = height() - 1;
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);
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);
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;
260 p.drawPixmap(0, 0, *d->m_pixmapCache);
262 p.fillRect(rect(), palette().background());
265 d->drawGrid(p, wWidth, wHeight);
268 if (cfg.antialiasCurves())
269 p.setRenderHint(QPainter::Antialiasing);*/
272 double prevY = wHeight - d->m_curve.value(0.) * wHeight;
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;
284 * Keep in mind that QLineF rounds doubles
285 * to ints mathematically, not just rounds down
288 p.drawLine(QLineF(prevX, prevY,
293 p.drawLine(QLineF(prevX, prevY ,
294 x, wHeight - d->m_curve.value(1.0) * wHeight));
296 // Drawing curve handles.
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();
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));
309 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
310 p.drawEllipse(QRectF(curveX * wWidth - 3,
311 wHeight - 3 - curveY * wHeight, 6, 6));
317 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
319 if (d->m_readOnlyMode) return;
321 if (e->button() != Qt::LeftButton)
324 double x = e->pos().x() / (double)(width() - 1);
325 double y = 1.0 - e->pos().y() / (double)(height() - 1);
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))
334 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
336 d->m_grab_point_index = closest_point_index;
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));
345 d->m_draggedAwayPointIndex = -1;
346 d->setState(ST_DRAG);
349 d->setCurveModified();
353 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
355 if (d->m_readOnlyMode) return;
357 if (e->button() != Qt::LeftButton)
360 setCursor(Qt::ArrowCursor);
361 d->setState(ST_NORMAL);
363 d->setCurveModified();
367 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
369 if (d->m_readOnlyMode) return;
371 double x = e->pos().x() / (double)(width() - 1);
372 double y = 1.0 - e->pos().y() / (double)(height() - 1);
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());
377 if (nearestPointIndex < 0)
378 setCursor(Qt::ArrowCursor);
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;
387 bool removePoint = (crossedHoriz || crossedVert);
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;
397 (d->m_draggedAwayPointIndex >= 0))
401 setCursor(Qt::CrossCursor);
403 x += d->m_grabOffsetX;
404 y += d->m_grabOffsetY;
408 if (d->m_grab_point_index == 0) {
410 if (d->m_curve.points().count() > 1)
411 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
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;
418 Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
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;
425 x = bounds(x, leftX, rightX);
426 y = bounds(y, 0., 1.);
428 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
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);
437 d->setCurveModified();
441 KisCubicCurve KisCurveWidget::curve()
446 void KisCurveWidget::setCurve(KisCubicCurve inlist)
449 d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
450 d->setCurveModified();
453 void KisCurveWidget::leaveEvent(QEvent *)
457 #include "kis_curve_widget.moc"