File indexing completed on 2024-12-01 11:20:50
0001 /*************************************************************************** 0002 * Copyright (C) 2005 by David Saxton * 0003 * david@bluehaze.org * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 2 of the License, or * 0008 * (at your option) any later version. * 0009 ***************************************************************************/ 0010 0011 #include "itemdocumentdata.h" 0012 #include "connector.h" 0013 #include "ecsubcircuit.h" 0014 #include "electronicconnector.h" 0015 #include "flowcodedocument.h" 0016 #include "flowconnector.h" 0017 #include "flowcontainer.h" 0018 #include "itemlibrary.h" 0019 #include "junctionflownode.h" 0020 #include "junctionnode.h" 0021 #include "picitem.h" 0022 #include "pinmapping.h" 0023 0024 #include <KIO/FileCopyJob> 0025 #include <KJobWidgets> 0026 #include <KLocalizedString> 0027 #include <KMessageBox> 0028 0029 #include <QBitArray> 0030 #include <QFile> 0031 #include <QScopedPointer> 0032 #include <QTemporaryFile> 0033 0034 #include <ktechlab_debug.h> 0035 0036 // Converts the QBitArray into a string (e.g. "F289A9E") that can be stored in an xml file 0037 static QString toAsciiHex(QBitArray _data) 0038 { 0039 QBitArray data = _data; 0040 // data = qCompress(data); 0041 0042 // Pad out the data to a nice size 0043 if ((data.size() % 4) != 0) { 0044 data.detach(); 0045 data.resize(data.size() + 4 - (data.size() % 4)); 0046 } 0047 0048 QString text; 0049 for (int i = 0; i < data.size() / 4; ++i) { 0050 unsigned val = 0; 0051 for (unsigned j = 0; j < 4; ++j) 0052 val += (data[4 * i + j] ? 1 : 0) << j; 0053 0054 text += QString::number(val, 16); 0055 } 0056 return text; 0057 } 0058 0059 // Converts a string (e.g. "F289A9E") into a QBitArray, the opposite of the above function 0060 static QBitArray toQBitArray(QString text) 0061 { 0062 unsigned size = text.length(); 0063 QBitArray data(size * 4); 0064 0065 for (unsigned i = 0; i < size; ++i) { 0066 unsigned val = QString(text[i]).toInt(nullptr, 16); 0067 for (unsigned j = 0; j < 4; ++j) 0068 data[4 * i + j] = val & (1 << j); 0069 } 0070 0071 // data = qUncompress(data); 0072 0073 return data; 0074 } 0075 0076 // BEGIN class ItemDocumentData 0077 ItemDocumentData::ItemDocumentData(uint documentType) 0078 { 0079 reset(); 0080 m_documentType = documentType; 0081 } 0082 0083 ItemDocumentData::~ItemDocumentData() 0084 { 0085 } 0086 0087 void ItemDocumentData::reset() 0088 { 0089 m_itemDataMap.clear(); 0090 m_connectorDataMap.clear(); 0091 m_nodeDataMap.clear(); 0092 m_microData.reset(); 0093 m_documentType = Document::dt_none; 0094 } 0095 0096 bool ItemDocumentData::loadData(const QUrl &url) 0097 { 0098 QScopedPointer<QFile> file; 0099 if (!url.isLocalFile()) { 0100 QScopedPointer<QTemporaryFile> downloadedFile(new QTemporaryFile()); 0101 downloadedFile->open(); 0102 KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(downloadedFile->fileName())); 0103 KJobWidgets::setWindow(job, nullptr); 0104 if (!job->exec()) { 0105 KMessageBox::error(nullptr, job->errorString()); 0106 return false; 0107 } 0108 file.reset(downloadedFile.take()); 0109 } else { 0110 QScopedPointer<QFile> localFile(new QFile(url.toLocalFile())); 0111 if (!localFile->open(QIODevice::ReadOnly)) { 0112 KMessageBox::error(nullptr, i18n("Could not open %1 for reading", localFile->fileName())); 0113 return false; 0114 } 0115 file.reset(localFile.take()); 0116 } 0117 0118 QString xml; 0119 QTextStream textStream(file.data()); 0120 while (!textStream.atEnd() /* eof() */) 0121 xml += textStream.readLine() + '\n'; 0122 0123 return fromXML(xml); 0124 } 0125 0126 bool ItemDocumentData::fromXML(const QString &xml) 0127 { 0128 reset(); 0129 0130 QDomDocument doc("KTechlab"); 0131 QString errorMessage; 0132 if (!doc.setContent(xml, &errorMessage)) { 0133 KMessageBox::error(nullptr, i18n("Could not parse XML:\n%1", errorMessage)); 0134 return false; 0135 } 0136 0137 QDomElement root = doc.documentElement(); 0138 0139 QDomNode node = root.firstChild(); 0140 while (!node.isNull()) { 0141 QDomElement element = node.toElement(); 0142 if (!element.isNull()) { 0143 const QString tagName = element.tagName(); 0144 0145 if (tagName == "item") 0146 elementToItemData(element); 0147 0148 else if (tagName == "node") 0149 elementToNodeData(element); 0150 0151 else if (tagName == "connector") 0152 elementToConnectorData(element); 0153 0154 else if (tagName == "pic-settings" || tagName == "micro") 0155 elementToMicroData(element); 0156 0157 else if (tagName == "code") 0158 ; // do nothing - we no longer use this tag 0159 0160 else 0161 qCWarning(KTL_LOG) << "Unrecognised element tag name: " << tagName; 0162 } 0163 0164 node = node.nextSibling(); 0165 } 0166 0167 return true; 0168 } 0169 0170 bool ItemDocumentData::saveData(const QUrl &url) 0171 { 0172 if (url.isLocalFile()) { 0173 QFile file(url.toLocalFile()); 0174 if (!file.open(QIODevice::WriteOnly)) { 0175 KMessageBox::error(nullptr, i18n("Could not open '%1' for writing. Check that you have write permissions", file.fileName()), i18n("Saving File")); 0176 return false; 0177 } 0178 0179 QTextStream stream(&file); 0180 stream << toXML(); 0181 file.close(); 0182 } else { 0183 QTemporaryFile file; 0184 if (!file.open()) { 0185 KMessageBox::error(nullptr, file.errorString()); 0186 return false; 0187 } 0188 QTextStream str(&file); 0189 str << toXML(); 0190 file.close(); 0191 0192 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), url); 0193 KJobWidgets::setWindow(job, nullptr); 0194 if (!job->exec()) { 0195 KMessageBox::error(nullptr, job->errorString()); 0196 return false; 0197 } 0198 } 0199 0200 return true; 0201 } 0202 0203 QString ItemDocumentData::toXML() 0204 { 0205 QDomDocument doc("KTechlab"); 0206 // TODO Add revision information to save file 0207 0208 QDomElement root = doc.createElement("document"); 0209 root.setAttribute("type", documentTypeString()); 0210 doc.appendChild(root); 0211 0212 { 0213 const ItemDataMap::iterator end = m_itemDataMap.end(); 0214 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != end; ++it) { 0215 QDomElement node = itemDataToElement(doc, it.value()); 0216 node.setAttribute("id", it.key()); 0217 root.appendChild(node); 0218 } 0219 } 0220 { 0221 const ConnectorDataMap::iterator end = m_connectorDataMap.end(); 0222 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != end; ++it) { 0223 QDomElement node = connectorDataToElement(doc, it.value()); 0224 node.setAttribute("id", it.key()); 0225 root.appendChild(node); 0226 } 0227 } 0228 { 0229 const NodeDataMap::iterator end = m_nodeDataMap.end(); 0230 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != end; ++it) { 0231 QDomElement node = nodeDataToElement(doc, it.value()); 0232 node.setAttribute("id", it.key()); 0233 root.appendChild(node); 0234 } 0235 } 0236 if (m_documentType == Document::dt_flowcode) { 0237 QDomElement node = microDataToElement(doc); 0238 root.appendChild(node); 0239 } 0240 0241 return doc.toString(); 0242 } 0243 0244 // BEGIN functions for generating / reading QDomElements 0245 QDomElement ItemDocumentData::microDataToElement(QDomDocument &doc) 0246 { 0247 QDomElement node = doc.createElement("micro"); 0248 node.setAttribute("id", m_microData.id); 0249 0250 { 0251 const PinMappingMap::iterator end = m_microData.pinMappings.end(); 0252 for (PinMappingMap::iterator it = m_microData.pinMappings.begin(); it != end; ++it) { 0253 QDomElement pinMapNode = doc.createElement("pinmap"); 0254 0255 QString type; 0256 switch (it.value().type()) { 0257 case PinMapping::SevenSegment: 0258 type = "sevensegment"; 0259 break; 0260 0261 case PinMapping::Keypad_4x3: 0262 type = "keypad_4x3"; 0263 break; 0264 0265 case PinMapping::Keypad_4x4: 0266 type = "keypad_4x4"; 0267 break; 0268 0269 case PinMapping::Invalid: 0270 break; 0271 } 0272 0273 pinMapNode.setAttribute("id", it.key()); 0274 pinMapNode.setAttribute("type", type); 0275 pinMapNode.setAttribute("map", it.value().pins().join(" ")); 0276 0277 node.appendChild(pinMapNode); 0278 } 0279 } 0280 0281 { 0282 const PinDataMap::iterator end = m_microData.pinMap.end(); 0283 for (PinDataMap::iterator it = m_microData.pinMap.begin(); it != end; ++it) { 0284 QDomElement pinNode = doc.createElement("pin"); 0285 0286 pinNode.setAttribute("id", it.key()); 0287 pinNode.setAttribute("type", (it.value().type == PinSettings::pt_input) ? "input" : "output"); 0288 pinNode.setAttribute("state", (it.value().state == PinSettings::ps_off) ? "off" : "on"); 0289 0290 node.appendChild(pinNode); 0291 } 0292 } 0293 0294 { 0295 const QStringMap::iterator end = m_microData.variableMap.end(); 0296 for (QStringMap::iterator it = m_microData.variableMap.begin(); it != end; ++it) { 0297 QDomElement variableNode = doc.createElement("variable"); 0298 0299 variableNode.setAttribute("name", it.key()); 0300 variableNode.setAttribute("value", it.value()); 0301 0302 node.appendChild(variableNode); 0303 } 0304 } 0305 0306 return node; 0307 } 0308 0309 void ItemDocumentData::elementToMicroData(QDomElement element) 0310 { 0311 QString id = element.attribute("id", QString()); 0312 0313 if (id.isNull()) 0314 id = element.attribute("pic", QString()); 0315 0316 if (id.isNull()) { 0317 qCCritical(KTL_LOG) << "Could not find id in element"; 0318 return; 0319 } 0320 0321 m_microData.reset(); 0322 m_microData.id = id; 0323 0324 QDomNode node = element.firstChild(); 0325 while (!node.isNull()) { 0326 QDomElement childElement = node.toElement(); 0327 if (!childElement.isNull()) { 0328 const QString tagName = childElement.tagName(); 0329 0330 if (tagName == "pinmap") { 0331 QString id = childElement.attribute("id", QString()); 0332 QString typeString = childElement.attribute("type", QString()); 0333 0334 if (!id.isEmpty() && !typeString.isEmpty()) { 0335 PinMapping::Type type = PinMapping::Invalid; 0336 0337 if (typeString == "sevensegment") 0338 type = PinMapping::SevenSegment; 0339 0340 else if (typeString == "keypad_4x3") 0341 type = PinMapping::Keypad_4x3; 0342 0343 else if (typeString == "keypad_4x4") 0344 type = PinMapping::Keypad_4x4; 0345 0346 PinMapping pinMapping(type); 0347 // pinMapping.setPins( QStringList::split( " ", childElement.attribute( "map", 0 ) ) ); // 2018.12.01 0348 pinMapping.setPins(childElement.attribute("map", nullptr).split(" ", Qt::SkipEmptyParts)); 0349 0350 m_microData.pinMappings[id] = pinMapping; 0351 } 0352 } 0353 0354 else if (tagName == "pin") { 0355 QString pinID = childElement.attribute("id", QString()); 0356 if (!pinID.isEmpty()) { 0357 m_microData.pinMap[pinID].type = (childElement.attribute("type", "input") == "input") ? PinSettings::pt_input : PinSettings::pt_output; 0358 m_microData.pinMap[pinID].state = (childElement.attribute("state", "off") == "off") ? PinSettings::ps_off : PinSettings::ps_on; 0359 } 0360 } 0361 0362 else if (tagName == "variable") { 0363 QString variableId = childElement.attribute("name", QString()); 0364 m_microData.variableMap[variableId] = childElement.attribute("value", QString()); 0365 } 0366 0367 else 0368 qCCritical(KTL_LOG) << "Unrecognised element tag name: " << tagName; 0369 } 0370 0371 node = node.nextSibling(); 0372 } 0373 } 0374 0375 QDomElement ItemDocumentData::itemDataToElement(QDomDocument &doc, const ItemData &itemData) 0376 { 0377 QDomElement node = doc.createElement("item"); 0378 node.setAttribute("type", itemData.type); 0379 node.setAttribute("x", itemData.x); 0380 node.setAttribute("y", itemData.y); 0381 if (itemData.z != -1) 0382 node.setAttribute("z", itemData.z); 0383 if (itemData.setSize) { 0384 node.setAttribute("offset-x", itemData.size.x()); 0385 node.setAttribute("offset-y", itemData.size.y()); 0386 node.setAttribute("width", itemData.size.width()); 0387 node.setAttribute("height", itemData.size.height()); 0388 } 0389 0390 // If the "orientation" is >= 0, then set by a FlowPart, so we don't need to worry about the angle / flip 0391 if (itemData.orientation >= 0) { 0392 node.setAttribute("orientation", itemData.orientation); 0393 } else { 0394 node.setAttribute("angle", itemData.angleDegrees); 0395 node.setAttribute("flip", itemData.flipped); 0396 } 0397 0398 if (!itemData.parentId.isEmpty()) 0399 node.setAttribute("parent", itemData.parentId); 0400 0401 const QStringMap::const_iterator stringEnd = itemData.dataString.end(); 0402 for (QStringMap::const_iterator it = itemData.dataString.begin(); it != stringEnd; ++it) { 0403 QDomElement e = doc.createElement("data"); 0404 node.appendChild(e); 0405 e.setAttribute("id", it.key()); 0406 e.setAttribute("type", "string"); 0407 e.setAttribute("value", it.value()); 0408 } 0409 0410 const DoubleMap::const_iterator numberEnd = itemData.dataNumber.end(); 0411 for (DoubleMap::const_iterator it = itemData.dataNumber.begin(); it != numberEnd; ++it) { 0412 QDomElement e = doc.createElement("data"); 0413 node.appendChild(e); 0414 e.setAttribute("id", it.key()); 0415 e.setAttribute("type", "number"); 0416 e.setAttribute("value", QString::number(it.value())); 0417 } 0418 0419 const QColorMap::const_iterator colorEnd = itemData.dataColor.end(); 0420 for (QColorMap::const_iterator it = itemData.dataColor.begin(); it != colorEnd; ++it) { 0421 QDomElement e = doc.createElement("data"); 0422 node.appendChild(e); 0423 e.setAttribute("id", it.key()); 0424 e.setAttribute("type", "color"); 0425 e.setAttribute("value", it.value().name()); 0426 } 0427 0428 const QBitArrayMap::const_iterator rawEnd = itemData.dataRaw.end(); 0429 for (QBitArrayMap::const_iterator it = itemData.dataRaw.begin(); it != rawEnd; ++it) { 0430 QDomElement e = doc.createElement("data"); 0431 node.appendChild(e); 0432 e.setAttribute("id", it.key()); 0433 e.setAttribute("type", "raw"); 0434 e.setAttribute("value", toAsciiHex(it.value())); 0435 } 0436 0437 const BoolMap::const_iterator boolEnd = itemData.dataBool.end(); 0438 for (BoolMap::const_iterator it = itemData.dataBool.begin(); it != boolEnd; ++it) { 0439 QDomElement e = doc.createElement("data"); 0440 node.appendChild(e); 0441 e.setAttribute("id", it.key()); 0442 e.setAttribute("type", "bool"); 0443 e.setAttribute("value", QString::number(it.value())); 0444 } 0445 0446 const BoolMap::const_iterator buttonEnd = itemData.buttonMap.end(); 0447 for (BoolMap::const_iterator it = itemData.buttonMap.begin(); it != buttonEnd; ++it) { 0448 QDomElement e = doc.createElement("button"); 0449 node.appendChild(e); 0450 e.setAttribute("id", it.key()); 0451 e.setAttribute("state", QString::number(it.value())); 0452 } 0453 0454 const IntMap::const_iterator sliderEnd = itemData.sliderMap.end(); 0455 for (IntMap::const_iterator it = itemData.sliderMap.begin(); it != sliderEnd; ++it) { 0456 QDomElement e = doc.createElement("slider"); 0457 node.appendChild(e); 0458 e.setAttribute("id", it.key()); 0459 e.setAttribute("value", QString::number(it.value())); 0460 } 0461 0462 return node; 0463 } 0464 0465 void ItemDocumentData::elementToItemData(QDomElement element) 0466 { 0467 QString id = element.attribute("id", QString()); 0468 if (id.isNull()) { 0469 qCCritical(KTL_LOG) << "Could not find id in element"; 0470 return; 0471 } 0472 0473 ItemData itemData; 0474 itemData.type = element.attribute("type", QString()); 0475 itemData.x = element.attribute("x", "120").toInt(); 0476 itemData.y = element.attribute("y", "120").toInt(); 0477 itemData.z = element.attribute("z", "-1").toInt(); 0478 0479 if (element.hasAttribute("width") && element.hasAttribute("height")) { 0480 itemData.setSize = true; 0481 itemData.size = QRect(element.attribute("offset-x", "0").toInt(), element.attribute("offset-y", "0").toInt(), element.attribute("width", "120").toInt(), element.attribute("height", "120").toInt()); 0482 } else 0483 itemData.setSize = false; 0484 0485 itemData.angleDegrees = element.attribute("angle", "0").toInt(); 0486 itemData.flipped = element.attribute("flip", "0").toInt(); 0487 itemData.orientation = element.attribute("orientation", "-1").toInt(); 0488 itemData.parentId = element.attribute("parent", QString()); 0489 0490 m_itemDataMap[id] = itemData; 0491 0492 QDomNode node = element.firstChild(); 0493 while (!node.isNull()) { 0494 QDomElement childElement = node.toElement(); 0495 if (!childElement.isNull()) { 0496 const QString tagName = childElement.tagName(); 0497 0498 if (tagName == "item") { 0499 // We're reading in a file saved in the older format, with 0500 // child items nestled, so we must specify that the new item 0501 // has the currently parsed item as its parent. 0502 elementToItemData(childElement); 0503 QString childId = childElement.attribute("id", QString()); 0504 if (!childId.isNull()) 0505 m_itemDataMap[childId].parentId = id; 0506 } 0507 0508 else if (tagName == "data") { 0509 QString dataId = childElement.attribute("id", QString()); 0510 if (!dataId.isNull()) { 0511 QString dataType = childElement.attribute("type", QString()); 0512 QString value = childElement.attribute("value", QString()); 0513 0514 if (dataType == "string" || dataType == "multiline") 0515 m_itemDataMap[id].dataString[dataId] = value; 0516 else if (dataType == "number") 0517 m_itemDataMap[id].dataNumber[dataId] = value.toDouble(); 0518 else if (dataType == "color") 0519 m_itemDataMap[id].dataColor[dataId] = QColor(value); 0520 else if (dataType == "raw") 0521 m_itemDataMap[id].dataRaw[dataId] = toQBitArray(value); 0522 else if (dataType == "bool") 0523 m_itemDataMap[id].dataBool[dataId] = bool(value.toInt()); 0524 else 0525 qCCritical(KTL_LOG) << "Unknown data type of \"" << dataType << "\" with id \"" << dataId << "\""; 0526 } 0527 } 0528 0529 else if (tagName == "button") { 0530 QString buttonId = childElement.attribute("id", QString()); 0531 if (!buttonId.isNull()) 0532 m_itemDataMap[id].buttonMap[buttonId] = childElement.attribute("state", "0").toInt(); 0533 } 0534 0535 else if (tagName == "slider") { 0536 QString sliderId = childElement.attribute("id", QString()); 0537 if (!sliderId.isNull()) 0538 m_itemDataMap[id].sliderMap[sliderId] = childElement.attribute("value", "0").toInt(); 0539 } 0540 0541 else if (tagName == "child-node") 0542 ; // Tag name was used in 0.1 file save format 0543 0544 else 0545 qCCritical(KTL_LOG) << "Unrecognised element tag name: " << tagName; 0546 } 0547 0548 node = node.nextSibling(); 0549 } 0550 } 0551 0552 QDomElement ItemDocumentData::nodeDataToElement(QDomDocument &doc, const NodeData &nodeData) 0553 { 0554 QDomElement node = doc.createElement("node"); 0555 node.setAttribute("x", nodeData.x); 0556 node.setAttribute("y", nodeData.y); 0557 return node; 0558 } 0559 0560 void ItemDocumentData::elementToNodeData(QDomElement element) 0561 { 0562 QString id = element.attribute("id", QString()); 0563 if (id.isNull()) { 0564 qCCritical(KTL_LOG) << "Could not find id in element"; 0565 return; 0566 } 0567 0568 NodeData nodeData; 0569 nodeData.x = element.attribute("x", "120").toInt(); 0570 nodeData.y = element.attribute("y", "120").toInt(); 0571 0572 m_nodeDataMap[id] = nodeData; 0573 } 0574 0575 QDomElement ItemDocumentData::connectorDataToElement(QDomDocument &doc, const ConnectorData &connectorData) 0576 { 0577 QDomElement node = doc.createElement("connector"); 0578 0579 node.setAttribute("manual-route", connectorData.manualRoute); 0580 0581 QString route; 0582 const QPointList::const_iterator end = connectorData.route.end(); 0583 for (QPointList::const_iterator it = connectorData.route.begin(); it != end; ++it) { 0584 route.append(QString::number((*it).x()) + ","); 0585 route.append(QString::number((*it).y()) + ","); 0586 } 0587 node.setAttribute("route", route); 0588 0589 if (connectorData.startNodeIsChild) { 0590 node.setAttribute("start-node-is-child", 1); 0591 node.setAttribute("start-node-cid", connectorData.startNodeCId); 0592 node.setAttribute("start-node-parent", connectorData.startNodeParent); 0593 } else { 0594 node.setAttribute("start-node-is-child", 0); 0595 node.setAttribute("start-node-id", connectorData.startNodeId); 0596 } 0597 0598 if (connectorData.endNodeIsChild) { 0599 node.setAttribute("end-node-is-child", 1); 0600 node.setAttribute("end-node-cid", connectorData.endNodeCId); 0601 node.setAttribute("end-node-parent", connectorData.endNodeParent); 0602 } else { 0603 node.setAttribute("end-node-is-child", 0); 0604 node.setAttribute("end-node-id", connectorData.endNodeId); 0605 } 0606 0607 return node; 0608 } 0609 0610 void ItemDocumentData::elementToConnectorData(QDomElement element) 0611 { 0612 QString id = element.attribute("id", QString()); 0613 if (id.isNull()) { 0614 qCCritical(KTL_LOG) << "Could not find id in element"; 0615 return; 0616 } 0617 0618 ConnectorData connectorData; 0619 0620 connectorData.manualRoute = (element.attribute("manual-route", "0") == "1"); 0621 QString route = element.attribute("route", ""); 0622 0623 QStringList points = route.split(",", Qt::SkipEmptyParts); // QStringList::split( ",", route ); // 2018.12.01 0624 if (route.isEmpty()) { 0625 points = QStringList(); 0626 } 0627 qCDebug(KTL_LOG) << "points=" << points; 0628 const QStringList::iterator end = points.end(); 0629 for (QStringList::iterator it = points.begin(); it != end; ++it) { 0630 int x = (*it).toInt(); 0631 it++; 0632 if (it != end) { 0633 int y = (*it).toInt(); 0634 connectorData.route.append(QPoint(x, y)); 0635 } 0636 } 0637 0638 connectorData.startNodeIsChild = element.attribute("start-node-is-child", "0").toInt(); 0639 if (connectorData.startNodeIsChild) { 0640 connectorData.startNodeCId = element.attribute("start-node-cid", QString()); 0641 connectorData.startNodeParent = element.attribute("start-node-parent", QString()); 0642 } else 0643 connectorData.startNodeId = element.attribute("start-node-id", QString()); 0644 0645 connectorData.endNodeIsChild = element.attribute("end-node-is-child", "0").toInt(); 0646 if (connectorData.endNodeIsChild) { 0647 connectorData.endNodeCId = element.attribute("end-node-cid", QString()); 0648 connectorData.endNodeParent = element.attribute("end-node-parent", QString()); 0649 } else 0650 connectorData.endNodeId = element.attribute("end-node-id", QString()); 0651 0652 m_connectorDataMap[id] = connectorData; 0653 } 0654 // END functions for generating / reading QDomElements 0655 0656 QString ItemDocumentData::documentTypeString() const 0657 { 0658 switch (m_documentType) { 0659 case Document::dt_circuit: 0660 return "circuit"; 0661 break; 0662 case Document::dt_flowcode: 0663 return "flowcode"; 0664 break; 0665 case Document::dt_mechanics: 0666 return "mechanics"; 0667 break; 0668 case Document::dt_text: 0669 case Document::dt_none: 0670 default: 0671 return "none"; 0672 break; 0673 } 0674 } 0675 0676 QString ItemDocumentData::revisionString() const 0677 { 0678 return "1"; 0679 } 0680 0681 void ItemDocumentData::saveDocumentState(ItemDocument *itemDocument) 0682 { 0683 if (!itemDocument) 0684 return; 0685 0686 reset(); 0687 0688 addItems(itemDocument->itemList()); 0689 0690 if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(itemDocument)) { 0691 addConnectors(icnd->connectorList()); 0692 addNodes(icnd->nodeList()); 0693 0694 if (FlowCodeDocument *fcd = dynamic_cast<FlowCodeDocument *>(itemDocument)) { 0695 if (fcd->microSettings()) 0696 setMicroData(fcd->microSettings()->microData()); 0697 } 0698 } 0699 0700 m_documentType = itemDocument->type(); 0701 } 0702 0703 void ItemDocumentData::generateUniqueIDs(ItemDocument *itemDocument) 0704 { 0705 if (!itemDocument) 0706 return; 0707 0708 QStringMap replaced; 0709 replaced[""] = QString(); 0710 replaced[QString()] = QString(); 0711 0712 ItemDataMap newItemDataMap; 0713 ConnectorDataMap newConnectorDataMap; 0714 NodeDataMap newNodeDataMap; 0715 0716 // BEGIN Go through and replace the old ids 0717 { 0718 const ItemDataMap::iterator end = m_itemDataMap.end(); 0719 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != end; ++it) { 0720 if (!replaced.contains(it.key())) 0721 replaced[it.key()] = itemDocument->generateUID(it.key()); 0722 0723 newItemDataMap[replaced[it.key()]] = it.value(); 0724 } 0725 } 0726 { 0727 const NodeDataMap::iterator end = m_nodeDataMap.end(); 0728 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != end; ++it) { 0729 if (!replaced.contains(it.key())) 0730 replaced[it.key()] = itemDocument->generateUID(it.key()); 0731 0732 newNodeDataMap[replaced[it.key()]] = it.value(); 0733 } 0734 } 0735 { 0736 const ConnectorDataMap::iterator end = m_connectorDataMap.end(); 0737 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != end; ++it) { 0738 if (!replaced.contains(it.key())) 0739 replaced[it.key()] = itemDocument->generateUID(it.key()); 0740 0741 newConnectorDataMap[replaced[it.key()]] = it.value(); 0742 } 0743 } 0744 // END Go through and replace the old ids 0745 0746 // BEGIN Go through and replace the internal references to the ids 0747 { 0748 const ItemDataMap::iterator end = newItemDataMap.end(); 0749 for (ItemDataMap::iterator it = newItemDataMap.begin(); it != end; ++it) { 0750 it.value().parentId = replaced[it.value().parentId]; 0751 } 0752 } 0753 { 0754 const ConnectorDataMap::iterator end = newConnectorDataMap.end(); 0755 for (ConnectorDataMap::iterator it = newConnectorDataMap.begin(); it != end; ++it) { 0756 it.value().startNodeParent = replaced[it.value().startNodeParent]; 0757 it.value().endNodeParent = replaced[it.value().endNodeParent]; 0758 0759 it.value().startNodeId = replaced[it.value().startNodeId]; 0760 it.value().endNodeId = replaced[it.value().endNodeId]; 0761 } 0762 } 0763 // END Go through and replace the internal references to the ids 0764 0765 m_itemDataMap = newItemDataMap; 0766 m_connectorDataMap = newConnectorDataMap; 0767 m_nodeDataMap = newNodeDataMap; 0768 } 0769 0770 void ItemDocumentData::translateContents(int dx, int dy) 0771 { 0772 // BEGIN Go through and replace the old ids 0773 { 0774 const ItemDataMap::iterator end = m_itemDataMap.end(); 0775 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != end; ++it) { 0776 it.value().x += dx; 0777 it.value().y += dx; 0778 } 0779 } 0780 { 0781 const NodeDataMap::iterator end = m_nodeDataMap.end(); 0782 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != end; ++it) { 0783 it.value().x += dx; 0784 it.value().y += dy; 0785 } 0786 } 0787 { 0788 const ConnectorDataMap::iterator end = m_connectorDataMap.end(); 0789 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != end; ++it) { 0790 const QPointList::iterator routeEnd = it.value().route.end(); 0791 for (QPointList::iterator routeIt = it.value().route.begin(); routeIt != routeEnd; ++routeIt) { 0792 *routeIt += QPoint(dx / 8, dy / 8); 0793 } 0794 } 0795 } 0796 } 0797 0798 void ItemDocumentData::restoreDocument(ItemDocument *itemDocument) 0799 { 0800 if (!itemDocument) 0801 return; 0802 0803 ICNDocument *icnd = dynamic_cast<ICNDocument *>(itemDocument); 0804 FlowCodeDocument *fcd = dynamic_cast<FlowCodeDocument *>(icnd); 0805 if (fcd && !m_microData.id.isEmpty()) { 0806 fcd->setPicType(m_microData.id); 0807 fcd->microSettings()->restoreFromMicroData(m_microData); 0808 } 0809 0810 mergeWithDocument(itemDocument, false); 0811 0812 { 0813 ItemList removeItems = itemDocument->itemList(); 0814 removeItems.removeAll(static_cast<Item *>(nullptr)); 0815 0816 const ItemDataMap::iterator end = m_itemDataMap.end(); 0817 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != end; ++it) 0818 removeItems.removeAll(itemDocument->itemWithID(it.key())); 0819 0820 const ItemList::iterator removeEnd = removeItems.end(); 0821 for (ItemList::iterator it = removeItems.begin(); it != removeEnd; ++it) { 0822 if ((*it)->canvas() && (*it)->type() != PicItem::typeString()) 0823 (*it)->removeItem(); 0824 } 0825 } 0826 0827 if (icnd) { 0828 { 0829 NodeList removeNodes = icnd->nodeList(); 0830 removeNodes.removeAll(static_cast<Node *>(nullptr)); 0831 0832 const NodeDataMap::iterator end = m_nodeDataMap.end(); 0833 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != end; ++it) 0834 removeNodes.removeAll(icnd->nodeWithID(it.key())); 0835 0836 const NodeList::iterator removeEnd = removeNodes.end(); 0837 for (NodeList::iterator it = removeNodes.begin(); it != removeEnd; ++it) { 0838 if ((*it)->canvas() && !(*it)->isChildNode()) 0839 (*it)->removeNode(); 0840 } 0841 } 0842 { 0843 ConnectorList removeConnectors = icnd->connectorList(); 0844 removeConnectors.removeAll(static_cast<Connector *>(nullptr)); 0845 0846 const ConnectorDataMap::iterator end = m_connectorDataMap.end(); 0847 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != end; ++it) 0848 removeConnectors.removeAll(icnd->connectorWithID(it.key())); 0849 0850 const ConnectorList::iterator removeEnd = removeConnectors.end(); 0851 for (ConnectorList::iterator it = removeConnectors.begin(); it != removeEnd; ++it) { 0852 if ((*it)->canvas()) 0853 (*it)->removeConnectorNoArg(); 0854 } 0855 } 0856 } 0857 0858 itemDocument->flushDeleteList(); 0859 } 0860 0861 void ItemDocumentData::mergeWithDocument(ItemDocument *itemDocument, bool selectNew) 0862 { 0863 if (!itemDocument) 0864 return; 0865 0866 ICNDocument *icnd = dynamic_cast<ICNDocument *>(itemDocument); 0867 0868 // BEGIN Restore Nodes 0869 if (icnd) { 0870 const NodeDataMap::iterator nodeEnd = m_nodeDataMap.end(); 0871 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != nodeEnd; ++it) { 0872 if (!icnd->nodeWithID(it.key())) { 0873 QString id = it.key(); 0874 if (itemDocument->type() == Document::dt_circuit) 0875 new JunctionNode(icnd, 270, QPoint(int(it.value().x), int(it.value().y)), &id); 0876 0877 else if (itemDocument->type() == Document::dt_flowcode) 0878 new JunctionFlowNode(icnd, 270, QPoint(int(it.value().x), int(it.value().y)), &id); 0879 } 0880 } 0881 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != nodeEnd; ++it) { 0882 Node *node = icnd->nodeWithID(it.key()); 0883 if (node) 0884 node->move(it.value().x, it.value().y); 0885 } 0886 } 0887 // END Restore Nodes 0888 0889 // BEGIN Restore items 0890 const ItemDataMap::iterator itemEnd = m_itemDataMap.end(); 0891 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != itemEnd; ++it) { 0892 if (!it.value().type.isEmpty() && !itemDocument->itemWithID(it.key())) { 0893 Item *item = itemLibrary()->createItem(it.value().type, itemDocument, false, it.key().toLatin1().data(), false); 0894 if (item && !itemDocument->isValidItem(item)) { 0895 qCWarning(KTL_LOG) << "Attempted to create invalid item with id: " << it.key(); 0896 item->removeItem(); 0897 itemDocument->flushDeleteList(); 0898 item = nullptr; 0899 } 0900 if (item) { 0901 // HACK We move the item now before restoreFromItemData is called later, in case it is to be parented 0902 //(as we don't want to move children)... 0903 item->move(it.value().x, it.value().y); 0904 } 0905 } 0906 } 0907 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != itemEnd; ++it) { 0908 Item *item = itemDocument->itemWithID(it.key()); 0909 if (!item) 0910 continue; 0911 0912 item->restoreFromItemData(it.value()); 0913 item->finishedCreation(); 0914 if (selectNew) 0915 itemDocument->select(item); 0916 item->show(); 0917 } 0918 // END Restore Items 0919 0920 // BEGIN Restore Connectors 0921 if (icnd) { 0922 const ConnectorDataMap::iterator connectorEnd = m_connectorDataMap.end(); 0923 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != connectorEnd; ++it) { 0924 if (icnd->connectorWithID(it.key())) 0925 continue; 0926 0927 QString id = it.key(); 0928 Node *startNode = nullptr; 0929 Node *endNode = nullptr; 0930 0931 if (it.value().startNodeIsChild) { 0932 CNItem *item = icnd->cnItemWithID(it.value().startNodeParent); 0933 if (!item) 0934 qCCritical(KTL_LOG) << "Unable to find node parent with id: " << it.value().startNodeParent; 0935 else 0936 startNode = item->childNode(it.value().startNodeCId); 0937 } else 0938 startNode = icnd->nodeWithID(it.value().startNodeId); 0939 0940 if (it.value().endNodeIsChild) { 0941 CNItem *item = icnd->cnItemWithID(it.value().endNodeParent); 0942 if (!item) 0943 qCCritical(KTL_LOG) << "Unable to find node parent with id: " << it.value().endNodeParent; 0944 else 0945 endNode = item->childNode(it.value().endNodeCId); 0946 } else 0947 endNode = icnd->nodeWithID(it.value().endNodeId); 0948 0949 if (!startNode || !endNode) { 0950 qCCritical(KTL_LOG) << "End and start nodes for the connector do not both exist"; 0951 } else { 0952 Connector *connector; 0953 0954 // HACK // FIXME // TODO 0955 // for some strange reason the lists in the ItemDocument class the ID lists for items 0956 // get out of sync, so some id's are considered to be registered, but in fact they 0957 // have no assiciated items; this causes stange bugs when insterting subcircuits in the circuit. 0958 // this is just a temporary fix; someone should get to the real cause of this problem and fix 0959 // ItemDocument 0960 if (icnd->connectorWithID(id)) { 0961 qCWarning(KTL_LOG) << "Unregistering connector with ID: " << id << ". This should not delete any of your connections!"; 0962 } 0963 icnd->unregisterUID(id); 0964 0965 // FIXME ICNDocument->type() used 0966 // FIXME tons of dynamic_cast 0967 if ((icnd->type() == Document::dt_circuit) || (icnd->type() == Document::dt_pinMapEditor)) { 0968 connector = new ElectronicConnector(dynamic_cast<ECNode *>(startNode), dynamic_cast<ECNode *>(endNode), icnd, &id); 0969 (dynamic_cast<ECNode *>(startNode))->addConnector(connector); 0970 (dynamic_cast<ECNode *>(endNode))->addConnector(connector); 0971 } else { 0972 connector = new FlowConnector(dynamic_cast<FPNode *>(startNode), dynamic_cast<FPNode *>(endNode), icnd, &id); 0973 (dynamic_cast<FPNode *>(startNode))->addOutputConnector(connector); 0974 (dynamic_cast<FPNode *>(endNode))->addInputConnector(connector); 0975 } 0976 } 0977 } 0978 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != connectorEnd; ++it) { 0979 Connector *connector = icnd->connectorWithID(it.key()); 0980 if (connector) { 0981 connector->restoreFromConnectorData(it.value()); 0982 if (selectNew) 0983 icnd->select(connector); 0984 } 0985 } 0986 } 0987 // END Restore Connectors 0988 0989 // This is kind of hackish, but never mind 0990 if (FlowCodeDocument *fcd = dynamic_cast<FlowCodeDocument *>(itemDocument)) { 0991 const ItemList fcdItems = fcd->itemList(); 0992 const ItemList::const_iterator fcdItemsEnd = fcdItems.constEnd(); 0993 for (ItemList::const_iterator it = fcdItems.constBegin(); it != fcdItemsEnd; ++it) { 0994 if (FlowContainer *fc = dynamic_cast<FlowContainer *>(static_cast<Item *>(*it))) 0995 fc->updateContainedVisibility(); 0996 } 0997 } 0998 } 0999 1000 void ItemDocumentData::setMicroData(const MicroData &data) 1001 { 1002 m_microData = data; 1003 } 1004 1005 void ItemDocumentData::addItems(const ItemList &itemList) 1006 { 1007 const ItemList::const_iterator end = itemList.constEnd(); 1008 for (ItemList::const_iterator it = itemList.constBegin(); it != end; ++it) { 1009 if (*it && (*it)->canvas() && (*it)->type() != PicItem::typeString()) 1010 addItemData((*it)->itemData(), (*it)->id()); 1011 } 1012 } 1013 1014 void ItemDocumentData::addConnectors(const ConnectorList &connectorList) 1015 { 1016 const ConnectorList::const_iterator end = connectorList.constEnd(); 1017 for (ConnectorList::const_iterator it = connectorList.constBegin(); it != end; ++it) { 1018 if (*it && (*it)->canvas()) { 1019 if ((*it)->startNode() && (*it)->endNode()) 1020 addConnectorData((*it)->connectorData(), (*it)->id()); 1021 1022 else 1023 qCDebug(KTL_LOG) << " *it=" << *it << " (*it)->startNode()=" << (*it)->startNode() << " (*it)->endNode()=" << (*it)->endNode(); 1024 } 1025 } 1026 } 1027 1028 void ItemDocumentData::addNodes(const NodeList &nodeList) 1029 { 1030 const NodeList::const_iterator end = nodeList.constEnd(); 1031 for (NodeList::const_iterator it = nodeList.constBegin(); it != end; ++it) { 1032 if (*it && (*it)->canvas() && !(*it)->isChildNode()) 1033 addNodeData((*it)->nodeData(), (*it)->id()); 1034 } 1035 } 1036 1037 void ItemDocumentData::addItemData(ItemData itemData, QString id) 1038 { 1039 if (m_itemDataMap.contains(id)) { 1040 qCWarning(KTL_LOG) << "Overwriting item: " << id; 1041 } 1042 m_itemDataMap[id] = itemData; 1043 } 1044 1045 void ItemDocumentData::addConnectorData(ConnectorData connectorData, QString id) 1046 { 1047 if (m_connectorDataMap.contains(id)) { 1048 qCWarning(KTL_LOG) << "Overwriting connector: " << id; 1049 } 1050 m_connectorDataMap[id] = connectorData; 1051 } 1052 1053 void ItemDocumentData::addNodeData(NodeData nodeData, QString id) 1054 { 1055 if (m_nodeDataMap.contains(id)) { 1056 qCWarning(KTL_LOG) << "Overwriting node: " << id; 1057 } 1058 m_nodeDataMap[id] = nodeData; 1059 } 1060 // END class ItemDocumentData 1061 1062 // BEGIN class ItemData 1063 ItemData::ItemData() 1064 { 1065 x = 0; 1066 y = 0; 1067 z = -1; 1068 angleDegrees = 0; 1069 flipped = false; 1070 orientation = -1; 1071 setSize = false; 1072 } 1073 // END class ItemData 1074 1075 // BEGIN class ConnectorData 1076 ConnectorData::ConnectorData() 1077 { 1078 manualRoute = false; 1079 startNodeIsChild = false; 1080 endNodeIsChild = false; 1081 } 1082 // END class ConnectorData 1083 1084 // BEGIN class NodeData 1085 NodeData::NodeData() 1086 { 1087 x = 0; 1088 y = 0; 1089 } 1090 // END class NodeDaata 1091 1092 // BEGIN class PinData 1093 PinData::PinData() 1094 { 1095 type = PinSettings::pt_input; 1096 state = PinSettings::ps_off; 1097 } 1098 // END class PinData 1099 1100 // BEGIN class MicroData 1101 MicroData::MicroData() 1102 { 1103 } 1104 1105 void MicroData::reset() 1106 { 1107 id = QString(); 1108 pinMap.clear(); 1109 } 1110 // END class MicroData 1111 1112 // BEGIN class SubcircuitData 1113 SubcircuitData::SubcircuitData() 1114 : ItemDocumentData(Document::dt_circuit) 1115 { 1116 } 1117 1118 void SubcircuitData::initECSubcircuit(ECSubcircuit *ecSubcircuit) 1119 { 1120 if (!ecSubcircuit) 1121 return; 1122 1123 generateUniqueIDs(ecSubcircuit->itemDocument()); 1124 1125 // Generate a list of the External Connections, sorting by x coordinate 1126 std::multimap<double, QString> extCon; 1127 ItemDataMap::iterator itemEnd = m_itemDataMap.end(); 1128 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != itemEnd; ++it) { 1129 if (it.value().type == "ec/external_connection") 1130 extCon.insert(std::make_pair(it.value().x, it.key())); 1131 } 1132 1133 // How many external connections do we have? 1134 ecSubcircuit->setNumExtCon(extCon.size()); 1135 1136 // Sort the connections into the pins of the subcircuit by y coordinate 1137 std::multimap<double, QString> leftPins; 1138 std::multimap<double, QString> rightPins; 1139 int at = 0; 1140 int size = (extCon.size() / 2) + (extCon.size() % 2); 1141 const std::multimap<double, QString>::iterator extConEnd = extCon.end(); 1142 for (std::multimap<double, QString>::iterator it = extCon.begin(); it != extConEnd; ++it) { 1143 if (at < size) 1144 leftPins.insert(std::make_pair(m_itemDataMap[it->second].y, it->second)); 1145 else 1146 rightPins.insert(std::make_pair(m_itemDataMap[it->second].y, it->second)); 1147 at++; 1148 } 1149 1150 // Remove the external connections (recording their names and associated numerical position) 1151 int nodeId = 0; 1152 typedef QMap<QString, int> IntMap; 1153 IntMap nodeMap; 1154 const std::multimap<double, QString>::iterator leftPinsEnd = leftPins.end(); 1155 for (std::multimap<double, QString>::iterator it = leftPins.begin(); it != leftPinsEnd; ++it) { 1156 nodeMap[it->second] = nodeId; 1157 ecSubcircuit->setExtConName(nodeId, m_itemDataMap[it->second].dataString["name"]); 1158 nodeId++; 1159 m_itemDataMap.remove(it->second); 1160 } 1161 nodeId = extCon.size() - 1; 1162 const std::multimap<double, QString>::iterator rightPinsEnd = rightPins.end(); 1163 for (std::multimap<double, QString>::iterator it = rightPins.begin(); it != rightPinsEnd; ++it) { 1164 nodeMap[it->second] = nodeId; 1165 ecSubcircuit->setExtConName(nodeId, m_itemDataMap[it->second].dataString["name"]); 1166 nodeId--; 1167 m_itemDataMap.remove(it->second); 1168 } 1169 1170 // Replace connector references to the old External Connectors to the nodes 1171 const ConnectorDataMap::iterator connectorEnd = m_connectorDataMap.end(); 1172 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != connectorEnd; ++it) { 1173 if (it.value().startNodeIsChild && nodeMap.contains(it.value().startNodeParent)) { 1174 it.value().startNodeCId = QString::number(nodeMap[it.value().startNodeParent]); 1175 it.value().startNodeParent = ecSubcircuit->id(); 1176 } 1177 if (it.value().endNodeIsChild && nodeMap.contains(it.value().endNodeParent)) { 1178 it.value().endNodeCId = QString::number(nodeMap[it.value().endNodeParent]); 1179 it.value().endNodeParent = ecSubcircuit->id(); 1180 } 1181 } 1182 1183 // Create all the new stuff 1184 mergeWithDocument(ecSubcircuit->itemDocument(), false); 1185 1186 // Parent and hide the new stuff 1187 itemEnd = m_itemDataMap.end(); 1188 for (ItemDataMap::iterator it = m_itemDataMap.begin(); it != itemEnd; ++it) { 1189 Component *component = static_cast<Component *>(ecSubcircuit->itemDocument()->itemWithID(it.key())); 1190 if (component) { 1191 component->setParentItem(ecSubcircuit); 1192 component->updateConnectorPoints(false); 1193 component->setVisible(false); 1194 component->setCanvas(nullptr); 1195 ecSubcircuit->connect(ecSubcircuit, &ECSubcircuit::subcircuitDeleted, component, &Component::removeItem); 1196 } 1197 } 1198 for (ConnectorDataMap::iterator it = m_connectorDataMap.begin(); it != connectorEnd; ++it) { 1199 Connector *connector = (static_cast<ICNDocument *>(ecSubcircuit->itemDocument()))->connectorWithID(it.key()); 1200 if (connector) { 1201 connector->updateConnectorPoints(false); 1202 connector->setVisible(false); 1203 connector->setCanvas(nullptr); 1204 ecSubcircuit->connect(ecSubcircuit, &ECSubcircuit::subcircuitDeleted, connector, &Connector::removeConnectorNoArg); 1205 } 1206 } 1207 const NodeDataMap::iterator nodeEnd = m_nodeDataMap.end(); 1208 for (NodeDataMap::iterator it = m_nodeDataMap.begin(); it != nodeEnd; ++it) { 1209 Node *node = (static_cast<ICNDocument *>(ecSubcircuit->itemDocument()))->nodeWithID(it.key()); 1210 if (node) { 1211 node->setVisible(false); 1212 node->setCanvas(nullptr); 1213 ecSubcircuit->connect(ecSubcircuit, &ECSubcircuit::subcircuitDeleted, node, qOverload<>(&Node::removeNode)); 1214 } 1215 } 1216 1217 ecSubcircuit->doneSCInit(); 1218 } 1219 // END class SubcircuitData