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: 6417 $:
22 $Author: cohen@irascible.com $:
23 $Date: 2012-09-14 23:34:09 +0200 (Fr, 14. Sep 2012) $
24 
25 ********************************************************************/
26 
27 #include <QMessageBox>
28 #include <QFileDialog>
29 #include <QtDebug>
30 #include <QSvgGenerator>
31 #include <QGraphicsProxyWidget>
32 #include <QVarLengthArray>
33 #include <QDialogButtonBox>
34 #include <QFormLayout>
35 #include <QComboBox>
36 #include <QLabel>
37 #include <QPushButton>
38 #include <QScrollBar>
39 
40 #include "partseditorview.h"
41 #include "partseditorconnectoritem.h"
42 #include "fixfontsdialog.h"
43 #include "zoomcontrols.h"
44 #include "kicadmoduledialog.h"
45 #include "../items/layerkinpaletteitem.h"
46 #include "../items/partfactory.h"
47 #include "../layerattributes.h"
48 #include "../mainwindow/fritzingwindow.h"
49 #include "../fsvgrenderer.h"
50 #include "../debugdialog.h"
51 #include "../utils/folderutils.h"
52 #include "../utils/textutils.h"
53 #include "../utils/graphicsutils.h"
54 #include "../utils/ratsnestcolors.h"
55 #include "../svg/gedaelement2svg.h"
56 #include "../svg/kicadmodule2svg.h"
57 #include "../svg/kicadschematic2svg.h"
58 #include "../connectors/connectorshared.h"
59 
60 
61 int PartsEditorView::ConnDefaultWidth = 5;
62 int PartsEditorView::ConnDefaultHeight = ConnDefaultWidth;
63 
PartsEditorView(ViewLayer::ViewIdentifier viewId,QDir tempDir,bool showingTerminalPoints,QGraphicsProxyWidget * startItem,QWidget * parent,int size,bool deleteModelPartOnClearScene,ItemBase * fromItem)64 PartsEditorView::PartsEditorView(
65 		ViewLayer::ViewIdentifier viewId, QDir tempDir,
66 		bool showingTerminalPoints, QGraphicsProxyWidget *startItem,
67 		QWidget *parent, int size, bool deleteModelPartOnClearScene,
68 		ItemBase * fromItem)
69 	: SketchWidget(viewId, parent, size, size)
70 {
71     m_alignToGrid = m_showGrid = false;
72 	m_viewItem = NULL;
73 	m_item = NULL;
74 	m_connsLayerID = ViewLayer::UnknownLayer;
75 	m_svgLoaded = false;
76 	m_deleteModelPartOnSceneClear = deleteModelPartOnClearScene;
77 	m_tempFolder = tempDir;
78 	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
79 	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
80 	setDefaultBackground();
81 
82 
83 
84 	//spec
85 	m_svgFilePath = new SvgAndPartFilePath;
86 	m_startItem = startItem;
87 	if(m_startItem) {
88 		addFixedToCenterItem(startItem);
89 		ensureFixedToCenterItems();
90 
91 		connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(ensureFixedItemsPositions()));
92 		connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(ensureFixedItemsPositions()));
93 
94 		connect(this, SIGNAL(resizeSignal()), this, SLOT(ensureFixedItemsPositions()));
95 		connect(this, SIGNAL(wheelSignal()),  this, SLOT(ensureFixedItemsPositions()));
96 
97 
98 		// TODO: do we still need this?
99 		QTimer::singleShot(400, this, SLOT(ensureFixedItemsPositions()));
100 	}
101 	addDefaultLayers(fromItem);
102 
103 
104 	// conns
105 	m_showingTerminalPoints = showingTerminalPoints;
106 	m_lastSelectedConnId = "";
107 
108 	setDragMode(QGraphicsView::ScrollHandDrag);
109 
110 	setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
111 
112 	m_terminalPointsTimer = new QTimer(this);
113 	connect(
114 		m_terminalPointsTimer,SIGNAL(timeout()),
115 		this,SLOT(recoverTerminalPointsState())
116 	);
117 	m_showingTerminalPointsBackup = m_showingTerminalPoints;
118 
119 	m_fitItemInViewTimer = new QTimer(this);
120 	m_fitItemInViewTimer->setSingleShot(true);
121 	m_fitItemInViewTimer->setInterval(200);
122 	connect(m_fitItemInViewTimer,SIGNAL(timeout()),this,SLOT(fitCenterAndDeselect()));
123 }
124 
~PartsEditorView()125 PartsEditorView::~PartsEditorView() {
126 	if (m_startItem) delete m_startItem;
127 	delete m_svgFilePath;
128 	clearScene();
129 }
130 
addDefaultLayers(ItemBase * fromItem)131 void PartsEditorView::addDefaultLayers(ItemBase * fromItem) {
132 	switch( m_viewIdentifier ) {
133 		case ViewLayer::BreadboardView:
134 			addBreadboardViewLayers();
135 			break;
136 		case ViewLayer::SchematicView:
137 			addSchematicViewLayers();
138 			break;
139 		case ViewLayer::PCBView:
140 			addPcbViewLayers();
141 			if (fromItem && fromItem->modelPart()->flippedSMD()) {
142 				DebugDialog::debug("editing an SMD part");
143 				setViewLayerIDs(ViewLayer::Silkscreen1, ViewLayer::Copper1Trace, ViewLayer::Copper1, ViewLayer::PcbRuler, ViewLayer::PcbNote);
144 				this->m_viewLayers.remove(ViewLayer::Copper0);
145 				this->m_viewLayers.remove(ViewLayer::Silkscreen0);
146 				this->m_viewLayers.remove(ViewLayer::Copper0Trace);
147 			}
148 			break;
149 		default:
150 			break;
151 	}
152 }
153 
addItemInPartsEditor(ModelPart * modelPart,SvgAndPartFilePath * svgFilePath)154 void PartsEditorView::addItemInPartsEditor(ModelPart * modelPart, SvgAndPartFilePath * svgFilePath) {
155 	if (modelPart == NULL) {
156 		throw "PartsEditorView::addItemInPartsEditor no model part";
157 	}
158 	clearScene();
159 
160 	m_item = newPartsEditorPaletteItem(modelPart, svgFilePath);
161 	this->addItem(modelPart, defaultViewLayerSpec(), BaseCommand::CrossView, m_item->getViewGeometry(), m_item->id(), -1, NULL, m_item);
162 
163 	fitCenterAndDeselect();
164 
165 	setItemProperties();
166 
167 	/*foreach(QWidget* w, m_fixedWidgets) {
168 		QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
169 		proxy->setWidget(w);
170 
171 		addFixedToBottomRightItem(proxy);
172 	}*/
173 
174 	emit connectorsFoundSignal(this->m_viewIdentifier,m_item->connectors());
175 }
176 
addItemAux(ModelPart * modelPart,ViewLayer::ViewLayerSpec viewLayerSpec,const ViewGeometry &,long id,PaletteItem * paletteItemAux,bool doConnectors,ViewLayer::ViewIdentifier,bool temporary)177 ItemBase * PartsEditorView::addItemAux(ModelPart * modelPart, ViewLayer::ViewLayerSpec viewLayerSpec, const ViewGeometry &, long id, PaletteItem * paletteItemAux, bool doConnectors, ViewLayer::ViewIdentifier, bool temporary) {
178 	Q_UNUSED(id);
179 	Q_UNUSED(temporary);
180 
181 	if(paletteItemAux == NULL) {
182 		paletteItemAux = newPartsEditorPaletteItem(modelPart);
183 	}
184 	PartsEditorPaletteItem *paletteItem = dynamic_cast<PartsEditorPaletteItem*>(paletteItemAux);
185 	if (paletteItem == NULL) {
186 		throw "PartsEditorView::addItemAux paletteItem not found";
187 	}
188 
189 	if(paletteItem) {
190 		modelPart->initConnectors();    // is a no-op if connectors already in place
191 		QString layerFileName = getLayerFileName(modelPart);
192 		if(!layerFileName.isEmpty()) {
193 			if(paletteItem->createSvgPath(modelPart->path(), layerFileName)) {
194 				paletteItem->createSvgFile(paletteItem->svgFilePath()->absolutePath());
195 				ViewLayer::ViewLayerID viewLayerID =
196 					ViewLayer::viewLayerIDFromXmlString(
197 						findConnectorsLayerId(paletteItem->svgDom())
198 					);
199 				if(viewLayerID == ViewLayer::UnknownLayer) {
200 					viewLayerID = getViewLayerID(modelPart, m_viewIdentifier, viewLayerSpec);
201 				}
202 				addDefaultLayers(NULL);
203 				if (m_viewItem != NULL) {
204 					QHash<QString, QString> svgHash;
205 					QString svg = "";
206 					foreach (ViewLayer * vl, m_viewLayers.values()) {
207 						svg += m_viewItem->retrieveSvg(vl->viewLayerID(), svgHash, false, GraphicsUtils::StandardFritzingDPI);
208 					}
209 					if (!svg.isEmpty()) {
210 						QSizeF size = m_viewItem->size();
211 						svg = TextUtils::makeSVGHeader(GraphicsUtils::SVGDPI, GraphicsUtils::StandardFritzingDPI, size.width(), size.height()) + svg + "</svg>";
212 						paletteItem->setItemSVG(svg);
213 					}
214 				}
215 
216 				QString error;
217 				if (paletteItem->renderImage(modelPart, m_viewIdentifier, m_viewLayers, viewLayerID, doConnectors, error)) {
218 					addToScene(paletteItemAux, paletteItemAux->viewLayerID());
219 					// layers are not needed on the parts editor (so far)
220 					return paletteItemAux;
221 				}
222 			}
223 		}
224 	}
225 	return NULL;
226 }
227 
fitCenterAndDeselect()228 void PartsEditorView::fitCenterAndDeselect() {
229 	if(m_item) {
230 		m_item->setSelected(false);
231 		m_item->setHidden(false);
232 		m_item->setInactive(false);
233 
234 		fitInView(m_item, Qt::KeepAspectRatio);
235 
236 		QRectF viewRect = rect();
237 		QRectF itemsRect = scene()->itemsBoundingRect();
238 
239 		double wRelation = viewRect.width()  / itemsRect.width();
240 		double hRelation = viewRect.height() / itemsRect.height();
241 
242 		if(wRelation < hRelation) {
243 			m_scaleValue = (wRelation * 100);
244 		} else {
245 			m_scaleValue = (hRelation * 100);
246 		}
247 
248 		emit zoomChanged(m_scaleValue);
249 	}
250 }
251 
setDefaultBackground()252 void PartsEditorView::setDefaultBackground() {
253 	QString bgColor = " PartsEditorView {background-color: rgb(%1,%2,%3);} ";
254 	QColor c = standardBackground();
255 	if (c.isValid()) {
256 		setStyleSheet(styleSheet()+bgColor.arg(c.red()).arg(c.green()).arg(c.blue()));
257 	}
258 }
259 
clearScene()260 void PartsEditorView::clearScene() {
261 	if(m_item) {
262 		deleteItem(m_item, m_deleteModelPartOnSceneClear, true, false);
263 
264 		scene()->clear();
265 		m_item = NULL;
266 	}
267 }
268 
removeConnectors()269 void PartsEditorView::removeConnectors() {
270 	QList<PartsEditorConnectorItem*> list;
271 	for (int i = m_item->childItems().count()-1; i >= 0; i--) {
272 		PartsEditorConnectorItem * connectorItem = dynamic_cast<PartsEditorConnectorItem *>(m_item->childItems()[i]);
273 		if (connectorItem == NULL) continue;
274 
275 		list << connectorItem;
276 	}
277 
278 	for(int i=0; i < list.size(); i++) {
279 		list[i]->removeFromModel();
280 		delete list[i];
281 	}
282 }
283 
createFakeModelPart(SvgAndPartFilePath * svgpath)284 ModelPart *PartsEditorView::createFakeModelPart(SvgAndPartFilePath *svgpath) {
285 	const QHash<QString,ConnectorTerminalSvgIdPair> connIds = getConnectorsSvgIds(svgpath->absolutePath());
286 	const QStringList layers = getLayers(svgpath->absolutePath());
287 
288 	QString path = svgpath->relativePath().isEmpty() ? svgpath->absolutePath() : svgpath->relativePath();
289 	ModelPart * mp = createFakeModelPart(connIds, layers, path);
290 
291 	return mp;
292 }
293 
createFakeModelPart(const QHash<QString,ConnectorTerminalSvgIdPair> & conns,const QStringList & layers,const QString & svgFilePath)294 ModelPart *PartsEditorView::createFakeModelPart(const QHash<QString,ConnectorTerminalSvgIdPair> &conns, const QStringList &layers, const QString &svgFilePath) {
295 	QDomDocument *domDoc = new QDomDocument();
296 	QString errorStr;
297 	int errorLine;
298 	int errorColumn;
299 	QString fakeFzFile =
300 		QString("<module><views>\n")+
301 			QString("<%1><layers image='%2' >\n").arg(ViewLayer::viewIdentifierXmlName(m_viewIdentifier)).arg(svgFilePath);
302 		foreach(QString layer, layers) { fakeFzFile +=
303 			QString("    <layer layerId='%1' />\n").arg(layer);
304 		}
305 	fakeFzFile +=
306 			QString("</layers></%1>\n").arg(ViewLayer::viewIdentifierXmlName(m_viewIdentifier))+
307 			QString("</views><connectors>\n");
308 
309 	QStringList defaultLayers = defaultLayerAsStringlist();
310 
311 	foreach(QString id, conns.keys()) {
312 		QString terminalAttr = conns[id].terminalId.isEmpty() ? "" : QString("terminalId='%1'").arg(conns[id].terminalId);
313 		QString name = conns[id].connectorName.isEmpty() ? "" : QString("name='%1'").arg(conns[id].connectorName);
314 		fakeFzFile += QString("<connector id='%1' %2><views>\n").arg(id).arg(name) +
315 							QString("<%1>\n").arg(ViewLayer::viewIdentifierXmlName(m_viewIdentifier));
316 		foreach (QString layer, defaultLayers) {
317 			if (layers.contains(layer)) {
318 				fakeFzFile += QString("<p layer='%1' svgId='%2' %3/>\n")
319 									.arg(layer)
320 									.arg(conns[id].connectorId)
321 									.arg(terminalAttr);
322 			}
323 		}
324 		fakeFzFile += QString("</%1>\n").arg(ViewLayer::viewIdentifierXmlName(m_viewIdentifier))+
325 						QString("</views></connector>\n");
326 	}
327 	fakeFzFile += QString("</connectors></module>\n");
328 
329     QString path = m_tempFolder.absolutePath()+"/"+FolderUtils::getRandText()+".fz";
330     TextUtils::writeUtf8(path, fakeFzFile);
331 
332   	domDoc->setContent(fakeFzFile, &errorStr, &errorLine, &errorColumn);
333 
334   	ModelPart *retval = m_sketchModel->root();
335   	retval->modelPartShared()->setDomDocument(domDoc);
336   	retval->modelPartShared()->resetConnectorsInitialization();
337 	retval->modelPartShared()->setPath(path);
338   	retval->initConnectors(true /*redo connectors*/);
339 
340 	return retval;
341 }
342 
getConnectorsSvgIds(const QString & path)343 const QHash<QString,ConnectorTerminalSvgIdPair> PartsEditorView::getConnectorsSvgIds(const QString &path) {
344 	QDomDocument dom ;
345 	QFile file(path);
346 	dom.setContent(&file);
347 
348 	QDomElement docElem = dom.documentElement();
349 	getConnectorsSvgIdsAux(docElem);
350 
351 	return m_svgIds;
352 }
353 
getConnectorsSvgIdsAux(QDomElement & docElem)354 void PartsEditorView::getConnectorsSvgIdsAux(QDomElement &docElem) {
355 	QDomElement e = docElem.firstChildElement();
356 	while(!e.isNull()) {
357 		QString id = e.attribute("id");
358 		if(id.startsWith("connector") && id.endsWith("terminal")) {
359 			QString conn = id.left(id.lastIndexOf(QRegExp("\\d"))+1);
360 			ConnectorTerminalSvgIdPair pair = m_svgIds.contains(conn) ? m_svgIds[conn] : ConnectorTerminalSvgIdPair();
361 			pair.terminalId = id;
362 			m_svgIds[conn] = pair;
363 		}
364 		else if(id.startsWith("connector") /*&& id.endsWith("pin") */ ) {
365 			QString conn = id.left(id.lastIndexOf(QRegExp("\\d"))+1);
366 			ConnectorTerminalSvgIdPair pair = m_svgIds.contains(conn) ? m_svgIds[conn] : ConnectorTerminalSvgIdPair();
367 			pair.connectorId = id;
368 			pair.connectorName = e.attribute("connectorname");
369 			m_svgIds[conn] = pair;
370 		}
371 		if(e.hasChildNodes()) {
372 			getConnectorsSvgIdsAux(e);
373 		}
374 		e = e.nextSiblingElement();
375 	}
376 }
377 
getLayers(const QString & path)378 const QStringList PartsEditorView::getLayers(const QString &path) {
379 	if(m_viewIdentifier == ViewLayer::IconView) { // defaulting layer to icon for iconview
380 		return defaultLayerAsStringlist();
381 	} else {
382 		QDomDocument dom;
383 		QFile file(path);
384 		dom.setContent(&file);
385 		return getLayers(&dom);
386 	}
387 }
388 
getLayers(const QDomDocument * dom,bool fakeDefaultIfNone)389 const QStringList PartsEditorView::getLayers(const QDomDocument *dom, bool fakeDefaultIfNone) {
390 	QStringList retval;
391 
392 	QDomNodeList nodeList = dom->elementsByTagName("g");
393 	for (uint i = 0; i < nodeList.length(); i++) {
394 		QDomElement e = nodeList.item(i).toElement();
395 		QString id = e.attribute("id");
396 		if (id.isEmpty()) continue;
397 		if (ViewLayer::viewLayerIDFromXmlString(id) == ViewLayer::UnknownLayer) continue;
398 
399 		retval << id;
400 	}
401 
402 	if(fakeDefaultIfNone && retval.isEmpty()) {
403 		retval << ViewLayer::viewIdentifierNaturalName(m_viewIdentifier);
404 	}
405 
406 	return retval;
407 }
408 
newPartsEditorPaletteItem(ModelPart * modelPart)409 PartsEditorPaletteItem *PartsEditorView::newPartsEditorPaletteItem(ModelPart *modelPart) {
410 	return new PartsEditorConnectorsPaletteItem(this, modelPart, m_viewIdentifier);
411 }
412 
newPartsEditorPaletteItem(ModelPart * modelPart,SvgAndPartFilePath * path)413 PartsEditorPaletteItem *PartsEditorView::newPartsEditorPaletteItem(ModelPart * modelPart, SvgAndPartFilePath *path) {
414 	return new PartsEditorConnectorsPaletteItem(this, modelPart, m_viewIdentifier, path);
415 }
416 
tempFolder()417 QDir PartsEditorView::tempFolder() {
418 	return m_tempFolder;
419 }
420 
getOrCreateViewFolderInTemp()421 QString PartsEditorView::getOrCreateViewFolderInTemp() {
422 	QString viewFolder = ViewLayer::viewIdentifierNaturalName(m_viewIdentifier);
423 
424 	if(!QFileInfo(m_tempFolder.absolutePath()+"/"+viewFolder).exists()) {
425 		bool mkResult = m_tempFolder.mkpath(m_tempFolder.absolutePath()+"/"+viewFolder);
426 		if (!mkResult) {
427 			throw "PartsEditorView::getOrCreateViewFolderInTemp failed";
428 		}
429 	}
430 
431 	return viewFolder;
432 }
433 
isEmpty()434 bool PartsEditorView::isEmpty() {
435 	return m_item == NULL;
436 }
437 
ensureFilePath(const QString & filePath)438 bool PartsEditorView::ensureFilePath(const QString &filePath) {
439 	QString svgFolder = FolderUtils::getUserDataStorePath("parts")+"/svg";
440 
441 	Qt::CaseSensitivity cs = Qt::CaseSensitive;
442 #ifdef Q_WS_WIN
443 	cs = Qt::CaseInsensitive;
444 #endif
445 	if(!filePath.contains(svgFolder, cs)) {
446 		// This has to be here in order of all this, to work in release mode
447 		m_tempFolder.mkpath(QFileInfo(filePath).absoluteDir().path());
448 	}
449 	return true;
450 }
451 
connectorsLayerId()452 ViewLayer::ViewLayerID PartsEditorView::connectorsLayerId() {
453 	//Q_ASSERT(m_item);
454 	findConnectorsLayerId();
455 	return m_connsLayerID;
456 }
457 
terminalIdForConnector(const QString & connId)458 QString PartsEditorView::terminalIdForConnector(const QString &connId) {
459 	//Q_ASSERT(m_item)
460 
461 	if (m_item == NULL) return "";
462 
463 	QString result = "";
464 	QDomElement elem = m_item->svgDom()->documentElement();
465 	if(terminalIdForConnectorIdAux(result, connId, elem, true)) {
466 		return result;
467 	} else {
468 		return "";
469 	}
470 }
471 
terminalIdForConnectorIdAux(QString & result,const QString & connId,QDomElement & docElem,bool wantTerminal)472 bool PartsEditorView::terminalIdForConnectorIdAux(QString &result, const QString &connId, QDomElement &docElem, bool wantTerminal) {
473 	QDomElement e = docElem.firstChildElement();
474 	while(!e.isNull()) {
475 		QString id = e.attribute("id");
476 		if(id.startsWith(connId) && ((wantTerminal && id.endsWith("terminal")) || (!wantTerminal && !id.endsWith("terminal")))) {
477 			// the id is the one from the previous iteration
478 			result = id;
479 			return true;
480 		} else if(e.hasChildNodes()) {
481 			// potencial solution, if the next iteration returns true
482 			if(terminalIdForConnectorIdAux(result, connId, e, wantTerminal)) {
483 				return true;
484 			}
485 		}
486 		e = e.nextSiblingElement();
487 	}
488 	return false;
489 }
490 
findConnectorsLayerId()491 void PartsEditorView::findConnectorsLayerId() {
492 	if(m_connsLayerID == ViewLayer::UnknownLayer) {
493 		if (m_item != NULL) {
494 			m_connsLayerID = ViewLayer::viewLayerIDFromXmlString(
495 				findConnectorsLayerId(m_item->svgDom())
496 			);
497 		}
498 		if(m_connsLayerID == ViewLayer::UnknownLayer) {
499 			m_connsLayerID = SketchWidget::defaultConnectorLayer(m_viewIdentifier);
500 		}
501 	}
502 }
503 
findConnectorsLayerIds(QDomDocument * svgDom)504 QStringList PartsEditorView::findConnectorsLayerIds(QDomDocument *svgDom) {
505 	QStringList result;
506 	QDomElement docElem = svgDom->documentElement();
507 	findConnectorsLayerIdsAux(result, docElem);
508 	if (result.count() > 0) return result;
509 
510 	return defaultLayerAsStringlist();
511 }
512 
findConnectorsLayerIdsAux(QStringList & result,QDomElement & docElem)513 void PartsEditorView::findConnectorsLayerIdsAux(QStringList &result, QDomElement &docElem) {
514 	QDomElement e = docElem.firstChildElement();
515 	while(!e.isNull()) {
516 		QString id = e.attribute("id");
517 		if (id.startsWith("connector")) {
518 			QDomElement parent = e.parentNode().toElement();
519 			QString id = parent.attribute("id");
520 			if (!id.isEmpty() && (ViewLayer::viewLayerIDFromXmlString(id) != ViewLayer::UnknownLayer)) {
521 				result << id;
522 			}
523 		}
524 		else if(e.hasChildNodes()) {
525 			findConnectorsLayerIdsAux(result, e);
526 		}
527 		e = e.nextSiblingElement();
528 	}
529 }
530 
findConnectorsLayerId(QDomDocument * svgDom)531 QString PartsEditorView::findConnectorsLayerId(QDomDocument *svgDom) {
532 	QString result;
533 	QStringList layers;
534 	QDomElement docElem = svgDom->documentElement();
535 	if(findConnectorsLayerIdAux(result, docElem, layers)) {
536 		if(ViewLayer::viewLayerIDFromXmlString(result) == ViewLayer::UnknownLayer) {
537 			foreach(QString layer, layers) {
538 				ViewLayer::ViewLayerID vlid = ViewLayer::viewLayerIDFromXmlString(layer);
539 				if(m_viewLayers.keys().contains(vlid)) {
540 					result = layer;
541 				}
542 			}
543 		}
544 		return result;
545 	} else {
546 		return defaultLayerAsStringlist().at(0);
547 	}
548 }
549 
findConnectorsLayerIdAux(QString & result,QDomElement & docElem,QStringList & prevLayers)550 bool PartsEditorView::findConnectorsLayerIdAux(QString &result, QDomElement &docElem, QStringList &prevLayers) {
551 	QDomElement e = docElem.firstChildElement();
552 	while(!e.isNull()) {
553 		QString id = e.attribute("id");
554 		if(id.startsWith("connector")) {
555 			// the id is the one from the previous iteration
556 			return true;
557 		} else if(e.hasChildNodes()) {
558 			// potencial solution, if the next iteration returns true
559 			result = id;
560 			prevLayers << id;
561 			if(findConnectorsLayerIdAux(result, e, prevLayers)) {
562 				return true;
563 			}
564 		}
565 		e = e.nextSiblingElement();
566 	}
567 	return false;
568 }
569 
getLayerFileName(ModelPart * modelPart)570 QString PartsEditorView::getLayerFileName(ModelPart * modelPart) {
571     return modelPart->imageFileName(m_viewIdentifier);
572 }
573 
574 
575 // specs
copySvgFileToDestiny(const QString & partFileName)576 void PartsEditorView::copySvgFileToDestiny(const QString &partFileName) {
577 	Qt::CaseSensitivity cs = Qt::CaseSensitive;
578 #ifdef Q_WS_WIN
579 	cs = Qt::CaseInsensitive;
580 #endif
581 
582 	// if the svg file is in the temp folder, then copy it to destiny
583 	if(m_svgFilePath->absolutePath().startsWith(m_tempFolder.absolutePath(),cs)) {
584 		QString origFile = svgFilePath();
585 		setFriendlierSvgFileName(partFileName);
586 		QString destFile = FolderUtils::getUserDataStorePath("parts")+"/svg/user/"+m_svgFilePath->relativePath();
587 
588 		ensureFilePath(origFile);
589 		QFile tempFile(origFile);
590 		DebugDialog::debug(QString("copying from %1 to %2")
591 				.arg(origFile)
592 				.arg(destFile));
593 		tempFile.copy(destFile);
594 		tempFile.close();
595 
596 		// update the item info, to point to this file
597 		m_svgFilePath->setAbsolutePath(destFile);
598 	}
599 }
600 
loadFile()601 void PartsEditorView::loadFile() {
602 	QStringList extras;
603 	extras.append("");
604 	extras.append("");
605 	QString imageFiles;
606 	if (m_viewIdentifier == ViewLayer::PCBView) {
607 		imageFiles = tr("Image & Footprint Files (%1 %2 %3 %4 %5);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3);;gEDA Footprint Files (%4);;Kicad Module Files (%5)");   //
608 		extras[0] = "*.fp";
609 		extras[1] = "*.mod";
610 	}
611 	else {
612 		imageFiles = tr("Image Files (%1 %2 %3);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3)%4%5");
613 	}
614 
615 	if (m_viewIdentifier == ViewLayer::SchematicView) {
616 		extras[0] = "*.lib";
617 		imageFiles = tr("Image & Footprint Files (%1 %2 %3 %4);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3);;Kicad Schematic Files (%4)%5");   //
618 	}
619 
620 	QString origPath = FolderUtils::getOpenFileName(this,
621 		tr("Open Image"),
622 		m_originalSvgFilePath.isEmpty() ? FolderUtils::openSaveFolder() /* FolderUtils::getUserDataStorePath("parts")+"/parts/svg/" */ : m_originalSvgFilePath,
623 		imageFiles.arg("*.svg").arg("*.jpg *.jpeg").arg("*.png").arg(extras[0]).arg(extras[1])
624 	);
625 
626 	if(origPath.isEmpty()) {
627 		return; // Cancel pressed
628 	}
629 
630 	if(!origPath.endsWith(".svg")) {
631 		try {
632 			origPath = createSvgFromImage(origPath);
633 		}
634 		catch (const QString & msg) {
635     		QMessageBox::warning(
636     			NULL,
637     			tr("Conversion problem"),
638     			tr("Unable to load image file: \n%1").arg(msg)
639     		);
640 			return;
641 		}
642 	}
643 	if(!origPath.isEmpty()) {
644 		if(m_startItem) {
645 			m_fixedToCenterItems.removeAll(m_startItem);
646 			delete m_startItem;
647 			m_startItem = NULL;
648 		}
649 		m_viewItem = NULL;				// loading a new file, so m_viewItem is obsolete
650 		loadSvgFile(origPath);
651 	}
652 }
653 
updateModelPart(const QString & origPath)654 void PartsEditorView::updateModelPart(const QString& origPath) {
655 	m_undoStack->push(new QUndoCommand("Dummy parts editor command"));
656 
657 	setSvgFilePath(origPath);
658 	copyToTempAndRenameIfNecessary(m_svgFilePath);
659 	m_item->setSvgFilePath(m_svgFilePath);
660 
661 	ModelPart *mp = createFakeModelPart(m_svgFilePath);
662 	m_item->setModelPart(mp);
663 }
664 
loadSvgFile(const QString & origPath)665 void PartsEditorView::loadSvgFile(const QString& origPath) {
666 	// back to an empty state
667 	m_drawnConns.clear();
668 	m_removedConnIds.clear();
669 	m_connsLayerID = ViewLayer::UnknownLayer;
670 	m_svgIds.clear();
671 
672 	m_svgLoaded = true;
673 
674 	bool canceled = false;
675 	beforeSVGLoading(origPath, canceled);
676 
677 	if(!canceled) {
678 		m_undoStack->push(new QUndoCommand("Dummy parts editor command"));
679 		setSvgFilePath(origPath);
680 
681 		// TODO: this code reuses the current modelpart and replaces its connectors,
682 		// it would be better to delete the modelpart and create a new one
683 		// however, one would have to tidy up the various objects that rely on pointers
684 		// to the original modelpart and its connectors
685 		ModelPart * mp = createFakeModelPart(m_svgFilePath);
686 		loadSvgFile(mp);
687 	}
688 }
689 
beforeSVGLoading(const QString & filename,bool & canceled)690 void PartsEditorView::beforeSVGLoading(const QString &filename, bool &canceled) {
691     QFile file(filename);
692     if(!file.open(QIODevice::ReadOnly )) {
693     	QMessageBox::warning(
694     		NULL,
695     		tr("Couldn't open svg file"),
696     		tr(
697     		"The file couldn't be opened. If this file defines its dimensions \n"
698     		"in non-real-world units (e.g. pixels), then they won't be translated \n"
699     		"into real life ones.\n"
700     		"Malformed font-family definitions won't be fixed either.")
701     	);
702         return;
703     }
704 
705     QString fileContent(file.readAll());
706 	bool fileHasChanged = (m_viewIdentifier == ViewLayer::IconView) ? false : TextUtils::fixPixelDimensionsIn(fileContent);
707 	fileHasChanged |= TextUtils::cleanSodipodi(fileContent);
708 	fileHasChanged |= TextUtils::fixViewboxOrigin(fileContent);
709 	fileHasChanged |= TextUtils::tspanRemove(fileContent);
710 	fileHasChanged |= fixFonts(fileContent,filename,canceled);
711 
712 	if(fileHasChanged) {
713 		file.close();
714 		if(!TextUtils::writeUtf8(filename, fileContent)) {
715 			QMessageBox::warning(
716 				NULL,
717 				tr("Couldn't write into file"),
718 				tr(
719 				"This file needs to be fixed to fit fritzing needs, but it couldn't\n"
720 				"be written.\n"
721 				"Fritzing is not compatible with this kind of svg files. Please \n"
722 				"check your permissions, and try again.\n\n"
723 
724 				"More information at http://fritzing.org/using-svg-images-new-parts/"
725 				)
726 			);
727 		}
728 	}
729 
730 }
731 
fixFonts(QString & fileContent,const QString & filename,bool & canceled)732 bool PartsEditorView::fixFonts(QString &fileContent, const QString &filename, bool &canceled) {
733 	bool changed = removeFontFamilySingleQuotes(fileContent, filename);
734 	changed |= fixUnavailableFontFamilies(fileContent, filename, canceled);
735 
736 	return changed;
737 }
738 
removeFontFamilySingleQuotes(QString & fileContent,const QString & filename)739 bool PartsEditorView::removeFontFamilySingleQuotes(QString &fileContent, const QString &filename) {
740 	QString pattern = "font-family=\"('.*')\"";
741 	QSet<QString> wrongFontFamilies = TextUtils::getRegexpCaptures(pattern,fileContent);
742 
743 	foreach(QString ff, wrongFontFamilies) {
744 		QString wrongFF = ff;
745 		QString fixedFF = ff.remove('\'');
746 		fileContent.replace(wrongFF,fixedFF);
747 		DebugDialog::debug(
748 			QString("removing font-family single quotes: \"%1\" to \"%2\" in file '%3'")
749 				.arg(wrongFF).arg(fixedFF).arg(filename)
750 		);
751 	}
752 
753 	return wrongFontFamilies.size() > 0;
754 }
755 
fixUnavailableFontFamilies(QString & fileContent,const QString & filename,bool & canceled)756 bool PartsEditorView::fixUnavailableFontFamilies(QString &fileContent, const QString &filename, bool &canceled) {
757 	QSet<QString> definedFFs;
758 	definedFFs.unite(getAttrFontFamilies(fileContent));
759 	definedFFs.unite(getFontFamiliesInsideStyleTag(fileContent));
760 
761 	FixedFontsHash fixedFonts = FixFontsDialog::fixFonts(this,definedFFs,canceled);
762 
763 	if(!canceled) {
764 		foreach(QString oldF, fixedFonts.keys()) {
765 			QString newF = fixedFonts[oldF];
766 			fileContent.replace(oldF,newF);
767 			DebugDialog::debug(
768 				QString("replacing font-family: \"%1\" to \"%2\" in file '%3'")
769 					.arg(oldF).arg(newF).arg(filename)
770 			);
771 		}
772 	}
773 
774 	return !canceled && fixedFonts.size() > 0;
775 }
776 
getAttrFontFamilies(const QString & fileContent)777 QSet<QString> PartsEditorView::getAttrFontFamilies(const QString &fileContent) {
778 	/*
779 	 * font-family defined as attr example:
780 
781 <text xmlns="http://www.w3.org/2000/svg" font-family="DroidSans"
782 id="text2732" transform="matrix(1 0 0 1 32.2012 236.969)"
783 font-size="9.9771" >A0</text>
784 
785 	 */
786 
787 	QString pattern = "font-family\\s*=\\s*\"(.|[^\"]*)\\s*\"";
788 	return TextUtils::getRegexpCaptures(pattern,fileContent);
789 }
790 
getFontFamiliesInsideStyleTag(const QString & fileContent)791 QSet<QString> PartsEditorView::getFontFamiliesInsideStyleTag(const QString &fileContent) {
792 	/*
793 	 * regexp: font-family\s*:\s*(.|[^;"]*).*"
794 	 * font-family defined in a style attr example:
795 
796 style="font-size:9;-inkscape-font-specification:Droid Sans;font-family:Droid Sans;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
797 
798 style="font-size:144px;font-style:normal;font-weight:normal;line-height:100%;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans" x="18.000002"
799 
800 	 */
801 
802 	QString pattern = "font-family\\s*:\\s*(.|[^;\"]*).*\"";
803 	return TextUtils::getRegexpCaptures(pattern,fileContent);
804 }
805 
loadSvgFile(ModelPart * modelPart)806 void PartsEditorView::loadSvgFile(ModelPart * modelPart) {
807 	addItemInPartsEditor(modelPart, m_svgFilePath);
808 	copyToTempAndRenameIfNecessary(m_svgFilePath);
809 	m_item->setSvgFilePath(m_svgFilePath);
810 }
811 
loadFromModel(PaletteModel * paletteModel,ModelPart * modelPart)812 void PartsEditorView::loadFromModel(PaletteModel *paletteModel, ModelPart * modelPart) {
813 	clearScene();
814 
815 	ViewGeometry viewGeometry;
816 	this->setPaletteModel(paletteModel);
817 	m_item = (PartsEditorPaletteItem*) addItemAux(modelPart, defaultViewLayerSpec(), viewGeometry, ItemBase::getNextID(), NULL, true, m_viewIdentifier, true);
818 
819 	fitCenterAndDeselect();
820 
821 	setItemProperties();
822 
823 	if(m_item) {
824 		if(m_startItem) {
825 			m_fixedToCenterItems.removeAll(m_startItem);
826 			delete m_startItem;
827 			m_startItem = NULL;
828 		}
829 
830 
831 		SvgAndPartFilePath *sp = m_item->svgFilePath();
832 
833 		copyToTempAndRenameIfNecessary(sp);
834 		m_item->setSvgFilePath(m_svgFilePath);
835 	}
836 }
837 
copyToTempAndRenameIfNecessary(SvgAndPartFilePath * filePathOrig)838 void PartsEditorView::copyToTempAndRenameIfNecessary(SvgAndPartFilePath *filePathOrig) {
839 	m_originalSvgFilePath = filePathOrig->absolutePath();
840 	QString userSvgFolderPath = FolderUtils::getUserDataStorePath("parts")+"/svg";
841 	QString coreSvgFolderPath = FolderUtils::getApplicationSubFolderPath("parts")+"/svg";
842 	QString pfSvgFolderPath = PartFactory::folderPath()+"/svg";
843 
844 	if(!(filePathOrig->absolutePath().startsWith(userSvgFolderPath)
845 		|| filePathOrig->absolutePath().startsWith(coreSvgFolderPath)
846 		|| filePathOrig->absolutePath().startsWith(pfSvgFolderPath))
847 		)
848 	{ // it's outside the parts folder
849 		DebugDialog::debug(QString("copying from %1").arg(m_originalSvgFilePath));
850 		QString viewFolder = ViewLayer::viewIdentifierNaturalName(m_viewIdentifier);
851 
852 		if(!QFileInfo(m_tempFolder.path()+"/"+viewFolder).exists()
853 		   && !m_tempFolder.mkdir(viewFolder)) return;
854 		if(!m_tempFolder.cd(viewFolder)) return;
855 
856 		QString destFilePath = FolderUtils::getRandText()+".svg";
857 		DebugDialog::debug(QString("dest file: %1").arg(m_tempFolder.absolutePath()+"/"+destFilePath));
858 
859 		ensureFilePath(m_tempFolder.absolutePath()+"/"+destFilePath);
860 
861 		QFile tempFile(m_originalSvgFilePath);
862 		tempFile.copy(m_tempFolder.absolutePath()+"/"+destFilePath);
863 		tempFile.close();
864 
865 		if(!m_tempFolder.cd("..")) return; // out of view folder
866 
867 		m_svgFilePath->setRelativePath(viewFolder+"/"+destFilePath);
868 		m_svgFilePath->setAbsolutePath(m_tempFolder.absolutePath()+"/"+m_svgFilePath->relativePath());
869 
870 	} else {
871 		QString relPathAux = filePathOrig->relativePath();
872 		m_svgFilePath->setAbsolutePath(m_originalSvgFilePath);
873 		if (relPathAux.count("/") > 2) {
874 			throw "PartsEditorView::copyToTempAndRenameIfNecessary bad path";
875 		}
876 
877 		if(relPathAux.count("/") == 2) { // this means that core/user/contrib is still in the file name
878 			m_svgFilePath->setRelativePath(
879 				relPathAux.right(// remove user/core/contrib
880 					relPathAux.size() -
881 					relPathAux.indexOf("/") - 1
882 				)
883 			);
884 		} else { //otherwise, just leave it as it is
885 			m_svgFilePath->setRelativePath(relPathAux);
886 		}
887 	}
888 }
889 
setSvgFilePath(const QString & filePath)890 void PartsEditorView::setSvgFilePath(const QString &filePath) {
891 	ensureFilePath(filePath);
892 	m_originalSvgFilePath = filePath;
893 
894 	QString userSvgFolder = FolderUtils::getUserDataStorePath("parts")+"/svg";
895 	QString coreSvgFolder = FolderUtils::getApplicationSubFolderPath("parts")+"/svg";
896 	QString pfSvgFolder = PartFactory::folderPath()+"/svg";
897 
898 	QString tempFolder = m_tempFolder.absolutePath();
899 
900 	QString relative;
901 	Qt::CaseSensitivity cs = Qt::CaseSensitive;
902 	QString filePathAux = filePath;
903 
904 #ifdef Q_WS_WIN
905 	// seems to be necessary for Windows: getUserDataStorePath() returns a string starting with "c:"
906 	// but the file dialog returns a string beginning with "C:"
907 	cs = Qt::CaseInsensitive;
908 #endif
909 	if(filePath.contains(userSvgFolder, cs) || filePath.contains(coreSvgFolder, cs) || filePath.contains(pfSvgFolder, cs)) {
910 		int ix = filePath.indexOf("svg");
911 		// is core/user file
912 		relative = filePathAux.remove(0, ix + 4);
913 		//Mariano: I don't like this folder thing anymore
914 		relative = relative.mid(filePathAux.indexOf("/")+1); // remove core/user/contrib
915 	} else {
916 		// generated jpeg/png or file outside fritzing folder
917 		relative = "";
918 	}
919 
920 	if (m_svgFilePath) delete m_svgFilePath;
921 	m_svgFilePath = new SvgAndPartFilePath(filePath,relative);
922 }
923 
924 
svgFilePath()925 const QString PartsEditorView::svgFilePath() {
926 	return m_svgFilePath->absolutePath();
927 }
928 
svgFileSplit()929 const SvgAndPartFilePath& PartsEditorView::svgFileSplit() {
930 	return *m_svgFilePath;
931 }
932 
createSvgFromImage(const QString & origFilePath)933 QString PartsEditorView::createSvgFromImage(const QString &origFilePath) {
934 	QString viewFolder = getOrCreateViewFolderInTemp();
935 
936 	QString newFilePath = m_tempFolder.absolutePath()+"/"+viewFolder+"/"+FolderUtils::getRandText()+".svg";
937 	ensureFilePath(newFilePath);
938 
939 	if (origFilePath.endsWith(".fp")) {
940 		// this is a geda footprint file
941 		GedaElement2Svg geda;
942 		QString svg = geda.convert(origFilePath, false);
943 		return saveSvg(svg, newFilePath);
944 	}
945 
946 	if (origFilePath.endsWith(".lib")) {
947 		// Kicad schematic library file
948 		QStringList defs = KicadSchematic2Svg::listDefs(origFilePath);
949 		if (defs.count() == 0) {
950 			throw tr("no schematics found in %1").arg(origFilePath);
951 		}
952 
953 		QString def;
954 		if (defs.count() > 1) {
955 			KicadModuleDialog kmd(tr("schematic part"), origFilePath, defs, this);
956 			int result = kmd.exec();
957 			if (result != QDialog::Accepted) {
958 				return "";
959 			}
960 
961 			def = kmd.selectedModule();
962 		}
963 		else {
964 			def = defs.at(0);
965 		}
966 
967 		KicadSchematic2Svg kicad;
968 		QString svg = kicad.convert(origFilePath, def);
969 		return saveSvg(svg, newFilePath);
970 	}
971 
972 	if (origFilePath.endsWith(".mod")) {
973 		// Kicad footprint (Module) library file
974 		QStringList modules = KicadModule2Svg::listModules(origFilePath);
975 		if (modules.count() == 0) {
976 			throw tr("no footprints found in %1").arg(origFilePath);
977 		}
978 
979 		QString module;
980 		if (modules.count() > 1) {
981 			KicadModuleDialog kmd("footprint", origFilePath, modules, this);
982 			int result = kmd.exec();
983 			if (result != QDialog::Accepted) {
984 				return "";
985 			}
986 
987 			module = kmd.selectedModule();
988 		}
989 		else {
990 			module = modules.at(0);
991 		}
992 
993 		KicadModule2Svg kicad;
994 		QString svg = kicad.convert(origFilePath, module, false);
995 		return saveSvg(svg, newFilePath);
996 	}
997 
998 	// deal with png, jpg, etc.:
999 
1000 
1001 /* %1=witdh in mm
1002  * %2=height in mm
1003  * %3=width in local coords
1004  * %4=height in local coords
1005  * %5=binary data
1006  */
1007 /*	QString svgTemplate =
1008 "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n"
1009 "	<svg width='%1mm' height='%2mm' viewBox='0 0 %3 %4' xmlns='http://www.w3.org/2000/svg'\n"
1010 "		xmlns:xlink='http://www.w3.org/1999/xlink' version='1.2' baseProfile='tiny'>\n"
1011 "		<g fill='none' stroke='black' vector-effect='non-scaling-stroke' stroke-width='1'\n"
1012 "			fill-rule='evenodd' stroke-linecap='square' stroke-linejoin='bevel' >\n"
1013 "			<image x='0' y='0' width='%3' height='%4'\n"
1014 "				xlink:href='data:image/png;base64,%5' />\n"
1015 "		</g>\n"
1016 "	</svg>";
1017 
1018 	QPixmap pixmap(origFilePath);
1019 	QByteArray bytes;
1020 	QBuffer buffer(&bytes);
1021 	buffer.open(QIODevice::WriteOnly);
1022 	pixmap.save(&buffer,"png"); // writes pixmap into bytes in PNG format
1023 
1024 	QString svgDom = svgTemplate
1025 		.arg(pixmap.widthMM()).arg(pixmap.heightMM())
1026 		.arg(pixmap.width()).arg(pixmap.height())
1027 		.arg(QString("data:image/png;base64,%2").arg(QString(bytes.toBase64())));
1028 
1029 	QFile destFile(newFilePath);
1030 	if(!destFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
1031 		QMessageBox::information(NULL, "", "file not created");
1032 		if(!destFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
1033 				QMessageBox::information(NULL, "", "file not created 2");
1034 			}
1035 	}
1036 	QTextStream out(&destFile);
1037 	out << svgDom;
1038 	destFile.close();
1039 	qDebug() << newFilePath;
1040 	bool existsResult = QFileInfo(newFilePath).exists();
1041 	Q_ASSERT(existsResult);
1042 */
1043 
1044 	QImage img(origFilePath);
1045 	QSvgGenerator svgGenerator;
1046 	svgGenerator.setResolution(90);
1047 	svgGenerator.setFileName(newFilePath);
1048 	QSize sz = img.size();
1049     svgGenerator.setSize(sz);
1050 	svgGenerator.setViewBox(QRect(0, 0, sz.width(), sz.height()));
1051 	QPainter svgPainter(&svgGenerator);
1052 	svgPainter.drawImage(QPoint(0,0), img);
1053 	svgPainter.end();
1054 
1055 	return newFilePath;
1056 }
1057 
setFriendlierSvgFileName(const QString & partFileName)1058 QString PartsEditorView::setFriendlierSvgFileName(const QString &partFileName) {
1059 	QString aux = partFileName;
1060 	aux = aux
1061 		.remove(FritzingPartExtension)
1062 		.replace(" ","_");
1063 	if(aux.length()>40) aux.truncate(40);
1064 	aux+=QString("__%1__%2.svg")
1065 			.arg(ViewLayer::viewIdentifierNaturalName(m_viewIdentifier))
1066 			.arg(FolderUtils::getRandText());
1067 	int slashIdx = m_svgFilePath->relativePath().indexOf("/");
1068 	QString relpath = m_svgFilePath->relativePath();
1069 	QString relpath2 = relpath;
1070 	QString abspath = m_svgFilePath->absolutePath();
1071 	QString viewFolder = relpath.remove(slashIdx,relpath.size()-slashIdx+1);
1072 	m_svgFilePath->setAbsolutePath(abspath.remove(relpath2)+viewFolder+"/"+aux);
1073 	m_svgFilePath->setRelativePath(viewFolder+"/"+aux);
1074 	return aux;
1075 }
1076 
1077 
1078 // conns
wheelEvent(QWheelEvent * event)1079 void PartsEditorView::wheelEvent(QWheelEvent* event) {
1080 	if(m_showingTerminalPoints) {
1081 		if(!m_terminalPointsTimer->isActive()) {
1082 			m_showingTerminalPointsBackup = m_showingTerminalPoints;
1083 			showTerminalPoints(false);
1084 			m_terminalPointsTimer->start(50);
1085 		}
1086 	} else if(m_terminalPointsTimer->isActive()) {
1087 		m_terminalPointsTimer->stop();
1088 		m_terminalPointsTimer->start(50);
1089 	}
1090 	SketchWidget::wheelEvent(event);
1091 }
1092 
mousePressEvent(QMouseEvent * event)1093 void PartsEditorView::mousePressEvent(QMouseEvent *event) {
1094 	SketchWidget::mousePressEvent(event);
1095 }
1096 
mouseMoveEvent(QMouseEvent * event)1097 void PartsEditorView::mouseMoveEvent(QMouseEvent *event) {
1098 	QGraphicsView::mouseMoveEvent(event);
1099 }
1100 
mouseReleaseEvent(QMouseEvent * event)1101 void PartsEditorView::mouseReleaseEvent(QMouseEvent *event) {
1102 	SketchWidget::mouseReleaseEvent(event);
1103 }
1104 
resizeEvent(QResizeEvent * event)1105 void PartsEditorView::resizeEvent(QResizeEvent * event) {
1106 	SketchWidget::resizeEvent(event);
1107 	if(m_fitItemInViewTimer->isActive()) {
1108 		m_fitItemInViewTimer->stop();
1109 	}
1110 	m_fitItemInViewTimer->start();
1111 
1112 }
1113 
drawConector(Connector * conn,bool showTerminalPoint)1114 void PartsEditorView::drawConector(Connector *conn, bool showTerminalPoint) {
1115 	QSize size(ConnDefaultWidth,ConnDefaultHeight);
1116 	createConnector(conn,size,showTerminalPoint);
1117 }
1118 
createConnector(Connector * conn,const QSize & connSize,bool showTerminalPoint)1119 void PartsEditorView::createConnector(Connector *conn, const QSize &connSize, bool showTerminalPoint) {
1120 	QString connId = conn->connectorSharedID();
1121 
1122 	QRectF bounds = m_item
1123 			? QRectF(m_item->boundingRect().center(),connSize)
1124 			: QRectF(scene()->itemsBoundingRect().center(),connSize);
1125 	PartsEditorConnectorsConnectorItem *connItem = new PartsEditorConnectorsConnectorItem(conn, m_item, m_showingTerminalPoints, bounds);
1126 	m_drawnConns[connId] = connItem;
1127 	connItem->setShowTerminalPoint(showTerminalPoint);
1128 
1129 	m_undoStack->push(new QUndoCommand(
1130 		QString("connector '%1' added to %2 view")
1131 		.arg(connId).arg(ViewLayer::viewIdentifierName(m_viewIdentifier))
1132 	));
1133 }
1134 
removeConnector(const QString & connId)1135 void PartsEditorView::removeConnector(const QString &connId) {
1136 	ConnectorItem *connToRemove = NULL;
1137 	foreach(QGraphicsItem *item, items()) {
1138 		ConnectorItem *connItem = dynamic_cast<ConnectorItem*>(item);
1139 		if(connItem && connItem->connector()->connectorSharedID() == connId) {
1140 			connToRemove = connItem;
1141 			break;
1142 		}
1143 	}
1144 
1145 	if(connToRemove) {
1146 		scene()->removeItem(connToRemove);
1147 		scene()->update();
1148 		m_undoStack->push(new QUndoCommand(
1149 			QString("connector '%1' removed from %2 view")
1150 			.arg(connId).arg(ViewLayer::viewIdentifierName(m_viewIdentifier))
1151 		));
1152 
1153 		PartsEditorConnectorsConnectorItem *connToRemoveAux = dynamic_cast<PartsEditorConnectorsConnectorItem*>(connToRemove);
1154 		m_drawnConns.remove(connToRemoveAux->connectorSharedID());
1155 		m_removedConnIds << connId;
1156 	}
1157 }
1158 
setItemProperties()1159 void PartsEditorView::setItemProperties() {
1160 	if(m_item) {
1161 		m_item->setFlag(QGraphicsItem::ItemIsSelectable, false);
1162 		m_item->setFlag(QGraphicsItem::ItemIsMovable, false);
1163 		m_item->setFlag(QGraphicsItem::ItemClipsToShape, true);
1164 		//m_item->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
1165 		myItem()->highlightConnectors(m_lastSelectedConnId);
1166 
1167 		double size = 500; // just make sure the user get enough space to play
1168 		setSceneRect(0,0,size,size);
1169 
1170 
1171 		m_item->setPos((size-m_item->size().width())/2,(size-m_item->size().height())/2);
1172 		centerOn(m_item);
1173 
1174 	}
1175 	//ensureFixedToBottomRight(m_zoomControls);
1176 }
1177 
informConnectorSelection(const QString & connId)1178 void PartsEditorView::informConnectorSelection(const QString &connId) {
1179 	if(m_item) {
1180 		m_lastSelectedConnId = connId;
1181 		myItem()->highlightConnectors(connId);
1182 	}
1183 }
1184 
informConnectorSelectionFromView(const QString & connId)1185 void PartsEditorView::informConnectorSelectionFromView(const QString &connId) {
1186 	informConnectorSelection(connId);
1187 	emit connectorSelected(connId);
1188 }
1189 
setMismatching(ViewLayer::ViewIdentifier viewId,const QString & id,bool mismatching)1190 void PartsEditorView::setMismatching(ViewLayer::ViewIdentifier viewId, const QString &id, bool mismatching) {
1191 	if(m_item && viewId == m_viewIdentifier) {
1192 		for (int i = 0; i < m_item->childItems().count(); i++) {
1193 			PartsEditorConnectorsConnectorItem * connectorItem
1194 				= dynamic_cast<PartsEditorConnectorsConnectorItem *>(m_item->childItems()[i]);
1195 			if(connectorItem == NULL) continue;
1196 
1197 			if(connectorItem->connector()->connectorSharedID() == id) {
1198 				connectorItem->setMismatching(mismatching);
1199 			}
1200 		}
1201 	}
1202 }
1203 
aboutToSave(bool fakeDefaultIfNone)1204 void PartsEditorView::aboutToSave(bool fakeDefaultIfNone) {
1205 	if(m_item) {
1206 		FSvgRenderer renderer;
1207 		QByteArray bytes = renderer.loadSvg(m_item->flatSvgFilePath());
1208 		if (!bytes.isEmpty()) {
1209 			QRectF svgViewBox = renderer.viewBoxF();
1210 			QSizeF sceneViewBox = renderer.defaultSizeF();
1211 			QDomDocument *svgDom = m_item->svgDom();
1212 
1213 			// this may change the layers defined in the file, so
1214 			// let's get the connectorsLayer after it
1215 			bool somethingChanged = addDefaultLayerIfNotInSvg(svgDom, fakeDefaultIfNone);
1216 
1217 			QDomElement elem = svgDom->documentElement();
1218 
1219 			somethingChanged |= removeConnectorsIfNeeded(elem);
1220 			QStringList connectorsLayerIds = findConnectorsLayerIds(svgDom);
1221 			foreach (QString connectorsLayerId, connectorsLayerIds) {
1222 				somethingChanged |= updateTerminalPoints(svgDom, sceneViewBox, svgViewBox, connectorsLayerId);
1223 				somethingChanged |= addConnectorsIfNeeded(svgDom, sceneViewBox, svgViewBox, connectorsLayerId);
1224 			}
1225 			somethingChanged |= (m_viewItem != NULL);
1226 
1227 			if(somethingChanged) {
1228 				QString viewFolder = getOrCreateViewFolderInTemp();
1229 
1230 				QString tempFile = m_tempFolder.absolutePath()+"/"+viewFolder+"/"+FolderUtils::getRandText()+".svg";
1231 
1232 				ensureFilePath(tempFile);
1233 
1234 				if(!TextUtils::writeUtf8(tempFile, TextUtils::removeXMLEntities(svgDom->toString()))) {
1235 					/*QMessageBox::information(NULL,"",
1236 						QString("Couldn't open file for update, after drawing connectors: '%1'")
1237 							.arg(tempFile)
1238 					);*/
1239                     DebugDialog::debug(QString("Couldn't open file for update, after drawing connectors: '%1'").arg(tempFile));
1240 				}
1241 			}
1242 		} else {
1243 			DebugDialog::debug("updating part view svg file: could not load file "+m_item->flatSvgFilePath());
1244 		}
1245 	}
1246 }
1247 
addConnectorsIfNeeded(QDomDocument * svgDom,const QSizeF & sceneViewBox,const QRectF & svgViewBox,const QString & connectorsLayerId)1248 bool PartsEditorView::addConnectorsIfNeeded(QDomDocument *svgDom, const QSizeF &sceneViewBox, const QRectF &svgViewBox, const QString &connectorsLayerId) {
1249 	bool changed = false;
1250 	if(!m_drawnConns.isEmpty()) {
1251 		QRectF bounds;
1252 		QString connId;
1253 
1254 		foreach(PartsEditorConnectorsConnectorItem* drawnConn, m_drawnConns.values()) {
1255 			bounds = drawnConn->mappedRect();
1256 			connId = drawnConn->connector()->connectorSharedID();
1257 
1258 			QRectF svgRect = mapFromSceneToSvg(bounds,sceneViewBox,svgViewBox);
1259 			QString svgId = svgIdForConnector(drawnConn->connector(), connId);
1260 			addRectToSvg(svgDom,svgId,svgRect, connectorsLayerId);
1261 		}
1262 		changed = true;
1263 	}
1264 
1265 	return changed;
1266 }
1267 
1268 
addDefaultLayerIfNotInSvg(QDomDocument * svgDom,bool fakeDefaultIfNone)1269 bool PartsEditorView::addDefaultLayerIfNotInSvg(QDomDocument *svgDom, bool fakeDefaultIfNone)
1270 {
1271 	QStringList defaultLayers = defaultLayerAsStringlist();
1272 	QStringList layers = getLayers(svgDom, fakeDefaultIfNone);
1273 	foreach (QString defaultLayer, defaultLayers) {
1274 		if (layers.contains(defaultLayer)) return false;
1275 	}
1276 
1277 	// jrc 4/7/2010: not sure if making a new top level layer is correct
1278 	// since it could swallow other layers
1279 
1280 	QDomElement docElem = svgDom->documentElement();
1281 
1282 	QDomElement newTopLevel = svgDom->createElement("g");
1283 	newTopLevel.setAttribute("id", defaultLayers.at(0));
1284 
1285 	// place the child in a aux list, cause the
1286 	// qdomnodelist takes care of the references
1287 	QList<QDomNode> children;
1288 	for(QDomNode child=docElem.firstChild(); !child.isNull(); child=child.nextSibling()) {
1289 		children << child;
1290 	}
1291 
1292 	foreach(QDomNode child, children) {
1293 		newTopLevel.appendChild(child);
1294 	}
1295 
1296 	docElem.appendChild(newTopLevel);
1297 
1298 	return true;
1299 }
1300 
defaultLayers()1301 LayerList PartsEditorView::defaultLayers() {
1302 	LayerList layers;
1303 	switch( m_viewIdentifier ) {
1304 		case ViewLayer::IconView:
1305 			layers << ViewLayer::Icon;
1306 			break;
1307 		case ViewLayer::BreadboardView:
1308 			layers << ViewLayer::Breadboard;
1309 			break;
1310 		case ViewLayer::SchematicView:
1311 			layers << ViewLayer::Schematic;
1312 			break;
1313 		case ViewLayer::PCBView:
1314 			layers << ViewLayer::Copper0 << ViewLayer::Copper1;
1315 			break;
1316 		default:
1317 			layers << ViewLayer::UnknownLayer;
1318 			break;
1319 	}
1320 	return layers;
1321 }
1322 
defaultLayerAsStringlist()1323 QStringList PartsEditorView::defaultLayerAsStringlist() {
1324 	QStringList layers;
1325 	foreach (ViewLayer::ViewLayerID viewLayerID, defaultLayers()) {
1326 		layers << ViewLayer::viewLayerXmlNameFromID(viewLayerID);
1327 	}
1328 
1329 	return layers;
1330 }
1331 
svgIdForConnector(const QString & connId)1332 QString PartsEditorView::svgIdForConnector(const QString &connId) {
1333 	//Q_ASSERT(m_item)
1334 
1335 	if (m_item == NULL) return connId;
1336 
1337 
1338 	QString result = "";
1339 	QDomElement elem = m_item->svgDom()->documentElement();
1340 	if(terminalIdForConnectorIdAux(result, connId, elem, false)) {
1341 		return result;
1342 	}
1343 
1344 /*
1345 	foreach(Connector* conn, m_item->connectors()) {
1346 		QString svgId = svgIdForConnector(conn, connId);
1347 		if(connId != svgId) {
1348 			return svgId;
1349 		}
1350 	}
1351 
1352 */
1353 
1354 	return connId;
1355 }
1356 
svgIdForConnector(Connector * conn,const QString & connId)1357 QString PartsEditorView::svgIdForConnector(Connector* conn, const QString &connId) {
1358 	if (conn->connectorShared() && conn->connectorSharedID() == connId) {
1359 		foreach(SvgIdLayer *sil, conn->connectorShared()->pins().values(m_viewIdentifier)) {
1360 			return sil->m_svgId;
1361 		}
1362 	}
1363 	return connId;
1364 }
1365 
updateTerminalPoints(QDomDocument * svgDom,const QSizeF & sceneViewBox,const QRectF & svgViewBox,const QString & connectorsLayerId)1366 bool PartsEditorView::updateTerminalPoints(QDomDocument *svgDom, const QSizeF &sceneViewBox, const QRectF &svgViewBox, const QString &connectorsLayerId) {
1367 	QList<PartsEditorConnectorsConnectorItem*> connsWithNewTPs;
1368 	QStringList tpIdsToRemove;
1369 	foreach(QGraphicsItem *item, items()) {
1370 		PartsEditorConnectorsConnectorItem *citem = dynamic_cast<PartsEditorConnectorsConnectorItem*>(item);
1371 		if(citem) {
1372 			TerminalPointItem *tp = citem->terminalPointItem();
1373 			if (!tp) {
1374 				citem->setShowTerminalPoint(citem->isShowingTerminalPoint());
1375 				tp = citem->terminalPointItem();
1376 			}
1377 
1378 			QString connId = citem->connector()->connectorSharedID();
1379 			QString terminalId = connId+"terminal";
1380 
1381 			if(tp && !tp->isInTheCenter()) {
1382 				if(tp->hasBeenMoved() || citem->hasBeenMoved()) {
1383 					connsWithNewTPs << citem;
1384 					tpIdsToRemove << terminalId;
1385 					//DebugDialog::debug("<<<< MOVED! removing terminal "+terminalId+" in view: "+ViewLayer::viewIdentifierName(m_viewIdentifier));
1386 					updateSvgIdLayer(connId, terminalId, connectorsLayerId);
1387 				}
1388 			} else {
1389 				//DebugDialog::debug("<<<< removing terminal "+terminalId+" in view: "+ViewLayer::viewIdentifierName(m_viewIdentifier));
1390 				tpIdsToRemove << terminalId;
1391 				emit removeTerminalPoint(connId, m_viewIdentifier);
1392 			}
1393 		}
1394 	}
1395 	QDomElement elem = svgDom->documentElement();
1396 	removeTerminalPoints(tpIdsToRemove,elem);
1397 	addNewTerminalPoints(connsWithNewTPs, svgDom, sceneViewBox, svgViewBox, connectorsLayerId);
1398 	return !tpIdsToRemove.isEmpty();
1399 }
1400 
updateSvgIdLayer(const QString & connId,const QString & terminalId,const QString & connectorsLayerId)1401 void PartsEditorView::updateSvgIdLayer(const QString &connId, const QString &terminalId, const QString &connectorsLayerId) {
1402 	ViewLayer::ViewLayerID viewLayerID = ViewLayer::viewLayerIDFromXmlString(connectorsLayerId);
1403 	foreach(Connector *conn, m_item->connectors()) {
1404 		foreach(SvgIdLayer *sil, conn->connectorShared()->pins().values(m_viewIdentifier)) {
1405 			if(conn->connectorSharedID() == connId) {
1406 				if (sil->m_svgViewLayerID == viewLayerID) {
1407 					sil->m_terminalId = terminalId;
1408 					return;
1409 				}
1410 			}
1411 		}
1412 
1413 		foreach(SvgIdLayer *sil, conn->connectorShared()->pins().values(m_viewIdentifier)) {
1414 			if(conn->connectorSharedID() == connId) {
1415 				sil->m_terminalId = terminalId;
1416 
1417 				if(viewLayerID != ViewLayer::UnknownLayer) {
1418 					sil->m_svgViewLayerID = viewLayerID;
1419 				}
1420 			}
1421 		}
1422 	}
1423 }
1424 
removeTerminalPoints(const QStringList & tpIdsToRemove,QDomElement & docElem)1425 void PartsEditorView::removeTerminalPoints(const QStringList &tpIdsToRemove, QDomElement &docElem) {
1426 	QDomElement e = docElem.firstChildElement();
1427 	while(!e.isNull()) {
1428 		bool doRemove = false;
1429 		QString id = e.attribute("id");
1430 		if(tpIdsToRemove.contains(id)) {
1431 			doRemove = true;
1432 		} else if(e.hasChildNodes()) {
1433 			removeTerminalPoints(tpIdsToRemove,e);
1434 		}
1435 		QDomElement e2;
1436 		if(doRemove) {
1437 			e2 = e;
1438 		}
1439 		e = e.nextSiblingElement();
1440 		if(doRemove) {
1441 			e2.removeAttribute("id");
1442 		}
1443 	}
1444 }
1445 
addNewTerminalPoints(const QList<PartsEditorConnectorsConnectorItem * > & connsWithNewTPs,QDomDocument * svgDom,const QSizeF & sceneViewBox,const QRectF & svgViewBox,const QString & connectorsLayerId)1446 void PartsEditorView::addNewTerminalPoints(
1447 			const QList<PartsEditorConnectorsConnectorItem*> &connsWithNewTPs, QDomDocument *svgDom,
1448 			const QSizeF &sceneViewBox, const QRectF &svgViewBox, const QString &connectorsLayerId
1449 ) {
1450 	foreach(PartsEditorConnectorsConnectorItem* citem, connsWithNewTPs) {
1451 		QString connId = citem->connector()->connectorSharedID();
1452 		TerminalPointItem *tp = citem->terminalPointItem();
1453 		if (tp == NULL) {
1454 			throw "PartsEditorView::addNewTerminalPoints tp missing";
1455 		}
1456 
1457 		if(tp) {
1458 			QRectF tpointRect(tp->mappedPoint(), QPointF(0,0));
1459 			QRectF svgTpRect = mapFromSceneToSvg(tpointRect,sceneViewBox,svgViewBox);
1460 
1461 			double halfTPSize = 0.001; // a tiny rectangle
1462 			svgTpRect.setSize(QSizeF(halfTPSize*2,halfTPSize*2));
1463 
1464 			addRectToSvg(svgDom,connId+"terminal",svgTpRect, connectorsLayerId);
1465 		} else {
1466 			qWarning() << tr(
1467 				"Parts Editor: couldn't save terminal "
1468 				"point for connector %1 in %2 view")
1469 				.arg(citem->connector()->connectorSharedID())
1470 				.arg(ViewLayer::viewIdentifierNaturalName(m_viewIdentifier));
1471 		}
1472 	}
1473 }
1474 
removeConnectorsIfNeeded(QDomElement & docElem)1475 bool PartsEditorView::removeConnectorsIfNeeded(QDomElement &docElem) {
1476 	if(!m_removedConnIds.isEmpty()) {
1477 		//Q_ASSERT(docElem.tagName() == "svg");
1478 
1479 		QDomElement e = docElem.firstChildElement();
1480 		while(!e.isNull()) {
1481 			QString id = e.attribute("id");
1482 			if(isSupposedToBeRemoved(id)) {
1483 				e.removeAttribute("id");
1484 			} else if(e.hasChildNodes()) {
1485 				removeConnectorsIfNeeded(e);
1486 			}
1487 			e = e.nextSiblingElement();
1488 		}
1489 		return true;
1490 	}
1491 	return false;
1492 }
1493 
mapFromSceneToSvg(const QRectF & itemRect,const QSizeF & sceneViewBox,const QRectF & svgViewBox)1494 QRectF PartsEditorView::mapFromSceneToSvg(const QRectF &itemRect, const QSizeF &sceneViewBox, const QRectF &svgViewBox) {
1495 	double relationW = svgViewBox.width() / sceneViewBox.width();
1496 	double relationH = svgViewBox.height() / sceneViewBox.height();
1497 
1498 	double x = itemRect.x() * relationW;
1499 	double y = itemRect.y() * relationH;
1500 	double width = itemRect.width() * relationW;
1501 	double height = itemRect.height() * relationH;
1502 
1503 	return QRectF(x,y,width,height);
1504 }
1505 
addRectToSvg(QDomDocument * svgDom,const QString & id,const QRectF & rect,const QString & connectorsLayerId)1506 bool PartsEditorView::addRectToSvg(QDomDocument* svgDom, const QString &id, const QRectF &rect, const QString &connectorsLayerId) {
1507 	QDomElement connElem = svgDom->createElement("rect");
1508 	connElem.setAttribute("id",id);
1509 	connElem.setAttribute("x",rect.x());
1510 	connElem.setAttribute("y",rect.y());
1511 	connElem.setAttribute("width",rect.width());
1512 	connElem.setAttribute("height",rect.height());
1513 	connElem.setAttribute("fill","none");
1514 
1515 	if(connectorsLayerId.isEmpty()) {
1516 		svgDom->firstChildElement("svg").appendChild(connElem);
1517 		return true;
1518 	} else {
1519 		QDomElement docElem = svgDom->documentElement();
1520 		return addRectToSvgAux(docElem, connectorsLayerId, connElem);
1521 	}
1522 }
1523 
addRectToSvgAux(QDomElement & docElem,const QString & connectorsLayerId,QDomElement & rectElem)1524 bool PartsEditorView::addRectToSvgAux(QDomElement &docElem, const QString &connectorsLayerId, QDomElement &rectElem) {
1525 	QDomElement e = docElem.firstChildElement();
1526 	while(!e.isNull()) {
1527 		QString id = e.attribute("id");
1528 		if(id == connectorsLayerId) {
1529 			e.appendChild(rectElem);
1530 			return true;
1531 		} else if(e.hasChildNodes()) {
1532 			if(addRectToSvgAux(e, connectorsLayerId, rectElem)) {
1533 				return true;
1534 			}
1535 		}
1536 		e = e.nextSiblingElement();
1537 	}
1538 	return false;
1539 }
1540 
1541 
isSupposedToBeRemoved(const QString & id)1542 bool PartsEditorView::isSupposedToBeRemoved(const QString& id) {
1543 	if (id.isEmpty()) return false;
1544 
1545 	// TODO: m_removedConnIds should be svg ids and not connectorSharedIDs (from fzp)
1546 
1547 	foreach(QString toBeRemoved, m_removedConnIds) {
1548 		if(id.startsWith(toBeRemoved)) {
1549 			QString temp = id;
1550 			temp = temp.remove(0, toBeRemoved.length());
1551 			if (temp.length() == 0) return true;
1552 
1553 			// assumes svg id is always prefixDsuffix where D is some string of decimal digits
1554 			// and prefixD matches toBeRemoved
1555 			if (!temp.at(0).isDigit()) {
1556 				return true;
1557 			}
1558 		}
1559 	}
1560 	return false;
1561 }
1562 
myItem()1563 PartsEditorConnectorsPaletteItem *PartsEditorView::myItem() {
1564 	return dynamic_cast<PartsEditorConnectorsPaletteItem*>(m_item.data());
1565 }
1566 
showTerminalPoints(bool show)1567 void PartsEditorView::showTerminalPoints(bool show) {
1568 	m_showingTerminalPoints = show;
1569 	foreach(QGraphicsItem *item, items()) {
1570 		PartsEditorConnectorsConnectorItem *connItem
1571 			= dynamic_cast<PartsEditorConnectorsConnectorItem*>(item);
1572 		if(connItem) {
1573 			connItem->setShowTerminalPoint(show);
1574 		}
1575 	}
1576 	scene()->update();
1577 
1578 	/*if(!m_showingTerminalPoints) {
1579 		m_terminalPointsTimer->stop();
1580 	}*/
1581 }
1582 
showingTerminalPoints()1583 bool PartsEditorView::showingTerminalPoints() {
1584 	return m_showingTerminalPoints;
1585 }
1586 
inFileDefinedConnectorChanged(PartsEditorConnectorsConnectorItem * connItem)1587 void PartsEditorView::inFileDefinedConnectorChanged(PartsEditorConnectorsConnectorItem *connItem) {
1588 	QString connId = connItem->connectorSharedID();
1589 	m_drawnConns[connId] = connItem;
1590 	if(!m_removedConnIds.contains(connId)) {
1591 		m_removedConnIds << connId;
1592 	}
1593 }
1594 
1595 
addFixedToBottomRight(QWidget * widget)1596 void PartsEditorView::addFixedToBottomRight(QWidget *widget) {
1597 	m_fixedWidgets << widget;
1598 	QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget();
1599 	proxy->setWidget(widget);
1600 
1601 	addFixedToBottomRightItem(proxy);
1602 }
1603 
imageLoaded()1604 bool PartsEditorView::imageLoaded() {
1605 	return m_item != NULL;
1606 }
1607 
drawBackground(QPainter * painter,const QRectF & rect)1608 void PartsEditorView::drawBackground(QPainter *painter, const QRectF &rect) {
1609 	SketchWidget::drawBackground(painter,rect);
1610 
1611 	// 10mm spacing grid
1612 	/*const int gridSize = 10*width()/widthMM();
1613 
1614 	QRectF itemRect = m_item->mapToScene(m_item->boundingRect()).boundingRect();
1615 	painter->drawRect(itemRect);
1616 	double itemTop = itemRect.top();
1617 	double itemLeft = itemRect.left();
1618 
1619 	QVarLengthArray<QLineF, 100> lines;
1620 
1621 	for (double x = itemLeft; x < rect.right(); x += gridSize) {
1622 		lines.append(QLineF(x, rect.top(), x, rect.bottom()));
1623 	}
1624 	for (double x = itemLeft-gridSize; x > rect.left(); x -= gridSize) {
1625 		lines.append(QLineF(x, rect.top(), x, rect.bottom()));
1626 	}
1627 
1628 	for (double y = itemTop; y < rect.bottom(); y += gridSize) {
1629 		lines.append(QLineF(rect.left(), y, rect.right(), y));
1630 	}
1631 	for (double y = itemTop-gridSize; y > rect.top(); y -= gridSize) {
1632 		lines.append(QLineF(rect.left(), y, rect.right(), y));
1633 	}
1634 
1635 	painter->drawLines(lines.data(), lines.size());*/
1636 }
1637 
recoverTerminalPointsState()1638 void PartsEditorView::recoverTerminalPointsState() {
1639 	showTerminalPoints(m_showingTerminalPointsBackup);
1640 	m_terminalPointsTimer->stop();
1641 }
1642 
connsPosOrSizeChanged()1643 bool PartsEditorView::connsPosOrSizeChanged() {
1644 	foreach(QGraphicsItem *item, items()) {
1645 		PartsEditorConnectorsConnectorItem *citem =
1646 			dynamic_cast<PartsEditorConnectorsConnectorItem*>(item);
1647 		if(citem) {
1648 			TerminalPointItem *tp = citem->terminalPointItem();
1649 			if((tp && tp->hasBeenMoved()) || citem->hasBeenMoved() || citem->hasBeenResized()) {
1650 				return true;
1651 			}
1652 		}
1653 	}
1654 	return false;
1655 }
1656 
setViewItem(ItemBase * item)1657 void PartsEditorView::setViewItem(ItemBase * item) {
1658 	m_viewItem = item;
1659 }
1660 
1661 
checkConnectorLayers(ViewLayer::ViewIdentifier viewIdentifier,const QString & connId,Connector * existingConnector,Connector * newConnector)1662 void PartsEditorView::checkConnectorLayers(ViewLayer::ViewIdentifier viewIdentifier, const QString & connId, Connector* existingConnector, Connector * newConnector)
1663 {
1664 	if (m_viewIdentifier != viewIdentifier) return;
1665 
1666 	Q_UNUSED(connId);
1667 	QList<SvgIdLayer *> newpins = newConnector->connectorShared()->pins().values(viewIdentifier);
1668 	QList<SvgIdLayer *> oldpins = existingConnector->connectorShared()->pins().values(viewIdentifier);
1669 
1670 	LayerList layerList;
1671 
1672 	QList<SvgIdLayer *> changes;
1673 	foreach (SvgIdLayer * newSvgIdLayer, newpins) {
1674 		bool gotOne = false;
1675 		layerList << newSvgIdLayer->m_svgViewLayerID;
1676 
1677 		foreach (SvgIdLayer * oldSvgIdLayer, oldpins) {
1678 			if (newSvgIdLayer->m_svgViewLayerID == oldSvgIdLayer->m_svgViewLayerID) {
1679 				gotOne = true;
1680 				break;
1681 			}
1682 		}
1683 		if (!gotOne) {
1684 			changes << newSvgIdLayer;
1685 		}
1686 	}
1687 
1688 
1689 	foreach (SvgIdLayer * svgIdLayer, changes) {
1690 		SvgIdLayer * cpy = svgIdLayer->copyLayer();
1691 		existingConnector->connectorShared()->insertPin(viewIdentifier, cpy);
1692 	}
1693 
1694 	changes.clear();
1695 	foreach (SvgIdLayer * oldSvgIdLayer, oldpins) {
1696 		bool gotOne = false;
1697 		foreach (SvgIdLayer * newSvgIdLayer, newpins) {
1698 			if (newSvgIdLayer->m_svgViewLayerID == oldSvgIdLayer->m_svgViewLayerID) {
1699 				gotOne = true;
1700 				break;
1701 			}
1702 		}
1703 		if (!gotOne) {
1704 			changes << oldSvgIdLayer;
1705 		}
1706 	}
1707 
1708 	foreach (SvgIdLayer * svgIdLayer, changes) {
1709 		existingConnector->connectorShared()->removePin(viewIdentifier, svgIdLayer);
1710 	}
1711 }
1712 
updatePinsInfo(QList<QPointer<ConnectorShared>> connsShared)1713 void PartsEditorView::updatePinsInfo(QList< QPointer<ConnectorShared> > connsShared) {
1714 	if(!m_svgLoaded) return;  // if the user has not changed the svg file, there's nothing to update
1715 
1716 	ViewLayer::ViewLayerID layerID = connectorsLayerId();
1717 	QList<ConnectorShared *> notFound;
1718 
1719 	foreach(ConnectorShared* cs, connsShared) {
1720 		QString connId = cs->id();
1721 		SvgIdLayer* pinInfo = cs->fullPinInfo(m_viewIdentifier, layerID);
1722 		if (pinInfo == NULL) {
1723 			notFound << cs;
1724 		}
1725 		else if(!m_svgIds[connId].connectorId.isEmpty()) {
1726 			pinInfo->m_svgId = m_svgIds[connId].connectorId;
1727 			// terminal points are already updated (by the function updateTerminalPoints)
1728 				// pinInfo->m_terminalId = m_svgIds[connId].terminalId;
1729 		}
1730 	}
1731 
1732 	if (notFound.length() == 0) return;
1733 
1734 	// not sure this is the right place to handle the change of connector layers...
1735 
1736 	LayerList alts = ViewLayer::findAlternativeLayers(layerID);
1737 	if (alts.length() == 0) return;
1738 
1739 	foreach (ViewLayer::ViewLayerID vlid, alts) {
1740 		QList<ConnectorShared*> found;
1741 		foreach(ConnectorShared* cs, notFound) {
1742 			QString connId = cs->id();
1743 			SvgIdLayer* pinInfo = cs->fullPinInfo(m_viewIdentifier, vlid);
1744 			if (pinInfo != NULL && !m_svgIds[connId].connectorId.isEmpty()) {
1745 				pinInfo->m_svgId = m_svgIds[connId].connectorId;
1746 				pinInfo->m_svgViewLayerID = layerID;
1747 				found << cs;
1748 			}
1749 		}
1750 
1751 		foreach (ConnectorShared * cs, found) {
1752 			notFound.removeOne(cs);
1753 		}
1754 	}
1755 }
1756 
saveSvg(const QString & svg,const QString & newFilePath)1757 QString PartsEditorView::saveSvg(const QString & svg, const QString & newFilePath) {
1758     if (!TextUtils::writeUtf8(newFilePath, svg)) {
1759         throw tr("unable to open temp file %1").arg(newFilePath);
1760     }
1761 	return newFilePath;
1762 }
1763 
clearFixedItems()1764 void PartsEditorView::clearFixedItems() {
1765 	m_fixedToBottomLeftItems.clear();
1766 	m_fixedToBottomRightItems.clear();
1767 	m_fixedToCenterItems.clear();
1768 	m_fixedToTopLeftItems.clear();
1769 	m_fixedToTopRightItems.clear();
1770 }
1771 
1772 
ensureFixedItemsPositions()1773 void PartsEditorView::ensureFixedItemsPositions() {
1774 
1775 	//DebugDialog::debug("ensure fixed items positions");
1776 
1777 	ensureFixedToBottomLeftItems();
1778 	ensureFixedToCenterItems();
1779 	ensureFixedToTopLeftItems();
1780 	ensureFixedToTopRightItems();
1781 	ensureFixedToBottomRightItems();
1782 
1783 	scene()->update(sceneRect());
1784 }
1785 
addFixedToTopLeftItem(QGraphicsItem * item)1786 void PartsEditorView::addFixedToTopLeftItem(QGraphicsItem *item) {
1787 	item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
1788 	if(!scene()->items().contains(item)) {
1789 		scene()->addItem(item);
1790 	}
1791 	m_fixedToTopLeftItems << item;
1792 	ensureFixedToTopLeft(item);
1793 }
1794 
addFixedToTopRightItem(QGraphicsItem * item)1795 void PartsEditorView::addFixedToTopRightItem(QGraphicsItem *item) {
1796 	item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
1797 	if(!scene()->items().contains(item)) {
1798 		scene()->addItem(item);
1799 	}
1800 	m_fixedToTopRightItems << item;
1801 	ensureFixedToTopRight(item);
1802 }
1803 
addFixedToBottomLeftItem(QGraphicsItem * item)1804 void PartsEditorView::addFixedToBottomLeftItem(QGraphicsItem *item) {
1805 	item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
1806 	if(!scene()->items().contains(item)) {
1807 		scene()->addItem(item);
1808 	}
1809 	m_fixedToBottomLeftItems << item;
1810 	ensureFixedToBottomLeft(item);
1811 }
1812 
addFixedToBottomRightItem(QGraphicsItem * item)1813 void PartsEditorView::addFixedToBottomRightItem(QGraphicsItem *item) {
1814 	item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
1815 	if(!scene()->items().contains(item)) {
1816 		scene()->addItem(item);
1817 	}
1818 	m_fixedToBottomRightItems << item;
1819 	ensureFixedToBottomRight(item);
1820 }
1821 
addFixedToCenterItem(QGraphicsItem * item)1822 void PartsEditorView::addFixedToCenterItem(QGraphicsItem *item) {
1823 	item->setFlag(QGraphicsItem::ItemIgnoresTransformations);
1824 	if(!scene()->items().contains(item)) {
1825 		scene()->addItem(item);
1826 	}
1827 	m_fixedToCenterItems << item;
1828 	ensureFixedToCenter(item);
1829 }
1830 
ensureFixedToTopLeftItems()1831 void PartsEditorView::ensureFixedToTopLeftItems() {
1832 	if(isVisible()) {
1833 		QList<QGraphicsItem*> toRemove;
1834 
1835 		foreach(QGraphicsItem* item, m_fixedToTopLeftItems) {
1836 			if(scene()->items().contains(item)) {
1837 				ensureFixedToTopLeft(item);
1838 			} else {
1839 				toRemove << item;
1840 			}
1841 		}
1842 
1843 		foreach(QGraphicsItem* item, toRemove) {
1844 			m_fixedToTopLeftItems.removeAll(item);
1845 		}
1846 	}
1847 }
1848 
ensureFixedToTopLeft(QGraphicsItem * item)1849 void PartsEditorView::ensureFixedToTopLeft(QGraphicsItem* item) {
1850 	item->setPos(mapToScene(0,0));
1851 }
1852 
ensureFixedToTopRightItems()1853 void PartsEditorView::ensureFixedToTopRightItems() {
1854 	if(isVisible()) {
1855 		QList<QGraphicsItem*> toRemove;
1856 
1857 		foreach(QGraphicsItem* item, m_fixedToTopRightItems) {
1858 			if(scene()->items().contains(item)) {
1859 				ensureFixedToTopRight(item);
1860 			} else {
1861 				toRemove << item;
1862 			}
1863 		}
1864 
1865 		foreach(QGraphicsItem* item, toRemove) {
1866 			m_fixedToTopRightItems.removeAll(item);
1867 		}
1868 	}
1869 }
1870 
ensureFixedToTopRight(QGraphicsItem * item)1871 void PartsEditorView::ensureFixedToTopRight(QGraphicsItem* item) {
1872 	int x = (int) (width()-fixedItemWidth(item));
1873 	int y = 0;
1874 
1875 	item->setPos(mapToScene(x,y));
1876 }
1877 
ensureFixedToBottomLeftItems()1878 void PartsEditorView::ensureFixedToBottomLeftItems() {
1879 	if(isVisible()) {
1880 		QList<QGraphicsItem*> toRemove;
1881 
1882 		foreach(QGraphicsItem* item, m_fixedToBottomLeftItems) {
1883 			if(scene()->items().contains(item)) {
1884 				ensureFixedToBottomLeft(item);
1885 			} else {
1886 				toRemove << item;
1887 			}
1888 		}
1889 
1890 		foreach(QGraphicsItem* item, toRemove) {
1891 			m_fixedToBottomLeftItems.removeAll(item);
1892 		}
1893 	}
1894 }
1895 
ensureFixedToBottomLeft(QGraphicsItem * item)1896 void PartsEditorView::ensureFixedToBottomLeft(QGraphicsItem* item) {
1897 	int x = 0;
1898 	int y = (int) (height()-fixedItemHeight(item));
1899 
1900 	item->setPos(mapToScene(x,y));
1901 }
1902 
ensureFixedToBottomRightItems()1903 void PartsEditorView::ensureFixedToBottomRightItems() {
1904 	if(isVisible()) {
1905 		QList<QGraphicsItem*> toRemove;
1906 
1907 		foreach(QGraphicsItem* item, m_fixedToBottomRightItems) {
1908 			if(scene()->items().contains(item)) {
1909 				ensureFixedToBottomRight(item);
1910 			} else {
1911 				toRemove << item;
1912 			}
1913 		}
1914 
1915 		foreach(QGraphicsItem* item, toRemove) {
1916 			m_fixedToBottomRightItems.removeAll(item);
1917 		}
1918 	}
1919 }
1920 
ensureFixedToBottomRight(QGraphicsItem * item)1921 void PartsEditorView::ensureFixedToBottomRight(QGraphicsItem* item) {
1922 	int x = (int) (width()-fixedItemWidth(item));
1923 	int y = (int) (height()-fixedItemHeight(item));
1924 
1925 	item->setPos(mapToScene(x,y));
1926 }
1927 
ensureFixedToCenterItems()1928 void PartsEditorView::ensureFixedToCenterItems() {
1929 	if(isVisible()) {
1930 		QList<QGraphicsItem*> toRemove;
1931 
1932 		foreach(QGraphicsItem* item, m_fixedToCenterItems) {
1933 			if(scene()->items().contains(item)) {
1934 				ensureFixedToCenter(item);
1935 			} else {
1936 				toRemove << item;
1937 			}
1938 		}
1939 
1940 		foreach(QGraphicsItem* item, toRemove) {
1941 			m_fixedToCenterItems.removeAll(item);
1942 		}
1943 	}
1944 }
1945 
ensureFixedToCenter(QGraphicsItem * item)1946 void PartsEditorView::ensureFixedToCenter(QGraphicsItem* item) {
1947 	double x = (width()-fixedItemWidth(item))/2;
1948 	double y = (height()-fixedItemHeight(item))/2;
1949 
1950 	QPointF pos = mapToScene(x,y);
1951 
1952 	if(pos.x() < scene()->width() && pos.y() < scene()->height()) {
1953 		item->setPos(pos);
1954 	}
1955 }
1956 
removeIfFixedPos(QGraphicsItem * item)1957 void PartsEditorView::removeIfFixedPos(QGraphicsItem *item) {
1958 	m_fixedToBottomLeftItems.removeAll(item);
1959 	m_fixedToBottomRightItems.removeAll(item);
1960 	m_fixedToCenterItems.removeAll(item);
1961 	m_fixedToTopLeftItems.removeAll(item);
1962 	m_fixedToTopRightItems.removeAll(item);
1963 }
1964 
1965 
fixedItemWidth(QGraphicsItem * item)1966 double PartsEditorView::fixedItemWidth(QGraphicsItem* item) {
1967 	QGraphicsProxyWidget* gWidget = dynamic_cast<QGraphicsProxyWidget*>(item);
1968 	if(gWidget) {
1969 		return gWidget->widget()->width();
1970 	} else {
1971 		return item->boundingRect().width();
1972 	}
1973 }
1974 
fixedItemHeight(QGraphicsItem * item)1975 double PartsEditorView::fixedItemHeight(QGraphicsItem* item) {
1976 	QGraphicsProxyWidget* gWidget = dynamic_cast<QGraphicsProxyWidget*>(item);
1977 	if(gWidget) {
1978 		return gWidget->widget()->height();
1979 	} else {
1980 		return item->boundingRect().height();
1981 	}
1982 }
1983 
deleteItem(ItemBase * itemBase,bool deleteModelPart,bool doEmit,bool later)1984 void PartsEditorView::deleteItem(ItemBase * itemBase, bool deleteModelPart, bool doEmit, bool later)
1985 {
1986 	removeIfFixedPos(itemBase);
1987 	SketchWidget::deleteItem(itemBase, deleteModelPart, doEmit, later);
1988 }
1989 
setPaletteModel(PaletteModel * paletteModel)1990 void PartsEditorView::setPaletteModel(PaletteModel * paletteModel)
1991 {
1992     m_paletteModel = paletteModel;
1993 }
1994