1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6984 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 23:44:56 +0200 (Mo, 22. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "stripboard.h"
28 #include "../utils/graphicsutils.h"
29 #include "../utils/textutils.h"
30 #include "../utils/familypropertycombobox.h"
31 #include "../svg/gerbergenerator.h"
32 #include "../fsvgrenderer.h"
33 #include "../sketch/infographicsview.h"
34 #include "moduleidnames.h"
35 #include "../connectors/connectoritem.h"
36 #include "../connectors/busshared.h"
37 #include "../connectors/connectorshared.h"
38 #include "../debugdialog.h"
39 
40 #include <QCursor>
41 #include <QBitmap>
42 
43 
44 //////////////////////////////////////////////////
45 
46 // TODO:
47 //
48 //	new cursors, new hover states?
49 //	disconnect and reconnect affected parts
50 //	swapping
51 
52 static QCursor * SpotFaceCutterCursor = NULL;
53 static QCursor * MagicWandCursor = NULL;
54 
55 static bool ShiftDown = false;
56 static QPointF OriginalShiftPos;
57 static bool ShiftX = false;
58 static bool ShiftY = false;
59 static bool SpaceBarWasPressed = false;
60 static const double MinMouseMove = 2;
61 
62 static QPainterPath HPath;
63 static QPainterPath VPath;
64 
65 static QString HorizontalString("horizontal strips");
66 static QString VerticalString("vertical strips");
67 
68 /////////////////////////////////////////////////////////////////////
69 
Stripbit(const QPainterPath & path,int x,int y,bool horizontal,QGraphicsItem * parent=0)70 Stripbit::Stripbit(const QPainterPath & path, int x, int y, bool horizontal, QGraphicsItem * parent = 0)
71 	: QGraphicsPathItem(path, parent)
72 {
73 	if (SpotFaceCutterCursor == NULL) {
74 		QBitmap bitmap(":resources/images/cursor/spot_face_cutter.bmp");
75 		QBitmap bitmapm(":resources/images/cursor/spot_face_cutter_mask.bmp");
76 		SpotFaceCutterCursor = new QCursor(bitmap, bitmapm, 0, 0);
77 	}
78 
79 	if (MagicWandCursor == NULL) {
80 		QBitmap bitmap(":resources/images/cursor/magic_wand.bmp");
81 		QBitmap bitmapm(":resources/images/cursor/magic_wand_mask.bmp");
82 		MagicWandCursor = new QCursor(bitmap, bitmapm, 0, 0);
83 	}
84 
85 	setZValue(-999);			// beneath connectorItems
86 
87 	setPen(Qt::NoPen);
88 	// TODO: don't hardcode this color
89 	setBrush(QColor(0xbc, 0x94, 0x51));							// QColor(0xc4, 0x9c, 0x59)
90 
91 
92 	m_horizontal = horizontal;
93 	m_x = x;
94 	m_y = y;
95 	m_inHover = m_removed = false;
96 
97 	setAcceptHoverEvents(true);
98 	setAcceptedMouseButtons(Qt::LeftButton);
99 	setFlag(QGraphicsItem::ItemIsMovable, true);
100 	setFlag(QGraphicsItem::ItemIsSelectable, false);
101 
102 }
103 
~Stripbit()104 Stripbit::~Stripbit() {
105 }
106 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)107 void Stripbit::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
108 	double newOpacity = 1;
109 	if (m_removed) {
110 		if (m_inHover) newOpacity = 0.50;
111 		else newOpacity = 0.00;
112 	}
113 	else {
114 		if (m_inHover) newOpacity = 0.40;
115 		else newOpacity = 1.00;
116 	}
117 
118 	double opacity = painter->opacity();
119 	painter->setOpacity(newOpacity);
120 	QGraphicsPathItem::paint(painter, option, widget);
121 	painter->setOpacity(opacity);
122 
123 }
124 
mousePressEvent(QGraphicsSceneMouseEvent * event)125 void Stripbit::mousePressEvent(QGraphicsSceneMouseEvent *event)
126 {
127 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
128 	if (infoGraphicsView != NULL && infoGraphicsView->spaceBarIsPressed()) {
129 		event->ignore();
130 		return;
131 	}
132 
133 	if (!event->buttons() && Qt::LeftButton) {
134 		event->ignore();
135 		return;
136 	}
137 
138 	if (dynamic_cast<ItemBase *>(this->parentItem())->moveLock()) {
139 		event->ignore();
140 		return;
141 	}
142 
143 	if (event->modifiers() & Qt::ShiftModifier) {
144 		ShiftDown = true;
145         ShiftX = ShiftY = false;
146 		OriginalShiftPos = event->scenePos();
147 	}
148 
149 	event->accept();
150 	dynamic_cast<Stripboard *>(this->parentItem())->initCutting(this);
151 	m_removed = !m_removed;
152 	m_inHover = false;
153 	m_changed = true;
154 	update();
155 
156 
157 	//DebugDialog::debug("got press");
158 }
159 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)160 void Stripbit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
161 {
162 	Q_UNUSED(event);
163 	dynamic_cast<Stripboard *>(this->parentItem())->reinitBuses(true);
164 }
165 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)166 void Stripbit::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
167 {
168 	if (!event->buttons() && Qt::LeftButton) return;
169 
170 	if (ShiftDown && !(event->modifiers() & Qt::ShiftModifier)) {
171 		ShiftDown = false;
172 	}
173 
174 	//DebugDialog::debug("got move");
175 
176 	Stripbit * other = NULL;
177 	QPointF p = event->scenePos();
178 	if (ShiftDown) {
179 		if (ShiftX) {
180 			// moving along x, constrain y
181 			p.setY(OriginalShiftPos.y());
182 		}
183 		else if (ShiftY) {
184 			// moving along y, constrain x
185 			p.setX(OriginalShiftPos.x());
186 		}
187         else {
188             double dx = qAbs(p.x() - OriginalShiftPos.x());
189             double dy = qAbs(p.y() - OriginalShiftPos.y());
190             if (dx - dy > MinMouseMove) {
191                 ShiftX = true;
192             }
193             else if (dy - dx > MinMouseMove) {
194                 ShiftY = true;
195             }
196         }
197 	}
198 
199 
200 	if (!ShiftDown && (event->modifiers() & Qt::ShiftModifier)) {
201 		ShiftDown = true;
202         ShiftX = ShiftY = false;
203 		OriginalShiftPos = event->scenePos();
204 	}
205 
206 	foreach (QGraphicsItem * item, scene()->items(p)) {
207 		other = dynamic_cast<Stripbit *>(item);
208 		if (other) break;
209 	}
210 
211 	if (!other) return;
212 
213 	//DebugDialog::debug("got other");
214 
215 	if (other->removed() == m_removed) return;
216 
217 	//DebugDialog::debug("change other");
218 
219 	other->setRemoved(m_removed);
220 	other->setChanged(true);
221 	other->update();
222 	dynamic_cast<Stripboard *>(this->parentItem())->restoreRowColors(other);
223 
224 }
225 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)226 void Stripbit::hoverEnterEvent( QGraphicsSceneHoverEvent * event )
227 {
228 	if (dynamic_cast<ItemBase *>(this->parentItem())->moveLock()) return;
229 
230 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
231 	if (infoGraphicsView != NULL && infoGraphicsView->spaceBarIsPressed()) {
232 		SpaceBarWasPressed = true;
233 		return;
234 	}
235 
236 	SpaceBarWasPressed = false;
237 	setCursor(m_removed ? *MagicWandCursor : *SpotFaceCutterCursor);
238 	Q_UNUSED(event);
239 	m_inHover = true;
240 	update();
241 }
242 
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)243 void Stripbit::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event )
244 {
245 	if (dynamic_cast<ItemBase *>(this->parentItem())->moveLock()) return;
246 	if (SpaceBarWasPressed) return;
247 
248 	unsetCursor();
249 	Q_UNUSED(event);
250 	m_inHover = false;
251 	update();
252 }
253 
254 
255 
horizontal()256 bool Stripbit::horizontal() {
257 	return m_horizontal;
258 }
259 
setRemoved(bool removed)260 void Stripbit::setRemoved(bool removed) {
261 	m_removed = removed;
262 }
263 
removed()264 bool Stripbit::removed() {
265 	return m_removed;
266 }
267 
setChanged(bool changed)268 void Stripbit::setChanged(bool changed) {
269 	m_changed = changed;
270 }
271 
changed()272 bool Stripbit::changed() {
273 	return m_changed;
274 }
275 
y()276 int Stripbit::y() {
277 	return m_y;
278 }
279 
x()280 int Stripbit::x() {
281 	return m_x;
282 }
283 
makeRemovedString()284 QString Stripbit::makeRemovedString() {
285     return QString("%1.%2%3 ").arg(m_x).arg(m_y).arg(m_horizontal ? 'h' : 'v');
286 }
287 
288 /////////////////////////////////////////////////////////////////////
289 
StripConnector()290 StripConnector::StripConnector() {
291     down = right = NULL;
292     connectorItem = NULL;
293 }
294 
295 struct StripLayout {
296     QString name;
297     int rows;
298     int columns;
299     QString buses;
300 
301     StripLayout(QString name, int rows, int columns, QString buses);
302 };
303 
StripLayout(QString name_,int rows_,int columns_,QString buses_)304 StripLayout::StripLayout(QString name_, int rows_, int columns_, QString buses_) {
305     name = name_;
306     rows = rows_;
307     columns = columns_;
308     buses = buses_;
309 }
310 
311 static QList<StripLayout> StripLayouts;
312 
313 /////////////////////////////////////////////////////////////////////
314 
Stripboard(ModelPart * modelPart,ViewLayer::ViewID viewID,const ViewGeometry & viewGeometry,long id,QMenu * itemMenu,bool doLabel)315 Stripboard::Stripboard( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel)
316 	: Perfboard(modelPart, viewID, viewGeometry, id, itemMenu, doLabel)
317 {
318 	getXY(m_x, m_y, m_size);
319     if (StripLayouts.count() == 0) {
320         initStripLayouts();
321     }
322 
323 	m_layout = modelPart->localProp("layout").toString();
324 	if (m_layout.isEmpty()) {
325 		m_layout = modelPart->properties().value("m_layout");
326         if (!m_layout.isEmpty()) {
327 		    modelPart->setLocalProp("layout", m_layout);
328         }
329 	}
330 }
331 
~Stripboard()332 Stripboard::~Stripboard() {
333     foreach (StripConnector * sc, m_strips) {
334         delete sc;
335     }
336 
337     m_strips.clear();
338 }
339 
retrieveSvg(ViewLayer::ViewLayerID viewLayerID,QHash<QString,QString> & svgHash,bool blackOnly,double dpi,double & factor)340 QString Stripboard::retrieveSvg(ViewLayer::ViewLayerID viewLayerID, QHash<QString, QString> & svgHash, bool blackOnly, double dpi, double & factor)
341 {
342 	QString svg = Perfboard::retrieveSvg(viewLayerID, svgHash, blackOnly, dpi, factor);
343 	if (svg.isEmpty()) return svg;
344 	if (!svg.contains(GerberGenerator::MagicBoardOutlineID)) return svg;
345 
346 	/*
347 	QFile file(filename());
348 	if (!file.open(QFile::ReadOnly | QFile::Text)) {
349 		return svg;
350 	}
351 	QString originalSvg = file.readAll();
352 	file.close();
353 
354 	QString stripSvg = originalSvg.left(svg.indexOf(">") + 1);
355 	*/
356 
357 	QString stripSvg;
358 	foreach(QGraphicsItem * item, childItems()) {
359 		Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
360 		if (stripbit == NULL) continue;
361 		if (stripbit->removed()) continue;
362 
363 		QPointF p = stripbit->pos();
364 		QRectF r = stripbit->boundingRect();
365 
366 		stripSvg += QString("<path stroke='none' stroke-width='0' fill='%6' "
367 						"d='m%1,%2 %3,0 0,%4 -%3,0z m0,%4a%5,%5  0,1,0 0,-%4z  m%3,-%4a%5,%5 0,1,0 0,%4z'/>\n")
368 					.arg(p.x() * dpi / GraphicsUtils::SVGDPI)
369 					.arg(p.y() * dpi / GraphicsUtils::SVGDPI)
370 					.arg(r.width() * dpi / GraphicsUtils::SVGDPI )
371 					.arg(r.height() * dpi / GraphicsUtils::SVGDPI)
372 					.arg(r.height() * dpi * .5 / GraphicsUtils::SVGDPI)
373 					.arg(blackOnly ? "black" : "#c49c59")
374 				;
375 	}
376 
377 	svg.truncate(svg.lastIndexOf("</g>"));
378 
379 	return svg + stripSvg + "</g>\n";
380 }
381 
genFZP(const QString & moduleid)382 QString Stripboard::genFZP(const QString & moduleid)
383 {
384 	QString fzp = Perfboard::genFZP(moduleid);
385     fzp.replace(ModuleIDNames::PerfboardModuleIDName, ModuleIDNames::Stripboard2ModuleIDName);
386 	fzp.replace("Perfboard", "Stripboard");
387 	fzp.replace("perfboard", "stripboard");
388 	fzp.replace("stripboard.svg", "perfboard.svg");  // replaced just above; restore it
389     QString findString("<properties>");
390     int ix = fzp.indexOf(findString);
391     if (ix > 0) {
392         fzp.insert(ix + findString.count(), "<property name='layout'></property>");
393         if (moduleid.endsWith(ModuleIDNames::StripboardModuleIDName)) {
394             fzp.insert(ix + findString.count(), "<property name='oldstyle'>yes</property>");
395         }
396     }
397 	return fzp;
398 }
399 
collectExtraInfo(QWidget * parent,const QString & family,const QString & prop,const QString & value,bool swappingEnabled,QString & returnProp,QString & returnValue,QWidget * & returnWidget,bool & hide)400 bool Stripboard::collectExtraInfo(QWidget * parent, const QString & family, const QString & prop, const QString & value, bool swappingEnabled, QString & returnProp, QString & returnValue, QWidget * & returnWidget, bool & hide)
401 {
402 	return Perfboard::collectExtraInfo(parent, family, prop, value, swappingEnabled, returnProp, returnValue, returnWidget, hide);
403 }
404 
addedToScene(bool temporary)405 void Stripboard::addedToScene(bool temporary)
406 {
407     Perfboard::addedToScene(temporary);
408 	if (this->scene() == NULL) return;
409     if (temporary) return;
410     if (m_viewID != ViewLayer::BreadboardView) return;
411 
412 	QList<QGraphicsItem *> items = childItems();
413 
414     if (HPath.isEmpty()) {
415         makeInitialPath();
416     }
417 
418     int count = m_x * m_y;
419     for (int i = 0; i < count; i++) {
420        m_strips.append(new StripConnector);
421     }
422 
423     bool oldStyle = false;
424     if (moduleID().endsWith(ModuleIDNames::StripboardModuleIDName) ||  !modelPart()->properties().value("oldstyle", "").isEmpty()) {
425         modelPart()->modelPartShared()->setModuleID(QString("%1.%2%3").arg(m_x).arg(m_y).arg(ModuleIDNames::Stripboard2ModuleIDName));
426         oldStyle = true;
427         m_layout = HorizontalString;
428     }
429 
430 	QString config = prop("buses");
431     QString additionalConfig;
432 
433 	foreach (ConnectorItem * ci, cachedConnectorItems()) {
434 		int cx, cy;
435 		getXY(cx, cy, ci->connectorSharedName());
436         StripConnector * sc =  getStripConnector(cx, cy);
437         sc->connectorItem = ci;
438 
439         if (cx < m_x - 1) {
440 		    Stripbit * stripbit = new Stripbit(HPath, cx, cy, true, this);
441 		    QRectF r = ci->rect();
442 		    stripbit->setPos(r.center().x(), r.top());
443             stripbit->setVisible(true);
444             sc->right = stripbit;
445             if (!oldStyle) {
446                 additionalConfig += stripbit->makeRemovedString();
447             }
448         }
449 
450         if (cy < m_y - 1) {
451 		    Stripbit * stripbit = new Stripbit(VPath, cx, cy, false, this);
452 		    QRectF r = ci->rect();
453 		    stripbit->setPos(r.left(), r.center().y());
454             stripbit->setVisible(true);
455             sc->down = stripbit;
456             if (oldStyle) {
457                 additionalConfig += stripbit->makeRemovedString();
458             }
459         }
460 	}
461 
462 	if (config.isEmpty() || oldStyle) {
463         config += additionalConfig;
464         setProp("buses", config);
465     }
466 
467     if (m_layout.isEmpty()) m_layout = VerticalString;
468 
469 	QStringList removed = config.split(" ", QString::SkipEmptyParts);
470 
471 	foreach (QString name, removed) {
472 		int cx, cy;
473 		if (getXY(cx, cy, name)) {
474             StripConnector * sc = getStripConnector(cx, cy);
475             bool vertical = name.contains("v");
476             if (vertical) {
477                 if (sc->down != NULL) sc->down->setRemoved(true);
478             }
479             else {
480                 if (sc->right != NULL) sc->right->setRemoved(true);
481             }
482 		}
483 	}
484 
485 	reinitBuses(false);
486 }
487 
genModuleID(QMap<QString,QString> & currPropsMap)488 QString Stripboard::genModuleID(QMap<QString, QString> & currPropsMap)
489 {
490 	QString size = currPropsMap.value("size");
491 	return size + ModuleIDNames::Stripboard2ModuleIDName;
492 }
493 
initCutting(Stripbit *)494 void Stripboard::initCutting(Stripbit *)
495 {
496 	m_beforeCut.clear();
497 	foreach (QGraphicsItem * item, childItems()) {
498 		Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
499 		if (stripbit == NULL) continue;
500 
501 		stripbit->setChanged(false);
502 		if (stripbit->removed()) {
503             m_beforeCut += stripbit->makeRemovedString();
504 		}
505 	}
506 }
507 
appendConnectors(QList<ConnectorItem * > & connectorItems,ConnectorItem * connectorItem)508 void appendConnectors(QList<ConnectorItem *> & connectorItems, ConnectorItem * connectorItem) {
509 	if (connectorItem == NULL) return;
510 
511 	foreach (ConnectorItem * ci, connectorItem->connectedToItems()) {
512 		connectorItems.append(ci);
513 	}
514 }
515 
reinitBuses(bool triggerUndo)516 void Stripboard::reinitBuses(bool triggerUndo)
517 {
518 	if (triggerUndo) {
519 		QString afterCut;
520 		QSet<ConnectorItem *> affectedConnectors;
521 		int changeCount = 0;
522 		bool connect = true;
523 		foreach (QGraphicsItem * item, childItems()) {
524 			Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
525 			if (stripbit == NULL) continue;
526 
527 			if (stripbit->removed()) {
528 				afterCut += stripbit->makeRemovedString();
529 			}
530 		}
531 
532         collectTo(affectedConnectors);
533 
534 		InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
535 		if (infoGraphicsView != NULL) {
536 			QString changeType = (connect) ? tr("Restored") : tr("Cut") ;
537 			QString changeText = tr("%1 %n strip(s)", "", changeCount).arg(changeType);
538             QList<ConnectorItem *> affected = affectedConnectors.toList();
539 			infoGraphicsView->changeBus(this, connect, m_beforeCut, afterCut, affected, changeText, m_layout, m_layout);  // affectedConnectors is used for updating ratsnests; it's just a wild guess
540 		}
541 
542 		return;
543 	}
544     if (viewID() != ViewLayer::BreadboardView) return;
545 
546 	foreach (BusShared * busShared, m_buses) delete busShared;
547 	m_buses.clear();
548 
549 	foreach (QGraphicsItem * item, childItems()) {
550 		Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
551 		if (stripbit == NULL) {
552 			ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
553 			if (connectorItem == NULL) continue;
554 
555 			connectorItem->connector()->connectorShared()->setBus(NULL);
556 			connectorItem->connector()->setBus(NULL);
557 			continue;
558 		}
559 
560 	}
561 
562 	QString busPropertyString;
563 	foreach (QGraphicsItem * item, childItems()) {
564 		Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
565 		if (stripbit == NULL) continue;
566 
567 		if (stripbit->removed()) {
568             busPropertyString += stripbit->makeRemovedString();
569 		}
570 	}
571 
572 	QList<ConnectorItem *> visited;
573     for (int iy = 0; iy < m_y; iy++) {
574         for (int ix = 0; ix < m_x; ix++) {
575             StripConnector * sc = getStripConnector(ix, iy);
576             if (visited.contains(sc->connectorItem)) continue;
577 
578             QList<ConnectorItem *> connected;
579             collectConnected(ix, iy, connected);
580             visited.append(connected);
581             nextBus(connected);
582         }
583     }
584 
585 	modelPart()->clearBuses();
586 	modelPart()->initBuses();
587 	modelPart()->setLocalProp("buses",  busPropertyString);
588 
589 
590 
591     QList<ConnectorItem *> visited2;
592 	foreach (ConnectorItem * connectorItem, cachedConnectorItems()) {
593 		connectorItem->restoreColor(visited2);
594 	}
595 
596     update();
597 }
598 
collectConnected(int ix,int iy,QList<ConnectorItem * > & connected)599 void Stripboard::collectConnected(int ix, int iy, QList<ConnectorItem *> & connected) {
600     StripConnector * sc = getStripConnector(ix, iy);
601     if (connected.contains(sc->connectorItem)) return;
602 
603     connected << sc->connectorItem;
604 
605     if (sc->right != NULL && !sc->right->removed()) {
606         collectConnected(ix + 1, iy, connected);
607 
608     }
609     if (sc->down != NULL && !sc->down->removed()) {
610         collectConnected(ix, iy + 1, connected);
611     }
612 
613     StripConnector * left = ix > 0 ? getStripConnector(ix - 1, iy) : NULL;
614     if (left != NULL && left->right != NULL && !left->right->removed()) {
615         collectConnected(ix - 1, iy, connected);
616     }
617 
618     StripConnector * up = iy > 0 ? getStripConnector(ix, iy - 1) : NULL;
619     if (up != NULL && up->down != NULL && !up->down->removed()) {
620         collectConnected(ix, iy - 1, connected);
621     }
622 }
623 
nextBus(QList<ConnectorItem * > & soFar)624 void Stripboard::nextBus(QList<ConnectorItem *> & soFar)
625 {
626 	if (soFar.count() > 1) {
627 		BusShared * busShared = new BusShared(QString::number(m_buses.count()));
628 		m_buses.append(busShared);
629 		foreach (ConnectorItem * connectorItem, soFar) {
630 			busShared->addConnectorShared(connectorItem->connector()->connectorShared());
631 		}
632 	}
633 	soFar.clear();
634 }
635 
setProp(const QString & prop,const QString & value)636 void Stripboard::setProp(const QString & prop, const QString & value)
637 {
638     if (prop.compare("buses") == 0) {
639 	    QStringList removed = value.split(" ", QString::SkipEmptyParts);
640 	    foreach (QGraphicsItem * item, childItems()) {
641 		    Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
642 		    if (stripbit == NULL) continue;
643 
644             QString removedString = stripbit->makeRemovedString();
645             removedString.chop(1);          // remove trailing space
646 		    bool remove = removed.contains(removedString);
647 		    stripbit->setRemoved(remove);
648 		    if (remove) {
649 			    removed.removeOne(removedString);
650 		    }
651 	    }
652 
653 	    reinitBuses(false);
654         return;
655     }
656 
657     if (prop.compare("layout") == 0) {
658         m_layout = value;
659     }
660 
661 	Perfboard::setProp(prop, value);
662 }
663 
getConnectedColor(ConnectorItem * ci,QBrush & brush,QPen & pen,double & opacity,double & negativePenWidth,bool & negativeOffsetRect)664 void Stripboard::getConnectedColor(ConnectorItem * ci, QBrush &brush, QPen &pen, double & opacity, double & negativePenWidth, bool & negativeOffsetRect) {
665 	Perfboard::getConnectedColor(ci, brush, pen, opacity, negativePenWidth, negativeOffsetRect);
666 	opacity *= .66667;
667 }
668 
restoreRowColors(Stripbit * stripbit)669 void Stripboard::restoreRowColors(Stripbit * stripbit)
670 {
671 	// TODO: find a quick way to update the buses (and connectorItem colors) in just the row...
672 	Q_UNUSED(stripbit);
673 	//int y = stripbit->y();
674 	//stripbit = m_firstColumn.at(y);
675 
676 
677 	//ci->restoreColor(false, 0, false);
678 }
679 
getRowLabel()680 QString Stripboard::getRowLabel() {
681 	return tr("rows");
682 }
683 
getColumnLabel()684 QString Stripboard::getColumnLabel() {
685 	return tr("columns");
686 }
687 
makeInitialPath()688 void Stripboard::makeInitialPath() {
689 	ConnectorItem * ciFirst = NULL;
690 	ConnectorItem * ciNextH = NULL;
691 	ConnectorItem * ciNextV = NULL;
692 	foreach (ConnectorItem * ci, cachedConnectorItems()) {
693 		int cx, cy;
694 		getXY(cx, cy, ci->connectorSharedName());
695 		if (cy == 0 && cx == 0) {
696 			ciFirst = ci;
697 			break;
698 		}
699 	}
700 	foreach (ConnectorItem * ci, cachedConnectorItems()) {
701 		int cx, cy;
702 		getXY(cx, cy, ci->connectorSharedName());
703 		if (cy == 0 && cx == 1) {
704 			ciNextH = ci;
705             if (ciNextV) break;
706 		}
707 		else if (cy == 1 && cx == 0) {
708 			ciNextV = ci;
709             if (ciNextH) break;
710 		}
711 	}
712 
713 	if (ciFirst == NULL) return;
714 	if (ciNextH == NULL) return;
715 	if (ciNextV == NULL) return;
716 
717 	QRectF r1 = ciFirst->rect();
718 	QRectF rh = ciNextH->rect();
719 
720 	double h = r1.height();
721 	double w = rh.center().x() - r1.center().x();
722 
723 	r1.moveTo(-(r1.width() / 2), 0);
724 	rh.moveTo(w - (rh.width() / 2), 0);
725 
726 	HPath.addRect(0, 0, w / 2, h);
727 	HPath.arcTo(r1, 90, -180);
728 	HPath.addRect(w / 2, 0, w / 2, h);
729 	HPath.moveTo(w, 0);
730 	HPath.arcTo(rh, 90, 180);
731 
732     r1 = ciFirst->rect();
733     QRectF rv = ciNextV->rect();
734 
735 	h = rv.center().y() - r1.center().y();
736 	w = r1.width();
737 
738 	r1.moveTo(0, -(r1.height() / 2));
739 	rv.moveTo(0, h - (rv.height() / 2));
740 
741 	VPath.addRect(0, 0, w, h / 2);
742 	VPath.arcTo(r1, 0, -180);
743 	VPath.addRect(0, h / 2, w, h / 2);
744 	VPath.moveTo(0, h);
745 	VPath.arcTo(rv, 0, 180);
746 }
747 
getStripConnector(int ix,int iy)748 StripConnector * Stripboard::getStripConnector(int ix, int iy) {
749     return m_strips.at((iy * m_x) + ix);
750 }
751 
swapEntry(const QString & text)752 void Stripboard::swapEntry(const QString & text) {
753 
754     FamilyPropertyComboBox * comboBox = qobject_cast<FamilyPropertyComboBox *>(sender());
755     if (comboBox == NULL) return;
756 
757     if (comboBox->prop().compare("layout", Qt::CaseInsensitive) == 0) {
758         InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
759 	    if (infoGraphicsView == NULL) return;
760 
761         QString afterCut;
762         if (text.compare(HorizontalString, Qt::CaseInsensitive) == 0) {
763             foreach (QGraphicsItem * item, childItems()) {
764 		        Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
765 		        if (stripbit == NULL) continue;
766 
767                 if (!stripbit->horizontal()) {
768                     afterCut += stripbit->makeRemovedString();
769                 }
770             }
771         }
772         else if (text.compare(VerticalString, Qt::CaseInsensitive) == 0) {
773             foreach (QGraphicsItem * item, childItems()) {
774 		        Stripbit * stripbit = dynamic_cast<Stripbit *>(item);
775 		        if (stripbit == NULL) continue;
776 
777                 if (stripbit->horizontal()) {
778                     afterCut += stripbit->makeRemovedString();
779                 }
780             }
781         }
782         else {
783             for (int i = 0; i < StripLayouts.count(); i++) {
784                 if (StripLayouts.at(i).name.compare(text) == 0) {
785                     StripLayout stripLayout = StripLayouts.at(i);
786                     QMap<QString, QString> propsMap;
787                     propsMap.insert("size", QString("%1.%2").arg(stripLayout.columns).arg(stripLayout.rows));
788                     propsMap.insert("type", "Stripboard");
789                     propsMap.insert("buses", stripLayout.buses);
790                     propsMap.insert("layout", text);
791                     InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
792                     if (infoGraphicsView != NULL) {
793                         infoGraphicsView->swap(family(), "size", propsMap, this);
794                     }
795                     return;
796                 }
797             }
798         }
799 
800         if (!afterCut.isEmpty()) {
801             initCutting(NULL);
802 	        QString changeText = tr("%1 layout").arg(text);
803             QSet<ConnectorItem *> affectedConnectors;
804             collectTo(affectedConnectors);
805             QList<ConnectorItem *> affected = affectedConnectors.toList();
806             infoGraphicsView->changeBus(this, true, m_beforeCut, afterCut, affected, changeText, m_layout, text);  // affectedConnectors is used for updating ratsnests; it's just a wild guess
807         }
808 
809         return;
810     }
811 
812 
813     Perfboard::swapEntry(text);
814 }
815 
collectTo(QSet<ConnectorItem * > & affectedConnectors)816 void Stripboard::collectTo(QSet<ConnectorItem *> & affectedConnectors) {
817     foreach (ConnectorItem * connectorItem, cachedConnectorItems()) {
818         foreach (ConnectorItem * toConnectorItem, connectorItem->connectedToItems()) {
819             affectedConnectors.insert(toConnectorItem);
820         }
821     }
822 }
823 
initStripLayouts()824 void Stripboard::initStripLayouts() {
825 	QFile file(":/resources/templates/stripboards.xml");
826 	QString errorStr;
827 	int errorLine;
828 	int errorColumn;
829 	QDomDocument domDocument;
830 
831 	if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
832 		DebugDialog::debug(QString("unable to parse stripboards.xml: %1 %2 %3").arg(errorStr).arg(errorLine).arg(errorColumn));
833 		return;
834 	}
835 
836     QDomElement root = domDocument.documentElement();
837     QDomElement stripboard = root.firstChildElement("stripboard");
838     while (!stripboard.isNull()) {
839         QString name = stripboard.attribute("name");
840         bool rok;
841         int rows = stripboard.attribute("rows").toInt(&rok);
842         bool cok;
843         int columns = stripboard.attribute("columns").toInt(&cok);
844         QString buses = stripboard.attribute("buses");
845         stripboard = stripboard.nextSiblingElement("stripboard");
846         if (!rok) continue;
847         if (!cok) continue;
848         if (name.isEmpty() || buses.isEmpty()) continue;
849 
850         StripLayout stripLayout(name, rows, columns, buses);
851         StripLayouts.append(stripLayout);
852     }
853 }
854 
collectValues(const QString & family,const QString & prop,QString & value)855 QStringList Stripboard::collectValues(const QString & family, const QString & prop, QString & value) {
856     QStringList values = Perfboard::collectValues(family, prop, value);
857 
858 	if (prop.compare("layout", Qt::CaseInsensitive) == 0) {
859         foreach (StripLayout stripLayout, StripLayouts) {
860             values.append(stripLayout.name);
861             value = m_layout;
862         }
863 	}
864 
865 	return values;
866 }
867