File indexing completed on 2024-05-19 09:39:43

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