Warning, file /sdk/ktechlab/src/connector.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /*************************************************************************** 0002 * Copyright (C) 2003-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 "connector.h" 0012 #include "circuitdocument.h" 0013 #include "cnitem.h" 0014 #include "component.h" 0015 #include "conrouter.h" 0016 #include "ecnode.h" 0017 #include "itemdocumentdata.h" 0018 #include "junctionnode.h" 0019 #include "utils.h" 0020 #include "wire.h" 0021 0022 #include <QPainter> 0023 0024 #include <cmath> 0025 #include <cstdlib> 0026 0027 #include <ktlconfig.h> 0028 #include <ktechlab_debug.h> 0029 0030 // BEGIN class Connector 0031 Connector::Connector(Node * /*startNode*/, Node * /*endNode*/, ICNDocument *icnDocument, QString *id) 0032 : // QObject(icnDocument), 0033 KtlQCanvasPolygon(icnDocument->canvas()) 0034 { 0035 QString name("Connector"); 0036 if (id) { 0037 name.append(QString("-%1").arg(*id)); 0038 } else { 0039 name.append("-Unknown"); 0040 } 0041 setObjectName(name.toLatin1().data()); 0042 qCDebug(KTL_LOG) << " this=" << this; 0043 0044 m_currentAnimationOffset = 0.0; 0045 p_parentContainer = nullptr; 0046 p_nodeGroup = nullptr; 0047 b_semiHidden = false; 0048 b_deleted = false; 0049 b_pointsAdded = false; 0050 b_manualPoints = false; 0051 p_icnDocument = icnDocument; 0052 m_conRouter = new ConRouter(p_icnDocument); 0053 0054 if (id) { 0055 m_id = *id; 0056 if (!p_icnDocument->registerUID(*id)) { 0057 qCDebug(KTL_LOG) << "Connector attempted to register given ID, but ID already in use: " << *id; 0058 m_id = p_icnDocument->generateUID(*id); 0059 qCDebug(KTL_LOG) << "Creating a new one: " << m_id; 0060 } 0061 } else 0062 m_id = p_icnDocument->generateUID("connector"); 0063 0064 p_icnDocument->registerItem(this); 0065 p_icnDocument->requestRerouteInvalidatedConnectors(); 0066 0067 setVisible(true); 0068 } 0069 0070 Connector::~Connector() 0071 { 0072 p_icnDocument->unregisterUID(id()); 0073 0074 delete m_conRouter; 0075 0076 qDeleteAll(m_wires); 0077 0078 // m_wires.resize(0); 0079 } 0080 0081 void Connector::setParentContainer(const QString &cnItemId) 0082 { 0083 // // We only allow the node to be parented once 0084 // if ( p_parentContainer || !ICNDocument->itemWithID(cnItemId) ) return; 0085 p_parentContainer = p_icnDocument->cnItemWithID(cnItemId); 0086 } 0087 0088 void Connector::removeConnector(Node *) 0089 { 0090 if (b_deleted) 0091 return; 0092 0093 b_deleted = true; 0094 0095 // Remove 'penalty' points for this connector from the ICNDocument 0096 updateConnectorPoints(false); 0097 0098 emit selected(false); 0099 emit removed(this); 0100 0101 if (startNode()) 0102 startNode()->removeConnector(this); 0103 if (endNode()) 0104 endNode()->removeConnector(this); 0105 0106 p_icnDocument->appendDeleteList(this); 0107 } 0108 0109 int getSlope(float x1, float y1, float x2, float y2) 0110 { 0111 enum slope { 0112 s_n = 0, // . 0113 s_v, // | 0114 s_h, // - 0115 s_s, // / 0116 s_d // \ (backwards slash) 0117 }; 0118 0119 if (x1 == x2) { 0120 if (y1 == y2) 0121 return s_n; 0122 return s_v; 0123 } else if (y1 == y2) { 0124 return s_h; 0125 } else if ((y2 - y1) / (x2 - x1) > 0) { 0126 return s_s; 0127 } else 0128 return s_d; 0129 } 0130 0131 void Connector::updateDrawList() 0132 { 0133 if (!startNode() || !endNode() || !canvas()) 0134 return; 0135 0136 QPointList drawLineList; 0137 0138 int prevX = (*m_conRouter->cellPointList()->begin()).x(); 0139 int prevY = (*m_conRouter->cellPointList()->begin()).y(); 0140 0141 int prevX_canvas = toCanvas(prevX); 0142 int prevY_canvas = toCanvas(prevY); 0143 0144 Cells *cells = p_icnDocument->cells(); 0145 0146 bool bumpNow = false; 0147 for (QPoint p : *m_conRouter->cellPointList()) { 0148 const int x = p.x(); 0149 const int y = p.y(); 0150 0151 const int numCon = cells->haveCell(x, y) ? cells->cell(x, y).numCon : 0; 0152 0153 const int y_canvas = toCanvas(y); 0154 const int x_canvas = toCanvas(x); 0155 0156 const bool bumpNext = (prevX == x && numCon > 1 && std::abs(y_canvas - startNode()->y()) > 8 && std::abs(y_canvas - endNode()->y()) > 8); 0157 0158 int x0 = prevX_canvas; 0159 int x2 = x_canvas; 0160 int x1 = (x0 + x2) / 2; 0161 0162 int y0 = prevY_canvas; 0163 int y3 = y_canvas; 0164 int y1 = (y0 == y3) ? y0 : ((y0 < y3) ? y0 + 3 : y0 - 3); 0165 int y2 = (y0 == y3) ? y3 : ((y0 < y3) ? y3 - 3 : y3 + 3); 0166 0167 if (bumpNow) 0168 x0 += 3; 0169 if (bumpNext) 0170 x2 += 3; 0171 0172 if (!bumpNow && !bumpNext) { 0173 drawLineList += QPoint(x0, y0); 0174 drawLineList += QPoint(x2, y3); 0175 } else if (bumpNow) { 0176 drawLineList += QPoint(x0, y0); 0177 drawLineList += QPoint(x1, y1); 0178 drawLineList += QPoint(x2, y3); 0179 } else if (bumpNext) { 0180 drawLineList += QPoint(x0, y0); 0181 drawLineList += QPoint(x1, y2); 0182 drawLineList += QPoint(x2, y3); 0183 } else { 0184 drawLineList += QPoint(x0, y0); 0185 drawLineList += QPoint(x1, y1); 0186 drawLineList += QPoint(x1, y2); 0187 drawLineList += QPoint(x2, y3); 0188 } 0189 0190 prevX = x; 0191 prevY = y; 0192 0193 prevY_canvas = y_canvas; 0194 prevX_canvas = x_canvas; 0195 bumpNow = bumpNext; 0196 } 0197 0198 // Now, remove redundant points (i.e. those that are either repeated or are 0199 // in the same direction as the previous points) 0200 0201 if (drawLineList.size() < 3) 0202 return; 0203 0204 const QPointList::iterator dllEnd = drawLineList.end(); 0205 0206 QPointList::iterator previous = drawLineList.begin(); 0207 QPointList::iterator current = previous; 0208 current++; 0209 QPointList::const_iterator next = current; 0210 next++; 0211 0212 int invalid = -(1 << 30); 0213 0214 while (previous != dllEnd && current != dllEnd && next != dllEnd) { 0215 const int slope1 = getSlope((*previous).x(), (*previous).y(), (*current).x(), (*current).y()); 0216 const int slope2 = getSlope((*current).x(), (*current).y(), (*next).x(), (*next).y()); 0217 0218 if (slope1 == slope2 || slope1 == 0 || slope2 == 0) { 0219 *current = QPoint(invalid, invalid); 0220 } else 0221 previous = current; 0222 0223 current++; 0224 next++; 0225 } 0226 0227 drawLineList.removeAll(QPoint(invalid, invalid)); 0228 0229 // Find the bounding rect 0230 { 0231 int x1 = invalid, y1 = invalid, x2 = invalid, y2 = invalid; 0232 0233 for (const QPoint p : drawLineList) { 0234 if (p.x() < x1 || x1 == invalid) 0235 x1 = p.x(); 0236 if (p.x() > x2 || x2 == invalid) 0237 x2 = p.x(); 0238 0239 if (p.y() < y1 || y1 == invalid) 0240 y1 = p.y(); 0241 if (p.y() > y2 || y2 == invalid) 0242 y2 = p.y(); 0243 } 0244 0245 QRect boundRect(x1, y1, x2 - x1, y2 - y1); 0246 0247 if (boundRect != m_oldBoundRect) { 0248 canvas()->setChanged(boundRect | m_oldBoundRect); 0249 m_oldBoundRect = boundRect; 0250 } 0251 } 0252 0253 // BEGIN build up ConnectorLine list 0254 qDeleteAll(m_connectorLineList); 0255 m_connectorLineList.clear(); 0256 0257 if (drawLineList.size() > 1) { 0258 QPoint prev = drawLineList.first(); 0259 int pixelOffset = 0; 0260 0261 for (QPoint next : drawLineList) { 0262 ConnectorLine *line = new ConnectorLine(this, pixelOffset); 0263 m_connectorLineList.append(line); 0264 0265 line->setPoints(prev.x(), prev.y(), next.x(), next.y()); 0266 0267 // (note that only one of the following QABS will be non-zero) 0268 pixelOffset += abs(prev.x() - next.x()) + abs(prev.y() - next.y()); 0269 0270 prev = next; 0271 } 0272 } 0273 0274 updateConnectorLines(); 0275 0276 // END build up ConnectorPoint list 0277 } 0278 0279 void Connector::setSemiHidden(bool semiHidden) 0280 { 0281 if (!canvas() || semiHidden == b_semiHidden) 0282 return; 0283 0284 b_semiHidden = semiHidden; 0285 updateConnectorLines(); 0286 } 0287 0288 void Connector::updateConnectorPoints(bool add) 0289 { 0290 if (!canvas()) 0291 return; 0292 0293 if (b_deleted || !isVisible()) 0294 add = false; 0295 0296 // Check we haven't already added/removed the points... 0297 if (b_pointsAdded == add) 0298 return; 0299 0300 b_pointsAdded = add; 0301 0302 // We don't include the end points in the mapping 0303 if (m_conRouter->cellPointList()->size() < 3) 0304 return; 0305 0306 Cells *cells = p_icnDocument->cells(); 0307 0308 const int mult = (add) ? 1 : -1; 0309 for (QPoint p : *m_conRouter->cellPointList()) { 0310 int x = p.x(); 0311 int y = p.y(); 0312 0313 // Add the points of this connector to the cell array in the ICNDocument, 0314 // so that other connectors still to calculate their points know to try 0315 // and avoid this connector 0316 0317 p_icnDocument->addCPenalty(x, y - 1, mult * ICNDocument::hs_connector / 2); 0318 p_icnDocument->addCPenalty(x - 1, y, mult * ICNDocument::hs_connector / 2); 0319 p_icnDocument->addCPenalty(x, y, mult * ICNDocument::hs_connector); 0320 p_icnDocument->addCPenalty(x + 1, y, mult * ICNDocument::hs_connector / 2); 0321 p_icnDocument->addCPenalty(x, y + 1, mult * ICNDocument::hs_connector / 2); 0322 0323 if (cells->haveCell(x, y)) 0324 cells->cell(x, y).numCon += mult; 0325 } 0326 0327 // updateDrawList(); 0328 } 0329 0330 void Connector::setRoutePoints(QPointList pointList, bool setManual, bool checkEndPoints) 0331 { 0332 if (!canvas()) 0333 return; 0334 0335 updateConnectorPoints(false); 0336 0337 bool reversed = pointsAreReverse(pointList); 0338 0339 // a little performance boost: don't call (start|end)Node 4 times 0340 Node *l_endNode = endNode(); 0341 Node *l_startNode = startNode(); 0342 0343 if (checkEndPoints) { 0344 if (reversed) { 0345 pointList.prepend(QPoint(int(l_endNode->x()), int(l_endNode->y()))); 0346 pointList.append(QPoint(int(l_startNode->x()), int(l_startNode->y()))); 0347 } else { 0348 pointList.prepend(QPoint(int(l_startNode->x()), int(l_startNode->y()))); 0349 pointList.append(QPoint(int(l_endNode->x()), int(l_endNode->y()))); 0350 } 0351 } 0352 0353 m_conRouter->setPoints(pointList, reversed); 0354 0355 b_manualPoints = setManual; 0356 updateConnectorPoints(true); 0357 } 0358 0359 bool Connector::pointsAreReverse(const QPointList &pointList) const 0360 { 0361 if (!startNode() || !endNode()) { 0362 qCWarning(KTL_LOG) << "Cannot determine orientation as no start and end nodes"; 0363 return false; 0364 } 0365 0366 if (pointList.isEmpty()) 0367 return false; 0368 0369 int plsx = pointList.first().x(); 0370 int plsy = pointList.first().y(); 0371 int plex = pointList.last().x(); 0372 int pley = pointList.last().y(); 0373 0374 double nsx = startNode()->x(); 0375 double nsy = startNode()->y(); 0376 double nex = endNode()->x(); 0377 double ney = endNode()->y(); 0378 0379 double dist_normal = (nsx - plsx) * (nsx - plsx) + (nsy - plsy) * (nsy - plsy) + (nex - plex) * (nex - plex) + (ney - pley) * (ney - pley); 0380 0381 double dist_reverse = (nsx - plex) * (nsx - plex) + (nsy - pley) * (nsy - pley) + (nex - plsx) * (nex - plsx) + (ney - plsy) * (ney - plsy); 0382 0383 return dist_reverse < dist_normal; 0384 } 0385 0386 void Connector::rerouteConnector() 0387 { 0388 if (!isVisible()) 0389 return; 0390 0391 if (nodeGroup()) { 0392 qCWarning(KTL_LOG) << "Connector is controlled by a NodeGroup! Use that to reroute the connector"; 0393 return; 0394 } 0395 0396 if (!startNode() || !endNode()) 0397 return; 0398 0399 updateConnectorPoints(false); 0400 0401 m_conRouter->mapRoute(int(startNode()->x()), int(startNode()->y()), int(endNode()->x()), int(endNode()->y())); 0402 0403 b_manualPoints = false; 0404 updateConnectorPoints(true); 0405 } 0406 0407 void Connector::translateRoute(int dx, int dy) 0408 { 0409 updateConnectorPoints(false); 0410 m_conRouter->translateRoute(dx, dy); 0411 updateConnectorPoints(true); 0412 updateDrawList(); 0413 } 0414 0415 void Connector::restoreFromConnectorData(const ConnectorData &connectorData) 0416 { 0417 updateConnectorPoints(false); 0418 b_manualPoints = connectorData.manualRoute; 0419 m_conRouter->setRoutePoints(connectorData.route); 0420 updateConnectorPoints(true); 0421 updateDrawList(); 0422 } 0423 0424 ConnectorData Connector::connectorData() const 0425 { 0426 ConnectorData connectorData; 0427 0428 if (!startNode() || !endNode()) { 0429 qCDebug(KTL_LOG) << " m_startNode=" << startNode() << " m_endNode=" << endNode(); 0430 return connectorData; 0431 } 0432 0433 connectorData.manualRoute = usesManualPoints(); 0434 0435 connectorData.route = *m_conRouter->cellPointList(); 0436 0437 if (startNode()->isChildNode()) { 0438 connectorData.startNodeIsChild = true; 0439 connectorData.startNodeCId = startNode()->childId(); 0440 connectorData.startNodeParent = startNode()->parentItem()->id(); 0441 } else { 0442 connectorData.startNodeIsChild = false; 0443 connectorData.startNodeId = startNode()->id(); 0444 } 0445 0446 if (endNode()->isChildNode()) { 0447 connectorData.endNodeIsChild = true; 0448 connectorData.endNodeCId = endNode()->childId(); 0449 connectorData.endNodeParent = endNode()->parentItem()->id(); 0450 } else { 0451 connectorData.endNodeIsChild = false; 0452 connectorData.endNodeId = endNode()->id(); 0453 } 0454 0455 return connectorData; 0456 } 0457 0458 void Connector::setVisible(bool yes) 0459 { 0460 if (!canvas() || isVisible() == yes) 0461 return; 0462 0463 KtlQCanvasPolygon::setVisible(yes); 0464 updateConnectorLines(); 0465 } 0466 0467 Wire *Connector::wire(unsigned num) const 0468 { 0469 return (int(num) < m_wires.size()) ? m_wires[num] : nullptr; 0470 } 0471 0472 void Connector::setSelected(bool yes) 0473 { 0474 if (!canvas() || isSelected() == yes) 0475 return; 0476 0477 KtlQCanvasPolygon::setSelected(yes); 0478 updateConnectorLines(); 0479 0480 emit selected(yes); 0481 } 0482 0483 void Connector::updateConnectorLines(bool forceRedraw) 0484 { 0485 QColor color; 0486 0487 if (b_semiHidden) 0488 color = Qt::gray; 0489 else if (isSelected()) 0490 color = QColor(101, 134, 192); 0491 else if (!KTLConfig::showVoltageColor()) 0492 color = Qt::black; 0493 else 0494 color = Component::voltageColor(wire() ? wire()->voltage() : 0.0); 0495 0496 int z = ICNDocument::Z::Connector + (isSelected() ? 5 : 0); 0497 0498 QPen pen(color, (numWires() > 1) ? 2 : 1); 0499 0500 //bool animateWires = KTLConfig::animateWires(); 0501 for (KtlQCanvasPolygonalItem *item : m_connectorLineList) { 0502 bool changed = (item->z() != z) || (item->pen() != pen) || (item->isVisible() != isVisible()); 0503 0504 if (!changed) { 0505 if (forceRedraw) 0506 canvas()->setChanged(item->boundingRect()); 0507 continue; 0508 } 0509 0510 item->setZ(z); 0511 item->setPen(pen); 0512 item->setVisible(isVisible()); 0513 } 0514 } 0515 0516 QList<QPointList> Connector::splitConnectorPoints(const QPoint &pos) const 0517 { 0518 return m_conRouter->splitPoints(pos); 0519 } 0520 0521 QPointList Connector::connectorPoints(bool reverse) const 0522 { 0523 bool doReverse = (reverse != pointsAreReverse(m_conRouter->pointList(false))); 0524 return m_conRouter->pointList(doReverse); 0525 } 0526 0527 void Connector::incrementCurrentAnimation(double deltaTime) 0528 { 0529 // The values and equations used in this function have just been developed 0530 // empircally to be able to show a nice range of currents while still giving 0531 // a good indication of the amount of current flowing 0532 0533 double I_min = 1e-4; 0534 double sf = 3.0; // scaling factor 0535 0536 for (int i = 0; i < m_wires.size(); ++i) { 0537 if (!m_wires[i]) 0538 continue; 0539 0540 double I = m_wires[i]->current(); 0541 double sign = (I > 0) ? 1 : -1; 0542 double I_abs = I * sign; 0543 double prop = (I_abs > I_min) ? std::log(I_abs / I_min) : 0.0; 0544 0545 m_currentAnimationOffset += deltaTime * sf * std::pow(prop, 1.3) * sign; 0546 } 0547 } 0548 // END class Connector 0549 0550 // BEGIN class ConnectorLine 0551 ConnectorLine::ConnectorLine(Connector *connector, int pixelOffset) 0552 : // QObject(connector), 0553 KtlQCanvasLine(connector->canvas()) 0554 { 0555 qCDebug(KTL_LOG) << " this=" << this; 0556 m_pConnector = connector; 0557 m_pixelOffset = pixelOffset; 0558 } 0559 0560 /** 0561 * @returns x, possibly moving it to the closest bound if it is out of bounds. 0562 */ 0563 int boundify(int x, int bound1, int bound2) 0564 { 0565 if (bound2 < bound1) { 0566 // swap bounds 0567 int temp = bound2; 0568 bound2 = bound1; 0569 bound1 = temp; 0570 } 0571 0572 // now, have bound1 <= bound2 0573 if (x < bound1) 0574 return bound1; 0575 else if (x > bound2) 0576 return bound2; 0577 else 0578 return x; 0579 } 0580 0581 void ConnectorLine::drawShape(QPainter &p) 0582 { 0583 if (!m_bAnimateCurrent) { 0584 KtlQCanvasLine::drawShape(p); 0585 return; 0586 } 0587 0588 int ss = 3; // segment spacing 0589 int sl = 13; // segment length (includes segment spacing) 0590 0591 int offset = int(m_pConnector->currentAnimationOffset() - m_pixelOffset); 0592 offset = ((offset % sl) - sl) % sl; 0593 0594 int x1 = startPoint().x(); 0595 int y1 = startPoint().y(); 0596 int x2 = endPoint().x(); 0597 int y2 = endPoint().y(); 0598 0599 QPen pen = p.pen(); 0600 // pen.setStyle( Qt::DashDotLine ); 0601 p.setPen(pen); 0602 0603 if (x1 == x2) { 0604 int _x = int(x() + x1); 0605 int y_end = int(y() + y2); 0606 0607 if (y1 > y2) { 0608 // up connector line 0609 for (int _y = int(y() + y1 - offset); _y >= y_end; _y -= sl) { 0610 int y_1 = boundify(_y, int(y() + y1), y_end); 0611 int y_2 = boundify(_y - (sl - ss), int(y() + y1), y_end); 0612 p.drawLine(_x, y_1, _x, y_2); 0613 } 0614 } else { 0615 // down connector line 0616 for (int _y = int(y() + y1 + offset); _y <= y_end; _y += sl) { 0617 int y_1 = boundify(_y, int(y() + y1), y_end); 0618 int y_2 = boundify(_y + (sl - ss), int(y() + y1), y_end); 0619 p.drawLine(_x, y_1, _x, y_2); 0620 } 0621 } 0622 } else { 0623 // y1 == y2 0624 0625 int _y = int(y() + y1); 0626 int x_end = int(x() + x2); 0627 0628 if (x1 > x2) { 0629 // left connector line 0630 int x_start = int(x() + x1 - offset); 0631 0632 for (int _x = x_start; _x >= x_end; _x -= sl) { 0633 int x_1 = boundify(_x, int(x() + x1), x_end); 0634 int x_2 = boundify(_x - (sl - ss), int(x() + x1), x_end); 0635 p.drawLine(x_1, _y, x_2, _y); 0636 } 0637 } else { 0638 // right connector line 0639 for (int _x = int(x() + x1 + offset); _x <= x_end; _x += sl) { 0640 int x_1 = boundify(_x, int(x() + x1), x_end); 0641 int x_2 = boundify(_x + (sl - ss), int(x() + x1), x_end); 0642 p.drawLine(x_1, _y, x_2, _y); 0643 } 0644 } 0645 } 0646 } 0647 // END class ConnectorLine