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