KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_setup_net_chains.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 <set>
24#include <vector>
25
26#include <wx/grid.h>
27#include <wx/msgdlg.h>
28#include <wx/textdlg.h>
29
30#include <connection_graph.h>
31#include <sch_edit_frame.h>
32#include <sch_netchain.h>
33#include <schematic.h>
34
35#include <netclass.h>
36#include <project.h>
39
42#include <widgets/wx_grid.h>
43
44#include <bitmaps.h>
46
47
48static const wxString c_statusCommitted = _( "Committed" );
49static const wxString c_statusPotential = _( "Potential" );
50
51
54 m_frame( aFrame )
55{
56 wxGridCellAttr* attr = new wxGridCellAttr;
57 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( PAGED_DIALOG::GetDialog( this ) ) );
58 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( PAGED_DIALOG::GetDialog( this ),
59 m_chainsGrid ) );
60 m_chainsGrid->SetColAttr( COL_COLOUR, attr );
61
62 wxGridCellAttr* roAttr = new wxGridCellAttr;
63 roAttr->SetReadOnly( true );
64 m_chainsGrid->SetColAttr( COL_MEMBERS, roAttr );
65
66 wxGridCellAttr* roAttr2 = new wxGridCellAttr;
67 roAttr2->SetReadOnly( true );
68 m_classesGrid->SetColAttr( CLASS_COL_MEMBERS, roAttr2 );
69
74
75 m_chainsGrid->Bind( wxEVT_GRID_CELL_CHANGED,
76 [this]( wxGridEvent& evt )
77 {
78 if( evt.GetCol() == COL_NAME )
79 {
80 wxString val = m_chainsGrid->GetCellValue( evt.GetRow(), COL_NAME );
81
82 if( !SCH_NETCHAIN::IsValidName( val ) )
83 {
84 wxMessageBox( wxString::Format(
85 _( "Name '%s' contains invalid characters." ), val ),
86 _( "Net Chains" ), wxOK | wxICON_ERROR, this );
87 m_chainsGrid->SetCellValue( evt.GetRow(), COL_NAME, evt.GetString() );
88 return;
89 }
90
91 int gridRow = evt.GetRow();
92
93 if( gridRow >= 0 && gridRow < static_cast<int>( m_gridToChainIdx.size() ) )
94 {
95 int dataIdx = m_gridToChainIdx[gridRow];
96
97 if( dataIdx >= 0 && dataIdx < static_cast<int>( m_chainRows.size() ) )
98 m_chainRows[dataIdx].newName = val;
99 }
100 }
101 } );
102}
103
104
108
109
111{
112 m_chainRows.clear();
113 m_classRows.clear();
114
115 if( !m_frame )
116 return;
117
118 CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph();
119
120 if( !graph )
121 return;
122
123 std::shared_ptr<NET_SETTINGS> ns = m_frame->Prj().GetProjectFile().NetSettings();
124 std::map<wxString, wxString> chainToClass;
125
126 if( ns )
127 chainToClass = ns->GetNetChainClasses();
128
129 for( const std::unique_ptr<SCH_NETCHAIN>& chain : graph->GetCommittedNetChains() )
130 {
131 if( !chain )
132 continue;
133
134 CHAIN_ROW row;
135 row.origName = chain->GetName();
136 row.newName = row.origName;
137 row.newColor = chain->GetColor();
138 row.newNetClass = chain->GetNetClass();
139 row.livePtr = chain.get();
140 row.memberNets = chain->GetNets();
141
142 auto it = chainToClass.find( row.origName );
143
144 if( it != chainToClass.end() )
145 row.newChainClass = it->second;
146
147 m_chainRows.push_back( std::move( row ) );
148 }
149
150 // Distinct class names from the chain->class map.
151 std::set<wxString> distinctClasses;
152
153 for( const auto& [chainName, className] : chainToClass )
154 {
155 if( !className.IsEmpty() )
156 distinctClasses.insert( className );
157 }
158
159 for( const wxString& cn : distinctClasses )
160 {
161 CLASS_ROW row;
162 row.origName = cn;
163 row.newName = cn;
164 m_classRows.push_back( std::move( row ) );
165 }
166}
167
168
170{
171 wxArrayString choices;
172 choices.Add( wxEmptyString ); // allow clearing the assignment
173
174 for( const CLASS_ROW& cr : m_classRows )
175 {
176 if( !cr.deletePending && !cr.newName.IsEmpty() )
177 choices.Add( cr.newName );
178 }
179
180 wxGridCellAttr* attr = new wxGridCellAttr;
181 attr->SetEditor( new wxGridCellChoiceEditor( choices, true /* allowOthers */ ) );
182 m_chainsGrid->SetColAttr( COL_CHAIN_CLASS, attr );
183}
184
185
187{
188 wxArrayString choices;
189 choices.Add( wxEmptyString ); // empty == no override
190
191 if( m_frame )
192 {
193 std::shared_ptr<NET_SETTINGS> ns = m_frame->Prj().GetProjectFile().NetSettings();
194
195 if( ns )
196 {
197 for( const auto& [name, nc] : ns->GetNetclasses() )
198 choices.Add( name );
199 }
200 }
201
202 wxGridCellAttr* attr = new wxGridCellAttr;
203 attr->SetEditor( new wxGridCellChoiceEditor( choices, true /* allowOthers */ ) );
204 m_chainsGrid->SetColAttr( COL_NET_CLASS, attr );
205}
206
207
209{
210 if( m_chainsGrid->GetNumberRows() )
211 m_chainsGrid->DeleteRows( 0, m_chainsGrid->GetNumberRows() );
212
215
216 int activeCount = 0;
217
218 for( const CHAIN_ROW& row : m_chainRows )
219 {
220 if( !row.deletePending )
221 ++activeCount;
222 }
223
224 m_chainsHeader->SetLabel( wxString::Format( _( "%d net chain(s)" ), activeCount ) );
225
226 m_gridToChainIdx.clear();
227
228 for( size_t i = 0; i < m_chainRows.size(); ++i )
229 {
230 if( !m_chainRows[i].deletePending )
231 m_gridToChainIdx.push_back( static_cast<int>( i ) );
232 }
233
234 m_chainsGrid->AppendRows( static_cast<int>( m_gridToChainIdx.size() ) );
235
236 for( int r = 0; r < static_cast<int>( m_gridToChainIdx.size() ); ++r )
237 {
238 const CHAIN_ROW& row = m_chainRows[m_gridToChainIdx[r]];
239
240 m_chainsGrid->SetCellValue( r, COL_NAME, row.newName );
241 m_chainsGrid->SetCellValue( r, COL_MEMBERS,
242 wxString::Format( _( "%zu nets" ), row.memberNets.size() ) );
243 m_chainsGrid->SetCellValue( r, COL_CHAIN_CLASS, row.newChainClass );
244 m_chainsGrid->SetCellValue( r, COL_NET_CLASS, row.newNetClass );
245
247 m_chainsGrid->SetCellValue( r, COL_COLOUR, row.newColor.ToCSSString() );
248 }
249
250 int sel = selectedChainRow();
251 int dataIdx = ( sel >= 0 && sel < static_cast<int>( m_gridToChainIdx.size() ) )
252 ? m_gridToChainIdx[sel] : -1;
253 updateMembersDetail( dataIdx );
254}
255
256
258{
259 if( m_classesGrid->GetNumberRows() )
260 m_classesGrid->DeleteRows( 0, m_classesGrid->GetNumberRows() );
261
262 m_classesGrid->AppendRows( static_cast<int>( m_classRows.size() ) );
263
264 // Member-count map: count chains that reference each class in the (live
265 // edit-buffered) chain rows.
266 std::map<wxString, int> memberCount;
267
268 for( const CHAIN_ROW& row : m_chainRows )
269 {
270 if( row.deletePending || row.newChainClass.IsEmpty() )
271 continue;
272
273 ++memberCount[row.newChainClass];
274 }
275
276 for( size_t i = 0; i < m_classRows.size(); ++i )
277 {
278 const CLASS_ROW& row = m_classRows[i];
279 int r = static_cast<int>( i );
280
281 m_classesGrid->SetCellValue( r, CLASS_COL_NAME, row.newName );
282 m_classesGrid->SetCellValue( r, CLASS_COL_MEMBERS,
283 wxString::Format( wxT( "%d" ),
284 memberCount[row.newName] ) );
285
286 if( row.deletePending )
287 {
288 for( int c = 0; c < m_classesGrid->GetNumberCols(); ++c )
289 m_classesGrid->SetReadOnly( r, c, true );
290 }
291 }
292}
293
294
302
303
305{
306 if( !m_chainsGrid->CommitPendingChanges() )
307 return false;
308
309 if( !m_classesGrid->CommitPendingChanges() )
310 return false;
311
312 // Sync grid cell values back into the buffered rows so validation works
313 // against what the user actually sees.
314 for( int gr = 0; gr < static_cast<int>( m_gridToChainIdx.size() ); ++gr )
315 {
317
318 row.newName = m_chainsGrid->GetCellValue( gr, COL_NAME );
319 row.newChainClass = m_chainsGrid->GetCellValue( gr, COL_CHAIN_CLASS );
320 row.newNetClass = m_chainsGrid->GetCellValue( gr, COL_NET_CLASS );
321
322 wxString colorStr = m_chainsGrid->GetCellValue( gr, COL_COLOUR );
323
324 if( colorStr.IsEmpty() )
326 else
327 row.newColor = KIGFX::COLOR4D( colorStr );
328 }
329
330 for( size_t i = 0; i < m_classRows.size(); ++i )
331 {
332 if( m_classRows[i].deletePending )
333 continue;
334
335 m_classRows[i].newName =
336 m_classesGrid->GetCellValue( static_cast<int>( i ), CLASS_COL_NAME );
337 }
338
339 // Reject empty committed chain names and duplicate names within the grid.
340 for( size_t i = 0; i < m_chainRows.size(); ++i )
341 {
342 const CHAIN_ROW& row = m_chainRows[i];
343
344 if( row.deletePending )
345 continue;
346
347 if( row.newName.IsEmpty() )
348 {
349 wxMessageBox( wxString::Format( _( "Net chain on row %zu cannot have an empty name." ),
350 i + 1 ),
351 _( "Net Chains" ), wxOK | wxICON_ERROR, this );
352 return false;
353 }
354
356 {
357 wxMessageBox( wxString::Format( _( "Net chain name '%s' contains invalid characters." ),
358 row.newName ),
359 _( "Net Chains" ), wxOK | wxICON_ERROR, this );
360 return false;
361 }
362
363 if( nameInChainGridAlready( row.newName, static_cast<int>( i ) ) )
364 {
365 wxMessageBox( wxString::Format( _( "Duplicate net chain name '%s' on row %zu." ),
366 row.newName, i + 1 ),
367 _( "Net Chains" ), wxOK | wxICON_ERROR, this );
368 return false;
369 }
370 }
371
372 // Reject empty / duplicate class names.
373 for( size_t i = 0; i < m_classRows.size(); ++i )
374 {
375 const CLASS_ROW& row = m_classRows[i];
376
377 if( row.deletePending )
378 continue;
379
380 if( row.newName.IsEmpty() )
381 {
382 wxMessageBox( wxString::Format( _( "Class on row %zu cannot have an empty name." ),
383 i + 1 ),
384 _( "Net Chain Classes" ), wxOK | wxICON_ERROR, this );
385 return false;
386 }
387
388 if( nameInClassGridAlready( row.newName, static_cast<int>( i ) ) )
389 {
390 wxMessageBox( wxString::Format( _( "Duplicate class name '%s' on row %zu." ),
391 row.newName, i + 1 ),
392 _( "Net Chain Classes" ), wxOK | wxICON_ERROR, this );
393 return false;
394 }
395 }
396
397 return true;
398}
399
400
402{
403 if( !Validate() )
404 return false;
405
406 return ApplyEdits();
407}
408
409
411{
412 if( !m_frame )
413 return false;
414
415 CONNECTION_GRAPH* graph = m_frame->Schematic().ConnectionGraph();
416
417 if( !graph )
418 return false;
419
420 std::shared_ptr<NET_SETTINGS> ns = m_frame->Prj().GetProjectFile().NetSettings();
421
422 // Apply renames on chains whose name changed.
423 for( CHAIN_ROW& row : m_chainRows )
424 {
425 if( row.deletePending )
426 continue;
427
428 if( !row.origName.IsEmpty() && row.origName != row.newName )
429 {
430 if( graph->RenameCommittedNetChain( row.origName, row.newName ) )
431 {
432 if( ns && !row.origName.IsEmpty() )
433 {
434 wxString oldClass = ns->GetNetChainClass( row.origName );
435
436 if( !oldClass.IsEmpty() )
437 {
438 ns->SetNetChainClass( row.origName, wxEmptyString );
439 ns->SetNetChainClass( row.newName, oldClass );
440 }
441 }
442
443 row.origName = row.newName;
444 }
445 }
446 }
447
448 // Apply colour and netclass override edits.
449 for( CHAIN_ROW& row : m_chainRows )
450 {
451 if( row.deletePending || !row.livePtr )
452 continue;
453
454 row.livePtr->SetColor( row.newColor );
455 row.livePtr->SetNetClass( row.newNetClass );
456 }
457
458 // Chain-class assignments into NET_SETTINGS.
459 if( ns )
460 {
461 for( const CHAIN_ROW& row : m_chainRows )
462 {
463 if( row.deletePending )
464 continue;
465
466 ns->SetNetChainClass( row.newName, row.newChainClass );
467 }
468 }
469
470 // Deletions — done last so renames above don't see ghost rows.
471 for( CHAIN_ROW& row : m_chainRows )
472 {
473 if( !row.deletePending )
474 continue;
475
476 if( !row.origName.IsEmpty() && graph->DeleteCommittedNetChain( row.origName ) )
477 {
478 if( ns )
479 ns->SetNetChainClass( row.origName, wxEmptyString );
480 }
481 }
482
483 // Step 6 — chain-class master list. Drop classes the user marked deleted
484 // and clear them from every chain that referenced them. Renames are
485 // already reflected in row.newName because we round-tripped via grid earlier.
486 if( ns )
487 {
488 for( const CLASS_ROW& cls : m_classRows )
489 {
490 if( !cls.deletePending )
491 continue;
492
493 // Collect first; SetNetChainClass with empty value erases from the map and would
494 // invalidate the active iterator if mutated during the range-for.
495 std::vector<wxString> toClear;
496
497 for( const auto& [chainName, className] : ns->GetNetChainClasses() )
498 {
499 if( className == cls.origName )
500 toClear.push_back( chainName );
501 }
502
503 for( const wxString& chainName : toClear )
504 ns->SetNetChainClass( chainName, wxEmptyString );
505 }
506
507 for( const CLASS_ROW& cls : m_classRows )
508 {
509 if( cls.deletePending )
510 continue;
511
512 if( !cls.origName.IsEmpty() && cls.origName != cls.newName )
513 {
514 // Two-pass for symmetry with the delete loop above and to avoid coupling to
515 // std::map mutation semantics during iteration.
516 std::vector<wxString> toRename;
517
518 for( const auto& [chainName, className] : ns->GetNetChainClasses() )
519 {
520 if( className == cls.origName )
521 toRename.push_back( chainName );
522 }
523
524 for( const wxString& chainName : toRename )
525 ns->SetNetChainClass( chainName, cls.newName );
526 }
527 }
528 }
529
530 m_frame->OnModify();
531
532 return true;
533}
534
535
537{
538 wxArrayInt rows = m_chainsGrid->GetSelectedRows();
539
540 if( !rows.empty() )
541 return rows.front();
542
543 // The grid cursor can survive a DeleteRows/AppendRows rebuild and point at an index
544 // beyond the new row count. Clamp before returning so callers don't dereference a
545 // stale data slot.
546 int cursor = m_chainsGrid->GetGridCursorRow();
547
548 if( cursor < 0 || cursor >= m_chainsGrid->GetNumberRows() )
549 return -1;
550
551 return cursor;
552}
553
554
556{
557 wxArrayInt rows = m_classesGrid->GetSelectedRows();
558
559 if( !rows.empty() )
560 return rows.front();
561
562 int cursor = m_classesGrid->GetGridCursorRow();
563
564 if( cursor < 0 || cursor >= m_classesGrid->GetNumberRows() )
565 return -1;
566
567 return cursor;
568}
569
570
571
572
573bool PANEL_SETUP_NET_CHAINS::nameInChainGridAlready( const wxString& aName, int aExceptRow ) const
574{
575 for( int i = 0; i < static_cast<int>( m_chainRows.size() ); ++i )
576 {
577 if( i == aExceptRow )
578 continue;
579
580 if( m_chainRows[i].deletePending )
581 continue;
582
583 if( m_chainRows[i].newName == aName && !aName.IsEmpty() )
584 return true;
585 }
586
587 return false;
588}
589
590
591bool PANEL_SETUP_NET_CHAINS::nameInClassGridAlready( const wxString& aName, int aExceptRow ) const
592{
593 for( int i = 0; i < static_cast<int>( m_classRows.size() ); ++i )
594 {
595 if( i == aExceptRow )
596 continue;
597
598 if( m_classRows[i].deletePending )
599 continue;
600
601 if( m_classRows[i].newName == aName && !aName.IsEmpty() )
602 return true;
603 }
604
605 return false;
606}
607
608
610{
611 int gridRow = selectedChainRow();
612
613 if( gridRow < 0 || gridRow >= static_cast<int>( m_gridToChainIdx.size() ) )
614 return;
615
616 CHAIN_ROW& row = m_chainRows[m_gridToChainIdx[gridRow]];
617
618 if( wxMessageBox( wxString::Format( _( "Delete net chain '%s'?" ), row.newName ),
619 _( "Delete Net Chain" ), wxYES_NO | wxICON_QUESTION, this ) != wxYES )
620 {
621 return;
622 }
623
624 row.deletePending = true;
627}
628
629
631{
632 int gridRow = aEvent.GetRow();
633 int dataIdx = ( gridRow >= 0 && gridRow < static_cast<int>( m_gridToChainIdx.size() ) )
634 ? m_gridToChainIdx[gridRow] : -1;
635 updateMembersDetail( dataIdx );
636 aEvent.Skip();
637}
638
639
641{
642 m_membersListBox->Clear();
643
644 if( aRow < 0 || aRow >= static_cast<int>( m_chainRows.size() ) )
645 {
646 m_membersLabel->SetLabel( _( "Member Nets" ) );
647 return;
648 }
649
650 const CHAIN_ROW& row = m_chainRows[aRow];
651
652 wxString label = row.newName.IsEmpty()
653 ? wxString::Format( _( "Member Nets (%zu)" ), row.memberNets.size() )
654 : wxString::Format( _( "Member Nets \u2014 %s (%zu)" ), row.newName,
655 row.memberNets.size() );
656
657 m_membersLabel->SetLabel( label );
658
659 wxArrayString sorted;
660 sorted.reserve( row.memberNets.size() );
661
662 for( const wxString& net : row.memberNets )
663 sorted.Add( net );
664
665 sorted.Sort();
666 m_membersListBox->Append( sorted );
667}
668
669
671{
672 wxTextEntryDialog dlg( this, _( "New net chain class name:" ), _( "Add Class" ) );
673
674 if( dlg.ShowModal() != wxID_OK )
675 return;
676
677 wxString name = dlg.GetValue();
678
679 if( name.IsEmpty() || nameInClassGridAlready( name, -1 ) )
680 {
681 wxMessageBox( _( "That class name is empty or already in use." ),
682 _( "Add Class" ), wxOK | wxICON_ERROR, this );
683 return;
684 }
685
686 CLASS_ROW row;
687 row.origName = wxEmptyString;
688 row.newName = name;
689 m_classRows.push_back( std::move( row ) );
690
693}
694
695
697{
698 int r = selectedClassRow();
699
700 if( r < 0 || r >= static_cast<int>( m_classRows.size() ) )
701 return;
702
703 CLASS_ROW& row = m_classRows[r];
704
705 wxTextEntryDialog dlg( this, _( "Rename net chain class:" ), _( "Rename Class" ),
706 row.newName );
707
708 if( dlg.ShowModal() != wxID_OK )
709 return;
710
711 wxString name = dlg.GetValue();
712
713 if( name.IsEmpty() || nameInClassGridAlready( name, r ) )
714 {
715 wxMessageBox( _( "That class name is empty or already in use." ),
716 _( "Rename Class" ), wxOK | wxICON_ERROR, this );
717 return;
718 }
719
720 wxString oldName = row.newName;
721 row.newName = name;
722
723 for( CHAIN_ROW& chainRow : m_chainRows )
724 {
725 if( chainRow.newChainClass == oldName )
726 chainRow.newChainClass = name;
727 }
728
732}
733
734
736{
737 int r = selectedClassRow();
738
739 if( r < 0 || r >= static_cast<int>( m_classRows.size() ) )
740 return;
741
742 CLASS_ROW& row = m_classRows[r];
743
744 if( wxMessageBox( wxString::Format( _( "Delete net chain class '%s'? Chains assigned to it "
745 "will become unclassified." ),
746 row.newName ),
747 _( "Delete Class" ), wxYES_NO | wxICON_QUESTION, this ) != wxYES )
748 {
749 return;
750 }
751
752 row.deletePending = true;
753
754 // Strip the class label from any chain row that referenced it.
755 for( CHAIN_ROW& chainRow : m_chainRows )
756 {
757 if( chainRow.newChainClass == row.newName )
758 chainRow.newChainClass = wxEmptyString;
759 }
760
764}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
Calculate the connectivity of a schematic and generates netlists.
bool RenameCommittedNetChain(const wxString &aOld, const wxString &aNew)
Rename a committed net chain.
const std::vector< std::unique_ptr< SCH_NETCHAIN > > & GetCommittedNetChains() const
Return user-created (committed) net chains (legacy accessor retained under net-chain API).
bool DeleteCommittedNetChain(const wxString &aName)
Delete a committed net chain by name.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
wxString ToCSSString() const
Definition color4d.cpp:150
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
PANEL_SETUP_NET_CHAINS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
PANEL_SETUP_NET_CHAINS(wxWindow *aParent, SCH_EDIT_FRAME *aFrame)
void OnClassDeleteClicked(wxCommandEvent &aEvent) override
std::vector< CHAIN_ROW > m_chainRows
void OnClassAddClicked(wxCommandEvent &aEvent) override
bool nameInChainGridAlready(const wxString &aName, int aExceptRow) const
void OnChainGridSelectionChanged(wxGridEvent &aEvent) override
std::vector< CLASS_ROW > m_classRows
void OnClassRenameClicked(wxCommandEvent &aEvent) override
void OnDeleteChainClicked(wxCommandEvent &aEvent) override
bool nameInClassGridAlready(const wxString &aName, int aExceptRow) const
std::vector< int > m_gridToChainIdx
Schematic editor (Eeschema) main window.
static bool IsValidName(const wxString &aName)
#define _(s)
static const wxString c_statusPotential
static const wxString c_statusCommitted
const SHAPE_LINE_CHAIN chain