File indexing completed on 2024-04-28 13:39:32

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