View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0017022 | Scribus | User Interface | public | 2023-10-08 11:10 | 2023-11-04 09:09 |
Reporter | nitramr | Assigned To | nitramr | ||
Priority | normal | Severity | tweak | Reproducibility | N/A |
Status | closed | Resolution | fixed | ||
Platform | Desktop PC | OS | Ubuntu | OS Version | 23.04 64-bit |
Product Version | 1.7.0.svn | ||||
Target Version | 1.7.0 | Fixed in Version | 1.7.0.svn | ||
Summary | 0017022: Improve UI of CurveEditWidget | ||||
Description | The current curve edit widget draws a pixelated curve. Antialiasing can't be enabled because of the implemented drawing method. I reworked the widget and changed the behavior a bit. In the previous version you always had to click on the curve to add a new point and then you could move it. The new widget will add a new point directly at the location you clicked. So you save one step. | ||||
Tags | No tags attached. | ||||
Patch | Yes | ||||
|
curvewidget_2023-10-08_01.patch (16,532 bytes)
Index: scribus/ui/curvewidget.cpp =================================================================== --- scribus/ui/curvewidget.cpp (Revision 25725) +++ scribus/ui/curvewidget.cpp (Arbeitskopie) @@ -30,11 +30,23 @@ #include "ui/customfdialog.h" #include "ui/scmessagebox.h" #include "util.h" -#include "util_color.h" +#include "util_math.h" +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ +const qreal POINT_RADIUS = 4.0; +const qreal TOLLERANCE = 2.0; // pixel tollerance to hit a point -KCurve::KCurve(QWidget *parent) : QWidget(parent) +KCurve::KCurve(QWidget *parent) : QWidget(parent), + m_leftmost(0.0), + m_rightmost(0.0), + m_dragging(false), + m_linear(false), + m_selectedPoint(0) { setMouseTracking(true); setMinimumSize(150, 150); @@ -41,6 +53,7 @@ m_points.resize(0); m_points.addPoint(0.0, 0.0); m_points.addPoint(1.0, 1.0); + setFocusPolicy(Qt::StrongFocus); } @@ -50,52 +63,59 @@ void KCurve::paintEvent(QPaintEvent *) { - int x = 0; - int wWidth = width() - 1; - int wHeight = height() - 1; - // Drawing selection or all histogram values. - QPainter p1; - p1.begin(this); - // draw background - p1.fillRect(QRect(0, 0, wWidth, wHeight), QColor(255, 255, 255)); - // Draw grid separators. - p1.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); - p1.drawLine(wWidth/4, 0, wWidth/4, wHeight); - p1.drawLine(wWidth/2, 0, wWidth/2, wHeight); - p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight); - p1.drawLine(0, wHeight/4, wWidth, wHeight/4); - p1.drawLine(0, wHeight/2, wWidth, wHeight/2); - p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4); + int wWidth = width(); + int wHeight = height(); + QPointF offset(-.5, -.5); + QColor colorHandle = palette().color(QPalette::Highlight); + QColor colorBackground = palette().color(QPalette::Base); + QColor colorGraph = palette().color(QPalette::WindowText); + QColor colorGrid = palette().color(QPalette::Midlight); // Qt::gray + QPainterPath path = curvePath(); - // Draw curve. - double curvePrevVal = getCurveValue(0.0); - p1.setPen(QPen(Qt::black, 1, Qt::SolidLine)); - for (x = 0 ; x < wWidth ; x++) + // Drawing selection or all histogram values + QPainter painter; + painter.begin(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Draw background + painter.fillRect(this->rect(), colorBackground); + + // Draw grid separators + painter.setPen(QPen(colorGrid, 1, Qt::SolidLine)); + painter.drawLine( QPoint(wWidth * .25, 0) - offset, QPoint(wWidth * .25, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .5, 0) - offset, QPoint(wWidth * .5, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .75, 0) - offset, QPoint(wWidth * .75, wHeight) - offset ); + painter.drawLine( QPoint(0, wHeight * .25) - offset, QPoint(wWidth, wHeight * .25) - offset ); + painter.drawLine( QPoint(0, wHeight * .5) - offset, QPoint(wWidth, wHeight * .5) - offset ); + painter.drawLine( QPoint(0, wHeight * .75) - offset, QPoint(wWidth, wHeight * .75) - offset ); + + // Draw curve + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.drawPath(path); + + painter.setBrush(Qt::NoBrush); + + // Draw handles + for (int i = 0; i < m_points.size(); i++) { - double curveX; - double curveVal; - // curveX = (x + 0.5) / wWidth; - curveX = x / static_cast<double>(wWidth); - curveVal = getCurveValue(curveX); - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(curveVal * wHeight)); - curvePrevVal = curveVal; - } - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(getCurveValue(1.0) * wHeight)); - for (int dh = 0; dh < m_points.size(); dh++) - { - FPoint p = m_points.point(dh); - if (p == m_grab_point) + FPoint p = m_points.point(i); + QPointF dot = QPointF(p.x() * width(), (1 - p.y()) * height()); + + if (p == m_points.at(m_selectedPoint)) { - p1.setPen(QPen(Qt::red, 3, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 2, wHeight - 2 - int(p.y() * wHeight), 4, 4 ); + painter.setPen(QPen(colorHandle, 2, Qt::SolidLine)); + painter.setBrush(colorHandle); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } else { - p1.setPen(QPen(Qt::red, 1, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 3, wHeight - 3 - int(p.y() * wHeight), 6, 6 ); + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.setBrush(colorBackground); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } } - p1.end(); + + painter.end(); } void KCurve::keyPressEvent(QKeyEvent *e) @@ -104,29 +124,10 @@ { if (m_points.size() > 2) { - FPoint closest_point = m_points.point(0); - FPoint p = m_points.point(0); - int pos = 0; - int cc = 0; - double distance = 1000; // just a big number - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (fabs (m_grab_point.x() - p.x()) < distance) - { - distance = fabs(m_grab_point.x() - p.x()); - closest_point = p; - m_pos = pos; - } - cc++; - pos++; - } - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.putPoints(cli.size(), m_points.size()-m_pos-1, m_points, m_pos+1); - m_points.resize(0); - m_points = cli.copy(); - m_grab_point = closest_point; + m_points.removeAt(m_selectedPoint); + + m_selectedPoint = qBound(0, m_selectedPoint - 1, m_points.size() - 1); + repaint(); emit modified(); QWidget::keyPressEvent(e); @@ -136,84 +137,55 @@ void KCurve::mousePressEvent ( QMouseEvent * e ) { - FPoint closest_point = FPoint(); - double distance; + if (e->button() != Qt::LeftButton) return; - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); - distance = 1000; // just a big number - FPoint p = m_points.point(0); - int insert_pos =0; - int pos = 0; - int cc = 0; - while (cc < m_points.size()) + + // invert mouseY + m_mousePos = QPointF( e->pos().x(), (height() - e->pos().y())); + + m_leftmost = - 1; + m_selectedPoint = 0; + m_rightmost = m_points.size(); + + m_dragging = false; + + for(int i = 0; i < m_points.size(); i++) { - p = m_points.point(cc); - if (fabs (x - p.x()) < distance) + QPointF pt(m_points[i].x() * width(), m_points[i].y() * height()); + + // calculate left point of mouse position + if(pt.x() >= 0 && pt.x() <= m_mousePos.x()) + m_leftmost = i; + + if( distance(pt, m_mousePos) <= POINT_RADIUS + TOLLERANCE ) { - distance = fabs(x - p.x()); - closest_point = p; - insert_pos = pos; - } - cc++; - pos++; - } - m_pos = insert_pos; - m_grab_point = closest_point; - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - y; - m_grab_point = FPoint(x + m_grabOffsetX, y + m_grabOffsetY); - double curveVal = getCurveValue(x); - if (distance * width() > 5) - { - m_dragging = false; - if (fabs(y - curveVal) * width() > 5) + m_selectedPoint = i; + m_leftmost = i - 1; + m_rightmost = i + 1; + + m_dragging = true; + repaint(); return; - if (m_points.size() < 14) - { - if (x > closest_point.x()) - m_pos++; - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.resize(cli.size()+1); - cli.putPoints(cli.size()-1, 1, x, curveVal); - cli.putPoints(cli.size(), m_points.size()-m_pos, m_points, m_pos); - m_points.resize(0); - m_points = cli.copy(); - m_dragging = true; - m_grab_point = m_points.point(m_pos); - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - curveVal; - m_grab_point = FPoint(x + m_grabOffsetX, curveVal + m_grabOffsetY); - setCursor(QCursor(Qt::CrossCursor)); } + } - else + + // add new point + if(!m_dragging) { - if (fabs(y - closest_point.y()) * width() > 5) - return; + FPoint onCurve(m_mousePos.x() / width(), m_mousePos.y() / height() ); + m_selectedPoint = m_leftmost + 1; + m_rightmost = m_leftmost + 2; + m_points.insert(m_selectedPoint, onCurve ); + m_dragging = true; - setCursor(QCursor(Qt::CrossCursor)); + + repaint(); + emit modified(); } - // Determine the leftmost and rightmost points. - m_leftmost = 0; - m_rightmost = 1; - cc = 0; - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (p != m_grab_point) - { - if (p.x() > m_leftmost && p.x() < x) - m_leftmost = p.x(); - if (p.x() < m_rightmost && p.x() > x) - m_rightmost = p.x(); - } - cc++; - } - repaint(); - emit modified(); + + } void KCurve::mouseReleaseEvent ( QMouseEvent * e ) @@ -228,57 +200,117 @@ void KCurve::mouseMoveEvent ( QMouseEvent * e ) { - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); + // boolHoverCurve = false; - if (!m_dragging) // If no point is selected set the the cursor shape if on top + if(m_dragging) { - double distance = 1000; - double ydistance = 1000; - int cc = 0; - while (cc < m_points.size()) - { - FPoint p = m_points.point(cc); - if (fabs (x - p.x()) < distance) - { - distance = fabs(x - p.x()); - ydistance = fabs(y - p.y()); - } - cc++; - } - if (distance * width() > 5 || ydistance * height() > 5) - setCursor(QCursor(Qt::ArrowCursor)); - else - setCursor(QCursor(Qt::CrossCursor)); - } - else // Else, drag the selected point - { setCursor(QCursor(Qt::CrossCursor)); - x += m_grabOffsetX; - y += m_grabOffsetY; - if (x <= m_leftmost) - x = m_leftmost + 1E-4; // the addition so we can grab the dot later. - if (x >= m_rightmost) - x = m_rightmost - 1E-4; - if (y > 1.0) - y = 1.0; - if (y < 0.0) - y = 0.0; - m_grab_point = FPoint(x, y); - m_points.setPoint( m_pos, m_grab_point); + + // invert mouseY + QPointF mouse(e->pos().x(), height() - e->pos().y()); + + // left bound + qreal leftBound = (m_leftmost < 0) ? 0. : m_points.at(m_leftmost).x(); + qreal rightBound = (m_rightmost >= m_points.size()) ? 1. : m_points.at(m_rightmost).x(); + + // limit to bounds + qreal yPos = qBound(0., mouse.y() / height(), 1.); + qreal xPos = qBound(leftBound + 1E-4, mouse.x() / width(), rightBound - 1E-4); + + QPointF pointSelectedDot(xPos, yPos ); + + // set new position + m_points.setPoint( m_selectedPoint, pointSelectedDot); + repaint(); emit modified(); } } -double KCurve::getCurveValue(double x) +FPointArray KCurve::getCurve() { - return getCurveYValue(m_points, x, m_linear); + return m_points.copy(); } -FPointArray KCurve::getCurve() + +QPainterPath KCurve::curvePath() { - return m_points.copy(); + QPointF pointStart( 0, (1 - m_points.at(0).y()) * height() ); + QPointF pointEnd( width(), (1 - m_points.at(m_points.size() - 1).y()) * height() ); + + + QList<QPointF> points; + points.append( pointStart ); + + // Calculate absolute position of each point + for (int i = 0; i < m_points.size(); i++) + { + FPoint p = m_points.at(i); + points.append(QPointF(p.x() * width(), (1 - p.y()) * height())); + } + + points.append( pointEnd ); + + + // Construct Path + QPainterPath path; + + // Add start line + path.moveTo( pointStart ); + path.lineTo( points.at(1) ); + + for (int id = 0; id < points.size(); id++) + { + + if(m_linear) + { + + path.lineTo(points.at(id).toPoint()); + + } + else + { + + int i0 = id-1; + int i1 = id; + int i2 = id+1; + int i3 = id+2; + + if(i0 < 0 || i2 > points.size()-1 || i3 > points.size()-1){ + + continue; + } + + FPoint p0 = (i0 >= 0) ? points.at(i0) : points.at(i1); + FPoint p1 = points.at(i1); + FPoint p2 = (i2 < points.size()) ? points.at(i2) : points.at(points.size()-1); + FPoint p3 = (i3 < points.size()) ? points.at(i3) : points.at(points.size()-1); + + QList<QPointF> inputPoints; + inputPoints.append( p0.toQPointF() ); // Previous point + inputPoints.append( p1.toQPointF() ); // Point + inputPoints.append( p2.toQPointF() ); // Next point + inputPoints.append( p3.toQPointF() ); // Next but one point + + double t = qMax(width() / (p2.x() - p1.x()) / 6, 1.); + + QList<QPointF> *bezPoints = catmullToBezier(inputPoints, t); + + path.cubicTo( + QPointF( bezPoints->at(1) ), // Control 1 + QPointF( bezPoints->at(2) ), // Control 2 + QPointF( bezPoints->at(3) ) // Next Point + + ); + } + + } + + // Add end line + path.lineTo( pointEnd ); + + return path; + } void KCurve::setCurve(const FPointArray& inlist) @@ -310,6 +342,13 @@ return m_linear; } + +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ + CurveWidget::CurveWidget( QWidget* parent ) : QWidget( parent ) { CurveWidgetLayout = new QHBoxLayout(this); Index: scribus/ui/curvewidget.h =================================================================== --- scribus/ui/curvewidget.h (Revision 25725) +++ scribus/ui/curvewidget.h (Arbeitskopie) @@ -44,6 +44,7 @@ FPointArray getCurve(); void setCurve(const FPointArray& inlist); void resetCurve(); + QPainterPath curvePath(); void setLinear(bool setter); bool isLinear(); @@ -53,14 +54,16 @@ private: double m_leftmost { 0.0 }; double m_rightmost { 0.0 }; - FPoint m_grab_point; - int m_pos { 0 }; +// FPoint m_grab_point; +// int m_pos { 0 }; bool m_dragging { false }; bool m_linear { false }; - double m_grabOffsetX { 0.0 }; - double m_grabOffsetY { 0.0 }; +// double m_grabOffsetX { 0.0 }; +// double m_grabOffsetY { 0.0 }; FPointArray m_points; FPointArray m_points_back; + QPointF m_mousePos; + int m_selectedPoint { 0 }; }; class SCRIBUS_API CurveWidget : public QWidget Index: scribus/util_math.cpp =================================================================== --- scribus/util_math.cpp (Revision 25725) +++ scribus/util_math.cpp (Arbeitskopie) @@ -547,3 +547,28 @@ dx = lineX.x1(); dy = lineX.y1(); } + +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t) +{ + // Inspired by the explanation on: https://pomax.github.io/bezierinfo/#catmullconv + + QList<QPointF> *result = new QList<QPointF>(); + + // qDebug() << "t" << t; + + double x0 = inputPoints[1].x(); + double y0 = inputPoints[1].y(); + double x1 = inputPoints[1].x() + (inputPoints[2].x() - inputPoints[0].x()) / (6 * t); + double y1 = inputPoints[1].y() + (inputPoints[2].y() - inputPoints[0].y()) / (6 * t); + double x2 = inputPoints[2].x() - (inputPoints[3].x() - inputPoints[1].x()) / (6 * t); + double y2 = inputPoints[2].y() - (inputPoints[3].y() - inputPoints[1].y()) / (6 * t); + double x3 = inputPoints[2].x(); + double y3 = inputPoints[2].y(); + + result->append( QPointF(x0, y0) ); + result->append( QPointF(x1, y1) ); + result->append( QPointF(x2, y2) ); + result->append( QPointF(x3, y3) ); + + return result; +} Index: scribus/util_math.h =================================================================== --- scribus/util_math.h (Revision 25725) +++ scribus/util_math.h (Arbeitskopie) @@ -36,6 +36,13 @@ bool SCRIBUS_API regionContainsRect(const QRegion& shape, QRect rect); QPolygon SCRIBUS_API flattenPath(const FPointArray& ina, QList<uint> &segments); QList<QPainterPath> SCRIBUS_API decomposePath(const QPainterPath &path); +/** + * @brief CatmullToBezier + * @param inputPoints Point Array of at least 4 points (p0, p1, p2, p3). You will get cubic bezier points from p1 to p2. + * @param t + * @return + */ +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t = 1); QPainterPath SCRIBUS_API regularPolygonPath(double w, double h, uint c, bool star, double factor, double rota, double factor2 = 0.0, double innerRot = 0.0, double factor3 = 0.0); QPainterPath SCRIBUS_API spiralPath(double spiralWidth, double spiralHeight, double spiralStartAngle, double spiralEndAngle, double spiralFactor); inline double SCRIBUS_API xy2Deg(double x, double y); @@ -43,6 +50,7 @@ inline double SCRIBUS_API cosd(double); inline double SCRIBUS_API square(double); inline double SCRIBUS_API distance(double, double); +inline double SCRIBUS_API distance(QPointF p1, QPointF p2); /*! \brief Constrains an angle of rotation to 45 degree intervals Will make code simpler and reduce interval or provide as a parameter \param angle angle Angle in degrees @@ -78,6 +86,12 @@ return sqrt(x*x + y*y); } +inline double distance(QPointF p1, QPointF p2) +{ + QPointF point = p2 - p1; + return point.manhattanLength(); +} + inline double xy2Deg(double x, double y) { return atan2(y,x) * (180.0/M_PI); |
|
There's a new crash introduced when undoing changes to the curve: ASSERT failure in QList::at: "index out of range", file /Users/craig/Qt/6.5.3/macos/lib/QtCore.framework/Headers/qlist.h, line 423 Assertion failed: (false), function abort_on_error, file scribuscore.cpp, line 84. Scribus Crash ------------- Scribus crashes due to Signal 0000006 QWidget::repaint: Recursive repaint detected ASSERT failure in QList::at: "index out of range", file /Users/craig/Qt/6.5.3/macos/lib/QtCore.framework/Headers/qlist.h, line 423 |
|
@cbradney Do you reset the curve by click on rest button? I can't reproduce the crash. |
|
Yes, the reset button causes it |
|
I still could not reproduce it, no idea why it is not crashing for me, but I found something. "m_points_back" has no initial data in constructor and will set only if you load a scu file or invert the curve, but it is used when you click the reset button. Now, I set some dummy data in constructor. m_points_back = m_points.copy(); The paint event draw a curve based on a QPainterPath that is created from "m_points". I guess when "m_point" is empty it occurs the crash. To protect this function, I added a check in KCurve::curvePath(). I hope it is fixed now, because it is just a guess. curvewidget_2023-10-08_02.patch (16,322 bytes)
Index: scribus/ui/curvewidget.cpp =================================================================== --- scribus/ui/curvewidget.cpp (Revision 25725) +++ scribus/ui/curvewidget.cpp (Arbeitskopie) @@ -30,9 +30,16 @@ #include "ui/customfdialog.h" #include "ui/scmessagebox.h" #include "util.h" -#include "util_color.h" +#include "util_math.h" +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ +const qreal POINT_RADIUS = 4.0; +const qreal TOLLERANCE = 2.0; // pixel tollerance to hit a point KCurve::KCurve(QWidget *parent) : QWidget(parent) { @@ -41,6 +48,8 @@ m_points.resize(0); m_points.addPoint(0.0, 0.0); m_points.addPoint(1.0, 1.0); + m_points_back = m_points.copy(); + setFocusPolicy(Qt::StrongFocus); } @@ -50,52 +59,59 @@ void KCurve::paintEvent(QPaintEvent *) { - int x = 0; - int wWidth = width() - 1; - int wHeight = height() - 1; - // Drawing selection or all histogram values. - QPainter p1; - p1.begin(this); - // draw background - p1.fillRect(QRect(0, 0, wWidth, wHeight), QColor(255, 255, 255)); - // Draw grid separators. - p1.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); - p1.drawLine(wWidth/4, 0, wWidth/4, wHeight); - p1.drawLine(wWidth/2, 0, wWidth/2, wHeight); - p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight); - p1.drawLine(0, wHeight/4, wWidth, wHeight/4); - p1.drawLine(0, wHeight/2, wWidth, wHeight/2); - p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4); + int wWidth = width(); + int wHeight = height(); + QPointF offset(-.5, -.5); + QColor colorHandle = palette().color(QPalette::Highlight); + QColor colorBackground = palette().color(QPalette::Base); + QColor colorGraph = palette().color(QPalette::WindowText); + QColor colorGrid = palette().color(QPalette::Midlight); // Qt::gray + QPainterPath path = curvePath(); - // Draw curve. - double curvePrevVal = getCurveValue(0.0); - p1.setPen(QPen(Qt::black, 1, Qt::SolidLine)); - for (x = 0 ; x < wWidth ; x++) + // Drawing selection or all histogram values + QPainter painter; + painter.begin(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Draw background + painter.fillRect(this->rect(), colorBackground); + + // Draw grid separators + painter.setPen(QPen(colorGrid, 1, Qt::SolidLine)); + painter.drawLine( QPoint(wWidth * .25, 0) - offset, QPoint(wWidth * .25, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .5, 0) - offset, QPoint(wWidth * .5, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .75, 0) - offset, QPoint(wWidth * .75, wHeight) - offset ); + painter.drawLine( QPoint(0, wHeight * .25) - offset, QPoint(wWidth, wHeight * .25) - offset ); + painter.drawLine( QPoint(0, wHeight * .5) - offset, QPoint(wWidth, wHeight * .5) - offset ); + painter.drawLine( QPoint(0, wHeight * .75) - offset, QPoint(wWidth, wHeight * .75) - offset ); + + // Draw curve + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.drawPath(path); + + painter.setBrush(Qt::NoBrush); + + // Draw handles + for (int i = 0; i < m_points.size(); i++) { - double curveX; - double curveVal; - // curveX = (x + 0.5) / wWidth; - curveX = x / static_cast<double>(wWidth); - curveVal = getCurveValue(curveX); - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(curveVal * wHeight)); - curvePrevVal = curveVal; - } - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(getCurveValue(1.0) * wHeight)); - for (int dh = 0; dh < m_points.size(); dh++) - { - FPoint p = m_points.point(dh); - if (p == m_grab_point) + FPoint p = m_points.point(i); + QPointF dot = QPointF(p.x() * width(), (1 - p.y()) * height()); + + if (p == m_points.at(m_selectedPoint)) { - p1.setPen(QPen(Qt::red, 3, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 2, wHeight - 2 - int(p.y() * wHeight), 4, 4 ); + painter.setPen(QPen(colorHandle, 2, Qt::SolidLine)); + painter.setBrush(colorHandle); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } else { - p1.setPen(QPen(Qt::red, 1, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 3, wHeight - 3 - int(p.y() * wHeight), 6, 6 ); + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.setBrush(colorBackground); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } } - p1.end(); + + painter.end(); } void KCurve::keyPressEvent(QKeyEvent *e) @@ -104,29 +120,10 @@ { if (m_points.size() > 2) { - FPoint closest_point = m_points.point(0); - FPoint p = m_points.point(0); - int pos = 0; - int cc = 0; - double distance = 1000; // just a big number - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (fabs (m_grab_point.x() - p.x()) < distance) - { - distance = fabs(m_grab_point.x() - p.x()); - closest_point = p; - m_pos = pos; - } - cc++; - pos++; - } - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.putPoints(cli.size(), m_points.size()-m_pos-1, m_points, m_pos+1); - m_points.resize(0); - m_points = cli.copy(); - m_grab_point = closest_point; + m_points.removeAt(m_selectedPoint); + + m_selectedPoint = qBound(0, m_selectedPoint - 1, m_points.size() - 1); + repaint(); emit modified(); QWidget::keyPressEvent(e); @@ -136,84 +133,55 @@ void KCurve::mousePressEvent ( QMouseEvent * e ) { - FPoint closest_point = FPoint(); - double distance; + if (e->button() != Qt::LeftButton) return; - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); - distance = 1000; // just a big number - FPoint p = m_points.point(0); - int insert_pos =0; - int pos = 0; - int cc = 0; - while (cc < m_points.size()) + + // invert mouseY + m_mousePos = QPointF( e->pos().x(), (height() - e->pos().y())); + + m_leftmost = - 1; + m_selectedPoint = 0; + m_rightmost = m_points.size(); + + m_dragging = false; + + for(int i = 0; i < m_points.size(); i++) { - p = m_points.point(cc); - if (fabs (x - p.x()) < distance) + QPointF pt(m_points[i].x() * width(), m_points[i].y() * height()); + + // calculate left point of mouse position + if(pt.x() >= 0 && pt.x() <= m_mousePos.x()) + m_leftmost = i; + + if( distance(pt, m_mousePos) <= POINT_RADIUS + TOLLERANCE ) { - distance = fabs(x - p.x()); - closest_point = p; - insert_pos = pos; - } - cc++; - pos++; - } - m_pos = insert_pos; - m_grab_point = closest_point; - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - y; - m_grab_point = FPoint(x + m_grabOffsetX, y + m_grabOffsetY); - double curveVal = getCurveValue(x); - if (distance * width() > 5) - { - m_dragging = false; - if (fabs(y - curveVal) * width() > 5) + m_selectedPoint = i; + m_leftmost = i - 1; + m_rightmost = i + 1; + + m_dragging = true; + repaint(); return; - if (m_points.size() < 14) - { - if (x > closest_point.x()) - m_pos++; - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.resize(cli.size()+1); - cli.putPoints(cli.size()-1, 1, x, curveVal); - cli.putPoints(cli.size(), m_points.size()-m_pos, m_points, m_pos); - m_points.resize(0); - m_points = cli.copy(); - m_dragging = true; - m_grab_point = m_points.point(m_pos); - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - curveVal; - m_grab_point = FPoint(x + m_grabOffsetX, curveVal + m_grabOffsetY); - setCursor(QCursor(Qt::CrossCursor)); } + } - else + + // add new point + if(!m_dragging) { - if (fabs(y - closest_point.y()) * width() > 5) - return; + FPoint onCurve(m_mousePos.x() / width(), m_mousePos.y() / height() ); + m_selectedPoint = m_leftmost + 1; + m_rightmost = m_leftmost + 2; + m_points.insert(m_selectedPoint, onCurve ); + m_dragging = true; - setCursor(QCursor(Qt::CrossCursor)); + + repaint(); + emit modified(); } - // Determine the leftmost and rightmost points. - m_leftmost = 0; - m_rightmost = 1; - cc = 0; - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (p != m_grab_point) - { - if (p.x() > m_leftmost && p.x() < x) - m_leftmost = p.x(); - if (p.x() < m_rightmost && p.x() > x) - m_rightmost = p.x(); - } - cc++; - } - repaint(); - emit modified(); + + } void KCurve::mouseReleaseEvent ( QMouseEvent * e ) @@ -228,57 +196,113 @@ void KCurve::mouseMoveEvent ( QMouseEvent * e ) { - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); - - if (!m_dragging) // If no point is selected set the the cursor shape if on top + if(m_dragging) { - double distance = 1000; - double ydistance = 1000; - int cc = 0; - while (cc < m_points.size()) - { - FPoint p = m_points.point(cc); - if (fabs (x - p.x()) < distance) - { - distance = fabs(x - p.x()); - ydistance = fabs(y - p.y()); - } - cc++; - } - if (distance * width() > 5 || ydistance * height() > 5) - setCursor(QCursor(Qt::ArrowCursor)); - else - setCursor(QCursor(Qt::CrossCursor)); - } - else // Else, drag the selected point - { setCursor(QCursor(Qt::CrossCursor)); - x += m_grabOffsetX; - y += m_grabOffsetY; - if (x <= m_leftmost) - x = m_leftmost + 1E-4; // the addition so we can grab the dot later. - if (x >= m_rightmost) - x = m_rightmost - 1E-4; - if (y > 1.0) - y = 1.0; - if (y < 0.0) - y = 0.0; - m_grab_point = FPoint(x, y); - m_points.setPoint( m_pos, m_grab_point); + + // invert mouseY + QPointF mouse(e->pos().x(), height() - e->pos().y()); + + // left bound + qreal leftBound = (m_leftmost < 0) ? 0. : m_points.at(m_leftmost).x(); + qreal rightBound = (m_rightmost >= m_points.size()) ? 1. : m_points.at(m_rightmost).x(); + + // limit to bounds + qreal yPos = qBound(0., mouse.y() / height(), 1.); + qreal xPos = qBound(leftBound + 1E-4, mouse.x() / width(), rightBound - 1E-4); + + QPointF pointSelectedDot(xPos, yPos ); + + // set new position + m_points.setPoint( m_selectedPoint, pointSelectedDot); + repaint(); emit modified(); } } -double KCurve::getCurveValue(double x) +FPointArray KCurve::getCurve() { - return getCurveYValue(m_points, x, m_linear); + return m_points.copy(); } -FPointArray KCurve::getCurve() + +QPainterPath KCurve::curvePath() { - return m_points.copy(); + QPainterPath path; + + if(m_points.size() <= 0) + { + qWarning() << Q_FUNC_INFO << "Can't create path because there are no path nodes!"; + return path; + } + + QPointF pointStart( 0, (1 - m_points.at(0).y()) * height() ); + QPointF pointEnd( width(), (1 - m_points.at(m_points.size() - 1).y()) * height() ); + + QList<QPointF> points; + points.append( pointStart ); + + // Calculate absolute position of each point + for (int i = 0; i < m_points.size(); i++) + { + FPoint p = m_points.at(i); + points.append(QPointF(p.x() * width(), (1 - p.y()) * height())); + } + + points.append( pointEnd ); + + // Add start line + path.moveTo( pointStart ); + path.lineTo( points.at(1) ); + + for (int id = 0; id < points.size(); id++) + { + + if(m_linear) + { + path.lineTo(points.at(id).toPoint()); + } + else + { + + int i0 = id-1; + int i1 = id; + int i2 = id+1; + int i3 = id+2; + + if(i0 < 0 || i2 > points.size()-1 || i3 > points.size() - 1) + continue; + + FPoint p0 = (i0 >= 0) ? points.at(i0) : points.at(i1); + FPoint p1 = points.at(i1); + FPoint p2 = (i2 < points.size()) ? points.at(i2) : points.at(points.size() - 1); + FPoint p3 = (i3 < points.size()) ? points.at(i3) : points.at(points.size() - 1); + + QList<QPointF> inputPoints; + inputPoints.append( p0.toQPointF() ); // Previous point + inputPoints.append( p1.toQPointF() ); // Point + inputPoints.append( p2.toQPointF() ); // Next point + inputPoints.append( p3.toQPointF() ); // Next but one point + + double t = qMax(width() / (p2.x() - p1.x()) / 6, 1.); + + QList<QPointF> *bezPoints = catmullToBezier(inputPoints, t); + + path.cubicTo( + QPointF( bezPoints->at(1) ), // Control 1 + QPointF( bezPoints->at(2) ), // Control 2 + QPointF( bezPoints->at(3) ) // Next Point + ); + } + + } + + // Add end line + path.lineTo( pointEnd ); + + return path; + } void KCurve::setCurve(const FPointArray& inlist) @@ -310,6 +334,13 @@ return m_linear; } + +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ + CurveWidget::CurveWidget( QWidget* parent ) : QWidget( parent ) { CurveWidgetLayout = new QHBoxLayout(this); Index: scribus/ui/curvewidget.h =================================================================== --- scribus/ui/curvewidget.h (Revision 25725) +++ scribus/ui/curvewidget.h (Arbeitskopie) @@ -44,6 +44,7 @@ FPointArray getCurve(); void setCurve(const FPointArray& inlist); void resetCurve(); + QPainterPath curvePath(); void setLinear(bool setter); bool isLinear(); @@ -53,14 +54,12 @@ private: double m_leftmost { 0.0 }; double m_rightmost { 0.0 }; - FPoint m_grab_point; - int m_pos { 0 }; bool m_dragging { false }; bool m_linear { false }; - double m_grabOffsetX { 0.0 }; - double m_grabOffsetY { 0.0 }; FPointArray m_points; FPointArray m_points_back; + QPointF m_mousePos; + int m_selectedPoint { 0 }; }; class SCRIBUS_API CurveWidget : public QWidget Index: scribus/util_math.cpp =================================================================== --- scribus/util_math.cpp (Revision 25725) +++ scribus/util_math.cpp (Arbeitskopie) @@ -547,3 +547,28 @@ dx = lineX.x1(); dy = lineX.y1(); } + +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t) +{ + // Inspired by the explanation on: https://pomax.github.io/bezierinfo/#catmullconv + + QList<QPointF> *result = new QList<QPointF>(); + + // qDebug() << "t" << t; + + double x0 = inputPoints[1].x(); + double y0 = inputPoints[1].y(); + double x1 = inputPoints[1].x() + (inputPoints[2].x() - inputPoints[0].x()) / (6 * t); + double y1 = inputPoints[1].y() + (inputPoints[2].y() - inputPoints[0].y()) / (6 * t); + double x2 = inputPoints[2].x() - (inputPoints[3].x() - inputPoints[1].x()) / (6 * t); + double y2 = inputPoints[2].y() - (inputPoints[3].y() - inputPoints[1].y()) / (6 * t); + double x3 = inputPoints[2].x(); + double y3 = inputPoints[2].y(); + + result->append( QPointF(x0, y0) ); + result->append( QPointF(x1, y1) ); + result->append( QPointF(x2, y2) ); + result->append( QPointF(x3, y3) ); + + return result; +} Index: scribus/util_math.h =================================================================== --- scribus/util_math.h (Revision 25725) +++ scribus/util_math.h (Arbeitskopie) @@ -36,6 +36,13 @@ bool SCRIBUS_API regionContainsRect(const QRegion& shape, QRect rect); QPolygon SCRIBUS_API flattenPath(const FPointArray& ina, QList<uint> &segments); QList<QPainterPath> SCRIBUS_API decomposePath(const QPainterPath &path); +/** + * @brief CatmullToBezier + * @param inputPoints Point Array of at least 4 points (p0, p1, p2, p3). You will get cubic bezier points from p1 to p2. + * @param t + * @return + */ +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t = 1); QPainterPath SCRIBUS_API regularPolygonPath(double w, double h, uint c, bool star, double factor, double rota, double factor2 = 0.0, double innerRot = 0.0, double factor3 = 0.0); QPainterPath SCRIBUS_API spiralPath(double spiralWidth, double spiralHeight, double spiralStartAngle, double spiralEndAngle, double spiralFactor); inline double SCRIBUS_API xy2Deg(double x, double y); @@ -43,6 +50,7 @@ inline double SCRIBUS_API cosd(double); inline double SCRIBUS_API square(double); inline double SCRIBUS_API distance(double, double); +inline double SCRIBUS_API distance(QPointF p1, QPointF p2); /*! \brief Constrains an angle of rotation to 45 degree intervals Will make code simpler and reduce interval or provide as a parameter \param angle angle Angle in degrees @@ -78,6 +86,12 @@ return sqrt(x*x + y*y); } +inline double distance(QPointF p1, QPointF p2) +{ + QPointF point = p2 - p1; + return point.manhattanLength(); +} + inline double xy2Deg(double x, double y) { return atan2(y,x) * (180.0/M_PI); |
|
(lldb) bt * thread 0000001, queue = 'com.apple.main-thread', stop reason = hit program assert frame #0: 0x00007ff8040a37a6 libsystem_kernel.dylib`__pthread_kill + 10 frame 0000001: 0x00007ff8040dbf30 libsystem_pthread.dylib`pthread_kill + 262 frame 0000002: 0x00007ff803ffaa4d libsystem_c.dylib`abort + 126 frame 0000003: 0x00007ff803ff9d60 libsystem_c.dylib`__assert_rtn + 314 * frame 0000004: 0x00000001007ec336 Scribus`abort_on_error(t=QtFatalMsg, (null)=0x00007ff7bfef8be0, m=0x00007ff7bfef8b98) at scribuscore.cpp:84:22 frame 0000005: 0x00000001059e1148 QtCore`___lldb_unnamed_symbol7338 + 232 frame 0000006: 0x00000001059dc2ea QtCore`___lldb_unnamed_symbol7324 + 42 frame 0000007: 0x0000000105d480e5 QtCore`QMessageLogger::fatal(char const*, ...) const + 151 frame 0000008: 0x0000000105d47431 QtCore`qt_assert_x(char const*, char const*, char const*, int) + 70 frame 0000009: 0x000000010017f45a Scribus`QList<FPoint>::at(this=0x00006000069a6480, i=2) const at qlist.h:423:9 frame 0000010: 0x0000000100b7e96d Scribus`KCurve::paintEvent(this=0x00006000069a6440, (null)=0x00007ff7bfef9470) at curvewidget.cpp:100:21 frame 0000011: 0x0000000107ec292e QtWidgets`QWidget::event(QEvent*) + 1262 frame 0000012: 0x0000000107e72fa7 QtWidgets`QApplicationPrivate::notify_helper(QObject*, QEvent*) + 247 frame 0000013: 0x0000000107e73dcc QtWidgets`QApplication::notify(QObject*, QEvent*) + 508 frame 0000014: 0x0000000105a3da1a QtCore`QCoreApplication::notifyInternal2(QObject*, QEvent*) + 170 frame 0000015: 0x0000000107eb4a2d QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 3981 frame 0000016: 0x0000000107ebc66f QtWidgets`QWidgetPrivate::paintSiblingsRecursive(QPaintDevice*, QList<QObject*> const&, int, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 1007 frame 0000017: 0x0000000107eb4b68 QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 4296 frame 0000018: 0x0000000107ebc66f QtWidgets`QWidgetPrivate::paintSiblingsRecursive(QPaintDevice*, QList<QObject*> const&, int, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 1007 frame 0000019: 0x0000000107eb4b68 QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 4296 frame 0000020: 0x0000000107ebc66f QtWidgets`QWidgetPrivate::paintSiblingsRecursive(QPaintDevice*, QList<QObject*> const&, int, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 1007 frame 0000021: 0x0000000107eb4b68 QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 4296 frame 0000022: 0x0000000107ebc66f QtWidgets`QWidgetPrivate::paintSiblingsRecursive(QPaintDevice*, QList<QObject*> const&, int, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 1007 frame 0000023: 0x0000000107eb4b68 QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 4296 frame 0000024: 0x0000000107ebc66f QtWidgets`QWidgetPrivate::paintSiblingsRecursive(QPaintDevice*, QList<QObject*> const&, int, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 1007 frame 0000025: 0x0000000107eb4b68 QtWidgets`QWidgetPrivate::drawWidget(QPaintDevice*, QRegion const&, QPoint const&, QFlags<QWidgetPrivate::DrawWidgetFlag>, QPainter*, QWidgetRepaintManager*) + 4296 frame 0000026: 0x0000000107ed4b8d QtWidgets`QWidgetRepaintManager::paintAndFlush() + 5085 frame 0000027: 0x0000000107ed4e6f QtWidgets`QWidgetRepaintManager::sync() + 255 frame 0000028: 0x0000000107ec2b55 QtWidgets`QWidget::event(QEvent*) + 1813 frame 0000029: 0x0000000107e72fa7 QtWidgets`QApplicationPrivate::notify_helper(QObject*, QEvent*) + 247 frame 0000030: 0x0000000107e73dcc QtWidgets`QApplication::notify(QObject*, QEvent*) + 508 frame 0000031: 0x0000000105a3da1a QtCore`QCoreApplication::notifyInternal2(QObject*, QEvent*) + 170 frame 0000032: 0x0000000107ed11b5 QtWidgets`QWidgetRepaintManager::sendUpdateRequest(QWidget*, QWidgetRepaintManager::UpdateTime) + 629 frame 0000033: 0x0000000107ed0ee3 QtWidgets`void QWidgetRepaintManager::markDirty<QRect>(QRect const&, QWidget*, QWidgetRepaintManager::UpdateTime, QWidgetRepaintManager::BufferState) + 2163 frame 0000034: 0x0000000107ec5927 QtWidgets`QWidget::repaint(QRect const&) + 151 frame 0000035: 0x0000000107ec5884 QtWidgets`QWidget::repaint() + 52 frame 0000036: 0x0000000100b8033c Scribus`KCurve::resetCurve(this=0x00006000069a6440) at curvewidget.cpp:321:2 frame 0000037: 0x0000000100b81689 Scribus`CurveWidget::doReset(this=0x000060000300ccb0) at curvewidget.cpp:416:12 frame 0000038: 0x000000010002d344 Scribus`CurveWidget::qt_static_metacall(_o=0x000060000300ccb0, _c=InvokeMetaMethod, _id=1, _a=0x00007ff7bfefaa80) at moc_curvewidget.cpp:278:21 |
|
Next try, I fixed a place in paint event function. curvewidget_2023-10-08_03.patch (16,450 bytes)
Index: scribus/ui/curvewidget.cpp =================================================================== --- scribus/ui/curvewidget.cpp (Revision 25725) +++ scribus/ui/curvewidget.cpp (Arbeitskopie) @@ -30,9 +30,16 @@ #include "ui/customfdialog.h" #include "ui/scmessagebox.h" #include "util.h" -#include "util_color.h" +#include "util_math.h" +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ +const qreal POINT_RADIUS = 4.0; +const qreal TOLLERANCE = 2.0; // pixel tollerance to hit a point KCurve::KCurve(QWidget *parent) : QWidget(parent) { @@ -41,6 +48,8 @@ m_points.resize(0); m_points.addPoint(0.0, 0.0); m_points.addPoint(1.0, 1.0); + m_points_back = m_points.copy(); + setFocusPolicy(Qt::StrongFocus); } @@ -50,52 +59,58 @@ void KCurve::paintEvent(QPaintEvent *) { - int x = 0; - int wWidth = width() - 1; - int wHeight = height() - 1; - // Drawing selection or all histogram values. - QPainter p1; - p1.begin(this); - // draw background - p1.fillRect(QRect(0, 0, wWidth, wHeight), QColor(255, 255, 255)); - // Draw grid separators. - p1.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); - p1.drawLine(wWidth/4, 0, wWidth/4, wHeight); - p1.drawLine(wWidth/2, 0, wWidth/2, wHeight); - p1.drawLine(3*wWidth/4, 0, 3*wWidth/4, wHeight); - p1.drawLine(0, wHeight/4, wWidth, wHeight/4); - p1.drawLine(0, wHeight/2, wWidth, wHeight/2); - p1.drawLine(0, 3*wHeight/4, wWidth, 3*wHeight/4); + int wWidth = width(); + int wHeight = height(); + QPointF offset(-.5, -.5); + QColor colorHandle = palette().color(QPalette::Highlight); + QColor colorBackground = palette().color(QPalette::Base); + QColor colorGraph = palette().color(QPalette::WindowText); + QColor colorGrid = palette().color(QPalette::Midlight); // Qt::gray + QPainterPath path = curvePath(); - // Draw curve. - double curvePrevVal = getCurveValue(0.0); - p1.setPen(QPen(Qt::black, 1, Qt::SolidLine)); - for (x = 0 ; x < wWidth ; x++) + // Drawing selection or all histogram values + QPainter painter; + painter.begin(this); + painter.setRenderHint(QPainter::Antialiasing, true); + + // Draw background + painter.fillRect(this->rect(), colorBackground); + + // Draw grid separators + painter.setPen(QPen(colorGrid, 1, Qt::SolidLine)); + painter.drawLine( QPoint(wWidth * .25, 0) - offset, QPoint(wWidth * .25, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .5, 0) - offset, QPoint(wWidth * .5, wHeight) - offset ); + painter.drawLine( QPoint(wWidth * .75, 0) - offset, QPoint(wWidth * .75, wHeight) - offset ); + painter.drawLine( QPoint(0, wHeight * .25) - offset, QPoint(wWidth, wHeight * .25) - offset ); + painter.drawLine( QPoint(0, wHeight * .5) - offset, QPoint(wWidth, wHeight * .5) - offset ); + painter.drawLine( QPoint(0, wHeight * .75) - offset, QPoint(wWidth, wHeight * .75) - offset ); + + // Draw curve + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.drawPath(path); + painter.setBrush(Qt::NoBrush); + + // Draw handles + for (int i = 0; i < m_points.size(); i++) { - double curveX; - double curveVal; - // curveX = (x + 0.5) / wWidth; - curveX = x / static_cast<double>(wWidth); - curveVal = getCurveValue(curveX); - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(curveVal * wHeight)); - curvePrevVal = curveVal; - } - p1.drawLine(x - 1, wHeight - int(curvePrevVal * wHeight), x, wHeight - int(getCurveValue(1.0) * wHeight)); - for (int dh = 0; dh < m_points.size(); dh++) - { - FPoint p = m_points.point(dh); - if (p == m_grab_point) + FPoint p = m_points.point(i); + QPointF dot = QPointF(p.x() * width(), (1 - p.y()) * height()); + + if(i == selectedPoint()) { - p1.setPen(QPen(Qt::red, 3, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 2, wHeight - 2 - int(p.y() * wHeight), 4, 4 ); + painter.setPen(QPen(colorHandle, 2, Qt::SolidLine)); + painter.setBrush(colorHandle); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } else { - p1.setPen(QPen(Qt::red, 1, Qt::SolidLine)); - p1.drawEllipse( int(p.x() * wWidth) - 3, wHeight - 3 - int(p.y() * wHeight), 6, 6 ); + painter.setPen(QPen(colorGraph, 2, Qt::SolidLine)); + painter.setBrush(colorBackground); + painter.drawEllipse( dot, POINT_RADIUS, POINT_RADIUS ); } } - p1.end(); + + painter.end(); } void KCurve::keyPressEvent(QKeyEvent *e) @@ -104,29 +119,10 @@ { if (m_points.size() > 2) { - FPoint closest_point = m_points.point(0); - FPoint p = m_points.point(0); - int pos = 0; - int cc = 0; - double distance = 1000; // just a big number - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (fabs (m_grab_point.x() - p.x()) < distance) - { - distance = fabs(m_grab_point.x() - p.x()); - closest_point = p; - m_pos = pos; - } - cc++; - pos++; - } - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.putPoints(cli.size(), m_points.size()-m_pos-1, m_points, m_pos+1); - m_points.resize(0); - m_points = cli.copy(); - m_grab_point = closest_point; + m_points.removeAt(selectedPoint()); + + m_selectedPoint = qBound(0, m_selectedPoint - 1, m_points.size() - 1); + repaint(); emit modified(); QWidget::keyPressEvent(e); @@ -136,84 +132,55 @@ void KCurve::mousePressEvent ( QMouseEvent * e ) { - FPoint closest_point = FPoint(); - double distance; + if (e->button() != Qt::LeftButton) return; - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); - distance = 1000; // just a big number - FPoint p = m_points.point(0); - int insert_pos =0; - int pos = 0; - int cc = 0; - while (cc < m_points.size()) + + // invert mouseY + m_mousePos = QPointF( e->pos().x(), (height() - e->pos().y())); + + m_leftmost = - 1; + m_selectedPoint = 0; + m_rightmost = m_points.size(); + + m_dragging = false; + + for(int i = 0; i < m_points.size(); i++) { - p = m_points.point(cc); - if (fabs (x - p.x()) < distance) + QPointF pt(m_points[i].x() * width(), m_points[i].y() * height()); + + // calculate left point of mouse position + if(pt.x() >= 0 && pt.x() <= m_mousePos.x()) + m_leftmost = i; + + if( distance(pt, m_mousePos) <= POINT_RADIUS + TOLLERANCE ) { - distance = fabs(x - p.x()); - closest_point = p; - insert_pos = pos; - } - cc++; - pos++; - } - m_pos = insert_pos; - m_grab_point = closest_point; - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - y; - m_grab_point = FPoint(x + m_grabOffsetX, y + m_grabOffsetY); - double curveVal = getCurveValue(x); - if (distance * width() > 5) - { - m_dragging = false; - if (fabs(y - curveVal) * width() > 5) + m_selectedPoint = i; + m_leftmost = i - 1; + m_rightmost = i + 1; + + m_dragging = true; + repaint(); return; - if (m_points.size() < 14) - { - if (x > closest_point.x()) - m_pos++; - FPointArray cli; - cli.putPoints(0, m_pos, m_points); - cli.resize(cli.size()+1); - cli.putPoints(cli.size()-1, 1, x, curveVal); - cli.putPoints(cli.size(), m_points.size()-m_pos, m_points, m_pos); - m_points.resize(0); - m_points = cli.copy(); - m_dragging = true; - m_grab_point = m_points.point(m_pos); - m_grabOffsetX = m_grab_point.x() - x; - m_grabOffsetY = m_grab_point.y() - curveVal; - m_grab_point = FPoint(x + m_grabOffsetX, curveVal + m_grabOffsetY); - setCursor(QCursor(Qt::CrossCursor)); } + } - else + + // add new point + if(!m_dragging) { - if (fabs(y - closest_point.y()) * width() > 5) - return; + FPoint onCurve(m_mousePos.x() / width(), m_mousePos.y() / height() ); + m_selectedPoint = m_leftmost + 1; + m_rightmost = m_leftmost + 2; + m_points.insert(m_selectedPoint, onCurve ); + m_dragging = true; - setCursor(QCursor(Qt::CrossCursor)); + + repaint(); + emit modified(); } - // Determine the leftmost and rightmost points. - m_leftmost = 0; - m_rightmost = 1; - cc = 0; - while (cc < m_points.size()) - { - p = m_points.point(cc); - if (p != m_grab_point) - { - if (p.x() > m_leftmost && p.x() < x) - m_leftmost = p.x(); - if (p.x() < m_rightmost && p.x() > x) - m_rightmost = p.x(); - } - cc++; - } - repaint(); - emit modified(); + + } void KCurve::mouseReleaseEvent ( QMouseEvent * e ) @@ -228,57 +195,113 @@ void KCurve::mouseMoveEvent ( QMouseEvent * e ) { - double x = e->pos().x() / (float)width(); - double y = 1.0 - e->pos().y() / (float)height(); - - if (!m_dragging) // If no point is selected set the the cursor shape if on top + if(m_dragging) { - double distance = 1000; - double ydistance = 1000; - int cc = 0; - while (cc < m_points.size()) - { - FPoint p = m_points.point(cc); - if (fabs (x - p.x()) < distance) - { - distance = fabs(x - p.x()); - ydistance = fabs(y - p.y()); - } - cc++; - } - if (distance * width() > 5 || ydistance * height() > 5) - setCursor(QCursor(Qt::ArrowCursor)); - else - setCursor(QCursor(Qt::CrossCursor)); - } - else // Else, drag the selected point - { setCursor(QCursor(Qt::CrossCursor)); - x += m_grabOffsetX; - y += m_grabOffsetY; - if (x <= m_leftmost) - x = m_leftmost + 1E-4; // the addition so we can grab the dot later. - if (x >= m_rightmost) - x = m_rightmost - 1E-4; - if (y > 1.0) - y = 1.0; - if (y < 0.0) - y = 0.0; - m_grab_point = FPoint(x, y); - m_points.setPoint( m_pos, m_grab_point); + + // invert mouseY + QPointF mouse(e->pos().x(), height() - e->pos().y()); + + // left bound + qreal leftBound = (m_leftmost < 0) ? 0. : m_points.at(m_leftmost).x(); + qreal rightBound = (m_rightmost >= m_points.size()) ? 1. : m_points.at(m_rightmost).x(); + + // limit to bounds + qreal yPos = qBound(0., mouse.y() / height(), 1.); + qreal xPos = qBound(leftBound + 1E-4, mouse.x() / width(), rightBound - 1E-4); + + QPointF pointSelectedDot(xPos, yPos ); + + // set new position + m_points.setPoint( m_selectedPoint, pointSelectedDot); + repaint(); emit modified(); } } -double KCurve::getCurveValue(double x) +FPointArray KCurve::getCurve() { - return getCurveYValue(m_points, x, m_linear); + return m_points.copy(); } -FPointArray KCurve::getCurve() + +QPainterPath KCurve::curvePath() { - return m_points.copy(); + QPainterPath path; + + if(m_points.size() <= 0) + { + qWarning() << Q_FUNC_INFO << "Can't create path because there are no path nodes!"; + return path; + } + + QPointF pointStart( 0, (1 - m_points.at(0).y()) * height() ); + QPointF pointEnd( width(), (1 - m_points.at(m_points.size() - 1).y()) * height() ); + + QList<QPointF> points; + points.append( pointStart ); + + // Calculate absolute position of each point + for (int i = 0; i < m_points.size(); i++) + { + FPoint p = m_points.at(i); + points.append(QPointF(p.x() * width(), (1 - p.y()) * height())); + } + + points.append( pointEnd ); + + // Add start line + path.moveTo( pointStart ); + path.lineTo( points.at(1) ); + + for (int id = 0; id < points.size(); id++) + { + + if(m_linear) + { + path.lineTo(points.at(id).toPoint()); + } + else + { + + int i0 = id-1; + int i1 = id; + int i2 = id+1; + int i3 = id+2; + + if(i0 < 0 || i2 > points.size()-1 || i3 > points.size() - 1) + continue; + + FPoint p0 = (i0 >= 0) ? points.at(i0) : points.at(i1); + FPoint p1 = points.at(i1); + FPoint p2 = (i2 < points.size()) ? points.at(i2) : points.at(points.size() - 1); + FPoint p3 = (i3 < points.size()) ? points.at(i3) : points.at(points.size() - 1); + + QList<QPointF> inputPoints; + inputPoints.append( p0.toQPointF() ); // Previous point + inputPoints.append( p1.toQPointF() ); // Point + inputPoints.append( p2.toQPointF() ); // Next point + inputPoints.append( p3.toQPointF() ); // Next but one point + + double t = qMax(width() / (p2.x() - p1.x()) / 6, 1.); + + QList<QPointF> *bezPoints = catmullToBezier(inputPoints, t); + + path.cubicTo( + QPointF( bezPoints->at(1) ), // Control 1 + QPointF( bezPoints->at(2) ), // Control 2 + QPointF( bezPoints->at(3) ) // Next Point + ); + } + + } + + // Add end line + path.lineTo( pointEnd ); + + return path; + } void KCurve::setCurve(const FPointArray& inlist) @@ -310,6 +333,18 @@ return m_linear; } +int KCurve::selectedPoint() +{ + return m_points.size() > 0 ? qBound(0, m_selectedPoint, m_points.size() -1) : -1; +} + + +/* ********************************************************************************* * + * + * Constructor + Setup + * + * ********************************************************************************* */ + CurveWidget::CurveWidget( QWidget* parent ) : QWidget( parent ) { CurveWidgetLayout = new QHBoxLayout(this); Index: scribus/ui/curvewidget.h =================================================================== --- scribus/ui/curvewidget.h (Revision 25725) +++ scribus/ui/curvewidget.h (Arbeitskopie) @@ -44,6 +44,7 @@ FPointArray getCurve(); void setCurve(const FPointArray& inlist); void resetCurve(); + QPainterPath curvePath(); void setLinear(bool setter); bool isLinear(); @@ -53,14 +54,13 @@ private: double m_leftmost { 0.0 }; double m_rightmost { 0.0 }; - FPoint m_grab_point; - int m_pos { 0 }; bool m_dragging { false }; bool m_linear { false }; - double m_grabOffsetX { 0.0 }; - double m_grabOffsetY { 0.0 }; FPointArray m_points; FPointArray m_points_back; + QPointF m_mousePos; + int m_selectedPoint { 0 }; + int selectedPoint(); }; class SCRIBUS_API CurveWidget : public QWidget Index: scribus/util_math.cpp =================================================================== --- scribus/util_math.cpp (Revision 25725) +++ scribus/util_math.cpp (Arbeitskopie) @@ -547,3 +547,28 @@ dx = lineX.x1(); dy = lineX.y1(); } + +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t) +{ + // Inspired by the explanation on: https://pomax.github.io/bezierinfo/#catmullconv + + QList<QPointF> *result = new QList<QPointF>(); + + // qDebug() << "t" << t; + + double x0 = inputPoints[1].x(); + double y0 = inputPoints[1].y(); + double x1 = inputPoints[1].x() + (inputPoints[2].x() - inputPoints[0].x()) / (6 * t); + double y1 = inputPoints[1].y() + (inputPoints[2].y() - inputPoints[0].y()) / (6 * t); + double x2 = inputPoints[2].x() - (inputPoints[3].x() - inputPoints[1].x()) / (6 * t); + double y2 = inputPoints[2].y() - (inputPoints[3].y() - inputPoints[1].y()) / (6 * t); + double x3 = inputPoints[2].x(); + double y3 = inputPoints[2].y(); + + result->append( QPointF(x0, y0) ); + result->append( QPointF(x1, y1) ); + result->append( QPointF(x2, y2) ); + result->append( QPointF(x3, y3) ); + + return result; +} Index: scribus/util_math.h =================================================================== --- scribus/util_math.h (Revision 25725) +++ scribus/util_math.h (Arbeitskopie) @@ -36,6 +36,13 @@ bool SCRIBUS_API regionContainsRect(const QRegion& shape, QRect rect); QPolygon SCRIBUS_API flattenPath(const FPointArray& ina, QList<uint> &segments); QList<QPainterPath> SCRIBUS_API decomposePath(const QPainterPath &path); +/** + * @brief CatmullToBezier + * @param inputPoints Point Array of at least 4 points (p0, p1, p2, p3). You will get cubic bezier points from p1 to p2. + * @param t + * @return + */ +QList<QPointF> *catmullToBezier(QList<QPointF> inputPoints, double t = 1); QPainterPath SCRIBUS_API regularPolygonPath(double w, double h, uint c, bool star, double factor, double rota, double factor2 = 0.0, double innerRot = 0.0, double factor3 = 0.0); QPainterPath SCRIBUS_API spiralPath(double spiralWidth, double spiralHeight, double spiralStartAngle, double spiralEndAngle, double spiralFactor); inline double SCRIBUS_API xy2Deg(double x, double y); @@ -43,6 +50,7 @@ inline double SCRIBUS_API cosd(double); inline double SCRIBUS_API square(double); inline double SCRIBUS_API distance(double, double); +inline double SCRIBUS_API distance(QPointF p1, QPointF p2); /*! \brief Constrains an angle of rotation to 45 degree intervals Will make code simpler and reduce interval or provide as a parameter \param angle angle Angle in degrees @@ -78,6 +86,12 @@ return sqrt(x*x + y*y); } +inline double distance(QPointF p1, QPointF p2) +{ + QPointF point = p2 - p1; + return point.manhattanLength(); +} + inline double xy2Deg(double x, double y) { return atan2(y,x) * (180.0/M_PI); |
|
That fixed it |
|
I have now applied your patches. i've brought two noticeable changes tho: - I renamed the distance(QPointF, QPointF) function to manhattanDistance() to avoid any ambiguity with euclidian distance - I modified the allocation method of the returned point list in catmullToBezier() in order to avoid a memory leak |
Date Modified | Username | Field | Change |
---|---|---|---|
2023-10-08 11:10 | nitramr | New Issue | |
2023-10-08 11:10 | nitramr | Status | new => assigned |
2023-10-08 11:10 | nitramr | Assigned To | => nitramr |
2023-10-08 11:10 | nitramr | File Added: curvewidget_2023-10-08_01.patch | |
2023-10-08 11:10 | nitramr | File Added: old-widget.png | |
2023-10-08 11:10 | nitramr | File Added: new-widget.png | |
2023-10-08 14:10 | cbradney | Note Added: 0050370 | |
2023-10-08 15:07 | nitramr | Note Added: 0050371 | |
2023-10-08 15:53 | cbradney | Note Added: 0050372 | |
2023-10-08 16:31 | nitramr | Note Added: 0050373 | |
2023-10-08 16:31 | nitramr | File Added: curvewidget_2023-10-08_02.patch | |
2023-10-08 19:25 | cbradney | Note Added: 0050375 | |
2023-10-08 19:59 | nitramr | Note Added: 0050376 | |
2023-10-08 19:59 | nitramr | File Added: curvewidget_2023-10-08_03.patch | |
2023-10-08 20:19 | cbradney | Note Added: 0050377 | |
2023-10-19 20:23 | jghali | Status | assigned => resolved |
2023-10-19 20:23 | jghali | Resolution | open => fixed |
2023-10-19 20:23 | jghali | Fixed in Version | => 1.7.0.svn |
2023-10-19 20:23 | jghali | Note Added: 0050393 | |
2023-11-04 09:09 | cbradney | Status | resolved => closed |