File indexing completed on 2024-04-21 05:43:33
0001 // 0002 // C++ Implementation: circuiticndocument 0003 // 0004 // Description: 0005 // 0006 // 0007 // Author: Zoltan P <zoltan.padrah@gmail.com>, (C) 2008 0008 // 0009 // Copyright: See COPYING file that comes with this distribution 0010 // 0011 // 0012 #include "circuiticndocument.h" 0013 0014 #include "cnitemgroup.h" 0015 #include "connector.h" 0016 #include "conrouter.h" 0017 #include "ecnode.h" 0018 #include "flowcontainer.h" 0019 #include "item.h" 0020 #include "junctionnode.h" 0021 #include "nodegroup.h" 0022 0023 #include <ktechlab_debug.h> 0024 0025 CircuitICNDocument::CircuitICNDocument(const QString &caption) 0026 : ICNDocument(caption) 0027 { 0028 } 0029 0030 CircuitICNDocument::~CircuitICNDocument() 0031 { 0032 // Go to hell, KtlQCanvas. I'm in charge of what gets deleted. 0033 KtlQCanvasItemList all = m_canvas->allItems(); 0034 const KtlQCanvasItemList::Iterator end = all.end(); 0035 for (KtlQCanvasItemList::Iterator it = all.begin(); it != end; ++it) 0036 (*it)->setCanvas(nullptr); 0037 0038 // Remove all items from the canvas 0039 selectAll(); 0040 deleteSelection(); 0041 0042 // Delete anything that got through the above couple of lines 0043 ConnectorList connectorsToDelete = m_connectorList; 0044 connectorsToDelete.clear(); 0045 qDeleteAll(connectorsToDelete); 0046 0047 deleteAllNodes(); 0048 } 0049 0050 void CircuitICNDocument::deleteAllNodes() 0051 { 0052 const ECNodeMap::iterator nodeListEnd = m_ecNodeList.end(); 0053 for (ECNodeMap::iterator it = m_ecNodeList.begin(); it != nodeListEnd; ++it) { 0054 qCDebug(KTL_LOG) << "CircuitICNDocument::deleteAllNodes removing [" << it.key() << "] " << it.value(); 0055 // delete *it; // 2015.07.31 - this will not work 0056 0057 // prevent crash on node destructor 0058 { 0059 ECNode *ecNode = it.value(); 0060 // unregisterUID( ecNode->id() ); // do not use, will modify m_ecNodeList 0061 ICNDocument::unregisterUID(ecNode->id()); 0062 ecNode->setICNDocument(nullptr); 0063 } 0064 0065 it.value()->deleteLater(); 0066 } 0067 0068 m_ecNodeList.clear(); 0069 } 0070 0071 bool CircuitICNDocument::canConnect(KtlQCanvasItem *qcanvasItem1, KtlQCanvasItem *qcanvasItem2) const 0072 { 0073 // Rough outline of what can and can't connect: 0074 // * At most three connectors to a node 0075 // * Can't have connectors going between different levels (e.g. can't have 0076 // a connector coming outside a FlowContainer from inside). 0077 // * Can't have more than one route between any two nodes 0078 // * In all connections between nodes, must have at least one input and one 0079 // output node at the ends. 0080 0081 // nothing special in a circuit; we can connect almost anything 0082 return ICNDocument::canConnect(qcanvasItem1, qcanvasItem2); 0083 } 0084 0085 Connector *CircuitICNDocument::createConnector(Node *node, Connector *con, const QPoint &pos2, QPointList *pointList) 0086 { 0087 if (!canConnect(node, con)) 0088 return nullptr; 0089 0090 // FIXME dynamic_cast used, fix it in Connector class 0091 0092 ECNode *conStartNode = dynamic_cast<ECNode *>(con->startNode()); 0093 ECNode *conEndNode = dynamic_cast<ECNode *>(con->endNode()); 0094 ECNode *ecNode = dynamic_cast<ECNode *>(node); 0095 0096 const bool usedManual = con->usesManualPoints(); 0097 0098 ECNode *newNode = new JunctionNode(this, 0, pos2); 0099 0100 QPointList autoPoints; 0101 if (!pointList) { 0102 addAllItemConnectorPoints(); 0103 ConRouter cr(this); 0104 cr.mapRoute(int(node->x()), int(node->y()), pos2.x(), pos2.y()); 0105 autoPoints = cr.pointList(false); 0106 pointList = &autoPoints; 0107 } 0108 0109 QList<QPointList> oldConPoints = con->splitConnectorPoints(pos2); 0110 con->hide(); 0111 0112 // The actual new connector 0113 Connector *new1 = newNode->createConnector(node); 0114 ecNode->addConnector(new1); 0115 new1->setRoutePoints(*pointList, usedManual); 0116 0117 // The two connectors formed from the original one when split 0118 Connector *new2 = newNode->createConnector(conStartNode); 0119 conStartNode->addConnector(new2); 0120 new2->setRoutePoints(oldConPoints.at(0), usedManual); 0121 0122 Connector *new3 = conEndNode->createConnector(newNode); 0123 newNode->addConnector(new3); 0124 new3->setRoutePoints(oldConPoints.at(1), usedManual); 0125 0126 // Avoid flicker: tell them to update their draw lists now 0127 con->updateConnectorPoints(false); 0128 new1->updateDrawList(); 0129 new2->updateDrawList(); 0130 new3->updateDrawList(); 0131 0132 // Now it's safe to remove the connector... 0133 con->removeConnectorNoArg(); 0134 flushDeleteList(); 0135 0136 deleteNodeGroup(conStartNode); 0137 deleteNodeGroup(conEndNode); 0138 createNodeGroup(newNode)->init(); 0139 0140 return new1; 0141 } 0142 0143 Connector *CircuitICNDocument::createConnector(Connector *con1, Connector *con2, const QPoint &pos1, const QPoint &pos2, QPointList *pointList) 0144 { 0145 if (!canConnect(con1, con2)) 0146 return nullptr; 0147 0148 const bool con1UsedManual = con1->usesManualPoints(); 0149 const bool con2UsedManual = con2->usesManualPoints(); 0150 0151 QList<QPointList> oldCon1Points = con1->splitConnectorPoints(pos1); 0152 QList<QPointList> oldCon2Points = con2->splitConnectorPoints(pos2); 0153 0154 ECNode *node1a = dynamic_cast<ECNode *>(con1->startNode()); 0155 ECNode *node1b = dynamic_cast<ECNode *>(con1->endNode()); 0156 0157 ECNode *node2a = dynamic_cast<ECNode *>(con2->startNode()); 0158 ECNode *node2b = dynamic_cast<ECNode *>(con2->endNode()); 0159 0160 if (!node1a || !node1b || !node2a || !node2b) 0161 return nullptr; 0162 0163 con1->hide(); 0164 con2->hide(); 0165 0166 // from this point forward, we are dealing with a circuit document -> all nodes are electronic 0167 0168 ECNode *newNode1 = new JunctionNode(this, 0, pos1); 0169 ECNode *newNode2 = new JunctionNode(this, 0, pos2); 0170 0171 Connector *con1a = newNode1->createConnector(node1a); 0172 node1a->addConnector(con1a); 0173 0174 Connector *con1b = newNode1->createConnector(node1b); 0175 node1b->addConnector(con1b); 0176 0177 Connector *newCon = newNode1->createConnector(newNode2); 0178 newNode2->addConnector(newCon); 0179 0180 Connector *con2a = node2a->createConnector(newNode2); 0181 newNode2->addConnector(con2a); 0182 0183 Connector *con2b = node2b->createConnector(newNode2); 0184 newNode2->addConnector(con2b); 0185 0186 if (!con1a || !con1b || !con2a || !con2b) { 0187 // This should never happen, as the canConnect function should strictly 0188 // determine whether the connectors could be created before hand. 0189 qCWarning(KTL_LOG) << "Not all the connectors were created, this should never happen"; 0190 0191 if (con1a) 0192 con1a->removeConnectorNoArg(); 0193 if (con1b) 0194 con1b->removeConnectorNoArg(); 0195 if (con2a) 0196 con2a->removeConnectorNoArg(); 0197 if (con2b) 0198 con2b->removeConnectorNoArg(); 0199 0200 newNode1->removeNode(); 0201 newNode2->removeNode(); 0202 0203 flushDeleteList(); 0204 return nullptr; 0205 } 0206 0207 con1a->setRoutePoints(oldCon1Points.at(0), con1UsedManual); 0208 con1b->setRoutePoints(oldCon1Points.at(1), con1UsedManual); 0209 0210 con2a->setRoutePoints(oldCon2Points.at(0), con2UsedManual); 0211 con2b->setRoutePoints(oldCon2Points.at(1), con2UsedManual); 0212 0213 QPointList autoPoints; 0214 0215 if (!pointList) { 0216 addAllItemConnectorPoints(); 0217 ConRouter cr(this); 0218 cr.mapRoute(pos1.x(), pos1.y(), pos2.x(), pos2.y()); 0219 autoPoints = cr.pointList(false); 0220 pointList = &autoPoints; 0221 } 0222 0223 newCon->setRoutePoints(*pointList, true); 0224 0225 // Avoid flicker: tell them to update their draw lists now 0226 con1->updateConnectorPoints(false); 0227 con2->updateConnectorPoints(false); 0228 newCon->updateDrawList(); 0229 con1a->updateDrawList(); 0230 con1b->updateDrawList(); 0231 con2a->updateDrawList(); 0232 con2b->updateDrawList(); 0233 0234 // Now it's safe to remove the connectors 0235 con1->removeConnectorNoArg(); 0236 con2->removeConnectorNoArg(); 0237 0238 flushDeleteList(); 0239 0240 deleteNodeGroup(node1a); 0241 deleteNodeGroup(node1b); 0242 deleteNodeGroup(node2a); 0243 deleteNodeGroup(node2b); 0244 0245 NodeGroup *ng = createNodeGroup(newNode1); 0246 0247 ng->addNode(newNode2, true); 0248 ng->init(); 0249 0250 return newCon; 0251 } 0252 0253 Connector *CircuitICNDocument::createConnector(const QString &startNodeId, const QString &endNodeId, QPointList *pointList) 0254 { 0255 ECNode *startNode = getEcNodeWithID(startNodeId); 0256 ECNode *endNode = getEcNodeWithID(endNodeId); 0257 0258 if (!startNode || !endNode) { 0259 qCDebug(KTL_LOG) << "Either/both the connector start node and end node could not be found"; 0260 return nullptr; 0261 } 0262 0263 if (!canConnect(startNode, endNode)) 0264 return nullptr; 0265 0266 Connector *connector = endNode->createConnector(startNode); 0267 if (!connector) { 0268 qCCritical(KTL_LOG) << "End node did not create the connector"; 0269 return nullptr; 0270 } 0271 0272 startNode->addConnector(connector); 0273 flushDeleteList(); // Delete any connectors that might have been removed by the nodes 0274 0275 // Set the route to the manual created one if the user created such a route 0276 if (pointList) 0277 connector->setRoutePoints(*pointList, true); 0278 0279 // FIXME WTF is going on here? Redundant/meaningless code? 0280 // ConnectorList connectorList; 0281 // connectorList.append(connector); 0282 0283 setModified(true); 0284 0285 requestRerouteInvalidatedConnectors(); 0286 return connector; 0287 } 0288 0289 Node *CircuitICNDocument::nodeWithID(const QString &id) 0290 { 0291 if (m_ecNodeList.contains(id)) 0292 return m_ecNodeList[id]; 0293 else 0294 return nullptr; 0295 } 0296 0297 ECNode *CircuitICNDocument::getEcNodeWithID(const QString &id) 0298 { 0299 if (m_ecNodeList.contains(id)) 0300 return m_ecNodeList[id]; 0301 else 0302 return nullptr; 0303 } 0304 0305 void CircuitICNDocument::slotAssignNodeGroups() 0306 { 0307 ICNDocument::slotAssignNodeGroups(); 0308 0309 const ECNodeMap::iterator end = m_ecNodeList.end(); 0310 for (ECNodeMap::iterator it = m_ecNodeList.begin(); it != end; ++it) { 0311 NodeGroup *ng = createNodeGroup(*it); 0312 if (ng) 0313 ng->init(); 0314 } 0315 0316 // We've destroyed the old node groups, so any collapsed flowcontainers 0317 // containing new node groups need to update them to make them invisible. 0318 const ItemMap::const_iterator itemListEnd = m_itemList.end(); 0319 for (ItemMap::const_iterator it = m_itemList.begin(); it != itemListEnd; ++it) { 0320 if (FlowContainer *fc = dynamic_cast<FlowContainer *>(*it)) 0321 fc->updateContainedVisibility(); 0322 } 0323 } 0324 0325 void CircuitICNDocument::flushDeleteList() 0326 { 0327 // Remove duplicate items in the delete list 0328 KtlQCanvasItemList::iterator end = m_itemDeleteList.end(); 0329 for (KtlQCanvasItemList::iterator it = m_itemDeleteList.begin(); it != end; ++it) { 0330 if (*it && (m_itemDeleteList.count(*it) > 1)) { 0331 *it = nullptr; 0332 } 0333 } 0334 0335 m_itemDeleteList.removeAll(nullptr); 0336 0337 /* again we're spending time to figure out what special method to call instead of a generic call..*/ 0338 end = m_itemDeleteList.end(); 0339 for (KtlQCanvasItemList::iterator it = m_itemDeleteList.begin(); it != end; ++it) { 0340 KtlQCanvasItem *qcanvasItem = *it; 0341 m_selectList->removeQCanvasItem(*it); 0342 0343 if (Item *item = dynamic_cast<Item *>(qcanvasItem)) 0344 m_itemList.remove(item->id()); 0345 else if (ECNode *node = dynamic_cast<ECNode *>(qcanvasItem)) 0346 m_ecNodeList.remove(node->id()); 0347 else if (Connector *con = dynamic_cast<Connector *>(qcanvasItem)) 0348 m_connectorList.removeAll(con); 0349 else 0350 qCCritical(KTL_LOG) << "Unknown qcanvasItem! " << qcanvasItem; 0351 0352 qcanvasItem->setCanvas(nullptr); 0353 0354 delete qcanvasItem; 0355 *it = nullptr; 0356 } 0357 0358 // // Check connectors for merging 0359 bool doneJoin = false; 0360 const ECNodeMap::iterator nlEnd = m_ecNodeList.end(); 0361 for (ECNodeMap::iterator it = m_ecNodeList.begin(); it != nlEnd; ++it) { 0362 (*it)->removeNullConnectors(); 0363 int conCount = (*it)->connectorList().count(); 0364 if (conCount == 2 && !(*it)->parentItem()) { 0365 if (joinConnectors(*it)) 0366 doneJoin = true; 0367 } 0368 } 0369 0370 if (doneJoin) 0371 flushDeleteList(); 0372 0373 requestRerouteInvalidatedConnectors(); 0374 } 0375 0376 bool CircuitICNDocument::registerItem(KtlQCanvasItem *qcanvasItem) 0377 { 0378 if (!qcanvasItem) 0379 return false; 0380 0381 if (!ItemDocument::registerItem(qcanvasItem)) { 0382 if (ECNode *node = dynamic_cast<ECNode *>(qcanvasItem)) { 0383 m_ecNodeList[node->id()] = node; 0384 emit nodeAdded(static_cast<Node *>(node)); 0385 } else if (Connector *connector = dynamic_cast<Connector *>(qcanvasItem)) { 0386 m_connectorList.append(connector); 0387 emit connectorAdded(connector); 0388 } else { 0389 qCCritical(KTL_LOG) << "Unrecognised item"; 0390 return false; 0391 } 0392 } 0393 0394 requestRerouteInvalidatedConnectors(); 0395 0396 return true; 0397 } 0398 0399 void CircuitICNDocument::unregisterUID(const QString &uid) 0400 { 0401 m_ecNodeList.remove(uid); 0402 ICNDocument::unregisterUID(uid); 0403 } 0404 0405 /* this method smells a little funny */ 0406 NodeList CircuitICNDocument::nodeList() const 0407 { 0408 NodeList l; 0409 0410 ECNodeMap::const_iterator end = m_ecNodeList.end(); 0411 for (ECNodeMap::const_iterator it = m_ecNodeList.begin(); it != end; ++it) 0412 l << it.value(); 0413 0414 return l; 0415 } 0416 0417 void CircuitICNDocument::selectAllNodes() 0418 { 0419 const ECNodeMap::iterator nodeEnd = m_ecNodeList.end(); 0420 for (ECNodeMap::iterator nodeIt = m_ecNodeList.begin(); nodeIt != nodeEnd; ++nodeIt) { 0421 if (*nodeIt) 0422 select(*nodeIt); 0423 } 0424 } 0425 0426 bool CircuitICNDocument::joinConnectors(ECNode *node) 0427 { 0428 // We don't want to destroy the node if it has a parent 0429 if (node->parentItem()) 0430 return false; 0431 0432 node->removeNullConnectors(); 0433 0434 // an electronic node can be removed if it has exactly 2 connectors connected to it 0435 0436 int conCount = node->getAllConnectors().count(); 0437 if (conCount != 2) 0438 return false; 0439 0440 Connector *con1, *con2; 0441 ECNode *startNode = nullptr; 0442 ECNode *endNode = nullptr; 0443 QPointList conPoints; 0444 0445 con1 = node->connectorList().at(0).data(); 0446 con2 = node->connectorList().at(1).data(); 0447 0448 if (con1 == con2) 0449 return false; 0450 0451 // we don't know on which end of the connectors is our node, so we must check both ends 0452 // HACK // TODO // dynamic_cast used, because Connector doesn't know about ECNode, only Node 0453 if (con1->startNode() == node) 0454 startNode = dynamic_cast<ECNode *>(con1->endNode()); 0455 else 0456 startNode = dynamic_cast<ECNode *>(con1->startNode()); 0457 0458 if (con2->startNode() == node) 0459 endNode = dynamic_cast<ECNode *>(con2->endNode()); 0460 else 0461 endNode = dynamic_cast<ECNode *>(con2->startNode()); 0462 0463 conPoints = con1->connectorPoints(false) + con2->connectorPoints(false); 0464 0465 if (!startNode || !endNode) 0466 return false; 0467 0468 Connector *newCon = endNode->createConnector(startNode); 0469 0470 if (!newCon) 0471 return false; 0472 0473 startNode->addConnector(newCon); 0474 newCon->setRoutePoints(conPoints, con1->usesManualPoints() || con2->usesManualPoints()); 0475 0476 // Avoid flicker: update draw lists now 0477 con1->updateConnectorPoints(false); 0478 con2->updateConnectorPoints(false); 0479 newCon->updateDrawList(); 0480 0481 node->removeNode(); 0482 con1->removeConnectorNoArg(); 0483 con2->removeConnectorNoArg(); 0484 0485 return true; 0486 } 0487 0488 #include "moc_circuiticndocument.cpp"