/*******************************************************************
skw
Part of the Fritzing project - http://fritzing.org
Copyright (c) 2007-2011 Fachhochschule Potsdam - http://fh-potsdam.de

Fritzing is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Fritzing is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.

********************************************************************

$Revision: 5892 $:
$Author: cohen@irascible.com $:
$Date: 2012-03-05 04:23:26 +0100 (Mon, 05 Mar 2012) $

********************************************************************/

#include <QtGui>
#include <QSvgGenerator>
#include <QColor>
#include <QImageWriter>

#include "mainwindow.h"
#include "debugdialog.h"
#include "waitpushundostack.h"
#include "partseditor/partseditormainwindow.h"
#include "help/aboutbox.h"
#include "autoroute/cmrouter/cmrouter.h"
#include "autoroute/autorouteprogressdialog.h"
#include "items/virtualwire.h"
#include "items/jumperitem.h"
#include "items/via.h"
#include "fsvgrenderer.h"
#include "items/note.h"
#include "eagle/fritzing2eagle.h"
#include "sketch/breadboardsketchwidget.h"
#include "sketch/schematicsketchwidget.h"
#include "sketch/pcbsketchwidget.h"
#include "partsbinpalette/binmanager/binmanager.h"
#include "utils/expandinglabel.h"
#include "infoview/htmlinfoview.h"
#include "utils/bendpointaction.h"
#include "fgraphicsscene.h"
#include "utils/fileprogressdialog.h"
#include "svg/svgfilesplitter.h"
#include "version/version.h"
#include "help/tipsandtricks.h"
#include "dialogs/setcolordialog.h"
#include "utils/folderutils.h"
#include "utils/graphicsutils.h"
#include "utils/textutils.h"
#include "connectors/ercdata.h"
#include "items/moduleidnames.h"
#include "navigator/miniviewcontainer.h"
#include "utils/zoomslider.h"
#include "layerpalette.h"
#include "program/programwindow.h"
#include "utils/autoclosemessagebox.h"
#include "svg/gerbergenerator.h"
#include "processeventblocker.h"

static QString eagleActionType = ".eagle";
static QString gerberActionType = ".gerber";
static QString jpgActionType = ".jpg";
static QString psActionType = ".ps";
static QString pdfActionType = ".pdf";
static QString pngActionType = ".png";
static QString svgActionType = ".svg";
static QString bomActionType = ".html";
static QString netlistActionType = ".xml";

static QHash<QString, QPrinter::OutputFormat> filePrintFormats;
static QHash<QString, QImage::Format> fileExportFormats;
static QHash<QString, QString> fileExtFormats;

static QRegExp AaCc("[aAcC]");

////////////////////////////////////////////////////////

bool sortPartList(ItemBase * b1, ItemBase * b2){
    return b1->instanceTitle().toLower() < b2->instanceTitle().toLower();
}

void massageOutput(QString & svg, bool doMask, bool doSilk, QString & maskTop, QString & maskBottom, const QString & fileName, int dpi)
{
	if (doMask) {
		if (fileName.contains("bottom")) maskBottom = svg; else maskTop = svg;
		svg = TextUtils::expandAndFill(svg, "black", GerberGenerator::MaskClearanceMils * 2 * dpi / 1000);
	}
	else if (doSilk) {
		QString use = (fileName.contains("bottom")) ? maskBottom : maskTop;
		use = TextUtils::expandAndFill(use, "white", GerberGenerator::MaskClearanceMils * 2 * dpi / 1000);
		svg = TextUtils::mergeSvg(svg, use, "", false);
	}
}

/////////////////////////////////////////////////////////

void MainWindow::initNames()
{
	OtherKnownExtensions << jpgActionType << psActionType << pdfActionType << pngActionType << svgActionType << bomActionType << netlistActionType;

	filePrintFormats[pdfActionType] = QPrinter::PdfFormat;
	filePrintFormats[psActionType] = QPrinter::PostScriptFormat;

	fileExportFormats[pngActionType] = QImage::Format_ARGB32;
	fileExportFormats[jpgActionType] = QImage::Format_RGB32;

	fileExtFormats[pdfActionType] = tr("PDF (*.pdf)");
	fileExtFormats[psActionType] = tr("PostScript (*.ps)");
	fileExtFormats[pngActionType] = tr("PNG Image (*.png)");
	fileExtFormats[jpgActionType] = tr("JPEG Image (*.jpg)");
	fileExtFormats[svgActionType] = tr("SVG Image (*.svg)");
	fileExtFormats[bomActionType] = tr("BoM Text File (*.html)");

	QSettings settings;
	AutosaveEnabled = settings.value("autosaveEnabled", QString("%1").arg(AutosaveEnabled)).toBool();
	AutosaveTimeoutMinutes = settings.value("autosavePeriod", QString("%1").arg(AutosaveTimeoutMinutes)).toInt();
}

void MainWindow::print() {
	#ifndef QT_NO_PRINTER
		QPrinter printer(QPrinter::HighResolution);

		QPrintDialog *printDialog = new QPrintDialog(&printer, this);
		if (printDialog->exec() == QDialog::Accepted) {
			m_statusBar->showMessage(tr("Printing..."));
			printAux(printer, true, true);
			m_statusBar->showMessage(tr("Ready"), 2000);
		} else {
			return;
		}
	#endif
}

void MainWindow::exportEtchable() {
	if (sender() == NULL) return;

	bool wantSvg = sender()->property("svg").toBool();
	exportEtchable(!wantSvg, wantSvg, sender()->property("flip").toBool());
}


void MainWindow::exportEtchable(bool wantPDF, bool wantSVG, bool flip)
{
	RoutingStatus routingStatus;
	m_pcbGraphicsView->updateRoutingStatus(NULL, routingStatus, true);
	if (routingStatus.m_connectorsLeftToRoute > 0) {
		QMessageBox msgBox(this);
		msgBox.setWindowModality(Qt::WindowModal);
		msgBox.setText(tr("All traces have not yet been routed."));
		msgBox.setInformativeText(tr("Do you want to proceed anyway?"));
		msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
		msgBox.button(QMessageBox::Yes)->setText(tr("Proceed"));
		msgBox.button(QMessageBox::No)->setText(tr("Cancel"));
		msgBox.setDefaultButton(QMessageBox::Yes);
		int ret = msgBox.exec();
		if (ret != QMessageBox::Yes) return;
	}

	QStringList fileNames;
	QString path = defaultSaveFolder();
	QString suffix = (wantPDF) ? pdfActionType : svgActionType;
	QString extFmt = (wantPDF) ? fileExtFormats.value(pdfActionType) : fileExtFormats.value(svgActionType);
	QString fileExt;


	QString exportDir = QFileDialog::getExistingDirectory(this, tr("Choose a folder for exporting"),
												defaultSaveFolder(),
												QFileDialog::ShowDirsOnly
												| QFileDialog::DontResolveSymlinks);
	if (exportDir.isEmpty()) return;

	FolderUtils::setOpenSaveFolder(exportDir);
	fileNames.append(exportDir + "/" + constructFileName("etch_copper_bottom", suffix));
	fileNames.append(exportDir + "/" + constructFileName("etch_mask_bottom", suffix));
	if (m_pcbGraphicsView->boardLayers() > 1) {
		fileNames.append(exportDir + "/" + constructFileName("etch_copper_top", suffix));
		fileNames.append(exportDir + "/" + constructFileName("etch_mask_top", suffix));
	}
	fileNames.append(exportDir + "/" + constructFileName("etch_silk_top", suffix));
	fileExt = extFmt;
	
	FileProgressDialog * fileProgressDialog = exportProgress();

    ItemBase * board = m_pcbGraphicsView->findBoard();

	QString maskTop, maskBottom;
	QList<ItemBase *> copperLogoItems;
	for (int ix = 0; ix < fileNames.count(); ix++) {
		bool doMask = false;
		bool doSilk = false;
		QString fileName = fileNames[ix];
		LayerList viewLayerIDs;
		if (fileName.contains("copper") && fileName.contains("bottom")) {
			viewLayerIDs << ViewLayer::GroundPlane0 << ViewLayer::Copper0 << ViewLayer::Copper0Trace;
		}
		if (fileName.contains("mask") && fileName.contains("bottom")) {
			doMask = true;
			viewLayerIDs << ViewLayer::Copper0;
		}
		else if (fileName.contains("copper") && fileName.contains("top")) {
			viewLayerIDs << ViewLayer::GroundPlane1 << ViewLayer::Copper1 << ViewLayer::Copper1Trace;
		}
		else if (fileName.contains("mask") && fileName.contains("top")) {
			viewLayerIDs << ViewLayer::Copper1;
			doMask = true;
		}
		else if (fileName.contains("silk") && fileName.contains("top")) {
			viewLayerIDs << ViewLayer::Silkscreen1 << ViewLayer::Silkscreen1Label;
			doSilk = true;
		}

		if (doMask) {
			m_pcbGraphicsView->hideCopperLogoItems(copperLogoItems);
		}

		QSizeF imageSize;
		if (wantSVG) {
			bool empty;
			QString svg = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, true, imageSize, board, GraphicsUtils::IllustratorDPI, false, false, false, empty);
			massageOutput(svg, doMask, doSilk, maskTop, maskBottom, fileName, GraphicsUtils::IllustratorDPI);		
			svg = mergeBoardSvg(svg, board, GraphicsUtils::IllustratorDPI, imageSize, flip);
			
			QFile file(fileName);
			file.open(QIODevice::WriteOnly);
			QTextStream out(&file);
			out.setCodec("UTF-8");
			out << svg;
			file.close();
		}
		else {
			QPrinter printer(QPrinter::HighResolution);
			printer.setOutputFormat(filePrintFormats[fileExt]);
			printer.setOutputFileName(fileName);
			int res = printer.resolution();
			bool empty;
			QString svg = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, true, imageSize, board, res, false, false, false, empty);
			massageOutput(svg, doMask, doSilk, maskTop, maskBottom, fileName, res);
			svg = mergeBoardSvg(svg, board, res, imageSize, flip);
			
			// now convert to pdf
			QSvgRenderer svgRenderer;
			svgRenderer.load(svg.toLatin1());
			double trueWidth = imageSize.width() / FSvgRenderer::printerScale();
			double trueHeight = imageSize.height() / FSvgRenderer::printerScale();
			QRectF target(0, 0, trueWidth * res, trueHeight * res);

			QSizeF psize((target.width() + printer.paperRect().width() - printer.width()) / res, 
						 (target.height() + printer.paperRect().height() - printer.height()) / res);
			printer.setPaperSize(psize, QPrinter::Inch);

			QPainter painter;
			if (painter.begin(&printer))
			{
				svgRenderer.render(&painter, target);
			}

			painter.end();
		}
		if (doMask) {
			m_pcbGraphicsView->restoreCopperLogoItems(copperLogoItems);
		}

	}

	m_statusBar->showMessage(tr("Sketch exported"), 2000);
	delete fileProgressDialog;

/*

	int width = m_pcbGraphicsView->width();
	if (m_pcbGraphicsView->verticalScrollBar()->isVisible()) {
		width -= m_pcbGraphicsView->verticalScrollBar()->width();
	}
	int height = m_pcbGraphicsView->height();
	if (m_pcbGraphicsView->horizontalScrollBar()->isVisible()) {
		height -= m_pcbGraphicsView->horizontalScrollBar()->height();
	}

	double trueWidth = width / m_printerScale;
	double trueHeight = height / m_printerScale;

	// set everything to a 1200 dpi resolution
	QSize imgSize(trueWidth * 1200, trueHeight * 1200);
	QImage image(imgSize, QImage::Format_RGB32);
	image.setDotsPerMeterX(1200 * GraphicsUtils::InchesPerMeter);
	image.setDotsPerMeterY(1200 * GraphicsUtils::InchesPerMeter);
	QPainter painter;

	QColor color;
	color = m_pcbGraphicsView->background();
	m_pcbGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));

	m_pcbGraphicsView->scene()->clearSelection();
	m_pcbGraphicsView->saveLayerVisibility();
	m_pcbGraphicsView->setAllLayersVisible(false);
	m_pcbGraphicsView->setLayerVisible(ViewLayer::Copper0, true);
	m_pcbGraphicsView->hideConnectors(true);

	painter.begin(&image);
	m_pcbGraphicsView->render(&painter);
	painter.end();


	QSvgGenerator svgGenerator;
	svgGenerator.setFileName("c:/fritzing2/testsvggenerator.svg");
    svgGenerator.setSize(QSize(width * 8, height * 8));
	QPainter svgPainter(&svgGenerator);
	m_pcbGraphicsView->render(&svgPainter);
	svgPainter.end();


	m_pcbGraphicsView->hideConnectors(false);
	m_pcbGraphicsView->setBackground(color);
	m_pcbGraphicsView->restoreLayerVisibility();
	// TODO: restore the selection

	QRgb black = 0;
	for (int x = 0; x < imgSize.width(); x++) {
		for (int y = 0; y < imgSize.height(); y++) {
			QRgb p = image.pixel(x, y);
			if (p != 0xffffffff) {
				image.setPixel(x, y, black);
			}
		}
	}

	bool result = image.save(fileName);
	if (!result) {
		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save %1").arg(fileName) );
	}

*/

}

QString MainWindow::mergeBoardSvg(QString & svg, ItemBase * board, int res, QSizeF & imageSize, bool flip) {
	QSizeF boardImageSize;
	QString boardSvg = getBoardSilkscreenSvg(board, res, boardImageSize);
	if (boardSvg.isEmpty()) return svg;

	//QByteArray byteArray;
	//SvgFileSplitter::changeStrokeWidth(boardSvg, res / 360.0, true, byteArray);
	imageSize = boardImageSize;

	return TextUtils::convertExtendedChars(TextUtils::mergeSvg(boardSvg /* QString(byteArray) */, svg, "", flip));
}

QString MainWindow::getBoardSilkscreenSvg(ItemBase * board, int res, QSizeF & imageSize) {
	if (board == NULL) return ___emptyString___;

	m_pcbGraphicsView->setIgnoreSelectionChangeEvents(true);

	QList<QGraphicsItem *> items = m_pcbGraphicsView->scene()->selectedItems();
	foreach (QGraphicsItem * item, items) {
		item->setSelected(false);
	}
	board->setSelected(true);
	LayerList viewLayerIDs;
	viewLayerIDs << ViewLayer::Silkscreen1;
	bool empty;
	QString svg = m_pcbGraphicsView->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, true, imageSize, board, res, true, false, false, empty);
	board->setSelected(false);
	foreach (QGraphicsItem * item, items) {
		item->setSelected(true);
	}

	m_pcbGraphicsView->setIgnoreSelectionChangeEvents(false);

	return svg;
}


void MainWindow::doExport() {
	QAction * action = qobject_cast<QAction *>(sender());
	if (action == NULL) return;

	QString actionType = action->data().toString();
	QString path = defaultSaveFolder();

	if (actionType.compare(eagleActionType) == 0) {
		exportToEagle();
		return;
	}

	if (actionType.compare(gerberActionType) == 0) {
		exportToGerber();
		return;
	}

	if (actionType.compare(bomActionType) == 0) {
		exportBOM();
		return;
	}

	if (actionType.compare(netlistActionType) == 0) {
		exportNetlist();
		return;
	}

	if (actionType.compare(svgActionType) == 0) {
		exportSvg(GraphicsUtils::IllustratorDPI, false, false);
		return;
	}

	#ifndef QT_NO_PRINTER
		QString fileExt;
		QString extFmt = fileExtFormats.value(actionType);
		DebugDialog::debug(QString("file export string %1").arg(extFmt));
		QString fileName = FolderUtils::getSaveFileName(this,
			tr("Export..."),
			path+"/"+constructFileName("", actionType),
			extFmt,
			&fileExt
		);

		if (fileName.isEmpty()) {
			return; //Cancel pressed
		} else {
			FileProgressDialog * fileProgressDialog = exportProgress();
			DebugDialog::debug(fileExt+" selected to export");
			if(!alreadyHasExtension(fileName, actionType)) {
				fileName += actionType;
			}

			if(filePrintFormats.contains(actionType)) { // PDF or PS
				QPrinter printer(QPrinter::HighResolution);
				printer.setOutputFormat(filePrintFormats[actionType]);
				printer.setOutputFileName(fileName);
				m_statusBar->showMessage(tr("Exporting..."));
				printAux(printer, true, false);
				m_statusBar->showMessage(tr("Sketch exported"), 2000);
			} else { // PNG...
				DebugDialog::debug(QString("format: %1 %2").arg(fileExt).arg(fileExportFormats[actionType]));
				exportAux(fileName,fileExportFormats[actionType], true);
			}
			delete fileProgressDialog;

		}
	#endif
}

void MainWindow::exportAux(QString fileName, QImage::Format format, bool removeBackground) 
{
	QRectF source = m_currentGraphicsView->scene()->itemsBoundingRect();
	QGraphicsItem * watermark = m_currentGraphicsView->addWatermark(":resources/images/watermark_fritzing_outline.svg");
	if (watermark) {
		watermark->setPos(source.right() - watermark->boundingRect().width(), source.bottom());
		source.adjust(0, 0, 0, watermark->boundingRect().height());
	}

	int width = source.width();
	int height = source.height();

	/*
	int width = m_currentGraphicsView->width();
	if (m_currentGraphicsView->verticalScrollBar()->isVisible()) {
		width -= m_currentGraphicsView->verticalScrollBar()->width();
	}
	int height = m_currentGraphicsView->height();
	if (m_currentGraphicsView->horizontalScrollBar()->isVisible()) {
		height -= m_currentGraphicsView->horizontalScrollBar()->height();
	}
	*/

	QSize imgSize(width, height);
	QImage image(imgSize,format);
	image.setDotsPerMeterX(39.3700787 * FSvgRenderer::printerScale());
	image.setDotsPerMeterY(39.3700787 * FSvgRenderer::printerScale());
	QPainter painter;
	QColor color;
	if (removeBackground) {
		color = m_currentGraphicsView->background();
		m_currentGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));
	}

	painter.begin(&image);
	//m_currentGraphicsView->render(&painter);
	QRectF target(0, 0, width, height);
	m_currentGraphicsView->scene()->render(&painter, target, source, Qt::KeepAspectRatio);
	painter.end();

	if (removeBackground) {
		m_currentGraphicsView->setBackground(color);
	}

	if (watermark) {
		delete watermark;
	}

	QImageWriter imageWriter(fileName);
	if (imageWriter.supportsOption(QImageIOHandler::Description)) {
		imageWriter.setText("", TextUtils::CreatedWithFritzingString);
	}
	bool result = imageWriter.write(image);
	//bool result = image.save(fileName);
	if (!result) {
		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save %1").arg(fileName) );
	}
}

void MainWindow::printAux(QPrinter &printer, bool removeBackground, bool paginate) {
	int res = printer.resolution();
	double scale2 = res / FSvgRenderer::printerScale();
	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")
		.arg(printer.width())
		.arg(printer.height())
		.arg(printer.pageRect().width())
		.arg(printer.pageRect().height())
		.arg(printer.paperRect().width())
		.arg(printer.paperRect().height())
		.arg(printer.width() / scale2)
		.arg(printer.height() / scale2) );

	// oSceneStart oSceneEnd: shows only what's visible in the viewport, not the entire view
	//QPointF oSceneStart = m_currentGraphicsView->mapToScene(QPoint(0,0));
	//QPointF oSceneEnd = m_currentGraphicsView->mapToScene(QPoint(m_currentGraphicsView->viewport()->width(), m_currentGraphicsView->viewport()->height()));
	//QRectF source(oSceneStart, oSceneEnd);
	
	QRectF source = m_currentGraphicsView->scene()->itemsBoundingRect();
	QGraphicsItem * watermark = m_currentGraphicsView->addWatermark(":resources/images/watermark_fritzing_outline.svg");
	if (watermark) {
		watermark->setPos(source.right() - watermark->boundingRect().width(), source.bottom());
		source.adjust(0, 0, 0, watermark->boundingRect().height());
	}

	QRectF target(0, 0, source.width() * scale2, source.height() * scale2);

	if (!paginate) {
		QSizeF psize((target.width() + printer.paperRect().width() - printer.width()) / res, 
					 (target.height() + printer.paperRect().height() - printer.height()) / res);
		printer.setPaperSize(psize, QPrinter::Inch);
	}

	QPainter painter;
	if (!painter.begin(&printer)) {
		if (watermark) {
			delete watermark;
		}
		QMessageBox::warning(this, tr("Fritzing"), tr("Cannot print to %1").arg(printer.docName()));
		return;
	}

	QColor color;
	if(removeBackground) {
		color = m_currentGraphicsView->background();
		m_currentGraphicsView->setBackground(QColor::fromRgb(255,255,255,255));
	}

	QList<QGraphicsItem*> selItems = m_currentGraphicsView->scene()->selectedItems();
	foreach(QGraphicsItem *item, selItems) {
		item->setSelected(false);
	}

	if (paginate) {
		int xPages = qCeil(target.width() / printer.width());
		int yPages = qCeil(target.height() / printer.height());
		int lastPage = xPages * yPages;

		int xSourcePage = qFloor(printer.width() / scale2);
		int ySourcePage = qFloor(printer.height() / scale2);

		int page = 0;
		for (int iy = 0; iy < yPages; iy++) {
			for (int ix = 0; ix < xPages; ix++) {
				// render to printer:
				QRectF pSource((ix * xSourcePage) + source.left(), 
							   (iy * ySourcePage) + source.top(), 
							   qMin(xSourcePage, (int) source.width() - (ix * xSourcePage)), 
							   qMin(ySourcePage, (int) source.height() - (iy * ySourcePage)));
				QRectF pTarget(0, 0, pSource.width() * scale2, pSource.height() * scale2);
				m_currentGraphicsView->scene()->render(&painter, pTarget, pSource, Qt::KeepAspectRatio);
				if (++page < lastPage) {
					printer.newPage();
				}
			}
		}
	}
	else {
		m_currentGraphicsView->scene()->render(&painter, target, source, Qt::KeepAspectRatio);
	}

	foreach(QGraphicsItem *item, selItems) {
		item->setSelected(true);
	}

	if(removeBackground) {
		m_currentGraphicsView->setBackground(color);
	}

	if (watermark) {
		delete watermark;
	}

	DebugDialog::debug(QString("source w:%1 h:%2 target w:%5 h:%6 pres:%3 screenres:%4")
		.arg(source.width())
		.arg(source.height()).arg(res).arg(this->physicalDpiX())
		.arg(target.width()).arg(target.height()) );

	//#ifndef QT_NO_CONCURRENT
		//QProgressDialog dialog;
		//dialog.setLabelText(message);
 	//
		// Create a QFutureWatcher and conncect signals and slots.
		//QFutureWatcher<void> futureWatcher;
		//QObject::connect(&futureWatcher, SIGNAL(finished()), &dialog, SLOT(reset()));
		//QObject::connect(&dialog, SIGNAL(canceled()), &futureWatcher, SLOT(cancel()));
		//QObject::connect(&futureWatcher, SIGNAL(progressRangeChanged(int, int)), &dialog, SLOT(setRange(int, int)));
		//QObject::connect(&futureWatcher, SIGNAL(progressValueChanged(int)), &dialog, SLOT(setValue(int)));
	//
		// Start the computation.
		//futureWatcher.setFuture(QtConcurrent::run(painter,&QPainter::end));
		//dialog.exec();
	//
		//futureWatcher.waitForFinished();
	//#endif

	//#ifdef QT_NO_CONCURRENT
		painter.end();
	//#endif

}

bool MainWindow::saveAsAux(const QString & fileName) {
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("Fritzing"),
                             tr("Cannot write file %1:\n%2.")
                             .arg(fileName)
                             .arg(file.errorString()));
        return false;
    }

    file.close();

    setReadOnly(false);
    //FritzingWindow::saveAsAux(fileName);

	saveAsAuxAux(fileName);
	m_autosaveNeeded = false;
	undoStackCleanChanged(true);

	m_statusBar->showMessage(tr("Saved '%1'").arg(fileName), 2000);
    setCurrentFile(fileName, true, true);

	if(m_restarting && m_fwFilename != ___emptyString___) {
		QSettings settings;
		settings.setValue("lastOpenSketch",m_fwFilename);
	}

   // mark the stack clean so we update the window dirty flag
    m_undoStack->setClean();

	// slam it here in case we were modified due to m_linkedProgramFiles changes
	setWindowModified(false);

    m_saveAct->setEnabled(true);

	return true;
}

void MainWindow::saveAsAuxAux(const QString & fileName) {
    QApplication::setOverrideCursor(Qt::WaitCursor);

	connectStartSave(true);

	QDir dir(this->m_fzzFolder);
	QStringList nameFilters("*" + FritzingSketchExtension);
	QFileInfoList fileList = dir.entryInfoList(nameFilters, QDir::Files | QDir::NoSymLinks);
	foreach (QFileInfo fileInfo, fileList) {
		QFile file(fileInfo.absoluteFilePath());
		file.remove();
	}

	QString fzName = dir.absoluteFilePath(QFileInfo(fileName).completeBaseName() + FritzingSketchExtension); 
	m_sketchModel->save(fzName, false);

	saveAsShareable(fileName, false);

	connectStartSave(false);

	QApplication::restoreOverrideCursor();
}


void MainWindow::createExportActions() {

	m_saveAct = new QAction(tr("&Save"), this);
	m_saveAct->setShortcut(tr("Ctrl+S"));
	m_saveAct->setStatusTip(tr("Save the current sketch"));
	connect(m_saveAct, SIGNAL(triggered()), this, SLOT(save()));

	m_saveAsAct = new QAction(tr("&Save As..."), this);
	m_saveAsAct->setShortcut(tr("Shift+Ctrl+S"));
	m_saveAsAct->setStatusTip(tr("Save the current sketch"));
	connect(m_saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));

	m_shareOnlineAct = new QAction(tr("Share online..."), this);
	m_shareOnlineAct->setStatusTip(tr("Post a project to the Fritzing website"));
	connect(m_shareOnlineAct, SIGNAL(triggered()), this, SLOT(shareOnline()));

	m_exportJpgAct = new QAction(tr("JPG..."), this);
	m_exportJpgAct->setData(jpgActionType);
	m_exportJpgAct->setStatusTip(tr("Export the visible area of the current sketch as a JPG image"));
	connect(m_exportJpgAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportPngAct = new QAction(tr("PNG..."), this);
	m_exportPngAct->setData(pngActionType);
	m_exportPngAct->setStatusTip(tr("Export the visible area of the current sketch as a PNG image"));
	connect(m_exportPngAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportPsAct = new QAction(tr("PostScript..."), this);
	m_exportPsAct->setData(psActionType);
	m_exportPsAct->setStatusTip(tr("Export the visible area of the current sketch as a PostScript image"));
	connect(m_exportPsAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportPdfAct = new QAction(tr("PDF..."), this);
	m_exportPdfAct->setData(pdfActionType);
	m_exportPdfAct->setStatusTip(tr("Export the visible area of the current sketch as a PDF image"));
	connect(m_exportPdfAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportSvgAct = new QAction(tr("SVG..."), this);
	m_exportSvgAct->setData(svgActionType);
	m_exportSvgAct->setStatusTip(tr("Export the current sketch as an SVG image"));
	connect(m_exportSvgAct, SIGNAL(triggered()), this, SLOT(doExport()));

    m_exportBomAct = new QAction(tr("List of parts (&Bill of Materials)..."), this);
    m_exportBomAct->setData(bomActionType);
    m_exportBomAct->setStatusTip(tr("Save a Bill of Materials (BoM)/Shopping List as text"));
    connect(m_exportBomAct, SIGNAL(triggered()), this, SLOT(doExport()));

    m_exportNetlistAct = new QAction(tr("XML Netlist..."), this);
    m_exportNetlistAct->setData(netlistActionType);
    m_exportNetlistAct->setStatusTip(tr("Save a netlist in XML format"));
    connect(m_exportNetlistAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportEagleAct = new QAction(tr("Eagle..."), this);
	m_exportEagleAct->setData(eagleActionType);
	m_exportEagleAct->setStatusTip(tr("Export the current sketch to Eagle CAD"));
	connect(m_exportEagleAct, SIGNAL(triggered()), this, SLOT(doExport()));

        m_exportGerberAct = new QAction(tr("Extended Gerber (RS-274X)..."), this);
	m_exportGerberAct->setData(gerberActionType);
        m_exportGerberAct->setStatusTip(tr("Export the current sketch to Extended Gerber format (RS-274X) for professional PCB production"));
	connect(m_exportGerberAct, SIGNAL(triggered()), this, SLOT(doExport()));

	m_exportEtchablePdfAct = new QAction(tr("Etchable (PDF)..."), this);
	m_exportEtchablePdfAct->setStatusTip(tr("Export the current sketch to PDF for DIY PCB production (photoresist)"));
	m_exportEtchablePdfAct->setProperty("svg", false);
	m_exportEtchablePdfAct->setProperty("flip", false);
	connect(m_exportEtchablePdfAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));

	m_exportEtchablePdfFlipAct = new QAction(tr("Etchable mirrored (PDF)..."), this);
	m_exportEtchablePdfFlipAct->setStatusTip(tr("Export the current sketch to PDF for DIY PCB production (tone transfer)"));
	m_exportEtchablePdfFlipAct->setProperty("svg", false);
	m_exportEtchablePdfFlipAct->setProperty("flip", true);
	connect(m_exportEtchablePdfFlipAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));

	m_exportEtchableSvgAct = new QAction(tr("Etchable (SVG)..."), this);
	m_exportEtchableSvgAct->setStatusTip(tr("Export the current sketch to SVG for DIY PCB production (photoresist)"));
	m_exportEtchableSvgAct->setProperty("svg", true);
	m_exportEtchableSvgAct->setProperty("flip", false);
	connect(m_exportEtchableSvgAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));

	m_exportEtchableSvgFlipAct = new QAction(tr("Etchable mirrored (SVG)..."), this);
	m_exportEtchableSvgFlipAct->setStatusTip(tr("Export the current sketch to SVG for DIY PCB production (tone transfer)"));
	m_exportEtchableSvgFlipAct->setProperty("svg", true);
	m_exportEtchableSvgFlipAct->setProperty("flip", true);
	connect(m_exportEtchableSvgFlipAct, SIGNAL(triggered()), this, SLOT(exportEtchable()));

	/*m_pageSetupAct = new QAction(tr("&Page Setup..."), this);
	m_pageSetupAct->setShortcut(tr("Shift+Ctrl+P"));
	m_pageSetupAct->setStatusTip(tr("Setup the current sketch page"));
	connect(m_pageSetupAct, SIGNAL(triggered()), this, SLOT(pageSetup()));*/

	m_printAct = new QAction(tr("&Print..."), this);
	m_printAct->setShortcut(tr("Ctrl+P"));
	m_printAct->setStatusTip(tr("Print the current view"));
	connect(m_printAct, SIGNAL(triggered()), this, SLOT(print()));

}

void MainWindow::exportToEagle() {

	QString text =
		tr("This will soon provide an export of your Fritzing sketch to the EAGLE layout "
		"software. If you'd like to have more exports to your favourite EDA tool, please let "
		"us know, or contribute.");
/*
	QString text =
		tr("The Eagle export module is very experimental.  If anything breaks or behaves "
		"strangely, please let us know.");
*/

	QMessageBox::information(this, tr("Fritzing"), text);

	Fritzing2Eagle eagle = Fritzing2Eagle(m_pcbGraphicsView);

	/*
	QList <ItemBase*> partList;

	// bail out if something is wrong
	// TODO: show an error in QMessageBox
    if(m_currentWidget == NULL) {
		return;
	}

    m_pcbGraphicsView->collectParts(partList);

	QString exportInfoString = tr("parts include:\n");
	QString exportString = tr("GRID INCH 0.005\n");


	for(int i=0; i < partList.size(); i++){
		QString label = partList.at(i)->instanceTitle();
		QString desc = partList.at(i)->title();

		QHash<QString,QString> properties = partList.at(i)->modelPartShared()->properties();
		QString package = properties["package"];
		if (package == NULL) {
			package = tr("*** package not specified ***");
		}

		exportInfoString += label + tr(" which is a ") + desc + tr(" in a ") + package + tr(" package.\n");
	}
	QMessageBox::information(this, tr("Fritzing"), exportInfoString);
	*/

	/*
	QFile fp( fileName );
	fp.open(QIODevice::WriteOnly);
	fp.write(bom.toUtf8(),bom.length());
	fp.close();
	*/


	/*
	GRID INCH 0.005
	USE '/Applications/eclipse/eagle/lbr/fritzing.lbr';
	ADD RESISTOR@fritzing 'R_1' R0.000 (2.3055117 2.1307087);
	ADD LED@fritzing 'L_2' R0.000 (5.423622 2.425197);
	GRID LAST;
	*/
}

void MainWindow::exportSvg(double res, bool selectedItems, bool flatten) {
	QString path = defaultSaveFolder();
	QString fileExt;
	QString fileName = FolderUtils::getSaveFileName(this,
		tr("Export SVG..."),
		path+"/"+constructFileName("", svgActionType),
		fileExtFormats[svgActionType],
		&fileExt
		);

	if (fileName.isEmpty()) return;

	FileProgressDialog * fileProgressDialog = exportProgress();
	LayerList viewLayerIDs;
	foreach (ViewLayer * viewLayer, m_currentGraphicsView->viewLayers()) {
		if (viewLayer == NULL) continue;
		if (!viewLayer->visible()) continue;

		viewLayerIDs << viewLayer->viewLayerID();
	}

	QSizeF imageSize;
	bool empty;
	QString svg = m_currentGraphicsView->renderToSVG(FSvgRenderer::printerScale(), viewLayerIDs, false, imageSize, NULL, res, selectedItems, flatten, false, empty);
	if (svg.isEmpty()) {
		// tell the user something reasonable
		return;
	}

	if (selectedItems == false && flatten == false) {
		exportSvgWatermark(svg, res);
	}

	QFile file(fileName);
	file.open(QIODevice::WriteOnly);
	QTextStream out(&file);
	out.setCodec("UTF-8");
	out << TextUtils::convertExtendedChars(svg);
	file.close();
	delete fileProgressDialog;

}

void MainWindow::exportSvgWatermark(QString & svg, double res)
{
	QFile file(":/resources/images/watermark_fritzing_outline.svg");
	if (!file.open(QFile::ReadOnly)) return;

	QString watermarkSvg = file.readAll();
	file.close();

	if (!watermarkSvg.contains("<svg")) return;

	QXmlStreamReader wStreamReader(watermarkSvg);
	QSizeF watermarkSize = FSvgRenderer::parseForWidthAndHeight(wStreamReader);

	QXmlStreamReader streamReader(svg);
	QSizeF svgSize = FSvgRenderer::parseForWidthAndHeight(streamReader);

	SvgFileSplitter splitter;
	bool result = splitter.splitString(watermarkSvg, "watermark");
	if (!result) return;

	result = splitter.normalize(res, "watermark", false);
	if (!result) return;

	QString transWatermark = splitter.shift((svgSize.width() - watermarkSize.width()) * res, svgSize.height() * res, "watermark", true);
	QString newSvg = TextUtils::makeSVGHeader(1, res, svgSize.width(), svgSize.height() + watermarkSize.height()) + transWatermark + "</svg>";
	svg = TextUtils::mergeSvg(newSvg, svg, "", false);
}

void MainWindow::exportBOM() {

    // bail out if something is wrong
    // TODO: show an error in QMessageBox
	if (m_currentWidget == NULL) {
        return;
    }

	QString bomTemplate;
	QFile file(":/resources/templates/bom.html");
	if (file.open(QFile::ReadOnly)) {
		bomTemplate = file.readAll();
		file.close();
	}
	else {
		return;
	}

	QString bomRowTemplate;
	QFile file2(":/resources/templates/bom_row.html");
	if (file2.open(QFile::ReadOnly)) {
		bomRowTemplate = file2.readAll();
		file2.close();
	}
	else {
		return;
	}

    QList <ItemBase*> partList;
    QMap<QString, int> shoppingList;
	QHash<QString, ItemBase *> descrs;

	m_currentGraphicsView->collectParts(partList);

    qSort(partList.begin(), partList.end(), sortPartList);

	foreach (ItemBase * itemBase, partList) {
		if (itemBase->itemType() != ModelPart::Part) continue;

        QString label = itemBase->instanceTitle();
        QString desc = itemBase->title();

        if(!shoppingList.contains(desc)) {
            shoppingList.insert(desc, 1);
			descrs.insert(desc, itemBase);
        }
        else {
            shoppingList[desc]++;
        }
    }


	QString assemblyString;
	foreach (ItemBase * itemBase, partList) {
		if (itemBase->itemType() != ModelPart::Part) continue;

		assemblyString += bomRowTemplate.arg(itemBase->instanceTitle()).arg(itemBase->title()).arg(getBomProps(itemBase));
    }

	QString shoppingListString;
    QMapIterator<QString, int> it(shoppingList);
    while (it.hasNext()) {
        it.next();
		ItemBase * itemBase = descrs.value(it.key());

		shoppingListString += bomRowTemplate.arg(it.value()).arg(it.key()).arg(getBomProps(itemBase));
    }

	QString bom = bomTemplate
		.arg("Fritzing Bill of Materials")
		.arg(QFileInfo(m_fwFilename).fileName())
		.arg(m_fwFilename)
		.arg(QDateTime::currentDateTime().toString("dddd, MMMM d yyyy, hh:mm:ss"))
		.arg(assemblyString)
		.arg(shoppingListString)
		.arg(QString("%1.%2.%3").arg(Version::majorVersion()).arg(Version::minorVersion()).arg(Version::minorSubVersion()));


    QString path = defaultSaveFolder();

    QString fileExt;
    QString extFmt = fileExtFormats.value(bomActionType);
    QString fname = path+"/"+constructFileName("bom", bomActionType);
    DebugDialog::debug(QString("fname %1\n%2").arg(fname).arg(extFmt));

    QString fileName = FolderUtils::getSaveFileName(this,
            tr("Export Bill of Materials (BoM)..."),
            fname,
            extFmt,
            &fileExt
    );

    if (fileName.isEmpty()) {
		return; //Cancel pressed
    }

	FileProgressDialog * fileProgressDialog = exportProgress();
    DebugDialog::debug(fileExt+" selected to export");
    if(!alreadyHasExtension(fileName, bomActionType)) {
		fileName += bomActionType;
    }

    QFile fp(fileName);
   	if (fp.open(QIODevice::WriteOnly)) {
   		QTextStream out(&fp);
		out.setCodec("UTF-8");
   		out << bom;
		fp.close();
	}
	else {
		QMessageBox::warning(this, tr("Fritzing"), tr("Unable to save BOM file, but the text is on the clipboard."));
	}

	if (fp.exists()) {
		QDesktopServices::openUrl(QString("file:///%1").arg(fileName));
	}

	QClipboard *clipboard = QApplication::clipboard();
	if (clipboard != NULL) {
		clipboard->setText(bom);
	}
	delete fileProgressDialog;
}

void MainWindow::exportNetlist() {
	QHash<ConnectorItem *, int> indexer;
	QList< QList<ConnectorItem *>* > netList;
	this->m_currentGraphicsView->collectAllNets(indexer, netList, true, m_currentGraphicsView->boardLayers() > 1);

	QDomDocument doc;
	doc.setContent(QString("<?xml version='1.0' encoding='UTF-8'?>\n") + TextUtils::CreatedWithFritzingXmlComment);
	QDomElement netlist = doc.createElement("netlist");
	doc.appendChild(netlist);
	netlist.setAttribute("sketch", QFileInfo(m_fwFilename).fileName());
    netlist.setAttribute("date", QDateTime::currentDateTime().toString());

	// TODO: filter out 'ignore' connectors

	QList< QList<ConnectorItem *>* > deleteNets;
	foreach (QList<ConnectorItem *> * net, netList) {
		QList<ConnectorItem *> deleteItems;
		foreach (ConnectorItem * connectorItem, *net) {
			ErcData * ercData = connectorItem->connectorSharedErcData();
			if (ercData == NULL) continue;

			if (ercData->ignore() == ErcData::Always) {
				deleteItems.append(connectorItem);
			}
			else if ((ercData->ignore() == ErcData::IfUnconnected) && (net->count() == 1)) {
				deleteItems.append(connectorItem);
			}
		}

		foreach (ConnectorItem * connectorItem, deleteItems) {
			net->removeOne(connectorItem);
		}
		if (net->count() == 0) {
			deleteNets.append(net);
		}
	}

	foreach (QList<ConnectorItem *> * net, deleteNets) {
		netList.removeOne(net);
	}

	foreach (QList<ConnectorItem *> * net, netList) {
		QDomElement netElement = doc.createElement("net");
		netlist.appendChild(netElement);
		foreach (ConnectorItem * connectorItem, *net) {
			QDomElement connector = doc.createElement("connector");
			netElement.appendChild(connector);
			connector.setAttribute("id", connectorItem->connectorSharedID());
			connector.setAttribute("name", connectorItem->connectorSharedName());
			QDomElement part = doc.createElement("part");
			connector.appendChild(part);
			ItemBase * itemBase = connectorItem->attachedTo();
			part.setAttribute("id", itemBase->id());
			part.setAttribute("label", itemBase->instanceTitle());
			part.setAttribute("title", itemBase->title());
			ErcData * ercData = connectorItem->connectorSharedErcData();
			if (ercData != NULL) {
				QDomElement erc = doc.createElement("erc");
				if (ercData->writeToElement(erc, doc)) {
					connector.appendChild(erc);
				}
			}
		}
	}

	foreach (QList<ConnectorItem *> * net, netList) {
		delete net;
	}
	netList.clear();

    QString path = defaultSaveFolder();

    QString fileExt;
    QString extFmt = fileExtFormats.value(netlistActionType);
    QString fname = path + "/" +constructFileName("netlist", netlistActionType);
    //DebugDialog::debug(QString("fname %1\n%2").arg(fname).arg(extFmt));

    QString fileName = FolderUtils::getSaveFileName(this,
            tr("Export Netlist..."),
            fname,
            extFmt,
            &fileExt
    );

    if (fileName.isEmpty()) {
		return; //Cancel pressed
    }

	FileProgressDialog * fileProgressDialog = exportProgress();
    //DebugDialog::debug(fileExt + " selected to export");
    if(!alreadyHasExtension(fileName, netlistActionType)) {
		fileName += netlistActionType;
    }

    QFile fp( fileName );
    fp.open(QIODevice::WriteOnly);
    fp.write(doc.toByteArray());
    fp.close();

	QClipboard *clipboard = QApplication::clipboard();
	if (clipboard != NULL) {
		clipboard->setText(doc.toByteArray());
	}
	delete fileProgressDialog;


}

FileProgressDialog * MainWindow::exportProgress() {
    return (new FileProgressDialog("Exporting...", 0, this));
}

void MainWindow::exportNormalizedSVG() {
	exportSvg(GraphicsUtils::StandardFritzingDPI, true, false);
}

void MainWindow::exportNormalizedFlattenedSVG() {
	exportSvg(GraphicsUtils::StandardFritzingDPI, true, true);
}

QString MainWindow::getBomProps(ItemBase * itemBase)
{
	if (itemBase == NULL) return "";

	QStringList keys;
	QHash<QString, QString> properties = HtmlInfoView::getPartProperties(itemBase->modelPart(), itemBase, false, keys);
	QString pString;
	foreach (QString key, keys) {
		if (key.compare("family") == 0) continue;

		QString value = properties.value(key);

		QWidget widget;
		QWidget * resultWidget = NULL;
		QString resultKey, resultValue;
		itemBase->collectExtraInfo(&widget, properties.value("family"), key, value, false, resultKey, resultValue, resultWidget);
		if (resultValue.isEmpty()) continue;

		pString += ItemBase::translatePropertyName(resultKey) + " " + resultValue + "; ";
	}

	if (pString.length() > 2) pString.chop(2);

	return pString;
}

void MainWindow::exportToGerber() {

    //NOTE: this assumes just one board per sketch

    // grab the list of parts
    ItemBase * board = m_pcbGraphicsView->findBoard();
    // barf an error if there's no board
    if (!board) {
        QMessageBox::critical(this, tr("Fritzing"),
                   tr("Your sketch does not have a board yet!  Please add a PCB in order to export to Gerber."));
        return;
    }

    QString exportDir = QFileDialog::getExistingDirectory(this, tr("Choose a folder for exporting"),
                                             defaultSaveFolder(),
                                             QFileDialog::ShowDirsOnly
                                             | QFileDialog::DontResolveSymlinks);

	if (exportDir.isEmpty()) return;

	FileProgressDialog * fileProgressDialog = exportProgress();

	FolderUtils::setOpenSaveFolder(exportDir);
	GerberGenerator::exportToGerber(m_fwFilename, exportDir, board, m_pcbGraphicsView, true);
	m_statusBar->showMessage(tr("Sketch exported to Gerber"), 2000);

	delete fileProgressDialog;
}

void MainWindow::exportToGerber(const QString & exportDir) {
	GerberGenerator::exportToGerber(m_fwFilename, exportDir, NULL, m_pcbGraphicsView, false);
}

void MainWindow::connectStartSave(bool doConnect) {

	if (doConnect) {
		connect(m_sketchModel->root(), SIGNAL(startSaveInstances(const QString &, ModelPart *, QXmlStreamWriter &)),
				this, SLOT(startSaveInstancesSlot(const QString &, ModelPart *, QXmlStreamWriter &)), Qt::DirectConnection);
	}
	else {
		disconnect(m_sketchModel->root(), SIGNAL(startSaveInstances(const QString &, ModelPart *, QXmlStreamWriter &)),
				this, SLOT(startSaveInstancesSlot(const QString &, ModelPart *, QXmlStreamWriter &)));
	}
}
