KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_generated_table_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 <algorithm>
23
24#include <board.h>
25#include <board_commit.h>
28#include <confirm.h>
29#include <pcb_base_edit_frame.h>
30#include <pcb_generated_table.h>
32#include <widgets/wx_grid.h>
33
34#include <wx/grid.h>
35
36
37namespace
38{
39
40enum COLUMN_GRID_COL
41{
42 COL_SHOW = 0,
44 COL_HEADING,
45 COL_ALIGN,
47};
48
49
50// What the column reports, whatever the user has retitled its heading to
51wxString columnLabel( const GENERATED_TABLE_SCHEMA& aSchema, int aId )
52{
53 const GENERATED_TABLE_COLUMN_DEF* def = aSchema.Find( aId );
54
55 if( !def )
56 return wxEmptyString;
57
58 return wxGetTranslation( wxString( def->m_Label ? def->m_Label : def->m_Heading ) );
59}
60
61
62wxArrayString alignChoices()
63{
64 wxArrayString choices;
65 choices.Add( _( "Left" ) );
66 choices.Add( _( "Center" ) );
67 choices.Add( _( "Right" ) );
68
69 return choices;
70}
71
72} // namespace
73
74
76 PCB_GENERATED_TABLE* aTable,
79 m_frame( aFrame ),
80 m_table( aTable ),
81 m_options( nullptr ),
84{
85 SetTitle( wxString::Format( _( "%s Properties" ), m_table->GetFriendlyName() ) );
86
87 // Not merely "not copper". Silkscreen, mask, paste, adhesive, Edge.Cuts, Margin and
88 // courtyard are manufacturing inputs this would corrupt rather than document
89 m_LayerSelectionCtrl->SetNotAllowedLayerSet( ~DocumentationLayers() );
90 m_LayerSelectionCtrl->SetLayersHotkeys( false );
91 m_LayerSelectionCtrl->SetBoardFrame( m_frame );
92 m_LayerSelectionCtrl->Resync();
93
94 m_columnGrid->SetColLabelValue( COL_SHOW, _( "Show" ) );
95 m_columnGrid->SetColLabelValue( COL_NAME, _( "Column" ) );
96 m_columnGrid->SetColLabelValue( COL_HEADING, _( "Heading" ) );
97 m_columnGrid->SetColLabelValue( COL_ALIGN, _( "Align" ) );
98 m_columnGrid->SetColLabelValue( COL_WIDTH, _( "Min Width" ) );
99
100 m_columnGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
101 m_columnGrid->SetUnitsProvider( m_frame );
102 m_columnGrid->SetAutoEvalCols( { COL_WIDTH } );
103
105
106 if( aOptions )
107 {
108 // The page, not the dialog, since wx insists a sizer's windows are children of its window
109 m_options = aOptions( m_columnsPanel );
110 m_options->m_GetFormat = [this]()
111 {
112 return getFormat();
113 };
114 m_options->m_SetFormat = [this]( const GENERATED_TABLE_FORMAT& aFormat )
115 {
116 setFormat( aFormat );
117 };
118 m_optionsSizer->Add( m_options, 0, wxEXPAND, 0 );
119 }
120
123}
124
125
127{
128 if( m_columnGrid->GetNumberRows() )
129 m_columnGrid->DeleteRows( 0, m_columnGrid->GetNumberRows() );
130
131 m_columnGrid->AppendRows( static_cast<int>( m_rows.size() ) );
132
133 const wxArrayString aligns = alignChoices();
134
135 for( size_t row = 0; row < m_rows.size(); ++row )
136 {
137 const int r = static_cast<int>( row );
138 const GENERATED_TABLE_COLUMN& col = m_rows[row].m_Column;
139
140 m_columnGrid->SetCellRenderer( r, COL_SHOW, new wxGridCellBoolRenderer );
141 m_columnGrid->SetCellEditor( r, COL_SHOW, new wxGridCellBoolEditor );
142 m_columnGrid->SetCellValue( r, COL_SHOW, m_rows[row].m_Shown ? wxT( "1" ) : wxEmptyString );
143
144 m_columnGrid->SetCellValue( r, COL_NAME, columnLabel( m_table->Schema(), col.m_Id ) );
145 m_columnGrid->SetReadOnly( r, COL_NAME );
146
147 m_columnGrid->SetCellValue( r, COL_HEADING, col.m_Heading );
148
149 m_columnGrid->SetCellEditor( r, COL_ALIGN, new wxGridCellChoiceEditor( aligns ) );
150 m_columnGrid->SetCellValue( r, COL_ALIGN, aligns[static_cast<int>( col.m_Align )] );
151
152 // Empty rather than zero, since zero means the column sizes itself to its text
153 m_columnGrid->SetOptionalUnitValue( r, COL_WIDTH,
154 col.m_Width > 0 ? std::optional<int>( col.m_Width ) : std::nullopt );
155 }
156
157 m_columnGrid->AutoSizeColumns();
159}
160
161
163{
164 if( !m_columnGrid->CommitPendingChanges() )
165 return false;
166
167 const wxArrayString aligns = alignChoices();
168
169 for( size_t row = 0; row < m_rows.size(); ++row )
170 {
171 const int r = static_cast<int>( row );
172 GENERATED_TABLE_COLUMN& col = m_rows[row].m_Column;
173
174 m_rows[row].m_Shown = !m_columnGrid->GetCellValue( r, COL_SHOW ).IsEmpty();
175 col.m_Heading = m_columnGrid->GetCellValue( r, COL_HEADING );
176
177 const int align = aligns.Index( m_columnGrid->GetCellValue( r, COL_ALIGN ) );
178
179 if( align != wxNOT_FOUND )
180 col.m_Align = static_cast<GENERATED_TABLE_ALIGN>( align );
181
182 col.m_Width = std::clamp( m_columnGrid->GetOptionalUnitValue( r, COL_WIDTH ).value_or( 0 ), 0,
184 }
185
186 return true;
187}
188
189
190void DIALOG_GENERATED_TABLE_PROPERTIES::setColumns( const std::vector<GENERATED_TABLE_COLUMN>& aColumns )
191{
192 m_rows.clear();
193
194 for( const GENERATED_TABLE_COLUMN& col : aColumns )
195 m_rows.push_back( { col, true } );
196
197 for( const GENERATED_TABLE_COLUMN_DEF& def : m_table->Schema().Defs() )
198 {
199 const bool present = std::any_of( aColumns.begin(), aColumns.end(),
200 [&def]( const GENERATED_TABLE_COLUMN& aCol )
201 {
202 return aCol.m_Id == def.m_Id;
203 } );
204
205 if( !present )
206 m_rows.push_back( { m_table->Schema().DefaultColumn( def.m_Id ), false } );
207 }
208
210}
211
212
213std::vector<GENERATED_TABLE_COLUMN> DIALOG_GENERATED_TABLE_PROPERTIES::shownColumns() const
214{
215 std::vector<GENERATED_TABLE_COLUMN> columns;
216
217 for( const COLUMN_ROW& row : m_rows )
218 {
219 if( row.m_Shown )
220 columns.push_back( row.m_Column );
221 }
222
223 return columns;
224}
225
226
227std::optional<GENERATED_TABLE_FORMAT> DIALOG_GENERATED_TABLE_PROPERTIES::getFormat()
228{
229 if( !harvestColumnGrid() )
230 return std::nullopt;
231
233 format.m_Columns = shownColumns();
234 format.m_Units = static_cast<GENERATED_TABLE_UNITS>( m_unitsCtrl->GetSelection() );
235 format.m_Precision = m_precisionCtrl->GetValue();
236
237 return format;
238}
239
240
242{
243 setColumns( aFormat.m_Columns );
244 m_unitsCtrl->SetSelection( static_cast<int>( aFormat.m_Units ) );
245 m_precisionCtrl->SetValue( aFormat.m_Precision );
246}
247
248
250{
251 if( !wxDialog::TransferDataToWindow() )
252 return false;
253
254 // After DIALOG_SHIM has restored the last page, which is otherwise whatever the user
255 // happened to leave open the time before
256 m_notebook->SetSelection( 0 );
257
258 m_LayerSelectionCtrl->SetLayerSelection( m_table->GetLayer() );
259 m_cbLocked->SetValue( m_table->IsLocked() );
260
261 m_unitsCtrl->SetSelection( static_cast<int>( m_table->GetUnits() ) );
262 m_precisionCtrl->SetValue( m_table->GetPrecision() );
263
264 setColumns( m_table->Columns() );
265
266 if( m_options )
267 m_options->TransferToWindow( *m_table );
268
269 m_borderCheckbox->SetValue( m_table->StrokeExternal() );
270 m_headerBorder->SetValue( m_table->StrokeHeaderSeparator() );
271 m_rowSeparators->SetValue( m_table->StrokeRows() );
272 m_colSeparators->SetValue( m_table->StrokeColumns() );
273
274 if( m_table->GetBorderStroke().GetWidth() >= 0 )
275 m_borderWidth.SetValue( m_table->GetBorderStroke().GetWidth() );
276
277 if( m_table->GetSeparatorsStroke().GetWidth() >= 0 )
278 m_separatorsWidth.SetValue( m_table->GetSeparatorsStroke().GetWidth() );
279
280 m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
281 m_separatorsWidth.Enable( m_table->StrokeRows() || m_table->StrokeColumns() );
282
283 return true;
284}
285
286
288{
289 if( !harvestColumnGrid() )
290 return false;
291
292 if( !wxDialog::TransferDataFromWindow() )
293 return false;
294
295 std::vector<GENERATED_TABLE_COLUMN> columns = shownColumns();
296
297 if( columns.empty() )
298 {
299 DisplayError( this, _( "A table needs at least one column." ) );
300 return false;
301 }
302
303 if( !m_table->Schema().Validate( columns ) )
304 {
305 DisplayError( this, _( "The columns are too wide in total." ) );
306 return false;
307 }
308
309 BOARD* board = m_frame->GetBoard();
310 BOARD_COMMIT commit( m_frame );
312
313 commit.Modify( m_table );
314
315 if( m_options && !m_options->TransferFromWindow( *m_table, refresh ) )
316 {
317 commit.Revert();
318 return false;
319 }
320
321 m_table->SetLayer( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
322 m_table->SetLocked( m_cbLocked->GetValue() );
323
324 m_table->SetUnits( static_cast<GENERATED_TABLE_UNITS>( m_unitsCtrl->GetSelection() ) );
325 m_table->SetPrecision( m_precisionCtrl->GetValue() );
326
327 m_table->Columns() = columns;
328
329 m_table->SetStrokeExternal( m_borderCheckbox->GetValue() );
330 m_table->SetStrokeHeaderSeparator( m_headerBorder->GetValue() );
331 m_table->SetStrokeRows( m_rowSeparators->GetValue() );
332 m_table->SetStrokeColumns( m_colSeparators->GetValue() );
333
334 const auto strokeWidth =
335 []( bool aStroked, UNIT_BINDER& aWidth )
336 {
337 return aStroked ? std::max( 0, aWidth.GetIntValue() ) : -1;
338 };
339
340 STROKE_PARAMS border = m_table->GetBorderStroke();
341 border.SetWidth( strokeWidth( m_borderCheckbox->GetValue() || m_headerBorder->GetValue(), m_borderWidth ) );
342 m_table->SetBorderStroke( border );
343
344 STROKE_PARAMS separators = m_table->GetSeparatorsStroke();
345 separators.SetWidth( strokeWidth( m_rowSeparators->GetValue() || m_colSeparators->GetValue(), m_separatorsWidth ) );
346 m_table->SetSeparatorsStroke( separators );
347
348 // Committed, or the table reads fresh while board state it was built against is still pending
349 m_table->RebuildCells( *board, &refresh );
350 refresh.Commit();
351
352 commit.Push( wxString::Format( _( "Edit %s" ), m_table->GetFriendlyName() ), SKIP_CONNECTIVITY );
353
354 return true;
355}
356
357
359{
360 const int cols = m_columnGrid->GetNumberCols();
361 const int width = m_columnGrid->GetClientSize().x;
362
363 if( cols < 1 || width < 1 )
364 return;
365
366 // The checkbox and choice columns keep the width they auto-sized to. Whatever is left
367 // over goes to the two text columns, so the grid has no unused strip down its side
368 int fixed = 0;
369
370 for( int col = 0; col < cols; ++col )
371 {
372 if( col != COL_NAME && col != COL_HEADING )
373 fixed += m_columnGrid->GetColSize( col );
374 }
375
376 const int spare = width - fixed;
377
378 if( spare < 2 )
379 return;
380
381 m_columnGrid->SetColSize( COL_NAME, spare / 2 );
382 m_columnGrid->SetColSize( COL_HEADING, spare - spare / 2 );
383}
384
385
387{
389 aEvent.Skip();
390}
391
392
394{
395 const int defaultWidth =
396 m_frame->GetDesignSettings().GetLineThickness( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
397
398 const bool border = m_borderCheckbox->GetValue() || m_headerBorder->GetValue();
399 const bool separators = m_rowSeparators->GetValue() || m_colSeparators->GetValue();
400
401 if( border && m_borderWidth.GetValue() < 0 )
402 m_borderWidth.SetValue( defaultWidth );
403
404 if( separators && m_separatorsWidth.GetValue() < 0 )
405 m_separatorsWidth.SetValue( defaultWidth );
406
407 m_borderWidth.Enable( border );
408 m_separatorsWidth.Enable( separators );
409}
410
411
413{
414 if( !harvestColumnGrid() )
415 return;
416
417 const int row = m_columnGrid->GetGridCursorRow();
418 const int target = row + aDelta;
419
420 if( row < 0 || target < 0 || target >= static_cast<int>( m_rows.size() ) )
421 return;
422
423 std::swap( m_rows[row], m_rows[target] );
424
426 m_columnGrid->SetGridCursor( target, m_columnGrid->GetGridCursorCol() );
427 m_columnGrid->SelectRow( target );
428}
429
430
432{
433 moveColumn( -1 );
434}
435
436
438{
439 moveColumn( 1 );
440}
441
442
444{
445 setColumns( m_table->Schema().Defaults() );
446}
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
void Revert() override
Revert the commit by restoring the modified items state.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
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_GENERATED_TABLE_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Table Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void setColumns(const std::vector< GENERATED_TABLE_COLUMN > &aColumns)
Show aColumns checked and in order, then every other schema column unchecked so turning one back on d...
bool harvestColumnGrid()
Read the grid back into m_rows.
void onMoveUp(wxCommandEvent &aEvent) override
void onResetColumns(wxCommandEvent &aEvent) override
DIALOG_GENERATED_TABLE_PROPERTIES(PCB_BASE_EDIT_FRAME *aFrame, PCB_GENERATED_TABLE *aTable, GENERATED_TABLE_OPTIONS_FACTORY aOptions)
aOptions may be empty. When given, the panel is created on the dialog and put below the column grid
void setFormat(const GENERATED_TABLE_FORMAT &aFormat)
void fillColumnGrid()
Fill the column grid from m_rows.
std::vector< GENERATED_TABLE_COLUMN > shownColumns() const
void onMoveDown(wxCommandEvent &aEvent) override
void onBorderChecked(wxCommandEvent &aEvent) override
std::optional< GENERATED_TABLE_FORMAT > getFormat()
std::vector< COLUMN_ROW > m_rows
Every known column, checked ones first in table order.
void fitColumnGrid()
Share the grid's width out across its columns so none of it goes unused.
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...
One pass of rebuilding tables.
The columns one generator (the drill chart, a stackup table, ...) can produce, and the units and prec...
const GENERATED_TABLE_COLUMN_DEF * Find(int aId) const
Common, abstract interface for edit frames.
A table whose cell text is derived from the board and rebuilt whenever it goes stale.
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.
std::function< GENERATED_TABLE_OPTIONS_PANEL *(wxWindow *aParent)> GENERATED_TABLE_OPTIONS_FACTORY
The options panel must be created with its real parent, since a wxPanel cannot be reparented reliably...
#define _(s)
LSET DocumentationLayers()
Layers a drill chart or drill map may live on.
GENERATED_TABLE_UNITS
constexpr int GENERATED_TABLE_MAX_COLUMN_WIDTH
A column can be no wider than this.
GENERATED_TABLE_ALIGN
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
#define SKIP_CONNECTIVITY
Definition sch_commit.h:41
@ COL_WIDTH
The column a schema knows how to build, independent of whether any particular table shows it.
A column as a generated table actually carries it: what a row's cell in that column holds and how it ...
GENERATED_TABLE_ALIGN m_Align
What the shared dialog edits that a template also carries.
std::vector< GENERATED_TABLE_COLUMN > m_Columns
Shown columns only, in table order.