#include "EdgeItem.h" #include "DiagramScene.h" #include "core/NetworkModel.h" #include "core/Router.h" #include #include #include namespace diag { EdgeItem::EdgeItem(DiagramScene* scene, QUuid edgeId) : m_scene(scene), m_id(edgeId) { setFlag(ItemIsSelectable); setZValue(0); setAcceptHoverEvents(true); } NetworkModel* EdgeItem::model() const { return m_scene->model(); } void EdgeItem::setRoute(const QList& poly, const QList& hops) { prepareGeometryChange(); m_poly = poly; m_hops = hops; const RouteConfig& cfg = m_scene->routeConfig(); m_path = Router::toPath(poly, cfg.arcOnIntersection ? hops : QList{}, cfg.arcRadius); update(); } QRectF EdgeItem::boundingRect() const { const double m = 14; return m_path.boundingRect().adjusted(-m, -m, m, m); } QPainterPath EdgeItem::shape() const { QPainterPathStroker stroker; stroker.setWidth(qMax(10.0, basePen().widthF() + 6)); return stroker.createStroke(m_path); } QPen EdgeItem::basePen() const { const Theme& theme = m_scene->theme(); const Edge* e = model()->edge(m_id); QColor color; const QString colorName = model()->edgeProperty(m_id, QStringLiteral("color")).toString(); if (QColor::isValidColorName(colorName)) color = QColor(colorName); else color = theme.relationColor(e ? model()->registry()->relation(e->relation) : nullptr); const double width = model()->edgeProperty(m_id, QStringLiteral("lineWidth")).toDouble(); const QString style = model()->edgeProperty(m_id, QStringLiteral("lineStyle")).toString(); Qt::PenStyle penStyle = Qt::SolidLine; if (style == QLatin1String("Dashed")) penStyle = Qt::DashLine; else if (style == QLatin1String("Dotted")) penStyle = Qt::DotLine; else if (style == QLatin1String("DashDot")) penStyle = Qt::DashDotLine; QPen pen(color, qMax(0.5, width), penStyle, Qt::FlatCap, Qt::MiterJoin); return pen; } void EdgeItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { if (m_poly.size() < 2) return; painter->setRenderHint(QPainter::Antialiasing); const Theme& theme = m_scene->theme(); const QPen pen = basePen(); if (isSelected()) { QColor halo = theme.selection; halo.setAlpha(90); painter->setPen(QPen(halo, pen.widthF() + 6, Qt::SolidLine, Qt::RoundCap)); painter->setBrush(Qt::NoBrush); painter->drawPath(m_path); } painter->setPen(pen); painter->setBrush(Qt::NoBrush); painter->drawPath(m_path); // End decorations sit on the raw polyline ends (arcs never reach ends). const QString head = model()->edgeProperty(m_id, QStringLiteral("headDecoration")).toString(); const QString tail = model()->edgeProperty(m_id, QStringLiteral("tailDecoration")).toString(); drawDecoration(painter, head, m_poly.last(), m_poly.at(m_poly.size() - 2), pen.color(), pen.widthF()); drawDecoration(painter, tail, m_poly.first(), m_poly.at(1), pen.color(), pen.widthF()); if (m_scene->flowAnimationEnabled()) drawFlowOverlay(painter, m_path, pen.widthF()); const QString title = model()->edgeProperty(m_id, QStringLiteral("title")).toString(); if (!title.isEmpty()) { const QPointF mid = Router::pointAt(m_poly, 0.5); painter->setPen(theme.label); QFont f = painter->font(); f.setPointSizeF(8.0); painter->setFont(f); painter->drawText(QRectF(mid.x() - 60, mid.y() - 18, 120, 14), Qt::AlignCenter, title); } } void EdgeItem::drawDecoration(QPainter* painter, const QString& kind, QPointF tip, QPointF back, const QColor& color, double width) const { if (kind.isEmpty() || kind == QLatin1String("None")) return; QLineF dir(back, tip); if (dir.length() < 1e-6) return; dir.setLength(1.0); const QPointF u = dir.p2() - dir.p1(); // unit vector toward the tip const QPointF n(-u.y(), u.x()); // unit normal const double s = qMax(8.0, width * 3.5); // decoration size painter->save(); painter->setPen(QPen(color, qMax(1.0, width * 0.8))); painter->setBrush(color); if (kind == QLatin1String("Arrow")) { QPolygonF poly{tip, tip - u * s + n * s * 0.45, tip - u * s - n * s * 0.45}; painter->drawPolygon(poly); } else if (kind == QLatin1String("Circle")) { painter->drawEllipse(tip - u * s * 0.5, s * 0.5, s * 0.5); } else if (kind == QLatin1String("Diamond")) { const QPointF c = tip - u * s * 0.5; QPolygonF poly{tip, c + n * s * 0.4, tip - u * s, c - n * s * 0.4}; painter->drawPolygon(poly); } else if (kind == QLatin1String("Bar")) { painter->setPen(QPen(color, qMax(1.5, width))); painter->drawLine(tip + n * s * 0.5, tip - n * s * 0.5); } painter->restore(); } void EdgeItem::drawFlowOverlay(QPainter* painter, const QPainterPath& path, double width) const { const Edge* e = model()->edge(m_id); if (!e || qAbs(e->flowRate) < 1e-9) return; const double maxFlow = qMax(1e-9, m_scene->maxAbsFlow()); const double ratio = qBound(0.15, qAbs(e->flowRate) / maxFlow, 1.0); // Moving dashes: speed and thickness encode volume, offset sign encodes // direction (positive flow runs from tail to head). QPen pen(m_scene->theme().flowOverlay, qMax(1.0, width * (0.25 + 0.45 * ratio)), Qt::CustomDashLine, Qt::FlatCap); pen.setDashPattern({1.2, 3.2}); const double dir = e->flowRate > 0 ? -1.0 : 1.0; pen.setDashOffset(dir * m_scene->animationPhase() * (0.4 + 1.6 * ratio)); painter->setPen(pen); painter->setBrush(Qt::NoBrush); painter->drawPath(path); } } // namespace diag