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
17 * along with this program. If not, see <https://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>
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 if( m_frame )
102 : m_frame->GetCurrentSheet() );
103}
104
105
107{
108 return true;
109}
110
111
113{
114 if( !validateAndCreate() )
115 return false;
116
117 // Drop livePtr-bearing rows BEFORE the OnModify notify, then reload AFTER. OnModify()
118 // does not recalculate today, but if a future hook attaches a recalc to it, the cleared
119 // window keeps livePtrs from dangling across the destruction of m_potentialNetChains.
120 m_rows.clear();
121 m_filteredIndices.clear();
122 m_nameInput->Clear();
123 m_frame->OnModify();
125 rebuildGrid();
126
127 m_headerLabel->SetLabel(
128 wxString::Format( _( "Chain created (%d total). Select another or close." ), m_createdCount ) );
129
130 // Return false to keep the dialog open for multi-create
131 return false;
132}
133
134
136{
137 int gridRow = selectedRow();
138
139 if( gridRow < 0 || gridRow >= static_cast<int>( m_filteredIndices.size() ) )
140 {
141 wxMessageBox( _( "Please select a chain from the list." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
142 return false;
143 }
144
145 int dataIdx = m_filteredIndices[gridRow];
146
147 // Sync the edited name from the grid cell before using it
148 wxString editedName = m_chainsGrid->GetCellValue( gridRow, 0 );
149
150 if( !editedName.IsEmpty() )
151 m_rows[dataIdx].suggestedName = editedName;
152
153 // Use the Chain Name input field (or fall back to the grid cell name)
154 wxString name = m_nameInput->GetValue().Trim().Trim( false );
155
156 if( name.IsEmpty() )
157 name = m_rows[dataIdx].suggestedName;
158
159 if( name.IsEmpty() )
160 {
161 wxMessageBox( _( "Please enter a name for the net chain." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR,
162 this );
163 return false;
164 }
165
167 {
168 wxMessageBox( _( "Chain name cannot contain spaces, quotes, or parentheses." ),
169 _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
170 return false;
171 }
172
173 auto& chains = m_frame->Schematic().NetChains();
174
175 if( chains.GetNetChainByName( name ) )
176 {
177 wxMessageBox( wxString::Format( _( "A net chain named '%s' already exists." ), name ), _( "Create Net Chain" ),
178 wxOK | wxICON_ERROR, this );
179 return false;
180 }
181
182 const POTENTIAL_ROW& prow = m_rows[dataIdx];
183
184 if( prow.livePtr )
185 {
186 SCH_NETCHAIN* committed = chains.CreateNetChainFromPotential( prow.livePtr, name );
187
188 if( !committed )
189 {
190 wxMessageBox( _( "Failed to create the net chain." ), _( "Create Net Chain" ), wxOK | wxICON_ERROR, this );
191 return false;
192 }
193 }
194 else
195 {
196 std::set<SCH_SYMBOL*> symbols;
197
198 SCH_ITEM* fromItem = m_frame->Schematic().ResolveItem( prow.forceFromUuid, nullptr, true );
199 SCH_ITEM* toItem = m_frame->Schematic().ResolveItem( prow.forceToUuid, nullptr, true );
200
201 if( fromItem && fromItem->Type() == SCH_SYMBOL_T )
202 symbols.insert( static_cast<SCH_SYMBOL*>( fromItem ) );
203
204 if( toItem && toItem->Type() == SCH_SYMBOL_T )
205 symbols.insert( static_cast<SCH_SYMBOL*>( toItem ) );
206
207 SCH_NETCHAIN* committed = chains.CreateManualNetChain( name, symbols, prow.memberNets,
208 prow.forceFromPinUuid,
209 prow.forceToPinUuid,
210 prow.forceFromRef, prow.forceFromPinNum,
211 prow.forceToRef, prow.forceToPinNum );
212
213 if( !committed )
214 {
215 wxMessageBox( _( "Failed to create the manual net chain." ), _( "Create Net Chain" ),
216 wxOK | wxICON_ERROR, this );
217 return false;
218 }
219 }
220
222 return true;
223}
224
225
227{
228 if( m_rebuilding )
229 {
230 aEvent.Skip();
231 return;
232 }
233
234 int gridRow = aEvent.GetRow();
235
236 // Map grid row to m_rows index through filter
237 int dataIdx = ( gridRow >= 0 && gridRow < static_cast<int>( m_filteredIndices.size() ) )
238 ? m_filteredIndices[gridRow]
239 : -1;
240
241 updateMemberDetail( dataIdx );
242
243 if( dataIdx >= 0 )
244 {
245 // Sync the edited name from the grid cell (user may have edited column 0)
246 wxString editedName = m_chainsGrid->GetCellValue( gridRow, 0 );
247
248 if( !editedName.IsEmpty() )
249 m_rows[dataIdx].suggestedName = editedName;
250
251 m_nameInput->SetValue( m_rows[dataIdx].suggestedName );
252
254 }
255 else
256 {
258 : m_frame->GetCurrentSheet() );
259 }
260
261 aEvent.Skip();
262}
263
264
265void DIALOG_CREATE_NET_CHAIN::OnRefreshClicked( wxCommandEvent& aEvent )
266{
267 // Clear any focus-hint filter so Refresh truly restores the full list. Otherwise the
268 // empty-state message ("Use Refresh to restore the full list.") would refresh and
269 // immediately re-apply the same no-match filter.
270 m_filterInput->Clear();
271
273 recalculateAndReload( true );
274}
275
276
278{
279 // Drop all references into m_potentialNetChains BEFORE any recalc clears that vector.
280 // This is the load-bearing step: while m_rows is non-empty, livePtr values may be live
281 // pointers into the pool, and Recalculate() destroys those entries.
282 m_rows.clear();
283 m_filteredIndices.clear();
284
285 if( aRunRecalculate )
286 m_frame->Schematic().RebuildConnectivity();
287
289 rebuildGrid();
290}
291
292
293void DIALOG_CREATE_NET_CHAIN::OnFindPathClicked( wxCommandEvent& aEvent )
294{
295 wxString fromRef = m_fromComponent->GetValue().Trim().Trim( false );
296 wxString toRef = m_toComponent->GetValue().Trim().Trim( false );
297
298 if( fromRef.IsEmpty() || toRef.IsEmpty() )
299 {
300 wxMessageBox( _( "Please select both a From and To component." ), _( "Find Path" ),
301 wxOK | wxICON_ERROR, this );
302 return;
303 }
304
305 if( fromRef == toRef )
306 {
307 wxMessageBox( _( "From and To must be different components." ), _( "Find Path" ),
308 wxOK | wxICON_ERROR, this );
309 return;
310 }
311
312 // Find the symbols by reference
314 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
315
316 SCH_SYMBOL* fromSym = nullptr;
317 SCH_SYMBOL* toSym = nullptr;
318 SCH_SHEET_PATH fromSheet, toSheet;
319
320 for( const SCH_REFERENCE& ref : refs )
321 {
322 if( ref.GetRef() == fromRef && ref.GetSymbol() )
323 {
324 fromSym = ref.GetSymbol();
325 fromSheet = ref.GetSheetPath();
326 }
327
328 if( ref.GetRef() == toRef && ref.GetSymbol() )
329 {
330 toSym = ref.GetSymbol();
331 toSheet = ref.GetSheetPath();
332 }
333 }
334
335 if( !fromSym || !toSym )
336 {
337 wxMessageBox( wxString::Format( _( "Could not find component '%s' or '%s'." ),
338 fromRef, toRef ),
339 _( "Find Path" ), wxOK | wxICON_ERROR, this );
340 return;
341 }
342
343 auto& chains = m_frame->Schematic().NetChains();
344
345 // Find ALL unique chains between all pin pairs of the two components
346 struct FOUND_CHAIN
347 {
349 wxString fromPin;
350 wxString toPin;
351 std::set<wxString> nets;
352 };
353
354 std::vector<FOUND_CHAIN> foundChains;
355 std::set<SCH_NETCHAIN*> seenChains;
356
357 std::vector<SCH_PIN*> fromPins = fromSym->GetPins( &fromSheet );
358 std::vector<SCH_PIN*> toPins = toSym->GetPins( &toSheet );
359
360 for( SCH_PIN* pinA : fromPins )
361 {
362 for( SCH_PIN* pinB : toPins )
363 {
364 SCH_NETCHAIN* chain = chains.FindPotentialNetChainBetweenPins( pinA, fromSheet, pinB, toSheet );
365
366 if( chain && !seenChains.count( chain ) )
367 {
368 seenChains.insert( chain );
369
370 FOUND_CHAIN fc;
371 fc.chain = chain;
372 fc.fromPin = fromRef + wxT( ":" ) + pinA->GetNumber();
373 fc.toPin = toRef + wxT( ":" ) + pinB->GetNumber();
374 fc.nets = chain->GetNets();
375 foundChains.push_back( fc );
376 }
377 }
378 }
379
380 if( foundChains.empty() )
381 {
382 // No potential chain found — offer force-create
383 int answer = wxMessageBox(
384 wxString::Format( _( "No net chain path found between %s and %s through "
385 "passthrough components.\n\n"
386 "Would you like to force-create a manual chain link "
387 "between these components?" ),
388 fromRef, toRef ),
389 _( "Find Path" ), wxYES_NO | wxICON_QUESTION, this );
390
391 if( answer == wxYES )
392 {
393 std::set<wxString> fromNets, toNets;
394 SCH_PIN* fromTerminalPin = nullptr;
395 SCH_PIN* toTerminalPin = nullptr;
396
397 for( SCH_PIN* pin : fromPins )
398 {
399 const auto name = pin->GetConnectionName( &fromSheet );
400
401 if( name && !name->IsEmpty() )
402 {
403 fromNets.insert( *name );
404
405 if( !fromTerminalPin )
406 fromTerminalPin = pin;
407 }
408 }
409
410 for( SCH_PIN* pin : toPins )
411 {
412 const auto name = pin->GetConnectionName( &toSheet );
413
414 if( name && !name->IsEmpty() )
415 {
416 toNets.insert( *name );
417
418 if( !toTerminalPin )
419 toTerminalPin = pin;
420 }
421 }
422
423 if( !fromTerminalPin || !toTerminalPin )
424 {
425 wxMessageBox( _( "Selected components have no connected pins to anchor a manual chain." ),
426 _( "Find Path" ), wxOK | wxICON_ERROR, this );
427 return;
428 }
429
430 // Replace grid with just this one force-created entry. Clear m_filteredIndices
431 // before mutating m_rows so rebuildGrid()'s "sync edited names back" pass
432 // cannot index stale grid rows into freshly-replaced m_rows entries.
433 m_filteredIndices.clear();
434 m_rows.clear();
435
436 POTENTIAL_ROW row;
437 row.livePtr = nullptr;
438 row.isManual = true;
439 row.forceFromUuid = fromSym->m_Uuid;
440 row.forceToUuid = toSym->m_Uuid;
441 row.forceFromPinUuid = fromTerminalPin->m_Uuid;
442 row.forceToPinUuid = toTerminalPin->m_Uuid;
443 row.forceFromRef = fromRef;
444 row.forceToRef = toRef;
445 row.forceFromPinNum = fromTerminalPin->GetNumber();
446 row.forceToPinNum = toTerminalPin->GetNumber();
447 row.suggestedName = fromRef + wxT( "_" ) + toRef;
448 row.terminals = fromRef + wxT( " \u2192 " ) + toRef;
449 row.memberNets.insert( fromNets.begin(), fromNets.end() );
450 row.memberNets.insert( toNets.begin(), toNets.end() );
451
452 m_rows.push_back( std::move( row ) );
453 rebuildGrid();
454
456 m_headerLabel->SetLabel( wxString::Format(
457 _( "Showing manual chain between %s and %s. Use Refresh to restore all." ),
458 fromRef, toRef ) );
459 }
460
461 return;
462 }
463
464 // Filter out chains that are already committed
465 std::set<wxString> committedNames;
466
467 for( const std::unique_ptr<SCH_NETCHAIN>& chain : chains.GetCommittedNetChains() )
468 {
469 if( chain )
470 committedNames.insert( chain->GetName() );
471 }
472
473 std::vector<FOUND_CHAIN> uncommitted;
474
475 for( const FOUND_CHAIN& fc : foundChains )
476 {
477 bool isCommitted = false;
478
479 for( const wxString& net : fc.nets )
480 {
481 if( SCH_NETCHAIN* existing = chains.GetNetChainForNet( net ) )
482 {
483 if( committedNames.count( existing->GetName() ) )
484 {
485 isCommitted = true;
486 break;
487 }
488 }
489 }
490
491 if( !isCommitted )
492 uncommitted.push_back( fc );
493 }
494
495 if( uncommitted.empty() )
496 {
497 wxMessageBox( wxString::Format( _( "All %zu net chain paths between %s and %s are "
498 "already committed as net chains." ),
499 foundChains.size(), fromRef, toRef ),
500 _( "Find Path" ), wxOK | wxICON_INFORMATION, this );
501 return;
502 }
503
504 // Replace grid contents with only the found paths. Clear m_filteredIndices first so
505 // rebuildGrid()'s "sync edited names back" pass cannot index stale grid rows into
506 // freshly-replaced m_rows entries.
507 m_filteredIndices.clear();
508 m_rows.clear();
509
510 for( const FOUND_CHAIN& fc : uncommitted )
511 {
512 POTENTIAL_ROW row;
513 row.livePtr = fc.chain;
514 row.memberNets = fc.nets;
515 row.isManual = true;
516 row.terminals = fc.fromPin + wxT( " \u2192 " ) + fc.toPin;
517 row.suggestedName = fromRef + wxT( "_" ) + toRef;
518
519 if( uncommitted.size() > 1 )
520 {
521 wxString pinNum = fc.fromPin.AfterLast( ':' );
522 row.suggestedName = fromRef + wxT( "_" ) + toRef + wxT( "_" ) + pinNum;
523 }
524
525 m_rows.push_back( std::move( row ) );
526 }
527
528 rebuildGrid();
529
531
532 m_headerLabel->SetLabel( wxString::Format(
533 _( "Showing %zu path(s) between %s and %s. Use Refresh to restore all." ),
534 uncommitted.size(), fromRef, toRef ) );
535}
536
537
539{
540 m_fromComponent->Clear();
541 m_toComponent->Clear();
542
544 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
545
546 std::set<wxString> refNames;
547
548 for( const SCH_REFERENCE& ref : refs )
549 {
550 if( ref.GetSymbol() )
551 refNames.insert( ref.GetRef() );
552 }
553
554 for( const wxString& name : refNames )
555 {
556 m_fromComponent->Append( name );
557 m_toComponent->Append( name );
558 }
559}
560
561
563{
564 m_rows.clear();
565
566 auto& chains = m_frame->Schematic().NetChains();
567
568 std::set<wxString> committedNames;
569
570 for( const std::unique_ptr<SCH_NETCHAIN>& chain : chains.GetCommittedNetChains() )
571 {
572 if( chain )
573 committedNames.insert( chain->GetName() );
574 }
575
576 for( const std::unique_ptr<SCH_NETCHAIN>& chain : chains.GetPotentialNetChains() )
577 {
578 if( !chain )
579 continue;
580
581 // Skip potentials that already match a committed chain by net membership
582 bool alreadyCommitted = false;
583
584 for( const wxString& net : chain->GetNets() )
585 {
586 if( SCH_NETCHAIN* existing = chains.GetNetChainForNet( net ) )
587 {
588 if( committedNames.count( existing->GetName() ) )
589 {
590 alreadyCommitted = true;
591 break;
592 }
593 }
594 }
595
596 if( alreadyCommitted )
597 continue;
598
599 POTENTIAL_ROW row;
600 row.livePtr = chain.get();
601 row.memberNets = chain->GetNets();
602
603 // Suggest name from the longest member net name
604 for( const wxString& net : row.memberNets )
605 {
606 if( net.length() > row.suggestedName.length() )
607 row.suggestedName = net;
608 }
609
610 // Surface the terminal refs in the row so a ref-keyed filter (e.g. right-click on a
611 // single symbol → focus on chains touching that symbol) can match them.
612 wxString terminalA = chain->GetTerminalRef( 0 );
613 wxString terminalB = chain->GetTerminalRef( 1 );
614
615 if( !chain->GetTerminalPinNum( 0 ).IsEmpty() )
616 terminalA += wxT( ":" ) + chain->GetTerminalPinNum( 0 );
617
618 if( !chain->GetTerminalPinNum( 1 ).IsEmpty() )
619 terminalB += wxT( ":" ) + chain->GetTerminalPinNum( 1 );
620
621 if( !terminalA.IsEmpty() || !terminalB.IsEmpty() )
622 row.terminals = terminalA + wxT( " → " ) + terminalB;
623
624 m_rows.push_back( std::move( row ) );
625 }
626}
627
628
629void DIALOG_CREATE_NET_CHAIN::OnFilterChanged( wxCommandEvent& aEvent )
630{
631 // Sync any edited names back before rebuilding
632 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
633 {
634 int dataIdx = m_filteredIndices[gi];
635
636 if( dataIdx >= 0 && dataIdx < static_cast<int>( m_rows.size() ) )
637 {
638 wxString edited = m_chainsGrid->GetCellValue( static_cast<int>( gi ), 0 );
639
640 if( !edited.IsEmpty() )
641 m_rows[dataIdx].suggestedName = edited;
642 }
643 }
644
645 rebuildGrid();
646}
647
648
650{
651 m_rebuilding = true;
652
653 // Invariant: callers that replace m_rows must clear m_filteredIndices first. Otherwise
654 // this sync-edits pass would copy grid cell values into the wrong (or out-of-bounds)
655 // m_rows entries. The dataIdx bounds check below is belt-and-suspenders.
656 // Sync any edited names back from the grid before clearing it
657 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
658 {
659 int gridRow = static_cast<int>( gi );
660 int dataIdx = m_filteredIndices[gi];
661
662 if( gridRow < m_chainsGrid->GetNumberRows()
663 && dataIdx >= 0 && dataIdx < static_cast<int>( m_rows.size() ) )
664 {
665 wxString edited = m_chainsGrid->GetCellValue( gridRow, 0 );
666
667 if( !edited.IsEmpty() )
668 m_rows[dataIdx].suggestedName = edited;
669 }
670 }
671
672 if( m_chainsGrid->GetNumberRows() )
673 m_chainsGrid->DeleteRows( 0, m_chainsGrid->GetNumberRows() );
674
675 // Build filtered index list
676 m_filteredIndices.clear();
677 wxString filter = m_filterInput->GetValue().Lower().Trim().Trim( false );
678
679 for( size_t i = 0; i < m_rows.size(); ++i )
680 {
681 if( filter.IsEmpty() )
682 {
683 m_filteredIndices.push_back( static_cast<int>( i ) );
684 continue;
685 }
686
687 const POTENTIAL_ROW& row = m_rows[i];
688
689 // Match against suggested name
690 if( row.suggestedName.Lower().Contains( filter ) )
691 {
692 m_filteredIndices.push_back( static_cast<int>( i ) );
693 continue;
694 }
695
696 // Match against terminal refs/pins (e.g. "J1:1 → J2:1") so a focus hint based on a
697 // selected symbol or pin reference catches chains anchored at that ref.
698 if( !row.terminals.IsEmpty() && row.terminals.Lower().Contains( filter ) )
699 {
700 m_filteredIndices.push_back( static_cast<int>( i ) );
701 continue;
702 }
703
704 // Match against member net names
705 bool netMatch = false;
706
707 for( const wxString& net : row.memberNets )
708 {
709 if( net.Lower().Contains( filter ) )
710 {
711 netMatch = true;
712 break;
713 }
714 }
715
716 if( netMatch )
717 m_filteredIndices.push_back( static_cast<int>( i ) );
718 }
719
720 m_chainsGrid->AppendRows( static_cast<int>( m_filteredIndices.size() ) );
721
722 for( size_t gi = 0; gi < m_filteredIndices.size(); ++gi )
723 {
724 const POTENTIAL_ROW& row = m_rows[m_filteredIndices[gi]];
725 int r = static_cast<int>( gi );
726
727 m_chainsGrid->SetCellValue( r, 0, row.suggestedName );
728 m_chainsGrid->SetCellValue( r, 1, wxString::Format( wxT( "%zu" ), row.memberNets.size() ) );
729
730 // Column 0 (Suggested Name) is editable, columns 1 and 2 are read-only
731 m_chainsGrid->SetReadOnly( r, 1, true );
732 m_chainsGrid->SetReadOnly( r, 2, true );
733
734 wxString preview;
735
736 if( !row.terminals.IsEmpty() )
737 preview = wxT( "[" ) + row.terminals + wxT( "] " );
738
739 for( const wxString& net : row.memberNets )
740 {
741 if( !preview.IsEmpty() && preview.Last() != ' ' )
742 preview += wxT( ", " );
743
744 preview += net;
745 }
746
747 m_chainsGrid->SetCellValue( r, 2, preview );
748 }
749
750 m_membersListBox->Clear();
751 m_membersLabel->SetLabel( _( "Member Nets" ) );
752
753 // m_nameInput is intentionally not cleared here. OnFilterChanged calls rebuildGrid, so
754 // clearing would erase a name the user is typing while narrowing the filter list. The
755 // explicit clear in TransferDataFromWindow handles the post-create reset, and the row
756 // selection handler overwrites the field with each row's suggested name on click.
757
758 m_rebuilding = false;
759}
760
761
763{
764 m_membersListBox->Clear();
765
766 if( aRow < 0 || aRow >= static_cast<int>( m_rows.size() ) )
767 {
768 m_membersLabel->SetLabel( _( "Member Nets" ) );
769 return;
770 }
771
772 const POTENTIAL_ROW& row = m_rows[aRow];
773
774 wxString label;
775
776 if( !row.terminals.IsEmpty() )
777 {
778 label = wxString::Format( _( "Member Nets \u2014 %s (%zu) [%s]" ), row.suggestedName, row.memberNets.size(),
779 row.terminals );
780 }
781 else
782 {
783 label = wxString::Format( _( "Member Nets \u2014 %s (%zu)" ), row.suggestedName, row.memberNets.size() );
784 }
785
786 m_membersLabel->SetLabel( label );
787
788 wxArrayString sorted;
789 sorted.reserve( row.memberNets.size() );
790
791 for( const wxString& net : row.memberNets )
792 sorted.Add( net );
793
794 sorted.Sort();
795 m_membersListBox->Append( sorted );
796}
797
798
800{
801 wxArrayInt rows = m_chainsGrid->GetSelectedRows();
802
803 if( !rows.empty() )
804 return rows.front();
805
806 // The grid cursor can survive a DeleteRows/AppendRows rebuild and point at an index
807 // beyond the new row count. Clamp before returning so callers don't dereference a
808 // stale data slot.
809 int cursor = m_chainsGrid->GetGridCursorRow();
810
811 if( cursor < 0 || cursor >= m_chainsGrid->GetNumberRows() )
812 return -1;
813
814 return cursor;
815}
816
817
818BOX2I DIALOG_CREATE_NET_CHAIN::highlightChainNets( const std::set<wxString>& aNets,
819 const SCH_SHEET_PATH& aPath )
820{
821 BOX2I highlightedBBox;
822 SCH_SCREEN* screen = aPath.LastScreen();
823
824 if( !m_frame || !screen )
825 return highlightedBBox;
826
827 // Only the current sheet's view can be live-updated; brightening flags on items belonging
828 // to other screens still apply visually next time that sheet is shown.
829 bool onCurrentSheet = screen == m_frame->GetCurrentSheet().LastScreen();
830 KIGFX::VIEW* view = onCurrentSheet ? m_frame->GetCanvas()->GetView() : nullptr;
831 std::vector<EDA_ITEM*> itemsToRedraw;
832
833 auto recordHighlight = [&]( SCH_ITEM* aItem )
834 {
835 if( highlightedBBox.GetWidth() == 0 && highlightedBBox.GetHeight() == 0 )
836 highlightedBBox = aItem->GetBoundingBox();
837 else
838 highlightedBBox.Merge( aItem->GetBoundingBox() );
839 };
840
841 for( SCH_ITEM* item : screen->Items() )
842 {
843 if( !item || !item->IsConnectable() )
844 continue;
845
846 SCH_ITEM* redrawItem = nullptr;
847
848 if( item->Type() == SCH_SYMBOL_T )
849 {
850 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
851 bool anyPinHighlighted = false;
852
853 for( SCH_PIN* pin : symbol->GetPins( &aPath ) )
854 {
855 const auto pinConn = pin->GetConnectionName( &aPath );
856
857 if( pinConn && !aNets.empty() )
858 {
859 if( !pin->IsBrightened() && aNets.count( *pinConn ) )
860 {
861 pin->SetBrightened();
862 redrawItem = symbol;
863 anyPinHighlighted = true;
864 }
865 else if( pin->IsBrightened() && !aNets.count( *pinConn ) )
866 {
867 pin->ClearBrightened();
868 redrawItem = symbol;
869 }
870 else if( pin->IsBrightened() )
871 {
872 anyPinHighlighted = true;
873 }
874 }
875 else if( pin->IsBrightened() )
876 {
877 pin->ClearBrightened();
878 redrawItem = symbol;
879 }
880 }
881
882 if( anyPinHighlighted )
883 recordHighlight( symbol );
884 }
885 else
886 {
887 const auto itemConn = item->GetConnectionName( &aPath );
888
889 if( itemConn && !aNets.empty() )
890 {
891 if( !item->IsBrightened() && aNets.count( *itemConn ) )
892 {
893 item->SetBrightened();
894 redrawItem = item;
895 recordHighlight( item );
896 }
897 else if( item->IsBrightened() && !aNets.count( *itemConn ) )
898 {
899 item->ClearBrightened();
900 redrawItem = item;
901 }
902 else if( item->IsBrightened() )
903 {
904 recordHighlight( item );
905 }
906 }
907 else if( item->IsBrightened() )
908 {
909 item->ClearBrightened();
910 redrawItem = item;
911 }
912 }
913
914 if( redrawItem )
915 itemsToRedraw.push_back( redrawItem );
916 }
917
918 if( !itemsToRedraw.empty() && view )
919 {
920 for( EDA_ITEM* redrawItem : itemsToRedraw )
921 view->Update( static_cast<KIGFX::VIEW_ITEM*>( redrawItem ), KIGFX::REPAINT );
922
923 m_frame->GetCanvas()->Refresh();
924 }
925
926 return highlightedBBox;
927}
928
929
931{
932 if( aRow.cachedSheetResolved )
933 return aRow.cachedSheet;
934
935 aRow.cachedSheetResolved = true;
936 aRow.cachedSheet = m_frame->GetCurrentSheet(); // safe default
937
938 // Prefer the deterministic terminal ref recorded with the chain. std::set<SCH_SYMBOL*>
939 // ordering depends on pointer values and is unstable across runs, so we never key off
940 // GetSymbols().begin() here.
941 wxString targetRef;
942
943 if( aRow.livePtr )
944 {
945 targetRef = aRow.livePtr->GetTerminalRef( 0 );
946
947 if( targetRef.IsEmpty() )
948 targetRef = aRow.livePtr->GetTerminalRef( 1 );
949 }
950 else
951 {
952 targetRef = aRow.forceFromRef;
953
954 if( targetRef.IsEmpty() )
955 targetRef = aRow.forceToRef;
956 }
957
958 if( !targetRef.IsEmpty() )
959 {
961 m_frame->Schematic().Hierarchy().GetSymbols( refs, SYMBOL_FILTER_ALL );
962
963 for( const SCH_REFERENCE& ref : refs )
964 {
965 if( ref.GetRef() == targetRef && ref.GetSymbol() )
966 {
967 aRow.cachedSheet = ref.GetSheetPath();
968 return aRow.cachedSheet;
969 }
970 }
971 }
972
973 // Fallback: scan every sheet for an item whose connection matches a member net.
974 for( const SCH_SHEET_PATH& path : m_frame->Schematic().Hierarchy() )
975 {
976 SCH_SCREEN* screen = path.LastScreen();
977
978 if( !screen )
979 continue;
980
981 for( SCH_ITEM* item : screen->Items() )
982 {
983 if( !item || !item->IsConnectable() )
984 continue;
985
986 if( const auto conn = item->GetConnectionName( &path ) )
987 {
988 if( aRow.memberNets.count( *conn ) )
989 {
990 aRow.cachedSheet = path;
991 return aRow.cachedSheet;
992 }
993 }
994 }
995 }
996
997 return aRow.cachedSheet;
998}
999
1000
1002{
1003 if( !m_frame )
1004 return;
1005
1006 SCH_SHEET_PATH targetPath = findSheetForRow( aRow );
1007
1008 // Clear leftover brightening on the previously-touched sheet first when it differs from
1009 // the new target. Otherwise navigating from row A (sheet 1) to row B (sheet 2) leaves
1010 // sheet 1's items lit when the user pages back. Skip when the path is unchanged so we
1011 // don't double-walk the same screen.
1012 if( !m_lastHighlightedSheet.empty() && m_lastHighlightedSheet != targetPath )
1014
1015 if( targetPath != m_frame->GetCurrentSheet() )
1016 {
1017 // SCH_ACTIONS::changeSheet wraps cancelInteractive, selectionClear, zoom-state save,
1018 // history push, and DisplayCurrentSheet — same path eeschema cross-probing uses.
1019 m_frame->GetToolManager()->RunAction<SCH_SHEET_PATH*>( SCH_ACTIONS::changeSheet, &targetPath );
1020 }
1021
1022 BOX2I bbox = highlightChainNets( aRow.memberNets, m_frame->GetCurrentSheet() );
1023
1024 if( bbox.GetWidth() > 0 || bbox.GetHeight() > 0 )
1025 {
1026 if( SCH_SELECTION_TOOL* selTool = m_frame->GetToolManager()->GetTool<SCH_SELECTION_TOOL>() )
1027 selTool->ZoomFitCrossProbeBBox( bbox );
1028 }
1029
1030 m_lastHighlightedSheet = m_frame->GetCurrentSheet();
1031}
1032
1033
1035{
1036 if( m_filteredIndices.empty() )
1037 return;
1038
1039 int dataIdx = m_filteredIndices[0];
1040 m_chainsGrid->SelectRow( 0 );
1041 m_chainsGrid->SetGridCursor( 0, 0 );
1042 updateMemberDetail( dataIdx );
1043 m_nameInput->SetValue( m_rows[dataIdx].suggestedName );
1045}
1046
1047
1049{
1050 // Both refs set: existing two-component find-path flow.
1051 if( !m_hint.fromRef.IsEmpty() && !m_hint.toRef.IsEmpty() )
1052 {
1053 wxCommandEvent dummy;
1055 return;
1056 }
1057
1058 wxString filterValue;
1059 wxString hintLabel;
1060
1061 if( !m_hint.netName.IsEmpty() )
1062 {
1063 filterValue = m_hint.netName;
1064 hintLabel = wxString::Format( _( "net '%s'" ), m_hint.netName );
1065 }
1066 else if( !m_hint.fromRef.IsEmpty() )
1067 {
1068 filterValue = m_hint.fromRef;
1069 hintLabel = wxString::Format( _( "component %s" ), m_hint.fromRef );
1070 }
1071 else if( !m_hint.toRef.IsEmpty() )
1072 {
1073 filterValue = m_hint.toRef;
1074 hintLabel = wxString::Format( _( "component %s" ), m_hint.toRef );
1075 }
1076 else
1077 {
1078 return; // No hint supplied — leave the dialog showing the full list.
1079 }
1080
1081 m_filterInput->ChangeValue( filterValue );
1082
1083 wxCommandEvent dummy;
1085
1086 if( m_filteredIndices.size() == 1 )
1087 {
1089 }
1090 else if( m_filteredIndices.empty() )
1091 {
1092 m_headerLabel->SetLabel( wxString::Format(
1093 _( "No uncommitted chains match the selected %s. Use Refresh to restore the full list." ),
1094 hintLabel ) );
1095 }
1096}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
constexpr size_type GetHeight() const
Definition box2.h:212
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...
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={})
BOX2I highlightChainNets(const std::set< wxString > &aNets, const SCH_SHEET_PATH &aPath)
Walk aPath and brighten/un-brighten items whose connection name is in aNets.
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:98
const KIID m_Uuid
Definition eda_item.h:597
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
An abstract base class for deriving all objects that can be added to a VIEW.
Definition view_item.h:82
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:63
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:1852
static TOOL_ACTION changeSheet
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
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:142
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:118
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
Schematic symbol object.
Definition sch_symbol.h:75
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:54
@ 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
KIBIS_PIN * pinA
const SHAPE_LINE_CHAIN chain
@ SCH_SYMBOL_T
Definition typeinfo.h:168