From 42cf5b9c775b50a2839a33434ec4ced07bf71ec9 Mon Sep 17 00:00:00 2001 From: Shantanu Tushar Date: Thu, 9 Jan 2025 14:46:57 +0100 Subject: [PATCH 1/8] Use standard QML import path as per QTP0001 See: https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html https://doc.qt.io/qt-6/qt-add-qml-module.html#caching-compiled-qml-sources --- licence.txt | 1 + src/CMakeLists.txt | 3 ++- src/QuickQanava.h | 19 ++++++++++--------- src/RectNodeTemplate.qml | 15 ++++++++------- src/qanConnector.cpp | 3 ++- src/qanEdge.cpp | 3 ++- src/qanGraph.cpp | 17 +++++++++-------- src/qanGroup.cpp | 3 ++- src/qanNode.cpp | 3 ++- src/qanTableGroup.cpp | 3 ++- src/qanTableGroupItem.cpp | 3 ++- 11 files changed, 42 insertions(+), 31 deletions(-) diff --git a/licence.txt b/licence.txt index 5686b2a4..d0ac9a70 100644 --- a/licence.txt +++ b/licence.txt @@ -1,6 +1,7 @@ BSD License 2.0 Copyright (c) 2008-2023, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80b48aa4..efe4ce7b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -147,6 +147,8 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:QT_ # Configure QuickQanava library ############################################### +qt_policy(SET QTP0001 NEW) + # qt_add_library(QuickQanava STATIC) qt_add_qml_module(QuickQanava STATIC @@ -156,7 +158,6 @@ qt_add_qml_module(QuickQanava ${qan_source_files} ${qan_header_files} ${qan_moc_files} ${quickcontainers_source_files} ${quickcontainers_header_files} QML_FILES ${qan_qml_files} - RESOURCE_PREFIX / OUTPUT_DIRECTORY QuickQanava ) set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/QuickQanava) diff --git a/src/QuickQanava.h b/src/QuickQanava.h index 7577ead5..23c8530c 100755 --- a/src/QuickQanava.h +++ b/src/QuickQanava.h @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -74,17 +75,17 @@ struct QuickQanava { engine->rootContext()->setContextProperty("defaultEdgeStyle", QVariant::fromValue(qan::Edge::style())); engine->rootContext()->setContextProperty("defaultGroupStyle", QVariant::fromValue(qan::Group::style())); - engine->rootContext()->setContextProperty("qanEdgeStraightPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeStraightPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeOrthoPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeOrthoPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeCurvedPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeCurvedPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeStraightPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeStraightPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeOrthoPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeCurvedPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeCurvedPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeSrcArrowPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeSrcArrowPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeSrcCirclePathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeSrcCirclePath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeSrcRectPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeSrcRectPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeSrcArrowPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeSrcArrowPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeSrcCirclePathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeSrcCirclePath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeSrcRectPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeSrcRectPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeDstArrowPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeDstArrowPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeDstCirclePathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeDstCirclePath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeDstRectPathComponent", new QQmlComponent(engine, "qrc:/QuickQanava/EdgeDstRectPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeDstArrowPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeDstArrowPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeDstCirclePathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeDstCirclePath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeDstRectPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeDstRectPath.qml", engine)); } } // initialize() }; diff --git a/src/RectNodeTemplate.qml b/src/RectNodeTemplate.qml index 636755b8..a0d6b6f4 100644 --- a/src/RectNodeTemplate.qml +++ b/src/RectNodeTemplate.qml @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -67,20 +68,20 @@ Item { anchors.fill: parent source: { if (!nodeItem?.style) // Defaul to solid no effect with unconfigured nodes - return "qrc:/QuickQanava/RectSolidBackground.qml"; + return "qrc:/qt/qml/QuickQanava/RectSolidBackground.qml"; switch (nodeItem.style.fillType) { // Otherwise, select the delegate according to current style configuration case Qan.NodeStyle.FillSolid: switch (nodeItem.style.effectType) { - case Qan.NodeStyle.EffectNone: return "qrc:/QuickQanava/RectSolidBackground.qml"; - case Qan.NodeStyle.EffectShadow: return "qrc:/QuickQanava/RectSolidShadowBackground.qml"; - case Qan.NodeStyle.EffectGlow: return "qrc:/QuickQanava/RectSolidGlowBackground.qml"; + case Qan.NodeStyle.EffectNone: return "qrc:/qt/qml/QuickQanava/RectSolidBackground.qml"; + case Qan.NodeStyle.EffectShadow: return "qrc:/qt/qml/QuickQanava/RectSolidShadowBackground.qml"; + case Qan.NodeStyle.EffectGlow: return "qrc:/qt/qml/QuickQanava/RectSolidGlowBackground.qml"; } break; case Qan.NodeStyle.FillGradient: switch (nodeItem.style.effectType) { - case Qan.NodeStyle.EffectNone: return "qrc:/QuickQanava/RectGradientBackground.qml"; - case Qan.NodeStyle.EffectShadow: return "qrc:/QuickQanava/RectGradientShadowBackground.qml"; - case Qan.NodeStyle.EffectGlow: return "qrc:/QuickQanava/RectGradientGlowBackground.qml"; + case Qan.NodeStyle.EffectNone: return "qrc:/qt/qml/QuickQanava/RectGradientBackground.qml"; + case Qan.NodeStyle.EffectShadow: return "qrc:/qt/qml/QuickQanava/RectGradientShadowBackground.qml"; + case Qan.NodeStyle.EffectGlow: return "qrc:/qt/qml/QuickQanava/RectGradientGlowBackground.qml"; } break; } // case fillType diff --git a/src/qanConnector.cpp b/src/qanConnector.cpp index 2e71c220..63836c3a 100644 --- a/src/qanConnector.cpp +++ b/src/qanConnector.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -75,7 +76,7 @@ QQmlComponent* Connector::delegate(QQmlEngine& engine, QObject* parent) noexcep { static std::unique_ptr delegate; if (!delegate) - delegate = std::make_unique(&engine, "qrc:/QuickQanava/VisualConnector.qml", + delegate = std::make_unique(&engine, "qrc:/qt/qml/QuickQanava/VisualConnector.qml", QQmlComponent::PreferSynchronous, parent); return delegate.get(); } diff --git a/src/qanEdge.cpp b/src/qanEdge.cpp index 8e130b97..df9c78d1 100644 --- a/src/qanEdge.cpp +++ b/src/qanEdge.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -75,7 +76,7 @@ QQmlComponent* Edge::delegate(QQmlEngine& engine, QObject* parent) noexcept { static std::unique_ptr delegate; if (!delegate) - delegate = std::make_unique(&engine, "qrc:/QuickQanava/Edge.qml", + delegate = std::make_unique(&engine, "qrc:/qt/qml/QuickQanava/Edge.qml", QQmlComponent::PreferSynchronous, parent); return delegate.get(); } diff --git a/src/qanGraph.cpp b/src/qanGraph.cpp index 43d4a5c0..6a245b27 100644 --- a/src/qanGraph.cpp +++ b/src/qanGraph.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -74,14 +75,14 @@ Graph::~Graph() void Graph::classBegin() { - setPortDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/Port.qml"))); - setHorizontalDockDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/HorizontalDock.qml"))); - setVerticalDockDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/VerticalDock.qml"))); - setGroupDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/Group.qml"))); + setPortDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/Port.qml"))); + setHorizontalDockDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/HorizontalDock.qml"))); + setVerticalDockDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/VerticalDock.qml"))); + setGroupDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/Group.qml"))); // Note: Do not set a default node delegate, otherwise it would be used instead // of qan::Node::delegate(), just let the user specify one. - setEdgeDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/Edge.qml"))); - setSelectionDelegate(createComponent(QStringLiteral("qrc:/QuickQanava/SelectionItem.qml"))); + setEdgeDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/Edge.qml"))); + setSelectionDelegate(createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/SelectionItem.qml"))); const auto engine = qmlEngine(this); if (engine != nullptr) { @@ -97,7 +98,7 @@ void Graph::componentComplete() const auto engine = qmlEngine(this); if (engine != nullptr) { // Visual connector initialization - auto connectorComponent = std::make_unique(engine, QStringLiteral("qrc:/QuickQanava/VisualConnector.qml")); + auto connectorComponent = std::make_unique(engine, QStringLiteral("qrc:/qt/qml/QuickQanava/VisualConnector.qml")); if (connectorComponent) { qan::Style* style = qan::Connector::style(nullptr); if (style != nullptr) { @@ -495,7 +496,7 @@ void Graph::setSelectionDelegate(std::unique_ptr selectionDelegat delegateChanged = true; } } else { // Use QuickQanava default selection delegate - _selectionDelegate = createComponent(QStringLiteral("qrc:/QuickQanava/SelectionItem.qml")); + _selectionDelegate = createComponent(QStringLiteral("qrc:/qt/qml/QuickQanava/SelectionItem.qml")); delegateChanged = true; } if (delegateChanged) { // Update all existing delegates... diff --git a/src/qanGroup.cpp b/src/qanGroup.cpp index fb5f3bb0..08a6cd70 100644 --- a/src/qanGroup.cpp +++ b/src/qanGroup.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -111,7 +112,7 @@ QQmlComponent* Group::delegate(QQmlEngine& engine, QObject* parent) noexcept { static std::unique_ptr delegate; if (!delegate) - delegate = std::make_unique(&engine, "qrc:/QuickQanava/Group.qml", + delegate = std::make_unique(&engine, "qrc:/qt/qml/QuickQanava/Group.qml", QQmlComponent::PreferSynchronous, parent); return delegate.get(); } diff --git a/src/qanNode.cpp b/src/qanNode.cpp index 4f0681cb..6061a2be 100644 --- a/src/qanNode.cpp +++ b/src/qanNode.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -95,7 +96,7 @@ QQmlComponent* Node::delegate(QQmlEngine& engine, QObject* parent) noexcept Q_UNUSED(parent) static std::unique_ptr delegate; if (!delegate) - delegate = std::make_unique(&engine, "qrc:/QuickQanava/Node.qml", + delegate = std::make_unique(&engine, "qrc:/qt/qml/QuickQanava/Node.qml", QQmlComponent::PreferSynchronous); return delegate.get(); } diff --git a/src/qanTableGroup.cpp b/src/qanTableGroup.cpp index 3b6555dd..aa16dea0 100644 --- a/src/qanTableGroup.cpp +++ b/src/qanTableGroup.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -66,7 +67,7 @@ QQmlComponent* TableGroup::delegate(QQmlEngine& engine, QObject* parent) noexce { static std::unique_ptr delegate; if (!delegate) - delegate = std::make_unique(&engine, "qrc:/QuickQanava/TableGroup.qml", + delegate = std::make_unique(&engine, "qrc:/qt/qml/QuickQanava/TableGroup.qml", QQmlComponent::PreferSynchronous, parent); return delegate.get(); } diff --git a/src/qanTableGroupItem.cpp b/src/qanTableGroupItem.cpp index f4bfb188..21b6e41e 100644 --- a/src/qanTableGroupItem.cpp +++ b/src/qanTableGroupItem.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -249,7 +250,7 @@ qan::TableBorder* TableGroupItem::createBorder() return nullptr; } // Component is parented to graph, will be destroyed when graph is destroyed - TableGroupItem::_borderComponent = new QQmlComponent(engine, "qrc:/QuickQanava/TableBorder.qml", + TableGroupItem::_borderComponent = new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/TableBorder.qml", QQmlComponent::PreferSynchronous, getGraph()); } if (!TableGroupItem::_borderComponent) From 5e31c78162eec6f1b2370e25da0273bdb25e6ca7 Mon Sep 17 00:00:00 2001 From: Shantanu Tushar Date: Mon, 13 Jan 2025 21:14:52 +0100 Subject: [PATCH 2/8] Generate QML files in ${CMAKE_BINARY_DIR}/qml This way we can "redirect" QML files from third party libraries to a common directory, and add that to QML_IMPORT_PATH. --- src/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efe4ce7b..f745a744 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -158,9 +158,8 @@ qt_add_qml_module(QuickQanava ${qan_source_files} ${qan_header_files} ${qan_moc_files} ${quickcontainers_source_files} ${quickcontainers_header_files} QML_FILES ${qan_qml_files} - OUTPUT_DIRECTORY QuickQanava ) -set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/QuickQanava) +set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) target_include_directories(QuickQanava PUBLIC From 134a4254d5d4badc9db4449e482b0275911ba54e Mon Sep 17 00:00:00 2001 From: Shantanu Tushar Date: Mon, 27 Jan 2025 10:20:06 +0100 Subject: [PATCH 3/8] Remove seemingly unused qt_wrap_cpp call Needed an update due to changes in Qt 6.8, and ideally should not be required as AUTOMOC is supposed to take of it automagically. https://doc.qt.io/qt-6/qt-wrap-cpp.html https://cmake.org/cmake/help/latest/manual/cmake-qt.7.html#automoc --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f745a744..340284d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,7 +141,6 @@ set(qan_qml_files # Configure Qt set(CMAKE_AUTOMOC ON) -#qt_wrap_cpp(qan_moc_files ${qan_header_files}) # Creates .moc files from sources set(CMAKE_AUTORCC ON) set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$:QT_QML_DEBUG>) From a4e96efe56875615cb97778f825dda1148f66fa8 Mon Sep 17 00:00:00 2001 From: Shantanu Tushar Date: Fri, 16 May 2025 11:57:58 +0200 Subject: [PATCH 4/8] Make sure to update EdgeItem based on the initial style Without this change, the edges will not use the style unless the style changes at least once. --- src/qanEdgeItem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qanEdgeItem.cpp b/src/qanEdgeItem.cpp index 4a6eb8c1..99e13dbe 100644 --- a/src/qanEdgeItem.cpp +++ b/src/qanEdgeItem.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -1225,6 +1226,7 @@ void EdgeItem::setStyle(EdgeStyle* style) noexcept this, &EdgeItem::styleModified); } emit styleChanged(); + styleModified(); updateItem(); // Force initial style settings } } From 333e5d49cd015e5881393eafd046cf20e4d4bab9 Mon Sep 17 00:00:00 2001 From: Christoph Schleifenbaum Date: Wed, 16 Jul 2025 10:38:44 +0200 Subject: [PATCH 5/8] Add qan::Node::groupChanged signal --- src/qanGraph.cpp | 1 + src/qanNode.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qanGraph.cpp b/src/qanGraph.cpp index 6a245b27..3729bde4 100644 --- a/src/qanGraph.cpp +++ b/src/qanGraph.cpp @@ -1077,6 +1077,7 @@ bool qan::Graph::groupNode(qan::Group* group, qan::Node* node, qan::TableCell group->getGroupItem() != nullptr && node->getItem() != nullptr ) { group->getGroupItem()->groupNodeItem(node->getItem(), groupCell, transform); + emit node->groupChanged(); emit nodeGrouped(node, group); } return true; diff --git a/src/qanNode.h b/src/qanNode.h index c0c0e5c2..a3daecb5 100644 --- a/src/qanNode.h +++ b/src/qanNode.h @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -218,7 +219,7 @@ class Node : public gtpo::node _cell; signals: + void groupChanged(); void cellChanged(); //@} //------------------------------------------------------------------------- From 299b2792463a22937b740dfc6d38ad773e439a8c Mon Sep 17 00:00:00 2001 From: Morgan Bengtsson Date: Tue, 22 Jul 2025 14:35:30 +0200 Subject: [PATCH 6/8] Custom ortho edge style - New edge type OrthoCustom that only considers the horizontal layout case. - New style property orthoCustomRadius to customize the path radius. - A control point is computed in the middle, then the edge is drawn through it with a couple of lines and arcs. --- src/CMakeLists.txt | 1 + src/EdgeOrthoRoundedPath.qml | 87 ++++++++++++++++++++++++++++++++++++ src/EdgeTemplate.qml | 15 +++++++ src/QuickQanava.h | 1 + src/qanEdgeItem.cpp | 31 ++++++++++++- src/qanEdgeItem.h | 4 ++ src/qanStyle.cpp | 12 +++++ src/qanStyle.h | 13 +++++- 8 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/EdgeOrthoRoundedPath.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 340284d1..c105ea79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,6 +106,7 @@ set(qan_qml_files EdgeTemplate.qml EdgeStraightPath.qml EdgeOrthoPath.qml + EdgeOrthoRoundedPath.qml EdgeCurvedPath.qml EdgeSrcArrowPath.qml EdgeSrcCirclePath.qml diff --git a/src/EdgeOrthoRoundedPath.qml b/src/EdgeOrthoRoundedPath.qml new file mode 100644 index 00000000..dc150318 --- /dev/null +++ b/src/EdgeOrthoRoundedPath.qml @@ -0,0 +1,87 @@ +/* + Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the author or Destrat.io nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//----------------------------------------------------------------------------- +// This file is a part of the QuickQanava software library. +// +// \file EdgeOrthoRoundedPath.qml +// \author morgan.bengtsson.ext@siemens-energy.com +// \date 2025 07 21 +//----------------------------------------------------------------------------- + +import QtQuick +import QtQuick.Shapes + +ShapePath { + id: edgeShapePath + + // Set in EdgeTemplate.qml createObject() from global qanEdgeOrthoPathComponent + property var edgeTemplate: undefined + property var edgeItem: edgeTemplate.edgeItem + + startX: edgeItem.p1.x + startY: edgeItem.p1.y + capStyle: ShapePath.FlatCap + strokeWidth: edgeItem?.style?.lineWidth ?? 2 + strokeColor: edgeTemplate.color + strokeStyle: edgeTemplate.dashed + dashPattern: edgeItem?.style?.dashPattern ?? [4, 2] + fillColor: Qt.rgba(0,0,0,0) + + property real radius: Math.min(edgeItem?.style?.orthoRoundedRadius ?? 10, Math.abs(edgeItem.p2.y - edgeItem.p1.y) / 2) + property bool goesUp : edgeItem.p2.y > edgeItem.p1.y; + property real radiusCompensation: edgeShapePath.goesUp ? edgeShapePath.radius : -edgeShapePath.radius + + PathLine { + x: edgeShapePath.edgeItem.c1.x - edgeShapePath.radius + y: edgeShapePath.edgeItem.c1.y + } + PathArc { + x: edgeShapePath.edgeItem.c1.x + y: edgeShapePath.edgeItem.c1.y + edgeShapePath.radiusCompensation + radiusX: edgeShapePath.radius + radiusY: edgeShapePath.radius + useLargeArc: false + direction: edgeShapePath.goesUp ? PathArc.Clockwise : PathArc.Counterclockwise + } + PathLine { + x: edgeShapePath.edgeItem.c1.x + y: edgeShapePath.edgeItem.p2.y - edgeShapePath.radiusCompensation + } + PathArc { + x: edgeShapePath.edgeItem.c1.x + edgeShapePath.radius + y: edgeShapePath.edgeItem.p2.y + radiusX: edgeShapePath.radius + radiusY: edgeShapePath.radius + useLargeArc: false + direction: edgeShapePath.goesUp ? PathArc.Counterclockwise: PathArc.Clockwise + } + PathLine { + x: edgeShapePath.edgeItem.p2.x + y: edgeShapePath.edgeItem.p2.y + } +} diff --git a/src/EdgeTemplate.qml b/src/EdgeTemplate.qml index 5b08f5e6..731f406e 100644 --- a/src/EdgeTemplate.qml +++ b/src/EdgeTemplate.qml @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -167,6 +168,15 @@ Item { strokeColor: lineColor }) break; + case Qan.EdgeStyle.OrthoRounded: + if (straightLine) straightLine.destroy() + if (curvedLine) curvedLine.destroy() + edgeSelectionShape.data = orthoLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeSelectionShape, { + edgeTemplate: edgeTemplate, + strokeWidth: lineWidth, + strokeColor: lineColor + }) + break; case Qan.EdgeStyle.Curved: if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() @@ -203,6 +213,11 @@ Item { if (curvedLine) curvedLine.destroy() edgeShape.data = orthoLine = qanEdgeOrthoPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) break; + case Qan.EdgeStyle.OrthoRounded: + if (straightLine) straightLine.destroy() + if (curvedLine) curvedLine.destroy() + edgeShape.data = orthoLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) + break; case Qan.EdgeStyle.Curved: if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() diff --git a/src/QuickQanava.h b/src/QuickQanava.h index 23c8530c..e7061851 100755 --- a/src/QuickQanava.h +++ b/src/QuickQanava.h @@ -77,6 +77,7 @@ struct QuickQanava { engine->rootContext()->setContextProperty("qanEdgeStraightPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeStraightPath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeOrthoPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeOrthoRoundedPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoRoundedPath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeCurvedPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeCurvedPath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeSrcArrowPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeSrcArrowPath.qml", engine)); diff --git a/src/qanEdgeItem.cpp b/src/qanEdgeItem.cpp index 99e13dbe..c1e8d325 100644 --- a/src/qanEdgeItem.cpp +++ b/src/qanEdgeItem.cpp @@ -256,6 +256,7 @@ void EdgeItem::updateItem() noexcept case qan::EdgeStyle::LineType::Straight: generateStraightEnds(cache); break; case qan::EdgeStyle::LineType::Curved: generateStraightEnds(cache); break; case qan::EdgeStyle::LineType::Ortho: generateOrthoEnds(cache); break; + case qan::EdgeStyle::LineType::OrthoRounded: generateOrthoRoundedEnds(cache); break; } if (cache.isValid()) { switch (cache.lineType) { // 3. @@ -263,6 +264,7 @@ void EdgeItem::updateItem() noexcept case qan::EdgeStyle::LineType::Straight: /* Nil */ break; case qan::EdgeStyle::LineType::Curved: generateCurvedControlPoints(cache); break; case qan::EdgeStyle::LineType::Ortho: /* Nil */ break; // Ortho C1 control point is generated in generateOrthoEnds() + case qan::EdgeStyle::LineType::OrthoRounded: /* Nil */ break; // Ortho C1, C2 control point are generated in generateOrthoEndsRounded() } generateArrowGeometry(cache); generateLabelPosition(cache); @@ -614,6 +616,21 @@ void EdgeItem::generateOrthoEnds(GeometryCache& cache) const noexcept } } +void EdgeItem::generateOrthoRoundedEnds(GeometryCache &cache) const noexcept +{ + // PRECONDITIONS: + // cache should be valid + // cache srcBs and dstBs must not be empty (valid bounding shapes are necessary) + if ( !cache.isValid() ) + return; + + cache.p1 = QPointF{cache.srcBr.right(), cache.srcBrCenter.y()}; + cache.p2 = QPointF{cache.dstBr.left(), cache.dstBrCenter.y()}; + auto center = QLineF{cache.p1, cache.p2}.center(); + cache.c1 = QPointF{center.x(), cache.p1.y()}; + cache.c2 = QPointF{center.x(), cache.p2.y()}; // Unused +} + void EdgeItem::generateArrowGeometry(GeometryCache& cache) const noexcept { // PRECONDITIONS: @@ -685,6 +702,7 @@ void EdgeItem::generateArrowGeometry(GeometryCache& cache) const noexcept break; case qan::EdgeStyle::LineType::Ortho: + case qan::EdgeStyle::LineType::OrthoRounded: cache.dstAngle = generateStraightArrowAngle(cache.c1, cache.p2, dstShape, arrowLength); cache.srcAngle = generateStraightArrowAngle(cache.c1, cache.p1, srcShape, arrowLength); break; @@ -994,7 +1012,13 @@ void EdgeItem::applyGeometry(const GeometryCache& cache) noexcept if (cache.lineType == qan::EdgeStyle::LineType::Ortho) { _c1 = mapFromItem(graphContainerItem, cache.c1); emit controlPointsChanged(); - } else if (cache.lineType == qan::EdgeStyle::LineType::Curved) { // Apply control point geometry + } + else if (cache.lineType == qan::EdgeStyle::LineType::OrthoRounded) { + _c1 = mapFromItem(graphContainerItem, cache.c1); + _c2 = mapFromItem(graphContainerItem, cache.c2); + emit controlPointsChanged(); + } + else if (cache.lineType == qan::EdgeStyle::LineType::Curved) { // Apply control point geometry _c1 = mapFromItem(graphContainerItem, cache.c1); _c2 = mapFromItem(graphContainerItem, cache.c2); emit controlPointsChanged(); @@ -1330,7 +1354,12 @@ bool EdgeItem::contains(const QPointF& point) const r = (d2 > -0.001 && d2 < 6.001); } break; + case qan::EdgeStyle::LineType::OrthoRounded: + // Not implemented since we do not support selecting edges + // TODO: Implement later if needed + break; } + return r; } diff --git a/src/qanEdgeItem.h b/src/qanEdgeItem.h index 0dc4618f..c8aa51b0 100644 --- a/src/qanEdgeItem.h +++ b/src/qanEdgeItem.h @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -227,6 +228,9 @@ public slots: //! \brief Generate P1 and P2 for ortho edge style. void generateOrthoEnds(GeometryCache& cache) const noexcept; + //! \brief Generate P1 and P2 for ortho rounded edge style. + void generateOrthoRoundedEnds(GeometryCache& cache) const noexcept; + /*! \brief FIXME * * \note Line geometry may (cache.p1 and cache.p2) could be modified to fit arrow geometry. diff --git a/src/qanStyle.cpp b/src/qanStyle.cpp index a611bb89..409b00bb 100644 --- a/src/qanStyle.cpp +++ b/src/qanStyle.cpp @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -265,6 +266,17 @@ bool EdgeStyle::setArrowSize(qreal arrowSize) noexcept return false; } +auto EdgeStyle::setOrthoRoundedRadius(qreal orthoRoundedRadius) noexcept -> bool +{ + if (_orthoRoundedRadius != orthoRoundedRadius) { + _orthoRoundedRadius = orthoRoundedRadius; + emit orthoRoundedRadiusChanged(); + emit styleModified(); + return true; + } + return false; +} + auto EdgeStyle::setSrcShape(ArrowShape srcShape) noexcept -> bool { if (_srcShape != srcShape) { diff --git a/src/qanStyle.h b/src/qanStyle.h index ccb4db57..29509f38 100644 --- a/src/qanStyle.h +++ b/src/qanStyle.h @@ -1,5 +1,6 @@ /* Copyright (c) 2008-2024, Benoit AUTHEMAN All rights reserved. + Copyright (c) 2025, Siemens Energy Global GmbH & Co. KG Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -375,7 +376,8 @@ class EdgeStyle : public qan::Style Undefined = 0, Straight = 1, Curved = 2, - Ortho = 3 + Ortho = 3, + OrthoRounded = 4 }; Q_ENUM(LineType) @@ -416,6 +418,15 @@ class EdgeStyle : public qan::Style signals: void arrowSizeChanged(); +public: + Q_PROPERTY(qreal orthoRoundedRadius READ getOrthoRoundedRadius WRITE setOrthoRoundedRadius NOTIFY orthoRoundedRadiusChanged FINAL) + bool setOrthoRoundedRadius(qreal orthoRoundedRadius) noexcept; + inline qreal getOrthoRoundedRadius() const noexcept { return _orthoRoundedRadius; } +protected: + qreal _orthoRoundedRadius = 10.0; +signals: + void orthoRoundedRadiusChanged(); + public: //! \copydoc Define shape of source arrow, default None. From a862a76db9d7f28cda1978d04c6c718ebcf4dc42 Mon Sep 17 00:00:00 2001 From: Morgan Bengtsson Date: Mon, 8 Sep 2025 11:36:18 +0200 Subject: [PATCH 7/8] Consistency cleanup * Add separate orthoRoundedLine property on the shapes for consistency with other code --- src/EdgeTemplate.qml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/EdgeTemplate.qml b/src/EdgeTemplate.qml index 731f406e..5297a74d 100644 --- a/src/EdgeTemplate.qml +++ b/src/EdgeTemplate.qml @@ -138,6 +138,7 @@ Item { property var curvedLine : undefined property var straightLine : undefined property var orthoLine : undefined + property var orthoRoundedLine: undefined property var lineType: edgeTemplate.lineType property var lineWidth: edgeItem?.style?.lineWidth + 2. ?? 4. property var lineColor: edgeItem && @@ -151,6 +152,7 @@ Item { switch (lineType) { case Qan.EdgeStyle.Undefined: // falltrought case Qan.EdgeStyle.Straight: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (orthoLine) orthoLine.destroy() if (curvedLine) curvedLine.destroy() edgeSelectionShape.data = straightLine = qanEdgeStraightPathComponent.createObject(edgeSelectionShape, { @@ -160,6 +162,7 @@ Item { }); break; case Qan.EdgeStyle.Ortho: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() edgeSelectionShape.data = orthoLine = qanEdgeOrthoPathComponent.createObject(edgeSelectionShape, { @@ -169,15 +172,17 @@ Item { }) break; case Qan.EdgeStyle.OrthoRounded: + if (orthoLine) orthoLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() - edgeSelectionShape.data = orthoLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeSelectionShape, { + edgeSelectionShape.data = orthoRoundedLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeSelectionShape, { edgeTemplate: edgeTemplate, strokeWidth: lineWidth, strokeColor: lineColor }) break; case Qan.EdgeStyle.Curved: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() edgeSelectionShape.data = curvedLine = qanEdgeCurvedPathComponent.createObject(edgeSelectionShape, { @@ -199,26 +204,31 @@ Item { property var curvedLine : undefined property var straightLine : undefined property var orthoLine : undefined + property var orthoRoundedLine: undefined property var lineType: edgeTemplate.lineType onLineTypeChanged: { switch (lineType) { case Qan.EdgeStyle.Undefined: // falltrought case Qan.EdgeStyle.Straight: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (orthoLine) orthoLine.destroy() if (curvedLine) curvedLine.destroy() edgeShape.data = straightLine = qanEdgeStraightPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}); break; case Qan.EdgeStyle.Ortho: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() edgeShape.data = orthoLine = qanEdgeOrthoPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) break; case Qan.EdgeStyle.OrthoRounded: + if (orthoLine) orthoLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() - edgeShape.data = orthoLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) + edgeShape.data = orthoRoundedLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) break; case Qan.EdgeStyle.Curved: + if (orthoRoundedLine) orthoRoundedLine.destroy() if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() edgeShape.data = curvedLine = qanEdgeCurvedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) From e497ea829c44ef711e74c547c254be79bfda3e25 Mon Sep 17 00:00:00 2001 From: "Bengtsson, Morgan (ext)" Date: Wed, 14 Jan 2026 10:37:29 +0100 Subject: [PATCH 8/8] Generalize ortho double edges --- samples/edges/Ortho.qml | 26 +++++-- src/CMakeLists.txt | 2 +- ...oundedPath.qml => EdgeOrthoDoublePath.qml} | 41 +++++++---- src/EdgeTemplate.qml | 24 +++---- src/QuickQanava.h | 2 +- src/qanEdgeItem.cpp | 70 +++++++++++++++---- src/qanEdgeItem.h | 2 +- src/qanStyle.cpp | 8 +-- src/qanStyle.h | 12 ++-- 9 files changed, 128 insertions(+), 59 deletions(-) rename src/{EdgeOrthoRoundedPath.qml => EdgeOrthoDoublePath.qml} (55%) diff --git a/samples/edges/Ortho.qml b/samples/edges/Ortho.qml index d98c79cb..d2d2d65c 100644 --- a/samples/edges/Ortho.qml +++ b/samples/edges/Ortho.qml @@ -57,14 +57,26 @@ Qan.GraphView { console.error('pos=' + pos) } } // Qan.Graph - CheckBox { + Column { anchors.top: parent.top; anchors.topMargin: 4 - anchors.right: parent.right; anchors.rightMargin: 2 - id: dashed - text: "Ortho" - checked: defaultEdgeStyle.lineType === Qan.EdgeStyle.Ortho - onClicked: { - defaultEdgeStyle.lineType = !checked ? Qan.EdgeStyle.Straight : Qan.EdgeStyle.Ortho + anchors.right: parent.right; anchors.rightMargin: 4 + + RadioButton { + text: "Straight" + checked: defaultEdgeStyle.lineType === Qan.EdgeStyle.Straight + onClicked: defaultEdgeStyle.lineType = Qan.EdgeStyle.Straight + } + RadioButton { + text: "Ortho" + checked: defaultEdgeStyle.lineType === Qan.EdgeStyle.Ortho + onClicked: defaultEdgeStyle.lineType = Qan.EdgeStyle.Ortho + } + RadioButton { + text: "OrthoDouble" + checked: defaultEdgeStyle.lineType === Qan.EdgeStyle.OrthoDouble + onClicked: defaultEdgeStyle.lineType = Qan.EdgeStyle.OrthoDouble } } + + } // Qan.GraphView diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c105ea79..2b615568 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,7 +106,7 @@ set(qan_qml_files EdgeTemplate.qml EdgeStraightPath.qml EdgeOrthoPath.qml - EdgeOrthoRoundedPath.qml + EdgeOrthoDoublePath.qml EdgeCurvedPath.qml EdgeSrcArrowPath.qml EdgeSrcCirclePath.qml diff --git a/src/EdgeOrthoRoundedPath.qml b/src/EdgeOrthoDoublePath.qml similarity index 55% rename from src/EdgeOrthoRoundedPath.qml rename to src/EdgeOrthoDoublePath.qml index dc150318..4b2bd716 100644 --- a/src/EdgeOrthoRoundedPath.qml +++ b/src/EdgeOrthoDoublePath.qml @@ -28,7 +28,7 @@ //----------------------------------------------------------------------------- // This file is a part of the QuickQanava software library. // -// \file EdgeOrthoRoundedPath.qml +// \file EdgeOrthoDoublePath.qml // \author morgan.bengtsson.ext@siemens-energy.com // \date 2025 07 21 //----------------------------------------------------------------------------- @@ -52,33 +52,46 @@ ShapePath { dashPattern: edgeItem?.style?.dashPattern ?? [4, 2] fillColor: Qt.rgba(0,0,0,0) - property real radius: Math.min(edgeItem?.style?.orthoRoundedRadius ?? 10, Math.abs(edgeItem.p2.y - edgeItem.p1.y) / 2) - property bool goesUp : edgeItem.p2.y > edgeItem.p1.y; - property real radiusCompensation: edgeShapePath.goesUp ? edgeShapePath.radius : -edgeShapePath.radius + property real edgeLengthY: Math.abs(edgeItem.p2.y - edgeItem.p1.y) + property real edgeLengthX: Math.abs(edgeItem.p2.x - edgeItem.p1.x) + property int horizontalDir: Math.sign(edgeItem.p1.x - edgeItem.p2.x); + property int verticalDir: Math.sign(edgeItem.p1.y - edgeItem.p2.y); + property bool isPoint: edgeItem.c1.x === edgeItem.c2.x && edgeItem.c1.y === edgeItem.c2.y + property bool horizontalEdge: (edgeItem.c1.x === edgeItem.c2.x) && !isPoint + property bool verticalEdge: (edgeItem.c1.y === edgeItem.c2.y) && !isPoint + property real radius: Math.min(edgeItem?.style?.orthoRadius ?? 10, (horizontalEdge ? edgeLengthY : edgeLengthX) / 2) PathLine { - x: edgeShapePath.edgeItem.c1.x - edgeShapePath.radius - y: edgeShapePath.edgeItem.c1.y + x: edgeShapePath.edgeItem.c1.x + edgeShapePath.horizontalEdge * edgeShapePath.horizontalDir * edgeShapePath.radius + y: edgeShapePath.edgeItem.c1.y + edgeShapePath.verticalEdge * edgeShapePath.verticalDir * edgeShapePath.radius } PathArc { - x: edgeShapePath.edgeItem.c1.x - y: edgeShapePath.edgeItem.c1.y + edgeShapePath.radiusCompensation + x: edgeShapePath.edgeItem.c1.x - edgeShapePath.verticalEdge * edgeShapePath.horizontalDir * edgeShapePath.radius + y: edgeShapePath.edgeItem.c1.y - edgeShapePath.horizontalEdge * edgeShapePath.verticalDir * edgeShapePath.radius radiusX: edgeShapePath.radius radiusY: edgeShapePath.radius useLargeArc: false - direction: edgeShapePath.goesUp ? PathArc.Clockwise : PathArc.Counterclockwise + direction: (edgeShapePath.verticalEdge + ? edgeShapePath.verticalDir !== edgeShapePath.horizontalDir + : edgeShapePath.verticalDir === edgeShapePath.horizontalDir) + ? PathArc.Clockwise + : PathArc.Counterclockwise; } PathLine { - x: edgeShapePath.edgeItem.c1.x - y: edgeShapePath.edgeItem.p2.y - edgeShapePath.radiusCompensation + x: edgeShapePath.edgeItem.c2.x + edgeShapePath.verticalEdge * edgeShapePath.horizontalDir * edgeShapePath.radius + y: edgeShapePath.edgeItem.c2.y + edgeShapePath.horizontalEdge * edgeShapePath.verticalDir * edgeShapePath.radius } PathArc { - x: edgeShapePath.edgeItem.c1.x + edgeShapePath.radius - y: edgeShapePath.edgeItem.p2.y + x: edgeShapePath.edgeItem.c2.x - edgeShapePath.horizontalEdge * edgeShapePath.horizontalDir * edgeShapePath.radius + y: edgeShapePath.edgeItem.c2.y - edgeShapePath.verticalEdge * edgeShapePath.verticalDir * edgeShapePath.radius radiusX: edgeShapePath.radius radiusY: edgeShapePath.radius useLargeArc: false - direction: edgeShapePath.goesUp ? PathArc.Counterclockwise: PathArc.Clockwise + direction: (edgeShapePath.horizontalEdge + ? edgeShapePath.verticalDir !== edgeShapePath.horizontalDir + : edgeShapePath.verticalDir === edgeShapePath.horizontalDir) + ? PathArc.Clockwise + : PathArc.Counterclockwise; } PathLine { x: edgeShapePath.edgeItem.p2.x diff --git a/src/EdgeTemplate.qml b/src/EdgeTemplate.qml index 5297a74d..a35014f9 100644 --- a/src/EdgeTemplate.qml +++ b/src/EdgeTemplate.qml @@ -138,7 +138,7 @@ Item { property var curvedLine : undefined property var straightLine : undefined property var orthoLine : undefined - property var orthoRoundedLine: undefined + property var orthoDoubleLine: undefined property var lineType: edgeTemplate.lineType property var lineWidth: edgeItem?.style?.lineWidth + 2. ?? 4. property var lineColor: edgeItem && @@ -152,7 +152,7 @@ Item { switch (lineType) { case Qan.EdgeStyle.Undefined: // falltrought case Qan.EdgeStyle.Straight: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (orthoLine) orthoLine.destroy() if (curvedLine) curvedLine.destroy() edgeSelectionShape.data = straightLine = qanEdgeStraightPathComponent.createObject(edgeSelectionShape, { @@ -162,7 +162,7 @@ Item { }); break; case Qan.EdgeStyle.Ortho: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() edgeSelectionShape.data = orthoLine = qanEdgeOrthoPathComponent.createObject(edgeSelectionShape, { @@ -171,18 +171,18 @@ Item { strokeColor: lineColor }) break; - case Qan.EdgeStyle.OrthoRounded: + case Qan.EdgeStyle.OrthoDouble: if (orthoLine) orthoLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() - edgeSelectionShape.data = orthoRoundedLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeSelectionShape, { + edgeSelectionShape.data = orthoDoubleLine = qanEdgeOrthoDoublePathComponent.createObject(edgeSelectionShape, { edgeTemplate: edgeTemplate, strokeWidth: lineWidth, strokeColor: lineColor }) break; case Qan.EdgeStyle.Curved: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() edgeSelectionShape.data = curvedLine = qanEdgeCurvedPathComponent.createObject(edgeSelectionShape, { @@ -204,31 +204,31 @@ Item { property var curvedLine : undefined property var straightLine : undefined property var orthoLine : undefined - property var orthoRoundedLine: undefined + property var orthoDoubleLine: undefined property var lineType: edgeTemplate.lineType onLineTypeChanged: { switch (lineType) { case Qan.EdgeStyle.Undefined: // falltrought case Qan.EdgeStyle.Straight: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (orthoLine) orthoLine.destroy() if (curvedLine) curvedLine.destroy() edgeShape.data = straightLine = qanEdgeStraightPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}); break; case Qan.EdgeStyle.Ortho: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() edgeShape.data = orthoLine = qanEdgeOrthoPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) break; - case Qan.EdgeStyle.OrthoRounded: + case Qan.EdgeStyle.OrthoDouble: if (orthoLine) orthoLine.destroy() if (straightLine) straightLine.destroy() if (curvedLine) curvedLine.destroy() - edgeShape.data = orthoRoundedLine = qanEdgeOrthoRoundedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) + edgeShape.data = orthoDoubleLine = qanEdgeOrthoDoublePathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) break; case Qan.EdgeStyle.Curved: - if (orthoRoundedLine) orthoRoundedLine.destroy() + if (orthoDoubleLine) orthoDoubleLine.destroy() if (straightLine) straightLine.destroy() if (orthoLine) orthoLine.destroy() edgeShape.data = curvedLine = qanEdgeCurvedPathComponent.createObject(edgeShape, {edgeTemplate: edgeTemplate}) diff --git a/src/QuickQanava.h b/src/QuickQanava.h index e7061851..c5d30eb2 100755 --- a/src/QuickQanava.h +++ b/src/QuickQanava.h @@ -77,7 +77,7 @@ struct QuickQanava { engine->rootContext()->setContextProperty("qanEdgeStraightPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeStraightPath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeOrthoPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoPath.qml", engine)); - engine->rootContext()->setContextProperty("qanEdgeOrthoRoundedPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoRoundedPath.qml", engine)); + engine->rootContext()->setContextProperty("qanEdgeOrthoDoublePathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeOrthoDoublePath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeCurvedPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeCurvedPath.qml", engine)); engine->rootContext()->setContextProperty("qanEdgeSrcArrowPathComponent", new QQmlComponent(engine, "qrc:/qt/qml/QuickQanava/EdgeSrcArrowPath.qml", engine)); diff --git a/src/qanEdgeItem.cpp b/src/qanEdgeItem.cpp index c1e8d325..9bcfc41a 100644 --- a/src/qanEdgeItem.cpp +++ b/src/qanEdgeItem.cpp @@ -256,7 +256,7 @@ void EdgeItem::updateItem() noexcept case qan::EdgeStyle::LineType::Straight: generateStraightEnds(cache); break; case qan::EdgeStyle::LineType::Curved: generateStraightEnds(cache); break; case qan::EdgeStyle::LineType::Ortho: generateOrthoEnds(cache); break; - case qan::EdgeStyle::LineType::OrthoRounded: generateOrthoRoundedEnds(cache); break; + case qan::EdgeStyle::LineType::OrthoDouble: generateOrthoDoubleEnds(cache); break; } if (cache.isValid()) { switch (cache.lineType) { // 3. @@ -264,7 +264,7 @@ void EdgeItem::updateItem() noexcept case qan::EdgeStyle::LineType::Straight: /* Nil */ break; case qan::EdgeStyle::LineType::Curved: generateCurvedControlPoints(cache); break; case qan::EdgeStyle::LineType::Ortho: /* Nil */ break; // Ortho C1 control point is generated in generateOrthoEnds() - case qan::EdgeStyle::LineType::OrthoRounded: /* Nil */ break; // Ortho C1, C2 control point are generated in generateOrthoEndsRounded() + case qan::EdgeStyle::LineType::OrthoDouble: /* Nil */ break; // Ortho C1, C2 control point are generated in generateOrthoEndsRounded() } generateArrowGeometry(cache); generateLabelPosition(cache); @@ -616,7 +616,7 @@ void EdgeItem::generateOrthoEnds(GeometryCache& cache) const noexcept } } -void EdgeItem::generateOrthoRoundedEnds(GeometryCache &cache) const noexcept +void EdgeItem::generateOrthoDoubleEnds(GeometryCache &cache) const noexcept { // PRECONDITIONS: // cache should be valid @@ -624,11 +624,44 @@ void EdgeItem::generateOrthoRoundedEnds(GeometryCache &cache) const noexcept if ( !cache.isValid() ) return; - cache.p1 = QPointF{cache.srcBr.right(), cache.srcBrCenter.y()}; - cache.p2 = QPointF{cache.dstBr.left(), cache.dstBrCenter.y()}; - auto center = QLineF{cache.p1, cache.p2}.center(); - cache.c1 = QPointF{center.x(), cache.p1.y()}; - cache.c2 = QPointF{center.x(), cache.p2.y()}; // Unused + const bool top = cache.dstBrCenter.y() < cache.srcBr.y(); + const bool right = cache.dstBrCenter.x() > cache.srcBr.x(); + + const auto margin = getStyle()->getOrthoRadius() * 2; + + // Promotes horizontal layout, only vertical if dst is under/above + margin + const bool vert = cache.dstBr.left() < cache.srcBr.right() + margin && cache.dstBr.right() + margin > cache.srcBr.left(); + + cache.valid = !cache.srcBr.intersects(cache.dstBr.adjusted(-margin, -margin, margin, margin)); + + if (vert) { + if (top) { + // Top vertical + cache.p1 = QPointF{cache.srcBrCenter.x(), cache.srcBr.top()}; + cache.p2 = QPointF{cache.dstBrCenter.x(), cache.dstBr.bottom()}; + } else { + // Bottom vertical + cache.p1 = QPointF{cache.srcBrCenter.x(), cache.srcBr.bottom()}; + cache.p2 = QPointF{cache.dstBrCenter.x(), cache.dstBr.top()}; + } + const auto center = QLineF{cache.p1, cache.p2}.center(); + cache.c1 = QPointF{cache.p1.x(), center.y()}; + cache.c2 = QPointF{cache.p2.x(), center.y()}; + } else { + if (right) { + // Right horizontal + cache.p1 = QPointF{cache.srcBr.right(), cache.srcBrCenter.y()}; + cache.p2 = QPointF{cache.dstBr.left(), cache.dstBrCenter.y()}; + } + else { + // Left horizontal + cache.p1 = QPointF{cache.srcBr.left(), cache.srcBrCenter.y()}; + cache.p2 = QPointF{cache.dstBr.right(), cache.dstBrCenter.y()}; + } + const auto center = QLineF{cache.p1, cache.p2}.center(); + cache.c1 = QPointF{center.x(), cache.p1.y()}; + cache.c2 = QPointF{center.x(), cache.p2.y()}; + } } void EdgeItem::generateArrowGeometry(GeometryCache& cache) const noexcept @@ -702,11 +735,15 @@ void EdgeItem::generateArrowGeometry(GeometryCache& cache) const noexcept break; case qan::EdgeStyle::LineType::Ortho: - case qan::EdgeStyle::LineType::OrthoRounded: cache.dstAngle = generateStraightArrowAngle(cache.c1, cache.p2, dstShape, arrowLength); cache.srcAngle = generateStraightArrowAngle(cache.c1, cache.p1, srcShape, arrowLength); break; + case qan::EdgeStyle::LineType::OrthoDouble: + cache.dstAngle = generateStraightArrowAngle(cache.c2, cache.p2, dstShape, arrowLength); + cache.srcAngle = generateStraightArrowAngle(cache.c1, cache.p1, srcShape, arrowLength); + break; + case qan::EdgeStyle::LineType::Curved: // Generate source arrow angle (p2 <-> p1 and c2 <-> c1) cache.srcAngle = generateCurvedArrowAngle(cache.p2, cache.p1, @@ -1013,7 +1050,7 @@ void EdgeItem::applyGeometry(const GeometryCache& cache) noexcept _c1 = mapFromItem(graphContainerItem, cache.c1); emit controlPointsChanged(); } - else if (cache.lineType == qan::EdgeStyle::LineType::OrthoRounded) { + else if (cache.lineType == qan::EdgeStyle::LineType::OrthoDouble) { _c1 = mapFromItem(graphContainerItem, cache.c1); _c2 = mapFromItem(graphContainerItem, cache.c2); emit controlPointsChanged(); @@ -1354,9 +1391,16 @@ bool EdgeItem::contains(const QPointF& point) const r = (d2 > -0.001 && d2 < 6.001); } break; - case qan::EdgeStyle::LineType::OrthoRounded: - // Not implemented since we do not support selecting edges - // TODO: Implement later if needed + case qan::EdgeStyle::LineType::OrthoDouble: + const std::array segments{QLineF{_p1, _c1}, QLineF{_c1, _c2}, QLineF{_c2, _p2}}; + + for (const auto &seg : segments) { + d = distanceFromLine(point, seg); + r = (d > -0.001 && d < 6.001); + if (r) { + break; + } + } break; } diff --git a/src/qanEdgeItem.h b/src/qanEdgeItem.h index c8aa51b0..2629e00d 100644 --- a/src/qanEdgeItem.h +++ b/src/qanEdgeItem.h @@ -229,7 +229,7 @@ public slots: void generateOrthoEnds(GeometryCache& cache) const noexcept; //! \brief Generate P1 and P2 for ortho rounded edge style. - void generateOrthoRoundedEnds(GeometryCache& cache) const noexcept; + void generateOrthoDoubleEnds(GeometryCache& cache) const noexcept; /*! \brief FIXME * diff --git a/src/qanStyle.cpp b/src/qanStyle.cpp index 409b00bb..1f56c608 100644 --- a/src/qanStyle.cpp +++ b/src/qanStyle.cpp @@ -266,11 +266,11 @@ bool EdgeStyle::setArrowSize(qreal arrowSize) noexcept return false; } -auto EdgeStyle::setOrthoRoundedRadius(qreal orthoRoundedRadius) noexcept -> bool +auto EdgeStyle::setOrthoRadius(qreal orthoRadius) noexcept -> bool { - if (_orthoRoundedRadius != orthoRoundedRadius) { - _orthoRoundedRadius = orthoRoundedRadius; - emit orthoRoundedRadiusChanged(); + if (_orthoRadius != orthoRadius) { + _orthoRadius = orthoRadius; + emit orthoRadiusChanged(); emit styleModified(); return true; } diff --git a/src/qanStyle.h b/src/qanStyle.h index 29509f38..884c8d7d 100644 --- a/src/qanStyle.h +++ b/src/qanStyle.h @@ -377,7 +377,7 @@ class EdgeStyle : public qan::Style Straight = 1, Curved = 2, Ortho = 3, - OrthoRounded = 4 + OrthoDouble = 4 }; Q_ENUM(LineType) @@ -419,13 +419,13 @@ class EdgeStyle : public qan::Style void arrowSizeChanged(); public: - Q_PROPERTY(qreal orthoRoundedRadius READ getOrthoRoundedRadius WRITE setOrthoRoundedRadius NOTIFY orthoRoundedRadiusChanged FINAL) - bool setOrthoRoundedRadius(qreal orthoRoundedRadius) noexcept; - inline qreal getOrthoRoundedRadius() const noexcept { return _orthoRoundedRadius; } + Q_PROPERTY(qreal orthoRadius READ getOrthoRadius WRITE setOrthoRadius NOTIFY orthoRadiusChanged FINAL) + bool setOrthoRadius(qreal orthoRadius) noexcept; + inline qreal getOrthoRadius() const noexcept { return _orthoRadius; } protected: - qreal _orthoRoundedRadius = 10.0; + qreal _orthoRadius = 10.0; signals: - void orthoRoundedRadiusChanged(); + void orthoRadiusChanged(); public: