#include "qt/graphics/QtAngledLineItem.h" #include #include #include #include "utility/math/Vector2.h" QtAngledLineItem::QtAngledLineItem(QGraphicsItem* parent) : QGraphicsLineItem(parent) , m_route(ROUTE_ANY) , m_pivot(PIVOT_THIRD) , m_onFront(false) , m_onBack(false) , m_horizontalIn(false) , m_showArrow(true) { this->setAcceptHoverEvents(true); this->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); } QtAngledLineItem::~QtAngledLineItem() { } void QtAngledLineItem::updateLine( Vec4i ownerRect, Vec4i targetRect, Vec4i ownerParentRect, Vec4i targetParentRect, GraphViewStyle::EdgeStyle style, size_t weight, bool showArrow ){ prepareGeometryChange(); m_polygon.clear(); m_ownerRect = ownerRect; m_targetRect = targetRect; m_ownerParentRect = ownerParentRect; m_targetParentRect = targetParentRect; m_ownerRect.x = m_ownerRect.x - 1; m_ownerRect.z = m_ownerRect.z + 1; m_targetRect.x = m_targetRect.x - 1; m_targetRect.z = m_targetRect.z + 1; m_style = style; m_showArrow = showArrow; this->setPen(QPen(QBrush(style.color.c_str()), style.width + int(log10(weight)), Qt::SolidLine, Qt::RoundCap)); } void QtAngledLineItem::setRoute(Route route) { m_route = route; } void QtAngledLineItem::setPivot(Pivot pivot) { m_pivot = pivot; } void QtAngledLineItem::setOnFront(bool front) { m_onFront = front; } void QtAngledLineItem::setOnBack(bool back) { m_onBack = back; } void QtAngledLineItem::setHorizontalIn(bool horizontal) { m_horizontalIn = horizontal; } QPainterPath QtAngledLineItem::shape() const { QPainterPath path; QPolygon poly = getPath(); for (int i = 0; i < poly.size() - 1; i++) { path.addRect(QRectF(poly.at(i), poly.at(i + 1)).normalized().adjusted(-3, -3, 3, 3)); } path.addRect(getArrowBoundingRect(poly).adjusted(-3, -3, 3, 3)); return path; } void QtAngledLineItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* options, QWidget* widget) { painter->setPen(pen()); QPainterPath path; QPolygon poly = getPath(); int i = poly.length() - 1; path.moveTo(poly.at(i)); // while (i > 0) // { // i--; // path.lineTo(poly.at(i)); // } // painter->drawPath(path); // return; QRectF drawRect = options->exposedRect; QRectF partRect; int radius = m_style.cornerRadius; int dir = getDirection(poly.at(i), poly.at(i - 1)); while (i > 1) { i--; QPointF a = poly.at(i); QPointF b = poly.at(i - 1); int newDir = getDirection(a, b); int ar = radius; int br = m_style.cornerRadius; if (i != 1) { if (dir % 2 == 1 && std::abs(a.y() - b.y()) < 2 * br) { br = std::abs(a.y() - b.y()) / 2; } else if (dir % 2 == 0 && std::abs(a.x() - b.x()) < 2 * br) { br = std::abs(a.x() - b.x()) / 2; } } switch (dir) { case 0: a.setY(a.y() + ar); break; case 1: a.setX(a.x() - ar); break; case 2: a.setY(a.y() - ar); break; case 3: a.setX(a.x() + ar); break; } b = poly.at(i); switch (newDir) { case 0: b.setY(b.y() - br); break; case 1: b.setX(b.x() + br); break; case 2: b.setY(b.y() + br); break; case 3: b.setX(b.x() - br); break; } partRect = QRectF(poly.at(i + 1), poly.at(i)).adjusted(-1, -1, 1, 1); if (drawRect.intersects(partRect)) { path.lineTo(a); } else { path.moveTo(a); } partRect = QRectF(a, b).normalized().adjusted(-1, -1, 1, 1); if (drawRect.intersects(partRect)) { switch (dir) { case 0: if (newDir == 1) { path.arcTo(a.x(), b.y(), 2 * br, 2 * ar, 180, -90); } else if (newDir == 3) { path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, 90); } break; case 1: if (newDir == 0) { path.arcTo(a.x() - ar, b.y() - br, 2 * ar, 2 * br, -90, 90); } else if (newDir == 2) { path.arcTo(a.x() - ar, a.y(), 2 * ar, 2 * br, 90, -90); } break; case 2: if (newDir == 1) { path.arcTo(a.x(), a.y() - ar, 2 * br, 2 * ar, 180, 90); } else if (newDir == 3) { path.arcTo(b.x() - br, a.y() - ar, 2 * br, 2 * ar, 0, -90); } break; case 3: if (newDir == 0) { path.arcTo(b.x(), b.y() - br, 2 * ar, 2 * br, -90, -90); } else if (newDir == 2) { path.arcTo(b.x(), a.y(), 2 * ar, 2 * br, 90, 90); } break; } } else { path.moveTo(b); } dir = newDir; radius = br; } partRect = QRectF(poly.at(0), poly.at(1)).adjusted(-1, -1, 1, 1); if (drawRect.intersects(partRect)) { partRect = getArrowBoundingRect(poly); if (drawRect.intersects(partRect) && m_showArrow) { drawArrow(poly, &path); } else { path.lineTo(poly.at(0)); } } painter->drawPath(path); } QPolygon QtAngledLineItem::getPath() const { if (m_polygon.size() > 0) { return m_polygon; } const Vec4i& oR = m_ownerRect; const Vec4i& oPR = m_ownerParentRect; const Vec4i& tR = m_targetRect; const Vec4i& tPR = m_targetParentRect; const Vec2i& oOff = m_style.originOffset; const Vec2i& tOff = m_style.targetOffset; Vec2f oP[4]; getPivotPoints(oP, oR, oPR, oOff.y, false); Vec2f tP[4]; getPivotPoints(tP, tR, tPR, tOff.y, true); int io = -1; int it = -1; float dist = -1; std::map dists; if (m_onFront) { io = 3; it = 3; } else if (m_onBack) { io = 1; it = 1; } else { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (m_route == ROUTE_HORIZONTAL && (i % 2 == 0 || j % 2 == 0)) { continue; } else if (m_route == ROUTE_VERTICAL && (i % 2 == 1 || j % 2 == 1)) { continue; } else if (i % 2 != j % 2) { continue; } Vec2f diff = oP[i] - tP[j]; float d = diff.getLength(); dists.emplace((i << 2) + j, d); if (dist < 0 || d < dist) { dist = d; io = i; it = j; } } } } Vec2f o[4]; getPivotPoints(o, oR, oR, oOff.y, false); Vec2f t[4]; getPivotPoints(t, tR, tR, tOff.y, true); QPoint a(t[it].x, t[it].y); QPoint d(o[io].x, o[io].y); QPoint b(tP[it].x, tP[it].y); QPoint c(oP[io].x, oP[io].y); switch (it) { case 0: b.setY(b.y() - tOff.x); break; case 1: b.setX(b.x() + tOff.x); break; case 2: b.setY(b.y() + tOff.x); break; case 3: b.setX(b.x() - tOff.x); break; } switch (io) { case 0: c.setY(c.y() - oOff.x); break; case 1: c.setX(c.x() + oOff.x); break; case 2: c.setY(c.y() + oOff.x); break; case 3: c.setX(c.x() - oOff.x); break; } if (it != io) { if ((it == 1 && b.x() < c.x()) || (io == 1 && b.x() > c.x())) { if (m_horizontalIn) { b.setX(c.x()); } else { c.setX(b.x()); } } else if ((it == 2 && b.y() < c.y()) || (io == 2 && b.y() > c.y())) { c.setY(b.y()); } else if ( (it == 3 && b.x() < c.x()) || (io == 3 && b.x() > c.x()) || (it == 0 && b.y() < c.y()) || (io == 0 && b.y() > c.y())) { float dist1 = dists[(io << 2) + ((it + 2) % 4)]; float dist2 = dists[(((io + 2) % 4) << 2) + it]; if (dist1 < dist2) { it = (it + 2) % 4; a = QPoint(t[it].x, t[it].y); b = QPoint(tP[it].x, tP[it].y); switch (it) { case 0: b.setY(b.y() - tOff.x); break; case 1: b.setX(b.x() + tOff.x); break; case 2: b.setY(b.y() + tOff.x); break; case 3: b.setX(b.x() - tOff.x); break; } } else { io = (io + 2) % 4; d = QPoint(o[io].x, o[io].y); c = QPoint(oP[io].x, oP[io].y); switch (io) { case 0: c.setY(c.y() - oOff.x); break; case 1: c.setX(c.x() + oOff.x); break; case 2: c.setY(c.y() + oOff.x); break; case 3: c.setX(c.x() - oOff.x); break; } } } } if (it % 2 == 1) { if (it == io && ((it == 1 && b.x() < c.x()) || (it == 3 && b.x() > c.x()))) { b.setX(c.x()); } else { c.setX(b.x()); } } else { if (it == io && ((it == 0 && b.y() > c.y()) || (it == 2 && b.y() < c.y()))) { b.setY(c.y()); } else { c.setY(b.y()); } } if (it % 2 == 0) { int vo = m_style.verticalOffset; if (c.x() > b.x()) { vo *= -1; } b.setY(b.y() + vo); c.setY(c.y() + vo); } else { int vo = m_style.verticalOffset; if (c.y() > b.y()) { vo *= -1; } b.setX(b.x() + vo); c.setX(c.x() + vo); } QPolygon poly; poly << a << b << c << d; m_polygon = poly; return poly; } int QtAngledLineItem::getDirection(const QPointF& a, const QPointF& b) const { if (a.x() != b.x()) { if (a.x() < b.x()) { return 1; // right } else { return 3; // left } } else { if (a.y() < b.y()) { return 2; // down } else { return 0; // up } } } QRectF QtAngledLineItem::getArrowBoundingRect(const QPolygon& poly) const { int dir = getDirection(poly.at(1), poly.at(0)); QRectF rect( poly.at(0).x(), poly.at(0).y(), (dir % 2 == 1 ? m_style.arrowLength : m_style.arrowWidth), (dir % 2 == 0 ? m_style.arrowLength : m_style.arrowWidth) ); switch (dir) { case 0: rect.moveLeft(rect.left() - rect.width() / 2); break; case 1: rect.moveLeft(rect.left() - rect.width()); rect.moveTop(rect.top() - rect.height() / 2); break; case 2: rect.moveLeft(rect.left() - rect.width() / 2); rect.moveTop(rect.top() - rect.height()); break; case 3: rect.moveTop(rect.top() - rect.height() / 2); break; } return rect; } void QtAngledLineItem::drawArrow(const QPolygon& poly, QPainterPath* path) const { int dir = getDirection(poly.at(1), poly.at(0)); QPointF tip = poly.at(0); QPointF toBack; QPointF toLeft; switch (dir) { case 0: toBack.setY(m_style.arrowLength); toLeft.setX(-m_style.arrowWidth / 2); break; case 1: toBack.setX(-m_style.arrowLength); toLeft.setY(-m_style.arrowWidth / 2); break; case 2: toBack.setY(-m_style.arrowLength); toLeft.setX(m_style.arrowWidth / 2); break; case 3: toBack.setX(m_style.arrowLength); toLeft.setY(m_style.arrowWidth / 2); break; } if (m_style.arrowClosed) { path->lineTo(tip + toBack); path->moveTo(tip); } else { path->lineTo(tip); } path->lineTo(tip + toBack + toLeft); if (m_style.arrowClosed) { path->lineTo(tip + toBack - toLeft); } else { path->moveTo(tip + toBack - toLeft); } path->lineTo(tip); } void QtAngledLineItem::getPivotPoints(Vec2f* p, const Vec4i& in, const Vec4i& out, int offset, bool target) const { float f = m_pivot == PIVOT_THIRD ? (target ? 2 / 3.f : 1 / 3.f) : 1 / 2.f; p[0] = Vec2f(in.x + (in.z - in.x) * f + offset, out.y); p[2] = Vec2f(in.x + (in.z - in.x) * f + offset, out.w); p[1] = Vec2f(out.z, in.y + (in.w - in.y) * f + offset); p[3] = Vec2f(out.x, in.y + (in.w - in.y) * f + offset); }