KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_generated_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
20#include <set>
21
22#include <pcb_generated_table.h>
23
24#include <board.h>
26#include <font/font.h>
27#include <i18n_utility.h>
28#include <pcb_tablecell.h>
30
31
32namespace
33{
34
35GR_TEXT_H_ALIGN_T toHorizJustify( GENERATED_TABLE_ALIGN aAlign )
36{
37 switch( aAlign )
38 {
42 }
43
45}
46
47
48// A subclass short of cells for a column shows it blank rather than reading past the end
49const wxString& textAt( const std::vector<wxString>& aCells, int aCol )
50{
51 static const wxString empty;
52
53 return aCol < static_cast<int>( aCells.size() ) ? aCells[aCol] : empty;
54}
55
56} // namespace
57
58
60 PCB_TABLE( aParent, aType, aLineWidth ),
62 m_precision( 3 )
63{
64 // RefreshGeneratedTables finds tables by type, so an unlisted one would never refresh
65 wxASSERT( IsGeneratedTableType( aType ) );
66}
67
68
70{
71 wxCHECK_RET( aImage && aImage->Type() == Type(), wxT( "Cannot swap data with invalid table." ) );
72
73 PCB_TABLE::swapData( aImage );
74
75 PCB_GENERATED_TABLE* other = static_cast<PCB_GENERATED_TABLE*>( aImage );
76
77 std::swap( m_columns, other->m_columns );
78 std::swap( m_units, other->m_units );
79 std::swap( m_precision, other->m_precision );
80 std::swap( m_rowKeys, other->m_rowKeys );
81}
82
83
85{
86 return m_columns == aOther.m_columns && m_units == aOther.m_units && m_precision == aOther.m_precision
87 && m_rowKeys == aOther.m_rowKeys && PCB_TABLE::operator==( aOther );
88}
89
90
91void PCB_GENERATED_TABLE::migrateRows( int aRows, int aCols, const std::vector<std::string>& aNewRowKeys )
92{
93 const int oldCols = GetColCount();
94
95 // With no recorded keys a row has no identity beyond its position, which is what
96 // ResizeCells already preserves
97 if( oldCols <= 0 || aCols <= 0 || m_cells.empty() || m_rowKeys.empty() )
98 return;
99
100 const int oldRows = static_cast<int>( m_cells.size() ) / oldCols;
101 const int firstDataRow = 1;
102
103 std::map<std::string, int> oldRowByKey;
104 int oldFirstData = oldRows;
105 int oldLastData = -1;
106
107 for( const auto& [oldRow, key] : m_rowKeys )
108 {
109 if( oldRow < 0 || oldRow >= oldRows )
110 continue;
111
112 oldRowByKey[key] = oldRow;
113 oldFirstData = std::min( oldFirstData, oldRow );
114 oldLastData = std::max( oldLastData, oldRow );
115 }
116
117 if( oldRowByKey.empty() )
118 return;
119
120 int newDataCount = 0;
121
122 for( const std::string& key : aNewRowKeys )
123 {
124 if( !key.empty() )
125 newDataCount++;
126 }
127
128 std::vector<int> sourceRow( aRows, -1 );
129
130 // The title and heading are matched from the end of their run, so the heading stays the
131 // heading when a title is added or removed
132 for( int ii = 0; ii < firstDataRow; ++ii )
133 {
134 const int oldIdx = oldFirstData - ( firstDataRow - ii );
135
136 if( oldIdx >= 0 )
137 sourceRow[ii] = oldIdx;
138 }
139
140 for( int ii = firstDataRow; ii < aRows; ++ii )
141 {
142 if( aNewRowKeys[ii].empty() )
143 continue;
144
145 const auto it = oldRowByKey.find( aNewRowKeys[ii] );
146
147 if( it != oldRowByKey.end() )
148 sourceRow[ii] = it->second;
149 }
150
151 const int trailingNewStart = firstDataRow + newDataCount;
152
153 for( int ii = trailingNewStart; ii < aRows; ++ii )
154 {
155 const int oldIdx = oldLastData + 1 + ( ii - trailingNewStart );
156
157 if( oldIdx < oldRows )
158 sourceRow[ii] = oldIdx;
159 }
160
161 std::vector<PCB_TABLECELL*> newCells( static_cast<size_t>( aRows ) * aCols, nullptr );
162 std::vector<bool> carried( m_cells.size(), false );
163 std::map<int, int> newRowHeights;
164
165 for( int ii = 0; ii < aRows; ++ii )
166 {
167 if( sourceRow[ii] < 0 )
168 continue;
169
170 // A column added since the last rebuild has no cell to carry, and one taken away
171 // leaves its cells behind to be deleted with the rest of the uncarried ones
172 for( int col = 0; col < std::min( oldCols, aCols ); ++col )
173 {
174 const size_t from = static_cast<size_t>( sourceRow[ii] ) * oldCols + col;
175
176 newCells[static_cast<size_t>( ii ) * aCols + col] = m_cells[from];
177 carried[from] = true;
178 }
179
180 const auto heightIt = m_rowHeights.find( sourceRow[ii] );
181
182 if( heightIt != m_rowHeights.end() )
183 newRowHeights[ii] = heightIt->second;
184 }
185
186 for( size_t ii = 0; ii < m_cells.size(); ++ii )
187 {
188 if( !carried[ii] )
189 delete m_cells[ii];
190 }
191
192 for( PCB_TABLECELL*& cell : newCells )
193 {
194 if( !cell )
195 {
196 cell = new PCB_TABLECELL( this );
197 cell->SetLayer( GetLayer() );
198 }
199 }
200
201 m_cells = std::move( newCells );
202 m_rowHeights = std::move( newRowHeights );
203}
204
205
207 const std::vector<KICAD_T>& aScanTypes )
208{
209 // A generated table answers to its own type and to PCB_TABLE_T, so reporting per matching
210 // type would hand it to the inspector twice and list it twice in the disambiguation menu
211 bool wantTable = false;
212 bool wantCells = false;
213
214 for( KICAD_T scanType : aScanTypes )
215 {
216 if( scanType == Type() || scanType == PCB_TABLE_T )
217 wantTable = true;
218 else if( scanType == PCB_TABLECELL_T )
219 wantCells = true;
220 }
221
222 if( wantTable && INSPECT_RESULT::QUIT == aInspector( this, aTestData ) )
224
225 if( wantCells )
226 {
227 for( PCB_TABLECELL* cell : GetCells() )
228 {
229 if( INSPECT_RESULT::QUIT == aInspector( cell, aTestData ) )
231 }
232 }
233
235}
236
237
238bool PCB_GENERATED_TABLE::IsStale( const BOARD& aBoard ) const
239{
240 const std::unique_ptr<GENERATED_TABLE_CONTENT> content = generate( aBoard, nullptr );
241
242 const int cols = static_cast<int>( m_columns.size() );
243 const int dataRows = static_cast<int>( content->m_Rows.size() );
244 const int rows = 1 + dataRows + static_cast<int>( content->m_Trailer.size() );
245
246 if( GetColCount() != cols || GetRowCount() != rows )
247 return true;
248
249 const auto differs =
250 [&]( int aRow, int aCol, const wxString& aText )
251 {
252 const PCB_TABLECELL* cell = GetCell( aRow, aCol );
253
254 return !cell || cell->GetText() != aText;
255 };
256
257 for( int col = 0; col < cols; ++col )
258 {
259 if( differs( 0, col, m_columns[col].m_Heading ) )
260 return true;
261 }
262
263 for( int ii = 0; ii < dataRows; ++ii )
264 {
265 const GENERATED_TABLE_ROW& row = content->m_Rows[ii];
266 const auto keyIt = m_rowKeys.find( 1 + ii );
267
268 if( keyIt == m_rowKeys.end() || keyIt->second != row.m_Key )
269 return true;
270
271 for( int col = 0; col < cols; ++col )
272 {
273 if( differs( 1 + ii, col, textAt( row.m_Cells, col ) ) )
274 return true;
275 }
276 }
277
278 for( size_t ii = 0; ii < content->m_Trailer.size(); ++ii )
279 {
280 for( int col = 0; col < cols; ++col )
281 {
282 if( differs( 1 + dataRows + static_cast<int>( ii ), col, textAt( content->m_Trailer[ii], col ) ) )
283 return true;
284 }
285 }
286
287 return false;
288}
289
290
292{
293 std::unique_ptr<GENERATED_TABLE_CONTENT> content = generate( aBoard, aRefresh );
294
295 const int cols = static_cast<int>( m_columns.size() );
296 const int dataRows = static_cast<int>( content->m_Rows.size() );
297
298 // The headings are the header row. A table carries no caption of its own
299 const int rows = 1 + dataRows + static_cast<int>( content->m_Trailer.size() );
300
301 // A new cell carries a half-INT_MAX rectangle and Normalize() anchors on cell 0's centre,
302 // so without holding the old position a rebuild lands the chart half a metre off-board
303 const VECTOR2I anchor = GetCells().empty() ? VECTOR2I( 0, 0 ) : GetPosition();
304
305 std::vector<std::string> newRowKeys( rows );
306
307 // What a row reports, so a rebuild hands its formatting to the row still reporting the
308 // same thing rather than to whatever lands on its index
309 for( int ii = 0; ii < dataRows; ++ii )
310 newRowKeys[1 + ii] = content->m_Rows[ii].m_Key;
311
312 // By UUID, not pointer. A cell migrateRows deletes can hand its address to a new one
313 std::set<KIID> existing;
314
315 for( PCB_TABLECELL* cell : GetCells() )
316 existing.insert( cell->m_Uuid );
317
318 migrateRows( rows, cols, newRowKeys );
319
320 SetColCount( cols );
321 ResizeCells( rows, cols );
322
323 m_rowKeys.clear();
324
325 for( int ii = 0; ii < rows; ++ii )
326 {
327 if( !newRowKeys[ii].empty() )
328 m_rowKeys[ii] = newRowKeys[ii];
329 }
330
331 const auto setCell =
332 [&]( int aRow, int aCol, const wxString& aText )
333 {
334 PCB_TABLECELL* cell = GetCell( aRow, aCol );
335
336 if( !cell )
337 return;
338
339 if( !existing.count( cell->m_Uuid ) )
340 styleNewCell( cell, aRow == 0 );
341
342 cell->SetText( aText );
343
344 // The column's alignment, which was otherwise editable, serialized and
345 // ignored
346 cell->SetHorizJustify( toHorizJustify( m_columns[aCol].m_Align ) );
347 };
348
349 for( int col = 0; col < cols; ++col )
350 setCell( 0, col, m_columns[col].m_Heading );
351
352 for( int ii = 0; ii < dataRows; ++ii )
353 {
354 for( int col = 0; col < cols; ++col )
355 setCell( 1 + ii, col, textAt( content->m_Rows[ii].m_Cells, col ) );
356 }
357
358 for( size_t ii = 0; ii < content->m_Trailer.size(); ++ii )
359 {
360 for( int col = 0; col < cols; ++col )
361 setCell( 1 + dataRows + static_cast<int>( ii ), col, textAt( content->m_Trailer[ii], col ) );
362 }
363
364 // Autosize measures text as wrapped to its cell, so a reused cell narrower than its new text
365 // would keep the wrap. Widen those to their resolved, unwrapped text, which Autosize then tightens
366 bool prewidened = false;
367
368 for( int col = 0; col < cols; ++col )
369 {
370 int width = 0;
371
372 for( int row = 0; row < rows; ++row )
373 {
374 const PCB_TABLECELL* cell = GetCell( row, col );
375
376 if( !cell )
377 continue;
378
379 const int penWidth = cell->GetEffectiveTextPenWidth();
380 const VECTOR2I extents = cell->GetDrawFont( nullptr )->StringBoundaryLimits(
381 cell->GetUnwrappedShownText( FOR_CANVAS ), cell->GetTextSize(), penWidth, cell->IsBold(),
382 cell->IsItalic(), cell->GetFontMetrics() );
383
384 // GetShownText breaks at the width less margins, and LinebreakText takes the pen off too
385 width = std::max( width, extents.x + penWidth + 2 * ( cell->GetMarginLeft() + cell->GetMarginRight() ) );
386 }
387
388 if( width > GetColWidth( col ) )
389 {
390 SetColWidth( col, width );
391 prewidened = true;
392 }
393 }
394
395 if( prewidened )
396 Normalize();
397
398 Autosize();
399
400 // After autosizing or the authored width never shows, and as a minimum so a width
401 // saved against a narrower board cannot clip its text
402 bool widened = false;
403
404 for( int col = 0; col < cols; ++col )
405 {
406 if( m_columns[col].m_Width > GetColWidth( col ) )
407 {
408 SetColWidth( col, m_columns[col].m_Width );
409 widened = true;
410 }
411 }
412
413 // The widths above are only a map until the cells are placed against them
414 if( widened )
415 Normalize();
416
417 Move( anchor - GetPosition() );
418
419 // Last, so a table is only ever marked fresh once its layout is complete
420 onRebuilt( aBoard, *content );
421}
422
423
425{
427 {
429
430 if( unitsEnum.Choices().GetCount() == 0 )
431 {
432 unitsEnum.Map( GENERATED_TABLE_UNITS::MM, _HKI( "Millimeters" ) )
434 .Map( GENERATED_TABLE_UNITS::INCH, _HKI( "Inches" ) );
435 }
436
439
444
445 const wxString generatorProps = _( "Table Generator" );
446
449 generatorProps );
450
451 propMgr.AddProperty( new PROPERTY<PCB_GENERATED_TABLE, int>( _HKI( "Decimal Places" ),
454 generatorProps );
455 }
457
458
BOARD_ITEM(BOARD_ITEM *aParent, KICAD_T idtype, PCB_LAYER_ID aLayer=F_Cu)
Definition board_item.h:86
friend class BOARD
Definition board_item.h:593
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
const KIFONT::METRICS & GetFontMetrics() const
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
bool IsItalic() const
Definition eda_text.h:200
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:118
virtual KIFONT::FONT * GetDrawFont(const RENDER_SETTINGS *aSettings) const
Definition eda_text.cpp:647
int GetEffectiveTextPenWidth(int aDefaultPenWidth=0) const
The EffectiveTextPenWidth uses the text thickness if > 1 or aDefaultPenWidth.
Definition eda_text.cpp:427
bool IsBold() const
Definition eda_text.h:215
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
void SetHorizJustify(GR_TEXT_H_ALIGN_T aType)
Definition eda_text.cpp:370
ENUM_MAP & Map(T aValue, const wxString &aName)
Definition property.h:789
static ENUM_MAP< T > & Instance()
Definition property.h:783
wxPGChoices & Choices()
Definition property.h:834
One pass of rebuilding tables.
VECTOR2I StringBoundaryLimits(const wxString &aText, const VECTOR2I &aSize, int aThickness, bool aBold, bool aItalic, const METRICS &aFontMetrics) const
Compute the boundary limits of aText (the bounding box of all shapes).
Definition font.cpp:439
A table whose cell text is derived from the board and rebuilt whenever it goes stale.
void migrateRows(int aRows, int aCols, const std::vector< std::string > &aNewRowKeys)
Move each row's cells to the row that reports the same thing.
std::vector< GENERATED_TABLE_COLUMN > m_columns
virtual void onRebuilt(const BOARD &aBoard, const GENERATED_TABLE_CONTENT &aContent)
Called once the cells are laid out, with the content they were built from.
GENERATED_TABLE_UNITS m_units
void RebuildCells(const BOARD &aBoard, GENERATED_TABLE_REFRESH *aRefresh=nullptr)
Regenerate the cells from the board.
virtual std::unique_ptr< GENERATED_TABLE_CONTENT > generate(const BOARD &aBoard, GENERATED_TABLE_REFRESH *aRefresh) const =0
The rows this table reports.
virtual bool IsStale(const BOARD &aBoard) const
True when the cells no longer say what the board says.
void SetPrecision(int aPrecision)
Clamped.
virtual void styleNewCell(PCB_TABLECELL *aCell, bool aIsHeading)
Called for each cell a rebuild creates, before its text is set.
bool generatedEquals(const PCB_GENERATED_TABLE &aOther) const
Equality of the members this class owns and of the underlying PCB_TABLE.
void swapData(BOARD_ITEM *aImage) override
INSPECT_RESULT Visit(INSPECTOR aInspector, void *aTestData, const std::vector< KICAD_T > &aScanTypes) override
May be re-implemented for each derived class in order to handle all the types given by its member dat...
PCB_GENERATED_TABLE(BOARD_ITEM *aParent, KICAD_T aType, int aLineWidth)
GENERATED_TABLE_UNITS GetUnits() const
void SetUnits(GENERATED_TABLE_UNITS aUnits)
std::map< int, std::string > m_rowKeys
wxString GetUnwrappedShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const
GetShownText() before it is line-broken to the cell's width, i.e.
int GetRowCount() const
Definition pcb_table.h:128
std::vector< PCB_TABLECELL * > m_cells
Definition pcb_table.h:341
void SetColWidth(int aCol, int aWidth)
Definition pcb_table.h:135
void Move(const VECTOR2I &aMoveVector) override
Move this object.
void ResizeCells(int aRows, int aCols)
Grow or shrink to aRows x aCols, keeping the cells that already exist.
PCB_TABLECELL * GetCell(int aRow, int aCol) const
Definition pcb_table.h:155
std::vector< PCB_TABLECELL * > GetCells() const
Definition pcb_table.h:165
void Autosize()
bool operator==(const PCB_TABLE &aOther) const
int GetColCount() const
Definition pcb_table.h:126
virtual void swapData(BOARD_ITEM *aImage) override
void Normalize() override
Perform any normalization required after a user rotate and/or flip.
void SetColCount(int aCount)
Definition pcb_table.h:125
int GetColWidth(int aCol) const
Definition pcb_table.h:137
VECTOR2I GetPosition() const override
PCB_TABLE(BOARD_ITEM *aParent, int aLineWidth)
Definition pcb_table.cpp:55
std::map< int, int > m_rowHeights
Definition pcb_table.h:340
VECTOR2I GetTextSize() const override
int GetMarginLeft() const
int GetMarginRight() const
Provide class metadata.
void InheritsAfter(TYPE_ID aDerived, TYPE_ID aBase)
Declare an inheritance relationship between types.
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE & AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
void AddTypeCast(TYPE_CAST_BASE *aCast)
Register a type converter.
@ FOR_CANVAS
Definition common.h:90
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
INSPECT_RESULT
Definition eda_item.h:44
const INSPECTOR_FUNC & INSPECTOR
std::function passed to nested users by ref, avoids copying std::function.
Definition eda_item.h:91
GENERATED_TABLE_UNITS
GENERATED_TABLE_ALIGN
Some functions to handle hotkeys in KiCad.
#define _HKI(x)
Definition page_info.cpp:40
static struct PCB_GENERATED_TABLE_DESC _PCB_GENERATED_TABLE_DESC
#define TYPE_HASH(x)
Macro to generate unique identifier for a type.
Definition property.h:74
#define ENUM_TO_WXANY(type)
Definition property.h:890
#define REGISTER_TYPE(x)
Helper macro to map type hashes to names.
One data row of a generated table, and the identity its formatting follows across rebuilds.
std::vector< wxString > m_Cells
static const long long MM
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
constexpr bool IsGeneratedTableType(const KICAD_T aType)
A PCB_TABLE subclass whose cells are generated from the board rather than typed by the user.
Definition typeinfo.h:325
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708