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