KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_create_net_chain.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <map>
24
25#include <wx/msgdlg.h>
26
27#include <bitmaps.h>
29#include <widgets/wx_grid.h>
30#include <connection_graph.h>
32#include <sch_edit_frame.h>
33#include <sch_field.h>
34#include <sch_netchain.h>
35#include <sch_pin.h>
36#include <sch_reference_list.h>
37#include <sch_screen.h>
38#include <sch_sheet.h>
39#include <sch_sheet_pin.h>
40#include <sch_symbol.h>
41#include <schematic.h>
42#include <tool/tool_manager.h>
43#include <tools/sch_actions.h>
45#include <view/view.h>
46
47
50 m_frame( aParent ),
51 m_hint( aHint )
52{
55
56 // Override hardcoded wxFormBuilder font with system default bold
57 m_manualLabel->SetFont( GetFont().Bold() );
58
59 m_chainsGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
60 m_chainsGrid->EnableEditing( true ); // Override wxFB default; per-cell ReadOnly controls editability
61
62 // Set placeholder hint on filter (wxFormBuilder only sets tooltip, not hint)
63 m_filterInput->SetHint( _( "Filter chains by name or net..." ) );
64
66
67 // Pre-seed From/To from caller (e.g. current selection)
68 if( !m_hint.fromRef.IsEmpty() )
69 m_fromComponent->SetValue( m_hint.fromRef );
70
71 if( !m_hint.toRef.IsEmpty() )
72 m_toComponent->SetValue( m_hint.toRef );
73
75
76 // Rename buttons after SetupStandardButtons() which resets labels
77 m_sdbSizerOK->SetLabel( _( "Create" ) );
78 m_sdbSizerCancel->SetLabel( _( "Close" ) );
79
80 m_chainsGrid->Bind( wxEVT_GRID_CELL_CHANGED,
81 [this]( wxGridEvent& evt )
82 {
83 if( evt.GetCol() == 0 )
84 m_nameInput->SetValue( m_chainsGrid->GetCellValue( evt.GetRow(), 0 ) );
85 } );
86
89
91
93}
94
95
97{
98 // Clear highlighting on whichever sheet we last touched. The user may have navigated
99 // away via row selection, and we don't want stale brightening to outlive the dialog.
100 SCH_SCREEN* screen = !m_lastHighlightedSheet.empty() ? m_lastHighlightedSheet.LastScreen()
101 : ( m_frame ? m_frame->GetCurrentSheet().LastScreen()
102 : nullptr );
103 highlightChainNets( {}, screen );
104}
105
106
108{
109 return true;
110}
111
112
114{
115 if( !validateAndCreate() )
116 return false;
117
118 // Drop livePtr-bearing rows BEFORE the OnModify notify, then reload AFTER. OnModify()
119 // does not recalculate today, but if a future hook attaches a recalc to it, the cleared
120 // window keeps livePtrs from dangling across the destruction of m_potentialNetChains.
121 m_rows.clear();
122 m_filteredIndices.clear();
123 m_nameInput->Clear();
124 m_frame->OnModify();
126 rebuildGrid();
127
128 m_headerLabel->SetLabel(
129 wxString::Format( _( "Chain created (%d total). Select another or close." ), m_createdCount ) );
130
131 // Return false to keep the dialog open for multi-create
132 return false;
133}
134
135
137{
138 int gridRow = selectedRow();
139
140 if( gridRow < 0 || gridRow >= static_cast<int>( m_filteredIndices.size() ) )
141 {
142 wxMessageBox( _( "Please select a chain from the list." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
143 return false;
144 }
145
146 int dataIdx = m_filteredIndices[gridRow];
147
148 // Sync the edited name from the grid cell before using it
149 wxString editedName = m_chainsGrid->GetCellValue( gridRow, 0 );
150
151 if( !editedName.IsEmpty() )
152 m_rows[dataIdx].suggestedName = editedName;
153
154 // Use the Chain Name input field (or fall back to the grid cell name)
155 wxString name = m_nameInput->GetValue().Trim().Trim( false );
156
157 if( name.IsEmpty() )
158 name = m_rows[dataIdx].suggestedName;
159
160 if( name.IsEmpty() )
161 {
162 wxMessageBox( _( "Please enter a name for the net chain." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR,
163 this );
164 return false;
165 }
166
168 {
169 wxMessageBox( _( "Chain name cannot contain spaces, quotes, or parentheses." ),
170 _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
171 return false;
172 }
173
174 CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph();
175
176 if( !graph )
177 {
178 wxMessageBox( _( "Connection graph not available." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
179 return false;
180 }
181
182 if( graph->GetNetChainByName( name ) )
183 {
184 wxMessageBox( wxString::Format( _( "A net chain named '%s' already exists." ), name ), _( "Create Net Chain" ),
185 wxOK | wxICON_ERROR, this );
186 return false;
187 }
188
189 const POTENTIAL_ROW& prow = m_rows[dataIdx];
190
191 if( prow.livePtr )
192 {
193 SCH_NETCHAIN* committed = graph->CreateNetChainFromPotential( prow.livePtr, name );
194
195 if( !committed )
196 {
197 wxMessageBox( _( "Failed to create the net chain." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
198 return false;
199 }
200 }
201 else
202 {
203 std::set<SCH_SYMBOL*> symbols;
204
205 SCH_ITEM* fromItem = m_frame->Schematic().ResolveItem( prow.forceFromUuid, nullptr, true );
206 SCH_ITEM* toItem = m_frame->Schematic().ResolveItem( prow.forceToUuid, nullptr, true );
207
208 if( fromItem && fromItem->Type() == SCH_SYMBOL_T )
209 symbols.insert( static_cast<SCH_SYMBOL*>( fromItem ) );
210
211 if( toItem && toItem->Type() == SCH_SYMBOL_T )
212 symbols.insert( static_cast<SCH_SYMBOL*>( toItem ) );
213
214 SCH_NETCHAIN* committed = graph->CreateManualNetChain( name, symbols, prow.memberNets,
215 prow.forceFromPinUuid,
216 prow.forceToPinUuid,
217 prow.forceFromRef, prow.forceFromPinNum,
218 prow.forceToRef, prow.forceToPinNum );
219
220 if( !committed )
221 {
222 wxMessageBox( _( "Failed to create the manual net chain." ), _( "Create Net Chain" ),
223 wxOK | wxICON_ERROR, this );
224 return false;
225 }
226 }
227
229 return true;
230}
231
232
234{
235 if( m_rebuilding )
236 {
237 aEvent.Skip();
238 return;
239 }
240
241 int gridRow = aEvent.GetRow();
242
243 // Map grid row to m_rows index through filter
244 int dataIdx = ( gridRow >= 0 && gridRow < static_cast<int>( m_filteredIndices.size() ) )
245 ? m_filteredIndices[gridRow]
246 : -1;
247
248 updateMemberDetail( dataIdx );
249
250 if( dataIdx >= 0 )
251 {
252 // Sync the edited name from the grid cell (user may have edited column 0)
253 wxString editedName = m_chainsGrid->GetCellValue( gridRow, 0 );
254
255 if( !editedName.IsEmpty() )
256 m_rows[dataIdx].suggestedName = editedName;
257
258 m_nameInput->SetValue( m_rows[dataIdx].suggestedName );
259
261 }
262 else
263 {
264 SCH_SCREEN* screen = !m_lastHighlightedSheet.empty() ? m_lastHighlightedSheet.LastScreen()
265 : m_frame->GetCurrentSheet().LastScreen();
266 highlightChainNets( {}, screen );
267 }
268
269 aEvent.Skip();
270}
271
272
273void DIALOG_CREATE_NET_CHAIN::OnRefreshClicked( wxCommandEvent& aEvent )
274{
275 if( !m_frame->Schematic().ConnectionGraph() )
276 return;
277
278 // Clear any focus-hint filter so Refresh truly restores the full list. Otherwise the
279 // empty-state message ("Use Refresh to restore the full list.") would refresh and
280 // immediately re-apply the same no-match filter.
281 m_filterInput->Clear();
282
284 recalculateAndReload( true );
285}
286
287
289{
290 // Drop all references into m_potentialNetChains BEFORE any recalc clears that vector.
291 // This is the load-bearing step: while m_rows is non-empty, livePtr values may be live
292 // pointers into the pool, and Recalculate() destroys those entries.
293 m_rows.clear();
294 m_filteredIndices.clear();
295
296 if( aRunRecalculate )
297 {
298 if( CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph() )
299 graph->Recalculate( m_frame->Schematic().BuildSheetListSortedByPageNumbers(), true );
300 }
301
303 rebuildGrid();
304}
305
306
307void DIALOG_CREATE_NET_CHAIN::OnFindPathClicked( wxCommandEvent& aEvent )
308{
309 wxString fromRef = m_fromComponent->GetValue().Trim().Trim( false );
310 wxString toRef = m_toComponent->GetValue().Trim().Trim( false );
311
312 if( fromRef.IsEmpty() || toRef.IsEmpty() )
313 {
314 wxMessageBox( _( "Please select both a From and To component." ), _( "Find Path" ),
315 wxOK | wxICON_ERROR, this );
316 return;
317 }
318
319 if( fromRef == toRef )
320 {
321 wxMessageBox( _( "From and To must be different components." ), _( "Find Path" ),
322 wxOK | wxICON_ERROR, this );
323 return;
324 }
325
326 // Find the symbols by reference
328 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
329
330 SCH_SYMBOL* fromSym = nullptr;
331 SCH_SYMBOL* toSym = nullptr;
332 SCH_SHEET_PATH fromSheet, toSheet;
333
334 for( const SCH_REFERENCE& ref : refs )
335 {
336 if( ref.GetRef() == fromRef && ref.GetSymbol() )
337 {
338 fromSym = ref.GetSymbol();
339 fromSheet = ref.GetSheetPath();
340 }
341
342 if( ref.GetRef() == toRef && ref.GetSymbol() )
343 {
344 toSym = ref.GetSymbol();
345 toSheet = ref.GetSheetPath();
346 }
347 }
348
349 if( !fromSym || !toSym )
350 {
351 wxMessageBox( wxString::Format( _( "Could not find component '%s' or '%s'." ),
352 fromRef, toRef ),
353 _( "Find Path" ), wxOK | wxICON_ERROR, this );
354 return;
355 }
356
357 CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph();
358
359 if( !graph )
360 return;
361
362 // Find ALL unique chains between all pin pairs of the two components
363 struct FOUND_CHAIN
364 {
366 wxString fromPin;
367 wxString toPin;
368 std::set<wxString> nets;
369 };
370
371 std::vector<FOUND_CHAIN> foundChains;
372 std::set<SCH_NETCHAIN*> seenChains;
373
374 std::vector<SCH_PIN*> fromPins = fromSym->GetPins( &fromSheet );
375 std::vector<SCH_PIN*> toPins = toSym->GetPins( &toSheet );
376
377 for( SCH_PIN* pinA : fromPins )
378 {
379 for( SCH_PIN* pinB : toPins )
380 {
382
383 if( chain && !seenChains.count( chain ) )
384 {
385 seenChains.insert( chain );
386
387 FOUND_CHAIN fc;
388 fc.chain = chain;
389 fc.fromPin = fromRef + wxT( ":" ) + pinA->GetNumber();
390 fc.toPin = toRef + wxT( ":" ) + pinB->GetNumber();
391 fc.nets = chain->GetNets();
392 foundChains.push_back( fc );
393 }
394 }
395 }
396
397 if( foundChains.empty() )
398 {
399 // No potential chain found — offer force-create
400 int answer = wxMessageBox(
401 wxString::Format( _( "No net chain path found between %s and %s through "
402 "passthrough components.\n\n"
403 "Would you like to force-create a manual chain link "
404 "between these components?" ),
405 fromRef, toRef ),
406 _( "Find Path" ), wxYES_NO | wxICON_QUESTION, this );
407
408 if( answer == wxYES )
409 {
410 std::set<wxString> fromNets, toNets;
411 SCH_PIN* fromTerminalPin = nullptr;
412 SCH_PIN* toTerminalPin = nullptr;
413
414 for( SCH_PIN* pin : fromPins )
415 {
416 if( pin->Connection() && !pin->Connection()->Name().IsEmpty() )
417 {
418 fromNets.insert( pin->Connection()->Name() );
419
420 if( !fromTerminalPin )
421 fromTerminalPin = pin;
422 }
423 }
424
425 for( SCH_PIN* pin : toPins )
426 {
427 if( pin->Connection() && !pin->Connection()->Name().IsEmpty() )
428 {
429 toNets.insert( pin->Connection()->Name() );
430
431 if( !toTerminalPin )
432 toTerminalPin = pin;
433 }
434 }
435
436 if( !fromTerminalPin || !toTerminalPin )
437 {
438 wxMessageBox( _( "Selected components have no connected pins to anchor a manual chain." ),
439 _( "Find Path" ), wxOK | wxICON_ERROR, this );
440 return;
441 }
442
443 // Replace grid with just this one force-created entry. Clear m_filteredIndices
444 // before mutating m_rows so rebuildGrid()'s "sync edited names back" pass
445 // cannot index stale grid rows into freshly-replaced m_rows entries.
446 m_filteredIndices.clear();
447 m_rows.clear();
448
449 POTENTIAL_ROW row;
450 row.livePtr = nullptr;
451 row.isManual = true;
452 row.forceFromUuid = fromSym->m_Uuid;
453 row.forceToUuid = toSym->m_Uuid;
454 row.forceFromPinUuid = fromTerminalPin->m_Uuid;
455 row.forceToPinUuid = toTerminalPin->m_Uuid;
456 row.forceFromRef = fromRef;
457 row.forceToRef = toRef;
458 row.forceFromPinNum = fromTerminalPin->GetNumber();
459 row.forceToPinNum = toTerminalPin->GetNumber();
460 row.suggestedName = fromRef + wxT( "_" ) + toRef;
461 row.terminals = fromRef + wxT( " \u2192 " ) + toRef;
462 row.memberNets.insert( fromNets.begin(), fromNets.end() );
463 row.memberNets.insert( toNets.begin(), toNets.end() );
464
465 m_rows.push_back( std::move( row ) );
466 rebuildGrid();
467
469 m_headerLabel->SetLabel( wxString::Format(
470 _( "Showing manual chain between %s and %s. Use Refresh to restore all." ),
471 fromRef, toRef ) );
472 }
473
474 return;
475 }
476
477 // Filter out chains that are already committed
478 std::set<wxString> committedNames;
479
480 for( const std::unique_ptr<SCH_NETCHAIN>& chain : graph->GetCommittedNetChains() )
481 {
482 if( chain )
483 committedNames.insert( chain->GetName() );
484 }
485
486 std::vector<FOUND_CHAIN> uncommitted;
487
488 for( const FOUND_CHAIN& fc : foundChains )
489 {
490 bool isCommitted = false;
491
492 for( const wxString& net : fc.nets )
493 {
494 if( SCH_NETCHAIN* existing = graph->GetNetChainForNet( net ) )
495 {
496 if( committedNames.count( existing->GetName() ) )
497 {
498 isCommitted = true;
499 break;
500 }
501 }
502 }
503
504 if( !isCommitted )
505 uncommitted.push_back( fc );
506 }
507
508 if( uncommitted.empty() )
509 {
510 wxMessageBox( wxString::Format( _( "All %zu net chain paths between %s and %s are "
511 "already committed as net chains." ),
512 foundChains.size(), fromRef, toRef ),
513 _( "Find Path" ), wxOK | wxICON_INFORMATION, this );
514 return;
515 }
516
517 // Replace grid contents with only the found paths. Clear m_filteredIndices first so
518 // rebuildGrid()'s "sync edited names back" pass cannot index stale grid rows into
519 // freshly-replaced m_rows entries.
520 m_filteredIndices.clear();
521 m_rows.clear();
522
523 for( const FOUND_CHAIN& fc : uncommitted )
524 {
525 POTENTIAL_ROW row;
526 row.livePtr = fc.chain;
527 row.memberNets = fc.nets;
528 row.isManual = true;
529 row.terminals = fc.fromPin + wxT( " \u2192 " ) + fc.toPin;
530 row.suggestedName = fromRef + wxT( "_" ) + toRef;
531
532 if( uncommitted.size() > 1 )
533 {
534 wxString pinNum = fc.fromPin.AfterLast( ':' );
535 row.suggestedName = fromRef + wxT( "_" ) + toRef + wxT( "_" ) + pinNum;
536 }
537
538 m_rows.push_back( std::move( row ) );
539 }
540
541 rebuildGrid();
542
544
545 m_headerLabel->SetLabel( wxString::Format(
546 _( "Showing %zu path(s) between %s and %s. Use Refresh to restore all." ),
547 uncommitted.size(), fromRef, toRef ) );
548}
549
550
552{
553 m_fromComponent->Clear();
554 m_toComponent->Clear();
555
557 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
558
559 std::set<wxString> refNames;
560
561 for( const SCH_REFERENCE& ref : refs )
562 {
563 if( ref.GetSymbol() )
564 refNames.insert( ref.GetRef() );
565 }
566
567 for( const wxString& name : refNames )
568 {
569 m_fromComponent->Append( name );
570 m_toComponent->Append( name );
571 }
572}
573
574
576{
577 m_rows.clear();
578
579 CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph();
580
581 if( !graph )
582 return;
583
584 std::set<wxString> committedNames;
585
586 for( const std::unique_ptr<SCH_NETCHAIN>& chain : graph->GetCommittedNetChains() )
587 {
588 if( chain )
589 committedNames.insert( chain->GetName() );
590 }
591
592 for( const std::unique_ptr<SCH_NETCHAIN>& chain : graph->GetPotentialNetChains() )
593 {
594 if( !chain )
595 continue;
596
597 // Skip potentials that already match a committed chain by net membership
598 bool alreadyCommitted = false;
599
600 for( const wxString& net : chain->GetNets() )
601 {
602 if( SCH_NETCHAIN* existing = graph->GetNetChainForNet( net ) )
603 {
604 if( committedNames.count( existing->GetName() ) )
605 {
606 alreadyCommitted = true;
607 break;
608 }
609 }
610 }
611
612 if( alreadyCommitted )
613 continue;
614
615 POTENTIAL_ROW row;
616 row.livePtr = chain.get();
617 row.memberNets = chain->GetNets();
618
619 // Suggest name from the longest member net name
620 for( const wxString& net : row.memberNets )
621 {
622 if( net.length() > row.suggestedName.length() )
623 row.suggestedName = net;
624 }
625
626 // Surface the terminal refs in the row so a ref-keyed filter (e.g. right-click on a
627 // single symbol → focus on chains touching that symbol) can match them.
628 wxString terminalA = chain->GetTerminalRef( 0 );
629 wxString terminalB = chain->GetTerminalRef( 1 );
630
631 if( !chain->GetTerminalPinNum( 0 ).IsEmpty() )
632 terminalA += wxT( ":" ) + chain->GetTerminalPinNum( 0 );
633
634 if( !chain->GetTerminalPinNum( 1 ).IsEmpty() )
635 terminalB += wxT( ":" ) + chain->GetTerminalPinNum( 1 );
636
637 if( !terminalA.IsEmpty() || !terminalB.IsEmpty() )
638 row.terminals = terminalA + wxT( " → " ) + terminalB;
639
640 m_rows.push_back( std::move( row ) );
641 }
642}
643
644
645void DIALOG_CREATE_NET_CHAIN::OnFilterChanged( wxCommandEvent& aEvent )
646{
647 // Sync any edited names back before rebuilding
648 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
649 {
650 int dataIdx = m_filteredIndices[gi];
651
652 if( dataIdx >= 0 && dataIdx < static_cast<int>( m_rows.size() ) )
653 {
654 wxString edited = m_chainsGrid->GetCellValue( static_cast<int>( gi ), 0 );
655
656 if( !edited.IsEmpty() )
657 m_rows[dataIdx].suggestedName = edited;
658 }
659 }
660
661 rebuildGrid();
662}
663
664
666{
667 m_rebuilding = true;
668
669 // Invariant: callers that replace m_rows must clear m_filteredIndices first. Otherwise
670 // this sync-edits pass would copy grid cell values into the wrong (or out-of-bounds)
671 // m_rows entries. The dataIdx bounds check below is belt-and-suspenders.
672 // Sync any edited names back from the grid before clearing it
673 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
674 {
675 int gridRow = static_cast<int>( gi );
676 int dataIdx = m_filteredIndices[gi];
677
678 if( gridRow < m_chainsGrid->GetNumberRows()
679 && dataIdx >= 0 && dataIdx < static_cast<int>( m_rows.size() ) )
680 {
681 wxString edited = m_chainsGrid->GetCellValue( gridRow, 0 );
682
683 if( !edited.IsEmpty() )
684 m_rows[dataIdx].suggestedName = edited;
685 }
686 }
687
688 if( m_chainsGrid->GetNumberRows() )
689 m_chainsGrid->DeleteRows( 0, m_chainsGrid->GetNumberRows() );
690
691 // Build filtered index list
692 m_filteredIndices.clear();
693 wxString filter = m_filterInput->GetValue().Lower().Trim().Trim( false );
694
695 for( size_t i = 0; i < m_rows.size(); ++i )
696 {
697 if( filter.IsEmpty() )
698 {
699 m_filteredIndices.push_back( static_cast<int>( i ) );
700 continue;
701 }
702
703 const POTENTIAL_ROW& row = m_rows[i];
704
705 // Match against suggested name
706 if( row.suggestedName.Lower().Contains( filter ) )
707 {
708 m_filteredIndices.push_back( static_cast<int>( i ) );
709 continue;
710 }
711
712 // Match against terminal refs/pins (e.g. "J1:1 → J2:1") so a focus hint based on a
713 // selected symbol or pin reference catches chains anchored at that ref.
714 if( !row.terminals.IsEmpty() && row.terminals.Lower().Contains( filter ) )
715 {
716 m_filteredIndices.push_back( static_cast<int>( i ) );
717 continue;
718 }
719
720 // Match against member net names
721 bool netMatch = false;
722
723 for( const wxString& net : row.memberNets )
724 {
725 if( net.Lower().Contains( filter ) )
726 {
727 netMatch = true;
728 break;
729 }
730 }
731
732 if( netMatch )
733 m_filteredIndices.push_back( static_cast<int>( i ) );
734 }
735
736 m_chainsGrid->AppendRows( static_cast<int>( m_filteredIndices.size() ) );
737
738 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
739 {
740 const POTENTIAL_ROW& row = m_rows[m_filteredIndices[gi]];
741 int r = static_cast<int>( gi );
742
743 m_chainsGrid->SetCellValue( r, 0, row.suggestedName );
744 m_chainsGrid->SetCellValue( r, 1, wxString::Format( wxT( "%zu" ), row.memberNets.size() ) );
745
746 // Column 0 (Suggested Name) is editable, columns 1 and 2 are read-only
747 m_chainsGrid->SetReadOnly( r, 1, true );
748 m_chainsGrid->SetReadOnly( r, 2, true );
749
750 wxString preview;
751
752 if( !row.terminals.IsEmpty() )
753 preview = wxT( "[" ) + row.terminals + wxT( "] " );
754
755 for( const wxString& net : row.memberNets )
756 {
757 if( !preview.IsEmpty() && preview.Last() != ' ' )
758 preview += wxT( ", " );
759
760 preview += net;
761 }
762
763 m_chainsGrid->SetCellValue( r, 2, preview );
764 }
765
766 m_membersListBox->Clear();
767 m_membersLabel->SetLabel( _( "Member Nets" ) );
768
769 // m_nameInput is intentionally not cleared here. OnFilterChanged calls rebuildGrid, so
770 // clearing would erase a name the user is typing while narrowing the filter list. The
771 // explicit clear in TransferDataFromWindow handles the post-create reset, and the row
772 // selection handler overwrites the field with each row's suggested name on click.
773
774 m_rebuilding = false;
775}
776
777
779{
780 m_membersListBox->Clear();
781
782 if( aRow < 0 || aRow >= static_cast<int>( m_rows.size() ) )
783 {
784 m_membersLabel->SetLabel( _( "Member Nets" ) );
785 return;
786 }
787
788 const POTENTIAL_ROW& row = m_rows[aRow];
789
790 wxString label;
791
792 if( !row.terminals.IsEmpty() )
793 {
794 label = wxString::Format( _( "Member Nets \u2014 %s (%zu) [%s]" ), row.suggestedName, row.memberNets.size(),
795 row.terminals );
796 }
797 else
798 {
799 label = wxString::Format( _( "Member Nets \u2014 %s (%zu)" ), row.suggestedName, row.memberNets.size() );
800 }
801
802 m_membersLabel->SetLabel( label );
803
804 wxArrayString sorted;
805 sorted.reserve( row.memberNets.size() );
806
807 for( const wxString& net : row.memberNets )
808 sorted.Add( net );
809
810 sorted.Sort();
811 m_membersListBox->Append( sorted );
812}
813
814
816{
817 wxArrayInt rows = m_chainsGrid->GetSelectedRows();
818
819 if( !rows.empty() )
820 return rows.front();
821
822 // The grid cursor can survive a DeleteRows/AppendRows rebuild and point at an index
823 // beyond the new row count. Clamp before returning so callers don't dereference a
824 // stale data slot.
825 int cursor = m_chainsGrid->GetGridCursorRow();
826
827 if( cursor < 0 || cursor >= m_chainsGrid->GetNumberRows() )
828 return -1;
829
830 return cursor;
831}
832
833
834BOX2I DIALOG_CREATE_NET_CHAIN::highlightChainNets( const std::set<wxString>& aNets, SCH_SCREEN* aScreen )
835{
836 BOX2I highlightedBBox;
837
838 if( !m_frame || !aScreen )
839 return highlightedBBox;
840
841 // Only the current sheet's view can be live-updated; brightening flags on items belonging
842 // to other screens still apply visually next time that sheet is shown.
843 bool onCurrentSheet = aScreen == m_frame->GetCurrentSheet().LastScreen();
844 KIGFX::VIEW* view = onCurrentSheet ? m_frame->GetCanvas()->GetView() : nullptr;
845 std::vector<EDA_ITEM*> itemsToRedraw;
846
847 auto recordHighlight = [&]( SCH_ITEM* aItem )
848 {
849 if( highlightedBBox.GetWidth() == 0 && highlightedBBox.GetHeight() == 0 )
850 highlightedBBox = aItem->GetBoundingBox();
851 else
852 highlightedBBox.Merge( aItem->GetBoundingBox() );
853 };
854
855 for( SCH_ITEM* item : aScreen->Items() )
856 {
857 if( !item || !item->IsConnectable() )
858 continue;
859
860 SCH_ITEM* redrawItem = nullptr;
861
862 if( item->Type() == SCH_SYMBOL_T )
863 {
864 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
865 bool anyPinHighlighted = false;
866
867 for( SCH_PIN* pin : symbol->GetPins() )
868 {
869 SCH_CONNECTION* pinConn = pin->Connection();
870
871 if( pinConn && !aNets.empty() )
872 {
873 if( !pin->IsBrightened() && aNets.count( pinConn->Name() ) )
874 {
875 pin->SetBrightened();
876 redrawItem = symbol;
877 anyPinHighlighted = true;
878 }
879 else if( pin->IsBrightened() && !aNets.count( pinConn->Name() ) )
880 {
881 pin->ClearBrightened();
882 redrawItem = symbol;
883 }
884 else if( pin->IsBrightened() )
885 {
886 anyPinHighlighted = true;
887 }
888 }
889 else if( pin->IsBrightened() )
890 {
891 pin->ClearBrightened();
892 redrawItem = symbol;
893 }
894 }
895
896 if( anyPinHighlighted )
897 recordHighlight( symbol );
898 }
899 else
900 {
901 SCH_CONNECTION* itemConn = item->Connection();
902
903 if( itemConn && !aNets.empty() )
904 {
905 if( !item->IsBrightened() && aNets.count( itemConn->Name() ) )
906 {
907 item->SetBrightened();
908 redrawItem = item;
909 recordHighlight( item );
910 }
911 else if( item->IsBrightened() && !aNets.count( itemConn->Name() ) )
912 {
913 item->ClearBrightened();
914 redrawItem = item;
915 }
916 else if( item->IsBrightened() )
917 {
918 recordHighlight( item );
919 }
920 }
921 else if( item->IsBrightened() )
922 {
923 item->ClearBrightened();
924 redrawItem = item;
925 }
926 }
927
928 if( redrawItem )
929 itemsToRedraw.push_back( redrawItem );
930 }
931
932 if( !itemsToRedraw.empty() && view )
933 {
934 for( EDA_ITEM* redrawItem : itemsToRedraw )
935 view->Update( static_cast<KIGFX::VIEW_ITEM*>( redrawItem ), KIGFX::REPAINT );
936
937 m_frame->GetCanvas()->Refresh();
938 }
939
940 return highlightedBBox;
941}
942
943
945{
946 if( aRow.cachedSheetResolved )
947 return aRow.cachedSheet;
948
949 aRow.cachedSheetResolved = true;
950 aRow.cachedSheet = m_frame->GetCurrentSheet(); // safe default
951
952 // Prefer the deterministic terminal ref recorded with the chain. std::set<SCH_SYMBOL*>
953 // ordering depends on pointer values and is unstable across runs, so we never key off
954 // GetSymbols().begin() here.
955 wxString targetRef;
956
957 if( aRow.livePtr )
958 {
959 targetRef = aRow.livePtr->GetTerminalRef( 0 );
960
961 if( targetRef.IsEmpty() )
962 targetRef = aRow.livePtr->GetTerminalRef( 1 );
963 }
964 else
965 {
966 targetRef = aRow.forceFromRef;
967
968 if( targetRef.IsEmpty() )
969 targetRef = aRow.forceToRef;
970 }
971
972 if( !targetRef.IsEmpty() )
973 {
975 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
976
977 for( const SCH_REFERENCE& ref : refs )
978 {
979 if( ref.GetRef() == targetRef && ref.GetSymbol() )
980 {
981 aRow.cachedSheet = ref.GetSheetPath();
982 return aRow.cachedSheet;
983 }
984 }
985 }
986
987 // Fallback: scan every sheet for an item whose connection matches a member net.
988 for( const SCH_SHEET_PATH& path : m_frame->Schematic().Hierarchy() )
989 {
990 SCH_SCREEN* screen = path.LastScreen();
991
992 if( !screen )
993 continue;
994
995 for( SCH_ITEM* item : screen->Items() )
996 {
997 if( !item || !item->IsConnectable() )
998 continue;
999
1000 if( SCH_CONNECTION* conn = item->Connection() )
1001 {
1002 if( aRow.memberNets.count( conn->Name() ) )
1003 {
1004 aRow.cachedSheet = path;
1005 return aRow.cachedSheet;
1006 }
1007 }
1008 }
1009 }
1010
1011 return aRow.cachedSheet;
1012}
1013
1014
1016{
1017 if( !m_frame )
1018 return;
1019
1020 SCH_SHEET_PATH targetPath = findSheetForRow( aRow );
1021
1022 // Clear leftover brightening on the previously-touched sheet first when it differs from
1023 // the new target. Otherwise navigating from row A (sheet 1) to row B (sheet 2) leaves
1024 // sheet 1's items lit when the user pages back. Skip when the path is unchanged so we
1025 // don't double-walk the same screen.
1026 if( !m_lastHighlightedSheet.empty() && m_lastHighlightedSheet != targetPath )
1027 highlightChainNets( {}, m_lastHighlightedSheet.LastScreen() );
1028
1029 if( targetPath != m_frame->GetCurrentSheet() )
1030 {
1031 // SCH_ACTIONS::changeSheet wraps cancelInteractive, selectionClear, zoom-state save,
1032 // history push, and DisplayCurrentSheet — same path eeschema cross-probing uses.
1033 m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>( SCH_ACTIONS::changeSheet, &targetPath );
1034 }
1035
1036 SCH_SCREEN* screen = m_frame->GetCurrentSheet().LastScreen();
1037 BOX2I bbox = highlightChainNets( aRow.memberNets, screen );
1038
1039 if( bbox.GetWidth() > 0 || bbox.GetHeight() > 0 )
1040 {
1041 if( SCH_SELECTION_TOOL* selTool = m_frame->GetToolManager()->GetTool<SCH_SELECTION_TOOL>() )
1042 selTool->ZoomFitCrossProbeBBox( bbox );
1043 }
1044
1045 m_lastHighlightedSheet = m_frame->GetCurrentSheet();
1046}
1047
1048
1050{
1051 if( m_filteredIndices.empty() )
1052 return;
1053
1054 int dataIdx = m_filteredIndices[0];
1055 m_chainsGrid->SelectRow( 0 );
1056 m_chainsGrid->SetGridCursor( 0, 0 );
1057 updateMemberDetail( dataIdx );
1058 m_nameInput->SetValue( m_rows[dataIdx].suggestedName );
1060}
1061
1062
1064{
1065 // Both refs set: existing two-component find-path flow.
1066 if( !m_hint.fromRef.IsEmpty() && !m_hint.toRef.IsEmpty() )
1067 {
1068 wxCommandEvent dummy;
1070 return;
1071 }
1072
1073 wxString filterValue;
1074 wxString hintLabel;
1075
1076 if( !m_hint.netName.IsEmpty() )
1077 {
1078 filterValue = m_hint.netName;
1079 hintLabel = wxString::Format( _( "net '%s'" ), m_hint.netName );
1080 }
1081 else if( !m_hint.fromRef.IsEmpty() )
1082 {
1083 filterValue = m_hint.fromRef;
1084 hintLabel = wxString::Format( _( "component %s" ), m_hint.fromRef );
1085 }
1086 else if( !m_hint.toRef.IsEmpty() )
1087 {
1088 filterValue = m_hint.toRef;
1089 hintLabel = wxString::Format( _( "component %s" ), m_hint.toRef );
1090 }
1091 else
1092 {
1093 return; // No hint supplied — leave the dialog showing the full list.
1094 }
1095
1096 m_filterInput->ChangeValue( filterValue );
1097
1098 wxCommandEvent dummy;
1100
1101 if( m_filteredIndices.size() == 1 )
1102 {
1104 }
1105 else if( m_filteredIndices.empty() )
1106 {
1107 m_headerLabel->SetLabel( wxString::Format(
1108 _( "No uncommitted chains match the selected %s. Use Refresh to restore the full list." ),
1109 hintLabel ) );
1110 }
1111}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
Calculate the connectivity of a schematic and generates netlists.
SCH_NETCHAIN * GetNetChainByName(const wxString &aName)
SCH_NETCHAIN * GetNetChainForNet(const wxString &aNet)
SCH_NETCHAIN * CreateNetChainFromPotential(SCH_NETCHAIN *aPotential, const wxString &aName)
Promote a potential net chain to an actual user net chain with the provided name.
const std::vector< std::unique_ptr< SCH_NETCHAIN > > & GetPotentialNetChains() const
Potential net chains are inferred groupings produced by RebuildNetChains() but not yet user-committed...
SCH_NETCHAIN * CreateManualNetChain(const wxString &aName, const std::set< class SCH_SYMBOL * > &aSymbols, const std::set< wxString > &aNets, const KIID &aTerminalPinA, const KIID &aTerminalPinB, const wxString &aRefA, const wxString &aPinNumA, const wxString &aRefB, const wxString &aPinNumB)
Commit a manually-defined net chain that the inferred-potential pass did not produce.
SCH_NETCHAIN * FindPotentialNetChainBetweenPins(SCH_PIN *aPinA, SCH_PIN *aPinB)
Locate a potential net chain that contains both pins (by subgraph net membership).
const std::vector< std::unique_ptr< SCH_NETCHAIN > > & GetCommittedNetChains() const
Return user-created (committed) net chains (legacy accessor retained under net-chain API).
DIALOG_CREATE_NET_CHAIN_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Create Net Chain"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void navigateAndHighlightChain(POTENTIAL_ROW &aRow)
Switch to the sheet owning aRow (if different from the current sheet), brighten the chain's nets ther...
BOX2I highlightChainNets(const std::set< wxString > &aNets, SCH_SCREEN *aScreen)
Walk aScreen and brighten/un-brighten items whose connection name is in aNets.
void applyFocusHint()
Apply the focus hint after rows are loaded — set the filter input, optionally trigger the existing tw...
std::vector< POTENTIAL_ROW > m_rows
void recalculateAndReload(bool aRunRecalculate)
Tear down row state, optionally trigger a connectivity recalculation, then reload rows and refresh th...
void autoSelectFirstRow()
Position the grid cursor on the first filtered row, sync the name input, run the member-detail update...
SCH_SHEET_PATH m_lastHighlightedSheet
empty when no prior highlight
void OnChainSelected(wxGridEvent &aEvent) override
void OnRefreshClicked(wxCommandEvent &aEvent) override
std::vector< int > m_filteredIndices
indices into m_rows shown in grid
void OnFindPathClicked(wxCommandEvent &aEvent) override
DIALOG_CREATE_NET_CHAIN(SCH_EDIT_FRAME *aParent, const FOCUS_HINT &aHint={})
void OnFilterChanged(wxCommandEvent &aEvent) override
const SCH_SHEET_PATH & findSheetForRow(POTENTIAL_ROW &aRow)
Resolve and cache the sheet path to navigate to for aRow.
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:100
const KIID m_Uuid
Definition eda_item.h:528
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:86
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:67
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition view.cpp:1828
static TOOL_ACTION changeSheet
Each graphical item can have a SCH_CONNECTION describing its logical connection (to a bus or net).
wxString Name(bool aIgnoreSheet=false) const
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:168
A net chain is a collection of nets that are connected together through passive components.
const wxString & GetTerminalRef(int aIdx) const
static bool IsValidName(const wxString &aName)
const wxString & GetNumber() const
Definition sch_pin.h:127
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
A helper to define a symbol's reference designator in a schematic.
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:119
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:76
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
#define _(s)
@ REPAINT
Item needs to be redrawn.
Definition view_item.h:58
@ SYMBOL_FILTER_ALL
std::vector< FAB_LAYER_COLOR > dummy
Optional context derived from the user selection that opened the dialog.
Row backing data for the chains grid.
SCH_SHEET_PATH cachedSheet
Lazily-resolved sheet to navigate to when this row is selected.
KIID forceFromUuid
For force-created rows without a livePtr.
SCH_NETCHAIN * livePtr
null for force-created manual rows
std::string path
KIBIS_PIN * pin
const SHAPE_LINE_CHAIN chain
@ SCH_SYMBOL_T
Definition typeinfo.h:173