KiCad PCB EDA Suite
panel_sym_lib_table.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017 Wayne Stambaugh <stambaughw@gmail.com>
5  * Copyright (C) 2017-2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <set>
22 #include <wx/regex.h>
23 
24 #include <fctsys.h>
25 #include <project.h>
26 #include <panel_sym_lib_table.h>
27 #include <lib_id.h>
28 #include <symbol_lib_table.h>
29 #include <lib_table_lexer.h>
30 #include <grid_tricks.h>
31 #include <widgets/wx_grid.h>
32 #include <confirm.h>
33 #include <bitmaps.h>
34 #include <lib_table_grid.h>
36 #include <env_paths.h>
37 #include <eeschema_id.h>
38 #include <lib_edit_frame.h>
39 #include <lib_view_frame.h>
40 #include <sch_edit_frame.h>
41 #include <kiway.h>
42 #include <pgm_base.h>
43 #include <sch_screen.h>
45 
48 
49 
50 // clang-format off
51 
56 {
57  wxString m_Description;
58  wxString m_FileFilter;
60  bool m_IsFile;
61  SCH_IO_MGR::SCH_FILE_T m_Plugin;
62 };
63 
64 
68 enum {
71 };
72 
73 
78 static const std::map<int, supportedFileType>& fileTypes()
79 {
80  static const std::map<int, supportedFileType> fileTypes =
81  {
83  {
84  "KiCad legacy symbol library file (*.lib)",
86  "",
87  true,
88  SCH_IO_MGR::SCH_LEGACY
89  }
90  },
91  {
93  {
94  "KiCad s-expression symbol library file (*.kicad_sym)",
96  "",
97  true,
98  SCH_IO_MGR::SCH_KICAD
99  }
100  }
101  };
102 
103  return fileTypes;
104 }
105 
106 
111 {
112  friend class SYMBOL_GRID_TRICKS;
113 
114 protected:
115  LIB_TABLE_ROW* at( size_t aIndex ) override { return &rows.at( aIndex ); }
116 
117  size_t size() const override { return rows.size(); }
118 
120  {
121  return dynamic_cast< LIB_TABLE_ROW* >( new SYMBOL_LIB_TABLE_ROW );
122  }
123 
124  LIB_TABLE_ROWS_ITER begin() override { return rows.begin(); }
125 
127  {
128  return rows.insert( aIterator, aRow );
129  }
130 
131  void push_back( LIB_TABLE_ROW* aRow ) override { rows.push_back( aRow ); }
132 
134  {
135  return rows.erase( aFirst, aLast );
136  }
137 
138 public:
139 
141  {
142  rows = aTableToEdit.rows;
143  }
144 };
145 
146 
148 {
149 public:
151  GRID_TRICKS( aGrid ),
152  m_dialog( aParent )
153  {
154  }
155 
156 protected:
158 
161  virtual void paste_text( const wxString& cb_text ) override
162  {
163  SYMBOL_LIB_TABLE_GRID* tbl = (SYMBOL_LIB_TABLE_GRID*) m_grid->GetTable();
164  size_t ndx = cb_text.find( "(sym_lib_table" );
165 
166  if( ndx != std::string::npos )
167  {
168  // paste the SYMBOL_LIB_TABLE_ROWs of s-expression (sym_lib_table), starting
169  // at column 0 regardless of current cursor column.
170 
171  STRING_LINE_READER slr( TO_UTF8( cb_text ), "Clipboard" );
172  LIB_TABLE_LEXER lexer( &slr );
173  SYMBOL_LIB_TABLE tmp_tbl;
174  bool parsed = true;
175 
176  try
177  {
178  tmp_tbl.Parse( &lexer );
179  }
180  catch( PARSE_ERROR& pe )
181  {
182  DisplayError( m_dialog, pe.What() );
183  parsed = false;
184  }
185 
186  if( parsed )
187  {
188  // make sure the table is big enough...
189  if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() )
190  tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() );
191 
192  for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i )
193  tbl->rows.replace( i, tmp_tbl.At( i ).clone() );
194  }
195 
196  m_grid->AutoSizeColumns( false );
197  }
198  else
199  {
200  // paste spreadsheet formatted text.
201  GRID_TRICKS::paste_text( cb_text );
202 
203  m_grid->AutoSizeColumns( false );
204  }
205  }
206 };
207 
208 
210  SYMBOL_LIB_TABLE* aGlobal,
211  const wxString& aGlobalTablePath,
212  SYMBOL_LIB_TABLE* aProject,
213  const wxString& aProjectTablePath,
214  const wxString& aProjectBasePath ) :
215  PANEL_SYM_LIB_TABLE_BASE( aParent ),
216  m_globalTable( aGlobal ),
217  m_projectTable( aProject ),
218  m_projectBasePath( aProjectBasePath ),
219  m_parent( aParent )
220 {
221  // wxGrid only supports user owned tables if they exist past end of ~wxGrid(),
222  // so make it a grid owned table.
223  m_global_grid->SetTable( new SYMBOL_LIB_TABLE_GRID( *aGlobal ), true );
224 
225  // For user info, shows the table filenames:
226  m_GblTableFilename->SetLabel( aGlobalTablePath );
227 
228  wxArrayString pluginChoices;
229 
230  pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD ) );
231  pluginChoices.Add( SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY ) );
232 
233  EESCHEMA_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>();
234 
235  if( cfg->m_lastSymbolLibDir.IsEmpty() )
236  {
238  }
239 
240  auto setupGrid =
241  [&]( WX_GRID* aGrid )
242  {
243  // Give a bit more room for combobox editors
244  aGrid->SetDefaultRowSize( aGrid->GetDefaultRowSize() + 4 );
245 
246  // add Cut, Copy, and Paste to wxGrids
247  aGrid->PushEventHandler( new SYMBOL_GRID_TRICKS( m_parent, aGrid ) );
248 
249  aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
250  aGrid->AutoSizeColumns( false );
251 
252  // Set special attributes
253  wxGridCellAttr* attr;
254 
255  attr = new wxGridCellAttr;
256 
257  wxString wildcards = AllSymbolLibFilesWildcard()
259  + "|" + LegacySymbolLibFileWildcard();
260  attr->SetEditor( new GRID_CELL_PATH_EDITOR( m_parent, &cfg->m_lastSymbolLibDir,
261  wildcards,
262  true, m_projectBasePath ) );
263  aGrid->SetColAttr( COL_URI, attr );
264 
265  attr = new wxGridCellAttr;
266  attr->SetEditor( new wxGridCellChoiceEditor( pluginChoices ) );
267  aGrid->SetColAttr( COL_TYPE, attr );
268 
269  attr = new wxGridCellAttr;
270  attr->SetRenderer( new wxGridCellBoolRenderer() );
271  attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
272  aGrid->SetColAttr( COL_ENABLED, attr );
273 
274  // all but COL_OPTIONS, which is edited with Option Editor anyways.
275  aGrid->AutoSizeColumn( COL_NICKNAME, false );
276  aGrid->AutoSizeColumn( COL_TYPE, false );
277  aGrid->AutoSizeColumn( COL_URI, false );
278  aGrid->AutoSizeColumn( COL_DESCR, false );
279  aGrid->AutoSizeColumn( COL_ENABLED, false );
280 
281  // would set this to width of title, if it was easily known.
282  aGrid->SetColSize( COL_OPTIONS, 80 );
283 
284  // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
285  // a currentCell which is sometimes not highlighted.
286  if( aGrid->GetNumberRows() > 0 )
287  aGrid->SelectRow( 0 );
288  };
289 
290  setupGrid( m_global_grid );
291 
292  if( aProject )
293  {
294  m_PrjTableFilename->SetLabel( aProjectTablePath );
295  m_project_grid->SetTable( new SYMBOL_LIB_TABLE_GRID( *aProject ), true );
296  setupGrid( m_project_grid );
297  }
298  else
299  {
300  m_pageNdx = 0;
301  m_auinotebook->DeletePage( 1 );
302  m_project_grid = nullptr;
303  }
304 
305  // add Cut, Copy, and Paste to wxGrids
306  m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
307 
309 
310  // select the last selected page
311  m_auinotebook->SetSelection( m_pageNdx );
313 
314  m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
315  m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
316 
317  // for ALT+A handling, we want the initial focus to be on the first selected grid.
319 
320  // Configure button logos
321  m_append_button->SetBitmap( KiBitmap( small_plus_xpm ) );
322  m_delete_button->SetBitmap( KiBitmap( trash_xpm ) );
323  m_move_up_button->SetBitmap( KiBitmap( small_up_xpm ) );
324  m_move_down_button->SetBitmap( KiBitmap( small_down_xpm ) );
325  m_browse_button->SetBitmap( KiBitmap( folder_xpm ) );
326 }
327 
328 
330 {
331  // When the dialog is closed it will hide the current notebook page first, which will
332  // in turn select the other one. We then end up saving its index as the "current page".
333  // So flip them back again:
334  m_pageNdx = m_pageNdx == 1 ? 0 : 1;
335 
336  // Delete the GRID_TRICKS.
337  // Any additional event handlers should be popped before the window is deleted.
338  m_global_grid->PopEventHandler( true );
339 
340  if( m_project_grid )
341  m_project_grid->PopEventHandler( true );
342 
343  m_path_subs_grid->PopEventHandler( true );
344 }
345 
346 
348 {
349  for( SYMBOL_LIB_TABLE_GRID* model : { global_model(), project_model() } )
350  {
351  if( !model )
352  continue;
353 
354  for( int r = 0; r < model->GetNumberRows(); )
355  {
356  wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
357  wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
358  unsigned illegalCh = 0;
359 
360  if( !nick || !uri )
361  {
362  // Delete the "empty" row, where empty means missing nick or uri.
363  // This also updates the UI which could be slow, but there should only be a few
364  // rows to delete, unless the user fell asleep on the Add Row
365  // button.
366  model->DeleteRows( r, 1 );
367  }
368  else if( ( illegalCh = LIB_ID::FindIllegalLibNicknameChar( nick, LIB_ID::ID_SCH ) ) )
369  {
370  wxString msg = wxString::Format( _( "Illegal character '%c' in Nickname: \"%s\"" ),
371  illegalCh,
372  nick );
373 
374  // show the tabbed panel holding the grid we have flunked:
375  if( model != cur_model() )
376  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
377 
378  m_cur_grid->MakeCellVisible( r, 0 );
379  m_cur_grid->SetGridCursor( r, 1 );
380 
381  wxMessageDialog errdlg( this, msg, _( "No Colon in Nicknames" ) );
382  errdlg.ShowModal();
383  return false;
384  }
385  else
386  {
387  // set the trimmed values back into the table so they get saved to disk.
388  model->SetValue( r, COL_NICKNAME, nick );
389  model->SetValue( r, COL_URI, uri );
390  ++r; // this row was OK.
391  }
392  }
393  }
394 
395  // check for duplicate nickNames, separately in each table.
396  for( SYMBOL_LIB_TABLE_GRID* model : { global_model(), project_model() } )
397  {
398  if( !model )
399  continue;
400 
401  for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
402  {
403  wxString nick1 = model->GetValue( r1, COL_NICKNAME );
404 
405  for( int r2=r1+1; r2 < model->GetNumberRows(); ++r2 )
406  {
407  wxString nick2 = model->GetValue( r2, COL_NICKNAME );
408 
409  if( nick1 == nick2 )
410  {
411  wxString msg = wxString::Format( _( "Duplicate Nickname: \"%s\"." ), nick1 );
412 
413  // show the tabbed panel holding the grid we have flunked:
414  if( model != cur_model() )
415  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
416 
417  // go to the lower of the two rows, it is technically the duplicate:
418  m_cur_grid->MakeCellVisible( r2, 0 );
419  m_cur_grid->SetGridCursor( r2, 1 );
420 
421  wxMessageDialog errdlg( this, msg, _( "Please Delete or Modify One" ) );
422  errdlg.ShowModal();
423 
424  return false;
425  }
426  }
427  }
428  }
429 
430  for( SYMBOL_LIB_TABLE* table : { global_model(), project_model() } )
431  {
432  if( !table )
433  continue;
434 
435  for( unsigned int r = 0; r < table->GetCount(); ++r )
436  {
437  SYMBOL_LIB_TABLE_ROW& row = dynamic_cast<SYMBOL_LIB_TABLE_ROW&>( table->At( r ) );
438 
439  if( !row.GetIsEnabled() )
440  continue;
441 
442  try
443  {
444  if( row.Refresh() )
445  {
446  if( table == global_model() )
448  else
450  }
451  }
452  catch( const IO_ERROR& ioe )
453  {
454  wxString msg = wxString::Format( _( "Symbol library \"%s\" failed to load.\n %s" ),
455  row.GetNickName(),
456  ioe.What() );
457 
458  wxMessageDialog errdlg( this, msg, _( "Error Loading Library" ) );
459  errdlg.ShowModal();
460 
461  return false;
462  }
463  }
464  }
465 
466  return true;
467 }
468 
469 
470 void PANEL_SYM_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event )
471 {
472  m_pageNdx = (unsigned) std::max( 0, m_auinotebook->GetSelection() );
474 }
475 
476 
477 void PANEL_SYM_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
478 {
479  wxString wildcards = AllSymbolLibFilesWildcard()
481  + "|" + LegacySymbolLibFileWildcard();
482 
483  EESCHEMA_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>();
484  wxFileDialog dlg( this, _( "Select Library" ), cfg->m_lastSymbolLibDir, wxEmptyString, wildcards,
485  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
486 
487  auto result = dlg.ShowModal();
488 
489  if( result == wxID_CANCEL )
490  return;
491 
492  cfg->m_lastSymbolLibDir = dlg.GetDirectory();
493 
494  const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
495  bool addDuplicates = false;
496  bool applyToAll = false;
497  wxString warning = _( "Warning: Duplicate Nickname" );
498  wxString msg = _( "A library nicknamed \"%s\" already exists." );
499  wxString detailedMsg = _( "Please change the library nickname after adding this library." );
500 
501  wxArrayString files;
502  dlg.GetFilenames( files );
503 
504  for( const auto& file : files )
505  {
506  wxString filePath = dlg.GetDirectory() + wxFileName::GetPathSeparator() + file;
507  wxFileName fn( filePath );
508  wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), LIB_ID::ID_SCH );
509  bool doAdd = true;
510 
511  if( cur_model()->ContainsNickname( nickname ) )
512  {
513  if( !applyToAll )
514  {
515  // The cancel button adds the library to the table anyway
516  addDuplicates = ( OKOrCancelDialog( this, warning, wxString::Format( msg, nickname ),
517  detailedMsg, _( "Skip" ), _( "Add Anyway" ), &applyToAll ) == wxID_CANCEL );
518  }
519 
520  doAdd = addDuplicates;
521  }
522 
523  if( doAdd && m_cur_grid->AppendRows( 1 ) )
524  {
525  int last_row = m_cur_grid->GetNumberRows() - 1;
526 
527  m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
528 
529  // TODO the following code can detect only schematic types, not libs
530  // SCH_IO_MGR needs to provide file extension information for libraries too
531 
532  // auto detect the plugin type
533  for( auto pluginType : SCH_IO_MGR::SCH_FILE_T_vector )
534  {
535  if( SCH_IO_MGR::GetLibraryFileExtension( pluginType ).Lower() == fn.GetExt().Lower() )
536  {
537  m_cur_grid->SetCellValue( last_row, COL_TYPE,
538  SCH_IO_MGR::ShowType( pluginType ) );
539  break;
540  }
541  }
542 
543  // try to use path normalized to an environmental variable or project path
544  wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
545 
546  // Do not use the project path in the global library table. This will almost
547  // assuredly be wrong for a different project.
548  if( path.IsEmpty() || (m_pageNdx == 0 && path.Contains( "${KIPRJMOD}" )) )
549  path = fn.GetFullPath();
550 
551  m_cur_grid->SetCellValue( last_row, COL_URI, path );
552  }
553  }
554 
555  if( !files.IsEmpty() )
556  {
557  m_cur_grid->MakeCellVisible( m_cur_grid->GetNumberRows() - 1, 0 );
558  m_cur_grid->SetGridCursor( m_cur_grid->GetNumberRows() - 1, 1 );
559  }
560 }
561 
562 
563 void PANEL_SYM_LIB_TABLE::appendRowHandler( wxCommandEvent& event )
564 {
566  return;
567 
568  if( m_cur_grid->AppendRows( 1 ) )
569  {
570  int row = m_cur_grid->GetNumberRows() - 1;
571 
572  // wx documentation is wrong, SetGridCursor does not make visible.
573  m_cur_grid->MakeCellVisible( row, 0 );
574  m_cur_grid->SetGridCursor( row, 1 );
575  m_cur_grid->EnableCellEditControl( true );
576  m_cur_grid->ShowCellEditControl();
577  }
578 }
579 
580 
581 void PANEL_SYM_LIB_TABLE::deleteRowHandler( wxCommandEvent& event )
582 {
584  return;
585 
586  int curRow = m_cur_grid->GetGridCursorRow();
587  int curCol = m_cur_grid->GetGridCursorCol();
588 
589  // In a wxGrid, collect rows that have a selected cell, or are selected
590  // It is not so easy: it depends on the way the selection was made.
591  // Here, we collect rows selected by clicking on a row label, and rows that contain
592  // previously-selected cells.
593  // If no candidate, just delete the row with the grid cursor.
594  wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
595  wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
596  wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
597  wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
598 
599  // Add all row having cell selected to list:
600  for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
601  selectedRows.Add( cells[ii].GetRow() );
602 
603  // Handle block selection
604  if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
605  {
606  for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
607  selectedRows.Add( i );
608  }
609 
610  // Use the row having the grid cursor only if we have no candidate:
611  if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
612  selectedRows.Add( m_cur_grid->GetGridCursorRow() );
613 
614  if( selectedRows.size() == 0 )
615  {
616  wxBell();
617  return;
618  }
619 
620  std::sort( selectedRows.begin(), selectedRows.end() );
621 
622  // Remove selected rows (note: a row can be stored more than once in list)
623  int last_row = -1;
624 
625  for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
626  {
627  int row = selectedRows[ii];
628 
629  if( row != last_row )
630  {
631  last_row = row;
632  m_cur_grid->DeleteRows( row, 1 );
633  }
634  }
635 
636  if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 )
637  m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
638 }
639 
640 
641 void PANEL_SYM_LIB_TABLE::moveUpHandler( wxCommandEvent& event )
642 {
644  return;
645 
647  int curRow = m_cur_grid->GetGridCursorRow();
648 
649  // @todo: add multiple selection moves.
650  if( curRow >= 1 )
651  {
652  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
653  tbl->rows.release( tbl->rows.begin() + curRow );
654 
655  --curRow;
656  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
657 
658  if( tbl->GetView() )
659  {
660  // Update the wxGrid
661  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 );
662  tbl->GetView()->ProcessTableMessage( msg );
663  }
664 
665  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
666  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
667  }
668 }
669 
670 
671 void PANEL_SYM_LIB_TABLE::moveDownHandler( wxCommandEvent& event )
672 {
674  return;
675 
677  int curRow = m_cur_grid->GetGridCursorRow();
678 
679  // @todo: add multiple selection moves.
680  if( unsigned( curRow + 1 ) < tbl->rows.size() )
681  {
682  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
683  tbl->rows.release( tbl->rows.begin() + curRow );
684 
685  ++curRow;
686  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
687 
688  if( tbl->GetView() )
689  {
690  // Update the wxGrid
691  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 );
692  tbl->GetView()->ProcessTableMessage( msg );
693  }
694 
695  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
696  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
697  }
698 }
699 
700 
702 {
704  return false;
705 
706  if( !verifyTables() )
707  return false;
708 
709  if( *global_model() != *m_globalTable )
710  {
712 
713  m_globalTable->Clear();
714  m_globalTable->rows.transfer( m_globalTable->rows.end(), global_model()->rows.begin(),
715  global_model()->rows.end(), global_model()->rows );
717  }
718 
719  if( project_model() && *project_model() != *m_projectTable )
720  {
722 
724  m_projectTable->rows.transfer( m_projectTable->rows.end(), project_model()->rows.begin(),
725  project_model()->rows.end(), project_model()->rows );
727  }
728 
729  return true;
730 }
731 
732 
734 {
735  wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
736  wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
737 
738  std::set< wxString > unique;
739 
740  // clear the table
741  m_path_subs_grid->DeleteRows( 0, m_path_subs_grid->GetNumberRows() );
742 
743  for( SYMBOL_LIB_TABLE_GRID* tbl : { global_model(), project_model() } )
744  {
745  if( !tbl )
746  continue;
747 
748  for( int row = 0; row < tbl->GetNumberRows(); ++row )
749  {
750  wxString uri = tbl->GetValue( row, COL_URI );
751 
752  while( re.Matches( uri ) )
753  {
754  wxString envvar = re.GetMatch( uri, 2 );
755 
756  // if not ${...} form then must be $(...)
757  if( envvar.IsEmpty() )
758  envvar = re.GetMatch( uri, 4 );
759 
760  // ignore duplicates
761  unique.insert( envvar );
762 
763  // delete the last match and search again
764  uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
765  }
766  }
767  }
768 
769  // Make sure this special environment variable shows up even if it was
770  // not used yet. It is automatically set by KiCad to the directory holding
771  // the current project.
772  unique.insert( PROJECT_VAR_NAME );
774 
775  for( const wxString& evName : unique )
776  {
777  int row = m_path_subs_grid->GetNumberRows();
778  m_path_subs_grid->AppendRows( 1 );
779 
780  m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
781  m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
782 
783  wxString evValue;
784  wxGetEnv( evName, &evValue );
785  m_path_subs_grid->SetCellValue( row, 1, evValue );
786  m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
787  }
788 
789  // No combobox editors here, but it looks better if its consistent with the other
790  // grids in the dialog.
791  m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 );
792 
793  adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
794 }
795 
796 
798 {
799  // Account for scroll bars
800  aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
801 
802  m_path_subs_grid->AutoSizeColumn( 0 );
803  m_path_subs_grid->SetColSize( 1, aWidth - m_path_subs_grid->GetColSize( 0 ) );
804 }
805 
806 
807 void PANEL_SYM_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
808 {
809  adjustPathSubsGridColumns( event.GetSize().GetX() );
810 
811  event.Skip();
812 }
813 
814 
816 {
817  return (SYMBOL_LIB_TABLE_GRID*) m_global_grid->GetTable();
818 }
819 
820 
822 {
823  return m_project_grid ? (SYMBOL_LIB_TABLE_GRID*) m_project_grid->GetTable() : nullptr;
824 }
825 
826 
828 {
829  return (SYMBOL_LIB_TABLE_GRID*) m_cur_grid->GetTable();
830 }
831 
832 
834 
835 
836 void InvokeSchEditSymbolLibTable( KIWAY* aKiway, wxWindow *aParent )
837 {
838  SCH_EDIT_FRAME* schEditor = (SCH_EDIT_FRAME*) aKiway->Player( FRAME_SCH, false );
839  LIB_EDIT_FRAME* libEditor = (LIB_EDIT_FRAME*) aKiway->Player( FRAME_SCH_LIB_EDITOR, false );
840  LIB_VIEW_FRAME* libViewer = (LIB_VIEW_FRAME*) aKiway->Player( FRAME_SCH_VIEWER, false );
841 
843  wxString globalTablePath = SYMBOL_LIB_TABLE::GetGlobalTableFileName();
844  SYMBOL_LIB_TABLE* projectTable = nullptr;
845  wxString projectPath = aKiway->Prj().GetProjectPath();
846  wxFileName projectTableFn( projectPath, SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
847  wxString msg;
848  wxString currentLib;
849 
850  // Don't allow editing project tables if no project is open
851  if( !aKiway->Prj().IsNullProject() )
852  projectTable = aKiway->Prj().SchSymbolLibTable();
853 
854  if( libEditor )
855  {
856  currentLib = libEditor->GetCurLib();
857 
858  // This prevents an ugly crash on OSX (https://bugs.launchpad.net/kicad/+bug/1765286)
859  libEditor->FreezeSearchTree();
860 
861  if( libEditor->HasLibModifications() )
862  {
863  msg = _( "Modifications have been made to one or more symbol libraries.\n"
864  "Changes must be saved or discarded before the symbol library "
865  "table can be modified." );
866 
867  switch( UnsavedChangesDialog( aParent, msg ) )
868  {
869  case wxID_YES: libEditor->SaveAll(); break;
870  case wxID_NO: libEditor->RevertAll(); break;
871  default:
872  case wxID_CANCEL: libEditor->ThawSearchTree(); return;
873  }
874  }
875  }
876 
877  DIALOG_EDIT_LIBRARY_TABLES dlg( aParent, _( "Symbol Libraries" ) );
878 
879  dlg.InstallPanel( new PANEL_SYM_LIB_TABLE( &dlg, globalTable, globalTablePath,
880  projectTable, projectTableFn.GetFullPath(),
881  aKiway->Prj().GetProjectPath() ) );
882 
883  if( dlg.ShowModal() == wxID_CANCEL )
884  {
885  if( libEditor )
886  libEditor->ThawSearchTree();
887 
888  return;
889  }
890 
891  if( dlg.m_GlobalTableChanged )
892  {
893  try
894  {
895  globalTable->Save( globalTablePath );
896  }
897  catch( const IO_ERROR& ioe )
898  {
899  msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
900  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
901  }
902  }
903 
904  if( projectTable && dlg.m_ProjectTableChanged )
905  {
906  try
907  {
908  projectTable->Save( projectTableFn.GetFullPath() );
909  }
910  catch( const IO_ERROR& ioe )
911  {
912  msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
913  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
914  }
915  }
916 
917  if( schEditor )
918  schEditor->SyncView();
919 
920  if( libEditor )
921  {
922  // Check if the currently selected symbol library been removed or disabled.
923  if( !currentLib.empty() && projectTable && !projectTable->HasLibrary( currentLib, true ) )
924  {
925  libEditor->SetCurLib( wxEmptyString );
926  libEditor->emptyScreen();
927  }
928 
929  libEditor->SyncLibraries( true );
930  libEditor->ThawSearchTree();
931  }
932 
933  if( libViewer )
934  libViewer->ReCreateListLib();
935 }
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:239
const BITMAP_OPAQUE trash_xpm[1]
Definition: trash.cpp:46
wxString m_FileFilter
Filter used for file pickers if m_IsFile is true.
static const wxString & GetSymbolLibTableFileName()
const BITMAP_OPAQUE folder_xpm[1]
Definition: folder.cpp:20
static SYMBOL_LIB_TABLE & GetGlobalLibTable()
virtual void paste_text(const wxString &cb_text) override
handle specialized clipboard text, with leading "(sym_lib_table" or spreadsheet formatted text.
wxString AllSymbolLibFilesWildcard()
bool m_IsFile
Whether the library is a folder or a file.
void moveUpHandler(wxCommandEvent &event) override
void pageChangedHandler(wxAuiNotebookEvent &event) override
SCH_IO_MGR::SCH_FILE_T m_Plugin
Hold a record identifying a symbol library accessed by the appropriate symbol library SCH_PLUGIN obje...
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library table.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:104
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
void Clear()
Delete all rows.
PANEL_SYM_LIB_TABLE(DIALOG_EDIT_LIBRARY_TABLES *aParent, SYMBOL_LIB_TABLE *aGlobal, const wxString &aGlobalTablePath, SYMBOL_LIB_TABLE *aProject, const wxString &aProjectTablePath, const wxString &m_projectBasePath)
This file is part of the common library.
void reindex()
bool ContainsNickname(const wxString &aNickname)
VTBL_ENTRY PROJECT & Prj() const
Function Prj returns the PROJECT associated with this KIWAY.
Definition: kiway.cpp:173
unsigned GetCount() const
Get the number of rows contained in the table.
static const wxString ShowType(SCH_FILE_T aFileType)
Return a brief name for a plugin, given aFileType enum.
Definition: sch_io_mgr.cpp:84
LIB_TABLE_ROW * at(size_t aIndex) override
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
void InvokeSchEditSymbolLibTable(KIWAY *aKiway, wxWindow *aParent)
WX_GRID * m_cur_grid
changed based on tab choice
GRID_TRICKS is used to add mouse and command handling (such as cut, copy, and paste) to a WX_GRID ins...
Definition: grid_tricks.h:51
static const wxString GetLibraryFileExtension(SCH_FILE_T aFileType)
Return the symbol library file extension (if any) for aFileType.
Definition: sch_io_mgr.cpp:146
static unsigned FindIllegalLibNicknameChar(const UTF8 &aNickname, LIB_ID_TYPE aType)
Looks for characters that are illegal in library nicknames.
Definition: lib_id.cpp:404
SYMBOL_LIB_TABLE_GRID * global_model() const
static const wxString GlobalPathEnvVariableName()
Return the name of the environment variable used to hold the directory of locally installed "KiCad sp...
static size_t m_pageNdx
Remember the last notebook page selected during a session.
Schematic editor (Eeschema) main window.
LIB_TABLE_ROWS rows
Container that describes file type info for the add a library options.
void SetTable(wxGridTableBase *table, bool aTakeOwnership=false)
Hide wxGrid's SetTable() method with one which doesn't mess up the grid column widths when setting th...
Definition: wx_grid.cpp:65
int GetNumberRows() override
VTBL_ENTRY const wxString GetProjectPath() const
Function GetProjectPath returns the full path of the project.
Definition: project.cpp:124
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:114
Symbol library viewer main window.
void adjustPathSubsGridColumns(int aWidth)
wxString GetCurLib() const
The nickname of the current library being edited and empty string if none.
This abstract base class mixes any object derived from LIB_TABLE into wxGridTableBase so the result c...
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:58
virtual void paste_text(const wxString &cb_text)
#define TO_UTF8(wxstring)
Macro TO_UTF8 converts a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:100
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:80
void populateEnvironReadOnlyTable()
Populate the readonly environment variable table with names and values by examining all the full_uri ...
const wxString & GetNickName() const
LIB_TABLE_ROW * clone() const
Build a wxGridTableBase by wrapping an SYMBOL_LIB_TABLE object.
bool GetIsEnabled() const
VTBL_ENTRY KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=NULL)
Function Player returns the KIWAY_PLAYER* given a FRAME_T.
Definition: kiway.cpp:343
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:29
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
Definition: pgm_base.h:117
void SaveAll()
Saves all modified parts and libraries.
SYMBOL_LIB_TABLE_GRID * cur_model() const
wxString m_FolderSearchExtension
In case of folders it stands for extensions of files stored inside.
void SyncView()
Mark all items for refresh.
SYMBOL_LIB_TABLE * m_projectTable
bool Refresh()
Attempt to reload the library.
bool TransferDataFromWindow() override
const BITMAP_OPAQUE small_down_xpm[1]
Definition: small_down.cpp:25
Class PANEL_SYM_LIB_TABLE_BASE.
Definition of file extensions used in Kicad.
DIALOG_EDIT_LIBRARY_TABLES * m_dialog
KIWAY is a minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the s...
Definition: kiway.h:273
void push_back(LIB_TABLE_ROW *aRow) override
wxString KiCadSymbolLibFileWildcard()
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
Dialog to show and edit symbol library tables.
void deleteRowHandler(wxCommandEvent &event) override
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:180
bool AppendRows(size_t aNumRows=1) override
int UnsavedChangesDialog(wxWindow *parent, wxString aMessage, bool *aApplyToAll)
A specialized version of HandleUnsavedChanges which handles an apply-to-all checkbox.
Definition: confirm.cpp:148
wxString LegacySymbolLibFileWildcard()
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Displays a warning dialog with aMessage and returns the user response.
Definition: confirm.cpp:214
void appendRowHandler(wxCommandEvent &event) override
const BITMAP_OPAQUE small_up_xpm[1]
Definition: small_up.cpp:26
SYMBOL_LIB_TABLE_GRID(const SYMBOL_LIB_TABLE &aTableToEdit)
VTBL_ENTRY bool IsNullProject() const
Checks if this project is a null project (i.e.
Definition: project.cpp:136
void emptyScreen()
Restores the empty editor screen, without any part or library selected.
DIALOG_EDIT_LIBRARY_TABLES * m_parent
static const std::map< int, supportedFileType > & fileTypes()
Map with event id as the key to supported file types that will be listed for the add a library option...
void SyncLibraries(bool aShowProgress)
Synchronize the library manager to the symbol library table, and then the symbol tree to the library ...
SYMBOL_LIB_TABLE_GRID * project_model() const
Struct PARSE_ERROR contains a filename or source description, a problem input line,...
Definition: ki_exception.h:123
The symbol library editor main window.
see class PGM_BASE
bool HasLibModifications() const
Check if any pending libraries have been modified.
LIB_TABLE_ROW & At(unsigned aIndex)
Get the 'n'th LIB_TABLE_ROW object.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:201
#define _(s)
Definition: 3d_actions.cpp:33
LIB_TABLE_ROWS_ITER erase(LIB_TABLE_ROWS_ITER aFirst, LIB_TABLE_ROWS_ITER aLast) override
virtual void Parse(LIB_TABLE_LEXER *aLexer) override
Parse the #LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW objec...
LIB_TABLE_ROWS::iterator LIB_TABLE_ROWS_ITER
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
bool verifyTables()
Trim important fields, removes blank row entries, and checks for duplicates.
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, LIB_ID_TYPE aType, bool aLib=false)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition: lib_id.cpp:352
wxString SetCurLib(const wxString &aLibNickname)
Sets the current library nickname and returns the old library nickname.
void onSizeGrid(wxSizeEvent &event) override
LIB_TABLE_ROWS_ITER insert(LIB_TABLE_ROWS_ITER aIterator, LIB_TABLE_ROW *aRow) override
SYMBOL_LIB_TABLE * m_globalTable
bool ReCreateListLib()
Creates or recreates a sorted list of currently loaded libraries.
LIB_TABLE_ROW * makeNewRow() override
STRING_LINE_READER is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition: richio.h:254
LIB_TABLE_ROWS_ITER begin() override
wxString m_Description
Description shown in the file picker dialog.
Struct IO_ERROR is a class used to hold an error message and may be used when throwing exceptions con...
Definition: ki_exception.h:76
static wxString GetGlobalTableFileName()
Fetch the global symbol library table file name.
size_t size() const override
const BITMAP_OPAQUE small_plus_xpm[1]
Definition: small_plus.cpp:20
void moveDownHandler(wxCommandEvent &event) override
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalizes a file path to an environmental variable, if possible.
Definition: env_paths.cpp:68
SYMBOL_GRID_TRICKS(DIALOG_EDIT_LIBRARY_TABLES *aParent, WX_GRID *aGrid)
void browseLibrariesHandler(wxCommandEvent &event) override