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.

1004 lines
32 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2024 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_lib_symbol_properties.h"
  24. #include <pgm_base.h>
  25. #include <eeschema_settings.h>
  26. #include <bitmaps.h>
  27. #include <confirm.h>
  28. #include <dialogs/dialog_text_entry.h>
  29. #include <kiway.h>
  30. #include <symbol_edit_frame.h>
  31. #include <lib_symbol_library_manager.h>
  32. #include <math/util.h> // for KiROUND
  33. #include <sch_symbol.h>
  34. #include <kiplatform/ui.h>
  35. #include <widgets/grid_text_button_helpers.h>
  36. #include <widgets/wx_grid.h>
  37. #include <widgets/std_bitmap_button.h>
  38. #include <string_utils.h>
  39. #include <project_sch.h>
  40. #include <refdes_utils.h>
  41. #include <dialog_sim_model.h>
  42. #include <panel_embedded_files.h>
  43. #include <settings/settings_manager.h>
  44. #include <symbol_editor_settings.h>
  45. #include <widgets/listbox_tricks.h>
  46. #include <wx/clipbrd.h>
  47. #include <wx/msgdlg.h>
  48. int DIALOG_LIB_SYMBOL_PROPERTIES::m_lastOpenedPage = 0;
  49. DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT DIALOG_LIB_SYMBOL_PROPERTIES::m_lastLayout =
  50. DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::NONE;
  51. DIALOG_LIB_SYMBOL_PROPERTIES::DIALOG_LIB_SYMBOL_PROPERTIES( SYMBOL_EDIT_FRAME* aParent,
  52. LIB_SYMBOL* aLibEntry ) :
  53. DIALOG_LIB_SYMBOL_PROPERTIES_BASE( aParent ),
  54. m_Parent( aParent ),
  55. m_libEntry( aLibEntry ),
  56. m_pinNameOffset( aParent, m_nameOffsetLabel, m_nameOffsetCtrl, m_nameOffsetUnits, true ),
  57. m_delayedFocusCtrl( nullptr ),
  58. m_delayedFocusGrid( nullptr ),
  59. m_delayedFocusRow( -1 ),
  60. m_delayedFocusColumn( -1 ),
  61. m_delayedFocusPage( -1 ),
  62. m_fpFilterTricks( std::make_unique<LISTBOX_TRICKS>( *this, *m_FootprintFilterListBox ) )
  63. {
  64. m_embeddedFiles = new PANEL_EMBEDDED_FILES( m_NoteBook, m_libEntry );
  65. m_NoteBook->AddPage( m_embeddedFiles, _( "Embedded Files" ) );
  66. // Give a bit more room for combobox editors
  67. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  68. m_fields = new FIELDS_GRID_TABLE( this, aParent, m_grid, m_libEntry );
  69. m_grid->SetTable( m_fields );
  70. m_grid->PushEventHandler( new FIELDS_GRID_TRICKS( m_grid, this, aLibEntry,
  71. [&]( wxCommandEvent& aEvent )
  72. {
  73. OnAddField( aEvent );
  74. } ) );
  75. m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
  76. // Show/hide columns according to the user's preference
  77. SYMBOL_EDITOR_SETTINGS* cfg = m_Parent->GetSettings();
  78. m_grid->ShowHideColumns( cfg->m_EditSymbolVisibleColumns );
  79. wxGridCellAttr* attr = new wxGridCellAttr;
  80. attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ), aLibEntry ) );
  81. m_grid->SetAttr( DATASHEET_FIELD, FDC_VALUE, attr );
  82. m_SymbolNameCtrl->SetValidator( FIELD_VALIDATOR( VALUE_FIELD ) );
  83. // Configure button logos
  84. m_bpAdd->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  85. m_bpDelete->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  86. m_bpMoveUp->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) );
  87. m_bpMoveDown->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) );
  88. m_addFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  89. m_deleteFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  90. m_editFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
  91. SetupStandardButtons();
  92. if( aParent->IsSymbolFromLegacyLibrary() && !aParent->IsSymbolFromSchematic() )
  93. {
  94. m_stdSizerButtonCancel->SetDefault();
  95. m_stdSizerButtonOK->SetLabel( _( "Read Only" ) );
  96. m_stdSizerButtonOK->Enable( false );
  97. }
  98. // wxFormBuilder doesn't include this event...
  99. m_grid->Connect( wxEVT_GRID_CELL_CHANGING,
  100. wxGridEventHandler( DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanging ),
  101. nullptr, this );
  102. // Forward the delete button to the tricks
  103. m_deleteFilterButton->Bind( wxEVT_BUTTON,
  104. [&]( wxCommandEvent& aEvent )
  105. {
  106. wxCommandEvent cmdEvent( EDA_EVT_LISTBOX_DELETE );
  107. m_fpFilterTricks->ProcessEvent( cmdEvent );
  108. } );
  109. // When the filter tricks modifies something, update outselves
  110. m_FootprintFilterListBox->Bind( EDA_EVT_LISTBOX_CHANGED,
  111. [&]( wxCommandEvent& aEvent )
  112. {
  113. OnModify();
  114. } );
  115. if( m_lastLayout != DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::NONE )
  116. {
  117. if( ( m_lastLayout == DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::ALIAS
  118. && aLibEntry->IsRoot() )
  119. || ( m_lastLayout == DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::PARENT
  120. && aLibEntry->IsAlias() ) )
  121. {
  122. resetSize();
  123. }
  124. }
  125. m_lastLayout = ( aLibEntry->IsAlias() ) ? DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::ALIAS
  126. : DIALOG_LIB_SYMBOL_PROPERTIES::LAST_LAYOUT::PARENT;
  127. m_grid->GetParent()->Layout();
  128. syncControlStates( m_libEntry->IsAlias() );
  129. Layout();
  130. finishDialogSettings();
  131. }
  132. DIALOG_LIB_SYMBOL_PROPERTIES::~DIALOG_LIB_SYMBOL_PROPERTIES()
  133. {
  134. m_lastOpenedPage = m_NoteBook->GetSelection( );
  135. if( SYMBOL_EDITOR_SETTINGS* cfg = m_Parent->GetSettings() )
  136. cfg->m_EditSymbolVisibleColumns = m_grid->GetShownColumnsAsString();
  137. // Prevents crash bug in wxGrid's d'tor
  138. m_grid->DestroyTable( m_fields );
  139. m_grid->Disconnect( wxEVT_GRID_CELL_CHANGING,
  140. wxGridEventHandler( DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanging ),
  141. nullptr, this );
  142. // Delete the GRID_TRICKS.
  143. m_grid->PopEventHandler( true );
  144. }
  145. bool DIALOG_LIB_SYMBOL_PROPERTIES::TransferDataToWindow()
  146. {
  147. if( !wxDialog::TransferDataToWindow() )
  148. return false;
  149. // Push a copy of each field into m_updateFields
  150. m_libEntry->CopyFields( *m_fields );
  151. std::set<wxString> defined;
  152. for( SCH_FIELD& field : *m_fields )
  153. defined.insert( field.GetName() );
  154. // Add in any template fieldnames not yet defined:
  155. // Read global fieldname templates
  156. SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
  157. EESCHEMA_SETTINGS* cfg = mgr.GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" );
  158. if( cfg )
  159. {
  160. TEMPLATES templateMgr;
  161. if( !cfg->m_Drawing.field_names.IsEmpty() )
  162. templateMgr.AddTemplateFieldNames( cfg->m_Drawing.field_names );
  163. for( const TEMPLATE_FIELDNAME& templateFieldname : templateMgr.GetTemplateFieldNames() )
  164. {
  165. if( defined.count( templateFieldname.m_Name ) <= 0 )
  166. {
  167. SCH_FIELD field( VECTOR2I( 0, 0 ), -1, m_libEntry, templateFieldname.m_Name );
  168. field.SetVisible( templateFieldname.m_Visible );
  169. m_fields->push_back( field );
  170. m_addedTemplateFields.insert( templateFieldname.m_Name );
  171. }
  172. }
  173. }
  174. // The Y axis for components in lib is from bottom to top while the screen axis is top
  175. // to bottom: we must change the y coord sign for editing
  176. for( size_t i = 0; i < m_fields->size(); ++i )
  177. {
  178. VECTOR2I pos = m_fields->at( i ).GetPosition();
  179. pos.y = -pos.y;
  180. m_fields->at( i ).SetPosition( pos );
  181. }
  182. // notify the grid
  183. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_fields->GetNumberRows() );
  184. m_grid->ProcessTableMessage( msg );
  185. adjustGridColumns();
  186. m_SymbolNameCtrl->ChangeValue( UnescapeString( m_libEntry->GetName() ) );
  187. m_KeywordCtrl->ChangeValue( m_libEntry->GetKeyWords() );
  188. m_SelNumberOfUnits->SetValue( m_libEntry->GetUnitCount() );
  189. m_OptionPartsInterchangeable->SetValue( !m_libEntry->UnitsLocked() ||
  190. m_libEntry->GetUnitCount() == 1 );
  191. // If a symbol contains no body-style-specific pins or graphic items,
  192. // symbol->HasAlternateBodyStyle() will return false.
  193. // But when editing a symbol with DeMorgan option set, we don't want to keep turning it off
  194. // just because there aren't any body-style-specific items yet, so we force it to on if the
  195. // parent frame has it enabled.
  196. m_hasAlternateBodyStyles->SetValue( m_Parent->GetShowDeMorgan() );
  197. m_OptionPower->SetValue( m_libEntry->IsPower() );
  198. if( m_libEntry->IsPower() )
  199. m_spiceFieldsButton->Hide();
  200. m_excludeFromSimCheckBox->SetValue( m_libEntry->GetExcludedFromSim() );
  201. m_excludeFromBomCheckBox->SetValue( m_libEntry->GetExcludedFromBOM() );
  202. m_excludeFromBoardCheckBox->SetValue( m_libEntry->GetExcludedFromBoard() );
  203. m_ShowPinNumButt->SetValue( m_libEntry->GetShowPinNumbers() );
  204. m_ShowPinNameButt->SetValue( m_libEntry->GetShowPinNames() );
  205. m_PinsNameInsideButt->SetValue( m_libEntry->GetPinNameOffset() != 0 );
  206. m_pinNameOffset.ChangeValue( m_libEntry->GetPinNameOffset() );
  207. wxArrayString tmp = m_libEntry->GetFPFilters();
  208. m_FootprintFilterListBox->Append( tmp );
  209. // Populate the list of root parts for inherited objects.
  210. if( m_libEntry->IsAlias() )
  211. {
  212. wxArrayString symbolNames;
  213. wxString libName = m_Parent->GetCurLib();
  214. // Someone forgot to set the current library in the editor frame window.
  215. wxCHECK( !libName.empty(), false );
  216. m_Parent->GetLibManager().GetSymbolNames( libName, symbolNames );
  217. // Do allow an inherited symbol to be derived from itself.
  218. symbolNames.Remove( m_libEntry->GetName() );
  219. m_inheritanceSelectCombo->Append( symbolNames );
  220. if( LIB_SYMBOL_SPTR rootSymbol = m_libEntry->GetParent().lock() )
  221. {
  222. wxString parentName = UnescapeString( rootSymbol->GetName() );
  223. int selection = m_inheritanceSelectCombo->FindString( parentName );
  224. if( selection == wxNOT_FOUND )
  225. return false;
  226. m_inheritanceSelectCombo->SetSelection( selection );
  227. }
  228. m_lastOpenedPage = 0;
  229. }
  230. m_NoteBook->SetSelection( (unsigned) m_lastOpenedPage );
  231. m_embeddedFiles->TransferDataToWindow();
  232. return true;
  233. }
  234. bool DIALOG_LIB_SYMBOL_PROPERTIES::Validate()
  235. {
  236. if( !m_grid->CommitPendingChanges() )
  237. return false;
  238. // Alias symbol reference can be empty because it inherits from the parent symbol.
  239. if( m_libEntry->IsRoot()
  240. && UTIL::GetRefDesPrefix( m_fields->at( REFERENCE_FIELD ).GetText() ).IsEmpty() )
  241. {
  242. if( m_NoteBook->GetSelection() != 0 )
  243. m_NoteBook->SetSelection( 0 );
  244. m_delayedErrorMessage = _( "References must start with a letter." );
  245. m_delayedFocusGrid = m_grid;
  246. m_delayedFocusColumn = FDC_VALUE;
  247. m_delayedFocusRow = REFERENCE_FIELD;
  248. m_delayedFocusPage = 0;
  249. return false;
  250. }
  251. // Check for missing field names.
  252. for( int ii = MANDATORY_FIELDS; ii < (int) m_fields->size(); ++ii )
  253. {
  254. SCH_FIELD& field = m_fields->at( ii );
  255. wxString fieldName = field.GetName( false );
  256. if( fieldName.IsEmpty() && !field.GetText().IsEmpty() )
  257. {
  258. if( m_NoteBook->GetSelection() != 0 )
  259. m_NoteBook->SetSelection( 0 );
  260. m_delayedErrorMessage = _( "Fields must have a name." );
  261. m_delayedFocusGrid = m_grid;
  262. m_delayedFocusColumn = FDC_NAME;
  263. m_delayedFocusRow = ii;
  264. m_delayedFocusPage = 0;
  265. return false;
  266. }
  267. }
  268. // Verify that the parent name is set if the symbol is inherited
  269. if( m_libEntry->IsAlias() )
  270. {
  271. wxString parentName = m_inheritanceSelectCombo->GetValue();
  272. if( parentName.IsEmpty() )
  273. {
  274. m_delayedErrorMessage = _( "Derived symbol must have a parent selected" );
  275. return false;
  276. }
  277. }
  278. /*
  279. * Confirm destructive actions.
  280. */
  281. if( m_SelNumberOfUnits->GetValue() < m_libEntry->GetUnitCount() )
  282. {
  283. if( !IsOK( this, _( "Delete extra units from symbol?" ) ) )
  284. return false;
  285. }
  286. if( !m_hasAlternateBodyStyles->GetValue() && m_libEntry->HasAlternateBodyStyle() )
  287. {
  288. if( !IsOK( this, _( "Delete alternate body style (De Morgan) from symbol?" ) ) )
  289. return false;
  290. }
  291. return true;
  292. }
  293. bool DIALOG_LIB_SYMBOL_PROPERTIES::TransferDataFromWindow()
  294. {
  295. if( !wxDialog::TransferDataFromWindow() )
  296. return false;
  297. if( !m_grid->CommitPendingChanges() )
  298. return false;
  299. wxString newName = EscapeString( m_SymbolNameCtrl->GetValue(), CTX_LIBID );
  300. wxString oldName = m_libEntry->GetName();
  301. if( newName.IsEmpty() )
  302. {
  303. wxMessageBox( _( "Symbol must have a name." ) );
  304. return false;
  305. }
  306. if( oldName != newName )
  307. {
  308. wxString libName = m_Parent->GetCurLib();
  309. if( m_Parent->GetLibManager().SymbolExists( newName, libName ) )
  310. {
  311. wxString msg;
  312. msg.Printf( _( "Symbol name '%s' already in use in library '%s'." ),
  313. UnescapeString( newName ),
  314. libName );
  315. DisplayErrorMessage( this, msg );
  316. return false;
  317. }
  318. m_Parent->SaveCopyInUndoList( _( "Edit Symbol Properties" ), m_libEntry,
  319. UNDO_REDO::LIB_RENAME );
  320. }
  321. else
  322. {
  323. m_Parent->SaveCopyInUndoList( _( "Edit Symbol Properties" ), m_libEntry );
  324. }
  325. // The Y axis for components in lib is from bottom to top while the screen axis is top
  326. // to bottom: we must change the y coord sign when writing back to the library
  327. for( int ii = 0; ii < (int) m_fields->size(); ++ii )
  328. {
  329. VECTOR2I pos = m_fields->at( ii ).GetPosition();
  330. pos.y = -pos.y;
  331. m_fields->at( ii ).SetPosition( pos );
  332. m_fields->at( ii ).SetId( ii );
  333. }
  334. for( int ii = m_fields->GetNumberRows() - 1; ii >= MANDATORY_FIELDS; ii-- )
  335. {
  336. SCH_FIELD& field = m_fields->at( ii );
  337. const wxString& fieldName = field.GetCanonicalName();
  338. if( field.GetText().IsEmpty() )
  339. {
  340. if( fieldName.IsEmpty() || m_addedTemplateFields.contains( fieldName ) )
  341. m_fields->erase( m_fields->begin() + ii );
  342. }
  343. else if( fieldName.IsEmpty() )
  344. {
  345. field.SetName( _( "untitled" ) );
  346. }
  347. }
  348. m_libEntry->SetFields( *m_fields );
  349. // Update the parent for inherited symbols
  350. if( m_libEntry->IsAlias() )
  351. {
  352. wxString parentName = EscapeString( m_inheritanceSelectCombo->GetValue(), CTX_LIBID );
  353. // The parentName was verified to be non-empty in the Validator
  354. wxString libName = m_Parent->GetCurLib();
  355. // Get the parent from the libManager based on the name set in the inheritance combo box.
  356. LIB_SYMBOL* newParent = m_Parent->GetLibManager().GetAlias( parentName, libName );
  357. // Verify that the requested parent exists
  358. wxCHECK( newParent, false );
  359. m_libEntry->SetParent( newParent );
  360. }
  361. m_libEntry->SetName( newName );
  362. m_libEntry->SetKeyWords( m_KeywordCtrl->GetValue() );
  363. m_libEntry->SetUnitCount( m_SelNumberOfUnits->GetValue() );
  364. m_libEntry->LockUnits( m_libEntry->GetUnitCount() > 1 &&
  365. !m_OptionPartsInterchangeable->GetValue() );
  366. m_libEntry->SetHasAlternateBodyStyle( m_hasAlternateBodyStyles->GetValue() );
  367. m_Parent->SetShowDeMorgan( m_hasAlternateBodyStyles->GetValue() );
  368. if( m_OptionPower->GetValue() )
  369. {
  370. m_libEntry->SetPower();
  371. // Power symbols must have value matching name for now
  372. m_libEntry->GetValueField().SetText( newName );
  373. }
  374. else
  375. {
  376. m_libEntry->SetNormal();
  377. }
  378. m_libEntry->SetExcludedFromSim( m_excludeFromSimCheckBox->GetValue() );
  379. m_libEntry->SetExcludedFromBOM( m_excludeFromBomCheckBox->GetValue() );
  380. m_libEntry->SetExcludedFromBoard( m_excludeFromBoardCheckBox->GetValue() );
  381. m_libEntry->SetShowPinNumbers( m_ShowPinNumButt->GetValue() );
  382. m_libEntry->SetShowPinNames( m_ShowPinNameButt->GetValue() );
  383. if( m_PinsNameInsideButt->GetValue() )
  384. {
  385. int offset = KiROUND( (double) m_pinNameOffset.GetValue() );
  386. // We interpret an offset of 0 as "outside", so make sure it's non-zero
  387. m_libEntry->SetPinNameOffset( offset == 0 ? 20 : offset );
  388. }
  389. else
  390. {
  391. m_libEntry->SetPinNameOffset( 0 ); // pin text outside the body (name is on the pin)
  392. }
  393. m_libEntry->SetFPFilters( m_FootprintFilterListBox->GetStrings());
  394. m_Parent->UpdateAfterSymbolProperties( &oldName );
  395. // It's possible that the symbol being edited has no pins, in which case there may be no
  396. // alternate body style objects causing #LIB_SYMBOL::HasAlternateBodyStyle() to always return
  397. // false. This allows the user to edit the alternate body style just in case this condition
  398. // occurs.
  399. m_Parent->SetShowDeMorgan( m_hasAlternateBodyStyles->GetValue() );
  400. m_embeddedFiles->TransferDataFromWindow();
  401. return true;
  402. }
  403. void DIALOG_LIB_SYMBOL_PROPERTIES::OnGridCellChanging( wxGridEvent& event )
  404. {
  405. wxGridCellEditor* editor = m_grid->GetCellEditor( event.GetRow(), event.GetCol() );
  406. wxControl* control = editor->GetControl();
  407. if( control && control->GetValidator() && !control->GetValidator()->Validate( control ) )
  408. {
  409. event.Veto();
  410. m_delayedFocusGrid = m_grid;
  411. m_delayedFocusRow = event.GetRow();
  412. m_delayedFocusColumn = event.GetCol();
  413. m_delayedFocusPage = 0;
  414. }
  415. else if( event.GetCol() == FDC_NAME )
  416. {
  417. wxString newName = event.GetString();
  418. for( int i = 0; i < m_grid->GetNumberRows(); ++i )
  419. {
  420. if( i == event.GetRow() )
  421. continue;
  422. if( newName.CmpNoCase( m_grid->GetCellValue( i, FDC_NAME ) ) == 0 )
  423. {
  424. DisplayError( this, wxString::Format( _( "The name '%s' is already in use." ),
  425. newName ) );
  426. event.Veto();
  427. m_delayedFocusRow = event.GetRow();
  428. m_delayedFocusColumn = event.GetCol();
  429. }
  430. }
  431. }
  432. editor->DecRef();
  433. }
  434. void DIALOG_LIB_SYMBOL_PROPERTIES::OnSymbolNameText( wxCommandEvent& event )
  435. {
  436. if( m_OptionPower->IsChecked() )
  437. m_grid->SetCellValue( VALUE_FIELD, FDC_VALUE, m_SymbolNameCtrl->GetValue() );
  438. OnModify();
  439. }
  440. void DIALOG_LIB_SYMBOL_PROPERTIES::OnSymbolNameKillFocus( wxFocusEvent& event )
  441. {
  442. if( !m_delayedFocusCtrl )
  443. {
  444. // If the validation fails and we throw up a dialog then GTK will give us another
  445. // KillFocus event and we end up in infinite recursion. So we use m_delayedFocusCtrl
  446. // as a re-entrancy block and then clear it again if validation passes.
  447. m_delayedFocusCtrl = m_SymbolNameCtrl;
  448. m_delayedFocusPage = 0;
  449. if( m_SymbolNameCtrl->GetValidator()->Validate( m_SymbolNameCtrl ) )
  450. {
  451. m_delayedFocusCtrl = nullptr;
  452. m_delayedFocusPage = -1;
  453. }
  454. }
  455. event.Skip();
  456. }
  457. void DIALOG_LIB_SYMBOL_PROPERTIES::OnAddField( wxCommandEvent& event )
  458. {
  459. if( !m_grid->CommitPendingChanges() )
  460. return;
  461. SYMBOL_EDITOR_SETTINGS* settings = m_Parent->GetSettings();
  462. int fieldID = (int) m_fields->size();
  463. SCH_FIELD newField( m_libEntry, fieldID );
  464. newField.SetTextSize( VECTOR2I( schIUScale.MilsToIU( settings->m_Defaults.text_size ),
  465. schIUScale.MilsToIU( settings->m_Defaults.text_size ) ) );
  466. m_fields->push_back( newField );
  467. // notify the grid
  468. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  469. m_grid->ProcessTableMessage( msg );
  470. m_grid->MakeCellVisible( (int) m_fields->size() - 1, 0 );
  471. m_grid->SetGridCursor( (int) m_fields->size() - 1, 0 );
  472. m_grid->EnableCellEditControl();
  473. m_grid->ShowCellEditControl();
  474. OnModify();
  475. }
  476. void DIALOG_LIB_SYMBOL_PROPERTIES::OnDeleteField( wxCommandEvent& event )
  477. {
  478. wxArrayInt selectedRows = m_grid->GetSelectedRows();
  479. if( selectedRows.empty() && m_grid->GetGridCursorRow() >= 0 )
  480. selectedRows.push_back( m_grid->GetGridCursorRow() );
  481. if( selectedRows.empty() )
  482. return;
  483. for( int row : selectedRows )
  484. {
  485. if( row < MANDATORY_FIELDS )
  486. {
  487. DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
  488. MANDATORY_FIELDS ) );
  489. return;
  490. }
  491. }
  492. m_grid->CommitPendingChanges( true /* quiet mode */ );
  493. m_grid->ClearSelection();
  494. // Reverse sort so deleting a row doesn't change the indexes of the other rows.
  495. selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
  496. for( int row : selectedRows )
  497. {
  498. m_fields->erase( m_fields->begin() + row );
  499. // notify the grid
  500. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
  501. m_grid->ProcessTableMessage( msg );
  502. if( m_grid->GetNumberRows() > 0 )
  503. {
  504. m_grid->MakeCellVisible( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  505. m_grid->SetGridCursor( std::max( 0, row-1 ), m_grid->GetGridCursorCol() );
  506. }
  507. }
  508. OnModify();
  509. }
  510. void DIALOG_LIB_SYMBOL_PROPERTIES::OnMoveUp( wxCommandEvent& event )
  511. {
  512. if( !m_grid->CommitPendingChanges() )
  513. return;
  514. int i = m_grid->GetGridCursorRow();
  515. if( i > MANDATORY_FIELDS )
  516. {
  517. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  518. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  519. m_fields->insert( m_fields->begin() + i - 1, tmp );
  520. m_grid->ForceRefresh();
  521. m_grid->SetGridCursor( i - 1, m_grid->GetGridCursorCol() );
  522. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  523. OnModify();
  524. }
  525. else
  526. {
  527. wxBell();
  528. }
  529. }
  530. void DIALOG_LIB_SYMBOL_PROPERTIES::OnMoveDown( wxCommandEvent& event )
  531. {
  532. if( !m_grid->CommitPendingChanges() )
  533. return;
  534. int i = m_grid->GetGridCursorRow();
  535. if( i >= MANDATORY_FIELDS && i + 1 < m_fields->GetNumberRows() )
  536. {
  537. SCH_FIELD tmp = m_fields->at( (unsigned) i );
  538. m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
  539. m_fields->insert( m_fields->begin() + i + 1, tmp );
  540. m_grid->ForceRefresh();
  541. m_grid->SetGridCursor( i + 1, m_grid->GetGridCursorCol() );
  542. m_grid->MakeCellVisible( m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol() );
  543. OnModify();
  544. }
  545. else
  546. {
  547. wxBell();
  548. }
  549. }
  550. void DIALOG_LIB_SYMBOL_PROPERTIES::OnEditSpiceModel( wxCommandEvent& event )
  551. {
  552. if( !m_grid->CommitPendingChanges() )
  553. return;
  554. std::vector<SCH_FIELD> fields;
  555. for( const SCH_FIELD& field : *m_fields )
  556. fields.emplace_back( field );
  557. DIALOG_SIM_MODEL dialog( this, m_parentFrame, *m_libEntry, fields );
  558. if( dialog.ShowModal() != wxID_OK )
  559. return;
  560. // Add in any new fields
  561. for( const SCH_FIELD& editedField : fields )
  562. {
  563. bool found = false;
  564. for( SCH_FIELD& existingField : *m_fields )
  565. {
  566. if( existingField.GetName() == editedField.GetName() )
  567. {
  568. found = true;
  569. existingField.SetText( editedField.GetText() );
  570. break;
  571. }
  572. }
  573. if( !found )
  574. {
  575. m_fields->emplace_back( editedField );
  576. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  577. m_grid->ProcessTableMessage( msg );
  578. }
  579. }
  580. // Remove any deleted fields
  581. for( int ii = (int) m_fields->size() - 1; ii >= 0; --ii )
  582. {
  583. SCH_FIELD& existingField = m_fields->at( ii );
  584. bool found = false;
  585. for( SCH_FIELD& editedField : fields )
  586. {
  587. if( editedField.GetName() == existingField.GetName() )
  588. {
  589. found = true;
  590. break;
  591. }
  592. }
  593. if( !found )
  594. {
  595. m_fields->erase( m_fields->begin() + ii );
  596. wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, ii, 1 );
  597. m_grid->ProcessTableMessage( msg );
  598. }
  599. }
  600. OnModify();
  601. m_grid->ForceRefresh();
  602. }
  603. void DIALOG_LIB_SYMBOL_PROPERTIES::OnFpFilterDClick( wxMouseEvent& event )
  604. {
  605. int idx = m_FootprintFilterListBox->HitTest( event.GetPosition() );
  606. wxCommandEvent dummy;
  607. if( idx >= 0 )
  608. OnEditFootprintFilter( dummy );
  609. else
  610. OnAddFootprintFilter( dummy );
  611. }
  612. void DIALOG_LIB_SYMBOL_PROPERTIES::OnCancelButtonClick( wxCommandEvent& event )
  613. {
  614. // Running the Footprint Browser gums up the works and causes the automatic cancel
  615. // stuff to no longer work. So we do it here ourselves.
  616. EndQuasiModal( wxID_CANCEL );
  617. }
  618. void DIALOG_LIB_SYMBOL_PROPERTIES::OnAddFootprintFilter( wxCommandEvent& event )
  619. {
  620. wxString filterLine;
  621. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Add Footprint Filter" ), filterLine );
  622. if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue().IsEmpty() )
  623. return;
  624. filterLine = dlg.GetValue();
  625. filterLine.Replace( wxT( " " ), wxT( "_" ) );
  626. // duplicate filters do no harm, so don't be a nanny.
  627. m_FootprintFilterListBox->Append( filterLine );
  628. m_FootprintFilterListBox->SetSelection( (int) m_FootprintFilterListBox->GetCount() - 1 );
  629. OnModify();
  630. }
  631. void DIALOG_LIB_SYMBOL_PROPERTIES::OnEditFootprintFilter( wxCommandEvent& event )
  632. {
  633. wxArrayInt selections;
  634. int n = m_FootprintFilterListBox->GetSelections( selections );
  635. if( n > 0 )
  636. {
  637. // Just edit the first one
  638. int idx = selections[0];
  639. wxString filter = m_FootprintFilterListBox->GetString( idx );
  640. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Edit Footprint Filter" ), filter );
  641. if( dlg.ShowModal() == wxID_OK && !dlg.GetValue().IsEmpty() )
  642. {
  643. m_FootprintFilterListBox->SetString( (unsigned) idx, dlg.GetValue() );
  644. OnModify();
  645. }
  646. }
  647. }
  648. void DIALOG_LIB_SYMBOL_PROPERTIES::adjustGridColumns()
  649. {
  650. // Account for scroll bars
  651. int width = KIPLATFORM::UI::GetUnobscuredSize( m_grid ).x;
  652. m_grid->AutoSizeColumn( FDC_NAME );
  653. m_grid->SetColSize( FDC_NAME, std::max( 72, m_grid->GetColSize( FDC_NAME ) ) );
  654. int fixedColsWidth = m_grid->GetColSize( FDC_NAME );
  655. for( int i = 2; i < m_grid->GetNumberCols(); i++ )
  656. fixedColsWidth += m_grid->GetColSize( i );
  657. m_grid->SetColSize( FDC_VALUE, std::max( 120, width - fixedColsWidth ) );
  658. }
  659. void DIALOG_LIB_SYMBOL_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
  660. {
  661. m_OptionPartsInterchangeable->Enable( m_SelNumberOfUnits->GetValue() > 1 );
  662. m_pinNameOffset.Enable( m_PinsNameInsideButt->GetValue() );
  663. if( m_grid->IsCellEditControlShown() )
  664. {
  665. int row = m_grid->GetGridCursorRow();
  666. int col = m_grid->GetGridCursorCol();
  667. if( row == VALUE_FIELD && col == FDC_VALUE && m_OptionPower->IsChecked() )
  668. {
  669. wxGridCellEditor* editor = m_grid->GetCellEditor( row, col );
  670. m_SymbolNameCtrl->ChangeValue( editor->GetValue() );
  671. editor->DecRef();
  672. }
  673. }
  674. // Handle shown columns changes
  675. std::bitset<64> shownColumns = m_grid->GetShownColumns();
  676. if( shownColumns != m_shownColumns )
  677. {
  678. m_shownColumns = shownColumns;
  679. if( !m_grid->IsCellEditControlShown() )
  680. adjustGridColumns();
  681. }
  682. // Handle a delayed focus. The delay allows us to:
  683. // a) change focus when the error was triggered from within a killFocus handler
  684. // b) show the correct notebook page in the background before the error dialog comes up
  685. // when triggered from an OK or a notebook page change
  686. if( m_delayedFocusPage >= 0 && m_NoteBook->GetSelection() != m_delayedFocusPage )
  687. {
  688. m_NoteBook->ChangeSelection( (unsigned) m_delayedFocusPage );
  689. m_delayedFocusPage = -1;
  690. }
  691. if( !m_delayedErrorMessage.IsEmpty() )
  692. {
  693. // We will re-enter this routine when the error dialog is displayed, so make
  694. // sure we don't keep putting up more dialogs.
  695. wxString msg = m_delayedErrorMessage;
  696. m_delayedErrorMessage = wxEmptyString;
  697. // Do not use DisplayErrorMessage(); it screws up window order on Mac
  698. DisplayError( nullptr, msg );
  699. }
  700. if( m_delayedFocusCtrl )
  701. {
  702. m_delayedFocusCtrl->SetFocus();
  703. if( auto textEntry = dynamic_cast<wxTextEntry*>( m_delayedFocusCtrl ) )
  704. textEntry->SelectAll();
  705. m_delayedFocusCtrl = nullptr;
  706. }
  707. else if( m_delayedFocusGrid )
  708. {
  709. m_delayedFocusGrid->SetFocus();
  710. m_delayedFocusGrid->MakeCellVisible( m_delayedFocusRow, m_delayedFocusColumn );
  711. m_delayedFocusGrid->SetGridCursor( m_delayedFocusRow, m_delayedFocusColumn );
  712. m_delayedFocusGrid->EnableCellEditControl( true );
  713. m_delayedFocusGrid->ShowCellEditControl();
  714. m_delayedFocusGrid = nullptr;
  715. m_delayedFocusRow = -1;
  716. m_delayedFocusColumn = -1;
  717. }
  718. }
  719. void DIALOG_LIB_SYMBOL_PROPERTIES::OnSizeGrid( wxSizeEvent& event )
  720. {
  721. auto new_size = event.GetSize();
  722. if( new_size != m_size )
  723. {
  724. m_size = new_size;
  725. adjustGridColumns();
  726. }
  727. // Always propagate a wxSizeEvent:
  728. event.Skip();
  729. }
  730. void DIALOG_LIB_SYMBOL_PROPERTIES::syncControlStates( bool aIsAlias )
  731. {
  732. bSizerLowerBasicPanel->Show( !aIsAlias );
  733. m_inheritanceSelectCombo->Enable( aIsAlias );
  734. m_inheritsStaticText->Enable( aIsAlias );
  735. m_grid->ForceRefresh();
  736. }
  737. void DIALOG_LIB_SYMBOL_PROPERTIES::onPowerCheckBox( wxCommandEvent& aEvent )
  738. {
  739. if( m_OptionPower->IsChecked() )
  740. {
  741. m_excludeFromSimCheckBox->SetValue( true );
  742. m_excludeFromBomCheckBox->SetValue( true );
  743. m_excludeFromBoardCheckBox->SetValue( true );
  744. m_excludeFromBomCheckBox->Enable( false );
  745. m_excludeFromBoardCheckBox->Enable( false );
  746. m_excludeFromSimCheckBox->Enable( false );
  747. m_spiceFieldsButton->Show( false );
  748. }
  749. else
  750. {
  751. m_excludeFromBomCheckBox->Enable( true );
  752. m_excludeFromBoardCheckBox->Enable( true );
  753. m_excludeFromSimCheckBox->Enable( true );
  754. m_spiceFieldsButton->Show( true );
  755. }
  756. OnModify();
  757. }
  758. void DIALOG_LIB_SYMBOL_PROPERTIES::OnText( wxCommandEvent& event )
  759. {
  760. OnModify();
  761. }
  762. void DIALOG_LIB_SYMBOL_PROPERTIES::OnCombobox( wxCommandEvent& event )
  763. {
  764. OnModify();
  765. }
  766. void DIALOG_LIB_SYMBOL_PROPERTIES::OnCheckBox( wxCommandEvent& event )
  767. {
  768. OnModify();
  769. }
  770. void DIALOG_LIB_SYMBOL_PROPERTIES::OnSpinCtrl( wxSpinEvent& event )
  771. {
  772. OnModify();
  773. }
  774. void DIALOG_LIB_SYMBOL_PROPERTIES::OnSpinCtrlText( wxCommandEvent& event )
  775. {
  776. OnModify();
  777. }
  778. void DIALOG_LIB_SYMBOL_PROPERTIES::OnPageChanging( wxBookCtrlEvent& aEvent )
  779. {
  780. if( !m_grid->CommitPendingChanges() )
  781. aEvent.Veto();
  782. }