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