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.old
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: 6963 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-14 10:13:52 +0200 (So, 14. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "modelbase.h"
28 #include "../debugdialog.h"
29 #include "../items/partfactory.h"
30 #include "../items/moduleidnames.h"
31 #include "../utils/textutils.h"
32 #include "../utils/folderutils.h"
33 #include "../utils/fmessagebox.h"
34 #include "../version/version.h"
35 #include "../viewgeometry.h"
36 
37 #include <QMessageBox>
38 
39 QList<QString> ModelBase::CoreList;
40 
41 /////////////////////////////////////////////////
42 
ModelBase(bool makeRoot)43 ModelBase::ModelBase( bool makeRoot )
44 {
45     m_useOldSchematics = false;
46 	m_reportMissingModules = true;
47 	m_referenceModel = NULL;
48 	m_root = NULL;
49 	if (makeRoot) {
50 		m_root = new ModelPart();
51 		m_root->setModelPartShared(new ModelPartSharedRoot());
52 	}
53 }
54 
~ModelBase()55 ModelBase::~ModelBase() {
56 	if (m_root) {
57         ModelPartShared * modelPartShared = m_root->modelPartShared();
58         if (modelPartShared) {
59             m_root->setModelPartShared(NULL);
60             delete modelPartShared;
61         }
62 		delete m_root;
63 	}
64 }
65 
root()66 ModelPart * ModelBase::root() {
67 	return m_root;
68 }
69 
retrieveModelPart(const QString &)70 ModelPart * ModelBase::retrieveModelPart(const QString & /* moduleID */)  {
71 	return NULL;
72 }
73 
74 // loads a model from an fz file--assumes a reference model exists with all parts
loadFromFile(const QString & fileName,ModelBase * referenceModel,QList<ModelPart * > & modelParts,bool checkViews)75 bool ModelBase::loadFromFile(const QString & fileName, ModelBase * referenceModel, QList<ModelPart *> & modelParts, bool checkViews) {
76 	m_referenceModel = referenceModel;
77 
78     QFile file(fileName);
79     if (!file.open(QFile::ReadOnly | QFile::Text)) {
80         FMessageBox::warning(NULL, QObject::tr("Fritzing"),
81                              QObject::tr("Cannot read file %1:\n%2.")
82                              .arg(fileName)
83                              .arg(file.errorString()));
84         return false;
85     }
86 
87     QString errorStr;
88     int errorLine;
89     int errorColumn;
90     QDomDocument domDocument;
91 
92     if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
93         FMessageBox::information(NULL, QObject::tr("Fritzing"),
94                                  QObject::tr("Parse error (1) at line %1, column %2:\n%3\n%4")
95                                  .arg(errorLine)
96                                  .arg(errorColumn)
97                                  .arg(errorStr)
98 								 .arg(fileName));
99         return false;
100     }
101 
102     QDomElement root = domDocument.documentElement();
103    	if (root.isNull()) {
104         FMessageBox::information(NULL, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (2).").arg(fileName));
105    		return false;
106 	}
107 
108 	emit loadedRoot(fileName, this, root);
109 
110     if (root.tagName() != "module") {
111         FMessageBox::information(NULL, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (4).").arg(fileName));
112         return false;
113     }
114 
115     bool checkForOldSchematics = true;
116 	bool checkForRats = true;
117 	bool checkForTraces = true;
118     bool checkForMysteryParts = true;
119     bool checkForObsoleteSMDOrientation = true;
120 	m_fritzingVersion = root.attribute("fritzingVersion");
121 	if (m_fritzingVersion.length() > 0) {
122 		// with version 0.4.3 ratsnests in fz files are obsolete
123 		VersionThing versionThingRats;
124 		versionThingRats.majorVersion = 0;
125 		versionThingRats.minorVersion = 4;
126 		versionThingRats.minorSubVersion = 2;
127 		versionThingRats.releaseModifier = "";
128 		VersionThing versionThingFz;
129 		Version::toVersionThing(m_fritzingVersion,versionThingFz);
130 		checkForRats = !Version::greaterThan(versionThingRats, versionThingFz);
131 		// with version 0.6.5 traces are copied to all views
132 		versionThingRats.minorVersion = 6;
133 		versionThingRats.minorSubVersion = 4;
134 		checkForTraces = !Version::greaterThan(versionThingRats, versionThingFz);
135         // with version 0.7.6 mystery part spacing implementation changes
136 		versionThingRats.minorVersion = 7;
137 		versionThingRats.minorSubVersion = 5;
138 		checkForMysteryParts = !Version::greaterThan(versionThingRats, versionThingFz);
139         // with version 0.8.0 flipSMD is horizontal
140 	    versionThingRats.minorVersion = 7;
141 	    versionThingRats.minorSubVersion = 13;
142 	    checkForObsoleteSMDOrientation = !Version::greaterThan(versionThingRats, versionThingFz);
143         // with version 0.8.6 we get a new schematic template
144 	    versionThingRats.minorVersion = 8;
145 	    versionThingRats.minorSubVersion = 5;
146 	    checkForOldSchematics = !Version::greaterThan(versionThingRats, versionThingFz);
147 	}
148 
149 	ModelPartSharedRoot * modelPartSharedRoot = this->rootModelPartShared();
150 
151     QDomElement title = root.firstChildElement("title");
152 	if (!title.isNull()) {
153 		if (modelPartSharedRoot) {
154 			modelPartSharedRoot->setTitle(title.text());
155 		}
156 	}
157 
158     // ensures changeBinIcon() is not available
159     // this may be a bug?
160 	QString iconFilename = root.attribute("icon");
161 	if (iconFilename.isEmpty()) {
162 		iconFilename = title.text() + ".png";
163 	}
164 
165 	if (!iconFilename.isEmpty()) {
166 		if (modelPartSharedRoot) {
167 			modelPartSharedRoot->setIcon(iconFilename);
168 		}
169 	}
170 
171 	QString searchTerm = root.attribute("search");
172 	if (!searchTerm.isEmpty() && modelPartSharedRoot != NULL) {
173 		modelPartSharedRoot->setSearchTerm(searchTerm);
174 	}
175 
176     QDomElement views = root.firstChildElement("views");
177 	emit loadedViews(this, views);
178 
179 	QDomElement instances = root.firstChildElement("instances");
180 	if (instances.isNull()) {
181         FMessageBox::information(NULL, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (3).").arg(fileName));
182         return false;
183 	}
184 
185 	// delete any aready-existing model parts
186     for (int i = m_root->children().count() - 1; i >= 0; i--) {
187     	QObject* child = m_root->children()[i];
188     	child->setParent(NULL);
189     	delete child;
190    	}
191 
192 	emit loadingInstances(this, instances);
193 
194 	if (checkForRats) {
195 		QDomElement instance = instances.firstChildElement("instance");
196    		while (!instance.isNull()) {
197 			QDomElement nextInstance = instance.nextSiblingElement("instance");
198 			if (isRatsnest(instance)) {
199 				instances.removeChild(instance);
200 			}
201 
202 			instance = nextInstance;
203 		}
204 	}
205 
206 	if (checkForTraces) {
207 		QDomElement instance = instances.firstChildElement("instance");
208    		while (!instance.isNull()) {
209 			checkTraces(instance);
210 			instance = instance.nextSiblingElement("instance");
211 		}
212 	}
213 
214 	if (checkForMysteryParts) {
215 		QDomElement instance = instances.firstChildElement("instance");
216    		while (!instance.isNull()) {
217 			checkMystery(instance);
218 			instance = instance.nextSiblingElement("instance");
219 		}
220 	}
221 
222     if (checkForObsoleteSMDOrientation) {
223 		QDomElement instance = instances.firstChildElement("instance");
224    		while (!instance.isNull()) {
225 			if (checkObsoleteOrientation(instance)) {
226                 emit obsoleteSMDOrientationSignal();
227                 break;
228             }
229 			instance = instance.nextSiblingElement("instance");
230 		}
231     }
232 
233     m_useOldSchematics = false;
234     if (checkForOldSchematics) {
235         QDomElement instance = instances.firstChildElement("instance");
236    		while (!instance.isNull()) {
237 			if (checkOldSchematics(instance)) {
238                 emit oldSchematicsSignal(fileName, m_useOldSchematics);
239                 break;
240             }
241 			instance = instance.nextSiblingElement("instance");
242 		}
243     }
244 
245 	bool result = loadInstances(domDocument, instances, modelParts, checkViews);
246 	emit loadedInstances(this, instances);
247 	return result;
248 }
249 
fixObsoleteModuleID(QDomDocument & domDocument,QDomElement & instance,QString & moduleIDRef)250 ModelPart * ModelBase::fixObsoleteModuleID(QDomDocument & domDocument, QDomElement & instance, QString & moduleIDRef) {
251 	return PartFactory::fixObsoleteModuleID(domDocument, instance, moduleIDRef, m_referenceModel);
252 }
253 
loadInstances(QDomDocument & domDocument,QDomElement & instances,QList<ModelPart * > & modelParts,bool checkViews)254 bool ModelBase::loadInstances(QDomDocument & domDocument, QDomElement & instances, QList<ModelPart *> & modelParts, bool checkViews)
255 {
256 	QHash<QString, QString> missingModules;
257    	QDomElement instance = instances.firstChildElement("instance");
258    	ModelPart* modelPart = NULL;
259    	while (!instance.isNull()) {
260 		emit loadingInstance(this, instance);
261 
262         if (checkViews) {
263             QDomElement views = instance.firstChildElement("views");
264             QDomElement view = views.firstChildElement();
265             if (views.isNull() || view.isNull()) {
266                 // do not load a part with no views
267                 //QString text;
268                 //QTextStream stream(&text);
269                 //instance.save(stream, 0);
270                 //DebugDialog::debug(text);
271                 instance = instance.nextSiblingElement("instance");
272                 continue;
273             }
274         }
275 
276    		// for now assume all parts are in the palette
277    		QString moduleIDRef = instance.attribute("moduleIdRef");
278 
279         //DebugDialog::debug("loading " + moduleIDRef);
280 		if (moduleIDRef.compare(ModuleIDNames::SpacerModuleIDName) == 0) {
281 			ModelPart * mp = new ModelPart(ModelPart::Space);
282 			mp->setInstanceText(instance.attribute("path"));
283 			mp->setParent(m_root);
284             mp->modelPartShared()->setModuleID(ModuleIDNames::SpacerModuleIDName);
285             mp->modelPartShared()->setPath(instance.attribute("path"));
286 			modelParts.append(mp);
287 			instance = instance.nextSiblingElement("instance");
288 			continue;
289 		}
290 
291         bool generated = false;
292    		modelPart = m_referenceModel->retrieveModelPart(moduleIDRef);
293         if (modelPart == NULL) {
294 			DebugDialog::debug(QString("module id %1 not found in database").arg(moduleIDRef));
295 			modelPart = fixObsoleteModuleID(domDocument, instance, moduleIDRef);
296 			if (modelPart == NULL) {
297                 modelPart = genFZP(moduleIDRef, m_referenceModel);
298 				if (modelPart != NULL) {
299                     instance.setAttribute("moduleIdRef", modelPart->moduleID());
300                     moduleIDRef = modelPart->moduleID();
301                     generated = true;
302 				}
303 				if (modelPart == NULL) {
304 					missingModules.insert(moduleIDRef, instance.attribute("path"));
305    					instance = instance.nextSiblingElement("instance");
306    					continue;
307 				}
308 			}
309    		}
310 
311         if (modelPart->isCore() && m_useOldSchematics) {
312             modelPart = createOldSchematicPart(modelPart, moduleIDRef);
313         }
314 
315         modelPart->setInBin(true);
316    		modelPart = addModelPart(m_root, modelPart);
317    		modelPart->setInstanceDomElement(instance);
318 		modelParts.append(modelPart);
319 
320    		// TODO Mariano: i think this is not the way
321    		QString instanceTitle = instance.firstChildElement("title").text();
322    		if(!instanceTitle.isNull() && !instanceTitle.isEmpty()) {
323    			modelPart->setInstanceTitle(instanceTitle, false);
324    		}
325 
326 		QDomElement localConnectors = instance.firstChildElement("localConnectors");
327 		QDomElement localConnector = localConnectors.firstChildElement("localConnector");
328 		while (!localConnector.isNull()) {
329 			modelPart->setConnectorLocalName(localConnector.attribute("id"), localConnector.attribute("name"));
330 			localConnector = localConnector.nextSiblingElement("localConnector");
331 		}
332 
333    		QString instanceText = instance.firstChildElement("text").text();
334    		if(!instanceText.isNull() && !instanceText.isEmpty()) {
335    			modelPart->setInstanceText(instanceText);
336    		}
337 
338    		bool ok;
339    		long index = instance.attribute("modelIndex").toLong(&ok);
340    		if (ok) {
341 			// set the index so we can find the same model part later, as we continue loading
342 			modelPart->setModelIndex(index);
343   		}
344 
345 		// note: this QDomNamedNodeMap loop is obsolete, but leaving it here so that old sketches don't get broken (jc, 22 Oct 2009)
346 		QDomNamedNodeMap map = instance.attributes();
347 		for (int m = 0; m < map.count(); m++) {
348 			QDomNode node = map.item(m);
349 			QString nodeName = node.nodeName();
350 
351 			if (nodeName.isEmpty()) continue;
352 			if (nodeName.compare("moduleIdRef") == 0) continue;
353 			if (nodeName.compare("modelIndex") == 0) continue;
354 			if (nodeName.compare("originalModelIndex") == 0) continue;
355 			if (nodeName.compare("path") == 0) continue;
356 
357 			modelPart->setLocalProp(nodeName, node.nodeValue());
358 		}
359 
360 		// "property" loop replaces previous QDomNamedNodeMap loop (jc, 22 Oct 2009)
361 		QDomElement prop = instance.firstChildElement("property");
362 		while(!prop.isNull()) {
363 			QString name = prop.attribute("name");
364 			if (!name.isEmpty()) {
365 				QString value = prop.attribute("value");
366 				if (!value.isEmpty()) {
367 					modelPart->setLocalProp(name, value);
368 				}
369 			}
370 
371 			prop = prop.nextSiblingElement("property");
372 		}
373 
374    		instance = instance.nextSiblingElement("instance");
375   	}
376 
377 	if (m_reportMissingModules && missingModules.count() > 0) {
378 		QString unableToFind = QString("<html><body><b>%1</b><br/><table style='border-spacing: 0px 12px;'>")
379 			.arg(tr("Unable to find the following %n part(s):", "", missingModules.count()));
380 		foreach (QString key, missingModules.keys()) {
381 			unableToFind += QString("<tr><td>'%1'</td><td><b>%2</b></td><td>'%3'</td></tr>")
382 				.arg(key).arg(tr("at")).arg(missingModules.value(key, ""));
383 		}
384 		unableToFind += "</table></body></html>";
385 		FMessageBox::warning(NULL, QObject::tr("Fritzing"), unableToFind);
386 	}
387 
388 
389 	return true;
390 }
391 
addModelPart(ModelPart * parent,ModelPart * copyChild)392 ModelPart * ModelBase::addModelPart(ModelPart * parent, ModelPart * copyChild) {
393 
394     //if (copyChild->moduleID() == "df9d072afa2b594ac67b60b4153ff57b_29") {
395     //    DebugDialog::debug("alive in here");
396     //}
397 
398 	ModelPart * modelPart = new ModelPart();
399 	modelPart->copyNew(copyChild);
400 	modelPart->setParent(parent);
401 	modelPart->initConnectors();
402     modelPart->flipSMDAnd();
403 	return modelPart;
404 }
405 
addPart(QString newPartPath,bool addToReference)406 ModelPart * ModelBase::addPart(QString newPartPath, bool addToReference) {
407 	Q_UNUSED(newPartPath);
408 	Q_UNUSED(addToReference);
409 	throw "ModelBase::addPart should not be invoked";
410 	return NULL;
411 }
412 
addPart(QString newPartPath,bool addToReference,bool updateIdAlreadyExists)413 ModelPart * ModelBase::addPart(QString newPartPath, bool addToReference, bool updateIdAlreadyExists)
414 {
415 	Q_UNUSED(updateIdAlreadyExists);
416 	Q_UNUSED(newPartPath);
417 	Q_UNUSED(addToReference);
418 	throw "ModelBase::addPart should not be invoked";
419 	return NULL;
420 }
421 
422 // TODO Mariano: this function should never get called. Make pure virtual
addPart(ModelPart * modelPart,bool update)423 bool ModelBase::addPart(ModelPart * modelPart, bool update) {
424 	Q_UNUSED(modelPart);
425 	Q_UNUSED(update);
426 	throw "ModelBase::addPart should not be invoked";
427 	return false;
428 }
429 
430 
save(const QString & fileName,bool asPart)431 void ModelBase::save(const QString & fileName, bool asPart) {
432 	QFileInfo info(fileName);
433 	QDir dir = info.absoluteDir();
434 
435 	QString temp = dir.absoluteFilePath("temp.xml");
436     QFile file1(temp);
437     if (!file1.open(QFile::WriteOnly | QFile::Text)) {
438         FMessageBox::warning(NULL, QObject::tr("Fritzing"),
439                              QObject::tr("Cannot write file temp:\n%1\n%2\n%3.")
440 							  .arg(temp)
441 							  .arg(fileName)
442                               .arg(file1.errorString())
443 							  );
444         return;
445     }
446 
447     QXmlStreamWriter streamWriter(&file1);
448 	save(fileName, streamWriter, asPart);
449 	file1.close();
450 	QFile original(fileName);
451 	if(original.exists() && !original.remove()) {
452 		file1.remove();
453 		FMessageBox::warning(
454 			NULL,
455 			tr("File save failed!"),
456 			tr("Couldn't overwrite file '%1'.\nReason: %2 (errcode %3)")
457 				.arg(fileName)
458 				.arg(original.errorString())
459 				.arg(original.error())
460 			);
461 		return;
462 	}
463 	file1.rename(fileName);
464 }
465 
save(const QString & fileName,QXmlStreamWriter & streamWriter,bool asPart)466 void ModelBase::save(const QString & fileName, QXmlStreamWriter & streamWriter, bool asPart) {
467     streamWriter.setAutoFormatting(true);
468     if(asPart) {
469     	m_root->saveAsPart(streamWriter, true);
470     } else {
471     	m_root->saveInstances(fileName, streamWriter, true);
472     }
473 }
474 
paste(ModelBase * referenceModel,QByteArray & data,QList<ModelPart * > & modelParts,QHash<QString,QRectF> & boundingRects,bool preserveIndex)475 bool ModelBase::paste(ModelBase * referenceModel, QByteArray & data, QList<ModelPart *> & modelParts, QHash<QString, QRectF> & boundingRects, bool preserveIndex)
476 {
477 	m_referenceModel = referenceModel;
478 
479 	QDomDocument domDocument;
480 	QString errorStr;
481 	int errorLine;
482 	int errorColumn;
483 	bool result = domDocument.setContent(data, &errorStr, &errorLine, &errorColumn);
484 	if (!result) return false;
485 
486 	QDomElement module = domDocument.documentElement();
487 	if (module.isNull()) {
488 		return false;
489 	}
490 
491 	QDomElement boundingRectsElement = module.firstChildElement("boundingRects");
492 	if (!boundingRectsElement.isNull()) {
493 		QDomElement boundingRect = boundingRectsElement.firstChildElement("boundingRect");
494 		while (!boundingRect.isNull()) {
495 			QString name = boundingRect.attribute("name");
496 			QString rect = boundingRect.attribute("rect");
497 			QRectF br;
498 			if (!rect.isEmpty()) {
499 				QStringList s = rect.split(" ");
500 				if (s.count() == 4) {
501 					QRectF r(s[0].toDouble(), s[1].toDouble(), s[2].toDouble(), s[3].toDouble());
502 					br = r;
503 				}
504 			}
505 			boundingRects.insert(name, br);
506 			boundingRect = boundingRect.nextSiblingElement("boundingRect");
507 		}
508 	}
509 
510 	QDomElement instances = module.firstChildElement("instances");
511    	if (instances.isNull()) {
512    		return false;
513 	}
514 
515 	if (!preserveIndex) {
516 		// need to map modelIndexes from copied parts to new modelIndexes
517 		QHash<long, long> oldToNew;
518 		QDomElement instance = instances.firstChildElement("instance");
519 		while (!instance.isNull()) {
520 			long oldModelIndex = instance.attribute("modelIndex").toLong();
521 			oldToNew.insert(oldModelIndex, ModelPart::nextIndex());
522 			instance = instance.nextSiblingElement("instance");
523 		}
524 		renewModelIndexes(instances, "instance", oldToNew);
525 	}
526 
527 	//QFile file("test.xml");
528 	//file.open(QFile::WriteOnly);
529 	//file.write(domDocument.toByteArray());
530 	//file.close();
531 
532 	return loadInstances(domDocument, instances, modelParts, true);
533 }
534 
renewModelIndexes(QDomElement & parentElement,const QString & childName,QHash<long,long> & oldToNew)535 void ModelBase::renewModelIndexes(QDomElement & parentElement, const QString & childName, QHash<long, long> & oldToNew)
536 {
537 	QDomElement instance = parentElement.firstChildElement(childName);
538 	while (!instance.isNull()) {
539 		long oldModelIndex = instance.attribute("modelIndex").toLong();
540 		instance.setAttribute("modelIndex", QString::number(oldToNew.value(oldModelIndex)));
541 		QDomElement views = instance.firstChildElement("views");
542 		if (!views.isNull()) {
543 			QDomElement view = views.firstChildElement();
544 			while (!view.isNull()) {
545                 bool ok;
546                 int superpart = view.attribute("superpart").toLong(&ok);
547                 if (ok) {
548                     view.setAttribute("superpart", QString::number(oldToNew.value(superpart)));
549                 }
550 				QDomElement connectors = view.firstChildElement("connectors");
551 				if (!connectors.isNull()) {
552 					QDomElement connector = connectors.firstChildElement("connector");
553 					while (!connector.isNull()) {
554 						QDomElement connects = connector.firstChildElement("connects");
555 						if (!connects.isNull()) {
556 							QDomElement connect = connects.firstChildElement("connect");
557 							while (!connect.isNull()) {
558 								bool ok;
559 								oldModelIndex = connect.attribute("modelIndex").toLong(&ok);
560 								if (ok) {
561 									long newModelIndex = oldToNew.value(oldModelIndex, -1);
562 									if (newModelIndex != -1) {
563 										connect.setAttribute("modelIndex", QString::number(newModelIndex));
564 									}
565 									else {
566 										//DebugDialog::debug(QString("keep old model index %1").arg(oldModelIndex));
567 									}
568 								}
569 								connect = connect.nextSiblingElement("connect");
570 							}
571 						}
572 						connector = connector.nextSiblingElement("connector");
573 					}
574 				}
575 
576 				view = view.nextSiblingElement();
577 			}
578 		}
579 
580 		instance = instance.nextSiblingElement(childName);
581 	}
582 }
583 
setReportMissingModules(bool b)584 void ModelBase::setReportMissingModules(bool b) {
585 	m_reportMissingModules = b;
586 }
587 
genFZP(const QString & moduleID,ModelBase * referenceModel)588 ModelPart * ModelBase::genFZP(const QString & moduleID, ModelBase * referenceModel) {
589 	QString path = PartFactory::getFzpFilename(moduleID);
590 	if (path.isEmpty()) return NULL;
591 
592 	ModelPart* mp = referenceModel->addPart(path, true, true);
593 	if (mp) mp->setCore(true);
594 	return mp;
595 }
596 
rootModelPartShared()597 ModelPartSharedRoot * ModelBase::rootModelPartShared() {
598 	if (m_root == NULL) return NULL;
599 
600 	return m_root->modelPartSharedRoot();
601 }
602 
isRatsnest(QDomElement & instance)603 bool ModelBase::isRatsnest(QDomElement & instance) {
604 	QString moduleIDRef = instance.attribute("moduleIdRef");
605 	if (moduleIDRef.compare(ModuleIDNames::WireModuleIDName) != 0) return false;
606 
607 	QDomElement views = instance.firstChildElement("views");
608 	if (views.isNull()) return false;
609 
610 	QDomElement view = views.firstChildElement();
611 	while (!view.isNull()) {
612 		QDomElement geometry = view.firstChildElement("geometry");
613 		if (!geometry.isNull()) {
614 			int flags = geometry.attribute("wireFlags").toInt();
615 			if (flags & ViewGeometry::RatsnestFlag) {
616 				return true;
617 			}
618 			if (flags & ViewGeometry::ObsoleteJumperFlag) {
619 				return true;
620 			}
621 		}
622 
623 		view = view.nextSiblingElement();
624 	}
625 
626 	return false;
627 }
628 
629 
checkOldSchematics(QDomElement & instance)630 bool ModelBase::checkOldSchematics(QDomElement & instance)
631 {
632 	if (instance.attribute("moduleIdRef").compare(ModuleIDNames::WireModuleIDName) != 0) {
633         return false;
634     }
635 
636     QDomElement views = instance.firstChildElement("views");
637     QDomElement schematicView = views.firstChildElement("schematicView");
638     QDomElement geometry = schematicView.firstChildElement("geometry");
639     if (geometry.isNull()) return false;
640 
641     int flags = geometry.attribute("wireFlags", "0").toInt();
642     return (flags & ViewGeometry::SchematicTraceFlag) != 0;
643 }
644 
645 
checkObsoleteOrientation(QDomElement & instance)646 bool ModelBase::checkObsoleteOrientation(QDomElement & instance)
647 {
648 	QString flippedSMD = instance.attribute("flippedSMD", "");
649 	if (flippedSMD != "true") return false;
650 
651 	QDomElement views = instance.firstChildElement("views");
652 	QDomElement pcbView = views.firstChildElement("pcbView");
653     return (pcbView.attribute("layer", "") == "copper0");
654 }
655 
checkTraces(QDomElement & instance)656 void ModelBase::checkTraces(QDomElement & instance) {
657 	QString moduleIDRef = instance.attribute("moduleIdRef");
658 	if (moduleIDRef.compare(ModuleIDNames::WireModuleIDName) != 0) return;
659 
660 	QDomElement views = instance.firstChildElement("views");
661 	if (views.isNull()) return;
662 
663 	QDomElement bbView = views.firstChildElement("breadboardView");
664 	QDomElement schView = views.firstChildElement("schematicView");
665 	QDomElement pcbView = views.firstChildElement("pcbView");
666 
667 	if (!bbView.isNull() && !schView.isNull() && !pcbView.isNull()) {
668 		// if it's a breadboard wire; just make sure flag is correct
669 
670 		QList<QDomElement> elements;
671 		elements << bbView << schView << pcbView;
672 		foreach (QDomElement element, elements) {
673 			QDomElement geometry = element.firstChildElement("geometry");
674 			if (!geometry.isNull()) {
675 				int flags = geometry.attribute("wireFlags").toInt();
676 				if (flags & ViewGeometry::PCBTraceFlag) return;				// not a breadboard wire, bail out
677 				if (flags & ViewGeometry::SchematicTraceFlag) return;		// not a breadboard wire, bail out
678 
679 				if ((flags & ViewGeometry::NormalFlag) == 0) {
680 					flags |= ViewGeometry::NormalFlag;
681 					geometry.setAttribute("wireFlags", QString::number(flags));
682 				}
683 			}
684 		}
685 
686 
687 		return;
688 	}
689 
690 	if (!bbView.isNull()) {
691 		QDomElement geometry = bbView.firstChildElement("geometry");
692 		if (!geometry.isNull()) {
693 			int flags = geometry.attribute("wireFlags").toInt();
694 			if ((flags & ViewGeometry::NormalFlag) == 0) {
695 				flags |= ViewGeometry::NormalFlag;
696 				geometry.setAttribute("wireFlags", QString::number(flags));
697 			}
698 		}
699 		schView = bbView.cloneNode(true).toElement();
700 		pcbView = bbView.cloneNode(true).toElement();
701 		schView.setTagName("schematicView");
702 		pcbView.setTagName("pcbView");
703 		views.appendChild(pcbView);
704 		views.appendChild(schView);
705 		return;
706 	}
707 
708 	if (!schView.isNull()) {
709 		QDomElement geometry = schView.firstChildElement("geometry");
710 		if (!geometry.isNull()) {
711 			int flags = geometry.attribute("wireFlags").toInt();
712 			if (flags & ViewGeometry::PCBTraceFlag) {
713 				flags ^= ViewGeometry::PCBTraceFlag;
714 				flags |= ViewGeometry::SchematicTraceFlag;
715 				geometry.setAttribute("wireFlags", QString::number(flags));
716 			}
717 		}
718 		pcbView = schView.cloneNode(true).toElement();
719 		bbView = schView.cloneNode(true).toElement();
720 		pcbView.setTagName("pcbView");
721 		bbView.setTagName("breadboardView");
722 		views.appendChild(bbView);
723 		views.appendChild(pcbView);
724 		return;
725 	}
726 
727 	if (!pcbView.isNull()) {
728 		schView = pcbView.cloneNode(true).toElement();
729 		bbView = pcbView.cloneNode(true).toElement();
730 		schView.setTagName("schematicView");
731 		bbView.setTagName("breadboardView");
732 		views.appendChild(bbView);
733 		views.appendChild(schView);
734 		return;
735 	}
736 
737 	QDomElement iconView = views.firstChildElement("iconView");
738 	if (!iconView.isNull()) return;
739 
740 	QString string;
741 	QTextStream stream(&string);
742 	instance.save(stream, 0);
743 	stream.flush();
744 	DebugDialog::debug(QString("no wire view elements in fz file %1").arg(string));
745 }
746 
fritzingVersion()747 const QString & ModelBase::fritzingVersion() {
748 	return m_fritzingVersion;
749 }
750 
setReferenceModel(ModelBase * modelBase)751 void ModelBase::setReferenceModel(ModelBase * modelBase) {
752     m_referenceModel = modelBase;
753 }
754 
checkMystery(QDomElement & instance)755 void ModelBase::checkMystery(QDomElement & instance)
756 {
757 	QString moduleIDRef = instance.attribute("moduleIdRef");
758     bool mystery = false;
759     bool sip = false;
760     bool dip = false;
761 	if (moduleIDRef.contains("mystery", Qt::CaseInsensitive)) mystery = true;
762     else if (moduleIDRef.contains("sip", Qt::CaseInsensitive)) sip = true;
763     else if (moduleIDRef.contains("dip", Qt::CaseInsensitive)) dip = true;
764     else return;
765 
766     QString spacing;
767     int pins = TextUtils::getPinsAndSpacing(moduleIDRef, spacing);
768 
769     QDomElement prop = instance.firstChildElement("property");
770     while (!prop.isNull()) {
771         if (prop.attribute("name", "").compare("spacing") == 0) {
772             QString trueSpacing = prop.attribute("value", "");
773             if (trueSpacing.isEmpty()) trueSpacing = "300mil";
774 
775             if (moduleIDRef.contains(spacing)) {
776                 moduleIDRef.replace(spacing, trueSpacing);
777                 instance.setAttribute("moduleIdRef", moduleIDRef);
778                 return;
779             }
780 
781             // if we're here, it's a single sided mystery part.
782             moduleIDRef = QString("mystery_part_sip_%1_100mil").arg(pins);
783             instance.setAttribute("moduleIdRef", moduleIDRef);
784             return;
785         }
786         prop = prop.nextSiblingElement("property");
787     }
788 }
789 
onCoreList(const QString & moduleID)790 bool ModelBase::onCoreList(const QString & moduleID) {
791     // CoreList contains db entries that are (presumably) overridden by an fzp in the parts folder
792     return CoreList.contains(moduleID);
793 
794 }
795 
createOldSchematicPart(ModelPart * modelPart,QString & moduleIDRef)796 ModelPart * ModelBase::createOldSchematicPart(ModelPart * modelPart, QString & moduleIDRef) {
797     QString schematicFilename = modelPart->imageFileName(ViewLayer::SchematicView, ViewLayer::Schematic);
798     if (!schematicFilename.startsWith("schematic")) {
799         schematicFilename = modelPart->imageFileName(ViewLayer::SchematicView, ViewLayer::SchematicFrame);
800         if (!schematicFilename.startsWith("schematic")) {
801             return modelPart;
802         }
803     }
804 
805     DebugDialog::debug("schematic " + schematicFilename);
806     QString oldModuleIDRef = PartFactory::OldSchematicPrefix + moduleIDRef;
807     ModelPart * oldModelPart = m_referenceModel->retrieveModelPart(oldModuleIDRef);         // cached after the first time it's created
808     if (oldModelPart) {
809         moduleIDRef = oldModuleIDRef;
810         return oldModelPart;
811     }
812 
813     int ix = schematicFilename.indexOf("/");
814     schematicFilename.insert(ix + 1, PartFactory::OldSchematicPrefix);
815     QString oldSvgPath = FolderUtils::getApplicationSubFolderPath("parts") + "/svg/obsolete/"+ schematicFilename;
816     oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
817     if (oldModelPart) {
818         moduleIDRef = oldModuleIDRef;
819         return oldModelPart;
820     }
821 
822     oldSvgPath = ":resources/parts/svg/obsolete/"+ schematicFilename;
823     oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
824     if (oldModelPart) {
825         moduleIDRef = oldModuleIDRef;
826         return oldModelPart;
827     }
828 
829 
830     // see whether it's a generated part
831     oldSvgPath = PartFactory::getSvgFilename(schematicFilename);
832     if (!oldSvgPath.isEmpty()) {
833         oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
834         if (oldModelPart) {
835             moduleIDRef = oldModuleIDRef;
836             return oldModelPart;
837         }
838     }
839 
840     return modelPart;
841 }
842 
createOldSchematicPartAux(ModelPart * modelPart,const QString & oldModuleIDRef,const QString & oldSchematicFileName,const QString & oldSvgPath)843 ModelPart * ModelBase::createOldSchematicPartAux(ModelPart * modelPart, const QString & oldModuleIDRef, const QString & oldSchematicFileName, const QString & oldSvgPath)
844 {
845     if (!QFile::exists(oldSvgPath)) return NULL;
846 
847     // create oldModelPart, set up the new image file name, add it to refmodel
848     QFile newFzp(modelPart->path());
849     QDomDocument oldDoc;
850     bool ok = oldDoc.setContent(&newFzp);
851     if (!ok) {
852         // this shouldn't happen
853         return NULL;
854     }
855 
856     QDomElement root = oldDoc.documentElement();
857     root.setAttribute("moduleId", oldModuleIDRef);
858     QDomElement views = root.firstChildElement("views");
859     QDomElement schematicView = views.firstChildElement("schematicView");
860     QDomElement layers = schematicView.firstChildElement("layers");
861     if (layers.isNull()) {
862         // this shouldn't happen
863         return NULL;
864     }
865 
866     layers.setAttribute("image", oldSchematicFileName);
867 
868     QString oldFzpPath = PartFactory::fzpPath() + oldModuleIDRef + ".fzp";
869     if (!TextUtils::writeUtf8(oldFzpPath, oldDoc.toString())) {
870         // this shouldn't happen
871         return NULL;
872     }
873 
874     ModelPart * oldModelPart = m_referenceModel->addPart(oldFzpPath, true, true);
875     oldModelPart->setCore(modelPart->isCore());
876     oldModelPart->setContrib(modelPart->isContrib());
877     return oldModelPart;
878 }
879 
880