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
178static void UpdateDrillCells( WX_GRID& aGrid, UNITS_PROVIDER& aUnitsProvider, int aRowId, const PAD& aPad,
179 bool aPreserveValues )
180{
181 const bool drillIsEditable = DrillsAreEditable( aPad );
182 const wxColour drillTextColour = drillIsEditable ? aGrid.GetDefaultCellTextColour()
183 : wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
184
185 aGrid.SetReadOnly( aRowId, COLS::COL_DRILL_X, !drillIsEditable );
186 aGrid.SetReadOnly( aRowId, COLS::COL_DRILL_Y, !drillIsEditable );
187 aGrid.SetCellTextColour( aRowId, COLS::COL_DRILL_X, drillTextColour );
188 aGrid.SetCellTextColour( aRowId, COLS::COL_DRILL_Y, drillTextColour );
189
190 if( aPreserveValues )
191 return;
192
193 const VECTOR2I drill = aPad.GetDrillSize();
194 aGrid.SetCellValue( aRowId, COLS::COL_DRILL_X, drill.x > 0 ? aUnitsProvider.StringFromValue( drill.x, true )
195 : wxString{} );
196 aGrid.SetCellValue( aRowId, COLS::COL_DRILL_Y, drill.y > 0 ? aUnitsProvider.StringFromValue( drill.y, true )
197 : wxString{} );
198}
199
200
206static wxString GetPadTableColLabel( COLS aCol )
207{
208 switch( aCol )
209 {
210 case COLS::COL_NUMBER: return wxT( "Number" );
211 case COLS::COL_TYPE: return wxT( "Type" );
212 case COLS::COL_SHAPE: return wxT( "Shape" );
213 case COLS::COL_POS_X: return wxT( "Pos X" );
214 case COLS::COL_POS_Y: return wxT( "Pos Y" );
215 case COLS::COL_SIZE_X: return wxT( "Size X" );
216 case COLS::COL_SIZE_Y: return wxT( "Size Y" );
217 case COLS::COL_DRILL_X: return wxT( "Drill X" );
218 case COLS::COL_DRILL_Y: return wxT( "Drill Y" );
219 case COLS::COL_P2D_LENGTH: return wxT( "Pad to die length" );
220 case COLS::COL_P2D_DELAY: return wxT( "Pad to die delay" );
221 default:
222 wxFAIL_MSG( wxT( "Invalid column index" ) );
223 return wxEmptyString;
224 }
225}
226
227
228static COLS GetColTypeForString( const wxString& aStr )
229{
230 for( int i = 0; i < static_cast<int>( COLS::COL_COUNT ); i++ )
231 {
232 COLS col = static_cast<COLS>( i );
233
234 if( MatchTranslationOrNative( aStr, GetPadTableColLabel( col ), false ) )
235 return col;
236 }
237 return COLS::COL_COUNT;
238}
239
240
248{
249public:
250 PAD_INFO_FORMATTER( UNITS_PROVIDER& aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat,
251 REPORTER& aReporter ) :
252 COLUMN_FORMATTER( aUnitsProvider, aIncludeUnits, aBoolFormat, aReporter )
253 {
254 }
255
256 wxString Format( const PAD& aPin, int aFieldId ) const
257 {
258 wxCHECK_MSG( aFieldId >= 0 && aFieldId < static_cast<int>( COLS::COL_COUNT ), wxEmptyString,
259 wxT( "Invalid column index" ) );
260
261 const DIALOG_FP_EDIT_PAD_TABLE::COLS col = static_cast<DIALOG_FP_EDIT_PAD_TABLE::COLS>( aFieldId );
262
263 switch( col )
264 {
266 return aPin.GetNumber();
267
269 return GetPadTypeString( aPin );
270
273
275 return m_unitsProvider.StringFromValue( aPin.GetCenter().x, m_includeUnits );
276
278 return m_unitsProvider.StringFromValue( aPin.GetCenter().y, m_includeUnits );
279
281 return m_unitsProvider.StringFromValue( aPin.GetSize( PADSTACK::ALL_LAYERS ).x, m_includeUnits );
282
284 return m_unitsProvider.StringFromValue( aPin.GetSize( PADSTACK::ALL_LAYERS ).y, m_includeUnits );
285
287 return m_unitsProvider.StringFromValue( aPin.GetDrillSize().x, m_includeUnits );
288
290 return m_unitsProvider.StringFromValue( aPin.GetDrillSize().y, m_includeUnits );
291
293 if( aPin.GetPadToDieLength() )
294 return m_unitsProvider.StringFromValue( aPin.GetPadToDieLength(), m_includeUnits );
295
296 return wxEmptyString;
297
299 if( aPin.GetPadToDieDelay() )
300 return m_unitsProvider.StringFromValue( aPin.GetPadToDieDelay(), m_includeUnits, EDA_DATA_TYPE::TIME );
301
302 return wxEmptyString;
303
305 return wxEmptyString;
306 }
307
308 wxFAIL_MSG( "Invalid column index" );
309 return wxEmptyString;
310 }
311
318 void UpdatePad( PAD& aPad, const wxString& aValue, int aFieldId ) const
319 {
320 switch( aFieldId )
321 {
323 aPad.SetNumber( aValue );
324 break;
325
327 SetPadTypeFromString( aPad, aValue );
328 break;
329
331 // Note that this will blow away any layer-specific shapes, but this process
332 // doesn't support layer-specific editing.
334 [&]( PCB_LAYER_ID aLayer )
335 {
336 aPad.SetShape( aLayer, ShapeFromString( aValue ) );
337 } );
338 break;
339
341 {
342 VECTOR2I pos = aPad.GetPosition();
343 pos.x = m_unitsProvider.ValueFromString( aValue );
344 aPad.SetPosition( pos );
345 break;
346 }
347
349 {
350 VECTOR2I pos = aPad.GetPosition();
351 pos.y = m_unitsProvider.ValueFromString( aValue );
352 aPad.SetPosition( pos );
353 break;
354 }
355
357 // Same as shape: overwrite all layers
359 [&]( PCB_LAYER_ID aLayer )
360 {
361 VECTOR2I size = aPad.GetSize( aLayer );
362 size.x = m_unitsProvider.ValueFromString( aValue );
363 aPad.SetSize( aLayer, size );
364 } );
365 break;
366
368 // Same as shape: overwrite all layers
370 [&]( PCB_LAYER_ID aLayer )
371 {
372 VECTOR2I size = aPad.GetSize( aLayer );
373 size.y = m_unitsProvider.ValueFromString( aValue );
374 aPad.SetSize( aLayer, size );
375 } );
376 break;
377
379 {
380 VECTOR2I drill = aPad.GetDrillSize();
381 drill.x = m_unitsProvider.ValueFromString( aValue );
382 aPad.SetDrillSize( drill );
383 break;
384 }
385
387 {
388 VECTOR2I drill = aPad.GetDrillSize();
389 drill.y = m_unitsProvider.ValueFromString( aValue );
390 aPad.SetDrillSize( drill );
391 break;
392 }
393
395 if( aValue.empty() )
396 aPad.SetPadToDieLength( 0 );
397 else
398 aPad.SetPadToDieLength( m_unitsProvider.ValueFromString( aValue ) );
399
400 break;
401
403 if( aValue.empty() )
404 aPad.SetPadToDieDelay( 0 );
405 else
406 aPad.SetPadToDieDelay( m_unitsProvider.ValueFromString( aValue, EDA_DATA_TYPE::TIME ) );
407
408 break;
409
410 default:
411 wxFAIL_MSG( "Invalid column index" );
412 break;
413 }
414 }
415};
416
417
419 DIALOG_FP_EDIT_PAD_TABLE_BASE( (wxWindow*) aParent ),
420 m_frame( aParent ),
421 m_footprint( aFootprint ),
423 m_summaryDirty( true )
424{
426
427 // The base class created a single placeholder row; resize the grid to fit the pads.
428 if( m_grid->GetNumberRows() > 0 )
429 m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
430
431 if( !m_originalPads.empty() )
432 m_grid->AppendRows( static_cast<int>( m_originalPads.size() ) );
433
434 // Constrain summary label widths so they ellipsize rather than push the layout around
435 // when long pin-number summaries (or duplicate lists) are produced.
436 const int summaryW = m_pin_numbers_summary->GetCharWidth() * 30;
437
438 m_duplicate_pins->SetWindowStyleFlag( m_duplicate_pins->GetWindowStyleFlag() | wxST_ELLIPSIZE_END );
439 m_pin_numbers_summary->SetMaxSize( wxSize( summaryW, -1 ) );
440
441 m_duplicate_pins->SetWindowStyleFlag( m_duplicate_pins->GetWindowStyleFlag() | wxST_ELLIPSIZE_END );
442 m_duplicate_pins->SetMaxSize( wxSize( summaryW, -1 ) );
443
444 wxGridCellAttr* attr = nullptr;
445
446 // Type column editor (attribute)
447 attr = new wxGridCellAttr;
448 wxArrayString typeNames;
449 typeNames.push_back( _( "Through-hole" ) ); // PTH
450 typeNames.push_back( _( "SMD" ) ); // SMD
451 typeNames.push_back( _( "Connector" ) ); // CONN SMD? (use CONN?)
452 typeNames.push_back( _( "NPTH" ) ); // NPTH
453 typeNames.push_back( _( "Aperture" ) ); // inferred copper-less
454 attr->SetEditor( new GRID_CELL_COMBOBOX( typeNames ) );
455 m_grid->SetColAttr( COL_TYPE, attr );
456
457 attr = new wxGridCellAttr;
458 wxArrayString shapeNames;
466 attr->SetEditor( new GRID_CELL_COMBOBOX( shapeNames ) );
467 m_grid->SetColAttr( COL_SHAPE, attr );
468
469 attr = new wxGridCellAttr;
470 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
471 m_grid->SetColAttr( COL_POS_X, attr );
472
473 attr = new wxGridCellAttr;
474 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
475 m_grid->SetColAttr( COL_POS_Y, attr );
476
477 attr = new wxGridCellAttr;
478 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
479 m_grid->SetColAttr( COL_SIZE_X, attr );
480
481 attr = new wxGridCellAttr;
482 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
483 m_grid->SetColAttr( COL_SIZE_Y, attr );
484
485 // Drill X
486 attr = new wxGridCellAttr;
487 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
488 m_grid->SetColAttr( COL_DRILL_X, attr );
489
490 // Drill Y
491 attr = new wxGridCellAttr;
492 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
493 m_grid->SetColAttr( COL_DRILL_Y, attr );
494
495 // Pad->Die Length
496 m_grid->SetAutoEvalColUnits( COL_P2D_LENGTH, m_unitsProvider->GetUnitsFromType( EDA_DATA_TYPE::DISTANCE ) );
497
498 // Pad->Die Delay
499 m_grid->SetAutoEvalColUnits( COL_P2D_DELAY, m_unitsProvider->GetUnitsFromType( EDA_DATA_TYPE::TIME ) );
500
501 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_X );
502 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_Y );
503 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_X );
504 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_Y );
505 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_X );
506 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_Y );
507 m_grid->SetAutoEvalCols( { COL_POS_X, COL_POS_Y,
511 COL_P2D_DELAY } );
512
513 // add Cut, Copy, and Paste to wxGrid
514 m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
515
518
520
521 Layout();
523
524 // Cap the initial height so the dialog does not grow off-screen for footprints
525 // with many pads. The grid grows to fill the available space via wxEXPAND.
526 // Use the parent window to find the display since this dialog isn't shown yet.
527 int displayIdx = wxDisplay::GetFromWindow( aParent );
528
529 if( displayIdx == wxNOT_FOUND )
530 displayIdx = 0;
531
532 wxRect displayArea = wxDisplay( (unsigned int) displayIdx ).GetClientArea();
533 wxSize dlgSize = GetSize();
534 int maxH = ( displayArea.height * 4 ) / 5;
535
536 if( dlgSize.y > maxH )
537 {
538 dlgSize.y = maxH;
539 SetSize( dlgSize );
540
541 // Reset minimum height so the user can resize the capped dialog freely.
542 // The minimum width from finishDialogSettings() is still honoured.
543 wxSize minSz = GetMinSize();
544 minSz.y = -1;
545 SetMinSize( minSz );
546
547 Centre();
548 }
549}
550
551
553{
554 // Roll back any session changes unless the dialog was accepted. This means
555 // rollback happens on both title-bar 'X'/Esc and Cancel.
556 if( !m_accepted )
558
559 // destroy GRID_TRICKS before m_grid.
560 m_grid->PopEventHandler( true );
561}
562
563
565{
566 m_grid->SetCellValue( aRowId, COL_NUMBER, aPad->GetNumber() );
567
568 wxString attrStr = GetPadTypeString( *aPad );
569 int size_x = aPad->GetSize( F_Cu ).x;
570 int size_y = aPad->GetSize( F_Cu ).y;
571 wxString padShape = aPad->ShowPadShape( F_Cu );
572
574 [&]( PCB_LAYER_ID aLayer )
575 {
576 if( aPad->GetSize( aLayer ).x != size_x )
577 size_x = -1;
578
579 if( aPad->GetSize( aLayer ).y != size_y )
580 size_y = -1;
581
582 if( aPad->ShowPadShape( aLayer ) != padShape )
583 padShape = INDETERMINATE_STATE;
584 } );
585
586 m_grid->SetCellValue( aRowId, COL_TYPE, attrStr );
587 m_grid->SetCellValue( aRowId, COL_SHAPE, padShape );
588 m_grid->SetCellValue( aRowId, COL_POS_X, m_unitsProvider->StringFromValue( aPad->GetPosition().x, true ) );
589 m_grid->SetCellValue( aRowId, COL_POS_Y, m_unitsProvider->StringFromValue( aPad->GetPosition().y, true ) );
590 m_grid->SetCellValue( aRowId, COL_SIZE_X, size_x >= 0 ? m_unitsProvider->StringFromValue( size_x, true )
592 m_grid->SetCellValue( aRowId, COL_SIZE_Y, size_y >= 0 ? m_unitsProvider->StringFromValue( size_y, true )
594
595 UpdateDrillCells( *m_grid, *m_unitsProvider, aRowId, *aPad, false );
596
597 // Pad to die metrics
598 if( aPad->GetPadToDieLength() )
599 m_grid->SetUnitValue( aRowId, COL_P2D_LENGTH, aPad->GetPadToDieLength() );
600 else
601 m_grid->SetCellValue( aRowId, COL_P2D_LENGTH, wxEmptyString );
602
603 if( aPad->GetPadToDieDelay() )
604 m_grid->SetUnitValue( aRowId, COL_P2D_DELAY, aPad->GetPadToDieDelay() );
605 else
606 m_grid->SetCellValue( aRowId, COL_P2D_DELAY, wxEmptyString );
607
608 setRowNullableEditors( aRowId );
609}
610
611
613{
614 if( !m_footprint )
615 return false;
616
617 int row = 0;
618
619 for( PAD* pad : m_rowPads )
620 {
621 fillGridRow( row, pad );
622 row++;
623 }
624
625 // Auto size the data columns first to get reasonable initial widths
626 m_grid->AutoSizeColumns();
627
628 // Ensure the Shape column (index 1) is wide enough for the longest translated
629 // shape text plus the dropdown arrow / padding. We compute a max text width
630 // using a device context and add a platform neutral padding.
631 {
632 wxClientDC dc( m_grid );
633 dc.SetFont( m_grid->GetFont() );
634
635 wxArrayString shapeNames;
643
644 int maxWidth = 0;
645
646 for( const wxString& str : shapeNames )
647 {
648 int w, h;
649 dc.GetTextExtent( str, &w, &h );
650 maxWidth = std::max( maxWidth, w );
651 }
652
653 // Add padding for internal cell margins + dropdown control.
654 int padding = FromDIP( 30 ); // heuristic: 2*margin + arrow button
655 m_grid->SetColSize( COL_SHAPE, maxWidth + padding );
656 }
657
658 // Record initial proportions for proportional resizing later.
660
661 // Run an initial proportional resize using current client size so columns
662 // respect proportions immediately.
663 wxSizeEvent sizeEvt( GetSize(), GetId() );
664 CallAfter(
665 [this, sizeEvt]
666 {
667 wxSizeEvent evt( sizeEvt );
668 this->OnSize( evt );
669 } );
670
671 // If pads exist, select the first row to show initial highlight
672 if( m_grid->GetNumberRows() > 0 )
673 {
674 m_grid->SetGridCursor( 0, 0 );
675
676 // Construct event with required parameters (id, type, obj, row, col,...)
677 wxGridEvent ev( m_grid->GetId(), wxEVT_GRID_SELECT_CELL, m_grid, 0, 0, -1, -1, true );
678 OnSelectCell( ev );
679 }
680
681 return true;
682}
683
684
686{
687 // Set nullable editors
688 auto setCellEditor =
689 [this, aRowId]( int aCol )
690 {
692 wxGridCellAttr* attr = m_grid->GetOrCreateCellAttr( aRowId, aCol );
693 attr->SetEditor( cellEditor );
694 attr->DecRef();
695 };
696
697 setCellEditor( COL_P2D_LENGTH );
698 setCellEditor( COL_P2D_DELAY );
699}
700
701
703{
704 m_originalPads.clear();
705 m_rowPads.clear();
706 m_removedPads.clear();
707
708 if( !m_footprint )
709 return;
710
711 for( PAD* pad : m_footprint->Pads() )
712 {
713 PAD_SNAPSHOT snap( pad );
714 snap.number = pad->GetNumber();
715 snap.position = pad->GetPosition();
716 snap.padstack = pad->Padstack();
717 snap.attribute = pad->GetAttribute();
718 snap.padToDieLength= pad->GetPadToDieLength();
719 snap.padToDieDelay = pad->GetPadToDieDelay();
720
721 m_originalPads.try_emplace( pad, std::move( snap ) );
722 m_rowPads.push_back( pad );
723 }
724
725 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
726}
727
728
730{
731 aPad.SetNumber( aSnap.number );
732 aPad.SetPosition( aSnap.position );
733 aPad.SetPadstack( aSnap.padstack );
734 aPad.SetAttribute( aSnap.attribute );
735 aPad.SetPadToDieLength( aSnap.padToDieLength );
736 aPad.SetPadToDieDelay( aSnap.padToDieDelay );
737}
738
739
741{
742 if( !m_footprint )
743 return;
744
745 const PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
746 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
747
748 for( PAD* pad : m_footprint->Pads() )
749 {
750 // Clear brighted even if the pad isn't original
751 pad->ClearBrightened();
752
753 if( !m_originalPads.contains( pad ) )
754 continue;
755
757
758 if( canvas )
759 canvas->GetView()->Update( pad, KIGFX::REPAINT );
760 }
761}
762
763
765{
766 if( !m_footprint )
767 return;
768
770
771 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
772 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
773 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
774
775 // Remove pads added during the session; they have no snapshot entry.
776 std::vector<PAD*> livePads( m_footprint->Pads().begin(), m_footprint->Pads().end() );
777
778 for( PAD* pad : livePads )
779 {
780 if( m_originalPads.contains( pad ) )
781 continue;
782
783 m_footprint->Remove( pad );
784
785 if( view )
786 view->Remove( pad );
787
788 delete pad;
789 }
790
791 // Re-add pads removed during the session and restore their original data.
792 // Pads with no snapshot were added and removed again, so just drop them.
793 for( PAD* pad : m_removedPads )
794 {
795 auto snapIt = m_originalPads.find( pad );
796
797 if( snapIt == m_originalPads.end() )
798 {
799 delete pad;
800 continue;
801 }
802
803 restorePadFromSnapshot( *pad, snapIt->second );
804 m_footprint->Add( pad );
805
806 if( view )
807 view->Add( pad );
808 }
809
810 m_removedPads.clear();
811
812 // Rebuild the row mapping so no pointers to freed pads survive for any UI
813 // events still pending while the dialog closes.
814 m_rowPads.clear();
815
816 for( PAD* pad : m_footprint->Pads() )
817 m_rowPads.push_back( pad );
818
819 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
820
821 if( canvas )
822 {
824 canvas->ForceRefresh();
825 }
826
827 m_summaryDirty = true;
828}
829
830
832{
833 switch( aCol )
834 {
835 case COL_NUMBER:
836 aPad.SetNumber( m_grid->GetCellValue( aRowId, aCol ) );
837 break;
838
839 case COL_TYPE:
840 SetPadTypeFromString( aPad, m_grid->GetCellValue( aRowId, aCol ) );
841 break;
842
843 case COL_SHAPE:
844 {
845 const wxString shape = m_grid->GetCellValue( aRowId, aCol );
846
847 if( shape == INDETERMINATE_STATE )
848 break;
849
850 const PAD_SHAPE newShape = ShapeFromString( shape );
851
853 [&]( PCB_LAYER_ID aLayer )
854 {
855 aPad.SetShape( aLayer, newShape );
856 } );
857 break;
858 }
859
860 case COL_POS_X:
861 case COL_POS_Y:
862 {
863 VECTOR2I pos = aPad.GetPosition();
864
865 if( aCol == COL_POS_X )
866 pos.x = m_grid->GetUnitValue( aRowId, aCol );
867 else
868 pos.y = m_grid->GetUnitValue( aRowId, aCol );
869
870 aPad.SetPosition( pos );
871 break;
872 }
873
874 case COL_SIZE_X:
875 case COL_SIZE_Y:
876 {
877 const wxString sizeValue = m_grid->GetCellValue( aRowId, aCol );
878
879 if( sizeValue == INDETERMINATE_STATE )
880 break;
881
882 const int size = m_grid->GetUnitValue( aRowId, aCol );
883
885 [&]( PCB_LAYER_ID aLayer )
886 {
887 VECTOR2I layerSize = aPad.GetSize( aLayer );
888
889 if( aCol == COL_SIZE_X )
890 layerSize.x = size;
891 else
892 layerSize.y = size;
893
894 aPad.SetSize( aLayer, layerSize );
895 } );
896 break;
897 }
898
899 case COL_DRILL_X:
900 case COL_DRILL_Y:
901 {
902 // Drill sizes (only if attribute allows)
903 if( DrillsAreEditable( aPad ) )
904 {
905 int drillX = m_grid->GetUnitValue( aRowId, COL_DRILL_X );
906 int drillY = m_grid->GetUnitValue( aRowId, COL_DRILL_Y );
907
908 if( drillX > 0 || drillY > 0 )
909 {
910 if( drillX <= 0 )
911 drillX = drillY;
912
913 if( drillY <= 0 )
914 drillY = drillX;
915
916 aPad.SetDrillSize( { drillX, drillY } );
917 }
918 }
919
920 break;
921 }
922
923 case COL_P2D_LENGTH:
924 {
925 const wxString lenStr = m_grid->GetCellValue( aRowId, aCol );
926
927 if( lenStr.IsEmpty() )
928 aPad.SetPadToDieLength( 0 );
929 else
930 aPad.SetPadToDieLength( m_grid->GetUnitValue( aRowId, aCol ) );
931
932 break;
933 }
934
935 case COL_P2D_DELAY:
936 {
937 const wxString delayStr = m_grid->GetCellValue( aRowId, aCol );
938
939 if( delayStr.IsEmpty() )
940 aPad.SetPadToDieDelay( 0 );
941 else
942 aPad.SetPadToDieDelay( m_grid->GetUnitValue( aRowId, aCol ) );
943
944 break;
945 }
946
947 default:
948 wxFAIL_MSG( wxT( "Invalid column index" ) );
949 break;
950 }
951}
952
953
955{
956 if( !m_grid->CommitPendingChanges() )
957 return false;
958
959 if( !m_footprint )
960 return true;
961
962 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
963 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
964 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
965
967
968 BOARD_COMMIT commit( m_frame );
969
970 // Pads removed during the session are re-inserted momentarily so their
971 // removal can be staged in this commit (Push performs the actual removal).
972 for( PAD* pad : m_removedPads )
973 {
974 auto snapIt = m_originalPads.find( pad );
975
976 if( snapIt == m_originalPads.end() )
977 {
978 // A pad added and removed again during the session: it never needs
979 // to appear in the commit.
980 delete pad;
981 continue;
982 }
983
984 restorePadFromSnapshot( *pad, snapIt->second );
985 m_footprint->Add( pad );
986
987 if( view )
988 view->Add( pad );
989
990 commit.Remove( pad );
991 }
992
993 m_removedPads.clear();
994
995 int row = 0;
996
997 const auto applyRowDataToPad =
998 [&]( PAD& aPad, int aRowId )
999 {
1000 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
1001 setPadFromGridCell( aPad, aRowId, static_cast<COLS>( col ) );
1002 };
1003
1004 for( PAD* pad : m_rowPads )
1005 {
1006 if( m_originalPads.contains( pad ) )
1007 {
1008 // Existing pad: its data was already restored to the dialog-open
1009 // state above, so the commit's undo image is correct.
1010 commit.Modify( pad );
1011 applyRowDataToPad( *pad, row );
1012 }
1013 else
1014 {
1015 // Imported pad: it was added to the footprint at import time for the
1016 // canvas preview. Take it out again so the commit can stage a clean
1017 // addition.
1018 applyRowDataToPad( *pad, row );
1019
1020 m_footprint->Remove( pad );
1021
1022 if( view )
1023 view->Remove( pad );
1024
1025 commit.Add( pad );
1026 }
1027
1028 row++;
1029 }
1030
1031 commit.Push( _( "Edit Pads" ) );
1032 m_frame->Refresh();
1033
1034 m_accepted = true;
1035
1036 return true;
1037}
1038
1039
1041{
1042 m_colProportions.clear();
1043 m_minColWidths.clear();
1044
1045 if( !m_grid )
1046 return;
1047
1048 // Only consider the actual data columns (all of them since row labels are hidden)
1049 int cols = m_grid->GetNumberCols();
1050 int total = 0;
1051 std::vector<int> widths;
1052 widths.reserve( cols );
1053
1054 for( int c = 0; c < cols; ++c )
1055 {
1056 int w = m_grid->GetColSize( c );
1057 widths.push_back( w );
1058 total += w;
1059 }
1060
1061 if( total <= 0 )
1062 return;
1063
1064 for( int w : widths )
1065 {
1066 m_colProportions.push_back( (double) w / (double) total );
1067 m_minColWidths.push_back( w );
1068 }
1069}
1070
1071
1072void DIALOG_FP_EDIT_PAD_TABLE::OnSize( wxSizeEvent& aEvent )
1073{
1074 if( m_colProportions.empty() )
1075 {
1076 aEvent.Skip();
1077 return;
1078 }
1079
1080 // Compute available total width for columns and resize keeping proportions.
1081 int cols = m_grid->GetNumberCols();
1082 int available = 0;
1083
1084 for( int c = 0; c < cols; ++c )
1085 available += m_grid->GetColSize( c );
1086
1087 // Use client size of grid minus scrollbar estimate to better distribute.
1088 int clientW = m_grid->GetClientSize().x;
1089
1090 if( clientW > 0 )
1091 available = clientW; // prefer actual client width
1092
1093 int used = 0;
1094
1095 for( int c = 0; c < cols; ++c )
1096 {
1097 int target = (int) std::round( m_colProportions[c] * available );
1098 target = std::max( target, m_minColWidths[c] );
1099
1100 // Defer last column to absorb rounding diff.
1101 if( c == cols - 1 )
1102 target = std::max( available - used, m_minColWidths[c] );
1103
1104 m_grid->SetColSize( c, target );
1105 used += target;
1106 }
1107
1108 aEvent.Skip();
1109}
1110
1111
1113{
1114 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
1115 m_summaryDirty = true;
1116
1117 DIALOG_SHIM::OnCharHook( aEvent );
1118}
1119
1120
1122{
1123 int row = aEvent.GetRow();
1124 int col = aEvent.GetCol();
1125
1126 if( !m_footprint )
1127 return;
1128
1129 PAD* target = getPadForRow( row );
1130
1131 if( !target )
1132 return;
1133
1134 const bool drillsWereEditable = DrillsAreEditable( *target );
1135 bool needCanvasRefresh = true;
1136
1137 setPadFromGridCell( *target, row, static_cast<COLS>( col ) );
1138
1139 if( col == COL_TYPE )
1140 {
1141 const bool drillsAreEditable = DrillsAreEditable( *target );
1142
1143 if( drillsAreEditable )
1144 {
1145 // PAD::SetAttribute() removes drills for SMD pads. So if we roundtrip
1146 // from PTH -> SMD -> PTH, the drill size is lost. In that case, restore
1147 // from the "ghost" value in the grid if there is one.
1148 const int drillX = m_grid->GetUnitValue( row, COL_DRILL_X );
1149 const int drillY = m_grid->GetUnitValue( row, COL_DRILL_Y );
1150
1151 if( drillX > 0 || drillY > 0 )
1152 {
1153 setPadFromGridCell( *target, row, COL_DRILL_X );
1154 }
1155 else if( !drillsWereEditable )
1156 {
1157 // Use a default drill size the pad is becoming editable
1158 // And there was no "ghost" value in the grid to restore from.
1159 const int defaultDrill = pcbIUScale.mmToIU( 1.0 );
1160 target->SetDrillSize( { defaultDrill, defaultDrill } );
1161 }
1162 }
1163
1164 UpdateDrillCells( *m_grid, *m_unitsProvider, row, *target, !drillsAreEditable );
1165 }
1166 else if( col == COL_P2D_LENGTH || col == COL_P2D_DELAY )
1167 {
1168 // Pad-to-die values are not drawn on the canvas.
1169 needCanvasRefresh = false;
1170 }
1171
1172 if( col == COL_NUMBER )
1173 m_summaryDirty = true;
1174
1175 // Request redraw (simple approach)
1176 target->SetDirty();
1177
1178 if( needCanvasRefresh )
1179 {
1180 if( PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() ) )
1181 {
1182 if( KIGFX::PCB_VIEW* view = base->GetCanvas()->GetView() )
1183 {
1184 // Some changes, e.g. type change, can change the layers
1185 view->Update( target, KIGFX::REPAINT | KIGFX::LAYERS );
1186 }
1187
1188 base->GetCanvas()->ForceRefresh();
1189 }
1190 }
1191}
1192
1193
1195{
1196 int row = aEvent.GetRow();
1197
1198 if( !m_footprint )
1199 return;
1200
1201 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1202 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1203
1204 // Clear existing pad selections
1205 for( PAD* pad : m_footprint->Pads() )
1206 {
1207 if( pad->IsBrightened() )
1208 {
1209 pad->ClearBrightened();
1210
1211 if( canvas )
1212 canvas->GetView()->Update( pad, KIGFX::REPAINT );
1213 }
1214 }
1215
1216 PAD* pad = getPadForRow( row );
1217
1218 if( !pad )
1219 return;
1220
1221 pad->SetBrightened();
1222
1223 if( canvas )
1224 {
1225 canvas->GetView()->Update( pad, KIGFX::REPAINT );
1226 canvas->ForceRefresh();
1227 }
1228}
1229
1230
1231void DIALOG_FP_EDIT_PAD_TABLE::OnUpdateUI( wxUpdateUIEvent& aEvent )
1232{
1233 if( m_summaryDirty )
1234 {
1235 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
1236 {
1237 int row = m_grid->GetGridCursorRow();
1238 int col = m_grid->GetGridCursorCol();
1239
1240 PAD* target = getPadForRow( row );
1241
1242 if( !target )
1243 return;
1244
1245 wxGridCellEditor* editor = m_grid->GetCellEditor( row, col );
1246
1247 if( editor )
1248 {
1249 target->SetNumber( editor->GetValue() );
1250 editor->DecRef();
1251 }
1252 }
1253
1254 updateSummary();
1255 m_summaryDirty = false;
1256 }
1257}
1258
1259
1260void DIALOG_FP_EDIT_PAD_TABLE::OnCancel( wxCommandEvent& aEvent )
1261{
1262 // The destructor rolls back everything that was not accepted.
1263 m_accepted = false;
1264 aEvent.Skip();
1265}
1266
1267
1269{
1270 PIN_NUMBERS pinNumbers;
1271
1272 for( PAD* pad : m_footprint->Pads() )
1273 {
1274 if( pad->GetNumber().Length() )
1275 pinNumbers.insert( pad->GetNumber() );
1276 }
1277
1278 const wxString summary = pinNumbers.GetSummary();
1279 const wxString duplicates = pinNumbers.GetDuplicates();
1280
1281 m_pin_numbers_summary->SetLabel( summary );
1282 m_pin_numbers_summary->SetToolTip( summary );
1283 m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_footprint->Pads().size() ) );
1284 m_duplicate_pins->SetLabel( duplicates );
1285 m_duplicate_pins->SetToolTip( duplicates );
1286
1287 Layout();
1288}
1289
1290
1292{
1293 if( aRowId < 0 || static_cast<size_t>( aRowId ) >= m_rowPads.size() )
1294 return nullptr;
1295
1296 return m_rowPads[aRowId];
1297}
1298
1299
1301{
1302 bool toFile = aEvent.GetEventObject() == m_btnExportToFile;
1303
1304 wxString filePath;
1305
1306 if( toFile )
1307 {
1308 wxFileName fn( m_footprint->GetFPID().GetLibItemName() );
1309 fn.SetExt( FILEEXT::CsvFileExtension );
1310
1311 wxFileDialog dlg( this, _( "Select pad data file" ), "", fn.GetFullName(), FILEEXT::CsvTsvFileWildcard(),
1312 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1313
1315
1316 if( dlg.ShowModal() == wxID_CANCEL )
1317 return;
1318
1319 filePath = dlg.GetPath();
1320 }
1321
1322 std::vector<PAD*> padsToExport;
1323 bool complexPadstacks = false;
1324
1325 for( PAD* pad : m_rowPads )
1326 {
1327 if( pad->GetPadstackMode() != PADSTACK::MODE::NORMAL )
1328 {
1329 complexPadstacks = true;
1330 continue;
1331 }
1332
1333 padsToExport.push_back( pad );
1334 }
1335
1336 if( complexPadstacks )
1337 {
1338 if( wxMessageBox( _( "Complex padstacks cannot be exported via CSV.\n\nThey will be skipped." ),
1339 _( "Export Pad Table" ), wxOK | wxCANCEL | wxICON_WARNING, this ) != wxOK )
1340 {
1341 return;
1342 }
1343 }
1344
1345 static const std::vector<COLS> exportCols {
1346 COLS::COL_NUMBER,
1347 COLS::COL_TYPE,
1348 COLS::COL_SHAPE,
1349 COLS::COL_POS_X,
1350 COLS::COL_POS_Y,
1351 COLS::COL_SIZE_X,
1352 COLS::COL_SIZE_Y,
1353 COLS::COL_DRILL_X,
1354 COLS::COL_DRILL_Y,
1355 COLS::COL_P2D_LENGTH,
1356 COLS::COL_P2D_DELAY,
1357 };
1358
1361
1362 std::vector<std::vector<wxString>> table;
1363 table.reserve( padsToExport.size() + 1 );
1364
1365 std::vector<wxString> header;
1366 header.reserve( exportCols.size() );
1367
1368 for( COLS col : exportCols )
1369 header.emplace_back( wxGetTranslation( GetPadTableColLabel( col ) ) );
1370
1371 table.emplace_back( std::move( header ) );
1372
1373 for( PAD* pad : padsToExport )
1374 {
1375 std::vector<wxString>& row = table.emplace_back();
1376 row.reserve( exportCols.size() );
1377
1378 for( COLS col : exportCols )
1379 row.emplace_back( formatter.Format( *pad, col ) );
1380 }
1381
1383}
1384
1385
1387{
1388 bool fromFile = aEvent.GetEventObject() == m_btnImportFromFile;
1389 bool replaceAll = m_rbReplaceExisting->GetValue();
1390
1392
1394
1395 std::optional<std::vector<std::vector<wxString>>> csvData = ReadTableFromFileOrClipboard( *m_frame, fromFile );
1396
1397 // The pad table does not and cannot capture the full glory of a PAD object
1398 // (for example a custom pad's custom shape). So, when we are re-importing
1399 // pads and "replacing existing", we attempt to rematch the imported pads
1400 // to the existing ones by pad number. If a match is found, we update the
1401 // existing pad with the imported data. If no match is found, we add the
1402 // imported pad as a new pad. If "replace existing" is not selected, we
1403 // simply add the imported pads as new pads.
1404
1405 // Group the current pads by case-insensitive pad number so each imported
1406 // row can be rematched with a single map lookup rather than a linear scan.
1407 // Buckets keep the grid order, so this is stable as we'll match in grid order.
1408 std::map<wxString, std::vector<PAD*>> padsByNumber;
1409
1410 for( PAD* pad : m_rowPads )
1411 padsByNumber[pad->GetNumber()].push_back( pad );
1412
1413 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1414 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1415 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1416
1417 std::vector<std::unique_ptr<PAD>> createdPads;
1418
1419 if( csvData && csvData->size() >= 2 )
1420 {
1421 std::vector<COLS> headerCols;
1422 wxArrayString unknownHeaders;
1423 std::optional<size_t> numberColIdx;
1424
1425 for( const wxString& label : ( *csvData )[0] )
1426 {
1427 COLS col = GetColTypeForString( label );
1428
1429 if( col >= COLS::COL_COUNT )
1430 unknownHeaders.push_back( label );
1431
1432 if( col == COLS::COL_NUMBER )
1433 numberColIdx = headerCols.size();
1434
1435 headerCols.push_back( col );
1436 }
1437
1438 if( replaceAll && !numberColIdx.has_value() )
1439 {
1440 wxString msg = _( "Imported pad data must include pad numbers." );
1441 wxMessageBox( msg, _( "Import error" ), wxOK | wxICON_ERROR, this );
1442 return;
1443 }
1444
1445 if( !unknownHeaders.IsEmpty() )
1446 {
1447 wxString msg = wxString::Format( _( "Unknown columns in data: %s. These columns will be ignored." ),
1448 AccumulateDescriptions( unknownHeaders ) );
1449 reporter.Report( msg, RPT_SEVERITY_WARNING );
1450 }
1451
1452 if( reporter.HasMessage() )
1453 {
1454 int ret = wxMessageBox( reporter.GetMessages(), _( "Errors" ), wxOK | wxCANCEL | wxICON_ERROR, this );
1455
1456 if( ret == wxCANCEL )
1457 return;
1458 }
1459
1460 for( size_t i = 1; i < csvData->size(); ++i )
1461 {
1462 const std::vector<wxString>& cols = ( *csvData )[i];
1463
1464 // Decide whether to create a new pad or update an existing one based on the
1465 // "replace existing" option and the pad number.
1466 std::unique_ptr<PAD> newPad;
1467 PAD* padToUpdate = nullptr;
1468
1469 const size_t numberCol = numberColIdx.value_or( cols.size() );
1470
1471 if( replaceAll && numberCol < cols.size() && !cols[numberCol].IsEmpty() )
1472 {
1473 auto bucketIt = padsByNumber.find( cols[numberCol] );
1474
1475 if( bucketIt != padsByNumber.end() && !bucketIt->second.empty() )
1476 {
1477 padToUpdate = bucketIt->second.front();
1478 bucketIt->second.erase( bucketIt->second.begin() );
1479 }
1480 }
1481
1482 if( !padToUpdate )
1483 {
1484 // We are adding a new pad, not updating an existing one.
1485 newPad = std::make_unique<PAD>( m_footprint );
1486 padToUpdate = newPad.get();
1487 }
1488
1489 size_t maxCol = std::min( headerCols.size(), cols.size() );
1490
1491 for( size_t j = 0; j < maxCol; ++j )
1492 {
1493 if( headerCols[j] == COLS::COL_COUNT )
1494 continue;
1495
1496 fmt.UpdatePad( *padToUpdate, cols[j], headerCols[j] );
1497 }
1498
1499 // Invalidate the pad's cached draw data and mark it for re-render
1500 // so the canvas refresh below shows the imported values.
1501 padToUpdate->SetDirty();
1502
1503 // Type changes can change the pad's layers
1504 if( view )
1505 view->Update( padToUpdate, KIGFX::REPAINT | KIGFX::LAYERS );
1506
1507 if( newPad )
1508 createdPads.push_back( std::move( newPad ) );
1509 }
1510
1511 // Bulk-load the imported pads into the view.
1512 if( !createdPads.empty() )
1513 {
1514 if( view )
1515 {
1516 std::vector<KIGFX::VIEW_ITEM*> viewPads;
1517
1518 for( const auto& pad : createdPads )
1519 viewPads.push_back( pad.get() );
1520
1521 view->AddBatch( viewPads );
1522 }
1523
1524 for( std::unique_ptr<PAD>& pad : createdPads )
1525 m_footprint->Add( pad.release(), ADD_MODE::BULK_APPEND, true );
1526 }
1527
1528 // Any pads not matched to an imported pad are removed from the footprint.
1529 // They are kept alive so they can be restored on cancel and staged as
1530 // removals when the dialog is accepted.
1531 if( replaceAll )
1532 {
1533 for( const auto& [number, pads] : padsByNumber )
1534 {
1535 for( PAD* pad : pads )
1536 {
1537 m_footprint->Remove( pad );
1538
1539 if( view )
1540 view->Remove( pad );
1541
1542 m_removedPads.push_back( pad );
1543 }
1544 }
1545 }
1546 }
1547
1548 // Commit any in-progress cell edits so the grid rebuild below does not
1549 // discard them.
1550 m_grid->CommitPendingChanges();
1551
1552 // Rebuild the row mapping from the pads now in the footprint.
1553 m_rowPads.clear();
1554
1555 for( PAD* pad : m_footprint->Pads() )
1556 m_rowPads.push_back( pad );
1557
1558 std::sort( m_rowPads.begin(), m_rowPads.end(), PAD_SNAPSHOT_COMPARE() );
1559
1560 // Rebuild the grid to match the new pad count.
1561 int currentRows = m_grid->GetNumberRows();
1562 int neededRows = static_cast<int>( m_rowPads.size() );
1563
1564 if( neededRows > currentRows )
1565 m_grid->AppendRows( neededRows - currentRows );
1566 else if( neededRows < currentRows )
1567 m_grid->DeleteRows( neededRows, currentRows - neededRows );
1568
1570 updateSummary();
1571
1572 if( canvas )
1573 canvas->ForceRefresh();
1574}
1575
1576
1577void DIALOG_FP_EDIT_PAD_TABLE::OnAddRow( wxCommandEvent& aEvent )
1578{
1579 if( !m_footprint )
1580 return;
1581
1582 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1583 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1584 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1585
1586 m_grid->OnAddRow(
1587 [&]() -> std::pair<int, int>
1588 {
1589 PAD* newPad = nullptr;
1590
1591 // Copy the settings of the last pad onto the new pad and offset its position
1592 // by the current grid so the copy is easy to find.
1593 if( !m_rowPads.empty() )
1594 {
1595 newPad = static_cast<PAD*>( m_rowPads.back()->Duplicate( false, nullptr ) );
1596 newPad->Move( VECTOR2I( 0, KiROUND( canvas->GetGAL()->GetGridSize().y ) ) );
1597 }
1598 else
1599 {
1600 newPad = new PAD( m_footprint );
1601 }
1602
1603 if( view )
1604 view->Add( newPad );
1605
1606 m_rowPads.push_back( newPad );
1607 m_footprint->Add( newPad );
1608
1609 int row = m_grid->GetNumberRows();
1610 m_grid->AppendRows( 1 );
1611 fillGridRow( row, newPad );
1612 updateSummary();
1613
1614 if( canvas )
1615 canvas->ForceRefresh();
1616
1617 return { row, COL_NUMBER };
1618 } );
1619}
1620
1621
1622void DIALOG_FP_EDIT_PAD_TABLE::OnDeleteRow( wxCommandEvent& aEvent )
1623{
1624 if( !m_footprint )
1625 return;
1626
1627 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
1628 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
1629 KIGFX::PCB_VIEW* view = canvas ? canvas->GetView() : nullptr;
1630
1631 m_grid->OnDeleteRows(
1632 [&]( int row )
1633 {
1634 if( row < 0 || static_cast<size_t>( row ) >= m_rowPads.size() )
1635 return;
1636
1637 PAD* pad = m_rowPads[row];
1638
1639 pad->ClearBrightened();
1640 m_footprint->Remove( pad );
1641
1642 if( view )
1643 view->Remove( pad );
1644
1645 // Keep the pad alive: on OK it is staged as a removal, on cancel
1646 // it is re-added to the footprint.
1647 m_removedPads.push_back( pad );
1648
1649 m_rowPads.erase( m_rowPads.begin() + row );
1650 m_grid->DeleteRows( row, 1 );
1651 } );
1652
1653 updateSummary();
1654
1655 if( canvas )
1656 canvas->ForceRefresh();
1657}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
virtual 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:346
void MarkTargetDirty(int aTarget)
Set or clear target 'dirty' flag.
Definition view.h:659
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:565
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1639
static wxString ShowPadShape(PAD_SHAPE aShape)
Definition pad.cpp:2540
PAD_ATTRIB GetAttribute() const
Definition pad.h:558
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:327
VECTOR2I GetPosition() const override
Definition pad.cpp:246
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition pad.h:992
void SetDirty()
Definition pad.h:547
VECTOR2I GetDrillSize() const
Definition pad.h:318
void SetPadToDieDelay(int aDelay)
Definition pad.h:578
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:288
int GetPadToDieDelay() const
Definition pad.h:579
void SetPadstack(const PADSTACK &aPadstack)
Definition pad.h:331
void SetPosition(const VECTOR2I &aPos) override
Definition pad.cpp:235
const PADSTACK & Padstack() const
Definition pad.h:329
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:317
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.cpp:255
static LSET SMDMask()
layer set for a SMD pad on Front layer
Definition pad.cpp:613
void SetLayerSet(const LSET &aLayers) override
Definition pad.cpp:1955
void SetPadToDieLength(int aLength)
Definition pad.h:575
int GetPadToDieLength() const
Definition pad.h:576
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 #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:521
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:683
Definition of file extensions used in Kicad.