KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_fp_edit_pad_table.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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 <wx/display.h>
23#include <wx/dcclient.h>
24#include <wx/filedlg.h>
25#include <wx/msgdlg.h>
26#include <wx/settings.h>
27
28#include <column_formatter.h>
29#include <pcb_shape.h>
30#include <widgets/wx_grid.h>
34#include <base_units.h>
35#include <bitmaps.h>
36#include <units_provider.h>
38#include <board.h>
39#include <footprint.h>
41#include <grid_tricks.h>
42#include <kiplatform/ui.h>
43#include <pin_numbers.h>
44#include <board_commit.h>
45#include <reporter.h>
46#include <table_io.h>
48
49
51
52// Helper to map shape string to PAD_SHAPE
53static PAD_SHAPE ShapeFromString( const wxString& shape )
54{
55 if( shape == _( "Oval" ) ) return PAD_SHAPE::OVAL;
56 if( shape == _( "Rectangle" ) ) return PAD_SHAPE::RECTANGLE;
57 if( shape == _( "Trapezoid" ) ) return PAD_SHAPE::TRAPEZOID;
58 if( shape == _( "Rounded rectangle" ) ) return PAD_SHAPE::ROUNDRECT;
59 if( shape == _( "Chamfered rectangle" ) ) return PAD_SHAPE::CHAMFERED_RECT;
60 if( shape == _( "Custom shape" ) ) return PAD_SHAPE::CUSTOM;
61
62 return PAD_SHAPE::CIRCLE;
63}
64
65
66static wxString ShapeToString( PAD_SHAPE shape )
67{
68 switch( shape )
69 {
70 case PAD_SHAPE::CIRCLE: return _( "Circle" );
71 case PAD_SHAPE::OVAL: return _( "Oval" );
72 case PAD_SHAPE::RECTANGLE: return _( "Rectangle" );
73 case PAD_SHAPE::TRAPEZOID: return _( "Trapezoid" );
74 case PAD_SHAPE::ROUNDRECT: return _( "Rounded rectangle" );
75 case PAD_SHAPE::CHAMFERED_RECT: return _( "Chamfered rectangle" );
76 case PAD_SHAPE::CUSTOM: return _( "Custom shape" );
77 default:
78 wxFAIL_MSG( wxT( "Invalid pad shape" ) );
79 return wxEmptyString;
80 }
81}
82
83
84static wxString GetPadTypeString( const PAD& aPad )
85{
86 if( aPad.IsAperturePad() )
87 return _( "Aperture" );
88
89 const PAD_ATTRIB attrib = aPad.GetAttribute();
90
91 switch( attrib )
92 {
93 case PAD_ATTRIB::PTH: return _( "Through-hole" );
94 case PAD_ATTRIB::SMD: return _( "SMD" );
95 case PAD_ATTRIB::CONN: return _( "Connector" );
96 case PAD_ATTRIB::NPTH: return _( "NPTH" );
97 // No default - handle all cases
98 }
99
100 return wxEmptyString;
101}
102
103
104static void SetPadTypeFromString( PAD& aPad, const wxString& aType )
105{
106 // Heuristic for detecting pads that look like they mean to be
107 // back-only pads.
108 const auto isBackOnlyPad =
109 []( const PAD& pad ) -> bool
110 {
111 const LSET layers = pad.GetLayerSet();
112 return ( layers & LSET::BackMask() ).any() && ( layers & LSET::FrontMask() ).none();
113 };
114
115 if( MatchTranslationOrNative( aType, _HKI( "Through-hole" ), false ) )
116 {
118 }
119 else if( MatchTranslationOrNative( aType, _HKI( "SMD" ), false ) )
120 {
121 // If the pad was already SMD, don't mess with the layers, but if it
122 // is _becoming_ SMD (including if it was an aperture pad), set
123 // default layerset
124 if( aPad.GetAttribute() != PAD_ATTRIB::SMD || aPad.IsAperturePad() )
125 {
126 LSET newLayers = PAD::SMDMask();
127
128 if( isBackOnlyPad( aPad ) )
129 newLayers = newLayers.FlipStandardLayers();
130
131 aPad.SetLayerSet( newLayers );
132 }
133
135 }
136 else if( MatchTranslationOrNative( aType, _HKI( "Connector" ), false ) )
137 {
139 }
140 else if( MatchTranslationOrNative( aType, _HKI( "NPTH" ), false ) )
141 {
143 }
144 else if( MatchTranslationOrNative( aType, _HKI( "Aperture" ), false ) )
145 {
146 if( !aPad.IsAperturePad() )
147 {
148 // Aperture pads are SMD pads with no copper
150
151 // Unset layers except F.Paste
152 LSET apertureLayers{ F_Paste };
153
154 if( isBackOnlyPad( aPad ) )
155 apertureLayers = apertureLayers.FlipStandardLayers();
156
157 aPad.SetLayerSet( apertureLayers );
158 }
159 }
160
161 // Note, bad strings can sneak in here, e.g. via pasting into a dropdown cell.
162 // So don't assert or crash, it's not necessarily a programming error.
163}
164
165
166static bool DrillsAreEditable( const PAD& aPad )
167{
168 return aPad.GetAttribute() == PAD_ATTRIB::PTH || aPad.GetAttribute() == PAD_ATTRIB::NPTH;
169}
170
171
182static void UpdateDrillCells( WX_GRID& aGrid, UNITS_PROVIDER& aUnitsProvider, int aRowId, const PAD& aPad,
183 bool aPreserveValues )
184{
185 const bool drillIsEditable = DrillsAreEditable( aPad );
186 const wxColour drillTextColour = drillIsEditable ? aGrid.GetDefaultCellTextColour()
187 : wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
188
189 aGrid.SetReadOnly( aRowId, COLS::COL_DRILL_X, !drillIsEditable );
190 aGrid.SetReadOnly( aRowId, COLS::COL_DRILL_Y, !drillIsEditable );
191 aGrid.SetCellTextColour( aRowId, COLS::COL_DRILL_X, drillTextColour );
192 aGrid.SetCellTextColour( aRowId, COLS::COL_DRILL_Y, drillTextColour );
193
194 if( aPreserveValues )
195 return;
196
197 const VECTOR2I drill = aPad.GetDrillSize();
198 aGrid.SetCellValue( aRowId, COLS::COL_DRILL_X, drill.x > 0 ? aUnitsProvider.StringFromValue( drill.x, true )
199 : wxString{} );
200 aGrid.SetCellValue( aRowId, COLS::COL_DRILL_Y, drill.y > 0 ? aUnitsProvider.StringFromValue( drill.y, true )
201 : wxString{} );
202}
203
204
210static wxString GetPadTableColLabel( COLS aCol )
211{
212 switch( aCol )
213 {
214 case COLS::COL_NUMBER: return wxT( "Number" );
215 case COLS::COL_TYPE: return wxT( "Type" );
216 case COLS::COL_SHAPE: return wxT( "Shape" );
217 case COLS::COL_POS_X: return wxT( "Pos X" );
218 case COLS::COL_POS_Y: return wxT( "Pos Y" );
219 case COLS::COL_SIZE_X: return wxT( "Size X" );
220 case COLS::COL_SIZE_Y: return wxT( "Size Y" );
221 case COLS::COL_DRILL_X: return wxT( "Drill X" );
222 case COLS::COL_DRILL_Y: return wxT( "Drill Y" );
223 case COLS::COL_P2D_LENGTH: return wxT( "Pad to die length" );
224 case COLS::COL_P2D_DELAY: return wxT( "Pad to die delay" );
225 default:
226 wxFAIL_MSG( wxT( "Invalid column index" ) );
227 return wxEmptyString;
228 }
229}
230
231
232static COLS GetColTypeForString( const wxString& aStr )
233{
234 for( int i = 0; i < static_cast<int>( COLS::COL_COUNT ); i++ )
235 {
236 COLS col = static_cast<COLS>( i );
237
238 if( MatchTranslationOrNative( aStr, GetPadTableColLabel( col ), false ) )
239 return col;
240 }
241 return COLS::COL_COUNT;
242}
243
244
252{
253public:
254 PAD_INFO_FORMATTER( UNITS_PROVIDER& aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat,
255 REPORTER& aReporter ) :
256 COLUMN_FORMATTER( aUnitsProvider, aIncludeUnits, aBoolFormat, aReporter )
257 {
258 }
259
260 wxString Format( const PAD& aPin, int aFieldId ) const
261 {
262 wxCHECK_MSG( aFieldId >= 0 && aFieldId < static_cast<int>( COLS::COL_COUNT ), wxEmptyString,
263 wxT( "Invalid column index" ) );
264
265 const DIALOG_FP_EDIT_PAD_TABLE::COLS col = static_cast<DIALOG_FP_EDIT_PAD_TABLE::COLS>( aFieldId );
266
267 switch( col )
268 {
270 return aPin.GetNumber();
271
273 return GetPadTypeString( aPin );
274
277
279 return m_unitsProvider.StringFromValue( aPin.GetCenter().x, m_includeUnits );
280
282 return m_unitsProvider.StringFromValue( aPin.GetCenter().y, m_includeUnits );
283
285 return m_unitsProvider.StringFromValue( aPin.GetSize( PADSTACK::ALL_LAYERS ).x, m_includeUnits );
286
288 return m_unitsProvider.StringFromValue( aPin.GetSize( PADSTACK::ALL_LAYERS ).y, m_includeUnits );
289
291 return m_unitsProvider.StringFromValue( aPin.GetDrillSize().x, m_includeUnits );
292
294 return m_unitsProvider.StringFromValue( aPin.GetDrillSize().y, m_includeUnits );
295
297 if( aPin.GetPadToDieLength() )
298 return m_unitsProvider.StringFromValue( aPin.GetPadToDieLength(), m_includeUnits );
299
300 return wxEmptyString;
301
303 if( aPin.GetPadToDieDelay() )
304 return m_unitsProvider.StringFromValue( aPin.GetPadToDieDelay(), m_includeUnits, EDA_DATA_TYPE::TIME );
305
306 return wxEmptyString;
307
309 return wxEmptyString;
310 }
311
312 wxFAIL_MSG( "Invalid column index" );
313 return wxEmptyString;
314 }
315
322 void UpdatePad( PAD& aPad, const wxString& aValue, int aFieldId ) const
323 {
324 switch( aFieldId )
325 {
327 aPad.SetNumber( aValue );
328 break;
329
331 SetPadTypeFromString( aPad, aValue );
332 break;
333
335 // Note that this will blow away any layer-specific shapes, but this process
336 // doesn't support layer-specific editing.
338 [&]( PCB_LAYER_ID aLayer )
339 {
340 aPad.SetShape( aLayer, ShapeFromString( aValue ) );
341 } );
342 break;
343
345 {
346 VECTOR2I pos = aPad.GetPosition();
347 pos.x = m_unitsProvider.ValueFromString( aValue );
348 aPad.SetPosition( pos );
349 break;
350 }
351
353 {
354 VECTOR2I pos = aPad.GetPosition();
355 pos.y = m_unitsProvider.ValueFromString( aValue );
356 aPad.SetPosition( pos );
357 break;
358 }
359
361 // Same as shape: overwrite all layers
363 [&]( PCB_LAYER_ID aLayer )
364 {
365 VECTOR2I size = aPad.GetSize( aLayer );
366 size.x = m_unitsProvider.ValueFromString( aValue );
367 aPad.SetSize( aLayer, size );
368 } );
369 break;
370
372 // Same as shape: overwrite all layers
374 [&]( PCB_LAYER_ID aLayer )
375 {
376 VECTOR2I size = aPad.GetSize( aLayer );
377 size.y = m_unitsProvider.ValueFromString( aValue );
378 aPad.SetSize( aLayer, size );
379 } );
380 break;
381
383 {
384 VECTOR2I drill = aPad.GetDrillSize();
385 drill.x = m_unitsProvider.ValueFromString( aValue );
386 aPad.SetDrillSize( drill );
387 break;
388 }
389
391 {
392 VECTOR2I drill = aPad.GetDrillSize();
393 drill.y = m_unitsProvider.ValueFromString( aValue );
394 aPad.SetDrillSize( drill );
395 break;
396 }
397
399 if( aValue.empty() )
400 aPad.SetPadToDieLength( 0 );
401 else
402 aPad.SetPadToDieLength( m_unitsProvider.ValueFromString( aValue ) );
403
404 break;
405
407 if( aValue.empty() )
408 aPad.SetPadToDieDelay( 0 );
409 else
410 aPad.SetPadToDieDelay( m_unitsProvider.ValueFromString( aValue, EDA_DATA_TYPE::TIME ) );
411
412 break;
413
414 default:
415 wxFAIL_MSG( "Invalid column index" );
416 break;
417 }
418 }
419};
420
421
423 DIALOG_FP_EDIT_PAD_TABLE_BASE( (wxWindow*) aParent ),
424 m_frame( aParent ),
425 m_footprint( aFootprint ),
427 m_summaryDirty( true )
428{
430
431 // The base class created a single placeholder row; resize the grid to fit the pads.
432 if( m_grid->GetNumberRows() > 0 )
433 m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
434
435 if( !m_originalPads.empty() )
436 m_grid->AppendRows( static_cast<int>( m_originalPads.size() ) );
437
438 // Constrain summary label widths so they ellipsize rather than push the layout around
439 // when long pin-number summaries (or duplicate lists) are produced.
440 const int summaryW = m_pin_numbers_summary->GetCharWidth() * 30;
441
442 m_duplicate_pins->SetWindowStyleFlag( m_duplicate_pins->GetWindowStyleFlag() | wxST_ELLIPSIZE_END );
443 m_pin_numbers_summary->SetMaxSize( wxSize( summaryW, -1 ) );
444
445 m_duplicate_pins->SetWindowStyleFlag( m_duplicate_pins->GetWindowStyleFlag() | wxST_ELLIPSIZE_END );
446 m_duplicate_pins->SetMaxSize( wxSize( summaryW, -1 ) );
447
448 wxGridCellAttr* attr = nullptr;
449
450 // Type column editor (attribute)
451 attr = new wxGridCellAttr;
452 wxArrayString typeNames;
453 typeNames.push_back( _( "Through-hole" ) ); // PTH
454 typeNames.push_back( _( "SMD" ) ); // SMD
455 typeNames.push_back( _( "Connector" ) ); // CONN SMD? (use CONN?)
456 typeNames.push_back( _( "NPTH" ) ); // NPTH
457 typeNames.push_back( _( "Aperture" ) ); // inferred copper-less
458 attr->SetEditor( new GRID_CELL_COMBOBOX( typeNames ) );
459 m_grid->SetColAttr( COL_TYPE, attr );
460
461 attr = new wxGridCellAttr;
462 wxArrayString shapeNames;
470 attr->SetEditor( new GRID_CELL_COMBOBOX( shapeNames ) );
471 m_grid->SetColAttr( COL_SHAPE, attr );
472
473 attr = new wxGridCellAttr;
474 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
475 m_grid->SetColAttr( COL_POS_X, attr );
476
477 attr = new wxGridCellAttr;
478 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
479 m_grid->SetColAttr( COL_POS_Y, attr );
480
481 attr = new wxGridCellAttr;
482 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
483 m_grid->SetColAttr( COL_SIZE_X, attr );
484
485 attr = new wxGridCellAttr;
486 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
487 m_grid->SetColAttr( COL_SIZE_Y, attr );
488
489 // Drill X
490 attr = new wxGridCellAttr;
491 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
492 m_grid->SetColAttr( COL_DRILL_X, attr );
493
494 // Drill Y
495 attr = new wxGridCellAttr;
496 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
497 m_grid->SetColAttr( COL_DRILL_Y, attr );
498
499 // Pad->Die Length
500 m_grid->SetAutoEvalColUnits( COL_P2D_LENGTH, m_unitsProvider->GetUnitsFromType( EDA_DATA_TYPE::DISTANCE ) );
501
502 // Pad->Die Delay
503 m_grid->SetAutoEvalColUnits( COL_P2D_DELAY, m_unitsProvider->GetUnitsFromType( EDA_DATA_TYPE::TIME ) );
504
505 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_X );
506 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_Y );
507 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_X );
508 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_Y );
509 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_X );
510 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_Y );
511 m_grid->SetAutoEvalCols( { COL_POS_X, COL_POS_Y,
515 COL_P2D_DELAY } );
516
517 // add Cut, Copy, and Paste to wxGrid
518 m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
519
522
524
525 Layout();
527
528 // Cap the initial height so the dialog does not grow off-screen for footprints
529 // with many pads. The grid grows to fill the available space via wxEXPAND.
530 // Use the parent window to find the display since this dialog isn't shown yet.
531 int displayIdx = wxDisplay::GetFromWindow( aParent );
532
533 if( displayIdx == wxNOT_FOUND )
534 displayIdx = 0;
535
536 wxRect displayArea = wxDisplay( (unsigned int) displayIdx ).GetClientArea();
537 wxSize dlgSize = GetSize();
538 int maxH = ( displayArea.height * 4 ) / 5;
539
540 if( dlgSize.y > maxH )
541 {
542 dlgSize.y = maxH;
543 SetSize( dlgSize );
544
545 // Reset minimum height so the user can resize the capped dialog freely.
546 // The minimum width from finishDialogSettings() is still honoured.
547 wxSize minSz = GetMinSize();
548 minSz.y = -1;
549 SetMinSize( minSz );
550
551 Centre();
552 }
553}
554
555
557{
558 // Roll back any session changes unless the dialog was accepted. This means
559 // rollback happens on both title-bar 'X'/Esc and Cancel.
560 if( !m_accepted )
562
563 // destroy GRID_TRICKS before m_grid.
564 m_grid->PopEventHandler( true );
565}
566
567
569{
570 m_grid->SetCellValue( aRowId, COL_NUMBER, aPad->GetNumber() );
571
572 wxString attrStr = GetPadTypeString( *aPad );
573 int size_x = aPad->GetSize( F_Cu ).x;
574 int size_y = aPad->GetSize( F_Cu ).y;
575 wxString padShape = aPad->ShowPadShape( F_Cu );
576
578 [&]( PCB_LAYER_ID aLayer )
579 {
580 if( aPad->GetSize( aLayer ).x != size_x )
581 size_x = -1;
582
583 if( aPad->GetSize( aLayer ).y != size_y )
584 size_y = -1;
585
586 if( aPad->ShowPadShape( aLayer ) != padShape )
587 padShape = INDETERMINATE_STATE;
588 } );
589
590 m_grid->SetCellValue( aRowId, COL_TYPE, attrStr );
591 m_grid->SetCellValue( aRowId, COL_SHAPE, padShape );
592 m_grid->SetCellValue( aRowId, COL_POS_X, m_unitsProvider->StringFromValue( aPad->GetPosition().x, true ) );
593 m_grid->SetCellValue( aRowId, COL_POS_Y, m_unitsProvider->StringFromValue( aPad->GetPosition().y, true ) );
594 m_grid->SetCellValue( aRowId, COL_SIZE_X, size_x >= 0 ? m_unitsProvider->StringFromValue( size_x, true )
596 m_grid->SetCellValue( aRowId, COL_SIZE_Y, size_y >= 0 ? m_unitsProvider->StringFromValue( size_y, true )
598
599 UpdateDrillCells( *m_grid, *m_unitsProvider, aRowId, *aPad, false );
600
601 // Pad to die metrics
602 if( aPad->GetPadToDieLength() )
603 m_grid->SetUnitValue( aRowId, COL_P2D_LENGTH, aPad->GetPadToDieLength() );
604 else
605 m_grid->SetCellValue( aRowId, COL_P2D_LENGTH, wxEmptyString );
606
607 if( aPad->GetPadToDieDelay() )
608 m_grid->SetUnitValue( aRowId, COL_P2D_DELAY, aPad->GetPadToDieDelay() );
609 else
610 m_grid->SetCellValue( aRowId, COL_P2D_DELAY, wxEmptyString );
611
612 setRowNullableEditors( aRowId );
613}
614
615
617{
618 if( !m_footprint )
619 return false;
620
621 int row = 0;
622
623 for( PAD* pad : m_rowPads )
624 {
625 fillGridRow( row, pad );
626 row++;
627 }
628
629 // Auto size the data columns first to get reasonable initial widths
630 m_grid->AutoSizeColumns();
631
632 // Ensure the Shape column (index 1) is wide enough for the longest translated
633 // shape text plus the dropdown arrow / padding. We compute a max text width
634 // using a device context and add a platform neutral padding.
635 {
636 wxClientDC dc( m_grid );
637 dc.SetFont( m_grid->GetFont() );
638
639 wxArrayString shapeNames;
647
648 int maxWidth = 0;
649
650 for( const wxString& str : shapeNames )
651 {
652 int w, h;
653 dc.GetTextExtent( str, &w, &h );
654 maxWidth = std::max( maxWidth, w );
655 }
656
657 // Add padding for internal cell margins + dropdown control.
658 int padding = FromDIP( 30 ); // heuristic: 2*margin + arrow button
659 m_grid->SetColSize( COL_SHAPE, maxWidth + padding );
660 }
661
662 // Record initial proportions for proportional resizing later.
664
665 // Run an initial proportional resize using current client size so columns
666 // respect proportions immediately.
667 wxSizeEvent sizeEvt( GetSize(), GetId() );
668 CallAfter(
669 [this, sizeEvt]
670 {
671 wxSizeEvent evt( sizeEvt );
672 this->OnSize( evt );
673 } );
674
675 // If pads exist, select the first row to show initial highlight
676 if( m_grid->GetNumberRows() > 0 )
677 {
678 m_grid->SetGridCursor( 0, 0 );
679
680 // Construct event with required parameters (id, type, obj, row, col,...)
681 wxGridEvent ev( m_grid->GetId(), wxEVT_GRID_SELECT_CELL, m_grid, 0, 0, -1, -1, true );
682 OnSelectCell( ev );
683 }
684
685 return true;
686}
687
688
690{
691 // Set nullable editors
692 auto setCellEditor =
693 [this, aRowId]( int aCol )
694 {
696 wxGridCellAttr* attr = m_grid->GetOrCreateCellAttr( aRowId, aCol );
697 attr->SetEditor( cellEditor );
698 attr->DecRef();
699 };
700
701 setCellEditor( COL_P2D_LENGTH );
702 setCellEditor( COL_P2D_DELAY );
703}
704
705
707{
708 m_originalPads.clear();
709 m_rowPads.clear();
710 m_removedPads.clear();
711
712 if( !m_footprint )
713 return;
714
715 for( PAD* pad : m_footprint->Pads() )
716 {
717 PAD_SNAPSHOT snap( pad );
718 snap.number = pad->GetNumber();
719 snap.position = pad->GetPosition();
720 snap.padstack = pad->Padstack();
721 snap.attribute = pad->GetAttribute();
722 snap.padToDieLength= pad->GetPadToDieLength();
723 snap.padToDieDelay = pad->GetPadToDieDelay();
724
725 m_originalPads.try_emplace( pad, std::move( snap ) );
726 m_rowPads.push_back( pad );
727 }
728
729 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
730}
731
732
734{
735 aPad.SetNumber( aSnap.number );
736 aPad.SetPosition( aSnap.position );
737 aPad.SetPadstack( aSnap.padstack );
738 aPad.SetAttribute( aSnap.attribute );
739 aPad.SetPadToDieLength( aSnap.padToDieLength );
740 aPad.SetPadToDieDelay( aSnap.padToDieDelay );
741}
742
743
745{
746 if( !m_footprint )
747 return;
748
749 const PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
750 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
751
752 for( PAD* pad : m_footprint->Pads() )
753 {
754 // Clear brighted even if the pad isn't original
755 pad->ClearBrightened();
756
757 if( !m_originalPads.contains( pad ) )
758 continue;
759
761
762 if( canvas )
763 canvas->GetView()->Update( pad, KIGFX::REPAINT );
764 }
765}
766
767
769{
770 if( !m_footprint )
771 return;
772
774
775 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
776 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
777 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
778
779 // Remove pads added during the session; they have no snapshot entry.
780 std::vector<PAD*> livePads( m_footprint->Pads().begin(), m_footprint->Pads().end() );
781
782 for( PAD* pad : livePads )
783 {
784 if( m_originalPads.contains( pad ) )
785 continue;
786
787 m_footprint->Remove( pad );
788
789 if( view )
790 view->Remove( pad );
791
792 delete pad;
793 }
794
795 // Re-add pads removed during the session and restore their original data.
796 // Pads with no snapshot were added and removed again, so just drop them.
797 for( PAD* pad : m_removedPads )
798 {
799 auto snapIt = m_originalPads.find( pad );
800
801 if( snapIt == m_originalPads.end() )
802 {
803 delete pad;
804 continue;
805 }
806
807 restorePadFromSnapshot( *pad, snapIt->second );
808 m_footprint->Add( pad );
809
810 if( view )
811 view->Add( pad );
812 }
813
814 m_removedPads.clear();
815
816 // Rebuild the row mapping so no pointers to freed pads survive for any UI
817 // events still pending while the dialog closes.
818 m_rowPads.clear();
819
820 for( PAD* pad : m_footprint->Pads() )
821 m_rowPads.push_back( pad );
822
823 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
824
825 if( canvas )
826 {
828 canvas->ForceRefresh();
829 }
830
831 m_summaryDirty = true;
832}
833
834
836{
837 switch( aCol )
838 {
839 case COL_NUMBER:
840 aPad.SetNumber( m_grid->GetCellValue( aRowId, aCol ) );
841 break;
842
843 case COL_TYPE:
844 SetPadTypeFromString( aPad, m_grid->GetCellValue( aRowId, aCol ) );
845 break;
846
847 case COL_SHAPE:
848 {
849 const wxString shape = m_grid->GetCellValue( aRowId, aCol );
850
851 if( shape == INDETERMINATE_STATE )
852 break;
853
854 const PAD_SHAPE newShape = ShapeFromString( shape );
855
857 [&]( PCB_LAYER_ID aLayer )
858 {
859 aPad.SetShape( aLayer, newShape );
860 } );
861 break;
862 }
863
864 case COL_POS_X:
865 case COL_POS_Y:
866 {
867 VECTOR2I pos = aPad.GetPosition();
868
869 if( aCol == COL_POS_X )
870 pos.x = m_grid->GetUnitValue( aRowId, aCol );
871 else
872 pos.y = m_grid->GetUnitValue( aRowId, aCol );
873
874 aPad.SetPosition( pos );
875 break;
876 }
877
878 case COL_SIZE_X:
879 case COL_SIZE_Y:
880 {
881 const wxString sizeValue = m_grid->GetCellValue( aRowId, aCol );
882
883 if( sizeValue == INDETERMINATE_STATE )
884 break;
885
886 const int size = m_grid->GetUnitValue( aRowId, aCol );
887
889 [&]( PCB_LAYER_ID aLayer )
890 {
891 VECTOR2I layerSize = aPad.GetSize( aLayer );
892
893 if( aCol == COL_SIZE_X )
894 layerSize.x = size;
895 else
896 layerSize.y = size;
897
898 aPad.SetSize( aLayer, layerSize );
899 } );
900 break;
901 }
902
903 case COL_DRILL_X:
904 case COL_DRILL_Y:
905 {
906 // Drill sizes (only if attribute allows)
907 if( DrillsAreEditable( aPad ) )
908 {
909 int drillX = m_grid->GetUnitValue( aRowId, COL_DRILL_X );
910 int drillY = m_grid->GetUnitValue( aRowId, COL_DRILL_Y );
911
912 if( drillX > 0 || drillY > 0 )
913 {
914 if( drillX <= 0 )
915 drillX = drillY;
916
917 if( drillY <= 0 )
918 drillY = drillX;
919
920 aPad.SetDrillSize( { drillX, drillY } );
921 }
922 }
923
924 break;
925 }
926
927 case COL_P2D_LENGTH:
928 {
929 const wxString lenStr = m_grid->GetCellValue( aRowId, aCol );
930
931 if( lenStr.IsEmpty() )
932 aPad.SetPadToDieLength( 0 );
933 else
934 aPad.SetPadToDieLength( m_grid->GetUnitValue( aRowId, aCol ) );
935
936 break;
937 }
938
939 case COL_P2D_DELAY:
940 {
941 const wxString delayStr = m_grid->GetCellValue( aRowId, aCol );
942
943 if( delayStr.IsEmpty() )
944 aPad.SetPadToDieDelay( 0 );
945 else
946 aPad.SetPadToDieDelay( m_grid->GetUnitValue( aRowId, aCol ) );
947
948 break;
949 }
950
951 default:
952 wxFAIL_MSG( wxT( "Invalid column index" ) );
953 break;
954 }
955}
956
957
959{
960 if( !m_grid->CommitPendingChanges() )
961 return false;
962
963 if( !m_footprint )
964 return true;
965
966 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
967 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
968 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
969
971
972 BOARD_COMMIT commit( m_frame );
973
974 // Pads removed during the session are re-inserted momentarily so their
975 // removal can be staged in this commit (Push performs the actual removal).
976 for( PAD* pad : m_removedPads )
977 {
978 auto snapIt = m_originalPads.find( pad );
979
980 if( snapIt == m_originalPads.end() )
981 {
982 // A pad added and removed again during the session: it never needs
983 // to appear in the commit.
984 delete pad;
985 continue;
986 }
987
988 restorePadFromSnapshot( *pad, snapIt->second );
989 m_footprint->Add( pad );
990
991 if( view )
992 view->Add( pad );
993
994 commit.Remove( pad );
995 }
996
997 m_removedPads.clear();
998
999 int row = 0;
1000
1001 const auto applyRowDataToPad =
1002 [&]( PAD& aPad, int aRowId )
1003 {
1004 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
1005 setPadFromGridCell( aPad, aRowId, static_cast<COLS>( col ) );
1006 };
1007
1008 for( PAD* pad : m_rowPads )
1009 {
1010 if( m_originalPads.contains( pad ) )
1011 {
1012 // Existing pad: its data was already restored to the dialog-open
1013 // state above, so the commit's undo image is correct.
1014 commit.Modify( pad );
1015 applyRowDataToPad( *pad, row );
1016 }
1017 else
1018 {
1019 // Imported pad: it was added to the footprint at import time for the
1020 // canvas preview. Take it out again so the commit can stage a clean
1021 // addition.
1022 applyRowDataToPad( *pad, row );
1023
1024 m_footprint->Remove( pad );
1025
1026 if( view )
1027 view->Remove( pad );
1028
1029 commit.Add( pad );
1030 }
1031
1032 row++;
1033 }
1034
1035 commit.Push( _( "Edit Pads" ) );
1036 m_frame->Refresh();
1037
1038 m_accepted = true;
1039
1040 return true;
1041}
1042
1043
1045{
1046 m_colProportions.clear();
1047 m_minColWidths.clear();
1048
1049 if( !m_grid )
1050 return;
1051
1052 // Only consider the actual data columns (all of them since row labels are hidden)
1053 int cols = m_grid->GetNumberCols();
1054 int total = 0;
1055 std::vector<int> widths;
1056 widths.reserve( cols );
1057
1058 for( int c = 0; c < cols; ++c )
1059 {
1060 int w = m_grid->GetColSize( c );
1061 widths.push_back( w );
1062 total += w;
1063 }
1064
1065 if( total <= 0 )
1066 return;
1067
1068 for( int w : widths )
1069 {
1070 m_colProportions.push_back( (double) w / (double) total );
1071 m_minColWidths.push_back( w );
1072 }
1073}
1074
1075
1076void DIALOG_FP_EDIT_PAD_TABLE::OnSize( wxSizeEvent& aEvent )
1077{
1078 if( m_colProportions.empty() )
1079 {
1080 aEvent.Skip();
1081 return;
1082 }
1083
1084 // Compute available total width for columns and resize keeping proportions.
1085 int cols = m_grid->GetNumberCols();
1086 int available = 0;
1087
1088 for( int c = 0; c < cols; ++c )
1089 available += m_grid->GetColSize( c );
1090
1091 // Use client size of grid minus scrollbar estimate to better distribute.
1092 int clientW = m_grid->GetClientSize().x;
1093
1094 if( clientW > 0 )
1095 available = clientW; // prefer actual client width
1096
1097 int used = 0;
1098
1099 for( int c = 0; c < cols; ++c )
1100 {
1101 int target = (int) std::round( m_colProportions[c] * available );
1102 target = std::max( target, m_minColWidths[c] );
1103
1104 // Defer last column to absorb rounding diff.
1105 if( c == cols - 1 )
1106 target = std::max( available - used, m_minColWidths[c] );
1107
1108 m_grid->SetColSize( c, target );
1109 used += target;
1110 }
1111
1112 aEvent.Skip();
1113}
1114
1115
1117{
1118 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
1119 m_summaryDirty = true;
1120
1121 DIALOG_SHIM::OnCharHook( aEvent );
1122}
1123
1124
1126{
1127 int row = aEvent.GetRow();
1128 int col = aEvent.GetCol();
1129
1130 if( !m_footprint )
1131 return;
1132
1133 PAD* target = getPadForRow( row );
1134
1135 if( !target )
1136 return;
1137
1138 const bool drillsWereEditable = DrillsAreEditable( *target );
1139 bool needCanvasRefresh = true;
1140
1141 setPadFromGridCell( *target, row, static_cast<COLS>( col ) );
1142
1143 if( col == COL_TYPE )
1144 {
1145 const bool drillsAreEditable = DrillsAreEditable( *target );
1146
1147 if( drillsAreEditable )
1148 {
1149 // PAD::SetAttribute() removes drills for SMD pads. So if we roundtrip
1150 // from PTH -> SMD -> PTH, the drill size is lost. In that case, restore
1151 // from the "ghost" value in the grid if there is one.
1152 const int drillX = m_grid->GetUnitValue( row, COL_DRILL_X );
1153 const int drillY = m_grid->GetUnitValue( row, COL_DRILL_Y );
1154
1155 if( drillX > 0 || drillY > 0 )
1156 {
1157 setPadFromGridCell( *target, row, COL_DRILL_X );
1158 }
1159 else if( !drillsWereEditable )
1160 {
1161 // Use a default drill size the pad is becoming editable
1162 // And there was no "ghost" value in the grid to restore from.
1163 const int defaultDrill = pcbIUScale.mmToIU( 1.0 );
1164 target->SetDrillSize( { defaultDrill, defaultDrill } );
1165 }
1166 }
1167
1168 UpdateDrillCells( *m_grid, *m_unitsProvider, row, *target, !drillsAreEditable );
1169 }
1170 else if( col == COL_P2D_LENGTH || col == COL_P2D_DELAY )
1171 {
1172 // Pad-to-die values are not drawn on the canvas.
1173 needCanvasRefresh = false;
1174 }
1175
1176 if( col == COL_NUMBER )
1177 m_summaryDirty = true;
1178
1179 // Request redraw (simple approach)
1180 target->SetDirty();
1181
1182 if( needCanvasRefresh )
1183 {
1184 if( PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() ) )
1185 {
1186 if( KIGFX::PCB_VIEW* view = base->GetCanvas()->GetView() )
1187 {
1188 // Some changes, e.g. type change, can change the layers
1189 view->Update( target, KIGFX::REPAINT | KIGFX::LAYERS );
1190 }
1191
1192 base->GetCanvas()->ForceRefresh();
1193 }
1194 }
1195}
1196
1197
1199{
1200 int row = aEvent.GetRow();
1201
1202 if( !m_footprint )
1203 return;
1204
1205 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1206 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1207
1208 // Clear existing pad selections
1209 for( PAD* pad : m_footprint->Pads() )
1210 {
1211 if( pad->IsBrightened() )
1212 {
1213 pad->ClearBrightened();
1214
1215 if( canvas )
1216 canvas->GetView()->Update( pad, KIGFX::REPAINT );
1217 }
1218 }
1219
1220 PAD* pad = getPadForRow( row );
1221
1222 if( !pad )
1223 return;
1224
1225 pad->SetBrightened();
1226
1227 if( canvas )
1228 {
1229 canvas->GetView()->Update( pad, KIGFX::REPAINT );
1230 canvas->ForceRefresh();
1231 }
1232}
1233
1234
1235void DIALOG_FP_EDIT_PAD_TABLE::OnUpdateUI( wxUpdateUIEvent& aEvent )
1236{
1237 if( m_summaryDirty )
1238 {
1239 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
1240 {
1241 int row = m_grid->GetGridCursorRow();
1242 int col = m_grid->GetGridCursorCol();
1243
1244 PAD* target = getPadForRow( row );
1245
1246 if( !target )
1247 return;
1248
1249 wxGridCellEditor* editor = m_grid->GetCellEditor( row, col );
1250
1251 if( editor )
1252 {
1253 target->SetNumber( editor->GetValue() );
1254 editor->DecRef();
1255 }
1256 }
1257
1258 updateSummary();
1259 m_summaryDirty = false;
1260 }
1261}
1262
1263
1264void DIALOG_FP_EDIT_PAD_TABLE::OnCancel( wxCommandEvent& aEvent )
1265{
1266 // The destructor rolls back everything that was not accepted.
1267 m_accepted = false;
1268 aEvent.Skip();
1269}
1270
1271
1273{
1274 PIN_NUMBERS pinNumbers;
1275
1276 for( PAD* pad : m_footprint->Pads() )
1277 {
1278 if( pad->GetNumber().Length() )
1279 pinNumbers.insert( pad->GetNumber() );
1280 }
1281
1282 const wxString summary = pinNumbers.GetSummary();
1283 const wxString duplicates = pinNumbers.GetDuplicates();
1284
1285 m_pin_numbers_summary->SetLabel( summary );
1286 m_pin_numbers_summary->SetToolTip( summary );
1287 m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_footprint->Pads().size() ) );
1288 m_duplicate_pins->SetLabel( duplicates );
1289 m_duplicate_pins->SetToolTip( duplicates );
1290
1291 Layout();
1292}
1293
1294
1296{
1297 if( aRowId < 0 || static_cast<size_t>( aRowId ) >= m_rowPads.size() )
1298 return nullptr;
1299
1300 return m_rowPads[aRowId];
1301}
1302
1303
1305{
1306 bool toFile = aEvent.GetEventObject() == m_btnExportToFile;
1307
1308 wxString filePath;
1309
1310 if( toFile )
1311 {
1312 wxFileName fn( m_footprint->GetFPID().GetLibItemName() );
1313 fn.SetExt( FILEEXT::CsvFileExtension );
1314
1315 wxFileDialog dlg( this, _( "Select pad data file" ), "", fn.GetFullName(), FILEEXT::CsvTsvFileWildcard(),
1316 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1317
1319
1320 if( dlg.ShowModal() == wxID_CANCEL )
1321 return;
1322
1323 filePath = dlg.GetPath();
1324 }
1325
1326 std::vector<PAD*> padsToExport;
1327 bool complexPadstacks = false;
1328
1329 for( PAD* pad : m_rowPads )
1330 {
1331 if( pad->GetPadstackMode() != PADSTACK::MODE::NORMAL )
1332 {
1333 complexPadstacks = true;
1334 continue;
1335 }
1336
1337 padsToExport.push_back( pad );
1338 }
1339
1340 if( complexPadstacks )
1341 {
1342 if( wxMessageBox( _( "Complex padstacks cannot be exported via CSV.\n\nThey will be skipped." ),
1343 _( "Export Pad Table" ), wxOK | wxCANCEL | wxICON_WARNING, this ) != wxOK )
1344 {
1345 return;
1346 }
1347 }
1348
1349 static const std::vector<COLS> exportCols {
1350 COLS::COL_NUMBER,
1351 COLS::COL_TYPE,
1352 COLS::COL_SHAPE,
1353 COLS::COL_POS_X,
1354 COLS::COL_POS_Y,
1355 COLS::COL_SIZE_X,
1356 COLS::COL_SIZE_Y,
1357 COLS::COL_DRILL_X,
1358 COLS::COL_DRILL_Y,
1359 COLS::COL_P2D_LENGTH,
1360 COLS::COL_P2D_DELAY,
1361 };
1362
1365
1366 std::vector<std::vector<wxString>> table;
1367 table.reserve( padsToExport.size() + 1 );
1368
1369 std::vector<wxString> header;
1370 header.reserve( exportCols.size() );
1371
1372 for( COLS col : exportCols )
1373 header.emplace_back( wxGetTranslation( GetPadTableColLabel( col ) ) );
1374
1375 table.emplace_back( std::move( header ) );
1376
1377 for( PAD* pad : padsToExport )
1378 {
1379 std::vector<wxString>& row = table.emplace_back();
1380 row.reserve( exportCols.size() );
1381
1382 for( COLS col : exportCols )
1383 row.emplace_back( formatter.Format( *pad, col ) );
1384 }
1385
1387}
1388
1389
1391{
1392 bool fromFile = aEvent.GetEventObject() == m_btnImportFromFile;
1393 bool replaceAll = m_rbReplaceExisting->GetValue();
1394
1396
1398
1399 std::optional<std::vector<std::vector<wxString>>> csvData = ReadTableFromFileOrClipboard( *m_frame, fromFile );
1400
1401 // The pad table does not and cannot capture the full glory of a PAD object
1402 // (for example a custom pad's custom shape). So, when we are re-importing
1403 // pads and "replacing existing", we attempt to rematch the imported pads
1404 // to the existing ones by pad number. If a match is found, we update the
1405 // existing pad with the imported data. If no match is found, we add the
1406 // imported pad as a new pad. If "replace existing" is not selected, we
1407 // simply add the imported pads as new pads.
1408
1409 // Group the current pads by case-insensitive pad number so each imported
1410 // row can be rematched with a single map lookup rather than a linear scan.
1411 // Buckets keep the grid order, so this is stable as we'll match in grid order.
1412 std::map<wxString, std::vector<PAD*>> padsByNumber;
1413
1414 for( PAD* pad : m_rowPads )
1415 padsByNumber[pad->GetNumber()].push_back( pad );
1416
1417 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1418 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1419 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1420
1421 std::vector<std::unique_ptr<PAD>> createdPads;
1422
1423 if( csvData && csvData->size() >= 2 )
1424 {
1425 std::vector<COLS> headerCols;
1426 wxArrayString unknownHeaders;
1427 std::optional<size_t> numberColIdx;
1428
1429 for( const wxString& label : ( *csvData )[0] )
1430 {
1431 COLS col = GetColTypeForString( label );
1432
1433 if( col >= COLS::COL_COUNT )
1434 unknownHeaders.push_back( label );
1435
1436 if( col == COLS::COL_NUMBER )
1437 numberColIdx = headerCols.size();
1438
1439 headerCols.push_back( col );
1440 }
1441
1442 if( replaceAll && !numberColIdx.has_value() )
1443 {
1444 wxString msg = _( "Imported pad data must include pad numbers." );
1445 wxMessageBox( msg, _( "Import error" ), wxOK | wxICON_ERROR, this );
1446 return;
1447 }
1448
1449 if( !unknownHeaders.IsEmpty() )
1450 {
1451 wxString msg = wxString::Format( _( "Unknown columns in data: %s. These columns will be ignored." ),
1452 AccumulateDescriptions( unknownHeaders ) );
1453 reporter.Report( msg, RPT_SEVERITY_WARNING );
1454 }
1455
1456 if( reporter.HasMessage() )
1457 {
1458 int ret = wxMessageBox( reporter.GetMessages(), _( "Errors" ), wxOK | wxCANCEL | wxICON_ERROR, this );
1459
1460 if( ret == wxCANCEL )
1461 return;
1462 }
1463
1464 for( size_t i = 1; i < csvData->size(); ++i )
1465 {
1466 const std::vector<wxString>& cols = ( *csvData )[i];
1467
1468 // Decide whether to create a new pad or update an existing one based on the
1469 // "replace existing" option and the pad number.
1470 std::unique_ptr<PAD> newPad;
1471 PAD* padToUpdate = nullptr;
1472
1473 const size_t numberCol = numberColIdx.value_or( cols.size() );
1474
1475 if( replaceAll && numberCol < cols.size() && !cols[numberCol].IsEmpty() )
1476 {
1477 auto bucketIt = padsByNumber.find( cols[numberCol] );
1478
1479 if( bucketIt != padsByNumber.end() && !bucketIt->second.empty() )
1480 {
1481 padToUpdate = bucketIt->second.front();
1482 bucketIt->second.erase( bucketIt->second.begin() );
1483 }
1484 }
1485
1486 if( !padToUpdate )
1487 {
1488 // We are adding a new pad, not updating an existing one.
1489 newPad = std::make_unique<PAD>( m_footprint );
1490 padToUpdate = newPad.get();
1491 }
1492
1493 size_t maxCol = std::min( headerCols.size(), cols.size() );
1494
1495 for( size_t j = 0; j < maxCol; ++j )
1496 {
1497 if( headerCols[j] == COLS::COL_COUNT )
1498 continue;
1499
1500 fmt.UpdatePad( *padToUpdate, cols[j], headerCols[j] );
1501 }
1502
1503 // Invalidate the pad's cached draw data and mark it for re-render
1504 // so the canvas refresh below shows the imported values.
1505 padToUpdate->SetDirty();
1506
1507 // Type changes can change the pad's layers
1508 if( view )
1509 view->Update( padToUpdate, KIGFX::REPAINT | KIGFX::LAYERS );
1510
1511 if( newPad )
1512 createdPads.push_back( std::move( newPad ) );
1513 }
1514
1515 // Bulk-load the imported pads into the view.
1516 if( !createdPads.empty() )
1517 {
1518 if( view )
1519 {
1520 std::vector<KIGFX::VIEW_ITEM*> viewPads;
1521
1522 for( const auto& pad : createdPads )
1523 viewPads.push_back( pad.get() );
1524
1525 view->AddBatch( viewPads );
1526 }
1527
1528 for( std::unique_ptr<PAD>& pad : createdPads )
1529 m_footprint->Add( pad.release(), ADD_MODE::BULK_APPEND, true );
1530 }
1531
1532 // Any pads not matched to an imported pad are removed from the footprint.
1533 // They are kept alive so they can be restored on cancel and staged as
1534 // removals when the dialog is accepted.
1535 if( replaceAll )
1536 {
1537 for( const auto& [number, pads] : padsByNumber )
1538 {
1539 for( PAD* pad : pads )
1540 {
1541 m_footprint->Remove( pad );
1542
1543 if( view )
1544 view->Remove( pad );
1545
1546 m_removedPads.push_back( pad );
1547 }
1548 }
1549 }
1550 }
1551
1552 // Commit any in-progress cell edits so the grid rebuild below does not
1553 // discard them.
1554 m_grid->CommitPendingChanges();
1555
1556 // Rebuild the row mapping from the pads now in the footprint.
1557 m_rowPads.clear();
1558
1559 for( PAD* pad : m_footprint->Pads() )
1560 m_rowPads.push_back( pad );
1561
1562 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
1563
1564 // Rebuild the grid to match the new pad count.
1565 int currentRows = m_grid->GetNumberRows();
1566 int neededRows = static_cast<int>( m_rowPads.size() );
1567
1568 if( neededRows > currentRows )
1569 m_grid->AppendRows( neededRows - currentRows );
1570 else if( neededRows < currentRows )
1571 m_grid->DeleteRows( neededRows, currentRows - neededRows );
1572
1574 updateSummary();
1575
1576 if( canvas )
1577 canvas->ForceRefresh();
1578}
1579
1580
1581void DIALOG_FP_EDIT_PAD_TABLE::OnAddRow( wxCommandEvent& aEvent )
1582{
1583 if( !m_footprint )
1584 return;
1585
1586 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1587 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1588 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1589
1590 m_grid->OnAddRow(
1591 [&]() -> std::pair<int, int>
1592 {
1593 PAD* newPad = nullptr;
1594
1595 // Copy the settings of the last pad onto the new pad and offset its position
1596 // by the current grid so the copy is easy to find.
1597 if( !m_rowPads.empty() )
1598 {
1599 newPad = static_cast<PAD*>( m_rowPads.back()->Duplicate( false, nullptr ) );
1600 newPad->Move( VECTOR2I( 0, KiROUND( canvas->GetGAL()->GetGridSize().y ) ) );
1601 }
1602 else
1603 {
1604 newPad = new PAD( m_footprint );
1605 }
1606
1607 if( view )
1608 view->Add( newPad );
1609
1610 m_rowPads.push_back( newPad );
1611 m_footprint->Add( newPad );
1612
1613 int row = m_grid->GetNumberRows();
1614 m_grid->AppendRows( 1 );
1615 fillGridRow( row, newPad );
1616 updateSummary();
1617
1618 if( canvas )
1619 canvas->ForceRefresh();
1620
1621 return { row, COL_NUMBER };
1622 } );
1623}
1624
1625
1626void DIALOG_FP_EDIT_PAD_TABLE::OnDeleteRow( wxCommandEvent& aEvent )
1627{
1628 if( !m_footprint )
1629 return;
1630
1631 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1632 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1633 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1634
1635 m_grid->OnDeleteRows(
1636 [&]( int row )
1637 {
1638 if( row < 0 || static_cast<size_t>( row ) >= m_rowPads.size() )
1639 return;
1640
1641 PAD* pad = m_rowPads[row];
1642
1643 pad->ClearBrightened();
1644 m_footprint->Remove( pad );
1645
1646 if( view )
1647 view->Remove( pad );
1648
1649 // Keep the pad alive: on OK it is staged as a removal, on cancel
1650 // it is re-added to the footprint.
1651 m_removedPads.push_back( pad );
1652
1653 m_rowPads.erase( m_rowPads.begin() + row );
1654 m_grid->DeleteRows( row, 1 );
1655 } );
1656
1657 updateSummary();
1658
1659 if( canvas )
1660 canvas->ForceRefresh();
1661}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
COLUMN_FORMATTER(UNITS_PROVIDER &aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat, REPORTER &aReporter)
UNITS_PROVIDER & m_unitsProvider
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
DIALOG_FP_EDIT_PAD_TABLE_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Pad Table"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void setPadFromGridCell(PAD &aPad, int aRowId, COLS aCol)
void OnCellChanged(wxGridEvent &aEvent) override
DIALOG_FP_EDIT_PAD_TABLE(PCB_BASE_FRAME *aParent, FOOTPRINT *aFootprint)
void OnCharHook(wxKeyEvent &aEvent) override
void fillGridRow(int aRowId, PAD *aPad)
void OnSelectCell(wxGridEvent &aEvent) override
void setRowNullableEditors(int aRowId) const
void OnImportButtonClick(wxCommandEvent &aEvent) override
bool m_accepted
Set when the changes are committed on OK.
void OnExportButtonClick(wxCommandEvent &aEvent) override
std::map< PAD *, PAD_SNAPSHOT > m_originalPads
void OnDeleteRow(wxCommandEvent &aEvent) override
void OnSize(wxSizeEvent &aEvent) override
void restorePadFromSnapshot(PAD &aPad, const PAD_SNAPSHOT &aSnap) const
void OnCancel(wxCommandEvent &aEvent) override
std::vector< double > m_colProportions
std::unique_ptr< UNITS_PROVIDER > m_unitsProvider
void OnUpdateUI(wxUpdateUIEvent &aEvent) override
void OnAddRow(wxCommandEvent &aEvent) override
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...
virtual void OnCharHook(wxKeyEvent &aEvt)
EDA_UNITS GetUserUnits() const
void ForceRefresh()
Force a redraw.
KIGFX::GAL * GetGAL() const
Return a pointer to the GAL instance used in the panel.
This class works around a bug in wxGrid where the first keystroke doesn't get sent through the valida...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
const VECTOR2D & GetGridSize() const
Return the grid size.
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition pcb_view.cpp:87
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1) override
Add a VIEW_ITEM to the view.
Definition pcb_view.cpp:53
virtual void Remove(VIEW_ITEM *aItem) override
Remove a VIEW_ITEM from the view.
Definition pcb_view.cpp:70
void AddBatch(const std::vector< VIEW_ITEM * > &aItems)
Add a batch of items to the view, using bulk-loaded R-trees for initial population.
Definition view.cpp:342
void MarkTargetDirty(int aTarget)
Set or clear target 'dirty' flag.
Definition view.h:661
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & FrontMask()
Return a mask holding all technical layers and the external CU layer on front side.
Definition lset.cpp:718
static const LSET & BackMask()
Return a mask holding all technical layers and the external CU layer on back side.
Definition lset.cpp:725
LSET & FlipStandardLayers(int aCopperLayersCount=0)
Flip the layers in this set.
Definition lset.cpp:481
A singleton reporter that reports to nowhere.
Definition reporter.h:267
void ForEachUniqueLayer(const std::function< void(PCB_LAYER_ID)> &aMethod) const
Runs the given callable for each active unique copper layer in this padstack, meaning F_Cu for MODE::...
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Class that handles conversion of various pad data fields into strings for display in the UI or serial...
void UpdatePad(PAD &aPad, const wxString &aValue, int aFieldId) const
Update the pad from the given col/string.
PAD_INFO_FORMATTER(UNITS_PROVIDER &aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat, REPORTER &aReporter)
wxString Format(const PAD &aPin, int aFieldId) const
Definition pad.h:61
bool IsAperturePad() const
Definition pad.h:567
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1651
static wxString ShowPadShape(PAD_SHAPE aShape)
Definition pad.cpp:2559
PAD_ATTRIB GetAttribute() const
Definition pad.h:560
const wxString & GetNumber() const
Definition pad.h:143
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:196
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pad.h:329
VECTOR2I GetPosition() const override
Definition pad.cpp:256
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pad.h:997
void SetDirty()
Definition pad.h:549
VECTOR2I GetDrillSize() const
Definition pad.h:320
void SetPadToDieDelay(int aDelay)
Definition pad.h:580
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:205
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:142
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:298
int GetPadToDieDelay() const
Definition pad.h:581
void SetPadstack(const PADSTACK &aPadstack)
Definition pad.h:333
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:245
const PADSTACK & Padstack() const
Definition pad.h:331
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:319
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:265
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:623
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:1968
void SetPadToDieLength(int aLength)
Definition pad.h:577
int GetPadToDieLength() const
Definition pad.h:578
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
virtual KIGFX::PCB_VIEW * GetView() const override
Return a pointer to the KIGFX::VIEW instance used in the panel.
wxString GetDuplicates() const
Gets a formatted string of all the pins that have duplicate numbers.
void insert(value_type const &v)
Definition pin_numbers.h:58
wxString GetSummary() const
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
wxString StringFromValue(double aValue, bool aAddUnitLabel=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aValue in internal units into a united string.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
bool MatchTranslationOrNative(const wxString &aStr, const wxString &aNativeLabel, bool aCaseSensitive)
Return true if the given string matches either the translated or native version of the given label.
static void UpdateDrillCells(WX_GRID &aGrid, UNITS_PROVIDER &aUnitsProvider, int aRowId, const PAD &aPad, bool aPreserveValues)
Update the drill size cells in the pad table for a given pad.
static COLS GetColTypeForString(const wxString &aStr)
static wxString GetPadTableColLabel(COLS aCol)
Get the label for a given column in the pin table.
static wxString GetPadTypeString(const PAD &aPad)
static PAD_SHAPE ShapeFromString(const wxString &shape)
static bool DrillsAreEditable(const PAD &aPad)
static wxString ShapeToString(PAD_SHAPE shape)
static void SetPadTypeFromString(PAD &aPad, const wxString &aType)
DIALOG_FP_EDIT_PAD_TABLE::COLS COLS
static COL_ORDER GetColTypeForString(const wxString &aStr)
#define _(s)
static std::map< int, wxString > shapeNames
static const std::string CsvFileExtension
static wxString CsvTsvFileWildcard()
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Paste
Definition layer_ids.h:100
@ F_Cu
Definition layer_ids.h:60
@ REPAINT
Item needs to be redrawn.
Definition view_item.h:54
@ LAYERS
Layers have changed.
Definition view_item.h:52
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:533
STL namespace.
PAD_ATTRIB
The set of pad shapes, used with PAD::{Set,Get}Attribute().
Definition padstack.h:96
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:99
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:51
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ TRAPEZOID
Definition padstack.h:55
@ RECTANGLE
Definition padstack.h:53
#define _HKI(x)
Definition page_info.cpp:40
@ RPT_SEVERITY_WARNING
void AccumulateDescriptions(wxString &aDesc, const T &aItemCollection)
Build a comma-separated list from a collection of wxStrings.
void WriteTableToFileOrClipboard(const wxString &aToFile, const std::vector< std::vector< wxString > > &aTable)
Write aTable as CSV to aToFile (if non-empty) or to the system clipboard.
Definition table_io.cpp:66
std::optional< std::vector< std::vector< wxString > > > ReadTableFromFileOrClipboard(EDA_BASE_FRAME &aFrame, bool aFromFile)
Read a CSV/TSV table from a file selected via a dialog (aFromFile) or from the system clipboard.
Definition table_io.cpp:34
IbisParser parser & reporter
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
Definition of file extensions used in Kicad.