#include "node.h" #include #include #include #include #include const double Node::m_pi = 3.14159265358979323846264338327950288419717; const double Node::m_oneAndHalfPi = Node::m_pi * 1.5; const double Node::m_twoPi = Node::m_pi * 2.0; const QColor Node::m_gold(255,215,0); const QColor Node::m_orange(255,102,0); Node::Node(GraphWidget *parent) : m_graph(parent), m_isActive(false), m_number(-1), m_hasBorder(false), m_numberIsSpecial(false), m_color(m_gold), m_textColor(0,0,0) { setFlag(ItemIsMovable); setFlag(ItemSendsGeometryChanges); setCacheMode(DeviceCoordinateCache); setDefaultTextColor(QColor(0,0,0)); setZValue(2); } Node::~Node() { // dtor of Edge will call removeEdgeFromList on booth nodes. foreach (EdgeElement element, m_edgeList) delete element.edge; } void Node::addEdge(Edge *edge, bool startsFromThisNode) { m_edgeList.push_back(EdgeElement(edge, startsFromThisNode)); edge->adjust(); } void Node::deleteEdge(Node *otherEnd) { for(QList::iterator it = m_edgeList.begin(); it != m_edgeList.end(); it++) { if ((it->edge->sourceNode() == otherEnd && it->edge->destNode() == this) || (it->edge->sourceNode() == this && it->edge->destNode() == otherEnd)) { delete it->edge; return; } } } void Node::removeEdgeFromList(Edge *edge) { for(QList::iterator it = m_edgeList.begin(); it != m_edgeList.end(); it++) { if (it->edge == edge) { m_edgeList.erase(it); return; } } } void Node::adjustEdges() { foreach (EdgeElement element, m_edgeList) element.edge->adjust(); } void Node::setBorder(const bool &hasBorder) { m_hasBorder = hasBorder; update(); } void Node::setActive(const bool &active) { m_isActive = active; update(); } void Node::setEditable(const bool &editable) { setTextInteractionFlags( editable ? Qt::TextEditable : Qt::NoTextInteraction); QTextCursor c = textCursor(); c.setPosition(c.document()->toPlainText().length()); setTextCursor(c); } void Node::setColor(const QColor &color) { m_color = color; update(); } void Node::setTextColor(const QColor &color) { m_textColor = color; update(); } void Node::scale(const qreal &factor) { if (factor * QGraphicsTextItem::scale() < 0.2 || factor * QGraphicsTextItem::scale() > 10 ) return; // it would make stuff difficult, like limiting the pos. inside scene // setTransformOriginPoint(boundingRect().center()); QGraphicsTextItem::setScale(factor * QGraphicsTextItem::scale()); foreach(EdgeElement element, m_edgeList) if (!element.startsFromThisNode) element.edge->setWidth(element.edge->width() + (factor>1 ? 2 : -2) ); adjustEdges(); } void Node::showNumber(const int &number, const bool& show, const bool &numberIsSpecial) { m_number = show ? number : -1; m_numberIsSpecial = numberIsSpecial; update(); } double Node::calculateBiggestAngle() { if (m_edgeList.empty()) return Node::m_oneAndHalfPi; if (m_edgeList.size()==1) return m_edgeList.first().startsFromThisNode ? Node::m_pi - m_edgeList.first().edge->getAngle() : Node::m_twoPi - m_edgeList.first().edge->getAngle(); QList tmp; for(QList::iterator it = m_edgeList.begin(); it != m_edgeList.end(); it++) { tmp.push_back(it->startsFromThisNode ? it->edge->getAngle() : doubleModulo(Node::m_pi + it->edge->getAngle(), Node::m_twoPi)); } qSort(tmp.begin(), tmp.end()); double prev(tmp.first()); double max_prev(tmp.last()); double max(Node::m_twoPi - tmp.last() + tmp.first()); for(QList::const_iterator it = ++tmp.begin(); it!=tmp.end(); it++) { if (*it - prev > max ) { max = *it - prev; max_prev = prev; } prev = *it; } return Node::m_twoPi - doubleModulo(max_prev + max / 2, Node::m_twoPi); } void Node::keyPressEvent(QKeyEvent *event) { // cursor movements switch (event->key()) { case Qt::Key_Left: { QTextCursor c = textCursor(); // textcursor() return just a copy c.movePosition( event->modifiers() == Qt::ControlModifier ? QTextCursor::PreviousWord : QTextCursor::PreviousCharacter); setTextCursor(c); break; } case Qt::Key_Right: { QTextCursor c = textCursor(); c.movePosition( event->modifiers() == Qt::ControlModifier ? QTextCursor::NextWord : QTextCursor::NextCharacter); setTextCursor(c); break; } case Qt::Key_Up: { QTextCursor c = textCursor(); c.movePosition(QTextCursor::Up); setTextCursor(c); break; } case Qt::Key_Down: { QTextCursor c = textCursor(); c.movePosition(QTextCursor::Down); setTextCursor(c); break; } default: // not cursor movement: editing QGraphicsTextItem::keyPressEvent(event); m_graph->contentChanged(); foreach (EdgeElement element, m_edgeList) element.edge->adjust(); } ///@note leaving editing mode is done with esc, handled by graphwidget } bool Node::isConnected(const Node *node) const { foreach (EdgeElement element, m_edgeList) if (element.edge->sourceNode() == node || element.edge->destNode() == node) return true; return false; } QPointF Node::intersect(const QLineF &line, const bool &reverse) const { // QPainterPath shape; // shape.addRoundedRect(sceneBoundingRect(), 20.0, 15.0); // QPainterPath l; // l.moveTo(sceneBoundingRect().center()); // l.lineTo(line.p2()); // return shape.intersected(l).pointAtPercent(0.5); /// @but this just does not work, doing it with brute force QPainterPath path; path.addRoundedRect(sceneBoundingRect(), 28.0, 28.0); if (reverse) { for (qreal t = 1; t!=0; t-=0.01) { if (!path.contains(line.pointAt(t))) return line.pointAt(t); } } else { for (qreal t = 0; t!=1; t+=0.01) { if (!path.contains(line.pointAt(t))) return line.pointAt(t); } } return QPointF(0,0); } QList Node::edgesFrom() const { QList list; foreach(EdgeElement element, m_edgeList) if (element.startsFromThisNode) list.push_back(element.edge); return list; } Edge * Node::edgeTo(const Node *node) const { if (!node) { foreach(EdgeElement element, m_edgeList) if (!element.startsFromThisNode) return element.edge; } foreach(EdgeElement element, m_edgeList) if (element.edge->sourceNode() == node || element.edge->destNode() == node) return element.edge; return 0; } void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *w) { // draw background in hint mode. num == -1 : not in hint mode // if m_numberIsSpecial (can be selected with enter) bg is green, not yellow if (m_number != -1) { painter->setPen(Qt::transparent); painter->setBrush(m_numberIsSpecial ? Qt::green : Qt::yellow); painter->drawRoundedRect(boundingRect(), 20.0, 15.0); } else { m_hasBorder ? painter->setPen(m_isActive ? Qt::red : Qt::blue) : painter->setPen(Qt::transparent); m_isActive? // painter->setBrush(Node::m_orange) : painter->setBrush(m_color.darker(120)) : painter->setBrush(m_color); painter->drawRoundedRect(boundingRect(), 20.0, 15.0); } painter->setBrush(Qt::NoBrush); // the text itself setDefaultTextColor(m_textColor); QGraphicsTextItem::paint(painter, option, w); // print num to topleft corner in hint mode. if (m_number != -1) { painter->setPen(Qt::white); painter->setBackground(Qt::red); painter->setBackgroundMode(Qt::OpaqueMode); painter->drawText(boundingRect().topLeft()+QPointF(0,11), QString("%1").arg(m_number)); } } QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value) { switch (change) { case ItemPositionChange: if (change == ItemPositionChange && scene()) { // value is the new position. QPointF newPos = value.toPointF(); // the fence is reduced with the size of the node QRectF rect (scene()->sceneRect().topLeft(), scene()->sceneRect().bottomRight() - boundingRect().bottomRight() * QGraphicsTextItem::scale() ); if (!rect.contains(newPos)) { // Keep the item inside the scene rect. newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left()))); newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top()))); return newPos; } } break; case ItemPositionHasChanged: m_graph->contentChanged(); foreach (EdgeElement element, m_edgeList) element.edge->adjust(); break; default: break; }; return QGraphicsItem::itemChange(change, value); } void Node::mousePressEvent(QGraphicsSceneMouseEvent *event) { m_graph->nodeSelected(this); QGraphicsItem::mousePressEvent(event); } void Node::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); m_graph->setActiveNodeEditable(); } void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mouseReleaseEvent(event); } void Node::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mouseMoveEvent(event); } /// @bug it seems sceneBoundingRect().contains doesn't care about path retval... QPainterPath Node::shape () const { QPainterPath path; path.addRoundedRect(boundingRect(), 20.0, 15.0); return path; } double Node::doubleModulo(const double &devided, const double &devisor) { return devided - static_cast(devisor * static_cast(devided / devisor)); }