File indexing completed on 2024-06-16 08:27:17

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"