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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <wx/button.h>
27#include <wx/display.h>
28#include <wx/sizer.h>
29#include <wx/dcclient.h>
30#include <wx/stattext.h>
31#include <pcb_shape.h>
32#include <widgets/wx_grid.h>
35#include <base_units.h>
36#include <units_provider.h>
37#include <board.h>
38#include <footprint.h>
40#include <grid_tricks.h>
41#include <pin_numbers.h>
42#include <board_commit.h>
43
44
45// Helper to map shape string to PAD_SHAPE
46static PAD_SHAPE ShapeFromString( const wxString& shape )
47{
48 if( shape == _( "Oval" ) ) return PAD_SHAPE::OVAL;
49 if( shape == _( "Rectangle" ) ) return PAD_SHAPE::RECTANGLE;
50 if( shape == _( "Trapezoid" ) ) return PAD_SHAPE::TRAPEZOID;
51 if( shape == _( "Rounded rectangle" ) ) return PAD_SHAPE::ROUNDRECT;
52 if( shape == _( "Chamfered rectangle" ) ) return PAD_SHAPE::CHAMFERED_RECT;
53 if( shape == _( "Custom shape" ) ) return PAD_SHAPE::CUSTOM;
54
55 return PAD_SHAPE::CIRCLE;
56}
57
58
60 DIALOG_SHIM( (wxWindow*)aParent, wxID_ANY, _( "Pad Table" ), wxDefaultPosition, wxDefaultSize,
61 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
62 m_frame( aParent ),
63 m_grid( nullptr ),
64 m_footprint( aFootprint ),
66 m_summaryDirty( true )
67{
68 wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
69
70 wxBoxSizer* bSummarySizer;
71 bSummarySizer = new wxBoxSizer( wxHORIZONTAL );
72
73 m_staticTextPinNumbers = new wxStaticText( this, wxID_ANY, _( "Pad numbers:" ) );
74 m_staticTextPinNumbers->Wrap( -1 );
75 bSummarySizer->Add( m_staticTextPinNumbers, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
76
77 m_pin_numbers_summary = new wxStaticText( this, wxID_ANY, _( "0" ) );
78 m_pin_numbers_summary->Wrap( -1 );
79 bSummarySizer->Add( m_pin_numbers_summary, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
80
81 bSummarySizer->Add( 0, 0, 1, wxEXPAND, 5 );
82
83 m_staticTextPinCount = new wxStaticText( this, wxID_ANY, _( "Pad count:" ) );
84 m_staticTextPinCount->Wrap( -1 );
85 bSummarySizer->Add( m_staticTextPinCount, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 10 );
86
87 m_pin_count = new wxStaticText( this, wxID_ANY, _( "0" ) );
88 m_pin_count->Wrap( -1 );
89 bSummarySizer->Add( m_pin_count, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
90
91 bSummarySizer->Add( 0, 0, 1, wxEXPAND, 5 );
92
93 m_staticTextDuplicatePins = new wxStaticText( this, wxID_ANY, _("Duplicate pads:" ) );
94 m_staticTextDuplicatePins->Wrap( -1 );
95 bSummarySizer->Add( m_staticTextDuplicatePins, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 10 );
96
97 m_duplicate_pins = new wxStaticText( this, wxID_ANY, _( "0" ) );
98 m_duplicate_pins->Wrap( -1 );
99 bSummarySizer->Add( m_duplicate_pins, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
100
101 topSizer->Add( bSummarySizer, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 );
102
103 m_grid = new WX_GRID( this, wxID_ANY );
104 m_grid->CreateGrid( m_footprint->GetPadCount(), 11 );
105 m_grid->SetColLabelValue( COL_NUMBER, _( "Number" ) );
106 m_grid->SetColLabelValue( COL_TYPE, _( "Type" ) );
107 m_grid->SetColLabelValue( COL_SHAPE, _( "Shape" ) );
108 m_grid->SetColLabelValue( COL_POS_X, _( "X Position" ) );
109 m_grid->SetColLabelValue( COL_POS_Y, _( "Y Position" ) );
110 m_grid->SetColLabelValue( COL_SIZE_X, _( "Size X" ) );
111 m_grid->SetColLabelValue( COL_SIZE_Y, _( "Size Y" ) );
112 m_grid->SetColLabelValue( COL_DRILL_X, _( "Drill X" ) );
113 m_grid->SetColLabelValue( COL_DRILL_Y, _( "Drill Y" ) );
114 m_grid->SetColLabelValue( COL_P2D_LENGTH, _( "Pad->Die Length" ) );
115 m_grid->SetColLabelValue( COL_P2D_DELAY, _( "Pad->Die Delay" ) );
116 m_grid->SetColLabelSize( 24 );
117 m_grid->HideRowLabels();
118 m_grid->EnableEditing( true );
119
120 wxGridCellAttr* attr;
121
122 // Type column editor (attribute)
123 attr = new wxGridCellAttr;
124 wxArrayString typeNames;
125 typeNames.push_back( _( "Through-hole" ) ); // PTH
126 typeNames.push_back( _( "SMD" ) ); // SMD
127 typeNames.push_back( _( "Connector" ) ); // CONN SMD? (use CONN?)
128 typeNames.push_back( _( "NPTH" ) ); // NPTH
129 typeNames.push_back( _( "Aperture" ) ); // inferred copper-less
130 attr->SetEditor( new GRID_CELL_COMBOBOX( typeNames ) );
131 m_grid->SetColAttr( COL_TYPE, attr );
132
133 attr = new wxGridCellAttr;
134 wxArrayString shapeNames;
142 attr->SetEditor( new GRID_CELL_COMBOBOX( shapeNames ) );
143 m_grid->SetColAttr( COL_SHAPE, attr );
144
145 attr = new wxGridCellAttr;
146 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
147 m_grid->SetColAttr( COL_POS_X, attr );
148
149 attr = new wxGridCellAttr;
150 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
151 m_grid->SetColAttr( COL_POS_Y, attr );
152
153 attr = new wxGridCellAttr;
154 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
155 m_grid->SetColAttr( COL_SIZE_X, attr );
156
157 attr = new wxGridCellAttr;
158 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
159 m_grid->SetColAttr( COL_SIZE_Y, attr );
160
161 // Drill X
162 attr = new wxGridCellAttr;
163 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
164 m_grid->SetColAttr( COL_DRILL_X, attr );
165
166 // Drill Y
167 attr = new wxGridCellAttr;
168 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
169 m_grid->SetColAttr( COL_DRILL_Y, attr );
170
171 // Pad->Die Length
172 attr = new wxGridCellAttr;
173 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
174 m_grid->SetColAttr( COL_P2D_LENGTH, attr );
175
176 // Pad->Die Delay
177 attr = new wxGridCellAttr;
178 attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
179 m_grid->SetColAttr( COL_P2D_DELAY, attr );
180
181 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_X );
182 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_Y );
183 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_X );
184 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_Y );
185 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_X );
186 m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_Y );
188
189 // add Cut, Copy, and Paste to wxGrid
190 m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
191
192 topSizer->Add( m_grid, 1, wxEXPAND | wxALL, 5 );
193
194 wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer();
195 buttons->AddButton( new wxButton( this, wxID_OK ) );
196 buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
197 buttons->Realize();
198 topSizer->Add( buttons, 0, wxALIGN_RIGHT | wxALL, 5 );
199
200 SetSizer( topSizer );
202
204
205 // Bind cell change handlers for real-time updates
206 m_grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_FP_EDIT_PAD_TABLE::OnCellChanged, this );
207 m_grid->Bind( wxEVT_GRID_SELECT_CELL, &DIALOG_FP_EDIT_PAD_TABLE::OnSelectCell, this );
208 Bind( wxEVT_UPDATE_UI, &DIALOG_FP_EDIT_PAD_TABLE::OnUpdateUI, this );
209
210 // Listen for cancel
211 Bind( wxEVT_BUTTON,
212 [this]( wxCommandEvent& aEvt )
213 {
214 m_cancelled = true;
215 aEvt.Skip();
216 },
217 wxID_CANCEL );
218
219 Layout();
221
222 // Cap the initial height so the dialog does not grow off-screen for footprints
223 // with many pads. The grid grows to fill the available space via wxEXPAND.
224 // Use the parent window to find the display since this dialog isn't shown yet.
225 int displayIdx = wxDisplay::GetFromWindow( aParent );
226
227 if( displayIdx == wxNOT_FOUND )
228 displayIdx = 0;
229
230 wxRect displayArea = wxDisplay( (unsigned int) displayIdx ).GetClientArea();
231 wxSize dlgSize = GetSize();
232 int maxH = ( displayArea.height * 4 ) / 5;
233
234 if( dlgSize.y > maxH )
235 {
236 dlgSize.y = maxH;
237 SetSize( dlgSize );
238
239 // Reset minimum height so the user can resize the capped dialog freely.
240 // The minimum width from finishDialogSettings() is still honoured.
241 wxSize minSz = GetMinSize();
242 minSz.y = -1;
243 SetMinSize( minSz );
244
245 Centre();
246 }
247}
248
249
251{
252 if( m_cancelled )
254
255 // destroy GRID_TRICKS before m_grid.
256 m_grid->PopEventHandler( true );
257
258 m_grid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_FP_EDIT_PAD_TABLE::OnCellChanged, this );
259 m_grid->Unbind( wxEVT_GRID_SELECT_CELL, &DIALOG_FP_EDIT_PAD_TABLE::OnSelectCell, this );
260 Unbind( wxEVT_UPDATE_UI, &DIALOG_FP_EDIT_PAD_TABLE::OnUpdateUI, this );
261}
262
263
265{
266 if( !m_footprint )
267 return false;
268
269 int row = 0;
270
271 for( PAD* pad : m_footprint->Pads() )
272 {
273 if( row >= m_grid->GetNumberRows() )
274 continue;
275
276 m_grid->SetCellValue( row, COL_NUMBER, pad->GetNumber() );
277
278 // Pad attribute to string
279 wxString attrStr;
280
281 switch( pad->GetAttribute() )
282 {
283 case PAD_ATTRIB::PTH: attrStr = _( "Through-hole" ); break;
284 case PAD_ATTRIB::SMD: attrStr = _( "SMD" ); break;
285 case PAD_ATTRIB::CONN: attrStr = _( "Connector" ); break;
286 case PAD_ATTRIB::NPTH: attrStr = _( "NPTH" ); break;
287 default: attrStr = _( "Through-hole" ); break;
288 }
289
290 int size_x = pad->GetSize( PADSTACK::ALL_LAYERS ).x;
291 int size_y = pad->GetSize( PADSTACK::ALL_LAYERS ).y;
292 wxString padShape = pad->ShowPadShape( PADSTACK::ALL_LAYERS );
293
294 pad->Padstack().ForEachUniqueLayer(
295 [&]( PCB_LAYER_ID aLayer )
296 {
297 if( pad->GetSize( aLayer ).x != size_x )
298 size_x = -1;
299
300 if( pad->GetSize( aLayer ).y != size_y )
301 size_y = -1;
302
303 if( pad->ShowPadShape( aLayer ) != padShape )
304 padShape = INDETERMINATE_STATE;
305 } );
306
307 if( pad->IsAperturePad() )
308 attrStr = _( "Aperture" );
309
310 m_grid->SetCellValue( row, COL_TYPE, attrStr );
311 m_grid->SetCellValue( row, COL_SHAPE, padShape );
312 m_grid->SetCellValue( row, COL_POS_X, m_unitsProvider->StringFromValue( pad->GetPosition().x, true ) );
313 m_grid->SetCellValue( row, COL_POS_Y, m_unitsProvider->StringFromValue( pad->GetPosition().y, true ) );
314 m_grid->SetCellValue( row, COL_SIZE_X, size_x >= 0 ? m_unitsProvider->StringFromValue( size_x, true )
316 m_grid->SetCellValue( row, COL_SIZE_Y, size_y >= 0 ? m_unitsProvider->StringFromValue( size_y, true )
318
319 // Drill values (only meaningful for PTH or NPTH). Leave empty otherwise.
320 if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
321 {
322 VECTOR2I drill = pad->GetDrillSize();
323
324 if( drill.x > 0 )
325 m_grid->SetCellValue( row, COL_DRILL_X, m_unitsProvider->StringFromValue( drill.x, true ) );
326
327 if( drill.y > 0 )
328 m_grid->SetCellValue( row, COL_DRILL_Y, m_unitsProvider->StringFromValue( drill.y, true ) );
329 }
330 else
331 {
332 // For non-PTH pads, drill columns are not applicable.
333 m_grid->SetReadOnly( row, COL_DRILL_X, true );
334 m_grid->SetReadOnly( row, COL_DRILL_Y, true );
335 }
336
337 // Pad to die metrics
338 if( pad->GetPadToDieLength() )
339 m_grid->SetCellValue( row, COL_P2D_LENGTH, m_unitsProvider->StringFromValue( pad->GetPadToDieLength(),
340 true ) );
341
342 if( pad->GetPadToDieDelay() )
343 m_grid->SetCellValue( row, COL_P2D_DELAY, wxString::Format( "%d", pad->GetPadToDieDelay() ) );
344
345 row++;
346 }
347
348 // Auto size the data columns first to get reasonable initial widths
349 m_grid->AutoSizeColumns();
350
351 // Ensure the Shape column (index 1) is wide enough for the longest translated
352 // shape text plus the dropdown arrow / padding. We compute a max text width
353 // using a device context and add a platform neutral padding.
354 {
355 wxClientDC dc( m_grid );
356 dc.SetFont( m_grid->GetFont() );
357
358 wxArrayString shapeNames;
366
367 int maxWidth = 0;
368
369 for( const wxString& str : shapeNames )
370 {
371 int w, h;
372 dc.GetTextExtent( str, &w, &h );
373 maxWidth = std::max( maxWidth, w );
374 }
375
376 // Add padding for internal cell margins + dropdown control.
377 int padding = FromDIP( 30 ); // heuristic: 2*margin + arrow button
378 m_grid->SetColSize( COL_SHAPE, maxWidth + padding );
379 }
380
381 // Record initial proportions for proportional resizing later.
383 Bind( wxEVT_SIZE, &DIALOG_FP_EDIT_PAD_TABLE::OnSize, this );
384
385 // Run an initial proportional resize using current client size so columns
386 // respect proportions immediately.
387 wxSizeEvent sizeEvt( GetSize(), GetId() );
388 CallAfter(
389 [this, sizeEvt]
390 {
391 wxSizeEvent evt( sizeEvt );
392 this->OnSize( evt );
393 } );
394
395 // If pads exist, select the first row to show initial highlight
396 if( m_grid->GetNumberRows() > 0 )
397 {
398 m_grid->SetGridCursor( 0, 0 );
399
400 // Construct event with required parameters (id, type, obj, row, col,...)
401 wxGridEvent ev( m_grid->GetId(), wxEVT_GRID_SELECT_CELL, m_grid, 0, 0, -1, -1, true );
402 OnSelectCell( ev );
403 }
404
405 return true;
406}
407
408
410{
411 m_originalPads.clear();
412
413 if( !m_footprint )
414 return;
415
416 for( PAD* pad : m_footprint->Pads() )
417 {
418 PAD_SNAPSHOT snap( pad );
419 snap.number = pad->GetNumber();
420 snap.position = pad->GetPosition();
421 snap.padstack = pad->Padstack();
422 snap.attribute = pad->GetAttribute();
423 snap.padToDieLength= pad->GetPadToDieLength();
424 snap.padToDieDelay = pad->GetPadToDieDelay();
425 m_originalPads.push_back( snap );
426 }
427}
428
429
431{
432 if( !m_footprint )
433 return;
434
435 size_t idx = 0;
436 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
437 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
438
439 for( PAD* pad : m_footprint->Pads() )
440 {
441 if( idx >= m_originalPads.size() )
442 break;
443
444 const PAD_SNAPSHOT& snap = m_originalPads[idx++];
445 pad->SetNumber( snap.number );
446 pad->SetPosition( snap.position );
447 pad->SetPadstack( snap.padstack );
448 pad->SetAttribute( snap.attribute );
449 pad->SetPadToDieLength( snap.padToDieLength );
450 pad->SetPadToDieDelay( snap.padToDieDelay );
451 pad->ClearBrightened();
452
453 if( canvas )
454 canvas->GetView()->Update( pad, KIGFX::REPAINT );
455 }
456
457 if( canvas )
458 {
460 canvas->ForceRefresh();
461 }
462
463 m_summaryDirty = true;
464}
465
466
468{
469 if( !m_grid->CommitPendingChanges() )
470 return false;
471
472 if( !m_footprint )
473 return true;
474
476 BOARD_COMMIT commit( m_frame );
477
478 int row = 0;
479
480 for( PAD* pad : m_footprint->Pads() )
481 {
482 commit.Modify( pad );
483 pad->SetNumber( m_grid->GetCellValue( row, COL_NUMBER ) );
484
485 wxString typeStr = m_grid->GetCellValue( row, COL_TYPE );
486
487 if( typeStr == _( "Through-hole" ) )
488 pad->SetAttribute( PAD_ATTRIB::PTH );
489 else if( typeStr == _( "SMD" ) )
490 pad->SetAttribute( PAD_ATTRIB::SMD );
491 else if( typeStr == _( "Connector" ) )
492 pad->SetAttribute( PAD_ATTRIB::CONN );
493 else if( typeStr == _( "NPTH" ) )
494 pad->SetAttribute( PAD_ATTRIB::NPTH );
495 // Aperture derived by copper-less layers; do not overwrite attribute here.
496
497 wxString shape = m_grid->GetCellValue( row, COL_SHAPE );
498
499 if( shape != INDETERMINATE_STATE )
500 {
501 PAD_SHAPE newShape = ShapeFromString( shape );
502
503 pad->Padstack().ForEachUniqueLayer(
504 [&]( PCB_LAYER_ID aLayer )
505 {
506 pad->SetShape( aLayer, newShape );
507 } );
508 }
509
510 VECTOR2I pos;
511 pos.x = m_grid->GetUnitValue( row, COL_POS_X );
512 pos.y = m_grid->GetUnitValue( row, COL_POS_Y );
513 pad->SetPosition( pos );
514
515 wxString size_x_value = m_grid->GetCellValue( row, COL_SIZE_X );
516
517 if( size_x_value != INDETERMINATE_STATE )
518 {
519 int size_x = m_grid->GetUnitValue( row, COL_SIZE_X );
520
521 pad->Padstack().ForEachUniqueLayer(
522 [&]( PCB_LAYER_ID aLayer )
523 {
524 VECTOR2I size( size_x, pad->GetSize( aLayer ).y );
525 pad->SetSize( aLayer, size );
526 } );
527 }
528
529 wxString size_y_value = m_grid->GetCellValue( row, COL_SIZE_Y );
530
531 if( size_y_value != INDETERMINATE_STATE )
532 {
533 int size_y = m_grid->GetUnitValue( row, COL_SIZE_Y );
534
535 pad->Padstack().ForEachUniqueLayer(
536 [&]( PCB_LAYER_ID aLayer )
537 {
538 VECTOR2I size( pad->GetSize( aLayer ).x, size_y );
539 pad->SetSize( aLayer, size );
540 } );
541 }
542
543 // Drill sizes (only if attribute allows)
544 if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
545 {
546 int drill_x = m_grid->GetUnitValue( row, COL_DRILL_X );
547 int drill_y = m_grid->GetUnitValue( row, COL_DRILL_Y );
548
549 if( drill_x > 0 || drill_y > 0 )
550 {
551 if( drill_x <= 0 )
552 drill_x = drill_y;
553
554 if( drill_y <= 0 )
555 drill_y = drill_x;
556
557 pad->SetDrillSize( { drill_x, drill_y } );
558 }
559 }
560
561 // Pad->Die
562 wxString delayStr = m_grid->GetCellValue( row, COL_P2D_DELAY );
563 wxString lenStr = m_grid->GetCellValue( row, COL_P2D_LENGTH );
564
565 if( !lenStr.IsEmpty() )
566 pad->SetPadToDieLength( m_grid->GetUnitValue( row, COL_P2D_LENGTH ) );
567 else
568 pad->SetPadToDieLength( 0 );
569
570 if( !delayStr.IsEmpty() )
571 {
572 long delayVal;
573
574 if( delayStr.ToLong( &delayVal ) )
575 pad->SetPadToDieDelay( (int) delayVal );
576 else
577 pad->SetPadToDieDelay( 0 );
578 }
579 else
580 pad->SetPadToDieDelay( 0 );
581
582 row++;
583 }
584
585 commit.Push( _( "Edit Pads" ) );
586 m_frame->Refresh();
587
588 return true;
589}
590
591
593{
594 m_colProportions.clear();
595 m_minColWidths.clear();
596
597 if( !m_grid )
598 return;
599
600 // Only consider the actual data columns (all of them since row labels are hidden)
601 int cols = m_grid->GetNumberCols();
602 int total = 0;
603 std::vector<int> widths;
604 widths.reserve( cols );
605
606 for( int c = 0; c < cols; ++c )
607 {
608 int w = m_grid->GetColSize( c );
609 widths.push_back( w );
610 total += w;
611 }
612
613 if( total <= 0 )
614 return;
615
616 for( int w : widths )
617 {
618 m_colProportions.push_back( (double) w / (double) total );
619 m_minColWidths.push_back( w );
620 }
621}
622
623
624void DIALOG_FP_EDIT_PAD_TABLE::OnSize( wxSizeEvent& aEvent )
625{
626 if( m_colProportions.empty() )
627 {
628 aEvent.Skip();
629 return;
630 }
631
632 // Compute available total width for columns and resize keeping proportions.
633 int cols = m_grid->GetNumberCols();
634 int available = 0;
635
636 for( int c = 0; c < cols; ++c )
637 available += m_grid->GetColSize( c );
638
639 // Use client size of grid minus scrollbar estimate to better distribute.
640 int clientW = m_grid->GetClientSize().x;
641
642 if( clientW > 0 )
643 available = clientW; // prefer actual client width
644
645 int used = 0;
646
647 for( int c = 0; c < cols; ++c )
648 {
649 int target = (int) std::round( m_colProportions[c] * available );
650 target = std::max( target, m_minColWidths[c] );
651
652 // Defer last column to absorb rounding diff.
653 if( c == cols - 1 )
654 target = std::max( available - used, m_minColWidths[c] );
655
656 m_grid->SetColSize( c, target );
657 used += target;
658 }
659
660 aEvent.Skip();
661}
662
663
664void DIALOG_FP_EDIT_PAD_TABLE::OnCharHook( wxKeyEvent& aEvent )
665{
666 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
667 m_summaryDirty = true;
668
669 DIALOG_SHIM::OnCharHook( aEvent );
670}
671
672
674{
675 int row = aEvent.GetRow();
676 int col = aEvent.GetCol();
677
678 if( !m_footprint )
679 return;
680
681 // row -> pad mapping is current order of Pads() iteration; rebuild each time (pads count small)
682 int idx = 0;
683 PAD* target = nullptr;
684
685 for( PAD* pad : m_footprint->Pads() )
686 {
687 if( idx == row )
688 {
689 target = pad;
690 break;
691 }
692
693 ++idx;
694 }
695
696 if( !target )
697 return;
698
699 bool needCanvasRefresh = false;
700
701 switch( col )
702 {
703 case COL_NUMBER:
704 target->SetNumber( m_grid->GetCellValue( row, col ) );
705 needCanvasRefresh = true;
706 m_summaryDirty = true;
707 break;
708
709 case COL_TYPE:
710 {
711 wxString typeStr = m_grid->GetCellValue( row, col );
712 PAD_ATTRIB newAttr = target->GetAttribute();
713
714 if( typeStr == _( "Through-hole" ) )
715 newAttr = PAD_ATTRIB::PTH;
716 else if( typeStr == _( "SMD" ) )
717 newAttr = PAD_ATTRIB::SMD;
718 else if( typeStr == _( "Connector" ) )
719 newAttr = PAD_ATTRIB::CONN;
720 else if( typeStr == _( "NPTH" ) )
721 newAttr = PAD_ATTRIB::NPTH;
722
723 if( newAttr != target->GetAttribute() )
724 {
725 target->SetAttribute( newAttr );
726
727 // Toggle drill columns read-only state dynamically.
728 bool drillsEditable = ( newAttr == PAD_ATTRIB::PTH || newAttr == PAD_ATTRIB::NPTH );
729 m_grid->SetReadOnly( row, COL_DRILL_X, !drillsEditable );
730 m_grid->SetReadOnly( row, COL_DRILL_Y, !drillsEditable );
731 needCanvasRefresh = true;
732 }
733
734 break;
735 }
736
737 case COL_SHAPE:
738 target->SetShape( PADSTACK::ALL_LAYERS, ShapeFromString( m_grid->GetCellValue( row, col ) ) );
739 needCanvasRefresh = true;
740 break;
741
742 case COL_POS_X:
743 case COL_POS_Y:
744 {
745 VECTOR2I pos = target->GetPosition();
746
747 if( col == COL_POS_X )
748 pos.x = m_grid->GetUnitValue( row, col );
749 else
750 pos.y = m_grid->GetUnitValue( row, col );
751
752 target->SetPosition( pos );
753 needCanvasRefresh = true;
754 break;
755 }
756
757 case COL_SIZE_X:
758 case COL_SIZE_Y:
759 {
760 VECTOR2I size = target->GetSize( PADSTACK::ALL_LAYERS );
761
762 if( col == COL_SIZE_X )
763 size.x = m_grid->GetUnitValue( row, col );
764 else
765 size.y = m_grid->GetUnitValue( row, col );
766
767 target->SetSize( PADSTACK::ALL_LAYERS, size );
768 needCanvasRefresh = true;
769 break;
770 }
771
772 case COL_DRILL_X:
773 case COL_DRILL_Y:
774 {
775 if( target->GetAttribute() == PAD_ATTRIB::PTH || target->GetAttribute() == PAD_ATTRIB::NPTH )
776 {
777 int dx = m_grid->GetUnitValue( row, COL_DRILL_X );
778 int dy = m_grid->GetUnitValue( row, COL_DRILL_Y );
779
780 if( dx > 0 || dy > 0 )
781 {
782 if( dx <= 0 )
783 dx = dy;
784
785 if( dy <= 0 )
786 dy = dx;
787
788 target->SetDrillSize( { dx, dy } );
789 needCanvasRefresh = true;
790 }
791 }
792
793 break;
794 }
795
796 case COL_P2D_LENGTH:
797 if( !m_grid->GetCellValue( row, col ).IsEmpty() )
798 target->SetPadToDieLength( m_grid->GetUnitValue( row, col ) );
799
800 break;
801
802 case COL_P2D_DELAY:
803 {
804 wxString d = m_grid->GetCellValue( row, col );
805 long val;
806
807 if( d.ToLong( &val ) )
808 target->SetPadToDieDelay( (int) val );
809
810 break;
811 }
812
813 default:
814 break;
815 }
816
817 // Request redraw (simple approach)
818 target->SetDirty();
819
820 if( needCanvasRefresh )
821 {
822 if( PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() ) )
823 base->GetCanvas()->ForceRefresh();
824 }
825}
826
827
828void DIALOG_FP_EDIT_PAD_TABLE::OnSelectCell( wxGridEvent& aEvent )
829{
830 int row = aEvent.GetRow();
831
832 if( !m_footprint )
833 return;
834
835 PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
836 PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
837
838 // Clear existing pad selections
839 for( PAD* pad : m_footprint->Pads() )
840 {
841 if( pad->IsBrightened() )
842 {
843 pad->ClearBrightened();
844
845 if( canvas )
846 canvas->GetView()->Update( pad, KIGFX::REPAINT );
847 }
848 }
849
850 int idx = 0;
851
852 for( PAD* pad : m_footprint->Pads() )
853 {
854 if( idx == row )
855 {
856 pad->SetBrightened();
857
858 if( canvas )
859 {
860 canvas->GetView()->Update( pad, KIGFX::REPAINT );
861 canvas->ForceRefresh();
862 }
863
864 break;
865 }
866
867 ++idx;
868 }
869}
870
871
872void DIALOG_FP_EDIT_PAD_TABLE::OnUpdateUI( wxUpdateUIEvent& aEvent )
873{
874 if( m_summaryDirty )
875 {
876 if( m_grid->IsCellEditControlShown() && m_grid->GetGridCursorCol() == COL_NUMBER )
877 {
878 int row = m_grid->GetGridCursorRow();
879 int col = m_grid->GetGridCursorCol();
880 int idx = 0;
881 PAD* target = nullptr;
882
883 for( PAD* pad : m_footprint->Pads() )
884 {
885 if( idx == row )
886 {
887 target = pad;
888 break;
889 }
890
891 ++idx;
892 }
893
894 if( target )
895 target->SetNumber( m_grid->GetCellEditor( row, col )->GetValue() );
896 }
897
899 m_summaryDirty = false;
900 }
901}
902
903
905{
906 PIN_NUMBERS pinNumbers;
907
908 for( PAD* pad : m_footprint->Pads() )
909 {
910 if( pad->GetNumber().Length() )
911 pinNumbers.insert( pad->GetNumber() );
912 }
913
914 m_pin_numbers_summary->SetLabel( pinNumbers.GetSummary() );
915 m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_footprint->Pads().size() ) );
916 m_duplicate_pins->SetLabel( pinNumbers.GetDuplicates() );
917
918 Layout();
919}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
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:106
DIALOG_FP_EDIT_PAD_TABLE(PCB_BASE_FRAME *aParent, FOOTPRINT *aFootprint)
void OnCharHook(wxKeyEvent &aEvent) override
void OnUpdateUI(wxUpdateUIEvent &aEvent)
std::vector< PAD_SNAPSHOT > m_originalPads
void OnSize(wxSizeEvent &aEvent)
void OnCellChanged(wxGridEvent &aEvent)
std::vector< double > m_colProportions
std::unique_ptr< UNITS_PROVIDER > m_unitsProvider
void OnSelectCell(wxGridEvent &aEvent)
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...
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
virtual void OnCharHook(wxKeyEvent &aEvt)
EDA_UNITS GetUserUnits() const
void ForceRefresh()
Force a redraw.
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:61
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:91
void MarkTargetDirty(int aTarget)
Set or clear target 'dirty' flag.
Definition view.h:656
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
void SetAttribute(PAD_ATTRIB aAttribute)
Definition pad.cpp:1370
static wxString ShowPadShape(PAD_SHAPE aShape)
Definition pad.cpp:2146
PAD_ATTRIB GetAttribute() const
Definition pad.h:563
void SetShape(PCB_LAYER_ID aLayer, PAD_SHAPE aShape)
Set the new shape of this pad.
Definition pad.h:187
VECTOR2I GetPosition() const override
Definition pad.h:209
void SetDirty()
Definition pad.h:552
void SetPadToDieDelay(int aDelay)
Definition pad.h:580
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:136
void SetPosition(const VECTOR2I &aPos) override
Definition pad.h:203
void SetDrillSize(const VECTOR2I &aSize)
Definition pad.h:316
void SetSize(PCB_LAYER_ID aLayer, const VECTOR2I &aSize)
Definition pad.h:259
void SetPadToDieLength(int aLength)
Definition pad.h:577
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition pad.h:264
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:62
wxString GetSummary() const
static PAD_SHAPE ShapeFromString(const wxString &shape)
#define _(s)
static std::map< int, wxString > shapeNames
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ REPAINT
Item needs to be redrawn.
Definition view_item.h:58
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:39
STL namespace.
PAD_ATTRIB
The set of pad shapes, used with PAD::{Set,Get}Attribute().
Definition padstack.h:97
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:100
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
#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:687