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(250, 250);
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 double prevY = wHeight - d->m_curve.value(0.) * wHeight;
280 p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
281 for (x = 0 ; x < wWidth ; x++) {
282 normalizedX = double(x) / wWidth;
283 curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
286 * Keep in mind that QLineF rounds doubles
287 * to ints mathematically, not just rounds down
290 p.drawLine(QLineF(prevX, prevY,
295 p.drawLine(QLineF(prevX, prevY ,
296 x, wHeight - d->m_curve.value(1.0) * wHeight));
298 // Drawing curve handles.
301 if (!d->m_readOnlyMode) {
302 for (int i = 0; i < d->m_curve.points().count(); ++i) {
303 curveX = d->m_curve.points().at(i).x();
304 curveY = d->m_curve.points().at(i).y();
306 if (i == d->m_grab_point_index) {
307 p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
308 p.drawEllipse(QRectF(curveX * wWidth - 2,
309 wHeight - 2 - curveY * wHeight, 4, 4));
311 p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
312 p.drawEllipse(QRectF(curveX * wWidth - 3,
313 wHeight - 3 - curveY * wHeight, 6, 6));
319 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
321 if (d->m_readOnlyMode) return;
323 if (e->button() != Qt::LeftButton)
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());
332 if (closest_point_index < 0) {
333 if (d->m_maxPoints > 0 && d->m_curve.points().count() >= d->m_maxPoints)
335 QPointF newPoint(x, y);
336 if (!d->jumpOverExistingPoints(newPoint, -1))
338 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
340 d->m_grab_point_index = closest_point_index;
343 d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
344 d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
345 d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
346 d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
347 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
349 d->m_draggedAwayPointIndex = -1;
350 d->setState(ST_DRAG);
353 d->setCurveModified();
357 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
359 if (d->m_readOnlyMode) return;
361 if (e->button() != Qt::LeftButton)
364 setCursor(Qt::ArrowCursor);
365 d->setState(ST_NORMAL);
367 d->setCurveModified();
371 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
373 if (d->m_readOnlyMode) return;
375 double x = e->pos().x() / (double)(width() - 1);
376 double y = 1.0 - e->pos().y() / (double)(height() - 1);
378 if (d->state() == ST_NORMAL) { // If no point is selected set the the cursor shape if on top
379 int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
381 if (nearestPointIndex < 0)
382 setCursor(Qt::ArrowCursor);
384 setCursor(Qt::CrossCursor);
385 } else { // Else, drag the selected point
386 bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
387 e->pos().x() < -MOUSE_AWAY_THRES;
388 bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES ||
389 e->pos().y() < -MOUSE_AWAY_THRES;
391 bool removePoint = (crossedHoriz || crossedVert);
393 if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
394 // point is no longer dragged away so reinsert it
395 QPointF newPoint(d->m_draggedAwayPoint);
396 d->m_grab_point_index = d->m_curve.addPoint(newPoint);
397 d->m_draggedAwayPointIndex = -1;
401 (d->m_draggedAwayPointIndex >= 0))
405 setCursor(Qt::CrossCursor);
407 x += d->m_grabOffsetX;
408 y += d->m_grabOffsetY;
412 if (d->m_grab_point_index == 0) {
414 if (d->m_curve.points().count() > 1)
415 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
418 } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
419 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
422 Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
424 // the 1E-4 addition so we can grab the dot later.
425 leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
426 rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
429 x = bounds(x, leftX, rightX);
430 y = bounds(y, 0., 1.);
432 d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
434 if (removePoint && d->m_curve.points().count() > 2) {
435 d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
436 d->m_draggedAwayPointIndex = d->m_grab_point_index;
437 d->m_curve.removePoint(d->m_grab_point_index);
438 d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
441 d->setCurveModified();
445 KisCubicCurve KisCurveWidget::curve()
450 void KisCurveWidget::setCurve(KisCubicCurve inlist)
453 d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
454 d->setCurveModified();
457 void KisCurveWidget::leaveEvent(QEvent *)
461 void KisCurveWidget::setMaxPoints(int max)
463 d->m_maxPoints = max;
466 #include "kis_curve_widget.moc"