krita/ui

kis_curve_widget.cpp

Go to the documentation of this file.
00001 /*
00002  *  Copyright (c) 2005 Casper Boemann <cbr@boemann.dk>
00003  *  Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
00004  *
00005  *  This program is free software; you can redistribute it and/or modify
00006  *  it under the terms of the GNU General Public License as published by
00007  *  the Free Software Foundation; either version 2 of the License, or
00008  *  (at your option) any later version.
00009  *
00010  *  This program is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  *  GNU General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with this program; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018  */
00019 
00020 
00021 // C++ includes.
00022 
00023 #include <cmath>
00024 #include <cstdlib>
00025 
00026 // Qt includes.
00027 
00028 #include <QPixmap>
00029 #include <QPainter>
00030 #include <QPoint>
00031 #include <QPen>
00032 #include <QEvent>
00033 #include <QRect>
00034 #include <QFont>
00035 #include <QFontMetrics>
00036 #include <QMouseEvent>
00037 #include <QKeyEvent>
00038 #include <QPaintEvent>
00039 #include <QList>
00040 
00041 #include <QSpinBox>
00042 
00043 // KDE includes.
00044 
00045 #include <kis_debug.h>
00046 #include <kcursor.h>
00047 #include <klocale.h>
00048 
00049 // Local includes.
00050 
00051 #include "widgets/kis_curve_widget.h"
00052 
00053 
00054 #define bounds(x,a,b) (x<a ? a : (x>b ? b :x))
00055 #define MOUSE_AWAY_THRES 15
00056 #define POINT_AREA       1E-4
00057 #define CURVE_AREA       1E-4
00058 
00059 #include "kis_curve_widget_p.h"
00060 
00061 
00062 static
00063 bool pointLessThan(const QPointF &a, const QPointF &b);
00064 
00065 
00066 KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WFlags f)
00067         : QWidget(parent, f), d(new KisCurveWidget::Private(this))
00068 {
00069     setObjectName("KisCurveWidget");
00070     d->m_grab_point_index = -1;
00071     d->m_readOnlyMode   = false;
00072     d->m_guideVisible   = false;
00073     d->m_pixmapDirty = true;
00074     d->m_pixmapCache = NULL;
00075     d->setState(ST_NORMAL);
00076 
00077     d->m_intIn = NULL;
00078     d->m_intOut = NULL;
00079 
00080     setMouseTracking(true);
00081     setAutoFillBackground(false);
00082     setAttribute(Qt::WA_OpaquePaintEvent);
00083     setMinimumSize(150, 50);
00084     setMaximumSize(250, 250);
00085     QPointF p;
00086     p.rx() = 0.0; p.ry() = 0.0;
00087     d->m_points.append(p);
00088     p.rx() = 1.0; p.ry() = 1.0;
00089     d->m_points.append(p);
00090 
00091     d->setCurveModified();
00092 
00093     setFocusPolicy(Qt::StrongFocus);
00094 }
00095 
00096 KisCurveWidget::~KisCurveWidget()
00097 {
00098     if (d->m_pixmapCache)
00099         delete d->m_pixmapCache;
00100     delete d;
00101 }
00102 
00103 void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
00104 {
00105     d->m_intIn = in;
00106     d->m_intOut = out;
00107 
00108     if (!d->m_intIn || !d->m_intOut)
00109         return;
00110 
00111     d->m_inOutMin = min;
00112     d->m_inOutMax = max;
00113 
00114     d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
00115     d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
00116 
00117 
00118     connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
00119     connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
00120     d->syncIOControls();
00121 
00122 }
00123 void KisCurveWidget::dropInOutControls()
00124 {
00125     if (!d->m_intIn || !d->m_intOut)
00126         return;
00127 
00128     disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
00129     disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
00130 
00131     d->m_intIn = d->m_intOut = NULL;
00132 
00133 }
00134 
00135 void KisCurveWidget::inOutChanged(int)
00136 {
00137     QPointF pt;
00138 
00139     Q_ASSERT(d->m_grab_point_index >= 0);
00140 
00141     pt.rx() = d->io2sp(d->m_intIn->value());
00142     pt.ry() = d->io2sp(d->m_intOut->value());
00143 
00144     if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) {
00145         d->m_points[d->m_grab_point_index] = pt;
00146         qSort(d->m_points.begin(), d->m_points.end(), pointLessThan);
00147         d->m_grab_point_index = d->m_points.indexOf(pt);
00148     } else
00149         pt = d->m_points[d->m_grab_point_index];
00150 
00151 
00152     d->m_intIn->blockSignals(true);
00153     d->m_intOut->blockSignals(true);
00154 
00155     d->m_intIn->setValue(d->sp2io(pt.rx()));
00156     d->m_intOut->setValue(d->sp2io(pt.ry()));
00157 
00158     d->m_intIn->blockSignals(false);
00159     d->m_intOut->blockSignals(false);
00160 
00161     d->setCurveModified();
00162 }
00163 
00164 
00165 void KisCurveWidget::reset(void)
00166 {
00167     d->m_grab_point_index = -1;
00168     d->m_guideVisible = false;
00169 
00170     d->setCurveModified();
00171 }
00172 
00173 void KisCurveWidget::setCurveGuide(const QColor & color)
00174 {
00175     d->m_guideVisible = true;
00176     d->m_colorGuide   = color;
00177 
00178 }
00179 
00180 void KisCurveWidget::setPixmap(const QPixmap & pix)
00181 {
00182     d->m_pix = pix;
00183     d->m_pixmapDirty = true;
00184     d->setCurveRepaint();
00185 }
00186 
00187 void KisCurveWidget::keyPressEvent(QKeyEvent *e)
00188 {
00189     if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
00190         if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_points.count() - 1) {
00191             //rx() find closest point to get focus afterwards
00192             double grab_point_x = d->m_points[d->m_grab_point_index].rx();
00193 
00194             int left_of_grab_point_index = d->m_grab_point_index - 1;
00195             int right_of_grab_point_index = d->m_grab_point_index + 1;
00196             int new_grab_point_index;
00197 
00198             if (fabs(d->m_points[left_of_grab_point_index].rx() - grab_point_x) <
00199                     fabs(d->m_points[right_of_grab_point_index].rx() - grab_point_x)) {
00200                 new_grab_point_index = left_of_grab_point_index;
00201             } else {
00202                 new_grab_point_index = d->m_grab_point_index;
00203             }
00204             d->m_points.removeAt(d->m_grab_point_index);
00205             d->m_grab_point_index = new_grab_point_index;
00206             setCursor(Qt::ArrowCursor);
00207             d->setState(ST_NORMAL);
00208         }
00209         d->setCurveModified();
00210     } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) {
00211         d->m_points[d->m_grab_point_index].rx() = d->m_grabOriginalX;
00212         d->m_points[d->m_grab_point_index].ry() = d->m_grabOriginalY;
00213         setCursor(Qt::ArrowCursor);
00214         d->setState(ST_NORMAL);
00215 
00216         d->setCurveModified();
00217     } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) {
00218         /* FIXME: Lets user choose the hotkeys */
00219         addPointInTheMiddle();
00220     } else
00221         QWidget::keyPressEvent(e);
00222 }
00223 
00224 void KisCurveWidget::addPointInTheMiddle()
00225 {
00226     QPointF pt;
00227     pt.rx() = 0.5;
00228     pt.ry() = getCurveValue(pt.rx());
00229 
00230     if (!d->jumpOverExistingPoints(pt, -1))
00231         return;
00232 
00233     d->m_points.append(pt);
00234     qSort(d->m_points.begin(), d->m_points.end(), pointLessThan);
00235     d->m_grab_point_index = d->m_points.indexOf(pt);
00236 
00237     if (d->m_intIn)
00238         d->m_intIn->setFocus(Qt::TabFocusReason);
00239     d->setCurveModified();
00240 }
00241 
00242 void KisCurveWidget::resizeEvent(QResizeEvent *e)
00243 {
00244     d->m_pixmapDirty = true;
00245     QWidget::resizeEvent(e);
00246 }
00247 
00248 void KisCurveWidget::paintEvent(QPaintEvent *)
00249 {
00250     int    wWidth = width() - 1;
00251     int    wHeight = height() - 1;
00252 
00253     QPainter p(this);
00254 
00255     // Antialiasing is not a good idea here, because
00256     // the grid will drift one pixel to any side due to rounding of int
00257     // FIXME: let's user tell the last word (in config)
00258     //p.setRenderHint(QPainter::Antialiasing);
00259 
00260 
00261     //  draw background
00262     if (!d->m_pix.isNull()) {
00263         if (d->m_pixmapDirty || !d->m_pixmapCache) {
00264             if (d->m_pixmapCache)
00265                 delete d->m_pixmapCache;
00266             d->m_pixmapCache = new QPixmap(width(), height());
00267             QPainter cachePainter(d->m_pixmapCache);
00268 
00269             cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height());
00270             cachePainter.drawPixmap(0, 0, d->m_pix);
00271             d->m_pixmapDirty = false;
00272         }
00273         p.drawPixmap(0, 0, *d->m_pixmapCache);
00274     } else
00275         p.fillRect(rect(), palette().background());
00276 
00277 
00278     d->drawGrid(p, wWidth, wHeight);
00279 
00280 
00281     // Draw curve.
00282     double prevY = wHeight - getCurveValue(0.) * wHeight;
00283     double prevX = 0.;
00284     double curY;
00285     double normalizedX;
00286     int x;
00287 
00288     p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
00289     for (x = 0 ; x < wWidth ; x++) {
00290         normalizedX = double(x) / wWidth;
00291         curY = wHeight - getCurveValue(normalizedX) * wHeight;
00292 
00298         p.drawLine(QLineF(prevX, prevY,
00299                           x, curY));
00300         prevX = x;
00301         prevY = curY;
00302     }
00303     p.drawLine(QLineF(prevX, prevY ,
00304                       x, wHeight - getCurveValue(1.0) * wHeight));
00305 
00306     // Drawing curve handles.
00307     double curveX;
00308     double curveY;
00309     if (!d->m_readOnlyMode) {
00310         for (int i = 0; i < d->m_points.count(); ++i) {
00311             curveX = d->m_points.at(i).x();
00312             curveY = d->m_points.at(i).y();
00313 
00314             if (i == d->m_grab_point_index) {
00315                 p.setPen(QPen::QPen(Qt::red, 3, Qt::SolidLine));
00316                 p.drawEllipse(QRectF(curveX * wWidth - 2,
00317                                      wHeight - 2 - curveY * wHeight, 4, 4));
00318             } else {
00319                 p.setPen(QPen::QPen(Qt::red, 1, Qt::SolidLine));
00320                 p.drawEllipse(QRectF(curveX * wWidth - 3,
00321                                      wHeight - 3 - curveY * wHeight, 6, 6));
00322             }
00323         }
00324     }
00325 }
00326 
00327 static bool pointLessThan(const QPointF &a, const QPointF &b)
00328 {
00329     return a.x() < b.x();
00330 }
00331 
00332 void KisCurveWidget::mousePressEvent(QMouseEvent * e)
00333 {
00334     if (d->m_readOnlyMode) return;
00335 
00336     if (e->button() != Qt::LeftButton)
00337         return;
00338 
00339     double x = e->pos().x() / (double)(width() - 1);
00340     double y = 1.0 - e->pos().y() / (double)(height() - 1);
00341 
00342 
00343 
00344     int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
00345     if (closest_point_index < 0) {
00346         QPointF newPoint(x, y);
00347         if (!d->jumpOverExistingPoints(newPoint, -1))
00348             return;
00349         d->m_points.append(newPoint);
00350         qSort(d->m_points.begin(), d->m_points.end(), pointLessThan);
00351         d->m_grab_point_index = d->m_points.indexOf(newPoint);
00352     } else {
00353         d->m_grab_point_index = closest_point_index;
00354     }
00355 
00356     d->m_grabOriginalX = d->m_points[d->m_grab_point_index].x();
00357     d->m_grabOriginalY = d->m_points[d->m_grab_point_index].y();
00358     d->m_grabOffsetX = d->m_points[d->m_grab_point_index].x() - x;
00359     d->m_grabOffsetY = d->m_points[d->m_grab_point_index].y() - y;
00360     d->m_points[d->m_grab_point_index].rx() = x + d->m_grabOffsetX;
00361     d->m_points[d->m_grab_point_index].ry() = y + d->m_grabOffsetY;
00362 
00363     d->m_draggedAwayPointIndex = -1;
00364     d->setState(ST_DRAG);
00365 
00366 
00367     d->setCurveModified();
00368 }
00369 
00370 
00371 void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
00372 {
00373     if (d->m_readOnlyMode) return;
00374 
00375     if (e->button() != Qt::LeftButton)
00376         return;
00377 
00378     setCursor(Qt::ArrowCursor);
00379     d->setState(ST_NORMAL);
00380 
00381     d->setCurveModified();
00382 }
00383 
00384 
00385 void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
00386 {
00387     if (d->m_readOnlyMode) return;
00388 
00389     double x = e->pos().x() / (double)(width() - 1);
00390     double y = 1.0 - e->pos().y() / (double)(height() - 1);
00391 
00392     if (d->state() == ST_NORMAL) { // If no point is selected set the the cursor shape if on top
00393         int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
00394 
00395         if (nearestPointIndex < 0)
00396             setCursor(Qt::ArrowCursor);
00397         else
00398             setCursor(Qt::CrossCursor);
00399     } else { // Else, drag the selected point
00400         bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
00401                             e->pos().x() < -MOUSE_AWAY_THRES;
00402         bool crossedVert =  e->pos().y() - height() > MOUSE_AWAY_THRES ||
00403                             e->pos().y() < -MOUSE_AWAY_THRES;
00404 
00405         bool removePoint = (crossedHoriz || crossedVert);
00406 
00407         if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
00408             // point is no longer dragged away so reinsert it
00409             QPointF newPoint(d->m_draggedAwayPoint);
00410             d->m_points.insert(d->m_draggedAwayPointIndex, newPoint);
00411             d->m_grab_point_index = d->m_draggedAwayPointIndex;
00412             d->m_draggedAwayPointIndex = -1;
00413         }
00414 
00415         if (removePoint &&
00416                 (d->m_draggedAwayPointIndex >= 0))
00417             return;
00418 
00419 
00420         setCursor(Qt::CrossCursor);
00421 
00422         x += d->m_grabOffsetX;
00423         y += d->m_grabOffsetY;
00424 
00425         double leftX;
00426         double rightX;
00427         if (d->m_grab_point_index == 0) {
00428             leftX = 0.0;
00429             if (d->m_points.count() > 1)
00430                 rightX = d->m_points[d->m_grab_point_index + 1].rx() - POINT_AREA;
00431             else
00432                 rightX = 1.0;
00433         } else if (d->m_grab_point_index == d->m_points.count() - 1) {
00434             leftX = d->m_points[d->m_grab_point_index - 1].rx() + POINT_AREA;
00435             rightX = 1.0;
00436         } else {
00437             Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_points.count() - 1);
00438 
00439             // the 1E-4 addition so we can grab the dot later.
00440             leftX = d->m_points[d->m_grab_point_index - 1].rx() + POINT_AREA;
00441             rightX = d->m_points[d->m_grab_point_index + 1].rx() - POINT_AREA;
00442         }
00443 
00444         x = bounds(x, leftX, rightX);
00445         y = bounds(y, 0., 1.);
00446 
00447         d->m_points[d->m_grab_point_index].rx() = x;
00448         d->m_points[d->m_grab_point_index].ry() = y;
00449 
00450 
00451         if (removePoint && d->m_points.count() > 2) {
00452             d->m_draggedAwayPoint.rx() = d->m_points[d->m_grab_point_index].rx();
00453             d->m_draggedAwayPoint.ry() = d->m_points[d->m_grab_point_index].ry();
00454             d->m_draggedAwayPointIndex = d->m_grab_point_index;
00455             d->m_points.removeAt(d->m_grab_point_index);
00456             d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_points.count() - 1);
00457         }
00458 
00459         d->setCurveModified();
00460     }
00461 }
00462 
00463 double KisCurveWidget::getCurveValue(double x)
00464 {
00465     if (d->m_splineDirty) {
00466         d->m_spline.createSpline(QVector<QPointF>::fromList(d->m_points));
00467         d->m_splineDirty = false;
00468     }
00469     return Private::checkBounds(d->m_spline, x);
00470 }
00471 
00472 double KisCurveWidget::getCurveValue(const QList<QPointF>& curve, double x)
00473 {
00474     KisCubicSpline<QPointF, double> spline(QVector<QPointF>::fromList(curve));
00475     return Private::checkBounds(spline, x);
00476 }
00477 
00478 QList<QPointF> KisCurveWidget::getCurve()
00479 {
00480     return d->m_points;
00481 }
00482 
00483 void KisCurveWidget::setCurve(QList<QPointF >inlist)
00484 {
00485     d->m_points = inlist;
00486     d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_points.count() - 1);
00487     d->setCurveModified();
00488 }
00489 
00490 void KisCurveWidget::leaveEvent(QEvent *)
00491 {
00492 }
00493 
00494 #include "kis_curve_widget.moc"