KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 <[email protected]>
5 * Copyright (C) 2021 CERN
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include <set>
23#include <wx/regex.h>
24
25#include <build_version.h>
26#include <common.h> // For ExpandEnvVarSubstitutions
28#include <project.h>
29#include <panel_sym_lib_table.h>
30#include <lib_id.h>
34#include <widgets/wx_grid.h>
38#include <confirm.h>
39#include <bitmaps.h>
42#include <env_paths.h>
43#include <functional>
44#include <eeschema_id.h>
45#include <env_vars.h>
46#include <sch_io/sch_io.h>
47#include <symbol_edit_frame.h>
48#include <sch_edit_frame.h>
49#include <kiway.h>
50#include <paths.h>
51#include <kiplatform/ui.h>
52#include <pgm_base.h>
54#include <wx/dir.h>
55#include <wx/dirdlg.h>
56#include <wx/filedlg.h>
57#include <wx/msgdlg.h>
58#include <project_sch.h>
62
67{
68 wxString m_Description;
69 wxString m_FileFilter;
70
73 bool m_IsFile;
74 SCH_IO_MGR::SCH_FILE_T m_Plugin;
75};
76
77
82static constexpr int FIRST_MENU_ID = 1000;
83// This must not collide with any SCH_FILE_T enum values so we offset it below.
85
86
88{
89public:
90 SYMBOL_LIB_TABLE_GRID_DATA_MODEL( DIALOG_SHIM* aParent, WX_GRID* aGrid, const LIBRARY_TABLE& aTableToEdit,
91 SYMBOL_LIBRARY_ADAPTER* aAdapter, const wxArrayString& aPluginChoices,
92 wxString* aMRUDirectory, const wxString& aProjectPath ) :
93 LIB_TABLE_GRID_DATA_MODEL( aParent, aGrid, aTableToEdit, aAdapter, aPluginChoices, aMRUDirectory,
94 aProjectPath )
95 {
96 }
97
98 void SetValue( int aRow, int aCol, const wxString &aValue ) override
99 {
100 wxCHECK( aRow < (int) size(), /* void */ );
101
102 LIB_TABLE_GRID_DATA_MODEL::SetValue( aRow, aCol, aValue );
103
104 // If setting a filepath, attempt to auto-detect the format
105 if( aCol == COL_URI )
106 {
107 LIBRARY_TABLE_ROW& row = at( static_cast<size_t>( aRow ) );
108 wxString uri = LIBRARY_MANAGER::ExpandURI( row.URI(), Pgm().GetSettingsManager().Prj() );
109 SCH_IO_MGR::SCH_FILE_T pluginType = SCH_IO_MGR::GuessPluginTypeFromLibPath( uri );
110
111 if( pluginType != SCH_IO_MGR::SCH_FILE_UNKNOWN )
112 SetValue( aRow, COL_TYPE, SCH_IO_MGR::ShowType( pluginType ) );
113 }
114 }
115
116protected:
117 wxString getFileTypes( WX_GRID* aGrid, int aRow ) override
118 {
119 LIB_TABLE_GRID_DATA_MODEL* table = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( aGrid->GetTable() );
120 LIBRARY_TABLE_ROW& tableRow = table->At( aRow );
121
122 if( tableRow.Type() == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
123 {
124 wxString filter = _( "Symbol Library Tables" );
125#ifndef __WXOSX__
126 filter << wxString::Format( _( " (%s)|%s" ), FILEEXT::SymbolLibraryTableFileName,
128#else
129 filter << wxString::Format( _( " (%s)|%s" ), wxFileSelectorDefaultWildcardStr,
130 wxFileSelectorDefaultWildcardStr );
131#endif
132 return filter;
133 }
134
135 SCH_IO_MGR::SCH_FILE_T pi_type = SCH_IO_MGR::EnumFromStr( tableRow.Type() );
136 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( pi_type ) );
137
138 if( pi )
139 {
140 const IO_BASE::IO_FILE_DESC& desc = pi->GetLibraryDesc();
141
142 if( desc.m_IsFile )
143 return desc.FileFilter();
144 }
145
146 return wxEmptyString;
147 }
148};
149
150
152{
153public:
155 std::function<void( wxCommandEvent& )> aAddHandler ) :
156 LIB_TABLE_GRID_TRICKS( aGrid, aAddHandler ),
157 m_panel( aPanel )
158 {
160 }
161
163 {
164 return true;
165 }
166
167protected:
168 void optionsEditor( int aRow ) override
169 {
170 LIB_TABLE_GRID_DATA_MODEL* tbl = static_cast<LIB_TABLE_GRID_DATA_MODEL*>( m_grid->GetTable() );
171
172 if( tbl->GetNumberRows() > aRow )
173 {
174 LIBRARY_TABLE_ROW& row = tbl->At( static_cast<size_t>( aRow ) );
175 const wxString& options = row.Options();
176 wxString result = options;
177 std::map<std::string, UTF8> choices;
178
179 SCH_IO_MGR::SCH_FILE_T pi_type = SCH_IO_MGR::EnumFromStr( row.Type() );
180 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( pi_type ) );
181 pi->GetLibraryOptions( &choices );
182
183 DIALOG_PLUGIN_OPTIONS dlg( wxGetTopLevelParent( m_grid ), row.Nickname(), choices, options, &result );
184 dlg.ShowModal();
185
186 if( options != result )
187 {
188 row.SetOptions( result );
189 m_grid->Refresh();
190 }
191 }
192 }
193
194 void openTable( const LIBRARY_TABLE_ROW& aRow ) override
195 {
196 wxFileName fn( LIBRARY_MANAGER::ExpandURI( aRow.URI(), Pgm().GetSettingsManager().Prj() ) );
197 std::shared_ptr<LIBRARY_TABLE> child = std::make_shared<LIBRARY_TABLE>( fn, LIBRARY_TABLE_SCOPE::GLOBAL, LIBRARY_TABLE_TYPE::SYMBOL );
198
199 if( !child->IsOk() )
200 {
201 wxMessageBox( _( "Unable to load library table." ) );
202 }
203 else
204 {
205 m_panel->OpenTable( child, aRow.Nickname() );
206 }
207 }
208
209 wxString getTablePreamble() override
210 {
211 return wxT( "(sym_lib_table" );
212 }
213
218
219protected:
221};
222
223
224void PANEL_SYM_LIB_TABLE::OpenTable( const std::shared_ptr<LIBRARY_TABLE>& aTable, const wxString& aTitle )
225{
226 for( int ii = 2; ii < (int) m_notebook->GetPageCount(); ++ii )
227 {
228 if( m_notebook->GetPageText( ii ) == aTitle )
229 {
230 // Something is pretty fishy with wxAuiNotebook::ChangeSelection(); on Mac at least it
231 // results in a re-entrant call where the second call is one page behind.
232 for( int attempts = 0; attempts < 3; ++attempts )
233 m_notebook->ChangeSelection( ii );
234
235 return;
236 }
237 }
238
239 m_nestedTables.push_back( aTable );
240 AddTable( aTable.get(), aTitle, true );
241
242 // Something is pretty fishy with wxAuiNotebook::ChangeSelection(); on Mac at least it
243 // results in a re-entrant call where the second call is one page behind.
244 for( int attempts = 0; attempts < 3; ++attempts )
245 m_notebook->ChangeSelection( m_notebook->GetPageCount() - 1 );
246}
247
248
249void PANEL_SYM_LIB_TABLE::AddTable( LIBRARY_TABLE* table, const wxString& aTitle, bool aClosable )
250{
252 wxString projectPath = m_project->GetProjectPath();
253
255
256 WX_GRID* grid = get_grid( (int) m_notebook->GetPageCount() - 1 );
257
258 if( table->Path().StartsWith( projectPath ) )
259 {
261 &m_lastProjectLibDir, projectPath ),
262 true /* take ownership */ );
263 }
264 else
265 {
266 wxString* lastGlobalLibDir = nullptr;
267
269 {
270 if( cfg->m_lastSymbolLibDir.IsEmpty() )
271 cfg->m_lastSymbolLibDir = PATHS::GetDefaultUserSymbolsPath();
272
273 lastGlobalLibDir = &cfg->m_lastSymbolLibDir;
274 }
275
277 lastGlobalLibDir, wxEmptyString ),
278 true /* take ownership */ );
279 }
280
281 static_cast<LIB_TABLE_GRID_DATA_MODEL*>( grid->GetTable() )->RecheckRows();
282
283 LIB_TABLE_NOTEBOOK_PANEL* notebookPanel =
284 static_cast<LIB_TABLE_NOTEBOOK_PANEL*>( m_notebook->GetPage( m_notebook->GetPageCount() - 1 ) );
285
286 static_cast<LIB_TABLE_GRID_DATA_MODEL*>( grid->GetTable() )
288 [notebookPanel]()
289 {
290 notebookPanel->MarkDirty();
291 } );
292
293 // add Cut, Copy, and Paste to wxGrids
294 grid->PushEventHandler( new SYMBOL_GRID_TRICKS( this, grid,
295 [this]( wxCommandEvent& event )
296 {
297 appendRowHandler( event );
298 } ) );
299
300 auto autoSizeCol =
301 [&]( int aCol )
302 {
303 int prevWidth = grid->GetColSize( aCol );
304
305 grid->AutoSizeColumn( aCol, false );
306 grid->SetColSize( aCol, std::max( prevWidth, grid->GetColSize( aCol ) ) );
307 };
308
309 // all but COL_OPTIONS, which is edited with Option Editor anyways.
310 autoSizeCol( COL_NICKNAME );
311 autoSizeCol( COL_TYPE );
312 autoSizeCol( COL_URI );
313 autoSizeCol( COL_DESCR );
314
315 if( grid->GetNumberRows() > 0 )
316 {
317 grid->SetGridCursor( 0, COL_NICKNAME );
318 grid->SelectRow( 0 );
319 }
320}
321
322
324 PANEL_SYM_LIB_TABLE_BASE( aParent ),
325 m_project( aProject ),
326 m_parent( aParent ),
328{
329 m_lastProjectLibDir = m_project->GetProjectPath();
330
332
333 for( const SCH_IO_MGR::SCH_FILE_T& type : SCH_IO_MGR::SCH_FILE_T_vector )
334 {
335 if( type == SCH_IO_MGR::SCH_NESTED_TABLE )
336 {
338 continue;
339 }
340
341 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( type ) );
342
343 if( pi )
345 }
346
347 std::optional<LIBRARY_TABLE*> table = Pgm().GetLibraryManager().Table( LIBRARY_TABLE_TYPE::SYMBOL,
349 wxASSERT( table.has_value() );
350
351 AddTable( table.value(), _( "Global Libraries" ), false /* closable */ );
352
353 std::optional<LIBRARY_TABLE*> projectTable = Pgm().GetLibraryManager().Table( LIBRARY_TABLE_TYPE::SYMBOL,
355
356 if( projectTable.has_value() )
357 AddTable( projectTable.value(), _( "Project Specific Libraries" ), false /* closable */ );
358
359 m_notebook->SetArtProvider( new WX_AUI_TAB_ART() );
360
361 // add Cut, Copy, and Paste to wxGrids
362 m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
363
365
366 m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
367 m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
368
369 // Configure button logos
375
376 // For aesthetic reasons, we must set the size of m_browseButton to match the other bitmaps
377 Layout();
378 wxSize buttonSize = m_append_button->GetSize();
379
380 m_browseButton->SetWidthPadding( 4 );
381 m_browseButton->SetMinSize( buttonSize );
382
383 // Populate the browse library options
384 wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
385
386 auto joinExtensions =
387 []( const std::vector<std::string>& aExts ) -> wxString
388 {
389 wxString result;
390
391 for( const std::string& ext : aExts )
392 {
393 if( !result.IsEmpty() )
394 result << wxT( ", " );
395
396 result << wxT( "." ) << ext;
397 }
398
399 return result;
400 };
401
402 for( auto& [type, desc] : m_supportedSymFiles )
403 {
404 wxString entryStr = SCH_IO_MGR::ShowType( type );
405
406 if( !desc.m_FileExtensions.empty() )
407 entryStr << wxString::Format( wxS( " (%s)" ), joinExtensions( desc.m_FileExtensions ) );
408
409 browseMenu->Append( type + FIRST_MENU_ID, entryStr );
410 browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_SYM_LIB_TABLE::browseLibrariesHandler, this,
411 type + FIRST_MENU_ID );
412
413 // Add folder-based entry right after KiCad file-based entry
414 if( type == SCH_IO_MGR::SCH_KICAD )
415 {
416 wxString folderEntry = SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD );
417 folderEntry << wxString::Format( wxS( " (%s)" ), _( "folder with .kicad_sym files" ) );
418 browseMenu->Append( ID_PANEL_SYM_LIB_KICAD_FOLDER, folderEntry );
419 browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_SYM_LIB_TABLE::browseLibrariesHandler, this,
421 }
422 }
423
424 Layout();
425
426 m_notebook->Bind( wxEVT_AUINOTEBOOK_PAGE_CLOSE, &PANEL_SYM_LIB_TABLE::onNotebookPageCloseRequest, this );
427 m_notebook->Bind( wxEVT_AUINOTEBOOK_PAGE_CHANGING, &PANEL_SYM_LIB_TABLE::onNotebookPageChangeRequest, this );
429
430 m_parent->SetCanCloseCheck(
431 [this]()
432 {
433 for( int ii = 0; ii < (int) m_notebook->GetPageCount(); ++ii )
434 {
436 static_cast<LIB_TABLE_NOTEBOOK_PANEL*>( m_notebook->GetPage( ii ) );
437
438 if( panel->GetClosable() )
439 {
440 bool wasDirty = panel->TableModified();
441
442 if( !panel->GetCanClose() )
443 return false;
444
445 if( wasDirty && !panel->TableModified() )
446 {
447 m_parent->m_GlobalTableChanged = true;
448 m_parent->m_ProjectTableChanged = true;
449 }
450 }
451 }
452
453 return true;
454 } );
455}
456
457
459{
460 wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
461
462 for( auto& [type, desc] : m_supportedSymFiles )
463 browseMenu->Unbind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_SYM_LIB_TABLE::browseLibrariesHandler, this, type );
464
465 browseMenu->Unbind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_SYM_LIB_TABLE::browseLibrariesHandler,
467 m_browseButton->Unbind( wxEVT_BUTTON, &PANEL_SYM_LIB_TABLE::browseLibrariesHandler, this );
468
469 // Delete the GRID_TRICKS.
470 // (Notebook page GRID_TRICKS are deleted by LIB_TABLE_NOTEBOOK_PANEL.)
471 m_path_subs_grid->PopEventHandler( true );
472}
473
474
476{
477 for( const SCH_IO_MGR::SCH_FILE_T& type : SCH_IO_MGR::SCH_FILE_T_vector )
478 {
479 IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( type ) );
480
481 if( !pi )
482 continue;
483
484 if( const IO_BASE::IO_FILE_DESC& desc = pi->GetLibraryDesc() )
485 {
486 if( !desc.m_FileExtensions.empty() )
487 m_supportedSymFiles.emplace( type, desc );
488 }
489 }
490
491 m_supportedSymFiles.emplace( SCH_IO_MGR::SCH_NESTED_TABLE,
492 IO_BASE::IO_FILE_DESC( _( "Table (nested library table)" ), {} ) );
493}
494
495
497{
498 return static_cast<SYMBOL_LIB_TABLE_GRID_DATA_MODEL*>( get_grid( aPage )->GetTable() );
499}
500
501
503{
504 return static_cast<LIB_TABLE_NOTEBOOK_PANEL*>( m_notebook->GetPage( aPage ) )->GetGrid();
505}
506
507
509{
510 // for ALT+A handling, we want the initial focus to be on the first selected grid.
511 m_parent->SetInitialFocus( cur_grid() );
512
513 return true;
514}
515
516
518{
519 for( int page = 0 ; page < (int) m_notebook->GetPageCount(); ++page )
520 {
521 WX_GRID* grid = get_grid( page );
522
524 [&]( int aRow, int aCol )
525 {
526 // show the tabbed panel holding the grid we have flunked:
527 if( m_notebook->GetSelection() != page )
528 m_notebook->SetSelection( page );
529
530 grid->MakeCellVisible( aRow, 0 );
531 grid->SetGridCursor( aRow, aCol );
532 } ) )
533 {
534 return false;
535 }
536 }
537
538 return true;
539}
540
541
543{
544 if( !cur_grid()->CommitPendingChanges() )
545 return;
546
547 SCH_IO_MGR::SCH_FILE_T fileType = SCH_IO_MGR::SCH_FILE_UNKNOWN;
548 bool selectingFolder = false;
549
550 // We are bound both to the menu and button with this one handler
551 if( event.GetEventType() == wxEVT_BUTTON )
552 {
553 // Default to KiCad file format when clicking the button directly
554 fileType = SCH_IO_MGR::SCH_KICAD;
555 }
556 else if( event.GetId() == ID_PANEL_SYM_LIB_KICAD_FOLDER )
557 {
558 // Special case for folder-based KiCad library
559 fileType = SCH_IO_MGR::SCH_KICAD;
560 selectingFolder = true;
561 }
562 else
563 {
564 fileType = static_cast<SCH_IO_MGR::SCH_FILE_T>( event.GetId() - FIRST_MENU_ID );
565 }
566
567 if( fileType == SCH_IO_MGR::SCH_FILE_UNKNOWN )
568 return;
569
570 const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
571
573 wxString dummy;
574 wxString* lastDir;
575
576 if( m_notebook->GetSelection() == 0 )
577 lastDir = cfg ? &cfg->m_lastSymbolLibDir : &dummy;
578 else
579 lastDir = &m_lastProjectLibDir;
580
581 wxString title = wxString::Format( _( "Select %s Library" ), SCH_IO_MGR::ShowType( fileType ) );
582 wxWindow* topLevelParent = wxGetTopLevelParent( this );
583 wxArrayString files;
584
585 if( selectingFolder )
586 {
587 wxDirDialog dlg( topLevelParent, title, *lastDir, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST | wxDD_MULTIPLE );
588
589 if( dlg.ShowModal() == wxID_CANCEL )
590 return;
591
592 dlg.GetPaths( files );
593
594 if( !files.IsEmpty() )
595 {
596 wxFileName first( files.front() );
597 *lastDir = first.GetPath();
598 }
599 }
600 else
601 {
602 auto it = m_supportedSymFiles.find( fileType );
603
604 if( it == m_supportedSymFiles.end() )
605 return;
606
607 const IO_BASE::IO_FILE_DESC& fileDesc = it->second;
608
609 wxFileDialog dlg( topLevelParent, title, *lastDir, wxEmptyString, fileDesc.FileFilter(),
610 wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
611
613
614 if( dlg.ShowModal() == wxID_CANCEL )
615 return;
616
617 dlg.GetPaths( files );
618 *lastDir = dlg.GetDirectory();
619 }
620
621 bool addDuplicates = false;
622 bool applyToAll = false;
623 wxString warning = _( "Warning: Duplicate Nicknames" );
624 wxString msg = _( "An item nicknamed '%s' already exists." );
625 wxString detailedMsg = _( "One of the nicknames will need to be changed." );
626
627 for( const wxString& filePath : files )
628 {
629 wxFileName fn( filePath );
630 wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), true );
631 bool doAdd = true;
632
633 if( cur_model()->ContainsNickname( nickname ) )
634 {
635 if( !applyToAll )
636 {
637 addDuplicates = OKOrCancelDialog( topLevelParent, warning,
638 wxString::Format( msg, nickname ), detailedMsg,
639 _( "Skip" ), _( "Add Anyway" ),
640 &applyToAll ) == wxID_CANCEL;
641 }
642
643 doAdd = addDuplicates;
644 }
645
646 if( doAdd && cur_grid()->AppendRows( 1 ) )
647 {
648 int last_row = cur_grid()->GetNumberRows() - 1;
649
650 cur_grid()->SetCellValue( last_row, COL_NICKNAME, nickname );
651 cur_grid()->SetCellValue( last_row, COL_TYPE, SCH_IO_MGR::ShowType( fileType ) );
652
653 // try to use path normalized to an environmental variable or project path
654 wxString path = NormalizePath( filePath, &envVars, m_project->GetProjectPath() );
655
656 // Do not use the project path in the global library table. This will almost
657 // assuredly be wrong for a different project.
658 if( m_notebook->GetSelection() == 0 && path.Contains( wxT( "${KIPRJMOD}" ) ) )
659 path = fn.GetFullPath();
660
661 cur_grid()->SetCellValue( last_row, COL_URI, path );
662 }
663 }
664
665 if( !files.IsEmpty() )
666 {
667 cur_grid()->MakeCellVisible( cur_grid()->GetNumberRows() - 1, COL_ENABLED );
668 cur_grid()->SetGridCursor( cur_grid()->GetNumberRows() - 1, COL_NICKNAME );
669 }
670}
671
672
677
678
683
684
689
690
695
696
697void PANEL_SYM_LIB_TABLE::onReset( wxCommandEvent& event )
698{
699 if( !cur_grid()->CommitPendingChanges() )
700 return;
701
702 WX_GRID* grid = get_grid( 0 );
703
704 // No need to prompt to preserve an empty table
705 if( grid->GetNumberRows() > 0 && !IsOK( this, wxString::Format( _( "This action will reset your global library "
706 "table on disk and cannot be undone." ) ) ) )
707 {
708 return;
709 }
710
711 wxString* lastGlobalLibDir = nullptr;
712
714 {
715 if( cfg->m_lastSymbolLibDir.IsEmpty() )
716 cfg->m_lastSymbolLibDir = PATHS::GetDefaultUserSymbolsPath();
717
718 lastGlobalLibDir = &cfg->m_lastSymbolLibDir;
719 }
720
722
723 // Go ahead and reload here because this action takes place even if the dialog is canceled
725
726 if( KIFACE *schface = m_parent->Kiway().KiFACE( KIWAY::FACE_SCH ) )
727 schface->PreloadLibraries( &m_parent->Kiway() );
728
729 grid->Freeze();
730
731 wxGridTableBase* table = grid->GetTable();
732 grid->DestroyTable( table );
733
734 std::optional<LIBRARY_TABLE*> newTable = Pgm().GetLibraryManager().Table( LIBRARY_TABLE_TYPE::SYMBOL,
736 wxASSERT( newTable );
737
739
740 grid->SetTable( new SYMBOL_LIB_TABLE_GRID_DATA_MODEL( m_parent, grid, *newTable.value(), adapter, m_pluginChoices,
741 lastGlobalLibDir, wxEmptyString ),
742 true /* take ownership */ );
743
745 static_cast<LIB_TABLE_NOTEBOOK_PANEL*>( m_notebook->GetPage( 0 ) );
746 panel0->ClearDirty();
747 static_cast<LIB_TABLE_GRID_DATA_MODEL*>( grid->GetTable() )->SetChangeCallback(
748 [panel0]() { panel0->MarkDirty(); } );
749
750 m_parent->m_GlobalTableChanged = true;
751
752 grid->Thaw();
753
754 if( grid->GetNumberRows() > 0 )
755 {
756 grid->SetGridCursor( 0, COL_NICKNAME );
757 grid->SelectRow( 0 );
758 }
759}
760
761
762void PANEL_SYM_LIB_TABLE::onNotebookPageChangeRequest( wxAuiNotebookEvent& aEvent )
763{
765 aEvent.Veto();
766 else
767 aEvent.Skip();
768}
769
770
771void PANEL_SYM_LIB_TABLE::onPageChange( wxAuiNotebookEvent& event )
772{
773 m_resetGlobal->Enable( m_notebook->GetSelection() == 0 );
774}
775
776
777void PANEL_SYM_LIB_TABLE::onNotebookPageCloseRequest( wxAuiNotebookEvent& aEvent )
778{
779 wxAuiNotebook* notebook = (wxAuiNotebook*) aEvent.GetEventObject();
780 wxWindow* page = notebook->GetPage( aEvent.GetSelection() );
781
782 if( LIB_TABLE_NOTEBOOK_PANEL* panel = dynamic_cast<LIB_TABLE_NOTEBOOK_PANEL*>( page ) )
783 {
784 if( panel->GetClosable() )
785 {
786 if( !panel->GetCanClose() )
787 aEvent.Veto();
788 }
789 else
790 {
791 aEvent.Veto();
792 }
793 }
794}
795
796
798{
799 if( !cur_grid()->CommitPendingChanges() )
800 return;
801
802 wxArrayInt selectedRows = cur_grid()->GetSelectedRows();
803
804 if( selectedRows.empty() && cur_grid()->GetGridCursorRow() >= 0 )
805 selectedRows.push_back( cur_grid()->GetGridCursorRow() );
806
807 wxArrayInt legacyRows;
808 wxString databaseType = SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_DATABASE );
809 wxString kicadType = SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_KICAD );
810 wxString msg;
811
812 for( int row : selectedRows )
813 {
814 if( cur_grid()->GetCellValue( row, COL_TYPE ) != databaseType
815 && cur_grid()->GetCellValue( row, COL_TYPE ) != kicadType )
816 {
817 legacyRows.push_back( row );
818 }
819 }
820
821 if( legacyRows.size() <= 0 )
822 {
823 wxMessageBox( _( "Select one or more rows containing libraries to save as current KiCad format." ) );
824 return;
825 }
826 else
827 {
828 if( legacyRows.size() == 1 )
829 {
830 msg.Printf( _( "Save '%s' as current KiCad format (*.kicad_sym) and replace legacy entry in table?" ),
831 cur_grid()->GetCellValue( legacyRows[0], COL_NICKNAME ) );
832 }
833 else
834 {
835 msg.Printf( _( "Save %d libraries as current KiCad format (*.kicad_sym) and replace legacy entries "
836 "in table?" ),
837 (int) legacyRows.size() );
838 }
839
840 if( !IsOK( m_parent, msg ) )
841 return;
842 }
843
844 for( int row : legacyRows )
845 {
846 wxString relPath = cur_grid()->GetCellValue( row, COL_URI );
847 wxString resolvedPath = ExpandEnvVarSubstitutions( relPath, m_project );
848 wxFileName legacyLib( resolvedPath );
849
850 if( !legacyLib.Exists() )
851 {
852 DisplayErrorMessage( m_parent, wxString::Format( _( "Library '%s' not found." ), relPath ) );
853 continue;
854 }
855
856 wxFileName newLib( resolvedPath );
857 newLib.SetExt( "kicad_sym" );
858
859 if( newLib.Exists() )
860 {
861 msg.Printf( _( "File '%s' already exists. Do you want overwrite this file?" ), newLib.GetFullPath() );
862
863 switch( wxMessageBox( msg, _( "Migrate Library" ), wxYES_NO | wxCANCEL | wxICON_QUESTION, m_parent ) )
864 {
865 case wxYES: break;
866 case wxNO: continue;
867 case wxCANCEL: return;
868 }
869 }
870
871 wxString options = cur_grid()->GetCellValue( row, COL_OPTIONS );
872 std::map<std::string, UTF8> props( LIBRARY_TABLE::ParseOptions( options.ToStdString() ) );
873
874 if( SCH_IO_MGR::ConvertLibrary( &props, legacyLib.GetFullPath(), newLib.GetFullPath() ) )
875 {
876 relPath = NormalizePath( newLib.GetFullPath(), &Pgm().GetLocalEnvVariables(), m_project );
877
878 cur_grid()->SetCellValue( row, COL_URI, relPath );
879 cur_grid()->SetCellValue( row, COL_TYPE, kicadType );
880 cur_grid()->SetCellValue( row, COL_OPTIONS, wxEmptyString );
881 }
882 else
883 {
884 DisplayErrorMessage( m_parent, wxString::Format( _( "Failed to save symbol library file '%s'." ),
885 newLib.GetFullPath() ) );
886 }
887 }
888}
889
890
892{
893 if( !cur_grid()->CommitPendingChanges() )
894 return false;
895
896 if( !verifyTables() )
897 return false;
898
900 std::optional<LIBRARY_TABLE*> optTable = manager.Table( LIBRARY_TABLE_TYPE::SYMBOL, LIBRARY_TABLE_SCOPE::GLOBAL );
901 wxCHECK( optTable, false );
902 LIBRARY_TABLE* globalTable = *optTable;
903
904 if( get_model( 0 )->Table() != *globalTable )
905 {
906 m_parent->m_GlobalTableChanged = true;
907 *globalTable = get_model( 0 )->Table();
908
909 globalTable->Save().map_error(
910 []( const LIBRARY_ERROR& aError )
911 {
912 wxMessageBox( _( "Error saving global library table:\n\n" ) + aError.message,
913 _( "File Save Error" ), wxOK | wxICON_ERROR );
914 } );
915 }
916
918
919 if( optTable.has_value() && get_model( 1 )->Table().Path() == optTable.value()->Path() )
920 {
921 LIBRARY_TABLE* projectTable = *optTable;
922
923 if( get_model( 1 )->Table() != *projectTable )
924 {
925 m_parent->m_ProjectTableChanged = true;
926 *projectTable = get_model( 1 )->Table();
927
928 projectTable->Save().map_error(
929 []( const LIBRARY_ERROR& aError )
930 {
931 wxMessageBox( _( "Error saving project library table:\n\n" ) + aError.message,
932 _( "File Save Error" ), wxOK | wxICON_ERROR );
933 } );
934 }
935 }
936
938 return true;
939}
940
941
943{
944 wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
945 wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
946
947 std::set< wxString > unique;
948
949 // clear the table
950 m_path_subs_grid->ClearRows();
951
952 for( int page = 0 ; page < (int) m_notebook->GetPageCount(); ++page )
953 {
955
956 for( int row = 0; row < model->GetNumberRows(); ++row )
957 {
958 wxString uri = model->GetValue( row, COL_URI );
959
960 while( re.Matches( uri ) )
961 {
962 wxString envvar = re.GetMatch( uri, 2 );
963
964 // if not ${...} form then must be $(...)
965 if( envvar.IsEmpty() )
966 envvar = re.GetMatch( uri, 4 );
967
968 // ignore duplicates
969 unique.insert( envvar );
970
971 // delete the last match and search again
972 uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
973 }
974 }
975 }
976
977 // Make sure this special environment variable shows up even if it was
978 // not used yet. It is automatically set by KiCad to the directory holding
979 // the current project.
980 unique.insert( PROJECT_VAR_NAME );
981 unique.insert( ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ) );
982
983 for( const wxString& evName : unique )
984 {
985 int row = m_path_subs_grid->GetNumberRows();
986 m_path_subs_grid->AppendRows( 1 );
987
988 m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
989 m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
990
991 wxString evValue;
992 wxGetEnv( evName, &evValue );
993 m_path_subs_grid->SetCellValue( row, 1, evValue );
994 m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
995 }
996
997 adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
998}
999
1000
1002{
1003 // Account for scroll bars
1004 aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
1005
1006 m_path_subs_grid->AutoSizeColumn( 0 );
1007 m_path_subs_grid->SetColSize( 0, std::max( 72, m_path_subs_grid->GetColSize( 0 ) ) );
1008 m_path_subs_grid->SetColSize( 1, std::max( 120, aWidth - m_path_subs_grid->GetColSize( 0 ) ) );
1009}
1010
1011
1012void PANEL_SYM_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
1013{
1014 adjustPathSubsGridColumns( event.GetSize().GetX() );
1015
1016 event.Skip();
1017}
1018
1019
1020void InvokeSchEditSymbolLibTable( KIWAY* aKiway, wxWindow *aParent )
1021{
1022 auto symbolEditor = static_cast<SYMBOL_EDIT_FRAME*>( aKiway->Player( FRAME_SCH_SYMBOL_EDITOR, false ) );
1023 wxString msg;
1024
1025 // Refuse to open the dialog re-entrantly while a library sync is running. A
1026 // sync can yield the event loop (via the progress dialog), which dispatches any
1027 // pending UI events — including clicks that accumulated while the app was busy.
1028 // Opening the dialog mid-sync corrupts the library tree. Reschedule instead.
1029 if( symbolEditor && symbolEditor->IsSyncLibrariesInProgress() )
1030 {
1031 symbolEditor->CallAfter( [aKiway, aParent]()
1032 {
1033 InvokeSchEditSymbolLibTable( aKiway, aParent );
1034 } );
1035 return;
1036 }
1037
1038 if( symbolEditor )
1039 {
1040 // This prevents an ugly crash on OSX (https://bugs.launchpad.net/kicad/+bug/1765286)
1041 symbolEditor->FreezeLibraryTree();
1042
1043 if( symbolEditor->HasLibModifications() )
1044 {
1045 msg = _( "Modifications have been made to one or more symbol libraries.\n"
1046 "Changes must be saved or discarded before the symbol library table can be modified." );
1047
1048 switch( UnsavedChangesDialog( aParent, msg ) )
1049 {
1050 case wxID_YES: symbolEditor->SaveAll(); break;
1051 case wxID_NO: symbolEditor->RevertAll(); break;
1052 default:
1053 case wxID_CANCEL: symbolEditor->ThawLibraryTree(); return;
1054 }
1055 }
1056 }
1057
1058 DIALOG_EDIT_LIBRARY_TABLES dlg( aParent, _( "Symbol Libraries" ) );
1059 dlg.SetKiway( &dlg, aKiway );
1060
1061 dlg.InstallPanel( new PANEL_SYM_LIB_TABLE( &dlg, &aKiway->Prj() ) );
1062
1063 if( dlg.ShowModal() == wxID_CANCEL )
1064 {
1065 if( symbolEditor )
1066 symbolEditor->ThawLibraryTree();
1067
1068 return;
1069 }
1070
1071 if( dlg.m_GlobalTableChanged )
1073
1074 if( dlg.m_ProjectTableChanged )
1075 {
1076 // Trigger a reload of the table and cancel an in-progress background load
1078 }
1079
1080 // Trigger a reload in case any libraries have been added or removed
1081 if( KIFACE *schface = aKiway->KiFACE( KIWAY::FACE_SCH ) )
1082 schface->PreloadLibraries( aKiway );
1083
1084 if( symbolEditor )
1085 symbolEditor->ThawLibraryTree();
1086}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
An options editor in the form of a two column name/value spreadsheet like (table) UI.
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:68
int ShowModal() override
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:61
WX_GRID * m_grid
I don't own the grid, but he owns me.
void SetTooltipEnable(int aCol, bool aEnable=true)
Enable the tooltip for a column.
Definition grid_tricks.h:75
void SetKiway(wxWindow *aDest, KIWAY *aKiway)
It is only used for debugging, since "this" is not a wxWindow*.
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:315
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition kiway.cpp:402
virtual KIFACE * KiFACE(FACE_T aFaceId, bool doLoad=true)
Return the KIFACE* given a FACE_T.
Definition kiway.cpp:211
@ FACE_SCH
eeschema DSO
Definition kiway.h:322
virtual PROJECT & Prj() const
Return the PROJECT associated with this KIWAY.
Definition kiway.cpp:205
static wxString ExpandURI(const wxString &aShortURI, const PROJECT &aProject)
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
static bool CreateGlobalTable(LIBRARY_TABLE_TYPE aType, bool aPopulateDefaultLibraries)
void LoadGlobalTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the global library tables in the given list, or all tables if no list is given
void ProjectChanged()
Notify all adapters that the project has changed.
void SetOptions(const wxString &aOptions)
const wxString & Type() const
static const wxString TABLE_TYPE_NAME
const wxString & URI() const
const wxString & Nickname() const
const wxString & Options() const
LIBRARY_RESULT< void > Save()
static std::map< std::string, UTF8 > ParseOptions(const std::string &aOptionsList)
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:192
This abstract base class mixes any object derived from #LIB_TABLE into wxGridTableBase so the result ...
virtual LIBRARY_TABLE_ROW & at(size_t aIndex)
void SetChangeCallback(std::function< void()> aCallback)
LIBRARY_TABLE_ROW & At(size_t aIndex)
void SetValue(int aRow, int aCol, const wxString &aValue) override
LIB_TABLE_GRID_DATA_MODEL(DIALOG_SHIM *aParent, WX_GRID *aGrid, const LIBRARY_TABLE &aTableToEdit, LIBRARY_MANAGER_ADAPTER *aAdapter, const wxArrayString &aPluginChoices, wxString *aMRUDirectory, const wxString &aProjectPath)
static void MoveUpHandler(WX_GRID *aGrid)
LIB_TABLE_GRID_TRICKS(WX_GRID *aGrid)
static void DeleteRowHandler(WX_GRID *aGrid)
static void AppendRowHandler(WX_GRID *aGrid)
static bool VerifyTable(WX_GRID *aGrid, bool aSupportsVisibilityColumn, std::function< void(int aRow, int aCol)> aErrorHandler)
static void MoveDownHandler(WX_GRID *aGrid)
static void AddTable(wxAuiNotebook *aNotebook, const wxString &aTitle, bool aClosable)
PANEL_SYM_LIB_TABLE_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
STD_BITMAP_BUTTON * m_move_down_button
Dialog to show and edit symbol library tables.
void moveUpHandler(wxCommandEvent &event) override
SYMBOL_LIB_TABLE_GRID_DATA_MODEL * get_model(int aPage) const
void onReset(wxCommandEvent &event) override
void deleteRowHandler(wxCommandEvent &event) override
WX_GRID * get_grid(int aPage) const
void onNotebookPageCloseRequest(wxAuiNotebookEvent &aEvent)
void onPageChange(wxAuiNotebookEvent &event) override
void browseLibrariesHandler(wxCommandEvent &event) override
void AddTable(LIBRARY_TABLE *table, const wxString &aTitle, bool aClosable)
bool verifyTables()
Trim important fields, removes blank row entries, and checks for duplicates.
void adjustPathSubsGridColumns(int aWidth)
void OpenTable(const std::shared_ptr< LIBRARY_TABLE > &table, const wxString &aTitle)
void onSizeGrid(wxSizeEvent &event) override
std::vector< std::shared_ptr< LIBRARY_TABLE > > m_nestedTables
void onConvertLegacyLibraries(wxCommandEvent &event) override
void onNotebookPageChangeRequest(wxAuiNotebookEvent &aEvent)
std::map< SCH_IO_MGR::SCH_FILE_T, IO_BASE::IO_FILE_DESC > m_supportedSymFiles
bool TransferDataToWindow() override
wxArrayString m_pluginChoices
void moveDownHandler(wxCommandEvent &event) override
PANEL_SYM_LIB_TABLE(DIALOG_EDIT_LIBRARY_TABLES *aParent, PROJECT *m_project)
SYMBOL_LIB_TABLE_GRID_DATA_MODEL * cur_model() const
void populateEnvironReadOnlyTable()
Populate the readonly environment variable table with names and values by examining all the full_uri ...
WX_GRID * cur_grid() const
DIALOG_EDIT_LIBRARY_TABLES * m_parent
bool TransferDataFromWindow() override
void appendRowHandler(wxCommandEvent &event) override
static wxString GetDefaultUserSymbolsPath()
Gets the default path we point users to create projects.
Definition paths.cpp:82
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:787
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:132
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
Definition project.h:66
static bool ConvertLibrary(std::map< std::string, UTF8 > *aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilepath)
Convert a schematic symbol library to the latest KiCad format.
static SCH_FILE_T EnumFromStr(const wxString &aFileType)
Return the #SCH_FILE_T from the corresponding plugin type name: "kicad", "legacy",...
static const wxString ShowType(SCH_FILE_T aFileType)
Return a brief name for a plugin, given aFileType enum.
static SCH_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
Return a plugin type given a symbol library using the file extension of aLibPath.
The symbol library editor main window.
SYMBOL_GRID_TRICKS(PANEL_SYM_LIB_TABLE *aPanel, WX_GRID *aGrid, std::function< void(wxCommandEvent &)> aAddHandler)
bool supportsVisibilityColumn() override
wxString getTablePreamble() override
PANEL_SYM_LIB_TABLE * m_panel
void openTable(const LIBRARY_TABLE_ROW &aRow) override
void optionsEditor(int aRow) override
static bool SupportsVisibilityColumn()
An interface to the global shared library manager that is schematic-specific and linked to one projec...
SYMBOL_LIB_TABLE_GRID_DATA_MODEL(DIALOG_SHIM *aParent, WX_GRID *aGrid, const LIBRARY_TABLE &aTableToEdit, SYMBOL_LIBRARY_ADAPTER *aAdapter, const wxArrayString &aPluginChoices, wxString *aMRUDirectory, const wxString &aProjectPath)
void SetValue(int aRow, int aCol, const wxString &aValue) override
wxString getFileTypes(WX_GRID *aGrid, int aRow) override
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:708
The common library.
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition confirm.cpp:169
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:278
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:221
int UnsavedChangesDialog(wxWindow *parent, const wxString &aMessage, bool *aApplyToAll)
A specialized version of HandleUnsavedChanges which handles an apply-to-all checkbox.
Definition confirm.cpp:64
This file is part of the common library.
#define _(s)
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalize a file path to an environmental variable, if possible.
Definition env_paths.cpp:73
Helper functions to substitute paths with environmental variables.
Functions related to environment variables, including help functions.
@ FRAME_SCH_SYMBOL_EDITOR
Definition frame_type.h:35
static const std::string SymbolLibraryTableFileName
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PROJECT & Prj()
Definition kicad.cpp:655
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:435
static constexpr int FIRST_MENU_ID
Special menu ID for folder-based KiCad symbol library format.
static constexpr int ID_PANEL_SYM_LIB_KICAD_FOLDER
void InvokeSchEditSymbolLibTable(KIWAY *aKiway, wxWindow *aParent)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:41
T * GetAppSettings(const char *aFilename)
std::vector< FAB_LAYER_COLOR > dummy
MODEL3D_FORMAT_TYPE fileType(const char *aFileName)
Container that describes file type info.
Definition io_base.h:43
bool m_IsFile
Whether the library is a folder or a file.
Definition io_base.h:51
wxString FileFilter() const
Definition io_base.cpp:40
Implement a participant in the KIWAY alchemy.
Definition kiway.h:156
wxString message
Container that describes file type info for the add a library options.
bool m_IsFile
Whether the library is a folder or a file.
wxString m_Description
Description shown in the file picker dialog.
wxString m_FileFilter
Filter used for file pickers if m_IsFile is true.
DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T m_Plugin
wxString m_FolderSearchExtension
In case of folders it stands for extensions of files stored inside.
std::string path
KIBIS_MODEL * model
std::vector< std::vector< std::string > > table
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.