KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_drill_chart_properties.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 <board.h>
24#include <board_commit.h>
25#include <confirm.h>
29#include <pcb_base_edit_frame.h>
30#include <pcb_drill_chart.h>
32#include <widgets/wx_grid.h>
33#include <widgets/wx_infobar.h>
34
35#include <wx/filedlg.h>
36#include <wx/filename.h>
37#include <wx/grid.h>
38
39
40namespace
41{
42
43enum COLUMN_GRID_COL
44{
45 COL_SHOW = 0,
47 COL_HEADING,
48 COL_ALIGN,
49 COL_GROUP_BY
50};
51
52
53// The grouping key a column reports on, for the columns that report on one. Grouping is the
54// board's, not the chart's, so ticking one here changes every chart and map on the board.
55bool columnGroupKey( DRILL_CHART_COLUMN_ID aId, DRILL_GROUP_KEY& aKey )
56{
57 switch( aId )
58 {
65 default: return false;
66 }
67}
68
69
71const DRILL_CHART_COLUMN_ID g_allColumns[] = {
78};
79
80
81wxString columnLabel( DRILL_CHART_COLUMN_ID aId )
82{
83 switch( aId )
84 {
85 case DRILL_CHART_COLUMN_ID::SYMBOL: return _( "Symbol" );
86 case DRILL_CHART_COLUMN_ID::DRILL_DIAMETER: return _( "Drill diameter" );
87 case DRILL_CHART_COLUMN_ID::SLOT_SIZE: return _( "Slot size" );
88 case DRILL_CHART_COLUMN_ID::PLATING: return _( "Plating" );
89 case DRILL_CHART_COLUMN_ID::OPERATION_COUNT: return _( "Operation count" );
90 case DRILL_CHART_COLUMN_ID::SITE_COUNT: return _( "Site count" );
91 case DRILL_CHART_COLUMN_ID::LAYER_SPAN: return _( "Layer span" );
92 case DRILL_CHART_COLUMN_ID::OPERATION: return _( "Operation" );
93 case DRILL_CHART_COLUMN_ID::PROTECTION: return _( "Protection" );
94 case DRILL_CHART_COLUMN_ID::BACKDRILL_STUB: return _( "Backdrill stub" );
95 case DRILL_CHART_COLUMN_ID::ASPECT_RATIO: return _( "Aspect ratio" );
96 case DRILL_CHART_COLUMN_ID::DESCRIPTION: return _( "Description" );
97 }
98
99 return wxEmptyString;
100}
101
102
103wxArrayString alignChoices()
104{
105 wxArrayString choices;
106 choices.Add( _( "Left" ) );
107 choices.Add( _( "Center" ) );
108 choices.Add( _( "Right" ) );
109
110 return choices;
111}
112
113} // namespace
114
115
117 PCB_DRILL_CHART* aChart ) :
119 m_frame( aFrame ),
120 m_chart( aChart ),
124{
125 // Not merely "not copper". Silkscreen, mask, paste, adhesive, Edge.Cuts, Margin and
126 // courtyard are manufacturing inputs this would corrupt rather than document
127 m_LayerSelectionCtrl->SetNotAllowedLayerSet( ~DrillDocumentationLayers() );
128 m_LayerSelectionCtrl->SetLayersHotkeys( false );
129 m_LayerSelectionCtrl->SetBoardFrame( m_frame );
130 m_LayerSelectionCtrl->Resync();
131
132 m_columnGrid->SetColLabelValue( COL_SHOW, _( "Show" ) );
133 m_columnGrid->SetColLabelValue( COL_NAME, _( "Column" ) );
134 m_columnGrid->SetColLabelValue( COL_HEADING, _( "Heading" ) );
135 m_columnGrid->SetColLabelValue( COL_ALIGN, _( "Align" ) );
136 m_columnGrid->SetColLabelValue( COL_GROUP_BY, _( "Group By" ) );
137
138 m_columnGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
139
141
144}
145
146
150
151
153{
154 if( m_columnGrid->GetNumberRows() )
155 m_columnGrid->DeleteRows( 0, m_columnGrid->GetNumberRows() );
156
157 m_columnGrid->AppendRows( static_cast<int>( m_columns.size() ) );
158
159 const wxArrayString aligns = alignChoices();
160
161 for( size_t row = 0; row < m_columns.size(); ++row )
162 {
163 const int r = static_cast<int>( row );
164
165 m_columnGrid->SetCellRenderer( r, COL_SHOW, new wxGridCellBoolRenderer );
166 m_columnGrid->SetCellEditor( r, COL_SHOW, new wxGridCellBoolEditor );
167 m_columnGrid->SetCellValue( r, COL_SHOW, m_shown[row] ? wxT( "1" ) : wxEmptyString );
168
169 m_columnGrid->SetCellValue( r, COL_NAME, columnLabel( m_columns[row].m_Id ) );
170 m_columnGrid->SetReadOnly( r, COL_NAME );
171
172 m_columnGrid->SetCellValue( r, COL_HEADING, m_columns[row].m_Heading );
173
174 m_columnGrid->SetCellEditor( r, COL_ALIGN, new wxGridCellChoiceEditor( aligns ) );
175 m_columnGrid->SetCellValue( r, COL_ALIGN,
176 aligns[static_cast<int>( m_columns[row].m_Align )] );
177
178 DRILL_GROUP_KEY key;
179
180 if( columnGroupKey( m_columns[row].m_Id, key ) )
181 {
182 m_columnGrid->SetCellRenderer( r, COL_GROUP_BY, new wxGridCellBoolRenderer );
183 m_columnGrid->SetCellEditor( r, COL_GROUP_BY, new wxGridCellBoolEditor );
184 m_columnGrid->SetCellValue( r, COL_GROUP_BY,
185 m_profile.IsGroupedBy( key ) ? wxT( "1" )
186 : wxEmptyString );
187 }
188 else
189 {
190 // Nothing to group on, so an empty read-only cell rather than a dead checkbox
191 m_columnGrid->SetReadOnly( r, COL_GROUP_BY );
192 }
193 }
194
195 m_columnGrid->AutoSizeColumns();
197}
198
199
201{
202 if( !m_columnGrid->CommitPendingChanges() )
203 return false;
204
205 const wxArrayString aligns = alignChoices();
206
207 for( size_t row = 0; row < m_columns.size(); ++row )
208 {
209 const int r = static_cast<int>( row );
210
211 m_shown[row] = !m_columnGrid->GetCellValue( r, COL_SHOW ).IsEmpty();
212 m_columns[row].m_Heading = m_columnGrid->GetCellValue( r, COL_HEADING );
213
214 const int align = aligns.Index( m_columnGrid->GetCellValue( r, COL_ALIGN ) );
215
216 if( align != wxNOT_FOUND )
217 m_columns[row].m_Align = static_cast<DRILL_CHART_ALIGN>( align );
218
219 DRILL_GROUP_KEY key;
220
221 if( columnGroupKey( m_columns[row].m_Id, key ) )
222 {
223 m_profile.SetGroupedBy( key,
224 !m_columnGrid->GetCellValue( r, COL_GROUP_BY ).IsEmpty() );
225 }
226 }
227
228 return true;
229}
230
231
233{
234 if( !wxDialog::TransferDataToWindow() )
235 return false;
236
237 // After DIALOG_SHIM has restored the last page, which is otherwise whatever the user
238 // happened to leave open the time before
239 m_notebook->SetSelection( 0 );
240
241 m_profile = m_frame->GetBoard()->GetDesignSettings().GetDrillSymbolProfile();
242
243 m_LayerSelectionCtrl->SetLayerSelection( m_chart->GetLayer() );
244 m_cbLocked->SetValue( m_chart->IsLocked() );
245
246 m_unitsCtrl->SetSelection( static_cast<int>( m_chart->GetUnits() ) );
247 m_precisionCtrl->SetValue( m_chart->GetPrecision() );
248
249 const DRILL_CHART_FILTER& filter = m_chart->Filter();
250 m_filterPlated->SetValue( filter.m_Plated );
251 m_filterNonPlated->SetValue( filter.m_NonPlated );
252 m_filterVias->SetValue( filter.m_Vias );
253 m_filterSlots->SetValue( filter.m_Slots );
254 m_filterBackdrills->SetValue( filter.m_Backdrills );
255 m_filterCastellated->SetValue( filter.m_Castellated );
256
257 m_showTotals->SetValue( m_chart->GetShowTotals() );
258
259 // Chart columns first in their own order, then whatever is left, so unchecking and
260 // rechecking a column does not lose the heading the user typed
261 m_columns = m_chart->Columns();
262 m_shown.assign( m_columns.size(), true );
263
265
266 for( DRILL_CHART_COLUMN_ID id : g_allColumns )
267 {
268 const bool present = std::any_of( m_columns.begin(), m_columns.end(),
269 [id]( const DRILL_CHART_COLUMN& aCol )
270 {
271 return aCol.m_Id == id;
272 } );
273
274 if( present )
275 continue;
276
278 col.m_Id = id;
279 col.m_Heading = columnLabel( id ).Upper();
280
281 for( const DRILL_CHART_COLUMN& def : defaults.Columns() )
282 {
283 if( def.m_Id == id )
284 {
285 col = def;
286 break;
287 }
288 }
289
290 m_columns.push_back( col );
291 m_shown.push_back( false );
292 }
293
295
296 m_borderCheckbox->SetValue( m_chart->StrokeExternal() );
297 m_headerBorder->SetValue( m_chart->StrokeHeaderSeparator() );
298 m_rowSeparators->SetValue( m_chart->StrokeRows() );
299 m_colSeparators->SetValue( m_chart->StrokeColumns() );
300
301 if( m_chart->GetBorderStroke().GetWidth() >= 0 )
302 m_borderWidth.SetValue( m_chart->GetBorderStroke().GetWidth() );
303
304 if( m_chart->GetSeparatorsStroke().GetWidth() >= 0 )
305 m_separatorsWidth.SetValue( m_chart->GetSeparatorsStroke().GetWidth() );
306
307 m_borderWidth.Enable( m_chart->StrokeExternal() || m_chart->StrokeHeaderSeparator() );
308 m_separatorsWidth.Enable( m_chart->StrokeRows() || m_chart->StrokeColumns() );
309
310 return true;
311}
312
313
315{
316 if( !harvestColumnGrid() )
317 return false;
318
319 if( !wxDialog::TransferDataFromWindow() )
320 return false;
321
322 std::vector<DRILL_CHART_COLUMN> columns;
323
324 for( size_t row = 0; row < m_columns.size(); ++row )
325 {
326 if( m_shown[row] )
327 columns.push_back( m_columns[row] );
328 }
329
330 if( columns.empty() )
331 {
332 DisplayError( this, _( "A drill chart needs at least one column." ) );
333 return false;
334 }
335
336 BOARD* board = m_frame->GetBoard();
337 BOARD_COMMIT commit( m_frame );
338 commit.Modify( m_chart );
339
340 m_chart->SetLayer( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
341 m_chart->SetLocked( m_cbLocked->GetValue() );
342
343 m_chart->SetUnits( static_cast<DRILL_CHART_UNITS>( m_unitsCtrl->GetSelection() ) );
344 m_chart->SetPrecision( m_precisionCtrl->GetValue() );
345 m_chart->SetShowTotals( m_showTotals->GetValue() );
346
347 DRILL_CHART_FILTER& filter = m_chart->Filter();
348 filter.m_Plated = m_filterPlated->GetValue();
349 filter.m_NonPlated = m_filterNonPlated->GetValue();
350 filter.m_Vias = m_filterVias->GetValue();
351 filter.m_Slots = m_filterSlots->GetValue();
352 filter.m_Backdrills = m_filterBackdrills->GetValue();
353 filter.m_Castellated = m_filterCastellated->GetValue();
354
355 m_chart->Columns() = columns;
356
357 m_chart->SetStrokeExternal( m_borderCheckbox->GetValue() );
358 m_chart->SetStrokeHeaderSeparator( m_headerBorder->GetValue() );
359 m_chart->SetStrokeRows( m_rowSeparators->GetValue() );
360 m_chart->SetStrokeColumns( m_colSeparators->GetValue() );
361
362 {
363 STROKE_PARAMS stroke = m_chart->GetBorderStroke();
364 stroke.SetWidth( m_borderCheckbox->GetValue() || m_headerBorder->GetValue()
365 ? std::max( 0, m_borderWidth.GetIntValue() )
366 : -1 );
367 m_chart->SetBorderStroke( stroke );
368 }
369
370 {
371 STROKE_PARAMS stroke = m_chart->GetSeparatorsStroke();
372 stroke.SetWidth( m_rowSeparators->GetValue() || m_colSeparators->GetValue()
373 ? std::max( 0, m_separatorsWidth.GetIntValue() )
374 : -1 );
375 m_chart->SetSeparatorsStroke( stroke );
376 }
377
379 const std::set<DRILL_GROUP_KEY> wasGroupedBy = boardProfile.GroupKeys();
380
381 for( DRILL_CHART_COLUMN_ID id : g_allColumns )
382 {
383 DRILL_GROUP_KEY key;
384
385 if( columnGroupKey( id, key ) )
386 boardProfile.SetGroupedBy( key, m_profile.IsGroupedBy( key ) );
387 }
388
389 if( boardProfile.GroupKeys() != wasGroupedBy )
390 {
391 // Grouping is board-wide, so every chart and map has to be rebuilt against it
393 m_frame->OnModify();
394 }
395
396 // New assignments are discarded on purpose. The profile is not on the undo stack, so
397 // persisting here would survive an undo of this very edit
398 m_chart->RebuildCells( *board );
399
400 commit.Push( _( "Edit Drill Chart" ), SKIP_CONNECTIVITY );
401
402 return true;
403}
404
405
407{
408 const int cols = m_columnGrid->GetNumberCols();
409 const int width = m_columnGrid->GetClientSize().x;
410
411 if( cols < 1 || width < 1 )
412 return;
413
414 // The checkbox and choice columns keep the width they auto-sized to. Whatever is left
415 // over goes to the two text columns, so the grid has no unused strip down its side
416 int fixed = 0;
417
418 for( int col = 0; col < cols; ++col )
419 {
420 if( col != COL_NAME && col != COL_HEADING )
421 fixed += m_columnGrid->GetColSize( col );
422 }
423
424 const int spare = width - fixed;
425
426 if( spare < 2 )
427 return;
428
429 m_columnGrid->SetColSize( COL_NAME, spare / 2 );
430 m_columnGrid->SetColSize( COL_HEADING, spare - spare / 2 );
431}
432
433
435{
437 aEvent.Skip();
438}
439
440
442{
443 const int defaultWidth = m_frame->GetDesignSettings().GetLineThickness(
444 ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
445
446 const bool border = m_borderCheckbox->GetValue() || m_headerBorder->GetValue();
447 const bool separators = m_rowSeparators->GetValue() || m_colSeparators->GetValue();
448
449 if( border && m_borderWidth.GetValue() < 0 )
450 m_borderWidth.SetValue( defaultWidth );
451
452 if( separators && m_separatorsWidth.GetValue() < 0 )
453 m_separatorsWidth.SetValue( defaultWidth );
454
455 m_borderWidth.Enable( border );
456 m_separatorsWidth.Enable( separators );
457}
458
459
461{
462 if( !harvestColumnGrid() )
463 return;
464
465 const int row = m_columnGrid->GetGridCursorRow();
466 const int target = row + aDelta;
467
468 if( row < 0 || target < 0 || target >= static_cast<int>( m_columns.size() ) )
469 return;
470
471 std::swap( m_columns[row], m_columns[target] );
472
473 // std::vector<bool> has no swap-by-reference, so go through a copy
474 const bool shown = m_shown[row];
475 m_shown[row] = m_shown[target];
476 m_shown[target] = shown;
477
479 m_columnGrid->SetGridCursor( target, m_columnGrid->GetGridCursorCol() );
480 m_columnGrid->SelectRow( target );
481}
482
483
484void DIALOG_DRILL_CHART_PROPERTIES::onMoveUp( wxCommandEvent& aEvent )
485{
486 moveColumn( -1 );
487}
488
489
490void DIALOG_DRILL_CHART_PROPERTIES::onMoveDown( wxCommandEvent& aEvent )
491{
492 moveColumn( 1 );
493}
494
495
497{
498 m_columns = aTemplate.Columns();
499 m_shown.assign( m_columns.size(), true );
500
501 // Columns the template does not use stay in the grid unchecked, so turning one back on
502 // does not mean retyping its heading
503 for( DRILL_CHART_COLUMN_ID id : g_allColumns )
504 {
505 const bool present = std::any_of( m_columns.begin(), m_columns.end(),
506 [id]( const DRILL_CHART_COLUMN& aCol )
507 {
508 return aCol.m_Id == id;
509 } );
510
511 if( present )
512 continue;
513
515 col.m_Id = id;
516 col.m_Heading = columnLabel( id ).Upper();
517 m_columns.push_back( col );
518 m_shown.push_back( false );
519 }
520
522}
523
524
529
530
532{
533 wxFileDialog dlg( this, _( "Import Drill Chart Template" ), wxEmptyString, wxEmptyString,
534 _( "Drill chart templates" ) + wxT( " (*.json)|*.json" ),
535 wxFD_OPEN | wxFD_FILE_MUST_EXIST );
536
537 if( dlg.ShowModal() != wxID_OK )
538 return;
539
541 wxString error;
542
543 if( !tmpl.LoadFromFile( dlg.GetPath(), &error ) )
544 {
545 DisplayError( this, error );
546 return;
547 }
548
549 applyTemplate( tmpl );
550
551 m_unitsCtrl->SetSelection( static_cast<int>( tmpl.GetUnits() ) );
552 m_precisionCtrl->SetValue( tmpl.GetPrecision() );
553 m_showTotals->SetValue( tmpl.GetShowTotals() );
554}
555
556
558{
559 if( !harvestColumnGrid() )
560 return;
561
562 wxFileDialog dlg( this, _( "Export Drill Chart Template" ), wxEmptyString, wxT( "drill_chart" ),
563 _( "Drill chart templates" ) + wxT( " (*.json)|*.json" ),
564 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
565
566 if( dlg.ShowModal() != wxID_OK )
567 return;
568
570
571 // The file names the template, so whoever opens the json can tell which one it is
572 tmpl.SetName( wxFileName( dlg.GetPath() ).GetName() );
573 tmpl.SetUnits( static_cast<DRILL_CHART_UNITS>( m_unitsCtrl->GetSelection() ) );
574 tmpl.SetPrecision( m_precisionCtrl->GetValue() );
575 tmpl.SetShowTotals( m_showTotals->GetValue() );
576
577 for( size_t row = 0; row < m_columns.size(); ++row )
578 {
579 if( m_shown[row] )
580 tmpl.Columns().push_back( m_columns[row] );
581 }
582
583 wxString error;
584
585 if( !tmpl.SaveToFile( dlg.GetPath(), &error ) )
586 DisplayError( this, error );
587}
588
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
DRILL_SYMBOL_PROFILE & GetDrillSymbolProfile()
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void BumpDrillModelGeneration()
Bumped whenever anything a drill chart or map reports on has moved.
Definition board.cpp:176
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
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
DIALOG_DRILL_CHART_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Drill Chart Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void onExportTemplate(wxCommandEvent &aEvent) override
void onMoveUp(wxCommandEvent &aEvent) override
std::vector< DRILL_CHART_COLUMN > m_columns
Every known column, checked ones first in chart order.
void fitColumnGrid()
Share the grid's width out across its columns so none of it goes unused.
DIALOG_DRILL_CHART_PROPERTIES(PCB_BASE_EDIT_FRAME *aFrame, PCB_DRILL_CHART *aChart)
void onBorderChecked(wxCommandEvent &aEvent) override
DRILL_SYMBOL_PROFILE m_profile
The board's grouping, edited through the Group By column.
void onMoveDown(wxCommandEvent &aEvent) override
void fillColumnGrid()
Fill the column grid from m_columns, preserving the selected row.
void applyTemplate(const DRILL_CHART_TEMPLATE &aTemplate)
Rebuild m_columns/m_shown from a template, keeping unused columns available.
void onImportTemplate(wxCommandEvent &aEvent) override
bool harvestColumnGrid()
Read the grid back into m_columns.
void onResetColumns(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...
The column set and formatting a new chart starts from.
DRILL_CHART_UNITS GetUnits() const
bool SaveToFile(const wxString &aPath, wxString *aError) const
Read and write a template as JSON.
void SetName(const wxString &aName)
void SetPrecision(int aPrecision)
void SetUnits(DRILL_CHART_UNITS aUnits)
void SetShowTotals(bool aShow)
bool LoadFromFile(const wxString &aPath, wxString *aError)
std::vector< DRILL_CHART_COLUMN > & Columns()
static DRILL_CHART_TEMPLATE MakeDefault()
Grouping rules and symbol assignments, shared by reference so a chart and its map can never disagree ...
void SetGroupedBy(DRILL_GROUP_KEY aKey, bool aOn)
const std::set< DRILL_GROUP_KEY > & GroupKeys() const
Common, abstract interface for edit frames.
A drill chart placed on the board, kept in step with the holes.
Simple container to manage line stroke parameters.
void SetWidth(int aWidth)
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:192
This file is part of the common library.
LSET DrillDocumentationLayers()
Layers a chart or map may live on.
DRILL_CHART_ALIGN
DRILL_CHART_COLUMN_ID
DRILL_CHART_UNITS
DRILL_GROUP_KEY
Which properties split holes into separate chart rows and symbols.
#define _(s)
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
#define SKIP_CONNECTIVITY
Definition sch_commit.h:41
DRILL_CHART_COLUMN_ID m_Id
Which holes a chart reports on.