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: 6998 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-28 13:51:10 +0200 (So, 28. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include <qmath.h>
28 
29 #include "pcbsketchwidget.h"
30 #include "../debugdialog.h"
31 #include "../items/tracewire.h"
32 #include "../items/virtualwire.h"
33 #include "../items/resizableboard.h"
34 #include "../items/pad.h"
35 #include "../waitpushundostack.h"
36 #include "../connectors/connectoritem.h"
37 #include "../items/moduleidnames.h"
38 #include "../items/partlabel.h"
39 #include "../fsvgrenderer.h"
40 #include "../autoroute/autorouteprogressdialog.h"
41 #include "../autoroute/drc.h"
42 #include "../items/groundplane.h"
43 #include "../items/jumperitem.h"
44 #include "../utils/autoclosemessagebox.h"
45 #include "../utils/graphicsutils.h"
46 #include "../utils/textutils.h"
47 #include "../utils/folderutils.h"
48 #include "../processeventblocker.h"
49 #include "../autoroute/cmrouter/tileutils.h"
50 #include "../autoroute/cmrouter/cmrouter.h"
51 #include "../autoroute/panelizer.h"
52 #include "../autoroute/autoroutersettingsdialog.h"
53 #include "../svg/groundplanegenerator.h"
54 #include "../items/logoitem.h"
55 #include "../dialogs/groundfillseeddialog.h"
56 #include "../version/version.h"
57 
58 #include <limits>
59 #include <QApplication>
60 #include <QScrollBar>
61 #include <QDialog>
62 #include <QRadioButton>
63 #include <QVBoxLayout>
64 #include <QGroupBox>
65 #include <QDialogButtonBox>
66 #include <QSettings>
67 #include <QPushButton>
68 #include <QMessageBox>
69 #include <QNetworkAccessManager>
70 #include <QInputDialog>
71 
72 
73 /////////////////////////////////////////////////////
74 
75 static const int MAX_INT = std::numeric_limits<int>::max();
76 static const double StrokeWidthIncrement = 50;
77 
78 static QString PCBTraceColor1 = "trace1";
79 static QString PCBTraceColor = "trace";
80 
81 QSizeF PCBSketchWidget::m_jumperItemSize = QSizeF(0, 0);
82 
83 struct DistanceThing {
84 	int distance;
85 	bool fromConnector0;
86 };
87 
88 QHash <ConnectorItem *, DistanceThing *> distances;
89 
bySize(QList<ConnectorItem * > * l1,QList<ConnectorItem * > * l2)90 bool bySize(QList<ConnectorItem *> * l1, QList<ConnectorItem *> * l2) {
91 	return l1->count() >= l2->count();
92 }
93 
distanceLessThan(ConnectorItem * end0,ConnectorItem * end1)94 bool distanceLessThan(ConnectorItem * end0, ConnectorItem * end1) {
95 	if (end0->connectorType() == Connector::Male && end1->connectorType() == Connector::Female) {
96 		return true;
97 	}
98 	if (end1->connectorType() == Connector::Male && end0->connectorType() == Connector::Female) {
99 		return false;
100 	}
101 
102 	DistanceThing * dt0 = distances.value(end0, NULL);
103 	DistanceThing * dt1 = distances.value(end1, NULL);
104 	if (dt0 && dt1) {
105 		return dt0->distance <= dt1->distance;
106 	}
107 
108 	if (dt0) {
109 		return true;
110 	}
111 
112 	if (dt1) {
113 		return false;
114 	}
115 
116 	return true;
117 }
118 
119 //////////////////////////////////////////////////////
120 
121 const char * PCBSketchWidget::FakeTraceProperty = "FakeTrace";
122 
PCBSketchWidget(ViewLayer::ViewID viewID,QWidget * parent)123 PCBSketchWidget::PCBSketchWidget(ViewLayer::ViewID viewID, QWidget *parent)
124     : SketchWidget(viewID, parent)
125 {
126     m_rolloverQuoteDialog = NULL;
127     m_requestQuoteTimer.setSingleShot(true);
128     m_requestQuoteTimer.setInterval(100);
129     connect(&m_requestQuoteTimer, SIGNAL(timeout()), this, SLOT(requestQuoteNow()));
130 	m_resizingBoard = NULL;
131 	m_resizingJumperItem = NULL;
132 	m_viewName = QObject::tr("PCB View");
133 	m_shortName = QObject::tr("pcb");
134 	initBackgroundColor();
135 
136 	m_routingStatus.zero();
137 	m_cleanType = noClean;
138 }
139 
setWireVisible(Wire * wire)140 void PCBSketchWidget::setWireVisible(Wire * wire)
141 {
142 	bool visible = wire->getRatsnest() || (wire->isTraceType(this->getTraceFlag()));
143 	wire->setVisible(visible);
144 	wire->setEverVisible(visible);
145 }
146 
addViewLayers()147 void PCBSketchWidget::addViewLayers() {
148     setViewLayerIDs(ViewLayer::Silkscreen1, ViewLayer::Copper0Trace, ViewLayer::Copper0, ViewLayer::PcbRuler, ViewLayer::PcbNote);
149 	addViewLayersAux(ViewLayer::layersForView(ViewLayer::PCBView), ViewLayer::layersForViewFromBelow(ViewLayer::PCBView));
150 
151 	ViewLayer * silkscreen1 = m_viewLayers.value(ViewLayer::Silkscreen1);
152 	ViewLayer * silkscreen1Label = m_viewLayers.value(ViewLayer::Silkscreen1Label);
153 	if (silkscreen1 && silkscreen1Label) {
154 		silkscreen1Label->setParentLayer(silkscreen1);
155 	}
156 	ViewLayer * silkscreen0 = m_viewLayers.value(ViewLayer::Silkscreen0);
157 	ViewLayer * silkscreen0Label = m_viewLayers.value(ViewLayer::Silkscreen0Label);
158 	if (silkscreen0 && silkscreen0Label) {
159 		silkscreen0Label->setParentLayer(silkscreen0);
160 	}
161 
162 	ViewLayer * copper0 = m_viewLayers.value(ViewLayer::Copper0);
163 	ViewLayer * copper0Trace = m_viewLayers.value(ViewLayer::Copper0Trace);
164 	ViewLayer * copper1 = m_viewLayers.value(ViewLayer::Copper1);
165 	ViewLayer * copper1Trace = m_viewLayers.value(ViewLayer::Copper1Trace);
166 	if (copper0 && copper0Trace) {
167 		copper0Trace->setParentLayer(copper0);
168 	}
169 	ViewLayer * groundPlane0 = m_viewLayers.value(ViewLayer::GroundPlane0);
170 	if (copper0 && groundPlane0) {
171 		groundPlane0->setParentLayer(copper0);
172 	}
173 	if (copper1 && copper1Trace) {
174 		copper1Trace->setParentLayer(copper1);
175 	}
176 	ViewLayer * groundPlane1 = m_viewLayers.value(ViewLayer::GroundPlane1);
177 	if (copper1 && groundPlane1) {
178 		groundPlane1->setParentLayer(copper1);
179 	}
180 
181 	// disable these for now
182 	//viewLayer = m_viewLayers.value(ViewLayer::Keepout);
183 	//viewLayer->action()->setEnabled(false);
184 
185 	setBoardLayers(1, false);
186 }
187 
188 
189 
multiLayerGetViewLayerID(ModelPart * modelPart,ViewLayer::ViewID viewID,ViewLayer::ViewLayerPlacement viewLayerPlacement,LayerList & layerList)190 ViewLayer::ViewLayerID PCBSketchWidget::multiLayerGetViewLayerID(ModelPart * modelPart, ViewLayer::ViewID viewID, ViewLayer::ViewLayerPlacement viewLayerPlacement, LayerList & layerList)
191 {
192     if (modelPart->itemType() == ModelPart::CopperFill) {
193 	    if (viewLayerPlacement == ViewLayer::NewBottom) return ViewLayer::GroundPlane0;
194 	    else if (viewLayerPlacement == ViewLayer::NewTop) return ViewLayer::GroundPlane1;
195     }
196 
197 	// privilege Copper if it's available
198 	ViewLayer::ViewLayerID wantLayer = (modelPart->flippedSMD() && viewLayerPlacement == ViewLayer::NewTop) ? ViewLayer::Copper1 : ViewLayer::Copper0;
199 	if (layerList.contains(wantLayer)) return wantLayer;
200 
201     return SketchWidget::multiLayerGetViewLayerID(modelPart, viewID, viewLayerPlacement, layerList);
202 }
203 
canDeleteItem(QGraphicsItem * item,int count)204 bool PCBSketchWidget::canDeleteItem(QGraphicsItem * item, int count)
205 {
206 	VirtualWire * wire = dynamic_cast<VirtualWire *>(item);
207 	if (wire != NULL && count > 1) return false;
208 
209 	return SketchWidget::canDeleteItem(item, count);
210 }
211 
canCopyItem(QGraphicsItem * item,int count)212 bool PCBSketchWidget::canCopyItem(QGraphicsItem * item, int count)
213 {
214 	VirtualWire * wire = dynamic_cast<VirtualWire *>(item);
215 	if (wire != NULL) {
216 		if (wire->getRatsnest()) return false;
217 	}
218 
219 	return SketchWidget::canCopyItem(item, count);
220 }
221 
canChainWire(Wire * wire)222 bool PCBSketchWidget::canChainWire(Wire * wire) {
223 	bool result = SketchWidget::canChainWire(wire);
224 	if (!result) return result;
225 
226 	if (wire->getRatsnest()) {
227 		ConnectorItem * c0 = wire->connector0()->firstConnectedToIsh();
228 		if (c0 == NULL) return false;
229 
230 		ConnectorItem * c1 = wire->connector1()->firstConnectedToIsh();
231 		if (c1 == NULL) return false;
232 
233 		return !c0->wiredTo(c1, (ViewGeometry::NormalFlag | ViewGeometry::PCBTraceFlag | ViewGeometry::RatsnestFlag | ViewGeometry::SchematicTraceFlag) ^ getTraceFlag());
234 	}
235 
236 	return result;
237 }
238 
239 
createTrace(Wire * wire,bool useLastWireColor)240 void PCBSketchWidget::createTrace(Wire * wire, bool useLastWireColor) {
241 	QString commandString = tr("Create Trace from Ratsnest");
242 	SketchWidget::createTrace(wire, commandString, getTraceFlag(), useLastWireColor);
243 	ensureTraceLayerVisible();
244 }
245 
excludeFromAutoroute(bool exclude)246 void PCBSketchWidget::excludeFromAutoroute(bool exclude)
247 {
248 	foreach (QGraphicsItem * item, scene()->selectedItems()) {
249 		TraceWire * wire = dynamic_cast<TraceWire *>(item);
250 
251 		if (wire) {
252 			if (!wire->isTraceType(getTraceFlag())) continue;
253 
254 			QList<Wire *> wires;
255 			QList<ConnectorItem *> ends;
256 			wire->collectChained(wires, ends);
257 			foreach (Wire * w, wires) {
258 				w->setAutoroutable(!exclude);
259 			}
260 			continue;
261 		}
262 
263 		JumperItem * jumperItem = dynamic_cast<JumperItem *>(item);
264 		if (jumperItem) {
265 			jumperItem->setAutoroutable(!exclude);
266 			continue;
267 		}
268 
269 		Via * via = dynamic_cast<Via *>(item);
270 		if (via) {
271 			via->setAutoroutable(!exclude);
272 			continue;
273 		}
274 	}
275 }
276 
selectAllExcludedTraces()277 void PCBSketchWidget::selectAllExcludedTraces()
278 {
279 	selectAllXTraces(false, QObject::tr("Select all 'Don't autoroute' traces"), autorouteTypePCB());
280 }
281 
selectAllIncludedTraces()282 void PCBSketchWidget::selectAllIncludedTraces()
283 {
284 	selectAllXTraces(true, QObject::tr("Select all autorouteable traces"), autorouteTypePCB());
285 }
286 
selectAllXTraces(bool autoroutable,const QString & cmdText,bool forPCB)287 void PCBSketchWidget::selectAllXTraces(bool autoroutable, const QString & cmdText, bool forPCB)
288 {
289 	QList<Wire *> wires;
290     QList<QGraphicsItem *> items;
291     if (forPCB) {
292         int boardCount;
293         ItemBase * board = findSelectedBoard(boardCount);
294         if (boardCount == 0  && autorouteTypePCB()) {
295             QMessageBox::critical(this, tr("Fritzing"),
296                        tr("Your sketch does not have a board yet! Please add a PCB in order to use this selection operation."));
297             return;
298         }
299         if (board == NULL) {
300             QMessageBox::critical(this, tr("Fritzing"),
301                        tr("Please click on a PCB first--this selection operation only works for one board at a time."));
302             return;
303         }
304 
305         items = scene()->collidingItems(board);
306     }
307     else {
308         items = scene()->items();
309     }
310 	foreach (QGraphicsItem * item, items) {
311 		TraceWire * wire = dynamic_cast<TraceWire *>(item);
312 		if (wire == NULL) continue;
313 
314 		if (!wire->isTraceType(getTraceFlag())) continue;
315 
316 		if (wire->getAutoroutable() == autoroutable) {
317 			wires.append(wire);
318 		}
319 	}
320 
321 	QUndoCommand * parentCommand = new QUndoCommand(cmdText);
322 
323 	stackSelectionState(false, parentCommand);
324 	SelectItemCommand * selectItemCommand = new SelectItemCommand(this, SelectItemCommand::NormalSelect, parentCommand);
325 	foreach (Wire * wire, wires) {
326 		selectItemCommand->addRedo(wire->id());
327 	}
328 
329 	scene()->clearSelection();
330 	m_undoStack->push(parentCommand);
331 }
332 
333 
334 
hoverEnterPartConnectorMessage(QGraphicsSceneHoverEvent * event,ConnectorItem * item)335 const QString & PCBSketchWidget::hoverEnterPartConnectorMessage(QGraphicsSceneHoverEvent * event, ConnectorItem * item)
336 {
337 	Q_UNUSED(event);
338 	Q_UNUSED(item);
339 
340 	static QString message = tr("Click this connector to drag out a new trace.");
341 
342 	return message;
343 }
344 
addDefaultParts()345 void PCBSketchWidget::addDefaultParts() {
346 
347 	long newID = ItemBase::getNextID();
348 	ViewGeometry viewGeometry;
349 	viewGeometry.setLoc(QPointF(0, 0));
350 
351 	// have to put this off until later, because positioning the item doesn't work correctly until the view is visible
352 	m_addedDefaultPart = addItem(referenceModel()->retrieveModelPart(ModuleIDNames::TwoSidedRectanglePCBModuleIDName), ViewLayer::NewTop, BaseCommand::CrossView, viewGeometry, newID, -1, NULL);
353 	m_addDefaultParts = true;
354 
355 	changeBoardLayers(2, true);
356 }
357 
showEvent(QShowEvent * event)358 void PCBSketchWidget::showEvent(QShowEvent * event) {
359 	SketchWidget::showEvent(event);
360 	dealWithDefaultParts();
361 }
362 
dealWithDefaultParts()363 void PCBSketchWidget::dealWithDefaultParts() {
364 	if (!m_addDefaultParts) return;
365 	if  (m_addedDefaultPart == NULL) return;
366 
367 	m_addDefaultParts = false;
368 
369 	QSizeF vpSize = this->viewport()->size();
370 	QSizeF partSize(300, 200);
371 
372 	//if (vpSize.height() < helpSize.height() + 50 + partSize.height()) {
373 		//vpSize.setWidth(vpSize.width() - verticalScrollBar()->width());
374 	//}
375 
376 	QPointF p;
377 	p.setX((int) ((vpSize.width() - partSize.width()) / 2.0));
378 	p.setY((int) ((vpSize.height() - partSize.height()) / 2.0));
379 
380 	// place it
381 	QPointF q = mapToScene(p.toPoint());
382 	m_addedDefaultPart->setPos(q);
383 	alignOneToGrid(m_addedDefaultPart);
384 	ResizableBoard * rb = qobject_cast<ResizableBoard *>(m_addedDefaultPart);
385 	if (rb) rb->resizePixels(partSize.width(), partSize.height(), m_viewLayers);
386 	QTimer::singleShot(10, this, SLOT(vScrollToZero()));
387 
388     // set both layers active by default
389 	setLayerActive(ViewLayer::Copper1, true);
390 	setLayerActive(ViewLayer::Copper0, true);
391 	setLayerActive(ViewLayer::Silkscreen1, true);
392 	setLayerActive(ViewLayer::Silkscreen0, true);
393 }
394 
395 
setClipEnds(ClipableWire * vw,bool clipEnds)396 void PCBSketchWidget::setClipEnds(ClipableWire * vw, bool clipEnds) {
397 	vw->setClipEnds(clipEnds);
398 }
399 
getDragWireViewLayerID(ConnectorItem * connectorItem)400 ViewLayer::ViewLayerID PCBSketchWidget::getDragWireViewLayerID(ConnectorItem * connectorItem) {
401 	switch (connectorItem->attachedToViewLayerID()) {
402 		case ViewLayer::Copper1:
403 		case ViewLayer::Copper1Trace:
404 		case ViewLayer::GroundPlane1:
405 			return ViewLayer::Copper1Trace;
406 		default:
407 			return ViewLayer::Copper0Trace;
408 	}
409 }
410 
getWireViewLayerID(const ViewGeometry & viewGeometry,ViewLayer::ViewLayerPlacement viewLayerPlacement)411 ViewLayer::ViewLayerID PCBSketchWidget::getWireViewLayerID(const ViewGeometry & viewGeometry, ViewLayer::ViewLayerPlacement viewLayerPlacement) {
412 	if (viewGeometry.getRatsnest()) {
413 		return ViewLayer::PcbRatsnest;
414 	}
415 
416 	if (viewGeometry.getAnyTrace()) {
417 		switch (viewLayerPlacement) {
418 			case ViewLayer::NewTop:
419 				return ViewLayer::Copper1Trace;
420 			default:
421 				return ViewLayer::Copper0Trace;
422 		}
423 	}
424 
425 	switch (viewLayerPlacement) {
426 		case ViewLayer::NewTop:
427 			return ViewLayer::Copper1Trace;
428 		default:
429 			return m_wireViewLayerID;
430 	}
431 }
432 
initWire(Wire * wire,int penWidth)433 void PCBSketchWidget::initWire(Wire * wire, int penWidth) {
434 	Q_UNUSED(penWidth);
435 	if (wire->getRatsnest()) return;
436 
437 	wire->setColorString(traceColor(wire->connector0()), 1.0, false);
438 	wire->setPenWidth(1, this, 2);
439 }
440 
autorouteTypePCB()441 bool PCBSketchWidget::autorouteTypePCB() {
442 	return true;
443 }
444 
traceColor(ConnectorItem * forColor)445 const QString & PCBSketchWidget::traceColor(ConnectorItem * forColor) {
446 	switch(forColor->attachedToViewLayerID()) {
447 		case ViewLayer::Copper1:
448 		case ViewLayer::Copper1Trace:
449 		case ViewLayer::GroundPlane1:
450 			return PCBTraceColor1;
451 		default:
452 			return PCBTraceColor;
453 	}
454 }
455 
traceColor(ViewLayer::ViewLayerPlacement viewLayerPlacement)456 const QString & PCBSketchWidget::traceColor(ViewLayer::ViewLayerPlacement viewLayerPlacement) {
457 	if (viewLayerPlacement == ViewLayer::NewTop) {
458 		return PCBTraceColor1;
459 	}
460 
461 	return PCBTraceColor;
462 }
463 
cleanType()464 PCBSketchWidget::CleanType PCBSketchWidget::cleanType() {
465 	return m_cleanType;
466 }
467 
ensureTraceLayersVisible()468 void PCBSketchWidget::ensureTraceLayersVisible() {
469 	ensureLayerVisible(ViewLayer::Copper0);
470 	ensureLayerVisible(ViewLayer::Copper0Trace);
471 	ensureLayerVisible(ViewLayer::GroundPlane0);
472 	if (m_boardLayers == 2) {
473 		ensureLayerVisible(ViewLayer::Copper1);
474 		ensureLayerVisible(ViewLayer::Copper1Trace);
475 		ensureLayerVisible(ViewLayer::GroundPlane1);
476 	}
477 }
478 
ensureTraceLayerVisible()479 void PCBSketchWidget::ensureTraceLayerVisible() {
480 	ensureLayerVisible(ViewLayer::Copper0);
481 	ensureLayerVisible(ViewLayer::Copper0Trace);
482 }
483 
canChainMultiple()484 bool PCBSketchWidget::canChainMultiple() {
485 	return false;
486 }
487 
setNewPartVisible(ItemBase * itemBase)488 void PCBSketchWidget::setNewPartVisible(ItemBase * itemBase) {
489 	if (itemBase->itemType() == ModelPart::Breadboard  ||
490 		itemBase->itemType() == ModelPart::Symbol ||
491 		itemBase->itemType() == ModelPart::SchematicSubpart ||
492 		itemBase->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName) ||
493         itemBase->moduleID().endsWith(ModuleIDNames::PowerModuleIDName))
494 	{
495 		// don't need to see the breadboard in the other views
496 		// but it's there so connections can be more easily synched between views
497 		itemBase->setVisible(false);
498 		itemBase->setEverVisible(false);
499 	}
500 }
501 
canDropModelPart(ModelPart * modelPart)502 bool PCBSketchWidget::canDropModelPart(ModelPart * modelPart) {
503 	if (!SketchWidget::canDropModelPart(modelPart)) return false;
504 
505 	if (Board::isBoard(modelPart)) {
506         return matchesLayer(modelPart);
507     }
508 
509 	switch (modelPart->itemType()) {
510 		case ModelPart::Logo:
511             if (modelPart->moduleID().contains("schematic", Qt::CaseInsensitive)) return false;
512             if (modelPart->moduleID().contains("breadboard", Qt::CaseInsensitive)) return false;
513 		case ModelPart::Jumper:
514 		case ModelPart::Ruler:
515 		case ModelPart::CopperFill:
516 			return true;
517 		case ModelPart::Wire:
518 		case ModelPart::Breadboard:
519 		case ModelPart::Symbol:
520 		case ModelPart::SchematicSubpart:
521 			// can't drag and drop these parts in this view
522 			return false;
523 		default:
524 			return !modelPart->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName);
525 	}
526 
527 	return true;
528 }
529 
bothEndsConnected(Wire * wire,ViewGeometry::WireFlags flag,ConnectorItem * oneEnd,QList<Wire * > & wires,QList<ConnectorItem * > & partConnectorItems)530 bool PCBSketchWidget::bothEndsConnected(Wire * wire, ViewGeometry::WireFlags flag, ConnectorItem * oneEnd, QList<Wire *> & wires, QList<ConnectorItem *> & partConnectorItems)
531 {
532 	QList<Wire *> visited;
533 	return bothEndsConnectedAux(wire, flag, oneEnd, wires, partConnectorItems, visited);
534 }
535 
536 
bothEndsConnectedAux(Wire * wire,ViewGeometry::WireFlags flag,ConnectorItem * oneEnd,QList<Wire * > & wires,QList<ConnectorItem * > & partConnectorItems,QList<Wire * > & visited)537 bool PCBSketchWidget::bothEndsConnectedAux(Wire * wire, ViewGeometry::WireFlags flag, ConnectorItem * oneEnd, QList<Wire *> & wires, QList<ConnectorItem *> & partConnectorItems, QList<Wire *> & visited)
538 {
539 	if (visited.contains(wire)) return false;
540 	visited.append(wire);
541 
542 	bool result = false;
543 	ConnectorItem * otherEnd = wire->otherConnector(oneEnd);
544 	foreach (ConnectorItem * toConnectorItem, otherEnd->connectedToItems()) {
545 		if (partConnectorItems.contains(toConnectorItem)) {
546 			result = true;
547 			continue;
548 		}
549 
550 		if (toConnectorItem->attachedToItemType() != ModelPart::Wire) continue;
551 
552 		Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
553 		ViewGeometry::WireFlags wflag = w->wireFlags() & (ViewGeometry::RatsnestFlag | getTraceFlag());
554 		if (wflag != flag) continue;
555 
556 		result = bothEndsConnectedAux(w, flag, toConnectorItem, wires, partConnectorItems, visited) || result;   // let it recurse
557 	}
558 
559 	if (result) {
560 		wires.removeOne(wire);
561 	}
562 
563 	return result;
564 }
565 
canCreateWire(Wire * dragWire,ConnectorItem * from,ConnectorItem * to)566 bool PCBSketchWidget::canCreateWire(Wire * dragWire, ConnectorItem * from, ConnectorItem * to)
567 {
568 	Q_UNUSED(dragWire);
569 	return ((from != NULL) && (to != NULL));
570 }
571 
findNearestPartConnectorItem(ConnectorItem * fromConnectorItem)572 ConnectorItem * PCBSketchWidget::findNearestPartConnectorItem(ConnectorItem * fromConnectorItem) {
573 	// find the nearest part to fromConnectorItem
574 	Wire * wire = qobject_cast<Wire *>(fromConnectorItem->attachedTo());
575 	if (wire == NULL) return NULL;
576 
577 	QList<ConnectorItem *> ends;
578 	calcDistances(wire, ends);
579 	clearDistances();
580 	if (ends.count() < 1) return NULL;
581 
582 	return ends[0];
583 }
584 
calcDistances(Wire * wire,QList<ConnectorItem * > & ends)585 void PCBSketchWidget::calcDistances(Wire * wire, QList<ConnectorItem *> & ends) {
586 	QList<Wire *> chained;
587 	wire->collectChained(chained, ends);
588 	if (ends.count() < 2) return;
589 
590 	clearDistances();
591 	foreach (ConnectorItem * end, ends) {
592 		bool fromConnector0;
593 		QList<Wire *> distanceWires;
594 		int distance = calcDistance(wire, end, 0, distanceWires, fromConnector0);
595 		DistanceThing * dt = new DistanceThing;
596 		dt->distance = distance;
597 		dt->fromConnector0 = fromConnector0;
598 		DebugDialog::debug(QString("distance %1 %2 %3, %4 %5")
599 			.arg(end->attachedToID()).arg(end->attachedToTitle()).arg(end->connectorSharedID())
600 			.arg(distance).arg(fromConnector0 ? "connector0" : "connector1"));
601 		distances.insert(end, dt);
602 	}
603 	qSort(ends.begin(), ends.end(), distanceLessThan);
604 
605 }
606 
clearDistances()607 void PCBSketchWidget::clearDistances() {
608 	foreach (ConnectorItem * c, distances.keys()) {
609 		DistanceThing * dt = distances.value(c, NULL);
610 		if (dt) delete dt;
611 	}
612 	distances.clear();
613 }
614 
calcDistanceAux(ConnectorItem * from,ConnectorItem * to,int distance,QList<Wire * > & distanceWires)615 int PCBSketchWidget::calcDistanceAux(ConnectorItem * from, ConnectorItem * to, int distance, QList<Wire *> & distanceWires) {
616 	//DebugDialog::debug(QString("calc distance aux: %1 %2, %3 %4, %5").arg(from->attachedToID()).arg(from->connectorSharedID())
617 		//.arg(to->attachedToTitle()).arg(to->connectorSharedID()).arg(distance));
618 
619 	foreach (ConnectorItem * toConnectorItem, from->connectedToItems()) {
620 		if (toConnectorItem == to) {
621 			return distance;
622 		}
623 	}
624 
625 	int result = MAX_INT;
626 	foreach (ConnectorItem * toConnectorItem, from->connectedToItems()) {
627 		if (toConnectorItem->attachedToItemType() != ModelPart::Wire) continue;
628 
629 		Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
630 		if (distanceWires.contains(w)) continue;
631 
632 		bool fromConnector0;
633 		int temp = calcDistance(w, to, distance + 1, distanceWires, fromConnector0);
634 		if (temp < result) {
635 			result = temp;
636 		}
637 	}
638 
639 	return result;
640 }
641 
calcDistance(Wire * wire,ConnectorItem * end,int distance,QList<Wire * > & distanceWires,bool & fromConnector0)642 int PCBSketchWidget::calcDistance(Wire * wire, ConnectorItem * end, int distance, QList<Wire *> & distanceWires, bool & fromConnector0) {
643 	//DebugDialog::debug(QString("calc distance wire: %1 rat:%2 to %3 %4, %5").arg(wire->id()).arg(wire->getRatsnest())
644 		//.arg(end->attachedToTitle()).arg(end->connectorSharedID()).arg(distance));
645 
646 	distanceWires.append(wire);
647 	int d0 = calcDistanceAux(wire->connector0(), end, distance, distanceWires);
648 	if (d0 == distance) {
649 		fromConnector0 = true;
650 		return d0;
651 	}
652 
653 	int d1 = calcDistanceAux(wire->connector1(), end, distance, distanceWires);
654 	if (d0 <= d1) {
655 		fromConnector0 = true;
656 		return d0;
657 	}
658 
659 	fromConnector0 = false;
660 	return d1;
661 }
662 
showGroundTraces(QList<ConnectorItem * > & connectorItems,bool show)663 void PCBSketchWidget::showGroundTraces(QList<ConnectorItem *> & connectorItems, bool show) {
664 
665 	foreach (ConnectorItem * connectorItem, connectorItems) {
666 		TraceWire * trace = dynamic_cast<TraceWire *>(connectorItem->attachedTo());
667 		if (trace == NULL) continue;
668 
669 		if (!trace->isTraceType(getTraceFlag())) continue;
670 
671 		trace->setVisible(show);
672 	}
673 }
674 
getLabelFont(QFont & font,QColor & color,ItemBase * itemBase)675 void PCBSketchWidget::getLabelFont(QFont & font, QColor & color, ItemBase * itemBase) {
676 	font.setFamily(OCRAFontName);
677     font.setPointSize(getLabelFontSizeSmall());
678 	font.setBold(false);
679 	font.setItalic(false);
680 	color.setAlpha(255);
681 
682     QString name = ViewLayer::Silkscreen1Color;
683 
684     if (boardLayers() == 2) {
685         if (itemBase->viewLayerPlacement() == ViewLayer::NewBottom) name = ViewLayer::Silkscreen0Color;
686     }
687 
688     color.setNamedColor(name);
689 
690 }
691 
getLabelViewLayerID(ItemBase * itemBase)692 ViewLayer::ViewLayerID PCBSketchWidget::getLabelViewLayerID(ItemBase * itemBase) {
693 
694     if (boardLayers() == 2) {
695         if (itemBase->viewLayerPlacement() == ViewLayer::NewBottom) return ViewLayer::Silkscreen0Label;
696     }
697 
698     return ViewLayer::Silkscreen1Label;
699 }
700 
getLabelFontSizeTiny()701 double PCBSketchWidget::getLabelFontSizeTiny() {
702         return 3;
703 }
704 
getLabelFontSizeSmall()705 double PCBSketchWidget::getLabelFontSizeSmall() {
706 	return 5;
707 }
708 
getLabelFontSizeMedium()709 double PCBSketchWidget::getLabelFontSizeMedium() {
710 	return 7;
711 }
712 
getLabelFontSizeLarge()713 double PCBSketchWidget::getLabelFontSizeLarge() {
714 	return 12;
715 }
716 
resizeBoard(double mmW,double mmH,bool doEmit)717 void PCBSketchWidget::resizeBoard(double mmW, double mmH, bool doEmit)
718 {
719 	Q_UNUSED(doEmit);
720 
721 	PaletteItem * item = getSelectedPart();
722 	if (item == NULL) return;
723 
724 	bool handle = false;
725 	switch (item->itemType()) {
726 		case ModelPart::ResizableBoard:
727 		case ModelPart::Logo:
728 			handle = true;
729 			break;
730 		case ModelPart::Part:
731 			handle = item->moduleID().endsWith(ModuleIDNames::PadModuleIDName) ||
732                     item->moduleID().endsWith(ModuleIDNames::CopperBlockerModuleIDName) ||
733                     item->moduleID().endsWith(ModuleIDNames::SchematicFrameModuleIDName);
734 			break;
735 		default:
736 			break;
737 	}
738 
739 	if (!handle) return SketchWidget::resizeBoard(mmW, mmH, doEmit);
740 
741     resizeWithHandle(item, mmW, mmH);
742 }
743 
showLabelFirstTime(long itemID,bool show,bool doEmit)744 void PCBSketchWidget::showLabelFirstTime(long itemID, bool show, bool doEmit) {
745 	// called when new item is added, to decide whether to show part label
746 	SketchWidget::showLabelFirstTime(itemID, show, doEmit);
747 	ItemBase * itemBase = findItem(itemID);
748 	if (itemBase == NULL) return;
749 	if (!canDropModelPart(itemBase->modelPart())) return;
750 
751 	switch (itemBase->itemType()) {
752 		case ModelPart::Part:
753 		case ModelPart::Jumper:
754 			{
755 				if (itemBase->hasPartLabel()) {
756 					ViewLayer * viewLayer = m_viewLayers.value(getLabelViewLayerID(itemBase));
757 					itemBase->showPartLabel(itemBase->isVisible(), viewLayer);
758 					itemBase->partLabelSetHidden(!viewLayer->visible());
759 				}
760 			}
761 			break;
762 		default:
763 			break;
764 	}
765 
766 }
767 
findBoardBeneath(ItemBase * itemBase)768 ItemBase * PCBSketchWidget::findBoardBeneath(ItemBase * itemBase) {
769     foreach (QGraphicsItem * item, scene()->collidingItems(itemBase)) {
770         Board * board = dynamic_cast<Board *>(item);
771         if (board == NULL) continue;
772 
773         if (Board::isBoard(board)) return board;
774     }
775 
776     return NULL;
777 }
778 
findSelectedBoard(int & boardCount)779 ItemBase * PCBSketchWidget::findSelectedBoard(int & boardCount) {
780     QList<ItemBase *> boards = findBoard();
781     boardCount = boards.count();
782     if (boards.count() == 0) return NULL;
783     if (boards.count() == 1) return boards.at(0);
784 
785     int selectedCount = 0;
786     ItemBase * selectedBoard = NULL;
787     foreach (ItemBase * board, boards) {
788         if (board->isSelected()) {
789             selectedCount++;
790             selectedBoard = board;
791         }
792     }
793 
794     if (selectedCount == 1) return selectedBoard;
795     return NULL;
796 }
797 
findBoard()798 QList<ItemBase *> PCBSketchWidget::findBoard() {
799 	QSet<ItemBase *> boards;
800     foreach (QGraphicsItem * childItem, items()) {
801         Board * board = dynamic_cast<Board *>(childItem);
802         if (board == NULL) continue;
803 
804         if (Board::isBoard(board)) {
805            boards.insert(board->layerKinChief());
806         }
807     }
808 
809 	return boards.toList();
810 }
811 
forwardRoutingStatus(const RoutingStatus & routingStatus)812 void PCBSketchWidget::forwardRoutingStatus(const RoutingStatus & routingStatus)
813 {
814 	m_routingStatus = routingStatus;
815 	SketchWidget::forwardRoutingStatus(routingStatus);
816 }
817 
818 
defaultGridSizeInches()819 double PCBSketchWidget::defaultGridSizeInches() {
820 	return 0.1;
821 }
822 
wireViewLayerPlacement(ConnectorItem * connectorItem)823 ViewLayer::ViewLayerPlacement PCBSketchWidget::wireViewLayerPlacement(ConnectorItem * connectorItem) {
824 	switch (connectorItem->attachedToViewLayerID()) {
825 		case ViewLayer::Copper1:
826 		case ViewLayer::Copper1Trace:
827 		case ViewLayer::GroundPlane1:
828 			return ViewLayer::NewTop;
829 		default:
830 			return ViewLayer::NewBottom;
831 	}
832 }
833 
setBoardLayers(int layers,bool redraw)834 void PCBSketchWidget::setBoardLayers(int layers, bool redraw) {
835 	SketchWidget::setBoardLayers(layers, redraw);
836 
837 	QList <ViewLayer::ViewLayerID> viewLayerIDs;
838 	viewLayerIDs << ViewLayer::Copper1 << ViewLayer::Copper1Trace;
839 	foreach (ViewLayer::ViewLayerID viewLayerID, viewLayerIDs) {
840 		ViewLayer * layer = m_viewLayers.value(viewLayerID, NULL);
841 		if (layer) {
842 			layer->action()->setEnabled(layers == 2);
843 			layer->setVisible(layers == 2);
844 			if (redraw) {
845 				setLayerVisible(layer, layers == 2, true);
846 				if (layers == 2) {
847 					layer->action()->setChecked(true);
848 				}
849 			}
850 		}
851 	}
852 }
853 
swapLayers(ItemBase *,int newLayers,QUndoCommand * parentCommand)854 void PCBSketchWidget::swapLayers(ItemBase *, int newLayers, QUndoCommand * parentCommand)
855 {
856 	QList<ItemBase *> smds;
857 	QList<ItemBase *> pads;
858     QList<Wire *> already;
859 
860 	ChangeBoardLayersCommand * changeBoardCommand = new ChangeBoardLayersCommand(this, m_boardLayers, newLayers, parentCommand);
861     QList<ItemBase *> boards = findBoard();
862     foreach (ItemBase * board, boards) {
863         new SetPropCommand(this, board->id(), "layers", QString::number(m_boardLayers), QString::number(newLayers), true, parentCommand);
864     }
865 
866     if (newLayers == 2) {
867         new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
868 		new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
869 		return;
870 	}
871 
872 	// disconnect and flip smds
873 	foreach (QGraphicsItem * item, scene()->items()) {
874 		ItemBase * smd = dynamic_cast<ItemBase *>(item);
875 		if (smd == NULL) continue;
876         if (smd->moduleID().endsWith(ModuleIDNames::PadModuleIDName)) {
877             pads << smd;
878             continue;
879         }
880 
881 		if (!smd->modelPart()->flippedSMD()) continue;
882 
883 		smd = smd->layerKinChief();
884 		if (smds.contains(smd)) continue;
885 
886 		smds.append(smd);
887 	}
888 
889 	changeTraceLayer(NULL, true, changeBoardCommand);
890 
891 	foreach (ItemBase * smd, smds) {
892         long newID;
893 		emit subSwapSignal(this, smd, smd->moduleID(), (newLayers == 1) ? ViewLayer::NewBottom : ViewLayer::NewTop, newID, changeBoardCommand);
894 	}
895 
896 	foreach (ItemBase * itemBase, pads) {
897         Pad * pad = qobject_cast<Pad *>(itemBase);
898         if (pad == NULL) continue;
899 
900         long newID;
901 		emit subSwapSignal(this, pad,
902                 (newLayers == 1) ? ModuleIDNames::Copper0PadModuleIDName : ModuleIDNames::PadModuleIDName,
903                 (newLayers == 1) ? ViewLayer::NewBottom : ViewLayer::NewTop,
904                 newID, changeBoardCommand);
905 
906         double w = pad->modelPart()->localProp("width").toDouble();
907         double h = pad->modelPart()->localProp("height").toDouble();
908         new ResizeBoardCommand(this, newID, w, h, w, h, parentCommand);
909 	}
910 
911 }
912 
isBoardLayerChange(ItemBase * itemBase,const QString & newModuleID,int & newLayers)913 bool PCBSketchWidget::isBoardLayerChange(ItemBase * itemBase, const QString & newModuleID, int & newLayers)
914 {
915     newLayers = m_boardLayers;
916 	if (!Board::isBoard(itemBase)) {
917 		// no change
918 		return false;
919 	}
920 
921 	ModelPart * modelPart = referenceModel()->retrieveModelPart(newModuleID);
922 	if (modelPart == NULL) {
923 		// shouldn't happen
924 		return false;
925 	}
926 
927 	QString slayers = modelPart->properties().value("layers", "");
928 	if (slayers.isEmpty()) {
929 		// shouldn't happen
930 		return false;
931 	}
932 
933 	bool ok;
934 	int layers = slayers.toInt(&ok);
935 	if (!ok) {
936 		// shouldn't happen
937 		return false;
938 	}
939 
940     newLayers = layers;
941 	return (m_boardLayers != layers);
942 }
943 
changeBoardLayers(int layers,bool doEmit)944 void PCBSketchWidget::changeBoardLayers(int layers, bool doEmit) {
945 	setBoardLayers(layers, true);
946 	SketchWidget::changeBoardLayers(layers, doEmit);
947 	if (layers == 1) {
948 		this->setLayerActive(ViewLayer::Copper0, true);
949 		this->setLayerActive(ViewLayer::Silkscreen0, true);
950 	}
951 	emit updateLayerMenuSignal();
952 }
953 
loadFromModelParts(QList<ModelPart * > & modelParts,BaseCommand::CrossViewType crossViewType,QUndoCommand * parentCommand,bool offsetPaste,const QRectF * boundingRect,bool seekOutsideConnections,QList<long> & newIDs)954 void PCBSketchWidget::loadFromModelParts(QList<ModelPart *> & modelParts, BaseCommand::CrossViewType crossViewType, QUndoCommand * parentCommand,
955 						bool offsetPaste, const QRectF * boundingRect, bool seekOutsideConnections, QList<long> & newIDs) {
956 
957     int layers = 1;
958     if (parentCommand == NULL) {
959 		foreach (ModelPart * modelPart, modelParts) {
960             if (Board::isBoard(modelPart)) {
961                 QString slayers = modelPart->localProp("layers").toString();
962                 if (slayers.isEmpty()) {
963 				    slayers = modelPart->properties().value("layers", "");
964                 }
965 				if (slayers.isEmpty()) {
966 					// shouldn't happen
967 					continue;
968 				}
969 				bool ok;
970 				int localLayers = slayers.toInt(&ok);
971 				if (!ok) {
972 					// shouldn't happen
973 					continue;
974 				}
975                 if (localLayers == 2) {
976                     layers = 2;
977                     break;
978                 }
979 			}
980             else if (modelPart->itemType() == ModelPart::Wire) {
981 		        QDomElement instance = modelPart->instanceDomElement();
982 		        QDomElement views = instance.firstChildElement("views");
983 		        QDomElement view = views.firstChildElement("pcbView");
984                 if (view.attribute("layer").compare("copper1trace") == 0) {
985                     layers = 2;
986                     break;
987                 }
988             }
989             else if (modelPart->itemType() == ModelPart::CopperFill) {
990 		        QDomElement instance = modelPart->instanceDomElement();
991 		        QDomElement views = instance.firstChildElement("views");
992 		        QDomElement view = views.firstChildElement("pcbView");
993                 if (view.attribute("layer").compare("groundplane1") == 0) {
994                     layers = 2;
995                     break;
996                 }
997             }
998 		}
999         changeBoardLayers(layers, true);
1000 	}
1001 
1002 	SketchWidget::loadFromModelParts(modelParts, crossViewType, parentCommand, offsetPaste, boundingRect, seekOutsideConnections, newIDs);
1003 
1004 	if (parentCommand == NULL) {
1005         changeBoardLayers(layers, true);
1006 		shiftHoles();
1007 	}
1008 }
1009 
isInLayers(ConnectorItem * connectorItem,ViewLayer::ViewLayerPlacement viewLayerPlacement)1010 bool PCBSketchWidget::isInLayers(ConnectorItem * connectorItem, ViewLayer::ViewLayerPlacement viewLayerPlacement) {
1011 	return connectorItem->isInLayers(viewLayerPlacement);
1012 }
1013 
routeBothSides()1014 bool PCBSketchWidget::routeBothSides() {
1015 	return m_boardLayers > 1;
1016 }
1017 
sameElectricalLayer2(ViewLayer::ViewLayerID id1,ViewLayer::ViewLayerID id2)1018 bool PCBSketchWidget::sameElectricalLayer2(ViewLayer::ViewLayerID id1, ViewLayer::ViewLayerID id2) {
1019 	switch (id1) {
1020 		case ViewLayer::Copper0Trace:
1021 			if (id1 == id2) return true;
1022 			return (id2 == ViewLayer::Copper0 || id2 == ViewLayer::GroundPlane0);
1023 		case ViewLayer::Copper0:
1024 		case ViewLayer::GroundPlane0:
1025 			if (id1 == id2) return true;
1026 			return (id2 == ViewLayer::Copper0Trace);
1027 		case ViewLayer::Copper1Trace:
1028 			if (id1 == id2) return true;
1029 			return (id2 == ViewLayer::Copper1 || id2 == ViewLayer::GroundPlane1);
1030 		case ViewLayer::Copper1:
1031 		case ViewLayer::GroundPlane1:
1032 			if (id1 == id2) return true;
1033 			return (id2 == ViewLayer::Copper1Trace);
1034         default:
1035             break;
1036 	}
1037 
1038 	return false;
1039 }
1040 
changeTraceLayer(ItemBase * itemBase,bool force,QUndoCommand * parentCommand)1041 void PCBSketchWidget::changeTraceLayer(ItemBase * itemBase, bool force, QUndoCommand * parentCommand) {
1042 	QList<Wire *> visitedWires;
1043 	QSet<Wire *> changeWires;
1044     TraceWire * sample = NULL;
1045     QList<QGraphicsItem *> items;
1046     if (itemBase != NULL) items << itemBase;
1047     else if (force) items = scene()->items();
1048     else items =  scene()->selectedItems();
1049 	foreach (QGraphicsItem * item, items) {
1050 		TraceWire * tw = dynamic_cast<TraceWire *>(item);
1051 		if (tw == NULL) continue;
1052 
1053 		if (!tw->isTraceType(getTraceFlag())) continue;
1054 		if (visitedWires.contains(tw)) continue;
1055 
1056         sample = tw;
1057 
1058 		QList<Wire *> wires;
1059 		QList<ConnectorItem *> ends;
1060 		tw->collectChained(wires, ends);
1061 		visitedWires.append(wires);
1062 
1063         if (!force) {
1064 		    bool canChange = true;
1065 		    foreach(ConnectorItem * end, ends) {
1066 			    if (end->getCrossLayerConnectorItem() == NULL) {
1067 				    canChange = false;
1068 				    break;
1069 			    }
1070 		    }
1071 		    if (!canChange) continue;
1072         }
1073 
1074 		changeWires.insert(tw);
1075 	}
1076 
1077 	if (changeWires.count() == 0 || sample == NULL) return;
1078 
1079     bool createNew = false;
1080     if (parentCommand == NULL) {
1081 	    parentCommand = new QUndoCommand(tr("Change trace layer"));
1082         createNew = true;
1083     }
1084 
1085 	ViewLayer::ViewLayerID newViewLayerID = (sample->viewLayerID() == ViewLayer::Copper0Trace) ? ViewLayer::Copper1Trace : ViewLayer::Copper0Trace;
1086     if (force) {
1087         // move all traces to bottom layer (force == true when switching from 2 layers to 1)
1088         newViewLayerID = ViewLayer::Copper0Trace;
1089     }
1090 	foreach (Wire * wire, changeWires) {
1091 		QList<Wire *> wires;
1092 		QList<ConnectorItem *> ends;
1093 		wire->collectChained(wires, ends);
1094 
1095 		// probably safest to disconnect change the layers and reconnect, so that's why the redundant looping
1096 
1097 		foreach (ConnectorItem * end, ends) {
1098 			ConnectorItem * targetConnectorItem = NULL;
1099 			foreach (ConnectorItem * toConnectorItem, end->connectedToItems()) {
1100 				Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
1101 				if (w == NULL) continue;
1102 
1103 				if (wires.contains(w)) {
1104 					targetConnectorItem = toConnectorItem;
1105 					break;
1106 				}
1107 			}
1108 
1109 			extendChangeConnectionCommand(BaseCommand::SingleView,
1110 								targetConnectorItem, end,
1111 								ViewLayer::specFromID(end->attachedToViewLayerID()),
1112 								false, parentCommand);
1113 		}
1114 
1115 		foreach (Wire * w, wires) {
1116 			new ChangeLayerCommand(this, w->id(), w->zValue(), m_viewLayers.value(newViewLayerID)->nextZ(), w->viewLayerID(), newViewLayerID, parentCommand);
1117 		}
1118 
1119 		foreach (ConnectorItem * end, ends) {
1120 			ConnectorItem * targetConnectorItem = NULL;
1121 			foreach (ConnectorItem * toConnectorItem, end->connectedToItems()) {
1122 				Wire * w = qobject_cast<Wire *>(toConnectorItem->attachedTo());
1123 				if (w == NULL) continue;
1124 
1125 				if (wires.contains(w)) {
1126 					targetConnectorItem = toConnectorItem;
1127 					break;
1128 				}
1129 			}
1130 
1131 			new ChangeConnectionCommand(this, BaseCommand::SingleView,
1132 								targetConnectorItem->attachedToID(), targetConnectorItem->connectorSharedID(),
1133 								end->attachedToID(), end->connectorSharedID(),
1134 								ViewLayer::specFromID(newViewLayerID),
1135 								true, parentCommand);
1136 		}
1137 	}
1138 
1139     if (createNew) {
1140 	    m_undoStack->waitPush(parentCommand, PropChangeDelay);
1141     }
1142 }
1143 
changeLayer(long id,double z,ViewLayer::ViewLayerID viewLayerID)1144 void PCBSketchWidget::changeLayer(long id, double z, ViewLayer::ViewLayerID viewLayerID) {
1145 	ItemBase * itemBase = findItem(id);
1146 	if (itemBase == NULL) return;
1147 
1148 	itemBase->setViewLayerID(viewLayerID, m_viewLayers);
1149 	itemBase->setZValue(z);
1150 	itemBase->saveGeometry();
1151 
1152 	TraceWire * tw = qobject_cast<TraceWire *>(itemBase);
1153 	if (tw != NULL) {
1154 		ViewLayer::ViewLayerPlacement viewLayerPlacement = ViewLayer::specFromID(viewLayerID);
1155 		tw->setViewLayerPlacement(viewLayerPlacement);
1156 		tw->setColorString(traceColor(viewLayerPlacement), 1.0, true);
1157 		ViewLayer * viewLayer = m_viewLayers.value(viewLayerID);
1158 		tw->setInactive(!viewLayer->isActive());
1159 		tw->setHidden(!viewLayer->visible());
1160 		tw->update();
1161 	}
1162 
1163     updateInfoView();
1164 }
1165 
resizingJumperItemPress(ItemBase * itemBase)1166 bool PCBSketchWidget::resizingJumperItemPress(ItemBase * itemBase) {
1167     if (itemBase == NULL) return false;
1168 
1169 	JumperItem * jumperItem = qobject_cast<JumperItem *>(itemBase->layerKinChief());
1170 	if (jumperItem == NULL) return false;
1171 	if (!jumperItem->inDrag()) return false;
1172 
1173 	m_resizingJumperItem = jumperItem;
1174 	m_resizingJumperItem->saveParams();
1175 	if (m_alignToGrid) {
1176 		m_alignmentStartPoint = QPointF(0,0);
1177 		ItemBase * board = findBoardBeneath(m_resizingJumperItem);
1178 		QHash<long, ItemBase *> savedItems;
1179 		QHash<Wire *, ConnectorItem *> savedWires;
1180 		if (board == NULL) {
1181 			foreach (QGraphicsItem * item, scene()->items()) {
1182 				PaletteItemBase * itemBase = dynamic_cast<PaletteItemBase *>(item);
1183                 if (itemBase == NULL) continue;
1184 				if (itemBase->itemType() == ModelPart::Jumper) continue;
1185 
1186 				savedItems.insert(itemBase->layerKinChief()->id(), itemBase);
1187 			}
1188 		}
1189 		findAlignmentAnchor(board, savedItems, savedWires);
1190 		m_jumperDragOffset = jumperItem->dragOffset();
1191 		connect(m_resizingJumperItem, SIGNAL(alignMe(JumperItem *, QPointF &)), this, SLOT(alignJumperItem(JumperItem *, QPointF &)), Qt::DirectConnection);
1192 	}
1193 	return true;
1194 }
1195 
alignJumperItem(JumperItem * jumperItem,QPointF & loc)1196 void PCBSketchWidget::alignJumperItem(JumperItem * jumperItem, QPointF & loc) {
1197 	Q_UNUSED(jumperItem);
1198 	if (!m_alignToGrid) return;
1199 
1200 	QPointF newPos = loc - m_jumperDragOffset - m_alignmentStartPoint;
1201 	double ny = GraphicsUtils::getNearestOrdinate(newPos.y(), gridSizeInches() * GraphicsUtils::SVGDPI);
1202 	double nx = GraphicsUtils::getNearestOrdinate(newPos.x(), gridSizeInches() * GraphicsUtils::SVGDPI);
1203 	loc.setX(loc.x() + nx - newPos.x());
1204 	loc.setY(loc.y() + ny - newPos.y());
1205 }
1206 
resizingJumperItemRelease()1207 bool PCBSketchWidget::resizingJumperItemRelease() {
1208 	if (m_resizingJumperItem == NULL) return false;
1209 
1210 	if (m_alignToGrid) {
1211 		disconnect(m_resizingJumperItem, SIGNAL(alignMe(JumperItem *, QPointF &)), this, SLOT(alignJumperItem(JumperItem *, QPointF &)));
1212 	}
1213 	resizeJumperItem();
1214 	return true;
1215 }
1216 
resizeJumperItem()1217 void PCBSketchWidget::resizeJumperItem() {
1218 	QPointF oldC0, oldC1;
1219 	QPointF oldPos;
1220 	m_resizingJumperItem->getParams(oldPos, oldC0, oldC1);
1221 	QPointF newC0, newC1;
1222 	QPointF newPos;
1223 	m_resizingJumperItem->saveParams();
1224 	m_resizingJumperItem->getParams(newPos, newC0, newC1);
1225 	QUndoCommand * cmd = new ResizeJumperItemCommand(this, m_resizingJumperItem->id(), oldPos, oldC0, oldC1, newPos, newC0, newC1, NULL);
1226 	cmd->setText("Resize Jumper");
1227 	m_undoStack->waitPush(cmd, 10);
1228 	m_resizingJumperItem = NULL;
1229 }
1230 
canDragWire(Wire * wire)1231 bool PCBSketchWidget::canDragWire(Wire * wire) {
1232 	if (wire == NULL) return false;
1233 
1234 	if (wire->getRatsnest()) return false;
1235 
1236 	return true;
1237 }
1238 
wireSplitSlot(Wire * wire,QPointF newPos,QPointF oldPos,const QLineF & oldLine)1239 void PCBSketchWidget::wireSplitSlot(Wire* wire, QPointF newPos, QPointF oldPos, const QLineF & oldLine) {
1240 	if (!wire->getRatsnest()) {
1241 		SketchWidget::wireSplitSlot(wire, newPos, oldPos, oldLine);
1242 	}
1243 
1244 	createTrace(wire, false);
1245 }
1246 
1247 
addCopperLogoItem(ViewLayer::ViewLayerPlacement viewLayerPlacement)1248 ItemBase * PCBSketchWidget::addCopperLogoItem(ViewLayer::ViewLayerPlacement viewLayerPlacement)
1249 {
1250 	long newID = ItemBase::getNextID();
1251 	ViewGeometry viewGeometry;
1252 	viewGeometry.setLoc(QPointF(0, 0));
1253 	QString moduleID = (viewLayerPlacement == ViewLayer::NewBottom) ? ModuleIDNames::Copper0LogoTextModuleIDName : ModuleIDNames::Copper1LogoTextModuleIDName;
1254 	return addItem(referenceModel()->retrieveModelPart(moduleID), viewLayerPlacement, BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1255 }
1256 
hasAnyNets()1257 bool PCBSketchWidget::hasAnyNets() {
1258 	return m_routingStatus.m_netCount > 0;
1259 }
1260 
jumperItemSize()1261 QSizeF PCBSketchWidget::jumperItemSize() {
1262     if (m_jumperItemSize.width() == 0) {
1263 	    long newID = ItemBase::getNextID();
1264 	    ViewGeometry viewGeometry;
1265 	    viewGeometry.setLoc(QPointF(0, 0));
1266 	    ItemBase * itemBase = addItem(referenceModel()->retrieveModelPart(ModuleIDNames::JumperModuleIDName), ViewLayer::NewTop, BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1267 	    if (itemBase) {
1268 		    JumperItem * jumperItem = qobject_cast<JumperItem *>(itemBase);
1269              m_jumperItemSize = jumperItem->connector0()->rect().size();
1270              deleteItem(itemBase, true, false, false);
1271         }
1272     }
1273 
1274 	return m_jumperItemSize;
1275 }
1276 
getKeepout()1277 double PCBSketchWidget::getKeepout() {
1278     QString keepoutString = m_autorouterSettings.value(DRC::KeepoutSettingName);
1279     if (keepoutString.isEmpty()) {
1280         QSettings settings;
1281         keepoutString = settings.value(DRC::KeepoutSettingName, "").toString();
1282     }
1283     bool ok;
1284     double inches = TextUtils::convertToInches(keepoutString, &ok, false);
1285     if (!ok) {
1286         keepoutString = QString("%1in").arg(DRC::KeepoutDefaultMils / 1000);
1287         inches = DRC::KeepoutDefaultMils / 1000;
1288     }
1289 
1290     m_autorouterSettings.insert(DRC::KeepoutSettingName, keepoutString);
1291 
1292 	return inches * GraphicsUtils::SVGDPI;  // inches converted to pixels
1293 }
1294 
setKeepout(double mils)1295 void PCBSketchWidget::setKeepout(double mils) {
1296     QString keepoutString = QString("%1in").arg(mils / 1000);
1297     m_autorouterSettings.insert(DRC::KeepoutSettingName, keepoutString);
1298 }
1299 
resetKeepout()1300 void PCBSketchWidget::resetKeepout() {
1301     setKeepout(DRC::KeepoutDefaultMils);
1302 }
1303 
acceptsTrace(const ViewGeometry & viewGeometry)1304 bool PCBSketchWidget::acceptsTrace(const ViewGeometry & viewGeometry) {
1305 	return !viewGeometry.getSchematicTrace();
1306 }
1307 
placePartDroppedInOtherView(ModelPart * modelPart,ViewLayer::ViewLayerPlacement viewLayerPlacement,const ViewGeometry & viewGeometry,long id,SketchWidget * dropOrigin)1308 ItemBase * PCBSketchWidget::placePartDroppedInOtherView(ModelPart * modelPart, ViewLayer::ViewLayerPlacement viewLayerPlacement, const ViewGeometry & viewGeometry, long id, SketchWidget * dropOrigin)
1309 {
1310 	ItemBase * newItem = SketchWidget::placePartDroppedInOtherView(modelPart, viewLayerPlacement, viewGeometry, id, dropOrigin);
1311 	if (newItem == NULL) return newItem;
1312     if (!newItem->isEverVisible()) return newItem;
1313 
1314 	dealWithDefaultParts();
1315 
1316 	QList<ItemBase *> boards;
1317     if (autorouteTypePCB()) {
1318         boards = findBoard();
1319     }
1320     else {
1321         boards << NULL;
1322     }
1323 
1324     foreach (ItemBase * board, boards) {
1325 
1326 	    // This is a 2d bin-packing problem. We can use our tile datastructure for this.
1327 	    // Use a simple best-fit approach for now.  No idea how optimal a solution it is.
1328 
1329 	    CMRouter router(this, board, false);
1330 	    int keepout = 10;
1331 	    router.setKeepout(keepout);
1332 	    Plane * plane = router.initPlane(false);
1333 	    QList<Tile *> alreadyTiled;
1334 
1335 	    foreach (QGraphicsItem * item, (board) ? scene()->collidingItems(board) : scene()->items()) {
1336 		    ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1337 		    if (itemBase == NULL) continue;
1338             if (!itemBase->isEverVisible()) continue;
1339             if (itemBase->layerKinChief() != itemBase) continue;
1340 
1341 		    if (itemBase->layerKinChief() == board) continue;
1342 		    if (itemBase->layerKinChief() == newItem) continue;
1343 
1344             Wire * wire = qobject_cast<Wire *>(itemBase);
1345             if (wire != NULL) {
1346                 if (!wire->getTrace()) continue;
1347                 if (!wire->isTraceType(getTraceFlag())) continue;
1348             }
1349             else if (ResizableBoard::isBoard(itemBase)) continue;
1350 
1351             // itemBase->debugInfo("tiling");
1352 		    QRectF r = itemBase->sceneBoundingRect().adjusted(-keepout, -keepout, keepout, keepout);
1353 		    router.insertTile(plane, r, alreadyTiled, NULL, Tile::OBSTACLE, CMRouter::IgnoreAllOverlaps);
1354 	    }
1355 
1356 	    BestPlace bestPlace;
1357 	    bestPlace.maxRect = router.boardRect();
1358 	    bestPlace.rotate90 = false;
1359 	    bestPlace.width = realToTile(newItem->boundingRect().width());
1360 	    bestPlace.height = realToTile(newItem->boundingRect().height());
1361 	    bestPlace.plane = plane;
1362 
1363 	    TiSrArea(NULL, plane, &bestPlace.maxRect, Panelizer::placeBestFit, &bestPlace);
1364 	    if (bestPlace.bestTile != NULL) {
1365 		    QRectF r;
1366 		    tileToQRect(bestPlace.bestTile, r);
1367 		    ItemBase * chief = newItem->layerKinChief();
1368 		    chief->setPos(r.topLeft());
1369 		    DebugDialog::debug(QString("placing part with rotation:%1").arg(bestPlace.rotate90), r);
1370 		    if (bestPlace.rotate90) {
1371 			    chief->rotateItem(90, false);
1372 		    }
1373 		    alignOneToGrid(newItem);
1374 	    }
1375 	    router.drcClean();
1376         if (bestPlace.bestTile != NULL) {
1377             break;
1378         }
1379     }
1380 
1381 	return newItem;
1382 }
1383 
autorouterSettings()1384 void PCBSketchWidget::autorouterSettings() {
1385     // initialize settings values if they haven't already been initialized
1386     getKeepout();
1387     QString ringThickness, holeSize;
1388     getDefaultViaSize(ringThickness, holeSize);
1389     getAutorouterTraceWidth();
1390 
1391 	AutorouterSettingsDialog dialog(m_autorouterSettings);
1392 	if (QDialog::Accepted == dialog.exec()) {
1393         m_autorouterSettings = dialog.getSettings();
1394         QSettings settings;
1395         foreach (QString key, m_autorouterSettings.keys()) {
1396             settings.setValue(key, m_autorouterSettings.value(key));
1397         }
1398     }
1399 }
1400 
getViaSize(double & ringThickness,double & holeSize)1401 void PCBSketchWidget::getViaSize(double & ringThickness, double & holeSize) {
1402 	QString ringThicknessStr, holeSizeStr;
1403 	getDefaultViaSize(ringThicknessStr, holeSizeStr);
1404 	double rt = TextUtils::convertToInches(ringThicknessStr);
1405 	double hs = TextUtils::convertToInches(holeSizeStr);
1406 	ringThickness = rt * GraphicsUtils::SVGDPI;
1407 	holeSize = hs * GraphicsUtils::SVGDPI;
1408 }
1409 
getDefaultViaSize(QString & ringThickness,QString & holeSize)1410 void PCBSketchWidget::getDefaultViaSize(QString & ringThickness, QString & holeSize) {
1411 	// these settings are initialized in via.cpp
1412     ringThickness = m_autorouterSettings.value(Via::AutorouteViaRingThickness, "");
1413     holeSize = m_autorouterSettings.value(Via::AutorouteViaHoleSize, "");
1414 
1415 	QSettings settings;
1416     if (ringThickness.isEmpty()) {
1417 	    ringThickness = settings.value(Via::AutorouteViaRingThickness, Via::DefaultAutorouteViaRingThickness).toString();
1418     }
1419     if (holeSize.isEmpty()) {
1420 	    holeSize = settings.value(Via::AutorouteViaHoleSize, Via::DefaultAutorouteViaHoleSize).toString();
1421     }
1422 
1423     m_autorouterSettings.insert(Via::AutorouteViaRingThickness, ringThickness);
1424     m_autorouterSettings.insert(Via::AutorouteViaHoleSize, holeSize);
1425 }
1426 
deleteItem(ItemBase * itemBase,bool deleteModelPart,bool doEmit,bool later)1427 void PCBSketchWidget::deleteItem(ItemBase * itemBase, bool deleteModelPart, bool doEmit, bool later)
1428 {
1429 	bool boardDeleted = Board::isBoard(itemBase);
1430 	SketchWidget::deleteItem(itemBase, deleteModelPart, doEmit, later);
1431 	if (boardDeleted) {
1432 		if (findBoard().count() == 0) {
1433 			emit boardDeletedSignal();
1434 		}
1435         requestQuoteSoon();
1436 	}
1437 }
1438 
getTraceWidth()1439 double PCBSketchWidget::getTraceWidth() {
1440 	return Wire::STANDARD_TRACE_WIDTH;
1441 }
1442 
getAutorouterTraceWidth()1443 double PCBSketchWidget::getAutorouterTraceWidth() {
1444     QString traceWidthString = m_autorouterSettings.value(AutorouterSettingsDialog::AutorouteTraceWidth, "");
1445     if (traceWidthString.isEmpty()) {
1446 	    QSettings settings;
1447 	    QString def = QString::number(GraphicsUtils::pixels2mils(getTraceWidth(), GraphicsUtils::SVGDPI));
1448 	    traceWidthString = settings.value(AutorouterSettingsDialog::AutorouteTraceWidth, def).toString();
1449     }
1450 
1451     m_autorouterSettings.insert(AutorouterSettingsDialog::AutorouteTraceWidth, traceWidthString);
1452 
1453 	return GraphicsUtils::SVGDPI * traceWidthString.toInt() / 1000.0;  // traceWidthString is in mils
1454 }
1455 
getBendpointWidths(Wire * wire,double width,double & bendpointWidth,double & bendpoint2Width,bool & negativeOffsetRect)1456 void PCBSketchWidget::getBendpointWidths(Wire * wire, double width, double & bendpointWidth, double & bendpoint2Width, bool & negativeOffsetRect)
1457 {
1458 	Q_UNUSED(wire);
1459 	bendpointWidth = bendpoint2Width = (width / -2);
1460 	negativeOffsetRect = false;
1461 }
1462 
getSmallerTraceWidth(double minDim)1463 double PCBSketchWidget::getSmallerTraceWidth(double minDim) {
1464 	int mils = qMax((int) GraphicsUtils::pixels2mils(minDim, GraphicsUtils::SVGDPI) - 1, TraceWire::MinTraceWidthMils);
1465 	return GraphicsUtils::mils2pixels(mils, GraphicsUtils::SVGDPI);
1466 }
1467 
groundFill(bool fillGroundTraces,ViewLayer::ViewLayerID viewLayerID,QUndoCommand * parentCommand)1468 bool PCBSketchWidget::groundFill(bool fillGroundTraces, ViewLayer::ViewLayerID viewLayerID, QUndoCommand * parentCommand)
1469 {
1470 	m_groundFillSeeds = NULL;
1471     int boardCount;
1472 	ItemBase * board = findSelectedBoard(boardCount);
1473     // barf an error if there's no board
1474     if (boardCount == 0) {
1475         QMessageBox::critical(this, tr("Fritzing"),
1476                    tr("Your sketch does not have a board yet!  Please add a PCB in order to use copper fill."));
1477         return false;
1478     }
1479     if (board == NULL) {
1480         QMessageBox::critical(this, tr("Fritzing"),
1481                    tr("%1 Fill: please select the board you want to apply fill to.").arg(fillGroundTraces ? tr("Ground") : tr("Copper")));
1482         return false;
1483     }
1484 
1485 
1486 	QList<ConnectorItem *> seeds;
1487 	if (fillGroundTraces) {
1488 		bool gotTrueSeeds = collectGroundFillSeeds(seeds, false);
1489 
1490 		if (!gotTrueSeeds && (seeds.count() != 1)) {
1491 			QString message =  tr("Please designate one or more ground fill seeds before doing a ground fill.\n\n");
1492 			setGroundFillSeeds(message);
1493 			return false;
1494 		}
1495 
1496 		ConnectorItem::collectEqualPotential(seeds, true, ViewGeometry::NoFlag);
1497         //foreach (ConnectorItem * seed, seeds) {
1498         //    seed->debugInfo("seed");
1499         //}
1500 		m_groundFillSeeds = &seeds;
1501 	}
1502 
1503 	LayerList viewLayerIDs;
1504 	viewLayerIDs << ViewLayer::Board;
1505 
1506     QRectF boardImageRect, copperImageRect;
1507     RenderThing renderThing;
1508     renderThing.printerScale = GraphicsUtils::SVGDPI;
1509     renderThing.blackOnly = true;
1510     renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1511     renderThing.hideTerminalPoints = true;
1512     renderThing.selectedItems = renderThing.renderBlocker = false;
1513 	QString boardSvg = renderToSVG(renderThing, board, viewLayerIDs);
1514 	if (boardSvg.isEmpty()) {
1515         QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render board svg (1)."));
1516 		return false;
1517 	}
1518 
1519     boardImageRect = renderThing.imageRect;
1520     renderThing.renderBlocker = true;
1521 
1522     QString svg0;
1523     if (viewLayerID == ViewLayer::UnknownLayer || viewLayerID == ViewLayer::GroundPlane0) {
1524 	    viewLayerIDs.clear();
1525 	    viewLayerIDs << ViewLayer::Copper0 << ViewLayer::Copper0Trace  << ViewLayer::GroundPlane0;
1526 
1527 	    // hide ground traces so the ground plane will intersect them
1528 	    if (fillGroundTraces) showGroundTraces(seeds, false);
1529 	    svg0 = renderToSVG(renderThing, board, viewLayerIDs);
1530 	    if (fillGroundTraces) showGroundTraces(seeds, true);
1531 	    if (svg0.isEmpty()) {
1532             QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1533 		    return false;
1534 	    }
1535         copperImageRect = renderThing.imageRect;
1536     }
1537 
1538 	QString svg1;
1539 	if (boardLayers() > 1 && (viewLayerID == ViewLayer::UnknownLayer || viewLayerID == ViewLayer::GroundPlane1)) {
1540 		viewLayerIDs.clear();
1541 		viewLayerIDs << ViewLayer::Copper1 << ViewLayer::Copper1Trace << ViewLayer::GroundPlane1;
1542 
1543 		if (fillGroundTraces) showGroundTraces(seeds, false);
1544 		svg1 = renderToSVG(renderThing, board, viewLayerIDs);
1545 		if (fillGroundTraces) showGroundTraces(seeds, true);
1546 		if (svg1.isEmpty()) {
1547 			QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1548 			return false;
1549 		}
1550         copperImageRect = renderThing.imageRect;
1551 	}
1552 
1553 	QStringList exceptions;
1554 	exceptions << "none" << "" << background().name();    // the color of holes in the board
1555 
1556 	GroundPlaneGenerator gpg0;
1557     if (!svg0.isEmpty()) {
1558 	    gpg0.setLayerName("groundplane");
1559 	    gpg0.setStrokeWidthIncrement(StrokeWidthIncrement);
1560 	    gpg0.setMinRunSize(10, 10);
1561 	    if (fillGroundTraces) {
1562 		    connect(&gpg0, SIGNAL(postImageSignal(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1563 				    this, SLOT(postImageSlot(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1564                     Qt::DirectConnection);
1565 	    }
1566 
1567 	    bool result = gpg0.generateGroundPlane(boardSvg, boardImageRect.size(), svg0, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0  /* 2 MIL */,
1568 											    ViewLayer::Copper0Color, getKeepoutMils());
1569 	    if (result == false) {
1570             QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to write copper fill (1)."));
1571 		    return false;
1572 	    }
1573     }
1574 
1575 	GroundPlaneGenerator gpg1;
1576 	if (boardLayers() > 1 && !svg1.isEmpty()) {
1577 		gpg1.setLayerName("groundplane1");
1578 		gpg1.setStrokeWidthIncrement(StrokeWidthIncrement);
1579 		gpg1.setMinRunSize(10, 10);
1580 		if (fillGroundTraces) {
1581 			connect(&gpg1, SIGNAL(postImageSignal(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1582 					this, SLOT(postImageSlot(GroundPlaneGenerator *, QImage *, QImage *, QGraphicsItem *, QList<QRectF> *)),
1583                     Qt::DirectConnection);
1584 		}
1585 		bool result = gpg1.generateGroundPlane(boardSvg, boardImageRect.size(), svg1, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0  /* 2 MIL */,
1586 												ViewLayer::Copper1Color, getKeepoutMils());
1587 		if (result == false) {
1588 			QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to write copper fill (2)."));
1589 			return false;
1590 		}
1591 	}
1592 
1593 
1594 	QString fillType = (fillGroundTraces) ? GroundPlane::fillTypeGround : GroundPlane::fillTypePlain;
1595 	QRectF bsbr = board->sceneBoundingRect();
1596 
1597 	int ix = 0;
1598 	foreach (QString svg, gpg0.newSVGs()) {
1599 		ViewGeometry vg;
1600 		vg.setLoc(bsbr.topLeft() + gpg0.newOffsets()[ix++]);
1601 		long newID = ItemBase::getNextID();
1602 		new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::GroundPlaneModuleIDName, ViewLayer::NewBottom, vg, newID, false, -1, parentCommand);
1603 		new SetPropCommand(this, newID, "svg", svg, svg, true, parentCommand);
1604 		new SetPropCommand(this, newID, "fillType", fillType, fillType, false, parentCommand);
1605 	}
1606 
1607 	ix = 0;
1608 	foreach (QString svg, gpg1.newSVGs()) {
1609 		ViewGeometry vg;
1610 		vg.setLoc(bsbr.topLeft() + gpg1.newOffsets()[ix++]);
1611 		long newID = ItemBase::getNextID();
1612 		new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::GroundPlaneModuleIDName, ViewLayer::NewTop, vg, newID, false, -1, parentCommand);
1613 		new SetPropCommand(this, newID, "svg", svg, svg, true, parentCommand);
1614 		new SetPropCommand(this, newID, "fillType", fillType, fillType, false, parentCommand);
1615 	}
1616 
1617 	return true;
1618 
1619 }
1620 
generateCopperFillUnit(ItemBase * itemBase,QPointF whereToStart)1621 QString PCBSketchWidget::generateCopperFillUnit(ItemBase * itemBase, QPointF whereToStart)
1622 {
1623     int boardCount;
1624 	ItemBase * board = findSelectedBoard(boardCount);
1625     // barf an error if there's no board
1626     if (boardCount == 0) {
1627         QMessageBox::critical(this, tr("Fritzing"),
1628                    tr("Your sketch does not have a board yet!  Please add a PCB in order to use copper fill."));
1629         return "";
1630     }
1631     if (board == NULL) {
1632         QMessageBox::critical(this, tr("Fritzing"),
1633                    tr("Copper fill: please select only the board you want to fill."));
1634         return "";
1635     }
1636 
1637 	QRectF bsbr = board->sceneBoundingRect();
1638 	if (!bsbr.contains(whereToStart)) {
1639         QMessageBox::critical(this, tr("Fritzing"), tr("Unable to create copper fill--probably the part wasn't dropped onto the PCB."));
1640 		return "";
1641 	}
1642 
1643 	LayerList viewLayerIDs;
1644 	viewLayerIDs << ViewLayer::Board;
1645 
1646     QRectF boardImageRect, copperImageRect;
1647 
1648 	RenderThing renderThing;
1649     renderThing.printerScale = GraphicsUtils::SVGDPI;
1650     renderThing.blackOnly = true;
1651     renderThing.dpi = GraphicsUtils::StandardFritzingDPI;
1652     renderThing.hideTerminalPoints = true;
1653     renderThing.selectedItems = renderThing.renderBlocker = false;
1654 	QString boardSvg = renderToSVG(renderThing, board, viewLayerIDs);
1655 	if (boardSvg.isEmpty()) {
1656         QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render board svg (1)."));
1657 		return "";
1658 	}
1659 
1660     boardImageRect = renderThing.imageRect;
1661 	ViewLayer::ViewLayerPlacement viewLayerPlacement = ViewLayer::NewBottom;
1662 	QString color = ViewLayer::Copper0Color;
1663 	QString gpLayerName = "groundplane";
1664 
1665 	if (m_boardLayers == 2 && !dropOnBottom()) {
1666 		gpLayerName += "1";
1667 		color = ViewLayer::Copper1Color;
1668 		viewLayerPlacement = ViewLayer::NewTop;
1669 	}
1670 
1671 	viewLayerIDs = ViewLayer::copperLayers(viewLayerPlacement);
1672 
1673 	bool vis = itemBase->isVisible();
1674 	itemBase->setVisible(false);
1675     renderThing.renderBlocker = true;
1676 	QString svg = renderToSVG(renderThing, board, viewLayerIDs);
1677 	itemBase->setVisible(vis);
1678 	if (svg.isEmpty()) {
1679         QMessageBox::critical(this, tr("Fritzing"), tr("Fritzing error: unable to render copper svg (1)."));
1680 		return "";
1681 	}
1682 
1683     copperImageRect = renderThing.imageRect;
1684 
1685 	QStringList exceptions;
1686 	exceptions << "none" << "" << background().name();    // the color of holes in the board
1687 
1688 	GroundPlaneGenerator gpg;
1689 	gpg.setStrokeWidthIncrement(StrokeWidthIncrement);
1690 	gpg.setLayerName(gpLayerName);
1691 	gpg.setMinRunSize(10, 10);
1692 	bool result = gpg.generateGroundPlaneUnit(boardSvg, boardImageRect.size(), svg, copperImageRect.size(), exceptions, board, GraphicsUtils::StandardFritzingDPI / 2.0  /* 2 MIL */,
1693 												color, whereToStart, getKeepoutMils());
1694 
1695 	if (result == false || gpg.newSVGs().count() < 1) {
1696         QMessageBox::critical(this, tr("Fritzing"), tr("Unable to create copper fill--possibly the part was dropped onto another part or wire rather than the actual PCB."));
1697 		return "";
1698 	}
1699 
1700 	itemBase->setPos(bsbr.topLeft() + gpg.newOffsets()[0]);
1701 	itemBase->setViewLayerID(gpLayerName, m_viewLayers);
1702 
1703 	return gpg.newSVGs()[0];
1704 }
1705 
1706 
connectorItemHasSpec(ConnectorItem * connectorItem,ViewLayer::ViewLayerPlacement spec)1707 bool PCBSketchWidget::connectorItemHasSpec(ConnectorItem * connectorItem, ViewLayer::ViewLayerPlacement spec) {
1708 	if (ViewLayer::specFromID(connectorItem->attachedToViewLayerID()) == spec)  return true;
1709 
1710 	connectorItem = connectorItem->getCrossLayerConnectorItem();
1711 	if (connectorItem == NULL) return false;
1712 
1713 	return (ViewLayer::specFromID(connectorItem->attachedToViewLayerID()) == spec);
1714 }
1715 
createWireViewLayerPlacement(ConnectorItem * from,ConnectorItem * to)1716 ViewLayer::ViewLayerPlacement PCBSketchWidget::createWireViewLayerPlacement(ConnectorItem * from, ConnectorItem * to) {
1717 	QList<ViewLayer::ViewLayerPlacement> guesses;
1718 	guesses.append(layerIsActive(ViewLayer::Copper0) ? ViewLayer::NewBottom : ViewLayer::NewTop);
1719 	guesses.append(layerIsActive(ViewLayer::Copper0) ? ViewLayer::NewTop : ViewLayer::NewBottom);
1720 	foreach (ViewLayer::ViewLayerPlacement guess, guesses) {
1721 		if (connectorItemHasSpec(from, guess) && connectorItemHasSpec(to, guess)) {
1722 			return guess;
1723 		}
1724 	}
1725 
1726 	return ViewLayer::UnknownPlacement;
1727 }
1728 
getWireStrokeWidth(Wire * wire,double wireWidth)1729 double PCBSketchWidget::getWireStrokeWidth(Wire * wire, double wireWidth)
1730 {
1731 	double w, h;
1732 	wire->originalConnectorDimensions(w, h);
1733 	if (wireWidth < Wire::THIN_TRACE_WIDTH) {
1734 		wire->setConnectorDimensions(qMin(w, wireWidth + 1), qMin(w, wireWidth + 1));
1735 	}
1736 	if (wireWidth < Wire::STANDARD_TRACE_WIDTH) {
1737 		wire->setConnectorDimensions(qMin(w, wireWidth + 1.5), qMin(w, wireWidth + 1.5));
1738 	}
1739 	else {
1740 		wire->setConnectorDimensions(w, h);
1741 	}
1742 
1743 	return wireWidth + 6;
1744 }
1745 
createTempWireForDragging(Wire * fromWire,ModelPart * wireModel,ConnectorItem * connectorItem,ViewGeometry & viewGeometry,ViewLayer::ViewLayerPlacement spec)1746 Wire * PCBSketchWidget::createTempWireForDragging(Wire * fromWire, ModelPart * wireModel, ConnectorItem * connectorItem, ViewGeometry & viewGeometry, ViewLayer::ViewLayerPlacement spec)
1747 {
1748 	if (spec == ViewLayer::UnknownPlacement) {
1749 		spec = wireViewLayerPlacement(connectorItem);
1750 	}
1751 	viewGeometry.setPCBTrace(true);
1752 	Wire * wire =  SketchWidget::createTempWireForDragging(fromWire, wireModel, connectorItem, viewGeometry, spec);
1753 	if (fromWire == NULL) {
1754 		wire->setColorString(traceColor(connectorItem), 1.0, false);
1755 		double traceWidth = getTraceWidth();
1756 		double minDim = connectorItem->minDimension();
1757 		if (minDim < traceWidth) {
1758 			traceWidth = getSmallerTraceWidth(minDim);
1759 		}
1760 		wire->setWireWidth(traceWidth, this, getWireStrokeWidth(wire, traceWidth));
1761 		wire->setProperty(FakeTraceProperty, true);
1762 	}
1763 	else {
1764 		wire->setColorString(fromWire->colorString(), fromWire->opacity(), false);
1765 		wire->setWireWidth(fromWire->width(), this, fromWire->hoverStrokeWidth());
1766 	}
1767 
1768 	return wire;
1769 }
1770 
prereleaseTempWireForDragging(Wire * wire)1771 void PCBSketchWidget::prereleaseTempWireForDragging(Wire* wire)
1772 {
1773 	if (wire->property(PCBSketchWidget::FakeTraceProperty).toBool()) {
1774 		// make it not look like a trace, or modifyNewWireConnections will create the wrong kind of wire
1775 		wire->setWireFlags(0);
1776 	}
1777 }
1778 
rotatePartLabels(double degrees,QTransform & transform,QPointF center,QUndoCommand * parentCommand)1779 void PCBSketchWidget::rotatePartLabels(double degrees, QTransform & transform, QPointF center, QUndoCommand * parentCommand)
1780 {
1781     QList<ItemBase *> savedValues = m_savedItems.values();
1782     /*
1783     QSet<ItemBase *> boards;
1784 	foreach (ItemBase * itemBase, savedValues) {
1785         if (Board::isBoard(itemBase)) {
1786 		    boards.insert(itemBase);
1787 		}
1788 	}
1789 
1790 
1791 	if (boards.count() == 0) return;
1792 
1793     QRectF bbr;
1794     foreach (ItemBase * board, boards.values()) {
1795         bbr |= board->sceneBoundingRect();
1796     }
1797     */
1798 
1799 	foreach (QGraphicsItem * item, scene()->items()) {
1800 		PartLabel * partLabel = dynamic_cast<PartLabel *>(item);
1801 		if (partLabel == NULL) continue;
1802 		if (!partLabel->isVisible()) continue;
1803 		//if (!bbr.intersects(partLabel->sceneBoundingRect())) continue;  // if the part is on the board and the label is off the board, this does not rotate
1804         if (!savedValues.contains(partLabel->owner()->layerKinChief())) continue;
1805 
1806 		QPointF offset = partLabel->pos() - partLabel->owner()->pos();
1807 		new MoveLabelCommand(this, partLabel->owner()->id(), partLabel->pos(), offset, partLabel->pos(), offset, parentCommand);
1808 		new RotateFlipLabelCommand(this, partLabel->owner()->id(), degrees, 0, parentCommand);
1809 		QPointF p = GraphicsUtils::calcRotation(transform, center, partLabel->pos(), partLabel->boundingRect().center());
1810 		ViewGeometry vg;
1811 		partLabel->owner()->calcRotation(transform, center, vg);
1812 		new MoveLabelCommand(this, partLabel->owner()->id(), p, p - vg.loc(), p, p - vg.loc(), parentCommand);
1813 	}
1814 }
1815 
characterizeGroundFill(ViewLayer::ViewLayerID whichGroundPlane)1816 QString PCBSketchWidget::characterizeGroundFill(ViewLayer::ViewLayerID whichGroundPlane) {
1817 	QString result = GroundPlane::fillTypeNone;
1818 	bool gotOne = false;
1819 
1820     int boardCount;
1821     ItemBase * board = findSelectedBoard(boardCount);
1822 	foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
1823 		GroundPlane * gp = dynamic_cast<GroundPlane *>(item);
1824 		if (gp == NULL) continue;
1825 
1826 		if (gp->viewLayerID() == whichGroundPlane) {
1827 			gotOne = true;
1828             break;
1829 		}
1830 
1831 	}
1832 
1833 	if (!gotOne) return result;
1834 
1835 	foreach (QGraphicsItem * item, scene()->items()) {
1836 		GroundPlane * gp = dynamic_cast<GroundPlane *>(item);
1837 		if (gp == NULL) continue;
1838         if (gp->viewLayerID() != whichGroundPlane) continue;
1839 
1840 		QString fillType = gp->prop("fillType");
1841 		if (fillType.isEmpty()) {
1842 			// old style fill with no property
1843 			return GroundPlane::fillTypeGround;
1844 		}
1845 
1846 		if (fillType == GroundPlane::fillTypeGround) {
1847 			// assumes multiple fill types are not possible
1848 			return fillType;
1849 		}
1850 
1851 		if (fillType == GroundPlane::fillTypePlain) {
1852 			// assumes multiple fill types are not possible
1853 			return fillType;
1854 		}
1855 
1856 		result = fillType;
1857 	}
1858 
1859 	return result;
1860 }
1861 
setUpColor(ConnectorItem * fromConnectorItem,ConnectorItem * toConnectorItem,Wire * wire,QUndoCommand * parentCommand)1862 void PCBSketchWidget::setUpColor(ConnectorItem * fromConnectorItem, ConnectorItem * toConnectorItem, Wire * wire, QUndoCommand * parentCommand) {
1863 
1864 		QString tc = traceColor(fromConnectorItem);
1865 		new WireColorChangeCommand(this, wire->id(), tc, tc, 1.0, 1.0, parentCommand);
1866 		double traceWidth = getTraceWidth();
1867 		if (autorouteTypePCB()) {
1868 			double minDim = qMin(fromConnectorItem->minDimension(), toConnectorItem->minDimension());
1869 			if (minDim < traceWidth) {
1870 				traceWidth = getSmallerTraceWidth(minDim);
1871 			}
1872 		}
1873 		new WireWidthChangeCommand(this, wire->id(), traceWidth, traceWidth, parentCommand);
1874 
1875 }
1876 
getTraceFlag()1877 ViewGeometry::WireFlag PCBSketchWidget::getTraceFlag() {
1878 	return ViewGeometry::PCBTraceFlag;
1879 }
1880 
postImageSlot(GroundPlaneGenerator * gpg,QImage * copperImage,QImage * boardImage,QGraphicsItem * board,QList<QRectF> * rects)1881 void PCBSketchWidget::postImageSlot(GroundPlaneGenerator * gpg, QImage * copperImage, QImage * boardImage, QGraphicsItem * board, QList<QRectF> * rects) {
1882 
1883 	if (m_groundFillSeeds == NULL) return;
1884 
1885 	ViewLayer::ViewLayerID viewLayerID = (gpg->layerName() == "groundplane") ? ViewLayer::Copper0 : ViewLayer::Copper1;
1886 
1887 	QRectF boardRect = board->sceneBoundingRect();
1888 
1889 	foreach (ConnectorItem * connectorItem, *m_groundFillSeeds) {
1890 		if (connectorItem->attachedToViewLayerID() != viewLayerID) continue;
1891 		if (connectorItem->attachedToItemType() == ModelPart::Wire) continue;
1892 		if (!connectorItem->attachedTo()->isEverVisible()) continue;
1893 
1894 		//connectorItem->debugInfo("post image b");
1895 		QRectF r = connectorItem->sceneBoundingRect();
1896 		//DebugDialog::debug("pb", r);
1897 		QRectF check = r;
1898 		check.setLeft(r.right());
1899 		check.setRight(r.right() + r.width());
1900 		bool checkRight = !hasNeighbor(connectorItem, viewLayerID, check);
1901 
1902 		check = r;
1903 		check.setLeft(r.left() - r.width());
1904 		check.setRight(r.left());
1905 		bool checkLeft = !hasNeighbor(connectorItem, viewLayerID, check);
1906 
1907 		check = r;
1908 		check.setTop(r.bottom());
1909 		check.setBottom(r.bottom() + r.height());
1910 		bool checkDown = !hasNeighbor(connectorItem, viewLayerID, check);
1911 
1912 		check = r;
1913 		check.setTop(r.top() - r.width());
1914 		check.setBottom(r.top());
1915 		bool checkUp = !hasNeighbor(connectorItem, viewLayerID, check);
1916 
1917 		double x1 = (r.left() - boardRect.left()) * copperImage->width() / boardRect.width();
1918 		double x2 = (r.right() - boardRect.left()) * copperImage->width() / boardRect.width();
1919 		double y1 = (r.top() - boardRect.top()) * copperImage->height() / boardRect.height();
1920 		double y2 = (r.bottom() - boardRect.top()) * copperImage->height() / boardRect.height();
1921 		double w = x2 - x1;
1922 		double h = y2 - y1;
1923 
1924 		double cw = w / 4;
1925 		double ch = h / 4;
1926 		int cx = (x1 + x2) /2;
1927 		int cy = (y1 + y2) /2;
1928 
1929 		int rad = qFloor(connectorItem->calcClipRadius() * copperImage->width() / boardRect.width());
1930 
1931 		double borderl = qMax(0.0, x1 - w);
1932 		double borderr = qMin(x2 + w, copperImage->width());
1933 		double bordert = qMax(0.0, y1 - h);
1934 		double borderb = qMin(y2 + h, copperImage->height());
1935 
1936 		// check left, up, right, down for groundplane, and if it's there draw to it from the connector
1937 
1938 		if (checkUp){
1939 			for (int y = y1; y > bordert; y--) {
1940 				if ((copperImage->pixel(cx, y) & 0xffffff) || (boardImage->pixel(cx, y) == 0xff000000)) {
1941 					QRectF s(cx - cw, y - 1, cw + cw, cy - y - rad);
1942 					rects->append(s);
1943 					break;
1944 				}
1945 			}
1946 		}
1947 		if (checkDown) {
1948 			for (int y = y2; y < borderb; y++) {
1949 				if ((copperImage->pixel(cx, y) & 0xffffff) || (boardImage->pixel(cx, y) == 0xff000000)) {
1950 					QRectF s(cx - cw, cy + rad, cw + cw, y - cy - rad);
1951 					rects->append(s);
1952 					break;
1953 				}
1954 			}
1955 		}
1956 		if (checkLeft) {
1957 			for (int x = x1; x > borderl; x--) {
1958 				if ((copperImage->pixel(x, cy) & 0xffffff) || (boardImage->pixel(x, cy) == 0xff000000)) {
1959 					QRectF s(x - 1, cy - ch, cx - x - rad, ch + ch);
1960 					rects->append(s);
1961 					break;
1962 				}
1963 			}
1964 		}
1965 		if (checkRight) {
1966 			for (int x = x2; x < borderr; x++) {
1967 				if ((copperImage->pixel(x, cy) & 0xffffff) || (boardImage->pixel(x, cy) == 0xff000000)) {
1968 					QRectF s(cx + rad, cy - ch, x - cx - rad, ch + ch);
1969 					rects->append(s);
1970 					break;
1971 				}
1972 			}
1973 		}
1974 
1975 		DebugDialog::debug(QString("x1:%1 y1:%2 x2:%3 y2:%4").arg(x1).arg(y1).arg(x2).arg(y2));
1976 	}
1977 }
1978 
hasNeighbor(ConnectorItem * connectorItem,ViewLayer::ViewLayerID viewLayerID,const QRectF & r)1979 bool PCBSketchWidget::hasNeighbor(ConnectorItem * connectorItem, ViewLayer::ViewLayerID viewLayerID, const QRectF & r)
1980 {
1981 	foreach (QGraphicsItem * item, scene()->items(r)) {
1982 		ConnectorItem * ci = dynamic_cast<ConnectorItem *>(item);
1983 		if (ci != NULL) {
1984 			if (ci->attachedToViewLayerID() != viewLayerID) continue;
1985 			if (!ci->attachedTo()->isEverVisible()) continue;
1986 			if (ci == connectorItem) continue;
1987 
1988 			return true;
1989 		}
1990 
1991 		TraceWire * traceWire = dynamic_cast<TraceWire *>(item);
1992 		if (traceWire != NULL) {
1993 			if (!sameElectricalLayer2(traceWire->viewLayerID(), viewLayerID)) continue;
1994 			if (!traceWire->isTraceType(getTraceFlag())) continue;
1995 
1996 			return true;
1997 		}
1998 	}
1999 
2000 	return false;
2001 }
2002 
collectThroughHole(QList<ConnectorItem * > & th,QList<ConnectorItem * > & pads,const LayerList & layerList)2003 void PCBSketchWidget::collectThroughHole(QList<ConnectorItem *> & th, QList<ConnectorItem *> & pads, const LayerList & layerList)
2004 {
2005 	foreach (QGraphicsItem * item, scene()->items()) {
2006 		ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2007 		if (connectorItem == NULL) continue;
2008         if (!connectorItem->attachedTo()->isVisible()) continue;
2009         if (!layerList.contains(connectorItem->attachedToViewLayerID())) continue;
2010         if (connectorItem->attachedTo()->moduleID().endsWith(ModuleIDNames::PadModuleIDName)) {
2011             pads.append(connectorItem);
2012             continue;
2013         }
2014 
2015         if (connectorItem->attachedTo()->modelPart()->flippedSMD()) {
2016             pads.append(connectorItem);
2017             continue;
2018         }
2019 
2020         th << connectorItem;
2021 	}
2022 }
2023 
hideCopperLogoItems(QList<ItemBase * > & copperLogoItems)2024 void PCBSketchWidget::hideCopperLogoItems(QList<ItemBase *> & copperLogoItems)
2025 {
2026 	foreach (QGraphicsItem * item, this->items()) {
2027 		CopperLogoItem * logoItem = dynamic_cast<CopperLogoItem *>(item);
2028 		if (logoItem && logoItem->isVisible()) {
2029 			copperLogoItems.append(logoItem);
2030 			logoItem->setVisible(false);
2031 		}
2032 	}
2033 }
2034 
hideHoles(QList<ItemBase * > & holes)2035 void PCBSketchWidget::hideHoles(QList<ItemBase *> & holes)
2036 {
2037 	foreach (QGraphicsItem * item, this->items()) {
2038 		ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2039         // for some reason the layerkin of the hole doesn't have a modelPart->itemType() == ModelPart::Hole
2040 		if (itemBase && itemBase->isVisible() && itemBase->layerKinChief()->modelPart()->itemType() == ModelPart::Hole) {
2041 			holes.append(itemBase);
2042 			itemBase->setVisible(false);
2043 		}
2044 	}
2045 }
2046 
restoreCopperLogoItems(QList<ItemBase * > & copperLogoItems)2047 void PCBSketchWidget::restoreCopperLogoItems(QList<ItemBase *> & copperLogoItems)
2048 {
2049 	foreach (ItemBase * logoItem, copperLogoItems) {
2050 		logoItem->setVisible(true);
2051 	}
2052 }
2053 
clearGroundFillSeeds()2054 void PCBSketchWidget::clearGroundFillSeeds()
2055 {
2056 	QList<ConnectorItem *> trueSeeds;
2057 
2058     int boardCount;
2059     ItemBase * board = findSelectedBoard(boardCount);
2060     if (board == NULL) return;
2061 
2062 	foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
2063 		ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2064 		if (connectorItem == NULL) continue;
2065 		if (connectorItem->attachedToItemType() == ModelPart::CopperFill) continue;
2066 
2067 		if (connectorItem->isGroundFillSeed()) {
2068 			trueSeeds.append(connectorItem);
2069 			continue;
2070 		}
2071 	}
2072 
2073 	if (trueSeeds.count() == 0) return;
2074 
2075 	GroundFillSeedCommand * command = new GroundFillSeedCommand(this, NULL);
2076 	command->setText(tr("Clear ground fill seeds"));
2077 	foreach (ConnectorItem * connectorItem, trueSeeds) {
2078 		command->addItem(connectorItem->attachedToID(), connectorItem->connectorSharedID(), false);
2079 	}
2080 
2081 	m_undoStack->waitPush(command, PropChangeDelay);
2082 }
2083 
2084 
setGroundFillSeeds()2085 void PCBSketchWidget::setGroundFillSeeds()
2086 {
2087 	setGroundFillSeeds("");
2088 }
2089 
setGroundFillSeeds(const QString & intro)2090 void PCBSketchWidget::setGroundFillSeeds(const QString & intro)
2091 {
2092 	QList<ConnectorItem *> seeds;
2093 	collectGroundFillSeeds(seeds, true);
2094 	GroundFillSeedDialog gfsd(this, seeds, intro, NULL);
2095 	int result = gfsd.exec();
2096 	if (result == QDialog::Accepted) {
2097 		GroundFillSeedCommand * command = NULL;
2098 		QList<bool> results;
2099 		gfsd.getResults(results);
2100 		bool checked = false;
2101 		for (int i = 0; i < seeds.count(); i++) {
2102 			ConnectorItem * ci = seeds.at(i);
2103 			bool isSeed = results.at(i);
2104 			checked |= isSeed;
2105 			if (isSeed != ci->isGroundFillSeed()) {
2106 				if (command == NULL) {
2107 					command = new GroundFillSeedCommand(this, NULL);
2108 				}
2109 				command->addItem(ci->attachedToID(), ci->connectorSharedID(), isSeed);
2110 			}
2111 		}
2112 		if (command) {
2113 			m_undoStack->push(command);
2114 		}
2115 
2116 		if (gfsd.getFill()) {
2117 			if (checked) emit groundFillSignal();
2118 			else emit copperFillSignal();
2119 		}
2120 	}
2121 }
2122 
collectGroundFillSeeds(QList<ConnectorItem * > & seeds,bool includePotential)2123 bool PCBSketchWidget::collectGroundFillSeeds(QList<ConnectorItem *> & seeds, bool includePotential) {
2124 	QList<ConnectorItem *> trueSeeds;
2125 	QList<ConnectorItem *> potentialSeeds;
2126 
2127     int boardCount;
2128     ItemBase * board = findSelectedBoard(boardCount);
2129     if (board == NULL) return false;
2130 
2131 	foreach (QGraphicsItem * item, scene()->collidingItems(board)) {
2132 		ConnectorItem * connectorItem = dynamic_cast<ConnectorItem *>(item);
2133 		if (connectorItem == NULL) continue;
2134 		if (connectorItem->attachedToItemType() == ModelPart::CopperFill) continue;
2135 
2136 		if (connectorItem->isGroundFillSeed()) {
2137 			trueSeeds.append(connectorItem);
2138 			continue;
2139 		}
2140 
2141 		if (connectorItem->isGrounded()) {
2142 			potentialSeeds.append(connectorItem);
2143 		}
2144 	}
2145 
2146 	for (int ix = 0; ix < trueSeeds.count(); ix++) {
2147 		ConnectorItem * ci = trueSeeds.at(ix);
2148 		QList<ConnectorItem *> cis;
2149 		cis.append(ci);
2150 		ConnectorItem::collectEqualPotential(cis, true, ViewGeometry::NoFlag);
2151 		foreach (ConnectorItem * eq, cis) {
2152 			if (eq != ci) trueSeeds.removeAll(eq);
2153 			potentialSeeds.removeAll(eq);
2154 		}
2155 	}
2156 
2157 	for (int ix = 0; ix < potentialSeeds.count(); ix++) {
2158 		ConnectorItem * ci = potentialSeeds.at(ix);
2159 		QList<ConnectorItem *> cis;
2160 		cis.append(ci);
2161 		ConnectorItem::collectEqualPotential(cis, true, ViewGeometry::NoFlag);
2162 		foreach (ConnectorItem * eq, cis) {
2163 			if (eq != ci) potentialSeeds.removeAll(eq);
2164 		}
2165 	}
2166 
2167 	seeds.append(trueSeeds);
2168 	if (trueSeeds.count() == 0 || includePotential) {
2169 		seeds.append(potentialSeeds);
2170 	}
2171 
2172 	return trueSeeds.count() > 0;
2173 }
2174 
shiftHoles()2175 void PCBSketchWidget::shiftHoles() {
2176 	// vias and holes before version 0.7.3 did not have offset
2177 
2178 	VersionThing versionThingOffset;
2179 	versionThingOffset.majorVersion = 0;
2180 	versionThingOffset.minorVersion = 7;
2181 	versionThingOffset.minorSubVersion = 2;
2182 	versionThingOffset.releaseModifier = "b";
2183 	VersionThing versionThingFz;
2184 	Version::toVersionThing(m_sketchModel->fritzingVersion(), versionThingFz);
2185 	bool doShift = !Version::greaterThan(versionThingOffset, versionThingFz);
2186 	if (!doShift) return;
2187 
2188 	foreach (QGraphicsItem * item, scene()->items()) {
2189 		ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2190 		if (itemBase == NULL) continue;
2191 
2192 		switch (itemBase->itemType()) {
2193 			case ModelPart::Via:
2194 			case ModelPart::Hole:
2195 				itemBase->setPos(itemBase->pos().x() - (Hole::OffsetPixels / 2), itemBase->pos().y() - (Hole::OffsetPixels / 2));
2196 				break;
2197 
2198 			default:
2199 				continue;
2200 		}
2201 	}
2202 }
2203 
canAlignToCenter(ItemBase * itemBase)2204 bool PCBSketchWidget::canAlignToCenter(ItemBase * itemBase)
2205 {
2206 	return qobject_cast<Hole *>(itemBase) != NULL;
2207 }
2208 
selectAllItemType(ModelPart::ItemType itemType,const QString & typeName)2209 int PCBSketchWidget::selectAllItemType(ModelPart::ItemType itemType, const QString & typeName)
2210 {
2211     int boardCount;
2212     ItemBase * board = findSelectedBoard(boardCount);
2213     if (boardCount == 0  && autorouteTypePCB()) {
2214         QMessageBox::critical(this, tr("Fritzing"),
2215                    tr("Your sketch does not have a board yet!  Please add a PCB in order to use this selection operation."));
2216         return 0;
2217     }
2218     if (board == NULL) {
2219         QMessageBox::critical(this, tr("Fritzing"),
2220                    tr("Please click on a PCB first--this selection operation only works for one board at a time."));
2221         return 0;
2222     }
2223 
2224     QSet<ItemBase *> itemBases;
2225 	foreach (QGraphicsItem * item, (board == NULL ? scene()->items() : scene()->collidingItems(board))) {
2226 		ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2227 		if (itemBase == NULL) continue;
2228 		if (itemBase->itemType() != itemType) continue;
2229 
2230 		itemBases.insert(itemBase->layerKinChief());
2231 	}
2232 
2233 	return selectAllItems(itemBases, QObject::tr("Select all %1").arg(typeName));
2234 
2235 }
2236 
selectAllWires(ViewGeometry::WireFlag flag)2237 void PCBSketchWidget::selectAllWires(ViewGeometry::WireFlag flag)
2238 {
2239     int boardCount;
2240     ItemBase * board = findSelectedBoard(boardCount);
2241     if (boardCount == 0  && autorouteTypePCB()) {
2242         QMessageBox::critical(this, tr("Fritzing"),
2243                    tr("Your sketch does not have a board yet!  Please add a PCB in order to use this selection operation."));
2244         return;
2245     }
2246     if (board == NULL) {
2247         QMessageBox::critical(this, tr("Fritzing"),
2248                    tr("Please click on a PCB first--this selection operation only works for one board at a time."));
2249         return;
2250     }
2251 
2252     QList<QGraphicsItem *> items = scene()->collidingItems(board);
2253     selectAllWiresFrom(flag, items);
2254 }
2255 
defaultViewLayerPlacement(ModelPart * modelPart)2256 ViewLayer::ViewLayerPlacement PCBSketchWidget::defaultViewLayerPlacement(ModelPart * modelPart) {
2257     if (modelPart == NULL || boardLayers() == 2) return SketchWidget::defaultViewLayerPlacement(modelPart);
2258 
2259     if (modelPart->flippedSMD()) return ViewLayer::NewBottom;
2260     if (modelPart->moduleID() == ModuleIDNames::GroundPlaneModuleIDName) return ViewLayer::NewBottom;
2261 
2262     return SketchWidget::defaultViewLayerPlacement(modelPart);
2263 }
2264 
checkDroppedModuleID(const QString & moduleID)2265 QString PCBSketchWidget::checkDroppedModuleID(const QString & moduleID) {
2266     if (moduleID.endsWith(ModuleIDNames::CopperBlockerModuleIDName)) {
2267         if (dropOnBottom()) return ModuleIDNames::Copper0BlockerModuleIDName;
2268 
2269         return ModuleIDNames::Copper1BlockerModuleIDName;
2270     }
2271 
2272     if (moduleID.endsWith(ModuleIDNames::PadModuleIDName)) {
2273         if (dropOnBottom()) return ModuleIDNames::Copper0PadModuleIDName;
2274 
2275         return ModuleIDNames::PadModuleIDName;
2276     }
2277 
2278     if (moduleID.endsWith(ModuleIDNames::RectanglePCBModuleIDName)) {
2279         if (boardLayers() == 2) return ModuleIDNames::TwoSidedRectanglePCBModuleIDName;
2280         return ModuleIDNames::RectanglePCBModuleIDName;
2281     }
2282 
2283     if (moduleID.endsWith(ModuleIDNames::EllipsePCBModuleIDName)) {
2284         if (boardLayers() == 2) return ModuleIDNames::TwoSidedEllipsePCBModuleIDName;
2285         return ModuleIDNames::EllipsePCBModuleIDName;
2286     }
2287 
2288     return moduleID;
2289 }
2290 
convertToVia(ConnectorItem * lastHoverEnterConnectorItem)2291 void PCBSketchWidget::convertToVia(ConnectorItem * lastHoverEnterConnectorItem) {
2292     Wire * wire = qobject_cast<Wire *>(lastHoverEnterConnectorItem->attachedTo());
2293     if (wire == NULL) return;
2294 
2295     this->clearHoldingSelectItem();
2296 	this->m_moveEventCount = 0;  // clear this so an extra MoveItemCommand isn't posted
2297 
2298 	QUndoCommand * parentCommand = new QUndoCommand(QObject::tr("Convert to Via"));
2299 
2300     new CleanUpWiresCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2301     new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2302 
2303     double ringThickness, holeSize;
2304     getViaSize(ringThickness, holeSize);
2305     QPointF p = lastHoverEnterConnectorItem->sceneAdjustedTerminalPoint(NULL);
2306     double d = ringThickness + (holeSize / 2) + Via::OffsetPixels;
2307     QPointF loc(p.x() - d, p.y() - d);
2308     long newID = ItemBase::getNextID();
2309 	ViewGeometry viewGeometry;
2310 	viewGeometry.setLoc(loc);
2311     new AddItemCommand(this, BaseCommand::CrossView, ModuleIDNames::ViaModuleIDName, wire->viewLayerPlacement(), viewGeometry, newID, true, -1, parentCommand);
2312 
2313     QList<ConnectorItem *> connectorItems;
2314     connectorItems.append(lastHoverEnterConnectorItem);
2315     for (int i = 0; i < connectorItems.count(); i++) {
2316         ConnectorItem * from = connectorItems.at(i);
2317         foreach (ConnectorItem * to, from->connectedToItems()) {
2318             Wire * w = qobject_cast<Wire *>(to->attachedTo());
2319             if (w != NULL && w->isTraceType(getTraceFlag())) {
2320                 if (!connectorItems.contains(to)) {
2321                     connectorItems.append(to);
2322                 }
2323             }
2324         }
2325     }
2326 
2327 
2328     foreach (ConnectorItem * from, connectorItems) {
2329         foreach (ConnectorItem * to, from->connectedToItems()) {
2330             Wire * w = qobject_cast<Wire *>(to->attachedTo());
2331             if (w != NULL && w->isTraceType(getTraceFlag())) {
2332 		        new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2333 			        to->attachedToID(), to->connectorSharedID(),
2334 			        ViewLayer::specFromID(w->viewLayerID()),
2335 			        false, parentCommand);
2336             }
2337         }
2338     }
2339 
2340     foreach (ConnectorItem * from, connectorItems) {
2341 		new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2342 			newID, "connector0",
2343 			ViewLayer::specFromID(wire->viewLayerID()),
2344 			true, parentCommand);
2345 
2346     }
2347 
2348 	SelectItemCommand * selectItemCommand = new SelectItemCommand(this, SelectItemCommand::NormalSelect, parentCommand);
2349 	selectItemCommand->addRedo(newID);
2350 
2351     new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2352 	new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2353 
2354 	m_undoStack->push(parentCommand);
2355 
2356 }
2357 
convertToBendpoint()2358 void PCBSketchWidget::convertToBendpoint() {
2359 
2360     ItemBase * itemBase = NULL;
2361     foreach (QGraphicsItem * item,  scene()->selectedItems()) {
2362         ItemBase * candidate = dynamic_cast<ItemBase *>(item);
2363         if (candidate == NULL) continue;
2364 
2365         if (itemBase == NULL) itemBase = candidate->layerKinChief();
2366         else if (candidate->layerKinChief() != itemBase) return;
2367     }
2368 
2369     Via * via = dynamic_cast<Via *>(itemBase);
2370     if (via == NULL) return;
2371 
2372     QList<ConnectorItem *> viaConnectorItems;
2373     viaConnectorItems << via->connectorItem();
2374     if (via->connectorItem()->getCrossLayerConnectorItem()) {
2375         viaConnectorItems << via->connectorItem()->getCrossLayerConnectorItem();
2376     }
2377 
2378     QList<ConnectorItem *> targets;
2379     int copper0 = 0;
2380     int copper1 = 0;
2381     bool copper0Only = false;
2382     bool copper1Only = false;
2383 
2384     foreach (ConnectorItem * viaConnectorItem, viaConnectorItems) {
2385         foreach (ConnectorItem * connectorItem, viaConnectorItem->connectedToItems()) {
2386             Wire * wire = qobject_cast<Wire *>(connectorItem->attachedTo());
2387             if (wire == NULL) continue;
2388             if (wire->getRatsnest()) continue;
2389             if (!wire->isTraceType(getTraceFlag())) continue;
2390 
2391             bool gotOne = false;;
2392             if (wire->viewLayerID() == ViewLayer::Copper0Trace) {
2393                 copper0++;
2394                 gotOne = true;
2395             }
2396             else if (wire->viewLayerID() == ViewLayer::Copper1Trace) {
2397                 copper1++;
2398                 gotOne = true;
2399             }
2400 
2401             if (!gotOne) continue;
2402 
2403             targets.append(connectorItem);
2404             QList<Wire *> wires;
2405             QList<ConnectorItem *> ends;
2406             wire->collectChained(wires, ends);
2407             foreach (ConnectorItem * end, ends) {
2408                 if (end->getCrossLayerConnectorItem() == NULL) {
2409                     if (ViewLayer::copperLayers(ViewLayer::NewTop).contains(end->attachedToViewLayerID())) {
2410                         copper1Only = true;
2411                     }
2412                     else {
2413                         copper0Only = true;
2414                     }
2415                 }
2416             }
2417         }
2418     }
2419 
2420     if (copper0Only && copper1Only) {
2421         QMessageBox::warning(this, tr("Fritzing"),
2422               tr("Unable to convert this via to a bendpoint because it is connected to a part that is only on the bottom layer and another part that is only on the top layer."));
2423         return;
2424     }
2425 
2426     this->clearHoldingSelectItem();
2427 	this->m_moveEventCount = 0;  // clear this so an extra MoveItemCommand isn't posted
2428 
2429 	QUndoCommand * parentCommand = new QUndoCommand(QObject::tr("Convert Via to Bendpoint"));
2430 
2431     new CleanUpWiresCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2432     new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::UndoOnly, parentCommand);
2433 
2434     foreach (ConnectorItem * target, targets) {
2435 		new ChangeConnectionCommand(this, BaseCommand::CrossView, target->attachedToID(), target->connectorSharedID(),
2436 			via->id(), via->connectorItem()->connectorSharedID(),
2437 			ViewLayer::specFromID(target->attachedToViewLayerID()),
2438 			false, parentCommand);
2439     }
2440 
2441     ViewLayer::ViewLayerID dest = ViewLayer::Copper0Trace;
2442     if (copper1Only) {
2443         dest = ViewLayer::Copper1Trace;
2444     }
2445     else if (!copper0Only) {
2446         if (copper1 > copper0) {
2447             dest = ViewLayer::Copper1Trace;
2448         }
2449     }
2450 
2451     if (copper0 > 0 && copper1 > 0) {
2452         foreach (ConnectorItem * target, targets) {
2453             if (target->attachedToViewLayerID() == dest) continue;
2454 
2455             Wire * wire = qobject_cast<Wire *>(target->attachedTo());
2456             QList<Wire *> wires;
2457             QList<ConnectorItem *> ends;
2458             wire->collectChained(wires, ends);
2459             foreach (Wire * w, wires) {
2460 	            new ChangeLayerCommand(this, w->id(), w->zValue(), m_viewLayers.value(dest)->nextZ(), w->viewLayerID(), dest, parentCommand);
2461             }
2462         }
2463     }
2464 
2465 
2466     ConnectorItem * from = targets.at(0);
2467     for (int j = 1; j < targets.count(); j++) {
2468         ConnectorItem * to = targets.at(j);
2469         new ChangeConnectionCommand(this, BaseCommand::CrossView, from->attachedToID(), from->connectorSharedID(),
2470 			to->attachedToID(), to->connectorSharedID(),
2471 			ViewLayer::specFromID(dest),
2472 			true, parentCommand);
2473     }
2474 
2475 
2476     makeDeleteItemCommand(via, BaseCommand::CrossView, parentCommand);
2477 
2478     new CleanUpRatsnestsCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2479 	new CleanUpWiresCommand(this, CleanUpWiresCommand::RedoOnly, parentCommand);
2480 
2481 	m_undoStack->push(parentCommand);
2482 }
2483 
2484 
2485 
canConnect(Wire * from,ItemBase * to)2486 bool PCBSketchWidget::canConnect(Wire * from, ItemBase * to) {
2487     to = to->layerKinChief();
2488     QList<ItemBase *> kin;
2489     kin.append(to);
2490     kin.append(to->layerKin());
2491 
2492     foreach (ItemBase * itemBase, kin) {
2493         if (!ViewLayer::isCopperLayer(itemBase->viewLayerID())) continue;
2494 
2495         if (ViewLayer::canConnect(from->viewLayerID(), itemBase->viewLayerID())) return true;
2496     }
2497 
2498     return false;
2499 }
2500 
makePasteMask(const QString & svgMask,ItemBase * board,double dpi,const LayerList & maskLayerIDs)2501 QString PCBSketchWidget::makePasteMask(const QString & svgMask, ItemBase * board, double dpi, const LayerList & maskLayerIDs)
2502 {
2503     QList<ConnectorItem *> throughHoles;
2504     QList<ConnectorItem *> pads;
2505     collectThroughHole(throughHoles, pads, maskLayerIDs);
2506     if (pads.count() == 0) return "";
2507 
2508     QRectF boardRect = board->sceneBoundingRect();
2509     QList<QRectF> connectorRects;
2510     foreach (ConnectorItem * connectorItem, throughHoles) {
2511         QRectF r = connectorItem->sceneBoundingRect();
2512         QRectF s((r.left() - boardRect.left())  * dpi / GraphicsUtils::SVGDPI,
2513                  (r.top() - boardRect.top()) * dpi / GraphicsUtils::SVGDPI,
2514                  r.width() * dpi / GraphicsUtils::SVGDPI,
2515                  r.height() * dpi / GraphicsUtils::SVGDPI);
2516         connectorRects << s;
2517     }
2518 
2519     QDomDocument doc;
2520     doc.setContent(svgMask);
2521     QList<QDomElement> leaves;
2522     QDomElement root = doc.documentElement();
2523     TextUtils::collectLeaves(root, leaves);
2524     int ix = 0;
2525     foreach (QDomElement element, leaves) {
2526         element.setAttribute("id", ix++);
2527     }
2528 
2529     QSvgRenderer renderer;
2530     renderer.load(doc.toByteArray());
2531 
2532     foreach (QDomElement element, leaves) {
2533         QString id = element.attribute("id");
2534         QRectF bounds = renderer.boundsOnElement(id);
2535         QRectF leafRect = renderer.matrixForElement(id).mapRect(bounds);
2536         QPointF leafCenter = leafRect.center();
2537         foreach (QRectF r, connectorRects) {
2538             if (!leafRect.intersects(r)) continue;
2539 
2540             if (!r.contains(leafCenter)) continue;
2541 
2542             QPointF rCenter = r.center();
2543             if (!leafRect.contains(rCenter)) continue;
2544 
2545             element.setTagName("g");
2546             break;
2547         }
2548     }
2549 
2550     return doc.toString();
2551 }
2552 
checkLoadedTraces()2553 int PCBSketchWidget::checkLoadedTraces() {
2554 
2555     ProcessEventBlocker::processEvents();
2556     scene()->clearSelection();
2557     QList<Wire *> wires;
2558     QHash<Wire *, QLineF> lines;
2559     foreach (QGraphicsItem * item, scene()->items()) {
2560         Wire * wire = dynamic_cast<Wire *>(item);
2561         if (wire == NULL) continue;
2562         if (!wire->isTraceType(getTraceFlag())) continue;
2563 
2564         ConnectorItem * c0 = wire->connector0();
2565         ConnectorItem * c1 = wire->connector1();
2566         QPointF p0 = c0->sceneBoundingRect().center();
2567         QPointF p1 = c1->sceneBoundingRect().center();
2568         QLineF line(p0, p1);
2569         lines.insert(wire, line);
2570     }
2571 
2572     foreach (Wire * wire, lines.keys()) {
2573         QList<ConnectorItem *> already;
2574 		wire->updateConnections(wire->connector0(), false, already);
2575 		wire->updateConnections(wire->connector1(), false, already);
2576     }
2577 
2578     foreach (Wire * wire, lines.keys()) {
2579         QLineF line = wire->line();
2580         QPointF l0 = wire->pos() + line.p1();
2581         QPointF l1 = wire->pos() + line.p2();
2582         QLineF oldLine = lines.value(wire);
2583 
2584         double d = 0.1;
2585 
2586         if (qAbs(oldLine.p1().x() - l0.x()) > d ||
2587             qAbs(oldLine.p1().y() - l0.y()) > d ||
2588             qAbs(oldLine.p2().x() - l1.x()) > d ||
2589             qAbs(oldLine.p2().y() - l1.y()) > d)
2590         {
2591             wires.append(wire);
2592             wire->debugInfo2(QString("wire moved from:\n%1,%2 %3,%4\n%5,%6 %7,%8")
2593                 .arg(oldLine.p1().x()).arg(oldLine.p1().y()).arg(oldLine.p2().x()).arg(oldLine.p2().y())
2594                 .arg(l0.x()).arg(l0.y()).arg(l1.x()).arg(l1.y())
2595             );
2596         }
2597     }
2598 
2599     foreach (Wire * wire, wires) {
2600         wire->setSelected(true);
2601     }
2602 
2603     return wires.count();
2604 }
2605 
hasCustomBoardShape()2606 bool PCBSketchWidget::hasCustomBoardShape() {
2607 	QList<ItemBase *> boards = findBoard();
2608 	foreach (ItemBase * board, boards) {
2609 		if (qobject_cast<BoardLogoItem *>(board)) return true;
2610 	}
2611 
2612 	return false;
2613 }
2614 
getViewLayerPlacement(ModelPart * modelPart,QDomElement & instance,QDomElement & view,ViewGeometry & viewGeometry)2615 ViewLayer::ViewLayerPlacement PCBSketchWidget::getViewLayerPlacement(ModelPart * modelPart, QDomElement & instance, QDomElement & view, ViewGeometry & viewGeometry)
2616 {
2617     if (modelPart->flippedSMD()) {
2618         ViewLayer::ViewLayerID viewLayerID = ViewLayer::viewLayerIDFromXmlString(view.attribute("layer"));
2619         if (ViewLayer::bottomLayers().contains(viewLayerID)) return ViewLayer::NewBottom;
2620         return ViewLayer::NewTop;
2621     }
2622 
2623     if (modelPart->itemType() == ModelPart::Part) {
2624         QDomElement views = view.parentNode().toElement();
2625 	    QDomElement pcbview = views.firstChildElement("pcbView");
2626         bool bottom = pcbview.attribute("bottom", "").compare("true") == 0;
2627         if (bottom) return ViewLayer::NewBottom;
2628     }
2629 
2630     return SketchWidget::getViewLayerPlacement(modelPart, instance, view, viewGeometry);
2631 }
2632 
routingLayers(ViewLayer::ViewLayerPlacement spec)2633 LayerList PCBSketchWidget::routingLayers(ViewLayer::ViewLayerPlacement spec) {
2634     LayerList layerList = ViewLayer::copperLayers(spec);
2635     layerList.removeOne(ViewLayer::GroundPlane0);
2636     layerList.removeOne(ViewLayer::GroundPlane1);
2637     return layerList;
2638 }
2639 
attachedToBottomLayer(ConnectorItem * connectorItem)2640 bool PCBSketchWidget::attachedToBottomLayer(ConnectorItem * connectorItem) {
2641     return (connectorItem->attachedToViewLayerID() == ViewLayer::Copper0) ||
2642            (connectorItem->attachedToViewLayerID() == ViewLayer::Copper0Trace);
2643 }
2644 
attachedToTopLayer(ConnectorItem * connectorItem)2645 bool PCBSketchWidget::attachedToTopLayer(ConnectorItem * connectorItem) {
2646     return (connectorItem->attachedToViewLayerID() == ViewLayer::Copper1) ||
2647            (connectorItem->attachedToViewLayerID() == ViewLayer::Copper1Trace);
2648 }
2649 
getAutorouterSettings()2650 QHash<QString, QString> PCBSketchWidget::getAutorouterSettings() {
2651     return m_autorouterSettings;
2652 }
2653 
setAutorouterSettings(QHash<QString,QString> & autorouterSettings)2654 void PCBSketchWidget::setAutorouterSettings(QHash<QString, QString> & autorouterSettings) {
2655     QList<QString> keys;
2656     keys << DRC::KeepoutSettingName << AutorouterSettingsDialog::AutorouteTraceWidth << Via::AutorouteViaHoleSize << Via::AutorouteViaRingThickness << GroundPlaneGenerator::KeepoutSettingName;
2657     foreach (QString key, keys) {
2658         m_autorouterSettings.insert(key, autorouterSettings.value(key, ""));
2659     }
2660 }
2661 
hidePartSilkscreen()2662 void PCBSketchWidget::hidePartSilkscreen() {
2663 
2664     ItemBase * itemBase = NULL;
2665     foreach (QGraphicsItem * item,  scene()->selectedItems()) {
2666         ItemBase * candidate = dynamic_cast<ItemBase *>(item);
2667         if (candidate == NULL) continue;
2668 
2669         itemBase = candidate->layerKinChief();
2670         break;
2671     }
2672 
2673     if (itemBase == NULL) return;
2674 
2675     QList<ItemBase *> itemBases;
2676     itemBases.append(itemBase);
2677     itemBases.append(itemBase->layerKin());
2678     foreach (ItemBase * lkpi, itemBases) {
2679         if (lkpi->viewLayerID() == ViewLayer::Silkscreen1 || lkpi->viewLayerID() == ViewLayer::Silkscreen0) {
2680             bool layerHidden = lkpi->layerHidden();
2681             QUndoCommand * parentCommand = new QUndoCommand(layerHidden ? tr("Show part silkscreen") : tr("Hide part silkscreen"));
2682             new HidePartLayerCommand(this, itemBase->id(), ViewLayer::Silkscreen0, layerHidden, !layerHidden, parentCommand);
2683             new HidePartLayerCommand(this, itemBase->id(), ViewLayer::Silkscreen1, layerHidden, !layerHidden, parentCommand);
2684             m_undoStack->push(parentCommand);
2685             break;
2686         }
2687     }
2688 }
2689 
fabQuote()2690 void PCBSketchWidget::fabQuote() {
2691     int boardCount = 0;
2692     double area = calcBoardArea(boardCount);
2693     QuoteDialog::setArea(area, boardCount);
2694     if (boardCount == 0) {
2695         QMessageBox::information(this, tr("Fritzing Fab Quote"),
2696                    tr("Your sketch does not have a board yet. You cannot fabricate this sketch without a PCB part."));
2697         return;
2698     }
2699 
2700     if (!QuoteDialog::quoteSucceeded()) {
2701         QMessageBox::information(this, tr("Fritzing Fab Quote"),
2702                    tr("Sorry, http://fab.fritzing.org is not responding to the quote request. Please check your network connection and/or try again later."));
2703         requestQuote(true);
2704         return;
2705     }
2706 
2707     m_quoteDialog = new QuoteDialog(true, this);
2708     requestQuote(true);
2709 
2710     m_quoteDialog->exec();
2711     delete m_quoteDialog;
2712     m_quoteDialog = NULL;
2713 }
2714 
gotFabQuote(QNetworkReply * networkReply)2715 void PCBSketchWidget::gotFabQuote(QNetworkReply * networkReply) {
2716     QNetworkAccessManager * manager = networkReply->manager();
2717     QString count = manager->property("count").toString();
2718     QStringList countArgs = count.split(",");
2719 	int responseCode = networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2720 	if (responseCode == 200) {
2721         QString data(networkReply->readAll());
2722         QStringList values = data.split(",");
2723         if (values.count() == countArgs.count()) {
2724             for (int ix = 0; ix < countArgs.count(); ix++) {
2725                 QString value = values.at(ix);
2726                 QString c = countArgs.at(ix);
2727                 bool ok;
2728                 int count = c.toInt(&ok);
2729                 if (!ok) continue;
2730 
2731                 double cost = value.toDouble(&ok);
2732                 if (!ok) continue;
2733 
2734                QuoteDialog::setCountCost(ix, count, cost);
2735             }
2736             QuoteDialog::setQuoteSucceeded(true);
2737         }
2738 
2739         if (m_quoteDialog) m_quoteDialog->setText();
2740         if (m_rolloverQuoteDialog) m_rolloverQuoteDialog->setText();
2741 	}
2742     else {
2743     }
2744 
2745     manager->deleteLater();
2746     networkReply->deleteLater();
2747 }
2748 
requestQuote(bool byUser)2749 void PCBSketchWidget::requestQuote(bool byUser) {
2750     int boardCount;
2751     double area = calcBoardArea(boardCount);
2752     QuoteDialog::setArea(area, boardCount);
2753 
2754     QString paramString = Version::makeRequestParamsString(false);
2755     QNetworkAccessManager * manager = new QNetworkAccessManager(this);
2756 
2757 
2758     QString countArgs = QuoteDialog::countArgs();
2759     manager->setProperty("count", countArgs);
2760     QString filename = QUrl::toPercentEncoding(filenameIf());
2761 	connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(gotFabQuote(QNetworkReply *)));
2762     QString string = QString("http://fab.fritzing.org/fritzing-fab/quote%1&area=%2&count=%3&filename=%4&byuser=%5")
2763                 .arg(paramString)
2764                 .arg(area)
2765                 .arg(countArgs)
2766                 .arg(filename)
2767                 .arg(byUser)
2768                 ;
2769     QuoteDialog::setQuoteSucceeded(false);
2770 	manager->get(QNetworkRequest(QUrl(string)));
2771 }
2772 
calcBoardArea(int & boardCount)2773 double PCBSketchWidget::calcBoardArea(int & boardCount) {
2774     QList<ItemBase *> boards = findBoard();
2775     boardCount = boards.count();
2776     if (boardCount == 0) {
2777         return 0;
2778     }
2779 
2780     double area = 0;
2781     foreach (ItemBase * board, boards) {
2782         area += GraphicsUtils::pixels2mm(board->boundingRect().width(), GraphicsUtils::SVGDPI) *
2783             GraphicsUtils::pixels2mm(board->boundingRect().height(), GraphicsUtils::SVGDPI) /
2784             100;
2785     }
2786 
2787     return area;
2788 }
2789 
addPartItem(ModelPart * modelPart,ViewLayer::ViewLayerPlacement viewLayerPlacement,PaletteItem * paletteItem,bool doConnectors,bool & ok,ViewLayer::ViewID viewID,bool temporary)2790 PaletteItem* PCBSketchWidget::addPartItem(ModelPart * modelPart, ViewLayer::ViewLayerPlacement viewLayerPlacement, PaletteItem * paletteItem, bool doConnectors, bool & ok, ViewLayer::ViewID viewID, bool temporary) {
2791     if (viewID == ViewLayer::PCBView && Board::isBoard(modelPart)) {
2792         requestQuoteSoon();
2793     }
2794     return SketchWidget::addPartItem(modelPart, viewLayerPlacement, paletteItem, doConnectors, ok, viewID, temporary);
2795 }
2796 
requestQuoteSoon()2797 void PCBSketchWidget::requestQuoteSoon() {
2798     m_requestQuoteTimer.stop();
2799     m_requestQuoteTimer.start();
2800 }
2801 
requestQuoteNow()2802 void PCBSketchWidget::requestQuoteNow() {
2803     m_requestQuoteTimer.stop();
2804     requestQuote(false);
2805 }
2806 
resizeBoard(long itemID,double mmW,double mmH)2807 ItemBase * PCBSketchWidget::resizeBoard(long itemID, double mmW, double mmH) {
2808     ItemBase * itemBase = SketchWidget::resizeBoard(itemID, mmW, mmH);
2809     if (itemBase != NULL && Board::isBoard(itemBase)) requestQuoteSoon();
2810     return itemBase;
2811 }
2812 
quoteDialog(QWidget * parent)2813 QDialog * PCBSketchWidget::quoteDialog(QWidget * parent) {
2814     if (m_rolloverQuoteDialog == NULL) {
2815         m_rolloverQuoteDialog = new QuoteDialog(false, parent);
2816         requestQuote(false);
2817     }
2818     m_rolloverQuoteDialog->setText();
2819     return m_rolloverQuoteDialog;
2820 }
2821 
getKeepoutMils()2822 double PCBSketchWidget::getKeepoutMils() {
2823     QSettings settings;
2824     QString keepoutString = m_autorouterSettings.value(GroundPlaneGenerator::KeepoutSettingName);
2825     if (keepoutString.isEmpty()) {
2826         keepoutString = settings.value(GroundPlaneGenerator::KeepoutSettingName, "").toString();
2827     }
2828 
2829     bool ok;
2830     double mils = TextUtils::convertToInches(keepoutString, &ok, false);
2831     if (ok) {
2832         mils *= 1000;  // convert from inches
2833     }
2834     else {
2835         mils = GroundPlaneGenerator::KeepoutDefaultMils;
2836     }
2837 
2838     return mils;
2839 }
2840 
setGroundFillKeepout()2841 void PCBSketchWidget::setGroundFillKeepout() {
2842 
2843     bool ok;
2844     double mils = QInputDialog::getInt(this, tr("Enter Keepout"),
2845                                           tr("Keepout is in mils (.001 inches).\n\n") +
2846                                           tr("Note that due to aliasing, distances may be too short by up to 2 mils\n") +
2847                                           tr("so you may want to increase the keepout value by that much.\n\n") +
2848                                           tr("10 mils is a good default choice.\n\n") +
2849                                           tr("Enter keepout value:"),
2850                                           qRound(getKeepoutMils()), 0, 10 * 1000, 1, &ok);
2851 
2852 
2853     if (!ok) return;
2854 
2855     QString keepoutString = QString("%1in").arg(mils / 1000);
2856     m_autorouterSettings.insert(GroundPlaneGenerator::KeepoutSettingName, keepoutString);
2857 
2858     QSettings settings;
2859     settings.setValue(GroundPlaneGenerator::KeepoutSettingName, keepoutString);
2860 }
2861 
setViewFromBelow(bool viewFromBelow)2862 void PCBSketchWidget::setViewFromBelow(bool viewFromBelow) {
2863     if (m_viewFromBelow == viewFromBelow) return;
2864 
2865     QSet<ItemBase *> chiefs;
2866 	foreach (QGraphicsItem * item, scene()->items()) {
2867         ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2868         if (itemBase == NULL) continue;
2869 
2870         ViewLayer * viewLayer = m_viewLayers.value(itemBase->viewLayerID(), NULL);
2871         if (viewLayer == NULL) continue;
2872 
2873         double newZ = viewLayer->getZFromBelow(itemBase->z(), viewFromBelow);
2874         itemBase->setZValue(newZ);
2875         itemBase->getViewGeometry().setZ(newZ);
2876         chiefs.insert(itemBase->layerKinChief());
2877     }
2878 
2879     foreach (ItemBase * chief, chiefs) {
2880         chief->figureHover();
2881     }
2882 
2883     foreach (ViewLayer * viewLayer, m_viewLayers.values()) {
2884         viewLayer->setFromBelow(viewFromBelow);
2885     }
2886 
2887     // enable only with multiple layers?
2888     // active layer should be secondary mechanism?
2889 
2890     SketchWidget::setViewFromBelow(viewFromBelow);
2891 }
2892 
getDroppedItemViewLayerPlacement(ModelPart * modelPart,ViewLayer::ViewLayerPlacement & viewLayerPlacement)2893 void PCBSketchWidget::getDroppedItemViewLayerPlacement(ModelPart * modelPart, ViewLayer::ViewLayerPlacement & viewLayerPlacement) {
2894     if (ResizableBoard::isBoard(modelPart)) {
2895         viewLayerPlacement = ViewLayer::NewTop;
2896         return;
2897     }
2898 
2899     viewLayerPlacement = defaultViewLayerPlacement(modelPart);   // top for a two layer board
2900     if (boardLayers() == 2) {
2901         if (dropOnBottom()) {
2902             viewLayerPlacement = ViewLayer::NewBottom;
2903         }
2904         return;
2905     }
2906 }
2907 
dropOnBottom()2908 bool PCBSketchWidget::dropOnBottom() {
2909     if (boardLayers() == 1) return true;
2910 
2911     if (!layerIsActive(ViewLayer::Copper0)) return false;
2912     if (!layerIsActive(ViewLayer::Copper1)) return true;
2913 
2914     return viewFromBelow();
2915 }
2916 
updateOK(ConnectorItem * c1,ConnectorItem * c2)2917 bool PCBSketchWidget::updateOK(ConnectorItem * c1, ConnectorItem * c2) {
2918     // don't update if both connectors belong to parts--this isn't legit in schematic or pcb view
2919     if (c1->attachedTo()->wireFlags()) return true;
2920     return c2->attachedTo()->wireFlags() != 0;
2921 }
2922