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