KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_lib_symbol_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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <pgm_base.h>
27#include <eeschema_settings.h>
28#include <bitmaps.h>
29#include <confirm.h>
31#include <kiway.h>
32#include <symbol_edit_frame.h>
34#include <math/util.h> // for KiROUND
35#include <sch_symbol.h>
36#include <kiplatform/ui.h>
38#include <widgets/wx_grid.h>
40#include <string_utils.h>
41#include <project_sch.h>
42#include <refdes_utils.h>
43#include <dialog_sim_model.h>
44
49
50#include <wx/clipbrd.h>
51#include <wx/msgdlg.h>
52
53
57
58
60 LIB_SYMBOL* aLibEntry ) :
62 m_Parent( aParent ),
63 m_libEntry( aLibEntry ),
65 m_delayedFocusCtrl( nullptr ),
66 m_delayedFocusGrid( nullptr ),
71{
73 m_NoteBook->AddPage( m_embeddedFiles, _( "Embedded Files" ) );
74
75 m_fields = new FIELDS_GRID_TABLE( this, aParent, m_grid, m_libEntry,
76 { m_embeddedFiles->GetLocalFiles() } );
77 m_grid->SetTable( m_fields );
78 m_grid->PushEventHandler( new FIELDS_GRID_TRICKS( m_grid, this, { m_embeddedFiles->GetLocalFiles() },
79 [&]( wxCommandEvent& aEvent )
80 {
81 OnAddField( aEvent );
82 } ) );
83 m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
84
85 // Load the FIELDS_GRID_TABLE -- ensure we are calling the overloaded push_back method
86 std::vector<SCH_FIELD> fields;
87 m_libEntry->CopyFields( fields );
88
89 for( const SCH_FIELD& f : fields )
90 m_fields->push_back( f );
91
92 if( m_libEntry->IsDerived() )
93 {
94 if( LIB_SYMBOL_SPTR parent = m_libEntry->GetParent().lock() )
95 {
96 std::vector<SCH_FIELD*> parentFields;
97 parent->GetFields( parentFields );
98
99 for( size_t ii = 0; ii < parentFields.size(); ++ii )
100 {
101 SCH_FIELD* pf = parentFields[ii];
102 bool found = false;
103
104 if( pf->IsMandatory() )
105 continue; // Don't inherit mandatory fields
106
107 for( size_t jj = 0; jj < m_fields->size(); ++jj )
108 {
109 SCH_FIELD& f = m_fields->at( jj );
110
111 if( f.IsMandatory() )
112 continue; // Don't inherit mandatory fields
113
114 if( f.GetCanonicalName() == pf->GetCanonicalName() )
115 {
116 m_fields->SetFieldInherited( jj, *pf );
117 found = true;
118 break;
119 }
120 }
121
122 if( !found )
123 m_fields->AddInheritedField( *pf );
124 }
125 }
126 }
127
128 // Show/hide columns according to the user's preference
129 SYMBOL_EDITOR_SETTINGS* cfg = m_Parent->GetSettings();
130 m_grid->ShowHideColumns( cfg->m_EditSymbolVisibleColumns );
131
133
134 m_unitNamesGrid->PushEventHandler( new GRID_TRICKS( m_unitNamesGrid ) );
135 m_unitNamesGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
136
138 [this]( wxCommandEvent& aEvent )
139 {
140 OnAddBodyStyle( aEvent );
141 } ) );
142 m_bodyStyleNamesGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
143
144 // Configure button logos
149
154
158
161
163
164 if( aParent->IsSymbolFromLegacyLibrary() && !aParent->IsSymbolFromSchematic() )
165 {
166 m_stdSizerButtonCancel->SetDefault();
167 m_stdSizerButtonOK->SetLabel( _( "Read Only" ) );
168 m_stdSizerButtonOK->Enable( false );
169 }
170
171 // wxFormBuilder doesn't include this event...
172 m_grid->Bind( wxEVT_GRID_CELL_CHANGING, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanging, this );
173 m_grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanged, this );
174 m_grid->GetGridWindow()->Bind( wxEVT_MOTION, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridMotion, this );
175
176
177 // Forward the delete button to the tricks
178 m_deleteFilterButton->Bind( wxEVT_BUTTON,
179 [&]( wxCommandEvent& aEvent )
180 {
181 wxCommandEvent cmdEvent( EDA_EVT_LISTBOX_DELETE );
182 m_fpFilterTricks->ProcessEvent( cmdEvent );
183 } );
184
185 // When the filter tricks modifies something, update ourselves
186 m_FootprintFilterListBox->Bind( EDA_EVT_LISTBOX_CHANGED,
187 [&]( wxCommandEvent& aEvent )
188 {
189 OnModify();
190 } );
191
193 {
196 {
197 resetSize();
198 }
199 }
200
203
204 m_grid->GetParent()->Layout();
205 syncControlStates( m_libEntry->IsDerived() );
206 Layout();
207
209}
210
211
213{
214 m_lastOpenedPage = m_NoteBook->GetSelection( );
215
216 if( SYMBOL_EDITOR_SETTINGS* cfg = m_Parent->GetSettings() )
217 cfg->m_EditSymbolVisibleColumns = m_grid->GetShownColumnsAsString();
218
219 // Prevents crash bug in wxGrid's d'tor
220 m_grid->DestroyTable( m_fields );
221
222 m_grid->Unbind( wxEVT_GRID_CELL_CHANGING, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanging, this );
223 m_grid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanged, this );
224 m_grid->GetGridWindow()->Unbind( wxEVT_MOTION, &DIALOG_LIB_SYMBOL_PROPERTIES::OnGridMotion, this );
225
226 // Delete the GRID_TRICKS.
227 m_grid->PopEventHandler( true );
228 m_unitNamesGrid->PopEventHandler( true );
229 m_bodyStyleNamesGrid->PopEventHandler( true );
230}
231
232
234{
235 if( !wxDialog::TransferDataToWindow() )
236 return false;
237
238 std::set<wxString> defined;
239
240 for( SCH_FIELD& field : *m_fields )
241 defined.insert( field.GetName() );
242
243 // Add in any template fieldnames not yet defined:
244 // Read global fieldname templates
246 {
247 TEMPLATES templateMgr;
248
249 if( !cfg->m_Drawing.field_names.IsEmpty() )
250 templateMgr.AddTemplateFieldNames( cfg->m_Drawing.field_names );
251
252 for( const TEMPLATE_FIELDNAME& templateFieldname : templateMgr.GetTemplateFieldNames() )
253 {
254 if( defined.count( templateFieldname.m_Name ) <= 0 )
255 {
256 SCH_FIELD field( m_libEntry, FIELD_T::USER, templateFieldname.m_Name );
257 field.SetVisible( templateFieldname.m_Visible );
258 m_fields->push_back( field );
259 m_addedTemplateFields.insert( templateFieldname.m_Name );
260 }
261 }
262 }
263
264 // The Y axis for components in library file is from bottom to top while the screen axis is top
265 // to bottom.However it is nowhandled by the lib file parser/writer.
266
267 // notify the grid
268 wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_fields->GetNumberRows() );
269 m_grid->ProcessTableMessage( msg );
270
271 m_SymbolNameCtrl->ChangeValue( UnescapeString( m_libEntry->GetName() ) );
272
273 m_KeywordCtrl->ChangeValue( m_libEntry->GetKeyWords() );
274 m_unitSpinCtrl->SetValue( m_libEntry->GetUnitCount() );
275 m_OptionPartsInterchangeable->SetValue( !m_libEntry->UnitsLocked() || m_libEntry->GetUnitCount() == 1 );
276
278
279 for( int unit = 0; unit < m_libEntry->GetUnitCount(); unit++ )
280 {
281 if( m_libEntry->GetUnitDisplayNames().contains( unit + 1 ) )
282 m_unitNamesGrid->SetCellValue( unit, 1, m_libEntry->GetUnitDisplayNames().at( unit + 1 ) );
283 }
284
285 if( m_libEntry->HasDeMorganBodyStyles() )
286 {
287 m_radioDeMorgan->SetValue( true );
288 }
289 else if( m_libEntry->IsMultiBodyStyle() )
290 {
291 m_radioCustom->SetValue( true );
292
293 for( const wxString& name : m_libEntry->GetBodyStyleNames() )
294 {
295 int row = m_bodyStyleNamesGrid->GetNumberRows();
296 m_bodyStyleNamesGrid->AppendRows( 1 );
297 m_bodyStyleNamesGrid->SetCellValue( row, 0, name );
298 }
299 }
300 else
301 {
302 m_radioSingle->SetValue( true );
303 }
304
305 m_OptionPower->SetValue( m_libEntry->IsPower() );
306 m_OptionLocalPower->SetValue( m_libEntry->IsLocalPower() );
307
308 if( m_libEntry->IsPower() )
309 {
310 m_spiceFieldsButton->Hide();
311 m_OptionLocalPower->Enable();
312 }
313 else
314 {
315 m_OptionLocalPower->Enable( false );
316 }
317
318 m_excludeFromSimCheckBox->SetValue( m_libEntry->GetExcludedFromSim() );
319 m_excludeFromBomCheckBox->SetValue( m_libEntry->GetExcludedFromBOM() );
320 m_excludeFromBoardCheckBox->SetValue( m_libEntry->GetExcludedFromBoard() );
321
322 m_ShowPinNumButt->SetValue( m_libEntry->GetShowPinNumbers() );
323 m_ShowPinNameButt->SetValue( m_libEntry->GetShowPinNames() );
324 m_PinsNameInsideButt->SetValue( m_libEntry->GetPinNameOffset() != 0 );
325 m_pinNameOffset.ChangeValue( m_libEntry->GetPinNameOffset() );
326
327 wxArrayString tmp = m_libEntry->GetFPFilters();
328 m_FootprintFilterListBox->Append( tmp );
329
330 m_cbDuplicatePinsAreJumpers->SetValue( m_libEntry->GetDuplicatePinNumbersAreJumpers() );
331 m_btnCreateJumperPinGroup->Disable();
332 m_btnRemoveJumperPinGroup->Disable();
333
334 std::set<wxString> availablePins;
335
336 for( const SCH_PIN* pin : m_libEntry->GetPins() )
337 availablePins.insert( pin->GetNumber() );
338
339 for( const std::set<wxString>& group : m_libEntry->JumperPinGroups() )
340 {
341 wxString groupTxt;
342 size_t i = 0;
343
344 for( const wxString& pinNumber : group )
345 {
346 availablePins.erase( pinNumber );
347 groupTxt << pinNumber;
348
349 if( ++i < group.size() )
350 groupTxt << ", ";
351 }
352
353 m_listJumperPinGroups->Append( groupTxt );
354 }
355
356 for( const wxString& pin : availablePins )
357 m_listAvailablePins->AppendString( pin );
358
359 // Populate the list of root parts for inherited objects.
360 if( m_libEntry->IsDerived() )
361 {
362 wxArrayString symbolNames;
363 wxString libName = m_Parent->GetCurLib();
364
365 // Someone forgot to set the current library in the editor frame window.
366 wxCHECK( !libName.empty(), false );
367
368 m_Parent->GetLibManager().GetSymbolNames( libName, symbolNames );
369
370 // Sort the list of symbols for easier search
371 symbolNames.Sort(
372 []( const wxString& a, const wxString& b ) -> int
373 {
374 return StrNumCmp( a, b, true );
375 } );
376
377 // Do allow an inherited symbol to be derived from itself.
378 if( symbolNames.Index( m_libEntry->GetName() ) != wxNOT_FOUND )
379 symbolNames.Remove( m_libEntry->GetName() );
380
381 m_inheritanceSelectCombo->Append( symbolNames );
382
383 if( LIB_SYMBOL_SPTR rootSymbol = m_libEntry->GetParent().lock() )
384 {
385 wxString parentName = UnescapeString( rootSymbol->GetName() );
386 int selection = m_inheritanceSelectCombo->FindString( parentName );
387
388 if( selection == wxNOT_FOUND )
389 return false;
390
391 m_inheritanceSelectCombo->SetSelection( selection );
392 }
393
395 }
396
397 m_NoteBook->SetSelection( (unsigned) m_lastOpenedPage );
398
399 m_embeddedFiles->TransferDataToWindow();
400
401 return true;
402}
403
404
406{
407 if( !m_grid->CommitPendingChanges() )
408 return false;
409
410 // Symbol reference can be empty because it inherits from the parent symbol.
411 if( m_libEntry->IsRoot() )
412 {
413 SCH_FIELD* field = m_fields->GetField( FIELD_T::REFERENCE );
414
415 if( UTIL::GetRefDesPrefix( field->GetText() ).IsEmpty() )
416 {
417 if( m_NoteBook->GetSelection() != 0 )
418 m_NoteBook->SetSelection( 0 );
419
420 m_delayedErrorMessage = _( "References must start with a letter." );
425
426 return false;
427 }
428 }
429
430 // Check for missing field names.
431 for( int ii = 0; ii < (int) m_fields->size(); ++ii )
432 {
433 SCH_FIELD& field = m_fields->at( ii );
434
435 if( field.IsMandatory() )
436 continue;
437
438 wxString fieldName = field.GetName( false );
439
440 if( fieldName.IsEmpty() && !field.GetText().IsEmpty() )
441 {
442 if( m_NoteBook->GetSelection() != 0 )
443 m_NoteBook->SetSelection( 0 );
444
445 m_delayedErrorMessage = _( "Fields must have a name." );
450
451 return false;
452 }
453 }
454
455 // Verify that the parent name is set if the symbol is inherited
456 if( m_libEntry->IsDerived() )
457 {
458 wxString parentName = m_inheritanceSelectCombo->GetValue();
459
460 if( parentName.IsEmpty() )
461 {
462 m_delayedErrorMessage = _( "Derived symbol must have a parent selected" );
463 return false;
464 }
465 }
466
467 /*
468 * Confirm destructive actions.
469 */
470
471 if( m_unitSpinCtrl->GetValue() < m_libEntry->GetUnitCount() )
472 {
473 if( !IsOK( this, _( "Delete extra units from symbol?" ) ) )
474 return false;
475 }
476
477 int bodyStyleCount = 0;
478
479 if( m_radioSingle->GetValue() )
480 {
481 bodyStyleCount = 1;
482 }
483 if( m_radioDeMorgan->GetValue() )
484 {
485 bodyStyleCount = 2;
486 }
487 else if( m_radioCustom->GetValue() )
488 {
489 for( int ii = 0; ii < m_bodyStyleNamesGrid->GetNumberRows(); ++ii )
490 {
491 if( !m_bodyStyleNamesGrid->GetCellValue( ii, 0 ).IsEmpty() )
492 bodyStyleCount++;
493 }
494 }
495
496 if( bodyStyleCount == 0 )
497 {
498 m_delayedErrorMessage = _( "Symbol must have at least 1 body style" );
499 return false;
500 }
501
502 if( bodyStyleCount < m_libEntry->GetBodyStyleCount() )
503 {
504 if( !IsOK( this, _( "Delete extra body styles from symbol?" ) ) )
505 return false;
506 }
507
508 return true;
509}
510
511
513{
514 if( !wxDialog::TransferDataFromWindow() )
515 return false;
516
517 if( !m_grid->CommitPendingChanges() )
518 return false;
519
520 if( !m_unitNamesGrid->CommitPendingChanges() )
521 return false;
522
523 if( !m_bodyStyleNamesGrid->CommitPendingChanges() )
524 return false;
525
526 wxString newName = EscapeString( m_SymbolNameCtrl->GetValue(), CTX_LIBID );
527 wxString oldName = m_libEntry->GetName();
528
529 if( newName.IsEmpty() )
530 {
531 wxMessageBox( _( "Symbol must have a name." ) );
532 return false;
533 }
534
536
537 if( oldName != newName )
538 {
539 wxString libName = m_Parent->GetCurLib();
540
541 if( m_Parent->GetLibManager().SymbolNameInUse( newName, libName ) )
542 {
543 wxString msg;
544
545 msg.Printf( _( "Symbol name '%s' already in use in library '%s'." ),
546 UnescapeString( newName ),
547 libName );
548 DisplayErrorMessage( this, msg );
549 return false;
550 }
551
552 opType = UNDO_REDO::LIB_RENAME;
553 }
554
555 m_Parent->SaveCopyInUndoList( _( "Edit Symbol Properties" ), m_libEntry, opType );
556
557 // The Y axis for components in lib is from bottom to top while the screen axis is top
558 // to bottom: we must change the y coord sign when writing back to the library
559 std::vector<SCH_FIELD> fieldsToSave;
560 int ordinal = 42; // Arbitrarily larger than any mandatory FIELD_T ids.
561
562 for( size_t ii = 0; ii < m_fields->size(); ++ii )
563 {
564 SCH_FIELD& field = m_fields->at( ii );
565
566 if( !field.IsMandatory() )
567 field.SetOrdinal( ordinal++ );
568
569 wxString fieldName = field.GetCanonicalName();
570
571 if( m_fields->IsInherited( ii ) && field == m_fields->ParentField( ii ) )
572 continue; // Skip inherited fields
573
574 if( field.GetText().IsEmpty() )
575 {
576 if( fieldName.IsEmpty() || m_addedTemplateFields.contains( fieldName ) )
577 continue; // Skip empty fields that are not mandatory or template fields
578 }
579 else if( fieldName.IsEmpty() )
580 {
581 field.SetName( _( "untitled" ) ); // Set a default name for unnamed fields
582 }
583
584 fieldsToSave.push_back( field );
585 }
586
587 m_libEntry->SetFields( fieldsToSave );
588
589 // Update the parent for inherited symbols
590 if( m_libEntry->IsDerived() )
591 {
592 wxString parentName = EscapeString( m_inheritanceSelectCombo->GetValue(), CTX_LIBID );
593
594 // The parentName was verified to be non-empty in the Validator
595 wxString libName = m_Parent->GetCurLib();
596
597 // Get the parent from the libManager based on the name set in the inheritance combo box.
598 LIB_SYMBOL* newParent = m_Parent->GetLibManager().GetSymbol( parentName, libName );
599
600 // Verify that the requested parent exists
601 wxCHECK( newParent, false );
602
603 m_libEntry->SetParent( newParent );
604 }
605
606 m_libEntry->SetName( newName );
607 m_libEntry->SetKeyWords( m_KeywordCtrl->GetValue() );
608 m_libEntry->SetUnitCount( m_unitSpinCtrl->GetValue(), true );
609 m_libEntry->LockUnits( m_libEntry->GetUnitCount() > 1 && !m_OptionPartsInterchangeable->GetValue() );
610
611 m_libEntry->GetUnitDisplayNames().clear();
612
613 for( int row = 0; row < m_unitNamesGrid->GetNumberRows(); row++ )
614 {
615 if( !m_unitNamesGrid->GetCellValue( row, 1 ).IsEmpty() )
616 m_libEntry->GetUnitDisplayNames()[row+1] = m_unitNamesGrid->GetCellValue( row, 1 );
617 }
618
619 if( m_radioSingle->GetValue() )
620 {
621 m_libEntry->SetHasDeMorganBodyStyles( false );
622 m_libEntry->SetBodyStyleCount( 1, false, false );
623 m_libEntry->SetBodyStyleNames( {} );
624 }
625 else if( m_radioDeMorgan->GetValue() )
626 {
627 m_libEntry->SetHasDeMorganBodyStyles( true );
628 m_libEntry->SetBodyStyleCount( 2, false, true );
629 m_libEntry->SetBodyStyleNames( {} );
630 }
631 else
632 {
633 std::vector<wxString> bodyStyleNames;
634
635 for( int row = 0; row < m_bodyStyleNamesGrid->GetNumberRows(); ++row )
636 {
637 if( !m_bodyStyleNamesGrid->GetCellValue( row, 0 ).IsEmpty() )
638 bodyStyleNames.push_back( m_bodyStyleNamesGrid->GetCellValue( row, 0 ) );
639 }
640
641 m_libEntry->SetHasDeMorganBodyStyles( false );
642 m_libEntry->SetBodyStyleCount( bodyStyleNames.size(), true, true );
643 m_libEntry->SetBodyStyleNames( bodyStyleNames );
644 }
645
646 if( m_OptionPower->GetValue() )
647 {
648 if( m_OptionLocalPower->GetValue() )
649 m_libEntry->SetLocalPower();
650 else
651 m_libEntry->SetGlobalPower();
652
653 // Power symbols must have value matching name for now
654 m_libEntry->GetValueField().SetText( newName );
655 }
656 else
657 {
658 m_libEntry->SetNormal();
659 }
660
661 m_libEntry->SetExcludedFromSim( m_excludeFromSimCheckBox->GetValue() );
662 m_libEntry->SetExcludedFromBOM( m_excludeFromBomCheckBox->GetValue() );
663 m_libEntry->SetExcludedFromBoard( m_excludeFromBoardCheckBox->GetValue() );
664
665 m_libEntry->SetShowPinNumbers( m_ShowPinNumButt->GetValue() );
666 m_libEntry->SetShowPinNames( m_ShowPinNameButt->GetValue() );
667
668 if( m_PinsNameInsideButt->GetValue() )
669 {
670 int offset = KiROUND( (double) m_pinNameOffset.GetValue() );
671
672 // We interpret an offset of 0 as "outside", so make sure it's non-zero
673 m_libEntry->SetPinNameOffset( offset == 0 ? 20 : offset );
674 }
675 else
676 {
677 m_libEntry->SetPinNameOffset( 0 ); // pin text outside the body (name is on the pin)
678 }
679
680 m_libEntry->SetFPFilters( m_FootprintFilterListBox->GetStrings());
681
682 m_libEntry->SetDuplicatePinNumbersAreJumpers( m_cbDuplicatePinsAreJumpers->GetValue() );
683
684 std::vector<std::set<wxString>>& jumpers = m_libEntry->JumperPinGroups();
685 jumpers.clear();
686
687 for( unsigned i = 0; i < m_listJumperPinGroups->GetCount(); ++i )
688 {
689 wxStringTokenizer tokenizer( m_listJumperPinGroups->GetString( i ), ", \t\r\n", wxTOKEN_STRTOK );
690 std::set<wxString>& group = jumpers.emplace_back();
691
692 while( tokenizer.HasMoreTokens() )
693 {
694 if( wxString token = tokenizer.GetNextToken(); !token.IsEmpty() )
695 group.insert( token );
696 }
697 }
698
699 m_Parent->UpdateAfterSymbolProperties( &oldName );
700
701 return true;
702}
703
704
705void DIALOG_LIB_SYMBOL_PROPERTIES::OnBodyStyle( wxCommandEvent& event )
706{
707 m_bodyStyleNamesGrid->Enable( m_radioCustom->GetValue() );
708
709 m_bpAddBodyStyle->Enable( m_radioCustom->GetValue() );
710 m_bpMoveUpBodyStyle->Enable( m_radioCustom->GetValue() );
711 m_bpMoveDownBodyStyle->Enable( m_radioCustom->GetValue() );
712 m_bpDeleteBodyStyle->Enable( m_radioCustom->GetValue() );
713}
714
715
717{
718 aEvent.Skip();
719
720 wxPoint pos = aEvent.GetPosition();
721 wxPoint unscolled_pos = m_grid->CalcUnscrolledPosition( pos );
722 int row = m_grid->YToRow( unscolled_pos.y );
723 int col = m_grid->XToCol( unscolled_pos.x );
724
725 if( row == wxNOT_FOUND || col == wxNOT_FOUND || !m_fields->IsInherited( row ) )
726 {
727 m_grid->SetToolTip( "" );
728 return;
729 }
730
731 m_grid->SetToolTip( wxString::Format( _( "This field is inherited from '%s'." ),
732 m_fields->ParentField( row ).GetName() ) );
733}
734
735
737{
738 wxGridCellEditor* editor = m_grid->GetCellEditor( event.GetRow(), event.GetCol() );
739 wxControl* control = editor->GetControl();
740
741 if( control && control->GetValidator() && !control->GetValidator()->Validate( control ) )
742 {
743 event.Veto();
744
746 m_delayedFocusRow = event.GetRow();
747 m_delayedFocusColumn = event.GetCol();
749 }
750 else if( event.GetCol() == FDC_NAME )
751 {
752 wxString newName = event.GetString();
753
754 for( int i = 0; i < m_grid->GetNumberRows(); ++i )
755 {
756 if( i == event.GetRow() )
757 continue;
758
759 if( newName.CmpNoCase( m_grid->GetCellValue( i, FDC_NAME ) ) == 0 )
760 {
761 DisplayError( this, wxString::Format( _( "The name '%s' is already in use." ), newName ) );
762 event.Veto();
763 m_delayedFocusRow = event.GetRow();
764 m_delayedFocusColumn = event.GetCol();
765 }
766 }
767 }
768
769 editor->DecRef();
770}
771
772
774{
775 m_grid->ForceRefresh();
776 OnModify();
777}
778
779
781{
782 if( m_OptionPower->IsChecked() )
783 m_grid->SetCellValue( m_fields->GetFieldRow( FIELD_T::VALUE ), FDC_VALUE, m_SymbolNameCtrl->GetValue() );
784
785 OnModify();
786}
787
788
790{
791 if( !m_delayedFocusCtrl )
792 {
793 // If the validation fails and we throw up a dialog then GTK will give us another
794 // KillFocus event and we end up in infinite recursion. So we use m_delayedFocusCtrl
795 // as a re-entrancy block and then clear it again if validation passes.
798
799 if( m_SymbolNameCtrl->GetValidator()->Validate( m_SymbolNameCtrl ) )
800 {
801 m_delayedFocusCtrl = nullptr;
803 }
804 }
805
806 event.Skip();
807}
808
809
810void DIALOG_LIB_SYMBOL_PROPERTIES::OnAddField( wxCommandEvent& event )
811{
812 m_grid->OnAddRow(
813 [&]() -> std::pair<int, int>
814 {
815 SYMBOL_EDITOR_SETTINGS* settings = m_Parent->GetSettings();
817
818 newField.SetTextSize( VECTOR2I( schIUScale.MilsToIU( settings->m_Defaults.text_size ),
819 schIUScale.MilsToIU( settings->m_Defaults.text_size ) ) );
820 newField.SetVisible( false );
821
822 m_fields->push_back( newField );
823
824 // notify the grid
825 wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
826 m_grid->ProcessTableMessage( msg );
827 OnModify();
828
829 return { m_fields->size() - 1, FDC_NAME };
830 } );
831}
832
833
835{
836 m_grid->OnDeleteRows(
837 [&]( int row )
838 {
839 if( row < m_fields->GetMandatoryRowCount() )
840 {
841 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
842 m_fields->GetMandatoryRowCount() ) );
843 return false;
844 }
845
846 return true;
847 },
848 [&]( int row )
849 {
850 if( !m_fields->EraseRow( row ) )
851 return;
852
853 // notify the grid
854 wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
855 m_grid->ProcessTableMessage( msg );
856 } );
857
858 OnModify();
859}
860
861
862void DIALOG_LIB_SYMBOL_PROPERTIES::OnMoveUp( wxCommandEvent& event )
863{
864 m_grid->OnMoveRowUp(
865 [&]( int row )
866 {
867 return row > m_fields->GetMandatoryRowCount();
868 },
869 [&]( int row )
870 {
871 m_fields->SwapRows( row, row - 1 );
872 m_grid->ForceRefresh();
873 OnModify();
874 } );
875}
876
877
878void DIALOG_LIB_SYMBOL_PROPERTIES::OnMoveDown( wxCommandEvent& event )
879{
880 m_grid->OnMoveRowDown(
881 [&]( int row )
882 {
883 return row >= m_fields->GetMandatoryRowCount();
884 },
885 [&]( int row )
886 {
887 m_fields->SwapRows( row, row + 1 );
888 m_grid->ForceRefresh();
889 OnModify();
890 } );
891}
892
893
895{
896 m_bodyStyleNamesGrid->OnAddRow(
897 [&]() -> std::pair<int, int>
898 {
899 m_bodyStyleNamesGrid->AppendRows( 1 );
900 OnModify();
901
902 return { m_bodyStyleNamesGrid->GetNumberRows() - 1, 0 };
903 } );
904}
905
906
908{
909 m_bodyStyleNamesGrid->OnDeleteRows(
910 [&]( int row )
911 {
912 m_bodyStyleNamesGrid->DeleteRows( row );
913 } );
914
915 OnModify();
916}
917
918
920{
921 m_bodyStyleNamesGrid->OnMoveRowUp(
922 [&]( int row )
923 {
924 m_bodyStyleNamesGrid->SwapRows( row, row - 1 );
925 OnModify();
926 } );
927}
928
929
931{
932 m_bodyStyleNamesGrid->OnMoveRowDown(
933 [&]( int row )
934 {
935 m_bodyStyleNamesGrid->SwapRows( row, row + 1 );
936 OnModify();
937 } );
938}
939
940
942{
943 if( !m_grid->CommitPendingChanges() )
944 return;
945
946 m_grid->ClearSelection();
947
948 std::vector<SCH_FIELD> fields;
949
950 for( const SCH_FIELD& field : *m_fields )
951 fields.emplace_back( field );
952
953 DIALOG_SIM_MODEL dialog( this, m_parentFrame, *m_libEntry, fields );
954
955 if( dialog.ShowModal() != wxID_OK )
956 return;
957
958 // Add in any new fields
959 for( const SCH_FIELD& editedField : fields )
960 {
961 bool found = false;
962
963 for( SCH_FIELD& existingField : *m_fields )
964 {
965 if( existingField.GetName() == editedField.GetName() )
966 {
967 found = true;
968 existingField.SetText( editedField.GetText() );
969 break;
970 }
971 }
972
973 if( !found )
974 {
975 m_fields->emplace_back( editedField );
976 wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
977 m_grid->ProcessTableMessage( msg );
978 }
979 }
980
981 // Remove any deleted fields
982 for( int ii = (int) m_fields->size() - 1; ii >= 0; --ii )
983 {
984 SCH_FIELD& existingField = m_fields->at( ii );
985 bool found = false;
986
987 for( SCH_FIELD& editedField : fields )
988 {
989 if( editedField.GetName() == existingField.GetName() )
990 {
991 found = true;
992 break;
993 }
994 }
995
996 if( !found )
997 {
998 m_grid->ClearSelection();
999 m_fields->erase( m_fields->begin() + ii );
1000
1001 wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, ii, 1 );
1002 m_grid->ProcessTableMessage( msg );
1003 }
1004 }
1005
1006 OnModify();
1007 m_grid->ForceRefresh();
1008}
1009
1010
1012{
1013 int idx = m_FootprintFilterListBox->HitTest( event.GetPosition() );
1014 wxCommandEvent dummy;
1015
1016 if( idx >= 0 )
1018 else
1020}
1021
1022
1024{
1025 // Running the Footprint Browser gums up the works and causes the automatic cancel
1026 // stuff to no longer work. So we do it here ourselves.
1027 EndQuasiModal( wxID_CANCEL );
1028}
1029
1030
1032{
1033 wxString filterLine;
1034 WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Add Footprint Filter" ), filterLine );
1035
1036 if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue().IsEmpty() )
1037 return;
1038
1039 filterLine = dlg.GetValue();
1040 filterLine.Replace( wxT( " " ), wxT( "_" ) );
1041
1042 // duplicate filters do no harm, so don't be a nanny.
1043 m_FootprintFilterListBox->Append( filterLine );
1044 m_FootprintFilterListBox->SetSelection( (int) m_FootprintFilterListBox->GetCount() - 1 );
1045
1046 OnModify();
1047}
1048
1049
1051{
1052 wxArrayInt selections;
1053 int n = m_FootprintFilterListBox->GetSelections( selections );
1054
1055 if( n > 0 )
1056 {
1057 // Just edit the first one
1058 int idx = selections[0];
1059 wxString filter = m_FootprintFilterListBox->GetString( idx );
1060
1061 WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Edit Footprint Filter" ), filter );
1062
1063 if( dlg.ShowModal() == wxID_OK && !dlg.GetValue().IsEmpty() )
1064 {
1065 m_FootprintFilterListBox->SetString( (unsigned) idx, dlg.GetValue() );
1066 OnModify();
1067 }
1068 }
1069}
1070
1071
1072void DIALOG_LIB_SYMBOL_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
1073{
1074 m_OptionPartsInterchangeable->Enable( m_unitSpinCtrl->GetValue() > 1 );
1075 m_pinNameOffset.Enable( m_PinsNameInsideButt->GetValue() );
1076
1077 if( m_grid->IsCellEditControlShown() )
1078 {
1079 int row = m_grid->GetGridCursorRow();
1080 int col = m_grid->GetGridCursorCol();
1081
1082 if( row == m_fields->GetFieldRow( FIELD_T::VALUE ) && col == FDC_VALUE && m_OptionPower->IsChecked() )
1083 {
1084 wxGridCellEditor* editor = m_grid->GetCellEditor( row, col );
1085 m_SymbolNameCtrl->ChangeValue( editor->GetValue() );
1086 editor->DecRef();
1087 }
1088 }
1089
1090 // Handle shown columns changes
1091 std::bitset<64> shownColumns = m_grid->GetShownColumns();
1092
1093 if( shownColumns != m_shownColumns )
1094 {
1095 m_shownColumns = shownColumns;
1096
1097 if( !m_grid->IsCellEditControlShown() )
1098 m_grid->SetGridWidthsDirty();
1099 }
1100
1101 // Handle a delayed focus. The delay allows us to:
1102 // a) change focus when the error was triggered from within a killFocus handler
1103 // b) show the correct notebook page in the background before the error dialog comes up
1104 // when triggered from an OK or a notebook page change
1105
1106 if( m_delayedFocusPage >= 0 && m_NoteBook->GetSelection() != m_delayedFocusPage )
1107 {
1108 m_NoteBook->ChangeSelection( (unsigned) m_delayedFocusPage );
1109 m_delayedFocusPage = -1;
1110 }
1111
1112 if( !m_delayedErrorMessage.IsEmpty() )
1113 {
1114 // We will re-enter this routine when the error dialog is displayed, so make
1115 // sure we don't keep putting up more dialogs.
1116 wxString msg = m_delayedErrorMessage;
1117 m_delayedErrorMessage = wxEmptyString;
1118
1119 // Do not use DisplayErrorMessage(); it screws up window order on Mac
1120 DisplayError( nullptr, msg );
1121 }
1122
1123 if( m_delayedFocusCtrl )
1124 {
1125 m_delayedFocusCtrl->SetFocus();
1126
1127 if( wxTextEntry* textEntry = dynamic_cast<wxTextEntry*>( m_delayedFocusCtrl ) )
1128 textEntry->SelectAll();
1129
1130 m_delayedFocusCtrl = nullptr;
1131 }
1132 else if( m_delayedFocusGrid )
1133 {
1134 m_delayedFocusGrid->SetFocus();
1137
1138 m_delayedFocusGrid->EnableCellEditControl( true );
1139 m_delayedFocusGrid->ShowCellEditControl();
1140
1141 m_delayedFocusGrid = nullptr;
1142 m_delayedFocusRow = -1;
1144 }
1145}
1146
1147
1149{
1150 bSizerLowerBasicPanel->Show( !aIsAlias );
1151 m_inheritanceSelectCombo->Enable( aIsAlias );
1152 m_inheritsStaticText->Enable( aIsAlias );
1153 m_grid->ForceRefresh();
1154}
1155
1156
1158{
1159 if( m_OptionPower->IsChecked() )
1160 {
1161 m_excludeFromSimCheckBox->SetValue( true );
1162 m_excludeFromBomCheckBox->SetValue( true );
1163 m_excludeFromBoardCheckBox->SetValue( true );
1164 m_excludeFromBomCheckBox->Enable( false );
1165 m_excludeFromBoardCheckBox->Enable( false );
1166 m_excludeFromSimCheckBox->Enable( false );
1167 m_spiceFieldsButton->Show( false );
1168 m_OptionLocalPower->Enable( true );
1169 }
1170 else
1171 {
1172 m_excludeFromBomCheckBox->Enable( true );
1173 m_excludeFromBoardCheckBox->Enable( true );
1174 m_excludeFromSimCheckBox->Enable( true );
1175 m_spiceFieldsButton->Show( true );
1176 m_OptionLocalPower->Enable( false );
1177 }
1178
1179 OnModify();
1180}
1181
1182
1183void DIALOG_LIB_SYMBOL_PROPERTIES::OnText( wxCommandEvent& event )
1184{
1185 OnModify();
1186}
1187
1188
1189void DIALOG_LIB_SYMBOL_PROPERTIES::OnCombobox( wxCommandEvent& event )
1190{
1191 OnModify();
1192}
1193
1194
1195void DIALOG_LIB_SYMBOL_PROPERTIES::OnCheckBox( wxCommandEvent& event )
1196{
1197 OnModify();
1198}
1199
1200
1202{
1203 m_unitNamesGrid->CommitPendingChanges( true /* aQuietMode */ );
1204
1205 int extra = m_unitNamesGrid->GetNumberRows() - m_unitSpinCtrl->GetValue();
1206 int needed = m_unitSpinCtrl->GetValue() - m_unitNamesGrid->GetNumberRows();
1207
1208 if( extra > 0 )
1209 {
1210 m_unitNamesGrid->DeleteRows( m_unitNamesGrid->GetNumberRows() - extra, extra );
1211 return true;
1212 }
1213
1214 if( needed > 0 )
1215 {
1216 m_unitNamesGrid->AppendRows( needed );
1217
1218 for( int row = m_unitNamesGrid->GetNumberRows() - needed; row < m_unitNamesGrid->GetNumberRows(); ++row )
1219 m_unitNamesGrid->SetCellValue( row, 0, LIB_SYMBOL::LetterSubReference( row + 1, 'A' ) );
1220
1221 return true;
1222 }
1223
1224 return false;
1225}
1226
1227
1229{
1230 if( updateUnitCount() )
1231 OnModify();
1232}
1233
1234
1236{
1237 // wait for kill focus to update unit count
1238}
1239
1240
1242{
1243 if( updateUnitCount() )
1244 OnModify();
1245}
1246
1247
1249{
1250 if( updateUnitCount() )
1251 OnModify();
1252}
1253
1254
1256{
1257 if( !m_grid->CommitPendingChanges() )
1258 aEvent.Veto();
1259}
1260
1261
1263{
1264 wxArrayInt selections;
1265 m_listAvailablePins->GetSelections( selections );
1266
1267 if( !selections.empty() )
1268 {
1269 m_listJumperPinGroups->Freeze();
1270 m_listAvailablePins->Freeze();
1271
1272 wxString group;
1273 unsigned i = 0;
1274
1275 for( int idx : selections )
1276 {
1277 group << m_listAvailablePins->GetString( idx );
1278
1279 if( ++i < selections.Count() )
1280 group << ", ";
1281 }
1282
1283 for( int idx = (int) selections.size() - 1; idx >= 0; --idx )
1284 m_listAvailablePins->Delete( selections[idx] );
1285
1286 m_listJumperPinGroups->AppendString( group );
1287
1288 m_listJumperPinGroups->Thaw();
1289 m_listAvailablePins->Thaw();
1290 }
1291}
1292
1293
1295{
1296 wxArrayInt selections;
1297 m_listJumperPinGroups->GetSelections( selections );
1298
1299 if( !selections.empty() )
1300 {
1301 m_listJumperPinGroups->Freeze();
1302 m_listAvailablePins->Freeze();
1303
1304 for( int idx : selections )
1305 {
1306 wxStringTokenizer tokenizer( m_listJumperPinGroups->GetString( idx ), ", \t\r\n", wxTOKEN_STRTOK );
1307
1308 while( tokenizer.HasMoreTokens() )
1309 {
1310 if( wxString token = tokenizer.GetNextToken(); !token.IsEmpty() )
1311 m_listAvailablePins->AppendString( token );
1312 }
1313 }
1314
1315 for( int idx = (int) selections.size() - 1; idx >= 0; --idx )
1316 m_listJumperPinGroups->Delete( selections[idx] );
1317
1318 m_listJumperPinGroups->Thaw();
1319 m_listAvailablePins->Thaw();
1320 }
1321}
1322
1323
1325{
1326 wxArrayInt selections;
1327 int n = m_listJumperPinGroups->GetSelections( selections );
1328 m_btnRemoveJumperPinGroup->Enable( n > 0 );
1329}
1330
1331
1333{
1334 wxArrayInt selections;
1335 int n = m_listAvailablePins->GetSelections( selections );
1336 m_btnCreateJumperPinGroup->Enable( n > 0 );
1337}
const char * name
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:114
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
DIALOG_LIB_SYMBOL_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=ID_LIBEDIT_NOTEBOOK, const wxString &title=_("Library Symbol Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void OnSymbolNameKillFocus(wxFocusEvent &event) override
void OnUnitSpinCtrlText(wxCommandEvent &event) override
std::unique_ptr< LISTBOX_TRICKS > m_fpFilterTricks
void OnPageChanging(wxNotebookEvent &event) override
void OnBodyStyle(wxCommandEvent &event) override
DIALOG_LIB_SYMBOL_PROPERTIES(SYMBOL_EDIT_FRAME *parent, LIB_SYMBOL *aLibEntry)
void OnGroupedPinListClick(wxCommandEvent &event) override
void OnUpdateUI(wxUpdateUIEvent &event) override
void OnBodyStyleMoveUp(wxCommandEvent &event) override
void OnCancelButtonClick(wxCommandEvent &event) override
void OnEditFootprintFilter(wxCommandEvent &event) override
void OnDeleteField(wxCommandEvent &event) override
void OnCombobox(wxCommandEvent &event) override
void OnFpFilterDClick(wxMouseEvent &event) override
void OnUnitSpinCtrlEnter(wxCommandEvent &event) override
void OnAddBodyStyle(wxCommandEvent &event) override
void OnMoveUp(wxCommandEvent &event) override
void OnDeleteBodyStyle(wxCommandEvent &event) override
void OnAvailablePinsClick(wxCommandEvent &event) override
void OnText(wxCommandEvent &event) override
void OnUnitSpinCtrlKillFocus(wxFocusEvent &event) override
void OnMoveDown(wxCommandEvent &event) override
void OnAddFootprintFilter(wxCommandEvent &event) override
void OnCheckBox(wxCommandEvent &event) override
void OnSymbolNameText(wxCommandEvent &event) override
void OnBodyStyleMoveDown(wxCommandEvent &event) override
void onPowerCheckBox(wxCommandEvent &aEvent) override
void OnBtnCreateJumperPinGroup(wxCommandEvent &event) override
void OnAddField(wxCommandEvent &event) override
void OnEditSpiceModel(wxCommandEvent &event) override
void OnUnitSpinCtrl(wxSpinEvent &event) override
void OnBtnRemoveJumperPinGroup(wxCommandEvent &event) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void resetSize()
Clear the existing dialog size and position.
void EndQuasiModal(int retCode)
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
EDA_BASE_FRAME * m_parentFrame
int ShowModal() override
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition eda_text.cpp:544
virtual const wxString & GetText() const
Return the string associated with the text object.
Definition eda_text.h:98
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:397
A text control validator used for validating the text allowed in fields.
Definition validators.h:142
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:87
bool IsRoot() const override
For symbols derived from other symbols, IsRoot() indicates no derivation.
Definition lib_symbol.h:207
bool IsDerived() const
Definition lib_symbol.h:208
static wxString LetterSubReference(int aUnit, wxChar aInitialLetter)
void SetOrdinal(int aOrdinal)
Definition sch_field.h:122
bool IsMandatory() const
wxString GetCanonicalName() const
Get a non-language-specific name for a field which can be used for storage, variable look-up,...
wxString GetName(bool aUseDefaultName=true) const
Return the field name (not translated).
void SetName(const wxString &aName)
The symbol library editor main window.
bool IsSymbolFromLegacyLibrary() const
bool IsSymbolFromSchematic() const
void AddTemplateFieldNames(const wxString &aSerializedFieldNames)
Add a serialized list of template field names.
const std::vector< TEMPLATE_FIELDNAME > & GetTemplateFieldNames()
Return a template field name list for read only access.
A KICAD version of wxTextEntryDialog which supports the various improvements/work-arounds from DIALOG...
wxString GetValue() const
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:251
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:194
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:169
This file is part of the common library.
#define _(s)
@ FDC_NAME
@ FDC_VALUE
std::shared_ptr< LIB_SYMBOL > LIB_SYMBOL_SPTR
shared pointer to LIB_SYMBOL
Definition lib_symbol.h:54
wxString GetRefDesPrefix(const wxString &aRefDes)
Get the (non-numeric) prefix from a refdes - e.g.
STL namespace.
see class PGM_BASE
Collection of utility functions for component reference designators (refdes)
T * GetAppSettings(const char *aFilename)
std::vector< FAB_LAYER_COLOR > dummy
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
wxString UnescapeString(const wxString &aSource)
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_LIBID
Hold a name of a symbol's field, field value, and default visibility.
wxString GetUserFieldName(int aFieldNdx, bool aTranslateForHI)
#define DO_TRANSLATE
@ USER
The field ID hasn't been set yet; field is invalid.
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
UNDO_REDO
Undo Redo considerations: Basically we have 3 cases New item Deleted item Modified item there is also...
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695