File indexing completed on 2024-12-08 11:06:52
0001 // 0002 // C++ Implementation: flowicndocument 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 "flowicndocument.h" 0013 0014 #include "cnitemgroup.h" 0015 #include "connector.h" 0016 #include "conrouter.h" 0017 #include "flowcontainer.h" 0018 #include "fpnode.h" 0019 #include "item.h" 0020 #include "junctionflownode.h" 0021 #include "nodegroup.h" 0022 0023 #include <ktechlab_debug.h> 0024 0025 FlowICNDocument::FlowICNDocument(const QString &caption) 0026 : ICNDocument(caption) 0027 { 0028 } 0029 0030 FlowICNDocument::~FlowICNDocument() 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 FlowICNDocument::deleteAllNodes() 0051 { 0052 FPNodeMap nodesToDelete = m_flowNodeList; 0053 m_flowNodeList.clear(); 0054 qDeleteAll(nodesToDelete); 0055 } 0056 0057 bool FlowICNDocument::canConnect(KtlQCanvasItem *qcanvasItem1, KtlQCanvasItem *qcanvasItem2) const 0058 { 0059 // Rough outline of what can and can't connect: 0060 // * At most three connectors to a node 0061 // * Can't have connectors going between different levels (e.g. can't have 0062 // a connector coming outside a FlowContainer from inside). 0063 // * Can't have more than one route between any two nodes 0064 // * In all connections between nodes, must have at least one input and one 0065 // output node at the ends. 0066 0067 FPNode *startNode = dynamic_cast<FPNode *>(qcanvasItem1); 0068 FPNode *endNode = dynamic_cast<FPNode *>(qcanvasItem2); 0069 0070 if ((startNode && startNode->numCon(true, false) > 2) || (endNode && endNode->numCon(true, false) > 2)) 0071 return false; 0072 0073 Connector *startConnector = dynamic_cast<Connector *>(qcanvasItem1); 0074 Connector *endConnector = dynamic_cast<Connector *>(qcanvasItem2); 0075 0076 // Can't have I-junction in flowcode document 0077 if (startConnector && endConnector) 0078 return false; 0079 0080 // BEGIN Change connectors to nodes 0081 FPNode *startNode1 = nullptr; 0082 FPNode *startNode2 = nullptr; 0083 if (startConnector) { 0084 startNode1 = dynamic_cast<FPNode *>(startConnector->startNode()); 0085 startNode2 = dynamic_cast<FPNode *>(startConnector->endNode()); 0086 0087 if (!startNode1 || !startNode2) 0088 return false; 0089 } else if (!startNode) 0090 return false; 0091 0092 FPNode *endNode1 = nullptr; 0093 FPNode *endNode2 = nullptr; 0094 if (endConnector) { 0095 endNode1 = dynamic_cast<FPNode *>(endConnector->startNode()); 0096 endNode2 = dynamic_cast<FPNode *>(endConnector->endNode()); 0097 0098 if (!endNode1 || !endNode2) 0099 return false; 0100 } else if (!endNode) 0101 return false; 0102 0103 // END Change connectors to nodes 0104 0105 // BEGIN Check we have appropriate input and output allowance 0106 if (type() == Document::dt_flowcode) // this is obviouse 0107 { 0108 if (startNode1 && startNode2 && endNode1 && endNode2) { 0109 // Can't have I-configuration 0110 return false; 0111 } 0112 0113 if (startNode && endNode) { 0114 // Nice and easy straight line to check 0115 0116 if (!startNode->acceptInput() && !endNode->acceptInput()) 0117 return false; 0118 0119 if (!startNode->acceptOutput() && !endNode->acceptOutput()) 0120 return false; 0121 } 0122 0123 else { 0124 // We're in a T-configuration, we can only make this if the base of 0125 // the T is an output 0126 FPNode *base = startNode ? startNode : endNode; 0127 if (!base->acceptOutput()) 0128 return false; 0129 } 0130 } else 0131 qCCritical(KTL_LOG) << "BUG: document type is not dt_flowcode"; 0132 // END Check we have appropriate input and output allowance 0133 0134 return ICNDocument::canConnect(qcanvasItem1, qcanvasItem2); 0135 } 0136 0137 Connector *FlowICNDocument::createConnector(Connector *con1, Connector *con2, const QPoint & /*pos1*/, const QPoint & /*pos2*/, QPointList * /*pointList*/) 0138 { 0139 // FIXME isn't all this dead code? 0140 /* 0141 if ( !canConnect( con1, con2 ) ) 0142 return nullptr; 0143 0144 0145 const bool con1UsedManual = con1->usesManualPoints(); 0146 const bool con2UsedManual = con2->usesManualPoints(); 0147 0148 QList<QPointList> oldCon1Points = con1->splitConnectorPoints(pos1); 0149 QList<QPointList> oldCon2Points = con2->splitConnectorPoints(pos2); 0150 0151 0152 // FIXME dynamic_cast used because Connector doesn't know about FPNode 0153 0154 FPNode *node1a = dynamic_cast<FPNode*> ( con1->startNode() ); 0155 FPNode *node1b = dynamic_cast<FPNode*> ( con1->endNode() ); 0156 0157 FPNode *node2a = dynamic_cast<FPNode*> ( con2->startNode() ); 0158 FPNode *node2b = dynamic_cast<FPNode*> ( con2->endNode() ); 0159 0160 if ( !node1a || !node1b || !node2a || !node2b ) 0161 return nullptr; 0162 */ 0163 con1->hide(); 0164 con2->hide(); 0165 0166 // if ( type() != Document::dt_circuit ) 0167 return nullptr; 0168 } 0169 0170 Connector *FlowICNDocument::createConnector(Node *node, Connector *con, const QPoint &pos2, QPointList *pointList) 0171 { 0172 if (!canConnect(node, con)) 0173 return nullptr; 0174 0175 // FIXME dynamic_cast used, fix it in Connector class 0176 0177 FPNode *conStartNode = dynamic_cast<FPNode *>(con->startNode()); 0178 FPNode *conEndNode = dynamic_cast<FPNode *>(con->endNode()); 0179 0180 FPNode *fpNode = dynamic_cast<FPNode *>(node); 0181 0182 const bool usedManual = con->usesManualPoints(); 0183 0184 FPNode *newNode = new JunctionFlowNode(this, 0, pos2); 0185 0186 QPointList autoPoints; 0187 if (!pointList) { 0188 addAllItemConnectorPoints(); 0189 ConRouter cr(this); 0190 cr.mapRoute(int(node->x()), int(node->y()), pos2.x(), pos2.y()); 0191 autoPoints = cr.pointList(false); 0192 pointList = &autoPoints; 0193 } 0194 0195 QList<QPointList> oldConPoints = con->splitConnectorPoints(pos2); 0196 con->hide(); 0197 0198 // The actual new connector 0199 Connector *new1 = newNode->createInputConnector(node); 0200 fpNode->addOutputConnector(new1); 0201 new1->setRoutePoints(*pointList, usedManual); 0202 0203 // The two connectors formed from the original one when split 0204 Connector *new2 = newNode->createInputConnector(conStartNode); 0205 conStartNode->addOutputConnector(new2); 0206 new2->setRoutePoints(oldConPoints.at(0), usedManual); 0207 0208 Connector *new3 = conEndNode->createInputConnector(newNode); 0209 newNode->addOutputConnector(new3); 0210 new3->setRoutePoints(oldConPoints.at(1), usedManual); 0211 0212 // Avoid flicker: tell them to update their draw lists now 0213 con->updateConnectorPoints(false); 0214 new1->updateDrawList(); 0215 new2->updateDrawList(); 0216 new3->updateDrawList(); 0217 0218 // Now it's safe to remove the connector... 0219 con->removeConnectorNoArg(); 0220 flushDeleteList(); 0221 0222 deleteNodeGroup(conStartNode); 0223 deleteNodeGroup(conEndNode); 0224 createNodeGroup(newNode)->init(); 0225 0226 return new1; 0227 } 0228 0229 Connector *FlowICNDocument::createConnector(const QString &startNodeId, const QString &endNodeId, QPointList *pointList) 0230 { 0231 FPNode *startNode = getFPnodeWithID(startNodeId); 0232 FPNode *endNode = getFPnodeWithID(endNodeId); 0233 0234 if (!startNode || !endNode) { 0235 qCDebug(KTL_LOG) << "Either/both the connector start node and end node could not be found"; 0236 return nullptr; 0237 } 0238 0239 if (!canConnect(startNode, endNode)) 0240 return nullptr; 0241 0242 Connector *connector = endNode->createInputConnector(startNode); 0243 if (!connector) { 0244 qCCritical(KTL_LOG) << "End node did not create the connector"; 0245 return nullptr; 0246 } 0247 startNode->addOutputConnector(connector); 0248 flushDeleteList(); // Delete any connectors that might have been removed by the nodes 0249 0250 // Set the route to the manual created one if the user created such a route 0251 if (pointList) 0252 connector->setRoutePoints(*pointList, true); 0253 0254 // FIXME WTF is going on here? Redundant/meaningless code? 0255 ConnectorList connectorList; 0256 connectorList.append(connector); 0257 0258 setModified(true); 0259 0260 requestRerouteInvalidatedConnectors(); 0261 return connector; 0262 } 0263 0264 Node *FlowICNDocument::nodeWithID(const QString &id) 0265 { 0266 if (m_flowNodeList.contains(id)) 0267 return m_flowNodeList[id]; 0268 else 0269 return nullptr; 0270 } 0271 0272 FPNode *FlowICNDocument::getFPnodeWithID(const QString &id) 0273 { 0274 if (m_flowNodeList.contains(id)) 0275 return m_flowNodeList[id]; 0276 else 0277 return nullptr; 0278 } 0279 0280 void FlowICNDocument::slotAssignNodeGroups() 0281 { 0282 ICNDocument::slotAssignNodeGroups(); 0283 0284 const FPNodeMap::iterator end = m_flowNodeList.end(); 0285 for (FPNodeMap::iterator it = m_flowNodeList.begin(); it != end; ++it) { 0286 NodeGroup *ng = createNodeGroup(*it); 0287 if (ng) 0288 ng->init(); 0289 } 0290 0291 // We've destroyed the old node groups, so any collapsed flowcontainers 0292 // containing new node groups need to update them to make them invisible. 0293 const ItemMap::const_iterator itemListEnd = m_itemList.end(); 0294 for (ItemMap::const_iterator it = m_itemList.begin(); it != itemListEnd; ++it) { 0295 if (FlowContainer *fc = dynamic_cast<FlowContainer *>(*it)) 0296 fc->updateContainedVisibility(); 0297 } 0298 } 0299 0300 void FlowICNDocument::flushDeleteList() 0301 { 0302 // Remove duplicate items in the delete list 0303 KtlQCanvasItemList::iterator end = m_itemDeleteList.end(); 0304 for (KtlQCanvasItemList::iterator it = m_itemDeleteList.begin(); it != end; ++it) { 0305 if (*it && (m_itemDeleteList.count(*it) > 1)) { 0306 *it = nullptr; 0307 } 0308 } 0309 m_itemDeleteList.removeAll(nullptr); 0310 0311 end = m_itemDeleteList.end(); 0312 for (KtlQCanvasItemList::iterator it = m_itemDeleteList.begin(); it != end; ++it) { 0313 KtlQCanvasItem *qcanvasItem = *it; 0314 m_selectList->removeQCanvasItem(*it); 0315 0316 if (Item *item = dynamic_cast<Item *>(qcanvasItem)) 0317 m_itemList.remove(item->id()); 0318 0319 else if (FPNode *node = dynamic_cast<FPNode *>(qcanvasItem)) 0320 m_flowNodeList.remove(node->id()); 0321 0322 else if (Connector *con = dynamic_cast<Connector *>(qcanvasItem)) 0323 m_connectorList.removeAll(con); 0324 0325 else 0326 qCCritical(KTL_LOG) << "Unknown qcanvasItem! " << qcanvasItem; 0327 0328 qcanvasItem->setCanvas(nullptr); 0329 0330 delete qcanvasItem; 0331 *it = nullptr; 0332 } 0333 0334 // Check connectors for merging 0335 bool doneJoin = false; 0336 const FPNodeMap::iterator nlEnd = m_flowNodeList.end(); 0337 for (FPNodeMap::iterator it = m_flowNodeList.begin(); it != nlEnd; ++it) { 0338 (*it)->removeNullConnectors(); 0339 int conCount = (*it)->getAllConnectors().count(); 0340 if (conCount == 2 && !(*it)->parentItem()) { 0341 if (joinConnectors(*it)) 0342 doneJoin = true; 0343 } 0344 } 0345 0346 if (doneJoin) 0347 flushDeleteList(); 0348 0349 requestRerouteInvalidatedConnectors(); 0350 } 0351 0352 bool FlowICNDocument::registerItem(KtlQCanvasItem *qcanvasItem) 0353 { 0354 if (!qcanvasItem) 0355 return false; 0356 0357 if (!ItemDocument::registerItem(qcanvasItem)) { 0358 if (FPNode *node = dynamic_cast<FPNode *>(qcanvasItem)) { 0359 m_flowNodeList[node->id()] = node; 0360 emit nodeAdded(static_cast<Node *>(node)); 0361 } else if (Connector *connector = dynamic_cast<Connector *>(qcanvasItem)) { 0362 m_connectorList.append(connector); 0363 emit connectorAdded(connector); 0364 } else { 0365 qCCritical(KTL_LOG) << "Unrecognised item"; 0366 return false; 0367 } 0368 } 0369 0370 requestRerouteInvalidatedConnectors(); 0371 0372 return true; 0373 } 0374 0375 void FlowICNDocument::unregisterUID(const QString &uid) 0376 { 0377 m_flowNodeList.remove(uid); 0378 ICNDocument::unregisterUID(uid); 0379 } 0380 0381 NodeList FlowICNDocument::nodeList() const 0382 { 0383 NodeList l; 0384 0385 FPNodeMap::const_iterator end = m_flowNodeList.end(); 0386 for (FPNodeMap::const_iterator it = m_flowNodeList.begin(); it != end; ++it) 0387 l << it.value(); 0388 0389 return l; 0390 } 0391 0392 void FlowICNDocument::selectAllNodes() 0393 { 0394 const FPNodeMap::iterator nodeEnd = m_flowNodeList.end(); 0395 for (FPNodeMap::iterator nodeIt = m_flowNodeList.begin(); nodeIt != nodeEnd; ++nodeIt) { 0396 if (*nodeIt) 0397 select(*nodeIt); 0398 } 0399 } 0400 0401 bool FlowICNDocument::joinConnectors(FPNode *node) 0402 { 0403 // We don't want to destroy the node if it has a parent 0404 if (node->parentItem()) 0405 return false; 0406 0407 node->removeNullConnectors(); 0408 0409 int conCount = node->getAllConnectors().count(); 0410 if (conCount != 2) 0411 return false; 0412 0413 Connector *con1, *con2; 0414 Node *startNode, *endNode; 0415 QPointList conPoints; 0416 0417 if (node->inputConnectorList().count() == 0) { 0418 // Both connectors emerge from node - output - i.e. node is pure start node 0419 con1 = node->outputConnectorList().at(0).data(); 0420 con2 = node->outputConnectorList().at(1).data(); 0421 if (con1 == con2) { 0422 return false; 0423 } 0424 0425 startNode = con1->endNode(); 0426 endNode = con2->endNode(); 0427 conPoints = con1->connectorPoints(true) + con2->connectorPoints(false); 0428 } else if (node->inputConnectorList().count() == 1) { 0429 // Ont input, one output 0430 con1 = node->inputConnectorList().at(0).data(); 0431 con2 = node->outputConnectorList().at(0).data(); 0432 if (con1 == con2) { 0433 return false; 0434 } 0435 0436 startNode = con1->startNode(); 0437 endNode = con2->endNode(); 0438 conPoints = con1->connectorPoints(false) + con2->connectorPoints(false); 0439 } else { 0440 // Both input - i.e. node is pure end node 0441 con1 = node->inputConnectorList().at(0).data(); 0442 con2 = node->inputConnectorList().at(1).data(); 0443 if (con1 == con2) { 0444 return false; 0445 } 0446 0447 startNode = con1->startNode(); 0448 endNode = con2->startNode(); 0449 conPoints = con1->connectorPoints(false) + con2->connectorPoints(true); 0450 } 0451 0452 if (!startNode || !endNode) 0453 return false; 0454 0455 // HACK // FIXME // dynamic_cast used 0456 FPNode *startFpNode, *endFpNode; 0457 startFpNode = dynamic_cast<FPNode *>(startNode); 0458 endFpNode = dynamic_cast<FPNode *>(endNode); 0459 0460 Connector *newCon = endFpNode->createInputConnector(startFpNode); 0461 if (!newCon) 0462 return false; 0463 0464 startFpNode->addOutputConnector(newCon); 0465 newCon->setRoutePoints(conPoints, con1->usesManualPoints() || con2->usesManualPoints()); 0466 0467 // Avoid flicker: update draw lists now 0468 con1->updateConnectorPoints(false); 0469 con2->updateConnectorPoints(false); 0470 newCon->updateDrawList(); 0471 0472 node->removeNode(); 0473 con1->removeConnectorNoArg(); 0474 con2->removeConnectorNoArg(); 0475 0476 return true; 0477 } 0478 0479 #include "moc_flowicndocument.cpp"