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