diff --git a/pcbnew/dialogs/panel_fp_lib_table.cpp b/pcbnew/dialogs/panel_fp_lib_table.cpp index 2cfecfaf04..ac11afacdc 100644 --- a/pcbnew/dialogs/panel_fp_lib_table.cpp +++ b/pcbnew/dialogs/panel_fp_lib_table.cpp @@ -61,16 +61,21 @@ #include // For ID_PCBNEW_END_LIST // clang-format off + +/** +* Container that describes file type info for the add a library options +*/ struct supportedFileType { - wxString m_Description; ///< Description shown in the file picker dialog - wxString m_FileFilter; ///< In case of folders it stands for extensions of files stored inside - 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 + wxString m_FolderSearchExtension; ///< In case of folders it stands for extensions of files stored inside + bool m_IsFile; ///< Whether the library is a folder or a file IO_MGR::PCB_FILE_T m_Plugin; }; -/* - * Event IDs for the menu items in the split button menu +/** + * Event IDs for the menu items in the split button menu for add a library */ enum { ID_PANEL_FPLIB_ADD_KICADMOD = ID_PCBNEW_END_LIST, @@ -79,18 +84,99 @@ enum { ID_PANEL_FPLIB_ADD_GEDA, }; -/* - * Map of event id as key to the file type struct - */ -static const std::map fileTypes = +/** +* Map with event id as the key to supported file types that will be listed for the add a library option. +* +*/ +static const std::map& fileTypes() { - { ID_PANEL_FPLIB_ADD_KICADMOD, { "KiCad (folder with .kicad_mod files)", "", false, IO_MGR::KICAD_SEXP } }, - { ID_PANEL_FPLIB_ADD_EAGLE6, { "Eagle 6.x (*.lbr)", EagleFootprintLibPathWildcard(), true, IO_MGR::EAGLE } }, - { ID_PANEL_FPLIB_ADD_KICADLEGACY, { "KiCad legacy (*.mod)", LegacyFootprintLibPathWildcard(), true, IO_MGR::LEGACY } }, - { ID_PANEL_FPLIB_ADD_GEDA, { "Geda (folder with *.fp files)", "", false, IO_MGR::GEDA_PCB } }, -}; + /* + * This is wrapped inside a function to prevent a static initialization order fiasco with the file extension + * variables. Once C++20 is allowed in KiCad code, those file extensions can be made constexpr and this can + * be removed from a function call and placed in the file normally. + */ + static const std::map fileTypes = + { + { ID_PANEL_FPLIB_ADD_KICADMOD, { "KiCad (folder with .kicad_mod files)", "", KiCadFootprintFileExtension, false, IO_MGR::KICAD_SEXP } }, + { ID_PANEL_FPLIB_ADD_EAGLE6, { "Eagle 6.x (*.lbr)", EagleFootprintLibPathWildcard(), "", true, IO_MGR::EAGLE } }, + { ID_PANEL_FPLIB_ADD_KICADLEGACY, { "KiCad legacy (*.mod)", LegacyFootprintLibPathWildcard(), "", true, IO_MGR::LEGACY } }, + { ID_PANEL_FPLIB_ADD_GEDA, { "Geda (folder with *.fp files)", "", GedaPcbFootprintLibFileExtension, false, IO_MGR::GEDA_PCB } }, + }; + + return fileTypes; +} // clang-format on + +/** + * Traverser implementation that looks to find any and all "folder" libraries by looking for files + * with a specific extension inside folders + */ +class LIBRARY_TRAVERSER : public wxDirTraverser +{ +public: + LIBRARY_TRAVERSER( wxString aSearchExtension ) : m_searchExtension( aSearchExtension ) + { + } + + + virtual wxDirTraverseResult OnFile( const wxString& aFileName ) + { + wxFileName file( aFileName ); + if( m_searchExtension.IsSameAs( file.GetExt(), false ) ) + { + m_foundDirs.insert( { m_currentDir, 1 } ); + } + + return wxDIR_CONTINUE; + } + + + virtual wxDirTraverseResult OnOpenError( const wxString& aOpenErrorName ) + { + m_failedDirs.insert( { aOpenErrorName, 1 } ); + return wxDIR_IGNORE; + } + + + bool HasDirectoryOpenFailures() + { + return m_failedDirs.size() > 0; + } + + + virtual wxDirTraverseResult OnDir( const wxString& aDirName ) + { + m_currentDir = aDirName; + return wxDIR_CONTINUE; + } + + + void GetPaths( wxArrayString& aPathArray ) + { + for( auto foundDirsPair : m_foundDirs ) + { + aPathArray.Add( foundDirsPair.first ); + } + } + + + void GetFailedPaths( wxArrayString& aPathArray ) + { + for( auto failedDirsPair : m_failedDirs ) + { + aPathArray.Add( failedDirsPair.first ); + } + } + +private: + wxString m_searchExtension; + wxString m_currentDir; + std::unordered_map m_foundDirs; + std::unordered_map m_failedDirs; +}; + + /** * This class builds a wxGridTableBase by wrapping an #FP_LIB_TABLE object. */ @@ -353,7 +439,7 @@ PANEL_FP_LIB_TABLE::PANEL_FP_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, // Populate the browse library options wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu(); - for( auto& fileType : fileTypes ) + for( auto& fileType : fileTypes() ) { browseMenu->Append( fileType.first, fileType.second.m_Description ); @@ -618,14 +704,14 @@ void PANEL_FP_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) if( event.GetEventType() == wxEVT_BUTTON ) { // Let's default to adding a kicad module for just the module - fileTypeIt = fileTypes.find( ID_PANEL_FPLIB_ADD_KICADMOD ); + fileTypeIt = fileTypes().find( ID_PANEL_FPLIB_ADD_KICADMOD ); } else { - fileTypeIt = fileTypes.find( event.GetId() ); + fileTypeIt = fileTypes().find( event.GetId() ); } - if( fileTypeIt == fileTypes.end() ) + if( fileTypeIt == fileTypes().end() ) { wxLogWarning( "File type selection event received but could not find the file type in the table" ); return; @@ -666,7 +752,33 @@ void PANEL_FP_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) if( result == wxID_CANCEL ) return; - files.Add( dlg.GetPath() ); + // is there a file extension configured to hunt out their containing folders? + if( fileType.m_FolderSearchExtension != "" ) + { + wxDir rootDir( dlg.GetPath() ); + + LIBRARY_TRAVERSER traverser( fileType.m_FolderSearchExtension ); + rootDir.Traverse( traverser ); + + traverser.GetPaths( files ); + + if( traverser.HasDirectoryOpenFailures() ) + { + wxArrayString failedDirs; + traverser.GetPaths( failedDirs ); + wxString detailedMsg = _( "The following directories could not be opened: \n" ); + + for( auto& path : failedDirs ) + detailedMsg << path << "\n"; + + DisplayErrorMessage( this, _( "Failed to open directories to look for libraries" ), + detailedMsg ); + } + } + else + { + files.Add( dlg.GetPath() ); + } m_lastBrowseDir = dlg.GetPath(); }