diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 3507a11e5f..f2a8f68c86 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -14,6 +14,7 @@ include_directories( ${CMAKE_SOURCE_DIR}/resources/bitmaps_png ${CMAKE_SOURCE_DIR}/3d-viewer ${CMAKE_SOURCE_DIR}/pcbnew + ${CMAKE_SOURCE_DIR}/kicad ${INC_AFTER} ) @@ -117,6 +118,11 @@ set( KICOMMON_SRCS database/database_lib_settings.cpp + design_block_lib_table.cpp + design_block_io.cpp + design_block_info.cpp + design_block_info_impl.cpp + advanced_config.cpp asset_archive.cpp array_axis.cpp @@ -173,6 +179,8 @@ set( KICOMMON_SRCS ../scripting/python_scripting.cpp io/kicad/kicad_io_utils.cpp # needed by richio + io/io_base.cpp + io/io_utils.cpp ) if( KICAD_IPC_API ) diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 6a67df4c68..bbc6ebea4f 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -99,6 +99,7 @@ static const wxChar UpdateUIEventInterval[] = wxT( "UpdateUIEventInterval" ); static const wxChar V3DRT_BevelHeight_um[] = wxT( "V3DRT_BevelHeight_um" ); static const wxChar V3DRT_BevelExtentFactor[] = wxT( "V3DRT_BevelExtentFactor" ); static const wxChar UseClipper2[] = wxT( "UseClipper2" ); +static const wxChar EnableDesignBlocks[] = wxT( "EnableDesignBlocks" ); static const wxChar EnableGenerators[] = wxT( "EnableGenerators" ); static const wxChar EnableGit[] = wxT( "EnableGit" ); static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" ); @@ -241,6 +242,7 @@ ADVANCED_CFG::ADVANCED_CFG() m_CompactSave = false; m_UpdateUIEventInterval = 0; m_ShowRepairSchematic = false; + m_EnableDesignBlocks = false; m_EnableGenerators = false; m_EnableGit = false; m_EnableLibWithText = false; @@ -459,6 +461,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) m_DisambiguationMenuDelay, 50, 10000 ) ); + configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableDesignBlocks, + &m_EnableDesignBlocks, m_EnableDesignBlocks ) ); + configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGenerators, &m_EnableGenerators, m_EnableGenerators ) ); diff --git a/common/design_block.h b/common/design_block.h new file mode 100644 index 0000000000..74775ddbf9 --- /dev/null +++ b/common/design_block.h @@ -0,0 +1,54 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com> + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include <kicommon.h> +#include <lib_id.h> + + +class DESIGN_BLOCK +{ +public: + void SetLibId( const LIB_ID& aName ) { m_lib_id = aName; } + const LIB_ID& GetLibId() const { return m_lib_id; } + + wxString GetLibIdAsString() const { return m_lib_id.Format(); } + + wxString GetLibDescription() const { return m_libDescription; } + void SetLibDescription( const wxString& aDesc ) { m_libDescription = aDesc; } + + wxString GetKeywords() const { return m_keywords; } + void SetKeywords( const wxString& aKeywords ) { m_keywords = aKeywords; } + + wxString GetDocumentationUrl() const { return m_doc_url; } + void SetDocumentationUrl( const wxString& aDocumentationUrl ) { m_doc_url = aDocumentationUrl; } + + wxString GetSchematicFile() const { return m_schematicFile; } + void SetSchematicFile( const wxString& aFile ) { m_schematicFile = aFile; } + +private: + LIB_ID m_lib_id; + wxString m_schematicFile; // File name and path for schematic symbol. + wxString m_libDescription; // File name and path for documentation file. + wxString m_keywords; // Search keywords to find footprint in library. + wxString m_doc_url; // URL of external documentation +}; diff --git a/common/design_block_info.cpp b/common/design_block_info.cpp new file mode 100644 index 0000000000..9d1d397ad1 --- /dev/null +++ b/common/design_block_info.cpp @@ -0,0 +1,108 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* + * Functions to read design block libraries and fill m_design_blocks by available design blocks names + * and their documentation (comments and keywords) + */ + +#include <design_block_info.h> +#include <fp_lib_table.h> +#include <dialogs/html_message_box.h> +#include <string_utils.h> +#include <kiface_ids.h> +#include <kiway.h> +#include <lib_id.h> +#include <thread> +#include <utility> +#include <wx/tokenzr.h> +#include <kiface_base.h> + +DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aLibNickname, + const wxString& aDesignBlockName ) +{ + if( aDesignBlockName.IsEmpty() ) + return nullptr; + + for( std::unique_ptr<DESIGN_BLOCK_INFO>& db : m_list ) + { + if( aLibNickname == db->GetLibNickname() && aDesignBlockName == db->GetDesignBlockName() ) + return db.get(); + } + + return nullptr; +} + + +DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aDesignBlockName ) +{ + if( aDesignBlockName.IsEmpty() ) + return nullptr; + + LIB_ID dbid; + + wxCHECK_MSG( dbid.Parse( aDesignBlockName ) < 0, nullptr, + wxString::Format( wxT( "'%s' is not a valid LIB_ID." ), aDesignBlockName ) ); + + return GetDesignBlockInfo( dbid.GetLibNickname(), dbid.GetLibItemName() ); +} + + +std::vector<SEARCH_TERM> DESIGN_BLOCK_INFO::GetSearchTerms() +{ + std::vector<SEARCH_TERM> terms; + + terms.emplace_back( SEARCH_TERM( GetName(), 8 ) ); + + wxStringTokenizer keywordTokenizer( GetKeywords(), wxS( " " ), wxTOKEN_STRTOK ); + + while( keywordTokenizer.HasMoreTokens() ) + terms.emplace_back( SEARCH_TERM( keywordTokenizer.GetNextToken(), 4 ) ); + + // Also include keywords as one long string, just in case + terms.emplace_back( SEARCH_TERM( GetKeywords(), 1 ) ); + terms.emplace_back( SEARCH_TERM( GetDesc(), 1 ) ); + + return terms; +} + + +bool DESIGN_BLOCK_INFO::InLibrary( const wxString& aLibrary ) const +{ + return aLibrary == m_nickname; +} + + +bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs ) +{ + int retv = StrNumCmp( lhs.m_nickname, rhs.m_nickname, false ); + + if( retv != 0 ) + return retv < 0; + + // Technically design block names are not case sensitive because the file name is used + // as the design block name. On windows this would be problematic because windows does + // not support case sensitive file names by default. This should not cause any issues + // and allow for a future change to use the name defined in the design block file. + return StrNumCmp( lhs.m_dbname, rhs.m_dbname, false ) < 0; +} diff --git a/common/design_block_info.h b/common/design_block_info.h new file mode 100644 index 0000000000..9d8862bce4 --- /dev/null +++ b/common/design_block_info.h @@ -0,0 +1,219 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* + * @file design_block_info.h + */ + +#ifndef DESIGN_BLOCK_INFO_H_ +#define DESIGN_BLOCK_INFO_H_ + + +#include <kicommon.h> +#include <boost/ptr_container/ptr_vector.hpp> +#include <import_export.h> +#include <ki_exception.h> +#include <core/sync_queue.h> +#include <lib_tree_item.h> +#include <atomic> +#include <functional> +#include <memory> + + +class DESIGN_BLOCK_LIB_TABLE; +class DESIGN_BLOCK_LIST; +class DESIGN_BLOCK_LIST_IMPL; +class PROGRESS_REPORTER; +class wxTopLevelWindow; +class KIWAY; +class wxTextFile; + + +/* + * Helper class to handle the list of design blocks available in libraries. It stores + * design block names, doc and keywords. + * + * This is a virtual class; its implementation lives in common/design_block_info_impl.cpp. + * To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance(). + */ +class KICOMMON_API DESIGN_BLOCK_INFO : public LIB_TREE_ITEM +{ +public: + virtual ~DESIGN_BLOCK_INFO() {} + + // These two accessors do not have to call ensure_loaded(), because constructor + // fills in these fields: + + const wxString& GetDesignBlockName() const { return m_dbname; } + + wxString GetLibNickname() const override { return m_nickname; } + + wxString GetName() const override { return m_dbname; } + + LIB_ID GetLIB_ID() const override { return LIB_ID( m_nickname, m_dbname ); } + + wxString GetDesc() override + { + ensure_loaded(); + return m_doc; + } + + wxString GetKeywords() + { + ensure_loaded(); + return m_keywords; + } + + std::vector<SEARCH_TERM> GetSearchTerms() override; + + int GetOrderNum() + { + ensure_loaded(); + return m_num; + } + + /** + * Test if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary. + * + * @param aLibrary is the nickname of the library to test. + * + * @return true if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary. Otherwise + * false. + */ + bool InLibrary( const wxString& aLibrary ) const; + + /** + * Less than comparison operator, intended for sorting DESIGN_BLOCK_INFO objects + */ + friend bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs ); + +protected: + void ensure_loaded() + { + if( !m_loaded ) + load(); + } + + /// lazily load stuff not filled in by constructor. This may throw IO_ERRORS. + virtual void load(){}; + + DESIGN_BLOCK_LIST* m_owner; ///< provides access to DESIGN_BLOCK_LIB_TABLE + + bool m_loaded; + + wxString m_nickname; ///< library as known in DESIGN_BLOCK_LIB_TABLE + wxString m_dbname; ///< Module name. + int m_num; ///< Order number in the display list. + wxString m_doc; ///< Design block description. + wxString m_keywords; ///< Design block keywords. +}; + + +/** + * Holds a list of #DESIGN_BLOCK_INFO objects, along with a list of IO_ERRORs or + * PARSE_ERRORs that were thrown acquiring the DESIGN_BLOCK_INFOs. + * + * This is a virtual class; its implementation lives in common/design_block_info_impl.cpp. + * To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance(). + */ +class KICOMMON_API DESIGN_BLOCK_LIST +{ +public: + typedef std::vector<std::unique_ptr<DESIGN_BLOCK_INFO>> DBILIST; + typedef SYNC_QUEUE<std::unique_ptr<IO_ERROR>> ERRLIST; + + DESIGN_BLOCK_LIST() : m_lib_table( nullptr ) {} + + virtual ~DESIGN_BLOCK_LIST() {} + + virtual void WriteCacheToFile( const wxString& aFilePath ){}; + virtual void ReadCacheFromFile( const wxString& aFilePath ){}; + + /** + * @return the number of items stored in list + */ + unsigned GetCount() const { return m_list.size(); } + + /// Was forced to add this by modview_frame.cpp + const DBILIST& GetList() const { return m_list; } + + /** + * @return Clears the design block info cache + */ + void Clear() { m_list.clear(); } + + /** + * Get info for a design block by id. + */ + DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aDesignBlockName ); + + /** + * Get info for a design block by libNickname/designBlockName + */ + DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aLibNickname, + const wxString& aDesignBlockName ); + + /** + * Get info for a design block by index. + * + * @param aIdx index of the given item. + * @return the aIdx item in list. + */ + DESIGN_BLOCK_INFO& GetItem( unsigned aIdx ) const { return *m_list[aIdx]; } + + unsigned GetErrorCount() const { return m_errors.size(); } + + std::unique_ptr<IO_ERROR> PopError() + { + std::unique_ptr<IO_ERROR> error; + + m_errors.pop( error ); + return error; + } + + /** + * Read all the design blocks provided by the combination of aTable and aNickname. + * + * @param aTable defines all the libraries. + * @param aNickname is the library to read from, or if NULL means read all design blocks + * from all known libraries in aTable. + * @param aProgressReporter is an optional progress reporter. ReadDesignBlockFiles() will + * use 2 phases within the reporter. + * @return true if it ran to completion, else false if it aborted after some number of + * errors. If true, it does not mean there were no errors, check GetErrorCount() + * for that, should be zero to indicate success. + */ + virtual bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable, + const wxString* aNickname = nullptr, + PROGRESS_REPORTER* aProgressReporter = nullptr ) = 0; + + DESIGN_BLOCK_LIB_TABLE* GetTable() const { return m_lib_table; } + +protected: + DESIGN_BLOCK_LIB_TABLE* m_lib_table = nullptr; ///< no ownership + + DBILIST m_list; + ERRLIST m_errors; ///< some can be PARSE_ERRORs also +}; + +#endif // DESIGN_BLOCK_INFO_H_ diff --git a/common/design_block_info_impl.cpp b/common/design_block_info_impl.cpp new file mode 100644 index 0000000000..84eff9a1fb --- /dev/null +++ b/common/design_block_info_impl.cpp @@ -0,0 +1,376 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <design_block_info_impl.h> + +#include <design_block.h> +#include <design_block_info.h> +#include <design_block_lib_table.h> +#include <kiway.h> +#include <locale_io.h> +#include <lib_id.h> +#include <progress_reporter.h> +#include <string_utils.h> +#include <core/thread_pool.h> +#include <wildcards_and_files_ext.h> + +#include <kiplatform/io.h> + +#include <wx/textfile.h> +#include <wx/txtstrm.h> +#include <wx/wfstream.h> + + +void DESIGN_BLOCK_INFO_IMPL::load() +{ + DESIGN_BLOCK_LIB_TABLE* dbtable = m_owner->GetTable(); + + wxASSERT( dbtable ); + + const DESIGN_BLOCK* design_block = dbtable->GetEnumeratedDesignBlock( m_nickname, m_dbname ); + + if( design_block ) + { + m_keywords = design_block->GetKeywords(); + m_doc = design_block->GetLibDescription(); + } + + m_loaded = true; +} + + +bool DESIGN_BLOCK_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc ) +{ + try + { + aFunc(); + } + catch( const IO_ERROR& ioe ) + { + m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) ); + return false; + } + catch( const std::exception& se ) + { + // This is a round about way to do this, but who knows what THROW_IO_ERROR() + // may be tricked out to do someday, keep it in the game. + try + { + THROW_IO_ERROR( se.what() ); + } + catch( const IO_ERROR& ioe ) + { + m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) ); + } + + return false; + } + + return true; +} + + +bool DESIGN_BLOCK_LIST_IMPL::ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable, + const wxString* aNickname, + PROGRESS_REPORTER* aProgressReporter ) +{ + long long int generatedTimestamp = 0; + + if( !CatchErrors( + [&]() + { + generatedTimestamp = aTable->GenerateTimestamp( aNickname ); + } ) ) + { + return false; + } + + if( generatedTimestamp == m_list_timestamp ) + return true; + + // Disable KIID generation: not needed for library parts; sometimes very slow + KIID_NIL_SET_RESET reset_kiid; + + m_progress_reporter = aProgressReporter; + + if( m_progress_reporter ) + { + m_progress_reporter->SetMaxProgress( m_queue_in.size() ); + m_progress_reporter->Report( _( "Fetching design_block libraries..." ) ); + } + + m_cancelled = false; + m_lib_table = aTable; + + // Clear data before reading files + m_errors.clear(); + m_list.clear(); + m_queue_in.clear(); + m_queue_out.clear(); + + if( aNickname ) + { + m_queue_in.push( *aNickname ); + } + else + { + for( const wxString& nickname : aTable->GetLogicalLibs() ) + m_queue_in.push( nickname ); + } + + + loadLibs(); + + if( !m_cancelled ) + { + if( m_progress_reporter ) + { + m_progress_reporter->SetMaxProgress( m_queue_out.size() ); + m_progress_reporter->AdvancePhase(); + m_progress_reporter->Report( _( "Loading design_blocks..." ) ); + } + + loadDesignBlocks(); + + if( m_progress_reporter ) + m_progress_reporter->AdvancePhase(); + } + + if( m_cancelled ) + m_list_timestamp = 0; // God knows what we got before we were canceled + else + m_list_timestamp = generatedTimestamp; + + return m_errors.empty(); +} + + +void DESIGN_BLOCK_LIST_IMPL::loadLibs() +{ + thread_pool& tp = GetKiCadThreadPool(); + size_t num_returns = m_queue_in.size(); + std::vector<std::future<size_t>> returns( num_returns ); + + auto loader_job = + [this]() -> size_t + { + wxString nickname; + size_t retval = 0; + + if( !m_cancelled && m_queue_in.pop( nickname ) ) + { + if( CatchErrors( [this, &nickname]() + { + m_lib_table->PrefetchLib( nickname ); + m_queue_out.push( nickname ); + } ) && m_progress_reporter ) + { + m_progress_reporter->AdvanceProgress(); + } + + ++retval; + } + + return retval; + }; + + for( size_t ii = 0; ii < num_returns; ++ii ) + returns[ii] = tp.submit( loader_job ); + + for( const std::future<size_t>& ret : returns ) + { + std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + + while( status != std::future_status::ready ) + { + if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() ) + m_cancelled = true; + + status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + } + } +} + + +void DESIGN_BLOCK_LIST_IMPL::loadDesignBlocks() +{ + LOCALE_IO toggle_locale; + + // Parse the design_blocks in parallel. WARNING! This requires changing the locale, which is + // GLOBAL. It is only thread safe to construct the LOCALE_IO before the threads are created, + // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation + // from this will cause nasal demons. + // + // TODO: blast LOCALE_IO into the sun + + SYNC_QUEUE<std::unique_ptr<DESIGN_BLOCK_INFO>> queue_parsed; + thread_pool& tp = GetKiCadThreadPool(); + size_t num_elements = m_queue_out.size(); + std::vector<std::future<size_t>> returns( num_elements ); + + auto db_thread = + [ this, &queue_parsed ]() -> size_t + { + wxString nickname; + + if( m_cancelled || !m_queue_out.pop( nickname ) ) + return 0; + + wxArrayString dbnames; + + CatchErrors( + [&]() + { + m_lib_table->DesignBlockEnumerate( dbnames, nickname, false ); + } ); + + for( wxString dbname : dbnames ) + { + CatchErrors( + [&]() + { + auto* dbinfo = new DESIGN_BLOCK_INFO_IMPL( this, nickname, dbname ); + queue_parsed.move_push( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) ); + } ); + + if( m_cancelled ) + return 0; + } + + if( m_progress_reporter ) + m_progress_reporter->AdvanceProgress(); + + return 1; + }; + + for( size_t ii = 0; ii < num_elements; ++ii ) + returns[ii] = tp.submit( db_thread ); + + for( const std::future<size_t>& ret : returns ) + { + std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + + while( status != std::future_status::ready ) + { + if( m_progress_reporter ) + m_progress_reporter->KeepRefreshing(); + + status = ret.wait_for( std::chrono::milliseconds( 250 ) ); + } + } + + std::unique_ptr<DESIGN_BLOCK_INFO> dbi; + + while( queue_parsed.pop( dbi ) ) + m_list.push_back( std::move( dbi ) ); + + std::sort( m_list.begin(), m_list.end(), + []( std::unique_ptr<DESIGN_BLOCK_INFO> const& lhs, + std::unique_ptr<DESIGN_BLOCK_INFO> const& rhs ) -> bool + { + return *lhs < *rhs; + } ); +} + + +DESIGN_BLOCK_LIST_IMPL::DESIGN_BLOCK_LIST_IMPL() : + m_list_timestamp( 0 ), m_progress_reporter( nullptr ), m_cancelled( false ) +{ +} + + +void DESIGN_BLOCK_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath ) +{ + wxFileName tmpFileName = wxFileName::CreateTempFileName( aFilePath ); + wxFFileOutputStream outStream( tmpFileName.GetFullPath() ); + wxTextOutputStream txtStream( outStream ); + + if( !outStream.IsOk() ) + { + return; + } + + txtStream << wxString::Format( wxT( "%lld" ), m_list_timestamp ) << endl; + + for( std::unique_ptr<DESIGN_BLOCK_INFO>& dbinfo : m_list ) + { + txtStream << dbinfo->GetLibNickname() << endl; + txtStream << dbinfo->GetName() << endl; + txtStream << EscapeString( dbinfo->GetDesc(), CTX_LINE ) << endl; + txtStream << EscapeString( dbinfo->GetKeywords(), CTX_LINE ) << endl; + txtStream << wxString::Format( wxT( "%d" ), dbinfo->GetOrderNum() ) << endl; + } + + txtStream.Flush(); + outStream.Close(); + + // Preserve the permissions of the current file + KIPLATFORM::IO::DuplicatePermissions( aFilePath, tmpFileName.GetFullPath() ); + + if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) ) + { + // cleanup in case rename failed + // its also not the end of the world since this is just a cache file + wxRemoveFile( tmpFileName.GetFullPath() ); + } +} + + +void DESIGN_BLOCK_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath ) +{ + wxTextFile cacheFile( aFilePath ); + + m_list_timestamp = 0; + m_list.clear(); + + try + { + if( cacheFile.Exists() && cacheFile.Open() ) + { + cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp ); + + while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() ) + { + wxString libNickname = cacheFile.GetNextLine(); + wxString name = cacheFile.GetNextLine(); + wxString desc = UnescapeString( cacheFile.GetNextLine() ); + wxString keywords = UnescapeString( cacheFile.GetNextLine() ); + int orderNum = wxAtoi( cacheFile.GetNextLine() ); + + DESIGN_BLOCK_INFO_IMPL* dbinfo = + new DESIGN_BLOCK_INFO_IMPL( libNickname, name, desc, keywords, orderNum ); + + m_list.emplace_back( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) ); + } + } + } + catch( ... ) + { + // whatever went wrong, invalidate the cache + m_list_timestamp = 0; + } + + // Sanity check: an empty list is very unlikely to be correct. + if( m_list.size() == 0 ) + m_list_timestamp = 0; + + if( cacheFile.IsOpened() ) + cacheFile.Close(); +} diff --git a/common/design_block_info_impl.h b/common/design_block_info_impl.h new file mode 100644 index 0000000000..0f1f6bc4ce --- /dev/null +++ b/common/design_block_info_impl.h @@ -0,0 +1,112 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DESIGN_BLOCK_INFO_IMPL_H +#define DESIGN_BLOCK_INFO_IMPL_H + +#include <atomic> +#include <functional> +#include <memory> +#include <thread> +#include <vector> + +#include <kicommon.h> +#include <design_block_info.h> +#include <core/sync_queue.h> + +class LOCALE_IO; + +class KICOMMON_API DESIGN_BLOCK_INFO_IMPL : public DESIGN_BLOCK_INFO +{ +public: + DESIGN_BLOCK_INFO_IMPL( DESIGN_BLOCK_LIST* aOwner, const wxString& aNickname, + const wxString& aDesignBlockName ) + { + m_nickname = aNickname; + m_dbname = aDesignBlockName; + m_num = 0; + + m_owner = aOwner; + m_loaded = false; + load(); + } + + // A constructor for cached items + DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName, + const wxString& aDescription, const wxString& aKeywords, int aOrderNum ) + { + m_nickname = aNickname; + m_dbname = aDesignBlockName; + m_num = aOrderNum; + m_doc = aDescription; + m_keywords = aKeywords; + + m_owner = nullptr; + m_loaded = true; + } + + + // A dummy constructor for use as a target in a binary search + DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName ) + { + m_nickname = aNickname; + m_dbname = aDesignBlockName; + + m_owner = nullptr; + m_loaded = true; + } + +protected: + virtual void load() override; +}; + + +class KICOMMON_API DESIGN_BLOCK_LIST_IMPL : public DESIGN_BLOCK_LIST +{ +public: + DESIGN_BLOCK_LIST_IMPL(); + virtual ~DESIGN_BLOCK_LIST_IMPL(){}; + + void WriteCacheToFile( const wxString& aFilePath ) override; + void ReadCacheFromFile( const wxString& aFilePath ) override; + + bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable, const wxString* aNickname = nullptr, + PROGRESS_REPORTER* aProgressReporter = nullptr ) override; + +protected: + void loadLibs(); + void loadDesignBlocks(); + +private: + /** + * Call aFunc, pushing any IO_ERRORs and std::exceptions it throws onto m_errors. + * + * @return true if no error occurred. + */ + bool CatchErrors( const std::function<void()>& aFunc ); + + SYNC_QUEUE<wxString> m_queue_in; + SYNC_QUEUE<wxString> m_queue_out; + long long m_list_timestamp; + PROGRESS_REPORTER* m_progress_reporter; + std::atomic_bool m_cancelled; + std::mutex m_join; +}; + +#endif // DESIGN_BLOCK_INFO_IMPL_H diff --git a/common/design_block_io.cpp b/common/design_block_io.cpp new file mode 100644 index 0000000000..1266dcca20 --- /dev/null +++ b/common/design_block_io.cpp @@ -0,0 +1,387 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com> + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <i18n_utility.h> +#include <wx/dir.h> +#include <wx/filename.h> +#include <wx/log.h> +#include <wx/translation.h> +#include <wx/string.h> +#include <wx/arrstr.h> +#include <wx/datetime.h> +#include <wildcards_and_files_ext.h> +#include <kiway_player.h> +#include <design_block_io.h> +#include <design_block.h> +#include <ki_exception.h> +#include <trace_helpers.h> +#include <fstream> + +const wxString DESIGN_BLOCK_IO_MGR::ShowType( DESIGN_BLOCK_FILE_T aFileType ) +{ + switch( aFileType ) + { + case KICAD_SEXP: return _( "KiCad" ); + default: return wxString::Format( _( "UNKNOWN (%d)" ), aFileType ); + } +} + + +DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T +DESIGN_BLOCK_IO_MGR::EnumFromStr( const wxString& aFileType ) +{ + if( aFileType == _( "KiCad" ) ) + return DESIGN_BLOCK_FILE_T( KICAD_SEXP ); + + return DESIGN_BLOCK_FILE_T( DESIGN_BLOCK_FILE_UNKNOWN ); +} + + +DESIGN_BLOCK_IO* DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_FILE_T aFileType ) +{ + switch( aFileType ) + { + case KICAD_SEXP: return new DESIGN_BLOCK_IO(); + default: return nullptr; + } +} + + +DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T +DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl ) +{ + if( IO_RELEASER<DESIGN_BLOCK_IO>( FindPlugin( KICAD_SEXP ) )->CanReadLibrary( aLibPath ) && aCtl != KICTL_NONKICAD_ONLY ) + return KICAD_SEXP; + + return DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE; +} + + +bool DESIGN_BLOCK_IO_MGR::ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps, + const wxString& aOldFilePath, + const wxString& aNewFilePath ) +{ + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T oldFileType = + DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( aOldFilePath ); + + if( oldFileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE ) + return false; + + + IO_RELEASER<DESIGN_BLOCK_IO> oldFilePI( DESIGN_BLOCK_IO_MGR::FindPlugin( oldFileType ) ); + IO_RELEASER<DESIGN_BLOCK_IO> kicadPI( + DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ) ); + wxArrayString dbNames; + wxFileName newFileName( aNewFilePath ); + + if( newFileName.HasExt() ) + { + wxString extraDir = newFileName.GetFullName(); + newFileName.ClearExt(); + newFileName.SetName( "" ); + newFileName.AppendDir( extraDir ); + } + + if( !newFileName.DirExists() && !wxFileName::Mkdir( aNewFilePath, wxS_DIR_DEFAULT ) ) + return false; + + try + { + bool bestEfforts = false; // throw on first error + oldFilePI->DesignBlockEnumerate( dbNames, aOldFilePath, bestEfforts, aOldFileProps ); + + for( const wxString& dbName : dbNames ) + { + std::unique_ptr<const DESIGN_BLOCK> db( + oldFilePI->GetEnumeratedDesignBlock( aOldFilePath, dbName, aOldFileProps ) ); + kicadPI->DesignBlockSave( aNewFilePath, db.get() ); + } + } + catch( ... ) + { + return false; + } + + return true; +} + + +const DESIGN_BLOCK_IO::IO_FILE_DESC DESIGN_BLOCK_IO::GetLibraryDesc() const +{ + return IO_BASE::IO_FILE_DESC( _HKI( "KiCad Design Block folders" ), {}, + { FILEEXT::KiCadDesignBlockLibPathExtension }, false ); +} + + +long long DESIGN_BLOCK_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const +{ + wxFileName fn( aLibraryPath ); + + if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() ) + return fn.GetModificationTime().GetValue().GetValue(); + else + return wxDateTime( 0.0 ).GetValue().GetValue(); +} + + +void DESIGN_BLOCK_IO::CreateLibrary( const wxString& aLibraryPath, + const std::map<std::string, UTF8>* aProperties ) +{ + if( wxDir::Exists( aLibraryPath ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot overwrite library path '%s'." ), + aLibraryPath.GetData() ) ); + } + + wxFileName dir; + dir.SetPath( aLibraryPath ); + + if( !dir.Mkdir() ) + { + THROW_IO_ERROR( + wxString::Format( _( "Library path '%s' could not be created.\n\n" + "Make sure you have write permissions and try again." ), + dir.GetPath() ) ); + } +} + + +bool DESIGN_BLOCK_IO::DeleteLibrary( const wxString& aLibraryPath, + const std::map<std::string, UTF8>* aProperties ) +{ + wxFileName fn; + fn.SetPath( aLibraryPath ); + + // Return if there is no library path to delete. + if( !fn.DirExists() ) + return false; + + if( !fn.IsDirWritable() ) + { + THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to delete folder '%s'." ), + aLibraryPath.GetData() ) ); + } + + wxDir dir( aLibraryPath ); + + // Design block folders should only contain sub-folders for each design block + if( dir.HasFiles() ) + { + THROW_IO_ERROR( wxString::Format( _( "Library folder '%s' has unexpected files." ), + aLibraryPath.GetData() ) ); + } + + // Must delete all sub-directories before deleting the library directory + if( dir.HasSubDirs() ) + { + wxArrayString dirs; + + // Get all sub-directories in the library path + dir.GetAllFiles( aLibraryPath, &dirs, wxEmptyString, wxDIR_DIRS ); + + for( size_t i = 0; i < dirs.GetCount(); i++ ) + { + wxFileName tmp = dirs[i]; + + if( tmp.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension ) + { + THROW_IO_ERROR( wxString::Format( _( "Unexpected folder '%s' found in library " + "path '%s'." ), + dirs[i].GetData(), aLibraryPath.GetData() ) ); + } + } + + for( size_t i = 0; i < dirs.GetCount(); i++ ) + wxRemoveFile( dirs[i] ); + } + + wxLogTrace( traceDesignBlocks, wxT( "Removing design block library '%s'." ), + aLibraryPath.GetData() ); + + // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog + // we don't want that. we want bare metal portability with no UI here. + if( !wxFileName::Rmdir( aLibraryPath, wxPATH_RMDIR_RECURSIVE ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Design block library '%s' cannot be deleted." ), + aLibraryPath.GetData() ) ); + } + + // For some reason removing a directory in Windows is not immediately updated. This delay + // prevents an error when attempting to immediately recreate the same directory when over + // writing an existing library. +#ifdef __WINDOWS__ + wxMilliSleep( 250L ); +#endif + + return true; +} + + +void DESIGN_BLOCK_IO::DesignBlockEnumerate( wxArrayString& aDesignBlockNames, + const wxString& aLibraryPath, bool aBestEfforts, + const std::map<std::string, UTF8>* aProperties ) +{ + // From the starting directory, look for all directories ending in the .block extension + wxDir dir( aLibraryPath ); + + if( !dir.IsOpened() ) + return; + + wxString dirname; + wxString fileSpec = wxT( "*." ) + wxString( FILEEXT::KiCadDesignBlockPathExtension ); + bool cont = dir.GetFirst( &dirname, fileSpec, wxDIR_DIRS ); + + while( cont ) + { + aDesignBlockNames.Add( dirname.Before( wxT( '.' ) ) ); + cont = dir.GetNext( &dirname ); + } +} + + +DESIGN_BLOCK* DESIGN_BLOCK_IO::DesignBlockLoad( const wxString& aLibraryPath, + const wxString& aDesignBlockName, bool aKeepUUID, + const std::map<std::string, UTF8>* aProperties ) +{ + DESIGN_BLOCK* newDB = new DESIGN_BLOCK(); + wxString dbPath = aLibraryPath + wxFileName::GetPathSeparator() + + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator(); + wxString dbSchPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension; + wxString dbMetadataPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::JsonFileExtension; + + + + // Library name needs to be empty for when we fill it in with the correct library nickname + // one layer above + newDB->SetLibId( LIB_ID( wxEmptyString, aDesignBlockName ) ); + newDB->SetSchematicFile( + // Library path + aLibraryPath + wxFileName::GetPathSeparator() + + // Design block name (project folder) + aDesignBlockName + +wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxT( "/" ) + + // Schematic file + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension ); + + // Parse the JSON file if it exists + if( wxFileExists( dbMetadataPath ) ) + { + try + { + nlohmann::json dbMetadata; + std::ifstream dbMetadataFile( dbMetadataPath.fn_str() ); + dbMetadataFile >> dbMetadata; + + if( dbMetadata.contains( "description" ) ) + newDB->SetLibDescription( dbMetadata["description"] ); + + if( dbMetadata.contains( "keywords" ) ) + newDB->SetKeywords( dbMetadata["keywords"] ); + + if( dbMetadata.contains( "documentation_url" ) ) + newDB->SetDocumentationUrl( dbMetadata["documentation_url"] ); + } + catch( ... ) + { + THROW_IO_ERROR( wxString::Format( + _( "Design block metadata file '%s' could not be read." ), dbMetadataPath ) ); + } + } + + + return newDB; +} + + +void DESIGN_BLOCK_IO::DesignBlockSave( const wxString& aLibraryPath, + const DESIGN_BLOCK* aDesignBlock, + const std::map<std::string, UTF8>* aProperties ) +{ + // Make sure we have a valid LIB_ID or we can't save the design block + if( !aDesignBlock->GetLibId().IsValid() ) + { + THROW_IO_ERROR( _( "Design block does not have a valid library ID." ) ); + } + + if( !wxFileExists( aDesignBlock->GetSchematicFile() ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Schematic source file '%s' does not exist." ), + aDesignBlock->GetSchematicFile() ) ); + } + + // Create the design block folder + wxFileName dbFolder( aLibraryPath + wxFileName::GetPathSeparator() + + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." ) + + FILEEXT::KiCadDesignBlockPathExtension + + wxFileName::GetPathSeparator() ); + + if( !dbFolder.DirExists() ) + { + if( !dbFolder.Mkdir() ) + { + THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be created." ), + dbFolder.GetFullPath().GetData() ) ); + } + } + + // The new schematic file name is based on the design block name, not the source sheet name + wxString dbSchematicFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName() + + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension; + + // Copy the source sheet file to the design block folder, under the design block name + if( !wxCopyFile( aDesignBlock->GetSchematicFile(), dbSchematicFile ) ) + { + THROW_IO_ERROR( wxString::Format( + _( "Schematic file '%s' could not be saved as design block at '%s'." ), + aDesignBlock->GetSchematicFile().GetData(), dbSchematicFile ) ); + } +} + + +void DESIGN_BLOCK_IO::DesignBlockDelete( const wxString& aLibPath, const wxString& aDesignBlockName, + const std::map<std::string, UTF8>* aProperties ) +{ + wxFileName dbDir = wxFileName( aLibPath + wxFileName::GetPathSeparator() + aDesignBlockName + + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension ); + + + if( !dbDir.DirExists() ) + { + THROW_IO_ERROR( + wxString::Format( _( "Design block '%s' does not exist." ), dbDir.GetFullName() ) ); + } + + // Delete the whole design block folder + if( !wxFileName::Rmdir( dbDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be deleted." ), + dbDir.GetFullPath().GetData() ) ); + } +} + + +bool DESIGN_BLOCK_IO::IsLibraryWritable( const wxString& aLibraryPath ) +{ + wxFileName path( aLibraryPath ); + return path.IsOk() && path.IsDirWritable(); +} diff --git a/common/design_block_io.h b/common/design_block_io.h new file mode 100644 index 0000000000..6b6df633ce --- /dev/null +++ b/common/design_block_io.h @@ -0,0 +1,115 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com> + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef DESIGN_BLOCK_IO_H +#define DESIGN_BLOCK_IO_H + +#include <kicommon.h> +#include <io/io_base.h> +#include <io/io_mgr.h> + +class DESIGN_BLOCK; +class DESIGN_BLOCK_IO; +class wxArrayString; + +class KICOMMON_API DESIGN_BLOCK_IO_MGR : public IO_MGR +{ +public: + enum DESIGN_BLOCK_FILE_T + { + DESIGN_BLOCK_FILE_UNKNOWN = 0, ///< 0 is not a legal menu id on Mac + KICAD_SEXP, ///< S-expression KiCad file format. + + FILE_TYPE_NONE + }; + + static const wxString ShowType( DESIGN_BLOCK_FILE_T aFileType ); + static DESIGN_BLOCK_IO* FindPlugin( DESIGN_BLOCK_FILE_T aFileType ); + static DESIGN_BLOCK_FILE_T EnumFromStr( const wxString& aFileType ); + static DESIGN_BLOCK_FILE_T GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl = 0 ); + /** + * Convert a design block library to the latest KiCad format + */ + static bool ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps, + const wxString& aOldFilePath, const wxString& aNewFilePath ); +}; + + +class KICOMMON_API DESIGN_BLOCK_IO : public IO_BASE +{ +public: + DESIGN_BLOCK_IO() : IO_BASE( wxS( "KiCad" ) ) {} + + const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override; + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const; + + void DesignBlockEnumerate( wxArrayString& aDesignBlockNames, const wxString& aLibraryPath, + bool aBestEfforts, + const std::map<std::string, UTF8>* aProperties = nullptr ); + + const DESIGN_BLOCK* + GetEnumeratedDesignBlock( const wxString& aLibraryPath, const wxString& aDesignBlockName, + const std::map<std::string, UTF8>* aProperties = nullptr ) + { + return DesignBlockLoad( aLibraryPath, aDesignBlockName, false, aProperties ); + } + + bool DesignBlockExists( const wxString& aLibraryPath, const wxString& aDesignBlockName, + const std::map<std::string, UTF8>* aProperties = nullptr ) + { + return DesignBlockLoad( aLibraryPath, aDesignBlockName, true, aProperties ) != nullptr; + } + + DESIGN_BLOCK* ImportDesignBlock( const wxString& aDesignBlockPath, + wxString& aDesignBlockNameOut, + const std::map<std::string, UTF8>* aProperties = nullptr ) + { + return nullptr; + } + + void CreateLibrary( const wxString& aLibraryPath, + const std::map<std::string, UTF8>* aProperties = nullptr ) override; + + virtual bool DeleteLibrary( const wxString& aLibraryPath, + const std::map<std::string, UTF8>* aProperties = nullptr ) override; + + + bool IsLibraryWritable( const wxString& aLibraryPath ) override; + + DESIGN_BLOCK* DesignBlockLoad( const wxString& aLibraryPath, const wxString& aDesignBlockName, + bool aKeepUUID = false, + const std::map<std::string, UTF8>* aProperties = nullptr ); + + void DesignBlockSave( const wxString& aLibraryPath, const DESIGN_BLOCK* aDesignBlock, + const std::map<std::string, UTF8>* aProperties = nullptr ); + + void DesignBlockDelete( const wxString& aLibraryPath, const wxString& aDesignBlockName, + const std::map<std::string, UTF8>* aProperties = nullptr ); + + virtual void PrefetchLib( const wxString& aLibraryPath, + const std::map<std::string, UTF8>* aProperties = nullptr ) + { + } +}; + +#endif diff --git a/common/design_block_lib_table.cpp b/common/design_block_lib_table.cpp new file mode 100644 index 0000000000..69ab52f033 --- /dev/null +++ b/common/design_block_lib_table.cpp @@ -0,0 +1,683 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include <kiface_base.h> +#include <env_vars.h> +#include <design_block_info.h> +#include <lib_id.h> +#include <lib_table_lexer.h> +#include <paths.h> +#include <pgm_base.h> +#include <search_stack.h> +#include <settings/kicad_settings.h> +#include <settings/settings_manager.h> +#include <systemdirsappend.h> +#include <design_block_lib_table.h> +#include <design_block.h> + +#include <wx/dir.h> +#include <wx/hash.h> + +#define OPT_SEP '|' ///< options separator character + +/// The global design block library table. This is not dynamically allocated because +/// in a multiple project environment we must keep its address constant (since it is +/// the fallback table for multiple projects). +DESIGN_BLOCK_LIB_TABLE GDesignBlockTable; + +/// The global footprint info table. This is performance-intensive to build so we +/// keep a hash-stamped global version. Any deviation from the request vs. stored +/// hash will result in it being rebuilt. +DESIGN_BLOCK_LIST_IMPL GDesignBlockList; + + +using namespace LIB_TABLE_T; + + +static const wxChar global_tbl_name[] = wxT( "design-block-lib-table" ); + + +bool DESIGN_BLOCK_LIB_TABLE_ROW::operator==( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const +{ + return LIB_TABLE_ROW::operator==( aRow ) && type == aRow.type; +} + + +void DESIGN_BLOCK_LIB_TABLE_ROW::SetType( const wxString& aType ) +{ + type = DESIGN_BLOCK_IO_MGR::EnumFromStr( aType ); + + if( DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T( -1 ) == type ) + type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP; + + plugin.reset(); +} + + +DESIGN_BLOCK_LIB_TABLE::DESIGN_BLOCK_LIB_TABLE( DESIGN_BLOCK_LIB_TABLE* aFallBackTable ) : + LIB_TABLE( aFallBackTable ) +{ + // not copying fall back, simply search aFallBackTable separately + // if "nickName not found". +} + + +void DESIGN_BLOCK_LIB_TABLE::Parse( LIB_TABLE_LEXER* in ) +{ + T tok; + wxString errMsg; // to collect error messages + + // This table may be nested within a larger s-expression, or not. + // Allow for parser of that optional containing s-epression to have looked ahead. + if( in->CurTok() != T_design_block_lib_table ) + { + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_design_block_lib_table ) + in->Expecting( T_design_block_lib_table ); + } + + while( ( tok = in->NextTok() ) != T_RIGHT ) + { + std::unique_ptr<DESIGN_BLOCK_LIB_TABLE_ROW> row = + std::make_unique<DESIGN_BLOCK_LIB_TABLE_ROW>(); + + if( tok == T_EOF ) + in->Expecting( T_RIGHT ); + + if( tok != T_LEFT ) + in->Expecting( T_LEFT ); + + // in case there is a "row integrity" error, tell where later. + int lineNum = in->CurLineNumber(); + tok = in->NextTok(); + + // Optionally parse the current version number + if( tok == T_version ) + { + in->NeedNUMBER( "version" ); + m_version = std::stoi( in->CurText() ); + in->NeedRIGHT(); + continue; + } + + if( tok != T_lib ) + in->Expecting( T_lib ); + + // (name NICKNAME) + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_name ) + in->Expecting( T_name ); + + in->NeedSYMBOLorNUMBER(); + + row->SetNickName( in->FromUTF8() ); + + in->NeedRIGHT(); + + // After (name), remaining (lib) elements are order independent, and in + // some cases optional. + bool sawType = false; + bool sawOpts = false; + bool sawDesc = false; + bool sawUri = false; + bool sawDisabled = false; + + while( ( tok = in->NextTok() ) != T_RIGHT ) + { + if( tok == T_EOF ) + in->Unexpected( T_EOF ); + + if( tok != T_LEFT ) + in->Expecting( T_LEFT ); + + tok = in->NeedSYMBOLorNUMBER(); + + switch( tok ) + { + case T_uri: + if( sawUri ) + in->Duplicate( tok ); + sawUri = true; + in->NeedSYMBOLorNUMBER(); + row->SetFullURI( in->FromUTF8() ); + break; + + case T_type: + if( sawType ) + in->Duplicate( tok ); + sawType = true; + in->NeedSYMBOLorNUMBER(); + row->SetType( in->FromUTF8() ); + break; + + case T_options: + if( sawOpts ) + in->Duplicate( tok ); + sawOpts = true; + in->NeedSYMBOLorNUMBER(); + row->SetOptions( in->FromUTF8() ); + break; + + case T_descr: + if( sawDesc ) + in->Duplicate( tok ); + sawDesc = true; + in->NeedSYMBOLorNUMBER(); + row->SetDescr( in->FromUTF8() ); + break; + + case T_disabled: + if( sawDisabled ) + in->Duplicate( tok ); + sawDisabled = true; + row->SetEnabled( false ); + break; + + case T_hidden: + // Hiding design block libraries is not yet supported. Unclear what path can set this + // attribute, but clear it on load. + row->SetVisible(); + break; + + default: in->Unexpected( tok ); + } + + in->NeedRIGHT(); + } + + if( !sawType ) + in->Expecting( T_type ); + + if( !sawUri ) + in->Expecting( T_uri ); + + // All nickNames within this table fragment must be unique, so we do not use doReplace + // in doInsertRow(). (However a fallBack table can have a conflicting nickName and ours + // will supercede that one since in FindLib() we search this table before any fall back.) + wxString nickname = row->GetNickName(); // store it to be able to used it + // after row deletion if an error occurs + bool doReplace = false; + LIB_TABLE_ROW* tmp = row.release(); + + if( !doInsertRow( tmp, doReplace ) ) + { + delete tmp; // The table did not take ownership of the row. + + wxString msg = wxString::Format( _( "Duplicate library nickname '%s' found in " + "design block library table file line %d." ), + nickname, lineNum ); + + if( !errMsg.IsEmpty() ) + errMsg << '\n'; + + errMsg << msg; + } + } + + if( !errMsg.IsEmpty() ) + THROW_IO_ERROR( errMsg ); +} + + +bool DESIGN_BLOCK_LIB_TABLE::operator==( const DESIGN_BLOCK_LIB_TABLE& aDesignBlockTable ) const +{ + if( m_rows.size() == aDesignBlockTable.m_rows.size() ) + { + for( unsigned i = 0; i < m_rows.size(); ++i ) + { + if( (DESIGN_BLOCK_LIB_TABLE_ROW&) m_rows[i] + != (DESIGN_BLOCK_LIB_TABLE_ROW&) aDesignBlockTable.m_rows[i] ) + return false; + } + + return true; + } + + return false; +} + + +void DESIGN_BLOCK_LIB_TABLE::Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const +{ + aOutput->Print( aIndentLevel, "(design_block_lib_table\n" ); + aOutput->Print( aIndentLevel + 1, "(version %d)\n", m_version ); + + for( LIB_TABLE_ROWS_CITER it = m_rows.begin(); it != m_rows.end(); ++it ) + it->Format( aOutput, aIndentLevel + 1 ); + + aOutput->Print( aIndentLevel, ")\n" ); +} + + +long long DESIGN_BLOCK_LIB_TABLE::GenerateTimestamp( const wxString* aNickname ) +{ + long long hash = 0; + + if( aNickname ) + { + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( *aNickname, true ); + + wxCHECK( row && row->plugin, hash ); + + return row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) ) + + wxHashTable::MakeKey( *aNickname ); + } + + for( const wxString& nickname : GetLogicalLibs() ) + { + const DESIGN_BLOCK_LIB_TABLE_ROW* row = nullptr; + + try + { + row = FindRow( nickname, true ); + } + catch( ... ) + { + // Do nothing if not found: just skip. + } + + wxCHECK2( row && row->plugin, continue ); + + hash += row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) ) + + wxHashTable::MakeKey( nickname ); + } + + return hash; +} + + +void DESIGN_BLOCK_LIB_TABLE::DesignBlockEnumerate( wxArrayString& aDesignBlockNames, + const wxString& aNickname, bool aBestEfforts ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + row->plugin->DesignBlockEnumerate( aDesignBlockNames, row->GetFullURI( true ), aBestEfforts, + row->GetProperties() ); +} + + +void DESIGN_BLOCK_LIB_TABLE::PrefetchLib( const wxString& aNickname ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + row->plugin->PrefetchLib( row->GetFullURI( true ), row->GetProperties() ); +} + + +const DESIGN_BLOCK_LIB_TABLE_ROW* DESIGN_BLOCK_LIB_TABLE::FindRow( const wxString& aNickname, + bool aCheckIfEnabled ) +{ + DESIGN_BLOCK_LIB_TABLE_ROW* row = + static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( findRow( aNickname, aCheckIfEnabled ) ); + + if( !row ) + { + wxString msg = wxString::Format( + _( "design-block-lib-table files contain no library named '%s'." ), aNickname ); + THROW_IO_ERROR( msg ); + } + + if( !row->plugin ) + row->setPlugin( DESIGN_BLOCK_IO_MGR::FindPlugin( row->type ) ); + + return row; +} + + +static void setLibNickname( DESIGN_BLOCK* aModule, const wxString& aNickname, + const wxString& aDesignBlockName ) +{ + // The library cannot know its own name, because it might have been renamed or moved. + // Therefore design blocks cannot know their own library nickname when residing in + // a design block library. + // Only at this API layer can we tell the design block about its actual library nickname. + if( aModule ) + { + // remove "const"-ness, I really do want to set nickname without + // having to copy the LIB_ID and its two strings, twice each. + LIB_ID& dbid = (LIB_ID&) aModule->GetLibId(); + + // Catch any misbehaving plugin, which should be setting internal design block name properly: + wxASSERT( aDesignBlockName == dbid.GetLibItemName().wx_str() ); + + // and clearing nickname + wxASSERT( !dbid.GetLibNickname().size() ); + + dbid.SetLibNickname( aNickname ); + } +} + + +const DESIGN_BLOCK* +DESIGN_BLOCK_LIB_TABLE::GetEnumeratedDesignBlock( const wxString& aNickname, + const wxString& aDesignBlockName ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + + return row->plugin->GetEnumeratedDesignBlock( row->GetFullURI( true ), aDesignBlockName, + row->GetProperties() ); +} + + +bool DESIGN_BLOCK_LIB_TABLE::DesignBlockExists( const wxString& aNickname, + const wxString& aDesignBlockName ) +{ + try + { + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + + return row->plugin->DesignBlockExists( row->GetFullURI( true ), aDesignBlockName, + row->GetProperties() ); + } + catch( ... ) + { + return false; + } +} + + +DESIGN_BLOCK* DESIGN_BLOCK_LIB_TABLE::DesignBlockLoad( const wxString& aNickname, + const wxString& aDesignBlockName, + bool aKeepUUID ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + + DESIGN_BLOCK* ret = row->plugin->DesignBlockLoad( row->GetFullURI( true ), aDesignBlockName, + aKeepUUID, row->GetProperties() ); + + setLibNickname( ret, row->GetNickName(), aDesignBlockName ); + + return ret; +} + + +DESIGN_BLOCK_LIB_TABLE::SAVE_T +DESIGN_BLOCK_LIB_TABLE::DesignBlockSave( const wxString& aNickname, + const DESIGN_BLOCK* aDesignBlock, bool aOverwrite ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + + if( !aOverwrite ) + { + // Try loading the design block to see if it already exists, caller wants overwrite + // protection, which is atypical, not the default. + + wxString DesignBlockname = aDesignBlock->GetLibId().GetLibItemName(); + + std::unique_ptr<DESIGN_BLOCK> design_block( row->plugin->DesignBlockLoad( + row->GetFullURI( true ), DesignBlockname, row->GetProperties() ) ); + + if( design_block.get() ) + return SAVE_SKIPPED; + } + + row->plugin->DesignBlockSave( row->GetFullURI( true ), aDesignBlock, row->GetProperties() ); + + return SAVE_OK; +} + + +void DESIGN_BLOCK_LIB_TABLE::DesignBlockDelete( const wxString& aNickname, + const wxString& aDesignBlockName ) + +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + return row->plugin->DesignBlockDelete( row->GetFullURI( true ), aDesignBlockName, + row->GetProperties() ); +} + + +bool DESIGN_BLOCK_LIB_TABLE::IsDesignBlockLibWritable( const wxString& aNickname ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + return row->plugin->IsLibraryWritable( row->GetFullURI( true ) ); +} + + +void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibDelete( const wxString& aNickname ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + row->plugin->DeleteLibrary( row->GetFullURI( true ), row->GetProperties() ); +} + + +void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibCreate( const wxString& aNickname ) +{ + const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true ); + wxASSERT( row->plugin ); + row->plugin->CreateLibrary( row->GetFullURI( true ), row->GetProperties() ); +} + + +DESIGN_BLOCK* +DESIGN_BLOCK_LIB_TABLE::DesignBlockLoadWithOptionalNickname( const LIB_ID& aDesignBlockId, + bool aKeepUUID ) +{ + wxString nickname = aDesignBlockId.GetLibNickname(); + wxString DesignBlockname = aDesignBlockId.GetLibItemName(); + + if( nickname.size() ) + { + return DesignBlockLoad( nickname, DesignBlockname, aKeepUUID ); + } + + // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match: + else + { + std::vector<wxString> nicks = GetLogicalLibs(); + + // Search each library going through libraries alphabetically. + for( unsigned i = 0; i < nicks.size(); ++i ) + { + // DesignBlockLoad() returns NULL on not found, does not throw exception + // unless there's an IO_ERROR. + DESIGN_BLOCK* ret = DesignBlockLoad( nicks[i], DesignBlockname, aKeepUUID ); + + if( ret ) + return ret; + } + + return nullptr; + } +} + + +const wxString DESIGN_BLOCK_LIB_TABLE::GlobalPathEnvVariableName() +{ + return ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) ); +} + + +class PCM_DESIGN_BLOCK_LIB_TRAVERSER final : public wxDirTraverser +{ +public: + explicit PCM_DESIGN_BLOCK_LIB_TRAVERSER( const wxString& aPath, DESIGN_BLOCK_LIB_TABLE& aTable, + const wxString& aPrefix ) : + m_lib_table( aTable ), + m_path_prefix( aPath ), m_lib_prefix( aPrefix ) + { + wxFileName f( aPath, wxS( "" ) ); + m_prefix_dir_count = f.GetDirCount(); + } + + wxDirTraverseResult OnFile( const wxString& aFilePath ) override { return wxDIR_CONTINUE; } + + wxDirTraverseResult OnDir( const wxString& dirPath ) override + { + wxFileName dir = wxFileName::DirName( dirPath ); + + // consider a directory to be a lib if it's name ends with .pretty and + // it is under $KICADn_3RD_PARTY/design_blocks/<pkgid>/ i.e. has nested level of at least +3 + if( dirPath.EndsWith( wxS( ".pretty" ) ) && dir.GetDirCount() >= m_prefix_dir_count + 3 ) + { + wxString versionedPath = wxString::Format( + wxS( "${%s}" ), ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) ); + + wxArrayString parts = dir.GetDirs(); + parts.RemoveAt( 0, m_prefix_dir_count ); + parts.Insert( versionedPath, 0 ); + + wxString libPath = wxJoin( parts, '/' ); + + if( !m_lib_table.HasLibraryWithPath( libPath ) ) + { + wxString name = parts.Last().substr( 0, parts.Last().length() - 7 ); + wxString nickname = wxString::Format( wxS( "%s%s" ), m_lib_prefix, name ); + + if( m_lib_table.HasLibrary( nickname ) ) + { + int increment = 1; + do + { + nickname = + wxString::Format( wxS( "%s%s_%d" ), m_lib_prefix, name, increment ); + increment++; + } while( m_lib_table.HasLibrary( nickname ) ); + } + + m_lib_table.InsertRow( new DESIGN_BLOCK_LIB_TABLE_ROW( + nickname, libPath, wxT( "KiCad" ), wxEmptyString, + _( "Added by Plugin and Content Manager" ) ) ); + } + } + + return wxDIR_CONTINUE; + } + +private: + DESIGN_BLOCK_LIB_TABLE& m_lib_table; + wxString m_path_prefix; + wxString m_lib_prefix; + size_t m_prefix_dir_count; +}; + + +bool DESIGN_BLOCK_LIB_TABLE::LoadGlobalTable( DESIGN_BLOCK_LIB_TABLE& aTable ) +{ + bool tableExists = true; + wxFileName fn = GetGlobalTableFileName(); + + if( !fn.FileExists() ) + { + tableExists = false; + + if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ), + fn.GetPath() ) ); + } + + // Attempt to copy the default global file table from the KiCad + // template folder to the user's home configuration path. + SEARCH_STACK ss; + + SystemDirsAppend( &ss ); + + const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables(); + std::optional<wxString> v = + ENV_VAR::GetVersionedEnvVarValue( envVars, wxT( "TEMPLATE_DIR" ) ); + + if( v && !v->IsEmpty() ) + ss.AddPaths( *v, 0 ); + + wxString fileName = ss.FindValidPath( global_tbl_name ); + + // The fallback is to create an empty global design block table for the user to populate. + if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) ) + { + DESIGN_BLOCK_LIB_TABLE emptyTable; + + emptyTable.Save( fn.GetFullPath() ); + } + } + + aTable.clear(); + aTable.Load( fn.GetFullPath() ); + + SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager(); + KICAD_SETTINGS* settings = mgr.GetAppSettings<KICAD_SETTINGS>(); + + const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables(); + wxString packagesPath; + + if( std::optional<wxString> v = ENV_VAR::GetVersionedEnvVarValue( env, wxT( "3RD_PARTY" ) ) ) + packagesPath = *v; + + if( settings->m_PcmLibAutoAdd ) + { + // Scan for libraries in PCM packages directory + + wxFileName d( packagesPath, wxS( "" ) ); + d.AppendDir( wxS( "design_blocks" ) ); + + if( d.DirExists() ) + { + PCM_DESIGN_BLOCK_LIB_TRAVERSER traverser( packagesPath, aTable, + settings->m_PcmLibPrefix ); + wxDir dir( d.GetPath() ); + + dir.Traverse( traverser ); + } + } + + if( settings->m_PcmLibAutoRemove ) + { + // Remove PCM libraries that no longer exist + std::vector<wxString> to_remove; + + for( size_t i = 0; i < aTable.GetCount(); i++ ) + { + LIB_TABLE_ROW& row = aTable.At( i ); + wxString path = row.GetFullURI( true ); + + if( path.StartsWith( packagesPath ) && !wxDir::Exists( path ) ) + to_remove.push_back( row.GetNickName() ); + } + + for( const wxString& nickName : to_remove ) + aTable.RemoveRow( aTable.FindRow( nickName ) ); + } + + return tableExists; +} + + +wxString DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName() +{ + wxFileName fn; + + fn.SetPath( PATHS::GetUserSettingsPath() ); + fn.SetName( global_tbl_name ); + + return fn.GetFullPath(); +} diff --git a/common/lib_table.keywords b/common/lib_table.keywords index af323dca77..b923e33cf2 100644 --- a/common/lib_table.keywords +++ b/common/lib_table.keywords @@ -1,3 +1,4 @@ +design_block_lib_table fp_lib_table sym_lib_table version @@ -8,4 +9,4 @@ uri options descr disabled -hidden \ No newline at end of file +hidden diff --git a/common/lib_tree_model.cpp b/common/lib_tree_model.cpp index 81f18691d6..6a86d7def0 100644 --- a/common/lib_tree_model.cpp +++ b/common/lib_tree_model.cpp @@ -359,6 +359,23 @@ LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxStri } +void LIB_TREE_NODE_ROOT::RemoveLib( wxString const& aName ) +{ + m_Children.erase( std::remove_if( m_Children.begin(), m_Children.end(), + [&]( std::unique_ptr<LIB_TREE_NODE>& aNode ) + { + return aNode->m_Name == aName; + } ), + m_Children.end() ); +} + + +void LIB_TREE_NODE_ROOT::Clear() +{ + m_Children.clear(); +} + + void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib, std::function<bool( LIB_TREE_NODE& aNode )>* aFilter ) { diff --git a/common/lib_tree_model_adapter.cpp b/common/lib_tree_model_adapter.cpp index 81f93f1690..d1eb98b3ee 100644 --- a/common/lib_tree_model_adapter.cpp +++ b/common/lib_tree_model_adapter.cpp @@ -247,6 +247,12 @@ void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxSt } +void LIB_TREE_MODEL_ADAPTER::DoRemoveLibrary( const wxString& aNodeName ) +{ + m_tree.RemoveLib( aNodeName ); +} + + void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState ) { { @@ -383,7 +389,7 @@ void LIB_TREE_MODEL_ADAPTER::resortTree() void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode ) { - m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() ); + m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() ); aTreeNode->m_Pinned = true; resortTree(); @@ -393,7 +399,7 @@ void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode ) void LIB_TREE_MODEL_ADAPTER::UnpinLibrary( LIB_TREE_NODE* aTreeNode ) { - m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() ); + m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() ); aTreeNode->m_Pinned = false; resortTree(); diff --git a/common/paths.cpp b/common/paths.cpp index 4b239f0a85..e3ff662752 100644 --- a/common/paths.cpp +++ b/common/paths.cpp @@ -106,6 +106,17 @@ wxString PATHS::GetDefaultUserFootprintsPath() } +wxString PATHS::GetDefaultUserDesignBlocksPath() +{ + wxFileName tmp; + getUserDocumentPath( tmp ); + + tmp.AppendDir( wxT( "blocks" ) ); + + return tmp.GetPath(); +} + + wxString PATHS::GetDefaultUser3DModelsPath() { wxFileName tmp; @@ -224,6 +235,16 @@ wxString PATHS::GetStockFootprintsPath() } +wxString PATHS::GetStockDesignBlocksPath() +{ + wxString path; + + path = GetStockEDALibraryPath() + wxT( "/blocks" ); + + return path; +} + + wxString PATHS::GetStock3dmodelsPath() { wxString path; diff --git a/common/project.cpp b/common/project.cpp index e1cddf821b..6bbd3098df 100644 --- a/common/project.cpp +++ b/common/project.cpp @@ -28,6 +28,7 @@ #include <pgm_base.h> #include <confirm.h> #include <core/kicad_algo.h> +#include <design_block_lib_table.h> #include <fp_lib_table.h> #include <string_utils.h> #include <kiface_ids.h> @@ -167,40 +168,78 @@ const wxString PROJECT::FootprintLibTblName() const } -void PROJECT::PinLibrary( const wxString& aLibrary, bool isSymbolLibrary ) +const wxString PROJECT::DesignBlockLibTblName() const +{ + return libTableName( wxS( "design-block-lib-table" ) ); +} + + +void PROJECT::PinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType ) { COMMON_SETTINGS* cfg = Pgm().GetCommonSettings(); - std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs - : &m_projectFile->m_PinnedFootprintLibs; + std::vector<wxString>* pinnedLibsCfg = nullptr; + std::vector<wxString>* pinnedLibsFile = nullptr; - if( !alg::contains( *pinnedLibs, aLibrary ) ) - pinnedLibs->push_back( aLibrary ); + switch( aLibType ) + { + case LIB_TYPE_T::SYMBOL_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs; + break; + case LIB_TYPE_T::FOOTPRINT_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs; + break; + case LIB_TYPE_T::DESIGN_BLOCK_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs; + break; + default: + wxFAIL_MSG( "Cannot pin library: invalid library type" ); + return; + } + + if( !alg::contains( *pinnedLibsFile, aLibrary ) ) + pinnedLibsFile->push_back( aLibrary ); Pgm().GetSettingsManager().SaveProject(); - pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs - : &cfg->m_Session.pinned_fp_libs; - - if( !alg::contains( *pinnedLibs, aLibrary ) ) - pinnedLibs->push_back( aLibrary ); + if( !alg::contains( *pinnedLibsCfg, aLibrary ) ) + pinnedLibsCfg->push_back( aLibrary ); cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) ); } -void PROJECT::UnpinLibrary( const wxString& aLibrary, bool isSymbolLibrary ) +void PROJECT::UnpinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType ) { COMMON_SETTINGS* cfg = Pgm().GetCommonSettings(); - std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs - : &m_projectFile->m_PinnedFootprintLibs; + std::vector<wxString>* pinnedLibsCfg = nullptr; + std::vector<wxString>* pinnedLibsFile = nullptr; - alg::delete_matching( *pinnedLibs, aLibrary ); + switch( aLibType ) + { + case LIB_TYPE_T::SYMBOL_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs; + break; + case LIB_TYPE_T::FOOTPRINT_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs; + break; + case LIB_TYPE_T::DESIGN_BLOCK_LIB: + pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs; + pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs; + break; + default: + wxFAIL_MSG( "Cannot unpin library: invalid library type" ); + return; + } + + alg::delete_matching( *pinnedLibsFile, aLibrary ); Pgm().GetSettingsManager().SaveProject(); - pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs - : &cfg->m_Session.pinned_fp_libs; - - alg::delete_matching( *pinnedLibs, aLibrary ); + alg::delete_matching( *pinnedLibsCfg, aLibrary ); cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) ); } @@ -374,3 +413,39 @@ FP_LIB_TABLE* PROJECT::PcbFootprintLibs( KIWAY& aKiway ) return tbl; } + + +DESIGN_BLOCK_LIB_TABLE* PROJECT::DesignBlockLibs() +{ + // This is a lazy loading function, it loads the project specific table when + // that table is asked for, not before. + + DESIGN_BLOCK_LIB_TABLE* tbl = (DESIGN_BLOCK_LIB_TABLE*) GetElem( ELEM::DESIGN_BLOCK_LIB_TABLE ); + + if( tbl ) + { + wxASSERT( tbl->ProjectElementType() == PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE ); + } + else + { + try + { + tbl = new DESIGN_BLOCK_LIB_TABLE( &GDesignBlockTable ); + tbl->Load( DesignBlockLibTblName() ); + + SetElem( ELEM::DESIGN_BLOCK_LIB_TABLE, tbl ); + } + catch( const IO_ERROR& ioe ) + { + DisplayErrorMessage( nullptr, _( "Error loading project design block library table." ), + ioe.What() ); + } + catch( ... ) + { + DisplayErrorMessage( nullptr, + _( "Error loading project design block library table." ) ); + } + } + + return tbl; +} diff --git a/common/settings/common_settings.cpp b/common/settings/common_settings.cpp index 34b7e90406..99418b8b88 100644 --- a/common/settings/common_settings.cpp +++ b/common/settings/common_settings.cpp @@ -346,6 +346,9 @@ COMMON_SETTINGS::COMMON_SETTINGS() : m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_fp_libs", &m_Session.pinned_fp_libs, {} ) ); + m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_design_block_libs", + &m_Session.pinned_design_block_libs, {} ) ); + m_params.emplace_back( new PARAM<int>( "netclass_panel.sash_pos", &m_NetclassPanel.sash_pos, 160 ) ); @@ -666,6 +669,10 @@ void COMMON_SETTINGS::InitializeEnvironment() path = basePath; path.AppendDir( wxT( "symbols" ) ); addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ), path.GetFullPath() ); + + path = basePath; + path.AppendDir( wxT( "blocks" ) ); + addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) ), path.GetFullPath() ); } diff --git a/common/settings/kicad_settings.cpp b/common/settings/kicad_settings.cpp index 67a02bcaad..e7ad5e3d7c 100644 --- a/common/settings/kicad_settings.cpp +++ b/common/settings/kicad_settings.cpp @@ -43,6 +43,9 @@ KICAD_SETTINGS::KICAD_SETTINGS() : m_params.emplace_back( new PARAM_LIST<wxString>( "system.open_projects", &m_OpenProjects, {} ) ); + m_params.emplace_back( new PARAM<wxString>( "system.last_design_block_lib_dir", + &m_lastDesignBlockLibDir, "" ) ); + m_params.emplace_back( new PARAM<wxString>( "system.last_update_check_time", &m_lastUpdateCheckTime, "" ) ); diff --git a/common/settings/settings_manager.cpp b/common/settings/settings_manager.cpp index bbc87338c7..7a6f802c35 100644 --- a/common/settings/settings_manager.cpp +++ b/common/settings/settings_manager.cpp @@ -1049,7 +1049,11 @@ std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const std::vector<wxString> ret; for( const std::pair<const wxString, PROJECT*>& pair : m_projects ) - ret.emplace_back( pair.first ); + { + // Don't save empty projects (these are the default project settings) + if( !pair.first.IsEmpty() ) + ret.emplace_back( pair.first ); + } return ret; } diff --git a/common/tool/actions.cpp b/common/tool/actions.cpp index 1d34437fc1..754bea5083 100644 --- a/common/tool/actions.cpp +++ b/common/tool/actions.cpp @@ -1104,6 +1104,13 @@ TOOL_ACTION ACTIONS::showFootprintLibTable( TOOL_ACTION_ARGS() .Tooltip( _( "Edit the global and project footprint library lists" ) ) .Icon( BITMAPS::library_table ) ); +TOOL_ACTION ACTIONS::showDesignBlockLibTable( TOOL_ACTION_ARGS() + .Name( "common.SuiteControl.showDesignBLockLibTable" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Manage Design Block Libraries..." ) ) + .Tooltip( _( "Edit the global and project design block library lists" ) ) + .Icon( BITMAPS::library_table ) ); + TOOL_ACTION ACTIONS::gettingStarted( TOOL_ACTION_ARGS() .Name( "common.SuiteControl.gettingStarted" ) .Scope( AS_GLOBAL ) diff --git a/common/tool/library_editor_control.cpp b/common/tool/library_editor_control.cpp index 9e41123459..fbc86ea9d4 100644 --- a/common/tool/library_editor_control.cpp +++ b/common/tool/library_editor_control.cpp @@ -82,7 +82,8 @@ int LIBRARY_EDITOR_CONTROL::PinLibrary( const TOOL_EVENT& aEvent ) if( current && !current->m_Pinned ) { - m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(), false ); + m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(), + PROJECT::LIB_TYPE_T::FOOTPRINT_LIB ); current->m_Pinned = true; regenerateLibraryTree(); } @@ -98,7 +99,8 @@ int LIBRARY_EDITOR_CONTROL::UnpinLibrary( const TOOL_EVENT& aEvent ) if( current && current->m_Pinned ) { - m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(), false ); + m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(), + PROJECT::LIB_TYPE_T::FOOTPRINT_LIB ); current->m_Pinned = false; regenerateLibraryTree(); } diff --git a/common/trace_helpers.cpp b/common/trace_helpers.cpp index 5f97c6be8e..cd3017338e 100644 --- a/common/trace_helpers.cpp +++ b/common/trace_helpers.cpp @@ -58,6 +58,7 @@ const wxChar* const traceKiCad2Step = wxT( "KICAD2STEP" ); const wxChar* const traceUiProfile = wxT( "KICAD_UI_PROFILE" ); const wxChar* const traceGit = wxT( "KICAD_GIT" ); const wxChar* const traceEagleIo = wxT( "KICAD_EAGLE_IO" ); +const wxChar* const traceDesignBlocks = wxT( "KICAD_DESIGN_BLOCK" ); wxString dump( const wxArrayString& aArray ) diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp index cc6c1cbe46..c42ab64a91 100644 --- a/common/wildcards_and_files_ext.cpp +++ b/common/wildcards_and_files_ext.cpp @@ -186,6 +186,9 @@ const std::string FILEEXT::IpcD356FileExtension( "d356" ); const std::string FILEEXT::Ipc2581FileExtension( "xml" ); const std::string FILEEXT::WorkbookFileExtension( "wbk" ); +const std::string FILEEXT::KiCadDesignBlockLibPathExtension( "blocks" ); // this is a directory +const std::string FILEEXT::KiCadDesignBlockPathExtension( "block" ); // this is a directory + const std::string FILEEXT::PngFileExtension( "png" ); const std::string FILEEXT::JpegFileExtension( "jpg" ); const std::string FILEEXT::TextFileExtension( "txt" ); @@ -337,6 +340,20 @@ wxString FILEEXT::KiCadFootprintLibPathWildcard() } +wxString FILEEXT::KiCadDesignBlockPathWildcard() +{ + return _( "KiCad design block path" ) + + AddFileExtListToFilter( { KiCadDesignBlockPathExtension } ); +} + + +wxString FILEEXT::KiCadDesignBlockLibPathWildcard() +{ + return _( "KiCad design block library paths" ) + + AddFileExtListToFilter( { KiCadDesignBlockLibPathExtension } ); +} + + wxString FILEEXT::DrawingSheetFileWildcard() { return _( "Drawing sheet files" ) diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index ef3cd9be5f..7c32db1b2f 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -272,7 +272,10 @@ set( EESCHEMA_SIM_SRCS ) set( EESCHEMA_WIDGETS + widgets/design_block_pane.cpp + widgets/design_block_preview_widget.cpp widgets/hierarchy_pane.cpp + widgets/panel_design_block_chooser.cpp widgets/panel_sch_selection_filter_base.cpp widgets/panel_sch_selection_filter.cpp widgets/panel_symbol_chooser.cpp @@ -347,6 +350,8 @@ set( EESCHEMA_SRCS bus-wire-junction.cpp connection_graph.cpp cross-probing.cpp + design_block_tree_model_adapter.cpp + design_block_utils.cpp ee_collectors.cpp eeschema_config.cpp eeschema_helpers.cpp @@ -441,6 +446,7 @@ set( EESCHEMA_SRCS tools/ee_selection_tool.cpp tools/rule_area_create_helper.cpp tools/sch_drawing_tools.cpp + tools/sch_design_block_control.cpp tools/sch_edit_table_tool.cpp tools/sch_edit_tool.cpp tools/sch_editor_control.cpp diff --git a/eeschema/cross-probing.cpp b/eeschema/cross-probing.cpp index 8563476338..3b71d5ffe7 100644 --- a/eeschema/cross-probing.cpp +++ b/eeschema/cross-probing.cpp @@ -37,6 +37,7 @@ #include <tools/ee_actions.h> #include <tools/sch_editor_control.h> #include <advanced_config.h> +#include <widgets/design_block_pane.h> #include <wx/log.h> SCH_ITEM* SCH_EDITOR_CONTROL::FindSymbolAndItem( const wxString* aPath, const wxString* aReference, @@ -998,6 +999,7 @@ void SCH_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail ) break; case MAIL_RELOAD_LIB: + m_designBlocksPane->RefreshLibs(); SyncView(); break; diff --git a/eeschema/design_block_tree_model_adapter.cpp b/eeschema/design_block_tree_model_adapter.cpp new file mode 100644 index 0000000000..1a8763f76f --- /dev/null +++ b/eeschema/design_block_tree_model_adapter.cpp @@ -0,0 +1,199 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <pgm_base.h> +#include <eda_base_frame.h> +#include <core/kicad_algo.h> +#include <settings/common_settings.h> +#include <project/project_file.h> +#include <wx/log.h> +#include <wx/tokenzr.h> +#include <string_utils.h> +#include <eda_pattern_match.h> +#include <design_block.h> +#include <design_block_lib_table.h> +#include <design_block_info.h> +#include <footprint_info.h> +#include <wx/settings.h> + +#include <design_block_tree_model_adapter.h> +#include <tools/sch_design_block_control.h> + +wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER> +DESIGN_BLOCK_TREE_MODEL_ADAPTER::Create( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs ) +{ + auto* adapter = new DESIGN_BLOCK_TREE_MODEL_ADAPTER( aParent, aLibs ); + adapter->m_frame = aParent; + return wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>( adapter ); +} + + +DESIGN_BLOCK_TREE_MODEL_ADAPTER::DESIGN_BLOCK_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, + LIB_TABLE* aLibs ) : + LIB_TREE_MODEL_ADAPTER( aParent, wxT( "pinned_design_block_libs" ) ), + m_libs( (DESIGN_BLOCK_LIB_TABLE*) aLibs ) +{ +} + + +void DESIGN_BLOCK_TREE_MODEL_ADAPTER::AddLibraries( EDA_BASE_FRAME* aParent ) +{ + COMMON_SETTINGS* cfg = Pgm().GetCommonSettings(); + PROJECT_FILE& project = aParent->Prj().GetProjectFile(); + + for( const wxString& libName : m_libs->GetLogicalLibs() ) + { + const DESIGN_BLOCK_LIB_TABLE_ROW* library = nullptr; + + try + { + library = m_libs->FindRow( libName, true ); + } + catch( ... ) + { + // Skip loading this library, if not exists/ not found + continue; + } + bool pinned = alg::contains( cfg->m_Session.pinned_design_block_libs, libName ) + || alg::contains( project.m_PinnedDesignBlockLibs, libName ); + + DoAddLibrary( libName, library->GetDescr(), getDesignBlocks( aParent, libName ), pinned, + true ); + } + + m_tree.AssignIntrinsicRanks(); +} + + +void DESIGN_BLOCK_TREE_MODEL_ADAPTER::ClearLibraries() +{ + m_tree.Clear(); +} + + +std::vector<LIB_TREE_ITEM*> +DESIGN_BLOCK_TREE_MODEL_ADAPTER::getDesignBlocks( EDA_BASE_FRAME* aParent, + const wxString& aLibName ) +{ + std::vector<LIB_TREE_ITEM*> libList; + + + auto fullListStart = GDesignBlockList.GetList().begin(); + auto fullListEnd = GDesignBlockList.GetList().end(); + std::unique_ptr<DESIGN_BLOCK_INFO> dummy = + std::make_unique<DESIGN_BLOCK_INFO_IMPL>( aLibName, wxEmptyString ); + + // List is sorted, so use a binary search to find the range of footnotes for our library + auto libBounds = std::equal_range( + fullListStart, fullListEnd, dummy, + []( const std::unique_ptr<DESIGN_BLOCK_INFO>& a, + const std::unique_ptr<DESIGN_BLOCK_INFO>& b ) + { + return StrNumCmp( a->GetLibNickname(), b->GetLibNickname(), false ) < 0; + } ); + + for( auto i = libBounds.first; i != libBounds.second; ++i ) + libList.push_back( i->get() ); + + return libList; +} + + +wxString DESIGN_BLOCK_TREE_MODEL_ADAPTER::GenerateInfo( LIB_ID const& aLibId, int aUnit ) +{ + const wxString DescriptionFormat = wxT( + "<b>__NAME__</b>" + "<br>__DESC__" + "<hr><table border=0>" + "__FIELDS__" + "</table>" ); + + const wxString KeywordsFormat = wxT( + "<tr>" + " <td><b>" + _( "Keywords" ) + "</b></td>" + " <td>__KEYWORDS__</td>" + "</tr>" ); + + const wxString DocFormat = wxT( + "<tr>" + " <td><b>" + _( "Documentation" ) + "</b></td>" + " <td><a href=\"__HREF__\">__TEXT__</a></td>" + "</tr>" ); + + + if( !aLibId.IsValid() ) + return wxEmptyString; + + const DESIGN_BLOCK* db = nullptr; + + try + { + db = m_libs->GetEnumeratedDesignBlock( aLibId.GetLibNickname(), aLibId.GetLibItemName() ); + } + catch( const IO_ERROR& ioe ) + { + wxLogError( _( "Error loading design block %s from library '%s'." ) + wxS( "\n%s" ), + aLibId.GetLibItemName().wx_str(), aLibId.GetLibNickname().wx_str(), + ioe.What() ); + + return wxEmptyString; + } + + wxString html = DescriptionFormat; + + if( db ) + { + wxString name = aLibId.GetLibItemName(); + wxString desc = db->GetLibDescription(); + wxString keywords = db->GetKeywords(); + wxString doc = db->GetDocumentationUrl(); + + wxString esc_desc = EscapeHTML( UnescapeString( desc ) ); + + // Add line breaks + esc_desc.Replace( wxS( "\n" ), wxS( "<br>" ) ); + + // Add links + esc_desc = LinkifyHTML( esc_desc ); + + html.Replace( "__DESC__", esc_desc ); + html.Replace( "__NAME__", EscapeHTML( name ) ); + + wxString keywordsHtml = KeywordsFormat; + keywordsHtml.Replace( "__KEYWORDS__", EscapeHTML( keywords ) ); + + wxString docHtml = DocFormat; + docHtml.Replace( "__HREF__", doc ); + + if( doc.Length() > 75 ) + doc = doc.Left( 72 ) + wxT( "..." ); + + docHtml.Replace( "__TEXT__", EscapeHTML( doc ) ); + + html.Replace( "__FIELDS__", keywordsHtml + docHtml ); + } + + return html; +} + + +TOOL_INTERACTIVE* DESIGN_BLOCK_TREE_MODEL_ADAPTER::GetContextMenuTool() +{ + return m_frame->GetToolManager()->GetTool<SCH_DESIGN_BLOCK_CONTROL>(); +} diff --git a/eeschema/design_block_tree_model_adapter.h b/eeschema/design_block_tree_model_adapter.h new file mode 100644 index 0000000000..b1fc73aa98 --- /dev/null +++ b/eeschema/design_block_tree_model_adapter.h @@ -0,0 +1,62 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DESIGN_BLOCK_TREE_MODEL_ADAPTER_H +#define DESIGN_BLOCK_TREE_MODEL_ADAPTER_H + +#include <lib_tree_model_adapter.h> + +class LIB_TABLE; +class DESIGN_BLOCK_LIB_TABLE; + +class DESIGN_BLOCK_TREE_MODEL_ADAPTER : public LIB_TREE_MODEL_ADAPTER +{ +public: + /** + * Factory function: create a model adapter in a reference-counting container. + * + * @param aLibs library set from which parts will be loaded + */ + static wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER> Create( EDA_BASE_FRAME* aParent, + LIB_TABLE* aLibs ); + + void AddLibraries( EDA_BASE_FRAME* aParent ); + void ClearLibraries(); + + wxString GenerateInfo( LIB_ID const& aLibId, int aUnit ) override; + + TOOL_INTERACTIVE* GetContextMenuTool() override; + +protected: + /** + * Constructor; takes a set of libraries to be included in the search. + */ + DESIGN_BLOCK_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs ); + + std::vector<LIB_TREE_ITEM*> getDesignBlocks( EDA_BASE_FRAME* aParent, + const wxString& aLibName ); + + PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB; } + +protected: + DESIGN_BLOCK_LIB_TABLE* m_libs; + EDA_BASE_FRAME* m_frame; +}; + +#endif // DESIGN_BLOCK_TREE_MODEL_ADAPTER_H diff --git a/eeschema/design_block_utils.cpp b/eeschema/design_block_utils.cpp new file mode 100644 index 0000000000..a98d70d70a --- /dev/null +++ b/eeschema/design_block_utils.cpp @@ -0,0 +1,522 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <pgm_base.h> +#include <kiway.h> +#include <design_block.h> +#include <design_block_lib_table.h> +#include <design_block_pane.h> +#include <sch_edit_frame.h> +#include <wx/choicdlg.h> +#include <wx/msgdlg.h> +#include <wx/textdlg.h> +#include <wildcards_and_files_ext.h> +#include <paths.h> +#include <env_paths.h> +#include <common.h> +#include <kidialog.h> +#include <confirm.h> +#include <tool/tool_manager.h> +#include <ee_selection_tool.h> + +DESIGN_BLOCK_LIB_TABLE* SCH_EDIT_FRAME::selectDesignBlockLibTable( bool aOptional ) +{ + // If no project is loaded, always work with the global table + if( Prj().IsNullProject() ) + { + DESIGN_BLOCK_LIB_TABLE* ret = &GDesignBlockTable; + + if( aOptional ) + { + wxMessageDialog dlg( this, _( "Add the library to the global library table?" ), + _( "Add To Global Library Table" ), wxYES_NO ); + + if( dlg.ShowModal() != wxID_OK ) + ret = nullptr; + } + + return ret; + } + + wxArrayString libTableNames; + libTableNames.Add( _( "Global" ) ); + libTableNames.Add( _( "Project" ) ); + + wxSingleChoiceDialog dlg( this, _( "Choose the Library Table to add the library to:" ), + _( "Add To Library Table" ), libTableNames ); + + if( aOptional ) + { + dlg.FindWindow( wxID_CANCEL )->SetLabel( _( "Skip" ) ); + dlg.FindWindow( wxID_OK )->SetLabel( _( "Add" ) ); + } + + if( dlg.ShowModal() != wxID_OK ) + return nullptr; + + switch( dlg.GetSelection() ) + { + case 0: return &GDesignBlockTable; + case 1: return Prj().DesignBlockLibs(); + default: return nullptr; + } +} + + +wxString SCH_EDIT_FRAME::CreateNewDesignBlockLibrary( const wxString& aLibName, + const wxString& aProposedName ) +{ + return createNewDesignBlockLibrary( aLibName, aProposedName, selectDesignBlockLibTable() ); +} + + +wxString SCH_EDIT_FRAME::createNewDesignBlockLibrary( const wxString& aLibName, + const wxString& aProposedName, + DESIGN_BLOCK_LIB_TABLE* aTable ) +{ + if( aTable == nullptr ) + return wxEmptyString; + + wxString initialPath = aProposedName.IsEmpty() ? Prj().GetProjectPath() : aProposedName; + wxFileName fn; + bool doAdd = false; + bool isGlobal = ( aTable == &GDesignBlockTable ); + + if( aLibName.IsEmpty() ) + { + fn = initialPath; + + if( !LibraryFileBrowser( false, fn, FILEEXT::KiCadDesignBlockLibPathWildcard(), + FILEEXT::KiCadDesignBlockLibPathExtension, false, isGlobal, + PATHS::GetDefaultUserFootprintsPath() ) ) + { + return wxEmptyString; + } + + doAdd = true; + } + else + { + fn = EnsureFileExtension( aLibName, FILEEXT::KiCadDesignBlockLibPathExtension ); + + if( !fn.IsAbsolute() ) + { + fn.SetName( aLibName ); + fn.MakeAbsolute( initialPath ); + } + } + + // We can save libs only using DESIGN_BLOCK_IO_MGR::KICAD_SEXP format (.pretty libraries) + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T piType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP; + wxString libPath = fn.GetFullPath(); + + try + { + IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( piType ) ); + + bool writable = false; + bool exists = false; + + try + { + writable = pi->IsLibraryWritable( libPath ); + exists = fn.Exists(); + } + catch( const IO_ERROR& ) + { + // best efforts.... + } + + if( exists ) + { + if( !writable ) + { + wxString msg = wxString::Format( _( "Library %s is read only." ), libPath ); + ShowInfoBarError( msg ); + return wxEmptyString; + } + else + { + wxString msg = wxString::Format( _( "Library %s already exists." ), libPath ); + KIDIALOG dlg( this, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING ); + dlg.SetOKLabel( _( "Overwrite" ) ); + dlg.DoNotShowCheckbox( __FILE__, __LINE__ ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return wxEmptyString; + + pi->DeleteLibrary( libPath ); + } + } + + pi->CreateLibrary( libPath ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + return wxEmptyString; + } + + if( doAdd ) + AddDesignBlockLibrary( libPath, aTable ); + + return libPath; +} + + +bool SCH_EDIT_FRAME::AddDesignBlockLibrary( const wxString& aFilename, + DESIGN_BLOCK_LIB_TABLE* aTable ) +{ + if( aTable == nullptr ) + aTable = selectDesignBlockLibTable(); + + if( aTable == nullptr ) + return wxEmptyString; + + bool isGlobal = ( aTable == &GDesignBlockTable ); + + wxFileName fn( aFilename ); + + if( aFilename.IsEmpty() ) + { + if( !LibraryFileBrowser( true, fn, FILEEXT::KiCadFootprintLibPathWildcard(), + FILEEXT::KiCadFootprintLibPathExtension, true, isGlobal, + PATHS::GetDefaultUserFootprintsPath() ) ) + { + return false; + } + } + + wxString libPath = fn.GetFullPath(); + wxString libName = fn.GetName(); + + if( libName.IsEmpty() ) + return false; + + // Open a dialog to ask for a description + wxString description = wxGetTextFromUser( _( "Enter a description for the library:" ), + _( "Library Description" ), wxEmptyString, this ); + + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T lib_type = + DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( libPath ); + + if( lib_type == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE ) + lib_type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP; + + wxString type = DESIGN_BLOCK_IO_MGR::ShowType( lib_type ); + + // KiCad lib is our default guess. So it might not have the .blocks extension + // In this case, the extension is part of the library name + if( lib_type == DESIGN_BLOCK_IO_MGR::KICAD_SEXP + && fn.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension ) + libName = fn.GetFullName(); + + // try to use path normalized to an environmental variable or project path + wxString normalizedPath = NormalizePath( libPath, &Pgm().GetLocalEnvVariables(), &Prj() ); + + try + { + DESIGN_BLOCK_LIB_TABLE_ROW* row = new DESIGN_BLOCK_LIB_TABLE_ROW( + libName, normalizedPath, type, wxEmptyString, description ); + aTable->InsertRow( row ); + + if( isGlobal ) + GDesignBlockTable.Save( DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName() ); + else + Prj().DesignBlockLibs()->Save( Prj().DesignBlockLibTblName() ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + return false; + } + + LIB_ID libID( libName, wxEmptyString ); + m_designBlocksPane->RefreshLibs(); + m_designBlocksPane->SelectLibId( libID ); + + return true; +} + + +void SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName ) +{ + // Make sure the user has selected a library to save into + if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() ) + { + DisplayError( this, _( "Please select a library to save the design block to." ) ); + return; + } + + // Just block all attempts to create design blocks with nested sheets at this point + std::vector<SCH_ITEM*> sheets; + GetScreen()->GetSheets( &sheets ); + + if( !sheets.empty() ) + { + DisplayError( this, _( "Design blocks with nested sheets are not supported." ) ); + return; + } + + // Ask the user for the design block name + wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() ); + + wxString name = wxGetTextFromUser( _( "Enter a name for the design block:" ), + _( "Design Block Name" ), fn.GetName(), this ); + + if( name.IsEmpty() ) + return; + + // Save a temporary copy of the schematic file, as the plugin is just going to move it + wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); + if( !saveSchematicFile( GetCurrentSheet().Last(), tempFile ) ) + { + DisplayError( this, _( "Error saving temporary schematic file to create design block." ) ); + wxRemoveFile( tempFile ); + return; + } + + + DESIGN_BLOCK blk; + blk.SetSchematicFile( tempFile ); + blk.SetLibId( LIB_ID( aLibraryName, name ) ); + + try + { + Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + } + + // Clean up the temporary file + wxRemoveFile( tempFile ); + + m_designBlocksPane->RefreshLibs(); + m_designBlocksPane->SelectLibId( blk.GetLibId() ); +} + + +void SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName ) +{ + // Make sure the user has selected a library to save into + if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() ) + { + DisplayError( this, _( "Please select a library to save the design block to." ) ); + return; + } + + // Get all selected items + EE_SELECTION selection = m_toolManager->GetTool<EE_SELECTION_TOOL>()->GetSelection(); + + if( selection.Empty() ) + { + DisplayError( this, _( "Please select some items to save as a design block." ) ); + return; + } + + // Just block all attempts to create design blocks with nested sheets at this point + if( selection.HasType( SCH_SHEET_T ) ) + { + DisplayError( this, _( "Design blocks with nested sheets are not supported." ) ); + return; + } + + // Ask the user for the design block name + wxString name = wxGetTextFromUser( _( "Enter a name for the design block:" ), + _( "Design Block Name" ), wxEmptyString, this ); + + if( name.IsEmpty() ) + return; + + // Create a temperorary screen + SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic ); + + // Copy the selected items to the temporary screen + for( EDA_ITEM* item : selection ) + { + EDA_ITEM* copy = item->Clone(); + tempScreen->Append( static_cast<SCH_ITEM*>( copy ) ); + } + + // Create a sheet for the temporary screen + SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic ); + tempSheet->SetScreen( tempScreen ); + + // Save a temporary copy of the schematic file, as the plugin is just going to move it + wxString tempFile = wxFileName::CreateTempFileName( "design_block" ); + if( !saveSchematicFile( tempSheet, tempFile ) ) + { + DisplayError( this, _( "Error saving temporary schematic file to create design block." ) ); + wxRemoveFile( tempFile ); + return; + } + + // Create a design block + DESIGN_BLOCK blk; + blk.SetSchematicFile( tempFile ); + blk.SetLibId( LIB_ID( aLibraryName, name ) ); + + try + { + // Actually save it to disk + Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + } + + // Clean up the temporaries + wxRemoveFile( tempFile ); + // This will also delete the screen + delete tempSheet; + + m_designBlocksPane->RefreshLibs(); + m_designBlocksPane->SelectLibId( blk.GetLibId() ); +} + + +bool SCH_EDIT_FRAME::DeleteDesignBlockLibrary( const wxString& aLibName, bool aConfirm ) +{ + if( aLibName.IsEmpty() ) + { + DisplayError( this, _( "Please select a library to delete." ) ); + return false; + } + + if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( aLibName ) ) + { + wxString msg = wxString::Format( _( "Library '%s' is read only." ), aLibName ); + ShowInfoBarError( msg ); + return false; + } + + // Confirmation + wxString msg = wxString::Format( _( "Delete design block library '%s' from disk? This will " + "delete all design blocks within the library." ), + aLibName.GetData() ); + + if( aConfirm && !IsOK( this, msg ) ) + return false; + + try + { + Prj().DesignBlockLibs()->DesignBlockLibDelete( aLibName ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + return false; + } + + msg.Printf( _( "Design block library '%s' deleted" ), aLibName.GetData() ); + SetStatusText( msg ); + + m_designBlocksPane->RefreshLibs(); + + return true; +} + + +bool SCH_EDIT_FRAME::DeleteDesignBlockFromLibrary( const LIB_ID& aLibId, bool aConfirm ) +{ + if( !aLibId.IsValid() ) + return false; + + wxString libname = aLibId.GetLibNickname(); + wxString dbname = aLibId.GetLibItemName(); + + if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( libname ) ) + { + wxString msg = wxString::Format( _( "Library '%s' is read only." ), libname ); + ShowInfoBarError( msg ); + return false; + } + + // Confirmation + wxString msg = wxString::Format( _( "Delete design block '%s' in library '%s' from disk?" ), + dbname.GetData(), libname.GetData() ); + + if( aConfirm && !IsOK( this, msg ) ) + return false; + + try + { + Prj().DesignBlockLibs()->DesignBlockDelete( libname, dbname ); + } + catch( const IO_ERROR& ioe ) + { + DisplayError( this, ioe.What() ); + return false; + } + + msg.Printf( _( "Design block '%s' deleted from library '%s'" ), dbname.GetData(), + libname.GetData() ); + + SetStatusText( msg ); + + m_designBlocksPane->RefreshLibs(); + + return true; +} + + +DESIGN_BLOCK* SchGetDesignBlock( const LIB_ID& aLibId, DESIGN_BLOCK_LIB_TABLE* aLibTable, + wxWindow* aParent, bool aShowErrorMsg ) +{ + wxCHECK_MSG( aLibTable, nullptr, wxS( "Invalid design block library table." ) ); + + DESIGN_BLOCK* designBlock = nullptr; + + try + { + designBlock = aLibTable->DesignBlockLoadWithOptionalNickname( aLibId, true ); + } + catch( const IO_ERROR& ioe ) + { + if( aShowErrorMsg ) + { + wxString msg = wxString::Format( _( "Error loading designblock %s from library '%s'." ), + aLibId.GetLibItemName().wx_str(), + aLibId.GetLibNickname().wx_str() ); + DisplayErrorMessage( aParent, msg, ioe.What() ); + } + } + + return designBlock; +} + + +DESIGN_BLOCK* SCH_EDIT_FRAME::GetDesignBlock( const LIB_ID& aLibId, bool aUseCacheLib, + bool aShowErrorMsg ) +{ + return SchGetDesignBlock( aLibId, Prj().DesignBlockLibs(), this, aShowErrorMsg ); +} + + +void SCH_EDIT_FRAME::UpdateDesignBlockOptions() +{ + m_designBlocksPane->UpdateCheckboxes(); +} diff --git a/eeschema/dialogs/dialog_sheet_properties.cpp b/eeschema/dialogs/dialog_sheet_properties.cpp index 057ecf33c2..5415113d1a 100644 --- a/eeschema/dialogs/dialog_sheet_properties.cpp +++ b/eeschema/dialogs/dialog_sheet_properties.cpp @@ -178,26 +178,10 @@ bool DIALOG_SHEET_PROPERTIES::TransferDataToWindow() m_borderSwatch->SetSwatchBackground( canvas ); m_backgroundSwatch->SetSwatchBackground( canvas ); - SCH_SHEET_LIST hierarchy = m_frame->Schematic().GetFullHierarchy(); SCH_SHEET_PATH instance = m_frame->GetCurrentSheet(); - instance.push_back( m_sheet ); - wxString pageNumber; - - if( m_sheet->IsNew() ) - { - // Don't try to be too clever when assigning the next availabe page number. Just use - // the number of sheets plus one. - pageNumber.Printf( wxT( "%d" ), static_cast<int>( hierarchy.size() ) + 1 ); - instance.SetPageNumber( pageNumber ); - } - else - { - pageNumber = instance.GetPageNumber(); - } - - m_pageNumberTextCtrl->ChangeValue( pageNumber ); + m_pageNumberTextCtrl->ChangeValue( instance.GetPageNumber() ); m_cbExcludeFromSim->SetValue( m_sheet->GetExcludedFromSim() ); m_cbExcludeFromBom->SetValue( m_sheet->GetExcludedFromBOM() ); diff --git a/eeschema/eeschema_config.cpp b/eeschema/eeschema_config.cpp index d7ac921fd1..386a6f5dc0 100644 --- a/eeschema/eeschema_config.cpp +++ b/eeschema/eeschema_config.cpp @@ -34,6 +34,7 @@ #include <sch_edit_frame.h> #include <sch_painter.h> #include <schematic.h> +#include <widgets/design_block_pane.h> #include <widgets/hierarchy_pane.h> #include <widgets/sch_search_pane.h> #include <widgets/panel_sch_selection_filter.h> @@ -304,6 +305,17 @@ void SCH_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) cfg->m_AuiPanels.net_nav_panel_float_pos = netNavigatorPane.floating_pos; cfg->m_AuiPanels.net_nav_panel_float_size = netNavigatorPane.floating_size; } + + wxAuiPaneInfo& designBlocksPane = m_auimgr.GetPane( DesignBlocksPaneName() ); + cfg->m_AuiPanels.design_blocks_show = designBlocksPane.IsShown(); + + if( designBlocksPane.IsDocked() ) + cfg->m_AuiPanels.design_blocks_panel_docked_width = m_designBlocksPane->GetSize().x; + else + { + cfg->m_AuiPanels.design_blocks_panel_float_height = designBlocksPane.floating_size.y; + cfg->m_AuiPanels.design_blocks_panel_float_width = designBlocksPane.floating_size.x; + } } } diff --git a/eeschema/eeschema_helpers.cpp b/eeschema/eeschema_helpers.cpp index d99149bb35..c6074baaf5 100644 --- a/eeschema/eeschema_helpers.cpp +++ b/eeschema/eeschema_helpers.cpp @@ -60,7 +60,7 @@ SETTINGS_MANAGER* EESCHEMA_HELPERS::GetSettingsManager() } -PROJECT* EESCHEMA_HELPERS::GetDefaultProject() +PROJECT* EESCHEMA_HELPERS::GetDefaultProject( bool aSetActive ) { // For some reasons, LoadProject() needs a C locale, so ensure we have the right locale // This is mainly when running QA Python tests @@ -70,7 +70,7 @@ PROJECT* EESCHEMA_HELPERS::GetDefaultProject() if( !project ) { - GetSettingsManager()->LoadProject( "" ); + GetSettingsManager()->LoadProject( "", aSetActive ); project = GetSettingsManager()->GetProject( "" ); } @@ -78,20 +78,9 @@ PROJECT* EESCHEMA_HELPERS::GetDefaultProject() } -SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, bool aSetActive ) -{ - if( aFileName.EndsWith( FILEEXT::KiCadSchematicFileExtension ) ) - return LoadSchematic( aFileName, SCH_IO_MGR::SCH_KICAD, aSetActive ); - else if( aFileName.EndsWith( FILEEXT::LegacySchematicFileExtension ) ) - return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive ); - - // as fall back for any other kind use the legacy format - return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive ); -} - - -SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat, - bool aSetActive ) +SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( const wxString& aFileName, + SCH_IO_MGR::SCH_FILE_T aFormat, bool aSetActive, + bool aForceDefaultProject ) { wxFileName pro = aFileName; pro.SetExt( FILEEXT::ProjectFileExtension ); @@ -104,23 +93,26 @@ SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH PROJECT* project = GetSettingsManager()->GetProject( projectPath ); - if( !project ) + if( !aForceDefaultProject ) { - if( wxFileExists( projectPath ) ) + if( !project ) { - GetSettingsManager()->LoadProject( projectPath, aSetActive ); - project = GetSettingsManager()->GetProject( projectPath ); + if( wxFileExists( projectPath ) ) + { + GetSettingsManager()->LoadProject( projectPath, aSetActive ); + project = GetSettingsManager()->GetProject( projectPath ); + } + } + else if( s_SchEditFrame && project == &GetSettingsManager()->Prj() ) + { + // Project is already loaded? Then so is the board + return &s_SchEditFrame->Schematic(); } - } - else if( s_SchEditFrame && project == &GetSettingsManager()->Prj() ) - { - // Project is already loaded? Then so is the board - return &s_SchEditFrame->Schematic(); } // Board cannot be loaded without a project, so create the default project - if( !project ) - project = GetDefaultProject(); + if( !project || aForceDefaultProject ) + project = GetDefaultProject( aSetActive ); IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( aFormat ) ); diff --git a/eeschema/eeschema_helpers.h b/eeschema/eeschema_helpers.h index 02e8e240b9..ac663879ff 100644 --- a/eeschema/eeschema_helpers.h +++ b/eeschema/eeschema_helpers.h @@ -43,15 +43,13 @@ class EESCHEMA_HELPERS public: static SETTINGS_MANAGER* GetSettingsManager(); static void SetSchEditFrame( SCH_EDIT_FRAME* aSchEditFrame ); - static PROJECT* GetDefaultProject(); - static SCHEMATIC* LoadSchematic( wxString& aFileName, bool aSetActive ); - static SCHEMATIC* LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat, - bool aSetActive ); - + static PROJECT* GetDefaultProject( bool aSetActive ); + static SCHEMATIC* LoadSchematic( const wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat, + bool aSetActive, bool aForceDefaultProject ); private: static SCH_EDIT_FRAME* s_SchEditFrame; static SETTINGS_MANAGER* s_SettingsManager; }; -#endif \ No newline at end of file +#endif diff --git a/eeschema/eeschema_jobs_handler.cpp b/eeschema/eeschema_jobs_handler.cpp index 5176cf8d3b..11da7e1a6a 100644 --- a/eeschema/eeschema_jobs_handler.cpp +++ b/eeschema/eeschema_jobs_handler.cpp @@ -149,7 +149,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportPlot( JOB* aJob ) if( !aPlotJob ) return CLI::EXIT_CODES::ERR_UNKNOWN; - SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aPlotJob->m_filename, SCH_IO_MGR::SCH_KICAD, true ); + SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aPlotJob->m_filename, SCH_IO_MGR::SCH_KICAD, + true, false ); if( sch == nullptr ) { @@ -247,7 +248,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportNetlist( JOB* aJob ) if( !aNetJob ) return CLI::EXIT_CODES::ERR_UNKNOWN; - SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true ); + SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, + true, false ); if( sch == nullptr ) { @@ -358,7 +360,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob ) if( !aBomJob ) return CLI::EXIT_CODES::ERR_UNKNOWN; - SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aBomJob->m_filename, SCH_IO_MGR::SCH_KICAD, true ); + SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aBomJob->m_filename, SCH_IO_MGR::SCH_KICAD, + true, false ); if( sch == nullptr ) { @@ -616,7 +619,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportPythonBom( JOB* aJob ) if( !aNetJob ) return CLI::EXIT_CODES::ERR_UNKNOWN; - SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true ); + SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, + true, false ); if( sch == nullptr ) { @@ -963,7 +967,8 @@ int EESCHEMA_JOBS_HANDLER::JobSchErc( JOB* aJob ) if( !ercJob ) return CLI::EXIT_CODES::ERR_UNKNOWN; - SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( ercJob->m_filename, SCH_IO_MGR::SCH_KICAD, true ); + SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( ercJob->m_filename, SCH_IO_MGR::SCH_KICAD, + true, false ); if( sch == nullptr ) { diff --git a/eeschema/eeschema_settings.cpp b/eeschema/eeschema_settings.cpp index df8c373a21..6516a6c3e0 100644 --- a/eeschema/eeschema_settings.cpp +++ b/eeschema/eeschema_settings.cpp @@ -126,6 +126,28 @@ const wxAuiPaneInfo& defaultSchSelectionFilterPaneInfo( wxWindow* aWindow ) } +const wxAuiPaneInfo& defaultDesignBlocksPaneInfo( wxWindow* aWindow ) +{ + static wxAuiPaneInfo paneInfo; + + paneInfo.Name( EDA_DRAW_FRAME::DesignBlocksPaneName() ) + .Caption( _( "Design Blocks" ) ) + .CaptionVisible( true ) + .PaneBorder( true ) + .Right().Layer( 3 ).Position( 2 ) + .TopDockable( false ) + .BottomDockable( false ) + .CloseButton( false ) + .MinSize( aWindow->FromDIP( wxSize( 240, 60 ) ) ) + .BestSize( aWindow->FromDIP( wxSize( 300, 200 ) ) ) + .FloatingSize( aWindow->FromDIP( wxSize( 800, 600 ) ) ) + .FloatingPosition( aWindow->FromDIP( wxPoint( 50, 200 ) ) ) + .Show( true ); + + return paneInfo; +} + + EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() : APP_SETTINGS_BASE( "eeschema", eeschemaSchemaVersion ), m_Appearance(), @@ -240,6 +262,18 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() : m_params.emplace_back( new PARAM<int>( "aui.hierarchy_panel_float_height", &m_AuiPanels.hierarchy_panel_float_height, -1 ) ); + m_params.emplace_back( new PARAM<bool>( "aui.design_blocks_show", + &m_AuiPanels.design_blocks_show, false ) ); + + m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_docked_width", + &m_AuiPanels.design_blocks_panel_docked_width, -1 ) ); + + m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_float_width", + &m_AuiPanels.design_blocks_panel_float_width, -1 ) ); + + m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_float_height", + &m_AuiPanels.design_blocks_panel_float_height, -1 ) ); + m_params.emplace_back( new PARAM<bool>( "aui.schematic_hierarchy_float", &m_AuiPanels.schematic_hierarchy_float, false ) ); @@ -613,6 +647,24 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() : m_params.emplace_back( new PARAM<bool>( "symbol_chooser.place_all_units", &m_SymChooserPanel.place_all_units, true ) ); + m_params.emplace_back( new PARAM<int>( "design_block_chooser.sash_pos_v", + &m_DesignBlockChooserPanel.sash_pos_v, -1 ) ); + + m_params.emplace_back( new PARAM<int>( "design_block_chooser.width", + &m_DesignBlockChooserPanel.width, -1 ) ); + + m_params.emplace_back( new PARAM<int>( "design_block_chooser.height", + &m_DesignBlockChooserPanel.height, -1 ) ); + + m_params.emplace_back( new PARAM<int>( "design_block_chooser.sort_mode", + &m_DesignBlockChooserPanel.sort_mode, 0 ) ); + + m_params.emplace_back( new PARAM<bool>( "design_block_chooser.repeated_placement", + &m_DesignBlockChooserPanel.repeated_placement, false ) ); + + m_params.emplace_back( new PARAM<bool>( "design_block_chooser.place_as_sheet", + &m_DesignBlockChooserPanel.place_as_sheet, false ) ); + m_params.emplace_back( new PARAM<bool>( "import_graphics.interactive_placement", &m_ImportGraphics.interactive_placement, true ) ); diff --git a/eeschema/eeschema_settings.h b/eeschema/eeschema_settings.h index c685216eea..163f155caa 100644 --- a/eeschema/eeschema_settings.h +++ b/eeschema/eeschema_settings.h @@ -35,6 +35,7 @@ using KIGFX::COLOR4D; extern const wxAuiPaneInfo& defaultNetNavigatorPaneInfo(); extern const wxAuiPaneInfo& defaultPropertiesPaneInfo( wxWindow* aWindow ); extern const wxAuiPaneInfo& defaultSchSelectionFilterPaneInfo( wxWindow* aWindow ); +extern const wxAuiPaneInfo& defaultDesignBlocksPaneInfo( wxWindow* aWindow ); @@ -101,6 +102,10 @@ public: int properties_panel_width; float properties_splitter; bool show_properties; + bool design_blocks_show; + int design_blocks_panel_docked_width; + int design_blocks_panel_float_width; + int design_blocks_panel_float_height; }; struct AUTOPLACE_FIELDS @@ -269,6 +274,18 @@ public: bool place_all_units; }; + struct PANEL_DESIGN_BLOCK_CHOOSER + { + int sash_pos_h; + int sash_pos_v; + int width; + int height; + int sort_mode; + bool repeated_placement; + bool place_as_sheet; + bool keep_annotations; + }; + struct DIALOG_IMPORT_GRAPHICS { bool interactive_placement; @@ -359,6 +376,8 @@ public: PANEL_SYM_CHOOSER m_SymChooserPanel; + PANEL_DESIGN_BLOCK_CHOOSER m_DesignBlockChooserPanel; + DIALOG_IMPORT_GRAPHICS m_ImportGraphics; SELECTION m_Selection; diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index fc20503741..03d1a2534f 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -614,93 +614,6 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in } -bool SCH_EDIT_FRAME::AppendSchematic() -{ - SCH_SCREEN* screen = GetScreen(); - - wxCHECK( screen, false ); - - // open file chooser dialog - wxString path = wxPathOnly( Prj().GetProjectFullName() ); - - wxFileDialog dlg( this, _( "Insert Schematic" ), path, wxEmptyString, - FILEEXT::KiCadSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST ); - - if( dlg.ShowModal() == wxID_CANCEL ) - return false; - - return AddSheetAndUpdateDisplay( dlg.GetPath() ); -} - - -bool SCH_EDIT_FRAME::AddSheetAndUpdateDisplay( const wxString aFullFileName ) -{ - SCH_COMMIT commit( m_toolManager ); - EE_SELECTION_TOOL* selectionTool = m_toolManager->GetTool<EE_SELECTION_TOOL>(); - - selectionTool->ClearSelection(); - - // Mark all existing items on the screen so we don't select them after appending - for( EDA_ITEM* item : GetScreen()->Items() ) - item->SetFlags( SKIP_STRUCT ); - - if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), aFullFileName ) ) - return false; - - initScreenZoom(); - SetSheetNumberAndCount(); - - SyncView(); - OnModify(); - HardRedraw(); // Full reinit of the current screen and the display. - bool selected = false; - - // Select all new items - for( EDA_ITEM* item : GetScreen()->Items() ) - { - if( !item->HasFlag( SKIP_STRUCT ) ) - { - commit.Added( item, GetScreen() ); - selectionTool->AddItemToSel( item, true ); - selected = true; - - if( item->Type() == SCH_LINE_T ) - item->SetFlags( STARTPOINT | ENDPOINT ); - } - else - item->ClearFlags( SKIP_STRUCT ); - } - - if( selected ) - m_toolManager->ProcessEvent( EVENTS::SelectedEvent ); - - // Start moving selection, cancel undoes the insertion - if( !m_toolManager->RunSynchronousAction( EE_ACTIONS::move, &commit ) ) - commit.Revert(); - else - commit.Push( _( "Import Schematic Sheet Content..." ) ); - - UpdateHierarchyNavigator(); - - return true; -} - - -void SCH_EDIT_FRAME::OnAppendProject( wxCommandEvent& event ) -{ - if( GetScreen() && GetScreen()->IsModified() ) - { - wxString msg = _( "This operation cannot be undone.\n\n" - "Do you want to save the current document before proceeding?" ); - - if( IsOK( this, msg ) ) - SaveProject(); - } - - AppendSchematic(); -} - - void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent ) { if( Schematic().RootScreen() && !Schematic().RootScreen()->Items().empty() ) @@ -840,6 +753,19 @@ bool SCH_EDIT_FRAME::saveSchematicFile( SCH_SHEET* aSheet, const wxString& aSave // Write through symlinks, don't replace them WX_FILENAME::ResolvePossibleSymlinks( schematicFileName ); + if( !schematicFileName.DirExists() ) + { + if( !wxMkdir( schematicFileName.GetPath() ) ) + { + msg.Printf( _( "Error saving schematic file '%s'.\n%s" ), + schematicFileName.GetFullPath(), + "Could not create directory: %s" + schematicFileName.GetPath() ); + DisplayError( this, msg ); + + return false; + } + } + if( !IsWritable( schematicFileName ) ) return false; diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp index dbf7f98277..f86e7beb51 100644 --- a/eeschema/menubar.cpp +++ b/eeschema/menubar.cpp @@ -94,13 +94,6 @@ void SCH_EDIT_FRAME::doReCreateMenuBar() fileMenu->AppendSeparator(); - fileMenu->Add( _( "Insert Schematic Sheet Content..." ), - _( "Append schematic sheet content from another project to the current sheet" ), - ID_APPEND_PROJECT, - BITMAPS::add_document ); - - fileMenu->AppendSeparator(); - // Import submenu ACTION_MENU* submenuImport = new ACTION_MENU( false, selTool ); submenuImport->SetTitle( _( "Import" ) ); @@ -207,6 +200,9 @@ void SCH_EDIT_FRAME::doReCreateMenuBar() if( ADVANCED_CFG::GetCfg().m_IncrementalConnectivity ) viewMenu->Add( EE_ACTIONS::showNetNavigator, ACTION_MENU::CHECK ); + if( ADVANCED_CFG::GetCfg().m_EnableDesignBlocks ) + viewMenu->Add( ACTIONS::showLibraryTree, ACTION_MENU::CHECK, _( "Show Design Blocks" ) ); + viewMenu->AppendSeparator(); viewMenu->Add( EE_ACTIONS::navigateBack ); viewMenu->Add( EE_ACTIONS::navigateUp ); @@ -258,6 +254,7 @@ void SCH_EDIT_FRAME::doReCreateMenuBar() placeMenu->Add( EE_ACTIONS::drawSheet ); placeMenu->Add( EE_ACTIONS::placeSheetPin ); placeMenu->Add( EE_ACTIONS::syncAllSheetsPins ); + placeMenu->Add( EE_ACTIONS::importSheet ); placeMenu->AppendSeparator(); placeMenu->Add( EE_ACTIONS::placeSchematicText ); diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp index 4481411f15..f56164045b 100644 --- a/eeschema/sch_base_frame.cpp +++ b/eeschema/sch_base_frame.cpp @@ -40,8 +40,10 @@ #include <preview_items/selection_area.h> #include <project_sch.h> #include <symbol_library.h> -#include <sch_base_frame.h> #include <symbol_lib_table.h> +#include <sch_base_frame.h> +#include <design_block.h> +#include <design_block_lib_table.h> #include <tool/action_toolbar.h> #include <tool/tool_manager.h> #include <tool/tool_dispatcher.h> @@ -55,6 +57,7 @@ #include <navlib/nl_schematic_plugin.h> + LIB_SYMBOL* SchGetLibSymbol( const LIB_ID& aLibId, SYMBOL_LIB_TABLE* aLibTable, SYMBOL_LIB* aCacheLib, wxWindow* aParent, bool aShowErrorMsg ) { diff --git a/eeschema/sch_base_frame.h b/eeschema/sch_base_frame.h index 5fc4a3f6e0..28f10d0dfb 100644 --- a/eeschema/sch_base_frame.h +++ b/eeschema/sch_base_frame.h @@ -146,6 +146,7 @@ public: void UpdateStatusBar() override; + /** * Call the library viewer to select symbol to import into schematic. * If the library viewer is currently running, it is closed and reopened in modal mode. diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp index 395894abf6..c635caae1f 100644 --- a/eeschema/sch_edit_frame.cpp +++ b/eeschema/sch_edit_frame.cpp @@ -34,6 +34,7 @@ #include <dialogs/dialog_schematic_find.h> #include <dialogs/dialog_book_reporter.h> #include <dialogs/dialog_symbol_fields_table.h> +#include <widgets/design_block_pane.h> #include <eeschema_id.h> #include <executable_names.h> #include <gal/graphics_abstraction_layer.h> @@ -77,6 +78,7 @@ #include <tools/ee_grid_helper.h> #include <tools/ee_inspection_tool.h> #include <tools/ee_point_editor.h> +#include <tools/sch_design_block_control.h> #include <tools/sch_drawing_tools.h> #include <tools/sch_edit_tool.h> #include <tools/sch_edit_table_tool.h> @@ -119,7 +121,6 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, SCH_BASE_FRAME ) EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, SCH_EDIT_FRAME::OnLoadFile ) EVT_MENU( ID_FILE_LIST_CLEAR, SCH_EDIT_FRAME::OnClearFileHistory ) - EVT_MENU( ID_APPEND_PROJECT, SCH_EDIT_FRAME::OnAppendProject ) EVT_MENU( ID_IMPORT_NON_KICAD_SCH, SCH_EDIT_FRAME::OnImportProject ) EVT_MENU( wxID_EXIT, SCH_EDIT_FRAME::OnExit ) @@ -141,7 +142,8 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_diffSymbolDialog( nullptr ), m_symbolFieldsTableDialog( nullptr ), m_netNavigator( nullptr ), - m_highlightedConnChanged( false ) + m_highlightedConnChanged( false ), + m_designBlocksPane( nullptr ) { m_maximizeByDefault = true; m_schematic = new SCHEMATIC( nullptr ); @@ -208,6 +210,7 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_propertiesPanel->SetSplitterProportion( eeconfig()->m_AuiPanels.properties_splitter ); m_selectionFilterPanel = new PANEL_SCH_SELECTION_FILTER( this ); + m_designBlocksPane = new DESIGN_BLOCK_PANE( this, nullptr, m_designBlockHistoryList ); m_auimgr.SetManagedWindow( this ); @@ -236,6 +239,8 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_auimgr.AddPane( m_propertiesPanel, defaultPropertiesPaneInfo( this ) ); m_auimgr.AddPane( m_selectionFilterPanel, defaultSchSelectionFilterPaneInfo( this ) ); + m_auimgr.AddPane( m_designBlocksPane, defaultDesignBlocksPaneInfo( this ) ); + m_auimgr.AddPane( createHighlightedNetNavigator(), defaultNetNavigatorPaneInfo() ); m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( wxS( "OptToolbar" ) ) @@ -274,11 +279,13 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : wxAuiPaneInfo& netNavigatorPane = m_auimgr.GetPane( NetNavigatorPaneName() ); wxAuiPaneInfo& propertiesPane = m_auimgr.GetPane( PropertiesPaneName() ); wxAuiPaneInfo& selectionFilterPane = m_auimgr.GetPane( wxS( "SelectionFilter" ) ); + wxAuiPaneInfo& designBlocksPane = m_auimgr.GetPane( DesignBlocksPaneName() ); EESCHEMA_SETTINGS* cfg = eeconfig(); hierarchy_pane.Show( cfg->m_AuiPanels.show_schematic_hierarchy ); netNavigatorPane.Show( cfg->m_AuiPanels.show_net_nav_panel ); propertiesPane.Show( cfg->m_AuiPanels.show_properties ); + designBlocksPane.Show( cfg->m_AuiPanels.design_blocks_show ); updateSelectionFilterVisbility(); // The selection filter doesn't need to grow in the vertical direction when docked @@ -326,6 +333,10 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : if( cfg->m_AuiPanels.float_net_nav_panel ) netNavigatorPane.Float(); + if( cfg->m_AuiPanels.design_blocks_show ) + SetAuiPaneSize( m_auimgr, designBlocksPane, + cfg->m_AuiPanels.design_blocks_panel_docked_width, -1 ); + if( cfg->m_AuiPanels.hierarchy_panel_docked_width > 0 ) { // If the net navigator is not show, let the hierarchy navigator take all of the vertical @@ -522,6 +533,7 @@ void SCH_EDIT_FRAME::setupTools() m_toolManager->RegisterTool( new SCH_EDIT_TOOL ); m_toolManager->RegisterTool( new SCH_EDIT_TABLE_TOOL ); m_toolManager->RegisterTool( new EE_INSPECTION_TOOL ); + m_toolManager->RegisterTool( new SCH_DESIGN_BLOCK_CONTROL ); m_toolManager->RegisterTool( new SCH_EDITOR_CONTROL ); m_toolManager->RegisterTool( new SCH_FIND_REPLACE_TOOL ); m_toolManager->RegisterTool( new EE_POINT_EDITOR ); @@ -577,6 +589,12 @@ void SCH_EDIT_FRAME::setupUIConditions() return m_auimgr.GetPane( NetNavigatorPaneName() ).IsShown(); }; + auto designBlockCond = + [ this ] (const SELECTION& aSel ) + { + return m_auimgr.GetPane( DesignBlocksPaneName() ).IsShown(); + }; + auto undoCond = [ this ] (const SELECTION& aSel ) { @@ -597,6 +615,7 @@ void SCH_EDIT_FRAME::setupUIConditions() mgr->SetConditions( EE_ACTIONS::showHierarchy, CHECK( hierarchyNavigatorCond ) ); mgr->SetConditions( EE_ACTIONS::showNetNavigator, CHECK( netNavigatorCond ) ); mgr->SetConditions( ACTIONS::showProperties, CHECK( propertiesCond ) ); + mgr->SetConditions( ACTIONS::showLibraryTree, CHECK( designBlockCond ) ); mgr->SetConditions( ACTIONS::toggleGrid, CHECK( cond.GridVisible() ) ); mgr->SetConditions( ACTIONS::toggleGridOverrides, CHECK( cond.GridOverrides() ) ); mgr->SetConditions( ACTIONS::toggleCursorStyle, CHECK( cond.FullscreenCursor() ) ); @@ -745,12 +764,16 @@ void SCH_EDIT_FRAME::setupUIConditions() mgr->SetConditions( EE_ACTIONS::toggleAnnotateAuto, CHECK( showAnnotateAutomaticallyCond ) ); mgr->SetConditions( ACTIONS::toggleBoundingBoxes, CHECK( cond.BoundingBoxes() ) ); + mgr->SetConditions( EE_ACTIONS::saveSheetAsDesignBlock, ENABLE( hasElements ) ); + mgr->SetConditions( EE_ACTIONS::saveSelectionAsDesignBlock, ENABLE( SELECTION_CONDITIONS::NotEmpty ) ); + #define CURRENT_TOOL( action ) mgr->SetConditions( action, CHECK( cond.CurrentTool( action ) ) ) CURRENT_TOOL( ACTIONS::deleteTool ); CURRENT_TOOL( EE_ACTIONS::highlightNetTool ); CURRENT_TOOL( EE_ACTIONS::placeSymbol ); CURRENT_TOOL( EE_ACTIONS::placePower ); + CURRENT_TOOL( EE_ACTIONS::placeDesignBlock ); CURRENT_TOOL( EE_ACTIONS::drawWire ); CURRENT_TOOL( EE_ACTIONS::drawBus ); CURRENT_TOOL( EE_ACTIONS::placeBusWireEntry ); @@ -764,6 +787,7 @@ void SCH_EDIT_FRAME::setupUIConditions() CURRENT_TOOL( EE_ACTIONS::drawSheet ); CURRENT_TOOL( EE_ACTIONS::placeSheetPin ); CURRENT_TOOL( EE_ACTIONS::syncSheetPins ); + CURRENT_TOOL( EE_ACTIONS::drawSheetCopy ); CURRENT_TOOL( EE_ACTIONS::drawRectangle ); CURRENT_TOOL( EE_ACTIONS::drawCircle ); CURRENT_TOOL( EE_ACTIONS::drawArc ); diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index a08204bb70..a139b3d3c8 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -56,6 +56,8 @@ class SCH_FIELD; class SCH_JUNCTION; class SCHEMATIC; class SCH_COMMIT; +class DESIGN_BLOCK; +class DESIGN_BLOCK_PANE; class DIALOG_BOOK_REPORTER; class DIALOG_ERC; class DIALOG_SYMBOL_FIELDS_TABLE; @@ -262,6 +264,11 @@ public: */ void OnFindDialogClose(); + /** + * Design block panel options have changed and the panel needs to be refreshed. + */ + void UpdateDesignBlockOptions(); + /** * Break a single segment into two at the specified point. * @param aCommit Transaction container used to record changes for undo/redo @@ -484,23 +491,6 @@ public: wxString GetCurrentFileName() const override; - /** - * Import a KiCad schematic into the current sheet. - * - * @return True if the schematic was imported properly. - */ - bool AppendSchematic(); - - /** - * Add a sheet file into the current sheet and updates display - * - * @note Used in AppendSchematic() and SCH_EDIT_TOOL::ddAppendFile() (so it is public) - * - * @param aFullFileName Path and name of sheet - * @return True if the sheet was properly added - */ - bool AddSheetAndUpdateDisplay( const wxString aFullFileName ); - /** * Check if any of the screens has unsaved changes and asks the user whether to save or * drop them. @@ -636,11 +626,16 @@ public: * possible file recursion issues. * @param aFileName is the file name to load. The file name is expected to have an absolute * path. - * + * @param aSkipRecursionCheck is true to skip the recursion check. This is used when loading + * a schematic sheet that is not part of the current project. If we are placing + * sheet contents instead of a sheet, then we do not need to check for recursion. + * @param aSkipLibCheck is true to skip the new/duplicate lib check. This is always triggered when + * placing design blocks so it is not necessary to check for new/duplicate libs. * @return True if the schematic was imported properly. */ bool LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurrentSheet, - const wxString& aFileName ); + const wxString& aFileName, bool aSkipRecursionCheck = false, + bool aSkipLibCheck = false ); /** * Removes a given junction and heals any wire segments under the junction @@ -747,6 +742,51 @@ public: */ bool CreateArchiveLibrary( const wxString& aFileName ); + /** + * If a library name is given, creates a new design block library in the project folder + * with the given name. If no library name is given it prompts user for a library path, + * then creates a new design block library at that location. + * If library exists, user is warned about that, and is given a chance + * to abort the new creation, and in that case existing library is first deleted. + * + * @param aProposedName is the initial path and filename shown in the file chooser dialog. + * @return The newly created library path if library was successfully created, else + * wxEmptyString because user aborted or error. + */ + wxString CreateNewDesignBlockLibrary( const wxString& aLibName = wxEmptyString, + const wxString& aProposedName = wxEmptyString ); + + /** + * Add an existing library to either the global or project library table. + * + * @param aFileName the library to add; a file open dialog will be displayed if empty. + * @return true if successfully added. + */ + bool AddDesignBlockLibrary( const wxString& aFilename, DESIGN_BLOCK_LIB_TABLE* aTable ); + + void SaveSheetAsDesignBlock( const wxString& aLibraryName ); + + void SaveSelectionAsDesignBlock( const wxString& aLibraryName ); + + bool DeleteDesignBlockLibrary( const wxString& aLibName, bool aConfirm ); + + bool DeleteDesignBlockFromLibrary( const LIB_ID& aLibId, bool aConfirm ); + + + /** + * Load design block from design block library table. + * + * @param aLibId is the design block library identifier to load. + * @param aUseCacheLib set to true to fall back to cache library if design block is not found in + * design block library table. + * @param aShowErrorMessage set to true to show any error messages. + * @return The design block found in the library or NULL if the design block was not found. + */ + DESIGN_BLOCK* GetDesignBlock( const LIB_ID& aLibId, bool aUseCacheLib = false, + bool aShowErrorMsg = false ); + + DESIGN_BLOCK_PANE* GetDesignBlockPane() const { return m_designBlocksPane; } + /** * Plot or print the current sheet to the clipboard. */ @@ -840,6 +880,8 @@ public: void ToggleProperties() override; + void ToggleLibraryTree() override; + DIALOG_BOOK_REPORTER* GetSymbolDiffDialog(); DIALOG_ERC* GetErcDialog(); @@ -934,6 +976,20 @@ protected: void onPluginAvailabilityChanged( wxCommandEvent& aEvt ); #endif + /** + * Prompts a user to select global or project library tables + * + * @return Pointer to library table selected or nullptr if none selected/canceled + */ + DESIGN_BLOCK_LIB_TABLE* selectDesignBlockLibTable( bool aOptional = false ); + + /** + * Create a new library in the given table (presumed to be either the global or project + * library table). + */ + wxString createNewDesignBlockLibrary( const wxString& aLibName, const wxString& aProposedName, + DESIGN_BLOCK_LIB_TABLE* aTable ); + private: // Called when resizing the Hierarchy Navigator panel void OnResizeHierarchyNavigator( wxSizeEvent& aEvent ); @@ -946,7 +1002,6 @@ private: void OnExit( wxCommandEvent& event ); void OnLoadFile( wxCommandEvent& event ); - void OnAppendProject( wxCommandEvent& event ); void OnImportProject( wxCommandEvent& event ); void OnClearFileHistory( wxCommandEvent& aEvent ); @@ -1048,6 +1103,10 @@ private: std::vector<wxEvtHandler*> m_schematicChangeListeners; + std::vector<LIB_ID> m_designBlockHistoryList; + + DESIGN_BLOCK_PANE* m_designBlocksPane; + #ifdef KICAD_IPC_API std::unique_ptr<API_HANDLER_SCH> m_apiHandler; #endif diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index e3e3aa6f88..6577eabcda 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -2212,6 +2212,9 @@ wxString SCH_PAINTER::expandLibItemTextVars( const wxString& aSourceText, std::function<bool( wxString* )> symbolResolver = [&]( wxString* token ) -> bool { + if( !m_schematic ) + return false; + return aSymbolContext->ResolveTextVar( &m_schematic->CurrentSheet(), token ); }; @@ -2244,7 +2247,7 @@ void SCH_PAINTER::draw( const SCH_SYMBOL* aSymbol, int aLayer ) // return; } - int unit = aSymbol->GetUnitSelection( &m_schematic->CurrentSheet() ); + int unit = m_schematic ? aSymbol->GetUnitSelection( &m_schematic->CurrentSheet() ) : 1; int bodyStyle = aSymbol->GetBodyStyle(); // Use dummy symbol if the actual couldn't be found (or couldn't be locked). diff --git a/eeschema/sheet.cpp b/eeschema/sheet.cpp index 233509d9b7..3caf1a9a90 100644 --- a/eeschema/sheet.cpp +++ b/eeschema/sheet.cpp @@ -164,7 +164,8 @@ void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurrentSheet, - const wxString& aFileName ) + const wxString& aFileName, bool aSkipRecursionCheck, + bool aSkipLibCheck ) { wxASSERT( aSheet && aCurrentSheet ); @@ -235,20 +236,18 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr // If the loaded schematic is in a different folder from the current project and // it contains hierarchical sheets, the hierarchical sheet paths need to be updated. - if( fileName.GetPathWithSep() != Prj().GetProjectPath() && tmpSheet->CountSheets() ) + // + // Additionally, we need to make all backing screens absolute paths be in the current project + // path not the source path. + if( fileName.GetPathWithSep() != Prj().GetProjectPath() ) { SCH_SHEET_LIST loadedSheets( tmpSheet.get() ); for( const SCH_SHEET_PATH& sheetPath : loadedSheets ) { - // Skip the loaded sheet since the user already determined if the file path should - // be relative or absolute. - if( sheetPath.size() == 1 ) - continue; + wxString lastSheetPath = Prj().GetProjectPath(); - wxString lastSheetPath = fileName.GetPathWithSep(); - - for( unsigned i = 1; i < sheetPath.size(); i++ ) + for( unsigned i = 0; i < sheetPath.size(); i++ ) { SCH_SHEET* sheet = sheetPath.at( i ); wxCHECK2( sheet, continue ); @@ -256,6 +255,14 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr SCH_SCREEN* screen = sheet->GetScreen(); wxCHECK2( screen, continue ); + // Fix screen path to be based on the current project path. + // Basically, make an absolute screen path relative to the schematic file + // we started with, then make it absolute again using the current project path. + wxFileName screenFileName = screen->GetFileName(); + screenFileName.MakeRelativeTo( fileName.GetPath() ); + screenFileName.MakeAbsolute( Prj().GetProjectPath() ); + screen->SetFileName( screenFileName.GetFullPath() ); + // Use the screen file name which should always be absolute. wxFileName loadedSheetFileName = screen->GetFileName(); wxCHECK2( loadedSheetFileName.IsAbsolute(), continue ); @@ -268,7 +275,6 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr else sheetFileName = loadedSheetFileName.GetFullPath(); - sheetFileName.Replace( wxT( "\\" ), wxT( "/" ) ); sheet->SetFileName( sheetFileName ); lastSheetPath = loadedSheetFileName.GetPath(); } @@ -279,11 +285,11 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr SCH_SHEET_LIST schematicSheets = Schematic().BuildUnorderedSheetList(); // Make sure any new sheet changes do not cause any recursion issues. - if( CheckSheetForRecursion( tmpSheet.get(), aCurrentSheet ) - || checkForNoFullyDefinedLibIds( tmpSheet.get() ) ) - { + if( !aSkipRecursionCheck && CheckSheetForRecursion( tmpSheet.get(), aCurrentSheet ) ) + return false; + + if( checkForNoFullyDefinedLibIds( tmpSheet.get() ) ) return false; - } // Make a valiant attempt to warn the user of all possible scenarios where there could // be broken symbol library links. @@ -372,7 +378,7 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr // If there are any new or duplicate libraries, check to see if it's possible that // there could be any missing libraries that would cause broken symbol library links. - if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() ) + if( !aSkipLibCheck && ( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() ) ) { if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() ) { @@ -381,8 +387,8 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr "incorrect symbol library references.\n\n" "Do you wish to continue?" ); wxMessageDialog msgDlg4( this, msg, _( "Continue Load Schematic" ), - wxOK | wxCANCEL | wxCANCEL_DEFAULT | - wxCENTER | wxICON_QUESTION ); + wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxCENTER + | wxICON_QUESTION ); msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel ); if( msgDlg4.ShowModal() == wxID_CANCEL ) diff --git a/eeschema/symbol_tree_model_adapter.h b/eeschema/symbol_tree_model_adapter.h index 92f63bd1e5..037f60ce47 100644 --- a/eeschema/symbol_tree_model_adapter.h +++ b/eeschema/symbol_tree_model_adapter.h @@ -64,7 +64,7 @@ protected: */ SYMBOL_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs ); - bool isSymbolModel() override { return true; } + PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::SYMBOL_LIB; } private: friend class SYMBOL_ASYNC_LOADER; diff --git a/eeschema/symbol_tree_synchronizing_adapter.h b/eeschema/symbol_tree_synchronizing_adapter.h index 72313d574e..3884fe8aac 100644 --- a/eeschema/symbol_tree_synchronizing_adapter.h +++ b/eeschema/symbol_tree_synchronizing_adapter.h @@ -66,7 +66,7 @@ protected: SYMBOL_TREE_SYNCHRONIZING_ADAPTER( SYMBOL_EDIT_FRAME* aParent, SYMBOL_LIBRARY_MANAGER* aLibMgr ); - bool isSymbolModel() override { return true; } + PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::SYMBOL_LIB; } protected: SYMBOL_EDIT_FRAME* m_frame; diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp index 9220424bb6..e83919f161 100644 --- a/eeschema/toolbars_sch_editor.cpp +++ b/eeschema/toolbars_sch_editor.cpp @@ -37,6 +37,7 @@ #include <tool/action_toolbar.h> #include <tools/ee_actions.h> #include <tools/ee_selection_tool.h> +#include <widgets/design_block_pane.h> #include <widgets/hierarchy_pane.h> #include <widgets/wx_aui_utils.h> #include <widgets/sch_properties_panel.h> @@ -355,3 +356,45 @@ void SCH_EDIT_FRAME::ToggleSchematicHierarchy() m_auimgr.Update(); } } + + +void SCH_EDIT_FRAME::ToggleLibraryTree() +{ + EESCHEMA_SETTINGS* cfg = eeconfig(); + + wxCHECK( cfg, /* void */ ); + + wxAuiPaneInfo& db_library_pane = m_auimgr.GetPane( DesignBlocksPaneName() ); + + db_library_pane.Show( !db_library_pane.IsShown() ); + + if( db_library_pane.IsShown() ) + { + if( db_library_pane.IsFloating() ) + { + db_library_pane.FloatingSize( cfg->m_AuiPanels.design_blocks_panel_float_width, + cfg->m_AuiPanels.design_blocks_panel_float_height ); + m_auimgr.Update(); + } + else if( cfg->m_AuiPanels.design_blocks_panel_docked_width > 0 ) + { + // SetAuiPaneSize also updates m_auimgr + SetAuiPaneSize( m_auimgr, db_library_pane, + cfg->m_AuiPanels.design_blocks_panel_docked_width, -1 ); + } + } + else + { + if( db_library_pane.IsFloating() ) + { + cfg->m_AuiPanels.design_blocks_panel_float_width = db_library_pane.floating_size.x; + cfg->m_AuiPanels.design_blocks_panel_float_height = db_library_pane.floating_size.y; + } + else + { + cfg->m_AuiPanels.design_blocks_panel_docked_width = m_designBlocksPane->GetSize().x; + } + + m_auimgr.Update(); + } +} diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp index d3adc6828f..b2e50bb01f 100644 --- a/eeschema/tools/ee_actions.cpp +++ b/eeschema/tools/ee_actions.cpp @@ -149,6 +149,28 @@ TOOL_ACTION EE_ACTIONS::syncSelection( TOOL_ACTION_ARGS() .Name( "eeschema.InteractiveSelection.SyncSelection" ) .Scope( AS_GLOBAL ) ); +// SCH_DESIGN_BLOCK_CONTROL +TOOL_ACTION EE_ACTIONS::saveSheetAsDesignBlock( TOOL_ACTION_ARGS() + .Name( "eeschema.SchDesignBlockControl.saveSheetAsDesignBlock" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Save Sheet as Design Block..." ) ) + .Tooltip( _( "Create a new design block from the current sheet" ) ) + .Icon( BITMAPS::new_component ) ); + +TOOL_ACTION EE_ACTIONS::saveSelectionAsDesignBlock( TOOL_ACTION_ARGS() + .Name( "eeschema.SchDesignBlockControl.saveSelectionAsDesignBlock" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Save Selection as Design Block..." ) ) + .Tooltip( _( "Create a new design block from the current selection" ) ) + .Icon( BITMAPS::new_component ) ); + +TOOL_ACTION EE_ACTIONS::deleteDesignBlock( TOOL_ACTION_ARGS() + .Name( "eeschema.SchDesignBlockControl.saveDeleteDesignBlock" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Delete Design Block" ) ) + .Tooltip( _( "Remove the selected design block from its library" ) ) + .Icon( BITMAPS::trash ) ); + // SYMBOL_EDITOR_CONTROL // TOOL_ACTION EE_ACTIONS::saveLibraryAs( TOOL_ACTION_ARGS() @@ -399,6 +421,17 @@ TOOL_ACTION EE_ACTIONS::placePower( TOOL_ACTION_ARGS() .Flags( AF_ACTIVATE ) .Parameter<SCH_SYMBOL*>( nullptr ) ); +TOOL_ACTION EE_ACTIONS::placeDesignBlock( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.placeDesignBlock" ) + .Scope( AS_GLOBAL ) + .DefaultHotkey( MD_SHIFT + 'B' ) + .FriendlyName( _( "Place Design Block" ) ) + .Tooltip( _( "Add selected design block to current sheet" ) ) + .Icon( BITMAPS::add_component ) + .Flags( AF_ACTIVATE ) + .Parameter<DESIGN_BLOCK*>( nullptr ) ); + + TOOL_ACTION EE_ACTIONS::placeNoConnect( TOOL_ACTION_ARGS() .Name( "eeschema.InteractiveDrawing.placeNoConnect" ) .Scope( AS_GLOBAL ) @@ -464,6 +497,15 @@ TOOL_ACTION EE_ACTIONS::drawSheet( TOOL_ACTION_ARGS() .Flags( AF_ACTIVATE ) .Parameter( SCH_SHEET_T ) ); +TOOL_ACTION EE_ACTIONS::drawSheetCopy( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.drawSheetCopy" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Draw Sheet Copy" ) ) + .Tooltip( _( "Copy sheet into project and draw on current sheet" ) ) + .Icon( BITMAPS::add_hierarchical_subsheet ) + .Flags( AF_ACTIVATE ) + .Parameter<wxString*> ( nullptr ) ); + TOOL_ACTION EE_ACTIONS::placeSheetPin( TOOL_ACTION_ARGS() .Name( "eeschema.InteractiveDrawing.placeSheetPin" ) .Scope( AS_GLOBAL ) @@ -487,6 +529,15 @@ TOOL_ACTION EE_ACTIONS::syncAllSheetsPins( TOOL_ACTION_ARGS() .Icon( BITMAPS::import_hierarchical_label ) .Flags( AF_ACTIVATE ) ); +TOOL_ACTION EE_ACTIONS::importSheet( TOOL_ACTION_ARGS() + .Name( "eeschema.InteractiveDrawing.importSheet" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Import Sheet" ) ) + .Tooltip( _( "Import sheet into project" ) ) + .Icon( BITMAPS::add_hierarchical_subsheet ) + .Flags( AF_ACTIVATE ) + .Parameter<wxString*> ( nullptr ) ); + TOOL_ACTION EE_ACTIONS::placeGlobalLabel( TOOL_ACTION_ARGS() .Name( "eeschema.InteractiveDrawing.placeGlobalLabel" ) .Scope( AS_GLOBAL ) diff --git a/eeschema/tools/ee_actions.h b/eeschema/tools/ee_actions.h index 0ba2d4a6e1..3a256cc716 100644 --- a/eeschema/tools/ee_actions.h +++ b/eeschema/tools/ee_actions.h @@ -78,6 +78,7 @@ public: static TOOL_ACTION pickerTool; static TOOL_ACTION placeSymbol; static TOOL_ACTION placePower; + static TOOL_ACTION placeDesignBlock; static TOOL_ACTION drawWire; static TOOL_ACTION drawBus; static TOOL_ACTION unfoldBus; @@ -89,7 +90,9 @@ public: static TOOL_ACTION placeGlobalLabel; static TOOL_ACTION placeHierLabel; static TOOL_ACTION drawSheet; + static TOOL_ACTION drawSheetCopy; static TOOL_ACTION placeSheetPin; + static TOOL_ACTION importSheet; // Sync sheet pins for selected sheet symbol static TOOL_ACTION syncSheetPins; // Sync sheet pins for all sheet symbols @@ -197,6 +200,11 @@ public: static TOOL_ACTION unsetDNP; static TOOL_ACTION toggleDNP; + // Design Block management + static TOOL_ACTION saveSheetAsDesignBlock; + static TOOL_ACTION saveSelectionAsDesignBlock; + static TOOL_ACTION deleteDesignBlock; + // Library management static TOOL_ACTION saveLibraryAs; static TOOL_ACTION saveSymbolCopyAs; diff --git a/eeschema/tools/sch_design_block_control.cpp b/eeschema/tools/sch_design_block_control.cpp new file mode 100644 index 0000000000..af02729c93 --- /dev/null +++ b/eeschema/tools/sch_design_block_control.cpp @@ -0,0 +1,204 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include <tool/library_editor_control.h> +#include <wildcards_and_files_ext.h> +#include <bitmaps/bitmap_types.h> +#include <confirm.h> +#include <kidialog.h> +#include <gestfich.h> // To open with a text editor +#include <wx/filedlg.h> +#include <sch_design_block_control.h> +#include <design_block_pane.h> +#include <panel_design_block_chooser.h> +#include <ee_actions.h> + +bool SCH_DESIGN_BLOCK_CONTROL::Init() +{ + m_editFrame = getEditFrame<SCH_EDIT_FRAME>(); + m_frame = m_editFrame; + m_selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>(); + + auto pinnedLib = + [this]( const SELECTION& aSel ) + { + // + LIB_TREE_NODE* current = getCurrentTreeNode(); + return current && current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY + && current->m_Pinned; + }; + auto unpinnedLib = + [this](const SELECTION& aSel ) + { + LIB_TREE_NODE* current = getCurrentTreeNode(); + return current && current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY + && !current->m_Pinned; + }; + + auto isInLibrary = + [this](const SELECTION& aSel ) + { + LIB_TREE_NODE* current = getCurrentTreeNode(); + return current + && ( current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY + || current->m_Type == LIB_TREE_NODE::TYPE::ITEM ); + }; + + auto isDesignBlock = + [this](const SELECTION& aSel ) + { + LIB_TREE_NODE* current = getCurrentTreeNode(); + return current && current->m_Type == LIB_TREE_NODE::TYPE::ITEM; + }; + + CONDITIONAL_MENU& ctxMenu = m_menu->GetMenu(); + + ctxMenu.AddItem( EE_ACTIONS::placeDesignBlock, isDesignBlock, 1); + ctxMenu.AddItem( ACTIONS::pinLibrary, unpinnedLib, 1 ); + ctxMenu.AddItem( ACTIONS::unpinLibrary, pinnedLib, 1 ); + ctxMenu.AddSeparator( 1 ); + + ctxMenu.AddItem( ACTIONS::newLibrary, SELECTION_CONDITIONS::ShowAlways, 10 ); + ctxMenu.AddItem( EE_ACTIONS::saveSheetAsDesignBlock, isInLibrary, 10 ); + ctxMenu.AddItem( EE_ACTIONS::saveSelectionAsDesignBlock, isInLibrary, 10 ); + ctxMenu.AddItem( EE_ACTIONS::deleteDesignBlock, isDesignBlock, 10 ); + + ctxMenu.AddSeparator( 400 ); + ctxMenu.AddItem( ACTIONS::hideLibraryTree, SELECTION_CONDITIONS::ShowAlways, 400 ); + + return true; +} + + +int SCH_DESIGN_BLOCK_CONTROL::PinLibrary( const TOOL_EVENT& aEvent ) +{ + LIB_TREE_NODE* current = getCurrentTreeNode(); + + if( current && !current->m_Pinned ) + { + m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(), + PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB ); + current->m_Pinned = true; + getDesignBlockPane()->RefreshLibs(); + } + + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::UnpinLibrary( const TOOL_EVENT& aEvent ) +{ + LIB_TREE_NODE* current = getCurrentTreeNode(); + + if( current && current->m_Pinned ) + { + m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(), + PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB ); + current->m_Pinned = false; + getDesignBlockPane()->RefreshLibs(); + } + + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::NewLibrary( const TOOL_EVENT& aEvent ) +{ + m_editFrame->CreateNewDesignBlockLibrary(); + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::SaveSheetAsDesignBlock( const TOOL_EVENT& aEvent ) +{ + LIB_TREE_NODE* current = getCurrentTreeNode(); + + if( current ) + m_editFrame->SaveSheetAsDesignBlock( current->m_LibId.GetLibNickname() ); + + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::SaveSelectionAsDesignBlock( const TOOL_EVENT& aEvent ) +{ + LIB_TREE_NODE* current = getCurrentTreeNode(); + + if( current ) + m_editFrame->SaveSelectionAsDesignBlock( current->m_LibId.GetLibNickname() ); + + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::DeleteDesignBlock( const TOOL_EVENT& aEvent ) +{ + LIB_TREE_NODE* current = getCurrentTreeNode(); + + if( current ) + m_editFrame->DeleteDesignBlockFromLibrary( current->m_LibId, true ); + + return 0; +} + + +int SCH_DESIGN_BLOCK_CONTROL::HideLibraryTree( const TOOL_EVENT& aEvent ) +{ + m_editFrame->ToggleLibraryTree(); + return 0; +} + + +void SCH_DESIGN_BLOCK_CONTROL::setTransitions() +{ + Go( &SCH_DESIGN_BLOCK_CONTROL::PinLibrary, ACTIONS::pinLibrary.MakeEvent() ); + Go( &SCH_DESIGN_BLOCK_CONTROL::UnpinLibrary, ACTIONS::unpinLibrary.MakeEvent() ); + + Go( &SCH_DESIGN_BLOCK_CONTROL::NewLibrary, ACTIONS::newLibrary.MakeEvent() ); + + Go( &SCH_DESIGN_BLOCK_CONTROL::SaveSheetAsDesignBlock, EE_ACTIONS::saveSheetAsDesignBlock.MakeEvent() ); + Go( &SCH_DESIGN_BLOCK_CONTROL::SaveSelectionAsDesignBlock, EE_ACTIONS::saveSelectionAsDesignBlock.MakeEvent() ); + Go( &SCH_DESIGN_BLOCK_CONTROL::DeleteDesignBlock, EE_ACTIONS::deleteDesignBlock.MakeEvent() ); + + Go( &SCH_DESIGN_BLOCK_CONTROL::HideLibraryTree, ACTIONS::hideLibraryTree.MakeEvent() ); +} + + +LIB_ID SCH_DESIGN_BLOCK_CONTROL::getSelectedLibId() +{ + m_editFrame->GetDesignBlockPane()->GetSelectedLibId(); + + return LIB_ID(); +} + + +DESIGN_BLOCK_PANE* SCH_DESIGN_BLOCK_CONTROL::getDesignBlockPane() +{ + return m_editFrame->GetDesignBlockPane(); +} + + +LIB_TREE_NODE* SCH_DESIGN_BLOCK_CONTROL::getCurrentTreeNode() +{ + LIB_TREE* libTree = getDesignBlockPane()->GetDesignBlockPanel()->GetLibTree(); + return libTree ? libTree->GetCurrentTreeNode() : nullptr; +} diff --git a/eeschema/tools/sch_design_block_control.h b/eeschema/tools/sch_design_block_control.h new file mode 100644 index 0000000000..1191b253ca --- /dev/null +++ b/eeschema/tools/sch_design_block_control.h @@ -0,0 +1,66 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2019-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#ifndef SCH_DESIGN_BLOCK_CONTROL_H +#define SCH_DESIGN_BLOCK_CONTROL_H + +#include <sch_base_frame.h> +#include <tools/ee_tool_base.h> + +/** + * Handle schematic design block actions in the schematic editor. + */ +class SCH_DESIGN_BLOCK_CONTROL : public wxEvtHandler, public EE_TOOL_BASE<SCH_BASE_FRAME> +{ +public: + SCH_DESIGN_BLOCK_CONTROL() : EE_TOOL_BASE<SCH_BASE_FRAME>( "eeschema.SchDesignBlockControl" ) {} + + /// @copydoc TOOL_INTERACTIVE::Init() + bool Init() override; + + int PinLibrary( const TOOL_EVENT& aEvent ); + int UnpinLibrary( const TOOL_EVENT& aEvent ); + + int NewLibrary( const TOOL_EVENT& aEvent ); + int DeleteLibrary( const TOOL_EVENT& aEvent ); + + int SaveSheetAsDesignBlock( const TOOL_EVENT& aEvent ); + int SaveSelectionAsDesignBlock( const TOOL_EVENT& aEvent ); + int DeleteDesignBlock( const TOOL_EVENT& aEvent ); + + int HideLibraryTree( const TOOL_EVENT& aEvent ); + +private: + LIB_ID getSelectedLibId(); + ///< Set up handlers for various events. + void setTransitions() override; + + DESIGN_BLOCK_PANE* getDesignBlockPane(); + LIB_TREE_NODE* getCurrentTreeNode(); + + SCH_EDIT_FRAME* m_editFrame = nullptr; +}; + + +#endif diff --git a/eeschema/tools/sch_drawing_tools.cpp b/eeschema/tools/sch_drawing_tools.cpp index d6756c8a3f..6725b3fba9 100644 --- a/eeschema/tools/sch_drawing_tools.cpp +++ b/eeschema/tools/sch_drawing_tools.cpp @@ -34,9 +34,12 @@ #include <tools/ee_grid_helper.h> #include <tools/rule_area_create_helper.h> #include <gal/graphics_abstraction_layer.h> +#include <design_block_lib_table.h> #include <ee_actions.h> #include <sch_edit_frame.h> #include <pgm_base.h> +#include <design_block.h> +#include <design_block_pane.h> #include <eeschema_id.h> #include <confirm.h> #include <view/view_controls.h> @@ -541,6 +544,268 @@ int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent ) } +int SCH_DRAWING_TOOLS::ImportSheet( const TOOL_EVENT& aEvent ) +{ + bool placingDesignBlock = aEvent.IsAction( &EE_ACTIONS::placeDesignBlock ); + DESIGN_BLOCK* designBlock = + placingDesignBlock && m_frame->GetDesignBlockPane()->GetSelectedLibId().IsValid() + ? m_frame->GetDesignBlock( m_frame->GetDesignBlockPane()->GetSelectedLibId() ) + : nullptr; + wxString* importSourceFile = !placingDesignBlock ? aEvent.Parameter<wxString*>() : nullptr; + wxString sheetFileName = wxEmptyString; + + if( !placingDesignBlock ) + { + if( importSourceFile != nullptr ) + sheetFileName = *importSourceFile; + } + else if( designBlock ) + sheetFileName = designBlock->GetSchematicFile(); + + COMMON_SETTINGS* common_settings = Pgm().GetCommonSettings(); + EESCHEMA_SETTINGS* cfg = m_frame->eeconfig(); + SCHEMATIC_SETTINGS& schSettings = m_frame->Schematic().Settings(); + SCH_SCREEN* screen = m_frame->GetScreen(); + SCH_SHEET_PATH& sheetPath = m_frame->GetCurrentSheet(); + + KIGFX::VIEW_CONTROLS* controls = getViewControls(); + EE_GRID_HELPER grid( m_toolMgr ); + VECTOR2I cursorPos; + + if( !cfg || !common_settings ) + return 0; + + if( m_inDrawingTool ) + return 0; + + auto setCursor = + [&]() + { + m_frame->GetCanvas()->SetCurrentCursor( designBlock ? KICURSOR::MOVING + : KICURSOR::COMPONENT ); + }; + + auto placeSheetContents = + [&]() + { + SCH_COMMIT commit( m_toolMgr ); + EE_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>(); + EDA_ITEMS newItems; + bool keepAnnotations = cfg->m_DesignBlockChooserPanel.keep_annotations; + + selectionTool->ClearSelection(); + + // Mark all existing items on the screen so we don't select them after appending + for( EDA_ITEM* item : screen->Items() ) + item->SetFlags( SKIP_STRUCT ); + + if( !m_frame->LoadSheetFromFile( sheetPath.Last(), &sheetPath, sheetFileName, true, + placingDesignBlock ) ) + return false; + + m_frame->SetSheetNumberAndCount(); + + m_frame->SyncView(); + m_frame->OnModify(); + m_frame->HardRedraw(); // Full reinit of the current screen and the display. + + // Select all new items + for( EDA_ITEM* item : screen->Items() ) + { + if( !item->HasFlag( SKIP_STRUCT ) ) + { + if( item->Type() == SCH_SYMBOL_T && !keepAnnotations ) + static_cast<SCH_SYMBOL*>( item )->ClearAnnotation( &sheetPath, false ); + + if( item->Type() == SCH_LINE_T ) + item->SetFlags( STARTPOINT | ENDPOINT ); + + commit.Added( item, screen ); + newItems.emplace_back( item ); + } + else + item->ClearFlags( SKIP_STRUCT ); + } + + selectionTool->AddItemsToSel( &newItems, true ); + + cursorPos = grid.Align( controls->GetMousePosition(), + grid.GetSelectionGrid( selectionTool->GetSelection() ) ); + controls->ForceCursorPosition( true, cursorPos ); + + // Move everything to our current mouse position now + // that we have a selection to get a reference point + VECTOR2I anchorPos = selectionTool->GetSelection().GetReferencePoint(); + VECTOR2I delta = cursorPos - anchorPos; + + // Will all be SCH_ITEMs as these were pulled from the screen->Items() + for( EDA_ITEM* item : newItems ) + static_cast<SCH_ITEM*>( item )->Move( delta ); + + if( !keepAnnotations ) + { + EESCHEMA_SETTINGS::PANEL_ANNOTATE& annotate = cfg->m_AnnotatePanel; + + if( annotate.automatic ) + { + NULL_REPORTER reporter; + m_frame->AnnotateSymbols( &commit, ANNOTATE_SELECTION, + (ANNOTATE_ORDER_T) annotate.sort_order, + (ANNOTATE_ALGO_T) annotate.method, true /* recursive */, + schSettings.m_AnnotateStartNum, false, false, reporter ); + } + + // Annotation will clear selection, so we need to restore it + for( EDA_ITEM* item : newItems ) + { + if( item->Type() == SCH_LINE_T ) + item->SetFlags( STARTPOINT | ENDPOINT ); + } + + selectionTool->AddItemsToSel( &newItems, true ); + } + + // Start moving selection, cancel undoes the insertion + bool placed = m_toolMgr->RunSynchronousAction( EE_ACTIONS::move, &commit ); + + // Update our cursor position to the new location in case we're placing repeated copies + cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE ); + + if( placed ) + commit.Push( placingDesignBlock ? _( "Add design block" ) + : _( "Import Schematic Sheet Content..." ) ); + else + commit.Revert(); + + m_frame->UpdateHierarchyNavigator(); + + return placed; + }; + + // Whether we are placing the sheet as a sheet, or as its contents, we need to get a filename + // if we weren't provided one + if( sheetFileName.IsEmpty() ) + { + wxString path; + wxString file; + + if (!placingDesignBlock) { + + if( sheetFileName.IsEmpty() ) + { + path = wxPathOnly( m_frame->Prj().GetProjectFullName() ); + file = wxEmptyString; + } + else + { + path = wxPathOnly( sheetFileName ); + file = wxFileName( sheetFileName ).GetFullName(); + } + + // Open file chooser dialog even if we have been provided a file so the user + // can select the options they want + wxFileDialog dlg( m_frame, _( "Choose Schematic" ), path, file, + FILEEXT::KiCadSchematicFileWildcard(), + wxFD_OPEN | wxFD_FILE_MUST_EXIST ); + + FILEDLG_IMPORT_SHEET_CONTENTS dlgHook( cfg ); + dlg.SetCustomizeHook( dlgHook ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return 0; + + sheetFileName = dlg.GetPath(); + + m_frame->UpdateDesignBlockOptions(); + } + + if( sheetFileName.IsEmpty() ) + return 0; + } + + // If we're placing sheet contents, we don't even want to run our tool loop, just add the items + // to the canvas and run the move tool + if( !cfg->m_DesignBlockChooserPanel.place_as_sheet ) + { + while( placeSheetContents() && cfg->m_DesignBlockChooserPanel.repeated_placement ) + ; + + m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); + m_view->ClearPreview(); + delete designBlock; + designBlock = nullptr; + + return 0; + } + + // We're placing a sheet as a sheet, we need to run a small tool loop to get the starting + // coordinate of the sheet drawing + m_frame->PushTool( aEvent ); + + Activate(); + + // Must be done after Activate() so that it gets set into the correct context + getViewControls()->ShowCursor( true ); + + // Set initial cursor + setCursor(); + + if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() ) + { + m_toolMgr->PrimeTool( { 0, 0 } ); + } + + // Main loop: keep receiving events + while( TOOL_EVENT* evt = Wait() ) + { + setCursor(); + grid.SetSnap( !evt->Modifier( MD_SHIFT ) ); + grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() ); + + cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE ); + controls->ForceCursorPosition( true, cursorPos ); + + // The tool hotkey is interpreted as a click when drawing + bool isSyntheticClick = designBlock && evt->IsActivate() && evt->HasPosition() + && evt->Matches( aEvent ); + + if( evt->IsCancelInteractive() || ( designBlock && evt->IsAction( &ACTIONS::undo ) ) ) + { + m_frame->GetInfoBar()->Dismiss(); + break; + } + else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick ) + { + wxString* tempFileName = new wxString( sheetFileName ); + m_toolMgr->PostAction( EE_ACTIONS::drawSheetCopy, tempFileName ); + break; + } + else if( evt->IsClick( BUT_RIGHT ) ) + { + // Warp after context menu only if dragging... + if( !designBlock ) + m_toolMgr->VetoContextMenuMouseWarp(); + + m_menu->ShowContextMenu( m_selectionTool->GetSelection() ); + } + else if( evt->IsAction( &ACTIONS::duplicate ) + || evt->IsAction( &EE_ACTIONS::repeatDrawItem ) ) + { + wxBell(); + } + else + { + evt->SetPassEvent(); + } + } + + m_frame->PopTool( aEvent ); + m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); + + return 0; +} + + int SCH_DRAWING_TOOLS::PlaceImage( const TOOL_EVENT& aEvent ) { SCH_BITMAP* image = aEvent.Parameter<SCH_BITMAP*>(); @@ -707,7 +972,7 @@ int SCH_DRAWING_TOOLS::PlaceImage( const TOOL_EVENT& aEvent ) if( !image || !image->ReadImageFile( fullFilename ) ) { - wxMessageBox( _( "Could not load image from '%s'." ), fullFilename ); + wxMessageBox( wxString::Format( _( "Could not load image from '%s'." ), fullFilename ) ); delete image; image = nullptr; continue; @@ -2452,7 +2717,12 @@ int SCH_DRAWING_TOOLS::DrawTable( const TOOL_EVENT& aEvent ) int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent ) { - SCH_SHEET* sheet = nullptr; + bool isDrawSheetCopy = aEvent.IsAction( &EE_ACTIONS::drawSheetCopy ); + wxString* filename = isDrawSheetCopy ? aEvent.Parameter<wxString*>() : nullptr; + SCH_SHEET* sheet = nullptr; + + // Make sure we've been passed a filename if we're importing a sheet + wxCHECK( !isDrawSheetCopy || filename, 0 ); if( m_inDrawingTool ) return 0; @@ -2490,7 +2760,7 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent ) // Set initial cursor setCursor(); - if( aEvent.HasPosition() ) + if( aEvent.HasPosition() && !isDrawSheetCopy ) m_toolMgr->PrimeTool( aEvent.Position() ); // Main loop: keep receiving events @@ -2575,16 +2845,57 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent ) m_toolMgr->RunAction( EE_ACTIONS::clearSelection ); - sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos ); + if( isDrawSheetCopy ) + { + if( !wxFileExists( *filename ) ) + { + wxMessageBox( wxString::Format( _( "File '%s' does not exist." ), *filename ) ); + m_frame->PopTool( aEvent ); + break; + } + + sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos ); + + if( !m_frame->LoadSheetFromFile( sheet, &m_frame->GetCurrentSheet(), *filename ) ) + { + wxMessageBox( wxString::Format( _( "Could not import sheet from '%s'." ), *filename ) ); + delete sheet; + sheet = nullptr; + m_frame->PopTool( aEvent ); + break; + } + + wxFileName fn( *filename ); + + sheet->GetFields()[SHEETNAME].SetText( wxT( "Imported Sheet" ) ); + sheet->GetFields()[SHEETFILENAME].SetText( fn.GetName() + wxT( "." ) + + FILEEXT::KiCadSchematicFileExtension ); + } + else + { + sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos ); + sheet->SetScreen( nullptr ); + sheet->GetFields()[SHEETNAME].SetText( wxT( "Untitled Sheet" ) ); + sheet->GetFields()[SHEETFILENAME].SetText( wxT( "untitled." ) + + FILEEXT::KiCadSchematicFileExtension ); + } + sheet->SetFlags( IS_NEW | IS_MOVING ); - sheet->SetScreen( nullptr ); sheet->SetBorderWidth( schIUScale.MilsToIU( cfg->m_Drawing.default_line_thickness ) ); sheet->SetBorderColor( cfg->m_Drawing.default_sheet_border_color ); sheet->SetBackgroundColor( cfg->m_Drawing.default_sheet_background_color ); - sheet->GetFields()[ SHEETNAME ].SetText( "Untitled Sheet" ); - sheet->GetFields()[ SHEETFILENAME ].SetText( "untitled." + FILEEXT::KiCadSchematicFileExtension ); sizeSheet( sheet, cursorPos ); + SCH_SHEET_LIST hierarchy = m_frame->Schematic().GetFullHierarchy(); + SCH_SHEET_PATH instance = m_frame->GetCurrentSheet(); + instance.push_back( sheet ); + wxString pageNumber; + + // Don't try to be too clever when assigning the next availabe page number. Just use + // the number of sheets plus one. + pageNumber.Printf( wxT( "%d" ), static_cast<int>( hierarchy.size() ) + 1 ); + instance.SetPageNumber( pageNumber ); + m_view->ClearPreview(); m_view->AddToPreview( sheet->Clone() ); } @@ -2603,9 +2914,12 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent ) sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false ); - SCH_COMMIT commit( m_toolMgr ); - commit.Add( sheet, m_frame->GetScreen() ); - commit.Push( "Draw Sheet" ); + // Use the commit we were provided or make our own + SCH_COMMIT tempCommit = SCH_COMMIT( m_toolMgr ); + SCH_COMMIT& c = evt->Commit() ? *( (SCH_COMMIT*) evt->Commit() ) : tempCommit; + + c.Add( sheet, m_frame->GetScreen() ); + c.Push( isDrawSheetCopy ? "Import Sheet Copy" : "Draw Sheet" ); SCH_SHEET_PATH newPath = m_frame->GetCurrentSheet(); newPath.push_back( sheet ); @@ -2669,6 +2983,9 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent ) getViewControls()->CaptureCursor( false ); m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW ); + // We own the string if we're importing a sheet + delete filename; + return 0; } @@ -2834,7 +3151,10 @@ void SCH_DRAWING_TOOLS::setTransitions() Go( &SCH_DRAWING_TOOLS::TwoClickPlace, EE_ACTIONS::placeHierLabel.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::TwoClickPlace, EE_ACTIONS::placeGlobalLabel.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawSheet, EE_ACTIONS::drawSheet.MakeEvent() ); + Go( &SCH_DRAWING_TOOLS::DrawSheet, EE_ACTIONS::drawSheetCopy.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::TwoClickPlace, EE_ACTIONS::placeSheetPin.MakeEvent() ); + Go( &SCH_DRAWING_TOOLS::ImportSheet, EE_ACTIONS::placeDesignBlock.MakeEvent() ); + Go( &SCH_DRAWING_TOOLS::ImportSheet, EE_ACTIONS::importSheet.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::TwoClickPlace, EE_ACTIONS::placeSchematicText.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawShape, EE_ACTIONS::drawRectangle.MakeEvent() ); Go( &SCH_DRAWING_TOOLS::DrawShape, EE_ACTIONS::drawCircle.MakeEvent() ); diff --git a/eeschema/tools/sch_drawing_tools.h b/eeschema/tools/sch_drawing_tools.h index 3b516361d4..5e099c3964 100644 --- a/eeschema/tools/sch_drawing_tools.h +++ b/eeschema/tools/sch_drawing_tools.h @@ -55,6 +55,7 @@ public: int PlaceSymbol( const TOOL_EVENT& aEvent ); int SingleClickPlace( const TOOL_EVENT& aEvent ); int TwoClickPlace( const TOOL_EVENT& aEvent ); + int ImportSheet( const TOOL_EVENT& aEvent ); int DrawShape( const TOOL_EVENT& aEvent ); int DrawRuleArea( const TOOL_EVENT& aEvent ); int DrawTable( const TOOL_EVENT& aEvent ); @@ -83,6 +84,7 @@ private: std::vector<PICKED_SYMBOL> m_symbolHistoryList; std::vector<PICKED_SYMBOL> m_powerHistoryList; + std::vector<LIB_ID> m_designBlockHistoryList; LABEL_FLAG_SHAPE m_lastSheetPinType; LABEL_FLAG_SHAPE m_lastGlobalLabelShape; diff --git a/eeschema/tools/sch_edit_tool.cpp b/eeschema/tools/sch_edit_tool.cpp index cbad1666f4..2830183eff 100644 --- a/eeschema/tools/sch_edit_tool.cpp +++ b/eeschema/tools/sch_edit_tool.cpp @@ -2924,8 +2924,7 @@ int SCH_EDIT_TOOL::EditPageNumber( const TOOL_EVENT& aEvent ) int SCH_EDIT_TOOL::DdAppendFile( const TOOL_EVENT& aEvent ) { - wxString aFileName = *aEvent.Parameter<wxString*>(); - return ( m_frame->AddSheetAndUpdateDisplay( aFileName ) ? 0 : 1 ); + return m_toolMgr->RunAction( EE_ACTIONS::importSheet, aEvent.Parameter<wxString*>() ); } diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp index 914600e59f..6152563e83 100644 --- a/eeschema/tools/sch_editor_control.cpp +++ b/eeschema/tools/sch_editor_control.cpp @@ -2434,6 +2434,13 @@ int SCH_EDITOR_CONTROL::ToggleProperties( const TOOL_EVENT& aEvent ) } +int SCH_EDITOR_CONTROL::ToggleLibraryTree( const TOOL_EVENT& aEvent ) +{ + getEditFrame<SCH_EDIT_FRAME>()->ToggleLibraryTree(); + return 0; +} + + int SCH_EDITOR_CONTROL::ToggleHiddenPins( const TOOL_EVENT& aEvent ) { EESCHEMA_SETTINGS* cfg = m_frame->eeconfig(); @@ -2808,6 +2815,8 @@ void SCH_EDITOR_CONTROL::setTransitions() Go( &SCH_EDITOR_CONTROL::ShowHierarchy, EE_ACTIONS::showHierarchy.MakeEvent() ); Go( &SCH_EDITOR_CONTROL::ShowNetNavigator, EE_ACTIONS::showNetNavigator.MakeEvent() ); Go( &SCH_EDITOR_CONTROL::ToggleProperties, ACTIONS::showProperties.MakeEvent() ); + Go( &SCH_EDITOR_CONTROL::ToggleLibraryTree, ACTIONS::hideLibraryTree.MakeEvent() ); + Go( &SCH_EDITOR_CONTROL::ToggleLibraryTree, ACTIONS::showLibraryTree.MakeEvent() ); Go( &SCH_EDITOR_CONTROL::ToggleHiddenPins, EE_ACTIONS::toggleHiddenPins.MakeEvent() ); Go( &SCH_EDITOR_CONTROL::ToggleHiddenFields, EE_ACTIONS::toggleHiddenFields.MakeEvent() ); diff --git a/eeschema/tools/sch_editor_control.h b/eeschema/tools/sch_editor_control.h index eb1d698890..d0a04cd0cb 100644 --- a/eeschema/tools/sch_editor_control.h +++ b/eeschema/tools/sch_editor_control.h @@ -130,6 +130,7 @@ public: int ShowHierarchy( const TOOL_EVENT& aEvent ); int ShowNetNavigator( const TOOL_EVENT& aEvent ); int ToggleProperties( const TOOL_EVENT& aEvent ); + int ToggleLibraryTree( const TOOL_EVENT& aEvent ); int ToggleHiddenPins( const TOOL_EVENT& aEvent ); int ToggleHiddenFields( const TOOL_EVENT& aEvent ); diff --git a/eeschema/widgets/design_block_pane.cpp b/eeschema/widgets/design_block_pane.cpp new file mode 100644 index 0000000000..962c8d50af --- /dev/null +++ b/eeschema/widgets/design_block_pane.cpp @@ -0,0 +1,157 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <design_block.h> +#include <widgets/design_block_pane.h> +#include <widgets/panel_design_block_chooser.h> +#include <eeschema_settings.h> +#include <kiface_base.h> +#include <sch_edit_frame.h> +#include <core/kicad_algo.h> +#include <template_fieldnames.h> +#include <wx/button.h> +#include <wx/checkbox.h> +#include <wx/sizer.h> +#include <confirm.h> +#include <wildcards_and_files_ext.h> +#include <ee_actions.h> +#include <tool/tool_manager.h> + +static const wxString REPEATED_PLACEMENT = _( "Place repeated copies" ); +static const wxString PLACE_AS_SHEET = _( "Place as sheet" ); +static const wxString KEEP_ANNOTATIONS = _( "Keep annotations" ); + +DESIGN_BLOCK_PANE::DESIGN_BLOCK_PANE( SCH_EDIT_FRAME* aParent, const LIB_ID* aPreselect, + std::vector<LIB_ID>& aHistoryList ) : + WX_PANEL( aParent ), m_frame( aParent ) +{ + wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL ); + m_chooserPanel = new PANEL_DESIGN_BLOCK_CHOOSER( aParent, this, aHistoryList, + [aParent]() + { + aParent->GetToolManager()->RunAction( + EE_ACTIONS::placeDesignBlock ); + } ); + sizer->Add( m_chooserPanel, 1, wxEXPAND, 5 ); + + if( aPreselect && aPreselect->IsValid() ) + m_chooserPanel->SetPreselect( *aPreselect ); + + SetName( wxT( "Design Blocks" ) ); + + wxBoxSizer* cbSizer = new wxBoxSizer( wxVERTICAL ); + + m_repeatedPlacement = new wxCheckBox( this, wxID_ANY, REPEATED_PLACEMENT ); + m_repeatedPlacement->SetToolTip( _( "Place copies of the design block on subsequent clicks." ) ); + + m_placeAsSheet = new wxCheckBox( this, wxID_ANY, PLACE_AS_SHEET ); + m_placeAsSheet->SetToolTip( _( "Place the design block as a new sheet." ) ); + + m_keepAnnotations = new wxCheckBox( this, wxID_ANY, KEEP_ANNOTATIONS ); + m_keepAnnotations->SetToolTip( _( "Preserve reference designators in the source schematic. " + "Otherwise, clear then reannotate according to settings." ) ); + UpdateCheckboxes(); + + // Set all checkbox handlers to the same function + m_repeatedPlacement->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this ); + m_placeAsSheet->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this ); + m_keepAnnotations->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this ); + + cbSizer->Add( m_repeatedPlacement, 0, wxLEFT, 5 ); + cbSizer->Add( m_placeAsSheet, 0, wxLEFT, 5 ); + cbSizer->Add( m_keepAnnotations, 0, wxLEFT, 5 ); + + sizer->Add( cbSizer, 0, wxEXPAND | wxLEFT, 5 ); + SetSizer( sizer ); + + m_chooserPanel->FinishSetup(); + Layout(); + + Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, m_chooserPanel ); +} + + +void DESIGN_BLOCK_PANE::OnCheckBox( wxCommandEvent& aEvent ) +{ + if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) ) + { + cfg->m_DesignBlockChooserPanel.repeated_placement = m_repeatedPlacement->GetValue(); + cfg->m_DesignBlockChooserPanel.place_as_sheet = m_placeAsSheet->GetValue(); + cfg->m_DesignBlockChooserPanel.keep_annotations = m_keepAnnotations->GetValue(); + } +} + + +void DESIGN_BLOCK_PANE::UpdateCheckboxes() +{ + if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) ) + { + m_repeatedPlacement->SetValue( cfg->m_DesignBlockChooserPanel.repeated_placement ); + m_placeAsSheet->SetValue( cfg->m_DesignBlockChooserPanel.place_as_sheet ); + m_keepAnnotations->SetValue( cfg->m_DesignBlockChooserPanel.keep_annotations ); + } +} + + +LIB_ID DESIGN_BLOCK_PANE::GetSelectedLibId( int* aUnit ) const +{ + return m_chooserPanel->GetSelectedLibId( aUnit ); +} + + +void DESIGN_BLOCK_PANE::SelectLibId( const LIB_ID& aLibId ) +{ + m_chooserPanel->SelectLibId( aLibId ); +} + + +void DESIGN_BLOCK_PANE::RefreshLibs() +{ + m_chooserPanel->RefreshLibs(); +} + + +FILEDLG_IMPORT_SHEET_CONTENTS::FILEDLG_IMPORT_SHEET_CONTENTS( EESCHEMA_SETTINGS* aSettings ) +{ + wxASSERT( aSettings ); + m_settings = aSettings; +}; + + +void FILEDLG_IMPORT_SHEET_CONTENTS::TransferDataFromCustomControls() +{ + m_settings->m_DesignBlockChooserPanel.repeated_placement = m_cbRepeatedPlacement->GetValue(); + m_settings->m_DesignBlockChooserPanel.place_as_sheet = m_cbPlaceAsSheet->GetValue(); + m_settings->m_DesignBlockChooserPanel.keep_annotations = m_cbKeepAnnotations->GetValue(); +} + + +void FILEDLG_IMPORT_SHEET_CONTENTS::AddCustomControls( wxFileDialogCustomize& customizer ) +{ + m_cbRepeatedPlacement = customizer.AddCheckBox( REPEATED_PLACEMENT ); + m_cbRepeatedPlacement->SetValue( m_settings->m_DesignBlockChooserPanel.repeated_placement ); + m_cbPlaceAsSheet = customizer.AddCheckBox( PLACE_AS_SHEET ); + m_cbPlaceAsSheet->SetValue( m_settings->m_DesignBlockChooserPanel.place_as_sheet ); + m_cbKeepAnnotations = customizer.AddCheckBox( KEEP_ANNOTATIONS ); + m_cbKeepAnnotations->SetValue( m_settings->m_DesignBlockChooserPanel.keep_annotations ); +} diff --git a/eeschema/widgets/design_block_pane.h b/eeschema/widgets/design_block_pane.h new file mode 100644 index 0000000000..9df5b81b57 --- /dev/null +++ b/eeschema/widgets/design_block_pane.h @@ -0,0 +1,113 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef DESIGN_BLOCK_PANE_H +#define DESIGN_BLOCK_PANE_H + +#include <design_block_tree_model_adapter.h> +#include <widgets/html_window.h> +#include <widgets/wx_panel.h> +#include <wx/checkbox.h> +#include <wx/filedlgcustomize.h> +#include <eeschema_settings.h> + + +class SCH_EDIT_FRAME; +class PANEL_DESIGN_BLOCK_CHOOSER; + + +class DESIGN_BLOCK_PANE : public WX_PANEL +{ +public: + /** + * Create dialog to choose design_block. + * + * @param aParent a SCH_BASE_FRAME parent window. + * @param aAllowFieldEdits if false, all functions that allow the user to edit fields + * (currently just footprint selection) will not be available. + * @param aShowFootprints if false, all footprint preview and selection features are + * disabled. This forces aAllowFieldEdits false too. + */ + DESIGN_BLOCK_PANE( SCH_EDIT_FRAME* aParent, const LIB_ID* aPreselect, + std::vector<LIB_ID>& aHistoryList ); + + /** + * To be called after this dialog returns from ShowModal(). + * + * For multi-unit design_blocks, if the user selects the design_block itself rather than picking + * an individual unit, 0 will be returned in aUnit. + * Beware that this is an invalid unit number - this should be replaced with whatever + * default is desired (usually 1). + * + * @param aUnit if not NULL, the selected unit is filled in here. + * @return the #LIB_ID of the design_block that has been selected. + */ + LIB_ID GetSelectedLibId( int* aUnit = nullptr ) const; + void SelectLibId( const LIB_ID& aLibId ); + + void RefreshLibs(); + + /* Handler for checkbox events */ + void OnCheckBox( wxCommandEvent& aEvent ); + void UpdateCheckboxes(); + + void OnSaveSheetAsDesignBlock( wxCommandEvent& aEvent ); + void OnSaveSelectionAsDesignBlock( wxCommandEvent& aEvent ); + + void OnDeleteLibrary( wxCommandEvent& aEvent ); + void OnDeleteDesignBlock( wxCommandEvent& aEvent ); + + PANEL_DESIGN_BLOCK_CHOOSER* GetDesignBlockPanel() const { return m_chooserPanel; } + +protected: + PANEL_DESIGN_BLOCK_CHOOSER* m_chooserPanel; + + wxCheckBox* m_repeatedPlacement; + wxCheckBox* m_placeAsSheet; + wxCheckBox* m_keepAnnotations; + + SCH_EDIT_FRAME* m_frame; +}; + + +// This is a helper class for the file dialog to allow the user to choose similar options +// as the design block chooser when importing a sheet. +class FILEDLG_IMPORT_SHEET_CONTENTS : public wxFileDialogCustomizeHook +{ +public: + FILEDLG_IMPORT_SHEET_CONTENTS( EESCHEMA_SETTINGS* aSettings ); + + virtual void AddCustomControls( wxFileDialogCustomize& customizer ) override; + + virtual void TransferDataFromCustomControls() override; + +private: + EESCHEMA_SETTINGS* m_settings; + + wxFileDialogCheckBox* m_cbRepeatedPlacement; + wxFileDialogCheckBox* m_cbPlaceAsSheet; + wxFileDialogCheckBox* m_cbKeepAnnotations; + + wxDECLARE_NO_COPY_CLASS( FILEDLG_IMPORT_SHEET_CONTENTS ); +}; + +#endif diff --git a/eeschema/widgets/design_block_preview_widget.cpp b/eeschema/widgets/design_block_preview_widget.cpp new file mode 100644 index 0000000000..603228aad6 --- /dev/null +++ b/eeschema/widgets/design_block_preview_widget.cpp @@ -0,0 +1,241 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "design_block_preview_widget.h" +#include <schematic.h> +#include <sch_sheet.h> +#include <sch_view.h> +#include <sch_screen.h> +#include <gal/gal_display_options.h> +#include <gal/graphics_abstraction_layer.h> +#include <math/vector2wx.h> +#include <design_block_lib_table.h> +#include <design_block.h> +#include <sch_preview_panel.h> +#include <pgm_base.h> +#include <sch_painter.h> +#include <eda_draw_frame.h> +#include <project_sch.h> +#include <eeschema_settings.h> +#include <eeschema_helpers.h> +#include <settings/settings_manager.h> +#include <wx/log.h> +#include <wx/stattext.h> + + +DESIGN_BLOCK_PREVIEW_WIDGET::DESIGN_BLOCK_PREVIEW_WIDGET( wxWindow* aParent, bool aIncludeStatus, + EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType ) : + wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ), + m_preview( nullptr ), + m_status( nullptr ), + m_statusPanel( nullptr ), + m_statusSizer( nullptr ), + m_previewItem( nullptr ) +{ + auto common_settings = Pgm().GetCommonSettings(); + auto app_settings = Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>(); + + m_galDisplayOptions.ReadConfig( *common_settings, app_settings->m_Window, this ); + m_galDisplayOptions.m_forceDisplayCursor = false; + + EDA_DRAW_PANEL_GAL::GAL_TYPE canvasType = aCanvasType; + + // Allows only a CAIRO or OPENGL canvas: + if( canvasType != EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL + && canvasType != EDA_DRAW_PANEL_GAL::GAL_FALLBACK ) + { + canvasType = EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL; + } + + m_preview = new SCH_PREVIEW_PANEL( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ), + m_galDisplayOptions, canvasType ); + m_preview->SetStealsFocus( false ); + m_preview->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER ); + m_preview->GetGAL()->SetAxesEnabled( false ); + + // Do not display the grid: the look is not good for a small canvas area. + // But mainly, due to some strange bug I (JPC) was unable to fix, the grid creates + // strange artifacts on Windows when Eeschema is run from KiCad manager (but not in + // stand alone...). + m_preview->GetGAL()->SetGridVisibility( true ); + + // Early initialization of the canvas background color, + // before any OnPaint event is fired for the canvas using a wrong bg color + KIGFX::VIEW* view = m_preview->GetView(); + auto settings = static_cast<SCH_RENDER_SETTINGS*>( view->GetPainter()->GetSettings() ); + + if( auto* theme = Pgm().GetSettingsManager().GetColorSettings( app_settings->m_ColorTheme ) ) + settings->LoadColors( theme ); + + const COLOR4D& backgroundColor = settings->GetBackgroundColor(); + const COLOR4D& foregroundColor = settings->GetCursorColor(); + + m_preview->GetGAL()->SetClearColor( backgroundColor ); + + settings->m_ShowPinsElectricalType = false; + settings->m_ShowPinNumbers = false; + settings->m_ShowHiddenPins = false; + settings->m_ShowHiddenFields = false; + + m_outerSizer = new wxBoxSizer( wxVERTICAL ); + + if( aIncludeStatus ) + { + m_statusPanel = new wxPanel( this ); + m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() ); + m_status = new wxStaticText( m_statusPanel, wxID_ANY, wxEmptyString ); + m_status->SetForegroundColour( settings->GetLayerColor( LAYER_REFERENCEPART ).ToColour() ); + m_statusSizer = new wxBoxSizer( wxVERTICAL ); + m_statusSizer->Add( 0, 0, 1 ); // add a spacer + m_statusSizer->Add( m_status, 0, wxALIGN_CENTER ); + m_statusSizer->Add( 0, 0, 1 ); // add a spacer + m_statusPanel->SetSizer( m_statusSizer ); + + // Give the status panel the same color scheme as the canvas so it isn't jarring when + // switched to. + m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() ); + m_statusPanel->SetForegroundColour( foregroundColor.ToColour() ); + + // Give the preview panel a small top border to align its top with the status panel, + // and give the status panel a small bottom border to align its bottom with the preview + // panel. + m_outerSizer->Add( m_preview, 1, wxTOP | wxEXPAND, 5 ); + m_outerSizer->Add( m_statusPanel, 1, wxBOTTOM | wxEXPAND, 5 ); + + // Hide the status panel to start + m_statusPanel->Hide(); + } + else + { + m_outerSizer->Add( m_preview, 1, wxEXPAND, 0 ); + } + + SetSizer( m_outerSizer ); + Layout(); + + Connect( wxEVT_SIZE, wxSizeEventHandler( DESIGN_BLOCK_PREVIEW_WIDGET::onSize ), nullptr, this ); +} + + +DESIGN_BLOCK_PREVIEW_WIDGET::~DESIGN_BLOCK_PREVIEW_WIDGET() +{ + delete m_preview; + delete m_previewItem; +} + + +void DESIGN_BLOCK_PREVIEW_WIDGET::SetStatusText( wxString const& aText ) +{ + wxCHECK( m_statusPanel, /* void */ ); + + m_status->SetLabel( aText ); + m_preview->Hide(); + m_statusPanel->Show(); + Layout(); +} + + +void DESIGN_BLOCK_PREVIEW_WIDGET::onSize( wxSizeEvent& aEvent ) +{ + if( m_previewItem ) + { + fitOnDrawArea(); + m_preview->ForceRefresh(); + } + + aEvent.Skip(); +} + + +void DESIGN_BLOCK_PREVIEW_WIDGET::fitOnDrawArea() +{ + if( !m_previewItem ) + return; + + // set the view scale to fit the item on screen + KIGFX::VIEW* view = m_preview->GetView(); + + // Calculate the drawing area size, in internal units, for a scaling factor = 1.0 + view->SetScale( 1.0 ); + VECTOR2D clientSize = view->ToWorld( ToVECTOR2D( m_preview->GetClientSize() ), false ); + // Calculate the draw scale to fit the drawing area + double scale = std::min( fabs( clientSize.x / m_itemBBox.GetWidth() ), + fabs( clientSize.y / m_itemBBox.GetHeight() ) ); + + // Above calculation will yield an exact fit; add a bit of whitespace around block + scale /= 1.2; + + // Now fix the best scale + view->SetScale( scale ); + view->SetCenter( m_itemBBox.Centre() ); +} + + +void DESIGN_BLOCK_PREVIEW_WIDGET::DisplayDesignBlock( DESIGN_BLOCK* aDesignBlock ) +{ + KIGFX::VIEW* view = m_preview->GetView(); + + if( m_previewItem ) + { + view->Clear(); + delete m_previewItem; + m_previewItem = nullptr; + } + + if( aDesignBlock ) + { + m_previewItem = EESCHEMA_HELPERS::LoadSchematic( aDesignBlock->GetSchematicFile(), + SCH_IO_MGR::SCH_KICAD, false, true ); + BOX2I bBox; + + if( m_previewItem ) + { + for( EDA_ITEM* item : m_previewItem->CurrentSheet().LastScreen()->Items() ) + { + view->Add( item ); + + if( item->Type() == SCH_FIELD_T ) + { + if( !static_cast<const SCH_FIELD*>( item )->IsVisible() ) + continue; + } + + bBox.Merge( item->GetBoundingBox() ); + } + } + + m_itemBBox = bBox; + + if( !m_preview->IsShownOnScreen() ) + { + m_preview->Show(); + + if( m_statusPanel ) + m_statusPanel->Hide(); + + Layout(); // Ensure panel size is up to date. + } + + // Calculate the draw scale to fit the drawing area + fitOnDrawArea(); + } + + m_preview->ForceRefresh(); + m_preview->Show(); +} diff --git a/eeschema/widgets/design_block_preview_widget.h b/eeschema/widgets/design_block_preview_widget.h new file mode 100644 index 0000000000..e0b72599b4 --- /dev/null +++ b/eeschema/widgets/design_block_preview_widget.h @@ -0,0 +1,81 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DESIGN_BLOCK_PREVIEW_WIDGET_H +#define DESIGN_BLOCK_PREVIEW_WIDGET_H + +#include <wx/panel.h> +#include <kiway.h> +#include <gal_display_options_common.h> +#include <class_draw_panel_gal.h> + + +class LIB_ID; +class DESIGN_BLOCK; +class SCHEMATIC; +class SCH_SHEET; +class wxStaticText; +class wxSizer; + + +class DESIGN_BLOCK_PREVIEW_WIDGET : public wxPanel +{ +public: + /** + * Construct a symbol preview widget. + * + * @param aParent - parent window + * @param aCanvasType = the type of canvas (GAL_TYPE_OPENGL or GAL_TYPE_CAIRO only) + */ + DESIGN_BLOCK_PREVIEW_WIDGET( wxWindow* aParent, bool aIncludeStatus, + EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType ); + + ~DESIGN_BLOCK_PREVIEW_WIDGET() override; + + /** + * Set the contents of the status label and display it. + */ + void SetStatusText( const wxString& aText ); + + /** + * Set the currently displayed symbol. + */ + void DisplayDesignBlock( DESIGN_BLOCK* aDesignBlock ); + +protected: + void onSize( wxSizeEvent& aEvent ); + + void fitOnDrawArea(); // set the view scale to fit the item on screen and center + + GAL_DISPLAY_OPTIONS_IMPL m_galDisplayOptions; + EDA_DRAW_PANEL_GAL* m_preview; + + wxStaticText* m_status; + wxPanel* m_statusPanel; + wxSizer* m_statusSizer; + wxSizer* m_outerSizer; + + SCHEMATIC* m_previewItem; + + /// The bounding box of the current item + BOX2I m_itemBBox; +}; + + +#endif // DESIGN_BLOCK_PREVIEW_WIDGET_H diff --git a/eeschema/widgets/panel_design_block_chooser.cpp b/eeschema/widgets/panel_design_block_chooser.cpp new file mode 100644 index 0000000000..941fe8ecd1 --- /dev/null +++ b/eeschema/widgets/panel_design_block_chooser.cpp @@ -0,0 +1,431 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2016-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <pgm_base.h> +#include <design_block.h> +#include <design_block_lib_table.h> +#include <panel_design_block_chooser.h> +#include <design_block_preview_widget.h> +#include <kiface_base.h> +#include <sch_edit_frame.h> +#include <project_sch.h> +#include <widgets/lib_tree.h> +#include <settings/settings_manager.h> +#include <project/project_file.h> +#include <eeschema_settings.h> +#include <dialogs/html_message_box.h> +#include <string_utils.h> +#include <wx/button.h> +#include <wx/clipbrd.h> +#include <wx/log.h> +#include <wx/panel.h> +#include <wx/sizer.h> +#include <wx/splitter.h> +#include <wx/timer.h> +#include <wx/wxhtml.h> +#include <wx/msgdlg.h> +#include <widgets/wx_progress_reporters.h> + + +wxString PANEL_DESIGN_BLOCK_CHOOSER::g_designBlockSearchString; + + +PANEL_DESIGN_BLOCK_CHOOSER::PANEL_DESIGN_BLOCK_CHOOSER( SCH_EDIT_FRAME* aFrame, wxWindow* aParent, + std::vector<LIB_ID>& aHistoryList, + std::function<void()> aSelectHandler ) : + wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ), + m_vsplitter( nullptr ), + m_tree( nullptr ), + m_preview( nullptr ), + m_frame( aFrame ), + m_selectHandler( std::move( aSelectHandler ) ), + m_historyList( aHistoryList ) +{ + DESIGN_BLOCK_LIB_TABLE* libs = m_frame->Prj().DesignBlockLibs(); + + // Make sure settings are loaded before we start running multi-threaded design block loaders + Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>(); + + // Load design block files: + WX_PROGRESS_REPORTER* progressReporter = + new WX_PROGRESS_REPORTER( aParent, _( "Loading Design Block Libraries" ), 3 ); + GDesignBlockList.ReadDesignBlockFiles( libs, nullptr, progressReporter ); + + // Force immediate deletion of the WX_PROGRESS_REPORTER. Do not use Destroy(), or use + // Destroy() followed by wxSafeYield() because on Windows, APP_PROGRESS_DIALOG and + // WX_PROGRESS_REPORTER have some side effects on the event loop manager. For instance, a + // subsequent call to ShowModal() or ShowQuasiModal() for a dialog following the use of a + // WX_PROGRESS_REPORTER results in incorrect modal or quasi modal behavior. + delete progressReporter; + + if( GDesignBlockList.GetErrorCount() ) + displayErrors( aFrame ); + + m_adapter = DESIGN_BLOCK_TREE_MODEL_ADAPTER::Create( m_frame, libs ); + + // ------------------------------------------------------------------------------------- + // Construct the actual panel + // + + wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL ); + + m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH ); + + + // Avoid the splitter window being assigned as the parent to additional windows. + m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT ); + + wxPanel* treePanel = new wxPanel( m_vsplitter ); + wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL ); + treePanel->SetSizer( treeSizer ); + + wxPanel* detailsPanel = new wxPanel( m_vsplitter ); + wxBoxSizer* detailsSizer = new wxBoxSizer( wxVERTICAL ); + detailsPanel->SetSizer( detailsSizer ); + + m_preview = new DESIGN_BLOCK_PREVIEW_WIDGET( detailsPanel, false, + EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL ); + detailsSizer->Add( m_preview, 1, wxEXPAND, 5 ); + detailsPanel->Layout(); + detailsSizer->Fit( detailsPanel ); + + m_vsplitter->SetSashGravity( 0.5 ); + m_vsplitter->SetMinimumPaneSize( 20 ); + m_vsplitter->SplitHorizontally( treePanel, detailsPanel ); + + sizer->Add( m_vsplitter, 1, wxEXPAND, 5 ); + + + m_tree = new LIB_TREE( treePanel, wxT( "design_blocks" ), libs, m_adapter, + LIB_TREE::FLAGS::ALL_WIDGETS, nullptr ); + + treeSizer->Add( m_tree, 1, wxEXPAND, 5 ); + treePanel->Layout(); + treeSizer->Fit( treePanel ); + + RefreshLibs(); + m_adapter->FinishTreeInitialization(); + + m_tree->SetSearchString( g_designBlockSearchString ); + + m_dbl_click_timer = new wxTimer( this ); + m_open_libs_timer = new wxTimer( this ); + + SetSizer( sizer ); + + Layout(); + + Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this, + m_dbl_click_timer->GetId() ); + Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer, this, + m_open_libs_timer->GetId() ); + Bind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this ); + Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this ); + + // Open the user's previously opened libraries on timer expiration. + // This is done on a timer because we need a gross hack to keep GTK from garbling the + // display. Must be longer than the search debounce timer. + m_open_libs_timer->StartOnce( 300 ); +} + + +PANEL_DESIGN_BLOCK_CHOOSER::~PANEL_DESIGN_BLOCK_CHOOSER() +{ + Unbind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this ); + Unbind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this ); + Unbind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this ); + Unbind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this ); + + // Stop the timer during destruction early to avoid potential race conditions (that do happen) + m_dbl_click_timer->Stop(); + m_open_libs_timer->Stop(); + delete m_dbl_click_timer; + delete m_open_libs_timer; + + g_designBlockSearchString = m_tree->GetSearchString(); + + if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) ) + { + // Save any changes to column widths, etc. + m_adapter->SaveSettings(); + + cfg->m_DesignBlockChooserPanel.width = GetParent()->GetSize().x; + cfg->m_DesignBlockChooserPanel.height = GetParent()->GetSize().y; + + if( m_vsplitter ) + cfg->m_DesignBlockChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition(); + + cfg->m_DesignBlockChooserPanel.sort_mode = m_tree->GetSortMode(); + } +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::OnChar( wxKeyEvent& aEvent ) +{ + if( aEvent.GetKeyCode() == WXK_ESCAPE ) + { + wxObject* eventSource = aEvent.GetEventObject(); + + if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) ) + { + // First escape cancels search string value + if( textCtrl->GetValue() == m_tree->GetSearchString() + && !m_tree->GetSearchString().IsEmpty() ) + { + m_tree->SetSearchString( wxEmptyString ); + return; + } + } + } + else + { + aEvent.Skip(); + } +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::FinishSetup() +{ + if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) ) + { + auto horizPixelsFromDU = + [&]( int x ) -> int + { + wxSize sz( x, 0 ); + return GetParent()->ConvertDialogToPixels( sz ).x; + }; + + EESCHEMA_SETTINGS::PANEL_DESIGN_BLOCK_CHOOSER& panelCfg = cfg->m_DesignBlockChooserPanel; + + int w = panelCfg.width > 40 ? panelCfg.width : horizPixelsFromDU( 440 ); + int h = panelCfg.height > 40 ? panelCfg.height : horizPixelsFromDU( 340 ); + + GetParent()->SetSize( wxSize( w, h ) ); + GetParent()->Layout(); + + // We specify the width of the right window (m_design_block_view_panel), because specify + // the width of the left window does not work as expected when SetSashGravity() is called + + if( panelCfg.sash_pos_h < 0 ) + panelCfg.sash_pos_h = horizPixelsFromDU( 220 ); + + if( panelCfg.sash_pos_v < 0 ) + panelCfg.sash_pos_v = horizPixelsFromDU( 230 ); + + if( m_vsplitter ) + m_vsplitter->SetSashPosition( panelCfg.sash_pos_v ); + + m_adapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) panelCfg.sort_mode ); + } +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::RefreshLibs( bool aProgress ) +{ + // Unselect before syncing to avoid null reference in the adapter + // if a selected item is removed during the sync + m_tree->Unselect(); + + DESIGN_BLOCK_TREE_MODEL_ADAPTER* adapter = + static_cast<DESIGN_BLOCK_TREE_MODEL_ADAPTER*>( m_adapter.get() ); + + // Clear all existing libraries then re-add + adapter->ClearLibraries(); + + // Read the libraries from disk if they've changed + DESIGN_BLOCK_LIB_TABLE* fpTable = m_frame->Prj().DesignBlockLibs(); + + // Sync FOOTPRINT_INFO list to the libraries on disk + if( aProgress ) + { + WX_PROGRESS_REPORTER progressReporter( this, _( "Updating Design Block Libraries" ), 2 ); + GDesignBlockList.ReadDesignBlockFiles( fpTable, nullptr, &progressReporter ); + progressReporter.Show( false ); + } + else + { + GDesignBlockList.ReadDesignBlockFiles( fpTable, nullptr, nullptr ); + } + + rebuildHistoryNode(); + + if( !m_historyList.empty() ) + adapter->SetPreselectNode( m_historyList[0], 0 ); + + adapter->AddLibraries( m_frame ); + + m_tree->Regenerate( true ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::SetPreselect( const LIB_ID& aPreselect ) +{ + m_adapter->SetPreselectNode( aPreselect, 0 ); +} + + +LIB_ID PANEL_DESIGN_BLOCK_CHOOSER::GetSelectedLibId( int* aUnit ) const +{ + return m_tree->GetSelectedLibId( aUnit ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::SelectLibId( const LIB_ID& aLibId ) +{ + m_tree->CenterLibId( aLibId ); + m_tree->SelectLibId( aLibId ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer( wxTimerEvent& aEvent ) +{ + // Hack because of eaten MouseUp event. See PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen + // for the beginning of this spaghetti noodle. + + wxMouseState state = wxGetMouseState(); + + if( state.LeftIsDown() ) + { + // Mouse hasn't been raised yet, so fire the timer again. Otherwise the + // purpose of this timer is defeated. + m_dbl_click_timer->StartOnce( PANEL_DESIGN_BLOCK_CHOOSER::DBLCLICK_DELAY ); + } + else + { + m_selectHandler(); + addDesignBlockToHistory( m_tree->GetSelectedLibId() ); + } +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer( wxTimerEvent& aEvent ) +{ + if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) ) + m_adapter->OpenLibs( cfg->m_LibTree.open_libs ); + + // Bind this now se we don't spam the event queue with EVT_LIBITEM_SELECTED events during + // the initial load. + Bind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected( wxCommandEvent& aEvent ) +{ + if( GetSelectedLibId().IsValid() ) + m_preview->DisplayDesignBlock( m_frame->GetDesignBlock( GetSelectedLibId() ) ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen( wxCommandEvent& aEvent ) +{ + if( m_tree->GetSelectedLibId().IsValid() ) + { + // Got a selection. We can't just end the modal dialog here, because wx leaks some events + // back to the parent window (in particular, the MouseUp following a double click). + // + // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp. This isn't really + // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then + // might not be fully possible (docs are vague). To get around this, we use a one-shot + // timer to schedule the dialog close. + // + // See PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer for the other end of this spaghetti noodle. + m_dbl_click_timer->StartOnce( PANEL_DESIGN_BLOCK_CHOOSER::DBLCLICK_DELAY ); + } +} + +void PANEL_DESIGN_BLOCK_CHOOSER::addDesignBlockToHistory( const LIB_ID& aLibId ) +{ + LIB_ID savedId = GetSelectedLibId(); + + m_tree->Unselect(); + + // Remove duplicates + for( int i = (int) m_historyList.size() - 1; i >= 0; --i ) + { + if( m_historyList[i] == aLibId ) + m_historyList.erase( m_historyList.begin() + i ); + } + + // Add the new name at the beginning of the history list + m_historyList.insert( m_historyList.begin(), aLibId ); + + // Remove extra names + while( m_historyList.size() >= 8 ) + m_historyList.pop_back(); + + rebuildHistoryNode(); + m_tree->Regenerate( true ); + + SelectLibId( savedId ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::rebuildHistoryNode() +{ + wxString history = wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" ); + + m_adapter->DoRemoveLibrary( history ); + + // Build the history list + std::vector<LIB_TREE_ITEM*> historyInfos; + + for( const LIB_ID& lib : m_historyList ) + { + LIB_TREE_ITEM* fp_info = + GDesignBlockList.GetDesignBlockInfo( lib.GetLibNickname(), lib.GetLibItemName() ); + + // this can be null, for example, if the design block has been deleted from a library. + if( fp_info != nullptr ) + historyInfos.push_back( fp_info ); + } + + m_adapter->DoAddLibrary( history, wxEmptyString, historyInfos, false, true ); +} + + +void PANEL_DESIGN_BLOCK_CHOOSER::displayErrors( wxTopLevelWindow* aWindow ) +{ + // @todo: go to a more HTML !<table>! ? centric output, possibly with recommendations + // for remedy of errors. Add numeric error codes to PARSE_ERROR, and switch on them for + // remedies, etc. Full access is provided to everything in every exception! + + HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) ); + + dlg.MessageSet( _( "Errors were encountered loading footprints:" ) ); + + wxString msg; + + while( std::unique_ptr<IO_ERROR> error = GDesignBlockList.PopError() ) + { + wxString tmp = EscapeHTML( error->Problem() ); + + // Preserve new lines in error messages so queued errors don't run together. + tmp.Replace( wxS( "\n" ), wxS( "<BR>" ) ); + msg += wxT( "<p>" ) + tmp + wxT( "</p>" ); + } + + dlg.AddHTML_Text( msg ); + + dlg.ShowModal(); +} diff --git a/eeschema/widgets/panel_design_block_chooser.h b/eeschema/widgets/panel_design_block_chooser.h new file mode 100644 index 0000000000..127879a40a --- /dev/null +++ b/eeschema/widgets/panel_design_block_chooser.h @@ -0,0 +1,118 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef PANEL_DESIGN_BLOCK_CHOOSER_H +#define PANEL_DESIGN_BLOCK_CHOOSER_H + +#include <widgets/lib_tree.h> +#include <design_block_tree_model_adapter.h> +#include <widgets/html_window.h> + +class wxPanel; +class wxTimer; +class wxSplitterWindow; + +class SCH_EDIT_FRAME; +class DESIGN_BLOCK_PREVIEW_WIDGET; + + +class PANEL_DESIGN_BLOCK_CHOOSER : public wxPanel +{ +public: +/** + * Create dialog to choose design_block. + * + * @param aFrame the parent frame (usually a SCH_EDIT_FRAME or DESIGN_BLOCK_CHOOSER_FRAME) + * @param aParent the parent window (usually a DIALOG_SHIM or DESIGN_BLOCK_CHOOSER_FRAME) + * @param aAcceptHandler a handler to be called on double-click of a footprint + * @param aEscapeHandler a handler to be called on <ESC> + */ + PANEL_DESIGN_BLOCK_CHOOSER( SCH_EDIT_FRAME* aFrame, wxWindow* aParent, + std::vector<LIB_ID>& aHistoryList, + std::function<void()> aSelectHandler ); + + ~PANEL_DESIGN_BLOCK_CHOOSER(); + + void OnChar( wxKeyEvent& aEvent ); + + void FinishSetup(); + + void SetPreselect( const LIB_ID& aPreselect ); + + void RefreshLibs( bool aProgress = false ); + + /** + * To be called after this dialog returns from ShowModal(). + * + * For multi-unit design_blocks, if the user selects the design_block itself rather than picking + * an individual unit, 0 will be returned in aUnit. + * Beware that this is an invalid unit number - this should be replaced with whatever + * default is desired (usually 1). + * + * @param aUnit if not NULL, the selected unit is filled in here. + * @return the #LIB_ID of the design_block that has been selected. + */ + LIB_ID GetSelectedLibId( int* aUnit = nullptr ) const; + void SelectLibId( const LIB_ID& aLibId ); + + LIB_TREE* GetLibTree() { return m_tree; } + +protected: + static constexpr int DBLCLICK_DELAY = 100; // milliseconds + + void OnDetailsCharHook( wxKeyEvent& aEvt ); + void onCloseTimer( wxTimerEvent& aEvent ); + void onOpenLibsTimer( wxTimerEvent& aEvent ); + + void onDesignBlockSelected( wxCommandEvent& aEvent ); + + /** + * Handle the selection of an item. This is called when either the search box or the tree + * receive an Enter, or the tree receives a double click. + * If the item selected is a category, it is expanded or collapsed; if it is a design_block, the + * design_block is picked. + */ + void onDesignBlockChosen( wxCommandEvent& aEvent ); + + void addDesignBlockToHistory( const LIB_ID& aLibId ); + void rebuildHistoryNode(); + + void displayErrors( wxTopLevelWindow* aWindow ); + + static wxString g_designBlockSearchString; + + wxTimer* m_dbl_click_timer; + wxTimer* m_open_libs_timer; + wxSplitterWindow* m_vsplitter; + + wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER> m_adapter; + + LIB_TREE* m_tree; + DESIGN_BLOCK_PREVIEW_WIDGET* m_preview; + + SCH_EDIT_FRAME* m_frame; + std::function<void()> m_selectHandler; + + std::vector<LIB_ID> m_historyList; +}; + +#endif /* PANEL_DESIGN_BLOCK_CHOOSER_H */ diff --git a/include/advanced_config.h b/include/advanced_config.h index 90d765ca29..c6af868c14 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -466,6 +466,15 @@ public: */ int m_DisambiguationMenuDelay; + /** + * Enable the new Design Blocks feature + * + * Setting name: "EnableDesignBlocks" + * Valid values: true or false + * Default value: false + */ + bool m_EnableDesignBlocks; + /** * Enable support for generators. * diff --git a/include/core/typeinfo.h b/include/core/typeinfo.h index 20d663cb75..4ad06c1ede 100644 --- a/include/core/typeinfo.h +++ b/include/core/typeinfo.h @@ -228,6 +228,7 @@ enum KICAD_T */ SYMBOL_LIB_TABLE_T, FP_LIB_TABLE_T, + DESIGN_BLOCK_LIB_TABLE_T, SYMBOL_LIBS_T, SEARCH_STACK_T, S3D_CACHE_T, @@ -516,6 +517,7 @@ constexpr bool IsMiscType( const KICAD_T aType ) case SYMBOL_LIB_TABLE_T: case FP_LIB_TABLE_T: + case DESIGN_BLOCK_LIB_TABLE_T: case SYMBOL_LIBS_T: case SEARCH_STACK_T: case S3D_CACHE_T: diff --git a/include/design_block_lib_table.h b/include/design_block_lib_table.h new file mode 100644 index 0000000000..60c6868988 --- /dev/null +++ b/include/design_block_lib_table.h @@ -0,0 +1,291 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef DESIGN_BLOCK_LIB_TABLE_H_ +#define DESIGN_BLOCK_LIB_TABLE_H_ + +#include <kicommon.h> +#include <lib_table_base.h> +#include <design_block_io.h> +#include <design_block_info_impl.h> + +class DESIGN_BLOCK; +class DESIGN_BLOCK_LIB_TABLE_GRID; + +/** + * Hold a record identifying a library accessed by the appropriate design block library #PLUGIN + * object in the #DESIGN_BLOCK_LIB_TABLE. + */ +class KICOMMON_API DESIGN_BLOCK_LIB_TABLE_ROW : public LIB_TABLE_ROW +{ +public: + DESIGN_BLOCK_LIB_TABLE_ROW( const wxString& aNick, const wxString& aURI, const wxString& aType, + const wxString& aOptions, const wxString& aDescr = wxEmptyString ) : + LIB_TABLE_ROW( aNick, aURI, aOptions, aDescr ) + { + SetType( aType ); + } + + DESIGN_BLOCK_LIB_TABLE_ROW() : type( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ) {} + + bool operator==( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const; + + bool operator!=( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const { return !( *this == aRow ); } + + /** + * return the type of design block library table represented by this row. + */ + const wxString GetType() const override { return DESIGN_BLOCK_IO_MGR::ShowType( type ); } + + /** + * Change the type represented by this row. + */ + void SetType( const wxString& aType ) override; + + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T GetFileType() { return type; } + +protected: + DESIGN_BLOCK_LIB_TABLE_ROW( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) : + LIB_TABLE_ROW( aRow ), type( aRow.type ) + { + } + +private: + virtual LIB_TABLE_ROW* do_clone() const override + { + return new DESIGN_BLOCK_LIB_TABLE_ROW( *this ); + } + + void setPlugin( DESIGN_BLOCK_IO* aPlugin ) { plugin.reset( aPlugin ); } + + friend class DESIGN_BLOCK_LIB_TABLE; + +private: + IO_RELEASER<DESIGN_BLOCK_IO> plugin; + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T type; +}; + + +class KICOMMON_API DESIGN_BLOCK_LIB_TABLE : public LIB_TABLE +{ +public: + PROJECT::ELEM ProjectElementType() override { return PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE; } + + virtual void Parse( LIB_TABLE_LEXER* aLexer ) override; + + virtual void Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const override; + + /** + * Build a design block library table by pre-pending this table fragment in front of + * @a aFallBackTable. Loading of this table fragment is done by using Parse(). + * + * @param aFallBackTable is another DESIGN_BLOCK_LIB_TABLE which is searched only when a row + * is not found in this table. No ownership is taken of + * \a aFallBackTable. + */ + DESIGN_BLOCK_LIB_TABLE( DESIGN_BLOCK_LIB_TABLE* aFallBackTable = nullptr ); + + bool operator==( const DESIGN_BLOCK_LIB_TABLE& aFpTable ) const; + + bool operator!=( const DESIGN_BLOCK_LIB_TABLE& r ) const { return !( *this == r ); } + + /** + * Return an #DESIGN_BLOCK_LIB_TABLE_ROW if \a aNickName is found in this table or in any chained + * fall back table fragment. + * + * If \a aCheckIfEnabled is true, the library will be ignored even if it is disabled. + * Otherwise, the row found will be returned even if entry is disabled. + * + * The #PLUGIN is loaded and attached to the "plugin" field of the #DESIGN_BLOCK_LIB_TABLE_ROW if + * not already loaded. + * + * @param aNickName is the name of library nickname to find. + * @param aCheckIfEnabled is the flag to check if the library found is enabled. + * @return the library \a NickName if found. + * @throw IO_ERROR if \a aNickName cannot be found. + */ + const DESIGN_BLOCK_LIB_TABLE_ROW* FindRow( const wxString& aNickName, + bool aCheckIfEnabled = false ); + + /** + * Return a list of design block names contained within the library given by @a aNickname. + * + * @param aDesignBlockNames is the list to fill with the design block names found in \a aNickname + * @param aNickname is a locator for the "library", it is a "name" in LIB_TABLE_ROW. + * @param aBestEfforts if true, don't throw on errors. + * + * @throw IO_ERROR if the library cannot be found, or design block cannot be loaded. + */ + void DesignBlockEnumerate( wxArrayString& aDesignBlockNames, const wxString& aNickname, + bool aBestEfforts ); + + /** + * Generate a hashed timestamp representing the last-mod-times of the library indicated + * by \a aNickname, or all libraries if \a aNickname is NULL. + */ + long long GenerateTimestamp( const wxString* aNickname ); + + /** + * If possible, prefetches the specified library (e.g. performing downloads). Does not parse. + * Threadsafe. + * + * This is a no-op for libraries that cannot be prefetched. + * + * @param aNickname is a locator for the library; it is a name in LIB_TABLE_ROW. + * + * @throw IO_ERROR if there is an error prefetching the library. + */ + void PrefetchLib( const wxString& aNickname ); + + /** + * Load a design block having @a aDesignBlockName from the library given by @a aNickname. + * + * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW. + * @param aDesignBlockName is the name of the design block to load. + * @param aKeepUUID = true to keep initial items UUID, false to set new UUID + * normally true if loaded in the design block editor, false + * if loaded in the board editor. Used only in kicad_plugin + * @return the design block if found caller owns it, else NULL if not found. + * + * @throw IO_ERROR if the library cannot be found or read. No exception + * is thrown in the case where aDesignBlockName cannot be found. + */ + DESIGN_BLOCK* DesignBlockLoad( const wxString& aNickname, const wxString& aDesignBlockName, + bool aKeepUUID = false ); + + /** + * Indicates whether or not the given design block already exists in the given library. + */ + bool DesignBlockExists( const wxString& aNickname, const wxString& aDesignBlockName ); + + /** + * A version of #DesignBlockLoad() for use after #DesignBlockEnumerate() for more efficient + * cache management. + * + * The return value is const to allow it to return a reference to a cached item. + */ + const DESIGN_BLOCK* GetEnumeratedDesignBlock( const wxString& aNickname, + const wxString& aDesignBlockName ); + /** + * The set of return values from DesignBlockSave() below. + */ + enum SAVE_T + { + SAVE_OK, + SAVE_SKIPPED, + }; + + /** + * Write @a aDesignBlock to an existing library given by @a aNickname. + * + * If a design block by the same name already exists, it is replaced. + * + * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW. + * @param aDesignBlock is what to store in the library. The caller continues to own the + * design block after this call. + * @param aOverwrite when true means overwrite any existing design block by the same name, + * else if false means skip the write and return SAVE_SKIPPED. + * @return #SAVE_OK or #SAVE_SKIPPED. If error saving, then #IO_ERROR is thrown. + * + * @throw IO_ERROR if there is a problem saving. + */ + SAVE_T DesignBlockSave( const wxString& aNickname, const DESIGN_BLOCK* aDesignBlock, + bool aOverwrite = true ); + + /** + * Delete the @a aDesignBlockName from the library given by @a aNickname. + * + * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW. + * @param aDesignBlockName is the name of a design block to delete from the specified library. + * + * @throw IO_ERROR if there is a problem finding the design block or the library, or deleting it. + */ + void DesignBlockDelete( const wxString& aNickname, const wxString& aDesignBlockName ); + + /** + * Return true if the library given by @a aNickname is writable. + * + * Often system libraries are read only because of where they are installed. + * + * @throw IO_ERROR if no library at aLibraryPath exists. + */ + bool IsDesignBlockLibWritable( const wxString& aNickname ); + + void DesignBlockLibDelete( const wxString& aNickname ); + + void DesignBlockLibCreate( const wxString& aNickname ); + + /** + * Load a design block having @a aDesignBlockId with possibly an empty nickname. + * + * @param aDesignBlockId the [nickname] and name of the design block to load. + * @param aKeepUUID = true to keep initial items UUID, false to set new UUID + * normally true if loaded in the design block editor, false + * if loaded in the board editor + * used only in kicad_plugin + * @return the #DESIGN_BLOCK if found caller owns it, else NULL if not found. + * + * @throw IO_ERROR if the library cannot be found or read. No exception is + * thrown in the case where \a aDesignBlockName cannot be found. + * @throw PARSE_ERROR if @a aDesignBlockId is not parsed OK. + */ + DESIGN_BLOCK* DesignBlockLoadWithOptionalNickname( const LIB_ID& aDesignBlockId, + bool aKeepUUID = false ); + + /** + * Load the global design block library table into \a aTable. + * + * This probably should be move into the application object when KiCad is changed + * to a single process application. This is the least painful solution for the + * time being. + * + * @param aTable the #DESIGN_BLOCK_LIB_TABLE object to load. + * @return true if the global library table exists and is loaded properly. + * @throw IO_ERROR if an error occurs attempting to load the design block library + * table. + */ + static bool LoadGlobalTable( DESIGN_BLOCK_LIB_TABLE& aTable ); + + /** + * @return the platform specific global design block library path and file name. + */ + static wxString GetGlobalTableFileName(); + + /** + * Return the name of the environment variable used to hold the directory of + * locally installed "KiCad sponsored" system design block libraries. + * + *These can be either legacy or pretty format. The only thing special about this + * particular environment variable is that it is set automatically by KiCad on + * program start up, <b>if</b> it is not set already in the environment. + */ + static const wxString GlobalPathEnvVariableName(); + +private: + friend class DESIGN_BLOCK_LIB_TABLE_GRID; +}; + +KICOMMON_API extern DESIGN_BLOCK_LIB_TABLE GDesignBlockTable; +KICOMMON_API extern DESIGN_BLOCK_LIST_IMPL GDesignBlockList; + +#endif // DESIGN_BLOCK_LIB_TABLE_H_ diff --git a/include/eda_draw_frame.h b/include/eda_draw_frame.h index e387f8dfff..e0ca6f2064 100644 --- a/include/eda_draw_frame.h +++ b/include/eda_draw_frame.h @@ -417,6 +417,8 @@ public: static const wxString NetInspectorPanelName() { return wxS( "NetInspector" ); } + static const wxString DesignBlocksPaneName() { return wxS( "DesignBlocks" ); } + /** * Fetch an item by KIID. Frame-type-specific implementation. */ diff --git a/include/frame_type.h b/include/frame_type.h index 77be547e80..a3fa800942 100644 --- a/include/frame_type.h +++ b/include/frame_type.h @@ -62,7 +62,7 @@ enum FRAME_T FRAME_CALC, - KIWAY_PLAYER_COUNT, // counts subset of FRAME_T's which are KIWAY_PLAYER derivatives + KIWAY_PLAYER_COUNT, // counts subset of FRAME_T's which are KIWAY_PLAYER derivatives // C++ project manager is not a KIWAY_PLAYER KICAD_MAIN_FRAME_T = KIWAY_PLAYER_COUNT, @@ -112,11 +112,12 @@ enum FRAME_T // Library table dialogs are transient and are never returned DIALOG_CONFIGUREPATHS, + DIALOG_DESIGN_BLOCK_LIBRARY_TABLE, DIALOG_SCH_LIBRARY_TABLE, DIALOG_PCB_LIBRARY_TABLE }; - //TEXT_EDITOR_FRAME_T, +//TEXT_EDITOR_FRAME_T, #endif // FRAME_T_H_ diff --git a/include/id.h b/include/id.h index 332493cd26..67e5a59d62 100644 --- a/include/id.h +++ b/include/id.h @@ -71,8 +71,7 @@ enum main_id { - ID_APPEND_PROJECT = wxID_HIGHEST, - ID_LOAD_FILE, + ID_LOAD_FILE = wxID_HIGHEST, ID_NEW_BOARD, ID_SAVE_BOARD, ID_SAVE_BOARD_AS, diff --git a/include/lib_tree_model.h b/include/lib_tree_model.h index a97a551014..c2ea06a45c 100644 --- a/include/lib_tree_model.h +++ b/include/lib_tree_model.h @@ -285,6 +285,16 @@ public: */ LIB_TREE_NODE_LIBRARY& AddLib( wxString const& aName, wxString const& aDesc ); + /** + * Remove a library node from the root. + */ + void RemoveLib( wxString const& aName ); + + /** + * Clear the tree + */ + void Clear(); + void UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib, std::function<bool( LIB_TREE_NODE& aNode )>* aFilter ) override; }; diff --git a/include/lib_tree_model_adapter.h b/include/lib_tree_model_adapter.h index 5ed660f0b4..6b57667816 100644 --- a/include/lib_tree_model_adapter.h +++ b/include/lib_tree_model_adapter.h @@ -92,6 +92,8 @@ * - `HasDefaultCompare()` - whether sorted by default */ +#include <project.h> + class APP_SETTINGS_BASE; class TOOL_INTERACTIVE; class EDA_BASE_FRAME; @@ -182,6 +184,13 @@ public: const std::vector<LIB_TREE_ITEM*>& aItemList, bool pinned, bool presorted ); + /** + * Remove the library by name. + * + * @param aNodeName the name of the library to remove + */ + void DoRemoveLibrary( const wxString& aNodeName ); + std::vector<wxString> GetAvailableColumns() const { return m_availableColumns; } std::vector<wxString> GetShownColumns() const { return m_shownColumns; } @@ -390,7 +399,7 @@ protected: unsigned int aCol, wxDataViewItemAttr& aAttr ) const override; - virtual bool isSymbolModel() = 0; + virtual PROJECT::LIB_TYPE_T getLibType() = 0; void resortTree(); diff --git a/include/paths.h b/include/paths.h index 84052eb7c3..3cdafd9614 100644 --- a/include/paths.h +++ b/include/paths.h @@ -67,6 +67,11 @@ public: */ static wxString GetDefaultUserFootprintsPath(); + /** + * Gets the default path we point users to create projects + */ + static wxString GetDefaultUserDesignBlocksPath(); + /** * Gets the default path we point users to create projects */ @@ -98,6 +103,11 @@ public: */ static wxString GetStockFootprintsPath(); + /** + * Gets the stock (install) footprints path + */ + static wxString GetStockDesignBlocksPath(); + /** * Gets the stock (install) 3dmodels path */ diff --git a/include/project.h b/include/project.h index bf40950736..9afcb150b6 100644 --- a/include/project.h +++ b/include/project.h @@ -42,6 +42,7 @@ /// default name for nameless projects #define NAMELESS_PROJECT _( "untitled" ) +class DESIGN_BLOCK_LIB_TABLE; class FP_LIB_TABLE; class SYMBOL_LIBS; class SEARCH_STACK; @@ -75,6 +76,8 @@ public: SYMBOL_LIB_TABLE, SEARCH_STACK, + DESIGN_BLOCK_LIB_TABLE, + COUNT }; @@ -177,8 +180,22 @@ public: */ virtual const wxString SymbolLibTableName() const; - void PinLibrary( const wxString& aLibrary, bool isSymbolLibrary ); - void UnpinLibrary( const wxString& aLibrary, bool isSymbolLibrary ); + /** + * Return the path and file name of this projects design block library table. + */ + virtual const wxString DesignBlockLibTblName() const; + + enum LIB_TYPE_T + { + SYMBOL_LIB, + FOOTPRINT_LIB, + DESIGN_BLOCK_LIB, + + LIB_TYPE_COUNT + }; + + void PinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType ); + void UnpinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType ); virtual PROJECT_FILE& GetProjectFile() const { @@ -277,6 +294,11 @@ public: */ virtual FP_LIB_TABLE* PcbFootprintLibs( KIWAY& aKiway ); + /** + * Return the table of design block libraries. + */ + virtual DESIGN_BLOCK_LIB_TABLE* DesignBlockLibs(); + private: friend class SETTINGS_MANAGER; // so that SM can set project path friend class TEST_NETLISTS_FIXTURE; // TODO(JE) make this not required diff --git a/include/project/project_file.h b/include/project/project_file.h index 7b5371ef31..e4500dbcbe 100644 --- a/include/project/project_file.h +++ b/include/project/project_file.h @@ -123,6 +123,9 @@ public: /// The list of pinned footprint libraries std::vector<wxString> m_PinnedFootprintLibs; + /// The list of pinned design block libraries + std::vector<wxString> m_PinnedDesignBlockLibs; + std::map<wxString, wxString> m_TextVars; /** diff --git a/include/settings/common_settings.h b/include/settings/common_settings.h index c32f6865c3..1fd4d8df61 100644 --- a/include/settings/common_settings.h +++ b/include/settings/common_settings.h @@ -114,6 +114,7 @@ public: bool remember_open_files; std::vector<wxString> pinned_symbol_libs; std::vector<wxString> pinned_fp_libs; + std::vector<wxString> pinned_design_block_libs; }; struct SYSTEM diff --git a/include/settings/kicad_settings.h b/include/settings/kicad_settings.h index 20e6905841..08123c6a2c 100644 --- a/include/settings/kicad_settings.h +++ b/include/settings/kicad_settings.h @@ -39,6 +39,8 @@ public: std::vector<wxString> m_OpenProjects; + wxString m_lastDesignBlockLibDir; + std::vector<std::pair<wxString, wxString>> m_PcmRepositories; wxString m_PcmLastDownloadDir; diff --git a/include/tool/actions.h b/include/tool/actions.h index 1d110842a1..e3209c4282 100644 --- a/include/tool/actions.h +++ b/include/tool/actions.h @@ -217,6 +217,7 @@ public: static TOOL_ACTION configurePaths; static TOOL_ACTION showSymbolLibTable; static TOOL_ACTION showFootprintLibTable; + static TOOL_ACTION showDesignBlockLibTable; static TOOL_ACTION gettingStarted; static TOOL_ACTION help; static TOOL_ACTION about; diff --git a/include/trace_helpers.h b/include/trace_helpers.h index 3eae51bf89..4eb4080662 100644 --- a/include/trace_helpers.h +++ b/include/trace_helpers.h @@ -241,6 +241,13 @@ extern KICOMMON_API const wxChar* const traceGit; */ extern KICOMMON_API const wxChar* const traceEagleIo; +/* + * Flag to enable Design Block O debug tracing. + * + * Use "KICAD_EAGLE_IO" to enable. + */ +extern KICOMMON_API const wxChar* const traceDesignBlocks; + ///@} /** diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h index e7df1c4cc1..796d1be2bf 100644 --- a/include/wildcards_and_files_ext.h +++ b/include/wildcards_and_files_ext.h @@ -176,6 +176,9 @@ public: static const std::string Ipc2581FileExtension; static const std::string WorkbookFileExtension; + static const std::string KiCadDesignBlockLibPathExtension; + static const std::string KiCadDesignBlockPathExtension; + static const std::string PngFileExtension; static const std::string JpegFileExtension; static const std::string TextFileExtension; @@ -246,6 +249,8 @@ public: static wxString DocModulesFileName(); static wxString KiCadFootprintLibFileWildcard(); static wxString KiCadFootprintLibPathWildcard(); + static wxString KiCadDesignBlockLibPathWildcard(); + static wxString KiCadDesignBlockPathWildcard(); static wxString TextFileWildcard(); static wxString ModLegacyExportFileWildcard(); static wxString ErcFileWildcard(); diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt index e34422c9a2..9833eed72f 100644 --- a/kicad/CMakeLists.txt +++ b/kicad/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory( pcm ) include_directories( BEFORE ${INC_BEFORE} ) include_directories( + ${CMAKE_SOURCE_DIR}/common/dialogs ${CMAKE_SOURCE_DIR}/pcbnew ${CMAKE_SOURCE_DIR}/eeschema ${CMAKE_SOURCE_DIR}/kicad/pcm @@ -24,6 +25,8 @@ set( KICAD_SRCS dialogs/dialog_template_selector.cpp dialogs/panel_kicad_launcher_base.cpp dialogs/panel_kicad_launcher.cpp + dialogs/panel_design_block_lib_table_base.cpp + dialogs/panel_design_block_lib_table.cpp files-io.cpp import_proj.cpp import_project.cpp diff --git a/kicad/dialogs/panel_design_block_lib_table.cpp b/kicad/dialogs/panel_design_block_lib_table.cpp new file mode 100644 index 0000000000..e149d365e3 --- /dev/null +++ b/kicad/dialogs/panel_design_block_lib_table.cpp @@ -0,0 +1,1155 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +/* TODO: + +*) After any change to uri, reparse the environment variables. + +*/ + + +#include <set> +#include <wx/dir.h> +#include <wx/log.h> +#include <wx/regex.h> +#include <wx/grid.h> +#include <wx/dirdlg.h> +#include <wx/filedlg.h> +#include <wx/msgdlg.h> + +#include <common.h> +#include <project.h> +#include <env_vars.h> +#include <lib_id.h> +#include <design_block_lib_table.h> +#include <lib_table_lexer.h> +#include <bitmaps.h> +#include <lib_table_grid_tricks.h> +#include <widgets/wx_grid.h> +#include <widgets/std_bitmap_button.h> +#include <confirm.h> +#include <lib_table_grid.h> +#include <wildcards_and_files_ext.h> +#include <pgm_base.h> +#include <env_paths.h> +#include <dialogs/panel_design_block_lib_table.h> +#include <dialogs/dialog_edit_library_tables.h> +#include <dialogs/dialog_plugin_options.h> +#include <kiway.h> +#include <kiway_express.h> +#include <widgets/grid_readonly_text_helpers.h> +#include <widgets/grid_text_button_helpers.h> +#include <settings/settings_manager.h> +#include <settings/kicad_settings.h> +#include <paths.h> +#include <macros.h> + +// clang-format off + +/** + * Container that describes file type info for the add a library options + */ +struct SUPPORTED_FILE_TYPE +{ + 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 + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T m_Plugin; +}; + +// 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( std::vector<std::string> aSearchExtensions, wxString aInitialDir ) : + m_searchExtensions( aSearchExtensions ), m_currentDir( aInitialDir ) + { + } + + virtual wxDirTraverseResult OnFile( const wxString& aFileName ) override + { + wxFileName file( aFileName ); + + for( const std::string& ext : m_searchExtensions ) + { + if( file.GetExt().IsSameAs( ext, false ) ) + m_foundDirs.insert( { m_currentDir, 1 } ); + } + + return wxDIR_CONTINUE; + } + + virtual wxDirTraverseResult OnOpenError( const wxString& aOpenErrorName ) override + { + m_failedDirs.insert( { aOpenErrorName, 1 } ); + return wxDIR_IGNORE; + } + + bool HasDirectoryOpenFailures() { return m_failedDirs.size() > 0; } + + virtual wxDirTraverseResult OnDir( const wxString& aDirName ) override + { + m_currentDir = aDirName; + return wxDIR_CONTINUE; + } + + void GetPaths( wxArrayString& aPathArray ) + { + for( std::pair<const wxString, int>& foundDirsPair : m_foundDirs ) + aPathArray.Add( foundDirsPair.first ); + } + + void GetFailedPaths( wxArrayString& aPathArray ) + { + for( std::pair<const wxString, int>& failedDirsPair : m_failedDirs ) + aPathArray.Add( failedDirsPair.first ); + } + +private: + std::vector<std::string> m_searchExtensions; + wxString m_currentDir; + std::unordered_map<wxString, int> m_foundDirs; + std::unordered_map<wxString, int> m_failedDirs; +}; + + +/** + * This class builds a wxGridTableBase by wrapping an #DESIGN_BLOCK_LIB_TABLE object. + */ +class DESIGN_BLOCK_LIB_TABLE_GRID : public LIB_TABLE_GRID, public DESIGN_BLOCK_LIB_TABLE +{ + friend class PANEL_DESIGN_BLOCK_LIB_TABLE; + friend class DESIGN_BLOCK_GRID_TRICKS; + +protected: + LIB_TABLE_ROW* at( size_t aIndex ) override { return &m_rows.at( aIndex ); } + + size_t size() const override { return m_rows.size(); } + + LIB_TABLE_ROW* makeNewRow() override + { + return dynamic_cast<LIB_TABLE_ROW*>( new DESIGN_BLOCK_LIB_TABLE_ROW ); + } + + LIB_TABLE_ROWS_ITER begin() override { return m_rows.begin(); } + + LIB_TABLE_ROWS_ITER insert( LIB_TABLE_ROWS_ITER aIterator, LIB_TABLE_ROW* aRow ) override + { + return m_rows.insert( aIterator, aRow ); + } + + void push_back( LIB_TABLE_ROW* aRow ) override { m_rows.push_back( aRow ); } + + LIB_TABLE_ROWS_ITER erase( LIB_TABLE_ROWS_ITER aFirst, LIB_TABLE_ROWS_ITER aLast ) override + { + return m_rows.erase( aFirst, aLast ); + } + +public: + DESIGN_BLOCK_LIB_TABLE_GRID( const DESIGN_BLOCK_LIB_TABLE& aTableToEdit ) + { + m_rows = aTableToEdit.m_rows; + } + + void SetValue( int aRow, int aCol, const wxString& aValue ) override + { + wxCHECK( aRow < (int) size(), /* void */ ); + + LIB_TABLE_GRID::SetValue( aRow, aCol, aValue ); + + // If setting a filepath, attempt to auto-detect the format + if( aCol == COL_URI ) + { + LIB_TABLE_ROW* row = at( (size_t) aRow ); + wxString fullURI = row->GetFullURI( true ); + + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T pluginType = + DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( fullURI ); + + if( pluginType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE ) + pluginType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP; + + SetValue( aRow, COL_TYPE, DESIGN_BLOCK_IO_MGR::ShowType( pluginType ) ); + } + } +}; + + +class DESIGN_BLOCK_GRID_TRICKS : public LIB_TABLE_GRID_TRICKS +{ +public: + DESIGN_BLOCK_GRID_TRICKS( DIALOG_EDIT_LIBRARY_TABLES* aParent, WX_GRID* aGrid ) : + LIB_TABLE_GRID_TRICKS( aGrid ), m_dialog( aParent ) + { + } + +protected: + DIALOG_EDIT_LIBRARY_TABLES* m_dialog; + + void optionsEditor( int aRow ) override + { + DESIGN_BLOCK_LIB_TABLE_GRID* tbl = (DESIGN_BLOCK_LIB_TABLE_GRID*) m_grid->GetTable(); + + if( tbl->GetNumberRows() > aRow ) + { + LIB_TABLE_ROW* row = tbl->at( (size_t) aRow ); + const wxString& options = row->GetOptions(); + wxString result = options; + std::map<std::string, UTF8> choices; + + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T pi_type = + DESIGN_BLOCK_IO_MGR::EnumFromStr( row->GetType() ); + IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( pi_type ) ); + pi->GetLibraryOptions( &choices ); + + DIALOG_PLUGIN_OPTIONS dlg( m_dialog, row->GetNickName(), choices, options, &result ); + dlg.ShowModal(); + + if( options != result ) + { + row->SetOptions( result ); + m_grid->Refresh(); + } + } + } + + /// handle specialized clipboard text, with leading "(design_block_lib_table", OR + /// spreadsheet formatted text. + void paste_text( const wxString& cb_text ) override + { + DESIGN_BLOCK_LIB_TABLE_GRID* tbl = (DESIGN_BLOCK_LIB_TABLE_GRID*) m_grid->GetTable(); + size_t ndx = cb_text.find( "(design_block_lib_table" ); + + if( ndx != std::string::npos ) + { + // paste the DESIGN_BLOCK_LIB_TABLE_ROWs of s-expression (design_block_lib_table), starting + // at column 0 regardless of current cursor column. + + STRING_LINE_READER slr( TO_UTF8( cb_text ), wxT( "Clipboard" ) ); + LIB_TABLE_LEXER lexer( &slr ); + DESIGN_BLOCK_LIB_TABLE tmp_tbl; + bool parsed = true; + + try + { + tmp_tbl.Parse( &lexer ); + } + catch( PARSE_ERROR& pe ) + { + DisplayError( m_dialog, pe.What() ); + parsed = false; + } + + if( parsed ) + { + // make sure the table is big enough... + if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() ) + tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() ); + + for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i ) + tbl->m_rows.replace( i, tmp_tbl.At( i ).clone() ); + } + + m_grid->AutoSizeColumns( false ); + } + else + { + // paste spreadsheet formatted text. + GRID_TRICKS::paste_text( cb_text ); + + m_grid->AutoSizeColumns( false ); + } + } +}; + + +PANEL_DESIGN_BLOCK_LIB_TABLE::PANEL_DESIGN_BLOCK_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, + PROJECT* aProject, + DESIGN_BLOCK_LIB_TABLE* aGlobalTable, + const wxString& aGlobalTblPath, + DESIGN_BLOCK_LIB_TABLE* aProjectTable, + const wxString& aProjectTblPath, + const wxString& aProjectBasePath ) : + PANEL_DESIGN_BLOCK_LIB_TABLE_BASE( aParent ), + m_globalTable( aGlobalTable ), m_projectTable( aProjectTable ), m_project( aProject ), + m_projectBasePath( aProjectBasePath ), m_parent( aParent ) +{ + m_global_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aGlobalTable ), true ); + + // add Cut, Copy, and Paste to wxGrids + m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) ); + + populatePluginList(); + + wxArrayString choices; + + for( auto& [fileType, desc] : m_supportedDesignBlockFiles ) + choices.Add( DESIGN_BLOCK_IO_MGR::ShowType( fileType ) ); + + + KICAD_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<KICAD_SETTINGS>(); + + if( cfg->m_lastDesignBlockLibDir.IsEmpty() ) + cfg->m_lastDesignBlockLibDir = PATHS::GetDefaultUserDesignBlocksPath(); + + m_lastProjectLibDir = m_projectBasePath; + + auto autoSizeCol = [&]( WX_GRID* aGrid, int aCol ) + { + int prevWidth = aGrid->GetColSize( aCol ); + + aGrid->AutoSizeColumn( aCol, false ); + aGrid->SetColSize( aCol, std::max( prevWidth, aGrid->GetColSize( aCol ) ) ); + }; + + auto setupGrid = [&]( WX_GRID* aGrid ) + { + // Give a bit more room for wxChoice editors + aGrid->SetDefaultRowSize( aGrid->GetDefaultRowSize() + 4 ); + + // add Cut, Copy, and Paste to wxGrids + aGrid->PushEventHandler( new DESIGN_BLOCK_GRID_TRICKS( m_parent, aGrid ) ); + + aGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); + + wxGridCellAttr* attr; + + attr = new wxGridCellAttr; + attr->SetEditor( new GRID_CELL_PATH_EDITOR( + m_parent, aGrid, &cfg->m_lastDesignBlockLibDir, true, m_projectBasePath, + [this]( WX_GRID* grid, int row ) -> wxString + { + auto* libTable = static_cast<DESIGN_BLOCK_LIB_TABLE_GRID*>( grid->GetTable() ); + auto* tableRow = + static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( libTable->at( row ) ); + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T fileType = tableRow->GetFileType(); + const IO_BASE::IO_FILE_DESC& pluginDesc = + m_supportedDesignBlockFiles.at( fileType ); + + if( pluginDesc.m_IsFile ) + return pluginDesc.FileFilter(); + else + return wxEmptyString; + } ) ); + aGrid->SetColAttr( COL_URI, attr ); + + attr = new wxGridCellAttr; + attr->SetEditor( new wxGridCellChoiceEditor( choices ) ); + aGrid->SetColAttr( COL_TYPE, attr ); + + attr = new wxGridCellAttr; + attr->SetRenderer( new wxGridCellBoolRenderer() ); + attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS + aGrid->SetColAttr( COL_ENABLED, attr ); + + // No visibility control for design block libraries yet; this feature is primarily + // useful for database libraries and it's only implemented for schematic symbols + // at the moment. + aGrid->HideCol( COL_VISIBLE ); + + // all but COL_OPTIONS, which is edited with Option Editor anyways. + autoSizeCol( aGrid, COL_NICKNAME ); + autoSizeCol( aGrid, COL_TYPE ); + autoSizeCol( aGrid, COL_URI ); + autoSizeCol( aGrid, COL_DESCR ); + + // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with + // a currentCell which is sometimes not highlighted. + if( aGrid->GetNumberRows() > 0 ) + aGrid->SelectRow( 0 ); + }; + + setupGrid( m_global_grid ); + + populateEnvironReadOnlyTable(); + + if( aProjectTable ) + { + m_project_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aProjectTable ), true ); + setupGrid( m_project_grid ); + } + else + { + m_pageNdx = 0; + m_notebook->DeletePage( 1 ); + m_project_grid = nullptr; + } + + m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) ); + m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) ); + + // select the last selected page + m_notebook->SetSelection( m_pageNdx ); + m_cur_grid = ( m_pageNdx == 0 ) ? m_global_grid : m_project_grid; + + // for ALT+A handling, we want the initial focus to be on the first selected grid. + m_parent->SetInitialFocus( m_cur_grid ); + + // Configure button logos + m_append_button->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); + m_delete_button->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); + m_move_up_button->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) ); + m_move_down_button->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) ); + m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) ); + + // For aesthetic reasons, we must set the size of m_browseButton to match the other bitmaps + // manually (for instance m_append_button) + Layout(); // Needed at least on MSW to compute the actual buttons sizes, after initializing + // their bitmaps + wxSize buttonSize = m_append_button->GetSize(); + + m_browseButton->SetWidthPadding( 4 ); + m_browseButton->SetMinSize( buttonSize ); + + // Populate the browse library options + wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu(); + + auto joinExts = []( const std::vector<std::string>& aExts ) + { + wxString joined; + for( const std::string& ext : aExts ) + { + if( !joined.empty() ) + joined << wxS( ", " ); + + joined << wxS( "*." ) << ext; + } + + return joined; + }; + + for( auto& [type, desc] : m_supportedDesignBlockFiles ) + { + wxString entryStr = DESIGN_BLOCK_IO_MGR::ShowType( type ); + + if( desc.m_IsFile && !desc.m_FileExtensions.empty() ) + { + entryStr << wxString::Format( wxS( " (%s)" ), joinExts( desc.m_FileExtensions ) ); + } + else if( !desc.m_IsFile && !desc.m_ExtensionsInDir.empty() ) + { + wxString midPart = wxString::Format( _( "folder with %s files" ), + joinExts( desc.m_ExtensionsInDir ) ); + + entryStr << wxString::Format( wxS( " (%s)" ), midPart ); + } + + browseMenu->Append( type, entryStr ); + + browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, + &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, this, type ); + } + + Layout(); + + // This is the button only press for the browse button instead of the menu + m_browseButton->Bind( wxEVT_BUTTON, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, + this ); +} + + +PANEL_DESIGN_BLOCK_LIB_TABLE::~PANEL_DESIGN_BLOCK_LIB_TABLE() +{ + wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu(); + for( auto& [type, desc] : m_supportedDesignBlockFiles ) + { + browseMenu->Unbind( wxEVT_COMMAND_MENU_SELECTED, + &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, this, type ); + } + m_browseButton->Unbind( wxEVT_BUTTON, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, + this ); + + // Delete the GRID_TRICKS. + // Any additional event handlers should be popped before the window is deleted. + m_global_grid->PopEventHandler( true ); + + if( m_project_grid ) + m_project_grid->PopEventHandler( true ); + + m_path_subs_grid->PopEventHandler( true ); +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::populatePluginList() +{ + for( const DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T& type : + { DESIGN_BLOCK_IO_MGR::KICAD_SEXP } ) + { + IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( type ) ); + + if( !pi ) + continue; + + if( const IO_BASE::IO_FILE_DESC& desc = pi->GetLibraryDesc() ) + m_supportedDesignBlockFiles.emplace( type, desc ); + } +} + + +bool PANEL_DESIGN_BLOCK_LIB_TABLE::verifyTables() +{ + wxString msg; + + for( DESIGN_BLOCK_LIB_TABLE_GRID* model : { global_model(), project_model() } ) + { + if( !model ) + continue; + + for( int r = 0; r < model->GetNumberRows(); ) + { + wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim(); + wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim(); + unsigned illegalCh = 0; + + if( !nick || !uri ) + { + if( !nick && !uri ) + msg = _( "A library table row nickname and path cells are empty." ); + else if( !nick ) + msg = _( "A library table row nickname cell is empty." ); + else + msg = _( "A library table row path cell is empty." ); + + wxWindow* topLevelParent = wxGetTopLevelParent( this ); + + wxMessageDialog badCellDlg( topLevelParent, msg, _( "Invalid Row Definition" ), + wxYES_NO | wxCENTER | wxICON_QUESTION | wxYES_DEFAULT ); + badCellDlg.SetExtendedMessage( _( "Empty cells will result in all rows that are " + "invalid to be removed from the table." ) ); + badCellDlg.SetYesNoLabels( + wxMessageDialog::ButtonLabel( _( "Remove Invalid Cells" ) ), + wxMessageDialog::ButtonLabel( _( "Cancel Table Update" ) ) ); + + if( badCellDlg.ShowModal() == wxID_NO ) + return false; + + // Delete the "empty" row, where empty means missing nick or uri. + // This also updates the UI which could be slow, but there should only be a few + // rows to delete, unless the user fell asleep on the Add Row + // button. + model->DeleteRows( r, 1 ); + } + else if( ( illegalCh = LIB_ID::FindIllegalLibraryNameChar( nick ) ) ) + { + msg = wxString::Format( _( "Illegal character '%c' in nickname '%s'." ), illegalCh, + nick ); + + // show the tabbed panel holding the grid we have flunked: + if( model != cur_model() ) + m_notebook->SetSelection( model == global_model() ? 0 : 1 ); + + m_cur_grid->MakeCellVisible( r, 0 ); + m_cur_grid->SetGridCursor( r, 1 ); + + wxWindow* topLevelParent = wxGetTopLevelParent( this ); + + wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) ); + errdlg.ShowModal(); + return false; + } + else + { + // set the trimmed values back into the table so they get saved to disk. + model->SetValue( r, COL_NICKNAME, nick ); + model->SetValue( r, COL_URI, uri ); + + // Make sure to not save a hidden flag + model->SetValue( r, COL_VISIBLE, wxS( "1" ) ); + + ++r; // this row was OK. + } + } + } + + // check for duplicate nickNames, separately in each table. + for( DESIGN_BLOCK_LIB_TABLE_GRID* model : { global_model(), project_model() } ) + { + if( !model ) + continue; + + for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 ) + { + wxString nick1 = model->GetValue( r1, COL_NICKNAME ); + + for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 ) + { + wxString nick2 = model->GetValue( r2, COL_NICKNAME ); + + if( nick1 == nick2 ) + { + msg = wxString::Format( _( "Multiple libraries cannot share the same " + "nickname ('%s')." ), + nick1 ); + + // show the tabbed panel holding the grid we have flunked: + if( model != cur_model() ) + m_notebook->SetSelection( model == global_model() ? 0 : 1 ); + + // go to the lower of the two rows, it is technically the duplicate: + m_cur_grid->MakeCellVisible( r2, 0 ); + m_cur_grid->SetGridCursor( r2, 1 ); + + wxWindow* topLevelParent = wxGetTopLevelParent( this ); + + wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) ); + errdlg.ShowModal(); + return false; + } + } + } + } + + return true; +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::OnUpdateUI( wxUpdateUIEvent& event ) +{ + m_pageNdx = (unsigned) std::max( 0, m_notebook->GetSelection() ); + m_cur_grid = m_pageNdx == 0 ? m_global_grid : m_project_grid; +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::appendRowHandler( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + if( m_cur_grid->AppendRows( 1 ) ) + { + int last_row = m_cur_grid->GetNumberRows() - 1; + + // wx documentation is wrong, SetGridCursor does not make visible. + m_cur_grid->MakeCellVisible( last_row, COL_ENABLED ); + m_cur_grid->SetGridCursor( last_row, COL_NICKNAME ); + m_cur_grid->EnableCellEditControl( true ); + m_cur_grid->ShowCellEditControl(); + } +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::deleteRowHandler( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + int curRow = m_cur_grid->GetGridCursorRow(); + int curCol = m_cur_grid->GetGridCursorCol(); + + // In a wxGrid, collect rows that have a selected cell, or are selected + // It is not so easy: it depends on the way the selection was made. + // Here, we collect rows selected by clicking on a row label, and rows that contain any + // previously-selected cells. + // If no candidate, just delete the row with the grid cursor. + wxArrayInt selectedRows = m_cur_grid->GetSelectedRows(); + wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells(); + wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft(); + wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight(); + + // Add all row having cell selected to list: + for( unsigned ii = 0; ii < cells.GetCount(); ii++ ) + selectedRows.Add( cells[ii].GetRow() ); + + // Handle block selection + if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() ) + { + for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i ) + selectedRows.Add( i ); + } + + // Use the row having the grid cursor only if we have no candidate: + if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 ) + selectedRows.Add( m_cur_grid->GetGridCursorRow() ); + + if( selectedRows.size() == 0 ) + { + wxBell(); + return; + } + + std::sort( selectedRows.begin(), selectedRows.end() ); + + // Remove selected rows (note: a row can be stored more than once in list) + int last_row = -1; + + // Needed to avoid a wxWidgets alert if the row to delete is the last row + // at least on wxMSW 3.2 + m_cur_grid->ClearSelection(); + + for( int ii = selectedRows.GetCount() - 1; ii >= 0; ii-- ) + { + int row = selectedRows[ii]; + + if( row != last_row ) + { + last_row = row; + m_cur_grid->DeleteRows( row, 1 ); + } + } + + if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 ) + m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol ); +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::moveUpHandler( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + DESIGN_BLOCK_LIB_TABLE_GRID* tbl = cur_model(); + int curRow = m_cur_grid->GetGridCursorRow(); + + // @todo: add multiple selection moves. + if( curRow >= 1 ) + { + boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me = + tbl->m_rows.release( tbl->m_rows.begin() + curRow ); + + --curRow; + tbl->m_rows.insert( tbl->m_rows.begin() + curRow, move_me.release() ); + + if( tbl->GetView() ) + { + // Update the wxGrid + wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 ); + tbl->GetView()->ProcessTableMessage( msg ); + } + + m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() ); + m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() ); + } +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::moveDownHandler( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + DESIGN_BLOCK_LIB_TABLE_GRID* tbl = cur_model(); + int curRow = m_cur_grid->GetGridCursorRow(); + + // @todo: add multiple selection moves. + if( unsigned( curRow + 1 ) < tbl->m_rows.size() ) + { + boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me = + tbl->m_rows.release( tbl->m_rows.begin() + curRow ); + + ++curRow; + tbl->m_rows.insert( tbl->m_rows.begin() + curRow, move_me.release() ); + + if( tbl->GetView() ) + { + // Update the wxGrid + wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 ); + tbl->GetView()->ProcessTableMessage( msg ); + } + + m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() ); + m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() ); + } +} + + +// @todo refactor this function into single location shared with PANEL_SYM_LIB_TABLE +void PANEL_DESIGN_BLOCK_LIB_TABLE::onMigrateLibraries( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + wxArrayInt selectedRows = m_cur_grid->GetSelectedRows(); + + if( selectedRows.empty() && m_cur_grid->GetGridCursorRow() >= 0 ) + selectedRows.push_back( m_cur_grid->GetGridCursorRow() ); + + wxArrayInt rowsToMigrate; + wxString kicadType = DESIGN_BLOCK_IO_MGR::ShowType( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ); + wxString msg; + + for( int row : selectedRows ) + { + if( m_cur_grid->GetCellValue( row, COL_TYPE ) != kicadType ) + rowsToMigrate.push_back( row ); + } + + if( rowsToMigrate.size() <= 0 ) + { + wxMessageBox( wxString::Format( _( "Select one or more rows containing libraries " + "to save as current KiCad format." ) ) ); + return; + } + else + { + if( rowsToMigrate.size() == 1 ) + { + msg.Printf( _( "Save '%s' as current KiCad format " + "and replace entry in table?" ), + m_cur_grid->GetCellValue( rowsToMigrate[0], COL_NICKNAME ) ); + } + else + { + msg.Printf( _( "Save %d libraries as current KiCad format " + "and replace entries in table?" ), + (int) rowsToMigrate.size() ); + } + + if( !IsOK( m_parent, msg ) ) + return; + } + + for( int row : rowsToMigrate ) + { + wxString libName = m_cur_grid->GetCellValue( row, COL_NICKNAME ); + wxString relPath = m_cur_grid->GetCellValue( row, COL_URI ); + wxString resolvedPath = ExpandEnvVarSubstitutions( relPath, m_project ); + wxFileName legacyLib( resolvedPath ); + + if( !legacyLib.Exists() ) + { + msg.Printf( _( "Library '%s' not found." ), relPath ); + DisplayErrorMessage( wxGetTopLevelParent( this ), msg ); + continue; + } + + wxFileName newLib( resolvedPath ); + newLib.AppendDir( newLib.GetName() + "." + FILEEXT::KiCadDesignBlockLibPathExtension ); + newLib.SetName( "" ); + newLib.ClearExt(); + + if( newLib.DirExists() ) + { + msg.Printf( _( "Folder '%s' already exists. Do you want overwrite any existing design " + "blocks?" ), + newLib.GetFullPath() ); + + switch( wxMessageBox( msg, _( "Migrate Library" ), + wxYES_NO | wxCANCEL | wxICON_QUESTION, m_parent ) ) + { + case wxYES: break; + case wxNO: continue; + case wxCANCEL: return; + } + } + + wxString options = m_cur_grid->GetCellValue( row, COL_OPTIONS ); + std::unique_ptr<std::map<std::string, UTF8>> props( + LIB_TABLE::ParseOptions( options.ToStdString() ) ); + + if( DESIGN_BLOCK_IO_MGR::ConvertLibrary( props.get(), legacyLib.GetFullPath(), + newLib.GetFullPath() ) ) + { + relPath = + NormalizePath( newLib.GetFullPath(), &Pgm().GetLocalEnvVariables(), m_project ); + + // Do not use the project path in the global library table. This will almost + // assuredly be wrong for a different project. + if( m_cur_grid == m_global_grid && relPath.Contains( "${KIPRJMOD}" ) ) + relPath = newLib.GetFullPath(); + + m_cur_grid->SetCellValue( row, COL_URI, relPath ); + m_cur_grid->SetCellValue( row, COL_TYPE, kicadType ); + } + else + { + msg.Printf( _( "Failed to save design block library file '%s'." ), + newLib.GetFullPath() ); + DisplayErrorMessage( wxGetTopLevelParent( this ), msg ); + } + } +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event ) +{ + if( !m_cur_grid->CommitPendingChanges() ) + return; + + DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T fileType = DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE; + + // We are bound both to the menu and button with this one handler + // So we must set the file type based on it + if( event.GetEventType() == wxEVT_BUTTON ) + { + // Let's default to adding a kicad design block file for just the design block + fileType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP; + } + else + { + fileType = static_cast<DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T>( event.GetId() ); + } + + if( fileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE ) + { + wxLogWarning( wxT( "File type selection event received but could not find the file type " + "in the table" ) ); + return; + } + + const IO_BASE::IO_FILE_DESC& fileDesc = m_supportedDesignBlockFiles.at( fileType ); + KICAD_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<KICAD_SETTINGS>(); + + wxString title = + wxString::Format( _( "Select %s Library" ), DESIGN_BLOCK_IO_MGR::ShowType( fileType ) ); + wxString openDir = cfg->m_lastDesignBlockLibDir; + + if( m_cur_grid == m_project_grid ) + openDir = m_lastProjectLibDir; + + wxArrayString files; + + wxWindow* topLevelParent = wxGetTopLevelParent( this ); + + if( fileDesc.m_IsFile ) + { + wxFileDialog dlg( topLevelParent, title, openDir, wxEmptyString, fileDesc.FileFilter(), + wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE ); + + int result = dlg.ShowModal(); + + if( result == wxID_CANCEL ) + return; + + dlg.GetPaths( files ); + + if( m_cur_grid == m_global_grid ) + cfg->m_lastDesignBlockLibDir = dlg.GetDirectory(); + else + m_lastProjectLibDir = dlg.GetDirectory(); + } + else + { + wxDirDialog dlg( topLevelParent, title, openDir, + wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST | wxDD_MULTIPLE ); + + int result = dlg.ShowModal(); + + if( result == wxID_CANCEL ) + return; + + dlg.GetPaths( files ); + + if( !files.IsEmpty() ) + { + wxFileName first( files.front() ); + + if( m_cur_grid == m_global_grid ) + cfg->m_lastDesignBlockLibDir = first.GetPath(); + else + m_lastProjectLibDir = first.GetPath(); + } + } + + // Drop the last directory if the path is a .pretty folder + if( cfg->m_lastDesignBlockLibDir.EndsWith( FILEEXT::KiCadDesignBlockLibPathExtension ) ) + cfg->m_lastDesignBlockLibDir = + cfg->m_lastDesignBlockLibDir.BeforeLast( wxFileName::GetPathSeparator() ); + + const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables(); + bool addDuplicates = false; + bool applyToAll = false; + wxString warning = _( "Warning: Duplicate Nicknames" ); + wxString msg = _( "A library nicknamed '%s' already exists." ); + wxString detailedMsg = _( "One of the nicknames will need to be changed after " + "adding this library." ); + + for( const wxString& filePath : files ) + { + wxFileName fn( filePath ); + wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), true ); + bool doAdd = true; + + if( fileType == DESIGN_BLOCK_IO_MGR::KICAD_SEXP + && fn.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension ) + nickname = LIB_ID::FixIllegalChars( fn.GetFullName(), true ).wx_str(); + + if( cur_model()->ContainsNickname( nickname ) ) + { + if( !applyToAll ) + { + // The cancel button adds the library to the table anyway + addDuplicates = OKOrCancelDialog( wxGetTopLevelParent( this ), warning, + wxString::Format( msg, nickname ), detailedMsg, + _( "Skip" ), _( "Add Anyway" ), &applyToAll ) + == wxID_CANCEL; + } + + doAdd = addDuplicates; + } + + if( doAdd && m_cur_grid->AppendRows( 1 ) ) + { + int last_row = m_cur_grid->GetNumberRows() - 1; + + m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname ); + + m_cur_grid->SetCellValue( last_row, COL_TYPE, + DESIGN_BLOCK_IO_MGR::ShowType( fileType ) ); + + // try to use path normalized to an environmental variable or project path + wxString path = NormalizePath( filePath, &envVars, m_projectBasePath ); + + // Do not use the project path in the global library table. This will almost + // assuredly be wrong for a different project. + if( m_pageNdx == 0 && path.Contains( wxT( "${KIPRJMOD}" ) ) ) + path = fn.GetFullPath(); + + m_cur_grid->SetCellValue( last_row, COL_URI, path ); + } + } + + if( !files.IsEmpty() ) + { + int new_row = m_cur_grid->GetNumberRows() - 1; + m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() ); + m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() ); + } +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::adjustPathSubsGridColumns( int aWidth ) +{ + // Account for scroll bars + aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x ); + + m_path_subs_grid->AutoSizeColumn( 0 ); + m_path_subs_grid->SetColSize( 0, std::max( 72, m_path_subs_grid->GetColSize( 0 ) ) ); + m_path_subs_grid->SetColSize( 1, std::max( 120, aWidth - m_path_subs_grid->GetColSize( 0 ) ) ); +} + + +void PANEL_DESIGN_BLOCK_LIB_TABLE::onSizeGrid( wxSizeEvent& event ) +{ + adjustPathSubsGridColumns( event.GetSize().GetX() ); + + event.Skip(); +} + + +bool PANEL_DESIGN_BLOCK_LIB_TABLE::TransferDataFromWindow() +{ + if( !m_cur_grid->CommitPendingChanges() ) + return false; + + if( verifyTables() ) + { + if( *global_model() != *m_globalTable ) + { + m_parent->m_GlobalTableChanged = true; + + m_globalTable->TransferRows( global_model()->m_rows ); + } + + if( project_model() && *project_model() != *m_projectTable ) + { + m_parent->m_ProjectTableChanged = true; + + m_projectTable->TransferRows( project_model()->m_rows ); + } + + return true; + } + + return false; +} + + +/// Populate the readonly environment variable table with names and values +/// by examining all the full_uri columns. +void PANEL_DESIGN_BLOCK_LIB_TABLE::populateEnvironReadOnlyTable() +{ + wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED ); + wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required. + + std::set<wxString> unique; + + // clear the table + m_path_subs_grid->ClearRows(); + + for( DESIGN_BLOCK_LIB_TABLE_GRID* tbl : { global_model(), project_model() } ) + { + if( !tbl ) + continue; + + for( int row = 0; row < tbl->GetNumberRows(); ++row ) + { + wxString uri = tbl->GetValue( row, COL_URI ); + + while( re.Matches( uri ) ) + { + wxString envvar = re.GetMatch( uri, 2 ); + + // if not ${...} form then must be $(...) + if( envvar.IsEmpty() ) + envvar = re.GetMatch( uri, 4 ); + + // ignore duplicates + unique.insert( envvar ); + + // delete the last match and search again + uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString ); + } + } + } + + // Make sure this special environment variable shows up even if it was + // not used yet. It is automatically set by KiCad to the directory holding + // the current project. + unique.insert( PROJECT_VAR_NAME ); + unique.insert( DESIGN_BLOCK_LIB_TABLE::GlobalPathEnvVariableName() ); + + // This special environment variable is used to locate 3d shapes + unique.insert( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) ); + + for( const wxString& evName : unique ) + { + int row = m_path_subs_grid->GetNumberRows(); + m_path_subs_grid->AppendRows( 1 ); + + m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) ); + m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() ); + + wxString evValue; + wxGetEnv( evName, &evValue ); + m_path_subs_grid->SetCellValue( row, 1, evValue ); + m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() ); + } + + // No combobox editors here, but it looks better if its consistent with the other + // grids in the dialog. + m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 ); + + adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() ); +} + +//-----</event handlers>--------------------------------- + + +size_t PANEL_DESIGN_BLOCK_LIB_TABLE::m_pageNdx = 0; diff --git a/kicad/dialogs/panel_design_block_lib_table.h b/kicad/dialogs/panel_design_block_lib_table.h new file mode 100644 index 0000000000..ded14ccea8 --- /dev/null +++ b/kicad/dialogs/panel_design_block_lib_table.h @@ -0,0 +1,106 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef PANEL_DESIGN_BLOCK_LIB_TABLE_H +#define PANEL_DESIGN_BLOCK_LIB_TABLE_H + +#include <dialogs/dialog_edit_library_tables.h> +#include <dialogs/panel_design_block_lib_table_base.h> +#include <widgets/wx_grid.h> +#include <design_block_io.h> + +class DESIGN_BLOCK_LIB_TABLE; +class DESIGN_BLOCK_LIB_TABLE_GRID; +class PROJECT; + + +/** + * Dialog to show and edit symbol library tables. + */ +class PANEL_DESIGN_BLOCK_LIB_TABLE : public PANEL_DESIGN_BLOCK_LIB_TABLE_BASE +{ +public: + PANEL_DESIGN_BLOCK_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, PROJECT* aProject, + DESIGN_BLOCK_LIB_TABLE* aGlobalTable, + const wxString& aGlobalTblPath, + DESIGN_BLOCK_LIB_TABLE* aProjectTable, + const wxString& aProjectTblPath, + const wxString& aProjectBasePath ); + ~PANEL_DESIGN_BLOCK_LIB_TABLE() override; + +private: + bool TransferDataFromWindow() override; + + /** + * Trim important fields, removes blank row entries, and checks for duplicates. + * + * @return bool - true if tables are OK, else false. + */ + bool verifyTables(); + + void OnUpdateUI( wxUpdateUIEvent& event ) override; + void appendRowHandler( wxCommandEvent& event ) override; + void browseLibrariesHandler( wxCommandEvent& event ); + void deleteRowHandler( wxCommandEvent& event ) override; + void moveUpHandler( wxCommandEvent& event ) override; + void moveDownHandler( wxCommandEvent& event ) override; + void onMigrateLibraries( wxCommandEvent& event ) override; + void onSizeGrid( wxSizeEvent& event ) override; + + void adjustPathSubsGridColumns( int aWidth ); + + /// Populate the readonly environment variable table with names and values + /// by examining all the full_uri columns. + void populateEnvironReadOnlyTable(); + void populatePluginList(); + + DESIGN_BLOCK_LIB_TABLE_GRID* global_model() const + { + return (DESIGN_BLOCK_LIB_TABLE_GRID*) m_global_grid->GetTable(); + } + + DESIGN_BLOCK_LIB_TABLE_GRID* project_model() const + { + return m_project_grid ? (DESIGN_BLOCK_LIB_TABLE_GRID*) m_project_grid->GetTable() : nullptr; + } + + DESIGN_BLOCK_LIB_TABLE_GRID* cur_model() const + { + return (DESIGN_BLOCK_LIB_TABLE_GRID*) m_cur_grid->GetTable(); + } + + // caller's tables are modified only on OK button and successful verification. + DESIGN_BLOCK_LIB_TABLE* m_globalTable; + DESIGN_BLOCK_LIB_TABLE* m_projectTable; + PROJECT* m_project; + wxString m_projectBasePath; + + DIALOG_EDIT_LIBRARY_TABLES* m_parent; + + WX_GRID* m_cur_grid; // changed based on tab choice + static size_t m_pageNdx; // Remember last notebook page selected during a session + + //< Transient (unsaved) last browsed folder when adding a project level library. + wxString m_lastProjectLibDir; + + std::map<DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T, IO_BASE::IO_FILE_DESC> + m_supportedDesignBlockFiles; +}; + +#endif // PANEL_DESIGN_BLOCK_LIB_TABLE_H diff --git a/kicad/dialogs/panel_design_block_lib_table_base.cpp b/kicad/dialogs/panel_design_block_lib_table_base.cpp new file mode 100644 index 0000000000..3f55abd419 --- /dev/null +++ b/kicad/dialogs/panel_design_block_lib_table_base.cpp @@ -0,0 +1,227 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "widgets/std_bitmap_button.h" +#include "widgets/wx_grid.h" + +#include "panel_design_block_lib_table_base.h" + +/////////////////////////////////////////////////////////////////////////// + +PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::PANEL_DESIGN_BLOCK_LIB_TABLE_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name ) +{ + wxBoxSizer* bMainSizer; + bMainSizer = new wxBoxSizer( wxVERTICAL ); + + m_notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + m_global_panel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* m_global_sizer; + m_global_sizer = new wxBoxSizer( wxVERTICAL ); + + m_global_grid = new WX_GRID( m_global_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + // Grid + m_global_grid->CreateGrid( 1, 7 ); + m_global_grid->EnableEditing( true ); + m_global_grid->EnableGridLines( true ); + m_global_grid->EnableDragGridSize( false ); + m_global_grid->SetMargins( 0, 0 ); + + // Columns + m_global_grid->SetColSize( 0, 48 ); + m_global_grid->SetColSize( 1, 48 ); + m_global_grid->SetColSize( 2, 100 ); + m_global_grid->SetColSize( 3, 240 ); + m_global_grid->SetColSize( 4, 100 ); + m_global_grid->SetColSize( 5, 80 ); + m_global_grid->SetColSize( 6, 240 ); + m_global_grid->EnableDragColMove( false ); + m_global_grid->EnableDragColSize( true ); + m_global_grid->SetColLabelValue( 0, _("Active") ); + m_global_grid->SetColLabelValue( 1, _("Visible") ); + m_global_grid->SetColLabelValue( 2, _("Nickname") ); + m_global_grid->SetColLabelValue( 3, _("Library Path") ); + m_global_grid->SetColLabelValue( 4, _("Library Format") ); + m_global_grid->SetColLabelValue( 5, _("Options") ); + m_global_grid->SetColLabelValue( 6, _("Description") ); + m_global_grid->SetColLabelSize( 22 ); + m_global_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Rows + m_global_grid->EnableDragRowSize( false ); + m_global_grid->SetRowLabelSize( 0 ); + m_global_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Label Appearance + + // Cell Defaults + m_global_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER ); + m_global_sizer->Add( m_global_grid, 1, wxALL|wxEXPAND, 5 ); + + + m_global_panel->SetSizer( m_global_sizer ); + m_global_panel->Layout(); + m_global_sizer->Fit( m_global_panel ); + m_notebook->AddPage( m_global_panel, _("Global Libraries"), true ); + m_project_panel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* m_project_sizer; + m_project_sizer = new wxBoxSizer( wxVERTICAL ); + + m_project_grid = new WX_GRID( m_project_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + // Grid + m_project_grid->CreateGrid( 1, 7 ); + m_project_grid->EnableEditing( true ); + m_project_grid->EnableGridLines( true ); + m_project_grid->EnableDragGridSize( false ); + m_project_grid->SetMargins( 0, 0 ); + + // Columns + m_project_grid->SetColSize( 0, 48 ); + m_project_grid->SetColSize( 1, 48 ); + m_project_grid->SetColSize( 2, 100 ); + m_project_grid->SetColSize( 3, 240 ); + m_project_grid->SetColSize( 4, 100 ); + m_project_grid->SetColSize( 5, 80 ); + m_project_grid->SetColSize( 6, 240 ); + m_project_grid->EnableDragColMove( false ); + m_project_grid->EnableDragColSize( true ); + m_project_grid->SetColLabelValue( 0, _("Active") ); + m_project_grid->SetColLabelValue( 1, _("Visible") ); + m_project_grid->SetColLabelValue( 2, _("Nickname") ); + m_project_grid->SetColLabelValue( 3, _("Library Path") ); + m_project_grid->SetColLabelValue( 4, _("LIbrary Format") ); + m_project_grid->SetColLabelValue( 5, _("Options") ); + m_project_grid->SetColLabelValue( 6, _("Description") ); + m_project_grid->SetColLabelSize( 22 ); + m_project_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Rows + m_project_grid->EnableDragRowSize( false ); + m_project_grid->SetRowLabelSize( 0 ); + m_project_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Label Appearance + + // Cell Defaults + m_project_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER ); + m_project_sizer->Add( m_project_grid, 1, wxALL|wxEXPAND, 5 ); + + + m_project_panel->SetSizer( m_project_sizer ); + m_project_panel->Layout(); + m_project_sizer->Fit( m_project_panel ); + m_notebook->AddPage( m_project_panel, _("Project Specific Libraries"), false ); + + bMainSizer->Add( m_notebook, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 ); + + wxBoxSizer* bButtonsSizer; + bButtonsSizer = new wxBoxSizer( wxHORIZONTAL ); + + m_append_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 ); + m_append_button->SetToolTip( _("Add empty row to table") ); + + bButtonsSizer->Add( m_append_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + m_browseButton = new SPLIT_BUTTON( this, wxID_ANY, _( "Add Existing" ), wxDefaultPosition ); + m_browseButton->SetToolTip( _("Add Existing") ); + + bButtonsSizer->Add( m_browseButton, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + m_move_up_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 ); + m_move_up_button->SetToolTip( _("Move up") ); + + bButtonsSizer->Add( m_move_up_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + m_move_down_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 ); + m_move_down_button->SetToolTip( _("Move down") ); + + bButtonsSizer->Add( m_move_down_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bButtonsSizer->Add( 20, 0, 0, wxEXPAND, 5 ); + + m_delete_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 ); + m_delete_button->SetToolTip( _("Remove library from table") ); + + bButtonsSizer->Add( m_delete_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + + + bButtonsSizer->Add( 20, 0, 1, wxEXPAND, 5 ); + + m_migrate_libs_button = new wxButton( this, wxID_ANY, _("Migrate Libraries"), wxDefaultPosition, wxDefaultSize, 0 ); + bButtonsSizer->Add( m_migrate_libs_button, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + + bMainSizer->Add( bButtonsSizer, 0, wxEXPAND|wxALL, 8 ); + + wxStaticText* stPathsLabel; + stPathsLabel = new wxStaticText( this, wxID_ANY, _("Available path substitutions:"), wxDefaultPosition, wxDefaultSize, 0 ); + stPathsLabel->Wrap( -1 ); + bMainSizer->Add( stPathsLabel, 0, wxTOP|wxRIGHT|wxLEFT|wxEXPAND, 8 ); + + + bMainSizer->Add( 0, 2, 0, wxEXPAND, 5 ); + + m_path_subs_grid = new WX_GRID( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); + + // Grid + m_path_subs_grid->CreateGrid( 1, 2 ); + m_path_subs_grid->EnableEditing( false ); + m_path_subs_grid->EnableGridLines( true ); + m_path_subs_grid->EnableDragGridSize( false ); + m_path_subs_grid->SetMargins( 0, 0 ); + + // Columns + m_path_subs_grid->SetColSize( 0, 150 ); + m_path_subs_grid->SetColSize( 1, 500 ); + m_path_subs_grid->AutoSizeColumns(); + m_path_subs_grid->EnableDragColMove( false ); + m_path_subs_grid->EnableDragColSize( true ); + m_path_subs_grid->SetColLabelSize( 0 ); + m_path_subs_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Rows + m_path_subs_grid->EnableDragRowSize( true ); + m_path_subs_grid->SetRowLabelSize( 0 ); + m_path_subs_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); + + // Label Appearance + + // Cell Defaults + m_path_subs_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER ); + m_path_subs_grid->SetToolTip( _("This is a read-only table which shows pertinent environment variables.") ); + + bMainSizer->Add( m_path_subs_grid, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + + this->SetSizer( bMainSizer ); + this->Layout(); + bMainSizer->Fit( this ); + + // Connect Events + this->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::OnUpdateUI ) ); + m_append_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::appendRowHandler ), NULL, this ); + m_move_up_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveUpHandler ), NULL, this ); + m_move_down_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveDownHandler ), NULL, this ); + m_delete_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::deleteRowHandler ), NULL, this ); + m_migrate_libs_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onMigrateLibraries ), NULL, this ); + m_path_subs_grid->Connect( wxEVT_SIZE, wxSizeEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onSizeGrid ), NULL, this ); +} + +PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::~PANEL_DESIGN_BLOCK_LIB_TABLE_BASE() +{ + // Disconnect Events + this->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::OnUpdateUI ) ); + m_append_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::appendRowHandler ), NULL, this ); + m_move_up_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveUpHandler ), NULL, this ); + m_move_down_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveDownHandler ), NULL, this ); + m_delete_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::deleteRowHandler ), NULL, this ); + m_migrate_libs_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onMigrateLibraries ), NULL, this ); + m_path_subs_grid->Disconnect( wxEVT_SIZE, wxSizeEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onSizeGrid ), NULL, this ); + +} diff --git a/kicad/dialogs/panel_design_block_lib_table_base.fbp b/kicad/dialogs/panel_design_block_lib_table_base.fbp new file mode 100644 index 0000000000..748097e474 --- /dev/null +++ b/kicad/dialogs/panel_design_block_lib_table_base.fbp @@ -0,0 +1,1048 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<wxFormBuilder_Project> + <FileVersion major="1" minor="16" /> + <object class="Project" expanded="1"> + <property name="class_decoration"></property> + <property name="code_generation">C++</property> + <property name="disconnect_events">1</property> + <property name="disconnect_mode">source_name</property> + <property name="disconnect_php_events">0</property> + <property name="disconnect_python_events">0</property> + <property name="embedded_files_path">res</property> + <property name="encoding">UTF-8</property> + <property name="event_generation">connect</property> + <property name="file">panel_design_block_lib_table_base</property> + <property name="first_id">1000</property> + <property name="help_provider">none</property> + <property name="image_path_wrapper_function_name"></property> + <property name="indent_with_spaces"></property> + <property name="internationalize">1</property> + <property name="name">panel_design_block_lib_table</property> + <property name="namespace"></property> + <property name="path">.</property> + <property name="precompiled_header"></property> + <property name="relative_path">1</property> + <property name="skip_lua_events">1</property> + <property name="skip_php_events">1</property> + <property name="skip_python_events">1</property> + <property name="ui_table">UI</property> + <property name="use_array_enum">0</property> + <property name="use_enum">0</property> + <property name="use_microsoft_bom">0</property> + <object class="Panel" expanded="1"> + <property name="aui_managed">0</property> + <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property> + <property name="bg"></property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="enabled">1</property> + <property name="event_handler">impl_virtual</property> + <property name="fg"></property> + <property name="font"></property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="maximum_size"></property> + <property name="minimum_size"></property> + <property name="name">PANEL_DESIGN_BLOCK_LIB_TABLE_BASE</property> + <property name="pos"></property> + <property name="size">-1,-1</property> + <property name="subclass">; forward_declare</property> + <property name="tooltip"></property> + <property name="two_step_creation">0</property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style">wxTAB_TRAVERSAL</property> + <event name="OnUpdateUI">OnUpdateUI</event> + <object class="wxBoxSizer" expanded="1"> + <property name="minimum_size"></property> + <property name="name">bMainSizer</property> + <property name="orient">wxVERTICAL</property> + <property name="permission">none</property> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxEXPAND|wxRIGHT|wxLEFT</property> + <property name="proportion">1</property> + <object class="wxNotebook" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmapsize"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_notebook</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size"></property> + <property name="style"></property> + <property name="subclass">; ; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <object class="notebookpage" expanded="1"> + <property name="bitmap"></property> + <property name="label">Global Libraries</property> + <property name="select">1</property> + <object class="wxPanel" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">0</property> + <property name="context_help"></property> + <property name="context_menu">0</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_global_panel</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">0</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size"></property> + <property name="subclass"></property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style">wxTAB_TRAVERSAL</property> + <object class="wxBoxSizer" expanded="1"> + <property name="minimum_size"></property> + <property name="name">m_global_sizer</property> + <property name="orient">wxVERTICAL</property> + <property name="permission">none</property> + <object class="sizeritem" expanded="0"> + <property name="border">5</property> + <property name="flag">wxALL|wxEXPAND</property> + <property name="proportion">1</property> + <object class="wxGrid" expanded="0"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="autosize_cols">0</property> + <property name="autosize_rows">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="cell_bg"></property> + <property name="cell_font"></property> + <property name="cell_horiz_alignment">wxALIGN_LEFT</property> + <property name="cell_text"></property> + <property name="cell_vert_alignment">wxALIGN_CENTER</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="col_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="col_label_size">22</property> + <property name="col_label_values">"Active" "Visible" "Nickname" "Library Path" "Library Format" "Options" "Description"</property> + <property name="col_label_vert_alignment">wxALIGN_CENTER</property> + <property name="cols">7</property> + <property name="column_sizes">48,48,100,240,100,80,240</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">1</property> + <property name="docking">Left</property> + <property name="drag_col_move">0</property> + <property name="drag_col_size">1</property> + <property name="drag_grid_size">0</property> + <property name="drag_row_size">0</property> + <property name="editing">1</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">0</property> + <property name="font"></property> + <property name="grid_line_color"></property> + <property name="grid_lines">1</property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label_bg"></property> + <property name="label_font"></property> + <property name="label_text"></property> + <property name="margin_height">0</property> + <property name="margin_width">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">0</property> + <property name="name">m_global_grid</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Fixed</property> + <property name="row_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="row_label_size">0</property> + <property name="row_label_values"></property> + <property name="row_label_vert_alignment">wxALIGN_CENTER</property> + <property name="row_sizes"></property> + <property name="rows">1</property> + <property name="show">1</property> + <property name="size"></property> + <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + </object> + </object> + </object> + </object> + </object> + <object class="notebookpage" expanded="1"> + <property name="bitmap"></property> + <property name="label">Project Specific Libraries</property> + <property name="select">0</property> + <object class="wxPanel" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">0</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_project_panel</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size"></property> + <property name="subclass"></property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style">wxTAB_TRAVERSAL</property> + <object class="wxBoxSizer" expanded="1"> + <property name="minimum_size"></property> + <property name="name">m_project_sizer</property> + <property name="orient">wxVERTICAL</property> + <property name="permission">none</property> + <object class="sizeritem" expanded="0"> + <property name="border">5</property> + <property name="flag">wxALL|wxEXPAND</property> + <property name="proportion">1</property> + <object class="wxGrid" expanded="0"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="autosize_cols">0</property> + <property name="autosize_rows">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="cell_bg"></property> + <property name="cell_font"></property> + <property name="cell_horiz_alignment">wxALIGN_LEFT</property> + <property name="cell_text"></property> + <property name="cell_vert_alignment">wxALIGN_CENTER</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="col_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="col_label_size">22</property> + <property name="col_label_values">"Active" "Visible" "Nickname" "Library Path" "LIbrary Format" "Options" "Description"</property> + <property name="col_label_vert_alignment">wxALIGN_CENTER</property> + <property name="cols">7</property> + <property name="column_sizes">48,48,100,240,100,80,240</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">1</property> + <property name="docking">Left</property> + <property name="drag_col_move">0</property> + <property name="drag_col_size">1</property> + <property name="drag_grid_size">0</property> + <property name="drag_row_size">0</property> + <property name="editing">1</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">0</property> + <property name="font"></property> + <property name="grid_line_color"></property> + <property name="grid_lines">1</property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label_bg"></property> + <property name="label_font"></property> + <property name="label_text"></property> + <property name="margin_height">0</property> + <property name="margin_width">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">0</property> + <property name="name">m_project_grid</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Fixed</property> + <property name="row_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="row_label_size">0</property> + <property name="row_label_values"></property> + <property name="row_label_vert_alignment">wxALIGN_CENTER</property> + <property name="row_sizes"></property> + <property name="rows">1</property> + <property name="show">1</property> + <property name="size"></property> + <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">8</property> + <property name="flag">wxEXPAND|wxALL</property> + <property name="proportion">0</property> + <object class="wxBoxSizer" expanded="1"> + <property name="minimum_size"></property> + <property name="name">bButtonsSizer</property> + <property name="orient">wxHORIZONTAL</property> + <property name="permission">none</property> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property> + <property name="proportion">0</property> + <object class="wxBitmapButton" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="auth_needed">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmap"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="current"></property> + <property name="default">0</property> + <property name="default_pane">0</property> + <property name="disabled"></property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="focus"></property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Add Library</property> + <property name="margins"></property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_append_button</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="position"></property> + <property name="pressed"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size">-1,-1</property> + <property name="style"></property> + <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">Add empty row to table</property> + <property name="validator_data_type"></property> + <property name="validator_style">wxFILTER_NONE</property> + <property name="validator_type">wxDefaultValidator</property> + <property name="validator_variable"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnButtonClick">appendRowHandler</event> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property> + <property name="proportion">0</property> + <object class="CustomControl" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="class">SPLIT_BUTTON</property> + <property name="close_button">1</property> + <property name="construction">m_browseButton = new SPLIT_BUTTON( this, wxID_ANY, _( "Add Existing" ), wxDefaultPosition );</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="declaration"></property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="include">#include <widgets/split_button.h></property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size">-1,-1</property> + <property name="moveable">1</property> + <property name="name">m_browseButton</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="settings"></property> + <property name="show">1</property> + <property name="size">-1,-1</property> + <property name="subclass">; ; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">Add Existing</property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property> + <property name="proportion">0</property> + <object class="wxBitmapButton" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="auth_needed">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmap"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="current"></property> + <property name="default">0</property> + <property name="default_pane">0</property> + <property name="disabled"></property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="focus"></property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Move Up</property> + <property name="margins"></property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_move_up_button</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="position"></property> + <property name="pressed"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size">-1,-1</property> + <property name="style"></property> + <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">Move up</property> + <property name="validator_data_type"></property> + <property name="validator_style">wxFILTER_NONE</property> + <property name="validator_type">wxDefaultValidator</property> + <property name="validator_variable"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnButtonClick">moveUpHandler</event> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property> + <property name="proportion">0</property> + <object class="wxBitmapButton" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="auth_needed">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmap"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="current"></property> + <property name="default">0</property> + <property name="default_pane">0</property> + <property name="disabled"></property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="focus"></property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Move Down</property> + <property name="margins"></property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_move_down_button</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="position"></property> + <property name="pressed"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size">-1,-1</property> + <property name="style"></property> + <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">Move down</property> + <property name="validator_data_type"></property> + <property name="validator_style">wxFILTER_NONE</property> + <property name="validator_type">wxDefaultValidator</property> + <property name="validator_variable"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnButtonClick">moveDownHandler</event> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxEXPAND</property> + <property name="proportion">0</property> + <object class="spacer" expanded="1"> + <property name="height">0</property> + <property name="permission">protected</property> + <property name="width">20</property> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property> + <property name="proportion">0</property> + <object class="wxBitmapButton" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="auth_needed">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmap"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="current"></property> + <property name="default">0</property> + <property name="default_pane">0</property> + <property name="disabled"></property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="focus"></property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Remove Library</property> + <property name="margins"></property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_delete_button</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="position"></property> + <property name="pressed"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size">-1,-1</property> + <property name="style"></property> + <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">Remove library from table</property> + <property name="validator_data_type"></property> + <property name="validator_style">wxFILTER_NONE</property> + <property name="validator_type">wxDefaultValidator</property> + <property name="validator_variable"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnButtonClick">deleteRowHandler</event> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxEXPAND</property> + <property name="proportion">1</property> + <object class="spacer" expanded="1"> + <property name="height">0</property> + <property name="permission">protected</property> + <property name="width">20</property> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property> + <property name="proportion">0</property> + <object class="wxButton" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="auth_needed">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="bitmap"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="current"></property> + <property name="default">0</property> + <property name="default_pane">0</property> + <property name="disabled"></property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="focus"></property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Migrate Libraries</property> + <property name="margins"></property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_migrate_libs_button</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="position"></property> + <property name="pressed"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size"></property> + <property name="style"></property> + <property name="subclass">; ; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="validator_data_type"></property> + <property name="validator_style">wxFILTER_NONE</property> + <property name="validator_type">wxDefaultValidator</property> + <property name="validator_variable"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnButtonClick">onMigrateLibraries</event> + </object> + </object> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">8</property> + <property name="flag">wxTOP|wxRIGHT|wxLEFT|wxEXPAND</property> + <property name="proportion">0</property> + <object class="wxStaticText" expanded="1"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label">Available path substitutions:</property> + <property name="markup">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">stPathsLabel</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">none</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="show">1</property> + <property name="size"></property> + <property name="style"></property> + <property name="subclass">; ; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip"></property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <property name="wrap">-1</property> + </object> + </object> + <object class="sizeritem" expanded="1"> + <property name="border">5</property> + <property name="flag">wxEXPAND</property> + <property name="proportion">0</property> + <object class="spacer" expanded="1"> + <property name="height">2</property> + <property name="permission">protected</property> + <property name="width">0</property> + </object> + </object> + <object class="sizeritem" expanded="0"> + <property name="border">5</property> + <property name="flag">wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT</property> + <property name="proportion">0</property> + <object class="wxGrid" expanded="0"> + <property name="BottomDockable">1</property> + <property name="LeftDockable">1</property> + <property name="RightDockable">1</property> + <property name="TopDockable">1</property> + <property name="aui_layer"></property> + <property name="aui_name"></property> + <property name="aui_position"></property> + <property name="aui_row"></property> + <property name="autosize_cols">1</property> + <property name="autosize_rows">0</property> + <property name="best_size"></property> + <property name="bg"></property> + <property name="caption"></property> + <property name="caption_visible">1</property> + <property name="cell_bg"></property> + <property name="cell_font"></property> + <property name="cell_horiz_alignment">wxALIGN_LEFT</property> + <property name="cell_text"></property> + <property name="cell_vert_alignment">wxALIGN_CENTER</property> + <property name="center_pane">0</property> + <property name="close_button">1</property> + <property name="col_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="col_label_size">0</property> + <property name="col_label_values"></property> + <property name="col_label_vert_alignment">wxALIGN_CENTER</property> + <property name="cols">2</property> + <property name="column_sizes">150,500</property> + <property name="context_help"></property> + <property name="context_menu">1</property> + <property name="default_pane">0</property> + <property name="dock">Dock</property> + <property name="dock_fixed">0</property> + <property name="docking">Left</property> + <property name="drag_col_move">0</property> + <property name="drag_col_size">1</property> + <property name="drag_grid_size">0</property> + <property name="drag_row_size">1</property> + <property name="editing">0</property> + <property name="enabled">1</property> + <property name="fg"></property> + <property name="floatable">1</property> + <property name="font"></property> + <property name="grid_line_color"></property> + <property name="grid_lines">1</property> + <property name="gripper">0</property> + <property name="hidden">0</property> + <property name="id">wxID_ANY</property> + <property name="label_bg"></property> + <property name="label_font"></property> + <property name="label_text"></property> + <property name="margin_height">0</property> + <property name="margin_width">0</property> + <property name="max_size"></property> + <property name="maximize_button">0</property> + <property name="maximum_size"></property> + <property name="min_size"></property> + <property name="minimize_button">0</property> + <property name="minimum_size"></property> + <property name="moveable">1</property> + <property name="name">m_path_subs_grid</property> + <property name="pane_border">1</property> + <property name="pane_position"></property> + <property name="pane_size"></property> + <property name="permission">protected</property> + <property name="pin_button">1</property> + <property name="pos"></property> + <property name="resize">Resizable</property> + <property name="row_label_horiz_alignment">wxALIGN_CENTER</property> + <property name="row_label_size">0</property> + <property name="row_label_values"></property> + <property name="row_label_vert_alignment">wxALIGN_CENTER</property> + <property name="row_sizes"></property> + <property name="rows">1</property> + <property name="show">1</property> + <property name="size"></property> + <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property> + <property name="toolbar_pane">0</property> + <property name="tooltip">This is a read-only table which shows pertinent environment variables.</property> + <property name="window_extra_style"></property> + <property name="window_name"></property> + <property name="window_style"></property> + <event name="OnSize">onSizeGrid</event> + </object> + </object> + </object> + </object> + </object> +</wxFormBuilder_Project> diff --git a/kicad/dialogs/panel_design_block_lib_table_base.h b/kicad/dialogs/panel_design_block_lib_table_base.h new file mode 100644 index 0000000000..c815fd136d --- /dev/null +++ b/kicad/dialogs/panel_design_block_lib_table_base.h @@ -0,0 +1,74 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) +// http://www.wxformbuilder.org/ +// +// PLEASE DO *NOT* EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include <wx/artprov.h> +#include <wx/xrc/xmlres.h> +#include <wx/intl.h> +class STD_BITMAP_BUTTON; +class WX_GRID; + +#include <wx/colour.h> +#include <wx/settings.h> +#include <wx/string.h> +#include <wx/font.h> +#include <wx/grid.h> +#include <wx/gdicmn.h> +#include <wx/sizer.h> +#include <wx/panel.h> +#include <wx/bitmap.h> +#include <wx/image.h> +#include <wx/icon.h> +#include <wx/notebook.h> +#include <wx/bmpbuttn.h> +#include <wx/button.h> +#include <widgets/split_button.h> +#include <wx/stattext.h> + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class PANEL_DESIGN_BLOCK_LIB_TABLE_BASE +/////////////////////////////////////////////////////////////////////////////// +class PANEL_DESIGN_BLOCK_LIB_TABLE_BASE : public wxPanel +{ + private: + + protected: + wxNotebook* m_notebook; + wxPanel* m_global_panel; + WX_GRID* m_global_grid; + wxPanel* m_project_panel; + WX_GRID* m_project_grid; + STD_BITMAP_BUTTON* m_append_button; + SPLIT_BUTTON* m_browseButton; + STD_BITMAP_BUTTON* m_move_up_button; + STD_BITMAP_BUTTON* m_move_down_button; + STD_BITMAP_BUTTON* m_delete_button; + wxButton* m_migrate_libs_button; + WX_GRID* m_path_subs_grid; + + // Virtual event handlers, override them in your derived class + virtual void OnUpdateUI( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void appendRowHandler( wxCommandEvent& event ) { event.Skip(); } + virtual void moveUpHandler( wxCommandEvent& event ) { event.Skip(); } + virtual void moveDownHandler( wxCommandEvent& event ) { event.Skip(); } + virtual void deleteRowHandler( wxCommandEvent& event ) { event.Skip(); } + virtual void onMigrateLibraries( wxCommandEvent& event ) { event.Skip(); } + virtual void onSizeGrid( wxSizeEvent& event ) { event.Skip(); } + + + public: + + PANEL_DESIGN_BLOCK_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 ); + + ~PANEL_DESIGN_BLOCK_LIB_TABLE_BASE(); + +}; + diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp index 3d99f13634..631cfcccf3 100644 --- a/kicad/kicad.cpp +++ b/kicad/kicad.cpp @@ -47,6 +47,7 @@ #include <systemdirsappend.h> #include <trace_helpers.h> #include <wildcards_and_files_ext.h> +#include <confirm.h> #include <git2.h> #include <stdexcept> @@ -61,6 +62,7 @@ #include <api/api_server.h> #endif +#include <design_block_lib_table.h> // a dummy to quiet linking with EDA_BASE_FRAME::config(); #include <kiface_base.h> @@ -353,6 +355,27 @@ bool PGM_KICAD::OnPgmInit() } } + try + { + DESIGN_BLOCK_LIB_TABLE::LoadGlobalTable( GDesignBlockTable ); + } + catch( const IO_ERROR& ioe ) + { + // if we are here, a incorrect global design block library table was found. + // Incorrect global design block library table is not a fatal error: + // the user just has to edit the (partially) loaded table. + wxString msg = _( "An error occurred attempting to load the global design block library " + "table.\n" + "Please edit this global design block library table in Preferences " + "menu." ); + + DisplayErrorMessage( nullptr, msg, ioe.What() ); + } + + if( managerFrame->IsProjectActive() && GDesignBlockList.GetCount() == 0 ) + GDesignBlockList.ReadCacheFromFile( Prj().GetProjectPath() + + wxT( "design-block-info-cache" ) ); + frame->Show( true ); frame->Raise(); diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp index d2c3246c82..e12b6778d8 100644 --- a/kicad/menubar.cpp +++ b/kicad/menubar.cpp @@ -210,6 +210,8 @@ void KICAD_MANAGER_FRAME::doReCreateMenuBar() prefsMenu->Add( ACTIONS::configurePaths ); prefsMenu->Add( ACTIONS::showSymbolLibTable ); prefsMenu->Add( ACTIONS::showFootprintLibTable ); + if( ADVANCED_CFG::GetCfg().m_EnableDesignBlocks ) + prefsMenu->Add( ACTIONS::showDesignBlockLibTable ); prefsMenu->Add( ACTIONS::openPreferences ); prefsMenu->AppendSeparator(); diff --git a/kicad/pgm_kicad.h b/kicad/pgm_kicad.h index 12e98c7451..d131e0b4c9 100644 --- a/kicad/pgm_kicad.h +++ b/kicad/pgm_kicad.h @@ -28,6 +28,9 @@ #include <pgm_base.h> #include <bin_mod.h> +class DESIGN_BLOCK_LIB_TABLE; +class DESIGN_BLOCK_LIST_IMPL; + /** * PGM_KICAD * extends PGM_BASE to bring in FileHistory() and PdfBrowser() which were moved from EDA_APP diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp index 3a532fc03a..3e96f2aacb 100644 --- a/kicad/tools/kicad_manager_control.cpp +++ b/kicad/tools/kicad_manager_control.cpp @@ -22,6 +22,7 @@ #include <env_vars.h> #include <executable_names.h> #include <pgm_base.h> +#include <pgm_kicad.h> #include <policy_keys.h> #include <kiway.h> #include <kicad_manager_frame.h> @@ -36,6 +37,7 @@ #include <tool/tool_event.h> #include <tools/kicad_manager_actions.h> #include <tools/kicad_manager_control.h> +#include <dialogs/panel_design_block_lib_table.h> #include <dialogs/dialog_template_selector.h> #include <dialogs/git/dialog_git_repository.h> #include <git/git_clone_handler.h> @@ -43,6 +45,7 @@ #include <paths.h> #include <wx/dir.h> #include <wx/filedlg.h> +#include <design_block_lib_table.h> #include "dialog_pcm.h" #include "widgets/filedlg_new_project.h" @@ -887,6 +890,65 @@ int KICAD_MANAGER_CONTROL::Execute( const TOOL_EVENT& aEvent ) } +int KICAD_MANAGER_CONTROL::ShowDesignBlockLibTable( const TOOL_EVENT& aEvent ) +{ + // For some reason, after a click or a double click the bitmap button calling + // PCM keeps the focus althougt the focus was not set to this button. + // This hack force removing the focus from this button + m_frame->SetFocus(); + wxSafeYield(); + + DESIGN_BLOCK_LIB_TABLE* globalTable = &GDesignBlockTable; + wxString globalTablePath = DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName(); + DESIGN_BLOCK_LIB_TABLE* projectTable = Prj().DesignBlockLibs(); + wxString projectTablePath = Prj().DesignBlockLibTblName(); + wxString msg; + + DIALOG_EDIT_LIBRARY_TABLES dlg( m_frame, _( "Design Block Libraries" ) ); + + if( Prj().IsNullProject() ) + projectTable = nullptr; + + dlg.InstallPanel( new PANEL_DESIGN_BLOCK_LIB_TABLE( &dlg, &Prj(), globalTable, globalTablePath, + projectTable, projectTablePath, + Prj().GetProjectPath() ) ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return 0; + + if( dlg.m_GlobalTableChanged ) + { + try + { + globalTable->Save( globalTablePath ); + } + catch( const IO_ERROR& ioe ) + { + msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() ); + wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR ); + } + } + + if( projectTable && dlg.m_ProjectTableChanged ) + { + try + { + projectTable->Save( projectTablePath ); + } + catch( const IO_ERROR& ioe ) + { + msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() ); + wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR ); + } + } + + std::string payload = ""; + Kiway.ExpressMail( FRAME_SCH, MAIL_RELOAD_LIB, payload ); + Kiway.ExpressMail( FRAME_SCH_VIEWER, MAIL_RELOAD_LIB, payload ); + + return 0; +} + int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent ) { if( KIPLATFORM::POLICY::GetPolicyBool( POLICY_KEY_PCM ) @@ -924,6 +986,7 @@ int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent ) // Reset project tables Prj().SetElem( PROJECT::ELEM::SYMBOL_LIB_TABLE, nullptr ); Prj().SetElem( PROJECT::ELEM::FPTBL, nullptr ); + Prj().SetElem( PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE, nullptr ); KIWAY& kiway = m_frame->Kiway(); @@ -979,5 +1042,7 @@ void KICAD_MANAGER_CONTROL::setTransitions() Go( &KICAD_MANAGER_CONTROL::Execute, KICAD_MANAGER_ACTIONS::editOtherSch.MakeEvent() ); Go( &KICAD_MANAGER_CONTROL::Execute, KICAD_MANAGER_ACTIONS::editOtherPCB.MakeEvent() ); + Go( &KICAD_MANAGER_CONTROL::ShowDesignBlockLibTable, + ACTIONS::showDesignBlockLibTable.MakeEvent() ); Go( &KICAD_MANAGER_CONTROL::ShowPluginManager, KICAD_MANAGER_ACTIONS::showPluginManager.MakeEvent() ); } diff --git a/kicad/tools/kicad_manager_control.h b/kicad/tools/kicad_manager_control.h index f95466f41c..bf1c2dd5b0 100644 --- a/kicad/tools/kicad_manager_control.h +++ b/kicad/tools/kicad_manager_control.h @@ -65,6 +65,7 @@ public: int ShowPlayer( const TOOL_EVENT& aEvent ); int Execute( const TOOL_EVENT& aEvent ); + int ShowDesignBlockLibTable( const TOOL_EVENT& aEvent ); int ShowPluginManager( const TOOL_EVENT& aEvent ); ///< Set up handlers for various events. diff --git a/pcbnew/fp_tree_model_adapter.h b/pcbnew/fp_tree_model_adapter.h index 1b4d3a246e..f620c992d0 100644 --- a/pcbnew/fp_tree_model_adapter.h +++ b/pcbnew/fp_tree_model_adapter.h @@ -51,7 +51,7 @@ protected: std::vector<LIB_TREE_ITEM*> getFootprints( const wxString& aLibName ); - bool isSymbolModel() override { return false; } + PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::FOOTPRINT_LIB; } protected: FP_LIB_TABLE* m_libs; diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp index 13932a61f5..e3820b88a5 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -708,7 +708,7 @@ int DRAWING_TOOL::PlaceReferenceImage( const TOOL_EVENT& aEvent ) if( !image || !image->ReadImageFile( fullFilename ) ) { - wxMessageBox( _( "Could not load image from '%s'." ), fullFilename ); + wxMessageBox( wxString::Format(_( "Could not load image from '%s'." ), fullFilename ) ); delete image; image = nullptr; continue;