diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h index 1b6b9db84b..9252bb0b6d 100644 --- a/libs/global/kis_signal_compressor.h +++ b/libs/global/kis_signal_compressor.h @@ -1,102 +1,102 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SIGNAL_COMPRESSOR_H #define __KIS_SIGNAL_COMPRESSOR_H #include #include "kritaglobal_export.h" #include class QTimer; /** * Sets a timer to delay or throttle activation of a Qt slot. One example of * where this is used is to limit the amount of expensive redraw activity on the * canvas. * - * There are three behaviors to choose from. + * There are four behaviors to choose from. * * POSTPONE resets the timer after each call. Therefore if the calls are made * quickly enough, the timer will never be activated. * * FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all - * the other actions the other action like in POSTPONE. This mode is - * used e.g. in move/remove layer functionality. If you remove a + * the other actions like in POSTPONE. This mode is + * used e.g. in move/remove layer functionality. If you remove a * single layer, you'll see the result immediately. But if you want to * remove multiple layers, you should wait until all the actions are * finished. * * FIRST_ACTIVE emits the timeout() event immediately and sets a timer of * duration \p delay. If the compressor is triggered during this time, it will * wait until the end of the delay period to fire the signal. Further events are * ignored until the timer elapses. Think of it as a queue with size 1, and * where the leading element is popped every \p delay ms. * * FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p * delay ms. The compressor becomes inactive and all events are ignored until * the timer has elapsed. * * The current implementation allows the timeout() to be delayed by up to 2 times * \p delay in certain situations (for details see cpp file). */ class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject { Q_OBJECT public: enum Mode { POSTPONE, /* Calling start() resets the timer to \p delay ms */ FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */ FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */ FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */ UNDEFINED /* KisSignalCompressor is created without an explicit mode */ }; public: KisSignalCompressor(); KisSignalCompressor(int delay, Mode mode, QObject *parent = 0); bool isActive() const; void setMode(Mode mode); public Q_SLOTS: void setDelay(int delay); void start(); void stop(); private Q_SLOTS: void slotTimerExpired(); Q_SIGNALS: void timeout(); private: bool tryEmitOnTick(bool isFromTimer); bool tryEmitSignalSafely(); private: QTimer *m_timer = 0; Mode m_mode = UNDEFINED; bool m_signalsPending = false; QElapsedTimer m_lastEmittedTimer; int m_isEmitting = 0; }; #endif /* __KIS_SIGNAL_COMPRESSOR_H */ diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp index 623d11a572..1c5292d21f 100644 --- a/libs/ui/widgets/kis_curve_widget.cpp +++ b/libs/ui/widgets/kis_curve_widget.cpp @@ -1,535 +1,552 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include +#include +#include + + // Local includes. #include "widgets/kis_curve_widget.h" #define bounds(x,a,b) (xb ? b :x)) #define MOUSE_AWAY_THRES 15 #define POINT_AREA 1E-4 #define CURVE_AREA 1E-4 #include "kis_curve_widget_p.h" KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new KisCurveWidget::Private(this)) { setObjectName("KisCurveWidget"); d->m_grab_point_index = -1; d->m_readOnlyMode = false; d->m_guideVisible = false; d->m_pixmapDirty = true; d->m_pixmapCache = 0; d->setState(ST_NORMAL); d->m_intIn = 0; d->m_intOut = 0; + connect(&d->m_modifiedSignalsCompressor, SIGNAL(timeout()), SLOT(notifyModified())); + connect(this, SIGNAL(compressorShouldEmitModified()), SLOT(slotCompressorShouldEmitModified())); + setMouseTracking(true); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent); setMinimumSize(150, 50); setMaximumSize(250, 250); - d->setCurveModified(); setFocusPolicy(Qt::StrongFocus); } KisCurveWidget::~KisCurveWidget() { delete d->m_pixmapCache; delete d; } void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax) { + dropInOutControls(); + d->m_intIn = in; d->m_intOut = out; if (!d->m_intIn || !d->m_intOut) return; d->m_inMin = inMin; d->m_inMax = inMax; d->m_outMin = outMin; d->m_outMax = outMax; int realInMin = qMin(inMin, inMax); // tilt elevation has range (90, 0), which QSpinBox can't handle int realInMax = qMax(inMin, inMax); d->m_intIn->setRange(realInMin, realInMax); d->m_intOut->setRange(d->m_outMin, d->m_outMax); connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)), Qt::UniqueConnection); d->syncIOControls(); } void KisCurveWidget::dropInOutControls() { if (!d->m_intIn || !d->m_intOut) return; disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->m_intIn = d->m_intOut = 0; } void KisCurveWidget::inOutChanged(int) { QPointF pt; Q_ASSERT(d->m_grab_point_index >= 0); pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax)); pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax)); bool newPoint = false; if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) { newPoint = true; d->m_curve.setPoint(d->m_grab_point_index, pt); d->m_grab_point_index = d->m_curve.points().indexOf(pt); emit pointSelectedChanged(); } else { pt = d->m_curve.points()[d->m_grab_point_index]; } if (!newPoint) { // if there is a new Point, no point in rewriting values in spinboxes d->m_intIn->blockSignals(true); d->m_intOut->blockSignals(true); d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); d->m_intIn->blockSignals(false); d->m_intOut->blockSignals(false); } d->setCurveModified(false); } void KisCurveWidget::reset(void) { d->m_grab_point_index = -1; emit pointSelectedChanged(); d->m_guideVisible = false; //remove total - 2 points. while (d->m_curve.points().count() - 2 ) { d->m_curve.removePoint(d->m_curve.points().count() - 2); } d->setCurveModified(); } void KisCurveWidget::setCurveGuide(const QColor & color) { d->m_guideVisible = true; d->m_colorGuide = color; } void KisCurveWidget::setPixmap(const QPixmap & pix) { d->m_pix = pix; d->m_pixmapDirty = true; d->setCurveRepaint(); } QPixmap KisCurveWidget::getPixmap() { return d->m_pix; } void KisCurveWidget::setBasePixmap(const QPixmap &pix) { d->m_pixmapBase = pix; } QPixmap KisCurveWidget::getBasePixmap() { return d->m_pixmapBase; } bool KisCurveWidget::pointSelected() const { return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1; } void KisCurveWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) { //x() find closest point to get focus afterwards double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x(); int left_of_grab_point_index = d->m_grab_point_index - 1; int right_of_grab_point_index = d->m_grab_point_index + 1; int new_grab_point_index; if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) < fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) { new_grab_point_index = left_of_grab_point_index; } else { new_grab_point_index = d->m_grab_point_index; } d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = new_grab_point_index; emit pointSelectedChanged(); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); } e->accept(); d->setCurveModified(); } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) { d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) ); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); e->accept(); d->setCurveModified(); } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) { /* FIXME: Lets user choose the hotkeys */ addPointInTheMiddle(); e->accept(); } else QWidget::keyPressEvent(e); } void KisCurveWidget::addPointInTheMiddle() { QPointF pt(0.5, d->m_curve.value(0.5)); if (!d->jumpOverExistingPoints(pt, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(pt); emit pointSelectedChanged(); if (d->m_intIn) d->m_intIn->setFocus(Qt::TabFocusReason); d->setCurveModified(); } void KisCurveWidget::resizeEvent(QResizeEvent *e) { d->m_pixmapDirty = true; QWidget::resizeEvent(e); } void KisCurveWidget::paintEvent(QPaintEvent *) { int wWidth = width() - 1; int wHeight = height() - 1; QPainter p(this); // Antialiasing is not a good idea here, because // the grid will drift one pixel to any side due to rounding of int // FIXME: let's user tell the last word (in config) //p.setRenderHint(QPainter::Antialiasing); QPalette appPalette = QApplication::palette(); p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results // make the entire widget grayed out if it is disabled if (!this->isEnabled()) { p.setOpacity(0.2); } // draw background if (!d->m_pix.isNull()) { if (d->m_pixmapDirty || !d->m_pixmapCache) { delete d->m_pixmapCache; d->m_pixmapCache = new QPixmap(width(), height()); QPainter cachePainter(d->m_pixmapCache); cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height()); cachePainter.drawPixmap(0, 0, d->m_pix); d->m_pixmapDirty = false; } p.drawPixmap(0, 0, *d->m_pixmapCache); } d->drawGrid(p, wWidth, wHeight); KisConfig cfg(true); if (cfg.antialiasCurves()) { p.setRenderHint(QPainter::Antialiasing); } // Draw curve. double curY; double normalizedX; int x; QPolygonF poly; p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); for (x = 0 ; x < wWidth ; x++) { normalizedX = double(x) / wWidth; curY = wHeight - d->m_curve.value(normalizedX) * wHeight; /** * Keep in mind that QLineF rounds doubles * to ints mathematically, not just rounds down * like in C */ poly.append(QPointF(x, curY)); } poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight)); p.drawPolyline(poly); QPainterPath fillCurvePath; QPolygonF fillPoly = poly; fillPoly.append(QPoint(rect().width(), rect().height())); fillPoly.append(QPointF(0,rect().height())); // add a couple points to the edges so it fills in below always QColor fillColor = appPalette.color(QPalette::Text); fillColor.setAlphaF(0.2); fillCurvePath.addPolygon(fillPoly); p.fillPath(fillCurvePath, fillColor); // Drawing curve handles. double curveX; double curveY; if (!d->m_readOnlyMode) { for (int i = 0; i < d->m_curve.points().count(); ++i) { curveX = d->m_curve.points().at(i).x(); curveY = d->m_curve.points().at(i).y(); int handleSize = 12; // how big should control points be (diamater size) if (i == d->m_grab_point_index) { // active point is slightly more "bold" p.setPen(QPen(appPalette.color(QPalette::Text), 4, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } else { p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - (handleSize*0.5), wHeight - (handleSize*0.5) - curveY * wHeight, handleSize, handleSize)); } } } // add border around widget to help contain everything QPainterPath widgetBoundsPath; widgetBoundsPath.addRect(rect()); p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text)); p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before } void KisCurveWidget::mousePressEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height()); if (closest_point_index < 0) { QPointF newPoint(x, y); if (!d->jumpOverExistingPoints(newPoint, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(newPoint); emit pointSelectedChanged(); } else { d->m_grab_point_index = closest_point_index; emit pointSelectedChanged(); } d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x(); d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y(); d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x; d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y; d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY)); d->m_draggedAwayPointIndex = -1; d->setState(ST_DRAG); d->setCurveModified(); } void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); d->setCurveModified(); } void KisCurveWidget::mouseMoveEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height()); if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); } else { // Else, drag the selected point bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; bool removePoint = (crossedHoriz || crossedVert); if (!removePoint && d->m_draggedAwayPointIndex >= 0) { // point is no longer dragged away so reinsert it QPointF newPoint(d->m_draggedAwayPoint); d->m_grab_point_index = d->m_curve.addPoint(newPoint); d->m_draggedAwayPointIndex = -1; } if (removePoint && (d->m_draggedAwayPointIndex >= 0)) return; setCursor(Qt::CrossCursor); x += d->m_grabOffsetX; y += d->m_grabOffsetY; double leftX; double rightX; if (d->m_grab_point_index == 0) { leftX = 0.0; if (d->m_curve.points().count() > 1) rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; else rightX = 1.0; } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) { leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = 1.0; } else { Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1); // the 1E-4 addition so we can grab the dot later. leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; } x = bounds(x, leftX, rightX); y = bounds(y, 0., 1.); d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y)); if (removePoint && d->m_curve.points().count() > 2) { d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; d->m_draggedAwayPointIndex = d->m_grab_point_index; d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); emit pointSelectedChanged(); } d->setCurveModified(); } } KisCubicCurve KisCurveWidget::curve() { return d->m_curve; } void KisCurveWidget::setCurve(KisCubicCurve inlist) { d->m_curve = inlist; d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1); d->setCurveModified(); emit pointSelectedChanged(); } void KisCurveWidget::leaveEvent(QEvent *) { } +void KisCurveWidget::notifyModified() +{ + emit modified(); +} + +void KisCurveWidget::slotCompressorShouldEmitModified() +{ + d->m_modifiedSignalsCompressor.start(); +} diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h index 719c29093a..5ffca8bdf8 100644 --- a/libs/ui/widgets/kis_curve_widget.h +++ b/libs/ui/widgets/kis_curve_widget.h @@ -1,161 +1,178 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CURVE_WIDGET_H #define KIS_CURVE_WIDGET_H // Qt includes. #include #include #include #include #include #include #include #include #include #include class QSpinBox; class KisCubicCurve; /** * KisCurveWidget is a widget that shows a single curve that can be edited * by the user. The user can grab the curve and move it; this creates * a new control point. Control points can be deleted by selecting a point * and pressing the delete key. * * (From: http://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget) * KisCurveWidget allows editing of spline based y=f(x) curves. Handy for cases * where you want the user to control such things as tablet pressure * response, color transformations, acceleration by time, aeroplane lift *by angle of attack. */ class KRITAUI_EXPORT KisCurveWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool pointSelected READ pointSelected NOTIFY pointSelectedChanged); public: friend class CurveEditorItem; /** * Create a new curve widget with a default curve, that is a straight * line from bottom-left to top-right. */ KisCurveWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); ~KisCurveWidget() override; /** * Reset the curve to the default shape */ void reset(void); /** * Enable the guide and set the guide color to the specified color. * * XXX: it seems that the guide feature isn't actually implemented yet? */ void setCurveGuide(const QColor & color); /** * Set a background pixmap. The background pixmap will be drawn under * the grid and the curve. * * XXX: or is the pixmap what is drawn to the left and bottom of the curve * itself? */ void setPixmap(const QPixmap & pix); QPixmap getPixmap(); void setBasePixmap(const QPixmap & pix); QPixmap getBasePixmap(); /** * Whether or not there is a point selected * This does NOT include the first and last points */ bool pointSelected() const; Q_SIGNALS: /** * Emitted whenever a control point has changed position. */ void modified(void); /** * Emitted whenever the status of whether a control point is selected or not changes */ void pointSelectedChanged(); + /** + * Emitted to notify that the start() function in compressor can be activated. + * Thanks to that, blocking signals in curve widget blocks "sending signals" + * (calling start() function) *to* the signal compressor. + * It effectively makes signals work nearly the same way they worked before + * adding the signal compressor in between. + */ + void compressorShouldEmitModified(); + protected Q_SLOTS: void inOutChanged(int); + void notifyModified(); + + /** + * This function is called when compressorShouldEmitModified() is emitted. + * For why it's needed, \see compressorShouldEmitModified() + */ + void slotCompressorShouldEmitModified(); + protected: void keyPressEvent(QKeyEvent *) override; void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent * e) override; void mouseReleaseEvent(QMouseEvent * e) override; void mouseMoveEvent(QMouseEvent * e) override; void leaveEvent(QEvent *) override; void resizeEvent(QResizeEvent *e) override; public: /** * @return get a list with all defined points. If you want to know what the * y value for a given x is on the curve defined by these points, use getCurveValue(). * @see getCurveValue */ KisCubicCurve curve(); /** * Replace the current curve with a curve specified by the curve defined by the control * points in @p inlist. */ void setCurve(KisCubicCurve inlist); /** * Connect/disconnect external spinboxes to the curve * @p inMin / @p inMax - is the range for input values * @p outMin / @p outMax - is the range for output values */ void setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax); void dropInOutControls(); /** * Handy function that creates new point in the middle * of the curve and sets focus on the @p m_intIn field, * so the user can move this point anywhere in a moment */ void addPointInTheMiddle(); private: class Private; Private * const d; }; #endif /* KIS_CURVE_WIDGET_H */ diff --git a/libs/ui/widgets/kis_curve_widget_p.h b/libs/ui/widgets/kis_curve_widget_p.h index fe40104e8b..0e1c9470d1 100644 --- a/libs/ui/widgets/kis_curve_widget_p.h +++ b/libs/ui/widgets/kis_curve_widget_p.h @@ -1,276 +1,280 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CURVE_WIDGET_P_H_ #define _KIS_CURVE_WIDGET_P_H_ #include #include #include #include enum enumState { ST_NORMAL, ST_DRAG }; /** * Private members for KisCurveWidget class */ class Q_DECL_HIDDEN KisCurveWidget::Private { KisCurveWidget *m_curveWidget; KisSpinBoxSplineUnitConverter unitConverter; public: Private(KisCurveWidget *parent); /* Dragging variables */ int m_grab_point_index; double m_grabOffsetX; double m_grabOffsetY; double m_grabOriginalX; double m_grabOriginalY; QPointF m_draggedAwayPoint; int m_draggedAwayPointIndex; bool m_readOnlyMode; bool m_guideVisible; QColor m_colorGuide; /* The curve itself */ bool m_splineDirty; KisCubicCurve m_curve; QPixmap m_pix; QPixmap m_pixmapBase; bool m_pixmapDirty; QPixmap *m_pixmapCache; /* In/Out controls */ QSpinBox *m_intIn; QSpinBox *m_intOut; /* Working range of them */ int m_inMin; int m_inMax; int m_outMin; int m_outMax; /** * State functions. * At the moment used only for dragging. */ enumState m_state; inline void setState(enumState st); inline enumState state() const; + /** + * Compresses the modified() signals + */ + KisThreadSafeSignalCompressor m_modifiedSignalsCompressor; /*** Internal routines ***/ /** * Common update routines */ void setCurveModified(bool rewriteSpinBoxesValues); void setCurveRepaint(); /** * Convert working range of * In/Out controls to normalized * range of spline (and reverse) * See notes on KisSpinBoxSplineUnitConverter */ double io2sp(int x, int min, int max); int sp2io(double x, int min, int max); /** * Check whether newly created/moved point @p pt doesn't overlap * with any of existing ones from @p m_points and adjusts its coordinates. * @p skipIndex is the index of the point, that shouldn't be taken * into account during the search * (e.g. because it's @p pt itself) * * Returns false in case the point can't be placed anywhere * without overlapping */ bool jumpOverExistingPoints(QPointF &pt, int skipIndex); /** * Synchronize In/Out spinboxes with the curve */ void syncIOControls(); /** * Find the nearest point to @p pt from m_points */ int nearestPointInRange(QPointF pt, int wWidth, int wHeight) const; /** * Nothing to be said! =) */ inline void drawGrid(QPainter &p, int wWidth, int wHeight); }; KisCurveWidget::Private::Private(KisCurveWidget *parent) + : m_modifiedSignalsCompressor(100, KisSignalCompressor::Mode::FIRST_INACTIVE) { m_curveWidget = parent; } double KisCurveWidget::Private::io2sp(int x, int min, int max) { return unitConverter.io2sp(x, min, max); } int KisCurveWidget::Private::sp2io(double x, int min, int max) { return unitConverter.sp2io(x, min, max); } bool KisCurveWidget::Private::jumpOverExistingPoints(QPointF &pt, int skipIndex) { Q_FOREACH (const QPointF &it, m_curve.points()) { if (m_curve.points().indexOf(it) == skipIndex) continue; if (fabs(it.x() - pt.x()) < POINT_AREA) { pt.rx() = pt.x() >= it.x() ? it.x() + POINT_AREA : it.x() - POINT_AREA; } } return (pt.x() >= 0 && pt.x() <= 1.); } int KisCurveWidget::Private::nearestPointInRange(QPointF pt, int wWidth, int wHeight) const { double nearestDistanceSquared = 1000; int nearestIndex = -1; int i = 0; Q_FOREACH (const QPointF & point, m_curve.points()) { double distanceSquared = (pt.x() - point.x()) * (pt.x() - point.x()) + (pt.y() - point.y()) * (pt.y() - point.y()); if (distanceSquared < nearestDistanceSquared) { nearestIndex = i; nearestDistanceSquared = distanceSquared; } ++i; } if (nearestIndex >= 0) { if (fabs(pt.x() - m_curve.points()[nearestIndex].x()) *(wWidth - 1) < 5 && fabs(pt.y() - m_curve.points()[nearestIndex].y()) *(wHeight - 1) < 5) { return nearestIndex; } } return -1; } #define div2_round(x) (((x)+1)>>1) #define div4_round(x) (((x)+2)>>2) void KisCurveWidget::Private::drawGrid(QPainter &p, int wWidth, int wHeight) { /** * Hint: widget size should conform * formula 4n+5 to draw grid correctly * without curious shifts between * spline and it caused by rounding * * That is not mandatory but desirable */ QPalette appPalette = QApplication::palette(); p.setPen(QPen(appPalette.color(QPalette::Background), 1, Qt::SolidLine)); p.drawLine(div4_round(wWidth), 0, div4_round(wWidth), wHeight); p.drawLine(div2_round(wWidth), 0, div2_round(wWidth), wHeight); p.drawLine(div4_round(3*wWidth), 0, div4_round(3*wWidth), wHeight); p.drawLine(0, div4_round(wHeight), wWidth, div4_round(wHeight)); p.drawLine(0, div2_round(wHeight), wWidth, div2_round(wHeight)); p.drawLine(0, div4_round(3*wHeight), wWidth, div4_round(3*wHeight)); } void KisCurveWidget::Private::syncIOControls() { if (!m_intIn || !m_intOut) return; bool somethingSelected = (m_grab_point_index >= 0); m_intIn->setEnabled(somethingSelected); m_intOut->setEnabled(somethingSelected); if (m_grab_point_index >= 0) { m_intIn->blockSignals(true); m_intOut->blockSignals(true); m_intIn->setValue(sp2io(m_curve.points()[m_grab_point_index].x(), m_inMin, m_inMax)); m_intOut->setValue(sp2io(m_curve.points()[m_grab_point_index].y(), m_outMin, m_outMax)); m_intIn->blockSignals(false); m_intOut->blockSignals(false); } else { /*FIXME: Ideally, these controls should hide away now */ } } void KisCurveWidget::Private::setCurveModified(bool rewriteSpinBoxesValues = true) { - if (rewriteSpinBoxesValues) { syncIOControls(); } m_splineDirty = true; m_curveWidget->update(); - m_curveWidget->emit modified(); + m_curveWidget->emit compressorShouldEmitModified(); } void KisCurveWidget::Private::setCurveRepaint() { m_curveWidget->update(); } void KisCurveWidget::Private::setState(enumState st) { m_state = st; } enumState KisCurveWidget::Private::state() const { return m_state; } #endif /* _KIS_CURVE_WIDGET_P_H_ */ diff --git a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp index b1c318594d..2655434f52 100644 --- a/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp @@ -1,295 +1,299 @@ /* * Copyright (c) 2004,2007,2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "kis_auto_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_signals_blocker.h" #include "kis_signal_compressor.h" #include "kis_aspect_ratio_locker.h" #define showSlider(input, step) input->setRange(input->minimum(), input->maximum(), step) #include KisAutoBrushWidget::KisAutoBrushWidget(QWidget *parent, const char* name) : KisWdgAutoBrush(parent, name) , m_autoBrush(0) , m_updateCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE)) , m_fadeAspectLocker(new KisAspectRatioLocker()) { connect(m_updateCompressor.data(), SIGNAL(timeout()), SLOT(paramChanged())); connect((QObject*)comboBoxShape, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); inputRadius->setRange(0.01, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); inputRadius->setExponentRatio(3.0); inputRadius->setSingleStep(1); inputRadius->setValue(5); inputRadius->setSuffix(i18n(" px")); inputRadius->setBlockUpdateSignalOnDrag(true); connect(inputRadius, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputRatio->setRange(0.0, 1.0, 2); inputRatio->setSingleStep(0.1); inputRatio->setValue(1.0); inputRatio->setBlockUpdateSignalOnDrag(true); connect(inputRatio, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputHFade->setRange(0.0, 1.0, 2); inputHFade->setSingleStep(0.1); inputHFade->setValue(0.5); inputVFade->setRange(0.0, 1.0, 2); inputVFade->setSingleStep(0.1); inputVFade->setValue(0.5); aspectButton->setKeepAspectRatio(true); m_fadeAspectLocker->connectSpinBoxes(inputHFade, inputVFade, aspectButton); m_fadeAspectLocker->setBlockUpdateSignalOnDrag(true); connect(m_fadeAspectLocker.data(), SIGNAL(sliderValueChanged()), m_updateCompressor.data(), SLOT(start())); connect(m_fadeAspectLocker.data(), SIGNAL(aspectButtonChanged()), m_updateCompressor.data(), SLOT(start())); inputSpikes->setRange(2, 20); inputSpikes->setValue(2); inputSpikes->setBlockUpdateSignalOnDrag(true); connect(inputSpikes, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); inputRandomness->setRange(0, 100); inputRandomness->setValue(0); inputRandomness->setBlockUpdateSignalOnDrag(true); connect(inputRandomness, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); inputAngle->setRange(0, 360); inputAngle->setSuffix(QChar(Qt::Key_degree)); inputAngle->setValue(0); inputAngle->setBlockUpdateSignalOnDrag(true); connect(inputAngle, SIGNAL(valueChanged(int)), m_updateCompressor.data(), SLOT(start())); connect(spacingWidget, SIGNAL(sigSpacingChanged()), m_updateCompressor.data(), SLOT(start())); density->setRange(0, 100, 0); density->setSingleStep(1); density->setValue(100); density->setSuffix(i18n("%")); density->setBlockUpdateSignalOnDrag(true); connect(density, SIGNAL(valueChanged(qreal)), m_updateCompressor.data(), SLOT(start())); KisCubicCurve topLeftBottomRightLinearCurve; topLeftBottomRightLinearCurve.setPoint(0, QPointF(0.0, 1.0)); topLeftBottomRightLinearCurve.setPoint(1, QPointF(1.0, 0.0)); + + bool blockedBefore = softnessCurve->blockSignals(true); softnessCurve->setCurve(topLeftBottomRightLinearCurve); + softnessCurve->blockSignals(blockedBefore); + connect(softnessCurve, SIGNAL(modified()), m_updateCompressor.data(), SLOT(start())); m_brush = QImage(1, 1, QImage::Format_RGB32); connect(brushPreview, SIGNAL(clicked()), m_updateCompressor.data(), SLOT(start())); QList ids = KisMaskGenerator::maskGeneratorIds(); for (int i = 0; i < ids.size(); i++) { comboBoxMaskType->insertItem(i, ids[i].name()); } connect(comboBoxMaskType, SIGNAL(activated(int)), m_updateCompressor.data(), SLOT(start())); connect(comboBoxMaskType, SIGNAL(currentIndexChanged(int)), SLOT(setStackedWidget(int))); setStackedWidget(comboBoxMaskType->currentIndex()); brushPreview->setIconSize(QSize(100, 100)); connect(btnAntialiasing, SIGNAL(toggled(bool)), m_updateCompressor.data(), SLOT(start())); m_updateCompressor->start(); } KisAutoBrushWidget::~KisAutoBrushWidget() { } void KisAutoBrushWidget::resizeEvent(QResizeEvent *) { brushPreview->setMinimumHeight(brushPreview->width()); // dirty hack ! brushPreview->setMaximumHeight(brushPreview->width()); // dirty hack ! } void KisAutoBrushWidget::activate() { m_updateCompressor->start(); } void KisAutoBrushWidget::paramChanged() { KisMaskGenerator* kas; bool antialiasEdges = btnAntialiasing->isChecked(); if (comboBoxMaskType->currentIndex() == 2) { // gaussian brush if (comboBoxShape->currentIndex() == 0) { kas = new KisGaussCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisGaussRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } else if (comboBoxMaskType->currentIndex() == 1) { // soft brush if (comboBoxShape->currentIndex() == 0) { kas = new KisCurveCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } else { kas = new KisCurveRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), softnessCurve->curve(), antialiasEdges); } } else {// default == 0 or any other if (comboBoxShape->currentIndex() == 0) { // use index compare instead of comparing a translatable string kas = new KisCircleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } else { kas = new KisRectangleMaskGenerator(inputRadius->value(), inputRatio->value(), inputHFade->value(), inputVFade->value(), inputSpikes->value(), antialiasEdges); } } Q_CHECK_PTR(kas); m_autoBrush = new KisAutoBrush(kas, inputAngle->value() / 180.0 * M_PI, inputRandomness->value() / 100.0, density->value() / 100.0); m_autoBrush->setSpacing(spacingWidget->spacing()); m_autoBrush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush = m_autoBrush->image(); drawBrushPreviewArea(); emit sigBrushChanged(); } void KisAutoBrushWidget::drawBrushPreviewArea() { QImage pi(m_brush); double coeff = 1.0; int bPw = brushPreview->width() - 3; if (pi.width() > bPw) { coeff = bPw / (double)pi.width(); } int bPh = brushPreview->height() - 3; if (pi.height() > coeff * bPh) { coeff = bPh / (double)pi.height(); } if (coeff < 1.0) { pi = pi.scaled((int)(coeff * pi.width()) , (int)(coeff * pi.height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QPixmap p = QPixmap::fromImage(pi); brushPreview->setIcon(QIcon(p)); } void KisAutoBrushWidget::setStackedWidget(int index) { if (index == 1) { stackedWidget->setCurrentIndex(1); } else { stackedWidget->setCurrentIndex(0); } } KisBrushSP KisAutoBrushWidget::brush() { return m_autoBrush; } void KisAutoBrushWidget::setBrush(KisBrushSP brush) { m_autoBrush = brush; m_brush = brush->image(); // XXX: lock, set and unlock the widgets. KisAutoBrush* aBrush = dynamic_cast(brush.data()); KisSignalsBlocker b1(comboBoxShape, comboBoxMaskType); KisSignalsBlocker b2(inputRadius, inputRatio, inputHFade, inputVFade, inputAngle, inputSpikes); KisSignalsBlocker b3(spacingWidget, inputRandomness, density, softnessCurve, btnAntialiasing); if (aBrush->maskGenerator()->type() == KisMaskGenerator::CIRCLE) { comboBoxShape->setCurrentIndex(0); } else if (aBrush->maskGenerator()->type() == KisMaskGenerator::RECTANGLE) { comboBoxShape->setCurrentIndex(1); } else { comboBoxShape->setCurrentIndex(2); } const int mastTypeIndex = comboBoxMaskType->findText(aBrush->maskGenerator()->name()); comboBoxMaskType->setCurrentIndex(mastTypeIndex); setStackedWidget(mastTypeIndex); // adjusting manually because the signals are blocked inputRadius->setValue(aBrush->maskGenerator()->diameter()); inputRatio->setValue(aBrush->maskGenerator()->ratio()); inputHFade->setValue(aBrush->maskGenerator()->horizontalFade()); inputVFade->setValue(aBrush->maskGenerator()->verticalFade()); inputAngle->setValue(aBrush->angle() * 180 / M_PI); inputSpikes->setValue(aBrush->maskGenerator()->spikes()); spacingWidget->setSpacing(aBrush->autoSpacingActive(), aBrush->autoSpacingActive() ? aBrush->autoSpacingCoeff() : aBrush->spacing()); inputRandomness->setValue(aBrush->randomness() * 100); density->setValue(aBrush->density() * 100); if (!aBrush->maskGenerator()->curveString().isEmpty()) { KisCubicCurve curve; curve.fromString(aBrush->maskGenerator()->curveString()); softnessCurve->setCurve(curve); } btnAntialiasing->setChecked(aBrush->maskGenerator()->antialiasEdges()); drawBrushPreviewArea(); // sync up what the brush preview area looks like } void KisAutoBrushWidget::setBrushSize(qreal dxPixels, qreal dyPixels) { Q_UNUSED(dyPixels); qreal newWidth = inputRadius->value() + dxPixels; newWidth = qMax(newWidth, qreal(0.1)); inputRadius->setValue(newWidth); } QSizeF KisAutoBrushWidget::brushSize() const { return QSizeF(inputRadius->value(), inputRadius->value() * inputRatio->value()); } #include "moc_kis_auto_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_curve_option.cpp b/plugins/paintops/libpaintop/kis_curve_option.cpp index df291d8e74..74479cc67b 100644 --- a/plugins/paintops/libpaintop/kis_curve_option.cpp +++ b/plugins/paintops/libpaintop/kis_curve_option.cpp @@ -1,463 +1,468 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_curve_option.h" #include KisCurveOption::KisCurveOption(const QString& name, KisPaintOpOption::PaintopCategory category, bool checked, qreal value, qreal min, qreal max) : m_name(name) , m_category(category) , m_checkable(true) , m_checked(checked) , m_useCurve(true) , m_useSameCurve(true) , m_separateCurveValue(false) , m_curveMode(0) { Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) { KisDynamicSensorSP sensor = KisDynamicSensor::type2Sensor(sensorType, m_name); sensor->setActive(false); replaceSensor(sensor); } m_sensorMap[PRESSURE]->setActive(true); setValueRange(min, max); setValue(value); QList points; - points.push_back(QPointF(0,1)); + // needs to be set to something, weird curve is better for debugging + // it will be reset to the curve from the preset anyway though + points.push_back(QPointF(0,0)); points.push_back(QPointF(0.25,0.9)); - points.push_back(QPointF(0.75,0.1)); + points.push_back(QPointF(0.5,0)); + points.push_back(QPointF(0.75,0.6)); points.push_back(QPointF(1,0)); m_commonCurve = KisCubicCurve(points); } KisCurveOption::~KisCurveOption() { } const QString& KisCurveOption::name() const { return m_name; } KisPaintOpOption::PaintopCategory KisCurveOption::category() const { return m_category; } qreal KisCurveOption::minValue() const { return m_minValue; } qreal KisCurveOption::maxValue() const { return m_maxValue; } qreal KisCurveOption::value() const { return m_value; } void KisCurveOption::resetAllSensors() { Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) { if (sensor->isActive()) { sensor->reset(); } } } void KisCurveOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { if (m_checkable) { setting->setProperty("Pressure" + m_name, isChecked()); } if (activeSensors().size() == 1) { setting->setProperty(m_name + "Sensor", activeSensors().first()->toXML()); } else { QDomDocument doc = QDomDocument("params"); QDomElement root = doc.createElement("params"); doc.appendChild(root); root.setAttribute("id", "sensorslist"); Q_FOREACH (KisDynamicSensorSP sensor, activeSensors()) { QDomElement childelt = doc.createElement("ChildSensor"); sensor->toXML(doc, childelt); root.appendChild(childelt); } setting->setProperty(m_name + "Sensor", doc.toString()); } setting->setProperty(m_name + "UseCurve", m_useCurve); setting->setProperty(m_name + "UseSameCurve", m_useSameCurve); setting->setProperty(m_name + "Value", m_value); setting->setProperty(m_name + "curveMode", m_curveMode); + setting->setProperty(m_name + "commonCurve", qVariantFromValue(m_commonCurve)); } void KisCurveOption::readOptionSetting(KisPropertiesConfigurationSP setting) { - m_curveCache.clear(); readNamedOptionSetting(m_name, setting); } void KisCurveOption::lodLimitations(KisPaintopLodLimitations *l) const { Q_UNUSED(l); } int KisCurveOption::intMinValue() const { return 0; } int KisCurveOption::intMaxValue() const { return 100; } QString KisCurveOption::valueSuffix() const { return i18n("%"); } void KisCurveOption::readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting) { if (!setting) return; + + KisCubicCurve commonCurve = m_commonCurve; + //dbgKrita << "readNamedOptionSetting" << prefix; // setting->dump(); if (m_checkable) { setChecked(setting->getBool("Pressure" + prefix, false)); } //dbgKrita << "\tPressure" + prefix << isChecked(); m_sensorMap.clear(); // Replace all sensors with the inactive defaults Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) { replaceSensor(KisDynamicSensor::type2Sensor(sensorType, m_name)); } QString sensorDefinition = setting->getString(prefix + "Sensor"); if (!sensorDefinition.contains("sensorslist")) { KisDynamicSensorSP s = KisDynamicSensor::createFromXML(sensorDefinition, m_name); if (s) { replaceSensor(s); s->setActive(true); + commonCurve = s->curve(); //dbgKrita << "\tsingle sensor" << s::id(s->sensorType()) << s->isActive() << "added"; } } else { QDomDocument doc; doc.setContent(sensorDefinition); QDomElement elt = doc.documentElement(); QDomNode node = elt.firstChild(); while (!node.isNull()) { if (node.isElement()) { QDomElement childelt = node.toElement(); if (childelt.tagName() == "ChildSensor") { KisDynamicSensorSP s = KisDynamicSensor::createFromXML(childelt, m_name); if (s) { replaceSensor(s); s->setActive(true); + commonCurve = s->curve(); //dbgKrita << "\tchild sensor" << s::id(s->sensorType()) << s->isActive() << "added"; } } } node = node.nextSibling(); } } + + m_useSameCurve = setting->getBool(m_name + "UseSameCurve", true); + // Only load the old curve format if the curve wasn't saved by the sensor // This will give every sensor the same curve. //dbgKrita << ">>>>>>>>>>>" << prefix + "Sensor" << setting->getString(prefix + "Sensor"); if (!setting->getString(prefix + "Sensor").contains("curve")) { //dbgKrita << "\told format"; if (setting->getBool("Custom" + prefix, false)) { Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) { s->setCurve(setting->getCubicCurve("Curve" + prefix)); + commonCurve = s->curve(); } } } + if (m_useSameCurve) { + m_commonCurve = setting->getCubicCurve(prefix + "commonCurve", commonCurve); + } + // At least one sensor needs to be active if (activeSensors().size() == 0) { m_sensorMap[PRESSURE]->setActive(true); } m_value = setting->getDouble(m_name + "Value", m_maxValue); //dbgKrita << "\t" + m_name + "Value" << m_value; m_useCurve = setting->getBool(m_name + "UseCurve", true); //dbgKrita << "\t" + m_name + "UseCurve" << m_useSameCurve; - m_useSameCurve = setting->getBool(m_name + "UseSameCurve", true); + //dbgKrita << "\t" + m_name + "UseSameCurve" << m_useSameCurve; m_curveMode = setting->getInt(m_name + "curveMode"); //dbgKrita << "-----------------"; } void KisCurveOption::replaceSensor(KisDynamicSensorSP s) { Q_ASSERT(s); m_sensorMap[s->sensorType()] = s; } KisDynamicSensorSP KisCurveOption::sensor(DynamicSensorType sensorType, bool active) const { if (m_sensorMap.contains(sensorType)) { if (!active) { return m_sensorMap[sensorType]; } else { if (m_sensorMap[sensorType]->isActive()) { return m_sensorMap[sensorType]; } } } return 0; } bool KisCurveOption::isRandom() const { return bool(sensor(FUZZY_PER_DAB, true)) || bool(sensor(FUZZY_PER_STROKE, true)); } bool KisCurveOption::isCurveUsed() const { return m_useCurve; } bool KisCurveOption::isSameCurveUsed() const { return m_useSameCurve; } int KisCurveOption::getCurveMode() const { return m_curveMode; } KisCubicCurve KisCurveOption::getCommonCurve() const { return m_commonCurve; } void KisCurveOption::setSeparateCurveValue(bool separateCurveValue) { m_separateCurveValue = separateCurveValue; } bool KisCurveOption::isCheckable() { return m_checkable; } bool KisCurveOption::isChecked() const { return m_checked; } void KisCurveOption::setChecked(bool checked) { m_checked = checked; } void KisCurveOption::setCurveUsed(bool useCurve) { m_useCurve = useCurve; } void KisCurveOption::setCurveMode(int mode) { m_curveMode = mode; } +void KisCurveOption::setUseSameCurve(bool useSameCurve) +{ + m_useSameCurve = useSameCurve; +} + void KisCurveOption::setCommonCurve(KisCubicCurve curve) { m_commonCurve = curve; } void KisCurveOption::setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve) { - // No switch in state, don't mess with the cache if (useSameCurve == m_useSameCurve) { if (useSameCurve) { m_commonCurve = curve; } else { KisDynamicSensorSP s = sensor(sensorType, false); if (s) { s->setCurve(curve); } } } else { - // moving from not use same curve to use same curve: backup the custom curves if (!m_useSameCurve && useSameCurve) { - // Copy the custom curves to the cache and set the new curve on all sensors, active or not - m_curveCache.clear(); - Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) { - m_curveCache[s->sensorType()] = s->curve(); - } m_commonCurve = curve; } else { //if (m_useSameCurve && !useSameCurve) - // Restore the cached curves KisDynamicSensorSP s = 0; - Q_FOREACH (DynamicSensorType sensorType, m_curveCache.keys()) { - if (m_sensorMap.contains(sensorType)) { - s = m_sensorMap[sensorType]; - } - else { - s = KisDynamicSensor::type2Sensor(sensorType, m_name); - } - m_sensorMap[sensorType] = s; - } - s = 0; // And set the current sensor to the current curve if (!m_sensorMap.contains(sensorType)) { s = KisDynamicSensor::type2Sensor(sensorType, m_name); + } else { + KisDynamicSensorSP s = sensor(sensorType, false); } if (s) { s->setCurve(curve); } } m_useSameCurve = useSameCurve; } } void KisCurveOption::setValueRange(qreal min, qreal max) { m_minValue = qMin(min, max); m_maxValue = qMax(min, max); } void KisCurveOption::setValue(qreal value) { m_value = qBound(m_minValue, value, m_maxValue); } KisCurveOption::ValueComponents KisCurveOption::computeValueComponents(const KisPaintInformation& info) const { ValueComponents components; if (m_useCurve) { QMap::const_iterator i; QList sensorValues; for (i = m_sensorMap.constBegin(); i != m_sensorMap.constEnd(); ++i) { KisDynamicSensorSP s(i.value()); if (s->isActive()) { qreal valueFromCurve = m_useSameCurve ? s->parameter(info, m_commonCurve, true) : s->parameter(info); if (s->isAdditive()) { components.additive += valueFromCurve; components.hasAdditive = true; } else if (s->isAbsoluteRotation()) { components.absoluteOffset = valueFromCurve; components.hasAbsoluteOffset =true; } else { sensorValues << valueFromCurve; components.hasScaling = true; } } } if (sensorValues.count() == 1) { components.scaling = sensorValues.first(); } else { if (m_curveMode == 1){ // add components.scaling = 0; double i; foreach (i, sensorValues) { components.scaling += i; } } else if (m_curveMode == 2){ //max components.scaling = *std::max_element(sensorValues.begin(), sensorValues.end()); } else if (m_curveMode == 3){ //min components.scaling = *std::min_element(sensorValues.begin(), sensorValues.end()); } else if (m_curveMode == 4){ //difference double max = *std::max_element(sensorValues.begin(), sensorValues.end()); double min = *std::min_element(sensorValues.begin(), sensorValues.end()); components.scaling = max-min; } else { //multuply - default double i; foreach (i, sensorValues) { components.scaling *= i; } } } } if (!m_separateCurveValue) { components.constant = m_value; } components.minSizeLikeValue = m_minValue; components.maxSizeLikeValue = m_maxValue; return components; } qreal KisCurveOption::computeSizeLikeValue(const KisPaintInformation& info) const { const ValueComponents components = computeValueComponents(info); return components.sizeLikeValue(); } qreal KisCurveOption::computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const { const ValueComponents components = computeValueComponents(info); return components.rotationLikeValue(baseValue, absoluteAxesFlipped); } QList KisCurveOption::sensors() { //dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active."; return m_sensorMap.values(); } QList KisCurveOption::activeSensors() const { QList sensorList; Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) { if (sensor->isActive()) { sensorList << sensor; } } //dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active."; return sensorList; } diff --git a/plugins/paintops/libpaintop/kis_curve_option.h b/plugins/paintops/libpaintop/kis_curve_option.h index 59ace97cf5..22df067944 100644 --- a/plugins/paintops/libpaintop/kis_curve_option.h +++ b/plugins/paintops/libpaintop/kis_curve_option.h @@ -1,231 +1,235 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_CURVE_OPTION_H #define KIS_CURVE_OPTION_H #include #include #include "kis_paintop_option.h" #include "kis_global.h" #include #include "kritapaintop_export.h" #include "kis_dynamic_sensor.h" class KisDynamicSensor; /** * KisCurveOption is the base class for paintop options that are * defined through one or more curves. * * Note: it is NOT a KisPaintOpOption, even though the API is pretty similar! * * KisCurveOption classes have a generic GUI widget, KisCurveOptionWidget. So, * in contrast to KisPaintOpOption classes, KisCurveOption instances can and * will be created in the constructor of KisPaintOp paintops. This class can * manage to read and write its settings directly. * */ class PAINTOP_EXPORT KisCurveOption { public: KisCurveOption(const QString& name, KisPaintOpOption::PaintopCategory category, bool checked, qreal value = 1.0, qreal min = 0.0, qreal max = 1.0); virtual ~KisCurveOption(); virtual void writeOptionSetting(KisPropertiesConfigurationSP setting) const; virtual void readOptionSetting(KisPropertiesConfigurationSP setting); virtual void lodLimitations(KisPaintopLodLimitations *l) const; //Please override for other values than 0-100 and % virtual int intMinValue()const; virtual int intMaxValue()const; virtual QString valueSuffix()const; const QString& name() const; KisPaintOpOption::PaintopCategory category() const; qreal minValue() const; qreal maxValue() const; qreal value() const; void resetAllSensors(); KisDynamicSensorSP sensor(DynamicSensorType sensorType, bool active) const; void replaceSensor(KisDynamicSensorSP sensor); QList sensors(); QList activeSensors() const; bool isCheckable(); bool isChecked() const; bool isCurveUsed() const; bool isSameCurveUsed() const; bool isRandom() const; int getCurveMode() const; /** * Returns the curve that is being used instead of sensor ones * in case "Use the same curve" is checked. */ KisCubicCurve getCommonCurve() const; void setSeparateCurveValue(bool separateCurveValue); void setChecked(bool checked); void setCurveUsed(bool useCurve); void setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve); void setValue(qreal value); void setCurveMode(int mode); + /** + * Sets the bool indicating whether "Share curve across all settings" is checked. + */ + void setUseSameCurve(bool useSameCurve); + /** * Sets the curve that is being used instead of sensor ones - * in case "Use the same curve" is checked. + * in case "Share curve across all settings" is checked. */ void setCommonCurve(KisCubicCurve curve); struct ValueComponents { ValueComponents() : constant(1.0), scaling(1.0), additive(0.0), absoluteOffset(0.0), hasAbsoluteOffset(false), hasScaling(false), hasAdditive(false) { } qreal constant; qreal scaling; qreal additive; qreal absoluteOffset; bool hasAbsoluteOffset; bool hasScaling; bool hasAdditive; qreal minSizeLikeValue; qreal maxSizeLikeValue; /** * @param normalizedBaseAngle canvas rotation angle normalized to range [0; 1] * @param absoluteAxesFlipped true if underlying image coordinate system is flipped (horiz. mirror != vert. mirror) */ qreal rotationLikeValue(qreal normalizedBaseAngle, bool absoluteAxesFlipped) const { const qreal offset = !hasAbsoluteOffset ? normalizedBaseAngle : absoluteAxesFlipped ? 1.0 - absoluteOffset : absoluteOffset; const qreal realScalingPart = hasScaling ? KisDynamicSensor::scalingToAdditive(scaling) : 0.0; const qreal realAdditivePart = hasAdditive ? additive : 0; qreal value = wrapInRange(2 * offset + constant * (realScalingPart + realAdditivePart), -1.0, 1.0); if (qIsNaN(value)) { qWarning() << "rotationLikeValue returns NaN!" << normalizedBaseAngle << absoluteAxesFlipped; value = 0; } return value; } qreal sizeLikeValue() const { const qreal offset = hasAbsoluteOffset ? absoluteOffset : 1.0; const qreal realScalingPart = hasScaling ? scaling : 1.0; const qreal realAdditivePart = hasAdditive ? KisDynamicSensor::additiveToScaling(additive) : 1.0; return qBound(minSizeLikeValue, constant * offset * realScalingPart * realAdditivePart, maxSizeLikeValue); } private: static inline qreal wrapInRange(qreal x, qreal min, qreal max) { const qreal range = max - min; x -= min; if (x < 0.0) { x = range + fmod(x, range); } if (x > range) { x = fmod(x, range); } return x + min; } }; /** * Uses the curves set on the sensors to compute a single * double value that can control the parameters of a brush. * * This value is derives from the values stored in * ValuesComponents object. */ ValueComponents computeValueComponents(const KisPaintInformation& info) const; qreal computeSizeLikeValue(const KisPaintInformation &info) const; qreal computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const; protected: void setValueRange(qreal min, qreal max); /** * Read the option using the prefix in argument */ void readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting); QString m_name; KisPaintOpOption::PaintopCategory m_category; bool m_checkable; bool m_checked; bool m_useCurve; bool m_useSameCurve; bool m_separateCurveValue; /** * Curve that is being used instead of sensors' internal ones * in case "Use the same curve" is checked. */ KisCubicCurve m_commonCurve; int m_curveMode; QMap m_sensorMap; - QMap m_curveCache; private: qreal m_value; qreal m_minValue; qreal m_maxValue; }; #endif diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp index 8059603456..52be81b200 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp @@ -1,340 +1,360 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_curve_option_widget.h" #include "ui_wdgcurveoption.h" #include "widgets/kis_curve_widget.h" #include "kis_dynamic_sensor.h" #include "kis_global.h" #include "kis_curve_option.h" #include "kis_signals_blocker.h" #include "kis_icon_utils.h" inline void setLabel(QLabel* label, const KisCurveLabel& curve_label) { if (curve_label.icon().isNull()) { label->setText(curve_label.name()); } else { label->setPixmap(QPixmap::fromImage(curve_label.icon())); } } KisCurveOptionWidget::KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider) : KisPaintOpOption(curveOption->category(), curveOption->isChecked()) , m_widget(new QWidget) , m_curveOptionWidget(new Ui_WdgCurveOption()) , m_curveOption(curveOption) { setObjectName("KisCurveOptionWidget"); m_curveOptionWidget->setupUi(m_widget); setConfigurationPage(m_widget); m_curveOptionWidget->sensorSelector->setCurveOption(curveOption); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(slotModified())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(emitSettingChanged())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(updateLabelsOfCurrentSensor())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateSensorCurveLabels(KisDynamicSensorSP))); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateCurve(KisDynamicSensorSP))); - connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotStateChanged())); + connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotUseSameCurveChanged())); // set all the icons for the curve preset shapes updateThemedIcons(); // various curve preset buttons with predefined curves connect(m_curveOptionWidget->linearCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLinear())); connect(m_curveOptionWidget->revLinearButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseLinear())); connect(m_curveOptionWidget->jCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveJShape())); connect(m_curveOptionWidget->lCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLShape())); connect(m_curveOptionWidget->sCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveSShape())); connect(m_curveOptionWidget->reverseSCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseSShape())); connect(m_curveOptionWidget->uCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveUShape())); connect(m_curveOptionWidget->revUCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveArchShape())); m_curveOptionWidget->label_ymin->setText(minLabel); m_curveOptionWidget->label_ymax->setText(maxLabel); // strength settings is shown as 0-100% m_curveOptionWidget->strengthSlider->setRange(curveOption->minValue()*100, curveOption->maxValue()*100, 0); m_curveOptionWidget->strengthSlider->setValue(curveOption->value()*100); m_curveOptionWidget->strengthSlider->setPrefix(i18n("Strength: ")); m_curveOptionWidget->strengthSlider->setSuffix(i18n("%")); if (hideSlider) { m_curveOptionWidget->strengthSlider->hide(); } connect(m_curveOptionWidget->checkBoxUseCurve, SIGNAL(stateChanged(int)) , SLOT(updateValues())); connect(m_curveOptionWidget->curveMode, SIGNAL(currentIndexChanged(int)), SLOT(updateMode())); connect(m_curveOptionWidget->strengthSlider, SIGNAL(valueChanged(qreal)), SLOT(updateValues())); } KisCurveOptionWidget::~KisCurveOptionWidget() { delete m_curveOption; delete m_curveOptionWidget; } void KisCurveOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP setting) const { m_curveOption->writeOptionSetting(setting); } void KisCurveOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { //setting->dump(); m_curveOption->readOptionSetting(setting); + // Signals needs to be blocked, otherwise checking the checkbox will trigger + // setting the common curve to the widget curve, which is incorrect in this case. + bool blockedBefore = m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(true); + m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed()); + m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(blockedBefore); + m_curveOptionWidget->checkBoxUseCurve->setChecked(m_curveOption->isCurveUsed()); m_curveOptionWidget->strengthSlider->setValue(m_curveOption->value()*100); - m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed()); m_curveOptionWidget->curveMode->setCurrentIndex(m_curveOption->getCurveMode()); disableWidgets(!m_curveOption->isCurveUsed()); m_curveOptionWidget->sensorSelector->reload(); m_curveOptionWidget->sensorSelector->setCurrent(m_curveOption->activeSensors().first()); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); - - if (m_curveOption->isSameCurveUsed()) { - m_curveOption->setCommonCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()->curve()); - } updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } void KisCurveOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const { m_curveOption->lodLimitations(l); } bool KisCurveOptionWidget::isCheckable() const { return m_curveOption->isCheckable(); } bool KisCurveOptionWidget::isChecked() const { return m_curveOption->isChecked(); } void KisCurveOptionWidget::setChecked(bool checked) { m_curveOption->setChecked(checked); } KisCurveOption* KisCurveOptionWidget::curveOption() { return m_curveOption; } QWidget* KisCurveOptionWidget::curveWidget() { return m_widget; } void KisCurveOptionWidget::slotModified() { - transferCurve(); -} - -void KisCurveOptionWidget::slotStateChanged() -{ - transferCurve(); + if (!m_curveOption->isSameCurveUsed()) { + m_curveOptionWidget->sensorSelector->currentHighlighted()->setCurve(getWidgetCurve()); + } else { + m_curveOption->setCommonCurve(getWidgetCurve()); + } + emitSettingChanged(); } - -void KisCurveOptionWidget::transferCurve() +void KisCurveOptionWidget::slotUseSameCurveChanged() { - m_curveOptionWidget->sensorSelector->setCurrentCurve(m_curveOptionWidget->curveWidget->curve(), m_curveOptionWidget->checkBoxUseSameCurve->isChecked()); + // this is a slot that answers on "Share Curve across all settings" checkbox + m_curveOption->setUseSameCurve(m_curveOptionWidget->checkBoxUseSameCurve->isChecked()); + if (m_curveOption->isSameCurveUsed()) { + // !(UseSameCurve) => UseSameCurve + // set the current curve to the common curve + m_curveOption->setCommonCurve(getWidgetCurve()); + } else { + updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); + } emitSettingChanged(); } void KisCurveOptionWidget::updateSensorCurveLabels(KisDynamicSensorSP sensor) { if (sensor) { m_curveOptionWidget->label_xmin->setText(KisDynamicSensor::minimumLabel(sensor->sensorType())); m_curveOptionWidget->label_xmax->setText(KisDynamicSensor::maximumLabel(sensor->sensorType(), sensor->length())); int inMinValue = KisDynamicSensor::minimumValue(sensor->sensorType()); int inMaxValue = KisDynamicSensor::maximumValue(sensor->sensorType(), sensor->length()); QString inSuffix = KisDynamicSensor::valueSuffix(sensor->sensorType()); int outMinValue = m_curveOption->intMinValue(); int outMaxValue = m_curveOption->intMaxValue(); QString outSuffix = m_curveOption->valueSuffix(); m_curveOptionWidget->intIn->setSuffix(inSuffix); m_curveOptionWidget->intOut->setSuffix(outSuffix); m_curveOptionWidget->curveWidget->setupInOutControls(m_curveOptionWidget->intIn,m_curveOptionWidget->intOut, inMinValue,inMaxValue,outMinValue,outMaxValue); } } void KisCurveOptionWidget::updateCurve(KisDynamicSensorSP sensor) { if (sensor) { bool blockSignal = m_curveOptionWidget->curveWidget->blockSignals(true); KisCubicCurve curve = m_curveOption->isSameCurveUsed() ? m_curveOption->getCommonCurve() : sensor->curve(); m_curveOptionWidget->curveWidget->setCurve(curve); m_curveOptionWidget->curveWidget->blockSignals(blockSignal); } } void KisCurveOptionWidget::updateLabelsOfCurrentSensor() { updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } void KisCurveOptionWidget::updateValues() { m_curveOption->setValue(m_curveOptionWidget->strengthSlider->value()/100.0); // convert back to 0-1 for data m_curveOption->setCurveUsed(m_curveOptionWidget->checkBoxUseCurve->isChecked()); disableWidgets(!m_curveOptionWidget->checkBoxUseCurve->isChecked()); emitSettingChanged(); } void KisCurveOptionWidget::updateMode() { m_curveOption->setCurveMode(m_curveOptionWidget->curveMode->currentIndex()); emitSettingChanged(); } void KisCurveOptionWidget::changeCurveLinear() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseLinear() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveSShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.25,0.1)); points.push_back(QPointF(0.75,0.9)); points.push_back(QPointF(1, 1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseSShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.9)); points.push_back(QPointF(0.75,0.1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveJShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.35,0.1)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveLShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.48)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveUShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.5,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveArchShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.5,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::disableWidgets(bool disable) { m_curveOptionWidget->checkBoxUseSameCurve->setDisabled(disable); m_curveOptionWidget->curveWidget->setDisabled(disable); m_curveOptionWidget->sensorSelector->setDisabled(disable); m_curveOptionWidget->label_xmax->setDisabled(disable); m_curveOptionWidget->label_xmin->setDisabled(disable); m_curveOptionWidget->label_ymax->setDisabled(disable); m_curveOptionWidget->label_ymin->setDisabled(disable); } void KisCurveOptionWidget::updateThemedIcons() { // set all the icons for the curve preset shapes m_curveOptionWidget->linearCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear")); m_curveOptionWidget->revLinearButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear-reverse")); m_curveOptionWidget->jCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-j")); m_curveOptionWidget->lCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-l")); m_curveOptionWidget->sCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s")); m_curveOptionWidget->reverseSCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s-reverse")); m_curveOptionWidget->uCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-u")); m_curveOptionWidget->revUCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-arch")); // this helps make the checkboxes show themselves on the dark color themes QPalette pal = m_curveOptionWidget->sensorSelector->palette(); QPalette newPalette = pal; newPalette.setColor(QPalette::Active, QPalette::Background, pal.text().color() ); m_curveOptionWidget->sensorSelector->setPalette(newPalette); } + + + +KisCubicCurve KisCurveOptionWidget::getWidgetCurve() +{ + return m_curveOptionWidget->curveWidget->curve(); +} + +KisCubicCurve KisCurveOptionWidget::getHighlightedSensorCurve() +{ + return m_curveOptionWidget->sensorSelector->currentHighlighted()->curve(); +} diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/plugins/paintops/libpaintop/kis_curve_option_widget.h index e5ba4a2ccc..5c2396c1e5 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.h +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.h @@ -1,87 +1,90 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_CURVE_OPTION_WIDGET_H #define KIS_CURVE_OPTION_WIDGET_H #include class Ui_WdgCurveOption; class KisCurveOption; class QComboBox; #include class PAINTOP_EXPORT KisCurveOptionWidget : public KisPaintOpOption { Q_OBJECT public: KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider = false); ~KisCurveOptionWidget() override; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void lodLimitations(KisPaintopLodLimitations *l) const override; bool isCheckable() const override; bool isChecked() const override; void setChecked(bool checked) override; void show(); protected: KisCurveOption* curveOption(); QWidget* curveWidget(); private Q_SLOTS: void slotModified(); - void slotStateChanged(); + void slotUseSameCurveChanged(); - void transferCurve(); void updateSensorCurveLabels(KisDynamicSensorSP sensor); void updateCurve(KisDynamicSensorSP sensor); void updateValues(); void updateMode(); void updateLabelsOfCurrentSensor(); void disableWidgets(bool disable); void updateThemedIcons(); // curve shape preset buttons void changeCurveLinear(); void changeCurveReverseLinear(); void changeCurveSShape(); void changeCurveReverseSShape(); void changeCurveJShape(); void changeCurveLShape(); void changeCurveUShape(); void changeCurveArchShape(); private: QWidget* m_widget; Ui_WdgCurveOption* m_curveOptionWidget; QComboBox* m_curveMode; KisCurveOption* m_curveOption; + + KisCubicCurve getWidgetCurve(); + KisCubicCurve getHighlightedSensorCurve(); + }; #endif // KIS_CURVE_OPTION_WIDGET_H