From c316ea89830be7ef86bcb0e8bba2623303897ee9 Mon Sep 17 00:00:00 2001 From: mindon Date: Wed, 20 Sep 2023 13:39:11 +0800 Subject: [PATCH 01/12] fix issue: crashed when click twice on a no-connection-port of locked-node #396 fix issue: crashed when click twice on a no-connection-port of locked-node #396 --- src/NodeGraphicsObject.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index aa727cdaa..b9b66e653 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -226,7 +226,9 @@ void NodeGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event) _nodeState.setResizing(hit); } - QGraphicsObject::mousePressEvent(event); + if (!event->isAccepted()) { + QGraphicsObject::mousePressEvent(event); + } if (isSelected()) { Q_EMIT nodeScene()->nodeSelected(_nodeId); @@ -287,7 +289,9 @@ void NodeGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { _nodeState.setResizing(false); - QGraphicsObject::mouseReleaseEvent(event); + if (!event->isAccepted()) { + QGraphicsObject::mouseReleaseEvent(event); + } // position connections precisely after fast node move moveConnections(); From 854a626d2ad7865c9f0872ee957db6a21aca15ee Mon Sep 17 00:00:00 2001 From: mindon Date: Wed, 20 Sep 2023 14:34:17 +0800 Subject: [PATCH 02/12] feat: connection selected and double-click signals --- include/QtNodes/internal/BasicGraphicsScene.hpp | 4 ++++ include/QtNodes/internal/ConnectionGraphicsObject.hpp | 2 ++ src/ConnectionGraphicsObject.cpp | 10 ++++++++++ 3 files changed, 16 insertions(+) diff --git a/include/QtNodes/internal/BasicGraphicsScene.hpp b/include/QtNodes/internal/BasicGraphicsScene.hpp index a36e23724..106b54e59 100644 --- a/include/QtNodes/internal/BasicGraphicsScene.hpp +++ b/include/QtNodes/internal/BasicGraphicsScene.hpp @@ -113,6 +113,10 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene void nodeHoverLeft(NodeId const nodeId); + void connectionSelected(ConnectionId const connectionId); + + void connectionDoubleClicked(ConnectionId const connectionId); + void connectionHovered(ConnectionId const connectionId, QPoint const screenPos); void connectionHoverLeft(ConnectionId const connectionId); diff --git a/include/QtNodes/internal/ConnectionGraphicsObject.hpp b/include/QtNodes/internal/ConnectionGraphicsObject.hpp index f0e569d74..ba568781f 100644 --- a/include/QtNodes/internal/ConnectionGraphicsObject.hpp +++ b/include/QtNodes/internal/ConnectionGraphicsObject.hpp @@ -69,6 +69,8 @@ class ConnectionGraphicsObject : public QGraphicsObject void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 6a871c0ed..9e3f782ca 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -203,6 +203,9 @@ void ConnectionGraphicsObject::paint(QPainter *painter, void ConnectionGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (!isSelected()) { + Q_EMIT nodeScene()->connectionSelected(connectionId()); + } QGraphicsItem::mousePressEvent(event); } @@ -263,6 +266,13 @@ void ConnectionGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event } } +void ConnectionGraphicsObject::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseDoubleClickEvent(event); + Q_EMIT nodeScene()->connectionDoubleClicked(connectionId()); +} + + void ConnectionGraphicsObject::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { _connectionState.setHovered(true); From f5fd95f342e1b0d7f5f849ef86e78edc434c837b Mon Sep 17 00:00:00 2001 From: mindon Date: Thu, 21 Sep 2023 14:34:49 +0800 Subject: [PATCH 03/12] feat: 1) partly set node and connection styles; 2) color alpha style supports; 3) FillColor and CornerRadius for node style; 4) connection WithArrow style. fixings: 1) setLockedState crash issue; 2) inNodeId instead #400 --- .../internal/ConnectionGraphicsObject.hpp | 4 +- .../QtNodes/internal/ConnectionIdUtils.hpp | 4 +- include/QtNodes/internal/ConnectionStyle.hpp | 2 + .../QtNodes/internal/NodeGraphicsObject.hpp | 5 +- include/QtNodes/internal/NodeStyle.hpp | 2 + include/QtNodes/internal/Style.hpp | 54 ++++++++ resources/DefaultStyle.json | 7 +- src/ConnectionGraphicsObject.cpp | 18 +++ src/ConnectionPainter.cpp | 10 +- src/ConnectionStyle.cpp | 109 ++++----------- src/DefaultNodePainter.cpp | 19 +-- src/NodeGraphicsObject.cpp | 23 ++-- src/NodeStyle.cpp | 128 ++++++------------ 13 files changed, 195 insertions(+), 190 deletions(-) diff --git a/include/QtNodes/internal/ConnectionGraphicsObject.hpp b/include/QtNodes/internal/ConnectionGraphicsObject.hpp index ba568781f..29c1fb0bc 100644 --- a/include/QtNodes/internal/ConnectionGraphicsObject.hpp +++ b/include/QtNodes/internal/ConnectionGraphicsObject.hpp @@ -1,7 +1,8 @@ #pragma once #include - +#include +#include #include #include @@ -48,6 +49,7 @@ class ConnectionGraphicsObject : public QGraphicsObject QPointF in() const { return _in; } std::pair pointsC1C2() const; + QPolygonF arrow(qreal angle, qreal delta = M_PI / 8) const; void setEndPoint(PortType portType, QPointF const &point); diff --git a/include/QtNodes/internal/ConnectionIdUtils.hpp b/include/QtNodes/internal/ConnectionIdUtils.hpp index 7f70a1b4b..34be7af76 100644 --- a/include/QtNodes/internal/ConnectionIdUtils.hpp +++ b/include/QtNodes/internal/ConnectionIdUtils.hpp @@ -132,7 +132,7 @@ inline QJsonObject toJson(ConnectionId const &connId) connJson["outNodeId"] = static_cast(connId.outNodeId); connJson["outPortIndex"] = static_cast(connId.outPortIndex); - connJson["intNodeId"] = static_cast(connId.inNodeId); + connJson["inNodeId"] = static_cast(connId.inNodeId); connJson["inPortIndex"] = static_cast(connId.inPortIndex); return connJson; @@ -142,7 +142,7 @@ inline ConnectionId fromJson(QJsonObject const &connJson) { ConnectionId connId{static_cast(connJson["outNodeId"].toInt(InvalidNodeId)), static_cast(connJson["outPortIndex"].toInt(InvalidPortIndex)), - static_cast(connJson["intNodeId"].toInt(InvalidNodeId)), + static_cast((connJson.contains("inNodeId") ? connJson["inNodeId"] : connJson["intNodeId"]).toInt(InvalidNodeId)), static_cast(connJson["inPortIndex"].toInt(InvalidPortIndex))}; return connId; diff --git a/include/QtNodes/internal/ConnectionStyle.hpp b/include/QtNodes/internal/ConnectionStyle.hpp index b718bcfae..69d1afa10 100644 --- a/include/QtNodes/internal/ConnectionStyle.hpp +++ b/include/QtNodes/internal/ConnectionStyle.hpp @@ -37,6 +37,7 @@ class NODE_EDITOR_PUBLIC ConnectionStyle : public Style float pointDiameter() const; bool useDataDefinedColors() const; + bool withArrow() const; private: QColor ConstructionColor; @@ -50,5 +51,6 @@ class NODE_EDITOR_PUBLIC ConnectionStyle : public Style float PointDiameter; bool UseDataDefinedColors; + bool WithArrow; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/NodeGraphicsObject.hpp b/include/QtNodes/internal/NodeGraphicsObject.hpp index ef042f769..e39b2719a 100644 --- a/include/QtNodes/internal/NodeGraphicsObject.hpp +++ b/include/QtNodes/internal/NodeGraphicsObject.hpp @@ -50,6 +50,8 @@ class NodeGraphicsObject : public QGraphicsObject /// Repaints the node once with reacting ports. void reactToConnection(ConnectionGraphicsObject const *cgo); + void nodeFlagsUpdated(NodeId const nodeId); + protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, @@ -73,10 +75,11 @@ class NodeGraphicsObject : public QGraphicsObject void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + private: void embedQWidget(); - void setLockedState(); + void setLockedState(NodeFlags flags); private: NodeId _nodeId; diff --git a/include/QtNodes/internal/NodeStyle.hpp b/include/QtNodes/internal/NodeStyle.hpp index 5eca74924..a2aff540a 100644 --- a/include/QtNodes/internal/NodeStyle.hpp +++ b/include/QtNodes/internal/NodeStyle.hpp @@ -42,6 +42,7 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style QColor WarningColor; QColor ErrorColor; + QColor FillColor; float PenWidth; float HoveredPenWidth; @@ -49,5 +50,6 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style float ConnectionPointDiameter; float Opacity; + float CornerRadius; }; } // namespace QtNodes diff --git a/include/QtNodes/internal/Style.hpp b/include/QtNodes/internal/Style.hpp index f878083e2..4f8262692 100644 --- a/include/QtNodes/internal/Style.hpp +++ b/include/QtNodes/internal/Style.hpp @@ -7,6 +7,60 @@ #include #include +#define X_VALUE_EXISTS(v) \ + (v.type() != QJsonValue::Undefined && v.type() != QJsonValue::Null) + +#define X_STYLE_READ_COLOR(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + if (X_VALUE_EXISTS(valueRef)) { \ + if (valueRef.isArray()) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve(3); \ + int alpha = 255; \ + for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ + rgb.push_back((*it).toInt()); \ + } \ + if (colorArray.size() > 3) \ + alpha = colorArray[3].toInt(); \ + variable = QColor(rgb[0], rgb[1], rgb[2], alpha); \ + } else { \ + variable = QColor(valueRef.toString()); \ + } \ + } \ + } + +#define X_STYLE_WRITE_COLOR(values, variable) \ + { \ + values[#variable] = variable.name(QColor::HexArgb); \ + } + +#define X_STYLE_READ_FLOAT(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + if (X_VALUE_EXISTS(valueRef)) \ + variable = valueRef.toDouble(); \ + } + +#define X_STYLE_WRITE_FLOAT(values, variable) \ + { \ + values[#variable] = variable; \ + } + +#define X_STYLE_READ_BOOL(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + if (X_VALUE_EXISTS(valueRef)) \ + variable = valueRef.toBool(); \ + } + +#define X_STYLE_WRITE_BOOL(values, variable) \ + { \ + values[#variable] = variable; \ + } + + namespace QtNodes { class Style // : public QObject diff --git a/resources/DefaultStyle.json b/resources/DefaultStyle.json index da8dfe84c..7b1a181e1 100644 --- a/resources/DefaultStyle.json +++ b/resources/DefaultStyle.json @@ -18,13 +18,15 @@ "FilledConnectionPointColor": "cyan", "ErrorColor": "red", "WarningColor": [128, 128, 0], + "FillColor": "none", "PenWidth": 1.0, "HoveredPenWidth": 1.5, "ConnectionPointDiameter": 8.0, - "Opacity": 0.8 + "Opacity": 0.8, + "CornerRadius": 3.0 }, "ConnectionStyle": { "ConstructionColor": "gray", @@ -37,6 +39,7 @@ "ConstructionLineWidth": 2.0, "PointDiameter": 10.0, - "UseDataDefinedColors": false + "UseDataDefinedColors": false, + "WithArrow": false } } diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 9e3f782ca..9244fbe03 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -387,4 +387,22 @@ std::pair ConnectionGraphicsObject::pointsC1C2Vertical() const return std::make_pair(c1, c2); } +QPolygonF ConnectionGraphicsObject::arrow(qreal angle, qreal delta) const +{ + QPointF const &in = endPoint(PortType::In); + QPointF const &out = endPoint(PortType::In); + + QPointF first = in, second, third; + int z = 16; + if ((nodeScene()->orientation() == Qt::Horizontal && in.x() < out.x()) + || (nodeScene()->orientation() == Qt::Vertical && in.y() < out.y()) ) { + z = -16; + } + second = QPointF(in.x() - z * cos(angle - delta), in.y() + z * sin(angle - delta)); + third = QPointF(in.x() - z * cos(angle + delta), in.y() + z * sin(angle + delta)); + QPolygonF poly; + poly << first << second << third << first; + return poly; +} + } // namespace QtNodes diff --git a/src/ConnectionPainter.cpp b/src/ConnectionPainter.cpp index 97002ef79..7a61becd0 100644 --- a/src/ConnectionPainter.cpp +++ b/src/ConnectionPainter.cpp @@ -177,8 +177,16 @@ static void drawNormalLine(QPainter *painter, ConnectionGraphicsObject const &cg p.setWidth(lineWidth); bool const selected = cgo.isSelected(); - auto cubic = cubicPath(cgo); + + if (connectionStyle.withArrow()) { + painter->setBrush(selected ? connectionStyle.selectedHaloColor() : normalColorIn); + painter->setPen(Qt::NoPen); + + QPolygonF poly = cgo.arrow(cubic.angleAtPercent(.95) * M_PI / 180.0); + painter->drawPolygon(poly); + } + if (useGradientColor) { painter->setBrush(Qt::NoBrush); diff --git a/src/ConnectionStyle.cpp b/src/ConnectionStyle.cpp index 812658989..b8968e2d3 100644 --- a/src/ConnectionStyle.cpp +++ b/src/ConnectionStyle.cpp @@ -27,9 +27,8 @@ ConnectionStyle::ConnectionStyle() loadJsonFile(":DefaultStyle.json"); } -ConnectionStyle::ConnectionStyle(QString jsonText) +ConnectionStyle::ConnectionStyle(QString jsonText): ConnectionStyle() { - loadJsonFile(":DefaultStyle.json"); loadJsonText(jsonText); } @@ -40,103 +39,42 @@ void ConnectionStyle::setConnectionStyle(QString jsonText) StyleCollection::setConnectionStyle(style); } -#ifdef STYLE_DEBUG -#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) \ - { \ - if (v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null) \ - qWarning() << "Undefined value for parameter:" << #variable; \ - } -#else -#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) -#endif - -#define CONNECTION_VALUE_EXISTS(v) \ - (v.type() != QJsonValue::Undefined && v.type() != QJsonValue::Null) - -#define CONNECTION_STYLE_READ_COLOR(values, variable) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - if (CONNECTION_VALUE_EXISTS(valueRef)) { \ - if (valueRef.isArray()) { \ - auto colorArray = valueRef.toArray(); \ - std::vector rgb; \ - rgb.reserve(3); \ - for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ - rgb.push_back((*it).toInt()); \ - } \ - variable = QColor(rgb[0], rgb[1], rgb[2]); \ - } else { \ - variable = QColor(valueRef.toString()); \ - } \ - } \ - } - -#define CONNECTION_STYLE_WRITE_COLOR(values, variable) \ - { \ - values[#variable] = variable.name(); \ - } - -#define CONNECTION_STYLE_READ_FLOAT(values, variable) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - if (CONNECTION_VALUE_EXISTS(valueRef)) \ - variable = valueRef.toDouble(); \ - } - -#define CONNECTION_STYLE_WRITE_FLOAT(values, variable) \ - { \ - values[#variable] = variable; \ - } - -#define CONNECTION_STYLE_READ_BOOL(values, variable) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - if (CONNECTION_VALUE_EXISTS(valueRef)) \ - variable = valueRef.toBool(); \ - } - -#define CONNECTION_STYLE_WRITE_BOOL(values, variable) \ - { \ - values[#variable] = variable; \ - } - void ConnectionStyle::loadJson(QJsonObject const &json) { QJsonValue nodeStyleValues = json["ConnectionStyle"]; QJsonObject obj = nodeStyleValues.toObject(); - CONNECTION_STYLE_READ_COLOR(obj, ConstructionColor); - CONNECTION_STYLE_READ_COLOR(obj, NormalColor); - CONNECTION_STYLE_READ_COLOR(obj, SelectedColor); - CONNECTION_STYLE_READ_COLOR(obj, SelectedHaloColor); - CONNECTION_STYLE_READ_COLOR(obj, HoveredColor); + X_STYLE_READ_COLOR(obj, ConstructionColor); + X_STYLE_READ_COLOR(obj, NormalColor); + X_STYLE_READ_COLOR(obj, SelectedColor); + X_STYLE_READ_COLOR(obj, SelectedHaloColor); + X_STYLE_READ_COLOR(obj, HoveredColor); - CONNECTION_STYLE_READ_FLOAT(obj, LineWidth); - CONNECTION_STYLE_READ_FLOAT(obj, ConstructionLineWidth); - CONNECTION_STYLE_READ_FLOAT(obj, PointDiameter); + X_STYLE_READ_FLOAT(obj, LineWidth); + X_STYLE_READ_FLOAT(obj, ConstructionLineWidth); + X_STYLE_READ_FLOAT(obj, PointDiameter); - CONNECTION_STYLE_READ_BOOL(obj, UseDataDefinedColors); + X_STYLE_READ_BOOL(obj, UseDataDefinedColors); + X_STYLE_READ_BOOL(obj, WithArrow); } QJsonObject ConnectionStyle::toJson() const { QJsonObject obj; - CONNECTION_STYLE_WRITE_COLOR(obj, ConstructionColor); - CONNECTION_STYLE_WRITE_COLOR(obj, NormalColor); - CONNECTION_STYLE_WRITE_COLOR(obj, SelectedColor); - CONNECTION_STYLE_WRITE_COLOR(obj, SelectedHaloColor); - CONNECTION_STYLE_WRITE_COLOR(obj, HoveredColor); + X_STYLE_WRITE_COLOR(obj, ConstructionColor); + X_STYLE_WRITE_COLOR(obj, NormalColor); + X_STYLE_WRITE_COLOR(obj, SelectedColor); + X_STYLE_WRITE_COLOR(obj, SelectedHaloColor); + X_STYLE_WRITE_COLOR(obj, HoveredColor); - CONNECTION_STYLE_WRITE_FLOAT(obj, LineWidth); - CONNECTION_STYLE_WRITE_FLOAT(obj, ConstructionLineWidth); - CONNECTION_STYLE_WRITE_FLOAT(obj, PointDiameter); + X_STYLE_WRITE_FLOAT(obj, LineWidth); + X_STYLE_WRITE_FLOAT(obj, ConstructionLineWidth); + X_STYLE_WRITE_FLOAT(obj, PointDiameter); - CONNECTION_STYLE_WRITE_BOOL(obj, UseDataDefinedColors); + X_STYLE_WRITE_BOOL(obj, UseDataDefinedColors); + X_STYLE_WRITE_BOOL(obj, WithArrow); QJsonObject root; root["ConnectionStyle"] = obj; @@ -203,3 +141,8 @@ bool ConnectionStyle::useDataDefinedColors() const { return UseDataDefinedColors; } + +bool ConnectionStyle::withArrow() const +{ + return WithArrow; +} diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 8febe4cb1..9039200f3 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -58,19 +58,22 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo painter->setPen(p); } - QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, size.height())); + if (nodeStyle.FillColor.value()) { + painter->setBrush(nodeStyle.FillColor); + } else { + QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, size.height())); - gradient.setColorAt(0.0, nodeStyle.GradientColor0); - gradient.setColorAt(0.10, nodeStyle.GradientColor1); - gradient.setColorAt(0.90, nodeStyle.GradientColor2); - gradient.setColorAt(1.0, nodeStyle.GradientColor3); + gradient.setColorAt(0.0, nodeStyle.GradientColor0); + gradient.setColorAt(0.10, nodeStyle.GradientColor1); + gradient.setColorAt(0.90, nodeStyle.GradientColor2); + gradient.setColorAt(1.0, nodeStyle.GradientColor3); - painter->setBrush(gradient); + painter->setBrush(gradient); + } QRectF boundary(0, 0, size.width(), size.height()); - double const radius = 3.0; - + double const radius = nodeStyle.CornerRadius >= 0 ? nodeStyle.CornerRadius : 3.0; painter->drawRoundedRect(boundary, radius, radius); } diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index b9b66e653..ac3d2c4b5 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -29,7 +29,7 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); setFlag(QGraphicsItem::ItemIsFocusable, true); - setLockedState(); + setLockedState(_graphModel.nodeFlags(_nodeId)); setCacheMode(QGraphicsItem::DeviceCoordinateCache); @@ -60,10 +60,19 @@ NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) setPos(pos); - connect(&_graphModel, &AbstractGraphModel::nodeFlagsUpdated, [this](NodeId const nodeId) { - if (_nodeId == nodeId) - setLockedState(); - }); + connect(&_graphModel, &AbstractGraphModel::nodeFlagsUpdated, this, &NodeGraphicsObject::nodeFlagsUpdated); +} + +void NodeGraphicsObject::nodeFlagsUpdated(NodeId const nodeId) { + if (_nodeId != nodeId) + return; + QObject *obj = sender(); + if (!obj) + return; + AbstractGraphModel *ptr = qobject_cast(obj); + if (!ptr) + return; + setLockedState(ptr->nodeFlags(nodeId)); } AbstractGraphModel &NodeGraphicsObject::graphModel() const @@ -108,10 +117,8 @@ void NodeGraphicsObject::embedQWidget() } } -void NodeGraphicsObject::setLockedState() +void NodeGraphicsObject::setLockedState(NodeFlags flags) { - NodeFlags flags = _graphModel.nodeFlags(_nodeId); - bool const locked = flags.testFlag(NodeFlag::Locked); setFlag(QGraphicsItem::ItemIsMovable, !locked); diff --git a/src/NodeStyle.cpp b/src/NodeStyle.cpp index a82bf8fe2..f215d7e80 100644 --- a/src/NodeStyle.cpp +++ b/src/NodeStyle.cpp @@ -27,12 +27,12 @@ NodeStyle::NodeStyle() loadJsonFile(":DefaultStyle.json"); } -NodeStyle::NodeStyle(QString jsonText) +NodeStyle::NodeStyle(QString jsonText): NodeStyle() { loadJsonText(jsonText); } -NodeStyle::NodeStyle(QJsonObject const &json) +NodeStyle::NodeStyle(QJsonObject const &json): NodeStyle() { loadJson(json); } @@ -44,100 +44,60 @@ void NodeStyle::setNodeStyle(QString jsonText) StyleCollection::setNodeStyle(style); } -#ifdef STYLE_DEBUG -#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) \ - { \ - if (v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null) \ - qWarning() << "Undefined value for parameter:" << #variable; \ - } -#else -#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) -#endif - -#define NODE_STYLE_READ_COLOR(values, variable) \ - { \ - auto valueRef = values[#variable]; \ - NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - if (valueRef.isArray()) { \ - auto colorArray = valueRef.toArray(); \ - std::vector rgb; \ - rgb.reserve(3); \ - for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ - rgb.push_back((*it).toInt()); \ - } \ - variable = QColor(rgb[0], rgb[1], rgb[2]); \ - } else { \ - variable = QColor(valueRef.toString()); \ - } \ - } - -#define NODE_STYLE_WRITE_COLOR(values, variable) \ - { \ - values[#variable] = variable.name(); \ - } - -#define NODE_STYLE_READ_FLOAT(values, variable) \ - { \ - auto valueRef = values[#variable]; \ - NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - variable = valueRef.toDouble(); \ - } - -#define NODE_STYLE_WRITE_FLOAT(values, variable) \ - { \ - values[#variable] = variable; \ - } - void NodeStyle::loadJson(QJsonObject const &json) { QJsonValue nodeStyleValues = json["NodeStyle"]; QJsonObject obj = nodeStyleValues.toObject(); - NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor); - NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor); - NODE_STYLE_READ_COLOR(obj, GradientColor0); - NODE_STYLE_READ_COLOR(obj, GradientColor1); - NODE_STYLE_READ_COLOR(obj, GradientColor2); - NODE_STYLE_READ_COLOR(obj, GradientColor3); - NODE_STYLE_READ_COLOR(obj, ShadowColor); - NODE_STYLE_READ_COLOR(obj, FontColor); - NODE_STYLE_READ_COLOR(obj, FontColorFaded); - NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); - NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); - NODE_STYLE_READ_COLOR(obj, WarningColor); - NODE_STYLE_READ_COLOR(obj, ErrorColor); - - NODE_STYLE_READ_FLOAT(obj, PenWidth); - NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth); - NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); - - NODE_STYLE_READ_FLOAT(obj, Opacity); + X_STYLE_READ_COLOR(obj, NormalBoundaryColor); + X_STYLE_READ_COLOR(obj, SelectedBoundaryColor); + X_STYLE_READ_COLOR(obj, GradientColor0); + X_STYLE_READ_COLOR(obj, GradientColor1); + X_STYLE_READ_COLOR(obj, GradientColor2); + X_STYLE_READ_COLOR(obj, GradientColor3); + X_STYLE_READ_COLOR(obj, ShadowColor); + X_STYLE_READ_COLOR(obj, FontColor); + X_STYLE_READ_COLOR(obj, FontColorFaded); + X_STYLE_READ_COLOR(obj, ConnectionPointColor); + X_STYLE_READ_COLOR(obj, FilledConnectionPointColor); + X_STYLE_READ_COLOR(obj, WarningColor); + X_STYLE_READ_COLOR(obj, ErrorColor); + X_STYLE_READ_COLOR(obj, FillColor); + + X_STYLE_READ_FLOAT(obj, PenWidth); + X_STYLE_READ_FLOAT(obj, HoveredPenWidth); + X_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); + + X_STYLE_READ_FLOAT(obj, Opacity); + X_STYLE_READ_FLOAT(obj, CornerRadius); } QJsonObject NodeStyle::toJson() const { QJsonObject obj; - NODE_STYLE_WRITE_COLOR(obj, NormalBoundaryColor); - NODE_STYLE_WRITE_COLOR(obj, SelectedBoundaryColor); - NODE_STYLE_WRITE_COLOR(obj, GradientColor0); - NODE_STYLE_WRITE_COLOR(obj, GradientColor1); - NODE_STYLE_WRITE_COLOR(obj, GradientColor2); - NODE_STYLE_WRITE_COLOR(obj, GradientColor3); - NODE_STYLE_WRITE_COLOR(obj, ShadowColor); - NODE_STYLE_WRITE_COLOR(obj, FontColor); - NODE_STYLE_WRITE_COLOR(obj, FontColorFaded); - NODE_STYLE_WRITE_COLOR(obj, ConnectionPointColor); - NODE_STYLE_WRITE_COLOR(obj, FilledConnectionPointColor); - NODE_STYLE_WRITE_COLOR(obj, WarningColor); - NODE_STYLE_WRITE_COLOR(obj, ErrorColor); - - NODE_STYLE_WRITE_FLOAT(obj, PenWidth); - NODE_STYLE_WRITE_FLOAT(obj, HoveredPenWidth); - NODE_STYLE_WRITE_FLOAT(obj, ConnectionPointDiameter); - - NODE_STYLE_WRITE_FLOAT(obj, Opacity); + X_STYLE_WRITE_COLOR(obj, NormalBoundaryColor); + X_STYLE_WRITE_COLOR(obj, SelectedBoundaryColor); + X_STYLE_WRITE_COLOR(obj, GradientColor0); + X_STYLE_WRITE_COLOR(obj, GradientColor1); + X_STYLE_WRITE_COLOR(obj, GradientColor2); + X_STYLE_WRITE_COLOR(obj, GradientColor3); + X_STYLE_WRITE_COLOR(obj, ShadowColor); + X_STYLE_WRITE_COLOR(obj, FontColor); + X_STYLE_WRITE_COLOR(obj, FontColorFaded); + X_STYLE_WRITE_COLOR(obj, ConnectionPointColor); + X_STYLE_WRITE_COLOR(obj, FilledConnectionPointColor); + X_STYLE_WRITE_COLOR(obj, WarningColor); + X_STYLE_WRITE_COLOR(obj, ErrorColor); + X_STYLE_WRITE_COLOR(obj, FillColor); + + X_STYLE_WRITE_FLOAT(obj, PenWidth); + X_STYLE_WRITE_FLOAT(obj, HoveredPenWidth); + X_STYLE_WRITE_FLOAT(obj, ConnectionPointDiameter); + + X_STYLE_WRITE_FLOAT(obj, Opacity); + X_STYLE_WRITE_FLOAT(obj, CornerRadius); QJsonObject root; root["NodeStyle"] = obj; From 64eba18224aa6ac761b155924d376aed4b6aaac9 Mon Sep 17 00:00:00 2001 From: mindon Date: Thu, 21 Sep 2023 17:27:15 +0800 Subject: [PATCH 04/12] feat: node styled with data. fixing: connection and arrow --- .../internal/ConnectionGraphicsObject.hpp | 2 +- .../QtNodes/internal/DataFlowGraphModel.hpp | 2 ++ src/ConnectionGraphicsObject.cpp | 3 ++- src/ConnectionPainter.cpp | 21 ++++++++++++------- src/DataFlowGraphModel.cpp | 14 ++++++++++++- src/DefaultNodePainter.cpp | 1 - 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/include/QtNodes/internal/ConnectionGraphicsObject.hpp b/include/QtNodes/internal/ConnectionGraphicsObject.hpp index 29c1fb0bc..7e2966469 100644 --- a/include/QtNodes/internal/ConnectionGraphicsObject.hpp +++ b/include/QtNodes/internal/ConnectionGraphicsObject.hpp @@ -49,7 +49,7 @@ class ConnectionGraphicsObject : public QGraphicsObject QPointF in() const { return _in; } std::pair pointsC1C2() const; - QPolygonF arrow(qreal angle, qreal delta = M_PI / 8) const; + QPolygonF arrow(QPainterPath target, qreal delta = M_PI / 8) const; void setEndPoint(PortType portType, QPointF const &point); diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index 268800202..4a5cc5c22 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -130,6 +130,8 @@ private Q_SLOTS: std::unordered_set _connectivity; mutable std::unordered_map _nodeGeometryData; + + mutable std::unordered_map> _styles; }; } // namespace QtNodes diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 9244fbe03..53ab0f57c 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -387,13 +387,14 @@ std::pair ConnectionGraphicsObject::pointsC1C2Vertical() const return std::make_pair(c1, c2); } -QPolygonF ConnectionGraphicsObject::arrow(qreal angle, qreal delta) const +QPolygonF ConnectionGraphicsObject::arrow(QPainterPath target, qreal delta) const { QPointF const &in = endPoint(PortType::In); QPointF const &out = endPoint(PortType::In); QPointF first = in, second, third; int z = 16; + qreal angle = target.angleAtPercent((z/2)*cos(delta) / target.length()) * M_PI / 180.0; if ((nodeScene()->orientation() == Qt::Horizontal && in.x() < out.x()) || (nodeScene()->orientation() == Qt::Vertical && in.y() < out.y()) ) { z = -16; diff --git a/src/ConnectionPainter.cpp b/src/ConnectionPainter.cpp index 7a61becd0..d760dc46b 100644 --- a/src/ConnectionPainter.cpp +++ b/src/ConnectionPainter.cpp @@ -179,14 +179,6 @@ static void drawNormalLine(QPainter *painter, ConnectionGraphicsObject const &cg bool const selected = cgo.isSelected(); auto cubic = cubicPath(cgo); - if (connectionStyle.withArrow()) { - painter->setBrush(selected ? connectionStyle.selectedHaloColor() : normalColorIn); - painter->setPen(Qt::NoPen); - - QPolygonF poly = cgo.arrow(cubic.angleAtPercent(.95) * M_PI / 180.0); - painter->drawPolygon(poly); - } - if (useGradientColor) { painter->setBrush(Qt::NoBrush); @@ -233,6 +225,19 @@ static void drawNormalLine(QPainter *painter, ConnectionGraphicsObject const &cg painter->drawPath(cubic); } + + if (connectionStyle.withArrow()) { + bool hovered = cgo.connectionState().hovered(); + painter->setBrush(selected + ? connectionStyle.selectedHaloColor() + : (hovered + ? connectionStyle.hoveredColor() + : normalColorIn)); + painter->setPen(Qt::NoPen); + + QPolygonF poly = cgo.arrow(cubic); + painter->drawPolygon(poly); + } } void ConnectionPainter::paint(QPainter *painter, ConnectionGraphicsObject const &cgo) diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index a5ba8734f..bc8d715f6 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -212,7 +212,11 @@ QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const case NodeRole::Style: { auto style = StyleCollection::nodeStyle(); - result = style.toJson().toVariantMap(); + QVariantMap m = style.toJson().toVariantMap(); + if (!_styles.empty() && _styles.find(nodeId) != _styles.end()) { + m.insert(QVariantMap{{"NodeStyle",_styles.at(nodeId).get()->toVariantMap()}}); + } + result = m; } break; case NodeRole::InternalData: { @@ -282,6 +286,7 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu break; case NodeRole::Style: + _styles.insert({nodeId, std::make_shared(value.value())}); break; case NodeRole::InternalData: @@ -402,6 +407,8 @@ bool DataFlowGraphModel::deleteNode(NodeId const nodeId) _nodeGeometryData.erase(nodeId); _models.erase(nodeId); + if (!_styles.empty() && _styles.find(nodeId) != _styles.end()) + _styles.erase(nodeId); Q_EMIT nodeDeleted(nodeId); @@ -482,6 +489,11 @@ void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) setNodeData(restoredNodeId, NodeRole::Position, pos); + if (nodeJson.contains("style")) { + QJsonObject styleJson = nodeJson["style"].toObject(); + setNodeData(restoredNodeId, NodeRole::Style, styleJson); + } + _models[restoredNodeId]->load(internalDataJson); } else { throw std::logic_error(std::string("No registered model with name ") diff --git a/src/DefaultNodePainter.cpp b/src/DefaultNodePainter.cpp index 9039200f3..10b099785 100644 --- a/src/DefaultNodePainter.cpp +++ b/src/DefaultNodePainter.cpp @@ -45,7 +45,6 @@ void DefaultNodePainter::drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo QSize size = geometry.size(nodeId); QJsonDocument json = QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); - NodeStyle nodeStyle(json.object()); auto color = ngo.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor; From f212c691ff54ef925d008d29223b70a36792ea69 Mon Sep 17 00:00:00 2001 From: mindon Date: Thu, 21 Sep 2023 17:38:17 +0800 Subject: [PATCH 05/12] fix: arrow direction --- src/ConnectionGraphicsObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 53ab0f57c..86f3392e7 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -394,7 +394,7 @@ QPolygonF ConnectionGraphicsObject::arrow(QPainterPath target, qreal delta) cons QPointF first = in, second, third; int z = 16; - qreal angle = target.angleAtPercent((z/2)*cos(delta) / target.length()) * M_PI / 180.0; + qreal angle = target.angleAtPercent((z*2.0/3)*cos(delta) / target.length()) * M_PI / 180.0; if ((nodeScene()->orientation() == Qt::Horizontal && in.x() < out.x()) || (nodeScene()->orientation() == Qt::Vertical && in.y() < out.y()) ) { z = -16; From 79a59e374593de8bd2c2422b2a58bfb784b48754 Mon Sep 17 00:00:00 2001 From: mindon Date: Mon, 9 Oct 2023 10:13:00 +0800 Subject: [PATCH 06/12] fix: M_PI issue --- src/ConnectionGraphicsObject.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 86f3392e7..3b7947491 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -22,6 +22,10 @@ #include +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + namespace QtNodes { ConnectionGraphicsObject::ConnectionGraphicsObject(BasicGraphicsScene &scene, From 32a0b308513fcb383491b4c2e747ff3eb77f3b43 Mon Sep 17 00:00:00 2001 From: mindon Date: Mon, 9 Oct 2023 12:05:39 +0800 Subject: [PATCH 07/12] fix: M_PI issue --- include/QtNodes/internal/ConnectionGraphicsObject.hpp | 4 ++++ src/ConnectionGraphicsObject.cpp | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/QtNodes/internal/ConnectionGraphicsObject.hpp b/include/QtNodes/internal/ConnectionGraphicsObject.hpp index 7e2966469..d103b2dc2 100644 --- a/include/QtNodes/internal/ConnectionGraphicsObject.hpp +++ b/include/QtNodes/internal/ConnectionGraphicsObject.hpp @@ -1,5 +1,9 @@ #pragma once +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + #include #include #include diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 3b7947491..86f3392e7 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -22,10 +22,6 @@ #include -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - namespace QtNodes { ConnectionGraphicsObject::ConnectionGraphicsObject(BasicGraphicsScene &scene, From c2b160192c703cb3919b1243cd2ff25cc725e45e Mon Sep 17 00:00:00 2001 From: mindon Date: Mon, 30 Oct 2023 16:02:50 +0800 Subject: [PATCH 08/12] fix: avoid connect when detach connection not possible --- src/NodeGraphicsObject.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index ac3d2c4b5..ee9a81c33 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -222,7 +222,8 @@ void NodeGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event) ConnectionId const incompleteConnectionId = makeIncompleteConnectionId(_nodeId, portToCheck, portIndex); - + if (!_graphModel.detachPossible(incompleteConnectionId)) + continue; nodeScene()->makeDraftConnection(incompleteConnectionId); } } From ad5f1111761c36d7c1461932f92fa75d1fea7972 Mon Sep 17 00:00:00 2001 From: mindon Date: Fri, 17 Nov 2023 09:53:35 +0800 Subject: [PATCH 09/12] feat: add CtlOption, using virtual bool is(CltOption) to ctl scene context menu or other options --- include/QtNodes/internal/DataFlowGraphModel.hpp | 7 +++++++ src/DataFlowGraphModel.cpp | 5 +++++ src/DataFlowGraphicsScene.cpp | 3 +++ 3 files changed, 15 insertions(+) diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index 4a5cc5c22..70314c3a0 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -25,11 +25,18 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public QPointF pos; }; + enum CtlOption { + None, + NoSceneMenu, + } + public: DataFlowGraphModel(std::shared_ptr registry); std::shared_ptr dataModelRegistry() { return _registry; } + virtual bool is(CtlOption ctl); + public: std::unordered_set allNodeIds() const override; diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index bc8d715f6..6b98ae5b7 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -20,6 +20,11 @@ std::unordered_set DataFlowGraphModel::allNodeIds() const return nodeIds; } +bool DataFlowGraphModel::is(CtlOption ctl) { + Q_UNUSED(ctl); + return false; +} + std::unordered_set DataFlowGraphModel::allConnectionIds(NodeId const nodeId) const { std::unordered_set result; diff --git a/src/DataFlowGraphicsScene.cpp b/src/DataFlowGraphicsScene.cpp index 506f722ff..563068441 100644 --- a/src/DataFlowGraphicsScene.cpp +++ b/src/DataFlowGraphicsScene.cpp @@ -59,6 +59,9 @@ std::vector DataFlowGraphicsScene::selectedNodes() const QMenu *DataFlowGraphicsScene::createSceneMenu(QPointF const scenePos) { + if (_graphModel.is(DataFlowGraphModel::CtlOption::NoSceneMenu)) { + return nullptr; + } QMenu *modelMenu = new QMenu(); // Add filterbox to the context menu From e8a59f04a8861e7cdb813b5ef849c268cc605928 Mon Sep 17 00:00:00 2001 From: mindon Date: Fri, 17 Nov 2023 09:57:14 +0800 Subject: [PATCH 10/12] fix: lack of ; issue --- include/QtNodes/internal/DataFlowGraphModel.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/QtNodes/internal/DataFlowGraphModel.hpp b/include/QtNodes/internal/DataFlowGraphModel.hpp index 70314c3a0..8c529d7d0 100644 --- a/include/QtNodes/internal/DataFlowGraphModel.hpp +++ b/include/QtNodes/internal/DataFlowGraphModel.hpp @@ -28,7 +28,7 @@ class NODE_EDITOR_PUBLIC DataFlowGraphModel : public AbstractGraphModel, public enum CtlOption { None, NoSceneMenu, - } + }; public: DataFlowGraphModel(std::shared_ptr registry); From 9b8b95f1b7c1fe3f026e19b8e57595d3d6b6f31c Mon Sep 17 00:00:00 2001 From: mindon Date: Mon, 25 Dec 2023 11:56:12 +0800 Subject: [PATCH 11/12] fix: node update issue --- src/DataFlowGraphModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp index 6b98ae5b7..4942e5fe6 100644 --- a/src/DataFlowGraphModel.cpp +++ b/src/DataFlowGraphModel.cpp @@ -291,7 +291,7 @@ bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant valu break; case NodeRole::Style: - _styles.insert({nodeId, std::make_shared(value.value())}); + _styles[nodeId] = std::make_shared(value.value()); break; case NodeRole::InternalData: From 0382d805ac67d24fba3fee37414f669a0c74cad2 Mon Sep 17 00:00:00 2001 From: mindon Date: Thu, 4 Jan 2024 14:00:59 +0800 Subject: [PATCH 12/12] fix: centerScene with contents --- src/GraphicsView.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index 1ffa8f58b..aed482f30 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -151,7 +151,8 @@ void GraphicsView::centerScene() if (scene()) { scene()->setSceneRect(QRectF()); - QRectF sceneRect = scene()->sceneRect(); + QRectF rect = scene()->itemsBoundingRect(); + QRectF sceneRect = QRectF(0, 0, rect.left() * 2 + rect.width(), rect.top() * 2 + rect.height()); if (sceneRect.width() > this->rect().width() || sceneRect.height() > this->rect().height()) { fitInView(sceneRect, Qt::KeepAspectRatio);