1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program 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  * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "schematiceditorstate_drawwire.h"
24 
25 #include "../../cmd/cmdchangenetsignalofschematicnetsegment.h"
26 #include "../../cmd/cmdcombineschematicnetsegments.h"
27 #include "../schematiceditor.h"
28 
29 #include <librepcb/common/graphics/graphicsview.h>
30 #include <librepcb/common/undostack.h>
31 #include <librepcb/project/circuit/circuit.h>
32 #include <librepcb/project/circuit/cmd/cmdcompsiginstsetnetsignal.h>
33 #include <librepcb/project/circuit/cmd/cmdnetclassadd.h>
34 #include <librepcb/project/circuit/cmd/cmdnetsignaladd.h>
35 #include <librepcb/project/circuit/cmd/cmdnetsignaledit.h>
36 #include <librepcb/project/circuit/componentsignalinstance.h>
37 #include <librepcb/project/circuit/netsignal.h>
38 #include <librepcb/project/project.h>
39 #include <librepcb/project/schematics/cmd/cmdschematicnetsegmentadd.h>
40 #include <librepcb/project/schematics/cmd/cmdschematicnetsegmentaddelements.h>
41 #include <librepcb/project/schematics/cmd/cmdschematicnetsegmentremoveelements.h>
42 #include <librepcb/project/schematics/items/si_netpoint.h>
43 #include <librepcb/project/schematics/items/si_netsegment.h>
44 #include <librepcb/project/schematics/items/si_symbolpin.h>
45 
46 #include <QtCore>
47 #include <QtWidgets>
48 
49 /*******************************************************************************
50  *  Namespace
51  ******************************************************************************/
52 namespace librepcb {
53 namespace project {
54 namespace editor {
55 
56 /*******************************************************************************
57  *  Constructors / Destructor
58  ******************************************************************************/
59 
SchematicEditorState_DrawWire(const Context & context)60 SchematicEditorState_DrawWire::SchematicEditorState_DrawWire(
61     const Context& context) noexcept
62   : SchematicEditorState(context),
63     mCircuit(context.project.getCircuit()),
64     mSubState(SubState::IDLE),
65     mWireMode(WireMode_HV),
66     mCursorPos(),
67     mFixedStartAnchor(nullptr),
68     mPositioningNetLine1(nullptr),
69     mPositioningNetPoint1(nullptr),
70     mPositioningNetLine2(nullptr),
71     mPositioningNetPoint2(nullptr) {
72 }
73 
~SchematicEditorState_DrawWire()74 SchematicEditorState_DrawWire::~SchematicEditorState_DrawWire() noexcept {
75   Q_ASSERT(mSubState == SubState::IDLE);
76 }
77 
78 /*******************************************************************************
79  *  General Methods
80  ******************************************************************************/
81 
entry()82 bool SchematicEditorState_DrawWire::entry() noexcept {
83   Q_ASSERT(mSubState == SubState::IDLE);
84 
85   // clear schematic selection because selection does not make sense in this
86   // state
87   if (Schematic* schematic = getActiveSchematic()) {
88     schematic->clearSelection();
89   }
90 
91   // Add wire mode actions to the "command" toolbar
92   mWireModeActions.insert(
93       WireMode_HV,
94       mContext.editorUi.commandToolbar->addAction(
95           QIcon(":/img/command_toolbars/wire_h_v.png"), ""));
96   mWireModeActions.insert(
97       WireMode_VH,
98       mContext.editorUi.commandToolbar->addAction(
99           QIcon(":/img/command_toolbars/wire_v_h.png"), ""));
100   mWireModeActions.insert(
101       WireMode_9045,
102       mContext.editorUi.commandToolbar->addAction(
103           QIcon(":/img/command_toolbars/wire_90_45.png"), ""));
104   mWireModeActions.insert(
105       WireMode_4590,
106       mContext.editorUi.commandToolbar->addAction(
107           QIcon(":/img/command_toolbars/wire_45_90.png"), ""));
108   mWireModeActions.insert(
109       WireMode_Straight,
110       mContext.editorUi.commandToolbar->addAction(
111           QIcon(":/img/command_toolbars/wire_straight.png"), ""));
112   mActionSeparators.append(mContext.editorUi.commandToolbar->addSeparator());
113   updateWireModeActionsCheckedState();
114 
115   // connect the wire mode actions with the slot
116   // updateWireModeActionsCheckedState()
117   foreach (WireMode mode, mWireModeActions.keys()) {
118     connect(mWireModeActions.value(mode), &QAction::triggered, [this, mode]() {
119       mWireMode = mode;
120       updateWireModeActionsCheckedState();
121     });
122   }
123 
124   // change the cursor
125   mContext.editorGraphicsView.setCursor(Qt::CrossCursor);
126 
127   return true;
128 }
129 
exit()130 bool SchematicEditorState_DrawWire::exit() noexcept {
131   // abort the currently active command
132   if (mSubState != SubState::IDLE) {
133     abortPositioning(true);
134   }
135 
136   // Remove actions / widgets from the "command" toolbar
137   qDeleteAll(mWireModeActions);
138   mWireModeActions.clear();
139   qDeleteAll(mActionSeparators);
140   mActionSeparators.clear();
141 
142   // change the cursor
143   mContext.editorGraphicsView.setCursor(Qt::ArrowCursor);
144 
145   return true;
146 }
147 
148 /*******************************************************************************
149  *  Event Handlers
150  ******************************************************************************/
151 
processAbortCommand()152 bool SchematicEditorState_DrawWire::processAbortCommand() noexcept {
153   if (mSubState == SubState::POSITIONING_NETPOINT) {
154     return abortPositioning(true);
155   }
156 
157   return false;
158 }
159 
processKeyPressed(const QKeyEvent & e)160 bool SchematicEditorState_DrawWire::processKeyPressed(
161     const QKeyEvent& e) noexcept {
162   Schematic* schematic = getActiveSchematic();
163   if (!schematic) return false;
164 
165   switch (e.key()) {
166     case Qt::Key_Shift: {
167       if (mSubState == SubState::POSITIONING_NETPOINT) {
168         updateNetpointPositions(*schematic, false);
169         return true;
170       }
171       break;
172     }
173 
174     default: { break; }
175   }
176 
177   return false;
178 }
179 
processKeyReleased(const QKeyEvent & e)180 bool SchematicEditorState_DrawWire::processKeyReleased(
181     const QKeyEvent& e) noexcept {
182   Schematic* schematic = getActiveSchematic();
183   if (!schematic) return false;
184 
185   switch (e.key()) {
186     case Qt::Key_Shift: {
187       if (mSubState == SubState::POSITIONING_NETPOINT) {
188         updateNetpointPositions(*schematic, true);
189         return true;
190       }
191       break;
192     }
193 
194     default: { break; }
195   }
196 
197   return false;
198 }
199 
processGraphicsSceneMouseMoved(QGraphicsSceneMouseEvent & e)200 bool SchematicEditorState_DrawWire::processGraphicsSceneMouseMoved(
201     QGraphicsSceneMouseEvent& e) noexcept {
202   Schematic* schematic = getActiveSchematic();
203   if (!schematic) return false;
204 
205   mCursorPos = Point::fromPx(e.scenePos());
206 
207   if (mSubState == SubState::POSITIONING_NETPOINT) {
208     bool snap = !e.modifiers().testFlag(Qt::ShiftModifier);
209     updateNetpointPositions(*schematic, snap);
210     return true;
211   }
212 
213   return false;
214 }
215 
processGraphicsSceneLeftMouseButtonPressed(QGraphicsSceneMouseEvent & e)216 bool SchematicEditorState_DrawWire::processGraphicsSceneLeftMouseButtonPressed(
217     QGraphicsSceneMouseEvent& e) noexcept {
218   Schematic* schematic = getActiveSchematic();
219   if (!schematic) return false;
220 
221   mCursorPos = Point::fromPx(e.scenePos());
222   bool snap = !e.modifiers().testFlag(Qt::ShiftModifier);
223 
224   if (mSubState == SubState::IDLE) {
225     // start adding netpoints/netlines
226     return startPositioning(*schematic, snap);
227   } else if (mSubState == SubState::POSITIONING_NETPOINT) {
228     // fix the current point and add a new point + line
229     return addNextNetPoint(*schematic, snap);
230   }
231 
232   return false;
233 }
234 
235 bool SchematicEditorState_DrawWire::
processGraphicsSceneLeftMouseButtonDoubleClicked(QGraphicsSceneMouseEvent & e)236     processGraphicsSceneLeftMouseButtonDoubleClicked(
237         QGraphicsSceneMouseEvent& e) noexcept {
238   Schematic* schematic = getActiveSchematic();
239   if (!schematic) return false;
240 
241   mCursorPos = Point::fromPx(e.scenePos());
242   bool snap = !e.modifiers().testFlag(Qt::ShiftModifier);
243 
244   if (mSubState == SubState::POSITIONING_NETPOINT) {
245     // fix the current point and add a new point + line
246     return addNextNetPoint(*schematic, snap);
247   }
248 
249   return false;
250 }
251 
252 bool SchematicEditorState_DrawWire::
processGraphicsSceneRightMouseButtonReleased(QGraphicsSceneMouseEvent & e)253     processGraphicsSceneRightMouseButtonReleased(
254         QGraphicsSceneMouseEvent& e) noexcept {
255   Schematic* schematic = getActiveSchematic();
256   if (!schematic) return false;
257 
258   mCursorPos = Point::fromPx(e.scenePos());
259 
260   if (mSubState == SubState::POSITIONING_NETPOINT) {
261     // Only switch to next wire mode if cursor was not moved during click
262     if (e.screenPos() == e.buttonDownScreenPos(Qt::RightButton)) {
263       mWireMode = static_cast<WireMode>(mWireMode + 1);
264       if (mWireMode == WireMode_COUNT) mWireMode = static_cast<WireMode>(0);
265       updateWireModeActionsCheckedState();
266       bool snap = !e.modifiers().testFlag(Qt::ShiftModifier);
267       updateNetpointPositions(*schematic, snap);
268     }
269 
270     // Always accept the event if we are drawing a wire! When ignoring the
271     // event, the state machine will abort the tool by a right click!
272     return true;
273   }
274 
275   return false;
276 }
277 
processSwitchToSchematicPage(int index)278 bool SchematicEditorState_DrawWire::processSwitchToSchematicPage(
279     int index) noexcept {
280   Q_UNUSED(index);
281   return mSubState == SubState::IDLE;
282 }
283 
284 /*******************************************************************************
285  *  Private Methods
286  ******************************************************************************/
287 
startPositioning(Schematic & schematic,bool snap,SI_NetPoint * fixedPoint)288 bool SchematicEditorState_DrawWire::startPositioning(
289     Schematic& schematic, bool snap, SI_NetPoint* fixedPoint) noexcept {
290   try {
291     // start a new undo command
292     Q_ASSERT(mSubState == SubState::IDLE);
293     mContext.undoStack.beginCmdGroup(tr("Draw Wire"));
294     mSubState = SubState::POSITIONING_NETPOINT;
295 
296     // determine the fixed anchor (create one if it doesn't exist already)
297     NetSignal* netsignal = nullptr;
298     SI_NetSegment* netsegment = nullptr;
299     tl::optional<CircuitIdentifier> forcedNetName;
300     Point pos = mCursorPos.mappedToGrid(getGridInterval());
301     if (snap || fixedPoint) {
302       if (fixedPoint) {
303         mFixedStartAnchor = fixedPoint;
304         netsegment = &fixedPoint->getNetSegment();
305         pos = fixedPoint->getPosition();
306       } else if (SI_NetPoint* netpoint = findNetPoint(schematic, mCursorPos)) {
307         mFixedStartAnchor = netpoint;
308         netsegment = &netpoint->getNetSegment();
309         pos = netpoint->getPosition();
310       } else if (SI_SymbolPin* pin = findSymbolPin(schematic, mCursorPos)) {
311         mFixedStartAnchor = pin;
312         netsegment = pin->getNetSegmentOfLines();
313         netsignal = pin->getCompSigInstNetSignal();
314         pos = pin->getPosition();
315         if (pin->getComponentSignalInstance()) {
316           QString name =
317               pin->getComponentSignalInstance()->getForcedNetSignalName();
318           try {
319             if (!name.isEmpty())
320               forcedNetName = CircuitIdentifier(name);  // can throw
321           } catch (const Exception& e) {
322             QMessageBox::warning(
323                 parentWidget(), tr("Invalid net name"),
324                 tr("Could not apply the forced net name because '%1' is "
325                    "not a valid net name.")
326                     .arg(name));
327           }
328         }
329       } else if (SI_NetLine* netline = findNetLine(schematic, mCursorPos)) {
330         // split netline
331         netsegment = &netline->getNetSegment();
332         QScopedPointer<CmdSchematicNetSegmentAddElements> cmdAdd(
333             new CmdSchematicNetSegmentAddElements(*netsegment));
334         mFixedStartAnchor = cmdAdd->addNetPoint(pos);
335         cmdAdd->addNetLine(*mFixedStartAnchor, netline->getStartPoint());
336         cmdAdd->addNetLine(*mFixedStartAnchor, netline->getEndPoint());
337         mContext.undoStack.appendToCmdGroup(cmdAdd.take());  // can throw
338         QScopedPointer<CmdSchematicNetSegmentRemoveElements> cmdRemove(
339             new CmdSchematicNetSegmentRemoveElements(*netsegment));
340         cmdRemove->removeNetLine(*netline);
341         mContext.undoStack.appendToCmdGroup(cmdRemove.take());  // can throw
342       }
343     }
344 
345     // find netsignal if name is given
346     if (forcedNetName) {
347       netsignal = mCircuit.getNetSignalByName(**forcedNetName);
348     }
349 
350     // create new netsignal if none found
351     if ((!netsegment) && (!netsignal)) {
352       // get or add netclass with the name "default"
353       NetClass* netclass = mCircuit.getNetClassByName(ElementName("default"));
354       if (!netclass) {
355         CmdNetClassAdd* cmd =
356             new CmdNetClassAdd(mCircuit, ElementName("default"));
357         mContext.undoStack.appendToCmdGroup(cmd);  // can throw
358         netclass = cmd->getNetClass();
359         Q_ASSERT(netclass);
360       }
361       // add new netsignal
362       CmdNetSignalAdd* cmd =
363           new CmdNetSignalAdd(mCircuit, *netclass, forcedNetName);
364       mContext.undoStack.appendToCmdGroup(cmd);  // can throw
365       netsignal = cmd->getNetSignal();
366       Q_ASSERT(netsignal);
367     }
368 
369     // create new netsegment if none found
370     if (!netsegment) {
371       // connect pin if needed
372       if (SI_SymbolPin* pin = dynamic_cast<SI_SymbolPin*>(mFixedStartAnchor)) {
373         Q_ASSERT(pin->getComponentSignalInstance());
374         mContext.undoStack.appendToCmdGroup(new CmdCompSigInstSetNetSignal(
375             *pin->getComponentSignalInstance(), netsignal));
376       }
377       // add net segment
378       Q_ASSERT(netsignal);
379       CmdSchematicNetSegmentAdd* cmd =
380           new CmdSchematicNetSegmentAdd(schematic, *netsignal);
381       mContext.undoStack.appendToCmdGroup(cmd);  // can throw
382       netsegment = cmd->getNetSegment();
383     }
384 
385     // add netpoint if none found
386     Q_ASSERT(netsegment);
387     CmdSchematicNetSegmentAddElements* cmd =
388         new CmdSchematicNetSegmentAddElements(*netsegment);
389     if (!mFixedStartAnchor) {
390       mFixedStartAnchor = cmd->addNetPoint(pos);
391     }
392     Q_ASSERT(mFixedStartAnchor);
393 
394     // add more netpoints & netlines
395     SI_NetPoint* p2 = cmd->addNetPoint(pos);
396     Q_ASSERT(p2);  // second netpoint
397     SI_NetLine* l1 = cmd->addNetLine(*mFixedStartAnchor, *p2);
398     Q_ASSERT(l1);  // first netline
399     SI_NetPoint* p3 = cmd->addNetPoint(pos);
400     Q_ASSERT(p3);  // third netpoint
401     SI_NetLine* l2 = cmd->addNetLine(*p2, *p3);
402     Q_ASSERT(l2);  // second netline
403     mContext.undoStack.appendToCmdGroup(cmd);  // can throw
404 
405     // update members
406     mPositioningNetPoint1 = p2;
407     mPositioningNetLine1 = l1;
408     mPositioningNetPoint2 = p3;
409     mPositioningNetLine2 = l2;
410 
411     // properly place the new netpoints/netlines according the current wire mode
412     updateNetpointPositions(schematic, snap);
413 
414     // highlight all elements of the current netsignal
415     mCircuit.setHighlightedNetSignal(&netsegment->getNetSignal());
416 
417     return true;
418   } catch (const Exception& e) {
419     QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
420     if (mSubState != SubState::IDLE) {
421       abortPositioning(false);
422     }
423     return false;
424   }
425 }
426 
addNextNetPoint(Schematic & schematic,bool snap)427 bool SchematicEditorState_DrawWire::addNextNetPoint(Schematic& schematic,
428                                                     bool snap) noexcept {
429   Q_ASSERT(mSubState == SubState::POSITIONING_NETPOINT);
430 
431   // Snap to the item under the cursor and make sure the lines are up to date.
432   Point pos = updateNetpointPositions(schematic, snap);
433 
434   // abort if p2 == p0 (no line drawn)
435   if (pos == mFixedStartAnchor->getPosition()) {
436     abortPositioning(true);
437     return false;
438   } else {
439     bool finishCommand = false;
440 
441     try {
442       // create a new undo command group to make all changes atomic
443       QScopedPointer<UndoCommandGroup> cmdGroup(
444           new UndoCommandGroup("Add schematic netline"));
445 
446       // remove p1 if p1 == p0 || p1 == p2
447       if ((mPositioningNetPoint1->getPosition() ==
448            mFixedStartAnchor->getPosition()) ||
449           (mPositioningNetPoint1->getPosition() ==
450            mPositioningNetPoint2->getPosition())) {
451         QScopedPointer<CmdSchematicNetSegmentRemoveElements> cmdRemove(
452             new CmdSchematicNetSegmentRemoveElements(
453                 mPositioningNetPoint1->getNetSegment()));
454         cmdRemove->removeNetPoint(*mPositioningNetPoint1);
455         cmdRemove->removeNetLine(*mPositioningNetLine1);
456         cmdRemove->removeNetLine(*mPositioningNetLine2);
457         QScopedPointer<CmdSchematicNetSegmentAddElements> cmdAdd(
458             new CmdSchematicNetSegmentAddElements(
459                 mPositioningNetPoint1->getNetSegment()));
460         mPositioningNetLine2 =
461             cmdAdd->addNetLine(*mFixedStartAnchor, *mPositioningNetPoint2);
462         mContext.undoStack.appendToCmdGroup(cmdAdd.take());
463         mContext.undoStack.appendToCmdGroup(cmdRemove.take());
464       }
465 
466       // find anchor under cursor
467       SI_NetLineAnchor* otherAnchor = nullptr;
468       SI_NetSegment* otherNetSegment = nullptr;
469       QString otherForcedNetName;
470       if (snap) {
471         if (SI_NetPoint* netpoint =
472                 findNetPoint(schematic, pos, mPositioningNetPoint2)) {
473           otherAnchor = netpoint;
474           otherNetSegment = &netpoint->getNetSegment();
475         } else if (SI_SymbolPin* pin = findSymbolPin(schematic, pos)) {
476           otherAnchor = pin;
477           otherNetSegment = pin->getNetSegmentOfLines();
478           // connect pin if needed
479           if (!otherNetSegment) {
480             Q_ASSERT(pin->getComponentSignalInstance());
481             mContext.undoStack.appendToCmdGroup(new CmdCompSigInstSetNetSignal(
482                 *pin->getComponentSignalInstance(),
483                 &mPositioningNetPoint2->getNetSignalOfNetSegment()));
484             otherForcedNetName =
485                 pin->getComponentSignalInstance()->getForcedNetSignalName();
486           }
487         } else if (SI_NetLine* netline =
488                        findNetLine(schematic, pos, mPositioningNetLine2)) {
489           // split netline
490           otherNetSegment = &netline->getNetSegment();
491           QScopedPointer<CmdSchematicNetSegmentAddElements> cmdAdd(
492               new CmdSchematicNetSegmentAddElements(*otherNetSegment));
493           otherAnchor = cmdAdd->addNetPoint(pos);
494           cmdAdd->addNetLine(*otherAnchor, netline->getStartPoint());
495           cmdAdd->addNetLine(*otherAnchor, netline->getEndPoint());
496           mContext.undoStack.appendToCmdGroup(cmdAdd.take());  // can throw
497           QScopedPointer<CmdSchematicNetSegmentRemoveElements> cmdRemove(
498               new CmdSchematicNetSegmentRemoveElements(*otherNetSegment));
499           cmdRemove->removeNetLine(*netline);
500           mContext.undoStack.appendToCmdGroup(cmdRemove.take());  // can throw
501         }
502       }
503 
504       // if anchor found under the cursor, replace "mPositioningNetPoint2" with
505       // it
506       if (otherAnchor) {
507         if ((!otherNetSegment) ||
508             (otherNetSegment == &mPositioningNetPoint2->getNetSegment())) {
509           QScopedPointer<CmdSchematicNetSegmentAddElements> cmdAdd(
510               new CmdSchematicNetSegmentAddElements(
511                   mPositioningNetPoint2->getNetSegment()));
512           cmdAdd->addNetLine(*otherAnchor,
513                              mPositioningNetLine2->getStartPoint());
514           mContext.undoStack.appendToCmdGroup(cmdAdd.take());  // can throw
515           QScopedPointer<CmdSchematicNetSegmentRemoveElements> cmdRemove(
516               new CmdSchematicNetSegmentRemoveElements(
517                   mPositioningNetPoint2->getNetSegment()));
518           cmdRemove->removeNetPoint(*mPositioningNetPoint2);
519           cmdRemove->removeNetLine(*mPositioningNetLine2);
520           mContext.undoStack.appendToCmdGroup(cmdRemove.take());  // can throw
521         } else {
522           // change net signal if needed
523           NetSignal* thisSignal =
524               &mPositioningNetPoint2->getNetSignalOfNetSegment();
525           NetSignal* otherSignal = &otherNetSegment->getNetSignal();
526           if (thisSignal != otherSignal) {
527             NetSignal* resultingNetSignal = nullptr;
528             SI_NetSegment* netSegmentToChangeSignal = nullptr;
529             if (otherNetSegment->getForcedNetNames().count() > 0) {
530               resultingNetSignal = &otherNetSegment->getNetSignal();
531               netSegmentToChangeSignal =
532                   &mPositioningNetPoint2->getNetSegment();
533             } else if (mPositioningNetPoint2->getNetSegment()
534                            .getForcedNetNames()
535                            .count() > 0) {
536               resultingNetSignal =
537                   &mPositioningNetPoint2->getNetSignalOfNetSegment();
538               netSegmentToChangeSignal = otherNetSegment;
539             } else if (otherSignal->hasAutoName() &&
540                        (!thisSignal->hasAutoName())) {
541               resultingNetSignal =
542                   &mPositioningNetPoint2->getNetSignalOfNetSegment();
543               netSegmentToChangeSignal = otherNetSegment;
544             } else {
545               resultingNetSignal = &otherNetSegment->getNetSignal();
546               netSegmentToChangeSignal =
547                   &mPositioningNetPoint2->getNetSegment();
548             }
549             mContext.undoStack.appendToCmdGroup(
550                 new CmdChangeNetSignalOfSchematicNetSegment(
551                     *netSegmentToChangeSignal, *resultingNetSignal));
552           }
553           // combine both net segments
554           mContext.undoStack.appendToCmdGroup(
555               new CmdCombineSchematicNetSegments(
556                   mPositioningNetPoint2->getNetSegment(),
557                   *mPositioningNetPoint2, *otherNetSegment, *otherAnchor));
558         }
559         if (!otherForcedNetName.isEmpty()) {
560           // change net name if connected to a pin with forced net name
561           try {
562             CircuitIdentifier name =
563                 CircuitIdentifier(otherForcedNetName);  // can throw
564             NetSignal* signal =
565                 schematic.getProject().getCircuit().getNetSignalByName(*name);
566             if (signal) {
567               mContext.undoStack.appendToCmdGroup(
568                   new CmdChangeNetSignalOfSchematicNetSegment(
569                       mPositioningNetPoint2->getNetSegment(), *signal));
570             } else {
571               QScopedPointer<CmdNetSignalEdit> cmd(new CmdNetSignalEdit(
572                   schematic.getProject().getCircuit(),
573                   mPositioningNetPoint2->getNetSignalOfNetSegment()));
574               cmd->setName(name, false);
575               mContext.undoStack.appendToCmdGroup(cmd.take());
576             }
577           } catch (const Exception& e) {
578             QMessageBox::warning(
579                 parentWidget(), tr("Invalid net name"),
580                 QString(
581                     tr("Could not apply the forced net name because '%1' is "
582                        "not a valid net name."))
583                     .arg(otherForcedNetName));
584           }
585         }
586         finishCommand = true;
587       } else {
588         finishCommand = false;
589       }
590     } catch (const UserCanceled& e) {
591       return false;
592     } catch (const Exception& e) {
593       QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
594       return false;
595     }
596 
597     try {
598       // finish the current command
599       mContext.undoStack.commitCmdGroup();
600       mSubState = SubState::IDLE;
601 
602       // abort or start a new command
603       if (finishCommand) {
604         mContext.undoStack.beginCmdGroup(QString());  // this is ugly!
605         abortPositioning(true);
606         return false;
607       } else {
608         return startPositioning(schematic, snap, mPositioningNetPoint2);
609       }
610     } catch (const Exception& e) {
611       QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
612       if (mSubState != SubState::IDLE) {
613         abortPositioning(false);
614       }
615       return false;
616     }
617   }
618 }
619 
abortPositioning(bool showErrMsgBox)620 bool SchematicEditorState_DrawWire::abortPositioning(
621     bool showErrMsgBox) noexcept {
622   try {
623     mCircuit.setHighlightedNetSignal(nullptr);
624     mSubState = SubState::IDLE;
625     mFixedStartAnchor = nullptr;
626     mPositioningNetLine1 = nullptr;
627     mPositioningNetLine2 = nullptr;
628     mPositioningNetPoint1 = nullptr;
629     mPositioningNetPoint2 = nullptr;
630     mContext.undoStack.abortCmdGroup();  // can throw
631     return true;
632   } catch (const Exception& e) {
633     if (showErrMsgBox)
634       QMessageBox::critical(parentWidget(), tr("Error"), e.getMsg());
635     return false;
636   }
637 }
638 
findSymbolPin(Schematic & schematic,const Point & pos) const639 SI_SymbolPin* SchematicEditorState_DrawWire::findSymbolPin(
640     Schematic& schematic, const Point& pos) const noexcept {
641   QList<SI_SymbolPin*> items = schematic.getPinsAtScenePos(pos);
642   for (int i = items.count() - 1; i >= 0; --i) {
643     // only choose pins which are connected to a component signal!
644     if (!items.at(i)->getComponentSignalInstance()) {
645       items.removeAt(i);
646     }
647   }
648   return (items.count() > 0) ? items.first() : nullptr;
649 }
650 
findNetPoint(Schematic & schematic,const Point & pos,SI_NetPoint * except) const651 SI_NetPoint* SchematicEditorState_DrawWire::findNetPoint(
652     Schematic& schematic, const Point& pos, SI_NetPoint* except) const
653     noexcept {
654   QList<SI_NetPoint*> items = schematic.getNetPointsAtScenePos(pos);
655   items.removeAll(except);
656   return (items.count() > 0) ? items.first() : nullptr;
657 }
658 
findNetLine(Schematic & schematic,const Point & pos,SI_NetLine * except) const659 SI_NetLine* SchematicEditorState_DrawWire::findNetLine(Schematic& schematic,
660                                                        const Point& pos,
661                                                        SI_NetLine* except) const
662     noexcept {
663   QList<SI_NetLine*> items = schematic.getNetLinesAtScenePos(pos);
664   items.removeAll(except);
665   return (items.count() > 0) ? items.first() : nullptr;
666 }
667 
updateNetpointPositions(Schematic & schematic,bool snap)668 Point SchematicEditorState_DrawWire::updateNetpointPositions(
669     Schematic& schematic, bool snap) noexcept {
670   // Find anchor under cursor.
671   Point pos = mCursorPos.mappedToGrid(getGridInterval());
672   if (snap) {
673     if (SI_NetPoint* np = findNetPoint(schematic, mCursorPos)) {
674       pos = np->getPosition();
675     } else if (SI_SymbolPin* pin = findSymbolPin(schematic, mCursorPos)) {
676       pos = pin->getPosition();
677     }
678   }
679 
680   mPositioningNetPoint1->setPosition(
681       calcMiddlePointPos(mFixedStartAnchor->getPosition(), pos, mWireMode));
682   mPositioningNetPoint2->setPosition(pos);
683   return pos;
684 }
685 
686 void SchematicEditorState_DrawWire::
updateWireModeActionsCheckedState()687     updateWireModeActionsCheckedState() noexcept {
688   foreach (WireMode key, mWireModeActions.keys()) {
689     mWireModeActions.value(key)->setCheckable(key == mWireMode);
690     mWireModeActions.value(key)->setChecked(key == mWireMode);
691   }
692 }
693 
calcMiddlePointPos(const Point & p1,const Point p2,WireMode mode) const694 Point SchematicEditorState_DrawWire::calcMiddlePointPos(const Point& p1,
695                                                         const Point p2,
696                                                         WireMode mode) const
697     noexcept {
698   Point delta = p2 - p1;
699   switch (mode) {
700     case WireMode_HV:
701       return Point(p2.getX(), p1.getY());
702     case WireMode_VH:
703       return Point(p1.getX(), p2.getY());
704     case WireMode_9045:
705       if (delta.getX().abs() >= delta.getY().abs())
706         return Point(
707             p2.getX() - delta.getY().abs() * (delta.getX() >= 0 ? 1 : -1),
708             p1.getY());
709       else
710         return Point(
711             p1.getX(),
712             p2.getY() - delta.getX().abs() * (delta.getY() >= 0 ? 1 : -1));
713     case WireMode_4590:
714       if (delta.getX().abs() >= delta.getY().abs())
715         return Point(
716             p1.getX() + delta.getY().abs() * (delta.getX() >= 0 ? 1 : -1),
717             p2.getY());
718       else
719         return Point(
720             p2.getX(),
721             p1.getY() + delta.getX().abs() * (delta.getY() >= 0 ? 1 : -1));
722     case WireMode_Straight:
723       return p1;
724     default:
725       Q_ASSERT(false);
726       return Point();
727   }
728 }
729 
730 /*******************************************************************************
731  *  End of File
732  ******************************************************************************/
733 
734 }  // namespace editor
735 }  // namespace project
736 }  // namespace librepcb
737