KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sch_line_wire_bus_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <tool/tool_manager.h>
23
24#include <wx/debug.h>
25#include <wx/gdicmn.h>
26#include <wx/string.h>
27#include <wx/translation.h>
28#include <algorithm>
29#include <cstdlib>
30#include <iterator>
31#include <memory>
32#include <utility>
33#include <vector>
34
35#include <layer_ids.h>
36#include <math/vector2d.h>
37#include <advanced_config.h>
41#include <view/view_controls.h>
42#include <tool/actions.h>
44#include <tool/selection.h>
46#include <tool/tool_event.h>
47#include <trigo.h>
48#include <eeschema_id.h>
49#include <sch_bus_entry.h>
50#include <sch_connection.h>
51#include <sch_edit_frame.h>
52#include <widgets/wx_infobar.h>
53#include <sch_junction.h>
54#include <sch_line.h>
55#include <sch_screen.h>
56#include <sch_sheet.h>
57#include <sch_group.h>
58#include <sch_sheet_pin.h>
59#include <schematic.h>
60#include <sch_commit.h>
61#include <sch_actions.h>
62#include <junction_helpers.h>
63#include <ee_grid_helper.h>
64#include <sch_selection.h>
65#include <sch_selection_tool.h>
66
67
68using BUS_GETTER = std::function<SCH_LINE*()>;
69
71{
72public:
78 ACTION_MENU( true ),
79 m_showTitle( false ),
80 m_busGetter( aBusGetter )
81 {
83 SetTitle( _( "Unfold from Bus" ) );
84 }
85
87 {
88 m_showTitle = true;
89 }
90
91 bool PassHelpTextToHandler() override { return true; }
92
93protected:
94 ACTION_MENU* create() const override
95 {
96 return new BUS_UNFOLD_MENU( m_busGetter );
97 }
98
99private:
100 struct MEMBER
101 {
102 wxString name;
103 bool bus = false;
104 std::vector<wxString> members;
105 };
106
107 static std::vector<MEMBER> memberEntries( const SCH_LINE& aBus )
108 {
110 std::vector<MEMBER> result;
111
112 if( ADVANCED_CFG::GetCfg().m_ConnectivityEngine )
113 {
114 SCHEMATIC* schematic = aBus.Schematic();
115
116 if( !schematic )
117 return result;
118
119 const auto connection = schematic->Connectivity().Connection( aBus.m_Uuid,
120 schematic->CurrentSheet().PathRef() );
121
122 if( !connection || !connection->IsBus() )
123 return result;
124
125 const SCH_CONNECTIVITY::BUS_MEMBERS members = connection->Members();
126
127 if( !members.tree || !members.schema )
128 return result;
129
130 const auto leafName = [&]( const BUS_SCHEMA::NODE& aNode )
131 {
132 const auto& leaves = members.schema->leaves;
133 return aNode.leaf && *aNode.leaf < leaves.size() ? leaves[*aNode.leaf].name : wxString();
134 };
135
136 // Leaf names carry their full group prefix; nested buses take the root group's prefix
137 const bool namedGroup = members.tree->kind == BUS_SCHEMA::NODE::KIND::GROUP
138 && !members.tree->prefix.IsEmpty();
139 const wxString prefix = namedGroup ? members.tree->prefix + wxS( "." ) : wxString();
140
141 for( const BUS_SCHEMA::NODE& node : members.tree->members )
142 {
143 MEMBER& entry = result.emplace_back();
144 entry.bus = node.kind != BUS_SCHEMA::NODE::KIND::NET;
145 entry.name = entry.bus ? prefix + node.text : leafName( node );
146
147 for( const BUS_SCHEMA::NODE& sub : node.members )
148 {
149 if( wxString name = leafName( sub ); !name.IsEmpty() )
150 entry.members.push_back( std::move( name ) );
151 }
152 }
153
154 return result;
155 }
156
157 const SCH_CONNECTION* connection = aBus.Connection();
158
159 if( !connection || !connection->IsBus() )
160 return result;
161
162 for( const std::shared_ptr<SCH_CONNECTION>& member : connection->Members() )
163 {
164 MEMBER& entry = result.emplace_back();
165 entry.name = member->FullLocalName();
166 entry.bus = member->Type() == CONNECTION_TYPE::BUS;
167
168 for( const std::shared_ptr<SCH_CONNECTION>& sub : member->Members() )
169 entry.members.push_back( sub->FullLocalName() );
170 }
171
172 return result;
173 }
174
175 void update() override
176 {
177 SCH_LINE* bus = m_busGetter();
178 Clear();
179 // Pick up the pointer again because it may have been changed by SchematicCleanUp
180 bus = m_busGetter();
181
182 if( !bus )
183 {
184 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "No bus selected" ), wxEmptyString );
185 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
186 return;
187 }
188
189 const std::vector<MEMBER> members = memberEntries( *bus );
190
191 if( members.empty() )
192 {
193 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Bus has no members" ), wxEmptyString );
194 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
195 return;
196 }
197
198 int idx = 0;
199
200 if( m_showTitle )
201 {
202 Append( ID_POPUP_SCH_UNFOLD_BUS, _( "Unfold from Bus" ), wxEmptyString );
203 Enable( ID_POPUP_SCH_UNFOLD_BUS, false );
204 }
205
206 std::unordered_map<wxString, ACTION_MENU*> diff_busses{};
207
208 for( const MEMBER& member : members )
209 {
210 int id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
211 wxString name = member.name;
212
213 if( member.bus )
214 {
215 ACTION_MENU* submenu = nullptr;
216 // If we are building the menu for suffixed bus vectors, we need to do some more massaging
217 if( ( name.ends_with( '+' ) || name.ends_with( '-' ) || name.ends_with( 'P' ) || name.ends_with( 'N' ) )
218 && !member.members.empty() )
219 {
220 wxString submenu_name = name.substr( 0, name.length() - 1 );
221 auto bus_submenu = diff_busses.find( submenu_name );
222
223 if( bus_submenu == diff_busses.end() )
224 {
225 submenu = new ACTION_MENU( true, m_tool );
226 diff_busses.emplace( submenu_name, submenu );
227 AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( submenu_name ), submenu_name );
228 }
229 else
230 {
231 submenu = bus_submenu->second;
232 }
233 }
234 else
235 {
236 // Otherwise we can set the submenu up like normal
237 submenu = new ACTION_MENU( true, m_tool );
238 AppendSubMenu( submenu, SCH_CONNECTION::PrintBusForUI( name ), name );
239 }
240
241 for( const wxString& subMemberName : member.members )
242 {
243 id = ID_POPUP_SCH_UNFOLD_BUS + ( idx++ );
244 name = subMemberName;
245
246 if( ( name.ends_with( '+' ) || name.ends_with( '-' ) || name.ends_with( 'P' )
247 || name.ends_with( 'N' ) ) )
248 {
249 wxString submenu_name = name.substr( 0, name.length() - 1 );
250 auto bus_submenu = diff_busses.find( submenu_name );
251
252 ACTION_MENU* diff_submenu = nullptr;
253 if( bus_submenu == diff_busses.end() )
254 {
255 diff_submenu = new ACTION_MENU( true, m_tool );
256 diff_busses.emplace( submenu_name, diff_submenu );
257 submenu->AppendSubMenu( diff_submenu, SCH_CONNECTION::PrintBusForUI( submenu_name ),
258 submenu_name );
259 }
260 else
261 {
262 diff_submenu = bus_submenu->second;
263 }
264 diff_submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
265 }
266 else
267 {
268 submenu->Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
269 }
270 }
271 }
272 else
273 {
274 Append( id, SCH_CONNECTION::PrintBusForUI( name ), name );
275 }
276 }
277 }
278
281};
282
283
291
292
296
297
299 SCH_TOOL_BASE<SCH_EDIT_FRAME>( "eeschema.InteractiveDrawingLineWireBus" ),
300 m_inDrawingTool( false )
301{
302 m_busUnfold = {};
303 m_wires.reserve( 16 );
304}
305
306
310
311
313{
315
316 const auto busGetter =
317 [this]()
318 {
319 return getBusForUnfolding();
320 };
321
322 std::shared_ptr<BUS_UNFOLD_MENU>
323 busUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
324 busUnfoldMenu->SetTool( this );
325 m_menu->RegisterSubMenu( busUnfoldMenu );
326
327 std::shared_ptr<BUS_UNFOLD_MENU> selBusUnfoldMenu = std::make_shared<BUS_UNFOLD_MENU>( busGetter );
328 selBusUnfoldMenu->SetTool( m_selectionTool );
329 m_selectionTool->GetToolMenu().RegisterSubMenu( selBusUnfoldMenu );
330
331 auto wireOrBusTool =
332 [this]( const SELECTION& aSel )
333 {
334 return ( m_frame->IsCurrentTool( SCH_ACTIONS::drawWire )
335 || m_frame->IsCurrentTool( SCH_ACTIONS::drawBus ) );
336 };
337
338 auto lineTool =
339 [this]( const SELECTION& aSel )
340 {
341 return m_frame->IsCurrentTool( SCH_ACTIONS::drawLines );
342 };
343
344 auto belowRootSheetCondition =
345 [this]( const SELECTION& aSel )
346 {
347 return m_frame->GetCurrentSheet().Last() != &m_frame->Schematic().Root();
348 };
349
350 auto busSelection = SCH_CONDITIONS::MoreThan( 0 )
352
353 auto haveHighlight =
354 [this]( const SELECTION& sel )
355 {
356 SCH_EDIT_FRAME* editFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame );
357
358 return editFrame && !editFrame->GetHighlightedConnection().IsEmpty();
359 };
360
361 auto& ctxMenu = m_menu->GetMenu();
362
363 // Build the tool menu
364 //
365 ctxMenu.AddItem( SCH_ACTIONS::clearHighlight, haveHighlight && SCH_CONDITIONS::Idle, 1 );
366 ctxMenu.AddSeparator( haveHighlight && SCH_CONDITIONS::Idle, 1 );
367
368 ctxMenu.AddSeparator( 10 );
369 ctxMenu.AddItem( SCH_ACTIONS::drawWire, wireOrBusTool && SCH_CONDITIONS::Idle, 10 );
370 ctxMenu.AddItem( SCH_ACTIONS::drawBus, wireOrBusTool && SCH_CONDITIONS::Idle, 10 );
371 ctxMenu.AddItem( SCH_ACTIONS::drawLines, lineTool && SCH_CONDITIONS::Idle, 10 );
372
376
377 ctxMenu.AddMenu( busUnfoldMenu.get(), SCH_CONDITIONS::Idle, 10 );
378
379 ctxMenu.AddSeparator( 100 );
380 ctxMenu.AddItem( SCH_ACTIONS::placeJunction, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
381 ctxMenu.AddItem( SCH_ACTIONS::placeLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
382 ctxMenu.AddItem( SCH_ACTIONS::placeClassLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
383 ctxMenu.AddItem( SCH_ACTIONS::placeGlobalLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
384 ctxMenu.AddItem( SCH_ACTIONS::placeHierLabel, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
385 ctxMenu.AddItem( SCH_ACTIONS::breakWire, wireOrBusTool && SCH_CONDITIONS::Idle, 100 );
386 ctxMenu.AddItem( SCH_ACTIONS::slice, ( wireOrBusTool || lineTool )
387 && SCH_CONDITIONS::Idle, 100 );
388 ctxMenu.AddItem( SCH_ACTIONS::leaveSheet, belowRootSheetCondition, 150 );
389
390 ctxMenu.AddSeparator( 200 );
391 ctxMenu.AddItem( SCH_ACTIONS::selectNode, wireOrBusTool && SCH_CONDITIONS::Idle, 200 );
392 ctxMenu.AddItem( SCH_ACTIONS::selectConnection, wireOrBusTool && SCH_CONDITIONS::Idle, 200 );
393
394 // Add bus unfolding to the selection tool
395 //
396 CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
397
398 selToolMenu.AddMenu( selBusUnfoldMenu.get(), busSelection && SCH_CONDITIONS::Idle, 100 );
399
400 return true;
401}
402
403
405{
406 // NOTE: for immediate hotkeys, it is NOT required that the line, wire or bus tool
407 // be selected
408 SCH_ITEM* item = (SCH_ITEM*) aSelection.Front();
409 return item && item->IsNew() && item->Type() == SCH_LINE_T;
410}
411
412
414{
415 if( m_inDrawingTool )
416 return 0;
417
419
420 const DRAW_SEGMENT_EVENT_PARAMS* params = aEvent.Parameter<const DRAW_SEGMENT_EVENT_PARAMS*>();
421 SCH_COMMIT commit( m_toolMgr );
422
423 TOOL_EVENT originalEvent = aEvent; // This can change out from under us when the event loop runs
424 SCOPED_TOOL_PUSHER raii( m_frame, aEvent );
425
427
428 if( aEvent.HasPosition() )
429 {
431 GRID_HELPER_GRIDS gridType = ( params->layer == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
432
433 grid.SetSnap( !aEvent.Modifier( MD_SHIFT ) );
434 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !aEvent.DisableGridSnapping() );
435
436 VECTOR2D cursorPos = grid.ResolveSnap( aEvent.Position(), gridType, nullptr ).position;
437 startSegments( commit, params->layer, cursorPos, params->sourceSegment );
438 }
439
440 return doDrawSegments( originalEvent, commit, params->layer, params->quitOnDraw );
441}
442
443
445{
446 if( m_inDrawingTool )
447 return 0;
448
450
451 SCH_COMMIT commit( m_toolMgr );
452 wxString* netPtr = aEvent.Parameter<wxString*>();
453 wxString net;
454 SCH_LINE* segment = nullptr;
455
456 TOOL_EVENT originalEvent = aEvent; // This can change out from under us when the event loop runs
457 SCOPED_TOOL_PUSHER raii( m_frame, originalEvent );
458 Activate();
459
460 if( netPtr )
461 {
462 net = *netPtr;
463 delete netPtr;
464 }
465 else
466 {
467 const auto busGetter =
468 [this]()
469 {
470 return getBusForUnfolding();
471 };
472
473 BUS_UNFOLD_MENU unfoldMenu( busGetter );
474 unfoldMenu.SetTool( this );
475 unfoldMenu.SetShowTitle();
476
477 SetContextMenu( &unfoldMenu, CMENU_NOW );
478
479 while( TOOL_EVENT* evt = Wait() )
480 {
481 if( evt->Action() == TA_CHOICE_MENU_CHOICE )
482 {
483 std::optional<int> id = evt->GetCommandId();
484
485 if( id && ( *id > 0 ) )
486 net = *evt->Parameter<wxString*>();
487
488 break;
489 }
490 else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
491 {
492 break;
493 }
494 else
495 {
496 evt->SetPassEvent();
497 }
498 }
499 }
500
501 // Break a wire for the given net out of the bus
502 if( !net.IsEmpty() )
503 segment = doUnfoldBus( commit, net );
504
505 // If we have an unfolded wire to draw, then draw it
506 if( segment )
507 return doDrawSegments( originalEvent, commit, LAYER_WIRE, false );
508
509 return 0;
510}
511
512
514{
515 SCH_SELECTION& selection = m_selectionTool->RequestSelection( { SCH_ITEM_LOCATE_BUS_T } );
516 return static_cast<SCH_LINE*>( selection.Front() );
517}
518
519
521 const std::optional<VECTOR2I>& aPos )
522{
523 SCHEMATIC_SETTINGS& cfg = getModel<SCHEMATIC>()->Settings();
524 SCH_SCREEN* screen = m_frame->GetScreen();
525 // use the same function as the menu selector, so we choose the same bus segment
526 SCH_LINE* const bus = getBusForUnfolding();
527
528 if ( bus == nullptr )
529 {
530 wxASSERT_MSG( false, wxString::Format( "Couldn't find the originating bus line (but had a net: %s )",
531 aNet ) );
532 return nullptr;
533 }
534
535 VECTOR2I pos = aPos.value_or( static_cast<VECTOR2I>( getViewControls()->GetCursorPosition() ) );
536
537 // It is possible for the position to be near the bus, but not exactly on it, but
538 // we need the bus entry to be on the bus exactly to connect.
539 // If the bus segment is H or V, this will be on the selection grid, if it's not,
540 // it might not be, but it won't be a broken connection (and the user asked for it!)
541 pos = bus->GetSeg().NearestPoint( pos );
542
544
545 m_busUnfold.entry = new SCH_BUS_WIRE_ENTRY( pos );
546 m_busUnfold.entry->SetParent( screen );
547 m_frame->AddToScreen( m_busUnfold.entry, m_frame->GetScreen() );
548
549 m_busUnfold.label = new SCH_LABEL( m_busUnfold.entry->GetEnd(), aNet );
550 m_busUnfold.label->SetTextSize( VECTOR2I( cfg.m_DefaultTextSize, cfg.m_DefaultTextSize ) );
551 m_busUnfold.label->SetSpinStyle( busUnfoldPersistentSettings.label_spin_style );
552 m_busUnfold.label->SetParent( m_frame->GetScreen() );
553 m_busUnfold.label->SetFlags( IS_NEW | IS_MOVING );
554
555 m_busUnfold.in_progress = true;
556 m_busUnfold.origin = pos;
557 m_busUnfold.net_name = aNet;
558
559 getViewControls()->SetCrossHairCursorPosition( m_busUnfold.entry->GetEnd(), false );
560
561 std::vector<DANGLING_END_ITEM> endPointsByType;
562
563 for( SCH_ITEM* item : screen->Items().Overlapping( m_busUnfold.entry->GetBoundingBox() ) )
564 item->GetEndPoints( endPointsByType );
565
566 std::vector<DANGLING_END_ITEM> endPointsByPos = endPointsByType;
567 DANGLING_END_ITEM_HELPER::sort_dangling_end_items( endPointsByType, endPointsByPos );
568 m_busUnfold.entry->UpdateDanglingState( endPointsByType, endPointsByPos );
569 m_busUnfold.entry->SetEndDangling( false );
570 m_busUnfold.label->SetIsDangling( false );
571
572 return startSegments( aCommit, LAYER_WIRE, m_busUnfold.entry->GetEnd() );
573}
574
575
577{
578 SCH_SCREEN* screen = m_frame->GetScreen();
579
580 for( SCH_ITEM* item : screen->Items().Overlapping( SCH_SHEET_T, aPosition ) )
581 {
582 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
583
584 for( SCH_SHEET_PIN* pin : sheet->GetPins() )
585 {
586 if( pin->GetPosition() == aPosition )
587 return pin;
588 }
589 }
590
591 return nullptr;
592}
593
594
595void SCH_LINE_WIRE_BUS_TOOL::computeBreakPoint( const std::pair<SCH_LINE*, SCH_LINE*>& aSegments,
596 VECTOR2I& aPosition,
597 LINE_MODE mode,
598 bool posture )
599{
600 wxCHECK_RET( aSegments.first && aSegments.second,
601 wxT( "Cannot compute break point of NULL line segment." ) );
602
603 VECTOR2I midPoint;
604 SCH_LINE* segment = aSegments.first;
605 SCH_LINE* nextSegment = aSegments.second;
606
607 VECTOR2I delta = aPosition - segment->GetStartPoint();
608 int xDir = delta.x > 0 ? 1 : -1;
609 int yDir = delta.y > 0 ? 1 : -1;
610
611
612 bool preferHorizontal;
613 bool preferVertical;
614
615 if( ( mode == LINE_MODE_45 ) && posture )
616 {
617 preferHorizontal = ( nextSegment->GetEndPoint().x - nextSegment->GetStartPoint().x ) != 0;
618 preferVertical = ( nextSegment->GetEndPoint().y - nextSegment->GetStartPoint().y ) != 0;
619 }
620 else
621 {
622 preferHorizontal = ( segment->GetEndPoint().x - segment->GetStartPoint().x ) != 0;
623 preferVertical = ( segment->GetEndPoint().y - segment->GetStartPoint().y ) != 0;
624 }
625
626 // Check for times we need to force horizontal sheet pin connections
627 const SCH_SHEET_PIN* connectedPin = getSheetPin( segment->GetStartPoint() );
628 SHEET_SIDE force = connectedPin ? connectedPin->GetSide() : SHEET_SIDE::UNDEFINED;
629
630 if( force == SHEET_SIDE::LEFT || force == SHEET_SIDE::RIGHT )
631 {
632 if( aPosition.x == connectedPin->GetPosition().x ) // push outside sheet boundary
633 {
634 int direction = ( force == SHEET_SIDE::LEFT ) ? -1 : 1;
635 aPosition.x += KiROUND( getView()->GetGAL()->GetGridSize().x * direction );
636 }
637
638 preferHorizontal = true;
639 preferVertical = false;
640 }
641
642
643 auto breakVertical = [&]() mutable
644 {
645 switch( mode )
646 {
647 case LINE_MODE_45:
648 if( !posture )
649 {
650 midPoint.x = segment->GetStartPoint().x;
651 midPoint.y = aPosition.y - yDir * abs( delta.x );
652 }
653 else
654 {
655 midPoint.x = aPosition.x;
656 midPoint.y = segment->GetStartPoint().y + yDir * abs( delta.x );
657 }
658 break;
659 default:
660 midPoint.x = segment->GetStartPoint().x;
661 midPoint.y = aPosition.y;
662 }
663 };
664
665
666 auto breakHorizontal = [&]() mutable
667 {
668 switch( mode )
669 {
670 case LINE_MODE_45:
671 if( !posture )
672 {
673 midPoint.x = aPosition.x - xDir * abs( delta.y );
674 midPoint.y = segment->GetStartPoint().y;
675 }
676 else
677 {
678 midPoint.x = segment->GetStartPoint().x + xDir * abs( delta.y );
679 midPoint.y = aPosition.y;
680 }
681 break;
682 default:
683 midPoint.x = aPosition.x;
684 midPoint.y = segment->GetStartPoint().y;
685 }
686 };
687
688
689 // Maintain current line shape if we can, e.g. if we were originally moving
690 // vertically keep the first segment vertical
691 if( preferVertical )
692 breakVertical();
693 else if( preferHorizontal )
694 breakHorizontal();
695
696 // Check if our 45 degree angle is one of these shapes
697 // /
698 // /
699 // /
700 // /__________
701 VECTOR2I deltaMidpoint = midPoint - segment->GetStartPoint();
702
703 if( mode == LINE_MODE::LINE_MODE_45 && !posture
704 && ( ( alg::signbit( deltaMidpoint.x ) != alg::signbit( delta.x ) )
705 || ( alg::signbit( deltaMidpoint.y ) != alg::signbit( delta.y ) ) ) )
706 {
707 preferVertical = false;
708 preferHorizontal = false;
709 }
710 else if( mode == LINE_MODE::LINE_MODE_45 && posture
711 && ( ( abs( deltaMidpoint.x ) > abs( delta.x ) )
712 || ( abs( deltaMidpoint.y ) > abs( delta.y ) ) ) )
713 {
714 preferVertical = false;
715 preferHorizontal = false;
716 }
717
718 if( !preferHorizontal && !preferVertical )
719 {
720 if( std::abs( delta.x ) < std::abs( delta.y ) )
721 breakVertical();
722 else
723 breakHorizontal();
724 }
725
726 segment->SetEndPoint( midPoint );
727 nextSegment->SetStartPoint( midPoint );
728 nextSegment->SetEndPoint( aPosition );
729}
730
731
733 int aType, bool aQuitOnDraw )
734{
735 SCH_SCREEN* screen = m_frame->GetScreen();
736 SCH_LINE* segment = nullptr;
738 GRID_HELPER_GRIDS gridType = ( aType == LAYER_NOTES ) ? GRID_GRAPHICS : GRID_WIRES;
740 int lastMode = m_frame->eeconfig()->m_Drawing.line_mode;
741 static bool posture = false;
742
743 auto setCursor =
744 [&]()
745 {
746 if( aType == LAYER_WIRE )
747 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
748 else if( aType == LAYER_BUS )
749 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_BUS );
750 else if( aType == LAYER_NOTES )
751 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_GRAPHIC );
752 else
753 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::LINE_WIRE );
754 };
755
756 auto cleanup =
757 [&] ()
758 {
760
761 for( SCH_LINE* wire : m_wires )
762 delete wire;
763
764 m_wires.clear();
765 segment = nullptr;
766
767 if( m_busUnfold.entry )
768 m_frame->RemoveFromScreen( m_busUnfold.entry, screen );
769
770 if( m_busUnfold.label && !m_busUnfold.label_placed )
771 m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
772
773 if( m_busUnfold.label && m_busUnfold.label_placed )
774 m_frame->RemoveFromScreen( m_busUnfold.label, screen );
775
776 delete m_busUnfold.entry;
777 delete m_busUnfold.label;
778 m_busUnfold = {};
779
780 m_view->ClearPreview();
781 m_view->ShowPreview( false );
782 };
783
784 Activate();
785 // Must be done after Activate() so that it gets set into the correct context
786 controls->ShowCursor( true );
787 // Set initial cursor
788 setCursor();
789
790 // Add the new label to the selection so the rotate command operates on it
791 if( m_busUnfold.label )
792 m_selectionTool->AddItemToSel( m_busUnfold.label, true );
793
794 // Continue the existing wires if we've started (usually by immediate action preference)
795 if( !m_wires.empty() )
796 segment = m_wires.back();
797
798 VECTOR2I contextMenuPos;
799
800 // Main loop: keep receiving events
801 while( TOOL_EVENT* evt = Wait() )
802 {
803 LINE_MODE currentMode = (LINE_MODE) m_frame->eeconfig()->m_Drawing.line_mode;
804 bool twoSegments = currentMode != LINE_MODE::LINE_MODE_FREE;
805
806 // The tool hotkey is interpreted as a click when drawing
807 bool isSyntheticClick = ( segment || m_busUnfold.in_progress ) && evt->IsActivate()
808 && evt->HasPosition() && evt->Matches( aTool );
809
810 setCursor();
811 grid.SetMask( GRID_HELPER::ALL );
812 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
813 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
814
815 if( segment )
816 {
817 if( segment->GetStartPoint().x == segment->GetEndPoint().x )
818 grid.ClearMaskFlag( GRID_HELPER::VERTICAL );
819
820 if( segment->GetStartPoint().y == segment->GetEndPoint().y )
821 grid.ClearMaskFlag( GRID_HELPER::HORIZONTAL );
822 }
823
824 VECTOR2D eventPosition = evt->HasPosition() ? evt->Position()
825 : controls->GetMousePosition();
826
827 VECTOR2I cursorPos = grid.ResolveSnap( eventPosition, gridType, segment ).position;
828 controls->ForceCursorPosition( true, cursorPos );
829
830 // Need to handle change in H/V mode while drawing
831 if( currentMode != lastMode )
832 {
833 // Need to delete extra segment if we have one
834 if( segment && currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() >= 2 )
835 {
836 m_wires.pop_back();
837 m_selectionTool->RemoveItemFromSel( segment );
838 delete segment;
839
840 segment = m_wires.back();
841 segment->SetEndPoint( cursorPos );
842 }
843 // Add a segment so we can move orthogonally/45
844 else if( segment && lastMode == LINE_MODE::LINE_MODE_FREE )
845 {
846 segment->SetEndPoint( cursorPos );
847
848 // Create a new segment, and chain it after the current segment.
849 segment = static_cast<SCH_LINE*>( segment->Duplicate( true, &aCommit ) );
850 segment->SetFlags( IS_NEW | IS_MOVING );
851 segment->SetStartPoint( cursorPos );
852 m_wires.push_back( segment );
853
854 m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
855 }
856
857 lastMode = currentMode;
858 }
859
860 //------------------------------------------------------------------------
861 // Handle cancel:
862 //
863 if( evt->IsCancelInteractive() )
864 {
865 m_frame->GetInfoBar()->Dismiss();
866
867 if( segment || m_busUnfold.in_progress )
868 {
869 cleanup();
870
871 if( aQuitOnDraw )
872 break;
873 }
874 else
875 {
876 break;
877 }
878 }
879 else if( evt->IsActivate() && !isSyntheticClick )
880 {
881 if( segment || m_busUnfold.in_progress )
882 {
883 m_frame->ShowInfoBarMsg( _( "Press <ESC> to cancel drawing." ) );
884 evt->SetPassEvent( false );
885 continue;
886 }
887
888 if( evt->IsMoveTool() )
889 {
890 // Make sure we come back after the move tool runs
891 m_frame->PushTool( aTool );
892 }
893
894 break;
895 }
896 //------------------------------------------------------------------------
897 // Handle finish:
898 //
899 else if( evt->IsAction( &ACTIONS::finishInteractive ) )
900 {
901 if( segment || m_busUnfold.in_progress )
902 {
903 finishSegments( aCommit );
904 segment = nullptr;
905
906 aCommit.Push( _( "Draw Wires" ) );
907
908 if( aQuitOnDraw )
909 break;
910 }
911 }
912 //------------------------------------------------------------------------
913 // Handle click:
914 //
915 else if( evt->IsClick( BUT_LEFT )
916 || evt->IsAction( &ACTIONS::cursorClick )
917 || ( segment && evt->IsDblClick( BUT_LEFT ) )
918 || ( segment && evt->IsAction( &ACTIONS::cursorDblClick ) )
919 || isSyntheticClick )
920 {
921 // First click when unfolding places the label and wire-to-bus entry
922 if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
923 {
924 wxASSERT( aType == LAYER_WIRE );
925
926 m_frame->AddToScreen( m_busUnfold.label, screen );
927 m_selectionTool->RemoveItemFromSel( m_busUnfold.label, true );
928 m_busUnfold.label_placed = true;
929 }
930
931 if( !segment )
932 {
933 segment = startSegments( aCommit, aType, VECTOR2D( cursorPos ) );
934 }
935 // Create a new segment if we're out of previously-created ones
936 else if( !segment->IsNull()
937 || ( twoSegments && !m_wires[m_wires.size() - 2]->IsNull() ) )
938 {
939 // Terminate the command if the end point is on a pin, junction, label, or another
940 // wire or bus.
941 if( screen->IsTerminalPoint( cursorPos, segment->GetLayer() ) )
942 {
943 finishSegments( aCommit );
944 segment = nullptr;
945
946 aCommit.Push( _( "Draw Wires" ) );
947
948 if( aQuitOnDraw )
949 break;
950 }
951 else
952 {
953 int placedSegments = 1;
954
955 // When placing lines with the forty-five degree end, the user is
956 // targetting the endpoint with the angled portion, so it's more
957 // intuitive to place both segments at the same time.
958 if( currentMode == LINE_MODE::LINE_MODE_45 )
959 placedSegments++;
960
961 segment->SetEndPoint( cursorPos );
962
963 for( int i = 0; i < placedSegments; i++ )
964 {
965 // Create a new segment, and chain it after the current segment.
966 segment = static_cast<SCH_LINE*>( segment->Duplicate( true, &aCommit ) );
967 segment->SetFlags( IS_NEW | IS_MOVING );
968 segment->SetStartPoint( cursorPos );
969 m_wires.push_back( segment );
970
971 m_selectionTool->AddItemToSel( segment, true /*quiet mode*/ );
972 }
973 }
974 }
975
976 if( segment && ( evt->IsDblClick( BUT_LEFT )
977 || evt->IsAction( &ACTIONS::cursorDblClick ) ) )
978 {
979 if( twoSegments && m_wires.size() >= 2 )
980 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
981
982 finishSegments( aCommit );
983 segment = nullptr;
984
985 aCommit.Push( _( "Draw Wires" ) );
986
987 if( aQuitOnDraw )
988 break;
989 }
990 }
991 //------------------------------------------------------------------------
992 // Handle motion:
993 //
994 else if( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) )
995 {
996 m_view->ClearPreview();
997
998 // Update the bus unfold posture based on the mouse movement
999 if( m_busUnfold.in_progress && !m_busUnfold.label_placed )
1000 {
1001 VECTOR2I cursor_delta = cursorPos - m_busUnfold.origin;
1002 SCH_BUS_WIRE_ENTRY* entry = m_busUnfold.entry;
1003
1004 bool flipX = ( cursor_delta.x < 0 );
1005 bool flipY = ( cursor_delta.y < 0 );
1006
1007 // Erase and redraw if necessary
1008 if( flipX != m_busUnfold.flipX || flipY != m_busUnfold.flipY )
1009 {
1010 VECTOR2I size = entry->GetSize();
1011 int ySign = flipY ? -1 : 1;
1012 int xSign = flipX ? -1 : 1;
1013
1014 size.x = std::abs( size.x ) * xSign;
1015 size.y = std::abs( size.y ) * ySign;
1016 entry->SetSize( size );
1017
1018 m_busUnfold.flipY = flipY;
1019 m_busUnfold.flipX = flipX;
1020
1021 m_frame->UpdateItem( entry, false, true );
1022 m_wires.front()->SetStartPoint( entry->GetEnd() );
1023 }
1024
1025 // Update the label "ghost" position
1026 m_busUnfold.label->SetPosition( cursorPos );
1027 m_view->AddToPreview( m_busUnfold.label->Clone() );
1028
1029 // Ensure segment is non-null at the start of bus unfold
1030 if( !segment )
1031 segment = m_wires.back();
1032 }
1033
1034 if( segment )
1035 {
1036 // Coerce the line to vertical/horizontal/45 as necessary
1037 if( twoSegments && m_wires.size() >= 2 )
1038 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1039 else
1040 segment->SetEndPoint( cursorPos );
1041 }
1042
1043 for( SCH_LINE* wire : m_wires )
1044 {
1045 if( !wire->IsNull() )
1046 m_view->AddToPreview( wire->Clone() );
1047 }
1048
1049 std::vector<SCH_ITEM*> previewItems;
1050
1051 for( SCH_LINE* wire : m_wires )
1052 {
1053 if( !wire->IsNull() )
1054 previewItems.push_back( wire );
1055 }
1056
1057 if( m_busUnfold.entry )
1058 previewItems.push_back( m_busUnfold.entry );
1059
1060 for( SCH_JUNCTION* jct : JUNCTION_HELPERS::PreviewJunctions( m_frame->GetScreen(), previewItems ) )
1061 m_view->AddToPreview( jct, true );
1062 }
1063 else if( evt->IsAction( &SCH_ACTIONS::undoLastSegment )
1064 || evt->IsAction( &ACTIONS::doDelete )
1065 || evt->IsAction( &ACTIONS::undo ) )
1066 {
1067 if( ( currentMode == LINE_MODE::LINE_MODE_FREE && m_wires.size() > 1 )
1068 || ( LINE_MODE::LINE_MODE_90 && m_wires.size() > 2 ) )
1069 {
1070 m_view->ClearPreview();
1071
1072 m_wires.pop_back();
1073 m_selectionTool->RemoveItemFromSel( segment );
1074 delete segment;
1075
1076 segment = m_wires.back();
1077 cursorPos = segment->GetEndPoint();
1078 getViewControls()->WarpMouseCursor( cursorPos, true );
1079
1080 // Find new bend point for current mode
1081 if( twoSegments && m_wires.size() >= 2 )
1082 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1083 else
1084 segment->SetEndPoint( cursorPos );
1085
1086 for( SCH_LINE* wire : m_wires )
1087 {
1088 if( !wire->IsNull() )
1089 m_view->AddToPreview( wire->Clone() );
1090 }
1091 }
1092 else if( evt->IsAction( &ACTIONS::undo ) )
1093 {
1094 // Dispatch as normal undo event
1095 evt->SetPassEvent();
1096 }
1097 else
1098 {
1099 wxBell();
1100 }
1101 }
1102 else if( evt->IsAction( &SCH_ACTIONS::switchSegmentPosture ) && m_wires.size() >= 2 )
1103 {
1104 posture = !posture;
1105
1106 // The 90 degree mode doesn't have a forced posture like
1107 // the 45 degree mode and computeBreakPoint maintains existing 90s' postures.
1108 // Instead, just swap the 90 angle here.
1109 if( currentMode == LINE_MODE::LINE_MODE_90 )
1110 {
1111 m_view->ClearPreview();
1112
1113 SCH_LINE* line2 = m_wires[m_wires.size() - 1];
1114 SCH_LINE* line1 = m_wires[m_wires.size() - 2];
1115
1116 VECTOR2I delta2 = line2->GetEndPoint() - line2->GetStartPoint();
1117 VECTOR2I delta1 = line1->GetEndPoint() - line1->GetStartPoint();
1118
1119 line2->SetStartPoint(line2->GetEndPoint() - delta1);
1120 line1->SetEndPoint(line1->GetStartPoint() + delta2);
1121
1122 for( SCH_LINE* wire : m_wires )
1123 {
1124 if( !wire->IsNull() )
1125 m_view->AddToPreview( wire->Clone() );
1126 }
1127 }
1128 else
1129 {
1130 computeBreakPoint( { m_wires[m_wires.size() - 2], segment }, cursorPos, currentMode, posture );
1131
1132 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1133 }
1134 }
1135 //------------------------------------------------------------------------
1136 // Handle context menu:
1137 //
1138 else if( evt->IsClick( BUT_RIGHT ) )
1139 {
1140 // Warp after context menu only if dragging...
1141 if( !segment )
1142 m_toolMgr->VetoContextMenuMouseWarp();
1143
1144 contextMenuPos = cursorPos;
1145 m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
1146 }
1147 else if( evt->Category() == TC_COMMAND && evt->Action() == TA_CHOICE_MENU_CHOICE )
1148 {
1149 if( *evt->GetCommandId() >= ID_POPUP_SCH_UNFOLD_BUS
1150 && *evt->GetCommandId() <= ID_POPUP_SCH_UNFOLD_BUS_END )
1151 {
1152 wxASSERT_MSG( !segment, "Bus unfold event received when already drawing!" );
1153
1154 aType = LAYER_WIRE;
1155 wxString net = *evt->Parameter<wxString*>();
1156 segment = doUnfoldBus( aCommit, net, contextMenuPos );
1157 }
1158 }
1159 //------------------------------------------------------------------------
1160 // Handle TOOL_ACTION special cases
1161 //
1162 else if( evt->IsAction( &SCH_ACTIONS::rotateCW ) || evt->IsAction( &SCH_ACTIONS::rotateCCW ) )
1163 {
1164 if( m_busUnfold.in_progress )
1165 {
1166 m_busUnfold.label->Rotate90( evt->IsAction( &SCH_ACTIONS::rotateCW ) );
1167 busUnfoldPersistentSettings.label_spin_style = m_busUnfold.label->GetSpinStyle();
1168
1169 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1170 }
1171 else
1172 {
1173 wxBell();
1174 }
1175 }
1176 else if( evt->IsAction( &ACTIONS::redo ) )
1177 {
1178 wxBell();
1179 }
1180 else
1181 {
1182 evt->SetPassEvent();
1183 }
1184
1185 // Enable autopanning and cursor capture only when there is a segment to be placed
1186 controls->SetAutoPan( segment != nullptr );
1187 controls->CaptureCursor( segment != nullptr );
1188 }
1189
1190 controls->SetAutoPan( false );
1191 controls->CaptureCursor( false );
1192 m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
1193 controls->ForceCursorPosition( false );
1194 return 0;
1195}
1196
1197
1199 SCH_LINE* aSegment )
1200{
1201 if( !aSegment )
1202 aSegment = m_frame->GetScreen()->GetLine( aPos, 0, aType );
1203
1204 if( !aSegment )
1205 {
1206 switch( aType )
1207 {
1208 default: aSegment = new SCH_LINE( aPos, LAYER_NOTES ); break;
1209 case LAYER_WIRE: aSegment = new SCH_LINE( aPos, LAYER_WIRE ); break;
1210 case LAYER_BUS: aSegment = new SCH_LINE( aPos, LAYER_BUS ); break;
1211 }
1212
1213 // Give segments a parent so they find the default line/wire/bus widths
1214 aSegment->SetParent( &m_frame->Schematic() );
1215 }
1216 else
1217 {
1218 aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate( true, &aCommit ) );
1219 aSegment->SetStartPoint( aPos );
1220 }
1221
1222
1223 aSegment->SetFlags( IS_NEW | IS_MOVING );
1224 m_wires.push_back( aSegment );
1225
1226 m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
1227
1228 // We need 2 segments to go from a given start pin to an end point when the
1229 // horizontal and vertical lines only switch is on.
1230 if( m_frame->eeconfig()->m_Drawing.line_mode )
1231 {
1232 aSegment = static_cast<SCH_LINE*>( aSegment->Duplicate( true, &aCommit ) );
1233 aSegment->SetFlags( IS_NEW | IS_MOVING );
1234 m_wires.push_back( aSegment );
1235
1236 m_selectionTool->AddItemToSel( aSegment, true /*quiet mode*/ );
1237 }
1238
1239 return aSegment;
1240}
1241
1242
1257{
1258 for( auto it = m_wires.begin(); it != m_wires.end(); )
1259 {
1260 SCH_LINE* line = *it;
1261
1262 if( line->IsNull() )
1263 {
1264 delete line;
1265 it = m_wires.erase( it );
1266 continue;
1267 }
1268
1269 auto next_it = it;
1270 ++next_it;
1271
1272 if( next_it == m_wires.end() )
1273 break;
1274
1275 SCH_LINE* next_line = *next_it;
1276
1277 if( SCH_LINE* merged = line->MergeOverlap( m_frame->GetScreen(), next_line, false ) )
1278 {
1279 delete line;
1280 delete next_line;
1281 it = m_wires.erase( it );
1282 *it = merged;
1283 }
1284
1285 ++it;
1286 }
1287}
1288
1289
1291{
1292 // Clear selection when done so that a new wire can be started.
1293 // NOTE: this must be done before simplifyWireList is called or we might end up with
1294 // freed selected items.
1295 m_toolMgr->RunAction( ACTIONS::selectionClear );
1296
1297 SCH_SCREEN* screen = m_frame->GetScreen();
1298
1299 // Remove segments backtracking over others
1301
1302 // Collect the possible connection points for the new lines
1303 std::vector<VECTOR2I> connections = screen->GetConnections();
1304 std::vector<VECTOR2I> new_ends;
1305
1306 // Check each new segment for possible junctions and add/split if needed
1307 for( SCH_LINE* wire : m_wires )
1308 {
1309 if( wire->HasFlag( SKIP_STRUCT ) )
1310 continue;
1311
1312 std::vector<VECTOR2I> tmpends = wire->GetConnectionPoints();
1313
1314 new_ends.insert( new_ends.end(), tmpends.begin(), tmpends.end() );
1315
1316 for( const VECTOR2I& pt : connections )
1317 {
1318 if( IsPointOnSegment( wire->GetStartPoint(), wire->GetEndPoint(), pt ) )
1319 new_ends.push_back( pt );
1320 }
1321
1322 aCommit.Added( wire, screen );
1323 }
1324
1325 if( m_busUnfold.in_progress && m_busUnfold.label_placed )
1326 {
1327 wxASSERT( m_busUnfold.entry && m_busUnfold.label );
1328
1329 aCommit.Added( m_busUnfold.entry, screen );
1330 m_frame->SaveCopyForRepeatItem( m_busUnfold.entry );
1331
1332 aCommit.Added( m_busUnfold.label, screen );
1333 m_frame->AddCopyForRepeatItem( m_busUnfold.label );
1334 m_busUnfold.label->ClearEditFlags();
1335
1336 if( !m_wires.empty() )
1337 m_frame->AddCopyForRepeatItem( m_wires[0] );
1338 }
1339 else if( !m_wires.empty() )
1340 {
1341 m_frame->SaveCopyForRepeatItem( m_wires[0] );
1342 }
1343
1344 for( size_t ii = 1; ii < m_wires.size(); ++ii )
1345 m_frame->AddCopyForRepeatItem( m_wires[ii] );
1346
1347 // Add the new wires
1348 for( SCH_LINE* wire : m_wires )
1349 {
1350 wire->ClearFlags( IS_NEW | IS_MOVING );
1351 m_frame->AddToScreen( wire, screen );
1352 }
1353
1354 m_wires.clear();
1355 m_view->ClearPreview();
1356 m_view->ShowPreview( false );
1357
1358 getViewControls()->CaptureCursor( false );
1359 getViewControls()->SetAutoPan( false );
1360
1361 // Correct and remove segments that need to be merged.
1362 m_frame->Schematic().CleanUp( &aCommit );
1363
1364 std::vector<SCH_ITEM*> symbols;
1365
1366 for( SCH_ITEM* symbol : m_frame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
1367 symbols.push_back( symbol );
1368
1369 for( SCH_ITEM* symbol : symbols )
1370 {
1371 std::vector<VECTOR2I> pts = symbol->GetConnectionPoints();
1372
1373 if( pts.size() > 2 )
1374 continue;
1375
1376 for( auto pt = pts.begin(); pt != pts.end(); pt++ )
1377 {
1378 for( auto secondPt = pt + 1; secondPt != pts.end(); secondPt++ )
1379 m_frame->TrimWire( &aCommit, *pt, *secondPt );
1380 }
1381 }
1382
1383 for( const VECTOR2I& pt : new_ends )
1384 {
1385 if( m_frame->GetScreen()->IsExplicitJunctionNeeded( pt ) )
1386 AddJunction( &aCommit, m_frame->GetScreen(), pt );
1387 }
1388
1389 if( m_busUnfold.in_progress )
1390 m_busUnfold = {};
1391
1392 for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
1393 item->ClearEditFlags();
1394}
1395
1396
1398{
1400 SCH_SCREEN* screen = sch->CurrentSheet().LastScreen();
1401
1402 std::set<SCH_LINE*> lines;
1403 BOX2I bb = aSelection->GetBoundingBox();
1404
1405 for( EDA_ITEM* item : screen->Items().Overlapping( SCH_LINE_T, bb ) )
1406 lines.insert( static_cast<SCH_LINE*>( item ) );
1407
1408 for( unsigned ii = 0; ii < aSelection->GetSize(); ii++ )
1409 {
1410 SCH_ITEM* item = dynamic_cast<SCH_ITEM*>( aSelection->GetItem( ii ) );
1411
1412 if( !item || !item->IsConnectable() || ( item->Type() == SCH_LINE_T ) )
1413 continue;
1414
1415 std::vector<VECTOR2I> pts = item->GetConnectionPoints();
1416
1419 for( SCH_LINE* line : lines )
1420 {
1421 std::vector<VECTOR2I> conn_pts;
1422
1423 for( const VECTOR2I& pt : pts )
1424 {
1425 if( IsPointOnSegment( line->GetStartPoint(), line->GetEndPoint(), pt ) )
1426 conn_pts.push_back( pt );
1427
1428 if( conn_pts.size() > 2 )
1429 break;
1430 }
1431
1432 if( conn_pts.size() == 2 )
1433 m_frame->TrimWire( aCommit, conn_pts[0], conn_pts[1] );
1434 }
1435 }
1436
1437 return 0;
1438}
1439
1440
1442{
1443 SCH_SCREEN* screen = m_frame->GetScreen();
1444 std::deque<EDA_ITEM*> allItems;
1445
1446 for( EDA_ITEM* item : aSelection->Items() )
1447 {
1448 allItems.push_back( item );
1449
1450 if( item->Type() == SCH_GROUP_T )
1451 {
1452 static_cast<SCH_GROUP*>( item )->RunOnChildren(
1453 [&]( SCH_ITEM* child )
1454 {
1455 allItems.push_back( child );
1457 }
1458 }
1459
1460 for( const VECTOR2I& point : screen->GetNeededJunctions( allItems ) )
1461 {
1462 wxLogTrace( "KICAD_SCH_MOVE", "AddJunctionsIfNeeded: adding junction at %s", point.Format().c_str() );
1463 AddJunction( aCommit, m_frame->GetScreen(), point );
1464 }
1465
1466 return 0;
1467}
1468
1469
1470void SCH_LINE_WIRE_BUS_TOOL::BreakSegment( SCH_COMMIT* aCommit, SCH_LINE* aSegment, const VECTOR2I& aPoint,
1471 SCH_LINE** aNewSegment, SCH_SCREEN* aScreen )
1472{
1473 // Save the copy of aSegment before breaking it
1474 aCommit->Modify( aSegment, aScreen );
1475
1476 SCH_LINE* newSegment = aSegment->BreakAt( aCommit, aPoint );
1477
1478 aSegment->SetFlags( IS_CHANGED | IS_BROKEN );
1479 newSegment->SetFlags( IS_NEW | IS_BROKEN );
1480 m_frame->AddToScreen( newSegment, aScreen );
1481
1482 aCommit->Added( newSegment, aScreen );
1483
1484 *aNewSegment = newSegment;
1485}
1486
1487
1489{
1490 bool brokenSegments = false;
1491 SCH_LINE* new_line;
1492
1493 for( SCH_LINE* wire : aScreen->GetBusesAndWires( aPos, true ) )
1494 {
1495 BreakSegment( aCommit, wire, aPos, &new_line, aScreen );
1496 brokenSegments = true;
1497 }
1498
1499 return brokenSegments;
1500}
1501
1502
1504{
1505 bool brokenSegments = false;
1506
1507 std::set<VECTOR2I> point_set;
1508
1509 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_JUNCTION_T ) )
1510 point_set.insert( item->GetPosition() );
1511
1512 for( SCH_ITEM* item : aScreen->Items().OfType( SCH_BUS_WIRE_ENTRY_T ) )
1513 {
1514 SCH_BUS_WIRE_ENTRY* entry = static_cast<SCH_BUS_WIRE_ENTRY*>( item );
1515 point_set.insert( entry->GetPosition() );
1516 point_set.insert( entry->GetEnd() );
1517 }
1518
1519 for( const VECTOR2I& pt : point_set )
1520 {
1521 BreakSegments( aCommit, pt, aScreen );
1522 brokenSegments = true;
1523 }
1524
1525 return brokenSegments;
1526}
1527
1528
1530 const VECTOR2I& aPos )
1531{
1532 SCH_JUNCTION* junction = new SCH_JUNCTION( aPos );
1533
1534 if( aScreen->GetBus( aPos ) )
1535 junction->SetLayer( LAYER_BUS_JUNCTION );
1536
1537 m_frame->AddToScreen( junction, aScreen );
1538 aCommit->Added( junction, aScreen );
1539
1540 BreakSegments( aCommit, aPos, aScreen );
1541
1542 return junction;
1543}
1544
1545
const char * name
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
static TOOL_ACTION cursorDblClick
Definition actions.h:177
static TOOL_ACTION undo
Definition actions.h:71
static TOOL_ACTION doDelete
Definition actions.h:81
static TOOL_ACTION cursorClick
Definition actions.h:176
static TOOL_ACTION redo
Definition actions.h:72
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION refreshPreview
Definition actions.h:155
static TOOL_ACTION finishInteractive
Definition actions.h:69
ACTION_MENU(bool isContextMenu, TOOL_INTERACTIVE *aTool=nullptr)
Default constructor.
void Clear()
Remove all the entries from the menu (as well as its title).
void SetTitle(const wxString &aTitle) override
Set title for the menu.
void SetTool(TOOL_INTERACTIVE *aTool)
Set a tool that is the creator of the menu.
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
TOOL_INTERACTIVE * m_tool
Creator of the menu.
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
ACTION_MENU * create() const override
Return an instance of this class. It has to be overridden in inheriting classes.
static std::vector< MEMBER > memberEntries(const SCH_LINE &aBus)
bool PassHelpTextToHandler() override
BUS_UNFOLD_MENU(BUS_GETTER aBusGetter)
void update() override
Update menu state stub.
COMMIT & Added(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been added.
Definition commit.h:80
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
static void sort_dangling_end_items(std::vector< DANGLING_END_ITEM > &aItemListByType, std::vector< DANGLING_END_ITEM > &aItemListByPos)
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
bool IsNew() const
Definition eda_item.h:131
EE_TYPE Overlapping(const BOX2I &aRect) const
Definition sch_rtree.h:253
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
An interface for classes handling user events controlling the view behavior such as zooming,...
virtual void CaptureCursor(bool aEnabled)
Force the cursor to stay within the drawing panel area.
virtual void ForceCursorPosition(bool aEnabled, const VECTOR2D &aPosition=VECTOR2D(0, 0))
Place the cursor immediately at a given point.
virtual void ShowCursor(bool aEnabled)
Enable or disables display of cursor.
virtual void WarpMouseCursor(const VECTOR2D &aPosition, bool aWorldCoordinates=false, bool aWarpView=false)=0
If enabled (.
virtual void SetCrossHairCursorPosition(const VECTOR2D &aPosition, bool aWarpView=true)=0
Move the graphic crosshair cursor to the requested position expressed in world coordinates.
virtual VECTOR2D GetMousePosition(bool aWorldCoordinates=true) const =0
Return the current mouse pointer position.
virtual void SetAutoPan(bool aEnabled)
Turn on/off auto panning (this feature is used when there is a tool active (eg.
These are loaded from Eeschema settings but then overwritten by the project settings.
Holds all the data relating to one schematic.
Definition schematic.h:149
SCH_CONNECTIVITY::FACADE & Connectivity() const
Definition schematic.h:316
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:304
static TOOL_ACTION rotateCCW
static TOOL_ACTION placeClassLabel
Definition sch_actions.h:85
static TOOL_ACTION clearHighlight
static TOOL_ACTION placeGlobalLabel
Definition sch_actions.h:86
static TOOL_ACTION leaveSheet
static TOOL_ACTION breakWire
static TOOL_ACTION drawLines
static TOOL_ACTION placeHierLabel
Definition sch_actions.h:87
static TOOL_ACTION selectConnection
If current selection is a wire or bus, expand to entire connection.
Definition sch_actions.h:57
static TOOL_ACTION placeLabel
Definition sch_actions.h:84
static TOOL_ACTION drawWire
Definition sch_actions.h:78
static TOOL_ACTION rotateCW
static TOOL_ACTION placeJunction
Definition sch_actions.h:82
static TOOL_ACTION selectNode
Select the junction, wire or bus segment under the cursor.
Definition sch_actions.h:53
static TOOL_ACTION unfoldBus
Definition sch_actions.h:80
static TOOL_ACTION drawBus
Definition sch_actions.h:79
static TOOL_ACTION slice
static TOOL_ACTION undoLastSegment
static TOOL_ACTION switchSegmentPosture
VECTOR2I GetSize() const
void SetSize(const VECTOR2I &aSize)
VECTOR2I GetPosition() const override
VECTOR2I GetEnd() const
Class for a wire to bus entry.
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
bool IsBus() const
const std::vector< std::shared_ptr< SCH_CONNECTION > > & Members() const
static wxString PrintBusForUI(const wxString &aString)
std::optional< ITEM_VIEW > Connection(const KIID &aItem, const KIID_PATH &aPath) const
Schematic editor (Eeschema) main window.
const wxString & GetHighlightedConnection() const
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
SCH_ITEM * Duplicate(bool addToParentGroup, SCH_COMMIT *aCommit=nullptr, bool aDoClone=false) const
Routine to create a new copy of given item.
Definition sch_item.cpp:191
virtual bool IsConnectable() const
Definition sch_item.h:545
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:302
void SetLayer(SCH_LAYER_ID aLayer)
Definition sch_item.h:354
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:353
SCH_CONNECTION * Connection(const SCH_SHEET_PATH *aSheet=nullptr) const
Retrieve the connection associated with this object in the given sheet.
Definition sch_item.cpp:580
virtual std::vector< VECTOR2I > GetConnectionPoints() const
Add all the connection points for this item to aPoints.
Definition sch_item.h:558
void computeBreakPoint(const std::pair< SCH_LINE *, SCH_LINE * > &aSegments, VECTOR2I &aPosition, LINE_MODE mode, bool posture)
Compute the middle coordinate for 2 segments from the start point to aPosition with the segments kept...
int DrawSegments(const TOOL_EVENT &aEvent)
bool BreakSegmentsOnJunctions(SCH_COMMIT *aCommit, SCH_SCREEN *aScreen)
Test all junctions and bus entries in the schematic for intersections with wires and buses and breaks...
void setTransitions() override
Set up handlers for various events.
int AddJunctionsIfNeeded(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Handle the addition of junctions to a selection of objects.
SCH_JUNCTION * AddJunction(SCH_COMMIT *aCommit, SCH_SCREEN *aScreen, const VECTOR2I &aPos)
int UnfoldBus(const TOOL_EVENT &aEvent)
SCH_LINE * startSegments(SCH_COMMIT &aCommit, int aType, const VECTOR2D &aPos, SCH_LINE *aSegment=nullptr)
bool Init() override
Init() is called once upon a registration of the tool.
int TrimOverLappingWires(SCH_COMMIT *aCommit, SCH_SELECTION *aSelection)
Logic to remove wires when overlapping correct items.
const SCH_SHEET_PIN * getSheetPin(const VECTOR2I &aPosition)
Search for a sheet pin at a location.
SCH_LINE * doUnfoldBus(SCH_COMMIT &aCommit, const wxString &aNet, const std::optional< VECTOR2I > &aPos=std::nullopt)
Unfold the given bus from the given position.
SCH_LINE * getBusForUnfolding()
Choose a bus to unfold based on the current tool selection.
bool BreakSegments(SCH_COMMIT *aCommit, const VECTOR2I &aPoint, SCH_SCREEN *aScreen)
Check every wire and bus for a intersection at aPoint and break into two segments at aPoint if an int...
std::vector< SCH_LINE * > m_wires
static bool IsDrawingLineWireOrBus(const SELECTION &aSelection)
void BreakSegment(SCH_COMMIT *aCommit, SCH_LINE *aSegment, const VECTOR2I &aPoint, SCH_LINE **aNewSegment, SCH_SCREEN *aScreen)
Break a single segment into two at the specified point.
void simplifyWireList()
Iterate over the wire list and removes the null segments and overlapping segments to create a simplif...
int doDrawSegments(const TOOL_EVENT &aTool, SCH_COMMIT &aCommit, int aType, bool aQuitOnDraw)
void finishSegments(SCH_COMMIT &aCommit)
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:39
void SetStartPoint(const VECTOR2I &aPosition)
Definition sch_line.h:137
VECTOR2I GetEndPoint() const
Definition sch_line.h:145
VECTOR2I GetStartPoint() const
Definition sch_line.h:136
SEG GetSeg() const
Get the geometric aspect of the wire as a SEG.
Definition sch_line.h:155
SCH_LINE * MergeOverlap(SCH_SCREEN *aScreen, SCH_LINE *aLine, bool aCheckJunctions)
Check line against aLine to see if it overlaps and merge if it does.
Definition sch_line.cpp:547
bool IsNull() const
Definition sch_line.h:134
SCH_LINE * BreakAt(SCH_COMMIT *aCommit, const VECTOR2I &aPoint)
Break this segment into two at the specified point.
Definition sch_line.cpp:670
void SetEndPoint(const VECTOR2I &aPosition)
Definition sch_line.h:146
std::vector< SCH_LINE * > GetBusesAndWires(const VECTOR2I &aPosition, bool aIgnoreEndpoints=false) const
Return buses and wires passing through aPosition.
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
bool IsTerminalPoint(const VECTOR2I &aPosition, int aLayer) const
Test if aPosition is a connection point on aLayer.
SCH_LINE * GetBus(const VECTOR2I &aPosition, int aAccuracy=0, SCH_LINE_TEST_T aSearchType=ENTIRE_LENGTH_T) const
Definition sch_screen.h:511
std::vector< VECTOR2I > GetNeededJunctions(const std::deque< EDA_ITEM * > &aItems) const
Return the unique set of points belonging to aItems where a junction is needed.
std::vector< VECTOR2I > GetConnections() const
Collect a unique list of all possible connection points in the schematic.
BOX2I GetBoundingBox() const override
SCH_SCREEN * LastScreen()
const KIID_PATH & PathRef() const
Borrow the cached path until this sheet path is modified or destroyed.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
SHEET_SIDE GetSide() const
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:243
VECTOR2I GetPosition() const override
Definition sch_text.h:143
bool Init() override
Init() is called once upon a registration of the tool.
SCH_TOOL_BASE(const std::string &aName)
SCH_SELECTION_TOOL * m_selectionTool
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition seg.cpp:599
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
static bool Idle(const SELECTION &aSelection)
Test if there no items selected or being edited.
static bool ShowAlways(const SELECTION &aSelection)
The default condition function (always returns true).
static SELECTION_CONDITION OnlyTypes(std::vector< KICAD_T > aTypes)
Create a functor that tests if the selected items are only of given types.
virtual KIGFX::VIEW_ITEM * GetItem(unsigned int aIdx) const override
Definition selection.cpp:75
virtual unsigned int GetSize() const override
Return the number of stored items.
Definition selection.h:104
EDA_ITEM * Front() const
Definition selection.h:176
std::deque< EDA_ITEM * > & Items()
Definition selection.h:181
Spin style for labels of all kinds on schematics.
Definition sch_label.h:38
SCH_EDIT_FRAME * getModel() const
Definition tool_base.h:195
KIGFX::VIEW_CONTROLS * getViewControls() const
Definition tool_base.cpp:40
KIGFX::VIEW * getView() const
Definition tool_base.cpp:34
Generic, UI-independent tool event.
Definition tool_event.h:167
bool HasPosition() const
Returns if it this event has a valid position (true for mouse events and context-menu or hotkey-based...
Definition tool_event.h:256
bool DisableGridSnapping() const
Definition tool_event.h:367
const VECTOR2D Position() const
Return mouse cursor position in world coordinates.
Definition tool_event.h:289
int Modifier(int aMask=MD_MODIFIER_MASK) const
Return information about key modifiers state (Ctrl, Alt, etc.).
Definition tool_event.h:362
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:484
void SetContextMenu(ACTION_MENU *aMenu, CONTEXT_MENU_TRIGGER aTrigger=CMENU_BUTTON)
void Go(int(SCH_EDIT_FRAME::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
std::unique_ptr< TOOL_MENU > m_menu
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
@ LINE_BUS
Definition cursors.h:86
@ LINE_WIRE
Definition cursors.h:90
@ ARROW
Definition cursors.h:42
@ LINE_GRAPHIC
Definition cursors.h:88
#define _(s)
@ RECURSE
Definition eda_item.h:51
#define IS_CHANGED
Item was edited, and modified.
#define IS_NEW
New item, just created.
#define IS_BROKEN
Is a segment just broken by BreakSegment.
#define SKIP_STRUCT
flag indicating that the structure should be ignored
#define IS_MOVING
Item being moved.
@ ID_POPUP_SCH_UNFOLD_BUS_END
Definition eeschema_id.h:78
@ ID_POPUP_SCH_UNFOLD_BUS
Definition eeschema_id.h:77
@ LINE_MODE_90
@ LINE_MODE_45
@ LINE_MODE_FREE
GRID_HELPER_GRIDS
Definition grid_helper.h:55
@ GRID_GRAPHICS
Definition grid_helper.h:63
@ GRID_WIRES
Definition grid_helper.h:60
@ LAYER_WIRE
Definition layer_ids.h:474
@ LAYER_NOTES
Definition layer_ids.h:489
@ LAYER_BUS
Definition layer_ids.h:475
@ LAYER_BUS_JUNCTION
Definition layer_ids.h:520
std::vector< SCH_JUNCTION * > PreviewJunctions(const SCH_SCREEN *aScreen, const std::vector< class SCH_ITEM * > &aItems)
Determine the points where explicit junctions would be required if the given temporary items were com...
bool signbit(T v)
Integral version of std::signbit that works all compilers.
Definition kicad_algo.h:172
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ BUS
This item represents a bus vector.
Class to handle a set of SCH_ITEMs.
std::function< SCH_LINE *()> BUS_GETTER
static BUS_UNFOLD_PERSISTENT_SETTINGS busUnfoldPersistentSettings
SHEET_SIDE
Define the edge of the sheet that the sheet pin is positioned.
std::vector< wxString > members
Settings for bus unfolding that are persistent across invocations of the tool.
Query-time snapshot; reacquire after Update.
std::shared_ptr< const BUS_SCHEMA > schema
std::shared_ptr< const BUS_SCHEMA::NODE > tree
The parsed form of one bus text.
Definition conn_bus.h:48
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.
int delta
@ TA_CHOICE_MENU_CHOICE
Context menu choice.
Definition tool_event.h:94
@ TA_CHOICE_MENU_CLOSED
Context menu is closed, no matter whether anything has been chosen or not.
Definition tool_event.h:97
@ CMENU_NOW
Right now (after TOOL_INTERACTIVE::SetContextMenu).
Definition tool_event.h:152
@ MD_SHIFT
Definition tool_event.h:139
@ TC_COMMAND
Definition tool_event.h:53
@ BUT_LEFT
Definition tool_event.h:128
@ BUT_RIGHT
Definition tool_event.h:129
bool IsPointOnSegment(const VECTOR2I &aSegStart, const VECTOR2I &aSegEnd, const VECTOR2I &aTestPoint)
Test if aTestPoint is on line defined by aSegStart and aSegEnd.
Definition trigo.cpp:87
@ SCH_GROUP_T
Definition typeinfo.h:169
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_ITEM_LOCATE_BUS_T
Definition typeinfo.h:183
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_JUNCTION_T
Definition typeinfo.h:155
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707