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. | ||||
| Attached Files | 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);
| ||||
| Patch | Yes | ||||
|
|
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 |