1 /* 2 Copyright (c) 2008-2021, Benoit AUTHEMAN All rights reserved. 3 4 Redistribution and use in source and binary forms, with or without 5 modification, are permitted provided that the following conditions are met: 6 * Redistributions of source code must retain the above copyright 7 notice, this list of conditions and the following disclaimer. 8 * Redistributions in binary form must reproduce the above copyright 9 notice, this list of conditions and the following disclaimer in the 10 documentation and/or other materials provided with the distribution. 11 * Neither the name of the author or Destrat.io nor the 12 names of its contributors may be used to endorse or promote products 13 derived from this software without specific prior written permission. 14 15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY 19 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 //----------------------------------------------------------------------------- 28 // This file is a part of the QuickQanava software library. 29 // 30 // \file qanGraph.h 31 // \author benoit@destrat.io 32 // \date 2004 February 15 33 //----------------------------------------------------------------------------- 34 35 #pragma once 36 37 // GTpo headers 38 #include "../GTpo/src/gtpo/GTpo" 39 40 // Qt headers 41 #include <QString> 42 #include <QQuickItem> 43 #include <QQmlParserStatus> 44 #include <QSharedPointer> 45 #include <QAbstractListModel> 46 47 // QuickQanava headers 48 #include "./qanUtils.h" 49 #include "./qanGraphConfig.h" 50 #include "./qanStyleManager.h" 51 #include "./qanEdge.h" 52 #include "./qanNode.h" 53 #include "./qanGroup.h" 54 #include "./qanNavigable.h" 55 #include "./qanSelectable.h" 56 #include "./qanConnector.h" 57 58 59 //! Main QuickQanava namespace 60 namespace qan { // ::qan 61 62 class Graph; 63 class Connector; 64 class PortItem; 65 66 /*! \brief Main interface to manage graph topology. 67 * 68 * Visual connection of nodes: 69 * \li Visual connection of nodes with VisualConnector is enabled by setting the \c connectorEnabled property to \c true (default to true). 70 * \li When an edge is created with the visual node connector, the signal \c connectorEdgeInserted with the newly inserted \c edge as an argument. 71 72 * \nosubgrouping 73 */ 74 class Graph : public gtpo::graph<qan::Config> 75 { 76 Q_OBJECT 77 Q_INTERFACES(QQmlParserStatus) 78 79 using gtpo_graph_t = gtpo::graph<qan::Config>; 80 81 friend class qan::Selectable; 82 83 /*! \name Graph Object Management *///------------------------------------- 84 //@{ 85 public: 86 //! Graph default constructor. 87 explicit Graph(QQuickItem* parent = nullptr) noexcept; 88 /*! \brief Graph default destructor. 89 * 90 * Graph is a factory for inserted nodes and edges, even if they have been created trought 91 * QML delegates, they will be destroyed with the graph they have been created in. 92 */ 93 virtual ~Graph() override = default; 94 Graph(const Graph&) = delete; 95 Graph& operator=(const Graph&) = delete; 96 Graph(Graph&&) = delete; 97 Graph& operator=(Graph&&) = delete; 98 public: 99 //! QQmlParserStatus Component.onCompleted() overload to initialize default graph delegate in a valid QQmlEngine. 100 virtual void componentComplete() override; 101 102 virtual void classBegin() override; 103 104 public: 105 /*! \brief Clear this graph topology and styles. 106 * 107 */ 108 Q_INVOKABLE virtual void clearGraph() noexcept; 109 110 /*! \brief If unsure, use clearGraph() to clear the graph. 111 * 112 * Warning: clear() is not virtual, ensure you are calling clear() after a correct cast to the terminal 113 * graph type you are targetting. 114 */ 115 void clear() noexcept; 116 117 public: 118 /*! \brief Similar to QQuickItem::childAt() method, except that it take edge bounding shape into account. 119 * 120 * Using childAt() method will most of the time return qan::Edge items since childAt() use bounding boxes 121 * for item detection. 122 * 123 * \return nullptr if there is no child at requested position, or a QQuickItem that can be casted qan::Node, qan::Edge or qan::Group with qobject_cast<>. 124 */ 125 Q_INVOKABLE QQuickItem* graphChildAt(qreal x, qreal y) const; 126 127 /*! \brief Similar to QQuickItem::childAt() method, except that it only take groups into account (and is hence faster, but still O(n)). 128 * 129 * \arg except Return every compatible group except \c except (can be nullptr). 130 */ 131 Q_INVOKABLE qan::Group* groupAt(const QPointF& p, const QSizeF& s, const QQuickItem* except = nullptr) const; 132 133 public: 134 /*! \brief Quick item used as a parent for all graphics item "factored" by this graph (default to this). 135 * 136 * \note Container item should be initialized at startup, any change will _not_ be refelected to existing 137 * graphics items. 138 */ Q_PROPERTY(QQuickItem * containerItem READ getContainerItem NOTIFY containerItemChanged FINAL)139 Q_PROPERTY(QQuickItem* containerItem READ getContainerItem NOTIFY containerItemChanged FINAL) 140 //! \sa containerItem 141 inline QQuickItem* getContainerItem() noexcept { return _containerItem.data(); } getContainerItem()142 inline const QQuickItem* getContainerItem() const noexcept { return _containerItem.data(); } 143 void setContainerItem( QQuickItem* containerItem ); 144 signals: 145 void containerItemChanged(); 146 private: 147 QPointer< QQuickItem > _containerItem{nullptr}; 148 //@} 149 //------------------------------------------------------------------------- 150 151 /*! \name Visual connection Management *///-------------------------------- 152 //@{ 153 public: 154 /*! \brief Set the visual connector source node. 155 * 156 * \note If \c sourceNode is nullptr, visual connector is hidden. 157 */ 158 Q_INVOKABLE void setConnectorSource(qan::Node* sourceNode) noexcept; 159 signals: 160 //! \copydoc hlg::Connector::requestEdgeCreation 161 void connectorRequestEdgeCreation(qan::Node* src, QObject* dst); 162 //! \copydoc hlg::Connector::edgeInserted 163 void connectorEdgeInserted(qan::Edge* edge); 164 165 public: 166 //! Alias to VisualConnector::edgeColor property (default to Black). Q_PROPERTY(QColor connectorEdgeColor READ getConnectorEdgeColor WRITE setConnectorEdgeColor NOTIFY connectorEdgeColorChanged FINAL)167 Q_PROPERTY( QColor connectorEdgeColor READ getConnectorEdgeColor WRITE setConnectorEdgeColor NOTIFY connectorEdgeColorChanged FINAL ) 168 inline QColor getConnectorEdgeColor() const noexcept { return _connectorEdgeColor; } 169 void setConnectorEdgeColor( QColor connectorEdgeColor ) noexcept; 170 signals: 171 void connectorEdgeColorChanged(); 172 private: 173 QColor _connectorEdgeColor{Qt::black}; 174 175 public: 176 //! Alias to VisualConnector::connectorColor property (default to DodgerBlue). Q_PROPERTY(QColor connectorColor READ getConnectorColor WRITE setConnectorColor NOTIFY connectorColorChanged FINAL)177 Q_PROPERTY( QColor connectorColor READ getConnectorColor WRITE setConnectorColor NOTIFY connectorColorChanged FINAL ) 178 inline QColor getConnectorColor() const noexcept { return _connectorColor; } 179 void setConnectorColor( QColor connectorColor ) noexcept; 180 signals: 181 void connectorColorChanged(); 182 private: 183 QColor _connectorColor{30, 144, 255}; // dodgerblue=rgb(30, 144, 255) 184 185 public: 186 //! Alias to VisualConnector::createDefaultEdge (default to true). Q_PROPERTY(bool connectorCreateDefaultEdge READ getConnectorCreateDefaultEdge WRITE setConnectorCreateDefaultEdge NOTIFY connectorCreateDefaultEdgeChanged FINAL)187 Q_PROPERTY( bool connectorCreateDefaultEdge READ getConnectorCreateDefaultEdge WRITE setConnectorCreateDefaultEdge NOTIFY connectorCreateDefaultEdgeChanged FINAL ) 188 inline bool getConnectorCreateDefaultEdge() const noexcept { return _connectorCreateDefaultEdge; } 189 void setConnectorCreateDefaultEdge( bool connectorCreateDefaultEdge ) noexcept; 190 signals: 191 void connectorCreateDefaultEdgeChanged(); 192 private: 193 bool _connectorCreateDefaultEdge{true}; 194 195 public: 196 //! Alias to qan::Connector::connectorItem property (default to nullptr, ie default connector item). Q_PROPERTY(QQuickItem * connectorItem READ getConnectorItem WRITE setConnectorItem NOTIFY connectorItemChanged FINAL)197 Q_PROPERTY(QQuickItem* connectorItem READ getConnectorItem WRITE setConnectorItem NOTIFY connectorItemChanged FINAL) 198 inline QQuickItem* getConnectorItem() const noexcept { return _connectorItem; } 199 void setConnectorItem( QQuickItem* connectorItem ) noexcept; 200 signals: 201 void connectorItemChanged(); 202 private: 203 QPointer<QQuickItem> _connectorItem{nullptr}; 204 205 public: 206 //! Enable or disable visual connector of nodes in the graph (default to false). Q_PROPERTY(bool connectorEnabled READ getConnectorEnabled WRITE setConnectorEnabled NOTIFY connectorEnabledChanged FINAL)207 Q_PROPERTY( bool connectorEnabled READ getConnectorEnabled WRITE setConnectorEnabled NOTIFY connectorEnabledChanged FINAL ) 208 inline bool getConnectorEnabled() const noexcept { return _connectorEnabled; } 209 void setConnectorEnabled( bool connectorEnabled ) noexcept; 210 signals: 211 void connectorEnabledChanged(); 212 private: 213 bool _connectorEnabled{false}; 214 215 public: 216 //! Control node used as a connector when \c connectorEnabled is set to true (might be nullptr). 217 Q_PROPERTY(qan::Connector* connector READ getConnector NOTIFY connectorChanged FINAL) 218 qan::Connector* getConnector() noexcept; 219 private: 220 QScopedPointer<qan::Connector> _connector{}; 221 signals: 222 void connectorChanged(); 223 //@} 224 //------------------------------------------------------------------------- 225 226 /*! \name Delegates Management *///---------------------------------------- 227 //@{ 228 public: 229 //! Default delegate for qan::Node and Qan.Node nodes. Q_PROPERTY(QQmlComponent * nodeDelegate READ getNodeDelegate WRITE setNodeDelegate NOTIFY nodeDelegateChanged FINAL)230 Q_PROPERTY( QQmlComponent* nodeDelegate READ getNodeDelegate WRITE setNodeDelegate NOTIFY nodeDelegateChanged FINAL ) 231 inline QQmlComponent* getNodeDelegate() noexcept { return _nodeDelegate.get(); } 232 protected: 233 void setNodeDelegate(QQmlComponent* nodeDelegate) noexcept; 234 void setNodeDelegate(std::unique_ptr<QQmlComponent> nodeDelegate) noexcept; 235 signals: 236 void nodeDelegateChanged(); 237 private: 238 std::unique_ptr<QQmlComponent> _nodeDelegate; 239 240 public: 241 //! Default delegate for qan::Edge and Qan.Edge edges. Q_PROPERTY(QQmlComponent * edgeDelegate READ getEdgeDelegate WRITE setEdgeDelegate NOTIFY edgeDelegateChanged FINAL)242 Q_PROPERTY( QQmlComponent* edgeDelegate READ getEdgeDelegate WRITE setEdgeDelegate NOTIFY edgeDelegateChanged FINAL ) 243 inline QQmlComponent* getEdgeDelegate() noexcept { return _edgeDelegate.get(); } 244 protected: 245 void setEdgeDelegate(QQmlComponent* edgeDelegate) noexcept; 246 void setEdgeDelegate(std::unique_ptr<QQmlComponent> edgeDelegate) noexcept; 247 signals: 248 void edgeDelegateChanged(); 249 private: 250 std::unique_ptr<QQmlComponent> _edgeDelegate; 251 252 public: 253 //! Default delegate for qan::Group and Qan.Group groups. Q_PROPERTY(QQmlComponent * groupDelegate READ getGroupDelegate WRITE setGroupDelegate NOTIFY groupDelegateChanged FINAL)254 Q_PROPERTY( QQmlComponent* groupDelegate READ getGroupDelegate WRITE setGroupDelegate NOTIFY groupDelegateChanged FINAL ) 255 inline QQmlComponent* getGroupDelegate() noexcept { return _groupDelegate.get(); } 256 protected: 257 void setGroupDelegate(QQmlComponent* groupDelegate) noexcept; 258 void setGroupDelegate(std::unique_ptr<QQmlComponent> groupDelegate) noexcept; 259 signals: 260 void groupDelegateChanged(); 261 private: 262 std::unique_ptr<QQmlComponent> _groupDelegate; 263 264 protected: 265 //! Create a _styleable_ graph primitive using the given delegate \c component with either a source \c node or \c edge. 266 QQuickItem* createFromComponent( QQmlComponent* component, 267 qan::Style& style, 268 qan::Node* node = nullptr, 269 qan::Edge* edge = nullptr, 270 qan::Group* group = nullptr ) noexcept; 271 272 //! Shortcut to createComponent(), mainly used in Qan.StyleList View to generate item for style pre visualization. 273 Q_INVOKABLE QQuickItem* createFromComponent( QQmlComponent* component, qan::Style* style ); 274 275 public: 276 /*! \brief QML component used to create qan::NodeItem or qan::GroupItem \c selectionItem, could be dynamically changed from either c++ or QML. 277 * 278 * \note Using setSelectionDelegate(nullptr) from c++ or Qan.Graph.selectionDelegate=null from QML is valid, QuickQanava will 279 * default to a basic selection item delegate. 280 */ Q_PROPERTY(QQmlComponent * selectionDelegate READ getSelectionDelegate WRITE setSelectionDelegate NOTIFY selectionDelegateChanged FINAL)281 Q_PROPERTY(QQmlComponent* selectionDelegate READ getSelectionDelegate WRITE setSelectionDelegate NOTIFY selectionDelegateChanged FINAL) 282 //! \copydoc selectionDelegate 283 inline QQmlComponent* getSelectionDelegate() noexcept { return _selectionDelegate.get(); } 284 protected: 285 //! \copydoc selectionDelegate 286 void setSelectionDelegate(QQmlComponent* selectionDelegate) noexcept; 287 //! \copydoc selectionDelegate 288 void setSelectionDelegate(std::unique_ptr<QQmlComponent> selectionDelegate) noexcept; 289 signals: 290 //! \copydoc selectionDelegate 291 void selectionDelegateChanged(); 292 public: // should be considered private 293 /*! \brief Create a concrete QQuickItem using the current \c selectionDelegate (private API). 294 * 295 * \arg parent Returned selection item is automatically reparented to \c parent (could be nullptr). 296 * \return A selection item or nullptr if graph \c selectionDelegate is invalid, ownershipd goes to the caller with QmlEngine::CppOwnership, might be nullptr. 297 */ 298 QPointer<QQuickItem> createSelectionItem(QQuickItem* parent) noexcept; 299 protected: 300 301 struct QObjectDeleteLater { operatorQObjectDeleteLater302 void operator()(QObject *o) { 303 o->deleteLater(); 304 } 305 }; 306 template<typename T> 307 using unique_qptr = std::unique_ptr<T, QObjectDeleteLater>; 308 309 std::unique_ptr<QQmlComponent> _selectionDelegate{nullptr}; 310 private: 311 //! Secure factory for QML components, errors are reported on stderr. 312 std::unique_ptr<QQmlComponent> createComponent(const QString& url) noexcept; 313 //! Secure utility to create a QQuickItem from a given QML component \c component (might issue warning if component is nullptr or not successfully loaded). 314 QPointer<QQuickItem> createItemFromComponent(QQmlComponent* component) noexcept; 315 //@} 316 //------------------------------------------------------------------------- 317 318 /*! \name Graph Node Management *///--------------------------------------- 319 //@{ 320 public: 321 using Node = typename Config::final_node_t; 322 using WeakNode = std::weak_ptr<typename Config::final_node_t>; 323 using SharedNode = std::shared_ptr<typename Config::final_node_t>; 324 325 /*! \brief Insert an already existing node, proxy to GTpo graph insertNode(). 326 * 327 * \warning This method is mainly for tests purposes since inserted node delegate and style is 328 * left unconfigured. HAndle with a lot of care only to insert "pure topology" non visual nodes. 329 * \note trigger nodeInserted() signal after insertion and generate a call to onNodeInserted(). 330 */ 331 auto insertNonVisualNode(SharedNode node) noexcept(false) -> WeakNode; 332 333 /*! \brief Insert a new node in this graph and return a pointer on it, or \c nullptr if creation fails. 334 * 335 * gtpo::bad_topology_error is thrown if node insertion fails. 336 * 337 * A default node delegate must have been registered with registerNodeDelegate() if 338 * \c nodeComponent is unspecified (ie \c nullptr); it is done automatically if 339 * Qan.Graph is used, with a rectangular node delegate for default node. 340 * 341 * \note trigger nodeInserted() signal after insertion and generate a call to onNodeInserted(). 342 * \note graph keep ownership of the returned node. 343 */ 344 Q_INVOKABLE qan::Node* insertNode(QQmlComponent* nodeComponent = nullptr, qan::NodeStyle* nodeStyle = nullptr); 345 346 /*! \brief Insert a node using Node_t::delegate() and Node_t::style(), if no delegate is defined, default on graph \c nodeDelegate. 347 * 348 * \note trigger nodeInserted() signal after insertion and generate a call to onNodeInserted(). 349 */ 350 template <class Node_t> 351 qan::Node* insertNode(QQmlComponent* nodeComponent = nullptr, qan::NodeStyle* nodeStyle = nullptr); 352 353 /*! \brief Same semantic than insertNode<>() but for non visual nodes. 354 * 355 * \note trigger nodeInserted() signal after insertion and generate a call to onNodeInserted(). 356 */ 357 template <class Node_t> 358 qan::Node* insertNonVisualNode(); 359 360 /*! \brief Insert and existing node with a specific delegate component and a custom style. 361 * 362 * \warning \c node ownership is set to Cpp in current QmlEngine. 363 * 364 * \return true if \c node has been successfully inserted. 365 */ 366 bool insertNode(const SharedNode& node, QQmlComponent* nodeComponent = nullptr, qan::NodeStyle* nodeStyle = nullptr); 367 368 /*! \brief Remove node \c node from this graph. Shortcut to gtpo::GenGraph<>::removeNode(). 369 */ 370 Q_INVOKABLE void removeNode(qan::Node* node); 371 372 //! Shortcut to gtpo::GenGraph<>::getNodeCount(). 373 Q_INVOKABLE int getNodeCount() const noexcept; 374 375 //! Return true if \c node is registered in graph. 376 bool hasNode(const qan::Node* node) const; 377 378 public: 379 //! Access the list of nodes with an abstract item model interface. Q_PROPERTY(QAbstractItemModel * nodes READ getNodesModel CONSTANT FINAL)380 Q_PROPERTY(QAbstractItemModel* nodes READ getNodesModel CONSTANT FINAL) 381 QAbstractItemModel* getNodesModel() const { return get_nodes().model(); } 382 383 protected: 384 385 /*! \brief Notify user immediately after a new node \c node has been inserted in graph. 386 * 387 * \warning Since groups are node, onNodeInserted() is also emitted when insertGroup() is called. 388 * \note Signal nodeInserted() is emitted at the same time. 389 * \note Default implementation is empty. 390 */ 391 virtual void onNodeInserted(qan::Node& node); 392 393 /*! \brief Notify user immediately before a node \c node is removed. 394 * 395 * \warning Since groups are node, onNodeInserted() is also emitted when removeGroup() is called. 396 * \note Signal nodeRemoved() is emitted at the same time. 397 * \note Default implementation is empty. 398 */ 399 virtual void onNodeRemoved(qan::Node& node); 400 401 signals: 402 403 //! \copydoc onNodeInserted() 404 void nodeInserted(qan::Node* node); 405 406 //! \copydoc onNodeRemoved() 407 void nodeRemoved(qan::Node* node); 408 409 signals: 410 /*! \brief Emitted whenever a node registered in this graph is clicked. 411 */ 412 void nodeClicked(qan::Node* node, QPointF pos); 413 /*! \brief Emitted whenever a node registered in this graph is right clicked. 414 */ 415 void nodeRightClicked(qan::Node* node, QPointF pos); 416 /*! \brief Emitted whenever a node registered in this graph is double clicked. 417 */ 418 void nodeDoubleClicked(qan::Node* node, QPointF pos); 419 420 signals: 421 //! \brief Emitted _after_ a node (or a group...) has been grouped. 422 void nodeGrouped(qan::Node* node, qan::Group* group); 423 //! \brief Emitted _after_ a node (or a group...) has been un grouped. 424 void nodeUngrouped(qan::Node* node, qan::Group* group); 425 426 /*! \brief Emitted _after_ a node has been moved. 427 */ 428 void nodeMoved(qan::Node* node); 429 430 /*! \brief Emitted _after_ a node has been resized. 431 */ 432 void nodeResized(qan::Node* node); 433 434 /*! \brief Emitted _after_ a grouphas been resized. 435 */ 436 void groupResized(qan::Group* group); 437 438 //! Emitted when a node setLabel() method is called. 439 void nodeLabelChanged(qan::Node* node); 440 //------------------------------------------------------------------------- 441 442 /*! \name Graph Edge Management *///--------------------------------------- 443 //@{ 444 public: 445 //! QML shortcut to insertEdge() method. 446 Q_INVOKABLE qan::Edge* insertEdge(QObject* source, QObject* destination, QQmlComponent* edgeComponent = nullptr); 447 448 //! Shortcut to gtpo::GenGraph<>::insertEdge(). 449 virtual qan::Edge* insertEdge(qan::Node* source, qan::Node* destination, QQmlComponent* edgeComponent = nullptr); 450 451 //! Bind an existing edge source to a visual out port from QML. 452 Q_INVOKABLE void bindEdgeSource(qan::Edge* edge, qan::PortItem* outPort) noexcept; 453 454 //! Bind an existing edge destination to a visual in port from QML. 455 Q_INVOKABLE void bindEdgeDestination(qan::Edge* edge, qan::PortItem* inPort) noexcept; 456 457 //! Shortcut to bindEdgeSource() and bindEdgeDestination() for edge on \c outPort and \c inPort. 458 Q_INVOKABLE void bindEdge(qan::Edge* edge, qan::PortItem* outPort, qan::PortItem* inPort) noexcept; 459 460 /*! \brief Test if an edge source is actually bindable to a given port. 461 * 462 * This method could be used to check if an edge is bindable to a given port 463 * _before_ creating the edge and calling bindEdgeSource(). Port \c multiplicity 464 * and \c connectable properties are taken into account to return a result. 465 * 466 * Example: for an out port with \c Single \c multiplicity where an out edge already 467 * exists, this method returns false. 468 */ 469 virtual bool isEdgeSourceBindable(const qan::PortItem& outPort) const noexcept; 470 471 /*! \brief Test if an edge destination is bindable to a visual in port. 472 * 473 * Same behaviour than isEdgeSourceBindable() for edge destination port. 474 */ 475 virtual bool isEdgeDestinationBindable(const qan::PortItem& inPort) const noexcept; 476 477 //! Bind an existing edge source to a visual out port. 478 virtual void bindEdgeSource(qan::Edge& edge, qan::PortItem& outPort) noexcept; 479 480 //! Bind an existing edge destination to a visual in port. 481 virtual void bindEdgeDestination(qan::Edge& edge, qan::PortItem& inPort) noexcept; 482 483 public: 484 template <class Edge_t> 485 qan::Edge* insertEdge(qan::Node& src, qan::Node* dstNode, QQmlComponent* edgeComponent = nullptr); 486 private: 487 /*! \brief Internal utility used to insert an existing edge \c edge to either a destination \c dstNode node OR edge \c dstEdge. 488 * 489 * \note insertEdgeImpl() will automatically create \c edge graphical delegate using \c edgeComponent and \c style. 490 */ 491 bool configureEdge(qan::Edge& source, QQmlComponent& edgeComponent, qan::EdgeStyle& style, 492 qan::Node& src, qan::Node* dstNode); 493 public: 494 template <class Edge_t> 495 qan::Edge* insertNonVisualEdge(qan::Node& src, qan::Node* dstNode); 496 497 public: 498 //! Shortcut to gtpo::GenGraph<>::removeEdge(). 499 Q_INVOKABLE virtual void removeEdge(qan::Node* source, qan::Node* destination); 500 501 //! Shortcut to gtpo::GenGraph<>::removeEdge(). 502 Q_INVOKABLE virtual void removeEdge(qan::Edge* edge); 503 504 //! Return true if there is at least one directed edge between \c source and \c destination (Shortcut to gtpo::GenGraph<>::hasEdge()). 505 Q_INVOKABLE bool hasEdge(qan::Node* source, qan::Node* destination) const; 506 507 //! Return true if edge is in graph. 508 Q_INVOKABLE bool hasEdge(const qan::Edge* edge) const; 509 510 public: 511 //! Access the list of edges with an abstract item model interface. Q_PROPERTY(QAbstractItemModel * edges READ getEdgesModel CONSTANT FINAL)512 Q_PROPERTY( QAbstractItemModel* edges READ getEdgesModel CONSTANT FINAL ) 513 QAbstractItemModel* getEdgesModel() const { return get_edges().model(); } 514 515 signals: 516 /*! \brief Emitted whenever a node registered in this graph is clicked. 517 * 518 * \sa nodeClicked() 519 */ 520 void edgeClicked(qan::Edge* edge, QPointF pos); 521 /*! \brief Emitted whenever a node registered in this graph is right clicked. 522 * 523 * \sa nodeRightClicked() 524 */ 525 void edgeRightClicked(qan::Edge* edge, QPointF pos); 526 /*! \brief Emitted whenever a node registered in this graph is double clicked. 527 * 528 * \sa nodeDoubleClicked() 529 */ 530 void edgeDoubleClicked(qan::Edge* edge, QPointF pos); 531 532 /*! \brief Emitted _after_ an edge has been inserted (usually with insertEdge()). 533 */ 534 void edgeInserted(qan::Edge* edge); 535 //@} 536 //------------------------------------------------------------------------- 537 538 /*! \name Graph Group Management *///-------------------------------------- 539 //@{ 540 public: 541 using Group = typename Config::final_group_t; 542 using SharedGroup = std::shared_ptr<typename Config::final_group_t>; 543 544 //! Shortcut to gtpo::GenGraph<>::insertGroup(). 545 Q_INVOKABLE virtual qan::Group* insertGroup(); 546 547 /*! \brief Insert a new group in this graph and return a pointer on it, or \c nullptr if creation fails. 548 * 549 * If group insertion fails, method return \c false, no exception is thrown. 550 * 551 * If \c groupComponent is unspecified (ie \c nullptr), default qan::Graph::groupDelegate is 552 * used. 553 * 554 * \note trigger nodeInserted() signal after insertion and generate a call to onNodeInserted(). 555 * \note graph keep ownership of the returned node. 556 */ 557 bool insertGroup(const SharedGroup& group, QQmlComponent* groupComponent = nullptr, qan::NodeStyle* groupStyle = nullptr); 558 559 //! Insert a group using its static delegate() and style() factories. 560 template <class Group_t> 561 qan::Group* insertGroup(); 562 563 //! Shortcut to gtpo::GenGraph<>::removeGroup(). 564 Q_INVOKABLE virtual void removeGroup(qan::Group* group); 565 566 //! Return true if \c group is registered in graph. 567 bool hasGroup(qan::Group* group) const; 568 569 //! Shortcut to gtpo::GenGraph<>::getGroupCount(). getGroupCount()570 Q_INVOKABLE int getGroupCount() const { return gtpo_graph_t::get_group_count(); } 571 572 /*! \brief Group a node \c node inside \c group group. 573 * 574 * To disable node item coordinates transformation to group item, set transform to false then 575 * manually position node item. 576 * 577 * \sa gtpo::GenGraph::groupNode() 578 */ 579 Q_INVOKABLE virtual bool groupNode(qan::Group* group, qan::Node* node, bool transform = true) noexcept; 580 581 //! Ungroup node \c node from group \c group (using nullptr for \c group ungroup node from it's current group without further topology checks). 582 Q_INVOKABLE virtual bool ungroupNode(qan::Node* node, qan::Group* group = nullptr, bool transform = true) noexcept; 583 584 signals: 585 586 /*! \brief Emitted when a group registered in this graph is clicked. 587 */ 588 void groupClicked(qan::Group* group, QPointF pos); 589 /*! \brief Emitted when a group registered in this graph is right clicked. 590 */ 591 void groupRightClicked(qan::Group* group, QPointF pos); 592 /*! \brief Emitted when a group registered in this graph is double clicked. 593 */ 594 void groupDoubleClicked(qan::Group* group, QPointF pos); 595 //@} 596 //------------------------------------------------------------------------- 597 598 /*! \name Selection Management *///---------------------------------------- 599 //@{ 600 public: 601 /*! \brief Graph node selection policy (default to \c SelectOnClick); 602 * \li \c NoSelection: Selection of nodes is disabled. 603 * \li \c SelectOnClick (default): Node is selected when clicked, multiple selection is allowed with CTRL. 604 * \li \c SelectOnCtrlClick: Node is selected only when CTRL is pressed, multiple selection is allowed with CTRL. 605 */ 606 enum SelectionPolicy { NoSelection, SelectOnClick, SelectOnCtrlClick }; 607 Q_ENUM(SelectionPolicy) 608 609 public: 610 /*! \brief Current graph selection policy. 611 * 612 * \warning setting NoSeleciton will clear the actual \c selectedNodes model. 613 */ 614 Q_PROPERTY(SelectionPolicy selectionPolicy READ getSelectionPolicy WRITE setSelectionPolicy NOTIFY selectionPolicyChanged FINAL) 615 void setSelectionPolicy(SelectionPolicy selectionPolicy) noexcept; getSelectionPolicy()616 inline SelectionPolicy getSelectionPolicy() const noexcept { return _selectionPolicy; } 617 private: 618 SelectionPolicy _selectionPolicy = SelectionPolicy::SelectOnClick; 619 signals: 620 void selectionPolicyChanged(); 621 622 623 public: 624 //! Color for the node selection hilgither component (default to dodgerblue). 625 Q_PROPERTY(QColor selectionColor READ getSelectionColor WRITE setSelectionColor NOTIFY selectionColorChanged FINAL) 626 void setSelectionColor(QColor selectionColor) noexcept; getSelectionColor()627 inline QColor getSelectionColor() const noexcept { return _selectionColor; } 628 private: 629 QColor _selectionColor{30, 144, 255}; // dodgerblue=rgb(30, 144, 255) 630 signals: 631 void selectionColorChanged(); 632 633 public: 634 //! Selection hilgither item stroke width (default to 3.0). 635 Q_PROPERTY(qreal selectionWeight READ getSelectionWeight WRITE setSelectionWeight NOTIFY selectionWeightChanged FINAL) 636 void setSelectionWeight(qreal selectionWeight) noexcept; getSelectionWeight()637 inline qreal getSelectionWeight() const noexcept { return _selectionWeight; } 638 private: 639 qreal _selectionWeight = 3.; 640 signals: 641 void selectionWeightChanged(); 642 643 public: 644 //! Margin between the selection hilgither item and a selected item (default to 3.0). 645 Q_PROPERTY(qreal selectionMargin READ getSelectionMargin WRITE setSelectionMargin NOTIFY selectionMarginChanged FINAL) 646 void setSelectionMargin(qreal selectionMargin) noexcept; getSelectionMargin()647 inline qreal getSelectionMargin() const noexcept { return _selectionMargin; } 648 private: 649 qreal _selectionMargin = 3.; 650 signals: 651 void selectionMarginChanged(); 652 653 protected: 654 /*! \brief Force a call to qan::Selectable::configureSelectionItem() call on all currently selected primitives (either nodes or group). 655 */ 656 void configureSelectionItems() noexcept; 657 658 public: 659 /*! \brief Request insertion of a node in the current selection according to current policy and return true if the node was successfully selected. 660 * 661 * \note A call to selectNode() on an already selected node will deselect it, to set an "absolute" selection, use setNodeSelected(). 662 * 663 * \note If \c selectionPolicy is set to Qan.AbstractGraph.NoSelection or SelextionPolicy::NoSelection, 664 * method will always return false. 665 */ 666 bool selectNode(qan::Node& node, Qt::KeyboardModifiers modifiers = Qt::NoModifier); 667 Q_INVOKABLE bool selectNode(qan::Node* node); 668 669 //! Set the node selection state (graph selectionPolicy is not taken into account). 670 void setNodeSelected(qan::Node& node, bool selected); 671 //! \copydoc setNodeSelected 672 Q_INVOKABLE void setNodeSelected(qan::Node* node, bool selected); 673 674 //! Similar to selectNode() for qan::Group (internally group is a node). 675 bool selectGroup(qan::Group& group, Qt::KeyboardModifiers modifiers = Qt::NoModifier); 676 677 /*! \brief Add a node in the current selection. 678 */ 679 void addToSelection(qan::Node& node); 680 //! \copydoc addToSelection 681 void addToSelection(qan::Group& node); 682 683 //! Remove a node from the selection. 684 void removeFromSelection(qan::Node& node); 685 //! \copydoc removeFromSelection 686 void removeFromSelection(qan::Group& node); 687 //! \copydoc removeFromSelection 688 void removeFromSelection(QQuickItem* item); 689 690 //! Select all graph content (nodes, groups and edges). 691 Q_INVOKABLE void selectAll(); 692 693 //! Remove all selected nodes and groups and clear selection. 694 Q_INVOKABLE void removeSelection(); 695 696 //! Clear the current selection. 697 Q_INVOKABLE void clearSelection(); 698 699 //! Return true if multiple node are selected. hasMultipleSelection()700 inline bool hasMultipleSelection() const noexcept { return _selectedNodes.size() > 0 || _selectedGroups.size() > 0; } 701 702 public: 703 using SelectedNodes = qcm::Container<QVector, qan::Node*>; 704 705 //! Read-only list model of currently selected nodes. Q_PROPERTY(QAbstractItemModel * selectedNodes READ getSelectedNodesModel NOTIFY selectedNodesChanged FINAL)706 Q_PROPERTY(QAbstractItemModel* selectedNodes READ getSelectedNodesModel NOTIFY selectedNodesChanged FINAL) // In fact non-notifiable, avoid QML warning 707 QAbstractItemModel* getSelectedNodesModel() { return qobject_cast<QAbstractItemModel*>(_selectedNodes.model()); } 708 709 inline auto getSelectedNodes() noexcept -> SelectedNodes& { return _selectedNodes; } 710 inline auto getSelectedNodes() const noexcept -> const SelectedNodes& { return _selectedNodes; } 711 private: 712 SelectedNodes _selectedNodes; 713 signals: 714 void selectedNodesChanged(); 715 716 public: 717 using SelectedGroups = qcm::Container<QVector, qan::Group*>; 718 719 //! Read-only list model of currently selected groups. Q_PROPERTY(QAbstractItemModel * selectedGroups READ getSelectedGroupsModel NOTIFY selectedGroupsChanged FINAL)720 Q_PROPERTY(QAbstractItemModel* selectedGroups READ getSelectedGroupsModel NOTIFY selectedGroupsChanged FINAL) // In fact non-notifiable, avoid QML warning 721 QAbstractItemModel* getSelectedGroupsModel() { return qobject_cast<QAbstractItemModel*>(_selectedGroups.model()); } 722 723 inline auto getSelectedGroups() noexcept -> SelectedGroups& { return _selectedGroups; } 724 inline auto getSelectedGroups() const noexcept -> const SelectedGroups& { return _selectedGroups; } 725 private: 726 SelectedGroups _selectedGroups; 727 signals: 728 void selectedGroupsChanged(); 729 730 protected: 731 //! \brief Return a vector of currently selected nodes/groups items. 732 std::vector<QQuickItem*> getSelectedItems() const; 733 //@} 734 //------------------------------------------------------------------------- 735 736 /*! \name Alignment Management *///---------------------------------------- 737 //@{ 738 public: 739 //! \brief Align selected nodes/groups items horizontal center. 740 Q_INVOKABLE void alignSelectionHorizontalCenter(); 741 //! \brief Align selected nodes/groups items right. 742 Q_INVOKABLE void alignSelectionRight(); 743 //! \brief Align selected nodes/groups items left. 744 Q_INVOKABLE void alignSelectionLeft(); 745 //! \brief Align selected nodes/groups items top. 746 Q_INVOKABLE void alignSelectionTop(); 747 //! \brief Align selected nodes/groups items bottom. 748 Q_INVOKABLE void alignSelectionBottom(); 749 protected: 750 //! \brief Align \c items horizontal center. 751 void alignHorizontalCenter(std::vector<QQuickItem*>&& items); 752 //! \brief Align \c items right. 753 void alignRight(std::vector<QQuickItem*>&& items); 754 //! \brief Align \c items left. 755 void alignLeft(std::vector<QQuickItem*>&& items); 756 //! \brief Align \c items top. 757 void alignTop(std::vector<QQuickItem*>&& items); 758 //! \brief Align \c items bottom. 759 void alignBottom(std::vector<QQuickItem*>&& items); 760 //@} 761 //------------------------------------------------------------------------- 762 763 /*! \name Style Management *///-------------------------------------------- 764 //@{ 765 public: 766 /*! \brief Graph style manager (ie list of style applicable to graph primitive). 767 */ Q_PROPERTY(qan::StyleManager * styleManager READ getStyleManager CONSTANT FINAL)768 Q_PROPERTY( qan::StyleManager* styleManager READ getStyleManager CONSTANT FINAL ) 769 inline qan::StyleManager* getStyleManager() noexcept { return &_styleManager; } getStyleManager()770 inline const qan::StyleManager* getStyleManager() const noexcept { return &_styleManager; } 771 private: 772 qan::StyleManager _styleManager; 773 //@} 774 //------------------------------------------------------------------------- 775 776 /*! \name Port/Dock Management *///---------------------------------------- 777 //@{ 778 public: 779 780 /*! QML interface for adding an in port to node \c node using default port delegate. 781 * 782 * \note might return nullptr if std::bad_alloc is thrown internally or \c node is invalid or nullptr. 783 * \note After creation, you should configure node multiplicity (hability to have multiple connections 784 * per port), connecivity default to qan::PortItem::Multiple. 785 * 786 * \param node port host node. 787 * \param dock port dock, default to left for in port (either Dock::Top, Dock::Bottom, Dock::Right, Dock::Left). 788 * \param type either an in, out or in/out port (default to in/out). 789 * \param label port visible label. 790 * \param id Optional unique ID for the port (caller must ensure uniqueness), could be used to retrieve ports using qan::NodeItem::getPort(). 791 */ 792 Q_INVOKABLE qan::PortItem* insertPort(qan::Node* node, 793 qan::NodeItem::Dock dock, 794 qan::PortItem::Type portType = qan::PortItem::Type::InOut, 795 QString label = "", 796 QString id = "" ) noexcept; 797 798 /*! Remove a port from a node. 799 * 800 * \param node port host node. 801 * \param port node's port to remove. 802 */ 803 Q_INVOKABLE void removePort(qan::Node* node, qan::PortItem* port) noexcept; 804 805 public: 806 //! Default delegate for node in/out port. Q_PROPERTY(QQmlComponent * portDelegate READ getPortDelegate WRITE qmlSetPortDelegate NOTIFY portDelegateChanged FINAL)807 Q_PROPERTY( QQmlComponent* portDelegate READ getPortDelegate WRITE qmlSetPortDelegate NOTIFY portDelegateChanged FINAL ) 808 //! \copydoc portDelegate 809 inline QQmlComponent* getPortDelegate() noexcept { return _portDelegate.get(); } 810 protected: 811 //! \copydoc portDelegate 812 void qmlSetPortDelegate(QQmlComponent* portDelegate) noexcept; 813 //! \copydoc portDelegate 814 void setPortDelegate(std::unique_ptr<QQmlComponent> portDelegate) noexcept; 815 signals: 816 //! \copydoc portDelegate 817 void portDelegateChanged(); 818 private: 819 //! \copydoc portDelegate 820 std::unique_ptr<QQmlComponent> _portDelegate; 821 822 signals: 823 /*! \brief Emitted whenever a port node registered in this graph is clicked. 824 */ 825 void portClicked(qan::PortItem* port, QPointF pos); 826 /*! \brief Emitted whenever a port node registered in this graph is right clicked. 827 */ 828 void portRightClicked(qan::PortItem* port, QPointF pos); 829 830 public: 831 //! Default delegate for horizontal (either NodeItem::Dock::Top or NodeItem::Dock::Bottom) docks. Q_PROPERTY(QQmlComponent * horizontalDockDelegate READ getHorizontalDockDelegate WRITE setHorizontalDockDelegate NOTIFY horizontalDockDelegateChanged FINAL)832 Q_PROPERTY(QQmlComponent* horizontalDockDelegate READ getHorizontalDockDelegate WRITE setHorizontalDockDelegate NOTIFY horizontalDockDelegateChanged FINAL) 833 //! \copydoc horizontalDockDelegate 834 inline QQmlComponent* getHorizontalDockDelegate() noexcept { return _horizontalDockDelegate.get(); } 835 protected: 836 //! \copydoc horizontalDockDelegate 837 void setHorizontalDockDelegate(QQmlComponent* horizontalDockDelegate) noexcept; 838 //! \copydoc horizontalDockDelegate 839 void setHorizontalDockDelegate(std::unique_ptr<QQmlComponent> horizontalDockDelegate) noexcept; 840 signals: 841 //! \copydoc horizontalDockDelegate 842 void horizontalDockDelegateChanged(); 843 private: 844 //! \copydoc horizontalDockDelegate 845 std::unique_ptr<QQmlComponent> _horizontalDockDelegate; 846 847 public: 848 //! Default delegate for vertical (either NodeItem::Dock::Left or NodeItem::Dock::Right) docks. Q_PROPERTY(QQmlComponent * verticalDockDelegate READ getVerticalDockDelegate WRITE setVerticalDockDelegate NOTIFY verticalDockDelegateChanged FINAL)849 Q_PROPERTY(QQmlComponent* verticalDockDelegate READ getVerticalDockDelegate WRITE setVerticalDockDelegate NOTIFY verticalDockDelegateChanged FINAL) 850 //! \copydoc horizontalDockDelegate 851 inline QQmlComponent* getVerticalDockDelegate() noexcept { return _verticalDockDelegate.get(); } 852 protected: 853 //! \copydoc horizontalDockDelegate 854 void setVerticalDockDelegate(QQmlComponent* verticalDockDelegate) noexcept; 855 //! \copydoc horizontalDockDelegate 856 void setVerticalDockDelegate(std::unique_ptr<QQmlComponent> verticalDockDelegate) noexcept; 857 signals: 858 //! \copydoc horizontalDockDelegate 859 void verticalDockDelegateChanged(); 860 private: 861 //! \copydoc verticalDockDelegate 862 std::unique_ptr<QQmlComponent> _verticalDockDelegate; 863 864 protected: 865 //! Create a dock item from an existing dock item delegate. 866 QPointer<QQuickItem> createDockFromDelegate(qan::NodeItem::Dock dock, qan::Node& node) noexcept; 867 //@} 868 //------------------------------------------------------------------------- 869 870 /*! \name Stacking Management *///----------------------------------------- 871 //@{ 872 public: 873 /*! \brief Send a graphic item (either a node or a group) to top. 874 * 875 * \note When item is a group, the node is moved to top inside it's group, and group is also send 876 * to top. 877 * \note Method is non const as it might affect \c maxZ property. 878 */ 879 Q_INVOKABLE void sendToFront(QQuickItem* item); 880 881 /*! \brief Iterate over all graph container items, find and update the maxZ property. 882 * 883 * \note O(N) with N beeing the graph item count (might be quite costly, mainly defined to update 884 * maxZ after in serialization for example). 885 */ 886 Q_INVOKABLE void findMaxZ() noexcept; 887 888 /*! \brief Maximum global z for nodes and groups (ie top-most item). 889 * 890 * \note By global we mean that z value for a node parented to a group is parent(s) group(s) 891 * a plus item z. 892 */ 893 Q_PROPERTY(qreal maxZ READ getMaxZ WRITE setMaxZ NOTIFY maxZChanged FINAL) 894 qreal getMaxZ() const noexcept; 895 void setMaxZ(const qreal maxZ) noexcept; 896 private: 897 qreal _maxZ = 0.; 898 signals: 899 void maxZChanged(); 900 901 public: 902 //! Add 1. to \c maxZ and return the new \c maxZ. 903 qreal nextMaxZ() noexcept; 904 //! Update maximum z value if \c z is greater than actual \c maxZ value. 905 Q_INVOKABLE void updateMaxZ(qreal z) noexcept; 906 907 protected: 908 /*! \brief Utility to find a QQuickItem maximum z value of \c item childs. 909 * 910 * \return 0. if there is no child, maximum child z value otherwise. 911 */ 912 auto maxChildsZ(QQuickItem* item) const noexcept -> qreal; 913 //@} 914 //------------------------------------------------------------------------- 915 916 /*! \name Topology Algorithms *///----------------------------------------- 917 //@{ 918 public: 919 /*! \brief Synchronously collect all graph nodes of \c node using DFS. 920 * 921 * \warning this method is synchronous and recursive. 922 */ 923 std::vector<QPointer<const qan::Node>> collectRootNodes() const noexcept; 924 925 /*! \brief Synchronously collect all sub-nodes of graph root nodes using DFS. 926 * 927 * \warning this method is synchronous and recursive. 928 */ 929 std::vector<const qan::Node*> collectDfs(bool collectGroup = false) const noexcept; 930 931 /*! \brief Synchronously collect all child nodes of \c node using DFS. 932 * 933 * \note \c node is automatically added to the result and returned as the first 934 * node of the return set. 935 * \warning this method is synchronous and recursive. 936 */ 937 std::vector<const qan::Node*> collectDfs(const qan::Node& node, bool collectGroup = false) const noexcept; 938 939 //! \copydoc collectDfs() 940 auto collectSubNodes(const QVector<qan::Node*> nodes, bool collectGroup = false) const noexcept -> std::unordered_set<const qan::Node*>; 941 942 private: 943 void collectDfsRec(const qan::Node*, 944 std::unordered_set<const qan::Node*>& marks, 945 std::vector<const qan::Node*>& childs, 946 bool collectGroup) const noexcept; 947 948 public: 949 //! Return a set of all edges strongly connected to a set of nodes (ie where source AND destination is in \c nodes). 950 auto collectInerEdges(const std::vector<const qan::Node*>& nodes) const -> std::unordered_set<const qan::Edge*>; 951 952 public: 953 /*! \brief Synchronously collect all parent nodes of \c node using DFS. 954 * 955 * \note \c node is automatically added to the result and returned as the first 956 * node of the return set. 957 * \warning this method is synchronous and recursive. 958 */ 959 std::vector<const qan::Node*> collectAncestorsDfs(const qan::Node& node, bool collectGroup = false) const noexcept; 960 961 private: 962 void collectAncestorsDfsRec(const qan::Node*, 963 std::unordered_set<const qan::Node*>& marks, 964 std::vector<const qan::Node*>& parents, 965 bool collectGroup) const noexcept; 966 967 public: 968 //! \copydoc isAncestor() 969 Q_INVOKABLE bool isAncestor(qan::Node* node, qan::Node* candidate) const; 970 971 /*! \brief Return true if \c candidate node is an ancestor of given \c node. 972 * 973 * \warning this method is synchronous and recursive. 974 * \return true if \c candidate is an ancestor of \c node (ie \c node is an out 975 * node of \c candidate at any degree. 976 */ 977 bool isAncestor(const qan::Node& node, const qan::Node& candidate) const noexcept; 978 979 private: 980 bool isAncestorsDfsRec(const qan::Node*, 981 const qan::Node& candidate, 982 std::unordered_set<const qan::Node*>& marks, 983 bool collectGroup) const noexcept; 984 //@} 985 //------------------------------------------------------------------------- 986 }; 987 988 } // ::qan 989 990 #include "./qanGraph.hpp" 991 992 Q_DECLARE_METATYPE(QAbstractItemModel*) 993 Q_DECLARE_METATYPE(QAbstractListModel*) 994 QML_DECLARE_TYPE(qan::Graph) 995 QML_DECLARE_TYPE(qan::Graph::WeakNode) 996