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