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