1 /***************************************************************************
2 * Copyright (C) 2003-2005 by David Saxton *
3 * david@bluehaze.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
10
11 #include "circuitdocument.h"
12 #include "component.h"
13 #include "connector.h"
14 #include "conrouter.h"
15 #include "cnitem.h"
16 #include "ecnode.h"
17 #include "junctionnode.h"
18 #include "itemdocumentdata.h"
19 #include "wire.h"
20 #include "utils.h"
21
22 #include <QDebug>
23 #include <QPainter>
24
25 #include <cstdlib>
26 #include <cmath>
27
28 #include <ktlconfig.h>
29
30
31 //BEGIN class Connector
Connector(Node *,Node *,ICNDocument * icnDocument,QString * id)32 Connector::Connector(Node * /*startNode*/, Node * /*endNode*/, ICNDocument *icnDocument, QString *id)
33 : //QObject(icnDocument),
34 KtlQCanvasPolygon(icnDocument->canvas()) {
35
36 QString name("Connector");
37 if (id) {
38 name.append(QString( "-%1").arg(*id));
39 } else {
40 name.append("-Unknown");
41 }
42 setObjectName(name.toLatin1().data());
43 qDebug() << Q_FUNC_INFO << " this=" << this;
44
45 m_currentAnimationOffset = 0.0;
46 p_parentContainer = nullptr;
47 p_nodeGroup = nullptr;
48 b_semiHidden = false;
49 b_deleted = false;
50 b_pointsAdded = false;
51 b_manualPoints = false;
52 p_icnDocument = icnDocument;
53 m_conRouter = new ConRouter(p_icnDocument);
54
55 if (id) {
56 m_id = *id;
57 if ( !p_icnDocument->registerUID(*id) ) {
58 qDebug() << Q_FUNC_INFO << "Connector attempted to register given ID, but ID already in use: " << *id << endl;
59 m_id = p_icnDocument->generateUID( *id );
60 qDebug() << "Creating a new one: " << m_id << endl;
61 }
62 } else m_id = p_icnDocument->generateUID("connector");
63
64 p_icnDocument->registerItem(this);
65 p_icnDocument->requestRerouteInvalidatedConnectors();
66
67 setVisible(true);
68 }
69
70
~Connector()71 Connector::~Connector() {
72 p_icnDocument->unregisterUID(id());
73
74 delete m_conRouter;
75
76 for (int i = 0; i < m_wires.size(); i++)
77 delete m_wires[i];
78
79 // m_wires.resize(0);
80 }
81
82
setParentContainer(const QString & cnItemId)83 void Connector::setParentContainer(const QString &cnItemId) {
84 // // We only allow the node to be parented once
85 // if ( p_parentContainer || !ICNDocument->itemWithID(cnItemId) ) return;
86 p_parentContainer = p_icnDocument->cnItemWithID(cnItemId);
87 }
88
89
removeConnector(Node *)90 void Connector::removeConnector(Node*) {
91 if (b_deleted) return;
92
93 b_deleted = true;
94
95 // Remove 'penalty' points for this connector from the ICNDocument
96 updateConnectorPoints(false);
97
98 emit selected(false);
99 emit removed(this);
100
101 if (startNode()) startNode()->removeConnector(this);
102 if (endNode()) endNode()->removeConnector(this);
103
104 p_icnDocument->appendDeleteList(this);
105 }
106
107
getSlope(float x1,float y1,float x2,float y2)108 int getSlope(float x1, float y1, float x2, float y2) {
109 enum slope {
110 s_n = 0,// .
111 s_v, // |
112 s_h, // -
113 s_s, // /
114 s_d // \ (backwards slash)
115 };
116
117 if (x1 == x2) {
118 if (y1 == y2) return s_n;
119 return s_v;
120 } else if (y1 == y2) {
121 return s_h;
122 } else if ((y2 - y1) / (x2 - x1) > 0) {
123 return s_s;
124 } else return s_d;
125 }
126
127
updateDrawList()128 void Connector::updateDrawList() {
129 if (!startNode() || !endNode() || !canvas()) return;
130
131 QPointList drawLineList;
132
133 int prevX = (*m_conRouter->cellPointList()->begin()).x();
134 int prevY = (*m_conRouter->cellPointList()->begin()).y();
135
136 int prevX_canvas = toCanvas(prevX);
137 int prevY_canvas = toCanvas(prevY);
138
139 Cells *cells = p_icnDocument->cells();
140
141 bool bumpNow = false;
142 for (QPoint p: *m_conRouter->cellPointList()) {
143 const int x = p.x();
144 const int y = p.y();
145
146 const int numCon = cells->haveCell(x, y) ? cells->cell(x, y).numCon : 0;
147
148 const int y_canvas = toCanvas(y);
149 const int x_canvas = toCanvas(x);
150
151 const bool bumpNext = (prevX == x
152 && numCon > 1
153 && std::abs(y_canvas - startNode()->y()) > 8
154 && std::abs(y_canvas - endNode()->y()) > 8);
155
156 int x0 = prevX_canvas;
157 int x2 = x_canvas;
158 int x1 = (x0 + x2) / 2;
159
160 int y0 = prevY_canvas;
161 int y3 = y_canvas;
162 int y1 = (y0 == y3) ? y0 : ((y0 < y3) ? y0 + 3 : y0 - 3);
163 int y2 = (y0 == y3) ? y3 : ((y0 < y3) ? y3 - 3 : y3 + 3);
164
165 if (bumpNow) x0 += 3;
166 if (bumpNext) x2 += 3;
167
168 if (!bumpNow && !bumpNext) {
169 drawLineList += QPoint(x0, y0);
170 drawLineList += QPoint(x2, y3);
171 } else if (bumpNow) {
172 drawLineList += QPoint(x0, y0);
173 drawLineList += QPoint(x1, y1);
174 drawLineList += QPoint(x2, y3);
175 } else if (bumpNext) {
176 drawLineList += QPoint(x0, y0);
177 drawLineList += QPoint(x1, y2);
178 drawLineList += QPoint(x2, y3);
179 } else {
180 drawLineList += QPoint(x0, y0);
181 drawLineList += QPoint(x1, y1);
182 drawLineList += QPoint(x1, y2);
183 drawLineList += QPoint(x2, y3);
184 }
185
186 prevX = x;
187 prevY = y;
188
189 prevY_canvas = y_canvas;
190 prevX_canvas = x_canvas;
191 bumpNow = bumpNext;
192 }
193
194 // Now, remove redundant points (i.e. those that are either repeated or are
195 // in the same direction as the previous points)
196
197 if (drawLineList.size() < 3) return;
198
199 const QPointList::iterator dllEnd = drawLineList.end();
200
201 QPointList::iterator previous = drawLineList.begin();
202 QPointList::iterator current = previous;
203 current++;
204 QPointList::const_iterator next = current;
205 next++;
206
207 int invalid = -(1 << 30);
208
209 while (previous != dllEnd && current != dllEnd && next != dllEnd) {
210 const int slope1 = getSlope((*previous).x(), (*previous).y(), (*current).x(), (*current).y());
211 const int slope2 = getSlope((*current).x(), (*current).y(), (*next).x(), (*next).y());
212
213 if (slope1 == slope2 || slope1 == 0 || slope2 == 0) {
214 *current = QPoint(invalid, invalid);
215 } else previous = current;
216
217 current++;
218 next++;
219 }
220
221 drawLineList.removeAll(QPoint(invalid, invalid));
222
223 // Find the bounding rect
224 {
225 int x1 = invalid, y1 = invalid, x2 = invalid, y2 = invalid;
226
227 for (const QPoint p: drawLineList) {
228 if (p.x() < x1 || x1 == invalid) x1 = p.x();
229 if (p.x() > x2 || x2 == invalid) x2 = p.x();
230
231 if (p.y() < y1 || y1 == invalid) y1 = p.y();
232 if (p.y() > y2 || y2 == invalid) y2 = p.y();
233 }
234
235 QRect boundRect(x1, y1, x2 - x1, y2 - y1);
236
237 if (boundRect != m_oldBoundRect) {
238 canvas()->setChanged(boundRect | m_oldBoundRect);
239 m_oldBoundRect = boundRect;
240 }
241 }
242
243 //BEGIN build up ConnectorLine list
244 for (ConnectorLine* line: m_connectorLineList)
245 delete line;
246
247 m_connectorLineList.clear();
248
249 if (drawLineList.size() > 1) {
250 QPoint prev = drawLineList.first();
251 int pixelOffset = 0;
252
253 for (QPoint next: drawLineList) {
254 ConnectorLine *line = new ConnectorLine(this, pixelOffset);
255 m_connectorLineList.append(line);
256
257 line->setPoints(prev.x(), prev.y(), next.x(), next.y());
258
259 // (note that only one of the following QABS will be non-zero)
260 pixelOffset += abs(prev.x() - next.x()) + abs(prev.y() - next.y());
261
262 prev = next;
263 }
264 }
265
266 updateConnectorLines();
267
268 //END build up ConnectorPoint list
269 }
270
271
setSemiHidden(bool semiHidden)272 void Connector::setSemiHidden(bool semiHidden) {
273 if (!canvas() || semiHidden == b_semiHidden)
274 return;
275
276 b_semiHidden = semiHidden;
277 updateConnectorLines();
278 }
279
280
updateConnectorPoints(bool add)281 void Connector::updateConnectorPoints(bool add) {
282 if (!canvas()) return;
283
284 if (b_deleted || !isVisible()) add = false;
285
286 // Check we haven't already added/removed the points...
287 if (b_pointsAdded == add) return;
288
289 b_pointsAdded = add;
290
291 // We don't include the end points in the mapping
292 if (m_conRouter->cellPointList()->size() < 3) return;
293
294 Cells * cells = p_icnDocument->cells();
295
296 const int mult = (add) ? 1 : -1;
297 for (QPoint p: *m_conRouter->cellPointList()) {
298 int x = p.x();
299 int y = p.y();
300
301 // Add the points of this connector to the cell array in the ICNDocument,
302 // so that other connectors still to calculate their points know to try
303 // and avoid this connector
304
305 p_icnDocument->addCPenalty(x , y - 1, mult*ICNDocument::hs_connector / 2);
306 p_icnDocument->addCPenalty(x - 1, y , mult*ICNDocument::hs_connector / 2);
307 p_icnDocument->addCPenalty(x , y , mult*ICNDocument::hs_connector );
308 p_icnDocument->addCPenalty(x + 1, y , mult*ICNDocument::hs_connector / 2);
309 p_icnDocument->addCPenalty(x , y + 1, mult*ICNDocument::hs_connector / 2);
310
311 if (cells->haveCell(x , y))
312 cells->cell(x, y).numCon += mult;
313 }
314
315 // updateDrawList();
316 }
317
318
setRoutePoints(QPointList pointList,bool setManual,bool checkEndPoints)319 void Connector::setRoutePoints(QPointList pointList, bool setManual, bool checkEndPoints) {
320 if (!canvas()) return;
321
322 updateConnectorPoints(false);
323
324 bool reversed = pointsAreReverse(pointList);
325
326 // a little performance boost: don't call (start|end)Node 4 times
327 Node* l_endNode = endNode();
328 Node* l_startNode = startNode();
329
330 if (checkEndPoints) {
331 if (reversed) {
332 pointList.prepend(QPoint(int(l_endNode->x()), int(l_endNode->y())));
333 pointList.append(QPoint(int(l_startNode->x()), int(l_startNode->y())));
334 } else {
335 pointList.prepend(QPoint(int(l_startNode->x()), int(l_startNode->y())));
336 pointList.append(QPoint(int(l_endNode->x()), int(l_endNode->y())));
337 }
338 }
339
340 m_conRouter->setPoints(pointList, reversed);
341
342 b_manualPoints = setManual;
343 updateConnectorPoints(true);
344 }
345
346
pointsAreReverse(const QPointList & pointList) const347 bool Connector::pointsAreReverse(const QPointList &pointList) const {
348 if (!startNode() || !endNode()) {
349 qWarning() << Q_FUNC_INFO << "Cannot determine orientation as no start and end nodes" << endl;
350 return false;
351 }
352
353 if (pointList.isEmpty()) return false;
354
355 int plsx = pointList.first().x();
356 int plsy = pointList.first().y();
357 int plex = pointList.last().x();
358 int pley = pointList.last().y();
359
360 double nsx = startNode()->x();
361 double nsy = startNode()->y();
362 double nex = endNode()->x();
363 double ney = endNode()->y();
364
365 double dist_normal = (nsx - plsx) * (nsx - plsx)
366 + (nsy - plsy) * (nsy - plsy)
367 + (nex - plex) * (nex - plex)
368 + (ney - pley) * (ney - pley);
369
370 double dist_reverse = (nsx - plex) * (nsx - plex)
371 + (nsy - pley) * (nsy - pley)
372 + (nex - plsx) * (nex - plsx)
373 + (ney - plsy) * (ney - plsy);
374
375 return dist_reverse < dist_normal;
376 }
377
378
rerouteConnector()379 void Connector::rerouteConnector() {
380 if (!isVisible()) return;
381
382 if (nodeGroup()) {
383 qWarning() << Q_FUNC_INFO << "Connector is controlled by a NodeGroup! Use that to reroute the connector" << endl;
384 return;
385 }
386
387 if (!startNode() || !endNode()) return;
388
389 updateConnectorPoints(false);
390
391 m_conRouter->mapRoute(int(startNode()->x()),
392 int(startNode()->y()),
393 int(endNode()->x()),
394 int(endNode()->y()));
395
396 b_manualPoints = false;
397 updateConnectorPoints(true);
398 }
399
400
translateRoute(int dx,int dy)401 void Connector::translateRoute(int dx, int dy) {
402 updateConnectorPoints(false);
403 m_conRouter->translateRoute(dx, dy);
404 updateConnectorPoints(true);
405 updateDrawList();
406 }
407
408
restoreFromConnectorData(const ConnectorData & connectorData)409 void Connector::restoreFromConnectorData(const ConnectorData &connectorData) {
410 updateConnectorPoints(false);
411 b_manualPoints = connectorData.manualRoute;
412 m_conRouter->setRoutePoints(connectorData.route);
413 updateConnectorPoints(true);
414 updateDrawList();
415 }
416
417
connectorData() const418 ConnectorData Connector::connectorData() const {
419 ConnectorData connectorData;
420
421 if (!startNode() || !endNode()) {
422 qDebug() << Q_FUNC_INFO << " m_startNode=" << startNode() << " m_endNode=" << endNode() << endl;
423 return connectorData;
424 }
425
426 connectorData.manualRoute = usesManualPoints();
427
428 connectorData.route = *m_conRouter->cellPointList();
429
430 if (startNode()->isChildNode()) {
431 connectorData.startNodeIsChild = true;
432 connectorData.startNodeCId = startNode()->childId();
433 connectorData.startNodeParent = startNode()->parentItem()->id();
434 } else {
435 connectorData.startNodeIsChild = false;
436 connectorData.startNodeId = startNode()->id();
437 }
438
439 if (endNode()->isChildNode()) {
440 connectorData.endNodeIsChild = true;
441 connectorData.endNodeCId = endNode()->childId();
442 connectorData.endNodeParent = endNode()->parentItem()->id();
443 } else {
444 connectorData.endNodeIsChild = false;
445 connectorData.endNodeId = endNode()->id();
446 }
447
448 return connectorData;
449 }
450
451
setVisible(bool yes)452 void Connector::setVisible(bool yes) {
453 if (!canvas() || isVisible() == yes) return;
454
455 KtlQCanvasPolygon::setVisible(yes);
456 updateConnectorLines();
457 }
458
wire(unsigned num) const459 Wire *Connector::wire(unsigned num) const {
460 return (num < m_wires.size()) ? m_wires[num] : nullptr;
461 }
462
setSelected(bool yes)463 void Connector::setSelected(bool yes) {
464 if (!canvas() || isSelected() == yes) return;
465
466 KtlQCanvasPolygon::setSelected(yes);
467 updateConnectorLines();
468
469 emit selected(yes);
470 }
471
472
updateConnectorLines(bool forceRedraw)473 void Connector::updateConnectorLines(bool forceRedraw) {
474 QColor color;
475
476 if (b_semiHidden) color = Qt::gray;
477 else if (isSelected()) color = QColor(101, 134, 192);
478 else if (!KTLConfig::showVoltageColor()) color = Qt::black;
479 else color = Component::voltageColor(wire() ? wire()->voltage() : 0.0);
480
481 int z = ICNDocument::Z::Connector + (isSelected() ? 5 : 0);
482
483 QPen pen(color, (numWires() > 1) ? 2 : 1);
484
485 bool animateWires = KTLConfig::animateWires();
486 for (KtlQCanvasPolygonalItem *item: m_connectorLineList) {
487 bool changed = (item->z() != z)
488 || (item->pen() != pen)
489 || (item->isVisible() != isVisible());
490
491 if (!changed) {
492 if (forceRedraw)
493 canvas()->setChanged(item->boundingRect());
494 continue;
495 }
496
497 item->setZ(z);
498 item->setPen(pen);
499 item->setVisible(isVisible());
500 }
501 }
502
503
splitConnectorPoints(const QPoint & pos) const504 QList<QPointList> Connector::splitConnectorPoints(const QPoint & pos) const {
505 return m_conRouter->splitPoints(pos);
506 }
507
508
connectorPoints(bool reverse) const509 QPointList Connector::connectorPoints(bool reverse) const {
510 bool doReverse = (reverse != pointsAreReverse(m_conRouter->pointList(false)));
511 return m_conRouter->pointList(doReverse);
512 }
513
514
incrementCurrentAnimation(double deltaTime)515 void Connector::incrementCurrentAnimation(double deltaTime) {
516 // The values and equations used in this function have just been developed
517 // empircally to be able to show a nice range of currents while still giving
518 // a good indication of the amount of current flowing
519
520 double I_min = 1e-4;
521 double sf = 3.0; // scaling factor
522
523 for (int i = 0; i < m_wires.size(); ++i) {
524 if (!m_wires[i]) continue;
525
526 double I = m_wires[i]->current();
527 double sign = (I > 0) ? 1 : -1;
528 double I_abs = I * sign;
529 double prop = (I_abs > I_min) ? std::log(I_abs / I_min) : 0.0;
530
531 m_currentAnimationOffset += deltaTime * sf * std::pow(prop, 1.3) * sign;
532 }
533 }
534 //END class Connector
535
536
537 //BEGIN class ConnectorLine
ConnectorLine(Connector * connector,int pixelOffset)538 ConnectorLine::ConnectorLine(Connector * connector, int pixelOffset)
539 : //QObject(connector),
540 KtlQCanvasLine(connector->canvas()) {
541 qDebug() << Q_FUNC_INFO << " this=" << this;
542 m_pConnector = connector;
543 m_pixelOffset = pixelOffset;
544 }
545
546
547 /**
548 * @returns x, possibly moving it to the closest bound if it is out of bounds.
549 */
boundify(int x,int bound1,int bound2)550 int boundify(int x, int bound1, int bound2) {
551 if (bound2 < bound1) {
552 // swap bounds
553 int temp = bound2;
554 bound2 = bound1;
555 bound1 = temp;
556 }
557
558 // now, have bound1 <= bound2
559 if (x < bound1) return bound1;
560 else if (x > bound2) return bound2;
561 else return x;
562 }
563
564
drawShape(QPainter & p)565 void ConnectorLine::drawShape(QPainter & p) {
566 if (!m_bAnimateCurrent) {
567 KtlQCanvasLine::drawShape(p);
568 return;
569 }
570
571 int ss = 3; // segment spacing
572 int sl = 13; // segment length (includes segment spacing)
573
574 int offset = int(m_pConnector->currentAnimationOffset() - m_pixelOffset);
575 offset = ((offset % sl) - sl) % sl;
576
577 int x1 = startPoint().x();
578 int y1 = startPoint().y();
579 int x2 = endPoint().x();
580 int y2 = endPoint().y();
581
582 QPen pen = p.pen();
583 // pen.setStyle( Qt::DashDotLine );
584 p.setPen(pen);
585
586 if (x1 == x2) {
587 int _x = int(x() + x1);
588 int y_end = int(y() + y2);
589
590 if (y1 > y2) {
591 // up connector line
592 for (int _y = int(y() + y1 - offset); _y >= y_end; _y -= sl) {
593 int y_1 = boundify(_y, int(y() + y1), y_end);
594 int y_2 = boundify(_y - (sl - ss), int(y() + y1), y_end);
595 p.drawLine(_x, y_1, _x, y_2);
596 }
597 } else {
598 // down connector line
599 for (int _y = int(y() + y1 + offset); _y <= y_end; _y += sl) {
600 int y_1 = boundify(_y, int(y() + y1), y_end);
601 int y_2 = boundify(_y + (sl - ss), int(y() + y1), y_end);
602 p.drawLine(_x, y_1, _x, y_2);
603 }
604 }
605 } else {
606 // y1 == y2
607
608 int _y = int(y() + y1);
609 int x_end = int(x() + x2);
610
611 if (x1 > x2) {
612 // left connector line
613 int x_start = int(x() + x1 - offset);
614
615 for (int _x = x_start; _x >= x_end; _x -= sl) {
616 int x_1 = boundify(_x, int(x() + x1), x_end);
617 int x_2 = boundify(_x - (sl - ss), int(x() + x1), x_end);
618 p.drawLine(x_1, _y, x_2, _y);
619 }
620 } else {
621 // right connector line
622 for (int _x = int(x() + x1 + offset); _x <= x_end; _x += sl) {
623 int x_1 = boundify(_x, int(x() + x1), x_end);
624 int x_2 = boundify(_x + (sl - ss), int(x() + x1), x_end);
625 p.drawLine(x_1, _y, x_2, _y);
626 }
627 }
628 }
629 }
630 //END class ConnectorLine
631