1 /*******************************************************************
2 skw
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2015 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6984 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 23:44:56 +0200 (Mo, 22. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include <QtCore>
28 
29 #include <QSvgGenerator>
30 #include <QColor>
31 #include <QImageWriter>
32 #include <QPrinter>
33 #include <QSettings>
34 #include <QDesktopServices>
35 #include <QPrintDialog>
36 #include <QClipboard>
37 #include <QApplication>
38 
39 #include "mainwindow.h"
40 #include "../debugdialog.h"
41 #include "../waitpushundostack.h"
42 #include "../help/aboutbox.h"
43 #include "../autoroute/autorouteprogressdialog.h"
44 #include "../items/virtualwire.h"
45 #include "../items/jumperitem.h"
46 #include "../items/via.h"
47 #include "../fsvgrenderer.h"
48 #include "../items/note.h"
49 #include "../items/partfactory.h"
50 #include "../eagle/fritzing2eagle.h"
51 #include "../sketch/breadboardsketchwidget.h"
52 #include "../sketch/schematicsketchwidget.h"
53 #include "../sketch/pcbsketchwidget.h"
54 #include "../partsbinpalette/binmanager/binmanager.h"
55 #include "../utils/expandinglabel.h"
56 #include "../infoview/htmlinfoview.h"
57 #include "../utils/bendpointaction.h"
58 #include "../sketch/fgraphicsscene.h"
59 #include "../utils/fileprogressdialog.h"
60 #include "../svg/svgfilesplitter.h"
61 #include "../version/version.h"
62 #include "../help/tipsandtricks.h"
63 #include "../dialogs/setcolordialog.h"
64 #include "../utils/folderutils.h"
65 #include "../utils/graphicsutils.h"
66 #include "../utils/textutils.h"
67 #include "../connectors/ercdata.h"
68 #include "../items/moduleidnames.h"
69 #include "../utils/zoomslider.h"
70 #include "../dock/layerpalette.h"
71 #include "../program/programwindow.h"
72 #include "../utils/autoclosemessagebox.h"
73 #include "../svg/gerbergenerator.h"
74 #include "../processeventblocker.h"
75 
76 static QString eagleActionType = ".eagle";
77 static QString gerberActionType = ".gerber";
78 static QString jpgActionType = ".jpg";
79 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
80 static QString psActionType = ".ps";
81 #endif
82 static QString pdfActionType = ".pdf";
83 static QString pngActionType = ".png";
84 static QString svgActionType = ".svg";
85 static QString bomActionType = ".html";
86 static QString netlistActionType = ".xml";
87 static QString spiceNetlistActionType = ".cir";
88 
89 static QHash<QString, QPrinter::OutputFormat> filePrintFormats;
90 static QHash<QString, QImage::Format> fileExportFormats;
91 static QHash<QString, QString> fileExtFormats;
92 
93 static QRegExp AaCc("[aAcC]");
94 static QRegExp LabelNumber("([^\\d]+)(.*)");
95 
96 static const double InchesPerMeter = 39.3700787;
97 
98 ////////////////////////////////////////////////////////
99 
sortPartList(ItemBase * b1,ItemBase * b2)100 bool sortPartList(ItemBase * b1, ItemBase * b2) {
101     bool result = b1->instanceTitle().toLower() < b2->instanceTitle().toLower();
102 
103     int ix1 = LabelNumber.indexIn(b1->instanceTitle());
104     if (ix1 < 0) return result;
105 
106     QString label1 = LabelNumber.cap(1);
107     QString number1 = LabelNumber.cap(2);
108 
109     int ix2 = LabelNumber.indexIn(b2->instanceTitle());
110     if (ix2 < 0) return result;
111 
112     QString label2 = LabelNumber.cap(1);
113     QString number2 = LabelNumber.cap(2);
114     if (label2.compare(label1, Qt::CaseInsensitive) != 0) return result;
115 
116     bool ok;
117     double d1 = number1.toDouble(&ok);
118     if (!ok) return result;
119 
120     double d2 = number2.toDouble(&ok);
121     if (!ok) return result;
122 
123     return d1 < d2;
124 }
125 
126 /////////////////////////////////////////////////////////
127 
initNames()128 void MainWindow::initNames()
129 {
130 	OtherKnownExtensions << jpgActionType << pdfActionType << pngActionType << svgActionType << bomActionType << netlistActionType << spiceNetlistActionType;
131 
132 	filePrintFormats[pdfActionType] = QPrinter::PdfFormat;
133 
134 	fileExportFormats[pngActionType] = QImage::Format_ARGB32;
135 	fileExportFormats[jpgActionType] = QImage::Format_RGB32;
136 
137 	fileExtFormats[pdfActionType] = tr("PDF (*.pdf)");
138 	fileExtFormats[pngActionType] = tr("PNG Image (*.png)");
139 	fileExtFormats[jpgActionType] = tr("JPEG Image (*.jpg)");
140 	fileExtFormats[svgActionType] = tr("SVG Image (*.svg)");
141 	fileExtFormats[bomActionType] = tr("BoM Text File (*.html)");
142 
143 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
144     OtherKnownExtensions << psActionType;
145 	filePrintFormats[psActionType] = QPrinter::PostScriptFormat;
146 	fileExtFormats[psActionType] = tr("PostScript (*.ps)");
147 #endif
148 
149 	QSettings settings;
150 	AutosaveEnabled = settings.value("autosaveEnabled", QString("%1").arg(AutosaveEnabled)).toBool();
151 	AutosaveTimeoutMinutes = settings.value("autosavePeriod", QString("%1").arg(AutosaveTimeoutMinutes)).toInt();
152 }
153 
print()154 void MainWindow::print() {
155     if (m_currentWidget->contentView() == m_programView) {
156         m_programView->print();
157     }
158 
159     if (m_currentGraphicsView == NULL) return;
160 
161 	#ifndef QT_NO_PRINTER
162 		QPrinter printer(QPrinter::HighResolution);
163 
164 		QPrintDialog *printDialog = new QPrintDialog(&printer, this);
165 		if (printDialog->exec() == QDialog::Accepted) {
166 			m_statusBar->showMessage(tr("Printing..."));
167 			printAux(printer, true, true);
168 			m_statusBar->showMessage(tr("Ready"), 2000);
169 		} else {
170 			return;
171 		}
172 	#endif
173 }
174 
exportEtchable()175 void MainWindow::exportEtchable() {
176 	if (sender() == NULL) return;
177 
178     bool wantSvg = sender()->property("svg").toBool();
179 	exportEtchable(!wantSvg, wantSvg);
180 }
181 
182 
exportEtchable(bool wantPDF,bool wantSVG)183 void MainWindow::exportEtchable(bool wantPDF, bool wantSVG)
184 {
185     int boardCount;
186     ItemBase * board = m_pcbGraphicsView->findSelectedBoard(boardCount);
187     if (boardCount == 0) {
188         QMessageBox::critical(this, tr("Fritzing"),
189                    tr("Your sketch does not have a board yet! Please add a PCB in order to export etchable."));
190         return;
191     }
192     if (board == NULL) {
193         QMessageBox::critical(this, tr("Fritzing"),
194                    tr("Etchable export can only handle one board at a time--please select the board you want to export."));
195         return;
196     }
197 
198 	RoutingStatus routingStatus;
199 	m_pcbGraphicsView->updateRoutingStatus(NULL, routingStatus, true);
200 	if (routingStatus.m_connectorsLeftToRoute > 0) {
201 		QMessageBox msgBox(this);
202 		msgBox.setWindowModality(Qt::WindowModal);
203 		msgBox.setText(tr("All traces have not yet been routed."));
204 		msgBox.setInformativeText(tr("Do you want to proceed anyway?"));
205 		msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
206 		msgBox.button(QMessageBox::Yes)->setText(tr("Proceed"));
207 		msgBox.button(QMessageBox::No)->setText(tr("Cancel"));
208 		msgBox.setDefaultButton(QMessageBox::Yes);
209 		int ret = msgBox.exec();
210 		if (ret != QMessageBox::Yes) return;
211 	}
212 
213 	QString path = defaultSaveFolder();
214 	QString extFmt = (wantPDF) ? fileExtFormats.value(pdfActionType) : fileExtFormats.value(svgActionType);
215 	QString fileExt = extFmt;
216 
217 	QString suffix = (wantPDF) ? pdfActionType : svgActionType;
218     QString prefix = "";
219     if (boardCount > 1) {
220         prefix = QString("%1_%2_").arg(board->instanceTitle()).arg(board->id());
221     }
222 
223 	QString exportDir = QFileDialog::getExistingDirectory(this, tr("Choose a folder for exporting"),
224 												defaultSaveFolder(),
225 												QFileDialog::ShowDirsOnly
226 												| QFileDialog::DontResolveSymlinks);
227 	if (exportDir.isEmpty()) return;
228 
229 	FolderUtils::setOpenSaveFolder(exportDir);
230 	FileProgressDialog * fileProgressDialog = exportProgress();
231 
232     QRectF r = board->sceneBoundingRect();
233     QSizeF boardImageSize(r.width(), r.height());
234 
235 	QStringList fileNames;
236 
237 	fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_copper_bottom%1", suffix));
238 	fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_mask_bottom%1", suffix));
239 	fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_paste_mask_bottom%1", suffix));
240 	if (m_pcbGraphicsView->boardLayers() > 1) {
241 		fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_copper_top%1", suffix));
242 		fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_mask_top%1", suffix));
243 		fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_paste_mask_top%1", suffix));
244 	}
245 	fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_silk_top%1", suffix));
246 	fileNames.append(exportDir + "/" + constructFileName(prefix + "etch_silk_bottom%1", suffix));
247 
248 	QString maskTop, maskBottom;
249 	QList<ItemBase *> copperLogoItems, holes;
250 	for (int ix = 0; ix < fileNames.count(); ix++) {
251 		bool doMask = false;
252 		bool doSilk = false;
253         bool doPaste = false;
254 		QString fileName = fileNames[ix];
255 		LayerList viewLayerIDs;
256 		if (fileName.contains("copper_bottom")) {
257 			viewLayerIDs << ViewLayer::GroundPlane0 << ViewLayer::Copper0 << ViewLayer::Copper0Trace;
258 		}
259 		else if (fileName.contains("mask_bottom")) {
260 			doMask = true;
261 			viewLayerIDs << ViewLayer::Copper0;
262             doPaste = fileName.contains("paste");
263 		}
264 		else if (fileName.contains("copper_top")) {
265 			viewLayerIDs << ViewLayer::GroundPlane1 << ViewLayer::Copper1 << ViewLayer::Copper1Trace;
266 		}
267 		else if (fileName.contains("mask_top")) {
268 			viewLayerIDs << ViewLayer::Copper1;
269 			doMask = true;
270             doPaste = fileName.contains("paste");
271 		}
272 		else if (fileName.contains("silk_top")) {
273 			viewLayerIDs << ViewLayer::Silkscreen1 << ViewLayer::Silkscreen1Label;
274 			doSilk = true;
275 		}
276 		else if (fileName.contains("silk_bottom")) {
277 			viewLayerIDs << ViewLayer::Silkscreen0 << ViewLayer::Silkscreen0Label;
278 			doSilk = true;
279 		}
280 
281 		if (doMask) {
282 			m_pcbGraphicsView->hideCopperLogoItems(copperLogoItems);
283 		}
284 		if (doPaste) {
285 			m_pcbGraphicsView->hideHoles(holes);
286 		}
287 
288 		if (wantSVG) {
289 			RenderThing renderThing;
290             renderThing.printerScale = GraphicsUtils::SVGDPI;
291             renderThing.blackOnly = true;
292             renderThing.dpi = GraphicsUtils::IllustratorDPI;
293             renderThing.hideTerminalPoints = true;
294             renderThing.selectedItems = renderThing.renderBlocker = false;
295 			QString svg = m_pcbGraphicsView->renderToSVG(renderThing, board, viewLayerIDs);
296 			massageOutput(svg, doMask, doSilk, doPaste, maskTop, maskBottom, fileName, board, GraphicsUtils::IllustratorDPI, viewLayerIDs);
297 			QString merged = mergeBoardSvg(svg, board, GraphicsUtils::IllustratorDPI, false, viewLayerIDs);
298             TextUtils::writeUtf8(fileName.arg(""), merged);
299 			merged = mergeBoardSvg(svg, board, GraphicsUtils::IllustratorDPI, true, viewLayerIDs);
300             TextUtils::writeUtf8(fileName.arg("_mirror"), merged);
301 		}
302 		else {
303             QString svg;
304             QList<bool> flips;
305             flips << false << true;
306             foreach (bool flip, flips) {
307                 QString mirror = flip ? "_mirror" : "";
308 			    QPrinter printer(QPrinter::HighResolution);
309 			    printer.setOutputFormat(filePrintFormats[fileExt]);
310 			    printer.setOutputFileName(fileName.arg(mirror));
311 			    int res = printer.resolution();
312 
313                 if (svg.isEmpty()) {
314 			        RenderThing renderThing;
315                     renderThing.printerScale = GraphicsUtils::SVGDPI;
316                     renderThing.blackOnly = true;
317                     renderThing.dpi = res;
318                     renderThing.hideTerminalPoints = true;
319                     renderThing.selectedItems = renderThing.renderBlocker = false;
320 			        svg = m_pcbGraphicsView->renderToSVG(renderThing, board, viewLayerIDs);
321 			        massageOutput(svg, doMask, doSilk, doPaste, maskTop, maskBottom, fileName, board, res, viewLayerIDs);
322                 }
323 
324                 QString merged = mergeBoardSvg(svg, board, res, flip, viewLayerIDs);
325 
326 			    // now convert to pdf
327 			    QSvgRenderer svgRenderer;
328 			    svgRenderer.load(merged.toLatin1());
329 			    double trueWidth = boardImageSize.width() / GraphicsUtils::SVGDPI;
330 			    double trueHeight = boardImageSize.height() / GraphicsUtils::SVGDPI;
331 			    QRectF target(0, 0, trueWidth * res, trueHeight * res);
332 
333 			    QSizeF psize((target.width() + printer.paperRect().width() - printer.width()) / res,
334 						     (target.height() + printer.paperRect().height() - printer.height()) / res);
335 			    printer.setPaperSize(psize, QPrinter::Inch);
336 
337 			    QPainter painter;
338 			    if (painter.begin(&printer))
339 			    {
340 				    svgRenderer.render(&painter, target);
341 			    }
342 
343 			    painter.end();
344             }
345 		}
346 		if (doMask) {
347 			m_pcbGraphicsView->restoreCopperLogoItems(copperLogoItems);
348 		}
349 		if (doPaste) {
350 			m_pcbGraphicsView->restoreCopperLogoItems(holes);
351 		}
352 
353 	}
354 
355 	m_statusBar->showMessage(tr("Sketch exported"), 2000);
356 	delete fileProgressDialog;
357 
358 /*
359 
360 	int width = m_pcbGraphicsView->width();
361 	if (m_pcbGraphicsView->verticalScrollBar()->isVisible()) {
362 		width -= m_pcbGraphicsView->verticalScrollBar()->width();
363 	}
364 	int height = m_pcbGraphicsView->height();
365 	if (m_pcbGraphicsView->horizontalScrollBar()->isVisible()) {
366 		height -= m_pcbGraphicsView->horizontalScrollBar()->height();
367 	}
368 
369 	double trueWidth = width / m_printerScale;
370 	double trueHeight = height / m_printerScale;
371 
372 	// set everything to a 1200 dpi resolution
373 	QSize imgSize(trueWidth * 1200, trueHeight * 1200);
374 	QImage image(imgSize, QImage::Format_RGB32);
375 	image.setDotsPerMeterX(1200 * GraphicsUtils::InchesPerMeter);
376 	image.setDotsPerMeterY(1200 * GraphicsUtils::InchesPerMeter);
377 	QPainter painter;
378 
379 	QColor color;
380 	color = m_pcbGraphicsView->background();
381 	m_pcbGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));
382 
383 	m_pcbGraphicsView->scene()->clearSelection();
384 	m_pcbGraphicsView->saveLayerVisibility();
385 	m_pcbGraphicsView->setAllLayersVisible(false);
386 	m_pcbGraphicsView->setLayerVisible(ViewLayer::Copper0, true);
387 	m_pcbGraphicsView->hideConnectors(true);
388 
389 	painter.begin(&image);
390 	m_pcbGraphicsView->render(&painter);
391 	painter.end();
392 
393 
394 	QSvgGenerator svgGenerator;
395 	svgGenerator.setFileName("c:/fritzing2/testsvggenerator.svg");
396     svgGenerator.setSize(QSize(width * 8, height * 8));
397 	QPainter svgPainter(&svgGenerator);
398 	m_pcbGraphicsView->render(&svgPainter);
399 	svgPainter.end();
400 
401 
402 	m_pcbGraphicsView->hideConnectors(false);
403 	m_pcbGraphicsView->setBackground(color);
404 	m_pcbGraphicsView->restoreLayerVisibility();
405 	// TODO: restore the selection
406 
407 	QRgb black = 0;
408 	for (int x = 0; x < imgSize.width(); x++) {
409 		for (int y = 0; y < imgSize.height(); y++) {
410 			QRgb p = image.pixel(x, y);
411 			if (p != 0xffffffff) {
412 				image.setPixel(x, y, black);
413 			}
414 		}
415 	}
416 
417 	bool result = image.save(fileName);
418 	if (!result) {
419 		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save %1").arg(fileName) );
420 	}
421 
422 */
423 
424 }
425 
mergeBoardSvg(QString & svg,ItemBase * board,int res,bool flip,LayerList & viewLayerIDs)426 QString MainWindow::mergeBoardSvg(QString & svg, ItemBase * board, int res, bool flip, LayerList & viewLayerIDs) {
427 	QString boardSvg = getBoardSvg(board, res, viewLayerIDs);
428 
429     LayerList outlineLayerIDs = ViewLayer::outlineLayers();
430 	RenderThing renderThing;
431     renderThing.printerScale = GraphicsUtils::SVGDPI;
432     renderThing.blackOnly = true;
433     renderThing.dpi = res;
434     renderThing.hideTerminalPoints = true;
435     renderThing.selectedItems = renderThing.renderBlocker = false;
436 	QString outlineSvg = m_pcbGraphicsView->renderToSVG(renderThing, board, outlineLayerIDs);
437 	outlineSvg = GerberGenerator::cleanOutline(outlineSvg);
438     outlineSvg = TextUtils::slamStrokeAndFill(outlineSvg, "black", "0.5", "none");
439 
440     if (!boardSvg.isEmpty() && !outlineSvg.isEmpty()) {
441         boardSvg = TextUtils::mergeSvg(boardSvg, outlineSvg, "", false);
442     }
443     else if (boardSvg.isEmpty()) {
444         boardSvg = outlineSvg;
445     }
446 
447 	return TextUtils::convertExtendedChars(TextUtils::mergeSvg(boardSvg, svg, "", flip));
448 }
449 
getBoardSvg(ItemBase * board,int res,LayerList & viewLayerIDs)450 QString MainWindow::getBoardSvg(ItemBase * board, int res,  LayerList & viewLayerIDs) {
451 	if (board == NULL) return ___emptyString___;
452 
453     board = board->layerKinChief();
454     QList<ItemBase *> boardLayers;
455     boardLayers << board;
456     foreach (ItemBase * lk, board->layerKin()) {
457         boardLayers << lk;
458     }
459 
460     bool gotOne = false;
461     foreach (ItemBase * boardLayer, boardLayers) {
462         if (viewLayerIDs.contains(boardLayer->viewLayerID())) {
463             gotOne = true;
464             break;
465         }
466     }
467 
468     if (!gotOne) return "";
469 
470 	m_pcbGraphicsView->setIgnoreSelectionChangeEvents(true);
471 
472 	QList<QGraphicsItem *> items = m_pcbGraphicsView->scene()->selectedItems();
473 	foreach (QGraphicsItem * item, items) {
474 		item->setSelected(false);
475 	}
476 	board->setSelected(true);
477 
478 	RenderThing renderThing;
479     renderThing.printerScale = GraphicsUtils::SVGDPI;
480     renderThing.blackOnly = true;
481     renderThing.dpi = res;
482     renderThing.selectedItems = renderThing.hideTerminalPoints = true;
483     renderThing.renderBlocker = false;
484 	QString svg = m_pcbGraphicsView->renderToSVG(renderThing, board, viewLayerIDs);
485 	board->setSelected(false);
486 	foreach (QGraphicsItem * item, items) {
487 		item->setSelected(true);
488 	}
489 
490 	m_pcbGraphicsView->setIgnoreSelectionChangeEvents(false);
491 
492 	return svg;
493 }
494 
495 
doExport()496 void MainWindow::doExport() {
497 	QAction * action = qobject_cast<QAction *>(sender());
498 	if (action == NULL) return;
499 
500 	QString actionType = action->data().toString();
501 	QString path = defaultSaveFolder();
502 
503 	if (actionType.compare(eagleActionType) == 0) {
504 		exportToEagle();
505 		return;
506 	}
507 
508 	if (actionType.compare(gerberActionType) == 0) {
509 		exportToGerber();
510 		return;
511 	}
512 
513 	if (actionType.compare(bomActionType) == 0) {
514 		exportBOM();
515 		return;
516 	}
517 
518 	if (actionType.compare(netlistActionType) == 0) {
519 		exportNetlist();
520 		return;
521 	}
522 
523 	if (actionType.compare(spiceNetlistActionType) == 0) {
524 		exportSpiceNetlist();
525 		return;
526 	}
527 
528 	if (actionType.compare(svgActionType) == 0) {
529 		exportSvg(GraphicsUtils::IllustratorDPI, false, false);
530 		return;
531 	}
532 
533 	#ifndef QT_NO_PRINTER
534 		QString fileExt;
535 		QString extFmt = fileExtFormats.value(actionType);
536 		DebugDialog::debug(QString("file export string %1").arg(extFmt));
537 		QString fileName = FolderUtils::getSaveFileName(this,
538 			tr("Export..."),
539 			path+"/"+constructFileName("", actionType),
540 			extFmt,
541 			&fileExt
542 		);
543 
544 		if (fileName.isEmpty()) {
545 			return; //Cancel pressed
546 		} else {
547 			FileProgressDialog * fileProgressDialog = exportProgress();
548 			DebugDialog::debug(fileExt+" selected to export");
549 			if(!alreadyHasExtension(fileName, actionType)) {
550 				fileName += actionType;
551 			}
552 
553 			if(filePrintFormats.contains(actionType)) { // PDF or PS
554 				QPrinter printer(QPrinter::HighResolution);
555 				printer.setOutputFormat(filePrintFormats[actionType]);
556 				printer.setOutputFileName(fileName);
557 				m_statusBar->showMessage(tr("Exporting..."));
558 				printAux(printer, true, false);
559 				m_statusBar->showMessage(tr("Sketch exported"), 2000);
560 			} else { // PNG...
561 				DebugDialog::debug(QString("format: %1 %2").arg(fileExt).arg(fileExportFormats[actionType]));
562                 int quality = (actionType == pngActionType ? 1 : 100);
563 				exportAux(fileName,fileExportFormats[actionType], quality, true);
564 			}
565 			delete fileProgressDialog;
566 
567 		}
568 	#endif
569 }
570 
exportAux(QString fileName,QImage::Format format,int quality,bool removeBackground)571 void MainWindow::exportAux(QString fileName, QImage::Format format, int quality, bool removeBackground)
572 {
573     if (m_currentGraphicsView == NULL) return;
574 
575     double resMultiplier = 3;
576 
577     QRectF itemsBoundingRect;
578 	foreach(QGraphicsItem *item,  m_currentGraphicsView->scene()->items()) {
579 		if (!item->isVisible()) continue;
580 
581         itemsBoundingRect |= item->sceneBoundingRect();
582 	}
583 
584 	QRectF source = itemsBoundingRect;  // m_currentGraphicsView->scene()->itemsBoundingRect();
585 	QGraphicsItem * watermark = m_currentGraphicsView->addWatermark(":resources/images/watermark_fritzing_outline.svg");
586 	if (watermark) {
587 		watermark->setPos(source.right() - watermark->boundingRect().width(), source.bottom());
588 		source.adjust(0, 0, 0, watermark->boundingRect().height());
589 	}
590 
591 	int width = source.width();
592 	int height = source.height();
593 
594 	/*
595 	int width = m_currentGraphicsView->width();
596 	if (m_currentGraphicsView->verticalScrollBar()->isVisible()) {
597 		width -= m_currentGraphicsView->verticalScrollBar()->width();
598 	}
599 	int height = m_currentGraphicsView->height();
600 	if (m_currentGraphicsView->horizontalScrollBar()->isVisible()) {
601 		height -= m_currentGraphicsView->horizontalScrollBar()->height();
602 	}
603 	*/
604 
605 	QSize imgSize(width * resMultiplier, height * resMultiplier);
606 	QImage image(imgSize,format);
607 	image.setDotsPerMeterX(InchesPerMeter * GraphicsUtils::SVGDPI * resMultiplier);
608 	image.setDotsPerMeterY(InchesPerMeter * GraphicsUtils::SVGDPI * resMultiplier);
609 	QPainter painter;
610 	QColor color;
611 	if (removeBackground) {
612 		color = m_currentGraphicsView->background();
613 		m_currentGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));
614 	}
615 
616 	painter.begin(&image);
617 	//m_currentGraphicsView->render(&painter);
618 	QRectF target(0, 0, imgSize.width(), imgSize.height());
619 	m_currentGraphicsView->scene()->render(&painter, target, source, Qt::KeepAspectRatio);
620 	painter.end();
621 
622     //image.save(FolderUtils::getUserDataStorePath("") + "/export.png");
623 
624 	if (removeBackground) {
625 		m_currentGraphicsView->setBackground(color);
626 	}
627 
628 	if (watermark) {
629 		delete watermark;
630 	}
631 
632 	QImageWriter imageWriter(fileName);
633 	if (imageWriter.supportsOption(QImageIOHandler::Description)) {
634 		imageWriter.setText("", TextUtils::CreatedWithFritzingString);
635 	}
636     imageWriter.setQuality(quality);
637 	bool result = imageWriter.write(image);
638 	if (!result) {
639 		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save %1").arg(fileName) );
640 	}
641 }
642 
printAux(QPrinter & printer,bool removeBackground,bool paginate)643 void MainWindow::printAux(QPrinter &printer, bool removeBackground, bool paginate) {
644     if (m_currentGraphicsView == NULL) return;
645 
646 	int res = printer.resolution();
647 	double scale2 = res / GraphicsUtils::SVGDPI;
648 	DebugDialog::debug(QString("p.w:%1 p.h:%2 pager.w:%3 pager.h:%4 paperr.w:%5 paperr.h:%6 source.w:%7 source.h:%8")
649 		.arg(printer.width())
650 		.arg(printer.height())
651 		.arg(printer.pageRect().width())
652 		.arg(printer.pageRect().height())
653 		.arg(printer.paperRect().width())
654 		.arg(printer.paperRect().height())
655 		.arg(printer.width() / scale2)
656 		.arg(printer.height() / scale2) );
657 
658 	// oSceneStart oSceneEnd: shows only what's visible in the viewport, not the entire view
659 	//QPointF oSceneStart = m_currentGraphicsView->mapToScene(QPoint(0,0));
660 	//QPointF oSceneEnd = m_currentGraphicsView->mapToScene(QPoint(m_currentGraphicsView->viewport()->width(), m_currentGraphicsView->viewport()->height()));
661 	//QRectF source(oSceneStart, oSceneEnd);
662 
663     QRectF itemsBoundingRect;
664 	foreach(QGraphicsItem *item,  m_currentGraphicsView->scene()->items()) {
665 		if (!item->isVisible()) continue;
666 
667         itemsBoundingRect |= item->sceneBoundingRect();
668 	}
669 
670 	QRectF source = itemsBoundingRect;  // m_currentGraphicsView->scene()->itemsBoundingRect();
671     DebugDialog::debug("items bounding rect", source);
672     DebugDialog::debug("scene items bounding rect", m_currentGraphicsView->scene()->itemsBoundingRect());
673 	QGraphicsItem * watermark = m_currentGraphicsView->addWatermark(":resources/images/watermark_fritzing_outline.svg");
674 	if (watermark) {
675 		watermark->setPos(source.right() - watermark->boundingRect().width(), source.bottom());
676 		source.adjust(0, 0, 0, watermark->boundingRect().height());
677 	}
678 
679 	QRectF target(0, 0, source.width() * scale2, source.height() * scale2);
680 
681 	if (!paginate) {
682 		QSizeF psize((target.width() + printer.paperRect().width() - printer.width()) / res,
683 					 (target.height() + printer.paperRect().height() - printer.height()) / res);
684 		printer.setPaperSize(psize, QPrinter::Inch);
685 	}
686 
687 	QPainter painter;
688 	if (!painter.begin(&printer)) {
689 		if (watermark) {
690 			delete watermark;
691 		}
692 		QMessageBox::warning(this, tr("Fritzing"), tr("Cannot print to %1").arg(printer.docName()));
693 		return;
694 	}
695 
696 	QColor color;
697 	if(removeBackground) {
698 		color = m_currentGraphicsView->background();
699 		m_currentGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));
700 	}
701 
702 	QList<QGraphicsItem*> selItems = m_currentGraphicsView->scene()->selectedItems();
703 	foreach(QGraphicsItem *item, selItems) {
704 		item->setSelected(false);
705 	}
706 
707 	if (paginate) {
708 		int xPages = qCeil(target.width() / printer.width());
709 		int yPages = qCeil(target.height() / printer.height());
710 		int lastPage = xPages * yPages;
711 
712 		int xSourcePage = qFloor(printer.width() / scale2);
713 		int ySourcePage = qFloor(printer.height() / scale2);
714 
715 		int page = 0;
716 		for (int iy = 0; iy < yPages; iy++) {
717 			for (int ix = 0; ix < xPages; ix++) {
718 				// render to printer:
719 				QRectF pSource((ix * xSourcePage) + source.left(),
720 							   (iy * ySourcePage) + source.top(),
721 							   qMin(xSourcePage, (int) source.width() - (ix * xSourcePage)),
722 							   qMin(ySourcePage, (int) source.height() - (iy * ySourcePage)));
723 				QRectF pTarget(0, 0, pSource.width() * scale2, pSource.height() * scale2);
724 				m_currentGraphicsView->scene()->render(&painter, pTarget, pSource, Qt::KeepAspectRatio);
725 				if (++page < lastPage) {
726 					printer.newPage();
727 				}
728 			}
729 		}
730 	}
731 	else {
732 		m_currentGraphicsView->scene()->render(&painter, target, source, Qt::KeepAspectRatio);
733 	}
734 
735 	foreach(QGraphicsItem *item, selItems) {
736 		item->setSelected(true);
737 	}
738 
739 	if(removeBackground) {
740 		m_currentGraphicsView->setBackground(color);
741 	}
742 
743 	if (watermark) {
744 		delete watermark;
745 	}
746 
747 	DebugDialog::debug(QString("source w:%1 h:%2 target w:%5 h:%6 pres:%3 screenres:%4")
748 		.arg(source.width())
749 		.arg(source.height()).arg(res).arg(this->physicalDpiX())
750 		.arg(target.width()).arg(target.height()) );
751 
752 	//#ifndef QT_NO_CONCURRENT
753 		//QProgressDialog dialog;
754 		//dialog.setLabelText(message);
755  	//
756 		// Create a QFutureWatcher and conncect signals and slots.
757 		//QFutureWatcher<void> futureWatcher;
758 		//QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
759 		//QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
760 		//QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog, SLOT(setRange(int, int)));
761 		//QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog, SLOT(setValue(int)));
762 	//
763 		// Start the computation.
764 		//futureWatcher.setFuture(QtConcurrent::run(painter,&QPainter::end));
765 		//dialog.exec();
766 	//
767 		//futureWatcher.waitForFinished();
768 	//#endif
769 
770 	//#ifdef QT_NO_CONCURRENT
771 		painter.end();
772 	//#endif
773 
774 }
775 
saveAsAux(const QString & fileName)776 bool MainWindow::saveAsAux(const QString & fileName) {
777     QFile file(fileName);
778     if (!file.open(QFile::WriteOnly | QFile::Text)) {
779         QMessageBox::warning(this, tr("Fritzing"),
780                              tr("Cannot write file %1:\n%2.")
781                              .arg(fileName)
782                              .arg(file.errorString()));
783         return false;
784     }
785 
786     file.close();
787 
788     setReadOnly(false);
789     //FritzingWindow::saveAsAux(fileName);
790 
791 	saveAsAuxAux(fileName);
792 	m_autosaveNeeded = false;
793 	undoStackCleanChanged(true);
794 
795 	m_statusBar->showMessage(tr("Saved '%1'").arg(fileName), 2000);
796     setCurrentFile(fileName, true, true);
797 
798 	if(m_restarting && !m_fwFilename.isEmpty()) {
799 		QSettings settings;
800 		settings.setValue("lastOpenSketch",m_fwFilename);
801 	}
802 
803    // mark the stack clean so we update the window dirty flag
804     m_undoStack->setClean();
805 
806 	// slam it here in case we were modified due to m_linkedProgramFiles changes
807 	setWindowModified(false);
808 
809     m_saveAct->setEnabled(true);
810 
811 	return true;
812 }
813 
saveAsAuxAux(const QString & fileName)814 void MainWindow::saveAsAuxAux(const QString & fileName) {
815     QApplication::setOverrideCursor(Qt::WaitCursor);
816 
817 	connectStartSave(true);
818 
819     m_programView->saveAll();
820 
821 	QDir dir(this->m_fzzFolder);
822 	QStringList nameFilters("*" + FritzingSketchExtension);
823 	QFileInfoList fileList = dir.entryInfoList(nameFilters, QDir::Files | QDir::NoSymLinks);
824 	foreach (QFileInfo fileInfo, fileList) {
825 		QFile file(fileInfo.absoluteFilePath());
826 		file.remove();
827 	}
828 
829 	QString fzName = dir.absoluteFilePath(QFileInfo(fileName).completeBaseName() + FritzingSketchExtension);
830 	m_sketchModel->save(fzName, false);
831 
832     saveLastTabList();
833 
834 	saveAsShareable(fileName, false);
835 
836 	connectStartSave(false);
837 
838 	QApplication::restoreOverrideCursor();
839 }
840 
841 
saveAsShareable(const QString & path,bool saveModel)842 void MainWindow::saveAsShareable(const QString & path, bool saveModel)
843 {
844 	QString filename = path;
845 	QHash<QString, ModelPart *> saveParts;
846 	foreach (QGraphicsItem * item, m_pcbGraphicsView->scene()->items()) {
847 		ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
848 		if (itemBase == NULL) continue;
849         if (itemBase->modelPart() == NULL) {
850             continue;
851         }
852 		if (itemBase->modelPart()->isCore()) continue;
853         if (itemBase->moduleID().contains(PartFactory::OldSchematicPrefix)) continue;
854 
855 		saveParts.insert(itemBase->moduleID(), itemBase->modelPart());
856 	}
857 	saveBundledNonAtomicEntity(filename, FritzingBundleExtension, this, saveParts.values(), false, m_fzzFolder, saveModel, true);
858 
859 }
860 
saveBundledNonAtomicEntity(QString & filename,const QString & extension,Bundler * bundler,const QList<ModelPart * > & partsToSave,bool askForFilename,const QString & destFolderPath,bool saveModel,bool deleteLeftovers)861 void MainWindow::saveBundledNonAtomicEntity(QString &filename, const QString &extension, Bundler *bundler, const QList<ModelPart*> &partsToSave, bool askForFilename, const QString & destFolderPath, bool saveModel, bool deleteLeftovers) {
862 	QStringList names;
863 
864 	QString fileExt;
865 	QString path = defaultSaveFolder() + "/" + QFileInfo(filename).fileName()+"z";
866 	QString bundledFileName = askForFilename
867 		? FolderUtils::getSaveFileName(this, tr("Specify a file name"), path, tr("Fritzing (*%1)").arg(extension), &fileExt)
868 		: filename;
869 
870 	if (bundledFileName.isEmpty()) return; // Cancel pressed
871 
872     FileProgressDialog progress("Saving...", 0, this);
873 
874 	if(!alreadyHasExtension(bundledFileName, extension)) {
875 		bundledFileName += extension;
876 	}
877 
878 	ProcessEventBlocker::processEvents();
879 
880 	QDir destFolder;
881 	QString dirToRemove;
882 	if (destFolderPath.isEmpty()) {
883 		destFolder = QDir::temp();
884 		FolderUtils::createFolderAnCdIntoIt(destFolder, TextUtils::getRandText());
885 		dirToRemove = destFolder.path();
886 	}
887 	else {
888 		destFolder = QDir(destFolderPath);
889 	}
890 
891 	QString aux = QFileInfo(bundledFileName).fileName();
892 	QString destSketchPath = // remove the last "z" from the extension
893 							 destFolder.path()+"/"+aux.left(aux.size()-1);
894 	DebugDialog::debug("saving entity temporarily to "+destSketchPath);
895 
896     QStringList skipSuffixes;
897 
898 	if (extension.compare(FritzingBundleExtension) == 0) {
899 		for (int i = 0; i < m_linkedProgramFiles.count(); i++) {
900 			LinkedFile * linkedFile = m_linkedProgramFiles.at(i);
901 			QFileInfo fileInfo(linkedFile->linkedFilename);
902 			QFile file(linkedFile->linkedFilename);
903 			FolderUtils::slamCopy(file, destFolder.absoluteFilePath(fileInfo.fileName()));
904 		}
905         skipSuffixes << FritzingBinExtension << FritzingBundleExtension;
906 	}
907 
908 	if (saveModel) {
909 		QString prevFileName = filename;
910 		ProcessEventBlocker::processEvents();
911 		bundler->saveAsAux(destSketchPath);
912 		filename = prevFileName;
913 	}
914 
915 	foreach(ModelPart* mp, partsToSave) {
916 		names.append(saveBundledAux(mp, destFolder));
917 	}
918 
919 	if (deleteLeftovers) {
920 		QStringList nameFilters;
921 		nameFilters << ("*" + FritzingPartExtension) << "*.svg";
922 		QDir dir(destFolder);
923 		QStringList fileList = dir.entryList(nameFilters, QDir::Files | QDir::NoSymLinks);
924 		foreach (QString fileName, fileList) {
925 			if (!names.contains(fileName)) {
926 				QFile::remove(dir.absoluteFilePath(fileName));
927 			}
928 		}
929 	}
930 
931 	ProcessEventBlocker::processEvents();
932 
933 	if(!FolderUtils::createZipAndSaveTo(destFolder, bundledFileName, skipSuffixes)) {
934 		QMessageBox::warning(
935 			this,
936 			tr("Fritzing"),
937 			tr("Unable to export %1 as shareable").arg(bundledFileName)
938 		);
939 	}
940 
941 	if (!dirToRemove.isEmpty()) {
942 		FolderUtils::rmdir(dirToRemove);
943 	}
944 }
945 
946 
createExportActions()947 void MainWindow::createExportActions() {
948 
949 	m_saveAct = new QAction(tr("&Save"), this);
950 	m_saveAct->setShortcut(tr("Ctrl+S"));
951 	m_saveAct->setStatusTip(tr("Save the current sketch"));
952 	connect(m_saveAct, SIGNAL(triggered()), this, SLOT(save()));
953 
954 	m_saveAsAct = new QAction(tr("&Save As..."), this);
955 	m_saveAsAct->setShortcut(tr("Shift+Ctrl+S"));
956 	m_saveAsAct->setStatusTip(tr("Save the current sketch"));
957 	connect(m_saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
958 
959 	m_shareOnlineAct = new QAction(tr("Share online..."), this);
960 	m_shareOnlineAct->setStatusTip(tr("Post a project to the Fritzing website"));
961 	connect(m_shareOnlineAct, SIGNAL(triggered()), this, SLOT(shareOnline()));
962 
963 	m_exportJpgAct = new QAction(tr("JPG..."), this);
964 	m_exportJpgAct->setData(jpgActionType);
965 	m_exportJpgAct->setStatusTip(tr("Export the visible area of the current sketch as a JPG image"));
966 	connect(m_exportJpgAct, SIGNAL(triggered()), this, SLOT(doExport()));
967 
968 	m_exportPngAct = new QAction(tr("PNG..."), this);
969 	m_exportPngAct->setData(pngActionType);
970 	m_exportPngAct->setStatusTip(tr("Export the visible area of the current sketch as a PNG image"));
971 	connect(m_exportPngAct, SIGNAL(triggered()), this, SLOT(doExport()));
972 
973 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
974 	m_exportPsAct = new QAction(tr("PostScript..."), this);
975 	m_exportPsAct->setData(psActionType);
976 	m_exportPsAct->setStatusTip(tr("Export the visible area of the current sketch as a PostScript image"));
977 	connect(m_exportPsAct, SIGNAL(triggered()), this, SLOT(doExport()));
978 #endif
979 
980 	m_exportPdfAct = new QAction(tr("PDF..."), this);
981 	m_exportPdfAct->setData(pdfActionType);
982 	m_exportPdfAct->setStatusTip(tr("Export the visible area of the current sketch as a PDF image"));
983 	connect(m_exportPdfAct, SIGNAL(triggered()), this, SLOT(doExport()));
984 
985 	m_exportSvgAct = new QAction(tr("SVG..."), this);
986 	m_exportSvgAct->setData(svgActionType);
987 	m_exportSvgAct->setStatusTip(tr("Export the current sketch as an SVG image"));
988 	connect(m_exportSvgAct, SIGNAL(triggered()), this, SLOT(doExport()));
989 
990     m_exportBomAct = new QAction(tr("List of parts (&Bill of Materials)..."), this);
991     m_exportBomAct->setData(bomActionType);
992     m_exportBomAct->setStatusTip(tr("Save a Bill of Materials (BoM)/Shopping List as text"));
993     connect(m_exportBomAct, SIGNAL(triggered()), this, SLOT(doExport()));
994 
995     m_exportNetlistAct = new QAction(tr("XML Netlist..."), this);
996     m_exportNetlistAct->setData(netlistActionType);
997     m_exportNetlistAct->setStatusTip(tr("Save a netlist in XML format"));
998     connect(m_exportNetlistAct, SIGNAL(triggered()), this, SLOT(doExport()));
999 
1000     m_exportSpiceNetlistAct = new QAction(tr("SPICE Netlist..."), this);
1001     m_exportSpiceNetlistAct->setData(spiceNetlistActionType);
1002     m_exportSpiceNetlistAct->setStatusTip(tr("Save a netlist in SPICE format"));
1003     connect(m_exportSpiceNetlistAct, SIGNAL(triggered()), this, SLOT(doExport()));
1004 
1005 	m_exportEagleAct = new QAction(tr("Eagle..."), this);
1006 	m_exportEagleAct->setData(eagleActionType);
1007 	m_exportEagleAct->setStatusTip(tr("Export the current sketch to Eagle CAD"));
1008 	connect(m_exportEagleAct, SIGNAL(triggered()), this, SLOT(doExport()));
1009 
1010     m_exportGerberAct = new QAction(tr("Extended Gerber (RS-274X)..."), this);
1011 	m_exportGerberAct->setData(gerberActionType);
1012     m_exportGerberAct->setStatusTip(tr("Export the current sketch to Extended Gerber format (RS-274X) for professional PCB production"));
1013 	connect(m_exportGerberAct, SIGNAL(triggered()), this, SLOT(doExport()));
1014 
1015 	m_exportEtchablePdfAct = new QAction(tr("Etchable (PDF)..."), this);
1016 	m_exportEtchablePdfAct->setStatusTip(tr("Export the current sketch to PDF for DIY PCB production (photoresist)"));
1017 	m_exportEtchablePdfAct->setProperty("svg", false);
1018 	connect(m_exportEtchablePdfAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));
1019 
1020 	m_exportEtchableSvgAct = new QAction(tr("Etchable (SVG)..."), this);
1021 	m_exportEtchableSvgAct->setStatusTip(tr("Export the current sketch to SVG for DIY PCB production (photoresist)"));
1022 	m_exportEtchableSvgAct->setProperty("svg", true);
1023 	connect(m_exportEtchableSvgAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));
1024 
1025 	/*m_pageSetupAct = new QAction(tr("&Page Setup..."), this);
1026 	m_pageSetupAct->setShortcut(tr("Shift+Ctrl+P"));
1027 	m_pageSetupAct->setStatusTip(tr("Setup the current sketch page"));
1028 	connect(m_pageSetupAct, SIGNAL(triggered()), this, SLOT(pageSetup()));*/
1029 
1030 	m_printAct = new QAction(tr("&Print..."), this);
1031 	m_printAct->setShortcut(tr("Ctrl+P"));
1032 	m_printAct->setStatusTip(tr("Print the current view"));
1033 	connect(m_printAct, SIGNAL(triggered()), this, SLOT(print()));
1034 
1035 }
1036 
exportToEagle()1037 void MainWindow::exportToEagle() {
1038 
1039 	QString text =
1040 		tr("This will soon provide an export of your Fritzing sketch to the EAGLE layout "
1041 		"software. If you'd like to have more exports to your favourite EDA tool, please let "
1042 		"us know, or contribute.");
1043 /*
1044 	QString text =
1045 		tr("The Eagle export module is very experimental.  If anything breaks or behaves "
1046 		"strangely, please let us know.");
1047 */
1048 
1049 	QMessageBox::information(this, tr("Fritzing"), text);
1050 
1051 	Fritzing2Eagle eagle = Fritzing2Eagle(m_pcbGraphicsView);
1052 
1053 	/*
1054 	QList <ItemBase*> partList;
1055 
1056 	// bail out if something is wrong
1057 	// TODO: show an error in QMessageBox
1058     if(m_currentWidget == NULL) {
1059 		return;
1060 	}
1061 
1062     m_pcbGraphicsView->collectParts(partList);
1063 
1064 	QString exportInfoString = tr("parts include:\n");
1065 	QString exportString = tr("GRID INCH 0.005\n");
1066 
1067 
1068 	for(int i=0; i < partList.size(); i++){
1069 		QString label = partList.at(i)->instanceTitle();
1070 		QString desc = partList.at(i)->title();
1071 
1072 		QHash<QString,QString> properties = partList.at(i)->modelPartShared()->properties();
1073 		QString package = properties["package"];
1074 		if (package == NULL) {
1075 			package = tr("*** package not specified ***");
1076 		}
1077 
1078 		exportInfoString += label + tr(" which is a ") + desc + tr(" in a ") + package + tr(" package.\n");
1079 	}
1080 	QMessageBox::information(this, tr("Fritzing"), exportInfoString);
1081 	*/
1082 
1083 	/*
1084 	QFile fp( fileName );
1085 	fp.open(QIODevice::WriteOnly);
1086 	fp.write(bom.toUtf8(),bom.length());
1087 	fp.close();
1088 	*/
1089 
1090 
1091 	/*
1092 	GRID INCH 0.005
1093 	USE '/Applications/eclipse/eagle/lbr/fritzing.lbr';
1094 	ADD RESISTOR@fritzing 'R_1' R0.000 (2.3055117 2.1307087);
1095 	ADD LED@fritzing 'L_2' R0.000 (5.423622 2.425197);
1096 	GRID LAST;
1097 	*/
1098 }
1099 
exportSvg(double res,bool selectedItems,bool flatten)1100 void MainWindow::exportSvg(double res, bool selectedItems, bool flatten) {
1101 	QString path = defaultSaveFolder();
1102 	QString fileExt;
1103 	QString fileName = FolderUtils::getSaveFileName(this,
1104 		tr("Export SVG..."),
1105 		path+"/"+constructFileName("", svgActionType),
1106 		fileExtFormats[svgActionType],
1107 		&fileExt
1108 		);
1109 
1110 	if (fileName.isEmpty()) return;
1111 
1112     exportSvg(res, selectedItems, flatten, fileName);
1113 }
1114 
exportSvg(double res,bool selectedItems,bool flatten,const QString & fileName)1115 void MainWindow::exportSvg(double res, bool selectedItems, bool flatten, const QString & fileName)
1116 {
1117 	FileProgressDialog * fileProgressDialog = exportProgress();
1118 	LayerList viewLayerIDs;
1119 	foreach (ViewLayer * viewLayer, m_currentGraphicsView->viewLayers()) {
1120 		if (viewLayer == NULL) continue;
1121 		if (!viewLayer->visible()) continue;
1122 
1123 		viewLayerIDs << viewLayer->viewLayerID();
1124 	}
1125 
1126 	RenderThing renderThing;
1127     renderThing.printerScale = GraphicsUtils::SVGDPI;
1128     renderThing.blackOnly = false;
1129     renderThing.dpi = res;
1130     renderThing.selectedItems = selectedItems;
1131     renderThing.hideTerminalPoints = true;
1132     renderThing.renderBlocker = false;
1133 	QString svg = m_currentGraphicsView->renderToSVG(renderThing, NULL, viewLayerIDs);
1134 	if (svg.isEmpty()) {
1135 		// tell the user something reasonable
1136 		return;
1137 	}
1138 
1139 	if (selectedItems == false && flatten == false) {
1140 		exportSvgWatermark(svg, res);
1141 	}
1142 
1143     TextUtils::writeUtf8(fileName, TextUtils::convertExtendedChars(svg));
1144 	delete fileProgressDialog;
1145 }
1146 
exportSvgWatermark(QString & svg,double res)1147 void MainWindow::exportSvgWatermark(QString & svg, double res)
1148 {
1149 	QFile file(":/resources/images/watermark_fritzing_outline.svg");
1150 	if (!file.open(QFile::ReadOnly)) return;
1151 
1152 	QString watermarkSvg = file.readAll();
1153 	file.close();
1154 
1155 	if (!watermarkSvg.contains("<svg")) return;
1156 
1157 	QSizeF watermarkSize = TextUtils::parseForWidthAndHeight(watermarkSvg);
1158 	QSizeF svgSize = TextUtils::parseForWidthAndHeight(svg);
1159 
1160 	SvgFileSplitter splitter;
1161 	bool result = splitter.splitString(watermarkSvg, "watermark");
1162 	if (!result) return;
1163 
1164     double factor;
1165 	result = splitter.normalize(res, "watermark", false, factor);
1166 	if (!result) return;
1167 
1168 	QString transWatermark = splitter.shift((svgSize.width() - watermarkSize.width()) * res, svgSize.height() * res, "watermark", true);
1169 	QString newSvg = TextUtils::makeSVGHeader(1, res, svgSize.width(), svgSize.height() + watermarkSize.height()) + transWatermark + "</svg>";
1170 	svg = TextUtils::mergeSvg(newSvg, svg, "", false);
1171 }
1172 
exportBOM()1173 void MainWindow::exportBOM() {
1174 
1175     // bail out if something is wrong
1176     // TODO: show an error in QMessageBox
1177 	if (m_currentWidget == NULL) {
1178         return;
1179     }
1180 
1181 	QString bomTemplate;
1182 	QFile file(":/resources/templates/bom.html");
1183 	if (file.open(QFile::ReadOnly)) {
1184 		bomTemplate = file.readAll();
1185 		file.close();
1186 	}
1187 	else {
1188 		return;
1189 	}
1190 
1191 	QString bomRowTemplate;
1192 	QFile file2(":/resources/templates/bom_row.html");
1193 	if (file2.open(QFile::ReadOnly)) {
1194 		bomRowTemplate = file2.readAll();
1195 		file2.close();
1196 	}
1197 	else {
1198 		return;
1199 	}
1200 
1201     QList <ItemBase*> partList;
1202     QList<QString> descrList;
1203 	QMultiHash<QString, ItemBase *> descrs;
1204 
1205 	m_currentGraphicsView->collectParts(partList);
1206 
1207     qSort(partList.begin(), partList.end(), sortPartList);
1208 
1209 	foreach (ItemBase * itemBase, partList) {
1210 		if (itemBase->itemType() != ModelPart::Part) continue;
1211 
1212         QString desc = itemBase->title() + "%%%%%" + getBomProps(itemBase);  // keeps different parts separate if there are no properties
1213         descrs.insert(desc, itemBase);
1214         if (!descrList.contains(desc)) {
1215             descrList.append(desc);
1216         }
1217     }
1218 
1219 	QString assemblyString;
1220 	foreach (ItemBase * itemBase, partList) {
1221 		if (itemBase->itemType() != ModelPart::Part) continue;
1222 
1223 		assemblyString += bomRowTemplate.arg(itemBase->instanceTitle()).arg(itemBase->title()).arg(getBomProps(itemBase));
1224     }
1225 
1226 	QString shoppingListString;
1227     foreach (QString descr, descrList) {
1228         QList<ItemBase *> itemBases = descrs.values(descr);
1229         QStringList split = descr.split("%%%%%");
1230 		shoppingListString += bomRowTemplate.arg(itemBases.count()).arg(split.at(0)).arg(split.at(1));
1231     }
1232 
1233 	QString bom = bomTemplate
1234 		.arg("Fritzing Bill of Materials")
1235 		.arg(QFileInfo(m_fwFilename).fileName())
1236 		.arg(m_fwFilename)
1237 		.arg(QDateTime::currentDateTime().toString("dddd, MMMM d yyyy, hh:mm:ss"))
1238 		.arg(assemblyString)
1239 		.arg(shoppingListString)
1240 		.arg(QString("%1.%2.%3").arg(Version::majorVersion()).arg(Version::minorVersion()).arg(Version::minorSubVersion()));
1241 
1242 
1243     QString path = defaultSaveFolder();
1244 
1245     QString fileExt;
1246     QString extFmt = fileExtFormats.value(bomActionType);
1247     QString fname = path+"/"+constructFileName("bom", bomActionType);
1248     DebugDialog::debug(QString("fname %1\n%2").arg(fname).arg(extFmt));
1249 
1250     QString fileName = FolderUtils::getSaveFileName(this,
1251             tr("Export Bill of Materials (BoM)..."),
1252             fname,
1253             extFmt,
1254             &fileExt
1255     );
1256 
1257     if (fileName.isEmpty()) {
1258 		return; //Cancel pressed
1259     }
1260 
1261 	FileProgressDialog * fileProgressDialog = exportProgress();
1262     DebugDialog::debug(fileExt+" selected to export");
1263     if(!alreadyHasExtension(fileName, bomActionType)) {
1264 		fileName += bomActionType;
1265     }
1266 
1267    	if (!TextUtils::writeUtf8(fileName, bom)) {
1268 		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save BOM file, but the text is on the clipboard."));
1269 	}
1270 
1271     QFileInfo info(fileName);
1272 	if (info.exists()) {
1273 		QDesktopServices::openUrl(QString("file:///%1").arg(fileName));
1274 	}
1275 
1276 	QClipboard *clipboard = QApplication::clipboard();
1277 	if (clipboard != NULL) {
1278 		clipboard->setText(bom);
1279 	}
1280 	delete fileProgressDialog;
1281 }
1282 
exportSpiceNetlist()1283 void MainWindow::exportSpiceNetlist() {
1284     if (m_schematicGraphicsView == NULL) return;
1285 
1286     // examples:
1287     // http://www.allaboutcircuits.com/vol_5/chpt_7/8.html
1288     // http://cutler.eecs.berkeley.edu/classes/icbook/spice/UserGuide/elements_fr.html
1289     // http://www.csd.uoc.gr/~hy422/2011s/datasheets/ngspice-user-manual.pdf
1290 
1291     QString path = defaultSaveFolder();
1292     QString fileExt;
1293     QString extFmt = fileExtFormats.value(spiceNetlistActionType);
1294     QString fname = path + "/" + constructFileName("spice", spiceNetlistActionType);
1295     //DebugDialog::debug(QString("fname %1\n%2").arg(fname).arg(extFmt));
1296     QString fileName = FolderUtils::getSaveFileName(this,
1297             tr("Export SPICE Netlist..."),
1298             fname,
1299             extFmt,
1300             &fileExt
1301     );
1302 
1303     if (fileName.isEmpty()) {
1304 		return; //Cancel pressed
1305     }
1306 
1307     static QRegExp curlies("\\{([^\\}]*)\\}");
1308 
1309     QFileInfo fileInfo(m_fwFilename);
1310     QString output = fileInfo.completeBaseName();
1311     output += "\n";
1312 
1313 	QHash<ConnectorItem *, int> indexer;
1314 	QList< QList<ConnectorItem *>* > netList;
1315 	this->m_schematicGraphicsView->collectAllNets(indexer, netList, true, false);
1316 
1317 
1318     //DebugDialog::debug("_______________");
1319     QSet<ItemBase *> itemBases;
1320     QList<ConnectorItem *> * ground = NULL;
1321 	foreach (QList<ConnectorItem *> * net, netList) {
1322         if (net->count() < 2) continue;
1323 
1324         foreach (ConnectorItem * ci, *net) {
1325             //ci->debugInfo("net");
1326             if (ci->isGrounded()) {
1327                 ground = net;
1328             }
1329             itemBases.insert(ci->attachedTo());
1330         }
1331         //DebugDialog::debug("_______________");
1332     }
1333 
1334     if (ground) {
1335         // make sure ground is index zero
1336         netList.removeOne(ground);
1337         netList.prepend(ground);
1338     }
1339 
1340     //DebugDialog::debug("_______________");
1341     //DebugDialog::debug("_______________");
1342     DebugDialog::debug("_______________");
1343 
1344 	foreach (QList<ConnectorItem *> * net, netList) {
1345         if (net->count() < 2) continue;
1346 
1347         foreach (ConnectorItem * ci, *net) {
1348             ci->debugInfo("net");
1349         }
1350 
1351         DebugDialog::debug("_______________");
1352     }
1353 
1354     //DebugDialog::debug("_______________");
1355     //DebugDialog::debug("_______________");
1356 
1357     foreach (ItemBase * itemBase, itemBases) {
1358         QString spice = itemBase->spice();
1359         if (spice.isEmpty()) continue;
1360 
1361         while (true) {
1362             int ix = curlies.indexIn(spice);
1363             if (ix < 0) break;
1364 
1365             QString token = curlies.cap(1).toLower();
1366             QString replacement;
1367             if (token == "instancetitle") {
1368                 replacement = itemBase->instanceTitle();
1369                 if (ix > 0 && replacement.at(0).toLower() == spice.at(ix - 1).toLower()) {
1370                     // if the type letter is repeated
1371                     replacement = replacement.mid(1);
1372                 }
1373                 replacement.replace(" ", "_");
1374             }
1375             else if (token.startsWith("net ")) {
1376                 QString cname = token.mid(4).trimmed();
1377                 foreach (ConnectorItem * ci, itemBase->cachedConnectorItems()) {
1378                     if (ci->connectorSharedID().toLower() == cname) {
1379                         int ix = -1;
1380 	                    foreach (QList<ConnectorItem *> * net, netList) {
1381                             ix++;
1382                             if (net->contains(ci)) break;
1383                         }
1384 
1385                         replacement = QString::number(ix);
1386                         break;
1387                     }
1388                 }
1389             }
1390             else {
1391                 QVariant variant = itemBase->modelPart()->localProp(token);
1392                 if (variant.isNull()) {
1393                     replacement = itemBase->modelPart()->properties().value(token, "");
1394                 }
1395                 else {
1396                     replacement = variant.toString();
1397                 }
1398             }
1399 
1400             spice.replace(ix, curlies.cap(0).count(), replacement);
1401             DebugDialog::debug("spice " + spice);
1402         }
1403 
1404         output += spice;
1405     }
1406 
1407     output += "\n";
1408 
1409     // remove redundant models
1410     QStringList models;
1411     foreach (ItemBase * itemBase, itemBases) {
1412         QString spiceModel = itemBase->spiceModel();
1413         if (spiceModel.isEmpty()) continue;
1414         if (models.contains(spiceModel, Qt::CaseInsensitive)) continue;
1415 
1416         models.append(spiceModel);
1417     }
1418 
1419     foreach (QString model, models) {
1420         output += model;
1421         output += "\n";
1422     }
1423 
1424     QString incl = ".include";
1425     if (output.contains(incl, Qt::CaseInsensitive)) {
1426         QStringList lines = output.split("\n");
1427         QList<QDir *> paths;
1428         paths << FolderUtils::getApplicationSubFolder("pdb");
1429         paths << FolderUtils::getApplicationSubFolder("parts");
1430         paths << new QDir(FolderUtils::getUserDataStorePath("parts"));
1431 
1432         QString output2;
1433         foreach (QString line, lines) {
1434             int ix = line.toLower().indexOf(incl);
1435             if (ix < 0) {
1436                 output2 += line + "\n";
1437                 continue;
1438             }
1439 
1440             QString temp = line;
1441             temp.replace(ix, incl.length(), "");
1442             QString filename = temp.trimmed();
1443 
1444             bool gotOne = false;
1445             foreach (QDir * dir, paths) {
1446                 foreach (QString folder, ModelPart::possibleFolders()) {
1447                     QDir sub(*dir);
1448                     sub.cd(folder);
1449                     sub.cd("spicemodels");
1450                     if (QFile::exists(sub.absoluteFilePath(filename))) {
1451                         output2 += incl.toUpper() + " " + QDir::toNativeSeparators(sub.absoluteFilePath(filename)) + "\n";
1452                         gotOne = true;
1453                         break;
1454                     }
1455                 }
1456                 if (gotOne) break;
1457             }
1458 
1459             // can't find the include file, so just restore the original line
1460             if (!gotOne) {
1461                 output2 += line + "\n";
1462             }
1463         }
1464 
1465         output = output2;
1466         foreach (QDir * dir, paths) delete dir;
1467     }
1468 
1469     output += ".TRAN 1ms 100ms\n";
1470     output += "* .AC DEC 100 100 1MEG\n";
1471     output += ".END\n";
1472 
1473 	foreach (QList<ConnectorItem *> * net, netList) {
1474 		delete net;
1475 	}
1476 	netList.clear();
1477 
1478 	QClipboard *clipboard = QApplication::clipboard();
1479 	if (clipboard != NULL) {
1480 		clipboard->setText(output);
1481 	}
1482 
1483     //DebugDialog::debug(fileExt + " selected to export");
1484     if(!alreadyHasExtension(fileName, spiceNetlistActionType)) {
1485 		fileName += spiceNetlistActionType;
1486     }
1487 
1488     TextUtils::writeUtf8(fileName, output);
1489 }
1490 
exportNetlist()1491 void MainWindow::exportNetlist() {
1492 	QHash<ConnectorItem *, int> indexer;
1493 	QList< QList<ConnectorItem *>* > netList;
1494 	this->m_currentGraphicsView->collectAllNets(indexer, netList, true, m_currentGraphicsView->boardLayers() > 1);
1495 
1496 	QDomDocument doc;
1497 	doc.setContent(QString("<?xml version='1.0' encoding='UTF-8'?>\n") + TextUtils::CreatedWithFritzingXmlComment);
1498 	QDomElement netlist = doc.createElement("netlist");
1499 	doc.appendChild(netlist);
1500 	netlist.setAttribute("sketch", QFileInfo(m_fwFilename).fileName());
1501     netlist.setAttribute("date", QDateTime::currentDateTime().toString());
1502 
1503 	// TODO: filter out 'ignore' connectors
1504 
1505 	QList< QList<ConnectorItem *>* > deleteNets;
1506 	foreach (QList<ConnectorItem *> * net, netList) {
1507 		QList<ConnectorItem *> deleteItems;
1508 		foreach (ConnectorItem * connectorItem, *net) {
1509 			ErcData * ercData = connectorItem->connectorSharedErcData();
1510 			if (ercData == NULL) continue;
1511 
1512 			if (ercData->ignore() == ErcData::Always) {
1513 				deleteItems.append(connectorItem);
1514 			}
1515 			else if ((ercData->ignore() == ErcData::IfUnconnected) && (net->count() == 1)) {
1516 				deleteItems.append(connectorItem);
1517 			}
1518 		}
1519 
1520 		foreach (ConnectorItem * connectorItem, deleteItems) {
1521 			net->removeOne(connectorItem);
1522 		}
1523 		if (net->count() == 0) {
1524 			deleteNets.append(net);
1525 		}
1526 	}
1527 
1528 	foreach (QList<ConnectorItem *> * net, deleteNets) {
1529 		netList.removeOne(net);
1530 	}
1531 
1532 	foreach (QList<ConnectorItem *> * net, netList) {
1533 		QDomElement netElement = doc.createElement("net");
1534 		netlist.appendChild(netElement);
1535 		foreach (ConnectorItem * connectorItem, *net) {
1536 			QDomElement connector = doc.createElement("connector");
1537 			netElement.appendChild(connector);
1538 			connector.setAttribute("id", connectorItem->connectorSharedID());
1539 			connector.setAttribute("name", connectorItem->connectorSharedName());
1540 			QDomElement part = doc.createElement("part");
1541 			connector.appendChild(part);
1542 			ItemBase * itemBase = connectorItem->attachedTo();
1543 			part.setAttribute("id", itemBase->id());
1544 			part.setAttribute("label", itemBase->instanceTitle());
1545 			part.setAttribute("title", itemBase->title());
1546 			ErcData * ercData = connectorItem->connectorSharedErcData();
1547 			if (ercData != NULL) {
1548 				QDomElement erc = doc.createElement("erc");
1549 				if (ercData->writeToElement(erc, doc)) {
1550 					connector.appendChild(erc);
1551 				}
1552 			}
1553 		}
1554 	}
1555 
1556 	foreach (QList<ConnectorItem *> * net, netList) {
1557 		delete net;
1558 	}
1559 	netList.clear();
1560 
1561     QString path = defaultSaveFolder();
1562 
1563     QString fileExt;
1564     QString extFmt = fileExtFormats.value(netlistActionType);
1565     QString fname = path + "/" +constructFileName("netlist", netlistActionType);
1566     //DebugDialog::debug(QString("fname %1\n%2").arg(fname).arg(extFmt));
1567 
1568     QString fileName = FolderUtils::getSaveFileName(this,
1569             tr("Export Netlist..."),
1570             fname,
1571             extFmt,
1572             &fileExt
1573     );
1574 
1575     if (fileName.isEmpty()) {
1576 		return; //Cancel pressed
1577     }
1578 
1579 	FileProgressDialog * fileProgressDialog = exportProgress();
1580     //DebugDialog::debug(fileExt + " selected to export");
1581     if(!alreadyHasExtension(fileName, netlistActionType)) {
1582 		fileName += netlistActionType;
1583     }
1584 
1585     QFile fp( fileName );
1586     fp.open(QIODevice::WriteOnly);
1587     fp.write(doc.toByteArray());
1588     fp.close();
1589 
1590 	QClipboard *clipboard = QApplication::clipboard();
1591 	if (clipboard != NULL) {
1592 		clipboard->setText(doc.toByteArray());
1593 	}
1594 	delete fileProgressDialog;
1595 }
1596 
exportProgress()1597 FileProgressDialog * MainWindow::exportProgress() {
1598     return (new FileProgressDialog("Exporting...", 0, this));
1599 }
1600 
exportNormalizedSVG()1601 void MainWindow::exportNormalizedSVG() {
1602 	exportSvg(GraphicsUtils::StandardFritzingDPI, true, false);
1603 }
1604 
exportNormalizedFlattenedSVG()1605 void MainWindow::exportNormalizedFlattenedSVG() {
1606 	exportSvg(GraphicsUtils::StandardFritzingDPI, true, true);
1607 }
1608 
getBomProps(ItemBase * itemBase)1609 QString MainWindow::getBomProps(ItemBase * itemBase)
1610 {
1611 	if (itemBase == NULL) return "";
1612 
1613 	QStringList keys;
1614 	QHash<QString, QString> properties = HtmlInfoView::getPartProperties(itemBase->modelPart(), itemBase, false, keys);
1615 	QString pString;
1616 	foreach (QString key, keys) {
1617 		if (key.compare("family") == 0) continue;
1618 
1619 		QString value = properties.value(key);
1620 
1621 		QWidget widget;
1622 		QWidget * resultWidget = NULL;
1623 		QString resultKey, resultValue;
1624         bool hide;
1625 		itemBase->collectExtraInfo(&widget, properties.value("family"), key, value, false, resultKey, resultValue, resultWidget, hide);
1626 		if (resultValue.isEmpty()) continue;
1627 
1628 		pString += ItemBase::translatePropertyName(resultKey) + " " + resultValue + "; ";
1629 	}
1630 
1631 	if (pString.length() > 2) pString.chop(2);
1632 
1633 	return pString;
1634 }
1635 
exportToGerber()1636 void MainWindow::exportToGerber() {
1637 
1638     //NOTE: this assumes just one board per sketch
1639 
1640     int boardCount;
1641 	ItemBase * board = m_pcbGraphicsView->findSelectedBoard(boardCount);
1642 
1643     // barf an error if there's no board
1644     if (boardCount == 0) {
1645         QMessageBox::critical(this, tr("Fritzing"),
1646                    tr("Your sketch does not have a board yet!  Please add a PCB in order to export to Gerber."));
1647         return;
1648     }
1649     if (board == NULL) {
1650         QMessageBox::critical(this, tr("Fritzing"),
1651                    tr("Gerber export can only handle one board at a time--please select the board you want to export."));
1652         return;
1653     }
1654 
1655     QString exportDir = QFileDialog::getExistingDirectory(this, tr("Choose a folder for exporting"),
1656                                              defaultSaveFolder(),
1657                                              QFileDialog::ShowDirsOnly
1658                                              | QFileDialog::DontResolveSymlinks);
1659 
1660 	if (exportDir.isEmpty()) return;
1661 
1662 	FileProgressDialog * fileProgressDialog = exportProgress();
1663 
1664 	FolderUtils::setOpenSaveFolder(exportDir);
1665 	m_pcbGraphicsView->saveLayerVisibility();
1666 	m_pcbGraphicsView->setAllLayersVisible(true);
1667 
1668     QFileInfo info(m_fwFilename);
1669     QString prefix = info.completeBaseName();
1670     if (boardCount > 1) {
1671         prefix += QString("_%1_%2").arg(board->instanceTitle()).arg(board->id());
1672     }
1673 	GerberGenerator::exportToGerber(prefix, exportDir, board, m_pcbGraphicsView, true);
1674 
1675 	m_pcbGraphicsView->restoreLayerVisibility();
1676 	m_statusBar->showMessage(tr("Sketch exported to Gerber"), 2000);
1677 
1678 	delete fileProgressDialog;
1679 }
1680 
connectStartSave(bool doConnect)1681 void MainWindow::connectStartSave(bool doConnect) {
1682 
1683 	if (doConnect) {
1684 		connect(m_sketchModel->root(), SIGNAL(startSaveInstances(const QString &, ModelPart *, QXmlStreamWriter &)),
1685 				this, SLOT(startSaveInstancesSlot(const QString &, ModelPart *, QXmlStreamWriter &)), Qt::DirectConnection);
1686 	}
1687 	else {
1688 		disconnect(m_sketchModel->root(), SIGNAL(startSaveInstances(const QString &, ModelPart *, QXmlStreamWriter &)),
1689 				this, SLOT(startSaveInstancesSlot(const QString &, ModelPart *, QXmlStreamWriter &)));
1690 	}
1691 }
1692 
constructFileName(const QString & differentiator,const QString & suffix)1693 QString MainWindow::constructFileName(const QString & differentiator, const QString & suffix) {
1694 	QString fn = QFileInfo(m_fwFilename).completeBaseName();
1695 	fn += "_" + (differentiator.isEmpty() ? m_currentGraphicsView->getShortName() : differentiator);
1696 	return fn + suffix;
1697 }
1698 
massageOutput(QString & svg,bool doMask,bool doSilk,bool doPaste,QString & maskTop,QString & maskBottom,const QString & fileName,ItemBase * board,int dpi,const LayerList & viewLayerIDs)1699 void MainWindow::massageOutput(QString & svg, bool doMask, bool doSilk, bool doPaste, QString & maskTop, QString & maskBottom, const QString & fileName, ItemBase * board, int dpi, const LayerList & viewLayerIDs)
1700 {
1701 	if (doPaste) {
1702         // must test doPaste first, since doMask will also be true
1703         svg = pcbView()->makePasteMask(svg, board, dpi, viewLayerIDs);
1704     }
1705 	else if (doSilk) {
1706 		QString use = (fileName.contains("bottom")) ? maskBottom : maskTop;
1707 		use = TextUtils::expandAndFill(use, "white", GerberGenerator::MaskClearanceMils * 2 * dpi / 1000);
1708 		svg = TextUtils::mergeSvg(svg, use, "", false);
1709 	}
1710     else if (doMask) {
1711 		if (fileName.contains("bottom")) maskBottom = svg; else maskTop = svg;
1712 		svg = TextUtils::expandAndFill(svg, "black", GerberGenerator::MaskClearanceMils * 2 * dpi / 1000);
1713 	}
1714 }
1715 
dumpAllParts()1716 void MainWindow::dumpAllParts() {
1717     if (m_currentGraphicsView == NULL) return;
1718 
1719     QList<ItemBase *> already;
1720     foreach (QGraphicsItem * item, m_currentGraphicsView->items()) {
1721         ItemBase * ib = dynamic_cast<ItemBase *>(item);
1722         if (ib == NULL) continue;
1723 
1724         ItemBase * chief = ib->layerKinChief();
1725         if (already.contains(chief)) continue;
1726 
1727         already << chief;
1728 
1729         QList<ItemBase *> itemBases;
1730         itemBases << chief;
1731         itemBases.append(chief->layerKin());
1732         foreach (ItemBase * itemBase, itemBases) {
1733             itemBase->debugInfo("");
1734             foreach (ConnectorItem * connectorItem, itemBase->cachedConnectorItems()) {
1735                 if (connectorItem->connectionsCount() > 0) {
1736                     connectorItem->debugInfo("\t");
1737                     foreach (ConnectorItem * to, connectorItem->connectedToItems()) {
1738                         to->debugInfo("\t\t");
1739                     }
1740                 }
1741             }
1742         }
1743     }
1744 }
1745