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	qanConnector.cpp
31 // \author	benoit@destrat.io
32 // \date	2017 03 10
33 //-----------------------------------------------------------------------------
34 
35 // Qt headers
36 #include <QQuickItem>
37 
38 // QuickQanava headers
39 #include "./qanGraph.h"
40 #include "./qanNode.h"
41 #include "./qanEdgeItem.h"
42 #include "./qanPortItem.h"
43 #include "./qanConnector.h"
44 
45 namespace qan { // ::qan
46 
47 /* Connector Object Management *///--------------------------------------------
Connector(QQuickItem * parent)48 Connector::Connector(QQuickItem* parent) :
49     qan::NodeItem{parent}
50 {
51     setAcceptDrops(false);
52     setVisible(false);
53 }
54 
setGraph(qan::Graph * graph)55 auto    Connector::setGraph(qan::Graph* graph) noexcept -> void
56 {
57     if (graph != _graph.data()) {
58         _graph = graph;
59         if (_edgeItem) {
60             _edgeItem->setParentItem(graph->getContainerItem());
61             _edgeItem->setGraph(graph);
62             _edgeItem->setVisible(false);
63         }
64         if (_graph == nullptr)
65             setVisible(false);
66         emit graphChanged();
67     }
68 }
69 
getGraph() const70 auto    Connector::getGraph() const noexcept -> qan::Graph* { return _graph.data(); }
71 //-----------------------------------------------------------------------------
72 
73 /* Node Static Factories *///--------------------------------------------------
delegate(QQmlEngine & engine,QObject * parent)74 QQmlComponent*  Connector::delegate(QQmlEngine& engine, QObject* parent) noexcept
75 {
76     static std::unique_ptr<QQmlComponent>   delegate;
77     if (!delegate)
78         delegate = std::make_unique<QQmlComponent>(&engine, "qrc:/QuickQanava/VisualConnector.qml",
79                                                    QQmlComponent::PreferSynchronous, parent);
80     return delegate.get();
81 }
82 
style(QObject * parent)83 qan::NodeStyle* Connector::style(QObject* parent) noexcept
84 {
85     static QScopedPointer<qan::NodeStyle>  qan_Connector_style;
86     if (!qan_Connector_style)
87         qan_Connector_style.reset(new qan::NodeStyle{parent});
88     return qan_Connector_style.get();
89 }
90 //-----------------------------------------------------------------------------
91 
92 /* Connector Configuration *///------------------------------------------------
connectorReleased(QQuickItem * target)93 void    Connector::connectorReleased(QQuickItem* target) noexcept
94 {
95     // Restore original position
96     if (_connectorItem)
97         _connectorItem->setState("NORMAL");
98 
99     if (_edgeItem)    // Hide connector "transcient" edge item
100         _edgeItem->setVisible(false);
101 
102     if (!_graph)
103         return;
104 
105     const auto dstNodeItem = qobject_cast<qan::NodeItem*>(target);
106     const auto dstPortItem = qobject_cast<qan::PortItem*>(target);
107 
108     const auto srcPortItem = _sourcePort;
109     const auto srcNode = _sourceNode ? _sourceNode.data() :
110                                        _sourcePort ? _sourcePort->getNode() : nullptr;
111     const auto dstNode = dstNodeItem ? dstNodeItem->getNode() :
112                                        dstPortItem ? dstPortItem->getNode() : nullptr;
113 
114     qan::Edge* createdEdge = nullptr;   // Result created edge
115     if ( srcNode != nullptr &&          //// Regular edge node to node connection //////////
116          dstNode != nullptr ) {
117         bool create = true;    // Do not create edge if ports are not bindable, create if there no ports bindings are necessary
118 
119         /*if (srcPortItem) {
120             qDebug() << "srcPortItem.multiplicity=" << srcPortItem->getMultiplicity();
121             qDebug() << "srcPortItem.outDegree=" << srcPortItem->getOutEdgeItems().size();
122             qDebug() << "edge source bindable=" << _graph->isEdgeSourceBindable(*srcPortItem );
123         }
124         if (dstPortItem) {
125             qDebug() << "dstPortItem.multiplicity=" << dstPortItem->getMultiplicity();
126             qDebug() << "srcPortItem.inDegree=" << dstPortItem->getInEdgeItems().size();
127             qDebug() << "edge source bindable=" << _graph->isEdgeDestinationBindable(*dstPortItem );
128         }*/
129 
130         if ( srcPortItem &&
131              dstPortItem != nullptr )
132             create = _graph->isEdgeSourceBindable(*srcPortItem ) &&
133                      _graph->isEdgeDestinationBindable(*dstPortItem);
134         else if ( !srcPortItem &&
135                   dstPortItem != nullptr )
136             create = _graph->isEdgeDestinationBindable(*dstPortItem);
137         else if ( srcPortItem &&
138                   dstPortItem == nullptr )
139             create = _graph->isEdgeSourceBindable(*srcPortItem);
140         if ( getCreateDefaultEdge() ) {
141             if ( create )
142                 createdEdge = _graph->insertEdge( srcNode, dstNode );
143             if ( createdEdge != nullptr ) {     // Special handling for src or dst port item binding
144                 if ( srcPortItem )
145                     _graph->bindEdgeSource(*createdEdge, *srcPortItem);
146                 if ( dstPortItem != nullptr )
147                     _graph->bindEdgeDestination(*createdEdge, *dstPortItem );   // Bind created edge to a destination port
148             }
149         } else
150             emit requestEdgeCreation(srcNode, dstNode);
151     }
152     if ( createdEdge ) // Notify user of the edge creation
153         emit edgeInserted( createdEdge );
154 }
155 
connectorPressed()156 void    Connector::connectorPressed() noexcept
157 {
158     // PRECONDITIONS:
159         // _graph can't be nullptr
160         // _edgeItem can't be nullptr
161     if (_graph == nullptr)
162         return;
163     if (_edgeItem == nullptr)
164         return;
165 
166     _edgeItem->setGraph(_graph);    // Eventually, configure edge item
167     const auto srcItem = _sourcePort ? _sourcePort :
168                                        _sourceNode ? _sourceNode->getItem() : nullptr;
169     _edgeItem->setSourceItem(srcItem);
170     _edgeItem->setDestinationItem(this);
171     _edgeItem->setVisible(true);
172 
173     if (_sourceNode)
174         _graph->selectNode(*_sourceNode);
175 }
176 
getCreateDefaultEdge() const177 auto    Connector::getCreateDefaultEdge() const noexcept -> bool { return _createDefaultEdge; }
setCreateDefaultEdge(bool createDefaultEdge)178 auto    Connector::setCreateDefaultEdge(bool createDefaultEdge) noexcept -> void
179 {
180     if ( createDefaultEdge != _createDefaultEdge ) {
181         _createDefaultEdge = createDefaultEdge;
182         emit createDefaultEdgeChanged();
183     }
184 }
185 
getConnectorItem()186 auto    Connector::getConnectorItem() noexcept -> QQuickItem* { return _connectorItem.data(); }
setConnectorItem(QQuickItem * connectorItem)187 auto    Connector::setConnectorItem(QQuickItem* connectorItem) noexcept -> void
188 {
189     if ( _connectorItem != connectorItem ) {
190         if ( _connectorItem ) {
191             disconnect(_connectorItem.data(), nullptr,
192                        this, nullptr);
193             _connectorItem->deleteLater();
194         }
195 
196         _connectorItem = connectorItem;
197         if ( _connectorItem ) {
198             _connectorItem->setParentItem(this);
199             _connectorItem->setVisible( isVisible() &&
200                                         _sourceNode != nullptr );
201         }
202         emit connectorItemChanged();
203     }
204 }
205 
getEdgeComponent()206 auto    Connector::getEdgeComponent() noexcept -> QQmlComponent* { return _edgeComponent.data(); }
207 
setEdgeComponent(QQmlComponent * edgeComponent)208 auto    Connector::setEdgeComponent(QQmlComponent* edgeComponent) noexcept -> void
209 {
210     if ( edgeComponent != _edgeComponent ) {
211         _edgeComponent = edgeComponent;
212         if ( _edgeComponent &&
213              _edgeComponent->isReady() ) {
214             const auto context = qmlContext(this);
215             if ( context != nullptr ) {
216                 const auto edgeObject = _edgeComponent->create(context);    // Create a new edge
217 
218                 _edgeItem.reset(qobject_cast<qan::EdgeItem*>(edgeObject));  // Any existing edge item is destroyed
219                 if ( _edgeItem &&
220                      !_edgeComponent->isError() ) {
221                     QQmlEngine::setObjectOwnership( _edgeItem.data(), QQmlEngine::CppOwnership );
222                     _edgeItem->setVisible( false );
223                     _edgeItem->setAcceptDrops(false);
224                     if ( getGraph() != nullptr ) {
225                         _edgeItem->setGraph( getGraph() );
226                         _edgeItem->setParentItem( getGraph()->getContainerItem() );
227                     }
228                     if ( _sourceNode &&
229                          _sourceNode->getItem() ) {
230                         _edgeItem->setSourceItem(_sourceNode->getItem());
231                         _edgeItem->setDestinationItem(this);
232                     }
233                     emit edgeItemChanged();
234                 } else {
235                     qWarning() << "qan::Connector::setEdgeComponent(): Error while creating edge:";
236                     qWarning() << "\t" << _edgeComponent->errors();
237                 }
238             }
239         }
240         emit edgeComponentChanged();
241     }
242 }
243 
getEdgeItem()244 auto    Connector::getEdgeItem() noexcept -> qan::EdgeItem*
245 {
246     return _edgeItem.data();
247 }
248 
setSourcePort(qan::PortItem * sourcePort)249 void    Connector::setSourcePort( qan::PortItem* sourcePort ) noexcept
250 {
251     if ( sourcePort != _sourcePort.data() ) {
252         if ( _sourcePort )  // Disconnect destroyed() signal connection with old  source node
253             _sourcePort->disconnect(this);
254         _sourcePort = sourcePort;
255 
256         if ( sourcePort != nullptr ) {      //// Connector configuration with port host /////
257             if ( _sourceNode ) {    // Erase source node, we can't have both a source port and node
258                 _sourceNode->disconnect(this);
259                 _sourceNode = nullptr;
260             }
261 
262             connect( sourcePort, &QObject::destroyed,
263                      this,       &Connector::sourcePortDestroyed );
264             setVisible(true);
265             if ( sourcePort->getNode() != nullptr ) {
266                 setParentItem(sourcePort);
267                 // Configure connector position according to port item dock type
268                 if ( _edgeItem )
269                     _edgeItem->setSourceItem(sourcePort);
270                 if ( _connectorItem ) {
271                     _connectorItem->setParentItem(this);
272                     _connectorItem->setState("NORMAL");
273                     _connectorItem->setVisible(true);
274                 }
275                 setVisible(true);
276             } else {    // Error: hide everything
277                 setVisible(false);
278                 if( _edgeItem )
279                     _edgeItem->setVisible(false);
280                 if( _connectorItem )
281                     _connectorItem->setVisible(false);
282             }
283         }
284 
285         if ( sourcePort == nullptr &&
286              _sourceNode == nullptr )
287             setVisible(false);
288 
289         emit sourcePortChanged();
290     }
291 }
292 
sourcePortDestroyed()293 void    Connector::sourcePortDestroyed()
294 {
295     if ( sender() == _sourcePort.data() ) {
296         if ( _sourcePort != nullptr &&
297              _sourcePort->getNode() != nullptr )       // Fall back on port node when port is destroyed
298             setSourceNode(_sourcePort->getNode());
299         setSourcePort(nullptr);
300     }
301 }
302 
setSourceNode(qan::Node * sourceNode)303 void    Connector::setSourceNode( qan::Node* sourceNode ) noexcept
304 {
305     if (sourceNode != _sourceNode.data()) {
306         if (_sourceNode) {  // Disconnect destroyed() signal connection with old  source node
307             _sourceNode->disconnect(this);
308             setParentItem(nullptr);
309         }
310         _sourceNode = sourceNode;
311 
312         if (_sourceNode) {    //// Connector configuration with port host /////
313             if (_sourcePort) { // Erase source port, we can't have both a source port and node
314                 _sourcePort->disconnect(this);
315                 _sourcePort = nullptr;
316             }
317             if (_sourceNode->getItem() != nullptr) {
318                 setParentItem(_sourceNode->getItem());
319                 if (_edgeItem)
320                     _edgeItem->setSourceItem(_sourceNode->getItem());
321                 if (_connectorItem) {
322                     _connectorItem->setParentItem(this);
323                     _connectorItem->setState("NORMAL");
324                     _connectorItem->setVisible(true);
325                 }
326                 setVisible(true);
327             } else {    // Error: hide everything
328                 setVisible(false);
329                 if (_edgeItem)
330                     _edgeItem->setVisible(false);
331                 if (_connectorItem)
332                     _connectorItem->setVisible(false);
333             }
334         }
335 
336         if (sourceNode == nullptr &&
337             _sourcePort == nullptr)
338             setVisible(false);
339         else {
340             connect(sourceNode, &QObject::destroyed,
341                     this,       &Connector::sourceNodeDestroyed);
342             setVisible(true);
343         }
344         emit sourceNodeChanged();
345     }
346 }
347 
sourceNodeDestroyed()348 void    Connector::sourceNodeDestroyed()
349 {
350     if (sender() == _sourceNode.data())
351         setSourceNode(nullptr);
352 }
353 //-----------------------------------------------------------------------------
354 
355 
356 } // ::qan
357