#include "qt/view/graphElements/QtGraphEdge.h" #include #include #include #include #include #include "utility/messaging/type/MessageActivateTokens.h" #include "component/view/graphElements/GraphNode.h" #include "data/graph/token_component/TokenComponentAggregation.h" #include "qt/graphics/QtGraphicsRoundedRectItem.h" QtStraightConnection::QtStraightConnection(Vec4i ownerRect, Vec4i targetRect, int number, QGraphicsItem* parent) : QGraphicsLineItem(parent) { const Vec4i& o = ownerRect; const Vec4i& t = targetRect; Vec2f a((o.x + o.z) / 2, (o.y + o.w) / 2); Vec2f b((t.x + t.z) / 2, (t.y + t.w) / 2); this->setLine(a.x, a.y, b.x, b.y); this->setAcceptHoverEvents(true); Vec2f u = b - a; u.normalize(); float alpha = atan2(u.y, u.x); Vec2f w; w.x = (o.z - o.x) / 2 * cos(alpha); w.y = (o.w - o.y) / 2 * sin(alpha); a += u * w.getLength(); w.x = (t.z - t.x) / 2 * cos(alpha); w.y = (t.w - t.y) / 2 * sin(alpha); b += u * -w.getLength(); Vec2f mid = (a + b) / 2; m_circle = new QtGraphicsRoundedRectItem(this); m_circle->setRect(mid.x - 10, mid.y - 10, 20, 20); m_circle->setRadius(10); m_circle->setPen(QPen(QColor("#F8F8F8"), 2)); m_circle->setBrush(QBrush(QColor("#FFF"))); m_circle->setAcceptHoverEvents(true); QFont font; font.setFamily("Myriad Pro"); font.setWeight(QFont::Normal); font.setPixelSize(9); m_number = new QGraphicsSimpleTextItem(this); m_number->setFont(font); QString numberStr = QString::number(number); m_number->setText(numberStr); m_number->setBrush(QBrush(QColor("#666"))); m_number->setPos( mid.x - QFontMetrics(m_number->font()).width(numberStr) / 2, mid.y - QFontMetrics(m_number->font()).height() / 2 ); } QtStraightConnection::~QtStraightConnection() { } void QtStraightConnection::setColor(QColor color) { QPen p = this->pen(); p.setColor(color); this->setPen(p); p = m_circle->pen(); p.setColor(color); m_circle->setPen(p); } QtCorneredConnection::QtCorneredConnection( Vec4i ownerRect, Vec4i targetRect, Vec4i ownerParentRect, Vec4i targetParentRect, QGraphicsItem* parent ) : QGraphicsLineItem(parent) , m_ownerRect(ownerRect) , m_targetRect(targetRect) , m_ownerParentRect(ownerParentRect) , m_targetParentRect(targetParentRect) { this->setAcceptHoverEvents(true); 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; } QtCorneredConnection::~QtCorneredConnection() { } QPainterPath QtCorneredConnection::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(-5, -5, 5, 5)); } return path; } void QtCorneredConnection::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)); int radius = 10; int dir = getDirection(poly.at(i), poly.at(i - 1)); while (i > 0) { i--; if (i == 0) { path.lineTo(poly.at(i)); } else { QPointF a = poly.at(i); QPointF b = poly.at(i - 1); int newDir = getDirection(a, b); int ar = radius; int br = 10; 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; } 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; } dir = newDir; radius = br; } } int arrowLength = 5; int arrowWidth = 8; QPointF arrow = poly.at(0) + QPointF((poly.at(0).x() - poly.at(1).x() > 0 ? -1 : 1) * arrowLength, -arrowWidth / 2); path.lineTo(arrow); arrow.setY(arrow.y() + arrowWidth); path.moveTo(arrow); path.lineTo(poly.at(0)); painter->drawPath(path); } QPolygon QtCorneredConnection::getPath() const { const Vec4i& oR = m_ownerRect; const Vec4i& tR = m_targetRect; Vec2f o[2] = { Vec2f(m_ownerParentRect.x, (oR.y + 2 * oR.w) / 3), Vec2f(m_ownerParentRect.z, (oR.y + 2 * oR.w) / 3) }; Vec2f t[2] = { Vec2f(m_targetParentRect.x, (2 * tR.y + tR.w) / 3), Vec2f(m_targetParentRect.z, (2 * tR.y + tR.w) / 3) }; int io = -1; int it = -1; float dist = -1; for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { Vec2f diff = o[i] - t[j]; if (dist < 0 || diff.getLength() < dist) { dist = diff.getLength(); io = i; it = j; } } } float offset = 15; QPoint tp((it ? 1 : -1) * offset + t[it].x, t[it].y); QPoint op((io ? 1 : -1) * offset + o[io].x, o[io].y); if (it != io) { if (it && tp.x() < op.x()) { io = 0; op = QPoint(o[io].x - offset, o[io].y); } else if (!it && tp.x() < op.x()) { it = 1; tp = QPoint(t[it].x + offset, t[it].y); } else if (io && tp.x() > op.x()) { io = 1; op = QPoint(o[io].x + offset, o[io].y); } else if (!io && tp.x() > op.x()) { it = 0; tp = QPoint(t[it].x - offset, t[it].y); } } if (it == io && ((it && tp.x() < op.x()) || (!it && tp.x() > op.x())) ) { tp.setX(op.x()); } else { op.setX(tp.x()); } o[0] = Vec2f(oR.x, (oR.y + 2 * oR.w) / 3); o[1] = Vec2f(oR.z, (oR.y + 2 * oR.w) / 3); t[0] = Vec2f(tR.x, (2 * tR.y + tR.w) / 3); t[1] = Vec2f(tR.z, (2 * tR.y + tR.w) / 3); QPolygon poly; poly << QPoint(t[it].x, t[it].y); poly << tp; poly << op; poly << QPoint(o[io].x, o[io].y); return poly; } int QtCorneredConnection::getDirection(const QPointF& a, const QPointF& b) const { if (a.x() != b.x()) { if (a.x() < b.x()) { return 1; } else { return 3; } } else { if (a.y() < b.y()) { return 2; } else { return 0; } } } QtGraphEdge::QtGraphEdge(const std::weak_ptr& owner, const std::weak_ptr& target, const Edge* data) : GraphEdge(data) , m_owner(owner) , m_target(target) , m_child(nullptr) , m_isActive(false) , m_mousePos(0.0f, 0.0f) , m_mouseMoved(false) { this->setAcceptHoverEvents(true); this->setZValue(getZValue(false)); // Used to draw edges always on top of nodes. this->updateLine(); } QtGraphEdge::~QtGraphEdge() { } std::weak_ptr QtGraphEdge::getOwner() { return m_owner; } std::weak_ptr QtGraphEdge::getTarget() { return m_target; } void QtGraphEdge::updateLine() { std::shared_ptr owner = m_owner.lock(); std::shared_ptr target = m_target.lock(); if (owner == NULL || target == NULL) { LOG_WARNING("Either the owner or the target node is null."); return; } if (m_child) { delete m_child; } if (isAggregation()) { m_child = new QtStraightConnection(owner->getBoundingRect(), target->getBoundingRect(), getAggregationCount(), this); } else { m_child = new QtCorneredConnection( owner->getBoundingRect(), target->getBoundingRect(), owner->getParentBoundingRect(), target->getParentBoundingRect(), this ); } QColor color; switch (getData()->getType()) { case Edge::EDGE_CALL: color = QColor("#F1C100"); break; case Edge::EDGE_USAGE: color = QColor("#62B29D"); break; case Edge::EDGE_INHERITANCE: color = QColor("#CC5E89"); break; case Edge::EDGE_AGGREGATION: color = QColor("#F8F8F8"); break; default: color = QColor("#878787"); break; } m_child->setPen(QPen(color, getPenWidth(), Qt::SolidLine, Qt::RoundCap)); setIsActive(m_isActive); } bool QtGraphEdge::getIsActive() const { return m_isActive; } void QtGraphEdge::setIsActive(bool isActive) { m_isActive = isActive; if (isActive) { if (isAggregation()) { dynamic_cast(m_child)->setColor(QColor("#EEE")); } else { QPen p = m_child->pen(); p.setWidthF(getPenWidth() + 1); m_child->setPen(p); } this->setZValue(getZValue(isActive)); } else { if (isAggregation()) { dynamic_cast(m_child)->setColor(QColor("#F8F8F8")); } else { QPen p = m_child->pen(); p.setWidthF(getPenWidth()); m_child->setPen(p); } this->setZValue(getZValue(isActive)); } } void QtGraphEdge::onClick() { if (isAggregation()) { const std::set& ids = getData()->getComponent()->getAggregationIds(); MessageActivateTokens message(std::vector(ids.begin(), ids.end())); message.isAggregation = true; message.dispatch(); } else { MessageActivateTokens message(getData()->getId()); message.isEdge = true; message.dispatch(); } } void QtGraphEdge::mousePressEvent(QGraphicsSceneMouseEvent* event) { m_mousePos = Vec2i(event->scenePos().x(), event->scenePos().y()); m_mouseMoved = false; } void QtGraphEdge::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { Vec2i mousePos = Vec2i(event->scenePos().x(), event->scenePos().y()); if ((mousePos - m_mousePos).getLength() > 1.0f) { m_mouseMoved = true; } } void QtGraphEdge::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (!m_mouseMoved) { this->onClick(); } } void QtGraphEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { bool isActive = m_isActive; this->setIsActive(true); m_isActive = isActive; } void QtGraphEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { this->setIsActive(m_isActive); } bool QtGraphEdge::isAggregation() const { return getData()->getType() == Edge::EDGE_AGGREGATION; } int QtGraphEdge::getZValue(bool active) const { if (isAggregation()) { if (active) { return -1; } return -5; } if (active) { return 5; } return 1; } float QtGraphEdge::getPenWidth() const { if (isAggregation()) { return getAggregationCount() + 1; } return 1.5; } int QtGraphEdge::getAggregationCount() const { if (isAggregation()) { return getData()->getComponent()->getAggregationCount(); } return 0; }