1 /*******************************************************************
2 
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 "resizableboard.h"
28 #include "../utils/resizehandle.h"
29 #include "../utils/graphicsutils.h"
30 #include "../utils/folderutils.h"
31 #include "../utils/textutils.h"
32 #include "../fsvgrenderer.h"
33 #include "../sketch/infographicsview.h"
34 #include "../svg/svgfilesplitter.h"
35 #include "../commands.h"
36 #include "moduleidnames.h"
37 #include "../layerattributes.h"
38 #include "../debugdialog.h"
39 #include "../svg/gerbergenerator.h"
40 
41 #include <QHBoxLayout>
42 #include <QVBoxLayout>
43 #include <QFrame>
44 #include <QLabel>
45 #include <QLineEdit>
46 #include <QPushButton>
47 #include <QImageReader>
48 #include <QMessageBox>
49 #include <QRegExp>
50 #include <qmath.h>
51 #include <qnumeric.h>
52 
53 QList<QString> PaperSizeNames;
54 QList<QSizeF> PaperSizeDimensions;
55 
56 static QHash<QString, QString> NamesToXmlNames;
57 static QHash<QString, QString> XmlNamesToNames;
58 
59 static QHash<QString, QString> BoardLayerTemplates;
60 static QHash<QString, QString> SilkscreenLayerTemplates;
61 static QHash<QString, QString> Silkscreen0LayerTemplates;
62 static const int LineThickness = 8;
63 static const QRegExp HeightExpr("height=\\'\\d*px");
64 static QString StandardCustomBoardExplanation;
65 
66 QStringList Board::BoardImageNames;
67 QStringList Board::NewBoardImageNames;
68 
69 const double ResizableBoard::CornerHandleSize = 7.0;
70 
71 static const double JND = (double) 0.01;
72 
73 QString Board::OneLayerTranslated;
74 QString Board::TwoLayersTranslated;
75 
Board(ModelPart * modelPart,ViewLayer::ViewID viewID,const ViewGeometry & viewGeometry,long id,QMenu * itemMenu,bool doLabel)76 Board::Board( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel)
77 	: PaletteItem(modelPart, viewID, viewGeometry, id, itemMenu, doLabel)
78 {
79     m_svgOnly = true;
80 	m_fileNameComboBox = NULL;
81     if (isBoard(modelPart)) {
82         if (modelPart->localProp("layers").isNull()) {
83             modelPart->setLocalProp("layers", modelPart->properties().value("layers"));
84         }
85         if (itemType() == ModelPart::Board && !modelPart->properties().keys().contains("filename")) {
86             // deal with old style custom boards
87             modelPart->modelPartShared()->setProperty("filename", "", false);
88         }
89     }
90 
91     if (StandardCustomBoardExplanation.isEmpty()) {
92         StandardCustomBoardExplanation = tr("\n\nA custom board svg typically has one or two silkscreen layers and one board layer.\n") +
93                                             tr("Have a look at the circle_pcb.svg file in your Fritzing installation folder at parts/svg/core/pcb/.\n\n");
94     }
95 
96     if (NamesToXmlNames.count() == 0) {
97         NamesToXmlNames.insert("copper bottom", "copper0");
98         NamesToXmlNames.insert("copper top", "copper1");
99         NamesToXmlNames.insert("silkscreen bottom", "silkscreen0");
100         NamesToXmlNames.insert("silkscreen top", "silkscreen");
101         foreach (QString key, NamesToXmlNames.keys()) {
102             XmlNamesToNames.insert(NamesToXmlNames.value(key), key);
103         }
104     }
105 
106 }
107 
~Board()108 Board::~Board() {
109 }
110 
paintHover(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)111 void Board::paintHover(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
112 {
113 	Q_UNUSED(widget);
114 	Q_UNUSED(option);
115 	painter->save();
116 	painter->setOpacity(0);
117 	painter->fillPath(this->hoverShape(), QBrush(HoverColor));
118 	painter->restore();
119 }
120 
collectValues(const QString & family,const QString & prop,QString & value)121 QStringList Board::collectValues(const QString & family, const QString & prop, QString & value) {
122 	if (prop.compare("layers", Qt::CaseInsensitive) == 0) {
123         QString realValue = modelPart()->localProp("layers").toString();
124         if (!realValue.isEmpty()) {
125             value = realValue;
126         }
127 		QStringList result;
128 		if (OneLayerTranslated.isEmpty()) {
129 			OneLayerTranslated = tr("one layer (single-sided)");
130 		}
131 		if (TwoLayersTranslated.isEmpty()) {
132 			TwoLayersTranslated = tr("two layers (double-sided)");
133 		}
134 
135 		result.append(OneLayerTranslated);
136 		result.append(TwoLayersTranslated);
137 
138 		if (value == "1") {
139 			value = OneLayerTranslated;
140 		}
141 		else if (value == "2") {
142 			value = TwoLayersTranslated;
143 		}
144 
145 		return result;
146 	}
147 
148 
149 	QStringList result = PaletteItem::collectValues(family, prop, value);
150     if (prop.compare("shape", Qt::CaseInsensitive) == 0 && isBoard(this) && !this->moduleID().contains(ModuleIDNames::BoardLogoImageModuleIDName)) {
151         result.removeAll("Custom Shape");
152     }
153 
154     return result;
155 
156 }
157 
collectExtraInfo(QWidget * parent,const QString & family,const QString & prop,const QString & value,bool swappingEnabled,QString & returnProp,QString & returnValue,QWidget * & returnWidget,bool & hide)158 bool Board::collectExtraInfo(QWidget * parent, const QString & family, const QString & prop, const QString & value, bool swappingEnabled, QString & returnProp, QString & returnValue, QWidget * & returnWidget, bool & hide)
159 {
160 	if (prop.compare("filename", Qt::CaseInsensitive) == 0 && isBoard(this)) {
161         setupLoadImage(parent, family, prop, value, swappingEnabled, returnProp, returnValue, returnWidget);
162 		return true;
163 	}
164 
165 	return PaletteItem::collectExtraInfo(parent, family, prop, value, swappingEnabled, returnProp, returnValue, returnWidget, hide);
166 }
167 
rotation45Allowed()168 bool Board::rotation45Allowed() {
169 	return false;
170 }
171 
stickyEnabled()172 bool Board::stickyEnabled() {
173 	return false;
174 }
175 
isPlural()176 ItemBase::PluralType Board::isPlural() {
177 	return Plural;
178 }
179 
canFindConnectorsUnder()180 bool Board::canFindConnectorsUnder() {
181 	return false;
182 }
183 
184 
isBoard(ItemBase * itemBase)185 bool Board::isBoard(ItemBase * itemBase) {
186     if (qobject_cast<Board *>(itemBase) == NULL) return false;
187 
188     return isBoard(itemBase->modelPart());
189 }
190 
isBoard(ModelPart * modelPart)191 bool Board::isBoard(ModelPart * modelPart) {
192     if (modelPart == NULL) return false;
193 
194     switch (modelPart->itemType()) {
195         case ModelPart::Board:
196             return true;
197         case ModelPart::ResizableBoard:
198             return true;
199         case ModelPart::Logo:
200             return modelPart->family().contains("pcb", Qt::CaseInsensitive);
201         default:
202             return false;
203     }
204 }
205 
setupLoadImage(QWidget * parent,const QString & family,const QString & prop,const QString & value,bool swappingEnabled,QString & returnProp,QString & returnValue,QWidget * & returnWidget)206 void Board::setupLoadImage(QWidget * parent, const QString & family, const QString & prop, const QString & value, bool swappingEnabled, QString & returnProp, QString & returnValue, QWidget * & returnWidget)
207 {
208     Q_UNUSED(returnValue);
209     Q_UNUSED(value);
210     Q_UNUSED(prop);
211     Q_UNUSED(family);
212     Q_UNUSED(parent);
213 
214 	returnProp = tr("image file");
215 
216 	QFrame * frame = new QFrame();
217 	frame->setObjectName("infoViewPartFrame");
218 	QVBoxLayout * vboxLayout = new QVBoxLayout();
219 	vboxLayout->setContentsMargins(0, 0, 0, 0);
220 	vboxLayout->setSpacing(0);
221 	vboxLayout->setMargin(0);
222 
223 	QComboBox * comboBox = new QComboBox();
224 	comboBox->setObjectName("infoViewComboBox");
225 	comboBox->setEditable(false);
226 	comboBox->setEnabled(swappingEnabled);
227 	m_fileNameComboBox = comboBox;
228 
229 	setFileNameItems();
230 
231 	connect(comboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(fileNameEntry(const QString &)));
232 
233 	QPushButton * button = new QPushButton (tr("load image file"));
234 	button->setObjectName("infoViewButton");
235 	connect(button, SIGNAL(pressed()), this, SLOT(prepLoadImage()));
236 	button->setEnabled(swappingEnabled);
237 
238 	vboxLayout->addWidget(comboBox);
239 	vboxLayout->addWidget(button);
240 
241 	frame->setLayout(vboxLayout);
242 	returnWidget = frame;
243 
244 	returnProp = "";
245 }
246 
setFileNameItems()247 void Board::setFileNameItems() {
248 	if (m_fileNameComboBox == NULL) return;
249 
250 	m_fileNameComboBox->addItems(getImageNames());
251 	m_fileNameComboBox->addItems(getNewImageNames());
252 
253 	int ix = 0;
254 	foreach (QString name, getImageNames()) {
255 		if (prop("lastfilename").contains(name)) {
256 			m_fileNameComboBox->setCurrentIndex(ix);
257 			return;
258 		}
259 		ix++;
260 	}
261 
262 	foreach (QString name, getNewImageNames()) {
263 		if (prop("lastfilename").contains(name)) {
264 			m_fileNameComboBox->setCurrentIndex(ix);
265 			return;
266 		}
267 		ix++;
268 	}
269 }
270 
getImageNames()271 QStringList & Board::getImageNames() {
272 	return BoardImageNames;
273 }
274 
getNewImageNames()275 QStringList & Board::getNewImageNames() {
276 	return NewBoardImageNames;
277 }
278 
fileNameEntry(const QString & filename)279 void Board::fileNameEntry(const QString & filename) {
280 	foreach (QString name, getImageNames()) {
281 		if (filename.compare(name) == 0) {
282 			QString f = FolderUtils::getApplicationSubFolderPath("parts") + "/svg/core/pcb/" + filename + ".svg";
283 			prepLoadImageAux(f, false);
284 			return;
285 		}
286 	}
287 
288 	prepLoadImageAux(filename, true);
289 }
290 
prepLoadImage()291 void Board::prepLoadImage() {
292 	QString imagesStr = tr("Images");
293 	imagesStr += " (";
294     if (!m_svgOnly) {
295 	    QList<QByteArray> supportedImageFormats = QImageReader::supportedImageFormats();
296 	    foreach (QByteArray ba, supportedImageFormats) {
297 		    imagesStr += "*." + QString(ba) + " ";
298 	    }
299     }
300 	if (!imagesStr.contains("svg")) {
301 		imagesStr += "*.svg";
302 	}
303 	imagesStr += ")";
304 	QString fileName = FolderUtils::getOpenFileName(
305 		NULL,
306 		tr("Select an image file to load"),
307 		"",
308 		imagesStr
309 	);
310 
311 	if (fileName.isEmpty()) return;
312 
313     if (!checkImage(fileName)) return;
314 
315 	prepLoadImageAux(fileName, true);
316 }
317 
checkImage(const QString & filename)318 bool Board::checkImage(const QString & filename) {
319     QFile file(filename);
320 
321 	QString errorStr;
322 	int errorLine;
323 	int errorColumn;
324 
325 	QDomDocument domDocument;
326 
327 	if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
328 		unableToLoad(filename, tr("due to an xml problem: %1 line:%2 column:%3").arg(errorStr).arg(errorLine).arg(errorColumn));
329 		return false;
330 	}
331 
332     QDomElement root = domDocument.documentElement();
333 	if (root.tagName() != "svg") {
334 		unableToLoad(filename, tr("because the xml is not correctly formatted"));
335 		return false;
336 	}
337 
338     QList<QDomElement> elements;
339     TextUtils::findElementsWithAttribute(root, "id", elements);
340     int layers = 0;
341     QList<QDomElement> boardElements;
342     int silk0Layers = 0;
343     int silk1Layers = 0;
344     bool boardHasChildren = false;
345     foreach (QDomElement element, elements) {
346         QString id = element.attribute("id");
347         ViewLayer::ViewLayerID viewLayerID = ViewLayer::viewLayerIDFromXmlString(id);
348         if (viewLayerID != ViewLayer::UnknownLayer) {
349             layers++;
350             if (viewLayerID == ViewLayer::Board) {
351                 boardElements << element;
352                 if (element.childNodes().count() > 0) {
353                     boardHasChildren = true;
354                 }
355             }
356             else if (viewLayerID == ViewLayer::Silkscreen1) {
357                 silk1Layers++;
358             }
359             else if (viewLayerID == ViewLayer::Silkscreen0) {
360                 silk0Layers++;
361             }
362         }
363     }
364 
365     if (boardElements.count() == 1 && !boardHasChildren) {
366         unableToLoad(filename, tr("the <board> element contains no shape elements") + StandardCustomBoardExplanation);
367         return false;
368     }
369 
370     if ((boardElements.count() == 1) && ((silk1Layers == 1) || (silk0Layers == 1))) {
371         moreCheckImage(filename);
372         return true;
373     }
374 
375     if (boardElements.count() > 1) {
376         unableToLoad(filename, tr("because there are multiple <board> layers") + StandardCustomBoardExplanation);
377         return false;
378     }
379 
380     if (silk1Layers > 1) {
381         unableToLoad(filename, tr("because there are multiple <silkscreen> layers") + StandardCustomBoardExplanation);
382         return false;
383     }
384 
385     if (silk0Layers > 1) {
386         unableToLoad(filename, tr("because there are multiple <silkscreen0> layers") + StandardCustomBoardExplanation);
387         return false;
388     }
389 
390     if (layers > 0 && boardElements.count() == 0) {
391         unableToLoad(filename, tr("because there is no <board> layer") + StandardCustomBoardExplanation);
392         return false;
393     }
394 
395     if (layers == 0 && root.childNodes().count() == 0) {
396         unableToLoad(filename, tr("the svg contains no shape elements") + StandardCustomBoardExplanation);
397         return false;
398     }
399 
400     if (layers == 0 || (boardElements.count() == 1 && silk1Layers == 0 && silk1Layers == 0)) {
401         bool result = canLoad(filename, tr("but the pcb itself will have no silkscreen layer") + StandardCustomBoardExplanation);
402         if (result) moreCheckImage(filename);
403         return result;
404     }
405 
406     unableToLoad(filename, tr("the svg doesn't fit the custom board format") + StandardCustomBoardExplanation);
407     return false;
408 }
409 
moreCheckImage(const QString & filename)410 void Board::moreCheckImage(const QString & filename) {
411     QFile file(filename);
412     file.open(QFile::ReadOnly);
413     QString svg = file.readAll();
414     file.close();
415 
416     QString nsvg = setBoardOutline(svg);
417 
418 	QString errorStr;
419 	int errorLine;
420 	int errorColumn;
421 	QDomDocument domDocument;
422 	domDocument.setContent(nsvg, &errorStr, &errorLine, &errorColumn);
423     QDomElement element = TextUtils::findElementWithAttribute(domDocument.documentElement(), "id", GerberGenerator::MagicBoardOutlineID);
424 
425     int subpaths = 1;
426     int mCount = 0;
427     if (element.tagName() == "path") {
428         QString originalPath = element.attribute("d", "").trimmed();
429         if (GerberGenerator::MultipleZs.indexIn(originalPath) >= 0) {
430             QStringList ds = element.attribute("d").split("z", QString::SkipEmptyParts);
431             subpaths = ds.count();
432             foreach (QString d, ds) {
433                 if (d.trimmed().startsWith("m", Qt::CaseInsensitive)) mCount++;
434             }
435         }
436     }
437 
438     QString msg = tr("<b>The custom shape has been loaded, and you will see the new board shortly.</b><br/><br/>");
439     msg += tr("Before actual PCB production we recommend that you test your custom shape by using the 'File > Export for Production > Extended Gerber' option. ");
440     msg += tr("Check the resulting contour file with a Gerber-viewer application to make sure the shape came out as expected.<br/><br/>");
441 
442     msg += tr("The rest of this message concerns 'cutouts'. ");
443     msg += tr("These are circular or irregularly-shaped holes that you can optionally incorporate into a custom PCB shape.<br/><br/>");
444     if (subpaths == 1) {
445         msg += tr("<b>The custom shape has no cutouts.</b>");
446     }
447     else {
448         msg += tr("<b>The custom shape has %n cutouts.</b>", "", subpaths - 1);
449         if (subpaths != mCount) {
450             msg += tr("<br/>However, the cutouts may not be formatted correctly.");
451         }
452     }
453     msg +=  tr("<br/><br/>If you intended your custom shape to have cutouts and you did not get the expected result, ");
454     msg += tr("it is because Fritzing requires that you make cutouts using a shape 'subtraction' or 'difference' operation in your vector graphics editor.");
455     QMessageBox::information(NULL, "Custom Shape", msg);
456 }
457 
setBoardOutline(const QString & svg)458 QString Board::setBoardOutline(const QString & svg) {
459     QString errorStr;
460 	int errorLine;
461 	int errorColumn;
462 
463 	QDomDocument domDocument;
464 	if (!domDocument.setContent(svg, true, &errorStr, &errorLine, &errorColumn)) {
465 		return svg;
466 	}
467 
468     QDomElement root = domDocument.documentElement();
469 	if (root.tagName() != "svg") {
470 		return svg;
471 	}
472 
473     QDomElement board = TextUtils::findElementWithAttribute(root, "id", "board");
474     if (board.isNull()) {
475         board = root;
476     }
477 
478     QList<QDomElement> leaves;
479     TextUtils::collectLeaves(board, leaves);
480 
481     if (leaves.count() == 1) {
482         QDomElement leaf = leaves.at(0);
483         leaf.setAttribute("id", GerberGenerator::MagicBoardOutlineID);
484         return TextUtils::removeXMLEntities(domDocument.toString());
485     }
486 
487     int ix = 0;
488     QStringList ids;
489     foreach (QDomElement leaf, leaves) {
490         ids.append(leaf.attribute("id", ""));
491         leaf.setAttribute("id", QString("____%1____").arg(ix++));
492     }
493 
494     QSvgRenderer renderer(domDocument.toByteArray());
495 
496     ix = 0;
497     foreach (QDomElement leaf, leaves) {
498         leaf.setAttribute("id", ids.at(ix++));
499     }
500 
501     double maxArea = 0;
502     int maxIndex = -1;
503     for (int i = 0; i < leaves.count(); i++) {
504         QRectF r = renderer.boundsOnElement(QString("____%1____").arg(i));
505         if (r.width() * r.height() > maxArea) {
506             maxArea = r.width() * r.height();
507             maxIndex = i;
508         }
509     }
510 
511     if (maxIndex >= 0) {
512         QDomElement leaf = leaves.at(maxIndex);
513         leaf.setAttribute("id", GerberGenerator::MagicBoardOutlineID);
514         return TextUtils::removeXMLEntities(domDocument.toString());
515     }
516 
517     return svg;
518 }
unableToLoad(const QString & fileName,const QString & reason)519 void Board::unableToLoad(const QString & fileName, const QString & reason) {
520 	QMessageBox::information(
521 		NULL,
522 		tr("Unable to load"),
523 		tr("Unable to load image from %1 %2").arg(fileName).arg(reason)
524 	);
525 }
526 
canLoad(const QString & fileName,const QString & reason)527 bool Board::canLoad(const QString & fileName, const QString & reason) {
528     QMessageBox::StandardButton answer = QMessageBox::question(
529 		NULL,
530 		tr("Can load, but"),
531 		tr("The image from %1 can be loaded, but %2\nUse the file?").arg(fileName).arg(reason),
532 		QMessageBox::Yes | QMessageBox::No,
533 		QMessageBox::No
534 	);
535 	return answer == QMessageBox::Yes;
536 }
537 
prepLoadImageAux(const QString & fileName,bool addName)538 void Board::prepLoadImageAux(const QString & fileName, bool addName)
539 {
540 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
541 	if (infoGraphicsView != NULL) {
542 		infoGraphicsView->loadLogoImage(this, "", QSizeF(0,0), "", fileName, addName);
543 	}
544 }
545 
useViewIDForPixmap(ViewLayer::ViewID vid,bool)546 ViewLayer::ViewID Board::useViewIDForPixmap(ViewLayer::ViewID vid, bool)
547 {
548     if (vid == ViewLayer::PCBView) {
549         return ViewLayer::IconView;
550     }
551 
552     return ViewLayer::UnknownView;
553 }
554 
convertToXmlName(const QString & name)555 QString Board::convertToXmlName(const QString & name) {
556     foreach (QString key, ItemBase::TranslatedPropertyNames.keys()) {
557         if (name.compare(ItemBase::TranslatedPropertyNames.value(key), Qt::CaseInsensitive) == 0) {
558             return NamesToXmlNames.value(key);
559         }
560     }
561 
562     return name;
563 }
564 
convertFromXmlName(const QString & xmlName)565 QString Board::convertFromXmlName(const QString & xmlName) {
566     QString result = ItemBase::TranslatedPropertyNames.value(XmlNamesToNames.value(xmlName));
567     if (result.isEmpty()) return xmlName;
568     return result;
569 }
570 
571 ///////////////////////////////////////////////////////////
572 
ResizableBoard(ModelPart * modelPart,ViewLayer::ViewID viewID,const ViewGeometry & viewGeometry,long id,QMenu * itemMenu,bool doLabel)573 ResizableBoard::ResizableBoard( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel)
574 	: Board(modelPart, viewID, viewGeometry, id, itemMenu, doLabel)
575 {
576 	fixWH();
577 
578 	m_keepAspectRatio = false;
579 	m_widthEditor = m_heightEditor = NULL;
580     m_aspectRatioCheck = NULL;
581     m_aspectRatioLabel = NULL;
582     m_revertButton = NULL;
583     m_paperSizeComboBox = NULL;
584 
585 	m_corner = ResizableBoard::NO_CORNER;
586 	m_currentScale = 1.0;
587 	m_decimalsAfter = 1;
588 }
589 
~ResizableBoard()590 ResizableBoard::~ResizableBoard() {
591 }
592 
addedToScene(bool temporary)593 void ResizableBoard::addedToScene(bool temporary) {
594 
595 	loadTemplates();
596 	if (this->scene()) {
597 		setInitialSize();
598 	}
599 
600 	Board::addedToScene(temporary);
601 }
602 
loadTemplates()603 void ResizableBoard::loadTemplates() {
604     if (!BoardLayerTemplates.value(moduleID(), "").isEmpty()) return;
605 
606     QFile file(m_filename);
607     if (!file.open(QIODevice::ReadOnly)) return;
608 
609     QString svg = file.readAll();
610     if (svg.isEmpty()) return;
611 
612     BoardLayerTemplates.insert(moduleID(), getShapeForRenderer(svg, ViewLayer::Board));
613     SilkscreenLayerTemplates.insert(moduleID(), getShapeForRenderer(svg, ViewLayer::Silkscreen1));
614     Silkscreen0LayerTemplates.insert(moduleID(), getShapeForRenderer(svg, ViewLayer::Silkscreen0));
615 }
616 
minWidth()617 double ResizableBoard::minWidth() {
618 	return 0.25 * GraphicsUtils::SVGDPI;
619 }
620 
minHeight()621 double ResizableBoard::minHeight() {
622 	return 0.25 * GraphicsUtils::SVGDPI;
623 }
624 
mousePressEvent(QGraphicsSceneMouseEvent * event)625 void ResizableBoard::mousePressEvent(QGraphicsSceneMouseEvent * event)
626 {
627 	m_corner = ResizableBoard::NO_CORNER;
628 
629 	if (m_spaceBarWasPressed) {
630 		Board::mousePressEvent(event);
631 		return;
632 	}
633 
634 	double right = m_size.width();
635 	double bottom = m_size.height();
636 
637 	m_resizeMousePos = event->scenePos();
638 	m_resizeStartPos = pos();
639 	m_resizeStartSize = m_size;
640 	m_resizeStartTopLeft = mapToScene(0, 0);
641 	m_resizeStartTopRight = mapToScene(right, 0);
642 	m_resizeStartBottomLeft = mapToScene(0, bottom);
643 	m_resizeStartBottomRight = mapToScene(right, bottom);
644 
645 	m_corner = findCorner(event->scenePos(), event->modifiers());
646 	switch (m_corner) {
647 		case ResizableBoard::NO_CORNER:
648 			Board::mousePressEvent(event);
649 			return;
650         default:
651                 break;
652 	}
653 
654 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
655 	if (infoGraphicsView) {
656 		setInitialSize();
657 		infoGraphicsView->viewItemInfo(this);
658 	}
659 }
660 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)661 void ResizableBoard::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
662 	if (m_corner == ResizableBoard::NO_CORNER) {
663 		Board::mouseMoveEvent(event);
664 		return;
665 	}
666 
667 	QPointF zero = mapToScene(0, 0);
668 	QPointF ds = mapFromScene(zero + event->scenePos() - m_resizeMousePos);
669 	QPointF newPos;
670 	QSizeF size = m_resizeStartSize;
671 
672 	switch (m_corner) {
673 		case ResizableBoard::BOTTOM_RIGHT:
674 			size.setWidth(size.width() + ds.x());
675 			size.setHeight(size.height() + ds.y());
676 			break;
677 		case ResizableBoard::TOP_RIGHT:
678 			size.setWidth(size.width() + ds.x());
679 			size.setHeight(size.height() - ds.y());
680 			break;
681 		case ResizableBoard::BOTTOM_LEFT:
682 			size.setWidth(size.width() - ds.x());
683 			size.setHeight(size.height() + ds.y());
684 			break;
685 		case ResizableBoard::TOP_LEFT:
686 			size.setWidth(size.width() - ds.x());
687 			size.setHeight(size.height() - ds.y());
688 			break;
689         default:
690             break;
691 	}
692 
693 	if (size.width() < minWidth()) {
694         DebugDialog::debug("to min width");
695         size.setWidth(minWidth());
696     }
697 	if (size.height() < minHeight()) {
698         DebugDialog::debug("to min height");
699         size.setHeight(minHeight());
700     }
701 
702 	if (m_keepAspectRatio) {
703 		double cw = size.height() * m_aspectRatio.width() / m_aspectRatio.height();
704 		double ch = size.width() * m_aspectRatio.height() / m_aspectRatio.width();
705 		if (ch < minHeight()) {
706             DebugDialog::debug("from min height");
707 			size.setWidth(cw);
708 		}
709 		else if (cw < minWidth()) {
710             DebugDialog::debug("from min width");
711 			size.setHeight(ch);
712 		}
713 		else {
714 			// figure out which one changes the area least
715 			double a1 = cw * size.height();
716 			double a2 = ch * size.width();
717 			double ac = m_size.width() * m_size.height();
718 			if (qAbs(ac - a1) <= qAbs(ac - a2)) {
719 				size.setWidth(cw);
720 			}
721 			else {
722 				size.setHeight(ch);
723 			}
724 		}
725 	}
726 
727 	bool changePos = (m_corner != ResizableBoard::BOTTOM_RIGHT);
728 	bool changeTransform = !this->transform().isIdentity();
729 
730 	LayerHash lh;
731 	QSizeF oldSize = m_size;
732 	resizePixels(size.width(), size.height(), lh);
733 
734 	if (changePos) {
735 		if (changeTransform) {
736 			QTransform oldT = transform();
737 
738 			DebugDialog::debug(QString("t old m:%1 p:%2,%3 sz:%4,%5")
739                 .arg(TextUtils::svgMatrix(oldT))
740 				.arg(pos().x()).arg(pos().y())
741 				.arg(oldSize.width()).arg(oldSize.height()));
742 
743 			double sw = size.width() / 2;
744 			double sh = size.height() / 2;
745 			QMatrix m(oldT.m11(), oldT.m12(), oldT.m21(), oldT.m22(), 0, 0);
746 			ds = m.inverted().map(ds);
747 			QTransform newT = QTransform().translate(-sw, -sh) * QTransform(m) * QTransform().translate(sw, sh);
748 
749 			QList<ItemBase *> kin;
750 			kin << this->layerKinChief();
751 			foreach (ItemBase * lk, this->layerKinChief()->layerKin()) {
752 				kin << lk;
753 			}
754 			foreach (ItemBase * itemBase, kin) {
755 				itemBase->getViewGeometry().setTransform(newT);
756 				itemBase->setTransform(newT);
757 			}
758 
759 			QTransform t = transform();
760 			DebugDialog::debug(QString("t new m:%1 p:%2,%3 sz:%4,%5")
761 				.arg(TextUtils::svgMatrix(t))
762 				.arg(pos().x()).arg(pos().y())
763 				.arg(size.width()).arg(size.height()));
764 		}
765 
766 		QPointF actual;
767 		QPointF desired;
768 		switch (m_corner) {
769 			case ResizableBoard::TOP_RIGHT:
770 				actual = mapToScene(0, size.height());
771 				desired = m_resizeStartBottomLeft;
772 				break;
773 			case ResizableBoard::BOTTOM_LEFT:
774 				actual = mapToScene(size.width(), 0);
775 				desired = m_resizeStartTopRight;
776 				break;
777 			case ResizableBoard::TOP_LEFT:
778 				actual = mapToScene(size.width(), size.height());
779 				desired = m_resizeStartBottomRight;
780 				break;
781                         default:
782                                 break;
783 		}
784 
785 		setPos(pos() + desired - actual);
786 	}
787 }
788 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)789 void ResizableBoard::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
790 	if (m_corner == ResizableBoard::NO_CORNER) {
791 		Board::mouseReleaseEvent(event);
792 		return;
793 	}
794 
795 	event->accept();
796 	m_corner = ResizableBoard::NO_CORNER;
797 	setKinCursor(Qt::ArrowCursor);
798 
799 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
800 	if (infoGraphicsView) {
801 		infoGraphicsView->viewItemInfo(this);
802 	}
803 }
804 
theViewID()805 ViewLayer::ViewID ResizableBoard::theViewID() {
806 	return ViewLayer::PCBView;
807 }
808 
resizePixels(double w,double h,const LayerHash & viewLayers)809 void ResizableBoard::resizePixels(double w, double h, const LayerHash & viewLayers) {
810 	resizeMM(GraphicsUtils::pixels2mm(w, GraphicsUtils::SVGDPI), GraphicsUtils::pixels2mm(h, GraphicsUtils::SVGDPI), viewLayers);
811 }
812 
resizeMM(double mmW,double mmH,const LayerHash & viewLayers)813 bool ResizableBoard::resizeMM(double mmW, double mmH, const LayerHash & viewLayers) {
814 	if (mmW == 0 || mmH == 0) {
815         LayerAttributes layerAttributes;
816         this->initLayerAttributes(layerAttributes, m_viewID, m_viewLayerID, m_viewLayerPlacement, true, true);
817 		setUpImage(modelPart(), viewLayers, layerAttributes);
818 		modelPart()->setLocalProp("height", QVariant());
819 		modelPart()->setLocalProp("width", QVariant());
820 		// do the layerkin
821 		return false;
822 	}
823 
824 	QRectF r = this->boundingRect();
825 	if (qAbs(GraphicsUtils::pixels2mm(r.width(), GraphicsUtils::SVGDPI) - mmW) < .001 &&
826 		qAbs(GraphicsUtils::pixels2mm(r.height(), GraphicsUtils::SVGDPI) - mmH) < .001)
827 	{
828 		return false;
829 	}
830 
831 	resizeMMAux(mmW, mmH);
832     return true;
833 }
834 
835 
resizeMMAux(double mmW,double mmH)836 void ResizableBoard::resizeMMAux(double mmW, double mmH)
837 {
838 	double milsW = GraphicsUtils::mm2mils(mmW);
839 	double milsH = GraphicsUtils::mm2mils(mmH);
840 
841 	QString s = makeFirstLayerSvg(mmW, mmH, milsW, milsH);
842 
843     bool result = resetRenderer(s);
844     if (result) {
845 
846 		modelPart()->setLocalProp("width", mmW);
847 		modelPart()->setLocalProp("height", mmH);
848 
849 		double tens = pow(10.0, m_decimalsAfter);
850 		setWidthAndHeight(qRound(mmW * tens) / tens, qRound(mmH * tens) / tens);
851 	}
852 	//	DebugDialog::debug(QString("fast load result %1 %2").arg(result).arg(s));
853 
854 	foreach (ItemBase * itemBase, m_layerKin) {
855 		QString s = makeNextLayerSvg(itemBase->viewLayerID(), mmW, mmH, milsW, milsH);
856 		if (!s.isEmpty()) {
857             result = itemBase->resetRenderer(s);
858             if (result) {
859 				itemBase->modelPart()->setLocalProp("width", mmW);
860 				itemBase->modelPart()->setLocalProp("height", mmH);
861 			}
862 		}
863 	}
864 
865 }
866 
loadLayerKin(const LayerHash & viewLayers,ViewLayer::ViewLayerPlacement viewLayerPlacement)867 void ResizableBoard::loadLayerKin( const LayerHash & viewLayers, ViewLayer::ViewLayerPlacement viewLayerPlacement) {
868 
869 	loadTemplates();
870 	Board::loadLayerKin(viewLayers, viewLayerPlacement);
871 	double w =  m_modelPart->localProp("width").toDouble();
872 	if (w != 0) {
873 		resizeMM(w, m_modelPart->localProp("height").toDouble(), viewLayers);
874 	}
875 }
876 
setInitialSize()877 void ResizableBoard::setInitialSize() {
878 	double w =  m_modelPart->localProp("width").toDouble();
879 	if (w == 0) {
880 		// set the size so the infoGraphicsView will display the size as you drag
881 		QSizeF sz = this->boundingRect().size();
882 		modelPart()->setLocalProp("width", GraphicsUtils::pixels2mm(sz.width(), GraphicsUtils::SVGDPI));
883 		modelPart()->setLocalProp("height", GraphicsUtils::pixels2mm(sz.height(), GraphicsUtils::SVGDPI));
884 	}
885 }
886 
retrieveSvg(ViewLayer::ViewLayerID viewLayerID,QHash<QString,QString> & svgHash,bool blackOnly,double dpi,double & factor)887 QString ResizableBoard::retrieveSvg(ViewLayer::ViewLayerID viewLayerID, QHash<QString, QString> & svgHash, bool blackOnly, double dpi, double & factor)
888 {
889 	double w = m_modelPart->localProp("width").toDouble();
890 	if (w != 0) {
891 		double h = m_modelPart->localProp("height").toDouble();
892 		QString xml = makeLayerSvg(viewLayerID, w, h, GraphicsUtils::mm2mils(w), GraphicsUtils::mm2mils(h));
893 		if (!xml.isEmpty()) {
894 			return PaletteItemBase::normalizeSvg(xml, viewLayerID, blackOnly, dpi, factor);
895 		}
896 	}
897 
898 	return Board::retrieveSvg(viewLayerID, svgHash, blackOnly, dpi, factor);
899 }
900 
getSizeMM()901 QSizeF ResizableBoard::getSizeMM() {
902 	double w = m_modelPart->localProp("width").toDouble();
903 	double h = m_modelPart->localProp("height").toDouble();
904 	return QSizeF(w, h);
905 }
906 
makeLayerSvg(ViewLayer::ViewLayerID viewLayerID,double mmW,double mmH,double milsW,double milsH)907 QString ResizableBoard::makeLayerSvg(ViewLayer::ViewLayerID viewLayerID, double mmW, double mmH, double milsW, double milsH)
908 {
909 	switch (viewLayerID) {
910 		case ViewLayer::Board:
911 			return makeBoardSvg(mmW, mmH, milsW, milsH);
912 		case ViewLayer::Silkscreen1:
913 		case ViewLayer::Silkscreen0:
914 			return makeSilkscreenSvg(viewLayerID, mmW, mmH, milsW, milsH);
915 			break;
916 		default:
917 			return "";
918 	}
919 }
920 
makeNextLayerSvg(ViewLayer::ViewLayerID viewLayerID,double mmW,double mmH,double milsW,double milsH)921 QString ResizableBoard::makeNextLayerSvg(ViewLayer::ViewLayerID viewLayerID, double mmW, double mmH, double milsW, double milsH) {
922 
923 	if (viewLayerID == ViewLayer::Silkscreen1) return makeSilkscreenSvg(viewLayerID, mmW, mmH, milsW, milsH);
924 	if (viewLayerID == ViewLayer::Silkscreen0) return makeSilkscreenSvg(viewLayerID, mmW, mmH, milsW, milsH);
925 
926 	return "";
927 }
928 
makeFirstLayerSvg(double mmW,double mmH,double milsW,double milsH)929 QString ResizableBoard::makeFirstLayerSvg(double mmW, double mmH, double milsW, double milsH) {
930 	return makeBoardSvg(mmW, mmH, milsW, milsH);
931 }
932 
makeBoardSvg(double mmW,double mmH,double milsW,double milsH)933 QString ResizableBoard::makeBoardSvg(double mmW, double mmH, double milsW, double milsH) {
934     Q_UNUSED(milsW);
935     Q_UNUSED(milsH);
936     return makeSvg(mmW, mmH, BoardLayerTemplates.value(moduleID()));
937 }
938 
makeSilkscreenSvg(ViewLayer::ViewLayerID viewLayerID,double mmW,double mmH,double milsW,double milsH)939 QString ResizableBoard::makeSilkscreenSvg(ViewLayer::ViewLayerID viewLayerID, double mmW, double mmH, double milsW, double milsH) {
940     Q_UNUSED(milsW);
941     Q_UNUSED(milsH);
942     if (viewLayerID == ViewLayer::Silkscreen0) return makeSvg(mmW, mmH, Silkscreen0LayerTemplates.value(moduleID()));
943     return makeSvg(mmW, mmH, SilkscreenLayerTemplates.value(moduleID()));
944 }
945 
makeSvg(double mmW,double mmH,const QString & layerTemplate)946 QString ResizableBoard::makeSvg(double mmW, double mmH, const QString & layerTemplate)
947 {
948     if (layerTemplate.isEmpty()) return "";
949 
950     QDomDocument doc;
951     if (!doc.setContent(layerTemplate)) return "";
952 
953     QDomElement root = doc.documentElement();
954     static const QString mmString("%1mm");
955     root.setAttribute("width", mmString.arg(mmW));
956     root.setAttribute("height", mmString.arg(mmH));
957     root.setAttribute("viewBox", QString("0 0 %1 %2").arg(mmW).arg(mmH));
958     QList<QDomElement> leaves;
959     TextUtils::collectLeaves(root, leaves);
960     if (leaves.count() > 1) return "";
961 
962     QDomElement leaf = leaves.at(0);
963 
964     bool ok;
965     double strokeWidth = leaf.attribute("stroke-width").toDouble(&ok);
966     if (!ok) return "";
967 
968     if (layerTemplate.contains("<ellipse")) {
969         leaf.setAttribute("cx", QString::number(mmW / 2));
970         leaf.setAttribute("cy", QString::number(mmH / 2));
971         leaf.setAttribute("rx", QString::number((mmW - strokeWidth) / 2));
972         leaf.setAttribute("ry", QString::number((mmH - strokeWidth) / 2));
973     }
974     else if (layerTemplate.contains("<rect")) {
975         leaf.setAttribute("width", QString::number(mmW - strokeWidth));
976         leaf.setAttribute("height", QString::number(mmH - strokeWidth));
977     }
978 
979     return doc.toString();
980 }
981 
saveParams()982 void ResizableBoard::saveParams() {
983 	double w = modelPart()->localProp("width").toDouble();
984 	double h = modelPart()->localProp("height").toDouble();
985 	m_boardSize = QSizeF(w, h);
986 	m_boardPos = pos();
987 }
988 
getParams(QPointF & p,QSizeF & s)989 void ResizableBoard::getParams(QPointF & p, QSizeF & s) {
990 	p = m_boardPos;
991 	s = m_boardSize;
992 }
993 
hasCustomSVG()994 bool ResizableBoard::hasCustomSVG() {
995 	return theViewID() == m_viewID;
996 }
997 
collectExtraInfo(QWidget * parent,const QString & family,const QString & prop,const QString & value,bool swappingEnabled,QString & returnProp,QString & returnValue,QWidget * & returnWidget,bool & hide)998 bool ResizableBoard::collectExtraInfo(QWidget * parent, const QString & family, const QString & prop, const QString & value, bool swappingEnabled, QString & returnProp, QString & returnValue, QWidget * & returnWidget, bool & hide)
999 {
1000 	bool result = Board::collectExtraInfo(parent, family, prop, value, swappingEnabled, returnProp, returnValue, returnWidget, hide);
1001 
1002 	if (prop.compare("shape", Qt::CaseInsensitive) == 0) {
1003 
1004 		returnProp = tr("shape");
1005 
1006 		if (!m_modelPart->localProp("height").isValid()) {
1007 			// display uneditable width and height
1008 			QFrame * frame = new QFrame();
1009 			frame->setObjectName("infoViewPartFrame");
1010 
1011 			QVBoxLayout * vboxLayout = new QVBoxLayout();
1012 			vboxLayout->setAlignment(Qt::AlignLeft);
1013 			vboxLayout->setSpacing(0);
1014 			vboxLayout->setMargin(0);
1015 			vboxLayout->setContentsMargins(0, 3, 0, 0);
1016 
1017 			double tens = pow(10.0, m_decimalsAfter);
1018 			QRectF r = this->boundingRect();
1019 			double w = qRound(GraphicsUtils::pixels2mm(r.width(), GraphicsUtils::SVGDPI) * tens) / tens;
1020 			QLabel * l1 = new QLabel(tr("width: %1mm").arg(w));
1021 			l1->setMargin(0);
1022 			l1->setObjectName("infoViewLabel");
1023 
1024 			double h = qRound(GraphicsUtils::pixels2mm(r.height(), GraphicsUtils::SVGDPI) * tens) / tens;
1025 			QLabel * l2 = new QLabel(tr("height: %1mm").arg(h));
1026 			l2->setMargin(0);
1027 			l2->setObjectName("infoViewLabel");
1028 
1029 			if (returnWidget) vboxLayout->addWidget(qobject_cast<QWidget *>(returnWidget));
1030 			vboxLayout->addWidget(l1);
1031 			vboxLayout->addWidget(l2);
1032 
1033 			frame->setLayout(vboxLayout);
1034 
1035 			returnValue = l1->text() + "," + l2->text();
1036 			returnWidget = frame;
1037 			return true;
1038 		}
1039 
1040 		returnWidget = setUpDimEntry(false, false, false, returnWidget);
1041         returnWidget->setEnabled(swappingEnabled);
1042 		return true;
1043 	}
1044 
1045 	return result;
1046 }
1047 
collectValues(const QString & family,const QString & prop,QString & value)1048 QStringList ResizableBoard::collectValues(const QString & family, const QString & prop, QString & value) {
1049 	return Board::collectValues(family, prop, value);
1050 }
1051 
paperSizeChanged(int index)1052 void ResizableBoard::paperSizeChanged(int index) {
1053     QComboBox * comboBox = qobject_cast<QComboBox *>(sender());
1054     if (comboBox == NULL) return;
1055 
1056     QModelIndex modelIndex = comboBox->model()->index(index,0);
1057     QSizeF size = comboBox->model()->data(modelIndex, Qt::UserRole).toSizeF();
1058 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
1059 	if (infoGraphicsView != NULL) {
1060 		infoGraphicsView->resizeBoard(size.width(), size.height(), true);
1061 	}
1062 }
1063 
widthEntry()1064 void ResizableBoard::widthEntry() {
1065 	QLineEdit * edit = qobject_cast<QLineEdit *>(sender());
1066 	if (edit == NULL) return;
1067 
1068 	double w = edit->text().toDouble();
1069 	double oldW = m_modelPart->localProp("width").toDouble();
1070 	if (w == oldW) return;
1071 
1072 	double h =  m_modelPart->localProp("height").toDouble();
1073 
1074 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
1075 	if (infoGraphicsView != NULL) {
1076 		infoGraphicsView->resizeBoard(w, h, true);
1077 	}
1078 }
1079 
heightEntry()1080 void ResizableBoard::heightEntry() {
1081 	QLineEdit * edit = qobject_cast<QLineEdit *>(sender());
1082 	if (edit == NULL) return;
1083 
1084 	double h = edit->text().toDouble();
1085 	double oldH =  m_modelPart->localProp("height").toDouble();
1086 	if (h == oldH) return;
1087 
1088 	double w =  m_modelPart->localProp("width").toDouble();
1089 
1090 	InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
1091 	if (infoGraphicsView != NULL) {
1092 		infoGraphicsView->resizeBoard(w, h, true);
1093 	}
1094 }
1095 
hasPartNumberProperty()1096 bool ResizableBoard::hasPartNumberProperty()
1097 {
1098 	return false;
1099 }
1100 
paintSelected(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)1101 void ResizableBoard::paintSelected(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
1102 {
1103 	if (m_hidden || m_layerHidden) return;
1104 
1105 	Board::paintSelected(painter, option, widget);
1106 
1107 	// http://www.gamedev.net/topic/441695-transform-matrix-decomposition/
1108 	double m11 = painter->worldTransform().m11();
1109 	double m12 = painter->worldTransform().m12();
1110 	double scale = m_currentScale = qSqrt((m11 * m11) + (m12 * m12));   // assumes same scaling for both x and y
1111 
1112 	double scalefull = CornerHandleSize / scale;
1113 	double scalehalf = scalefull / 2;
1114 	double bottom = m_size.height();
1115 	double right = m_size.width();
1116 
1117 	QPen pen;
1118 	pen.setWidthF(1.0 / scale);
1119 	pen.setColor(QColor(0, 0, 0));
1120 	QBrush brush(QColor(255, 255, 255));
1121 	painter->setPen(pen);
1122 	painter->setBrush(brush);
1123 
1124 	QPolygonF poly;
1125 
1126 	// upper left
1127 	poly.append(QPointF(0, 0));
1128 	poly.append(QPointF(0, scalefull));
1129 	poly.append(QPointF(scalehalf, scalefull));
1130 	poly.append(QPointF(scalehalf, scalehalf));
1131 	poly.append(QPointF(scalefull, scalehalf));
1132 	poly.append(QPointF(scalefull, 0));
1133 	painter->drawPolygon(poly);
1134 
1135 	// upper right
1136 	poly.clear();
1137 	poly.append(QPointF(right, 0));
1138 	poly.append(QPointF(right, scalefull));
1139 	poly.append(QPointF(right - scalehalf, scalefull));
1140 	poly.append(QPointF(right - scalehalf, scalehalf));
1141 	poly.append(QPointF(right - scalefull, scalehalf));
1142 	poly.append(QPointF(right - scalefull, 0));
1143 	painter->drawPolygon(poly);
1144 
1145 	// lower left
1146 	poly.clear();
1147 	poly.append(QPointF(0, bottom - scalefull));
1148 	poly.append(QPointF(0, bottom));
1149 	poly.append(QPointF(scalefull, bottom));
1150 	poly.append(QPointF(scalefull, bottom - scalehalf));
1151 	poly.append(QPointF(scalehalf, bottom - scalehalf));
1152 	poly.append(QPointF(scalehalf, bottom - scalefull));
1153 	painter->drawPolygon(poly);
1154 
1155 	// lower right
1156 	poly.clear();
1157 	poly.append(QPointF(right, bottom - scalefull));
1158 	poly.append(QPointF(right, bottom));
1159 	poly.append(QPointF(right - scalefull, bottom));
1160 	poly.append(QPointF(right - scalefull, bottom - scalehalf));
1161 	poly.append(QPointF(right - scalehalf, bottom - scalehalf));
1162 	poly.append(QPointF(right - scalehalf, bottom - scalefull));
1163 	painter->drawPolygon(poly);
1164 }
1165 
inResize()1166 bool ResizableBoard::inResize() {
1167 	return m_corner != ResizableBoard::NO_CORNER;
1168 }
1169 
figureHover()1170 void ResizableBoard::figureHover() {
1171     setAcceptHoverEvents(true);
1172     setAcceptedMouseButtons(ALLMOUSEBUTTONS);
1173 	foreach(ItemBase * lkpi, m_layerKin) {
1174 		lkpi->setAcceptHoverEvents(false);
1175 		lkpi->setAcceptedMouseButtons(Qt::NoButton);
1176 	}
1177 }
1178 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)1179 void ResizableBoard::hoverEnterEvent( QGraphicsSceneHoverEvent * event ) {
1180 	Board::hoverEnterEvent(event);
1181 }
1182 
hoverMoveEvent(QGraphicsSceneHoverEvent * event)1183 void ResizableBoard::hoverMoveEvent( QGraphicsSceneHoverEvent * event ) {
1184 	Board::hoverMoveEvent(event);
1185 
1186 	m_corner = findCorner(event->scenePos(), event->modifiers());
1187 	QCursor cursor;
1188 	switch (m_corner) {
1189 		case ResizableBoard::BOTTOM_RIGHT:
1190 		case ResizableBoard::TOP_LEFT:
1191 		case ResizableBoard::TOP_RIGHT:
1192 		case ResizableBoard::BOTTOM_LEFT:
1193 			//DebugDialog::debug("setting scale cursor");
1194 			cursor = *CursorMaster::ScaleCursor;
1195 			break;
1196 		default:
1197 			//DebugDialog::debug("setting other cursor");
1198 			cursor = Qt::ArrowCursor;
1199 			break;
1200 	}
1201 	setKinCursor(cursor);
1202 
1203 }
1204 
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)1205 void ResizableBoard::hoverLeaveEvent( QGraphicsSceneHoverEvent * event ) {
1206 	setKinCursor(Qt::ArrowCursor);
1207 	//DebugDialog::debug("setting arrow cursor");
1208 	Board::hoverLeaveEvent(event);
1209 }
1210 
findCorner(QPointF scenePos,Qt::KeyboardModifiers modifiers)1211 ResizableBoard::Corner ResizableBoard::findCorner(QPointF scenePos, Qt::KeyboardModifiers modifiers) {
1212 	Q_UNUSED(modifiers);
1213 
1214 	if (!this->isSelected()) return ResizableBoard::NO_CORNER;
1215     if (this->moveLock()) return ResizableBoard::NO_CORNER;
1216 
1217 	double d = CornerHandleSize / m_currentScale;
1218 	double d2 = d * d;
1219 	double right = m_size.width();
1220 	double bottom = m_size.height();
1221 	//DebugDialog::debug(QString("size %1 %2").arg(right).arg(bottom));
1222 	QPointF q = mapToScene(right, bottom);
1223 	if (GraphicsUtils::distanceSqd(scenePos, q) <= d2) {
1224 		return ResizableBoard::BOTTOM_RIGHT;
1225 	}
1226 	q = mapToScene(0, 0);
1227 	if (GraphicsUtils::distanceSqd(scenePos, q) <= d2) {
1228 		return ResizableBoard::TOP_LEFT;
1229 	}
1230 	q = mapToScene(right, 0);
1231 	if (GraphicsUtils::distanceSqd(scenePos, q) <= d2) {
1232 		return ResizableBoard::TOP_RIGHT;
1233 	}
1234 	q = mapToScene(0, bottom);
1235 	if (GraphicsUtils::distanceSqd(scenePos, q) <= d2) {
1236 		return ResizableBoard::BOTTOM_LEFT;
1237 	}
1238 
1239 	return ResizableBoard::NO_CORNER;
1240 }
1241 
setKinCursor(QCursor & cursor)1242 void ResizableBoard::setKinCursor(QCursor & cursor) {
1243 	ItemBase * chief = this->layerKinChief();
1244 	chief->setCursor(cursor);
1245 	foreach (ItemBase * itemBase, chief->layerKin()) {
1246 		itemBase->setCursor(cursor);
1247 	}
1248 }
1249 
setKinCursor(Qt::CursorShape cursor)1250 void ResizableBoard::setKinCursor(Qt::CursorShape cursor) {
1251 	ItemBase * chief = this->layerKinChief();
1252 	chief->setCursor(cursor);
1253 	foreach (ItemBase * itemBase, chief->layerKin()) {
1254 		itemBase->setCursor(cursor);
1255 	}
1256 }
1257 
setUpDimEntry(bool includeAspectRatio,bool includeRevert,bool includePaperSizes,QWidget * & returnWidget)1258 QFrame * ResizableBoard::setUpDimEntry(bool includeAspectRatio, bool includeRevert, bool includePaperSizes, QWidget * & returnWidget)
1259 {
1260 	double tens = pow(10.0, m_decimalsAfter);
1261 	double w = qRound(m_modelPart->localProp("width").toDouble() * tens) / tens;	// truncate to 1 decimal point
1262 	double h = qRound(m_modelPart->localProp("height").toDouble() * tens) / tens;  // truncate to 1 decimal point
1263 
1264 	QFrame * frame = new QFrame();
1265 	frame->setObjectName("infoViewPartFrame");
1266 	QVBoxLayout * vboxLayout = new QVBoxLayout();
1267 	vboxLayout->setAlignment(Qt::AlignLeft);
1268 	vboxLayout->setSpacing(1);
1269 	vboxLayout->setContentsMargins(0, 3, 0, 0);
1270 
1271 	QFrame * subframe1 = new QFrame();
1272 	QHBoxLayout * hboxLayout1 = new QHBoxLayout();
1273 	hboxLayout1->setAlignment(Qt::AlignLeft);
1274 	hboxLayout1->setContentsMargins(0, 0, 0, 0);
1275 	hboxLayout1->setSpacing(2);
1276 
1277 	QFrame * subframe2 = new QFrame();
1278 	QHBoxLayout * hboxLayout2 = new QHBoxLayout();
1279 	hboxLayout2->setAlignment(Qt::AlignLeft);
1280 	hboxLayout2->setContentsMargins(0, 0, 0, 0);
1281 	hboxLayout2->setSpacing(2);
1282 
1283 	QLabel * l1 = new QLabel(tr("width(mm)"));
1284 	l1->setMargin(0);
1285 	l1->setObjectName("infoViewLabel");
1286 	QLineEdit * e1 = new QLineEdit();
1287 	QDoubleValidator * validator = new QDoubleValidator(e1);
1288 	validator->setRange(0.1, 999.9, m_decimalsAfter);
1289 	validator->setNotation(QDoubleValidator::StandardNotation);
1290     validator->setLocale(QLocale::C);
1291 	e1->setObjectName("infoViewLineEdit");
1292 	e1->setValidator(validator);
1293 	e1->setMaxLength(4 + m_decimalsAfter);
1294 	e1->setText(QString::number(w));
1295 
1296 	QLabel * l2 = new QLabel(tr("height(mm)"));
1297 	l2->setMargin(0);
1298 	l2->setObjectName("infoViewLabel");
1299 	QLineEdit * e2 = new QLineEdit();
1300 	validator = new QDoubleValidator(e1);
1301 	validator->setRange(0.1, 999.9, m_decimalsAfter);
1302 	validator->setNotation(QDoubleValidator::StandardNotation);
1303     validator->setLocale(QLocale::C);
1304 	e2->setObjectName("infoViewLineEdit");
1305 	e2->setValidator(validator);
1306 	e2->setMaxLength(4 + m_decimalsAfter);
1307 	e2->setText(QString::number(h));
1308 
1309 	hboxLayout1->addWidget(l1);
1310 	hboxLayout1->addWidget(e1);
1311 	hboxLayout2->addWidget(l2);
1312 	hboxLayout2->addWidget(e2);
1313 
1314 	subframe1->setLayout(hboxLayout1);
1315 	subframe2->setLayout(hboxLayout2);
1316 	if (returnWidget != NULL) vboxLayout->addWidget(returnWidget);
1317 
1318 	connect(e1, SIGNAL(editingFinished()), this, SLOT(widthEntry()));
1319 	connect(e2, SIGNAL(editingFinished()), this, SLOT(heightEntry()));
1320 
1321 	m_widthEditor = e1;
1322 	m_heightEditor = e2;
1323 
1324 	vboxLayout->addWidget(subframe1);
1325 	vboxLayout->addWidget(subframe2);
1326 
1327 	if (includeAspectRatio || includeRevert || includePaperSizes) {
1328 		QFrame * subframe3 = new QFrame();
1329 		QHBoxLayout * hboxLayout3 = new QHBoxLayout();
1330 		hboxLayout3->setAlignment(Qt::AlignLeft);
1331 		hboxLayout3->setContentsMargins(0, 0, 0, 0);
1332 		hboxLayout3->setSpacing(0);
1333 
1334 		if (includeAspectRatio) {
1335 			QLabel * l3 = new QLabel(tr("keep aspect ratio"));
1336 			l3->setMargin(0);
1337 			l3->setObjectName("infoViewLabel");
1338 			QCheckBox * checkBox = new QCheckBox();
1339 			checkBox->setChecked(m_keepAspectRatio);
1340 			checkBox->setObjectName("infoViewCheckBox");
1341 
1342 			hboxLayout3->addWidget(l3);
1343 			hboxLayout3->addWidget(checkBox);
1344 			connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(keepAspectRatio(bool)));
1345 			m_aspectRatioCheck = checkBox;
1346 			m_aspectRatioLabel = l3;
1347 	    }
1348 	    if (includeRevert) {
1349 			QPushButton * pb = new QPushButton(tr("Revert"));
1350 			pb->setObjectName("infoViewButton");
1351 			hboxLayout3->addWidget(pb);
1352 			connect(pb, SIGNAL(clicked(bool)), this, SLOT(revertSize(bool)));
1353 			double w = modelPart()->localProp("width").toDouble();
1354 			double ow = modelPart()->localProp("originalWidth").toDouble();
1355 			double h = modelPart()->localProp("height").toDouble();
1356 			double oh = modelPart()->localProp("originalHeight").toDouble();
1357 			pb->setEnabled(qAbs(w - ow) > JND || qAbs(h - oh) > JND);
1358 			m_revertButton = pb;
1359 	    }
1360         if (includePaperSizes) {
1361             initPaperSizes();
1362 
1363 			QLabel * l3 = new QLabel(tr("size"));
1364 			l3->setMargin(0);
1365 			l3->setObjectName("infoViewLabel");
1366 
1367             m_paperSizeComboBox = new QComboBox();
1368 	        m_paperSizeComboBox->setObjectName("infoViewComboBox");
1369 	        m_paperSizeComboBox->setEditable(false);
1370             m_paperSizeComboBox->setMinimumWidth(150);
1371             m_paperSizeComboBox->addItem(tr("custom"));
1372             for (int i = 0; i < PaperSizeNames.count(); i++) {
1373                 QSizeF dim = PaperSizeDimensions.at(i);
1374                 m_paperSizeComboBox->addItem(PaperSizeNames.at(i), dim);
1375             }
1376 
1377             QModelIndex modelIndex = m_paperSizeComboBox->model()->index(0,0);
1378             m_paperSizeComboBox->model()->setData(modelIndex, 0, Qt::UserRole - 1);           // to make it selectable again use Qt::ItemIsSelectable | Qt::ItemIsEnabled)
1379 
1380             updatePaperSizes(w, h);
1381             connect(m_paperSizeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(paperSizeChanged(int)));
1382 
1383             hboxLayout3->addWidget(l3);
1384             hboxLayout3->addWidget(m_paperSizeComboBox);
1385 
1386         }
1387 
1388 		subframe3->setLayout(hboxLayout3);
1389 
1390 		vboxLayout->addWidget(subframe3);
1391 	}
1392 
1393 	frame->setLayout(vboxLayout);
1394 
1395 	return frame;
1396 }
1397 
fixWH()1398 void ResizableBoard::fixWH() {
1399 	bool okw, okh;
1400 	QString wstr = m_modelPart->localProp("width").toString();
1401 	QString hstr = m_modelPart->localProp("height").toString();
1402 	double w = wstr.toDouble(&okw);
1403 	double h = hstr.toDouble(&okh);
1404 
1405 	//DebugDialog::debug(QString("w:%1 %2 ok:%3 h:%4 %5 ok:%6")
1406 					//.arg(wstr).arg(w).arg(okw)
1407 					//.arg(hstr).arg(h).arg(okh));
1408 
1409 	if ((!okw && !wstr.isEmpty()) || qIsNaN(w) || qIsInf(w) || (!okh && !hstr.isEmpty()) || qIsNaN(h) || qIsInf(h)) {
1410 		DebugDialog::debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1411 		DebugDialog::debug("bad width or height in ResizableBoard or subclass " + wstr + " " + hstr);
1412 		DebugDialog::debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1413 		m_modelPart->setLocalProp("width", "");
1414 		m_modelPart->setLocalProp("height", "");
1415 	}
1416 }
1417 
setWidthAndHeight(double w,double h)1418 void ResizableBoard::setWidthAndHeight(double w, double h)
1419 {
1420 	if (m_widthEditor) {
1421 		m_widthEditor->setText(QString::number(w));
1422 	}
1423 	if (m_heightEditor) {
1424 		m_heightEditor->setText(QString::number(h));
1425 	}
1426     updatePaperSizes(w, h);
1427 }
1428 
getShapeForRenderer(const QString & svg,ViewLayer::ViewLayerID viewLayerID)1429 QString ResizableBoard::getShapeForRenderer(const QString & svg, ViewLayer::ViewLayerID viewLayerID)
1430 {
1431     QString xmlName = ViewLayer::viewLayerXmlNameFromID(viewLayerID);
1432 	SvgFileSplitter splitter;
1433     QString xml = svg;
1434 	bool result = splitter.splitString(xml, xmlName);
1435 	if (result) {
1436         xml = splitter.elementString(xmlName);
1437     }
1438     else {
1439 		xml = "";
1440 	}
1441 
1442     QString header("<?xml version='1.0' encoding='UTF-8'?>\n"
1443                     "<svg ");
1444     QDomNamedNodeMap map = splitter.domDocument().documentElement().attributes();
1445     for (int i = 0; i < map.count(); i++) {
1446         QDomNode node = map.item(i);
1447         header += node.nodeName() + "='" + node.nodeValue() + "' ";
1448     }
1449     header += ">\n";
1450 
1451     header = header + xml + "\n</svg>";
1452     //DebugDialog::debug("resizableBoard " + header);
1453 	return header;
1454 }
1455 
keepAspectRatio(bool checkState)1456 void ResizableBoard::keepAspectRatio(bool checkState) {
1457 	m_keepAspectRatio = checkState;
1458 }
1459 
revertSize(bool)1460 void ResizableBoard::revertSize(bool) {
1461     double ow = modelPart()->localProp("originalWidth").toDouble();
1462     double oh = modelPart()->localProp("originalHeight").toDouble();
1463 
1464     InfoGraphicsView * infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
1465 	if (infoGraphicsView != NULL) {
1466 		infoGraphicsView->resizeBoard(ow, oh, true);
1467         m_revertButton->setEnabled(false);
1468 	}
1469 }
1470 
updatePaperSizes(double w,double h)1471 void ResizableBoard::updatePaperSizes(double w, double h) {
1472     if (m_paperSizeComboBox ==  NULL) return;
1473 
1474     int currentIndex = 0;
1475     for (int i = 0; i < PaperSizeNames.count(); i++) {
1476         QSizeF dim = PaperSizeDimensions.at(i);
1477         if (qAbs(w - dim.width()) < 0.1 && qAbs(h - dim.height()) < 0.1) {
1478             currentIndex = i + 1;
1479         }
1480     }
1481 
1482     QString custom = tr("custom");
1483     if (currentIndex == 0) {
1484         custom =  QString("%1x%2").arg(w).arg(h);
1485     }
1486     m_paperSizeComboBox->setItemText(0, custom);
1487     m_paperSizeComboBox->setCurrentIndex(currentIndex);
1488 }
1489 
initPaperSizes()1490 void ResizableBoard::initPaperSizes() {
1491     if (PaperSizeNames.count() == 0) {
1492         PaperSizeNames << tr("A0 (1030x1456)") << tr("A1 (728x1030)") << tr("A2 (515x728)") << tr("A3 (364x515)") << tr("A4 (257x364)") << tr("A5 (182x257)") << tr("A6 (128x182)")
1493             << tr("Letter (8.5x11)") << tr("Legal (8.5x14)") << tr("Ledger (17x11)") << tr("Tabloid (11x17)");
1494         PaperSizeDimensions << QSizeF(1030,1456) << QSizeF(728,1030) << QSizeF(515,728) << QSizeF(364,515) << QSizeF(257,364) << QSizeF(182,257) << QSizeF(128,182)
1495             << QSizeF(215.9,279.4) << QSizeF(215.9,355.6) << QSizeF(432,279) << QSizeF(279,432);
1496     }
1497 }
1498