#include "FractoriumPch.h" #include "CurvesGraphicsView.h" /// /// Construct the scene which will have a fixed rect. /// Construct all points, pens and axes. /// /// Pass to the parent CurvesGraphicsView::CurvesGraphicsView(QWidget* parent) : QGraphicsView(parent) { m_Scene.setSceneRect(0, 0, 245, 245); m_AxisPen = QPen(Qt::GlobalColor::white); m_APen = QPen(Qt::GlobalColor::black); m_Pens[0] = &m_APen; m_RPen = QPen(Qt::GlobalColor::red); m_Pens[1] = &m_RPen; m_GPen = QPen(Qt::GlobalColor::green); m_Pens[2] = &m_GPen; m_BPen = QPen(Qt::GlobalColor::blue); m_Pens[3] = &m_BPen; m_APen.setWidth(2); m_RPen.setWidth(2); m_GPen.setWidth(2); m_BPen.setWidth(2); setScene(&m_Scene); //qDebug() << "Original scene rect before setting anything is: " << sceneRect(); m_OriginalRect = sceneRect(); Curves curves(true); Set(curves); show(); //qDebug() << "Original scene rect is: " << m_OriginalRect; } /// /// Called when an underlying point has had its position changed, so emit a signal so that a listener can take action. /// /// The curve whose point value was changed, 0-3. /// The point within the curve whose point value was changed. /// The position of the point. X,Y will each be within 0-1. void CurvesGraphicsView::PointChanged(int curveIndex, int pointIndex, const QPointF& point) { if (curveIndex == m_Index) { double x = point.x() / width(); double y = (height() - point.y()) / height(); emit PointChangedSignal(curveIndex, pointIndex, QPointF(x, y)); } } /// /// Get the position of a given point within a given curve. /// /// The curve whose point value will be retrieved, 0-3. /// The point within the curve whose value will be retrieved, 0-3. /// The position of the point. X,Y will each be within 0-1. QPointF CurvesGraphicsView::Get(int curveIndex, int pointIndex) { if (curveIndex < 4 && pointIndex < m_Points[curveIndex].size()) { EllipseItem* item = m_Points[curveIndex][pointIndex]; return QPointF(item->pos().x() / width(), (height() - item->pos().y()) / height()); } return QPointF(); } /// /// Set the position of a given point within a given curve. /// /// The curve whose point will be set, 0-3. /// The point within the curve which will be set, 0-3 /// The position to set the point to. X,Y will each be within 0-1. void CurvesGraphicsView::Set(int curveIndex, int pointIndex, const QPointF& point) { if (curveIndex < 4 && pointIndex < m_Points[curveIndex].size()) { m_Points[curveIndex][pointIndex]->setPos(point.x() * width(), (1.0 - point.y()) * height());//Scale to scene dimensions, Y axis is flipped. } } void CurvesGraphicsView::Set(Curves& curves) { m_Scene.clear(); m_XLine = new QGraphicsLineItem(); m_XLine->setPen(m_AxisPen); m_XLine->setZValue(0); m_YLine = new QGraphicsLineItem(); m_YLine->setPen(m_AxisPen); m_YLine->setZValue(0); m_Scene.addItem(m_XLine); m_Scene.addItem(m_YLine); auto createpoints = [&](int index, vector& items, Qt::GlobalColor col, int zval) { items.clear(); m_Points[index].clear(); for (int i = 0; i < curves.m_Points[index].size(); i++) { auto item = new EllipseItem(QRectF(-5, -5, 10, 10), index, i, this); items.push_back(item); item->setBrush(QBrush(col)); m_Scene.addItem(item); m_Points[index].push_back(item); item->setZValue(zval); QPointF point(curves.m_Points[index][i].x, curves.m_Points[index][i].y); Set(index, i, point); } }; createpoints(0, m_AllP, Qt::GlobalColor::black, 2); createpoints(1, m_RedP, Qt::GlobalColor::red, 1); createpoints(2, m_GrnP, Qt::GlobalColor::green, 1); createpoints(3, m_BluP, Qt::GlobalColor::blue, 1); SetTop(CurveIndex(m_Index)); } /// /// Set the topmost curve but setting its Z value. /// All other curves will get a value one less. /// /// The curve to set void CurvesGraphicsView::SetTop(CurveIndex curveIndex) { switch (curveIndex) { case CurveIndex::ALL: m_Index = 0; break; case CurveIndex::RED: m_Index = 1; break; case CurveIndex::GREEN: m_Index = 2; break; case CurveIndex::BLUE: default: m_Index = 3; } for (size_t i = 0; i < 4; i++) { bool b = (i == m_Index); for (auto& p : m_Points[i]) p->SetCurrent(b); } } /// /// Overridden paint even which draws the points, axes and curves. /// /// Ignored void CurvesGraphicsView::paintEvent(QPaintEvent* e) { QGraphicsView::paintEvent(e); int i; QRectF rect = scene()->sceneRect(); double w2 = width() / 2; double h2 = height() / 2; //Draw axis lines. m_XLine->setLine(QLineF(0, h2, width(), h2)); m_YLine->setLine(QLineF(w2, 0, w2, height())); //This must be constructed every time and cannot be a member. QPainter painter(viewport()); painter.setClipRect(rect); painter.setRenderHint(QPainter::Antialiasing); auto points = m_Points; for (auto& p : points) { if (p.size() < 2) return; std::sort(p.begin(), p.end(), [&](auto & lhs, auto & rhs) { return lhs->pos().x() < rhs->pos().x(); }); } //Create 4 new paths. These must be constructed every time and cannot be members. //Need to sort the points here first based on their x coordinate. QPainterPath paths[4] = { QPainterPath(points[0][0]->pos()), QPainterPath(points[1][0]->pos()), QPainterPath(points[2][0]->pos()), QPainterPath(points[3][0]->pos()) }; int topmost = 0; //Construct paths or all curves, and draw them for all but the topmost curve. for (i = 0; i < 4; i++) { vector vals; vals.reserve(points[i].size()); for (auto& p : points[i]) vals.push_back({ p->pos().x(), p->pos().y() }); Spline spline(vals); for (int j = 0; j < rect.width(); j++) { auto x = j; auto y = spline.Interpolate(x); paths[i].lineTo(QPointF(x, y)); } if (points[i][0]->zValue() == 1) { painter.setPen(*m_Pens[i]); painter.drawPath(paths[i]); } else topmost = i; } //Draw the topmost curve. painter.setPen(*m_Pens[topmost]); painter.drawPath(paths[topmost]); } void CurvesGraphicsView::mousePressEvent(QMouseEvent* e) { QGraphicsView::mousePressEvent(e); auto thresh = devicePixelRatioF() * 4; auto findpoint = [&](int x, int y, double thresh) -> int { for (int i = 0; i < m_Points[m_Index].size(); i++) { auto item = m_Points[m_Index][i]; auto xdist = std::abs(item->pos().x() - x); auto ydist = std::abs(item->pos().y() - y); auto threshAgain = thresh; if (xdist < threshAgain && ydist < threshAgain) return i; } return -1; }; if (e->button() == Qt::RightButton) { int i = findpoint(e->pos().x(), e->pos().y(), thresh); if (i != -1) emit PointRemovedSignal(m_Index, i); } else if (findpoint(e->pos().x(), e->pos().y(), thresh * 2) == -1) { QRectF rect = scene()->sceneRect(); auto points = m_Points[m_Index]; if (points.size() < 2) return; std::sort(points.begin(), points.end(), [&](auto & lhs, auto & rhs) { return lhs->pos().x() < rhs->pos().x(); }); vector vals; vals.reserve(points.size()); for (auto& p : points) vals.push_back({ p->pos().x(), p->pos().y() }); Spline spline(vals); for (int j = 0; j < rect.width(); j++) { auto y = spline.Interpolate(j); auto xdist = std::abs(j - e->pos().x()); auto ydist = std::abs(y - e->pos().y()); if (xdist < thresh && ydist < thresh) { double x = e->pos().x() / (double)width(); double y = (height() - e->pos().y()) / (double)height(); emit PointAddedSignal(m_Index, QPointF(x, y)); break; } } } }