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: 6980 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 01:45:43 +0200 (Mo, 22. Apr 2013) $
24
25 ********************************************************************/
26
27 /* TO DO ******************************************
28
29
30 ///////////////////////////////// first release ///////////////////////////////
31
32 clicking set bus mode fucks up anchor points
33
34 terminal point area is not highlighting properly
35
36 ////////////////////// second release /////////////////////////////////
37
38 icon editor?
39
40 need simplified infoview
41
42 change color of breadboard text item
43
44 for svg import check for flaws:
45 multiple connector or terminal ids
46 trash any other matching id
47
48 loading tht into smd or v.v.
49 can't seem to assign connectors after adding connectors
50
51 why isn't swapping available when a family has new parts with multiple variant values?
52
53 test button with export etchable to make sure the part is right?
54 export etchable is disabled without a board
55
56 align to grid when moving?
57
58 restore editable pin labels functionality
59 requires storing labels in the part rather than in the sketch
60
61 hidden connectors
62 allow multiple hits to the same place and add copies of the svg element if necessary
63
64 restore parts bin
65
66 buses
67 secondary representation (list view)
68 display during bus mode instead of connector list
69 allow to delete bus, delete nodemember, add nodemember using right-click for now
70
71 use the actual svg shape instead of rectangles
72 construct a new svg with the width and height of the bounding box of the element
73 translate the element by the current top-left of the element
74 set the fill color and kill the stroke width
75
76 properties and tags entries allow duplicates
77
78 delete temp files after crash
79
80 show in OS button only shows folder and not file in linux
81
82 smd vs. tht
83 after it's all over remove copper0 if part is all smd
84 split copper0 and copper1
85 allow mixed tht/smd parts in fritzing but for now disable flipsmd
86 allow smd with holes to flip?
87
88 import
89 kicad sch files?
90 kicad schematic does not match our schematic
91
92 on svg import detect all connector IDs
93 if any are invisible, tell user this is obsolete
94
95 sort connector list alphabetically or numerically?
96
97 full svg outline view
98
99 bendable legs
100 same as terminalID, but must be a line
101 use a radio buttons to distinguish?
102
103 set flippable
104
105 connector duplicate op
106
107 add layers: put everything in silkscreen, then give copper1, copper0 checkbox
108 what about breadboardbreadboard or other odd layers?
109 if you click something as a connector, automatically move it into copper
110 how to distinguish between both and top--default to both, let user set "pad"
111 setting layer for top level group sets all children?
112
113 swap connector metadata op
114
115 delete op
116
117 move connectors with arrow keys, or typed coordinates
118 drag and drop later
119
120 import
121 eagle lbr
122 eagle brd
123
124 for schematic view
125 offer lines-or-pins, rects, and a selection of standard schematic icons in the parts bin
126
127 for breadboard view
128 import
129 generate ICs, dips, sips, breakouts
130
131 for pcb view
132 pads, pins (circle, rect, oblong), holes
133 lines and curves?
134 import silkscreen
135
136 hybrids
137
138 flip and rotate images w/in the view?
139
140 taxonomy entry like tag entry?
141
142 new schematic layout specs
143
144 give users a family popup with all family names
145 ditto for other properties
146
147 matrix problem with move and duplicate (i.e. if element inherits a matrix from far above)
148 even a problem when inserting hole, pad, or pin
149 eliminate internal transforms, then insert inverted matrix, then use untransformed coords based on viewbox
150
151
152 ***************************************************/
153
154 #include "pemainwindow.h"
155 #include "pemetadataview.h"
156 #include "peconnectorsview.h"
157 #include "pecommands.h"
158 #include "petoolview.h"
159 #include "pesvgview.h"
160 #include "pegraphicsitem.h"
161 #include "kicadmoduledialog.h"
162 #include "../debugdialog.h"
163 #include "../sketch/breadboardsketchwidget.h"
164 #include "../sketch/schematicsketchwidget.h"
165 #include "../sketch/pcbsketchwidget.h"
166 #include "../mainwindow/sketchareawidget.h"
167 #include "../referencemodel/referencemodel.h"
168 #include "../utils/graphicsutils.h"
169 #include "../utils/textutils.h"
170 #include "../utils/folderutils.h"
171 #include "../utils/s2s.h"
172 #include "../mainwindow/fdockwidget.h"
173 #include "../fsvgrenderer.h"
174 #include "../partsbinpalette/binmanager/binmanager.h"
175 #include "../svg/gedaelement2svg.h"
176 #include "../svg/kicadmodule2svg.h"
177 #include "../svg/kicadschematic2svg.h"
178 #include "../sketchtoolbutton.h"
179 #include "../items/virtualwire.h"
180 #include "../connectors/connectoritem.h"
181 #include "../connectors/bus.h"
182 #include "../installedfonts.h"
183 #include "../dock/layerpalette.h"
184 #include "../utils/cursormaster.h"
185 #include "../infoview/htmlinfoview.h"
186
187 #include <QtDebug>
188 #include <QInputDialog>
189 #include <QApplication>
190 #include <QSvgGenerator>
191 #include <QMenuBar>
192 #include <QMessageBox>
193 #include <QDesktopServices>
194 #include <QUrl>
195 #include <QBuffer>
196 #include <QClipboard>
197 #include <limits>
198
199 ////////////////////////////////////////////////////
200
201 static bool RubberBandLegWarning = false;
202
203 static bool GotZeroConnector = false;
204
205 static const QString ReferenceFileString("referenceFile");
206
207 static const int IconViewIndex = 3;
208 static const int MetadataViewIndex = 4;
209 static const int ConnectorsViewIndex = 5;
210
211 const static int PegiZ = 5000;
212 const static int RatZ = 6000;
213
214 static long FakeGornSiblingNumber = 0;
215
removeGornAux(QDomElement & element)216 void removeGornAux(QDomElement & element) {
217 bool hasGorn = element.hasAttribute("gorn");
218 bool hasOld = element.hasAttribute("oldid");
219 if (hasGorn && hasOld) {
220 element.removeAttribute("gorn");
221 QString oldid = element.attribute("oldid");
222 if (oldid.isEmpty()) element.removeAttribute("id");
223 else element.setAttribute("id", oldid);
224 element.removeAttribute("oldid");
225 }
226 QDomElement child = element.firstChildElement();
227 while (!child.isNull()) {
228 removeGornAux(child);
229 child = child.nextSiblingElement();
230 }
231 }
232
removeGorn(QString & svg)233 QString removeGorn(QString & svg) {
234 QDomDocument doc;
235
236 QString errorStr;
237 int errorLine;
238 int errorColumn;
239 if (!doc.setContent(svg, &errorStr, &errorLine, &errorColumn)) {
240 // shouldn't happen
241 DebugDialog::debug(QString("remove gorn failure: %1 %2 %3 %4").arg(errorStr).arg(errorLine).arg(errorColumn).arg(svg));
242 return svg;
243 }
244
245 QDomElement root = doc.documentElement();
246 removeGornAux(root);
247
248 return doc.toString(4);
249 }
250
byID(QDomElement & c1,QDomElement & c2)251 bool byID(QDomElement & c1, QDomElement & c2)
252 {
253 int c1id = -1;
254 int c2id = -1;
255 int ix = IntegerFinder.indexIn(c1.attribute("id"));
256 if (ix >= 0) c1id = IntegerFinder.cap(0).toInt();
257 ix = IntegerFinder.indexIn(c2.attribute("id"));
258 if (ix >= 0) c2id = IntegerFinder.cap(0).toInt();
259
260 if (c1id == 0 || c2id == 0) GotZeroConnector = true;
261
262 return c1id <= c2id;
263 }
264
removeID(QDomElement & root,const QString & value)265 void removeID(QDomElement & root, const QString & value) {
266 if (root.attribute("id") == value) {
267 root.removeAttribute("id");
268 }
269
270 QDomNodeList nodeList = root.childNodes();
271 for (int i = 0; i < nodeList.count(); i++) {
272 QDomNode node = nodeList.item(i);
273 if (node.nodeType() == QDomNode::ElementNode) {
274 QDomElement element = node.toElement();
275 removeID(element, value);
276 }
277 }
278 }
279
280 ////////////////////////////////////////////////////
281
IconSketchWidget(ViewLayer::ViewID viewID,QWidget * parent)282 IconSketchWidget::IconSketchWidget(ViewLayer::ViewID viewID, QWidget *parent)
283 : SketchWidget(viewID, parent)
284 {
285 m_shortName = QObject::tr("ii");
286 m_viewName = QObject::tr("Icon View");
287 initBackgroundColor();
288 }
289
addViewLayers()290 void IconSketchWidget::addViewLayers() {
291 setViewLayerIDs(ViewLayer::Icon, ViewLayer::Icon, ViewLayer::Icon, ViewLayer::Icon, ViewLayer::Icon);
292 addViewLayersAux(ViewLayer::layersForView(ViewLayer::IconView), ViewLayer::layersForViewFromBelow(ViewLayer::IconView));
293 }
294
295 ////////////////////////////////////////////////////
296
ViewThing()297 ViewThing::ViewThing() {
298 itemBase = NULL;
299 document = NULL;
300 svgChangeCount = 0;
301 everZoomed = false;
302 sketchWidget = NULL;
303 firstTime = true;
304 busMode = false;
305 }
306
307 /////////////////////////////////////////////////////
308
PEMainWindow(ReferenceModel * referenceModel,QWidget * parent)309 PEMainWindow::PEMainWindow(ReferenceModel * referenceModel, QWidget * parent)
310 : MainWindow(referenceModel, parent)
311 {
312 m_viewThings.insert(ViewLayer::BreadboardView, new ViewThing);
313 m_viewThings.insert(ViewLayer::SchematicView, new ViewThing);
314 m_viewThings.insert(ViewLayer::PCBView, new ViewThing);
315 m_viewThings.insert(ViewLayer::IconView, new ViewThing);
316
317 m_useNextPick = m_inPickMode = false;
318 m_autosaveTimer.stop();
319 disconnect(&m_autosaveTimer, SIGNAL(timeout()), this, SLOT(backupSketch()));
320 m_gaveSaveWarning = m_canSave = false;
321 m_settingsPrefix = "pe/";
322 m_guid = TextUtils::getRandText();
323 m_prefix = "prefix0000";
324 m_fileIndex = 0;
325 m_userPartsFolderPath = FolderUtils::getUserDataStorePath("parts")+"/user/";
326 m_userPartsFolderSvgPath = FolderUtils::getUserDataStorePath("parts")+"/svg/user/";
327 m_peToolView = NULL;
328 m_peSvgView = NULL;
329 m_connectorsView = NULL;
330 }
331
~PEMainWindow()332 PEMainWindow::~PEMainWindow()
333 {
334 // PEGraphicsItems are still holding QDomElement so delete them before m_fzpDocument is deleted
335 killPegi();
336
337 // kill temp files
338 foreach (QString string, m_filesToDelete) {
339 QFile::remove(string);
340 }
341 QDir dir = QDir::temp();
342 dir.rmdir(makeDirName());
343 }
344
closeEvent(QCloseEvent * event)345 void PEMainWindow::closeEvent(QCloseEvent *event)
346 {
347 qDebug() << "close event";
348
349 QStringList messages;
350
351 if (m_inFocusWidgets.count() > 0) {
352 bool gotOne = false;
353 // should only be one in-focus widget
354 foreach (QWidget * widget, m_inFocusWidgets) {
355 QLineEdit * lineEdit = qobject_cast<QLineEdit *>(widget);
356 if (lineEdit) {
357 if (lineEdit->isModified()) {
358 lineEdit->clearFocus();
359 lineEdit->setModified(false);
360 gotOne = true;
361 }
362 }
363 else {
364 QTextEdit * textEdit = qobject_cast<QTextEdit *>(widget);
365 if (textEdit) {
366 if (textEdit->document()->isModified()) {
367 textEdit->clearFocus();
368 textEdit->document()->setModified(false);
369 gotOne = true;
370 }
371 }
372 }
373 }
374 if (gotOne) {
375 messages << tr("There is one last edit still pending.");
376 }
377 }
378
379 QString family = m_metadataView->family();
380 if (family.isEmpty()) {
381 messages << tr("The 'family' property can not be blank.");
382 }
383
384 QStringList keys = m_metadataView->properties().keys();
385
386 if (keys.contains("family", Qt::CaseInsensitive)) {
387 messages << tr("A duplicate 'family' property is not allowed");
388 }
389
390 if (keys.contains("variant", Qt::CaseInsensitive)) {
391 messages << tr("A duplicate 'variant' property is not allowed");
392 }
393
394 bool discard = true;
395 if (messages.count() > 0) {
396 QMessageBox messageBox(this);
397 messageBox.setWindowTitle(tr("Close without saving?"));
398
399 QString message = tr("This part can not be saved as-is:\n\n");
400 foreach (QString string, messages) {
401 message.append('\t');
402 message.append(string);
403 messages.append("\n\n");
404 }
405
406 message += tr("Do you want to keep working or close without saving?");
407
408 messageBox.setText(message);
409 messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
410 messageBox.setDefaultButton(QMessageBox::Cancel);
411 messageBox.setIcon(QMessageBox::Warning);
412 messageBox.setWindowModality(Qt::WindowModal);
413 messageBox.setButtonText(QMessageBox::Ok, tr("Close without saving"));
414 messageBox.setButtonText(QMessageBox::Cancel, tr("Keep working"));
415 QMessageBox::StandardButton answer = (QMessageBox::StandardButton) messageBox.exec();
416
417 if (answer != QMessageBox::Ok) {
418 event->ignore();
419 return;
420 }
421 }
422 else if (!beforeClosing(true, discard)) {
423 event->ignore();
424 return;
425 }
426
427 if (!discard) {
428 // if all connectors not found, put up a message
429 connectorWarning();
430 }
431
432 QSettings settings;
433 settings.setValue(m_settingsPrefix + "state", saveState());
434 settings.setValue(m_settingsPrefix + "geometry", saveGeometry());
435
436 QMainWindow::closeEvent(event);
437 }
438
initLockedFiles(bool)439 void PEMainWindow::initLockedFiles(bool) {
440 }
441
initSketchWidgets(bool whatever)442 void PEMainWindow::initSketchWidgets(bool whatever)
443 {
444 Q_UNUSED(whatever);
445
446 MainWindow::initSketchWidgets(false);
447
448 m_iconGraphicsView = new IconSketchWidget(ViewLayer::IconView, this);
449 initSketchWidget(m_iconGraphicsView);
450 m_iconWidget = new SketchAreaWidget(m_iconGraphicsView,this);
451 addTab(m_iconWidget, tr("Icon"));
452 initSketchWidget(m_iconGraphicsView);
453
454 ViewThing * viewThing = m_viewThings.value(m_breadboardGraphicsView->viewID());
455 viewThing->sketchWidget = m_breadboardGraphicsView;
456 viewThing->document = &m_breadboardDocument;
457
458 viewThing = m_viewThings.value(m_schematicGraphicsView->viewID());
459 viewThing->sketchWidget = m_schematicGraphicsView;
460 viewThing->document = &m_schematicDocument;
461
462 viewThing = m_viewThings.value(m_pcbGraphicsView->viewID());
463 viewThing->sketchWidget = m_pcbGraphicsView;
464 viewThing->document = &m_pcbDocument;
465
466 viewThing = m_viewThings.value(m_iconGraphicsView->viewID());
467 viewThing->sketchWidget = m_iconGraphicsView;
468 viewThing->document = &m_iconDocument;
469
470 foreach (ViewThing * viewThing, m_viewThings.values()) {
471 viewThing->sketchWidget->setAcceptWheelEvents(true);
472 viewThing->sketchWidget->setChainDrag(false); // no bendpoints
473 viewThing->firstTime = true;
474 viewThing->everZoomed = true;
475 connect(viewThing->sketchWidget, SIGNAL(newWireSignal(Wire *)), this, SLOT(newWireSlot(Wire *)));
476 connect(viewThing->sketchWidget, SIGNAL(showing(SketchWidget *)), this, SLOT(showing(SketchWidget *)));
477 connect(viewThing->sketchWidget, SIGNAL(itemMovedSignal(ItemBase *)), this, SLOT(itemMovedSlot(ItemBase *)));
478 connect(viewThing->sketchWidget, SIGNAL(resizedSignal(ItemBase *)), this, SLOT(resizedSlot(ItemBase *)));
479 connect(viewThing->sketchWidget, SIGNAL(clickedItemCandidateSignal(QGraphicsItem *, bool &)), this, SLOT(clickedItemCandidateSlot(QGraphicsItem *, bool &)), Qt::DirectConnection);
480 connect(viewThing->sketchWidget, SIGNAL(itemAddedSignal(ModelPart *, ItemBase *, ViewLayer::ViewLayerPlacement, const ViewGeometry &, long, SketchWidget *)),
481 this, SLOT(itemAddedSlot(ModelPart *, ItemBase *, ViewLayer::ViewLayerPlacement, const ViewGeometry &, long, SketchWidget *)));
482
483
484 }
485
486 m_metadataView = new PEMetadataView(this);
487 SketchAreaWidget * sketchAreaWidget = new SketchAreaWidget(m_metadataView, this, false, false);
488 addTab(sketchAreaWidget, tr("Metadata"));
489 connect(m_metadataView, SIGNAL(metadataChanged(const QString &, const QString &)), this, SLOT(metadataChanged(const QString &, const QString &)), Qt::DirectConnection);
490 connect(m_metadataView, SIGNAL(tagsChanged(const QStringList &)), this, SLOT(tagsChanged(const QStringList &)), Qt::DirectConnection);
491 connect(m_metadataView, SIGNAL(propertiesChanged(const QHash<QString, QString> &)), this, SLOT(propertiesChanged(const QHash<QString, QString> &)), Qt::DirectConnection);
492
493 m_connectorsView = new PEConnectorsView(this);
494 sketchAreaWidget = new SketchAreaWidget(m_connectorsView, this, false, false);
495 addTab(sketchAreaWidget, tr("Connectors"));
496
497 connect(m_connectorsView, SIGNAL(connectorMetadataChanged(ConnectorMetadata *)), this, SLOT(connectorMetadataChanged(ConnectorMetadata *)), Qt::DirectConnection);
498 connect(m_connectorsView, SIGNAL(connectorsTypeChanged(Connector::ConnectorType)), this, SLOT(connectorsTypeChanged(Connector::ConnectorType)));
499 connect(m_connectorsView, SIGNAL(removedConnectors(QList<ConnectorMetadata *> &)), this, SLOT(removedConnectors(QList<ConnectorMetadata *> &)), Qt::DirectConnection);
500 connect(m_connectorsView, SIGNAL(connectorCountChanged(int)), this, SLOT(connectorCountChanged(int)), Qt::DirectConnection);
501 connect(m_connectorsView, SIGNAL(smdChanged(const QString &)), this, SLOT(smdChanged(const QString &)));
502
503 }
504
initDock()505 void PEMainWindow::initDock()
506 {
507 m_layerPalette = new LayerPalette(this);
508
509 /*
510 m_infoView = new HtmlInfoView();
511 m_infoView->init(true);
512
513 m_binManager = new BinManager(m_referenceModel, m_infoView, m_undoStack, this);
514 m_binManager->openBin(":/resources/bins/pe.fzb");
515 m_binManager->hideTabBar();
516 */
517 }
518
moreInitDock()519 void PEMainWindow::moreInitDock()
520 {
521 static int BinMinHeight = 75;
522 static int ToolDefaultHeight = 150;
523 static int SvgDefaultHeight = 50;
524
525
526 m_peToolView = new PEToolView();
527 connect(m_peToolView, SIGNAL(getSpinAmount(double &)), this, SLOT(getSpinAmount(double &)), Qt::DirectConnection);
528 connect(m_peToolView, SIGNAL(terminalPointChanged(const QString &)), this, SLOT(terminalPointChanged(const QString &)));
529 connect(m_peToolView, SIGNAL(terminalPointChanged(const QString &, double)), this, SLOT(terminalPointChanged(const QString &, double)));
530 connect(m_peToolView, SIGNAL(switchedConnector(int)), this, SLOT(switchedConnector(int)));
531 connect(m_peToolView, SIGNAL(removedConnector(const QDomElement &)), this, SLOT(removedConnector(const QDomElement &)));
532 connect(m_peToolView, SIGNAL(pickModeChanged(bool)), this, SLOT(pickModeChanged(bool)));
533 connect(m_peToolView, SIGNAL(busModeChanged(bool)), this, SLOT(busModeChanged(bool)));
534 connect(m_peToolView, SIGNAL(connectorMetadataChanged(ConnectorMetadata *)), this, SLOT(connectorMetadataChanged(ConnectorMetadata *)), Qt::DirectConnection);
535 makeDock(tr("Connectors"), m_peToolView, DockMinWidth, ToolDefaultHeight);
536 m_peToolView->setMinimumSize(DockMinWidth, ToolDefaultHeight);
537
538 m_peSvgView = new PESvgView();
539 makeDock(tr("SVG"), m_peSvgView, DockMinWidth, SvgDefaultHeight);
540 m_peSvgView->setMinimumSize(DockMinWidth, SvgDefaultHeight);
541
542 if (m_binManager) {
543 QDockWidget * dockWidget = makeDock(BinManager::Title, m_binManager, DockMinWidth, BinMinHeight);
544 dockWidget->resize(0, 0);
545 }
546
547 if (m_infoView) {
548 makeDock(tr("Inspector"), m_infoView, InfoViewMinHeight, InfoViewHeightDefault);
549 this -> setObjectName("PEInspector");
550 }
551
552 makeDock(tr("Layers"), m_layerPalette, DockMinWidth, DockMinHeight)->hide();
553 m_layerPalette->setMinimumSize(DockMinWidth, DockMinHeight);
554 m_layerPalette->setShowAllLayersAction(m_showAllLayersAct);
555 m_layerPalette->setHideAllLayersAction(m_hideAllLayersAct);
556
557 }
558
createFileMenuActions()559 void PEMainWindow::createFileMenuActions() {
560 MainWindow::createFileMenuActions();
561
562 m_reuseBreadboardAct = new QAction(tr("Reuse breadboard image"), this);
563 m_reuseBreadboardAct->setStatusTip(tr("Reuse the breadboard image in this view"));
564 connect(m_reuseBreadboardAct, SIGNAL(triggered()), this, SLOT(reuseBreadboard()));
565
566 m_reuseSchematicAct = new QAction(tr("Reuse schematic image"), this);
567 m_reuseSchematicAct->setStatusTip(tr("Reuse the schematic image in this view"));
568 connect(m_reuseSchematicAct, SIGNAL(triggered()), this, SLOT(reuseSchematic()));
569
570 m_reusePCBAct = new QAction(tr("Reuse PCB image"), this);
571 m_reusePCBAct->setStatusTip(tr("Reuse the PCB image in this view"));
572 connect(m_reusePCBAct, SIGNAL(triggered()), this, SLOT(reusePCB()));
573
574 }
575
createActions()576 void PEMainWindow::createActions()
577 {
578 createFileMenuActions();
579
580 m_openAct->setText(tr("Load image for view..."));
581 m_openAct->setStatusTip(tr("Open a file to use as the image for this view of the part."));
582 disconnect(m_openAct, SIGNAL(triggered()), this, SLOT(mainLoad()));
583 connect(m_openAct, SIGNAL(triggered()), this, SLOT(loadImage()));
584
585 m_showInOSAct = new QAction(tr("Show in Folder"), this);
586 m_showInOSAct->setStatusTip(tr("On the desktop, open the folder containing the current svg file."));
587 connect(m_showInOSAct, SIGNAL(triggered()), this, SLOT(showInOS()));
588
589 createEditMenuActions();
590
591 m_convertToTenthAct = new QAction(tr("Convert schematic to 0.1 inch standard"), this);
592 m_convertToTenthAct->setStatusTip(tr("Convert pre-0.8.6 schematic image to new 0.1 inch standard"));
593 connect(m_convertToTenthAct, SIGNAL(triggered()), this, SLOT(convertToTenth()));
594
595 m_deleteBusConnectionAct = new WireAction(tr("Remove Internal Connection"), this);
596 connect(m_deleteBusConnectionAct, SIGNAL(triggered()), this, SLOT(deleteBusConnection()));
597
598 createViewMenuActions(false);
599 createHelpMenuActions();
600 createWindowMenuActions();
601 createActiveLayerActions();
602
603 }
604
createMenus()605 void PEMainWindow::createMenus()
606 {
607 createFileMenu();
608 createEditMenu();
609 createViewMenu();
610 createWindowMenu();
611 createHelpMenu();
612 }
613
createFileMenu()614 void PEMainWindow::createFileMenu() {
615 m_fileMenu = menuBar()->addMenu(tr("&File"));
616 m_fileMenu->addAction(m_openAct);
617 m_fileMenu->addAction(m_reuseBreadboardAct);
618 m_fileMenu->addAction(m_reuseSchematicAct);
619 m_fileMenu->addAction(m_reusePCBAct);
620
621 //m_fileMenu->addAction(m_revertAct);
622
623 m_fileMenu->addSeparator();
624 m_fileMenu->addAction(m_closeAct);
625 m_fileMenu->addAction(m_saveAct);
626 m_fileMenu->addAction(m_saveAsAct);
627 m_saveAsAct->setText(tr("Save as new part"));
628 m_saveAsAct->setStatusTip(tr("Make a copy of the part and save it in the 'My Parts' Bin"));
629
630 m_fileMenu->addSeparator();
631 m_exportMenu = m_fileMenu->addMenu(tr("&Export"));
632 //m_fileMenu->addAction(m_pageSetupAct);
633 m_fileMenu->addAction(m_printAct);
634 m_fileMenu->addAction(m_showInOSAct);
635
636 m_fileMenu->addSeparator();
637 m_fileMenu->addAction(m_quitAct);
638
639 populateExportMenu();
640
641 connect(m_fileMenu, SIGNAL(aboutToShow()), this, SLOT(updateFileMenu()));
642 }
643
createEditMenu()644 void PEMainWindow::createEditMenu()
645 {
646 m_editMenu = menuBar()->addMenu(tr("&Edit"));
647 m_editMenu->addAction(m_undoAct);
648 m_editMenu->addAction(m_redoAct);
649 m_editMenu->addSeparator();
650 m_editMenu->addAction(m_convertToTenthAct);
651
652 updateEditMenu();
653 connect(m_editMenu, SIGNAL(aboutToShow()), this, SLOT(updateEditMenu()));
654 }
655
getButtonsForView(ViewLayer::ViewID viewID)656 QList<QWidget*> PEMainWindow::getButtonsForView(ViewLayer::ViewID viewID) {
657
658 QList<QWidget*> retval;
659 SketchAreaWidget *parent;
660 switch(viewID) {
661 case ViewLayer::BreadboardView: parent = m_breadboardWidget; break;
662 case ViewLayer::SchematicView: parent = m_schematicWidget; break;
663 case ViewLayer::PCBView: parent = m_pcbWidget; break;
664 default: return retval;
665 }
666
667 //retval << createExportEtchableButton(parent);
668
669 switch (viewID) {
670 case ViewLayer::BreadboardView:
671 break;
672 case ViewLayer::SchematicView:
673 break;
674 case ViewLayer::PCBView:
675 // retval << createActiveLayerButton(parent);
676 break;
677 default:
678 break;
679 }
680
681 return retval;
682 }
683
activeLayerWidgetAlwaysOn()684 bool PEMainWindow::activeLayerWidgetAlwaysOn() {
685 return true;
686 }
687
connectPairs()688 void PEMainWindow::connectPairs() {
689 bool succeeded = true;
690 succeeded = succeeded && connect(qApp, SIGNAL(spaceBarIsPressedSignal(bool)), m_breadboardGraphicsView, SLOT(spaceBarIsPressedSlot(bool)));
691 succeeded = succeeded && connect(qApp, SIGNAL(spaceBarIsPressedSignal(bool)), m_schematicGraphicsView, SLOT(spaceBarIsPressedSlot(bool)));
692 succeeded = succeeded && connect(qApp, SIGNAL(spaceBarIsPressedSignal(bool)), m_pcbGraphicsView, SLOT(spaceBarIsPressedSlot(bool)));
693
694 succeeded = succeeded && connect(m_pcbGraphicsView, SIGNAL(cursorLocationSignal(double, double, double, double)), this, SLOT(cursorLocationSlot(double, double, double, double)));
695 succeeded = succeeded && connect(m_breadboardGraphicsView, SIGNAL(cursorLocationSignal(double, double, double, double)), this, SLOT(cursorLocationSlot(double, double, double, double)));
696 succeeded = succeeded && connect(m_schematicGraphicsView, SIGNAL(cursorLocationSignal(double, double, double, double)), this, SLOT(cursorLocationSlot(double, double, double, double)));
697
698 connect(m_breadboardGraphicsView, SIGNAL(setActiveWireSignal(Wire *)), this, SLOT(setActiveWire(Wire *)));
699 connect(m_schematicGraphicsView, SIGNAL(setActiveWireSignal(Wire *)), this, SLOT(setActiveWire(Wire *)));
700 connect(m_pcbGraphicsView, SIGNAL(setActiveWireSignal(Wire *)), this, SLOT(setActiveWire(Wire *)));
701
702 }
703
breadboardWireMenu()704 QMenu *PEMainWindow::breadboardWireMenu() {
705 QMenu *menu = new QMenu(QObject::tr("Internal Connections"), this);
706
707 menu->addAction(m_deleteBusConnectionAct);
708 connect( menu, SIGNAL(aboutToShow()), this, SLOT(updateWireMenu()));
709 return menu;
710 }
711
breadboardItemMenu()712 QMenu *PEMainWindow::breadboardItemMenu() {
713 return NULL;
714 }
715
schematicWireMenu()716 QMenu *PEMainWindow::schematicWireMenu() {
717 return breadboardWireMenu();
718 }
719
schematicItemMenu()720 QMenu *PEMainWindow::schematicItemMenu() {
721 return NULL;
722 }
723
pcbWireMenu()724 QMenu *PEMainWindow::pcbWireMenu() {
725 return breadboardWireMenu();
726 }
727
pcbItemMenu()728 QMenu *PEMainWindow::pcbItemMenu() {
729 return NULL;
730 }
731
setInitialItem(PaletteItem * paletteItem)732 bool PEMainWindow::setInitialItem(PaletteItem * paletteItem)
733 {
734 m_pcbGraphicsView->setBoardLayers(2, true);
735 m_pcbGraphicsView->setLayerActive(ViewLayer::Copper1, true);
736 m_pcbGraphicsView->setLayerActive(ViewLayer::Copper0, true);
737 m_pcbGraphicsView->setLayerActive(ViewLayer::Silkscreen1, true);
738 m_pcbGraphicsView->setLayerActive(ViewLayer::Silkscreen0, true);
739
740 ModelPart * originalModelPart = NULL;
741 if (paletteItem == NULL) {
742 // this shouldn't happen
743 originalModelPart = m_referenceModel->retrieveModelPart("generic_ic_dip_8_300mil");
744 }
745 else {
746 originalModelPart = paletteItem->modelPart();
747 }
748
749 m_originalFzpPath = originalModelPart->path();
750 m_originalModuleID = originalModelPart->moduleID();
751
752 QFileInfo info(originalModelPart->path());
753 QString basename = info.completeBaseName();
754 int ix = GuidMatcher.indexIn(basename);
755 if (ix > 1 && basename.at(ix - 1) == '_') {
756 int dix = ix + 32 + 1;
757 if (basename.count() > dix) {
758 bool gotPrefix = true;
759 for (int i = dix; i < basename.count(); i++) {
760 if (!basename.at(i).isDigit()) {
761 gotPrefix = false;
762 break;
763 }
764 }
765 if (gotPrefix) {
766 m_prefix = basename.left(ix - 1);
767 }
768 }
769 }
770
771 //DebugDialog::debug(QString("%1, %2").arg(info.absoluteFilePath()).arg(m_userPartsFolderPath));
772 m_canSave = info.absoluteFilePath().contains(m_userPartsFolderPath);
773
774 if (!loadFzp(originalModelPart->path())) return false;
775
776 QDomElement fzpRoot = m_fzpDocument.documentElement();
777 QString referenceFile = fzpRoot.attribute(ReferenceFileString);
778 if (referenceFile.isEmpty()) {
779 fzpRoot.setAttribute(ReferenceFileString, info.fileName());
780 }
781 QDomElement views = fzpRoot.firstChildElement("views");
782 QDomElement author = fzpRoot.firstChildElement("author");
783 if (author.isNull()) {
784 author = m_fzpDocument.createElement("author");
785 fzpRoot.appendChild(author);
786 }
787 if (author.text().isEmpty()) {
788 TextUtils::replaceChildText(author, QString(getenvUser()));
789 }
790 QDomElement date = fzpRoot.firstChildElement("date");
791 if (date.isNull()) {
792 date = m_fzpDocument.createElement("date");
793 fzpRoot.appendChild(date);
794 }
795 TextUtils::replaceChildText(date, QDate::currentDate().toString());
796
797 QDomElement properties = fzpRoot.firstChildElement("properties");
798 if (properties.isNull()) {
799 properties = m_fzpDocument.createElement("properties");
800 fzpRoot.appendChild(properties);
801 }
802 QHash<QString,QString> props = originalModelPart->properties();
803 foreach (QString key, props.keys()) {
804 replaceProperty(key, props.value(key), properties);
805 }
806 // record "local" properties
807 foreach (QByteArray byteArray, originalModelPart->dynamicPropertyNames()) {
808 replaceProperty(byteArray, originalModelPart->property(byteArray).toString(), properties);
809 }
810
811 bool hasLegID = false;
812 QDomElement connectors = fzpRoot.firstChildElement("connectors");
813 QDomElement connector = connectors.firstChildElement("connector");
814 while (!connector.isNull()) {
815 QString localName = originalModelPart->connectorLocalName(connector.attribute("id"));
816 if (!localName.isEmpty()) {
817 connector.setAttribute("name", localName);
818 }
819 if (!hasLegID) {
820 QDomNodeList plist = connector.elementsByTagName("p");
821 for (int i = 0; i < plist.count(); i++) {
822 QDomElement p = plist.at(i).toElement();
823 if (!p.attribute("legId").isEmpty()) {
824 hasLegID = true;
825 break;
826 }
827 }
828 }
829 connector = connector.nextSiblingElement("connector");
830 }
831
832 if (hasLegID && !RubberBandLegWarning) {
833 RubberBandLegWarning = true;
834 QMessageBox::warning(NULL, tr("Parts Editor"),
835 tr("This part has bendable legs. ") +
836 tr("This version of the Parts Editor does not yet support editing bendable legs, and the legs may not be displayed correctly in breadboard view . ") +
837 tr("If you make changes to breadboard view, or change connector metadata, the legs may no longer work. ") +
838 tr("You can safely make changes to Schematic or PCB view.\n\n") +
839 tr("This warning will not be repeated in this session of Fritzing")
840 );
841 }
842
843
844
845 // for now kill the editable pin labels property, otherwise the saved part will try to use the labels that are only found in the sketch
846 QDomElement epl = TextUtils::findElementWithAttribute(properties, "name", "editable pin labels");
847 if (!epl.isNull()) {
848 TextUtils::replaceChildText(epl, "false");
849 }
850
851 QDomElement family = TextUtils::findElementWithAttribute(properties, "name", "family");
852 if (family.isNull()) {
853 replaceProperty("family", m_guid, properties);
854 }
855 QDomElement variant = TextUtils::findElementWithAttribute(properties, "name", "variant");
856 if (variant.isNull()) {
857 QString newVariant = makeNewVariant(family.text());
858 replaceProperty("variant", newVariant, properties);
859 }
860
861 foreach (ViewThing * viewThing, m_viewThings.values()) {
862 if (viewThing->sketchWidget == NULL) continue;
863
864 ItemBase * itemBase = originalModelPart->viewItem(viewThing->sketchWidget->viewID());
865 if (itemBase == NULL) continue;
866
867 viewThing->referenceFile = getSvgReferenceFile(itemBase->filename());
868
869 if (!itemBase->hasCustomSVG()) {
870 QFile file(itemBase->filename());
871 if (!file.open(QFile::ReadOnly)) {
872 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to load '%1'. Please close the parts editor without saving and try again.").arg(itemBase->filename()));
873 continue;
874 }
875
876 QString svg = file.readAll();
877 insertDesc(viewThing->referenceFile, svg);
878 TextUtils::fixMuch(svg, true);
879 QString svgPath = makeSvgPath2(viewThing->sketchWidget);
880 bool result = writeXml(m_userPartsFolderSvgPath + svgPath, removeGorn(svg), true);
881 if (!result) {
882 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to write svg to %1").arg(svgPath));
883 return false;
884 }
885
886 continue;
887 }
888
889 QHash<QString, QString> svgHash;
890 QStringList svgList;
891 double factor;
892 foreach (ViewLayer * vl, viewThing->sketchWidget->viewLayers().values()) {
893 QString string = itemBase->retrieveSvg(vl->viewLayerID(), svgHash, false, GraphicsUtils::StandardFritzingDPI, factor);
894 if (!string.isEmpty()) {
895 svgList.append(string);
896 }
897 }
898
899 if (svgList.count() == 0) {
900 DebugDialog::debug(QString("pe: missing custom svg %1").arg(originalModelPart->moduleID()));
901 continue;
902 }
903
904
905 QString svg;
906 if (svgList.count() == 1) {
907 svg = svgList.at(0);
908 }
909 else {
910 // deal with copper0 and copper1 layers as parent/child
911 // have to remove whitespace in order to compare the two svgs
912 QRegExp white("\\s");
913 QStringList whiteList;
914 foreach (QString string, svgList) {
915 string.remove(white);
916 whiteList << string;
917 }
918 bool keepGoing = true;
919 while (keepGoing) {
920 keepGoing = false;
921 for (int i = 0; i < whiteList.count() - 1; i++) {
922 for (int j = i + 1; j < whiteList.count(); j++) {
923 if (whiteList.at(i).contains(whiteList.at(j))) {
924 keepGoing = true;
925 whiteList.removeAt(j);
926 svgList.removeAt(j);
927 break;
928 }
929 if (whiteList.at(j).contains(whiteList.at(i))) {
930 keepGoing = true;
931 whiteList.removeAt(i);
932 svgList.removeAt(i);
933 break;
934 }
935 }
936 if (keepGoing) break;
937 }
938 }
939 svg = svgList.join("\n");
940 }
941
942 QSizeF size = itemBase->size();
943 QString header = TextUtils::makeSVGHeader(GraphicsUtils::SVGDPI, GraphicsUtils::StandardFritzingDPI, size.width(), size.height());
944 header += makeDesc(viewThing->referenceFile);
945 svg = header + svg + "</svg>";
946 QString svgPath = makeSvgPath2(viewThing->sketchWidget);
947 bool result = writeXml(m_userPartsFolderSvgPath + svgPath, removeGorn(svg), true);
948 if (!result) {
949 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to write svg to %1").arg(svgPath));
950 return false;
951 }
952
953 QDomElement view = views.firstChildElement(ViewLayer::viewIDXmlName(viewThing->sketchWidget->viewID()));
954 QDomElement layers = view.firstChildElement("layers");
955 if (layers.isNull()) {
956 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to parse fzp file %1").arg(originalModelPart->path()));
957 return false;
958 }
959
960 setImageAttribute(layers, svgPath);
961 }
962
963 reload(true);
964
965 foreach (ViewThing * viewThing, m_viewThings.values()) {
966 viewThing->originalSvgPath = viewThing->itemBase->filename();
967 viewThing->svgChangeCount = 0;
968 }
969
970 setTitle();
971 createRaiseWindowActions();
972 return true;
973 }
974
initZoom()975 void PEMainWindow::initZoom() {
976 if (m_peToolView == NULL) return;
977 if (m_currentGraphicsView == NULL) return;
978
979 ViewThing * viewThing = m_viewThings.value(m_currentGraphicsView->viewID());
980 if (viewThing->itemBase == NULL) return;
981
982 if (!viewThing->everZoomed) {
983 viewThing->everZoomed = true;
984 m_currentGraphicsView->fitInWindow();
985 }
986
987 m_peSvgView->setFilename(viewThing->referenceFile);
988 }
989
setTitle()990 void PEMainWindow::setTitle() {
991 QString title = tr("Fritzing (New) Parts Editor");
992 QString partTitle = getPartTitle();
993
994 QString viewName;
995 if (m_currentGraphicsView) viewName = m_currentGraphicsView->viewName();
996 else if (currentTabIndex() == IconViewIndex) viewName = tr("Icon View");
997 else if (currentTabIndex() == MetadataViewIndex) viewName = tr("Metadata View");
998 else if (currentTabIndex() == ConnectorsViewIndex) viewName = tr("Connectors View");
999
1000 setWindowTitle(QString("%1: %2 [%3]%4").arg(title).arg(partTitle).arg(viewName).arg(QtFunkyPlaceholder));
1001 }
1002
createViewMenuActions(bool showWelcome)1003 void PEMainWindow::createViewMenuActions(bool showWelcome) {
1004 MainWindow::createViewMenuActions(showWelcome);
1005
1006 m_showIconAct = new QAction(tr("Show Icon"), this);
1007 m_showIconAct->setShortcut(tr("Ctrl+4"));
1008 m_showIconAct->setStatusTip(tr("Show the icon view"));
1009 connect(m_showIconAct, SIGNAL(triggered()), this, SLOT(showIconView()));
1010
1011 m_showMetadataViewAct = new QAction(tr("Show Metadata"), this);
1012 m_showMetadataViewAct->setShortcut(tr("Ctrl+5"));
1013 m_showMetadataViewAct->setStatusTip(tr("Show the metadata view"));
1014 connect(m_showMetadataViewAct, SIGNAL(triggered()), this, SLOT(showMetadataView()));
1015
1016 m_showConnectorsViewAct = new QAction(tr("Show Connectors"), this);
1017 m_showConnectorsViewAct->setShortcut(tr("Ctrl+6"));
1018 m_showConnectorsViewAct->setStatusTip(tr("Show the connector metadata in a list view"));
1019 connect(m_showConnectorsViewAct, SIGNAL(triggered()), this, SLOT(showConnectorsView()));
1020
1021 m_hideOtherViewsAct = new QAction(tr("Make only this view visible"), this);
1022 m_hideOtherViewsAct->setStatusTip(tr("The part will only be visible in this view and icon view"));
1023 connect(m_hideOtherViewsAct, SIGNAL(triggered()), this, SLOT(hideOtherViews()));
1024
1025 }
1026
createViewMenu()1027 void PEMainWindow::createViewMenu() {
1028 MainWindow::createViewMenu();
1029
1030 bool afterNext = false;
1031 foreach (QAction * action, m_viewMenu->actions()) {
1032 if (action == m_setBackgroundColorAct) {
1033 afterNext = true;
1034 }
1035 else if (afterNext) {
1036 m_viewMenu->insertSeparator(action);
1037 m_viewMenu->insertAction(action, m_hideOtherViewsAct);
1038 break;
1039 }
1040 }
1041
1042 afterNext = false;
1043 foreach (QAction * action, m_viewMenu->actions()) {
1044 if (action == m_showPCBAct) {
1045 afterNext = true;
1046 }
1047 else if (afterNext) {
1048 m_viewMenu->insertAction(action, m_showIconAct);
1049 m_viewMenu->insertAction(action, m_showMetadataViewAct);
1050 m_viewMenu->insertAction(action, m_showConnectorsViewAct);
1051 break;
1052 }
1053 }
1054 m_numFixedActionsInViewMenu = m_viewMenu->actions().size();
1055 }
1056
showMetadataView()1057 void PEMainWindow::showMetadataView() {
1058 setCurrentTabIndex(MetadataViewIndex);
1059 }
1060
showConnectorsView()1061 void PEMainWindow::showConnectorsView() {
1062 setCurrentTabIndex(ConnectorsViewIndex);
1063 }
1064
showIconView()1065 void PEMainWindow::showIconView() {
1066 setCurrentTabIndex(IconViewIndex);
1067 }
1068
changeSpecialProperty(const QString & name,const QString & value)1069 void PEMainWindow::changeSpecialProperty(const QString & name, const QString & value)
1070 {
1071 QHash<QString, QString> oldProperties = getOldProperties();
1072
1073 if (value.isEmpty()) {
1074 QMessageBox::warning(NULL, tr("Blank not allowed"), tr("The value of '%1' can not be blank.").arg(name));
1075 m_metadataView->resetProperty(name, value);
1076 return;
1077 }
1078
1079 if (oldProperties.value(name) == value) {
1080 return;
1081 }
1082
1083 QHash<QString, QString> newProperties(oldProperties);
1084 newProperties.insert(name, value);
1085
1086 ChangePropertiesCommand * cpc = new ChangePropertiesCommand(this, oldProperties, newProperties, NULL);
1087 cpc->setText(tr("Change %1 to %2").arg(name).arg(value));
1088 cpc->setSkipFirstRedo();
1089 changeProperties(newProperties, false);
1090 m_undoStack->waitPush(cpc, SketchWidget::PropChangeDelay);
1091 }
1092
metadataChanged(const QString & name,const QString & value)1093 void PEMainWindow::metadataChanged(const QString & name, const QString & value)
1094 {
1095 qDebug() << "metadata changed";
1096
1097 if (name.compare("family") == 0) {
1098 changeSpecialProperty(name, value);
1099 return;
1100 }
1101
1102 if (name.compare("variant") == 0) {
1103 QString family = m_metadataView->family();
1104 QHash<QString, QString> variants = m_referenceModel->allPropValues(family, "variant");
1105 QStringList values = variants.values(value);
1106 if (m_canSave) {
1107 QString moduleID = m_fzpDocument.documentElement().attribute("moduleId");
1108 values.removeOne(moduleID);
1109 }
1110 if (values.count() > 0) {
1111 QMessageBox::warning(NULL, tr("Must be unique"), tr("Variant '%1' is in use. The variant name must be unique.").arg(value));
1112 return;
1113 }
1114
1115 changeSpecialProperty(name, value);
1116 return;
1117 }
1118
1119 QString menuText = (name.compare("description") == 0) ? tr("Change description") : tr("Change %1 to '%2'").arg(name).arg(value);
1120
1121 // called from metadataView
1122 QDomElement root = m_fzpDocument.documentElement();
1123 QDomElement element = root.firstChildElement(name);
1124 QString oldValue = element.text();
1125 if (oldValue == value) return;
1126
1127 ChangeMetadataCommand * cmc = new ChangeMetadataCommand(this, name, oldValue, value, NULL);
1128 cmc->setText(menuText);
1129 cmc->setSkipFirstRedo();
1130 changeMetadata(name, value, false);
1131 m_undoStack->waitPush(cmc, SketchWidget::PropChangeDelay);
1132 }
1133
changeMetadata(const QString & name,const QString & value,bool updateDisplay)1134 void PEMainWindow::changeMetadata(const QString & name, const QString & value, bool updateDisplay)
1135 {
1136 // called from command object
1137 QDomElement root = m_fzpDocument.documentElement();
1138 TextUtils::replaceElementChildText(root, name, value);
1139
1140 //QString test = m_fzpDocument.toString();
1141
1142 if (updateDisplay) {
1143 m_metadataView->initMetadata(m_fzpDocument);
1144 }
1145 }
1146
tagsChanged(const QStringList & newTags)1147 void PEMainWindow::tagsChanged(const QStringList & newTags)
1148 {
1149
1150 // called from metadataView
1151 QDomElement root = m_fzpDocument.documentElement();
1152 QDomElement tags = root.firstChildElement("tags");
1153 QDomElement tag = tags.firstChildElement("tag");
1154 QStringList oldTags;
1155 while (!tag.isNull()) {
1156 oldTags << tag.text();
1157 tag = tag.nextSiblingElement("tag");
1158 }
1159
1160 ChangeTagsCommand * ctc = new ChangeTagsCommand(this, oldTags, newTags, NULL);
1161 ctc->setText(tr("Change tags"));
1162 ctc->setSkipFirstRedo();
1163 changeTags(newTags, false);
1164 m_undoStack->waitPush(ctc, SketchWidget::PropChangeDelay);
1165 }
1166
changeTags(const QStringList & newTags,bool updateDisplay)1167 void PEMainWindow::changeTags(const QStringList & newTags, bool updateDisplay)
1168 {
1169 QDomElement root = m_fzpDocument.documentElement();
1170 QDomElement tags = root.firstChildElement("tags");
1171 QDomElement tag = tags.firstChildElement("tag");
1172 while (!tag.isNull()) {
1173 tags.removeChild(tag);
1174 tag = tags.firstChildElement("tag");
1175 }
1176
1177 foreach (QString newTag, newTags) {
1178 QDomElement tag = m_fzpDocument.createElement("tag");
1179 tags.appendChild(tag);
1180 TextUtils::replaceChildText(tag, newTag);
1181 }
1182
1183 if (updateDisplay) {
1184 m_metadataView->initMetadata(m_fzpDocument);
1185 }
1186 }
1187
propertiesChanged(const QHash<QString,QString> & newProperties)1188 void PEMainWindow::propertiesChanged(const QHash<QString, QString> & newProperties)
1189 {
1190 qDebug() << "properties changed";
1191
1192 QStringList keys = newProperties.keys();
1193 if (keys.contains("family", Qt::CaseInsensitive)) {
1194 QMessageBox::warning(NULL, tr("Duplicate problem"), tr("Duplicate 'family' property not allowed"));
1195 return;
1196 }
1197
1198 if (keys.contains("variant", Qt::CaseInsensitive)) {
1199 QMessageBox::warning(NULL, tr("Duplicate problem"), tr("Duplicate 'variant' property not allowed"));
1200 return;
1201 }
1202
1203 // called from metadataView
1204 QHash<QString, QString> oldProperties = getOldProperties();
1205
1206 ChangePropertiesCommand * cpc = new ChangePropertiesCommand(this, oldProperties, newProperties, NULL);
1207 cpc->setText(tr("Change properties"));
1208 cpc->setSkipFirstRedo();
1209 changeProperties(newProperties, false);
1210 m_undoStack->waitPush(cpc, SketchWidget::PropChangeDelay);
1211 }
1212
1213
changeProperties(const QHash<QString,QString> & newProperties,bool updateDisplay)1214 void PEMainWindow::changeProperties(const QHash<QString, QString> & newProperties, bool updateDisplay)
1215 {
1216 bool incomingFamily = newProperties.keys().contains("family");
1217 bool incomingVariant = newProperties.keys().contains("variant");
1218
1219 QDomElement root = m_fzpDocument.documentElement();
1220 QDomElement properties = root.firstChildElement("properties");
1221 QDomElement prop = properties.firstChildElement("property");
1222 while (!prop.isNull()) {
1223 QDomElement next = prop.nextSiblingElement("property");
1224 bool doRemove = true;
1225 if (prop.attribute("name") == "family" && !incomingFamily) doRemove = false;
1226 if (prop.attribute("name") == "variant" && !incomingVariant) doRemove = false;
1227 if (doRemove) {
1228 properties.removeChild(prop);
1229 }
1230 prop = next;
1231 }
1232
1233 foreach (QString name, newProperties.keys()) {
1234 QDomElement prop = m_fzpDocument.createElement("property");
1235 properties.appendChild(prop);
1236 prop.setAttribute("name", name);
1237 TextUtils::replaceChildText(prop, newProperties.value(name));
1238 }
1239
1240 if (updateDisplay) {
1241 m_metadataView->initMetadata(m_fzpDocument);
1242 }
1243 }
1244
getOldProperties()1245 QHash<QString, QString> PEMainWindow::getOldProperties()
1246 {
1247 QDomElement root = m_fzpDocument.documentElement();
1248 QDomElement properties = root.firstChildElement("properties");
1249 QDomElement prop = properties.firstChildElement("property");
1250 QHash<QString, QString> oldProperties;
1251 while (!prop.isNull()) {
1252 QString name = prop.attribute("name");
1253 QString value = prop.text();
1254 oldProperties.insert(name, value);
1255 prop = prop.nextSiblingElement("property");
1256 }
1257
1258 return oldProperties;
1259 }
1260
connectorMetadataChanged(ConnectorMetadata * cmd)1261 void PEMainWindow::connectorMetadataChanged(ConnectorMetadata * cmd)
1262 {
1263 int index;
1264 QDomElement connector = findConnector(cmd->connectorID, index);
1265 if (connector.isNull()) return;
1266
1267 ConnectorMetadata oldcmd;
1268 fillInMetadata(connector, oldcmd);
1269
1270 ChangeConnectorMetadataCommand * ccmc = new ChangeConnectorMetadataCommand(this, &oldcmd, cmd, NULL);
1271 ccmc->setText(tr("Change connector %1").arg(cmd->connectorName));
1272 bool skipFirstRedo = (sender() == m_connectorsView);
1273 if (skipFirstRedo) {
1274 ccmc->setSkipFirstRedo();
1275 changeConnectorElement(connector, cmd);
1276 initConnectors(false);
1277 }
1278 m_undoStack->waitPush(ccmc, SketchWidget::PropChangeDelay);
1279 }
1280
findConnector(const QString & id,int & index)1281 QDomElement PEMainWindow::findConnector(const QString & id, int & index)
1282 {
1283 QDomElement root = m_fzpDocument.documentElement();
1284 QDomElement connectors = root.firstChildElement("connectors");
1285 QDomElement connector = connectors.firstChildElement("connector");
1286 index = 0;
1287 while (!connector.isNull()) {
1288 if (id.compare(connector.attribute("id")) == 0) {
1289 return connector;
1290 }
1291 connector = connector.nextSiblingElement("connector");
1292 index++;
1293 }
1294
1295 return QDomElement();
1296 }
1297
1298
changeConnectorMetadata(ConnectorMetadata * cmd,bool updateDisplay)1299 void PEMainWindow::changeConnectorMetadata(ConnectorMetadata * cmd, bool updateDisplay) {
1300 int index;
1301 QDomElement connector = findConnector(cmd->connectorID, index);
1302 if (connector.isNull()) return;
1303
1304 changeConnectorElement(connector, cmd);
1305 if (updateDisplay) {
1306 initConnectors(true);
1307 m_peToolView->setCurrentConnector(index);
1308 }
1309 }
1310
changeConnectorElement(QDomElement & connector,ConnectorMetadata * cmd)1311 void PEMainWindow::changeConnectorElement(QDomElement & connector, ConnectorMetadata * cmd)
1312 {
1313 connector.setAttribute("name", cmd->connectorName);
1314 connector.setAttribute("id", cmd->connectorID);
1315 connector.setAttribute("type", Connector::connectorNameFromType(cmd->connectorType));
1316 TextUtils::replaceElementChildText(connector, "description", cmd->connectorDescription);
1317
1318 #ifndef QT_NO_DEBUG
1319 QDomElement description = connector.firstChildElement("description");
1320 QString text = description.text();
1321 DebugDialog::debug(QString("description %1").arg(text));
1322 #endif
1323
1324 }
1325
initSvgTree(SketchWidget * sketchWidget,ItemBase * itemBase,QDomDocument & svgDocument)1326 void PEMainWindow::initSvgTree(SketchWidget * sketchWidget, ItemBase * itemBase, QDomDocument & svgDocument)
1327 {
1328 QString errorStr;
1329 int errorLine;
1330 int errorColumn;
1331
1332 QDomDocument tempSvgDoc;
1333 QFile file(itemBase->filename());
1334 if (!tempSvgDoc.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
1335 DebugDialog::debug(QString("unable to parse svg: %1 %2 %3").arg(errorStr).arg(errorLine).arg(errorColumn));
1336 return;
1337 }
1338
1339 if (itemBase->viewID() == ViewLayer::PCBView) {
1340 QDomElement root = tempSvgDoc.documentElement();
1341 QDomElement copper0 = TextUtils::findElementWithAttribute(root, "id", "copper0");
1342 QDomElement copper1 = TextUtils::findElementWithAttribute(root, "id", "copper1");
1343 if (!copper0.isNull() && !copper1.isNull()) {
1344 QDomElement parent0 = copper0.parentNode().toElement();
1345 QDomElement parent1 = copper1.parentNode().toElement();
1346 if (parent0.attribute("id") == "copper1") ;
1347 else if (parent1.attribute("id") == "copper0") ;
1348 else {
1349 QMessageBox::warning(NULL, tr("SVG problem"),
1350 tr("This version of the new Parts Editor can not deal with separate copper0 and copper1 layers in '%1'. ").arg(itemBase->filename()) +
1351 tr("So editing may produce an invalid PCB view image"));
1352 }
1353 }
1354 }
1355
1356 int z = PegiZ;
1357
1358 FSvgRenderer tempRenderer;
1359 QByteArray rendered = tempRenderer.loadSvg(tempSvgDoc.toByteArray(), "", false);
1360 // cleans up the svg
1361 if (!svgDocument.setContent(rendered, true, &errorStr, &errorLine, &errorColumn)) {
1362 DebugDialog::debug(QString("unable to parse svg (2): %1 %2 %3").arg(errorStr).arg(errorLine).arg(errorColumn));
1363 return;
1364 }
1365
1366 TextUtils::gornTree(svgDocument);
1367 FSvgRenderer renderer;
1368 renderer.loadSvg(svgDocument.toByteArray(), "", false);
1369
1370 QHash<QString, PEGraphicsItem *> pegiHash;
1371
1372 QList<QDomElement> traverse;
1373 traverse << svgDocument.documentElement();
1374 while (traverse.count() > 0) {
1375 QDomElement element = traverse.takeFirst();
1376
1377 // depth first
1378 QList<QDomElement> next;
1379 QDomElement child = element.firstChildElement();
1380 while (!child.isNull()) {
1381 next << child;
1382 child = child.nextSiblingElement();
1383 }
1384 while (next.count() > 0) {
1385 traverse.push_front(next.takeLast());
1386 }
1387
1388 bool isG = false;
1389 bool isSvg = false;
1390 QString tagName = element.tagName();
1391 if (tagName.compare("rect") == 0);
1392 else if (tagName.compare("g") == 0) {
1393 isG = true;
1394 }
1395 else if (tagName.compare("svg") == 0) {
1396 isSvg = true;
1397 }
1398 else if (tagName.compare("circle") == 0);
1399 else if (tagName.compare("ellipse") == 0);
1400 else if (tagName.compare("path") == 0);
1401 else if (tagName.compare("line") == 0);
1402 else if (tagName.compare("polyline") == 0);
1403 else if (tagName.compare("polygon") == 0);
1404 else if (tagName.compare("text") == 0);
1405 else continue;
1406
1407 QRectF bounds = getPixelBounds(renderer, element);
1408 // known Qt bug: boundsOnElement returns zero width and height for text elements.
1409 if (bounds.width() > 0 && bounds.height() > 0) {
1410 PEGraphicsItem * pegi = makePegi(bounds.size(), bounds.topLeft(), itemBase, element, z++);
1411 pegiHash.insert(element.attribute("id"), pegi);
1412
1413 /*
1414 QString string;
1415 QTextStream stream(&string);
1416 element.save(stream, 0);
1417 DebugDialog::debug("........");
1418 DebugDialog::debug(string);
1419 */
1420 }
1421 }
1422
1423 QDomElement root = m_fzpDocument.documentElement();
1424 QDomElement connectors = root.firstChildElement("connectors");
1425 QDomElement connector = connectors.firstChildElement("connector");
1426 while (!connector.isNull()) {
1427 QString svgID, terminalID;
1428 bool ok = ViewLayer::getConnectorSvgIDs(connector, sketchWidget->viewID(), svgID, terminalID);
1429 if (ok) {
1430 PEGraphicsItem * terminalItem = pegiHash.value(terminalID, NULL);
1431 PEGraphicsItem * pegi = pegiHash.value(svgID);
1432 if (pegi == NULL) {
1433 DebugDialog::debug(QString("missing pegi for svg id %1").arg(svgID));
1434 }
1435 else {
1436 QPointF terminalPoint = pegi->rect().center();
1437 if (terminalItem) {
1438 terminalPoint = terminalItem->pos() - pegi->pos() + terminalItem->rect().center();
1439 }
1440 pegi->setTerminalPoint(terminalPoint);
1441 pegi->showTerminalPoint(true);
1442 }
1443 }
1444
1445 connector = connector.nextSiblingElement("connector");
1446 }
1447 }
1448
highlightSlot(PEGraphicsItem * pegi)1449 void PEMainWindow::highlightSlot(PEGraphicsItem * pegi) {
1450 if (m_peToolView) {
1451 bool enableTerminalPointControls = anyMarquee();
1452 bool vis = anyVisible();
1453 m_peToolView->enableConnectorChanges(vis && pegi->showingMarquee(), vis && enableTerminalPointControls, m_connectorList.count() > 0, vis);
1454 }
1455
1456 if (m_peSvgView) {
1457 m_peSvgView->highlightElement(pegi);
1458 }
1459 }
1460
initConnectors(bool updateConnectorsView)1461 void PEMainWindow::initConnectors(bool updateConnectorsView) {
1462 QDomElement root = m_fzpDocument.documentElement();
1463 QDomElement connectors = root.firstChildElement("connectors");
1464 QDomElement connector = connectors.firstChildElement("connector");
1465 m_connectorList.clear();
1466 while (!connector.isNull()) {
1467 m_connectorList.append(connector);
1468 connector = connector.nextSiblingElement("connector");
1469 }
1470
1471 qSort(m_connectorList.begin(), m_connectorList.end(), byID);
1472
1473 if (updateConnectorsView) {
1474 m_connectorsView->initConnectors(&m_connectorList);
1475 }
1476 m_peToolView->initConnectors(&m_connectorList);
1477 updateAssignedConnectors();
1478 }
1479
switchedConnector(int ix)1480 void PEMainWindow::switchedConnector(int ix)
1481 {
1482 if (m_currentGraphicsView == NULL) return;
1483
1484 switchedConnector(ix, m_currentGraphicsView);
1485 }
1486
switchedConnector(int ix,SketchWidget * sketchWidget)1487 void PEMainWindow::switchedConnector(int ix, SketchWidget * sketchWidget)
1488 {
1489 if (m_connectorList.count() == 0) return;
1490
1491 QDomElement element = m_connectorList.at(ix);
1492 if (element.isNull()) return;
1493
1494 QString svgID, terminalID;
1495 if (!ViewLayer::getConnectorSvgIDs(element, sketchWidget->viewID(), svgID, terminalID)) return;
1496
1497 QList<PEGraphicsItem *> pegiList = getPegiList(sketchWidget);
1498 bool gotOne = false;
1499 foreach (PEGraphicsItem * pegi, pegiList) {
1500 QDomElement pegiElement = pegi->element();
1501 if (pegiElement.attribute("id").compare(svgID) == 0) {
1502 gotOne = true;
1503 pegi->showMarquee(true);
1504 pegi->setHighlighted(true);
1505 m_peToolView->setTerminalPointCoords(pegi->terminalPoint());
1506 m_peToolView->setTerminalPointLimits(pegi->rect().size());
1507 break;
1508 }
1509 }
1510
1511 if (!gotOne) {
1512 foreach (PEGraphicsItem * pegi, pegiList) {
1513 pegi->showMarquee(false);
1514 pegi->setHighlighted(false);
1515 }
1516 }
1517 }
1518
loadImage()1519 void PEMainWindow::loadImage()
1520 {
1521 if (m_currentGraphicsView == NULL) return;
1522
1523 QStringList extras;
1524 extras.append("");
1525 extras.append("");
1526 QString imageFiles;
1527 if (m_currentGraphicsView->viewID() == ViewLayer::PCBView) {
1528 imageFiles = tr("Image & Footprint Files (%1 %2 %3 %4 %5);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3);;gEDA Footprint Files (%4);;Kicad Module Files (%5)"); //
1529 extras[0] = "*.fp";
1530 extras[1] = "*.mod";
1531 }
1532 else {
1533 imageFiles = tr("Image Files (%1 %2 %3);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3)%4%5");
1534 }
1535
1536 /*
1537 if (m_currentGraphicsView->viewID() == ViewLayer::SchematicView) {
1538 extras[0] = "*.lib";
1539 imageFiles = tr("Image & Footprint Files (%1 %2 %3 %4);;SVG Files (%1);;JPEG Files (%2);;PNG Files (%3);;Kicad Schematic Files (%4)%5"); //
1540 }
1541 */
1542
1543 QString initialPath = FolderUtils::openSaveFolder();
1544 ViewThing * viewThing = m_viewThings.value(m_currentGraphicsView->viewID());
1545 ItemBase * itemBase = viewThing->itemBase;
1546 if (itemBase) {
1547 initialPath = itemBase->filename();
1548 }
1549 QString origPath = FolderUtils::getOpenFileName(this,
1550 tr("Open Image"),
1551 initialPath,
1552 imageFiles.arg("*.svg").arg("*.jpg *.jpeg").arg("*.png").arg(extras[0]).arg(extras[1])
1553 );
1554
1555 if (origPath.isEmpty()) {
1556 return; // Cancel pressed
1557 }
1558
1559 QString newReferenceFile;
1560 QString svg;
1561 if (origPath.endsWith(".svg")) {
1562 newReferenceFile = getSvgReferenceFile(origPath);
1563 QFile origFile(origPath);
1564 if (!origFile.open(QFile::ReadOnly)) {
1565 QMessageBox::warning(NULL, tr("Conversion problem"), tr("Unable to load '%1'").arg(origPath));
1566 return;
1567 }
1568
1569 svg = origFile.readAll();
1570 origFile.close();
1571 if (svg.contains("coreldraw", Qt::CaseInsensitive) && svg.contains("cdata", Qt::CaseInsensitive)) {
1572 QMessageBox::warning(NULL, tr("Conversion problem"),
1573 tr("The SVG file '%1' appears to have been exported from CorelDRAW without the 'presentation attributes' setting. ").arg(origPath) +
1574 tr("Please re-export the SVG file using that setting, and try loading again.")
1575 );
1576 return;
1577 }
1578
1579 QStringList availFonts = InstalledFonts::InstalledFontsList.toList();
1580 if (availFonts.count() > 0) {
1581 QString destFont = availFonts.at(0);
1582 foreach (QString f, availFonts) {
1583 if (f.contains("droid", Qt::CaseInsensitive)) {
1584 destFont = f;
1585 break;
1586 }
1587 }
1588 bool reallyFixed = false;
1589 TextUtils::fixFonts(svg, destFont, reallyFixed);
1590 if (reallyFixed) {
1591 QMessageBox::information(NULL, tr("Fonts"),
1592 tr("Fritzing currently only supports OCRA and Droid fonts--these have been substituted in for the fonts in '%1'").arg(origPath));
1593 }
1594 }
1595 }
1596 else {
1597 newReferenceFile = origPath;
1598 if (origPath.endsWith("png") || origPath.endsWith("jpg") || origPath.endsWith("jpeg")) {
1599 QString message = tr("You may use a PNG or JPG image to construct your part, but it is better to use an SVG. ") +
1600 tr("PNG and JPG images retain their nature as bitmaps and do not look good when scaled--") +
1601 tr("so for Fritzing parts it is best to use PNG and JPG only as placeholders.")
1602 ;
1603
1604 QMessageBox::information(NULL, tr("Use of PNG and JPG discouraged"), message);
1605
1606 }
1607
1608 try {
1609 svg = createSvgFromImage(origPath);
1610 }
1611 catch (const QString & msg) {
1612 QMessageBox::warning(NULL, tr("Conversion problem"), tr("Unable to load image file '%1': \n\n%2").arg(origPath).arg(msg));
1613 return;
1614 }
1615 }
1616
1617 if (svg.isEmpty()) {
1618 QMessageBox::warning(NULL, tr("Conversion problem"), tr("Unable to load image file '%1'").arg(origPath));
1619 return;
1620 }
1621
1622 QString errorStr;
1623 int errorLine;
1624 int errorColumn;
1625 QDomDocument doc;
1626 bool result = doc.setContent(svg.toUtf8(), &errorStr, &errorLine, &errorColumn);
1627 if (!result) {
1628 QMessageBox::warning(NULL, tr("SVG problem"), tr("Unable to parse '%1': %2 line:%3 column:%4").arg(origPath).arg(errorStr).arg(errorLine).arg(errorColumn));
1629 return;
1630 }
1631
1632 if (m_currentGraphicsView == m_pcbGraphicsView) {
1633 QDomElement root = doc.documentElement();
1634 QDomElement check = TextUtils::findElementWithAttribute(root, "id", "copper1");
1635 if (check.isNull()) {
1636 check = TextUtils::findElementWithAttribute(root, "id", "copper0");
1637 }
1638 if (check.isNull()) {
1639 QString message = tr("There are no copper layers defined in: %1. ").arg(origPath) +
1640 tr("See <a href=\"http://fritzing.org/learning/tutorials/creating-custom-parts/providing-part-graphics/\">this explanation</a>.") +
1641 tr("<br/><br/>This will not be a problem in the next release of the Parts Editor, ") +
1642 tr("but for now please modify the file according to the instructions in the link.")
1643 ;
1644
1645 QMessageBox::warning(NULL, tr("SVG problem"), message);
1646 return;
1647 }
1648 }
1649
1650 TextUtils::fixMuch(svg, true);
1651 insertDesc(newReferenceFile, svg);
1652 QString newPath = m_userPartsFolderSvgPath + makeSvgPath2(m_currentGraphicsView);
1653 bool success = writeXml(newPath, removeGorn(svg), true);
1654 if (!success) {
1655 QMessageBox::warning(NULL, tr("Copy problem"), tr("Unable to make a local copy of: '%1'").arg(origPath));
1656 return;
1657 }
1658
1659 QFileInfo info(origPath);
1660 QUndoCommand * parentCommand = new QUndoCommand(QString("Load '%1'").arg(info.fileName()));
1661 new ChangeSvgCommand(this, m_currentGraphicsView, itemBase->filename(), newPath, parentCommand);
1662 m_undoStack->waitPush(parentCommand, SketchWidget::PropChangeDelay);
1663 }
1664
createSvgFromImage(const QString & origFilePath)1665 QString PEMainWindow::createSvgFromImage(const QString &origFilePath) {
1666 if (origFilePath.endsWith(".fp")) {
1667 // this is a geda footprint file
1668 GedaElement2Svg geda;
1669 QString svg = geda.convert(origFilePath, false);
1670 return svg;
1671 }
1672
1673 if (origFilePath.endsWith(".lib")) {
1674 // Kicad schematic library file
1675 QStringList defs = KicadSchematic2Svg::listDefs(origFilePath);
1676 if (defs.count() == 0) {
1677 throw tr("no schematics found in %1").arg(origFilePath);
1678 }
1679
1680 QString def;
1681 if (defs.count() > 1) {
1682 KicadModuleDialog kmd(tr("schematic part"), origFilePath, defs, this);
1683 int result = kmd.exec();
1684 if (result != QDialog::Accepted) {
1685 return "";
1686 }
1687
1688 def = kmd.selectedModule();
1689 }
1690 else {
1691 def = defs.at(0);
1692 }
1693
1694 KicadSchematic2Svg kicad;
1695 QString svg = kicad.convert(origFilePath, def);
1696 return svg;
1697 }
1698
1699 if (origFilePath.endsWith(".mod")) {
1700 // Kicad footprint (Module) library file
1701 QStringList modules = KicadModule2Svg::listModules(origFilePath);
1702 if (modules.count() == 0) {
1703 throw tr("no footprints found in %1").arg(origFilePath);
1704 }
1705
1706 QString module;
1707 if (modules.count() > 1) {
1708 KicadModuleDialog kmd("footprint", origFilePath, modules, this);
1709 int result = kmd.exec();
1710 if (result != QDialog::Accepted) {
1711 return "";
1712 }
1713
1714 module = kmd.selectedModule();
1715 }
1716 else {
1717 module = modules.at(0);
1718 }
1719
1720 KicadModule2Svg kicad;
1721 QString svg = kicad.convert(origFilePath, module, false);
1722 return svg;
1723 }
1724
1725 // deal with png, jpg:
1726 QBuffer buffer;
1727 QImage img(origFilePath);
1728 QSvgGenerator svgGenerator;
1729 svgGenerator.setResolution(90);
1730 svgGenerator.setOutputDevice(&buffer);
1731 QSize sz = img.size();
1732 svgGenerator.setSize(sz);
1733 svgGenerator.setViewBox(QRect(0, 0, sz.width(), sz.height()));
1734 QPainter svgPainter(&svgGenerator);
1735 svgPainter.drawImage(QPoint(0,0), img);
1736 svgPainter.end();
1737
1738 return QString(buffer.buffer());
1739 }
1740
1741 /*
1742 QString PEMainWindow::makeSvgPath(const QString & referenceFile, SketchWidget * sketchWidget, bool useIndex)
1743 {
1744 QString rf = referenceFile;
1745 if (!rf.isEmpty()) rf += "_";
1746 QString viewName = ViewLayer::viewIDNaturalName(sketchWidget->viewID());
1747 QString indexString;
1748 if (useIndex) indexString = QString("_%3").arg(m_fileIndex++);
1749 return QString("%1/%2%3_%1%4.svg").arg(viewName).arg(rf).arg(m_guid).arg(indexString);
1750 }
1751 */
1752
makeSvgPath2(SketchWidget * sketchWidget)1753 QString PEMainWindow::makeSvgPath2(SketchWidget * sketchWidget)
1754 {
1755 QString viewName = ViewLayer::viewIDNaturalName(sketchWidget->viewID());
1756 return QString("%1/%2_%3_%4_%1.svg").arg(viewName).arg(m_prefix).arg(m_guid).arg(m_fileIndex);
1757 }
1758
changeSvg(SketchWidget * sketchWidget,const QString & filename,int changeDirection)1759 void PEMainWindow::changeSvg(SketchWidget * sketchWidget, const QString & filename, int changeDirection) {
1760 QDomElement fzpRoot = m_fzpDocument.documentElement();
1761 setImageAttribute(fzpRoot, filename, sketchWidget->viewID());
1762
1763 foreach (ViewThing * viewThing, m_viewThings.values()) {
1764 foreach(ItemBase * lk, viewThing->itemBase->layerKin()) {
1765 delete lk;
1766 }
1767 delete viewThing->itemBase;
1768 viewThing->itemBase = NULL;
1769 }
1770
1771 updateChangeCount(sketchWidget, changeDirection);
1772
1773 reload(false);
1774 }
1775
makeDirName()1776 QString PEMainWindow::makeDirName() {
1777 return "fritzing_pe_" + m_guid;
1778 }
1779
saveFzp()1780 QString PEMainWindow::saveFzp() {
1781 QDir dir = QDir::temp();
1782 QString dirName = makeDirName();
1783 dir.mkdir(dirName);
1784 dir.cd(dirName);
1785 QString fzpPath = dir.absoluteFilePath(QString("%1_%2_%3.fzp").arg(m_prefix).arg(m_guid).arg(m_fileIndex++));
1786 DebugDialog::debug("temp path " + fzpPath);
1787 writeXml(fzpPath, m_fzpDocument.toString(), true);
1788 return fzpPath;
1789 }
1790
reload(bool firstTime)1791 void PEMainWindow::reload(bool firstTime)
1792 {
1793 Q_UNUSED(firstTime);
1794
1795 CursorMaster::instance()->addCursor(this, Qt::WaitCursor);
1796
1797 QList<ItemBase *> toDelete;
1798
1799 foreach (ViewThing * viewThing, m_viewThings.values()) {
1800 if (viewThing->sketchWidget == NULL) continue;
1801
1802 foreach (QGraphicsItem * item, viewThing->sketchWidget->scene()->items()) {
1803 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
1804 if (itemBase) toDelete << itemBase;
1805 }
1806 }
1807
1808 foreach (ItemBase * itemBase, toDelete) {
1809 delete itemBase;
1810 }
1811
1812 killPegi();
1813
1814 QString fzpPath = saveFzp(); // needs a document somewhere to set up connectors--not part of the undo stack
1815 ModelPart * modelPart = new ModelPart(m_fzpDocument, fzpPath, ModelPart::Part);
1816
1817 long newID = ItemBase::getNextID();
1818 ViewGeometry viewGeometry;
1819 viewGeometry.setLoc(QPointF(0, 0));
1820
1821 QList<ItemBase *> itemBases;
1822 itemBases << m_iconGraphicsView->addItem(modelPart, m_iconGraphicsView->defaultViewLayerPlacement(modelPart), BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1823 itemBases << m_breadboardGraphicsView->addItem(modelPart, m_breadboardGraphicsView->defaultViewLayerPlacement(modelPart), BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1824 itemBases << m_schematicGraphicsView->addItem(modelPart, m_schematicGraphicsView->defaultViewLayerPlacement(modelPart), BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1825 itemBases << m_pcbGraphicsView->addItem(modelPart, m_pcbGraphicsView->defaultViewLayerPlacement(modelPart), BaseCommand::SingleView, viewGeometry, newID, -1, NULL);
1826
1827 foreach (ItemBase * itemBase, itemBases) {
1828 ViewThing * viewThing = m_viewThings.value(itemBase->viewID());
1829 viewThing->itemBase = itemBase;
1830 viewThing->referenceFile = getSvgReferenceFile(itemBase->filename());
1831 }
1832
1833 m_metadataView->initMetadata(m_fzpDocument);
1834
1835 QList<QWidget *> widgets;
1836 widgets << m_metadataView << m_peToolView << m_connectorsView;
1837 foreach (QWidget * widget, widgets) {
1838 QList<QLineEdit *> lineEdits = widget->findChildren<QLineEdit *>();
1839 foreach (QLineEdit * lineEdit, lineEdits) {
1840 lineEdit->installEventFilter(this);
1841 }
1842 QList<QTextEdit *> textEdits = widget->findChildren<QTextEdit *>();
1843 foreach (QTextEdit * textEdit, textEdits) {
1844 textEdit->installEventFilter(this);
1845 }
1846 }
1847
1848 if (m_currentGraphicsView) {
1849 showing(m_currentGraphicsView);
1850 }
1851
1852 foreach (ViewThing * viewThing, m_viewThings.values()) {
1853 initSvgTree(viewThing->sketchWidget, viewThing->itemBase, *viewThing->document);
1854 }
1855
1856 initConnectors(true);
1857 m_connectorsView->setSMD(modelPart->flippedSMD());
1858
1859 foreach (ViewThing * viewThing, m_viewThings.values()) {
1860 // TODO: may have to revisit this and move all pegi items
1861 //viewThing->itemBase->setMoveLock(true);
1862 //viewThing->itemBase->setItemIsSelectable(false);
1863 viewThing->itemBase->setAcceptsMousePressLegEvent(false);
1864 viewThing->itemBase->setSwappable(false);
1865 viewThing->sketchWidget->hideConnectors(true);
1866 viewThing->everZoomed = false;
1867 }
1868
1869 switchedConnector(m_peToolView->currentConnectorIndex());
1870
1871 // processEventBlocker might be enough?
1872 QTimer::singleShot(10, this, SLOT(initZoom()));
1873
1874 CursorMaster::instance()->removeCursor(this);
1875
1876 }
1877
busModeChanged(bool state)1878 void PEMainWindow::busModeChanged(bool state) {
1879 if (m_currentGraphicsView == NULL) return;
1880
1881 if (!state) {
1882 foreach (ViewThing * viewThing, m_viewThings.values()) {
1883 viewThing->busMode = false;
1884 viewThing->sketchWidget->hideConnectors(true);
1885 }
1886
1887 deleteBuses();
1888
1889 reload(false);
1890 return;
1891 }
1892
1893 QList<PEGraphicsItem *> pegiList = getPegiList(m_currentGraphicsView);
1894
1895 reload(false);
1896
1897 QDomElement root = m_fzpDocument.documentElement();
1898 QDomElement connectors = root.firstChildElement("connectors");
1899
1900 foreach (ViewThing * viewThing, m_viewThings.values()) {
1901 // items on pegiList no longer exist after reload so get them now
1902 pegiList = getPegiList(viewThing->sketchWidget);
1903 viewThing->sketchWidget->hideConnectors(true);
1904 foreach (PEGraphicsItem * pegi, pegiList) {
1905 pegi->setVisible(false);
1906 }
1907
1908 // show connectorItems that have connectors assigned
1909 QStringList connectorIDs;
1910 QDomElement connector = connectors.firstChildElement("connector");
1911 while (!connector.isNull()) {
1912 QDomElement p = ViewLayer::getConnectorPElement(connector, viewThing->sketchWidget->viewID());
1913 QString id = p.attribute("svgId");
1914 foreach (PEGraphicsItem * pegi, pegiList) {
1915 QDomElement pegiElement = pegi->element();
1916 if (pegiElement.attribute("id").compare(id) == 0) {
1917 connectorIDs.append(connector.attribute("id"));
1918 break;
1919 }
1920 }
1921 connector = connector.nextSiblingElement("connector");
1922 }
1923
1924 if (connectorIDs.count() > 0) {
1925 viewThing->itemBase->showConnectors(connectorIDs);
1926 }
1927 }
1928
1929 displayBuses();
1930 m_peToolView->enableConnectorChanges(false, false, m_connectorList.count() > 0, false);
1931 }
1932
pickModeChanged(bool state)1933 void PEMainWindow::pickModeChanged(bool state) {
1934 if (m_currentGraphicsView == NULL) return;
1935
1936 // never actually get state == false;
1937 m_inPickMode = state;
1938 if (m_inPickMode) {
1939 QApplication::setOverrideCursor(Qt::PointingHandCursor);
1940 foreach (QGraphicsItem * item, m_currentGraphicsView->scene()->items()) {
1941 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
1942 if (pegi) pegi->setPickAppearance(true);
1943 }
1944 qApp->installEventFilter(this);
1945 }
1946 }
1947
pegiTerminalPointMoved(PEGraphicsItem * pegi,QPointF p)1948 void PEMainWindow::pegiTerminalPointMoved(PEGraphicsItem * pegi, QPointF p)
1949 {
1950 // called while terminal point is being dragged, no need for an undo operation
1951
1952 Q_UNUSED(pegi);
1953 m_peToolView->setTerminalPointCoords(p);
1954 }
1955
pegiTerminalPointChanged(PEGraphicsItem * pegi,QPointF before,QPointF after)1956 void PEMainWindow::pegiTerminalPointChanged(PEGraphicsItem * pegi, QPointF before, QPointF after)
1957 {
1958 // called at the end of terminal point dragging
1959
1960 terminalPointChangedAux(pegi, before, after);
1961 }
1962
pegiMousePressed(PEGraphicsItem * pegi,bool & ignore)1963 void PEMainWindow::pegiMousePressed(PEGraphicsItem * pegi, bool & ignore)
1964 {
1965 ignore = false;
1966
1967 if (m_useNextPick) {
1968 m_useNextPick = false;
1969 relocateConnector(pegi);
1970 return;
1971 }
1972
1973 ViewThing * viewThing = m_viewThings.value(m_currentGraphicsView->viewID());
1974 if (pegi->itemBase() != viewThing->itemBase) {
1975 ignore = true;
1976 return;
1977 }
1978
1979 QString id = pegi->element().attribute("id");
1980 if (id.isEmpty()) return;
1981
1982 QDomElement current = m_connectorList.at(m_peToolView->currentConnectorIndex());
1983 if (current.attribute("id").compare(id) == 0) {
1984 // already there
1985 return;
1986 }
1987
1988 // if a connector has been clicked, make it the current connector
1989 QDomElement root = m_fzpDocument.documentElement();
1990 QDomElement connectors = root.firstChildElement("connectors");
1991 QDomElement connector = connectors.firstChildElement("connector");
1992 while (!connector.isNull()) {
1993 QDomElement p = ViewLayer::getConnectorPElement(connector, m_currentGraphicsView->viewID());
1994 if (p.attribute("svgId") == id || p.attribute("terminalId") == id) {
1995 m_peToolView->setCurrentConnector(connector);
1996 return;
1997 }
1998 connector = connector.nextSiblingElement("connector");
1999 }
2000 }
2001
pegiMouseReleased(PEGraphicsItem *)2002 void PEMainWindow::pegiMouseReleased(PEGraphicsItem *) {
2003 }
2004
relocateConnector(PEGraphicsItem * pegi)2005 void PEMainWindow::relocateConnector(PEGraphicsItem * pegi)
2006 {
2007 QString newGorn = pegi->element().attribute("gorn");
2008 QDomDocument * svgDoc = m_viewThings.value(m_currentGraphicsView->viewID())->document;
2009 QDomElement svgRoot = svgDoc->documentElement();
2010 QDomElement newGornElement = TextUtils::findElementWithAttribute(svgRoot, "gorn", newGorn);
2011 if (newGornElement.isNull()) {
2012 return;
2013 }
2014
2015 QDomElement fzpRoot = m_fzpDocument.documentElement();
2016 QDomElement connectors = fzpRoot.firstChildElement("connectors");
2017 QDomElement currentConnectorElement = m_connectorList.at(m_peToolView->currentConnectorIndex());
2018 QString svgID, terminalID;
2019 if (!ViewLayer::getConnectorSvgIDs(currentConnectorElement, m_currentGraphicsView->viewID(), svgID, terminalID)) {
2020 return;
2021 }
2022
2023 QDomElement oldGornElement = TextUtils::findElementWithAttribute(svgRoot, "id", svgID);
2024 QString oldGorn = oldGornElement.attribute("gorn");
2025 QString oldGornTerminal;
2026 if (!terminalID.isEmpty()) {
2027 QDomElement element = TextUtils::findElementWithAttribute(svgRoot, "id", terminalID);
2028 oldGornTerminal = element.attribute("gorn");
2029 }
2030
2031 if (newGornElement.attribute("gorn").compare(oldGornElement.attribute("gorn")) == 0) {
2032 // no change
2033 return;
2034 }
2035
2036 RelocateConnectorSvgCommand * rcsc = new RelocateConnectorSvgCommand(this, m_currentGraphicsView, svgID, terminalID, oldGorn, oldGornTerminal, newGorn, "", NULL);
2037 rcsc->setText(tr("Relocate connector %1").arg(currentConnectorElement.attribute("name")));
2038 m_undoStack->waitPush(rcsc, SketchWidget::PropChangeDelay);
2039 }
2040
relocateConnectorSvg(SketchWidget * sketchWidget,const QString & svgID,const QString & terminalID,const QString & oldGorn,const QString & oldGornTerminal,const QString & newGorn,const QString & newGornTerminal,int changeDirection)2041 void PEMainWindow::relocateConnectorSvg(SketchWidget * sketchWidget, const QString & svgID, const QString & terminalID,
2042 const QString & oldGorn, const QString & oldGornTerminal, const QString & newGorn, const QString & newGornTerminal,
2043 int changeDirection)
2044 {
2045 ViewLayer::ViewID viewID = sketchWidget->viewID();
2046 ViewThing * viewThing = m_viewThings.value(viewID);
2047 QDomDocument * svgDoc = viewThing->document;
2048 QDomElement svgRoot = svgDoc->documentElement();
2049
2050 QDomElement oldGornElement = TextUtils::findElementWithAttribute(svgRoot, "gorn", oldGorn);
2051 QDomElement oldGornTerminalElement;
2052 if (!oldGornTerminal.isEmpty()) {
2053 oldGornTerminalElement = TextUtils::findElementWithAttribute(svgRoot, "gorn", oldGornTerminal);
2054 }
2055 QDomElement newGornElement = TextUtils::findElementWithAttribute(svgRoot, "gorn", newGorn);
2056 QDomElement newGornTerminalElement;
2057 if (!newGornTerminal.isEmpty()) {
2058 newGornTerminalElement = TextUtils::findElementWithAttribute(svgRoot, "gorn", newGornTerminal);
2059 }
2060
2061 if (!oldGornElement.isNull()) {
2062 oldGornElement.removeAttribute("id");
2063 oldGornElement.removeAttribute("oldid"); // remove oldid so removeGorn() call later doesn't restore anything by accident
2064 }
2065 if (!oldGornTerminalElement.isNull()) {
2066 oldGornTerminalElement.removeAttribute("id");
2067 oldGornTerminalElement.removeAttribute("oldid");
2068 }
2069 if (!newGornElement.isNull()) {
2070 newGornElement.setAttribute("id", svgID);
2071 newGornElement.removeAttribute("oldid");
2072 }
2073 if (!newGornTerminalElement.isNull()) {
2074 newGornTerminalElement.setAttribute("id", terminalID);
2075 newGornTerminalElement.removeAttribute("oldid");
2076 }
2077
2078 QDomElement fzpRoot = m_fzpDocument.documentElement();
2079 QDomElement connectors = fzpRoot.firstChildElement("connectors");
2080 QDomElement connector = connectors.firstChildElement("connector");
2081 while (!connector.isNull()) {
2082 QString cSvgID, cTerminalID;
2083 if (ViewLayer::getConnectorSvgIDs(connector, viewID, cSvgID, cTerminalID)) {
2084 if (cSvgID == svgID) break;
2085 }
2086 connector = connector.nextSiblingElement("connector");
2087 }
2088 if (connector.isNull()) return;
2089
2090 QDomElement p = ViewLayer::getConnectorPElement(connector, viewID);
2091 if (terminalID.isEmpty()) {
2092 p.removeAttribute("terminalId");
2093 }
2094 else {
2095 p.setAttribute("terminalId", terminalID);
2096 }
2097
2098 // update svg in case there is a subsequent call to reload
2099 QString newPath = m_userPartsFolderSvgPath + makeSvgPath2(sketchWidget);
2100 QString svg = TextUtils::svgNSOnly(svgDoc->toString());
2101 writeXml(newPath, removeGorn(svg), true);
2102 setImageAttribute(fzpRoot, newPath, viewID);
2103
2104 foreach (QGraphicsItem * item, sketchWidget->scene()->items()) {
2105 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
2106 if (pegi == NULL) continue;
2107
2108 QDomElement element = pegi->element();
2109 if (element.attribute("gorn").compare(newGorn) == 0) {
2110 pegi->setHighlighted(true);
2111 pegi->showMarquee(true);
2112 pegi->showTerminalPoint(true);
2113 switchedConnector(m_peToolView->currentConnectorIndex(), sketchWidget);
2114
2115 }
2116 else if (element.attribute("gorn").compare(oldGorn) == 0) {
2117 pegi->showTerminalPoint(false);
2118 pegi->showMarquee(false);
2119 }
2120 }
2121
2122 updateAssignedConnectors();
2123 updateChangeCount(sketchWidget, changeDirection);
2124 }
2125
save()2126 bool PEMainWindow::save() {
2127 if (!canSave()) {
2128 return saveAs(false);
2129 }
2130
2131 return saveAs(true);
2132 }
2133
saveAs()2134 bool PEMainWindow::saveAs() {
2135 return saveAs(false);
2136 }
2137
saveAs(bool overWrite)2138 bool PEMainWindow::saveAs(bool overWrite)
2139 {
2140 bool ok = false;
2141 QString prefix = QInputDialog::getText(
2142 this,
2143 tr("Filename prefix"),
2144 tr("Please enter a prefix to help you identify the part files. The names will have the form 'PREFIX_%1'. (It is not necessary to change the prefix, since a unique suffix is always added.)").arg(m_guid),
2145 QLineEdit::Normal,
2146 m_prefix,
2147 &ok
2148 );
2149 if (!ok || prefix.isEmpty()) return false;
2150
2151 if (prefix != m_prefix) overWrite = false;
2152 m_prefix = prefix;
2153
2154 QDomElement fzpRoot = m_fzpDocument.documentElement();
2155
2156 QList<MainWindow *> affectedWindows;
2157 QList<MainWindow *> allWindows;
2158 if (overWrite) {
2159 foreach (QWidget *widget, QApplication::topLevelWidgets()) {
2160 MainWindow *mainWindow = qobject_cast<MainWindow *>(widget);
2161 if (mainWindow == NULL) continue;
2162
2163 if (qobject_cast<PEMainWindow *>(mainWindow) != NULL) continue;
2164
2165 allWindows.append(mainWindow);
2166 if (mainWindow->usesPart(m_originalModuleID)) {
2167 affectedWindows.append(mainWindow);
2168 }
2169 }
2170
2171 if (affectedWindows.count() > 0 && !m_gaveSaveWarning) {
2172 QMessageBox messageBox(this);
2173 messageBox.setWindowTitle(tr("Sketch Change Warning"));
2174 QString message;
2175 if (affectedWindows.count() == 1) {
2176 message = tr("The open sketch '%1' uses the part you are editing. ").arg(affectedWindows.first()->windowTitle());
2177 message += tr("Saving this part will make a change to the sketch that cannot be undone.");
2178 }
2179 else {
2180 message = tr("The open sketches ");
2181 for (int i = 0; i < affectedWindows.count() - 1; i++) {
2182 message += tr("'%1', ").arg(affectedWindows.at(i)->windowTitle());
2183 }
2184 message += tr("and '%1' ").arg(affectedWindows.last()->windowTitle());
2185 message += tr("Saving this part will make a change to these sketches that cannot be undone.");
2186
2187 }
2188 message += tr("\n\nGo ahead and save?");
2189
2190 messageBox.setText(message);
2191 messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
2192 messageBox.setDefaultButton(QMessageBox::Cancel);
2193 messageBox.setIcon(QMessageBox::Warning);
2194 messageBox.setWindowModality(Qt::WindowModal);
2195 messageBox.setButtonText(QMessageBox::Ok, tr("Save"));
2196 messageBox.setButtonText(QMessageBox::Cancel, tr("Cancel"));
2197 QMessageBox::StandardButton answer = (QMessageBox::StandardButton) messageBox.exec();
2198
2199 if (answer != QMessageBox::Ok) {
2200 return false;
2201 }
2202
2203 m_gaveSaveWarning = true;
2204 }
2205 }
2206
2207 QDomElement views = fzpRoot.firstChildElement("views");
2208
2209 QHash<ViewLayer::ViewID, QString> svgPaths;
2210
2211 foreach (ViewLayer::ViewID viewID, m_viewThings.keys()) {
2212 ViewThing * viewThing = m_viewThings.value(viewID);
2213 QDomElement view = views.firstChildElement(ViewLayer::viewIDXmlName(viewID));
2214 QDomElement layers = view.firstChildElement("layers");
2215
2216 QString currentSvgPath = layers.attribute("image");
2217 svgPaths.insert(viewID, currentSvgPath);
2218
2219 QMultiHash<ViewLayer::ViewLayerID, QString> extraSvg;
2220 QDomDocument writeDoc = viewThing->document->cloneNode(true).toDocument();
2221 QDomElement svgRoot = writeDoc.documentElement();
2222 double svgWidth, svgHeight, vbWidth, vbHeight;
2223 bool svgOK = TextUtils::getSvgSizes(writeDoc, svgWidth, svgHeight, vbWidth, vbHeight);
2224 if (svgOK) {
2225 QHash<QString, QString> svgHash;
2226 foreach (QGraphicsItem * item, viewThing->sketchWidget->scene()->items()) {
2227 ItemBase * itemBase = dynamic_cast<ItemBase *>(item);
2228 if (itemBase == NULL) continue;
2229
2230 if (itemBase->layerKinChief() == viewThing->itemBase) continue;
2231
2232 double factor;
2233 QString string = itemBase->layerKinChief()->retrieveSvg(itemBase->viewLayerID(), svgHash, false, vbWidth / svgWidth, factor);
2234 if (!string.isEmpty()) {
2235 QPointF d = itemBase->pos() - viewThing->itemBase->pos();
2236 double dx = d.x() * (vbWidth / svgWidth) / GraphicsUtils::SVGDPI;
2237 double dy = d.y() * (vbWidth / svgWidth) / GraphicsUtils::SVGDPI;
2238 string = QString("<g transform='translate(%1,%2)'>\n%3\n</g>\n").arg(dx).arg(dy).arg(string);
2239 extraSvg.insert(itemBase->viewLayerID(), string);
2240 }
2241 }
2242 }
2243
2244
2245 // deal with copper1 and copper0:
2246 // find innermost
2247 // don't save twice
2248 QDomElement copperChild;
2249 LayerList sketchLayers = viewThing->sketchWidget->viewLayers().keys();
2250 if (sketchLayers.contains(ViewLayer::Copper0) && sketchLayers.contains(ViewLayer::Copper1)) {
2251 QDomElement copper0 = TextUtils::findElementWithAttribute(svgRoot, "id", "copper0");
2252 QDomElement copper1 = TextUtils::findElementWithAttribute(svgRoot, "id", "copper1");
2253 if (!copper0.isNull() && !copper1.isNull()) {
2254 QDomElement parent0 = copper0.parentNode().toElement();
2255 QDomElement parent1 = copper1.parentNode().toElement();
2256 if (parent0.attribute("id") == "copper1") {
2257 copperChild = copper0;
2258 }
2259 else if (parent1.attribute("id") == "copper0") {
2260 copperChild = copper1;
2261 }
2262 }
2263 if (!copperChild.isNull()) {
2264 if (extraSvg.uniqueKeys().contains(ViewLayer::Copper0)) {
2265 extraSvg.remove(ViewLayer::Copper1);
2266 }
2267 }
2268 }
2269
2270 foreach (ViewLayer::ViewLayerID viewLayerID, extraSvg.uniqueKeys()) {
2271 QStringList svgList = extraSvg.values(viewLayerID);
2272 QDomElement svgViewElement = (viewLayerID == ViewLayer::Copper0 && !copperChild.isNull())
2273 ? copperChild
2274 : TextUtils::findElementWithAttribute(svgRoot, "id", ViewLayer::viewLayerXmlNameFromID(viewLayerID));
2275 if (svgViewElement.isNull()) {
2276 svgViewElement = svgRoot;
2277 }
2278
2279 foreach (QString svg, svgList) {
2280 QDomDocument doc;
2281 doc.setContent(svg, true);
2282 QDomElement root = doc.documentElement();
2283 foreach (ViewLayer::ViewLayerID vlid, sketchLayers) {
2284 removeID(root, ViewLayer::viewLayerXmlNameFromID(vlid));
2285 }
2286 svgViewElement.appendChild(doc.documentElement());
2287 }
2288 }
2289
2290 QString svg = writeDoc.toString();
2291 insertDesc(viewThing->referenceFile, svg);
2292
2293 QString svgPath = makeSvgPath2(viewThing->sketchWidget);
2294 setImageAttribute(layers, svgPath);
2295
2296 QString actualPath = m_userPartsFolderSvgPath + svgPath;
2297 bool result = writeXml(actualPath, removeGorn(svg), false);
2298 if (!result) {
2299 // TODO: warn user
2300 }
2301 }
2302
2303 QDir dir(m_userPartsFolderPath);
2304 QString suffix = QString("%1_%2_%3").arg(m_prefix).arg(m_guid).arg(m_fileIndex++);
2305 QString fzpPath = dir.absoluteFilePath(QString("%1.fzp").arg(suffix));
2306
2307 if (overWrite) {
2308 fzpPath = m_originalFzpPath;
2309 }
2310 else {
2311 fzpRoot.setAttribute("moduleId", suffix);
2312 QString family = m_metadataView->family();
2313 QString variant = m_metadataView->variant();
2314 QHash<QString, QString> variants = m_referenceModel->allPropValues(family, "variant");
2315 QStringList values = variants.values(variant);
2316 if (values.count() > 0) {
2317 QString newVariant = makeNewVariant(family);
2318 QDomElement properties = fzpRoot.firstChildElement("properties");
2319 replaceProperty("variant", newVariant, properties);
2320 m_metadataView->resetProperty("variant", newVariant);
2321 }
2322 }
2323
2324 bool result = writeXml(fzpPath, m_fzpDocument.toString(), false);
2325
2326 if (!overWrite) {
2327 m_originalFzpPath = fzpPath;
2328 m_canSave = true;
2329 m_originalModuleID = fzpRoot.attribute("moduleId");
2330 }
2331
2332 // restore the set of working svg files
2333 foreach (ViewLayer::ViewID viewID, m_viewThings.keys()) {
2334 QString svgPath = svgPaths.value(viewID);
2335 if (svgPath.isEmpty()) continue;
2336
2337 QDomElement view = views.firstChildElement(ViewLayer::viewIDXmlName(viewID));
2338 QDomElement layers = view.firstChildElement("layers");
2339 setImageAttribute(layers, svgPath);
2340 }
2341
2342 ModelPart * modelPart = m_referenceModel->retrieveModelPart(m_originalModuleID);
2343 if (modelPart == NULL) {
2344 modelPart = m_referenceModel->loadPart(fzpPath, true);
2345 emit addToMyPartsSignal(modelPart);
2346 }
2347 else {
2348 m_referenceModel->reloadPart(fzpPath, m_originalModuleID);
2349 WaitPushUndoStack undoStack;
2350 QUndoCommand * parentCommand = new QUndoCommand;
2351 foreach (MainWindow * mainWindow, affectedWindows) {
2352 mainWindow->updateParts(m_originalModuleID, parentCommand);
2353 }
2354 foreach (MainWindow * mainWindow, allWindows) {
2355 mainWindow->updatePartsBin(m_originalModuleID);
2356 }
2357 undoStack.push(parentCommand);
2358 }
2359
2360 m_autosaveNeeded = false;
2361 m_undoStack->setClean();
2362
2363 return result;
2364 }
2365
2366
updateChangeCount(SketchWidget * sketchWidget,int changeDirection)2367 void PEMainWindow::updateChangeCount(SketchWidget * sketchWidget, int changeDirection) {
2368 ViewThing * viewThing = m_viewThings.value(sketchWidget->viewID());
2369 viewThing->svgChangeCount += changeDirection;
2370 }
2371
findConnectorItem()2372 PEGraphicsItem * PEMainWindow::findConnectorItem()
2373 {
2374 if (m_currentGraphicsView == NULL) return NULL;
2375
2376 QDomElement connector = m_connectorList.at(m_peToolView->currentConnectorIndex());
2377 if (connector.isNull()) return NULL;
2378
2379 QString svgID, terminalID;
2380 bool ok = ViewLayer::getConnectorSvgIDs(connector, m_currentGraphicsView->viewID(), svgID, terminalID);
2381 if (!ok) return NULL;
2382
2383 QList<PEGraphicsItem *> pegiList = getPegiList(m_currentGraphicsView);
2384 foreach (PEGraphicsItem * pegi, pegiList) {
2385 if (pegi->element().attribute("id") == svgID) return pegi;
2386 }
2387
2388 return NULL;
2389 }
2390
terminalPointChanged(const QString & how)2391 void PEMainWindow::terminalPointChanged(const QString & how) {
2392 PEGraphicsItem * pegi = findConnectorItem();
2393 if (pegi == NULL) return;
2394
2395 QRectF r = pegi->rect();
2396 QPointF p = r.center();
2397 if (how == "center") {
2398 }
2399 else if (how == "N") {
2400 p.setY(0);
2401 }
2402 else if (how == "E") {
2403 p.setX(r.width());
2404 }
2405 else if (how == "S") {
2406 p.setY(r.height());
2407 }
2408 else if (how == "W") {
2409 p.setX(0);
2410 }
2411 terminalPointChangedAux(pegi, pegi->terminalPoint(), p);
2412
2413 // TODO: UndoCommand which changes fzp xml and svg xml
2414 }
2415
terminalPointChanged(const QString & coord,double value)2416 void PEMainWindow::terminalPointChanged(const QString & coord, double value)
2417 {
2418 PEGraphicsItem * pegi = findConnectorItem();
2419 if (pegi == NULL) return;
2420
2421 QPointF p = pegi->terminalPoint();
2422 if (coord == "x") {
2423 p.setX(qMax(0.0, qMin(value, pegi->rect().width())));
2424 }
2425 else {
2426 p.setY(qMax(0.0, qMin(value, pegi->rect().height())));
2427 }
2428
2429 terminalPointChangedAux(pegi, pegi->terminalPoint(), p);
2430 }
2431
terminalPointChangedAux(PEGraphicsItem * pegi,QPointF before,QPointF after)2432 void PEMainWindow::terminalPointChangedAux(PEGraphicsItem * pegi, QPointF before, QPointF after)
2433 {
2434 if (pegi->pendingTerminalPoint() == after) {
2435 return;
2436 }
2437
2438 pegi->setPendingTerminalPoint(after);
2439
2440 QDomElement currentConnectorElement = m_connectorList.at(m_peToolView->currentConnectorIndex());
2441
2442 MoveTerminalPointCommand * mtpc = new MoveTerminalPointCommand(this, this->m_currentGraphicsView, currentConnectorElement.attribute("id"), pegi->rect().size(), before, after, NULL);
2443 mtpc->setText(tr("Move terminal point"));
2444 m_undoStack->waitPush(mtpc, SketchWidget::PropChangeDelay);
2445 }
2446
getSpinAmount(double & amount)2447 void PEMainWindow::getSpinAmount(double & amount) {
2448 double zoom = m_currentGraphicsView->currentZoom() / 100;
2449 if (zoom == 0) {
2450 amount = 1;
2451 return;
2452 }
2453
2454 amount = qMin(1.0, 1.0 / zoom);
2455 if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
2456 amount *= 10;
2457 }
2458 }
2459
moveTerminalPoint(SketchWidget * sketchWidget,const QString & connectorID,QSizeF size,QPointF p,int changeDirection)2460 void PEMainWindow::moveTerminalPoint(SketchWidget * sketchWidget, const QString & connectorID, QSizeF size, QPointF p, int changeDirection)
2461 {
2462 // TODO: assumes these IDs are unique--need to check if they are not
2463
2464 QPointF center(size.width() / 2, size.height() / 2);
2465 bool centered = qAbs(center.x() - p.x()) < .05 && qAbs(center.y() - p.y()) < .05;
2466
2467 QDomElement fzpRoot = m_fzpDocument.documentElement();
2468 QDomElement connectorElement = TextUtils::findElementWithAttribute(fzpRoot, "id", connectorID);
2469 if (connectorElement.isNull()) {
2470 DebugDialog::debug(QString("missing connector %1").arg(connectorID));
2471 return;
2472 }
2473
2474 QDomElement pElement = ViewLayer::getConnectorPElement(connectorElement, sketchWidget->viewID());
2475 QString svgID = pElement.attribute("svgId");
2476 if (svgID.isEmpty()) {
2477 DebugDialog::debug(QString("Can't find svgId for connector %1").arg(connectorID));
2478 return;
2479 }
2480
2481 PEGraphicsItem * connectorPegi = NULL;
2482 QList<PEGraphicsItem *> pegiList = getPegiList(sketchWidget);
2483 foreach (PEGraphicsItem * pegi, pegiList) {
2484 QDomElement pegiElement = pegi->element();
2485 if (pegiElement.attribute("id").compare(svgID) == 0) {
2486 connectorPegi = pegi;
2487 }
2488 }
2489 if (connectorPegi == NULL) return;
2490
2491 if (centered) {
2492 pElement.removeAttribute("terminalId");
2493 // no need to change SVG in this case
2494 }
2495 else {
2496 ViewThing * viewThing = m_viewThings.value(sketchWidget->viewID());
2497 QDomDocument * svgDoc = viewThing->document;
2498 QDomElement svgRoot = svgDoc->documentElement();
2499 QDomElement svgConnectorElement = TextUtils::findElementWithAttribute(svgRoot, "id", svgID);
2500 if (svgConnectorElement.isNull()) {
2501 DebugDialog::debug(QString("Unable to find svg connector element %1").arg(svgID));
2502 return;
2503 }
2504
2505 QString terminalID = pElement.attribute("terminalId");
2506 if (terminalID.isEmpty()) {
2507 terminalID = svgID;
2508 if (terminalID.endsWith("pin") || terminalID.endsWith("pad")) {
2509 terminalID.chop(3);
2510 }
2511 terminalID += "terminal";
2512 pElement.setAttribute("terminalId", terminalID);
2513 }
2514
2515 FSvgRenderer renderer;
2516 renderer.loadSvg(svgDoc->toByteArray(), "", false);
2517 QRectF svgBounds = renderer.boundsOnElement(svgID);
2518 double cx = p.x () * svgBounds.width() / size.width();
2519 double cy = p.y() * svgBounds.height() / size.height();
2520 double dx = svgBounds.width() / 1000;
2521 double dy = svgBounds.height() / 1000;
2522
2523 QDomElement terminalElement = TextUtils::findElementWithAttribute(svgRoot, "id", terminalID);
2524 if (terminalElement.isNull()) {
2525 terminalElement = svgDoc->createElement("rect");
2526 }
2527 else if (terminalElement.tagName() != "rect" || terminalElement.attribute("fill") != "none" || terminalElement.attribute("stroke") != "none") {
2528 terminalElement.setAttribute("id", "");
2529 terminalElement = svgDoc->createElement("rect");
2530 }
2531 terminalElement.setAttribute("id", terminalID);
2532 terminalElement.setAttribute("oldid", terminalID);
2533 terminalElement.setAttribute("stroke", "none");
2534 terminalElement.setAttribute("fill", "none");
2535 terminalElement.setAttribute("stroke-width", "0");
2536 terminalElement.setAttribute("x", QString::number(svgBounds.left() + cx - dx));
2537 terminalElement.setAttribute("y", QString::number(svgBounds.top() + cy - dy));
2538 terminalElement.setAttribute("width", QString::number(dx * 2));
2539 terminalElement.setAttribute("height", QString::number(dy * 2));
2540 if (terminalElement.attribute("gorn").isEmpty()) {
2541 QString gorn = svgConnectorElement.attribute("gorn");
2542 int ix = gorn.lastIndexOf(".");
2543 if (ix > 0) {
2544 gorn.truncate(ix);
2545 }
2546 gorn = QString("%1.gen%2").arg(gorn).arg(FakeGornSiblingNumber++);
2547 terminalElement.setAttribute("gorn", gorn);
2548 }
2549
2550 svgConnectorElement.parentNode().insertAfter(terminalElement, svgConnectorElement);
2551
2552 double oldZ = connectorPegi->zValue() + 1;
2553 foreach (PEGraphicsItem * pegi, pegiList) {
2554 QDomElement pegiElement = pegi->element();
2555 if (pegiElement.attribute("id").compare(terminalID) == 0) {
2556 DebugDialog::debug("old pegi location", pegi->pos());
2557 oldZ = pegi->zValue();
2558 pegiList.removeOne(pegi);
2559 delete pegi;
2560 break;
2561 }
2562 }
2563
2564 // update svg in case there is a subsequent call to reload
2565 QString newPath = m_userPartsFolderSvgPath + makeSvgPath2(sketchWidget);
2566 QString svg = TextUtils::svgNSOnly(svgDoc->toString());
2567 writeXml(newPath, removeGorn(svg), true);
2568 setImageAttribute(fzpRoot, newPath, sketchWidget->viewID());
2569
2570 double invdx = dx * size.width() / svgBounds.width();
2571 double invdy = dy * size.height() / svgBounds.height();
2572 QPointF topLeft = connectorPegi->offset() + p - QPointF(invdx, invdy);
2573 PEGraphicsItem * pegi = makePegi(QSizeF(invdx * 2, invdy * 2), topLeft, viewThing->itemBase, terminalElement, oldZ);
2574 DebugDialog::debug("new pegi location", pegi->pos());
2575 updateChangeCount(sketchWidget, changeDirection);
2576 }
2577
2578 connectorPegi->setTerminalPoint(p);
2579 connectorPegi->update();
2580 m_peToolView->setTerminalPointCoords(p);
2581 }
2582
showInOS(QWidget * parent,const QString & pathIn)2583 void PEMainWindow::showInOS(QWidget *parent, const QString &pathIn)
2584 {
2585 Q_UNUSED(parent);
2586 FolderUtils::showInFolder(pathIn);
2587 QClipboard *clipboard = QApplication::clipboard();
2588 if (clipboard != NULL) {
2589 clipboard->setText(pathIn);
2590 }
2591 }
2592
showInOS()2593 void PEMainWindow::showInOS() {
2594 if (m_currentGraphicsView == NULL) return;
2595
2596 ViewThing * viewThing = m_viewThings.value(m_currentGraphicsView->viewID());
2597 showInOS(this, viewThing->itemBase->filename());
2598 }
2599
makePegi(QSizeF size,QPointF topLeft,ItemBase * itemBase,QDomElement & element,double z)2600 PEGraphicsItem * PEMainWindow::makePegi(QSizeF size, QPointF topLeft, ItemBase * itemBase, QDomElement & element, double z)
2601 {
2602 PEGraphicsItem * pegiItem = new PEGraphicsItem(0, 0, size.width(), size.height(), itemBase);
2603 pegiItem->showTerminalPoint(false);
2604 pegiItem->setPos(itemBase->pos() + topLeft);
2605 pegiItem->setZValue(z);
2606 itemBase->scene()->addItem(pegiItem);
2607 pegiItem->setElement(element);
2608 pegiItem->setOffset(topLeft);
2609 connect(pegiItem, SIGNAL(highlightSignal(PEGraphicsItem *)), this, SLOT(highlightSlot(PEGraphicsItem *)));
2610 connect(pegiItem, SIGNAL(mouseReleasedSignal(PEGraphicsItem *)), this, SLOT(pegiMouseReleased(PEGraphicsItem *)));
2611 connect(pegiItem, SIGNAL(mousePressedSignal(PEGraphicsItem *, bool &)), this, SLOT(pegiMousePressed(PEGraphicsItem *, bool &)), Qt::DirectConnection);
2612 connect(pegiItem, SIGNAL(terminalPointMoved(PEGraphicsItem *, QPointF)), this, SLOT(pegiTerminalPointMoved(PEGraphicsItem *, QPointF)));
2613 connect(pegiItem, SIGNAL(terminalPointChanged(PEGraphicsItem *, QPointF, QPointF)), this, SLOT(pegiTerminalPointChanged(PEGraphicsItem *, QPointF, QPointF)));
2614 return pegiItem;
2615 }
2616
getPixelBounds(FSvgRenderer & renderer,QDomElement & element)2617 QRectF PEMainWindow::getPixelBounds(FSvgRenderer & renderer, QDomElement & element)
2618 {
2619 QSizeF defaultSizeF = renderer.defaultSizeF();
2620 QRectF viewBox = renderer.viewBoxF();
2621
2622 QString id = element.attribute("id");
2623 QRectF r = renderer.boundsOnElement(id);
2624 QMatrix matrix = renderer.matrixForElement(id);
2625 QString oldid = element.attribute("oldid");
2626 if (!oldid.isEmpty()) {
2627 element.setAttribute("id", oldid);
2628 element.removeAttribute("oldid");
2629 }
2630 QRectF bounds = matrix.mapRect(r);
2631 bounds.setRect(bounds.x() * defaultSizeF.width() / viewBox.width(),
2632 bounds.y() * defaultSizeF.height() / viewBox.height(),
2633 bounds.width() * defaultSizeF.width() / viewBox.width(),
2634 bounds.height() * defaultSizeF.height() / viewBox.height());
2635 return bounds;
2636 }
2637
canSave()2638 bool PEMainWindow::canSave() {
2639
2640 return m_canSave;
2641 }
2642
tabWidget_currentChanged(int index)2643 void PEMainWindow::tabWidget_currentChanged(int index) {
2644 MainWindow::tabWidget_currentChanged(index);
2645
2646 if (m_peToolView == NULL) return;
2647
2648 switchedConnector(m_peToolView->currentConnectorIndex());
2649
2650 bool enabled = index < IconViewIndex;
2651 m_peSvgView->setChildrenVisible(enabled);
2652 m_peToolView->setChildrenVisible(enabled);
2653
2654 if (m_currentGraphicsView == NULL) {
2655 // update title when switching to connector and metadata view
2656 setTitle();
2657 }
2658 else {
2659 }
2660
2661 updateAssignedConnectors();
2662 updateActiveLayerButtons();
2663 }
2664
backupSketch()2665 void PEMainWindow::backupSketch()
2666 {
2667 }
2668
removedConnector(const QDomElement & element)2669 void PEMainWindow::removedConnector(const QDomElement & element)
2670 {
2671 QList<QDomElement> connectors;
2672 connectors.append(element);
2673 removedConnectorsAux(connectors);
2674 }
2675
removedConnectors(QList<ConnectorMetadata * > & cmdList)2676 void PEMainWindow::removedConnectors(QList<ConnectorMetadata *> & cmdList)
2677 {
2678 QList<QDomElement> connectors;
2679
2680 foreach (ConnectorMetadata * cmd, cmdList) {
2681 int index;
2682 QDomElement connector = findConnector(cmd->connectorID, index);
2683 if (connector.isNull()) return;
2684
2685 cmd->index = index;
2686 connectors.append(connector);
2687 }
2688
2689 removedConnectorsAux(connectors);
2690 }
2691
removedConnectorsAux(QList<QDomElement> & connectors)2692 void PEMainWindow::removedConnectorsAux(QList<QDomElement> & connectors)
2693 {
2694 QString originalPath = saveFzp();
2695
2696 foreach (QDomElement connector, connectors) {
2697 if (m_removedConnector.isEmpty()) {
2698 QTextStream stream(&m_removedConnector);
2699 connector.save(stream, 0);
2700 }
2701 connector.parentNode().removeChild(connector);
2702 }
2703
2704 QString newPath = saveFzp();
2705
2706 ChangeFzpCommand * cfc = new ChangeFzpCommand(this, originalPath, newPath, NULL);
2707 QString message;
2708 if (connectors.count() == 1) {
2709 message = tr("Remove connector");
2710 }
2711 else {
2712 message = tr("Remove %1 connectors").arg(connectors.count());
2713 }
2714 cfc->setText(message);
2715 m_undoStack->waitPush(cfc, SketchWidget::PropChangeDelay);
2716 }
2717
restoreFzp(const QString & fzpPath)2718 void PEMainWindow::restoreFzp(const QString & fzpPath)
2719 {
2720 if (!loadFzp(fzpPath)) return;
2721
2722 reload(false);
2723 }
2724
setBeforeClosingText(const QString & filename,QMessageBox & messageBox)2725 void PEMainWindow::setBeforeClosingText(const QString & filename, QMessageBox & messageBox)
2726 {
2727 Q_UNUSED(filename);
2728
2729 QString partTitle = getPartTitle();
2730 messageBox.setWindowTitle(tr("Save \"%1\"").arg(partTitle));
2731 messageBox.setText(tr("Do you want to save the changes you made in the part \"%1\"?").arg(partTitle));
2732 messageBox.setInformativeText(tr("Your changes will be lost if you don't save them."));
2733 }
2734
getPartTitle()2735 QString PEMainWindow::getPartTitle() {
2736 QString partTitle = tr("untitled part");
2737 QDomElement root = m_fzpDocument.documentElement();
2738 QDomElement title = root.firstChildElement("title");
2739 QString candidate = title.text();
2740 if (!candidate.isEmpty()) return candidate;
2741
2742 if (m_viewThings.count() > 0) {
2743 ViewThing * viewThing = m_viewThings.values().at(0);
2744 if (viewThing->itemBase) {
2745 candidate = viewThing->itemBase->title();
2746 if (!candidate.isEmpty()) return candidate;
2747 }
2748 }
2749
2750 return partTitle;
2751 }
2752
killPegi()2753 void PEMainWindow::killPegi() {
2754 foreach (ViewThing * viewThing, m_viewThings.values()) {
2755 if (viewThing->sketchWidget == NULL) continue;
2756
2757 foreach (QGraphicsItem * item, viewThing->sketchWidget->scene()->items()) {
2758 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
2759 if (pegi) delete pegi;
2760 }
2761 }
2762 }
2763
loadFzp(const QString & path)2764 bool PEMainWindow::loadFzp(const QString & path) {
2765 QFile file(path);
2766 QString errorStr;
2767 int errorLine;
2768 int errorColumn;
2769 bool result = m_fzpDocument.setContent(&file, &errorStr, &errorLine, &errorColumn);
2770 if (!result) {
2771 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to load fzp from %1").arg(path));
2772 return false;
2773 }
2774
2775 return true;
2776 }
2777
connectorCountChanged(int newCount)2778 void PEMainWindow::connectorCountChanged(int newCount) {
2779 QList<QDomElement> connectorList;
2780 QDomElement root = m_fzpDocument.documentElement();
2781 QDomElement connectors = root.firstChildElement("connectors");
2782 QDomElement connector = connectors.firstChildElement("connector");
2783 while (!connector.isNull()) {
2784 connectorList.append(connector);
2785 connector = connector.nextSiblingElement();
2786 }
2787
2788 if (newCount == connectorList.count()) return;
2789
2790 if (newCount < connectorList.count()) {
2791 qSort(connectorList.begin(), connectorList.end(), byID);
2792 QList<QDomElement> toDelete;
2793 for (int i = newCount; i < connectorList.count(); i++) {
2794 toDelete.append(connectorList.at(i));
2795 }
2796
2797 removedConnectorsAux(toDelete);
2798 return;
2799 }
2800
2801 // add connectors
2802 int id = 0;
2803 foreach (QDomElement connector, connectorList) {
2804 int ix = IntegerFinder.indexIn(connector.attribute("id"));
2805 if (ix >= 0) {
2806 int candidate = IntegerFinder.cap(0).toInt();
2807 if (candidate > id) id = candidate;
2808 }
2809 // sometimes id = 0 but name = 1, and we are now using name = id
2810 ix = IntegerFinder.indexIn(connector.attribute("name"));
2811 if (ix >= 0) {
2812 int candidate = IntegerFinder.cap(0).toInt();
2813 if (candidate > id) id = candidate;
2814 }
2815 }
2816
2817 QString originalPath = saveFzp();
2818
2819 QDomElement connectorModel;
2820 QDomDocument tempDoc;
2821 if (connectorList.count() > 0) connectorModel = connectorList.at(0);
2822 else {
2823 tempDoc.setContent(m_removedConnector);
2824 connectorModel = tempDoc.documentElement();
2825 if (connectorModel.isNull()) {
2826 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to create new connector--you may have to start over."));
2827 return;
2828 }
2829 }
2830 for (int i = connectorList.count(); i < newCount; i++) {
2831 id++;
2832 QDomElement element = connectorModel.cloneNode(true).toElement();
2833 connectors.appendChild(element);
2834 QString newName = QString("pin %1").arg(id);
2835 element.setAttribute("name", newName);
2836 QString cid = QString("connector%1").arg(id);
2837 element.setAttribute("id", cid);
2838 QString svgid = cid + "pin";
2839 QDomNodeList nodeList = element.elementsByTagName("p");
2840 for (int n = 0; n < nodeList.count(); n++) {
2841 QDomElement p = nodeList.at(n).toElement();
2842 p.removeAttribute("terminalId");
2843 p.removeAttribute("legId");
2844 p.setAttribute("svgId", svgid);
2845 }
2846 QDomElement description = element.firstChildElement("description");
2847 if (description.isNull()) {
2848 description = m_fzpDocument.createElement("description");
2849 element.appendChild(description);
2850 }
2851 TextUtils::replaceChildText(description, newName);
2852 }
2853
2854 QString newPath = saveFzp();
2855
2856 ChangeFzpCommand * cfc = new ChangeFzpCommand(this, originalPath, newPath, NULL);
2857 QString message;
2858 if (newCount - connectorList.count() == 1) {
2859 message = tr("Add connector");
2860 }
2861 else {
2862 message = tr("Add %1 connectors").arg(newCount - connectorList.count());
2863 }
2864 cfc->setText(message);
2865 m_undoStack->waitPush(cfc, SketchWidget::PropChangeDelay);
2866 }
2867
editsModuleID(const QString & moduleID)2868 bool PEMainWindow::editsModuleID(const QString & moduleID) {
2869 // only to detect whether a user tries to open the parts editor on the same part twice
2870 return (m_originalModuleID.compare(moduleID) == 0);
2871 }
2872
2873 /*
2874 QString PEMainWindow::getFzpReferenceFile() {
2875 QString referenceFile = m_fzpDocument.documentElement().attribute(ReferenceFileString);
2876 if (!referenceFile.isEmpty()) referenceFile += "_";
2877 return referenceFile;
2878 }
2879 */
2880
getSvgReferenceFile(const QString & filename)2881 QString PEMainWindow::getSvgReferenceFile(const QString & filename) {
2882 QFileInfo info(filename);
2883 QString referenceFile = info.fileName();
2884
2885 QFile file(filename);
2886 if (!file.open(QFile::ReadOnly)) return referenceFile;
2887
2888 QString svg = file.readAll();
2889 if (!svg.contains(ReferenceFileString)) return referenceFile;
2890
2891 QXmlStreamReader xml(svg.toUtf8());
2892 xml.setNamespaceProcessing(false);
2893 bool inDesc = false;
2894 while (!xml.atEnd()) {
2895 switch (xml.readNext()) {
2896 case QXmlStreamReader::StartElement:
2897 {
2898 QString name = xml.name().toString();
2899 if (inDesc && name.compare(ReferenceFileString) == 0) {
2900 QString candidate = xml.readElementText().trimmed();
2901 if (candidate.isEmpty()) return referenceFile;
2902 return candidate;
2903 }
2904 if (name.compare("desc") == 0) {
2905 inDesc = true;
2906 }
2907 }
2908 break;
2909 case QXmlStreamReader::EndElement:
2910 {
2911 QString name = xml.name().toString();
2912 if (name.compare("desc") == 0) {
2913 inDesc = false;
2914 }
2915 }
2916 break;
2917
2918 default:
2919 break;
2920 }
2921 }
2922
2923 return referenceFile;
2924 }
2925
makeDesc(const QString & referenceFile)2926 QString PEMainWindow::makeDesc(const QString & referenceFile)
2927 {
2928 return QString("\n<desc><%2>%1</%2></desc>\n").arg(referenceFile).arg(ReferenceFileString);
2929 }
2930
updateWindowMenu()2931 void PEMainWindow::updateWindowMenu() {
2932 }
2933
updateRaiseWindowAction()2934 void PEMainWindow::updateRaiseWindowAction() {
2935 QString title = tr("Fritzing (New) Parts Editor");
2936 QString partTitle = getPartTitle();
2937 QString actionText = QString("%1: %2").arg(title).arg(partTitle);
2938 m_raiseWindowAct->setText(actionText);
2939 m_raiseWindowAct->setToolTip(actionText);
2940 m_raiseWindowAct->setStatusTip("raise \""+actionText+"\" window");
2941 }
2942
writeXml(const QString & path,const QString & xml,bool temp)2943 bool PEMainWindow::writeXml(const QString & path, const QString & xml, bool temp)
2944 {
2945 bool result = TextUtils::writeUtf8(path, TextUtils::svgNSOnly(xml));
2946 if (result) {
2947 if (temp) m_filesToDelete.append(path);
2948 else m_filesToDelete.removeAll(path);
2949 }
2950
2951 return result;
2952 }
2953
displayBuses()2954 void PEMainWindow::displayBuses() {
2955 deleteBuses();
2956
2957 QDomElement root = m_fzpDocument.documentElement();
2958 QDomElement buses = root.firstChildElement("buses");
2959 QDomElement bus = buses.firstChildElement("bus");
2960 while (!bus.isNull()) {
2961 QDomElement nodeMember = bus.firstChildElement("nodeMember");
2962 QSet<QString> connectorIDs;
2963 while (!nodeMember.isNull()) {
2964 QString connectorID = nodeMember.attribute("connectorId");
2965 if (!connectorID.isEmpty()) {
2966 connectorIDs.insert(connectorID);
2967 }
2968 nodeMember = nodeMember.nextSiblingElement("nodeMember");
2969 }
2970
2971 foreach (ViewLayer::ViewID viewID, m_viewThings.keys()) {
2972 ViewThing * viewThing = m_viewThings.value(viewID);
2973 QList<ConnectorItem *> connectorItems;
2974 foreach (QString connectorID, connectorIDs) {
2975 ConnectorItem * connectorItem = viewThing->itemBase->findConnectorItemWithSharedID(connectorID, viewThing->itemBase->viewLayerPlacement());
2976 if (connectorItem) connectorItems.append(connectorItem);
2977 }
2978 for (int i = 0; i < connectorItems.count() - 1; i++) {
2979 ConnectorItem * c1 = connectorItems.at(i);
2980 ConnectorItem * c2 = connectorItems.at(i + 1);
2981 Wire * wire = viewThing->sketchWidget->makeOneRatsnestWire(c1, c2, false, QColor(0, 255, 0), true);
2982 wire->setZValue(RatZ);
2983 }
2984 }
2985
2986 bus = bus.nextSiblingElement("bus");
2987 }
2988 }
2989
updateWireMenu()2990 void PEMainWindow::updateWireMenu() {
2991 // assumes update wire menu is only called when right-clicking a wire
2992 // and that wire is cached by the menu in Wire::mousePressEvent
2993
2994 Wire * wire = m_activeWire;
2995 m_activeWire = NULL;
2996
2997 m_deleteBusConnectionAct->setWire(wire);
2998 m_deleteBusConnectionAct->setEnabled(true);
2999 }
3000
deleteBusConnection()3001 void PEMainWindow::deleteBusConnection() {
3002 WireAction * wireAction = qobject_cast<WireAction *>(sender());
3003 if (wireAction == NULL) return;
3004
3005 Wire * wire = wireAction->wire();
3006 if (wire == NULL) return;
3007
3008 QList<Wire *> wires;
3009 QList<ConnectorItem *> ends;
3010 wire->collectChained(wires, ends);
3011 if (ends.count() != 2) return;
3012
3013 QString id0 = ends.at(0)->connectorSharedID();
3014 QString id1 = ends.at(1)->connectorSharedID();
3015
3016 QDomElement root = m_fzpDocument.documentElement();
3017 QDomElement buses = root.firstChildElement("buses");
3018 QDomElement bus = buses.firstChildElement("bus");
3019 QDomElement nodeMember0;
3020 QDomElement nodeMember1;
3021 while (!bus.isNull()) {
3022 nodeMember0 = TextUtils::findElementWithAttribute(bus, "connectorId", id0);
3023 nodeMember1 = TextUtils::findElementWithAttribute(bus, "connectorId", id1);
3024 if (!nodeMember0.isNull() && !nodeMember1.isNull()) break;
3025
3026 bus = bus.nextSiblingElement("bus");
3027 }
3028
3029 QString busID = bus.attribute("id");
3030
3031 if (nodeMember0.isNull() || nodeMember1.isNull()) {
3032 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Internal connections are very messed up."));
3033 return;
3034 }
3035
3036 QUndoCommand * parentCommand = new QUndoCommand();
3037 QStringList names;
3038 names << ends.at(0)->connectorSharedName() << ends.at(1)->connectorSharedName() ;
3039 new RemoveBusConnectorCommand(this, busID, id0, false, parentCommand);
3040 new RemoveBusConnectorCommand(this, busID, id1, false, parentCommand);
3041 if (ends.at(0)->connectedToItems().count() > 1) {
3042 // restore it
3043 names.removeAt(0);
3044 new RemoveBusConnectorCommand(this, busID, id0, true, parentCommand);
3045 }
3046 if (ends.at(1)->connectedToItems().count() > 1) {
3047 // restore it
3048 new RemoveBusConnectorCommand(this, busID, id1, true, parentCommand);
3049 }
3050
3051 parentCommand->setText(tr("Remove internal connection from '%1'").arg(names.at(0)));
3052 m_undoStack->waitPush(parentCommand, SketchWidget::PropChangeDelay);
3053 }
3054
newWireSlot(Wire * wire)3055 void PEMainWindow::newWireSlot(Wire * wire) {
3056 wire->setDisplayBendpointCursor(false);
3057 disconnect(wire, 0, m_viewThings.value(wire->viewID())->sketchWidget, 0);
3058 connect(wire, SIGNAL(wireChangedSignal(Wire*, const QLineF & , const QLineF & , QPointF, QPointF, ConnectorItem *, ConnectorItem *) ),
3059 this, SLOT(wireChangedSlot(Wire*, const QLineF & , const QLineF & , QPointF, QPointF, ConnectorItem *, ConnectorItem *)),
3060 Qt::DirectConnection); // DirectConnection means call the slot directly like a subroutine, without waiting for a thread or queue
3061 }
3062
wireChangedSlot(Wire * wire,const QLineF &,const QLineF &,QPointF,QPointF,ConnectorItem * fromOnWire,ConnectorItem * to)3063 void PEMainWindow::wireChangedSlot(Wire* wire, const QLineF &, const QLineF &, QPointF, QPointF, ConnectorItem * fromOnWire, ConnectorItem * to) {
3064 wire->deleteLater();
3065
3066 if (to == NULL) return;
3067
3068 ConnectorItem * from = wire->otherConnector(fromOnWire)->firstConnectedToIsh();
3069 if (from == NULL) return;
3070
3071 QDomElement root = m_fzpDocument.documentElement();
3072 QDomElement buses = root.firstChildElement("buses");
3073 if (buses.isNull()) {
3074 buses = m_fzpDocument.createElement("buses");
3075 root.appendChild(buses);
3076 }
3077
3078
3079 QDomElement fromBus = findNodeMemberBus(buses, from->connectorSharedID());
3080 QString fromBusID = fromBus.attribute("id");
3081 QDomElement toBus = findNodeMemberBus(buses, to->connectorSharedID());
3082 QString toBusID = toBus.attribute("id");
3083
3084 QString busID = fromBusID.isEmpty() ? toBusID : fromBusID;
3085 if (busID.isEmpty()) {
3086 int theMax = std::numeric_limits<int>::max();
3087 for (int ix = 1; ix < theMax; ix++) {
3088 QString candidate = QString("internal%1").arg(ix);
3089 QDomElement busElement = findBus(buses, candidate);
3090 if (busElement.isNull()) {
3091 busID = candidate;
3092 break;
3093 }
3094 }
3095 }
3096
3097 QUndoCommand * parentCommand = new QUndoCommand(tr("Add internal connection from '%1' to '%2'").arg(from->connectorSharedName()).arg(to->connectorSharedName()));
3098 if (!fromBusID.isEmpty()) {
3099 // changing the bus for this nodeMember
3100 new RemoveBusConnectorCommand(this, fromBusID, from->connectorSharedID(), false, parentCommand);
3101 }
3102 if (!toBusID.isEmpty()) {
3103 // changing the bus for this nodeMember
3104 new RemoveBusConnectorCommand(this, toBusID, to->connectorSharedID(), false, parentCommand);
3105 }
3106 new RemoveBusConnectorCommand(this, busID, from->connectorSharedID(), true, parentCommand);
3107 new RemoveBusConnectorCommand(this, busID, to->connectorSharedID(), true, parentCommand);
3108 m_undoStack->waitPush(parentCommand, SketchWidget::PropChangeDelay);
3109 }
3110
findBus(const QDomElement & buses,const QString & id)3111 QDomElement PEMainWindow::findBus(const QDomElement & buses, const QString & id)
3112 {
3113 QDomElement busElement = buses.firstChildElement("bus");
3114 while (!busElement.isNull()) {
3115 if (busElement.attribute("id").compare(id) == 0) {
3116 return busElement;
3117 }
3118 busElement = busElement.nextSiblingElement("bus");
3119 }
3120
3121 return QDomElement();
3122 }
3123
findNodeMemberBus(const QDomElement & buses,const QString & connectorID)3124 QDomElement PEMainWindow::findNodeMemberBus(const QDomElement & buses, const QString & connectorID)
3125 {
3126 QDomElement bus = buses.firstChildElement("bus");
3127 while (!bus.isNull()) {
3128 QDomElement nodeMember = bus.firstChildElement("nodeMember");
3129 while (!nodeMember.isNull()) {
3130 if (nodeMember.attribute("connectorId").compare(connectorID) == 0) {
3131 return bus;
3132 }
3133 nodeMember = nodeMember.nextSiblingElement("nodeMember");
3134 }
3135 bus = bus.nextSiblingElement("bus");
3136 }
3137
3138 return QDomElement();
3139 }
3140
addBusConnector(const QString & busID,const QString & connectorID)3141 void PEMainWindow::addBusConnector(const QString & busID, const QString & connectorID)
3142 {
3143 // called from command object
3144 removeBusConnector(busID, connectorID, false); // keep the dom very clean
3145
3146 QDomElement root = m_fzpDocument.documentElement();
3147 QDomElement buses = root.firstChildElement("buses");
3148 if (buses.isNull()) {
3149 m_fzpDocument.createElement("buses");
3150 root.appendChild(buses);
3151 }
3152
3153 QDomElement theBusElement = findBus(buses, busID);
3154 if (theBusElement.isNull()) {
3155 theBusElement = m_fzpDocument.createElement("bus");
3156 theBusElement.setAttribute("id", busID);
3157 buses.appendChild(theBusElement);
3158 }
3159
3160 QDomElement nodeMember = m_fzpDocument.createElement("nodeMember");
3161 nodeMember.setAttribute("connectorId", connectorID);
3162 theBusElement.appendChild(nodeMember);
3163 displayBuses();
3164 }
3165
removeBusConnector(const QString & busID,const QString & connectorID,bool display)3166 void PEMainWindow::removeBusConnector(const QString & busID, const QString & connectorID, bool display)
3167 {
3168 // called from command object
3169 // for the sake of cleaning, deletes all matching nodeMembers so be careful about the order of deletion and addition within the same parentCommand
3170 Q_UNUSED(busID);
3171
3172 QDomElement root = m_fzpDocument.documentElement();
3173 QDomElement buses = root.firstChildElement("buses");
3174 QDomElement bus = buses.firstChildElement("bus");
3175 QList<QDomElement> toDelete;
3176 while (!bus.isNull()) {
3177 QDomElement nodeMember = bus.firstChildElement("nodeMember");
3178 while (!nodeMember.isNull()) {
3179 if (nodeMember.attribute("connectorId").compare(connectorID) == 0) {
3180 toDelete.append(nodeMember);
3181 }
3182 nodeMember = nodeMember.nextSiblingElement("nodeMember");
3183 }
3184 bus = bus.nextSiblingElement("bus");
3185 }
3186
3187 foreach (QDomElement element, toDelete) {
3188 element.parentNode().removeChild(element);
3189 }
3190
3191 if (display) displayBuses();
3192 }
3193
replaceProperty(const QString & key,const QString & value,QDomElement & properties)3194 void PEMainWindow::replaceProperty(const QString & key, const QString & value, QDomElement & properties)
3195 {
3196 QDomElement prop = properties.firstChildElement("property");
3197 while (!prop.isNull()) {
3198 QString name = prop.attribute("name");
3199 if (name.compare(key, Qt::CaseInsensitive) == 0) {
3200 TextUtils::replaceChildText(prop, value);
3201 return;
3202 }
3203
3204 prop = prop.nextSiblingElement("property");
3205 }
3206
3207 prop = m_fzpDocument.createElement("property");
3208 properties.appendChild(prop);
3209 prop.setAttribute("name", key);
3210 TextUtils::replaceChildText(prop, value);
3211 }
3212
createTabWidget()3213 QWidget * PEMainWindow::createTabWidget() {
3214 QTabWidget * tabWidget = new QTabWidget(this);
3215 tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
3216 tabWidget->setObjectName("pe_tabs");
3217 return tabWidget;
3218 }
3219
addTab(QWidget * widget,const QString & label)3220 void PEMainWindow::addTab(QWidget * widget, const QString & label) {
3221 qobject_cast<QTabWidget *>(m_tabWidget)->addTab(widget, label);
3222 this -> setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
3223 }
3224
currentTabIndex()3225 int PEMainWindow::currentTabIndex() {
3226 return qobject_cast<QTabWidget *>(m_tabWidget)->currentIndex();
3227 }
3228
setCurrentTabIndex(int index)3229 void PEMainWindow::setCurrentTabIndex(int index) {
3230 qobject_cast<QTabWidget *>(m_tabWidget)->setCurrentIndex(index);
3231 }
3232
currentTabWidget()3233 QWidget * PEMainWindow::currentTabWidget() {
3234 return qobject_cast<QTabWidget *>(m_tabWidget)->currentWidget();
3235 }
3236
event(QEvent * e)3237 bool PEMainWindow::event(QEvent * e) {
3238 if (e->type() == QEvent::Close) {
3239 //qDebug() << "event close";
3240 //if (m_inFocusWidgets.count() > 0) {
3241 // e->ignore();
3242 // qDebug() << "bail in focus";
3243 // return true;
3244 //}
3245 }
3246
3247 return MainWindow::event(e);
3248 }
3249
eventFilter(QObject * object,QEvent * event)3250 bool PEMainWindow::eventFilter(QObject *object, QEvent *event)
3251 {
3252 if (m_inPickMode) {
3253 switch (event->type()) {
3254 case QEvent::MouseButtonPress:
3255 clearPickMode();
3256 {
3257 QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
3258 m_useNextPick = (mouseEvent->button() == Qt::LeftButton);
3259 }
3260 QTimer::singleShot(1, this, SLOT(resetNextPick()));
3261 break;
3262
3263 case QEvent::ApplicationActivate:
3264 case QEvent::ApplicationDeactivate:
3265 case QEvent::WindowActivate:
3266 case QEvent::WindowDeactivate:
3267 case QEvent::NonClientAreaMouseButtonPress:
3268 clearPickMode();
3269 break;
3270
3271 case QEvent::KeyPress:
3272 {
3273 QKeyEvent * kevent = static_cast<QKeyEvent *>(event);
3274 if (kevent->key() == Qt::Key_Escape) {
3275 clearPickMode();
3276 return true;
3277 }
3278 }
3279
3280 default:
3281 break;
3282 }
3283
3284 return false;
3285 }
3286
3287 //qDebug() << "event" << event->type();
3288 if (event->type() == QEvent::FocusIn) {
3289 QLineEdit * lineEdit = qobject_cast<QLineEdit *>(object);
3290 if (lineEdit != NULL) {
3291 if (lineEdit->window() == this) {
3292 qDebug() << "inc focus";
3293 m_inFocusWidgets << lineEdit;
3294 }
3295 }
3296 else {
3297 QTextEdit * textEdit = qobject_cast<QTextEdit *>(object);
3298 if (textEdit != NULL && textEdit->window() == this) {
3299 qDebug() << "inc focus";
3300 m_inFocusWidgets << textEdit;
3301 }
3302 }
3303 }
3304 if (event->type() == QEvent::FocusOut) {
3305 QLineEdit * lineEdit = qobject_cast<QLineEdit *>(object);
3306 if (lineEdit != NULL) {
3307 if (lineEdit->window() == this) {
3308 qDebug() << "dec focus";
3309 m_inFocusWidgets.removeOne(lineEdit);
3310 }
3311 }
3312 else {
3313 QTextEdit * textEdit = qobject_cast<QTextEdit *>(object);
3314 if (textEdit != NULL && textEdit->window() == this) {
3315 qDebug() << "inc focus";
3316 m_inFocusWidgets.removeOne(textEdit);
3317 }
3318 }
3319 }
3320 return false;
3321 }
3322
closeLater()3323 void PEMainWindow::closeLater()
3324 {
3325 close();
3326 }
3327
resetNextPick()3328 void PEMainWindow::resetNextPick() {
3329 m_useNextPick = false;
3330 }
3331
clearPickMode()3332 void PEMainWindow::clearPickMode() {
3333 qApp->removeEventFilter(this);
3334 m_useNextPick = m_inPickMode = false;
3335 QApplication::restoreOverrideCursor();
3336 if (m_currentGraphicsView) {
3337 foreach (QGraphicsItem * item, m_currentGraphicsView->scene()->items()) {
3338 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
3339 if (pegi) pegi->setPickAppearance(false);
3340 }
3341 }
3342 }
3343
getPegiList(SketchWidget * sketchWidget)3344 QList<PEGraphicsItem *> PEMainWindow::getPegiList(SketchWidget * sketchWidget) {
3345 // DebugDialog::debug("-----------------------------");
3346
3347 QList<PEGraphicsItem *> pegiList;
3348 foreach (QGraphicsItem * item, sketchWidget->scene()->items()) {
3349 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
3350 if (pegi == NULL) continue;
3351
3352 pegiList.append(pegi);
3353 /*
3354 if (pegi->showingTerminalPoint() || pegi->showingMarquee() || !pegi->element().attribute("id").isEmpty()) {
3355 DebugDialog::debug(QString("pegi m:%1 t:%2 %3")
3356 .arg(pegi->showingMarquee())
3357 .arg(pegi->showingTerminalPoint())
3358 .arg(pegi->element().attribute("id")));
3359 }
3360 */
3361 }
3362
3363 //DebugDialog::debug("-----------------------------");
3364
3365 return pegiList;
3366 }
3367
deleteBuses()3368 void PEMainWindow::deleteBuses() {
3369 QList<Wire *> toDelete;
3370 foreach (ViewThing * viewThing, m_viewThings.values()) {
3371 foreach (QGraphicsItem * item, viewThing->sketchWidget->scene()->items()) {
3372 Wire * wire = dynamic_cast<Wire *>(item);
3373 if (wire == NULL) continue;
3374
3375 toDelete << wire;
3376 }
3377 }
3378
3379 foreach (Wire * wire, toDelete) {
3380 delete wire;
3381 }
3382 }
3383
connectorsTypeChanged(Connector::ConnectorType ct)3384 void PEMainWindow::connectorsTypeChanged(Connector::ConnectorType ct)
3385 {
3386 QUndoCommand * parentCommand = NULL;
3387
3388 QDomElement root = m_fzpDocument.documentElement();
3389 QDomElement connectors = root.firstChildElement("connectors");
3390 QDomElement connector = connectors.firstChildElement("connector");
3391 while (!connector.isNull()) {
3392 ConnectorMetadata oldCmd;
3393 fillInMetadata(connector, oldCmd);
3394 if (oldCmd.connectorType != ct) {
3395 if (parentCommand == NULL) {
3396 parentCommand = new QUndoCommand(tr("Change all connectors to %1").arg(Connector::connectorNameFromType(ct)));
3397 }
3398 ConnectorMetadata cmd = oldCmd;
3399 cmd.connectorType = ct;
3400 new ChangeConnectorMetadataCommand(this, &oldCmd, &cmd, parentCommand);
3401 }
3402
3403 connector = connector.nextSiblingElement("connector");
3404 }
3405
3406 if (parentCommand) {
3407 m_undoStack->waitPush(parentCommand, SketchWidget::PropChangeDelay);
3408 }
3409 }
3410
fillInMetadata(const QDomElement & connector,ConnectorMetadata & cmd)3411 void PEMainWindow::fillInMetadata(const QDomElement & connector, ConnectorMetadata & cmd)
3412 {
3413 cmd.connectorID = connector.attribute("id");
3414 cmd.connectorType = Connector::connectorTypeFromName(connector.attribute("type"));
3415 cmd.connectorName = connector.attribute("name");
3416 QDomElement description = connector.firstChildElement("description");
3417 cmd.connectorDescription = description.text();
3418 }
3419
smdChanged(const QString & after)3420 void PEMainWindow::smdChanged(const QString & after) {
3421 QString before;
3422 bool toSMD = true;
3423 if (after.compare("smd", Qt::CaseInsensitive) != 0) {
3424 before = "smd";
3425 toSMD = false;
3426 }
3427
3428 ViewThing * viewThing = m_viewThings.value(ViewLayer::PCBView);
3429 ItemBase * itemBase = viewThing->itemBase;
3430 if (itemBase == NULL) return;
3431
3432 QFile file(itemBase->filename());
3433 QDomDocument svgDoc;
3434 svgDoc.setContent(&file);
3435 QDomElement svgRoot = svgDoc.documentElement();
3436 QDomElement svgCopper0 = TextUtils::findElementWithAttribute(svgRoot, "id", "copper0");
3437 QDomElement svgCopper1 = TextUtils::findElementWithAttribute(svgRoot, "id", "copper1");
3438 if (svgCopper0.isNull() && svgCopper1.isNull()) {
3439 QMessageBox::critical(NULL, tr("Parts Editor"), tr("Unable to parse '%1'").arg(itemBase->filename()));
3440 return;
3441 }
3442
3443 if (toSMD) {
3444 if (svgCopper0.isNull() && !svgCopper1.isNull()) {
3445 // everything is fine
3446 }
3447 else if (svgCopper1.isNull()) {
3448 // just needs a swap
3449 svgCopper0.setAttribute("id", "copper1");
3450 }
3451 else {
3452 svgCopper0.removeAttribute("id");
3453 }
3454 }
3455 else {
3456 if (!svgCopper0.isNull() && !svgCopper1.isNull()) {
3457 // everything is fine
3458 }
3459 else if (svgCopper1.isNull()) {
3460 svgCopper1 = m_pcbDocument.createElement("g");
3461 svgCopper1.setAttribute("id", "copper1");
3462 svgRoot.appendChild(svgCopper1);
3463 QDomElement child = svgCopper0.firstChildElement();
3464 while (!child.isNull()) {
3465 QDomElement next = child.nextSiblingElement();
3466 svgCopper1.appendChild(child);
3467 child = next;
3468 }
3469 svgCopper0.appendChild(svgCopper1);
3470 }
3471 else {
3472 svgCopper0 = m_pcbDocument.createElement("g");
3473 svgCopper0.setAttribute("id", "copper0");
3474 svgRoot.appendChild(svgCopper0);
3475 QDomElement child = svgCopper1.firstChildElement();
3476 while (!child.isNull()) {
3477 QDomElement next = child.nextSiblingElement();
3478 svgCopper0.appendChild(child);
3479 child = next;
3480 }
3481 svgCopper1.appendChild(svgCopper0);
3482 }
3483 }
3484
3485 QString newPath = m_userPartsFolderSvgPath + makeSvgPath2(m_pcbGraphicsView);
3486 QString svg = TextUtils::svgNSOnly(svgDoc.toString());
3487 writeXml(newPath, removeGorn(svg), true);
3488
3489 ChangeSMDCommand * csc = new ChangeSMDCommand(this, before, after, itemBase->filename(), newPath, NULL);
3490 csc->setText(tr("Change to %1").arg(after));
3491 m_undoStack->waitPush(csc, SketchWidget::PropChangeDelay);
3492 }
3493
changeSMD(const QString & after,const QString & filename,int changeDirection)3494 void PEMainWindow::changeSMD(const QString & after, const QString & filename, int changeDirection)
3495 {
3496 QDomElement root = m_fzpDocument.documentElement();
3497 QDomElement views = root.firstChildElement("views");
3498 QDomElement pcbView = views.firstChildElement("pcbView");
3499 QDomElement layers = pcbView.firstChildElement("layers");
3500 QDomElement copper0 = TextUtils::findElementWithAttribute(layers, "layerId", "copper0");
3501 QDomElement copper1 = TextUtils::findElementWithAttribute(layers, "layerId", "copper1");
3502 bool toSMD = true;
3503 if (after.compare("smd", Qt::CaseInsensitive) == 0) {
3504 if (!copper0.isNull()) {
3505 copper0.parentNode().removeChild(copper0);
3506 }
3507 }
3508 else {
3509 toSMD = false;
3510 if (copper0.isNull()) {
3511 copper0 = m_fzpDocument.createElement("layer");
3512 copper0.setAttribute("layerId", "copper0");
3513 layers.appendChild(copper0);
3514 }
3515 }
3516 if (copper1.isNull()) {
3517 copper1 = m_fzpDocument.createElement("layer");
3518 copper1.setAttribute("layerId", "copper1");
3519 layers.appendChild(copper1);
3520 }
3521
3522 QDomElement connectors = root.firstChildElement("connectors");
3523 QDomElement connector = connectors.firstChildElement("connector");
3524 while (!connector.isNull()) {
3525 setConnectorSMD(toSMD, connector);
3526 connector = connector.nextSiblingElement("connector");
3527 }
3528
3529 changeSvg(m_pcbGraphicsView, filename, changeDirection);
3530 }
3531
setConnectorSMD(bool toSMD,QDomElement & connector)3532 void PEMainWindow::setConnectorSMD(bool toSMD, QDomElement & connector) {
3533 QDomElement views = connector.firstChildElement("views");
3534 QDomElement pcbView = views.firstChildElement("pcbView");
3535 QDomElement copper0 = TextUtils::findElementWithAttribute(pcbView, "layer", "copper0");
3536 QDomElement copper1 = TextUtils::findElementWithAttribute(pcbView, "layer", "copper1");
3537 if (copper0.isNull() && copper1.isNull()) {
3538 // SVG is seriously messed up
3539 DebugDialog::debug("setting SMD is very broken");
3540 return;
3541 }
3542
3543 if (toSMD) {
3544 if (copper0.isNull() && !copper1.isNull()) {
3545 // already correct
3546 return;
3547 }
3548 if (copper1.isNull()) {
3549 // swap it to copper1
3550 copper0.setAttribute("layer", "copper1");
3551 return;
3552 }
3553 // remove the extra layer
3554 copper0.parentNode().removeChild(copper0);
3555 return;
3556 }
3557
3558 if (!copper0.isNull() && !copper1.isNull()) {
3559 // already correct
3560 return;
3561 }
3562
3563 if (copper1.isNull()) {
3564 copper1 = copper0.cloneNode(true).toElement();
3565 copper0.parentNode().appendChild(copper1);
3566 copper1.setAttribute("layer", "copper1");
3567 return;
3568 }
3569
3570 copper0 = copper1.cloneNode(true).toElement();
3571 copper1.parentNode().appendChild(copper0);
3572 copper0.setAttribute("layer", "copper0");
3573 }
3574
3575
reuseBreadboard()3576 void PEMainWindow::reuseBreadboard()
3577 {
3578 reuseImage(ViewLayer::BreadboardView);
3579 }
3580
reuseSchematic()3581 void PEMainWindow::reuseSchematic()
3582 {
3583 reuseImage(ViewLayer::SchematicView);
3584 }
3585
reusePCB()3586 void PEMainWindow::reusePCB()
3587 {
3588 reuseImage(ViewLayer::PCBView);
3589 }
3590
reuseImage(ViewLayer::ViewID viewID)3591 void PEMainWindow::reuseImage(ViewLayer::ViewID viewID) {
3592 if (m_currentGraphicsView == NULL) return;
3593
3594 ViewThing * afterViewThing = m_viewThings.value(viewID);
3595 if (afterViewThing->itemBase == NULL) return;
3596
3597 QString afterFilename = afterViewThing->itemBase->filename();
3598
3599 ViewThing * beforeViewThing = m_viewThings.value(m_currentGraphicsView->viewID());
3600
3601 ChangeSvgCommand * csc = new ChangeSvgCommand(this, m_currentGraphicsView, beforeViewThing->itemBase->filename(), afterFilename, NULL);
3602 QFileInfo info(afterFilename);
3603 csc->setText(QString("Load '%1'").arg(info.fileName()));
3604 m_undoStack->waitPush(csc, SketchWidget::PropChangeDelay);
3605 }
3606
updateFileMenu()3607 void PEMainWindow::updateFileMenu() {
3608 MainWindow::updateFileMenu();
3609
3610 m_saveAct->setEnabled(canSave());
3611
3612 /*
3613 QHash<ViewLayer::ViewID, bool> enabled;
3614 enabled.insert(ViewLayer::BreadboardView, true);
3615 enabled.insert(ViewLayer::SchematicView, true);
3616 enabled.insert(ViewLayer::PCBView, true);
3617 bool enableAll = true;
3618 if (m_currentGraphicsView == NULL) {
3619 enableAll = false;
3620 }
3621 else {
3622 ViewLayer::ViewID viewID = m_currentGraphicsView->viewID();
3623 enabled.insert(viewID, false);
3624 }
3625
3626 m_reuseBreadboardAct->setEnabled(enableAll && enabled.value(ViewLayer::BreadboardView));
3627 m_reuseSchematicAct->setEnabled(enableAll && enabled.value(ViewLayer::SchematicView));
3628 m_reusePCBAct->setEnabled(enableAll && enabled.value(ViewLayer::PCBView));
3629 */
3630
3631 bool enabled = m_currentGraphicsView != NULL && m_currentGraphicsView->viewID() == ViewLayer::IconView;
3632 m_reuseBreadboardAct->setEnabled(enabled);
3633 m_reuseSchematicAct->setEnabled(enabled);
3634 m_reusePCBAct->setEnabled(enabled);
3635 }
3636
setImageAttribute(QDomElement & fzpRoot,const QString & svgPath,ViewLayer::ViewID viewID)3637 void PEMainWindow::setImageAttribute(QDomElement & fzpRoot, const QString & svgPath, ViewLayer::ViewID viewID)
3638 {
3639 QDomElement views = fzpRoot.firstChildElement("views");
3640 QDomElement view = views.firstChildElement(ViewLayer::viewIDXmlName(viewID));
3641 QDomElement layers = view.firstChildElement("layers");
3642 QFileInfo info(svgPath);
3643 QDir dir = info.absoluteDir();
3644 QString shortName = dir.dirName() + "/" + info.fileName();
3645 setImageAttribute(layers, shortName);
3646 }
3647
setImageAttribute(QDomElement & layers,const QString & svgPath)3648 void PEMainWindow::setImageAttribute(QDomElement & layers, const QString & svgPath)
3649 {
3650 layers.setAttribute("image", svgPath);
3651 QDomElement layer = layers.firstChildElement("layer");
3652 if (!layer.isNull()) return;
3653
3654 layer = m_fzpDocument.createElement("layer");
3655 layers.appendChild(layer);
3656 layer.setAttribute("layerId", ViewLayer::viewLayerXmlNameFromID(ViewLayer::UnknownLayer));
3657 }
3658
updateLayerMenu(bool resetLayout)3659 void PEMainWindow::updateLayerMenu(bool resetLayout) {
3660 MainWindow::updateLayerMenu(resetLayout);
3661
3662 bool enabled = false;
3663 if (m_currentGraphicsView != NULL) {
3664 switch (m_currentGraphicsView->viewID()) {
3665 case ViewLayer::BreadboardView:
3666 case ViewLayer::SchematicView:
3667 case ViewLayer::PCBView:
3668 enabled = true;
3669 default:
3670 break;
3671 }
3672 }
3673
3674 m_hideOtherViewsAct->setEnabled(enabled);
3675 }
3676
hideOtherViews()3677 void PEMainWindow::hideOtherViews() {
3678 if (m_currentGraphicsView == NULL) return;
3679
3680 ViewLayer::ViewID afterViewID = m_currentGraphicsView->viewID();
3681 ItemBase * afterItemBase = m_viewThings.value(afterViewID)->itemBase;
3682 if (afterItemBase == NULL) return;
3683 QString afterFilename = afterItemBase->filename();
3684
3685 QList<ViewLayer::ViewID> viewIDList;
3686 viewIDList << ViewLayer::BreadboardView << ViewLayer::SchematicView << ViewLayer::PCBView;
3687 viewIDList.removeOne(afterViewID);
3688
3689 QString originalPath = saveFzp();
3690
3691 QString afterViewName = ViewLayer::viewIDXmlName(afterViewID);
3692 QStringList beforeViewNames;
3693 foreach (ViewLayer::ViewID viewID, viewIDList) {
3694 beforeViewNames << ViewLayer::viewIDXmlName(viewID);
3695 }
3696
3697 QDomElement root = m_fzpDocument.documentElement();
3698 QDomElement connectors = root.firstChildElement("connectors");
3699 QDomElement connector = connectors.firstChildElement("connector");
3700 while (!connector.isNull()) {
3701 QDomElement views = connector.firstChildElement("views");
3702 QDomElement afterView = views.firstChildElement(afterViewName);
3703
3704 foreach (QString name, beforeViewNames) {
3705 QDomElement toRemove = views.firstChildElement(name);
3706 if (!toRemove.isNull()) {
3707 toRemove.parentNode().removeChild(toRemove);
3708 }
3709 QDomElement toReplace = afterView.cloneNode(true).toElement();
3710 toReplace.setTagName(name);
3711 views.appendChild(toReplace);
3712 }
3713
3714 connector = connector.nextSiblingElement("connector");
3715 }
3716
3717 QDomElement views = root.firstChildElement("views");
3718 QDomElement afterView = views.firstChildElement(afterViewName);
3719 foreach (QString name, beforeViewNames) {
3720 QDomElement toRemove = views.firstChildElement(name);
3721 if (!toRemove.isNull()) {
3722 toRemove.parentNode().removeChild(toRemove);
3723 }
3724 QDomElement toReplace = afterView.cloneNode(true).toElement();
3725 toReplace.setTagName(name);
3726 views.appendChild(toReplace);
3727 }
3728
3729 QString newPath = saveFzp();
3730 ChangeFzpCommand * cfc = new ChangeFzpCommand(this, originalPath, newPath, NULL);
3731 cfc->setText(tr("Make only %1 view visible").arg(m_currentGraphicsView->viewName()));
3732 m_undoStack->waitPush(cfc, SketchWidget::PropChangeDelay);
3733 }
3734
makeNewVariant(const QString & family)3735 QString PEMainWindow::makeNewVariant(const QString & family)
3736 {
3737 QStringList variants = m_referenceModel->propValues(family, "variant", true);
3738 int theMax = std::numeric_limits<int>::max();
3739 QString candidate;
3740 for (int i = 1; i < theMax; i++) {
3741 candidate = QString("variant %1").arg(i);
3742 if (!variants.contains(candidate, Qt::CaseInsensitive)) break;
3743 }
3744
3745 return candidate;
3746 }
3747
updateAssignedConnectors()3748 void PEMainWindow::updateAssignedConnectors() {
3749 if (m_currentGraphicsView == NULL) return;
3750
3751 QDomDocument * doc = m_viewThings.value(m_currentGraphicsView->viewID())->document;
3752 if (doc) m_peToolView->showAssignedConnectors(doc, m_currentGraphicsView->viewID());
3753 }
3754
connectorWarning()3755 void PEMainWindow::connectorWarning() {
3756 QHash<ViewLayer::ViewID, int> unassigned;
3757 foreach (ViewLayer::ViewID viewID, m_viewThings.keys()) {
3758 unassigned.insert(viewID, 0);
3759 }
3760 int unassignedTotal = 0;
3761
3762 QDomElement fzpRoot = m_fzpDocument.documentElement();
3763 QDomElement connectors = fzpRoot.firstChildElement("connectors");
3764 foreach (ViewLayer::ViewID viewID, m_viewThings.keys()) {
3765 if (viewID == ViewLayer::IconView) continue;
3766
3767 QDomDocument * svgDoc = m_viewThings.value(viewID)->document;
3768 QDomElement svgRoot = svgDoc->documentElement();
3769 QDomElement connector = connectors.firstChildElement("connector");
3770 while (!connector.isNull()) {
3771 QString svgID, terminalID;
3772 if (ViewLayer::getConnectorSvgIDs(connector, viewID, svgID, terminalID)) {
3773 QDomElement element = TextUtils::findElementWithAttribute(svgRoot, "id", svgID);
3774 if (element.isNull()) {
3775 unassigned.insert(viewID, 1 + unassigned.value(viewID));
3776 unassignedTotal++;
3777 }
3778 }
3779 else {
3780 unassigned.insert(viewID, 1 + unassigned.value(viewID));
3781 unassignedTotal++;
3782 }
3783
3784 connector = connector.nextSiblingElement("connector");
3785 }
3786 }
3787
3788 if (unassignedTotal > 0) {
3789 int viewCount = 0;
3790 foreach (ViewLayer::ViewID viewID, unassigned.keys()) {
3791 if (unassigned.value(viewID) > 0) viewCount++;
3792 }
3793 QMessageBox::warning(NULL, tr("Parts Editor"),
3794 tr("This part has %n unassigned connectors ", "", unassignedTotal) +
3795 tr("across %n views. ", "", viewCount) +
3796 tr("Until all connectors are assigned to SVG elements, the part will not work correctly. ") +
3797 tr("Exiting the Parts Editor now is fine, as long as you remember to finish the assignments later.")
3798 );
3799 }
3800
3801 }
3802
showing(SketchWidget * sketchWidget)3803 void PEMainWindow::showing(SketchWidget * sketchWidget) {
3804 ViewThing * viewThing = m_viewThings.value(sketchWidget->viewID());
3805 if (viewThing->firstTime) {
3806 viewThing->firstTime = false;
3807 QPointF offset = viewThing->sketchWidget->alignOneToGrid(viewThing->itemBase);
3808 if (offset.x() != 0 || offset.y() != 0) {
3809 QList<PEGraphicsItem *> pegiList = getPegiList(sketchWidget);
3810 foreach (PEGraphicsItem * pegi, pegiList) {
3811 pegi->setPos(pegi->pos() + offset);
3812 pegi->setOffset(pegi->offset() + offset);
3813 }
3814 }
3815 }
3816 }
3817
anyMarquee()3818 bool PEMainWindow::anyMarquee() {
3819 if (m_currentGraphicsView == NULL) return false;
3820
3821 QList<PEGraphicsItem *> pegiList = getPegiList(m_currentGraphicsView);
3822 foreach (PEGraphicsItem * pegi, pegiList) {
3823 if (pegi->showingMarquee()) {
3824 return true;
3825 }
3826 }
3827
3828 return false;
3829 }
3830
anyVisible()3831 bool PEMainWindow::anyVisible() {
3832 if (m_currentGraphicsView == NULL) return false;
3833
3834 foreach (QGraphicsItem * item, m_currentGraphicsView->scene()->items()) {
3835 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
3836 if (pegi == NULL) continue;
3837
3838 return pegi->isVisible();
3839 }
3840
3841 return false;
3842 }
3843
changeReferenceFile(ViewLayer::ViewID viewID,const QString referenceFile)3844 void PEMainWindow::changeReferenceFile(ViewLayer::ViewID viewID, const QString referenceFile)
3845 {
3846 ViewThing * viewThing = m_viewThings.value(viewID);
3847 if (viewThing == NULL) {
3848 // shouldn't happen
3849 DebugDialog::debug(QString("missing view thing for %1").arg(viewID));
3850 return;
3851 }
3852
3853 viewThing->referenceFile = referenceFile;
3854 }
3855
insertDesc(const QString & referenceFile,QString & svg)3856 void PEMainWindow::insertDesc(const QString & referenceFile, QString & svg) {
3857 if (svg.contains(ReferenceFileString)) return;
3858
3859 int ix = svg.indexOf("<svg");
3860 if (ix >= 0) {
3861 int jx = svg.indexOf(">", ix);
3862 if (jx > ix) {
3863 svg.insert(jx + 1, makeDesc(referenceFile));
3864 }
3865 }
3866 }
3867
itemAddedSlot(ModelPart *,ItemBase * itemBase,ViewLayer::ViewLayerPlacement,const ViewGeometry &,long id,SketchWidget *)3868 void PEMainWindow::itemAddedSlot(ModelPart *, ItemBase * itemBase, ViewLayer::ViewLayerPlacement, const ViewGeometry &, long id, SketchWidget *) {
3869 Q_UNUSED(id);
3870
3871 if (itemBase == NULL) return;
3872 if (itemBase->viewID() != m_currentGraphicsView->viewID()) return;
3873
3874 QDomElement element;
3875 double z = 0;
3876 foreach (PEGraphicsItem * pegi, getPegiList(m_currentGraphicsView)) {
3877 if (pegi->zValue() > z) z = pegi->zValue();
3878 }
3879
3880 QRectF bounds = itemBase->boundingRect();
3881 makePegi(bounds.size(), QPointF(0, 0), itemBase, element, z + 1);
3882 }
3883
itemMovedSlot(ItemBase * itemBase)3884 void PEMainWindow::itemMovedSlot(ItemBase * itemBase) {
3885 if (itemBase == NULL) return;
3886 if (itemBase->viewID() != m_currentGraphicsView->viewID()) return;
3887
3888 foreach (PEGraphicsItem * pegi, getPegiList(m_currentGraphicsView)) {
3889 if (pegi->itemBase() == itemBase) {
3890 pegi->setPos(itemBase->pos() + pegi->offset());
3891 }
3892 }
3893
3894 }
3895
resizedSlot(ItemBase * itemBase)3896 void PEMainWindow::resizedSlot(ItemBase * itemBase) {
3897 if (itemBase == NULL) return;
3898 if (itemBase->viewID() != m_currentGraphicsView->viewID()) return;
3899
3900 foreach (PEGraphicsItem * pegi, getPegiList(m_currentGraphicsView)) {
3901 if (pegi->itemBase() == itemBase) {
3902 pegi->setPos(itemBase->pos() + pegi->offset());
3903 QRectF bounds = itemBase->boundingRect();
3904 pegi->setRect(bounds);
3905 }
3906 }
3907 }
3908
clickedItemCandidateSlot(QGraphicsItem * item,bool & ok)3909 void PEMainWindow::clickedItemCandidateSlot(QGraphicsItem * item, bool & ok) {
3910 PEGraphicsItem * pegi = dynamic_cast<PEGraphicsItem *>(item);
3911 if (pegi == NULL) {
3912 ok = true;
3913 return;
3914 }
3915
3916 ok = pegi->showingMarquee();
3917 }
3918
initProgrammingWidget()3919 void PEMainWindow::initProgrammingWidget() {
3920 }
3921
initWelcomeView()3922 void PEMainWindow::initWelcomeView() {
3923 }
3924
setInitialView()3925 void PEMainWindow::setInitialView() {
3926 // do this the first time, since the current_changed signal wasn't sent
3927 int tab = 0;
3928 tabWidget_currentChanged(tab+1);
3929 tabWidget_currentChanged(tab);
3930 }
3931
updateExportMenu()3932 void PEMainWindow::updateExportMenu() {
3933 foreach (QAction * action, m_exportMenu->actions()) {
3934 action->setEnabled(false);
3935 }
3936 }
3937
convertToTenth()3938 void PEMainWindow::convertToTenth() {
3939 if (m_currentGraphicsView == NULL) return;
3940 if (m_currentGraphicsView->viewID() != ViewLayer::SchematicView) return;
3941
3942 QString originalFzpPath = saveFzp();
3943 QString newFzpPath = saveFzp();
3944
3945 ViewThing * viewThing = m_viewThings.value(m_currentGraphicsView->viewID());
3946 QString originalSvgPath = viewThing->itemBase->filename();
3947 QString newSvgPath = m_userPartsFolderSvgPath + makeSvgPath2(m_currentGraphicsView);
3948 QFile::copy(originalSvgPath, newSvgPath);
3949
3950 S2S s2s(false);
3951 connect(&s2s, SIGNAL(messageSignal(const QString &)), this, SLOT(s2sMessageSlot(const QString &)));
3952 bool result = s2s.onefzp(newFzpPath, newSvgPath);
3953
3954 if (!result) return; // if conversion fails
3955
3956 QUndoCommand * parentCommand = new QUndoCommand("Convert Schematic");
3957 new ChangeFzpCommand(this, originalFzpPath, newFzpPath, parentCommand);
3958 new ChangeSvgCommand(this, m_currentGraphicsView, originalSvgPath, newSvgPath, parentCommand);
3959 m_undoStack->waitPush(parentCommand, SketchWidget::PropChangeDelay);
3960 }
3961
s2sMessageSlot(const QString & message)3962 void PEMainWindow::s2sMessageSlot(const QString & message) {
3963 QMessageBox::information(this, "Schematic Conversion", message);
3964 }
3965
updateEditMenu()3966 void PEMainWindow::updateEditMenu() {
3967 MainWindow::updateEditMenu();
3968 m_convertToTenthAct->setEnabled(m_currentGraphicsView != NULL && m_currentGraphicsView->viewID() == ViewLayer::SchematicView);
3969 }
3970
3971