KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_lib_edit_pin_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#include "grid_tricks.h"
26#include <sch_pin.h>
27#include <pin_numbers.h>
28#include "pgm_base.h"
29#include <base_units.h>
30#include <bitmaps.h>
31#include <confirm.h>
32#include <symbol_edit_frame.h>
34#include <kiplatform/ui.h>
37#include <widgets/wx_grid.h>
41#include <wx/tokenzr.h>
42#include <string_utils.h>
43
44#define UNITS_ALL _( "ALL" )
45#define DEMORGAN_ALL _( "ALL" )
46#define DEMORGAN_STD _( "Standard" )
47#define DEMORGAN_ALT _( "Alternate" )
48
49
50void getSelectedArea( WX_GRID* aGrid, int* aRowStart, int* aRowCount )
51{
52 wxGridCellCoordsArray topLeft = aGrid->GetSelectionBlockTopLeft();
53 wxGridCellCoordsArray botRight = aGrid->GetSelectionBlockBottomRight();
54
55 wxArrayInt cols = aGrid->GetSelectedCols();
56 wxArrayInt rows = aGrid->GetSelectedRows();
57
58 if( topLeft.Count() && botRight.Count() )
59 {
60 *aRowStart = topLeft[0].GetRow();
61 *aRowCount = botRight[0].GetRow() - *aRowStart + 1;
62 }
63 else if( cols.Count() )
64 {
65 *aRowStart = 0;
66 *aRowCount = aGrid->GetNumberRows();
67 }
68 else if( rows.Count() )
69 {
70 *aRowStart = rows[0];
71 *aRowCount = rows.Count();
72 }
73 else
74 {
75 *aRowStart = aGrid->GetGridCursorRow();
76 *aRowCount = *aRowStart >= 0 ? 1 : 0;
77 }
78}
79
80
81class PIN_TABLE_DATA_MODEL : public wxGridTableBase
82{
83public:
85 LIB_SYMBOL* aSymbol ) :
86 m_frame( aFrame ),
87 m_unitFilter( -1 ),
88 m_edited( false ),
89 m_pinTable( aPinTable ),
90 m_symbol( aSymbol )
91 {
92 m_eval = std::make_unique<NUMERIC_EVALUATOR>( m_frame->GetUserUnits() );
93
94 m_frame->Bind( EDA_EVT_UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
95 }
96
98 {
99 m_frame->Unbind( EDA_EVT_UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
100 }
101
102 void onUnitsChanged( wxCommandEvent& aEvent )
103 {
104 if( GetView() )
105 GetView()->ForceRefresh();
106
107 aEvent.Skip();
108 }
109
110 void SetUnitFilter( int aFilter ) { m_unitFilter = aFilter; }
111
112 int GetNumberRows() override { return (int) m_rows.size(); }
113 int GetNumberCols() override { return COL_COUNT; }
114
115 wxString GetColLabelValue( int aCol ) override
116 {
117 switch( aCol )
118 {
119 case COL_PIN_COUNT: return _( "Count" );
120 case COL_NUMBER: return _( "Number" );
121 case COL_NAME: return _( "Name" );
122 case COL_TYPE: return _( "Electrical Type" );
123 case COL_SHAPE: return _( "Graphic Style" );
124 case COL_ORIENTATION: return _( "Orientation" );
125 case COL_NUMBER_SIZE: return _( "Number Text Size" );
126 case COL_NAME_SIZE: return _( "Name Text Size" );
127 case COL_LENGTH: return _( "Length" );
128 case COL_POSX: return _( "X Position" );
129 case COL_POSY: return _( "Y Position" );
130 case COL_VISIBLE: return _( "Visible" );
131 case COL_UNIT: return _( "Unit" );
132 case COL_DEMORGAN: return _( "De Morgan" );
133 default: wxFAIL; return wxEmptyString;
134 }
135 }
136
137 bool IsEmptyCell( int row, int col ) override
138 {
139 return false; // don't allow adjacent cell overflow, even if we are actually empty
140 }
141
142 wxString GetValue( int aRow, int aCol ) override
143 {
144 wxGrid* grid = GetView();
145
146 if( grid->GetGridCursorRow() == aRow && grid->GetGridCursorCol() == aCol
147 && grid->IsCellEditControlShown() )
148 {
149 auto it = m_evalOriginal.find( { m_rows[ aRow ], aCol } );
150
151 if( it != m_evalOriginal.end() )
152 return it->second;
153 }
154
155 return GetValue( m_rows[ aRow ], aCol, m_frame );
156 }
157
158 static wxString GetValue( const std::vector<SCH_PIN*>& pins, int aCol,
159 EDA_DRAW_FRAME* aParentFrame )
160 {
161 wxString fieldValue;
162
163 if( pins.empty() )
164 return fieldValue;
165
166 for( SCH_PIN* pin : pins )
167 {
168 wxString val;
169
170 switch( aCol )
171 {
172 case COL_PIN_COUNT:
173 val << pins.size();
174 break;
175
176 case COL_NUMBER:
177 val = pin->GetNumber();
178 break;
179
180 case COL_NAME:
181 val = pin->GetName();
182 break;
183
184 case COL_TYPE:
185 val = PinTypeNames()[static_cast<int>( pin->GetType() )];
186 break;
187
188 case COL_SHAPE:
189 val = PinShapeNames()[static_cast<int>( pin->GetShape() )];
190 break;
191
192 case COL_ORIENTATION:
193 if( PinOrientationIndex( pin->GetOrientation() ) >= 0 )
194 val = PinOrientationNames()[ PinOrientationIndex( pin->GetOrientation() ) ];
195
196 break;
197
198 case COL_NUMBER_SIZE:
199 val = aParentFrame->StringFromValue( pin->GetNumberTextSize(), true );
200 break;
201
202 case COL_NAME_SIZE:
203 val = aParentFrame->StringFromValue( pin->GetNameTextSize(), true );
204 break;
205
206 case COL_LENGTH:
207 val = aParentFrame->StringFromValue( pin->GetLength(), true );
208 break;
209
210 case COL_POSX:
211 val = aParentFrame->StringFromValue( pin->GetPosition().x, true );
212 break;
213
214 case COL_POSY:
215 val = aParentFrame->StringFromValue( pin->GetPosition().y, true );
216 break;
217
218 case COL_VISIBLE:
219 val = StringFromBool( pin->IsVisible() );
220 break;
221
222 case COL_UNIT:
223 if( pin->GetUnit() )
224 val = LIB_SYMBOL::LetterSubReference( pin->GetUnit(), 'A' );
225 else
226 val = UNITS_ALL;
227
228 break;
229
230 case COL_DEMORGAN:
231 switch( pin->GetBodyStyle() )
232 {
233 case BODY_STYLE::BASE:
234 val = DEMORGAN_STD;
235 break;
236 case BODY_STYLE::DEMORGAN:
237 val = DEMORGAN_ALT;
238 break;
239 default:
240 val = DEMORGAN_ALL;
241 break;
242 }
243 break;
244
245 default:
246 wxFAIL;
247 break;
248 }
249
250 if( aCol == COL_NUMBER )
251 {
252 if( fieldValue.length() )
253 fieldValue += wxT( ", " );
254
255 fieldValue += val;
256 }
257 else
258 {
259 if( !fieldValue.Length() )
260 fieldValue = val;
261 else if( val != fieldValue )
262 fieldValue = INDETERMINATE_STATE;
263 }
264 }
265
266 return fieldValue;
267 }
268
269 void SetValue( int aRow, int aCol, const wxString &aValue ) override
270 {
271 if( aValue == INDETERMINATE_STATE )
272 return;
273
274 wxString value = aValue;
275
276 switch( aCol )
277 {
278 case COL_NUMBER_SIZE:
279 case COL_NAME_SIZE:
280 case COL_LENGTH:
281 case COL_POSX:
282 case COL_POSY:
283 m_eval->SetDefaultUnits( m_frame->GetUserUnits() );
284
285 if( m_eval->Process( value ) )
286 {
287 m_evalOriginal[ { m_rows[ aRow ], aCol } ] = value;
288 value = m_eval->Result();
289 }
290
291 break;
292
293 default:
294 break;
295 }
296
297 std::vector<SCH_PIN*> pins = m_rows[ aRow ];
298
299 // If the NUMBER column is edited and the pins are grouped, renumber, and add or
300 // remove pins based on the comma separated list of pins.
301 if( aCol == COL_NUMBER && m_pinTable->IsDisplayGrouped() )
302 {
303 wxStringTokenizer tokenizer( value, "," );
304 size_t i = 0;
305
306 while( tokenizer.HasMoreTokens() )
307 {
308 wxString pinName = tokenizer.GetNextToken();
309
310 // Trim whitespace from both ends of the string
311 pinName.Trim( true ).Trim( false );
312
313 if( i < pins.size() )
314 {
315 // Renumber the existing pins
316 pins.at( i )->SetNumber( pinName );
317 }
318 else
319 {
320 // Create new pins
321 SCH_PIN* newPin = new SCH_PIN( this->m_symbol );
322 SCH_PIN* last = pins.back();
323
324 newPin->SetNumber( pinName );
325 newPin->SetName( last->GetName() );
326 newPin->SetOrientation( last->GetOrientation() );
327 newPin->SetType( last->GetType() );
328 newPin->SetShape( last->GetShape() );
329 newPin->SetUnit( last->GetUnit() );
330
331 VECTOR2I pos = last->GetPosition();
332
335 mgr.GetAppSettings<SYMBOL_EDITOR_SETTINGS>( "symbol_editor" );
336
337 if( last->GetOrientation() == PIN_ORIENTATION::PIN_LEFT
338 || last->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
339 {
340 pos.y -= schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
341 }
342 else
343 {
344 pos.x += schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
345 }
346
347 newPin->SetPosition( pos );
348
349 pins.push_back( newPin );
350 m_pinTable->AddPin( newPin );
351 }
352
353 i++;
354 }
355
356 while( pins.size() > i )
357 {
358 m_pinTable->RemovePin( pins.back() );
359 pins.pop_back();
360 }
361
362 m_rows[aRow] = pins;
363 m_edited = true;
364
365 return;
366 }
367
368 for( SCH_PIN* pin : pins )
369 {
370 switch( aCol )
371 {
372 case COL_NUMBER:
374 pin->SetNumber( value );
375
376 break;
377
378 case COL_NAME:
379 pin->SetName( value );
380 break;
381
382 case COL_TYPE:
383 if( PinTypeNames().Index( value ) != wxNOT_FOUND )
384 pin->SetType( (ELECTRICAL_PINTYPE) PinTypeNames().Index( value ) );
385
386 break;
387
388 case COL_SHAPE:
389 if( PinShapeNames().Index( value ) != wxNOT_FOUND )
390 pin->SetShape( (GRAPHIC_PINSHAPE) PinShapeNames().Index( value ) );
391
392 break;
393
394 case COL_ORIENTATION:
395 if( PinOrientationNames().Index( value ) != wxNOT_FOUND )
396 pin->SetOrientation(
397 PinOrientationCode( PinOrientationNames().Index( value ) ) );
398 break;
399
400 case COL_NUMBER_SIZE:
401 pin->SetNumberTextSize( m_frame->ValueFromString( value ) );
402 break;
403
404 case COL_NAME_SIZE:
405 pin->SetNameTextSize( m_frame->ValueFromString( value ) );
406 break;
407
408 case COL_LENGTH:
409 pin->ChangeLength( m_frame->ValueFromString( value ) );
410 break;
411
412 case COL_POSX:
413 pin->SetPosition( VECTOR2I( m_frame->ValueFromString( value ),
414 pin->GetPosition().y ) );
415 break;
416
417 case COL_POSY:
418 pin->SetPosition( VECTOR2I( pin->GetPosition().x,
419 m_frame->ValueFromString( value ) ) );
420 break;
421
422 case COL_VISIBLE:
423 pin->SetVisible(BoolFromString( value ));
424 break;
425
426 case COL_UNIT:
427 if( value == UNITS_ALL )
428 {
429 pin->SetUnit( 0 );
430 }
431 else
432 {
433 for( int i = 1; i <= m_symbol->GetUnitCount(); i++ )
434 {
435 if( value == LIB_SYMBOL::LetterSubReference( i, 'A' ) )
436 {
437 pin->SetUnit( i );
438 break;
439 }
440 }
441 }
442
443 break;
444
445 case COL_DEMORGAN:
446 if( value == DEMORGAN_STD )
447 pin->SetBodyStyle( 1 );
448 else if( value == DEMORGAN_ALT )
449 pin->SetBodyStyle( 2 );
450 else
451 pin->SetBodyStyle( 0 );
452 break;
453
454 default:
455 wxFAIL;
456 break;
457 }
458 }
459
460 m_edited = true;
461 }
462
463 static int findRow( const std::vector<std::vector<SCH_PIN*>>& aRowSet, const wxString& aName )
464 {
465 for( size_t i = 0; i < aRowSet.size(); ++i )
466 {
467 if( aRowSet[ i ][ 0 ] && aRowSet[ i ][ 0 ]->GetName() == aName )
468 return i;
469 }
470
471 return -1;
472 }
473
474 static bool compare( const std::vector<SCH_PIN*>& lhs, const std::vector<SCH_PIN*>& rhs,
475 int sortCol, bool ascending, EDA_DRAW_FRAME* parentFrame )
476 {
477 wxString lhStr = GetValue( lhs, sortCol, parentFrame );
478 wxString rhStr = GetValue( rhs, sortCol, parentFrame );
479
480 if( lhStr == rhStr )
481 {
482 // Secondary sort key is always COL_NUMBER
483 sortCol = COL_NUMBER;
484 lhStr = GetValue( lhs, sortCol, parentFrame );
485 rhStr = GetValue( rhs, sortCol, parentFrame );
486 }
487
488 bool res;
489
490 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
491 // to get the opposite sort. i.e. ~(a<b) != (a>b)
492 auto cmp = [ ascending ]( const auto a, const auto b )
493 {
494 if( ascending )
495 return a < b;
496 else
497 return b < a;
498 };
499
500 switch( sortCol )
501 {
502 case COL_NUMBER:
503 case COL_NAME:
504 res = cmp( PIN_NUMBERS::Compare( lhStr, rhStr ), 0 );
505 break;
506 case COL_NUMBER_SIZE:
507 case COL_NAME_SIZE:
508 res = cmp( parentFrame->ValueFromString( lhStr ),
509 parentFrame->ValueFromString( rhStr ) );
510 break;
511 case COL_LENGTH:
512 case COL_POSX:
513 case COL_POSY:
514 res = cmp( parentFrame->ValueFromString( lhStr ),
515 parentFrame->ValueFromString( rhStr ) );
516 break;
517 case COL_VISIBLE:
518 case COL_DEMORGAN:
519 default:
520 res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
521 break;
522 }
523
524 return res;
525 }
526
527 void RebuildRows( const std::vector<SCH_PIN*>& aPins, bool groupByName, bool groupBySelection )
528 {
529 WX_GRID* grid = dynamic_cast<WX_GRID*>( GetView() );
530 std::vector<SCH_PIN*> clear_flags;
531
532 clear_flags.reserve( aPins.size() );
533
534 if( grid )
535 {
536 if( groupBySelection )
537 {
538 for( SCH_PIN* pin : aPins )
539 pin->ClearTempFlags();
540
541 int firstSelectedRow;
542 int selectedRowCount;
543
544 getSelectedArea( grid, &firstSelectedRow, &selectedRowCount );
545
546 for( int ii = 0; ii < selectedRowCount; ++ii )
547 {
548 for( SCH_PIN* pin : m_rows[ firstSelectedRow + ii ] )
549 {
550 pin->SetFlags( CANDIDATE );
551 clear_flags.push_back( pin );
552 }
553 }
554 }
555
556 // Commit any pending in-place edits before the row gets moved out from under
557 // the editor.
558 grid->CommitPendingChanges( true );
559
560 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
561 GetView()->ProcessTableMessage( msg );
562 }
563
564 m_rows.clear();
565
566 if( groupBySelection )
567 m_rows.emplace_back( std::vector<SCH_PIN*>() );
568
569 for( SCH_PIN* pin : aPins )
570 {
571 if( m_unitFilter == -1 || pin->GetUnit() == 0 || pin->GetUnit() == m_unitFilter )
572 {
573 int rowIndex = -1;
574
575 if( groupByName )
576 rowIndex = findRow( m_rows, pin->GetName() );
577 else if( groupBySelection && ( pin->GetFlags() & CANDIDATE ) )
578 rowIndex = 0;
579
580 if( rowIndex < 0 )
581 {
582 m_rows.emplace_back( std::vector<SCH_PIN*>() );
583 rowIndex = m_rows.size() - 1;
584 }
585
586 m_rows[ rowIndex ].push_back( pin );
587 }
588 }
589
590 int sortCol = 0;
591 bool ascending = true;
592
593 if( GetView() && GetView()->GetSortingColumn() != wxNOT_FOUND )
594 {
595 sortCol = GetView()->GetSortingColumn();
596 ascending = GetView()->IsSortOrderAscending();
597 }
598
599 for( std::vector<SCH_PIN*>& row : m_rows )
600 SortPins( row );
601
602 if( !groupBySelection )
603 SortRows( sortCol, ascending );
604
605 if ( GetView() )
606 {
607 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, (int) m_rows.size() );
608 GetView()->ProcessTableMessage( msg );
609
610 if( groupBySelection )
611 GetView()->SelectRow( 0 );
612 }
613
614 for( SCH_PIN* pin : clear_flags )
615 pin->ClearFlags( CANDIDATE );
616 }
617
618 void SortRows( int aSortCol, bool ascending )
619 {
620 std::sort( m_rows.begin(), m_rows.end(),
621 [ aSortCol, ascending, this ]( const std::vector<SCH_PIN*>& lhs,
622 const std::vector<SCH_PIN*>& rhs ) -> bool
623 {
624 return compare( lhs, rhs, aSortCol, ascending, m_frame );
625 } );
626 }
627
628 void SortPins( std::vector<SCH_PIN*>& aRow )
629 {
630 std::sort( aRow.begin(), aRow.end(),
631 []( SCH_PIN* lhs, SCH_PIN* rhs ) -> bool
632 {
633 return PIN_NUMBERS::Compare( lhs->GetNumber(), rhs->GetNumber() ) < 0;
634 } );
635 }
636
637 void AppendRow( SCH_PIN* aPin )
638 {
639 std::vector<SCH_PIN*> row;
640 row.push_back( aPin );
641 m_rows.push_back( row );
642
643 if ( GetView() )
644 {
645 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
646 GetView()->ProcessTableMessage( msg );
647 }
648 }
649
650 std::vector<SCH_PIN*> RemoveRow( int aRow )
651 {
652 std::vector<SCH_PIN*> removedRow = m_rows[ aRow ];
653
654 m_rows.erase( m_rows.begin() + aRow );
655
656 if ( GetView() )
657 {
658 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow, 1 );
659 GetView()->ProcessTableMessage( msg );
660 }
661
662 return removedRow;
663 }
664
665 std::vector<SCH_PIN*> GetRowPins( int aRow )
666 {
667 return m_rows[ aRow ];
668 }
669
670 bool IsEdited()
671 {
672 return m_edited;
673 }
674
675private:
676 static wxString StringFromBool( bool aValue )
677 {
678 if( aValue )
679 return wxT( "1" );
680 else
681 return wxT( "0" );
682 }
683
684 static bool BoolFromString( wxString aValue )
685 {
686 if( aValue == wxS( "1" ) )
687 {
688 return true;
689 }
690 else if( aValue == wxS( "0" ) )
691 {
692 return false;
693 }
694 else
695 {
696 wxFAIL_MSG( wxString::Format( "string '%s' can't be converted to boolean correctly, "
697 "it will have been perceived as FALSE",
698 aValue ) );
699 return false;
700 }
701 }
702
703private:
705
706 // Because the rows of the grid can either be a single pin or a group of pins, the
707 // data model is a 2D vector. If we're in the single pin case, each row's SCH_PINs
708 // contains only a single pin.
709 std::vector<std::vector<SCH_PIN*>> m_rows;
710 int m_unitFilter; // 0 to show pins for all units
711
713
715 LIB_SYMBOL* m_symbol; // Parent symbol that the pins belong to.
716
717 std::unique_ptr<NUMERIC_EVALUATOR> m_eval;
718 std::map< std::pair<std::vector<SCH_PIN*>, int>, wxString > m_evalOriginal;
719};
720
721
723 LIB_SYMBOL* aSymbol ) :
725 m_editFrame( parent ),
726 m_symbol( aSymbol )
727{
729
730 // Save original columns widths so we can do proportional sizing.
731 for( int i = 0; i < COL_COUNT; ++i )
732 m_originalColWidths[ i ] = m_grid->GetColSize( i );
733
734 // Give a bit more room for combobox editors
735 m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
736
738 m_grid->PushEventHandler( new GRID_TRICKS( m_grid, [this]( wxCommandEvent& aEvent )
739 {
740 OnAddRow( aEvent );
741 } ) );
742
743 // Show/hide columns according to the user's preference
744 if( SYMBOL_EDITOR_SETTINGS* cfg = parent->GetSettings() )
745 {
746 m_grid->ShowHideColumns( cfg->m_PinTableVisibleColumns );
748 }
749
750 // Set special attributes
751 wxGridCellAttr* attr;
752
753 attr = new wxGridCellAttr;
754 attr->SetReadOnly( true );
755 m_grid->SetColAttr( COL_PIN_COUNT, attr );
756
757 attr = new wxGridCellAttr;
758 wxArrayString typeNames = PinTypeNames();
759 typeNames.push_back( INDETERMINATE_STATE );
760 attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(), typeNames ) );
761 attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinTypeIcons(), typeNames ) );
762 m_grid->SetColAttr( COL_TYPE, attr );
763
764 attr = new wxGridCellAttr;
765 wxArrayString shapeNames = PinShapeNames();
766 shapeNames.push_back( INDETERMINATE_STATE );
767 attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(), shapeNames ) );
768 attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinShapeIcons(), shapeNames ) );
769 m_grid->SetColAttr( COL_SHAPE, attr );
770
771 attr = new wxGridCellAttr;
772 wxArrayString orientationNames = PinOrientationNames();
773 orientationNames.push_back( INDETERMINATE_STATE );
774 attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinOrientationIcons(),
775 orientationNames ) );
776 attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinOrientationIcons(), orientationNames ) );
777 m_grid->SetColAttr( COL_ORIENTATION, attr );
778
779 attr = new wxGridCellAttr;
780 wxArrayString unitNames;
781 unitNames.push_back( UNITS_ALL );
782
783 for( int i = 1; i <= aSymbol->GetUnitCount(); i++ )
784 unitNames.push_back( LIB_SYMBOL::LetterSubReference( i, 'A' ) );
785
786 attr->SetEditor( new GRID_CELL_COMBOBOX( unitNames ) );
787 m_grid->SetColAttr( COL_UNIT, attr );
788
789 attr = new wxGridCellAttr;
790 wxArrayString demorganNames;
791 demorganNames.push_back( DEMORGAN_ALL );
792 demorganNames.push_back( DEMORGAN_STD );
793 demorganNames.push_back( DEMORGAN_ALT );
794 attr->SetEditor( new GRID_CELL_COMBOBOX( demorganNames ) );
795 m_grid->SetColAttr( COL_DEMORGAN, attr );
796
797 attr = new wxGridCellAttr;
798 attr->SetRenderer( new wxGridCellBoolRenderer() );
799 attr->SetEditor( new wxGridCellBoolEditor() );
800 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
801 m_grid->SetColAttr( COL_VISIBLE, attr );
802
803 /* Right-aligned position values look much better, but only MSW and GTK2+
804 * currently support right-aligned textEditCtrls, so the text jumps on all
805 * the other platforms when you edit it.
806 attr = new wxGridCellAttr;
807 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
808 m_grid->SetColAttr( COL_POSX, attr );
809
810 attr = new wxGridCellAttr;
811 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
812 m_grid->SetColAttr( COL_POSY, attr );
813 */
814
815 m_addButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
816 m_deleteButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
817 m_refreshButton->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
818
821
822 GetSizer()->SetSizeHints(this);
823 Centre();
824
825 if( aSymbol->IsMulti() )
826 {
827 m_unitFilter->Append( UNITS_ALL );
828
829 for( int ii = 0; ii < aSymbol->GetUnitCount(); ++ii )
830 m_unitFilter->Append( aSymbol->GetUnitReference( ii + 1 ) );
831
832 m_unitFilter->SetSelection( -1 );
833 }
834 else
835 {
836 m_cbFilterByUnit->Show( false );
837 m_unitFilter->Show( false );
838 }
839
841
842 if( !parent->IsSymbolEditable() || parent->IsSymbolAlias() )
843 {
844 m_ButtonsCancel->SetDefault();
845 m_ButtonsOK->SetLabel( _( "Read Only" ) );
846 m_ButtonsOK->Enable( false );
847 }
848
849 m_initialized = true;
850 m_modified = false;
851
852 // Connect Events
853 m_grid->Connect( wxEVT_GRID_COL_SORT,
854 wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
855}
856
857
859{
861 cfg->m_PinTableVisibleColumns = m_grid->GetShownColumnsAsString();
862
863 // Disconnect Events
864 m_grid->Disconnect( wxEVT_GRID_COL_SORT,
865 wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
866
867 // Prevents crash bug in wxGrid's d'tor
869
870 // Delete the GRID_TRICKS.
871 m_grid->PopEventHandler( true );
872
873 // This is our copy of the pins. If they were transferred to the part on an OK, then
874 // m_pins will already be empty.
875 for( SCH_PIN* pin : m_pins )
876 delete pin;
877
878 WINDOW_THAWER thawer( m_editFrame );
879
880 m_editFrame->FocusOnItem( nullptr );
882}
883
884
886{
887 // Make a copy of the pins for editing
888 std::vector<SCH_PIN*> pins = m_symbol->GetPins();
889
890 for( SCH_PIN* pin : pins )
891 m_pins.push_back( new SCH_PIN( *pin ) );
892
893 m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
894
895 if( m_symbol->IsMulti() )
896 m_grid->ShowCol( COL_UNIT );
897 else
898 m_grid->HideCol( COL_UNIT );
899
901 m_grid->ShowCol( COL_DEMORGAN );
902 else
903 m_grid->HideCol( COL_DEMORGAN );
904
906
907 return true;
908}
909
910
912{
914 return false;
915
916 // Delete the part's pins
917 std::vector<SCH_PIN*> pins = m_symbol->GetPins();
918
919 for( SCH_PIN* pin : pins )
921
922 // Transfer our pins to the part
923 for( SCH_PIN* pin : m_pins )
925
926 m_pins.clear();
927
928 return true;
929}
930
931
932void DIALOG_LIB_EDIT_PIN_TABLE::OnColSort( wxGridEvent& aEvent )
933{
934 int sortCol = aEvent.GetCol();
935 bool ascending;
936
937 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
938 // event, and if we ask it will give us pre-event info.
939 if( m_grid->IsSortingBy( sortCol ) )
940 // same column; invert ascending
941 ascending = !m_grid->IsSortOrderAscending();
942 else
943 // different column; start with ascending
944 ascending = true;
945
946 m_dataModel->SortRows( sortCol, ascending );
947}
948
949
950void DIALOG_LIB_EDIT_PIN_TABLE::OnAddRow( wxCommandEvent& event )
951{
953 return;
954
955 SCH_PIN* newPin = new SCH_PIN( this->m_symbol );
956
957 // Copy the settings of the last pin onto the new pin.
958 if( m_pins.size() > 0 )
959 {
960 SCH_PIN* last = m_pins.back();
961
962 newPin->SetOrientation( last->GetOrientation() );
963 newPin->SetType( last->GetType() );
964 newPin->SetShape( last->GetShape() );
965 newPin->SetUnit( last->GetUnit() );
966
967 VECTOR2I pos = last->GetPosition();
968
970
971 if( last->GetOrientation() == PIN_ORIENTATION::PIN_LEFT
972 || last->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
973 {
974 pos.y -= schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
975 }
976 else
977 {
978 pos.x += schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
979 }
980
981 newPin->SetPosition( pos );
982 }
983
984 m_pins.push_back( newPin );
985
986 m_dataModel->AppendRow( m_pins[ m_pins.size() - 1 ] );
987
988 m_grid->MakeCellVisible( m_grid->GetNumberRows() - 1, 1 );
989 m_grid->SetGridCursor( m_grid->GetNumberRows() - 1, 1 );
990
991 m_grid->EnableCellEditControl( true );
992 m_grid->ShowCellEditControl();
993
995}
996
997
999{
1000 m_pins.push_back( pin );
1001 updateSummary();
1002}
1003
1004
1005void DIALOG_LIB_EDIT_PIN_TABLE::OnDeleteRow( wxCommandEvent& event )
1006{
1007 // TODO: handle delete of multiple rows....
1009 return;
1010
1011 if( m_pins.size() == 0 ) // empty table
1012 return;
1013
1014 int curRow = m_grid->GetGridCursorRow();
1015
1016 if( curRow < 0 )
1017 return;
1018
1019 // move the selection first because wx internally will try to reselect the row we deleted in
1020 // out of order events
1021 int nextSelRow = std::max( curRow-1, 0 );
1022 m_grid->GoToCell( nextSelRow, m_grid->GetGridCursorCol() );
1023 m_grid->SetGridCursor( nextSelRow, m_grid->GetGridCursorCol() );
1024 m_grid->SelectRow( nextSelRow );
1025
1026 std::vector<SCH_PIN*> removedRow = m_dataModel->RemoveRow( curRow );
1027
1028 for( SCH_PIN* pin : removedRow )
1029 m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
1030
1031 updateSummary();
1032}
1033
1034
1036{
1037 m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
1038 updateSummary();
1039}
1040
1041
1043{
1044 updateSummary();
1045}
1046
1047
1049{
1050 SCH_PIN* pin = nullptr;
1051
1052 if( event.GetRow() >= 0 && event.GetRow() < m_dataModel->GetNumberRows() )
1053 {
1054 const std::vector<SCH_PIN*>& pins = m_dataModel->GetRowPins( event.GetRow() );
1055
1056 if( pins.size() == 1 && m_editFrame->GetCurSymbol() )
1057 {
1058 for( SCH_PIN* candidate : m_editFrame->GetCurSymbol()->GetPins() )
1059 {
1060 if( candidate->GetNumber() == pins.at( 0 )->GetNumber() )
1061 {
1062 pin = candidate;
1063 break;
1064 }
1065 }
1066 }
1067 }
1068
1069 WINDOW_THAWER thawer( m_editFrame );
1070
1073}
1074
1075
1077{
1078 return m_cbGroup->GetValue();
1079}
1080
1081
1083{
1084 m_cbGroup->SetValue( false );
1085
1086 m_dataModel->RebuildRows( m_pins, false, true );
1087
1088 m_grid->ShowCol( COL_PIN_COUNT );
1089 m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1090
1092}
1093
1094
1096{
1098 return;
1099
1100 m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
1101
1102 if( m_cbGroup->GetValue() )
1103 {
1104 m_grid->ShowCol( COL_PIN_COUNT );
1105 m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1106 }
1107
1109}
1110
1111
1113{
1114 if( event.IsChecked() )
1115 {
1116 m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
1117 }
1118 else
1119 {
1121 m_unitFilter->SetSelection( -1 );
1122 }
1123
1124 OnRebuildRows( event );
1125}
1126
1127
1129{
1130 m_cbFilterByUnit->SetValue( true );
1131 m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
1132
1133 OnRebuildRows( event );
1134}
1135
1136
1138{
1139 // Account for scroll bars
1141
1142 wxGridUpdateLocker deferRepaintsTillLeavingScope;
1143
1144 // The Number and Name columns must be at least wide enough to hold their contents, but
1145 // no less wide than their original widths.
1146 m_grid->AutoSizeColumn( COL_NUMBER );
1147
1148 if( m_grid->GetColSize( COL_NUMBER ) < m_originalColWidths[ COL_NUMBER ] )
1150
1151 m_grid->AutoSizeColumn( COL_NAME );
1152
1153 if( m_grid->GetColSize( COL_NAME ) < m_originalColWidths[ COL_NAME ] )
1154 m_grid->SetColSize( COL_NAME, m_originalColWidths[ COL_NAME ] );
1155
1156 // If the grid is still wider than the columns, then stretch the Number and Name columns
1157 // to fit.
1158 for( int i = 0; i < COL_COUNT; ++i )
1159 width -= m_grid->GetColSize( i );
1160
1161 if( width > 0 )
1162 {
1163 m_grid->SetColSize( COL_NUMBER, m_grid->GetColSize( COL_NUMBER ) + width / 2 );
1164 m_grid->SetColSize( COL_NAME, m_grid->GetColSize( COL_NAME ) + width / 2 );
1165 }
1166}
1167
1168
1169void DIALOG_LIB_EDIT_PIN_TABLE::OnSize( wxSizeEvent& event )
1170{
1171 wxSize new_size = event.GetSize();
1172
1173 if( m_initialized && m_size != new_size )
1174 {
1175 m_size = new_size;
1176
1178 }
1179
1180 // Always propagate for a grid repaint (needed if the height changes, as well as width)
1181 event.Skip();
1182}
1183
1184
1185void DIALOG_LIB_EDIT_PIN_TABLE::OnUpdateUI( wxUpdateUIEvent& event )
1186{
1187 std::bitset<64> columnsShown = m_grid->GetShownColumns();
1188
1189 if( columnsShown != m_columnsShown )
1190 {
1191 m_columnsShown = columnsShown;
1192
1193 if( !m_grid->IsCellEditControlShown() )
1195 }
1196
1197 int firstSelectedRow;
1198 int selectedRowCount;
1199
1200 getSelectedArea( m_grid, &firstSelectedRow, &selectedRowCount );
1201
1202 if( ( selectedRowCount > 1 ) != m_groupSelected->IsEnabled() )
1203 m_groupSelected->Enable( selectedRowCount > 1 );
1204}
1205
1206
1207void DIALOG_LIB_EDIT_PIN_TABLE::OnCancel( wxCommandEvent& event )
1208{
1209 Close();
1210}
1211
1212
1213void DIALOG_LIB_EDIT_PIN_TABLE::OnClose( wxCloseEvent& event )
1214{
1215 // This is a cancel, so commit quietly as we're going to throw the results away anyway.
1217
1218 int retval = wxID_CANCEL;
1219
1220 if( m_dataModel->IsEdited() )
1221 {
1222 if( HandleUnsavedChanges( this, _( "Save changes?" ),
1223 [&]() -> bool
1224 {
1226 {
1227 retval = wxID_OK;
1228 return true;
1229 }
1230
1231 return false;
1232 } ) )
1233 {
1234 if( IsQuasiModal() )
1235 EndQuasiModal( retval );
1236 else
1237 EndDialog( retval );
1238
1239 return;
1240 }
1241 else
1242 {
1243 event.Veto();
1244 return;
1245 }
1246 }
1247
1248 // No change in dialog: we can close it
1249 if( IsQuasiModal() )
1250 EndQuasiModal( retval );
1251 else
1252 EndDialog( retval );
1253
1254 return;
1255}
1256
1257
1259{
1260 PIN_NUMBERS pinNumbers;
1261
1262 for( SCH_PIN* pin : m_pins )
1263 {
1264 if( pin->GetNumber().Length() )
1265 pinNumbers.insert( pin->GetNumber() );
1266 }
1267
1268 m_pin_numbers_summary->SetLabel( pinNumbers.GetSummary() );
1269 m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_pins.size() ) );
1270 m_duplicate_pins->SetLabel( pinNumbers.GetDuplicates() );
1271
1272 Layout();
1273}
constexpr EDA_IU_SCALE schIUScale
Definition: base_units.h:110
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap)
Definition: bitmap.cpp:110
void SetIsSeparator()
Render button as a toolbar separator.
Class DIALOG_LIB_EDIT_PIN_TABLE_BASE.
bool m_modified
true when there are unsaved changes
void OnRebuildRows(wxCommandEvent &event) override
void OnCancel(wxCommandEvent &event) override
void OnUpdateUI(wxUpdateUIEvent &event) override
void OnColSort(wxGridEvent &aEvent)
void OnGroupSelected(wxCommandEvent &event) override
void OnClose(wxCloseEvent &event) override
void OnSize(wxSizeEvent &event) override
PIN_TABLE_DATA_MODEL * m_dataModel
void OnCellSelected(wxGridEvent &event) override
void OnAddRow(wxCommandEvent &event) override
void OnDeleteRow(wxCommandEvent &event) override
void OnCellEdited(wxGridEvent &event) override
DIALOG_LIB_EDIT_PIN_TABLE(SYMBOL_EDIT_FRAME *parent, LIB_SYMBOL *aSymbol)
std::vector< SCH_PIN * > m_pins
void OnFilterCheckBox(wxCommandEvent &event) override
void OnFilterChoice(wxCommandEvent &event) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
bool IsQuasiModal() const
Definition: dialog_shim.h:113
void EndQuasiModal(int retCode)
The base class for create windows for drawing purpose.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:61
Define a library symbol object.
Definition: lib_symbol.h:84
bool IsMulti() const override
Definition: lib_symbol.h:586
std::vector< SCH_PIN * > GetPins(int aUnit, int aBodyStyle) const
Return a list of pin object pointers from the draw item list.
Definition: lib_symbol.cpp:851
static wxString LetterSubReference(int aUnit, int aFirstId)
Definition: lib_symbol.cpp:476
void RemoveDrawItem(SCH_ITEM *aItem)
Remove draw aItem from list.
Definition: lib_symbol.cpp:812
wxString GetUnitReference(int aUnit) override
Return an identifier for aUnit for symbols with units.
Definition: lib_symbol.cpp:277
int GetUnitCount() const override
void AddDrawItem(SCH_ITEM *aItem, bool aSort=true)
Add a new draw aItem to the draw object list and sort according to aSort.
Definition: lib_symbol.cpp:837
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition: pgm_base.h:125
wxString GetDuplicates() const
Gets a formatted string of all the pins that have duplicate numbers.
static int Compare(const wxString &lhs, const wxString &rhs)
void insert(value_type const &v)
Definition: pin_numbers.h:63
wxString GetSummary() const
Definition: pin_numbers.cpp:70
static bool compare(const std::vector< SCH_PIN * > &lhs, const std::vector< SCH_PIN * > &rhs, int sortCol, bool ascending, EDA_DRAW_FRAME *parentFrame)
PIN_TABLE_DATA_MODEL(SYMBOL_EDIT_FRAME *aFrame, DIALOG_LIB_EDIT_PIN_TABLE *aPinTable, LIB_SYMBOL *aSymbol)
void RebuildRows(const std::vector< SCH_PIN * > &aPins, bool groupByName, bool groupBySelection)
static int findRow(const std::vector< std::vector< SCH_PIN * > > &aRowSet, const wxString &aName)
std::map< std::pair< std::vector< SCH_PIN * >, int >, wxString > m_evalOriginal
static wxString StringFromBool(bool aValue)
std::vector< std::vector< SCH_PIN * > > m_rows
bool IsEmptyCell(int row, int col) override
DIALOG_LIB_EDIT_PIN_TABLE * m_pinTable
std::vector< SCH_PIN * > GetRowPins(int aRow)
static wxString GetValue(const std::vector< SCH_PIN * > &pins, int aCol, EDA_DRAW_FRAME *aParentFrame)
std::unique_ptr< NUMERIC_EVALUATOR > m_eval
void SetValue(int aRow, int aCol, const wxString &aValue) override
wxString GetValue(int aRow, int aCol) override
void SortRows(int aSortCol, bool ascending)
void onUnitsChanged(wxCommandEvent &aEvent)
static bool BoolFromString(wxString aValue)
wxString GetColLabelValue(int aCol) override
std::vector< SCH_PIN * > RemoveRow(int aRow)
void SortPins(std::vector< SCH_PIN * > &aRow)
SCH_DRAW_PANEL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
int GetUnit() const
Definition: sch_item.h:230
virtual void SetUnit(int aUnit)
Definition: sch_item.h:229
void SetNumber(const wxString &aNumber)
Definition: sch_pin.cpp:527
void SetOrientation(PIN_ORIENTATION aOrientation)
Definition: sch_pin.h:85
void SetName(const wxString &aName)
Definition: sch_pin.cpp:406
void SetPosition(const VECTOR2I &aPos) override
Definition: sch_pin.h:198
const wxString & GetName() const
Definition: sch_pin.cpp:388
PIN_ORIENTATION GetOrientation() const
Definition: sch_pin.cpp:260
void SetShape(GRAPHIC_PINSHAPE aShape)
Definition: sch_pin.h:88
VECTOR2I GetPosition() const override
Definition: sch_pin.cpp:252
void SetType(ELECTRICAL_PINTYPE aType)
Definition: sch_pin.cpp:329
GRAPHIC_PINSHAPE GetShape() const
Definition: sch_pin.cpp:274
ELECTRICAL_PINTYPE GetType() const
Definition: sch_pin.cpp:309
T * GetAppSettings(const wxString &aFilename)
Return a handle to the a given settings by type.
void SetBitmap(const wxBitmapBundle &aBmp)
The symbol library editor main window.
void FocusOnItem(SCH_ITEM *aItem)
bool IsSymbolAlias() const
Return true if aLibId is an alias for the editor screen symbol.
bool GetShowDeMorgan() const
SYMBOL_EDITOR_SETTINGS * GetSettings() const
bool IsSymbolEditable() const
Test if a symbol is loaded and can be edited.
LIB_SYMBOL * GetCurSymbol() const
Return the current symbol being edited or NULL if none selected.
EDA_UNITS GetUserUnits() const
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.
int ValueFromString(const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aTextValue in aUnits to internal units used by the frame.
void ShowHideColumns(const wxString &shownColumns)
Show/hide the grid columns based on a tokenized string of shown column indexes.
Definition: wx_grid.cpp:496
void SetTable(wxGridTableBase *table, bool aTakeOwnership=false)
Hide wxGrid's SetTable() method with one which doesn't mess up the grid column widths when setting th...
Definition: wx_grid.cpp:277
void DestroyTable(wxGridTableBase *aTable)
Work-around for a bug in wxGrid which crashes when deleting the table if the cell edit control was no...
Definition: wx_grid.cpp:451
wxString GetShownColumnsAsString()
Get a tokenized string containing the shown column indexes.
Definition: wx_grid.cpp:466
std::bitset< 64 > GetShownColumns()
Definition: wx_grid.cpp:485
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:646
bool HandleUnsavedChanges(wxWindow *aParent, const wxString &aMessage, const std::function< bool()> &aSaveFunction)
Display a dialog with Save, Cancel and Discard Changes buttons.
Definition: confirm.cpp:130
This file is part of the common library.
#define UNITS_ALL
void getSelectedArea(WX_GRID *aGrid, int *aRowStart, int *aRowCount)
#define DEMORGAN_ALL
#define DEMORGAN_ALT
#define DEMORGAN_STD
#define _(s)
#define CANDIDATE
flag indicating that the structure is connected
static std::map< int, wxString > shapeNames
wxSize GetUnobscuredSize(const wxWindow *aWindow)
Tries to determine the size of the viewport of a scrollable widget (wxDataViewCtrl,...
Definition: wxgtk/ui.cpp:252
PGM_BASE & Pgm()
The global program "get" accessor.
Definition: pgm_base.cpp:1073
see class PGM_BASE
const std::vector< BITMAPS > & PinTypeIcons()
Definition: pin_type.cpp:162
const wxArrayString & PinTypeNames()
Definition: pin_type.cpp:153
int PinOrientationIndex(PIN_ORIENTATION code)
Definition: pin_type.cpp:70
const wxArrayString & PinShapeNames()
Definition: pin_type.cpp:171
const std::vector< BITMAPS > & PinShapeIcons()
Definition: pin_type.cpp:180
const wxArrayString & PinOrientationNames()
Definition: pin_type.cpp:189
PIN_ORIENTATION PinOrientationCode(size_t index)
Definition: pin_type.cpp:63
const std::vector< BITMAPS > & PinOrientationIcons()
Definition: pin_type.cpp:198
ELECTRICAL_PINTYPE
The symbol library pin object electrical types used in ERC tests.
Definition: pin_type.h:36
GRAPHIC_PINSHAPE
Definition: pin_type.h:57
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
constexpr int MilsToIU(int mils) const
Definition: base_units.h:93
VECTOR3I res
#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:695