You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1106 lines
34 KiB

Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
6 years ago
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
6 years ago
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
6 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2004-2021 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. #include "dialog_symbol_properties.h"
  24. #include <bitmaps.h>
  25. #include <wx/tooltip.h>
  26. #include <grid_tricks.h>
  27. #include <confirm.h>
  28. #include <kiface_base.h>
  29. #include <pin_numbers.h>
  30. #include <string_utils.h>
  31. #include <menus_helpers.h>
  32. #include <widgets/grid_icon_text_helpers.h>
  33. #include <widgets/grid_combobox.h>
  34. #include <widgets/wx_grid.h>
  35. #include <settings/settings_manager.h>
  36. #include <ee_collectors.h>
  37. #include <symbol_library.h>
  38. #include <fields_grid_table.h>
  39. #include <sch_edit_frame.h>
  40. #include <sch_reference_list.h>
  41. #include <schematic.h>
  42. #include <tool/tool_manager.h>
  43. #include <tool/actions.h>
  44. #ifdef KICAD_SPICE
  45. #include <dialog_spice_model.h>
  46. #endif /* KICAD_SPICE */
  47. enum PIN_TABLE_COL_ORDER
  48. {
  49. COL_NUMBER,
  50. COL_BASE_NAME,
  51. COL_ALT_NAME,
  52. COL_TYPE,
  53. COL_SHAPE,
  54. COL_COUNT // keep as last
  55. };
  56. class SCH_PIN_TABLE_DATA_MODEL : public wxGridTableBase, public std::vector<SCH_PIN>
  57. {
  58. protected:
  59. std::vector<wxGridCellAttr*> m_nameAttrs;
  60. wxGridCellAttr* m_readOnlyAttr;
  61. wxGridCellAttr* m_typeAttr;
  62. wxGridCellAttr* m_shapeAttr;
  63. public:
  64. SCH_PIN_TABLE_DATA_MODEL() :
  65. m_readOnlyAttr( nullptr ),
  66. m_typeAttr( nullptr ),
  67. m_shapeAttr( nullptr )
  68. {
  69. }
  70. ~SCH_PIN_TABLE_DATA_MODEL()
  71. {
  72. for( wxGridCellAttr* attr : m_nameAttrs )
  73. attr->DecRef();
  74. m_readOnlyAttr->DecRef();
  75. m_typeAttr->DecRef();
  76. m_shapeAttr->DecRef();
  77. }
  78. void BuildAttrs()
  79. {
  80. m_readOnlyAttr = new wxGridCellAttr;
  81. m_readOnlyAttr->SetReadOnly( true );
  82. for( const SCH_PIN& pin : *this )
  83. {
  84. LIB_PIN* lib_pin = pin.GetLibPin();
  85. wxGridCellAttr* attr = nullptr;
  86. if( lib_pin->GetAlternates().empty() )
  87. {
  88. attr = new wxGridCellAttr;
  89. attr->SetReadOnly( true );
  90. }
  91. else
  92. {
  93. wxArrayString choices;
  94. choices.push_back( lib_pin->GetName() );
  95. for( const std::pair<const wxString, LIB_PIN::ALT>& alt : lib_pin->GetAlternates() )
  96. choices.push_back( alt.first );
  97. attr = new wxGridCellAttr();
  98. attr->SetEditor( new GRID_CELL_COMBOBOX( choices ) );
  99. }
  100. m_nameAttrs.push_back( attr );
  101. }
  102. m_typeAttr = new wxGridCellAttr;
  103. m_typeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(),
  104. PinTypeNames() ) );
  105. m_typeAttr->SetReadOnly( true );
  106. m_shapeAttr = new wxGridCellAttr;
  107. m_shapeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(),
  108. PinShapeNames() ) );
  109. m_shapeAttr->SetReadOnly( true );
  110. }
  111. int GetNumberRows() override { return (int) size(); }
  112. int GetNumberCols() override { return COL_COUNT; }
  113. wxString GetColLabelValue( int aCol ) override
  114. {
  115. switch( aCol )
  116. {
  117. case COL_NUMBER: return _( "Number" );
  118. case COL_BASE_NAME: return _( "Base Name" );
  119. case COL_ALT_NAME: return _( "Alternate Assignment" );
  120. case COL_TYPE: return _( "Electrical Type" );
  121. case COL_SHAPE: return _( "Graphic Style" );
  122. default: wxFAIL; return wxEmptyString;
  123. }
  124. }
  125. bool IsEmptyCell( int row, int col ) override
  126. {
  127. return false; // don't allow adjacent cell overflow, even if we are actually empty
  128. }
  129. wxString GetValue( int aRow, int aCol ) override
  130. {
  131. return GetValue( at( aRow ), aCol );
  132. }
  133. static wxString GetValue( const SCH_PIN& aPin, int aCol )
  134. {
  135. switch( aCol )
  136. {
  137. case COL_NUMBER: return aPin.GetNumber();
  138. case COL_BASE_NAME: return aPin.GetLibPin()->GetName();
  139. case COL_ALT_NAME: return aPin.GetAlt();
  140. case COL_TYPE: return PinTypeNames()[static_cast<int>( aPin.GetType() )];
  141. case COL_SHAPE: return PinShapeNames()[static_cast<int>( aPin.GetShape() )];
  142. default: wxFAIL; return wxEmptyString;
  143. }
  144. }
  145. wxGridCellAttr* GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind ) override
  146. {
  147. switch( aCol )
  148. {
  149. case COL_NUMBER:
  150. case COL_BASE_NAME:
  151. m_readOnlyAttr->IncRef();
  152. return m_readOnlyAttr;
  153. case COL_ALT_NAME:
  154. m_nameAttrs[ aRow ]->IncRef();
  155. return m_nameAttrs[ aRow ];
  156. case COL_TYPE:
  157. m_typeAttr->IncRef();
  158. return m_typeAttr;
  159. case COL_SHAPE:
  160. m_shapeAttr->IncRef();
  161. return m_shapeAttr;
  162. default:
  163. wxFAIL;
  164. return nullptr;
  165. }
  166. }
  167. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  168. {
  169. switch( aCol )
  170. {
  171. case COL_ALT_NAME:
  172. if( aValue == at( aRow ).GetLibPin()->GetName() )
  173. at( aRow ).SetAlt( wxEmptyString );
  174. else
  175. at( aRow ).SetAlt( aValue );
  176. break;
  177. case COL_NUMBER:
  178. case COL_BASE_NAME:
  179. case COL_TYPE:
  180. case COL_SHAPE:
  181. // Read-only.
  182. break;
  183. default:
  184. wxFAIL;
  185. break;
  186. }
  187. }
  188. static bool compare( const SCH_PIN& lhs, const SCH_PIN& rhs, int sortCol, bool ascending )
  189. {
  190. wxString lhStr = GetValue( lhs, sortCol );
  191. wxString rhStr = GetValue( rhs, sortCol );
  192. if( lhStr == rhStr )
  193. {
  194. // Secondary sort key is always COL_NUMBER
  195. sortCol = COL_NUMBER;
  196. lhStr = GetValue( lhs, sortCol );
  197. rhStr = GetValue( rhs, sortCol );
  198. }
  199. bool res;
  200. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  201. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  202. auto cmp = [ ascending ]( const auto a, const auto b )
  203. {
  204. if( ascending )
  205. return a < b;
  206. else
  207. return b < a;
  208. };
  209. switch( sortCol )
  210. {
  211. case COL_NUMBER:
  212. case COL_BASE_NAME:
  213. case COL_ALT_NAME:
  214. res = cmp( PIN_NUMBERS::Compare( lhStr, rhStr ), 0 );
  215. break;
  216. case COL_TYPE:
  217. case COL_SHAPE:
  218. res = cmp( lhStr.CmpNoCase( rhStr ), 0 );
  219. break;
  220. default:
  221. res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
  222. break;
  223. }
  224. return res;
  225. }
  226. void SortRows( int aSortCol, bool ascending )
  227. {
  228. std::sort( begin(), end(),
  229. [ aSortCol, ascending ]( const SCH_PIN& lhs, const SCH_PIN& rhs ) -> bool
  230. {
  231. return compare( lhs, rhs, aSortCol, ascending );
  232. } );
  233. }
  234. };
  235. DIALOG_SYMBOL_PROPERTIES::DIALOG_SYMBOL_PROPERTIES( SCH_EDIT_FRAME* aParent,
  236. SCH_SYMBOL* aSymbol ) :
  237. DIALOG_SYMBOL_PROPERTIES_BASE( aParent ),
  238. m_symbol( nullptr ),
  239. m_part( nullptr ),
  240. m_fields( nullptr ),
  241. m_dataModel( nullptr )
  242. {
  243. m_symbol = aSymbol;
  244. m_part = m_symbol->GetLibSymbolRef().get();
  245. // GetLibSymbolRef() now points to the cached part in the schematic, which should always be
  246. // there for usual cases, but can be null when opening old schematics not storing the part
  247. // so we need to handle m_part == nullptr
  248. // wxASSERT( m_part );
  249. m_fields = new FIELDS_GRID_TABLE<SCH_FIELD>( this, aParent, m_fieldsGrid, m_part );
  250. m_width = 0;
  251. m_delayedFocusRow = REFERENCE_FIELD;
  252. m_delayedFocusColumn = FDC_VALUE;
  253. m_delayedSelection = true;
  254. #ifndef KICAD_SPICE
  255. m_spiceFieldsButton->Hide();
  256. #endif /* not KICAD_SPICE */
  257. // disable some options inside the edit dialog which can cause problems while dragging
  258. if( m_symbol->IsDragging() )
  259. {
  260. m_orientationLabel->Disable();
  261. m_orientationCtrl->Disable();
  262. m_mirrorLabel->Disable();
  263. m_mirrorCtrl->Disable();
  264. }
  265. // Give a bit more room for combobox editors
  266. m_fieldsGrid->SetDefaultRowSize( m_fieldsGrid->GetDefaultRowSize() + 4 );
  267. m_pinGrid->SetDefaultRowSize( m_pinGrid->GetDefaultRowSize() + 4 );
  268. m_fieldsGrid->SetTable( m_fields );
  269. m_fieldsGrid->PushEventHandler( new FIELDS_GRID_TRICKS( m_fieldsGrid, this ) );
  270. m_fieldsGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
  271. // Show/hide columns according to user's preference
  272. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  273. if( cfg )
  274. {
  275. m_shownColumns = cfg->m_Appearance.edit_component_visible_columns;
  276. m_fieldsGrid->ShowHideColumns( m_shownColumns );
  277. }
  278. if( m_part && m_part->HasConversion() )
  279. {
  280. // DeMorgan conversions are a subclass of alternate pin assignments, so don't allow
  281. // free-form alternate assignments as well. (We won't know how to map the alternates
  282. // back and forth when the conversion is changed.)
  283. m_pinTablePage->Disable();
  284. m_pinTablePage->SetToolTip( _( "Alternate pin assignments are not available for De Morgan "
  285. "symbols." ) );
  286. }
  287. else
  288. {
  289. m_dataModel = new SCH_PIN_TABLE_DATA_MODEL();
  290. // Make a copy of the pins for editing
  291. for( const std::unique_ptr<SCH_PIN>& pin : m_symbol->GetRawPins() )
  292. m_dataModel->push_back( *pin );
  293. m_dataModel->SortRows( COL_NUMBER, true );
  294. m_dataModel->BuildAttrs();
  295. m_pinGrid->SetTable( m_dataModel );
  296. }
  297. m_pinGrid->PushEventHandler( new GRID_TRICKS( m_pinGrid ) );
  298. m_pinGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
  299. wxToolTip::Enable( true );
  300. m_stdDialogButtonSizerOK->SetDefault();
  301. // Configure button logos
  302. m_bpAdd->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
  303. m_bpDelete->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
  304. m_bpMoveUp->SetBitmap( KiBitmap( BITMAPS::small_up ) );
  305. m_bpMoveDown->SetBitmap( KiBitmap( BITMAPS::small_down ) );
  306. // wxFormBuilder doesn't include this event...
  307. m_fieldsGrid->Connect( wxEVT_GRID_CELL_CHANGING,
  308. wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging ),
  309. nullptr, this );
  310. m_pinGrid->Connect( wxEVT_GRID_COL_SORT,
  311. wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort ),
  312. nullptr, this );
  313. finishDialogSettings();
  314. }
  315. DIALOG_SYMBOL_PROPERTIES::~DIALOG_SYMBOL_PROPERTIES()
  316. {
  317. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  318. if( cfg )
  319. cfg->m_Appearance.edit_component_visible_columns = m_fieldsGrid->GetShownColumns();
  320. // Prevents crash bug in wxGrid's d'tor
  321. m_fieldsGrid->DestroyTable( m_fields );
  322. if( m_dataModel )
  323. m_pinGrid->DestroyTable( m_dataModel );
  324. m_fieldsGrid->Disconnect( wxEVT_GRID_CELL_CHANGING,
  325. wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging ),
  326. nullptr, this );
  327. m_pinGrid->Disconnect( wxEVT_GRID_COL_SORT,
  328. wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort ),
  329. nullptr, this );
  330. // Delete the GRID_TRICKS.
  331. m_fieldsGrid->PopEventHandler( true );
  332. m_pinGrid->PopEventHandler( true );
  333. }
  334. SCH_EDIT_FRAME* DIALOG_SYMBOL_PROPERTIES::GetParent()
  335. {
  336. return dynamic_cast<SCH_EDIT_FRAME*>( wxDialog::GetParent() );
  337. }
  338. bool DIALOG_SYMBOL_PROPERTIES::TransferDataToWindow()
  339. {
  340. if( !wxDialog::TransferDataToWindow() )
  341. return false;
  342. std::set<wxString> defined;
  343. // Push a copy of each field into m_updateFields
  344. for( int i = 0; i < m_symbol->GetFieldCount(); ++i )
  345. {
  346. SCH_FIELD field( m_symbol->GetFields()[i] );
  347. // change offset to be symbol-relative
  348. field.Offset( -m_symbol->GetPosition() );
  349. defined.insert( field.GetName() );
  350. m_fields->push_back( field );
  351. }
  352. // Add in any template fieldnames not yet defined:
  353. for( const TEMPLATE_FIELDNAME& templateFieldname :
  354. GetParent()->Schematic().Settings().m_TemplateFieldNames.GetTemplateFieldNames() )
  355. {
  356. if( defined.count( templateFieldname.m_Name ) <= 0 )
  357. {
  358. SCH_FIELD field( wxPoint( 0, 0 ), -1, m_symbol, templateFieldname.m_Name );
  359. field.SetVisible( templateFieldname.m_Visible );
  360. m_fields->push_back( field );
  361. }
  362. }
  363. // notify the grid
  364. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_fields->size() );
  365. m_fieldsGrid->ProcessTableMessage( msg );
  366. AdjustGridColumns( m_fieldsGrid->GetRect().GetWidth() );
  367. // If a multi-unit symbol, set up the unit selector and interchangeable checkbox.
  368. if( m_symbol->GetUnitCount() > 1 )
  369. {
  370. for( int ii = 1; ii <= m_symbol->GetUnitCount(); ii++ )
  371. m_unitChoice->Append( LIB_SYMBOL::SubReference( ii, false ) );
  372. if( m_symbol->GetUnit() <= ( int )m_unitChoice->GetCount() )
  373. m_unitChoice->SetSelection( m_symbol->GetUnit() - 1 );
  374. }
  375. else
  376. {
  377. m_unitLabel->Enable( false );
  378. m_unitChoice->Enable( false );
  379. }
  380. if( m_part && m_part->HasConversion() )
  381. {
  382. if( m_symbol->GetConvert() > LIB_ITEM::LIB_CONVERT::BASE )
  383. m_cbAlternateSymbol->SetValue( true );
  384. }
  385. else
  386. {
  387. m_cbAlternateSymbol->Enable( false );
  388. }
  389. // Set the symbol orientation and mirroring.
  390. int orientation = m_symbol->GetOrientation() & ~( SYM_MIRROR_X | SYM_MIRROR_Y );
  391. switch( orientation )
  392. {
  393. default:
  394. case SYM_ORIENT_0: m_orientationCtrl->SetSelection( 0 ); break;
  395. case SYM_ORIENT_90: m_orientationCtrl->SetSelection( 1 ); break;
  396. case SYM_ORIENT_270: m_orientationCtrl->SetSelection( 2 ); break;
  397. case SYM_ORIENT_180: m_orientationCtrl->SetSelection( 3 ); break;
  398. }
  399. int mirror = m_symbol->GetOrientation() & ( SYM_MIRROR_X | SYM_MIRROR_Y );
  400. switch( mirror )
  401. {
  402. default: m_mirrorCtrl->SetSelection( 0 ) ; break;
  403. case SYM_MIRROR_X: m_mirrorCtrl->SetSelection( 1 ); break;
  404. case SYM_MIRROR_Y: m_mirrorCtrl->SetSelection( 2 ); break;
  405. }
  406. m_cbExcludeFromBom->SetValue( !m_symbol->GetIncludeInBom() );
  407. m_cbExcludeFromBoard->SetValue( !m_symbol->GetIncludeOnBoard() );
  408. if( m_part )
  409. {
  410. m_ShowPinNumButt->SetValue( m_part->ShowPinNumbers() );
  411. m_ShowPinNameButt->SetValue( m_part->ShowPinNames() );
  412. }
  413. // Set the symbol's library name.
  414. m_tcLibraryID->SetValue( UnescapeString( m_symbol->GetLibId().Format() ) );
  415. Layout();
  416. // Workaround to fix an annoying issue on wxGTK: in some cases selecting a field
  417. // to change its value make this value invisible. It happens until the dialog is resized.
  418. // So I am guessing there is a problem when initializing sizers settings
  419. // Do not create issues on other OS
  420. wxSafeYield(); // slice of time to handle events generated when creating the
  421. // dialog, especially size events
  422. m_fieldsGrid->Layout(); // Force recalculating all sizers in m_fieldsGrid
  423. return true;
  424. }
  425. void DIALOG_SYMBOL_PROPERTIES::OnEditSpiceModel( wxCommandEvent& event )
  426. {
  427. #ifdef KICAD_SPICE
  428. int diff = m_fields->size();
  429. DIALOG_SPICE_MODEL dialog( this, *m_symbol, m_fields );
  430. if( dialog.ShowModal() != wxID_OK )
  431. return;
  432. diff = (int) m_fields->size() - diff;
  433. if( diff > 0 )
  434. {
  435. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, diff );
  436. m_fieldsGrid->ProcessTableMessage( msg );
  437. }
  438. else if( diff < 0 )
  439. {
  440. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, -diff );
  441. m_fieldsGrid->ProcessTableMessage( msg );
  442. }
  443. OnModify();
  444. m_fieldsGrid->ForceRefresh();
  445. #endif /* KICAD_SPICE */
  446. }
  447. void DIALOG_SYMBOL_PROPERTIES::OnCancelButtonClick( wxCommandEvent& event )
  448. {
  449. // Running the Footprint Browser gums up the works and causes the automatic cancel
  450. // stuff to no longer work. So we do it here ourselves.
  451. EndQuasiModal( wxID_CANCEL );
  452. }
  453. bool DIALOG_SYMBOL_PROPERTIES::Validate()
  454. {
  455. wxString msg;
  456. LIB_ID id;
  457. if( !m_fieldsGrid->CommitPendingChanges() || !m_fieldsGrid->Validate() )
  458. return false;
  459. if( !SCH_SYMBOL::IsReferenceStringValid( m_fields->at( REFERENCE_FIELD ).GetText() ) )
  460. {
  461. DisplayErrorMessage( this, _( "References must start with a letter." ) );
  462. m_delayedFocusColumn = FDC_VALUE;
  463. m_delayedFocusRow = REFERENCE_FIELD;
  464. m_delayedSelection = false;
  465. return false;
  466. }
  467. // Check for missing field names.
  468. for( size_t i = MANDATORY_FIELDS; i < m_fields->size(); ++i )
  469. {
  470. SCH_FIELD& field = m_fields->at( i );
  471. wxString fieldName = field.GetName( false );
  472. if( fieldName.IsEmpty() )
  473. {
  474. DisplayErrorMessage( this, _( "Fields must have a name." ) );
  475. m_delayedFocusColumn = FDC_NAME;
  476. m_delayedFocusRow = i;
  477. m_delayedSelection = false;
  478. return false;
  479. }
  480. }
  481. return true;
  482. }
  483. bool DIALOG_SYMBOL_PROPERTIES::TransferDataFromWindow()
  484. {
  485. if( !wxDialog::TransferDataFromWindow() ) // Calls our Validate() method.
  486. return false;
  487. if( !m_fieldsGrid->CommitPendingChanges() )
  488. return false;
  489. if( !m_pinGrid->CommitPendingChanges() )
  490. return false;
  491. SCH_SCREEN* currentScreen = GetParent()->GetScreen();
  492. SCHEMATIC& schematic = GetParent()->Schematic();
  493. wxCHECK( currentScreen, false );
  494. // This needs to be done before the LIB_ID is changed to prevent stale library symbols in
  495. // the schematic file.
  496. currentScreen->Remove( m_symbol );
  497. wxString msg;
  498. // save old cmp in undo list if not already in edit, or moving ...
  499. if( m_symbol->GetEditFlags() == 0 )
  500. GetParent()->SaveCopyInUndoList( currentScreen, m_symbol, UNDO_REDO::CHANGED, false );
  501. // Save current flags which could be modified by next change settings
  502. EDA_ITEM_FLAGS flags = m_symbol->GetFlags();
  503. // For symbols with multiple shapes (De Morgan representation) Set the selected shape:
  504. if( m_cbAlternateSymbol->IsEnabled() && m_cbAlternateSymbol->GetValue() )
  505. m_symbol->SetConvert( LIB_ITEM::LIB_CONVERT::DEMORGAN );
  506. else
  507. m_symbol->SetConvert( LIB_ITEM::LIB_CONVERT::BASE );
  508. //Set the part selection in multiple part per package
  509. int unit_selection = m_unitChoice->IsEnabled() ? m_unitChoice->GetSelection() + 1 : 1;
  510. m_symbol->SetUnitSelection( &GetParent()->GetCurrentSheet(), unit_selection );
  511. m_symbol->SetUnit( unit_selection );
  512. switch( m_orientationCtrl->GetSelection() )
  513. {
  514. case 0: m_symbol->SetOrientation( SYM_ORIENT_0 ); break;
  515. case 1: m_symbol->SetOrientation( SYM_ORIENT_90 ); break;
  516. case 2: m_symbol->SetOrientation( SYM_ORIENT_270 ); break;
  517. case 3: m_symbol->SetOrientation( SYM_ORIENT_180 ); break;
  518. }
  519. switch( m_mirrorCtrl->GetSelection() )
  520. {
  521. case 0: break;
  522. case 1: m_symbol->SetOrientation( SYM_MIRROR_X ); break;
  523. case 2: m_symbol->SetOrientation( SYM_MIRROR_Y ); break;
  524. }
  525. if( m_part )
  526. {
  527. m_part->SetShowPinNames( m_ShowPinNameButt->GetValue() );
  528. m_part->SetShowPinNumbers( m_ShowPinNumButt->GetValue() );
  529. }
  530. // Restore m_Flag modified by SetUnit() and other change settings
  531. m_symbol->ClearFlags();
  532. m_symbol->SetFlags( flags );
  533. // change all field positions from relative to absolute
  534. for( unsigned i = 0; i < m_fields->size(); ++i )
  535. m_fields->at( i ).Offset( m_symbol->GetPosition() );
  536. LIB_SYMBOL* entry = GetParent()->GetLibSymbol( m_symbol->GetLibId() );
  537. if( entry && entry->IsPower() )
  538. m_fields->at( VALUE_FIELD ).SetText( m_symbol->GetLibId().GetLibItemName() );
  539. // Push all fields to the symbol -except- for those which are TEMPLATE_FIELDNAMES
  540. // with empty values.
  541. SCH_FIELDS& fields = m_symbol->GetFields();
  542. fields.clear();
  543. for( size_t i = 0; i < m_fields->size(); ++i )
  544. {
  545. SCH_FIELD& field = m_fields->at( i );
  546. bool emptyTemplateField = false;
  547. if( i >= MANDATORY_FIELDS )
  548. {
  549. for( const TEMPLATE_FIELDNAME& fieldname :
  550. schematic.Settings().m_TemplateFieldNames.GetTemplateFieldNames() )
  551. {
  552. if( field.GetName() == fieldname.m_Name && field.GetText().IsEmpty() )
  553. {
  554. emptyTemplateField = true;
  555. break;
  556. }
  557. }
  558. }
  559. if( !emptyTemplateField )
  560. fields.push_back( field );
  561. }
  562. // Reference has a specific initialization, depending on the current active sheet
  563. // because for a given symbol, in a complex hierarchy, there are more than one
  564. // reference.
  565. m_symbol->SetRef( &GetParent()->GetCurrentSheet(), m_fields->at( REFERENCE_FIELD ).GetText() );
  566. // Similar for Value and Footprint, except that the GUI behaviour is that they are kept
  567. // in sync between multiple instances.
  568. m_symbol->SetValue( m_fields->at( VALUE_FIELD ).GetText() );
  569. m_symbol->SetFootprint( m_fields->at( FOOTPRINT_FIELD ).GetText() );
  570. m_symbol->SetIncludeInBom( !m_cbExcludeFromBom->IsChecked() );
  571. m_symbol->SetIncludeOnBoard( !m_cbExcludeFromBoard->IsChecked() );
  572. // The value, footprint and datasheet fields and exclude from bill of materials setting
  573. // should be kept in sync in multi-unit parts.
  574. if( m_symbol->GetUnitCount() > 1 && m_symbol->IsAnnotated( &GetParent()->GetCurrentSheet() ) )
  575. {
  576. wxString ref = m_symbol->GetRef( &GetParent()->GetCurrentSheet() );
  577. int unit = m_symbol->GetUnit();
  578. LIB_ID libId = m_symbol->GetLibId();
  579. for( SCH_SHEET_PATH& sheet : GetParent()->Schematic().GetSheets() )
  580. {
  581. SCH_SCREEN* screen = sheet.LastScreen();
  582. std::vector<SCH_SYMBOL*> otherUnits;
  583. constexpr bool appendUndo = true;
  584. CollectOtherUnits( ref, unit, libId, sheet, &otherUnits );
  585. for( SCH_SYMBOL* otherUnit : otherUnits )
  586. {
  587. GetParent()->SaveCopyInUndoList( screen, otherUnit, UNDO_REDO::CHANGED,
  588. appendUndo );
  589. otherUnit->SetValue( m_fields->at( VALUE_FIELD ).GetText() );
  590. otherUnit->SetFootprint( m_fields->at( FOOTPRINT_FIELD ).GetText() );
  591. otherUnit->GetField( DATASHEET_FIELD )->SetText( m_fields->at( DATASHEET_FIELD ).GetText() );
  592. otherUnit->SetIncludeInBom( !m_cbExcludeFromBom->IsChecked() );
  593. otherUnit->SetIncludeOnBoard( !m_cbExcludeFromBoard->IsChecked() );
  594. GetParent()->UpdateItem( otherUnit, false, true );
  595. }
  596. }
  597. }
  598. // Update any assignments
  599. if( m_dataModel )
  600. {
  601. for( const SCH_PIN& model_pin : *m_dataModel )
  602. {
  603. // map from the edited copy back to the "real" pin in the symbol.
  604. SCH_PIN* src_pin = m_symbol->GetPin( model_pin.GetLibPin() );
  605. src_pin->SetAlt( model_pin.GetAlt() );
  606. }
  607. }
  608. currentScreen->Append( m_symbol );
  609. GetParent()->TestDanglingEnds();
  610. GetParent()->UpdateItem( m_symbol, false, true );
  611. GetParent()->OnModify();
  612. // This must go after OnModify() so that the connectivity graph will have been updated.
  613. GetParent()->GetToolManager()->PostEvent( EVENTS::SelectedItemsModified );
  614. return true;
  615. }
  616. void DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging( wxGridEvent& event )
  617. {
  618. wxGridCellEditor* editor = m_fieldsGrid->GetCellEditor( event.GetRow(), event.GetCol() );
  619. wxControl* control = editor->GetControl();
  620. if( control && control->GetValidator() && !control->GetValidator()->Validate( control ) )
  621. {
  622. event.Veto();
  623. m_delayedFocusRow = event.GetRow();
  624. m_delayedFocusColumn = event.GetCol();
  625. m_delayedSelection = false;
  626. }
  627. else if( event.GetCol() == FDC_NAME )
  628. {
  629. wxString newName = event.GetString();
  630. for( int i = 0; i < m_fieldsGrid->GetNumberRows(); ++i )
  631. {
  632. if( i == event.GetRow() )
  633. continue;
  634. if( newName.CmpNoCase( m_fieldsGrid->GetCellValue( i, FDC_NAME ) ) == 0 )
  635. {
  636. DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ),
  637. newName ) );
  638. event.Veto();
  639. m_delayedFocusRow = event.GetRow();
  640. m_delayedFocusColumn = event.GetCol();
  641. m_delayedSelection = false;
  642. }
  643. }
  644. }
  645. editor->DecRef();
  646. }
  647. void DIALOG_SYMBOL_PROPERTIES::OnGridEditorShown( wxGridEvent& aEvent )
  648. {
  649. if( aEvent.GetRow() == REFERENCE_FIELD && aEvent.GetCol() == FDC_VALUE )
  650. m_delayedSelection= true;
  651. }
  652. void DIALOG_SYMBOL_PROPERTIES::OnAddField( wxCommandEvent& event )
  653. {
  654. if( !m_fieldsGrid->CommitPendingChanges() )
  655. return;
  656. SCHEMATIC_SETTINGS& settings = m_symbol->Schematic()->Settings();
  657. int fieldID = m_fields->size();
  658. SCH_FIELD newField( wxPoint( 0, 0 ), fieldID, m_symbol,
  659. TEMPLATE_FIELDNAME::GetDefaultFieldName( fieldID ) );
  660. newField.SetTextAngle( m_fields->at( REFERENCE_FIELD ).GetTextAngle() );
  661. newField.SetTextSize( wxSize( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );
  662. m_fields->push_back( newField );
  663. // notify the grid
  664. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  665. m_fieldsGrid->ProcessTableMessage( msg );
  666. m_fieldsGrid->MakeCellVisible( (int) m_fields->size() - 1, 0 );
  667. m_fieldsGrid->SetGridCursor( (int) m_fields->size() - 1, 0 );
  668. m_fieldsGrid->EnableCellEditControl();
  669. m_fieldsGrid->ShowCellEditControl();
  670. OnModify();
  671. }
  672. void DIALOG_SYMBOL_PROPERTIES::OnDeleteField( wxCommandEvent& event )
  673. {
  674. wxArrayInt selectedRows = m_fieldsGrid->GetSelectedRows();
  675. if( selectedRows.empty() && m_fieldsGrid->GetGridCursorRow() >= 0 )
  676. selectedRows.push_back( m_fieldsGrid->GetGridCursorRow() );
  677. if( selectedRows.empty() )
  678. return;
  679. for( int row : selectedRows )
  680. {
  681. if( row < MANDATORY_FIELDS )
  682. {
  683. DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
  684. MANDATORY_FIELDS ) );
  685. return;
  686. }
  687. }
  688. m_fieldsGrid->CommitPendingChanges( true /* quiet mode */ );
  689. // Reverse sort so deleting a row doesn't change the indexes of the other rows.
  690. selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
  691. for( int row : selectedRows )
  692. {
  693. m_fields->erase( m_fields->begin() + row );
  694. // notify the grid
  695. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
  696. m_fieldsGrid->ProcessTableMessage( msg );
  697. if( m_fieldsGrid->GetNumberRows() > 0 )
  698. {
  699. m_fieldsGrid->MakeCellVisible( std::max( 0, row-1 ), m_fieldsGrid->GetGridCursorCol() );
  700. m_fieldsGrid->SetGridCursor( std::max( 0, row-1 ), m_fieldsGrid->GetGridCursorCol() );
  701. }
  702. }
  703. OnModify();
  704. }
  705. void DIALOG_SYMBOL_PROPERTIES::OnMoveUp( wxCommandEvent& event )
  706. {
  707. if( !m_fieldsGrid->CommitPendingChanges() )
  708. return;
  709. int i = m_fieldsGrid->GetGridCursorRow();
  710. if( i > MANDATORY_FIELDS )
  711. {
  712. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  713. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  714. m_fields->insert( m_fields->begin() + i - 1, tmp );
  715. m_fieldsGrid->ForceRefresh();
  716. m_fieldsGrid->SetGridCursor( i - 1, m_fieldsGrid->GetGridCursorCol() );
  717. m_fieldsGrid->MakeCellVisible( m_fieldsGrid->GetGridCursorRow(),
  718. m_fieldsGrid->GetGridCursorCol() );
  719. OnModify();
  720. }
  721. else
  722. {
  723. wxBell();
  724. }
  725. }
  726. void DIALOG_SYMBOL_PROPERTIES::OnMoveDown( wxCommandEvent& event )
  727. {
  728. if( !m_fieldsGrid->CommitPendingChanges() )
  729. return;
  730. int i = m_fieldsGrid->GetGridCursorRow();
  731. if( i >= MANDATORY_FIELDS && i < m_fieldsGrid->GetNumberRows() - 1 )
  732. {
  733. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  734. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  735. m_fields->insert( m_fields->begin() + i + 1, tmp );
  736. m_fieldsGrid->ForceRefresh();
  737. m_fieldsGrid->SetGridCursor( i + 1, m_fieldsGrid->GetGridCursorCol() );
  738. m_fieldsGrid->MakeCellVisible( m_fieldsGrid->GetGridCursorRow(),
  739. m_fieldsGrid->GetGridCursorCol() );
  740. OnModify();
  741. }
  742. else
  743. {
  744. wxBell();
  745. }
  746. }
  747. void DIALOG_SYMBOL_PROPERTIES::OnEditSymbol( wxCommandEvent& )
  748. {
  749. if( TransferDataFromWindow() )
  750. EndQuasiModal( SYMBOL_PROPS_EDIT_SCHEMATIC_SYMBOL );
  751. }
  752. void DIALOG_SYMBOL_PROPERTIES::OnEditLibrarySymbol( wxCommandEvent& )
  753. {
  754. if( TransferDataFromWindow() )
  755. EndQuasiModal( SYMBOL_PROPS_EDIT_LIBRARY_SYMBOL );
  756. }
  757. void DIALOG_SYMBOL_PROPERTIES::OnUpdateSymbol( wxCommandEvent& )
  758. {
  759. if( TransferDataFromWindow() )
  760. EndQuasiModal( SYMBOL_PROPS_WANT_UPDATE_SYMBOL );
  761. }
  762. void DIALOG_SYMBOL_PROPERTIES::OnExchangeSymbol( wxCommandEvent& )
  763. {
  764. if( TransferDataFromWindow() )
  765. EndQuasiModal( SYMBOL_PROPS_WANT_EXCHANGE_SYMBOL );
  766. }
  767. void DIALOG_SYMBOL_PROPERTIES::OnPinTableCellEdited( wxGridEvent& aEvent )
  768. {
  769. int row = aEvent.GetRow();
  770. if( m_pinGrid->GetCellValue( row, COL_ALT_NAME ) == m_dataModel->GetValue( row, COL_BASE_NAME ) )
  771. m_dataModel->SetValue( row, COL_ALT_NAME, wxEmptyString );
  772. // These are just to get the cells refreshed
  773. m_dataModel->SetValue( row, COL_TYPE, m_dataModel->GetValue( row, COL_TYPE ) );
  774. m_dataModel->SetValue( row, COL_SHAPE, m_dataModel->GetValue( row, COL_SHAPE ) );
  775. OnModify();
  776. }
  777. void DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort( wxGridEvent& aEvent )
  778. {
  779. int sortCol = aEvent.GetCol();
  780. bool ascending;
  781. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
  782. // event, and if we ask it will give us pre-event info.
  783. if( m_pinGrid->IsSortingBy( sortCol ) )
  784. // same column; invert ascending
  785. ascending = !m_pinGrid->IsSortOrderAscending();
  786. else
  787. // different column; start with ascending
  788. ascending = true;
  789. m_dataModel->SortRows( sortCol, ascending );
  790. }
  791. void DIALOG_SYMBOL_PROPERTIES::AdjustGridColumns( int aWidth )
  792. {
  793. wxGridUpdateLocker deferRepaintsTillLeavingScope;
  794. m_width = aWidth;
  795. // Account for scroll bars
  796. int fieldsWidth = aWidth - ( m_fieldsGrid->GetSize().x - m_fieldsGrid->GetClientSize().x );
  797. int pinTblWidth = aWidth - ( m_pinGrid->GetSize().x - m_pinGrid->GetClientSize().x );
  798. m_fieldsGrid->AutoSizeColumn( 0 );
  799. int fixedColsWidth = m_fieldsGrid->GetColSize( 0 );
  800. for( int i = 2; i < m_fieldsGrid->GetNumberCols(); i++ )
  801. fixedColsWidth += m_fieldsGrid->GetColSize( i );
  802. int colSize = std::max( fieldsWidth - fixedColsWidth, -1 );
  803. colSize = ( colSize == 0 ) ? -1 : colSize; // don't hide the column!
  804. m_fieldsGrid->SetColSize( 1, colSize );
  805. // Stretch the Base Name and Alternate Assignment columns to fit.
  806. for( int i = 0; i < COL_COUNT; ++i )
  807. {
  808. if( i != COL_BASE_NAME && i != COL_ALT_NAME )
  809. pinTblWidth -= m_pinGrid->GetColSize( i );
  810. }
  811. // Why? I haven't a clue....
  812. pinTblWidth += 22;
  813. m_pinGrid->SetColSize( COL_BASE_NAME, pinTblWidth / 2 );
  814. m_pinGrid->SetColSize( COL_ALT_NAME, pinTblWidth / 2 );
  815. }
  816. void DIALOG_SYMBOL_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
  817. {
  818. wxString shownColumns = m_fieldsGrid->GetShownColumns();
  819. if( shownColumns != m_shownColumns )
  820. {
  821. m_shownColumns = shownColumns;
  822. if( !m_fieldsGrid->IsCellEditControlShown() )
  823. AdjustGridColumns( m_fieldsGrid->GetRect().GetWidth() );
  824. }
  825. // Handle a delayed focus
  826. if( m_delayedFocusRow >= 0 )
  827. {
  828. m_fieldsGrid->SetFocus();
  829. m_fieldsGrid->MakeCellVisible( m_delayedFocusRow, m_delayedFocusColumn );
  830. m_fieldsGrid->SetGridCursor( m_delayedFocusRow, m_delayedFocusColumn );
  831. m_fieldsGrid->EnableCellEditControl( true );
  832. m_fieldsGrid->ShowCellEditControl();
  833. m_delayedFocusRow = -1;
  834. m_delayedFocusColumn = -1;
  835. }
  836. // Handle a delayed selection
  837. if( m_delayedSelection )
  838. {
  839. wxGridCellEditor* cellEditor = m_fieldsGrid->GetCellEditor( REFERENCE_FIELD, FDC_VALUE );
  840. if( wxTextEntry* txt = dynamic_cast<wxTextEntry*>( cellEditor->GetControl() ) )
  841. KIUI::SelectReferenceNumber( txt );
  842. cellEditor->DecRef(); // we're done; must release
  843. m_delayedSelection = false;
  844. }
  845. }
  846. void DIALOG_SYMBOL_PROPERTIES::OnSizeGrid( wxSizeEvent& event )
  847. {
  848. int new_size = event.GetSize().GetX();
  849. if( m_width != new_size )
  850. {
  851. AdjustGridColumns( new_size );
  852. Layout();
  853. }
  854. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  855. event.Skip();
  856. }
  857. void DIALOG_SYMBOL_PROPERTIES::OnInitDlg( wxInitDialogEvent& event )
  858. {
  859. TransferDataToWindow();
  860. // Now all widgets have the size fixed, call FinishDialogSettings
  861. finishDialogSettings();
  862. }
  863. void DIALOG_SYMBOL_PROPERTIES::OnCheckBox( wxCommandEvent& event )
  864. {
  865. OnModify();
  866. }
  867. void DIALOG_SYMBOL_PROPERTIES::OnChoice( wxCommandEvent& event )
  868. {
  869. OnModify();
  870. }