diff --git a/include/node.h b/include/node.h index 348c46d..b6a77db 100644 --- a/include/node.h +++ b/include/node.h @@ -16,14 +16,20 @@ public: Node(GraphWidget *graphWidget = 0); ~Node(); - void moveNode(QGraphicsSceneMouseEvent *event); + // add/remove edges void addEdge(Edge *edge, bool startsFromThisNode); void deleteEdge(Node *otherEnd); void removeEdgeFromList(Edge *edge); -// void adjustEdges(); - void setBorder(const bool &hasBorder); - void setActive(const bool &active = true); + // graph traversal + QList edgesFrom(const bool &excludeSecondaries = true) const; + QList edgesToThis(const bool &excludeSecondaries = true) const; + Edge * edgeTo(const Node* node) const; + QList subtree() const; + bool isConnected(const Node *node) const; + + // prop set/get + void setBorder(const bool &hasBorder = true); void setEditable(const bool &editable = true); void setColor(const QColor &color); QColor color() const { return m_color; } @@ -31,20 +37,21 @@ public: QColor textColor() const { return m_textColor; } void setScale(const qreal &factor, const QRectF &sceneRect); + // show numbers in hint mode void showNumber(const int &number, const bool& show = true, const bool &numberIsSpecial = false); - void insertPicture(const QString &picture, const int &pos = 0); - double calculateBiggestAngle(); + // insert picture to the cursor's current position + void insertPicture(const QString &picture); // changing visibility from prot to pub + // so GraphWidget::keyPressEvent can call it edit during editing void keyPressEvent(QKeyEvent *event); - bool isConnected(const Node *node) const; + + // calculetes the intersection of line and shape of this Node QPointF intersection(const QLineF &line, const bool &reverse = false) const; - QList edgesFrom(const bool &excludeSecondaries = true) const; - QList edgesToThis(const bool &excludeSecondaries = true) const; - Edge * edgeTo(const Node* node) const; - QList subtree() const; + // returns with the biggest angle between the edges + double calculateBiggestAngle() const; protected: @@ -60,7 +67,7 @@ protected: private: - double doubleModulo(const double &devided, const double &devisor); + double doubleModulo(const double &devided, const double &devisor) const; struct EdgeElement { @@ -71,7 +78,6 @@ private: QList m_edgeList; GraphWidget *m_graph; - bool m_isActive; int m_number; bool m_hasBorder; bool m_numberIsSpecial; @@ -82,7 +88,6 @@ private: static const double m_oneAndHalfPi; static const double m_twoPi; - static const QColor m_orange; static const QColor m_gold; }; diff --git a/src/graphwidget.cpp b/src/graphwidget.cpp index f71c525..07f21ba 100644 --- a/src/graphwidget.cpp +++ b/src/graphwidget.cpp @@ -165,7 +165,7 @@ bool GraphWidget::readContentFromXmlFile(const QString &fileName) // test the first node the active one m_activeNode = m_nodeList.first(); - m_activeNode->setActive(); + m_activeNode->setBorder(); m_activeNode->setFocus(); this->show(); @@ -197,8 +197,10 @@ void GraphWidget::writeContentToXmlFile(const QString &fileName) cn.setAttribute( "bg_green", QString::number(node->color().green())); cn.setAttribute( "bg_blue", QString::number(node->color().blue())); cn.setAttribute( "text_red", QString::number(node->textColor().red())); - cn.setAttribute( "text_green", QString::number(node->textColor().green())); - cn.setAttribute( "text_blue", QString::number(node->textColor().blue())); + cn.setAttribute( "text_green", + QString::number(node->textColor().green())); + cn.setAttribute( "text_blue", + QString::number(node->textColor().blue())); nodes_root.appendChild(cn); } @@ -260,10 +262,6 @@ void GraphWidget::writeContentToPngFile(const QString &fileName) void GraphWidget::insertNode() { - /// @note this is TERRIBLE! - // basically when insertNode() is called from mainToolBar, it needs to - // wait for a paralell nodeLostFocus() to finish... - // so I call ANOTHER one which takes the same amount of time...just kill me nodeLostFocus(); if (!m_activeNode) @@ -595,15 +593,21 @@ void GraphWidget::keyPressEvent(QKeyEvent *event) else if (event->key() == Qt::Key_Down) node->moveBy(0, 20); else if (event->key() == Qt::Key_Left) node->moveBy(-20, 0); else if (event->key() == Qt::Key_Right) node->moveBy(20, 0); + contentChanged(); } } else // Move just the active Node. { - if (event->key() == Qt::Key_Up) m_activeNode->moveBy(0, -20); - else if (event->key() == Qt::Key_Down) m_activeNode->moveBy(0, 20); - else if (event->key() == Qt::Key_Left) m_activeNode->moveBy(-20, 0); - else if (event->key() == Qt::Key_Right) m_activeNode->moveBy(20, 0); + if (event->key() == Qt::Key_Up) + m_activeNode->moveBy(0, -20); + else if (event->key() == Qt::Key_Down) + m_activeNode->moveBy(0, 20); + else if (event->key() == Qt::Key_Left) + m_activeNode->moveBy(-20, 0); + else if (event->key() == Qt::Key_Right) + m_activeNode->moveBy(20, 0); + contentChanged(); } } @@ -832,7 +836,7 @@ void GraphWidget::addFirstNode() m_nodeList.append(node); m_activeNode = m_nodeList.first(); - m_activeNode->setActive(); + m_activeNode->setBorder(); } void GraphWidget::removeAllNodes() @@ -848,10 +852,10 @@ void GraphWidget::removeAllNodes() void GraphWidget::setActiveNode(Node *node) { if (m_activeNode!=0) - m_activeNode->setActive(false); + m_activeNode->setBorder(false); m_activeNode = node; - m_activeNode->setActive(); + m_activeNode->setBorder(); } // re-draw numbers diff --git a/src/node.cpp b/src/node.cpp index 080a0ef..7b42a74 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -11,11 +11,9 @@ 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), @@ -35,11 +33,6 @@ Node::~Node() foreach (EdgeElement element, m_edgeList) delete element.edge; } -void Node::moveNode(QGraphicsSceneMouseEvent *event) -{ - QGraphicsItem::mouseMoveEvent(event); -} - void Node::addEdge(Edge *edge, bool startsFromThisNode) { m_edgeList.push_back(EdgeElement(edge, startsFromThisNode)); @@ -75,26 +68,83 @@ void Node::removeEdgeFromList(Edge *edge) } } -//void Node::adjustEdges() -//{ -// foreach (EdgeElement element, m_edgeList) element.edge->adjust(); -//} +// edges from this Node. Exclude secondaries if needed (calc subtree) +QList Node::edgesFrom(const bool &excludeSecondaries) const +{ + QList list; -void Node::setBorder(const bool &hasBorder) + foreach(EdgeElement element, m_edgeList) + if (element.startsFromThisNode && + (!element.edge->secondary() || !excludeSecondaries)) + list.push_back(element.edge); + + return list; +} + +// edges to this node (max 1 primary and any number of secondaries) +QList Node::edgesToThis(const bool &excludeSecondaries) const { - m_hasBorder = hasBorder; - update(); + QList list; + + foreach(EdgeElement element, m_edgeList) + if (!element.startsFromThisNode && + (!element.edge->secondary() || !excludeSecondaries)) + list.push_back(element.edge); + + return list; } -/** @note Used to have some disorder here, how does an active node looks like? -* Let is have border but the color shall not change, shadow is messy too. -*/ +// the edge from this Node to the parameter Node +Edge * Node::edgeTo(const Node *node) const +{ + foreach(EdgeElement element, m_edgeList) + if ((element.edge->sourceNode() == node || + element.edge->destNode() == node)) + return element.edge; -void Node::setActive(const bool &active) + return 0; +} + +QList Node::subtree() const { - m_isActive = active; - setBorder(active); - update(); + /** @note QList crashes if modified while traversal, + * QMutableListIterator lacks push_back, using good old std::list + */ + + std::list list; + list.push_back(const_cast(this)); + + // inorder: push_back the list of children Nodes of iterator + for(std::list::const_iterator it = list.begin(); + it != list.end(); + it++) + { + QList edges = (*it)->edgesFrom(); + foreach(Edge *edge, edges) + if (!edge->secondary()) + list.push_back( edge->destNode() != this ? + edge->destNode(): + edge->sourceNode()); + } + + return QList::fromStdList(list); +} + +// return thue if this and the parameter Node is connected with an edge +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; +} + +void Node::setBorder(const bool &hasBorder) +{ + m_hasBorder = hasBorder; + update(); } void Node::setEditable(const bool &editable) @@ -106,6 +156,8 @@ void Node::setEditable(const bool &editable) } setTextInteractionFlags(Qt::TextEditable); + + // set cursor to the end QTextCursor c = textCursor(); c.setPosition(c.document()->toPlainText().length()); setTextCursor(c); @@ -125,17 +177,20 @@ void Node::setTextColor(const QColor &color) void Node::setScale(const qreal &factor,const QRectF &sceneRect) { + // limit scale to a reasonable size if (factor * scale() < 0.4 || factor * scale() > 4 ) return; + // cannot scale out the Node from the scene if (!sceneRect.contains(pos() + boundingRect().bottomRight() * scale() * factor)) return; prepareGeometryChange(); - QGraphicsTextItem::setScale(factor * scale()); + + // scale edges to this Node too foreach(EdgeElement element, m_edgeList) { if (!element.startsFromThisNode) @@ -154,15 +209,11 @@ void Node::showNumber(const int &number, update(); } -void Node::insertPicture(const QString &picture, const int &pos) +void Node::insertPicture(const QString &picture) { QTextCursor c = textCursor(); - if (pos) - { - c.setPosition(pos); - } - + // strange, picture looks bad when node is scaled up c.insertHtml(QString("")); @@ -170,18 +221,56 @@ void Node::insertPicture(const QString &picture, const int &pos) foreach (EdgeElement element, m_edgeList) element.edge->adjust(); } -double Node::calculateBiggestAngle() +QPointF Node::intersection(const QLineF &line, const bool &reverse) const +{ + + /// @note What a shame, the following does not work, + /// doing it with brute (unaccurate) force + + // QPainterPath nodeShape(shape()); + // nodeShape.translate(pos()); + + // QPainterPath l; + // l.moveTo(line.p1()); + // l.lineTo(line.p2()); + + // return nodeShape.intersected(l); + + + 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); +} + +double Node::calculateBiggestAngle() const { + // in no edge, return with 12 o'clock if (m_edgeList.empty()) return Node::m_oneAndHalfPi; + // if there is only one edge, return with it's extension if (m_edgeList.size()==1) return m_edgeList.first().startsFromThisNode ? Node::m_pi - m_edgeList.first().edge->angle() : Node::m_twoPi - m_edgeList.first().edge->angle(); + // put angles of every edges from this node to a list QList tmp; - for(QList::iterator it = m_edgeList.begin(); + for(QList::const_iterator it = m_edgeList.begin(); it != m_edgeList.end(); it++) { tmp.push_back(it->startsFromThisNode ? @@ -190,6 +279,7 @@ double Node::calculateBiggestAngle() } qSort(tmp.begin(), tmp.end()); + // find the biggest diffrence, store prev angle double prev(tmp.first()); double max_prev(tmp.last()); double max(Node::m_twoPi - tmp.last() + tmp.first()); @@ -204,6 +294,7 @@ double Node::calculateBiggestAngle() prev = *it; } + // return with prev angle + max diff / 2 return Node::m_twoPi - doubleModulo(max_prev + max / 2, Node::m_twoPi); } @@ -258,117 +349,6 @@ void Node::keyPressEvent(QKeyEvent *event) ///@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::intersection(const QLineF &line, const bool &reverse) const -{ - - /// @note What a shame, the following does not work, - /// doing it with brute (unaccurate) force - - // 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); - - - 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 bool &excludeSecondaries) const -{ - QList list; - - foreach(EdgeElement element, m_edgeList) - if (element.startsFromThisNode && - (!element.edge->secondary() || !excludeSecondaries)) - list.push_back(element.edge); - - return list; -} - -QList Node::edgesToThis(const bool &excludeSecondaries) const -{ - QList list; - - foreach(EdgeElement element, m_edgeList) - if (!element.startsFromThisNode && - (!element.edge->secondary() || !excludeSecondaries)) - list.push_back(element.edge); - - return list; -} - -Edge * Node::edgeTo(const Node *node) const -{ - foreach(EdgeElement element, m_edgeList) - if ((element.edge->sourceNode() == node || - element.edge->destNode() == node)) - return element.edge; - - return 0; -} - - -QList Node::subtree() const -{ - /** @note QList crashes if modified while traversal, - * QMutableListIterator lacks push_back - */ - - std::list list; - list.push_back(const_cast(this)); - - // inorder - for(std::list::const_iterator it = list.begin(); - it != list.end(); - it++) - { - QList edges = (*it)->edgesFrom(); - if (!edges.empty()) - { - foreach(Edge *edge, edges) - { - if (!edge->secondary()) - { - list.push_back( edge->destNode() != this ? - edge->destNode(): - edge->sourceNode()); - } - } - } - } - return QList::fromStdList(list); -} - void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *w) @@ -379,7 +359,6 @@ void Node::paint(QPainter *painter, { painter->setPen(Qt::transparent); painter->setBrush(m_numberIsSpecial ? Qt::green : Qt::yellow); - painter->drawRoundedRect(boundingRect(), 20.0, 15.0); } else @@ -415,29 +394,29 @@ QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value) switch (change) { case ItemPositionChange: + { + // Node is about to move, check borders - if (change == ItemPositionChange && scene()) + QPointF newPos = value.toPointF(); + + // the fence is reduced with the size of the node + QRectF rect (scene()->sceneRect().topLeft(), + scene()->sceneRect().bottomRight() - + boundingRect().bottomRight() * scale() ); + + if (!rect.contains(newPos)) { - // 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() * 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; - } + // 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; + break; + } case ItemPositionHasChanged: + // Notify parent, adjust edges that a move has happended. m_graph->contentChanged(); foreach (EdgeElement element, m_edgeList) element.edge->adjust(); break; @@ -467,6 +446,7 @@ void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) QGraphicsItem::mouseReleaseEvent(event); } +// notify parent so subtree can be moved too if necessary void Node::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { m_graph->nodeMoved(event); @@ -479,16 +459,16 @@ QPainterPath Node::shape () const return path; } +// leave editing mode when user clicks on the view elsewhere for example void Node::focusOutEvent(QFocusEvent *event) { - qDebug() << __PRETTY_FUNCTION__; - Q_UNUSED(event); setEditable(false); m_graph->nodeLostFocus(); } -double Node::doubleModulo(const double &devided, const double &devisor) +// there is no such thing as modulo operator for double :P +double Node::doubleModulo(const double &devided, const double &devisor) const { return devided - static_cast(devisor * static_cast(devided / devisor));